From 42df6d37a6718645d60771b7650acf54111a611f Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sat, 3 Feb 2018 16:27:33 -0500 Subject: [PATCH 01/76] 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 1f5b2168fe..c1228c4939 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 f07e5e38a5..de44fb2521 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 0000000000..21713471a0 --- /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 0000000000..90ca923e3d --- /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 4c3373b3e3..eedf9c8f58 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 0bacd7b5d1..630c57c049 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 e71916d096..b938dddec6 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 7ec9ada9b6..89dce7ab23 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 0b7790a51c..a9d6874000 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 316fe95941..c8efcecbc3 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 5eb894ce8d..d0c024f1a3 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 3aeb42178b..d21b192a30 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 627a8fbae2..33139ace12 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 8d2b93d282..34fba208a3 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 450858b6b8..c22c59c6b8 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 16cb294d56..2a6040c6ee 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 3cb34efe4c..310af1cb6b 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 07dc5b4184..4a7d0ce23f 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 778497562b..8dd1cf5808 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 02/76] 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 21713471a0..2b2943fc4a 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 90ca923e3d..7aa2d4bb31 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 630c57c049..cdf286c740 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 b938dddec6..8c900430fb 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 89dce7ab23..f5a3028551 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 a9d6874000..7679fde2d0 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 d21b192a30..3aa8514242 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 03/76] 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 2b2943fc4a..8d3cc832e3 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 7aa2d4bb31..0531c23d07 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 cdf286c740..84f11ecc29 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 8c900430fb..5c200c02d9 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 7679fde2d0..71b3b8d640 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 c8efcecbc3..483d171cae 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 9abfd23d2e..64c9568ba6 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 33139ace12..046610daa1 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 9ed89a14e9..991decc3cc 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 127da02226..7a7f1fd94d 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 04/76] 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 8d3cc832e3..83942cf73f 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 0531c23d07..5f9369e559 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 84f11ecc29..74e9731fff 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 5c200c02d9..32511ea3d7 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 3aa8514242..5e5ab778a0 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 64c9568ba6..32b1a3ce60 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 991decc3cc..67f6cd06cb 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 05/76] 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 c1228c4939..ef9daabd28 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 83942cf73f..427e30890d 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 5f9369e559..59cec17291 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 74e9731fff..1f241f66d4 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 32511ea3d7..ad34c735ca 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 71b3b8d640..78dd0bb877 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 0000000000..73758b1104 --- /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 0000000000..d8e1ba39ac --- /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 483d171cae..81f6e12925 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 5e5ab778a0..59bb904475 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 310af1cb6b..e7a9645ae5 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 06/76] 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 73758b1104..006b7bbdd7 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 d8e1ba39ac..05b642e202 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 8dd1cf5808..b619dce223 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 07/76] 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 de83325e03..667594f66c 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 08/76] 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 667594f66c..de83325e03 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 427e30890d..6b37a68077 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 b619dce223..7ccd991b01 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 98af5ed6becda38858f0d0b6baff65b64effd1da Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Wed, 14 Feb 2018 12:20:49 -0500 Subject: [PATCH 09/76] 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 6b37a68077..e7b76a9943 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 11336f875f67e8fca8c0ded359d7b66d2316cb22 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Wed, 14 Feb 2018 17:48:24 -0500 Subject: [PATCH 10/76] 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 1f241f66d4..a6965cffb9 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 ad34c735ca..101bf8f9a1 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 006b7bbdd7..236e6d0bae 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 05b642e202..1825f396b2 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 81cc98010bbd6264570ae06dbba3484e63222075 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Thu, 15 Feb 2018 14:55:06 -0500 Subject: [PATCH 11/76] 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 236e6d0bae..463619a9fc 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 1825f396b2..18dbd53a40 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 ec14970cafb936ed3fac4df2f08a895d22f9d2bf Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Fri, 16 Feb 2018 15:22:08 -0500 Subject: [PATCH 12/76] 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 a6965cffb9..fff18d6c85 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 101bf8f9a1..0cc08f5e6a 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 463619a9fc..4730990349 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 18dbd53a40..2a4aa8865f 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 13/76] 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 0cc08f5e6a..c58ab1d6cd 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 4730990349..82f15401de 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 2a4aa8865f..81b0c2bdf3 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 14/76] 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 c58ab1d6cd..f5d79d0a6e 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 82f15401de..fd89cbf1e7 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 81b0c2bdf3..9ade739c02 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 4c36446ffeda245e5741cfa9abeb671e898c14b8 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sat, 17 Feb 2018 17:32:35 -0500 Subject: [PATCH 15/76] 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 fd89cbf1e7..2902e3e475 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 762313943d8bb07c5e55a70ead1d5fbfa493d33e Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Wed, 28 Feb 2018 13:22:03 -0500 Subject: [PATCH 16/76] 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 e7b76a9943..fe04f8c69f 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 59cec17291..de5e25aa75 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 f5d79d0a6e..af63cad7f4 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 a69baeaed55d8a738e25907ebd1380ee9a0e5333 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Fri, 2 Mar 2018 02:12:31 -0500 Subject: [PATCH 17/76] 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 fe04f8c69f..136f36e06c 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 de5e25aa75..8b540be35a 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 0000000000..b725809fe0 --- /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 046610daa1..2be21c0636 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 735c59650b78a791b90145db69b94fa5e0d6cba5 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sun, 29 Apr 2018 15:39:14 -0400 Subject: [PATCH 18/76] 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 136f36e06c..ed16a566a3 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 8b540be35a..99ca2caf4a 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 78dd0bb877..6457f587cc 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 59bb904475..e2ef1a2c14 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 98009a268d1a1d735822baa23170de0c8a8e6ceb Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sun, 29 Apr 2018 20:23:41 -0400 Subject: [PATCH 19/76] 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 ed16a566a3..56d7c45ca2 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 99ca2caf4a..5c06d81200 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 b725809fe0..8cc837a735 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 fff18d6c85..3fd5859f7f 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 af63cad7f4..ddd9e4c2d2 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 6457f587cc..a3bc6b8260 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 2902e3e475..9a716f76b2 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 9ade739c02..b21dc56040 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 e2ef1a2c14..834b2661a9 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 e7a9645ae5..39f8db5af4 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 20/76] 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 3fd5859f7f..a98ae8b95d 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 ddd9e4c2d2..117d3feaf4 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 f5a3028551..0b2e030c48 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 a3bc6b8260..26e2ff0a3b 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 9a716f76b2..cda5dff372 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 b21dc56040..78d83a2cbd 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 834b2661a9..160f3bfbda 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 42e1148fa68bce7442e5767281093ee54fa722e9 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Thu, 10 May 2018 00:12:22 -0400 Subject: [PATCH 21/76] 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 025fd69c55..02f9b7e82d 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 66757df8ee..232d2f6e7d 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 717e2cada8..4d48e8d144 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 a28d7efe79..5723b2280a 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 78d83a2cbd..8bdf469c30 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 4ff27963ec..22de03e7b4 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 22/76] 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 0d4fea0049..35e4d3ca57 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 02f9b7e82d..ebce31c455 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 4d48e8d144..c6aa8a142d 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 5723b2280a..7d2b517949 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 22de03e7b4..fb934726e7 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 23/76] 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 7d2b517949..93c53d88a2 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 8bdf469c30..da07df5115 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 24/76] 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 93c53d88a2..024e4cb483 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 da07df5115..938294ae4d 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 25/76] 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 368d57b03a..5bfd9104d2 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 c6aa8a142d..09da2d1f44 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 26/76] 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 d2a4348fda..dff755999f 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 5bfd9104d2..a3f55d5226 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 5c06d81200..231bb8ea78 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 d1893bf9e1..dbd4181dae 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 8cc837a735..685fbcfd72 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 35e4d3ca57..05ad98b86b 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 0000000000..2064bda02d --- /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 ebce31c455..309076bbe3 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 09da2d1f44..71408defb0 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 2613acd9d4..789ad0ee9b 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 fb934726e7..964ec34a35 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 dd12e808cb..0c369e154a 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 27/76] 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 a3f55d5226..6d7e598eba 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 231bb8ea78..fff8b749c3 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 05ad98b86b..be2e2be84d 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 2064bda02d..ec4da6f12a 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 309076bbe3..012e976afe 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 71408defb0..e43906c9af 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 0c369e154a..2b44f3503b 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 28/76] 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 be2e2be84d..ed5906032b 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 ec4da6f12a..906845cb93 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 e43906c9af..44a161f470 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 4c5bdd443871b4a2a5a88abd4a1dddf1242cc10b Mon Sep 17 00:00:00 2001 From: Zhuoran Han Date: Wed, 16 May 2018 14:54:24 -0400 Subject: [PATCH 29/76] Checkvalue bug --- openmc/checkvalue.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmc/checkvalue.py b/openmc/checkvalue.py index 3b334319f6..4a82983d96 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 30/76] 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 4a82983d96..cbeac9c373 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 31/76] 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 d3d45e955b..58e4b66dc1 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 86cfd5c41b..ef33247fca 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 e747b619ac..651005631e 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 6c42161ca7..ddf7f4d922 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 ef7608f2fc..f60cfa64f5 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 39e611e16a..a1c1e83116 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 cd6aa95d4f..e0143f0fff 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 de1b778772..cb598b9d3f 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 a824d7fce3..2d9835b1ab 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 8eb7c2b609..acd74781db 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 d1ef3e00aa..3bbcc20247 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 0ab4191d8b..d734e67bd0 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 5c79d4d6b6..ef797a1bc5 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 ee763b3bac..0f0a3d60a9 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 19e2c7664b..58e6ddac40 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 zcmeF42Uyff*Z7x?N=FepRf>%+En!Iju_HF@fTExvMN#Z1_6k-kAYcI%5erfTS*%z= zMZr!7Dbf`c<)6Ko*}3d{*L(4mkNf?<@H{d*$(b`JXU^};B$>r+_BM9XU3+(BaepKw zS)wetKR&`=t>8!5SNM-Zunx~VgAYQW+zQGVTAU@s5~KdHSjynI9MG>KLLbNk$0s@3 z+OjA~s2`T#G%H4&{^Se~Fy;R#57^sUJ8&;J9$d9MDDy|a$O8v{b0H7nl|QbfSoRbv z+=!KANm0?g%*}6!hxcNZoFs+OA2zW6_$?$v+Xrx9!IIx(>q51cWX<>RbX&T@ z$L$X#)GtM&hKtVo!;b9@FCATf)5MGxY_adktJ-`>u zp`1VtpN|LU1D??JJW#JKqU*Jw?*56c%YlLM5u;C-(uY|A z-RZhLsQ+ItAcq{_FMCj$o}@h^DBmXyrq4ru1+0l<6Zd9yqp8g0DkRWrH%??&oqaUh{x{C>>)# zf6Bz@(i!NB7zri7fnO{H`MtRttq_h6od;M<9squ3@<7{wgL%LS9EQ9(f%ySS!>EPa z!sEYM(s@7>%&{z1tpu$J_M;38F$H$OejUL42IW+A>2_?8m*8~-@X5Ikq2;y?qf2MH zV8!dvAE3TGP;WK>0{`*?&j0-WlA;nYy{-|b)-{qWkHz!c0;p^Gk97^-^+~ZzK|2Xn zcdFeoZ%=$;fZiYA756t}n z?FT`>1@RUL;s~~bas9h_W6RM!ID`J$%Nw}8q2|KQncwV{X!{x7Xg@H#L1O;86vSI? zkGAq1#ap~0-A^{?uf4p1;sg?S8cV3~#g_7~UW;e_aaVExT`9`Htc(xHsKTJm{~zyomubJ8L}|ssii> zb}-LN=tyCnmwZJZ|Z5-r##zHgH7f5!znp7Y?ZBAES@M?HQ8cZRPYp-64WL?f?`7M7EWS z0Cmm@`Z(P0qvzOGuK6@L502BdfA4Sj9w;BI3s@k|+B@#>eS1gWM=Q6<*!EOueB4zQ4H931hsY) zNBsMo+RG8#_SMlG3EBtbV@P|5S@+X^VAlPRn7=Ls@um#o2)61d-uU+mwwE`sUAM#6 z{epPQH=#Ym@J9QA;SCb=*QFrdwt_f~+_I*qqb3)+5$H`)&j zZ;+V3E(P({Y}Qu(ck>3H6S;$KQ~-CdAASyffL!o!`U8|p0`)v#FLw_*_s8CnE}@(c zs0ZWp@j$v@B~d%dxj#@YAJka{ef;148{XgY`=PygOB!5gXRXU6f!~?DMGpj%w|-8Z zpu80i_=c?^*ADQ&e_2A_`1477c>~+}J8hj10P-8d8yye~Z$Ae^5N|4c^JGWy#y>B& zmpAyeoz9xy%BjCv!Tn))qy50}M*Bg~Z$Z2TgE)fiI*K<35{wh*uf4p<0RuaWw}?@+ z0Ss?o9GSmY0V49R3qicGz~3o>t$sIe@IH7e=s+8A$LoiW!(bp6YDa&7avV_4x2KPX z0y&4#ZRG+$J#;L69C8{19$Z0v{NFzX>i)F_T$l6vp}l#F`?qtrKb@7g zz^oH!KM49QC~qYJzF|AawF5lxUzU(J{(RD2-r(1JJBzmfu&;{YjSdKgx1WO{h&OGp zFB7)zDBk$@JGPfMuqT`xA~6ECs#4j+ormUS^*oJrc}5 zM;JM6OF{8$58?<)|L%B(^ReVax(990UwiXIcVJ*=&BspDX#-$>V0dGAgUtL@DTucu zr?%3+n>Tph9s#=HfcrMs5C8WyVt`ye-}e&ud4+NBFokXa^#y=>D7da9pigo}TRA;Y zcbEkP{^bSCTm0A8-n`WVTxVzHtxVu=IDSmtV)E9{&=ZumeE7J9{5$+iSv$@_-uUxG zdwEmpWZq0&XagADXg@Ib?LUV=5O4X;ZQFGeZxOTUemJ1N_VNb)d{3v{x5vz*?PqwS z{lM@>`$5oeLA*JDID+juiZ?k|x(7YbUwe5|1_pN4deYg0Hh|%c_5;Hk2uJ~iAl^6& z+BW#zyutfm4(KKT%yY0G{&_DG$d$X$AD|pRuh~G($*rwiAgD)x>&gRiatqtanS#2L zClL6T7jRw9e|_!ETj0<8b=o>18~B^aTXeuNdF$uk3CddmeB8o3;qY^H|M)EAjXzJc zmpAZd{yL2}whw*D3~#g_7~X!4fFRz=z1z0yDBfbc=zcOmf9>V14=}T{*5&a_XagAD zXg@H#fq)cH2;$8N#1U-PQM@Vp(mj}h{@TkM`17@$Hov(C(DpOD(SBffgT(xGDTues zWo_kuH*fGhI3IKq2 zub>q6U|_q2j%#=mIEi; zgJ=VwTrjA|^Id;iZ~S{|a9z%CcYE`eD!8Q1%3Jx{X#<$NMf-utTR&$^P~O@K;s~~e zTsy!6|78hzv)@W9atHmjmp3(FM`!Wou$wl3;f?kK!y5=l0fivmns>Er&{4c4?V$U~ z1O2s^H+5iUXYs}fp$%Yoqy50}1_Dw*A&5735J#|CNAae;hwi~1^w(bAG=PDf#aqBZ z+5m<(+7AqGARq-4f_Tf@-?qW;=8e7&7H25~#VTN33H#w+XR3e`!C*dwa{Tk7Har11 zhE;y9^}q?Az4UcLxlm9~0@rT}PG}!$E9U^}K8NY!Ds;ixs@PU89@Mkp7jS{x?<(NB zoZs&D<}FQNP-o?>a^OcMZ_xw6# z%CR!(y2^dJi~(}?58BH4fO_yF`glB$OM2W^E+5ocPw3;F^&38SXg*C_;N(b`?Om7Y zf(z)Zby@a#+JJaq2a`YOz+>_U2nfDH(7G%D#1Rw+__7?}f&a3EyqTV(6*+1#=b}!!0<-O?0V$vm#9KKGAkeO(c#DanJIDn6wU@WSz`oAnE&d8^0CX2K zzcKS02+6-J1o7qs;s`eCDBhGW(LI=g{@Tmi5MW?u@#daP8^G{J`+=F?KtKv81o4)c z*tWs%=8e7&mS8yp#d2U>3H#w+XS#zEfnYv_a{Tk74>(Z^)_G8lpX&f{!a0GyPAIn( z)MLQ)2Z9sIDQ)H0pzfSXAKwb(wx+d}iwE`Wbow}a574aCR!*fSU1#^A%YXVCuFLuD zZg1Wi3Oekpyp;$1$mA`0Aeg-Ma{>hAtw0b*ur=h`0Ur1-OUN5P|Lx^%7_g(Wc(dow z1~9zQeqeY50V$vm#9J+x*I=`b;*EbEZ7*+nz`)MpEeSl&XLzFrg5eDk^Vg*y-kd=k z!B!o`8~=N#_VNZ^$9CHOGHvi4jNy&p4YKmHQV?(1;QcCW+flsnzt3$iZ(!R)r|}jF z-Y+w}F}y)mepU+NjSb=mw(Th1`1eJ$mp8Cosnd9qd`x?Y;f?kKvz~;+{B@?muPia3fywQGOcx%fIe@zf?Dt!Cqem8G$e_;|(?DLGa4E7TW z>PgS(;~a1z|3zCl$y~aw{gN&-ftd5exDCU58zknhOF_J8=eL#bDBk$* zJKM{f88EZ6crz`b4Pbbq{lM_nmK*+>Al^dxo~Qh7-stB}qAWdNkb7_X1MDXl)Z_co z$MeC7auDB8POg+buUAf&<=}+Vhqn3xK|P|9K3)stGOOCk)q=WQ4SoFI{u@4b;(rd- z-n?ZFF0`}qRty+FCU4OL!Q`! zj^T|S2!^+xgCU4F4wz42>yF}$fBtSSZH+ zmD>vHF<*wSN z%3GO$Z`c~<2?u!Kzbqkd{Q0E4ys?2DoyA))$Zrg9^guAY{TvKIyqWUNlO4qy|GeB@ z-mE*BHq5J-@lP()zc8{@TkM0WPw$=C>qqhBxjI z!229#eggq1pb*5HvKS5tPJi)*?>FP4?l-0KrSB4Cr~aA)SE@=BicaD zU4pJdxnNL_??NAe`x5i|wY@%8f4Z)sLYM#c-*8>d?}zs0EfQR4XXP#JuJpJt>jZir zn7s9K@&x6rY``~c4f+3Fc?A~>E9Rh|o5Qu+M2;xlz{C{Au`S0cp{{0I*aNn8-?t@@I{PUhEkh2GTLOFh3 z*+4D{%sWsnAJkdky4c{t>{aOxP|gR`gVpKd_CPL4qpe&%sI%aI&V;(;7o2Q-_~4&_D}_Y;4d#aPhiGchP9 z+D1-}MMsPZL7(^GvqOj__LuUe{b+d@Uw`-i=P>ch=h**aRT#fc;JPJ284B_mbO#+( ztl#$cTj29&*q>f6$N}A>6Q~COJ7^kMtXdG?&>rRf^e0mlx`dp$tG3k_4C?Xf^l=q% zVfh+u!fy0#Ww!t4LHmT=zU_m>ol%@U=I7Gfz-f7?cq<+9Y`-AlS8h7BfpVIWtxQhVm z42HWu+|OdTqx~T0w;=B9`PQo)#U20p81eFJ+`;QadVY;N$nQVpPI(aRA%;7KI~apM zD+O`K(S@YZtzqqVuQLO+Y0VL!Kg7$gaVHKg7U}sl?x3E3%3bhKhC8n7ndhq@0RN^C z#GNUKBiOv7xN8RUgfi$4@$zfjNdSY9o?qh*UhhBUPSSwkj_Z1cJ4npWOF`Vl>!Zly zYJj!hJ>U5ZqcsPE{tz#}#vQyHLwbIVJE-TMau;aKaL0`khC8ko{{BZ0ciJG1poWg( zt{lu0lAu4t%dc?NhjXOAR{!{L1%^B{vvBGc%iTQach`R{z9v`-b@%+2yJE)gj zqHoCkzh6eo(>Lb+pA_7Z`?vB((uY}-4GjPIFRiGOE5@yxa!V&ST7X+}+d=!BxhFf| z2kw{B2D+WVeJSjh|2|a@$Yq0dG?e3CSO0M_l&n3N_n{pBI)DxIaX_Aea{PJA9>|%R z&=OG28Po&$#ubSFj}`F!5x+esPySlIh0mXno?pwi@cl1;zNPn*2npN|mB*$%tqeTL z?1%av3#oraNBntI1z1%M_BlZ#wHFUw;Ns=wq5}@h_gJ!o+E&2JUoam)`M)dwZl4nq zmw)HM|G&@k$0fXv3k3NJUOk*Ae%Jg?&zHjJZ6M!1CpJ#^=**WlgTcN(XipNT=h?s` z(Bl6aC7dt${ng(6#mHN5hC)aF{7Hz~J;n7jl!E&)9c*d;`v74k@6i5$`8ojXYlrKv zKl}9m;%7m5rpk0WBzaB*)mN-3kg9bt@Is$}5=>zcnEYt_% zqYF?6x7Q0gFG7*XJtwbX#oMoF)Qx4-3Kptj)KTtvVG-I4NtVhVA4G)3;z!X+xxcyp zxLtF*m8AYryCeT7{y5=!vSm7p+Bf+-e*V)RPJgHs0)N-x@B9JR@qgzRDA#`fXaoO< zH#_~cf4Hh?=TIO1(@)yJlFfj?}C5)_{XXJ<3Y!_h_Hyg zN89oEV+{V&|L*_I<+Z!*KlKNUN9Ye&|1bOzxCHrw7n7as5AM9rT_eEjgeALs`&^r} zhQ2u14#w}_X$ekg+Yb8q@7lS4Zrcvs=l{GV+o)|1upPA59yEvf{Ewjhxk~0N;{nD4 zj0YGGFdkq$z<7Z10OJA11B?g$PkBJlePp2aueqT)guWw#?cx2|Kh>7~xNZBkcYS}a zC8itQ0`?E@6aT&?EfHwnwnx|=UI+huCX{CGH~*=J89BxSj0YGGFdkq$z<7Z10OJA1 z1B?e44=^71|G@)-o+td>{c!+zpA6;T{WiZ(9KicyCw$G6l!NO*Btd_Y6tHfDdYN@2zsHz5;{nD4j0YGGFdkq$zb}HvhCel>6VJCp!6|SRI9OiT~gF2ae}IE&sQS`#-%NDA&=~1>;Ik z$;2U($GCZ%iNl}AK}Y)s=7WD)GWPu1cnQjb@HmqXf9_ZQ_+;Yn=l&Ap9~f_fN+u3} z?pG!be~*6z#UDJ*#NqGpGk?7PU0qP#_`7!e=l{3pf$=7&Wa5yCL*_o^@1M8*Z{gi< z+ugX)fr>bM8fmy@>Kpxe>bSMdlNmF%+u#fLKh893$l#q1=&3q?RoOla%9jSbT$xv{ z#5-TG_^To7rZBHwn6<2F(mf;m*yoAcT{jkEKZ>Om$^J86%9(TXIAO*I>Dkmdj_UWm06iljAcp=o-unj z2U}+u**N8n1kauuC2Qu0ZB)ZWb`%}@reK4oeQCbe;KV`at#|u=-H-fJygXpk?S;sm zOV1;)!l^<$`EXs$0Ozgdc=?!%`r&oOSjD5#k-18LvgeSk*waT9n8dg@A9kGNU|53I zJfVSdy!O>DWc3OUHN4y0XHf)!vbJ8)Jsz>nBRdQ0Ir&3b8>>WlJ`S-{Z;0Rt;|P?^l6^#wAFc*?1j0{9vWr zw(02l61J+p_cl|-?e`4ic*R=dGi#<^pYEQ*Yo9l0%HY&PA-w)(F45R#dk*oSn|g0Y zugP6_@^@EXoK|6AhJVYw7akQ_jLn(wL(btYs8XwxM{= ztJtR?a$gNsEFU9oR!rbtS1kh{w_fMjV|`EV+lZ3Ay#6k4@y^rmK=vd?+&p~J{}b>0 zI(A6RmR3VNVSuXLJ+cHl`RTaLhU>_lIE6z$@;;YiRgYwA3tBUL>Wk2rD>+kBM zjZISrtK(&F`qd3owZXBPdWY}%D4q*P4Lu`#ANfZt|Exn)BH~L&UwxZLG>V@MoD2`o z6^6Lx<8^Q1e2cLy_b#)>=^?&;IDF8WyQvzB4xZQaF^+>Bxte4CWPdlFJ^LQM+deN% z4L@vt-*!b7iD$ix%CR_r@=u@4?K#(vBYQ;O7jM|G6WMdrSj~HCj0jKPyyk_+*dCU6 z@G^(^sxQUZvBe2zcgmuCczDf)+}n~Lun`BWKJ+T)VEHcRvKq`~dF@lm_Bb88p@v&) zWoC|1w83*vM<4Xxj_kR&=a3Lb7un-5f8xT!U(tBkT<>wI*d1M8;R$ouZo$@gN$Y#_ zilk!9b8eQemk6>)vTv#Lttr)5y!ZSXkM|sG(j%kpPxs64?0M}VFRXG(70+jFKV@`| zzDVIjJHE_Mn%Cc;@&NbIi`DT5bBkXTy|Tfd zrGLHy#9)!GDF`)ujASCe$2Pk0&&isHi=3m;|dwx2$;z-XyadQ-S=f|Et!LhAU#b0|5C^7OT@#^Nt zN!u#ZdFLD1D$zH-qw!)zH^lt7iQ;Fdqc!njgCtL0sQZY~gX)d&H$N6N4W{PP>Muv+ z&P+$VZ@2KCX}7ZiTi%-fNtw*Rt^`H+eW?A;yPkxdha*(qs^b|;kN7VknF2a40J#pF} z9?$8C;^+G0;vpjzA$ziZti^5?q4h@YGQC&3l{9gs@J;on@KVfL&)RP6QRE**rvaKx z$(7ixrVZV;)!)Dl_Us*UL>uY58hLqo@>~u4{ks!Er>EKAdles^u&zMkRWR)2vTvJ^ ze^$oae=*@t_8fAVb)`oNG(4X z*S@Skyf4}IJ>h1J7*F4&{C-7`hv0aX&!!;_-o;qoAe&pGbCG`>&$ODgbgjgm?bUV; zQ{iAAV)M%-Udr>@SBz3~H7HiaSq94*{pS$)`lS5OC3jMJ=j*pf#5~JH^ZSxUjp}P7 zkv(O{BJ&?M{NTmo@eMXJuWeSxe+1pOwv8^vdZ=mbn_`0eBj>W_hg4-XrY>tE5lXFp zx|ZE`d8>)m2XVp)cNTwC!wq8=dZhm#@d-zi0#0>7`MJ*RqvEp>XuN`U;Qgc3(0DH@ z8ZdEh|$6ik}eU=kccPcWnS)A?KEcR8tHfjr?%nu^DOyGDvfj(VJj1$bN_Fea zyB@PQLo1HB^uW{3KTI#x9)dp!F^mcyxP*7!cDvs9H%2RY{k32QW=_Fqy~7S7%+J3- z`6q3;(U>P`x;Um#6+j zy@tBg8Q``T5g1|;b6U{os?QT8QHTupfx>7 zK^+%ge1DnmcM><1Kg>}uMDg<`*E#XaQDjfU<)d#(wjq0JRk10iA2g zgPFIbY;f)H>ZCejWRF75*R-?A$et-Vr7_2hkUdg8ue$jCKngbhVuDs)6;`@r$AOJn9E`YTIApFVx<5#mpXnOaO%4CNdBPQoXcG66 z(Qt`+h~g*w%8EmY(~*C)CtHZjU?cx59-xy|u?_LHb<&rS?>1ui8;kn!-KC1L7Wb29 zpZ#gQDzzZ@(CA@hm_!(tk=|!|7`mw8cGKRQd*dX2MXv0EOAflfsGe}{ z-fJI}e@Yj=u@2Qi`DesJ=QNoZA>R1s({$K4zrFJO4f;e%846EH!B~UJYsq&-K?J zdp2!Le;lcS?Ab84^yOb7=65n6A2oNjn19d@nD2R*b3ccU50)f&8;yVxfCrZUt5}{*v0t zN)9&M=RvObrLH`Cg@4SM(fnN%??2D)>=sQDXAc-ScSQplFP2{qzq(z>o=Yv$k{3Fl z@d~*6#O-cV1J7S87To!C{jCXpb*#yS=@P}5Y2>qd?HOo&__4YE;iS46?EXe#^6On3 zEN0Wdh6XmuAJ*etHQh-KykNv`mu6=ImmixKbO%T4jiXaUHr~}j_IQ5mKW5ehEC)H|Ad#^>R)S&c)u%o$}LhHjraT+m&$87 zXgyvbH9}wJh5_E!ve($mx+1J@Ux1A5V8nayuDH~}$yHc#>P?3)=Q-G!S~=BR9NjNz z;pNNnj5Y9j#|^hFjcxHRlcoG+Pow-iJ6)?}mK|C@pZPX_RiGof|C-SEo7}E^v_7nB zoe&%^X@Or|Zqks+DaN*4-E;cjEtH>^1f5AM+ggX^DrE(=6mqca=&O6i`=b0@j8E9; zI!hh@5azyQ)J7XTYP4tT5L0B&I_XFvB?Z}&ZmDc~dlT}HiQI#1;yaq3i*n-a*K&sA zDYaFjWsQrmud?OeH06*ztHq`yy2LeN8;Lv5?o#Vj_UMzf76%azmRS|mJ@#wjlUt_c z-aci6v!7oSUn7C?vqpcTQ%W+(o`O;4`o}&ad-4ju2Y2;A{MW=j^fp{#iu;SbljTcD4xe`o1n02ave50;Mk1UJu1>%zSd#6y2Mb#>qQ-_+ArZ|_^q^1&uu%3urcS@ zMb#Q8eopF)JybZN9y9g!j=uEeI=1|^x>-psdLChX+2?seb5HyS`-1Hr6GQy7R=@ft z_5of#SiN$pEN&FU<9Y0i%Pza}(Rw#Ed0UlK0a_o1xUwYFt{UMicI(9{+C`ZDj^2aq zGYfe71|6s|)tdhiyKlF(;d5mg_Ga6MvO$CT@XjCKwf@|p8JhUD#cIua7TDn@VrIUI z7<`L&zF$zviv{kI%qwX;Smx2LDmj0aIzeH^=L6>zSZ>Y z;Ke9@HcLI%7Wev$^@(&VU)d!CTQhD2c6$r*?=0KdNjm#A@BtC2&mwAUam}<|1zD4j zJpt*{*1TDV);|tUMO*jXM)~XWdNn=vQ#2o5j8(B%-xtTX^r^K{vM7~f|sy59-bkefK~fI2?l{ZYkZM@jsx{llj>C!zZb zNom`wnocNwmiAkHC+bhnqq=X@{oH>V%0KIR6vY@SSmRoRj<)2E5^UnAqH()sqj+|5 zYMmm!_!HJc^=2>I-Z!vosfTP?S;*i0B5iN^XlUS~CRuxjb+g57S_YV1Sc-V}nfGvP z)egkh#>P}hi>fM&%w5MWi~#PNBPjAmvqvFYBgLZ?Rmc6VjH|PM&-uB6vX>G^T63FdLess zKKM^qH2~SO%gjDi=M;)(vHM-l&)#ByJKNoSCfiViNf#@o+FnBO^H_EI`t8vTn8CeU zHHlp~*z~;<=GV!g^#*osZYC-dZSZ{>?rKlPkbfSYdw6tTC>k%nQxo>w z--_~2xLl=ba3Wg&d=E?sPH?xv{WdEui!mw21~~W7AC4h=h*qzxZ#SB-A(!X4&(P&y z=UYB6dUhDu^DOQ{vUj68u6lU*x!P^ubz?UpFsTS>l@=wdP=iNgi(EGT^)29cy4?y?-ht4M6+LMO-qkc*` zbLKTRexq4>_US_9J~ zP7^1-{iwE@YKPkm(Dlh(g6t9NS0g?5CCZ2DyId-rhoF4%;7vpfCkn++Xz}x$Goq&W z;ods4CwUZOVWTgeQ}}}HiNCYw^XDIR*vjvjOP5wt&!dzMJy{zl#PiRaPYaE8j%ea9 z<&)C}uCv7#9#PxV>WKJ~D6hG;X*9Cu^W~a_sgfu^m$AZ9hRj0af9>w+!K*BF@F`Xa zty!Onv5hH7$xaO@eiA&s{jg1F!X}$b{kVTMvS;Yd8+b+#vZr*<$5W!2$evm5y>2+fqWPkSo95QdP3HJJqtBYIt4pvA zFK4o+JVE(TGJMGKz8~wbSchQ?(kEnKg(n6?NoS$)TDNa)f{LU%{%pg_3*`rF@R1rr zxAY4}_KclbeQQh(%0Frnea0WkLD!=Yb*OLe0`&f2&kWYHs<&47`V_4}-g}BMU9#!q z{Xe~5#x7imnRurWYmu1VwBbZLCj2fw)H@d0Q{2mIbk=d|d0p51f!*Gd_~M#j3-l8Z z?=k)}`i;{^_E_#z^Oow0>>2-Nhp_TAG+rwPDR&w9QXfBJ`}oCx+9GVc(mY~(D#|~X z4;Bf}KT?Yk_^EvMp>&KbFP5+Dgw~e_HxF`f4O7R@O&O&wanuI4m#m-Y;fCxv*kh6E z4+E5cBGg}P2)97_CvimN7pD|tPpy~R2EB{Mc(b@tSkcI0?1N*-M6b1Iyn6Ypue2Xu zhv94W=M{!>ur$I>eU&HjPodJD)04vn;{CSI-n~bB1a5k~`(&@w+q`&hvdrFDcnQVN z{3}8Qb=?tPLZ{l)f&!`x0IzLeY@t9R{c#Li}v z70jpR3!%vS^9Kf^`=@a~##UkpN_d%RTyA>4K5l5FIbg-7eLQ=teHYI2Gg!}Ck8SY3 zn{9Adn0J23*S*75yhro1o3Fm^#uz<(NaTc&fQ?01WM-Aubdh|X{Du+h-QVlhVzE}) zcCQz4urXyT_l9|(@o&hPB3UY-iNEmm9H5_Ti^m+(%odkM@qErWBCBgE8ZWyAr+SrV zqw)IWG61vAMbFn|=WWcbD;bI}O}8pL+FFb~mYgxK!4mO)vXB4kh{K<-oWVWcpFWy_ z9h8}GFnua&pVsG$ZsR)*e5dha=eb{P@vAF*OD*=_gNGd4e&OCa6wiT^4s;=6k$=)1-$rplkbj1yChWf2 z2kEQ7x$?=KO}hAX$;bO!sP*a%jfLCIZ=!gXOj6&GZt@wk;55Cl-OjDb8KFB-6juem$Qn>pAlL&dI}ywUn4TyDPK zLh5~P&iWzx3sr3JyS9E)oo$glo7AhlIMFEoNbZ@OckLL8zg}Nn*Hix!Id6P>NxW;8 zS!Ri!&DysjLZld*HF~^z@SoPJgYvheljobU8PktWb32lb4Pc*HcEKI3FNG$^9Bn?Y zj!$-46sR!S7T?rw)X~TDkUb0QER_@T(fv-eAt#|S8TqI8zB|ItY=wFDY>u9$et4o8 zu680wz5AUa?EB^1kY@#Gysm|LE;WBskID9V`8aq>I=1SR>JiO4v|iBJb?&T9qB{Q6 zHU`@jV}nl#`4Mqp0*W8g5mP6RRz&0Vz9dq7Mn7ba_m|uP-Hpf|{PRcMBiU?xzk;dn zim)QgEU|vbauZ~aIORN_p7mH-&oNIGpX8I@C9z*)pjvuzGtG@;wF1p2}+{+g4 z_VU$6`+6-f9lBf z%)tkF=V#_v=41zSzir2(=$&d`HKziWggbG#R|nw zo`c8-VMpYjSIaCeip)j+ak}F*HfT0_9v~+5@yJAl;kZlGqMhCfC0O#B&{(rSJ#V@4 zwToNteNEVjZar*wZ_mJt+z!gT3PJfI>+O-?rn#E9{;6^6$En-lB3B$`hMA-Jt6&Xb zd&mam!;ta&ZP`vJAKFfNJYWWOKMwb+y_H$+!`g3y6O)STPVOkiCVUD!eB-S+?|wP{ z@pYxw+nTX*tp5pbYX8QzImOlM>2X-OXFpaj@_~E#(Kz(fs$`ndpK~(7;y|TRvZGV~cyM3><2zipGn5c#&bv2{c}J z)ep|xnu*4%dB%ek&wmK<;^WL%jmiz*OmL0Gck*XZ_k+u>Ypm?@r~DIi-O6|6vs%n+ znQ`AY$2r*UD97ttO6z(4rh#y}hH;9n;hVR(1Q>~Hc{@E%|e+PBTR;a9e z^fa5)tBJ~vy^=^gDcg34JWxc)e3@r;BP@#aGR_{5F>?4L`;vJ-kov)67dG+?C%spR_I_VqOmQO`-Jc4~tl= zXD(w?)ucLus|Uy8jP~>tAp`63({K7W64`Fkrg(}ulZ#&6i`!eyCKqk>TOWoc6AoS< zv)@iCBEAhxnUY57IrCvm;72nVo5~HFl(>4jdz~wIy-%3jG2B;QZb~DuXg0oZR?BR% zXl(zht`FJdZlfgi=Wolcy$%~HlD%VtRwS5L^6Eshq# z9|_Jl`Nh7E8i?*So_)P@Fmk$3fakPEE7HiWe4tIl3-ZRIl(31KBvT`semwl<~b#Uvggv-3h#loGB*9kzlt!S?D6|yGQ~7onA}qvzo(37Buc{e zXe4Z(Lq?k)pH!2^CMzPld_MZ{D$%pgnkBoc3yB)FQla~lp4iM1RZ9yQ8_BQ@rk0c* zk5TK_l*kH`0r*AxDIpC+#`GQcEVX8nOLLz#Md`4~HEMRBhE^pLtDbi4lU7eJ%0G(^Z9!`x?JOB#s@x3}l&bay7%lD#IRm$J#x zs|v;|Ur8c#4?O5L&AEsWy|Yt^>*w|5nQL7;^kmrt zuAbc*(`Hxo5+O%Ki7r+wXdw3Qo3%S?pfj12{7Bx_n@vWn%1@d2Es^jH(Y(@oei8A~ zS*|FZ($n8YORSo*=W){lXRe+tj(ROU?g^3e_Z1Dld%KaSj&~n!-+LDMY*jx$` z66bHP={qjXnVfvXEh|aQn(X9yg~fy^$s|Hmib+ zL%8<5o<8SgGP{;A=+(pQgh&(7dgcE3xx1-xI;?VVYBQTGTWe`{Z~O}~=q zE@&h?Ug~X>F?S}n#|V#|^_ESJYL=2n%1q0oPO5w2f%Y$_tOE#j28*Y&zeWb;W=85_*ZTAs_hcdcI)r%;Hz6Yz0H z`Gf}Ig!HnxzSXnH$1PIbZ&g^4JrV~W4Q3}2Wj*@&O0f%x6Dn(Tx$!(y`oaV;YTVtA z#Ku`rdJ2=|hJDy7M0Qn_Xk754f#@PXx9H{jnPj<_wnx`MHko-Y<465~6k?NH(y^s@HYX$xMRM)AJ>%$WX^0-JzwL>v6K7G!WTiol~CUo1?fXdHHCLHFD*XHoNE3eN9led-*->h8U zKzwo&txa^ELzYCbUDm8(lNzn!!}b;=68#jiGDNNx61{?!M04}cwFRE3B~(6)eqTHl zqx1~*4&AiET7(=ZV>hnxej~AMoBoY~s?MbU%iH}pL#@d#wL`te?nok5HwkI}@GK(k zHTzX_^Us>f&9MrUpC4w}E#&%nXjJpYbt8pI2UdUi^XD6h117943HWSs^x5g5o(k5a zVfm;oA!5nI%&BpsA{+~em{`@DPbodS$L5XC!end|15?J~l%8h?KjKNI)VzG9ugf!b zBe67c(5LH`v&r>wzSpNGvq^E?k|DcGlZdf*(!|};3JI@$89TZDnI+yk*^i3DO%I#~ za{WA`)_6$H#VTToXF@^E*LtG)?9s)m##xdNwm%(M8IF^QkJ-ycQ1f!QzEaVcuxEsu z&)0e>Dh}l%9OETkN!zH6-m~@+6^9bKS`G6ng-9iXD+UUS8j1Ozyp?l;W|7b3-@018 zVv~=@T7TTPKZW>^rk!87x{zp18hnZCpTd<^cfM2e;Ld(Z7F>Ix&noqwh6|GyVgeS3 zyEYKk=T|GeIWwE=(<@#|QIt(`&hDNmM$KPRImcf*ohl?&CuP0mj+fEh*|8*bUE{_k z&gA;p-Y8;EWv~#ruiMZAA7vW}9Xs)}(Zw@Klf61YGpT$pU;bfISV;=;c%;vfha(G# zl4s3YTrTR(XN`G9<&8nZpU?awo)_)+YB(uOO0GL}BJ)5ap*J%ky;^h*8P&`C$1avN zS(WLZFhnPr@UqUH*5!L4ao42fc_yWYRsQ6l1(oM_5i`DUuPeD|jBt&rD7j)vw^8CN z8i_~5wpb4h7jn#Ov0jgB*yP-nmSMJWi3H|a+4Rh)n2_zec`MhRqZPJWP8iA9EZH-C zGFQ*ph~&O9D@Dl6d1D`6!x{LSAS z()X;}l%BCmkNdu*)|p|pI)CIH*~$IbIY)=8^dQ+lk%e@*22C&VINOXT59{QFnB}*Q=98Qhb)a3DJ zxO{o7FZVTCS5LH_xkWwJY9LgfypUHJWk-s1d(v=7n_92BHtEd>%O>Z&3thQ-%nRba zZ~u7iy!>gY^MX-X(l)NayO$?WamY!$X@AK~jFkQGaD-1*6Y*$a-G`%HUCELVs{|=2 zf($v@f4XnKL}I+J%A(s!B?Rs#X~@mbMUydrzV-6+Z))Oxw#{AD(qTq+Hf4LS~)4xLf5#Bhmj_PtN!fXHvHGSYsax zYtkzEg|9_SB2i?q=^6RGh)~~>^^SX8Mq5hnEHIX_vG`Img3E>U;gum%-9$+8!Y0{y zw;G6s;z5%g*HiIx;i=oyX>4-Vlsk@}s5n$>IbOMkTGtf~wzOMn^J+(({gyW#oYcN+2dzHrMeh-An==uJBH>ml`p*|yc)TBA&Hf-e9c`DZA zv!`#9f=46~s&5LUE*vQ&HjS!%mqqEhFf!?*8kL_Lqnu7zQF^wW_B$Bj#K!itCs8_zF)Na@*HK3Rf8`6s3S z$g|uyoYZ4pajZrI(d@YL;ULvUqF<&hU4JBPV`43Jox47iZy7ejN?n}9e9yk_b+Cz8BfWpeBl)@H{gK5hGno zuB`j0+el;?`-X{6bS2ds-&dGiVUyS6rtaFDa+UBCeXe*%w1hY+8@-IXj*{q}c5FHo z&-&Ls8*%eS>ST}LYEMyeL~OF&`shYNeSX8k9CsIT(t3}VrApSMLAApL&0|SKf5(cT zGqJ^l{FcwXx%Y$W5?JIYLm8X5)s5%4_UKO;Fk0JKj5G;%2re@IM7(&?y7*0S`nHF>Yf&*+$660zig_UvvKi-_>AGxE8->qN_pSwh+KG~uv0moJO31K+i8h>=S& z6M_eRY$P7ChbJt5;!5htPw{k|VogSli4U_(Ng%vObxrDPS3>c3c*Z?S&%RQQxz1o} z9W~$W4=#F}sV|V5S4TY6oFMhavVqXsdtG(hTpM!tn%r?c&8E#s(*F?#X%^Spuy_$%1PC-4-?{^{n zm*`FzmO`ytZY{hrUpJXp@OiKP?zhE6-iccY-21@^r^gpxpz>JtE7{)M@iJIF_`sI; zq9kd5SWicsio;pT7srdclDog9y(-wxCSN7a>3i^YBC$?=*`YM$5+bJX#&&Kz4|9sR z+Ktk4cnI-;o5yfFu|643MM$yzlb)V`&`3yb4SO~Dx-%)UBy4}RzBT#zV2L0oTv+`_ja}I*O9ldtKU~_NkGm7}+=f2JS-c z)VNRXeMn?; z{c+Kh$kUBP$SET!pGhv{$43s|%X(Ooa%l&H>sKTaQI8~BcKa3+KW1)<<&OKoF(N&W zQvT^Kyx*HU4_^2D@o1-8J<+&a*7JN;10iGNTXI9uj#R7L2b^kuMbb+o#zB9R7 zHa}<$^&Fs&)Q<6~=1By3-=1u-C?+cA40(HvvS;Cruo1~re*UtnVH`IOKNl>&Jol?8 zsp9vr=G4hX;zXatVUNUI$*cx!)K@Bx&D^IwIx07T=-aZ&%=}$3q4+(&k~{7;9wu+b z=uyuTyx%!+$9?jYVD`~IA|$RI_^#n?BQfyR)sH*M=a9E7YNEGc*5qrS+c$~t$%Lg% zqs-IMMZ}p;?%%lMo}hc}{Zne(%L^s9apUk^xme`PA)=(LoaA*Q=_aDcEn0fAf(v;$ z@5U+J{$L+=qT%-8OfSy+WFKEOF%GMj_7s5wOR%~9h$ zC*8N9>*d}>>iC;32ghsAB5|L-Vvaplad_ulCz;hxPDA@%Y`d>%8aCrk?~TGnB&=;j z@8yjjEJ$AeZ8+{b>-d?1`_%VRwS>nTPDamxFYGzp9q(O*Ieb38{?1GecIrfal~4hC zt{f0^RN19b6~FXj?bq571TNuLD|L~D_G!nh4ZNuT0DX^U+^lYPnzrbHpBWH0_*pAn!fFwwWx)-A(8@{r3mW&lS>9 z{M0W_+nkxMgZl~XZwOvmf;}Enydu@=2~XcN_K+V-L#X$O^Anmsjmp3>{DN$hCLsSf zm~Rxx-KvJ0NEVk`77{o+BGYMPe-uCS#uW8Ecpk;iNzcUVGMdOgIi)v4uVtfs2Ny2i z&8&Pm2p^!HI@_B1-pZ&Qg|?>`qWCEuI4;?4MJ0ApMrx`)WzW)k(j4J~Xy4S1JKtaA zHw?r};ZyXO05X7KGUy!Qm@DM$Q9s#akS>z@T~vdh3yH3nIg{ONo8(~>VM zEXh{KPl@ZtjF?K|qqUw43Z8-Dr#folh2wwnPnEvI+O)CAKZe3Bep(t$yzz8%CZpXZ z+u?eZBGY=WFTuW;O?f)?9f}{X!?kC^M60kNPj|V7FU-I`53ZXP&qD82Z~M!hTESAs zcMVu(ab_2Zue&;`WpOMTuj#7|N;AXJd!MsS8-`qoM)nNcFj=p7R|C(UzFs;Z_2VsZ zMIqJfB@M+`w@FlQ8?1~I#*Y;uaGTnJf=qK z+U!y+q|4-2s}G_0QH&fO)Sy^}aWo!DsVd#MKpx*T;te0(_lG9h<|I)1$6tfI(M62JE3gJ`Q4 zdT*#;HSp8ifhc||o>oj+pO5@A%(hak*G`lVZ?u}MUVYgB4-enuyg#J`JGK02{o)_U z9`^3N-A;MeVjHvTOznr?!1`NU-neZBYHzv2bH~!>N_h0xwPk}>>ET={XA>(W%sXG36RaO`64ibCp4Ig}Wr&BoO=})IrUbJtO-P9Do5Pcr z9iLzvT~dr4nX#mz+rbR%n(4$bxhv53Af#LJqrR7`;c?Nk+$IqOzR_^xyl;kRyk2P= zWu5Xy{xKdArMTS?jn{0M^`3{np?&qUc1_GGy{C?!?YTPO9#(?o6xjQz??&H4_dPk= ze#^5eY-8jupQc_nu&8A&JE9=9VjAMhGlx_&csz+7~F5jGCDT_3397n3v}6CeH`Q{NrW<@^0_W>ZE&lccvTTT!wqBQlarL{d>kN%jn7mu!-my$=W4 z7YZ9_SorZg1o1Az=YN7p2-K0Pt}f;JwEm+{8;{2oTc8is!@Gh$Z7T4Xp5IT;-&2s| z?E0;j8bF`h^Z`J=s)p`=>9ymgaZ$Vmin-YZ?hszKy`JP*V3P1K}x%YxEjto;uK{ zscW;cIrlfTL{gkBa~GSdFceo)`~!Sn8FzK_@FEwi`30&|T}0q^_otyengGw_68EoH zvjBf_FphIo7Xv(RT_~@7i39N(5M1=M=!ZP~Rev8@$NIi2@>&(|IsiNmETS?>zvrQ+ zTa%`wSf5%u`E94(f9AsMm)=tno#ccWDS6)AxPrjl;STiqcA)Qx;O|8()pEdx-}KHR z>J&hq$34_>uG|2RaqS(8shf&0k`r*ivNHv38GoA4t^oW~MfrT{!Qw0wy4c~6YElQK za4FmWeFpMXs;{h(|E+SsXQ0xCgv*$H9(EO{XjB~Wg|vFx!?jhQ&+E>KlxvTGK8>n6 zLi%=K{klkfJG<6h5l*2Ia4!>>f?91P^B3}feMYQF85etIp*S(C;@12+sKw!R=Idop z_poHFVsoD26pWjGt?6<`6t*lDUtppK`QZmH+RQF0z=vX#>rN#y;P?0BE+)4#0Dm!c zZr)GUQ-qcCNymL}O+if*)hr@bT}R)?;G|f@b9(|h*^;D8ITz$V(Xv*fZ8rg)yML(KWpV-g%#uGO?{ftE3z^;571KU~ z@Ct2xrWck!w}~%HNVNg`eDTxyaGh@f>NYGSI{{L*(!D! z>+AqLOP$VcD(P8*D34##cGRtfVw^0S+W)W~=@V_kF?MPe<3sVf@1i`aFhTdb_cvYv z{?SnuVA7fg`_rM}8upe-I;q)Z~0eF&Z79xwEmVh_NpOS5oPDAF-uQ}hUfjXtq zPXrI6V}3)Qnud#*cj_Rav6=)rd$8YOnReXm_=EK|$JgvW9YEorbG1UOd_W(Ep`rp& zA%Gto{FP8}1n?J2u>zYmJE$XFWO>YCYpV%gs3@))Ae@FuIv8}zCxCr8Ps}sgRn9|6 z!O7++kLw`Epssb786fYmfXw@L4la0t^y*x-B??o9RFh`XgY}DBu7hN~7Vx3@V~4o3 z+u-*nw9msDq(GkxiyS)gHVE!;=-ORxoq~KU9=URE13YJh1nNGaUxHNYgJ)9*>Y#1W zgy6I(fJd_he!+%1tnadmva|(3VUp~qoW8i1OhdnDi8kK5fc4@^p3$a(=^S+V0`(as^?Jy}`$DMqTj2jN z`7VcPPOOhlt}V6-yWV|(yeYk#1=b4-gUS2DBmh4T+-i@@XaId!G@&!&1t8yuxXXX- z+oS{>7C~YXkvR#)yTs24;(>j*pR?PqYR^GTDfExOVs(I?wMI)X_(9+J;0xBx$X+H` z?%;_cg|!GQKUONny94+z-qQM&a!tTdJ~3-GKFB~0@EhbpL5aizKFisks9IkWfi*6> zy>I_B37rsP@A54Nd}zV>EBeFRDah7x71k8b3(lX3xnO@Hrq-&g|cg_m+=XpT4F!)mp&%DksQUAzv55!Lt?F*u)Q=`Oe7 z6m-@1Q&qw+9qeU%FLV;RrA607%n8oxI-Bj?WPE0z z!I?X*;&QdnQ2mYaAM9X1$QAs8hKZR6KD#uSD5|Cg=NfOSjST~T1y5S%lK#_o=`b}E zCsqUUUC1nI==cwS?`xWL*&@4G{pZi>yB;A^Q2Fyg2Tm=(&zmbB$EC9WK!%?yf=3%_ zA$nf&=13t9@e}@@N zzN@MN`cU@~9z+L8!7`3E5Azf!q2^lMXWn-J|J)gV$r$o+4T^J~%4^5YGfyOE3Tm7O z^3D~FPjq2((jtjY%4}o&-1Fs9(F1Lu&rX1^mJS~9p|f3IbE`M_{kx9y9B(~=KCkPj zoqk@Cf}ud))7)<+p;C`-vgLoyqefQRpU1bZLm#4qb4!+MvH1!P5ne4IUg>UmP%%hz z!ClOxVZSy|_;!29%ZuS)y{N8my;0Ez@GNuVO85Iq06#P*S7+*`K|U5yt<6GQpbQI} zcM{#aiSvj{~dToXRwS_j#7h^?p#gY$?hVtn_!q)x*YJ@ei&8%D}<;H8ncz!nL7*4`@(YxwckACh6C^0#7KKv(=XI9!t``grc;LW-fH4C*RrpzhT2dz$u+L)M zBHQ@{V4p{YAD;is2K?@oDR4IbBgj`3+?%7*xfNkWSBiYOUMyezP@zaF3hMmJJ?2*z z;_*=LW}TpvMIGc`%E++71=bsVv4i;^O;}&y-E4*{JZkW4;aR?(4!}P)d5P3rdqAJ~ z5z?X&LBKz~7N%J}2H<>^!7XdX`jr$cdc$T!A!!n-DJF7%mk0D=p*bhfwz3I9Q}+aY zF#Bj1K5q0c1Nq?z3FiQXi`?+eP1)k>TR2!HX5Hs54D2J5M|~&M0qCPlM!ECK9N1@( zCR+MsH^>)4zjiJu$&12ouelpDWA`b?o(>_s7l1ylX6D^nLF-U|c*7JaZyj_$?9t+T zMbHPxP;9B9o{IGmU-p_2c1K}5W{o_x1rYD!cPj_B|H;R8e4j(V{>gu0CvKLnZvnn9 zJg4n9p(zTt4>b_+i%dcb^;$*NLm=K4i21W#m2E?=gl5)ZXRyAW#|sa-YruLa>>U>q z!ide8ky{w@e~!X62Rc?-`#>Kf;j;`C=TeUF+zltmh`j)OxPiOAR9XW3b(6BxM_5A) zW~BHMqtZMHjbebxf&7pLdLxIFwCG^gOXFJn*#nVD7x_DP*Pil5jzmo7_rE?8;C zVanbW2g`L0fAw*yJNkDM3IfM&B+hoAr0s-w8hBJ8KhF>0>HVx|V;8?>Z*@>p3 z?=czYYN@ON=VQKu#K%@Hfxam%RiT*c0wDjXlpafLyC?!HHOQZL^__$YelE^Xy&yl* z=fXL+SMTz-p(SfS7mY@2?hSlfV`K*K&%$?F!N1fz@bWbmRuyc$F#KL27M1|)6R|=r zXDJNgJwOJZwju@WbDUL!=YthkzXEW9^R|qV@U7_cB>uURQ2tK2N%BAI#qXYupTwtD zAr}*b?Nd57M^5m2ynZ>rPw$}Yhm-p}@KfDc_ds)X*j*3HDwxu^+I3M zsQUg}kUvM)^Yq;s1NxK*W{*)cAaF1AjVP`eJ8vn<7g=Wk`LkP=K_kW0RcNJt*;x!b z-{58CmNMf2=kqqcJHute+^_?WGbd$*Doi-@VM-qb{LHGaT;06^>_hbEzJn_r;OAR= zTglA{;5>ZnePH=HNfp@8s7Ww)V-o7DTN2f?1bkSX`F39-1Dk^pEX8;?wHC^6u$TQ@ z4*0OUL7$7Ugc~NPdz{I(gTi?M%vIU^ARn`2vlwYO2I4)pRn<4D8}N@okOT2UuI;1v zi_Ktz8^@*KgoEyAi|k2A^=-a!h9Ka>yCY&g-;D84t#>^!Usf&DK(=20SDNDpubytZ z(bl0{aMZKDrtNAJen9S^zlZ{T;ETna6dK7#f3L#gx54EHKp%I*8TuP?z&=lVjkjbt zMPMG5vHowdSbiu};9-~r_y=+y^>*(fA4KN^TI?P zWurI4)Zh&b*wb+rtY5n3omWIXK)lC)Z0*ID0e{6MBYvm<0(kkxA%3 zo1Uyzi1(7K?Dw|2DyIhc3org8 z?k6$e!_GXdKRU!9-Zv~?Q!icz=kt%r$kRHLx!{oC!sY7olaOeoGG~iDi1&l&ZD~k- z9s1JI_*`th25Q}r;{Wmi@N?OncT5&?ys*?DC6UIYDtwjxzc(o-fIgNssmV1z0iLPd z!w#wx0DdygLZ25R0RK4t>Mr_*&9!Tg&>#qm!p>J|$15iOnX@OJ#?G(nkB6?@iEuM| zRtrVUypWPU4f-SnJR3FaGxDVbjtrw09?A`66z9g~G>*QR zi>mPe_A%^c^S$)B;D2HL@fY*%56He zIvGW=`9h)-+xk;brAyMIAnJekFv}q9v?DQV-*H?8A$zJRSHDe$z|CwZCktR26 zT{SnoQiX#a;Q9K{d5Wn5cssIPX z_sl5=PD7?5^wLj^LB2u5#0?RNuS27X;iu&9)`yi$q_R)?*9-eLFeXW|0gVONmP zOEcUz^)dqMDdEkA{)^|p@2zE*9HJILe0@XplW2PR;r@oRAHSGQLE+>pk~S*xGwCaI|&sKP-UP2znFfPZ2?oAWVy z0e)V&M>05E3HV1u`nLTV7dT(gPtcWPo#%&tANTKmdu$SlD$bM6X#?j`6T#0Vgi==^ zm(oz4J;7RN$E0%JIR~tN4c@XV&S~6mk`4JAWo+)KM;f8I7M#NrWDWktF@Eel<&S6V~*XmEyqu8EUl^^NRSzuRxg5}_1B3zY*zSbtEBJG@QLskoyBjHT3e)eN)2 zC;1c&F4}^Xk_3}8IN716Z$yNCF&z*R#=h$XL7~~3RTosZGU@(t1$|V-$ba^GeI0swTzc5PZt&Xga-w z@x$|WWh{DV5?iD8=}+BgXKeZ_>r0c!vxv5zQu%tyf4Er3X+F~6oRTXz1F`y8`Wshj zO#2B@k+l$#LK$1sEPyU}b>!-HzOVEh z7xY}NT|Qff9!f=rWEOp?L_WMA3+o?WLu?P`)#@=hp*4kQGG^zN{eB_>OiuU~a^j{v z5$fUXu)mDejX(KXuy(526=gY9Q0BQfm#|Fd)dPT|J% zPa2$mgHY3ih769%^qRyeBbGj_8$bHxHl2BVx9`1cx|$evPz_8pEMaR~U3CXSOTr?E`K?)S~-! zDpx8Vp-d78yu5G|6{?t5`t?^2?Q8oG$uUuZ{9yL8il13U>@}_?3}JGvoQa)aS)jqm z>@CeuU~)?BNb}eViO`PmlJI=2Us5r|N1j*}i?5E7SfYFSSf54q9k!%OtbgsC8gDKh zA)ZG=KVxz>CH+b-WBqq5G{2<}{Z6F#{QDHg3bHMBx0|^YkF?L37gX))p%H8my7QZQ zX!5MVDWjL)Q6H&{#YOumjEi?@NosoC$A$CyB%&q*6wIsVKv2H6@B!3rFYrp_9x_a+NG^Djddg; zvbC@UlhYCty~f%_gIg^9k$Y(8m((n;tuXwk8pOFilUPUQZ?JF@hP$E-e9< zl~RW^pc1KW)F=}P#v|vIZ;O^-a+X71I+cym;G|yV@$8Wr;=Hl+V5@=%{Uz$^|1NbM zF}WIe?L!adcTMzK0^w!!&E&<8$7m}MpX+aFuU^I@cIxwN)tH=$nEM$D!x(<52`(Mt zu#u>y2zpP1J~yOzvh!pe(bx5R#HcZa_h|exFvottd)?KN?IMuK&I(1AaOuV$z8(B0;U5G$)H(Y4D zuXtlZ^vdhMkOLPse?ad}shGbrN>r{X<;$v%mP&^`J1DM3l8C&V`W@Dgod`{}L*8JO zA>_G%>2pS&B$XJG^DdEC?Oe<<5~~gG-=bMZEX+G*X>VUbA62Xg(dz1<4WwjRtt;KA zBCb^_yFWTni-CM-7;x9|h7>AS-(|>E7XM2*sCju0wtP8<=S0oyK@Ivzq15K9od`1~h7< zDA(CtzZMKRa<%)*dGxdIs(np;ZL(hl4uSC|)zR8c~ z#UoVePlmfNIk9T*RrfIb%rO7Gez<*prfKr)uC*mrKaqSgIebEN6|DCC;gblI{JN&m5T>xvW}DX}aR zJLDf9?fk!-n4GePDzQU-nEwln6qm&8^V?-pX#tOv5%SR*M7yBpdayBwWG^{YcFDdd$9e!8Fdtt?> zh9!iCE63``T|9DOv@@zl`Vv|q6EfVopo?;czGM=A+l|7NdP|;4lZb3PRjwq4L#BEK zfdy<`HrHEr55RDEMYFeC9VSG}22tG27Ca&@d-6b7*csiV|K&Bhpof0za=+6iT!{pW zZ>-(aSwl8{M3{cX^dYn^c&0y3gF6UfBdX(Lqa?03(QJi!zPc?vRKuGYS9-k)8UDMqOhLJZRFbqM9>#n8;Id}X z5)F>}s_WXJJ~l(=vrQycoWh`j~V&z7mt)9T&qjY z;-qFec{*)nf)0Y+dG-BmR15=TG&bB9gr%=woS*m&!xyNSM>Nla<--DDw;!!O3#u^JF8F~(;+x}pE#82oMAwoCwB7a91HO8w<{-d-a&E<%x<9Wu@T6pBq zEaU7Uul^+9?=c#o!L>>sGdNs#gv}#w>va&Je68a}>i0Gf5iw3%y#W`r)n?p0<*q(@ zg~hek0yq_%;y?hl>N(9DZcb;3NXtrh+gW z3W(OHK8zqhWM_vBRVAr9wQ->x@e^L&_Z7Y`m_>#~9J zecDeY*qn!uOH5+#x3{{Mub;a;g?zmcUh{FfWtmRky ziO`U@&4QK%c!awNB|qcmiptvDH%KMXN0mz7=%>b1Aki(1nJW^Q9qjHn(3TkB6 z)pa5RTbG0RL}{gpI0IhzHtsry{@J=rXzh>pq~hFGb|7(@#knZ6GwdhEFOnIahrBP|5}xoPmE^ z*#G39@~+)xB&etU&U)wCI`Wo0@QM(lJDNtO%$lL6k9u!37vFIzMUajM-F5wVMChv6 z%fmc@J?O7xJ*LmCH3j4lKaDHA)aA`YXxMxfwV%fZQf{q|+m3QY-CyK-e9zHGTR3<6 zb@?ig_WVoU-}l#$^0F$g!~F$aYB@JQ#?Q|#L?|E1sVD9|HX28Sa+hkp{2__)d~B%c z13owOe52F)C2T(!@!<7^Rk;e}osxF{EViGH&7zg=$K>d(pE&z%2;=$e`!R=h{`usC z;&VDu)D0IW+J0>V85+HIaz4@nB}qKFmo}h}T8ke4;_Oz2oVX4rm2hkz#C_DnhjNCk zn)7-v`+W5swmpomg-|F^uXG)e4- z;V;=FVOORhi&#YKp!f{q0g~66_64+cN_v4#{hx@oBgO<`IERWKnV68sfr*xbus(e9B zik9i;r^WoYjx-sQ;PVgFL(82md&8)Se4)Ye_g3 zFEqHF^NcqS*NZkTP5QrnSU)l()1}L?iRgSi5%=S)JIYY}CE+sDWz=4ggz+;rr%2>W z)`2PeI#T+D-Q&qtRu2Fam50p}AluVeZg zyI_)eh#!fMe-%*)BJ}F{{w@BWSYP~5yrsX88*2A5{>SV`ee|zQgs*{ZCDJ^ce0I(W zk8sKOMf6~De(qyqd$IkgO1Z1Zq2KE=*@9)Te*epj&^~=Vwyz9{{ct(a4K?_W)gbh- zKDuqz#F|xDj(EA@NBOJph)3@8uZMY5#P0UWAB@M`D}JmW+F3SlZ8&+E2(6QNLu*RE zjj-_C zE*BA@2kJ%a;5n$Qk6sHMQPo^6LvHwzT}g~tN1oOeM;^xezS@7>H(D_M(XqRDh#!05 z?s15T7_HOmzxqLM9kF>eEE2`#hU&a#`=)Jzowv}rlAPD5Kz23WY!ctYBUY|MyNCJD zZT_~lRxI9gmN`cb^MnKR!yPk-1Py=TPi!E(j=cZTNp#x69qq|t$5~nGqrW|EZZRET zbL0XirfV42kr1Z9{=@ZD`A1z38HOJ!qW6u5d4jXHmt%GcxF?bQeT54zfz|!`rno;= z#p;mrw<_<}f^%17A&44Y1@{`ML9cNKVc;I(^09kDKS@E|%WH*B*(A*U?9ojpbGq6URgQ$#2Ij}L1H{IMk1yRznpqyT**8I74In< zD~5q{a!Dor>LkYeWON-Q>pf4H=nd`xNP4V4X3B8G)#7bN^k;DJ82aVC)IaY>H!7(p zKm6xC+0x09$+}SR`}}YF?7@EEo};BtiF9N_0=^_2Wfj+m^&POMoy7lBhtv1-Hm_;j zGDLgu_SY(QkHF(bOU}UpzE37WWtia;Gh9?@geCprFp=SSiJ5ok(LIAfR4{&q>Hd+w z63HH(H~t9jX>JY#Xes7_duQL_d;Q!|Sl!fJjfYa0zkJJs9TR;9j^xeqg5V>ks%6F2MZ*uT2HxCZVkQ-PDWI`$zJ&cCCq6=T{($2d>+N zuWKQpMuDf5*FZds@9n3_E@5?3Qt0MZ2ns)*CJwjI0eD{E$@I;d0C<)%VQg4W1o0Kk zp1XWX9o*B#QoO4T_#_Ws+$*>!96Jr^kUr2hW(W7+bNsP8EjQ<(<0x(R57P!HxYca* z>T_W4NN0|!zhm6+LSf?Y7&d>H>CZ2Bzc6s`^Y-Ga+3Ww@(?z*P-|wvh{+^s@HzLpn z@kQV8RU_v>8O|7vt=v2}1qpV4KLeiuc%E8xJ)=mw0!6psv#nZcp$gvyXAdq=r(L-< z$0OIp38%DMOh%ibu-W|EcTLYg-$B!Ej}Dt5uur*N+BCy25bv+paw|{S5*+#C{P5|o z^??`QV<#flzsF2KPERZ6IZHvj*Gh@}*ZX1~GXK;Vs4Y|r>25qp=5+#n0w(z9x5hTO zVLz4!Jwp^~FkxMl)bmns??dZ$A#O(m-1}UeIJNRa7u@?iZ=c^$)dTm=0&I%s{kIk2 zH(jwO9ym@x${P1&pevo3bU~m#g&Eu%O1{!?dol~~;}o=992E%sFC>(F;=d0vFp`_vh>hxl zh<2@-pTYn?LXR`2$`9tClA9)>qiS^!-%KRy*Ku&~SR3_V==mp3nC-<9Jj{*4?5}j% z-^T!d5jLh)KVAX;x){t)T~!S9A(#7p)}IF48x{u}OF_yCuwiDb{QfvrSN8O($MFK- zuSs&+c!t6`D2J%q5z(lFl5?-{T%!YZE6hnxJWZRqV7tEWa}u>UII*$s`Q#OF558EH zd!BX|*oRiooS0(`@L|0NvFgtrP{((#TB_A58ig-cCb{+bOhG1Xy`Ljf!1~pp+z{Xs zKMlpW?~YWb)IoP#o-BN!1$Dy(7Z!apGS9$|M6@GvuzD5C_)P8nV-Jq-$t1~Iu$Te* z_*Cn?7=Doe`^LW4C+|&IKH{ev9}DH^_QhfM$d)UdIPASST2D!me!C{T`ut(ea$>4Jp8_2Y|RCM&&X!hN;UvKBx;XtOu7#GR%B{B zM5aaoAEtd`Tkr1#eD)t*(4$wQB5?bh)Qhw3laQcNq230{ms*LPIqw0sK{a zl`8MUPryIZVG5BdAEjWm#!~TWtS_FWx}I@(7vTBHE^WS`?h?fQ_rHh-jCGKzwaS_2 z|J1Q(8i^E(3vt6Gh7A##r6?Sxx%wb680b^+67O^{1n}H^!kTsTpSrSZH2(>+X@ffL zuBX1|c8MVP535>)9roULzSmUc))$~pxn3)UV9`8u-#oFa4SO#`!)-KNd=&86X29gS z|5HwwMeZ%~v;>72YV@vMv;g|->D%7s=mYxr3H46uhk*Ek@cvE=3?SaeBXV9pQ5J=7 zaH)_nyqbWh1ze}4g@8WnA^s6@ZnF@b+kv?!e=U^u!Ag)l8t~cc&jRO{mAPTc@}6@J zb~xCmZ?w?vIfyTs!u^DXZs4y#yDU}4_rPE0Pn+;b7l3>4OUFe+w=W`a-tAi+gb${$ z`Q5rSj?aNUhOa+$`=>5K7shN?xJ2t9?tNoNX8}-Ww7ah?*!hDC)~Kw%?ff4Oo;2u< zjh6=cTwt~J7&8O;nEKTcyZV3Xu2PHLOC0n;-NAlT{oW(O3-E{N$v)rODd@p(2d4{; zAl|(XXd^#{tU=k%Im=al>mX}-?}5=_kRJpLA1Hjl>cEH-?-gkUqc8z+V^lxU?N!f%xZ-KUIC%6@uyVFX$~%OhMaEa5JX-fDfY{Nuocr zm!L+&KdEhUbx`O7t#HY|pij0OGSTdI<$`y|Cz@XI_^t5W;YG;4e)X z;W{N8@E2jkrG-=f)YW%>eJULpgZ0-ZyTMwWQ;=P#;IRf0V4u&=yEHtB=b>vI4`tM` zd`#GJX=Wn|)M0!KabKwqXNCnrI)zUeiNg_`YG;qX4L;(hVMoWpXOZB2fz|kU@7r|x=U-XFuyhh_V(LBKxDd5rX0alk&g8L5&M4}pC&>TMqz zeFy71&4U-K73URT=kLYGO0Yghif8|EIQ{c}Ym-GwpexNHv~Y!8=!QcbM0Hh9XHy*1 z6+Oc7_^c>$!J!>3INsL{zw*P3i!B$Vy*s-V+1y6*F{Q zanw>8Hp*Sv)?vrqmn7W!*%bxii+_)3ipumiB%&PPd7`ftO6sB2G(kbWQT8pH`Jxy% z{B!H-PfkG;u69(gs^tawhVtkilFPb)e=Ld(j&Y3u{<`;`^77?-z&=WCI09Q?2+kX# zuH~e{-e;%KeCRU#Pamk(j}BU1Yz{_bieu|i4W#36?R7K^jLoA`Ioi@2kf&__ST)=oD;sykkB3!hQjjlAG$ou z0e%D|3?0i8s;~ocOvLhkKIjm`?1<13%}emwmhSb)!Aw~ww`4M`rw>}5{TGWAbo4C ztI@u7P?W6oMe9=_e_A|<3pKmT4R=$Vv)k2Cg@TuS6z(b$1AWAwmY@C`s0z;odbO`IPeT=^a%1C>fPY@J=DHi{EI@H@qk`{{*F%Yp zB9fW*AYbta{|2*FaKZN<;@z6CcQ>@zM4#vE1N(572l~Fq1@WaqSL{aQ3F6CAja~4b zC&=&i3UNte!#J3*EN3ORX&UP5G*R-j9Eb%ybI0=1ZX1v5v2jX2f#Qsi!#R^2z*P}H!f#pB1(-nQR!2Y(1`j_-C3J&<|+nK2A z*Hqv|e5BswpR%LxnFvMJ%_x9Aq{i%A+twf+7`n?G-zk86)5>Y)uOjxo$-M>gFp+0i zK3}2cRDc8dYVKJ<-%AS%(Ag<6_7CstpghU(YL;BUUw1{$wb{Mqf>mUas?!xw*h%xX z#Ib9@J~{>SS1Ub0e6>Wj)?8o)@l~`S|42w2tcU!0aW^wBslX=%@Gr)SC!xeI&eV!y zz&;7HWOX$*vykb^Fyhcr3-P`82}y=QKL5h^tON-e53H~7U3fEE72f}PSHE-@#8*V{ zPAPXSh%cw%W=76&5MR4itbgj?0sE*lo#~g8mVuX*uYa@_n}ojot}wHm2Y6l-MEoKg z=OL{HVNYAEZ$&~(sd-WlJlq`!;!yE`xux0JrDGu zM0ERT#eqI?H?$w0r2+ihpEG%j!BPo+KNcoq^=%R=kKC0FqF5}?2Gy4ZGdw_i$`l|vzS%7$7jz3!@BMRbu zKlfs}-$T&niayY(7$1~`i6_IfR-LAxlHM|l^bWv>1RR43qS>oZ{Ov1ag)Mauf4wB@ z0X@ja^giFLr_$nqNh}U*jm^~I);M*7SFMdl_R;#!WN$MC)Umh47$-&@2Yp!TFP->F z=mGyc3q0LeJ`TeKc@lG4I}=dhEBeZ}GoX%LJ9EkDhb$g4fQB{wuac3iv`T$@O`qD!@|C{9!(4;cF1@uBs!hl6D~8Z}h*6ykP*=_p7_|(DqtOLDC z$nkyOi%=<$&%0`ghs)ntg@R9~sVY^~LQ8M{lXR8>{G&Dp?Q39j1L_s!UH9^Fu=rKq z?DWq-pU|*t>AkbSU#;o7m$OTOzd}M=6og&^eNt$5`zWkbVE@@`EmPQ>qV-38B8Daa z&unYxsV~3Rq5n)W49$+$L;m)I-)qxBzG_R_8q`X`1B-4Xy=b>mg->-RNE(LJAKBOU za_#Z&|GbaB)VmA74r)-lrXD8Y{ndb&wyu!a9x)-K?m2bZmk z-TtmZp097c#w*uCN~06=fA4V|{k=4E6+4puc;OWNmwf@eYB1}?hZ>fZK%bS~mrMqD zz&~D!3v(%KV1KJ=k`{G71mp{=Yz20|F3ZA_GT*8MUQR#?ETLp=G=P76CGy|z3amll zfB&wYzE=(P2$;n;e9LZW_b4u&b|$bU}&AC}}M80K69`qbXD3AK{}-{Z&k zHMva$@K3Q?j>APhIe6|quJSE8md~S1%B@zwUs;0l-+5a%Ace2r+m=jfp%MN>-sBO0 zzXp@ROQa|_d|ROU{ml>@+@4#Y#XSS?JewEm|K~Tr^Jnr~CZhuY&tWnvf36Dwf06&W zcAWc`1pH{4obgZc1T^euNpUR%=<}wO-qaMg2IbOI-HFnug_s67r@nE3c>kjZOH;k# zf!B@fxYB;9!9PDw@6Fx*e1xxU=87SwF>u~{Pd{dCPXXMYW#kXE*hvF_X&f_jQ*V@k z^QkAVRMSpDG-a#lKbAn9zkJElj}~9oAufIA+#!WJ==(9h*^Mn=pQ|=84nMlsVE2>} zub=L6@B%5S^+hN5NT0fn;Lny6fDZ{*?oY98g8bs0gJ;_ddmyirIs3M)jXWHahKkaN zOh6<4)t%1QSB~U8`>Uq%Sz{U6ouk}sK2{C!5ZRsCzX;-ke|MJmW&qX)A*W(FyotSU zJ6=1aq67Rjj!tZ+e*%2Sp7PW;(HHRHz6PmB`c;rGDD+-9H{~P?lZlT0ev>l^jU<(I zjok-)ct5}XKjxf8$o!b4%l>dJB(3KR8$^Kb!FkQx^kd?I38=KI!@j7(0}3K070*F@ zalcc4o2mo+Rj#UA(-Q-}r&_(XC_o4BYq4qt(aB6n_|zj-xqx32(0*mRI>H0|wK%!J z^>twdQlYYO*1k{+eJOe0$i)uwU&Hhlr+vtI;GZ;HizTkAFpcQl{``O5E1Vt|(~~`s ze6)TQ%Z75R6oB~Z(RL9`@dkKKTtfYmBjjK&RH0!AyKmy2mbgk+4&uGj@Yzs$>k7oH z{$rD*ss@_B$NFn03FJS5mwKf0n7QFTeJS6_W*ppB%ey@B&wHsVKR(J{76tMCDuDaM zQXhyf)5l3y&qNa(;e))3W|gH3duLe&7f1Ya5}NAD?G(!Z@s<08$vg*pKXLsQwr#=Y zB1ZQO!X9$K|E|W}+u^yq@D~{o<~J(puqo$xwInhSUyBq(Nl6(X|2f!^D?P~y@}E+Z zjI69Fu>Mp^1-u*;5HvtLQeVhu~0RDR8`E_eQZ5?70fC{`wus)icb@ioN zV9v*=Yi#236Kt+#kfg6}ff}6TwksN!*KqW`!ErY#0DzCv?`$T1bHX z4)x!&t4_9(FshpM7?0iO{>;taO@9US`5r>Tm`Jq=sf=FvvL}VjEm{2JS6q6K;>B&i&w-9b(F6LxUyB4=I6@TQM@3-0YE_ExNdG7)+^w`h z81C^1CULQugsg%SeEhBhf8BVpnmqg&4^>!GYMRg2Le*=DQr&r=?^|Emldks~53H)& zY^YJK3LhV6vv2(;pTEUk%Kxz*=rctrdEI3O=<~^x<$_l?(UCq*Uu?WdX%*pZW|D3F zkVz=4(q;GSKl_Vw`o+(;qL(29rN+ivm_8C47BLbRK>qDtMM&tP&IS)Z&ihq0BL^oC z%kiq$y*`pp#g8A_UI2V(*YtxdS<|;KZ91^%9hm(8T#2 zKL(z)BY6_H=6e!&@eu8>j96n-4b(|>T$p1YtQWS)AyIM(JTT+uedEO#RX9gQ^C4?6 z;2*0L7n+1PV4satq@kf0*vIs<-Rd0yfS=U6?F^q{h2fc`2&dC26OjDglQc;s;ICjS zjwdJN*PuTyqZ-X%Y)%pCfVU$8=Oc|H`<)X%c;K=Xk8N+PkB_0=<`l~#pij5HN07D& zz%y2!Q#%|9_)x(+XJ>~2oFBrsC?|Zdbx^qcTNY*fB(%b{{bgAW;F;>(64iamWoRz4 zqy95bEws-1!H~Zm;D>zXnLh6T56raXcUAJ08hpmM(AM%Ah<6^NvYP`|fDb*SkH1wR z0r7P;(XR;q&)gdAP_#=kS`H3L*Ucrco`kN#?j7#hK%ep~v9K@7Ymk!1qd&aZ962>J zvg$}?kpIMcXuemf;(-(6&siB@bGNKazw?=>gLqHp84P(q3i=91#weRR9sv7ji?;IK z&IIdE<I|-v~X8eGk%QlYBQV@ao8f_Sw zIL@d5Gs^^j_KBW?^t>pTDN&HmC#T%E4Sl}`HI-~%Ah=iurMm8%M0Y^Ga7Ql1VoII| zz8FR)R#>JA>n@g8BnATe@H%Af*8DSfg55sq^~_mdpCzS-(j^MO-rb@@k>}V{U@Pg~ z1yo@Qy4|61)5jOs2Y)jBZ!4CMt*27T>SA-ZI3apjJ3+9X>Ktc1eQ$*auD0eO9fj54 zmQ36)fhPdZ*2Y&A<^GvdbU>JX+V(imXJYb%S_319uP15Iyh~*&FbhfXLgVEr$YH*F zT8snev-L*MzIoB9HRV!c&(blBYLiVIOXWdue~be)*!^@i19h zz=s2@E3R+$fWNIaO+t&P!1<4v>HRmke^_B#i(-srZuN){!@YG4m3;Bg z>UH10f1_)miwaU#NrHiW#ulSEWvIE}l$)^7&RHD1E`953yg9%#^WZR%PBqBq?~r9W zhZg`myE|X#^9vBO37;wDv(oFh~*o% zA|wW^gJE&MXEwUFNPC*h9ALH^Sr&?5Ip zk{5;-fal6>{@e>~0MB)Nuu3co!1Ir)uZu4k0snmC4(IXwEDh7G z`04mRoPZ7-w(_3fKzwO>t$f{jiifuB11DCAYay{HWiDkN{Qu?#9riOH_BGeO(NXaf z@f@vMCq#d%XmdZ1#`??lnb@lJebCZzp1zwm&CuRsp3l9M%8^|D+_aprJw$4+Be**? z3stut`mpGa)oD{OegB5lvCF)Z4a*ZJM42xU7E9US5shv-CfUEYQ6~0wt$hVEl$lP` zX&>uTV{}#)8vn72I8tRl@5kiqbIL1LMbO~3N4ROaF*$lOz3+Yo5~FG_%ya|=){*lI zM&&FYywMyX_3Z!~Gt{3g*E7kr43ViW7qWV>hwO}v4)kJjh{MD8>mOqEvGawc?UMLewemc8%Y)&Q8I+gbJ=aqfrYjPZK zD<&t#^xJI(HyT_<;iJVCOwKQ_H|Zv0#Hh3Cjjb=(J8{Y9`_(SBd!ykesB1&B&Cs{9 z&m7LElp#aX(M$a!dr0K)54BEA&M%exFqH>bKj7c>6K$9r!Wo$e+&&@td}dut7JL8h zok3i>(rs^4BJ5>W*+(-}PodfJyD1&r4872}|nKi(T|%1@HD!aJ__auiloV6y>_dk@;LW469l<~=|TDgH%KaTJ)- zBpvpp703xvR1V!SNQFN+oX%6nM#i!@YW}9L`{FmR$4N=+;R3=C*+35yCL-{WMgC+K$X$Cv=zBG1XW zREdbM*qyj5Qc#8ky8ZPih}^)k6oOM)fSj~}=2ev-P{&8(T|@Dgg~sDAc>}Z9Z~*eX zArsV>5h5&p9wOqJT^lb4K3d@)#yp*U7}{{@M?vnTe6v`}yu|N4A`?e54Z7)f3(viCV*~ zmGd`xfj)`kyQAlmL4B(ei!%j>_dRE2vOGXNh1llCx%LIjj{KjqwcZt6*y)_4P_q^O z$AIUg;x$Z=h>sMH2=YLl+e+5qIJ zx)s*wnNi~xo?RTsS0Q5+Jewu0ul;e!r^W24Z;3ef=)POTCqSQzLc&*4*RhYq1-yMg zj^n*Ti_UOXf{yp9IR%GavJx4WR;h8Via#&%!Cb9#B~m@Sf&Mt3cb;Dthzs_2%`ZtU zrPu;3(Io?2$LdjgJ_?U@^M~~A0(ic9$vK81XCzC-jZ8&@f3CYF)piin5f{c54A%SM z;Yx!qv3@JuGJZ#!(9Uwqu~zo9F5NmtEDyA32XYvV?IW-_R>ERv`7H_#bDz8Jo>$ELDA@$`;Wv5zgyQ#t*&V6N59hFf{)H&_4l=eXtWeEsZzi_uc`Z&mO})Ds0iLgubR} zgz{Znq4eQ`E@18jo=shS8yM$OdWb3D|}sJ_)!ykIacA#{O*0+26nf+ z{uG7hS?;Fr_X8Y`eXb~O2Xem67N{?Lqr%lL?MiS3_3XBhh*XQ4kaDHI1fsryC?T3M!aD%@?76Gh;lHp5+6n(l# zKc;)YeC;1c)7)=>{B`7{TlsZQf1EM5L!afl6@GE4e(tY(DR#c7+v5On9rKITFsI<> zdq7_}AHZQ`t9dsiFMIyA^tR##c`z|fWQdjw=AAR5s%QLh!BuX?$0|hJa!8S1VBH}v(WxobymSbM`RK(?)*Rc{(vn55&;B`r^)N8DSNk67likyJbZ>v8QXmER_ zo$Uv4GWO`nEMZgIA6Kcn9<;m93U4cNM|slAvCrK~c9+}%KQ}q*e*!ak<(;8aw%L;cV65?jC zOo5HP=K&6H@|>+%0dcX~9pP^Ra2WP3tyU?N3QrR7iRK0RB+pB@E)V(QPDgt7Dm<{l z8xP=N2iKQ7-6>G`>U zybybHc8Tj38hss&egbM8Iu0FXPYnhCy!3qM46s*r}V#S2rVHABDYFDN^T|xhfgOT|Zp3h}+zQ1>j z8V?b_%Jvl0Cn^<`Je@rhfFC;=V(b3H3P13E*sQ3a6dUQL9XIY>$NYJFpZx)HtOy>s za2V)&^Co?Zf@hJFDJ)3|ba=0(kaP{`SCc7rS@LJd6+Cjso;y}MiTId4^5SAsF?RWm z##yVe4J>$fy8H-`qk2nH#>ShK@Ks&Sm11WNhLk8U*9AYA<+eI=gN$v68pl|_@yG8- zd6K4#hqR06XM6&;TFzx1%IURmHY?fFIN4@{m-^avCBbGuKhI| zSWdg_0_D5@O8x9Q33x2C;QoIUzdw*V{<%Sd1|RP(QtV_{#4ZaSHyh6m!u>9qd5`B2 z@$Nd$H4WKvtgF{#=v?d;HfOu?rJo|lvSNR?4J#ovM*KR(J{z|O%l#W^@MxKK|Ja9M zuBbVxE5~{TKh4L-xUyn}Z(X073~VpKq=~u$Iu#q3lZC-93P0C)COWqHu@byDXOsRn zUwmQ>VsfU%_1d)?pQtWk0=Ld+9z+6hrJ7qeEt-fp-|ypG?HwhU=6D2+mFy;_vd%`g z0OY)O$?SH%$Vy;Pi4pz*^zkHZI6N_?!S@;Go^|{R`g^R=oh;c=il{+f@kS4zS+s+H26|#`);#HkWVwK z9|{-*;QPXG8HL|gxYQBXc$)4~EUAF7bm{#%#?%}vwhZL>>l8Qihp-Z!ux`q913B76 zqxC>$pbx>_WK@`p=}do&@*nfZN3R;ib!1uLdoT%_y1+8b@HR(PqvJXzKvU$j1>`(a zuB!eC)*Cxl_+C@wh=eLGUl6Cm)rhKlyg}WZpS9#-NZ%E_vhqv&)KMaS$3rjt`K}U7 z>^QUWd%jKVltM2v#qU_9`W01oRzg-hOEe{4=MB1+43O%0_ehm`w(fk8y>{?*G!OML6#?4J^vVS6(l6UAn9p69T z#!3il)@G%wS8eaGvIXp+#b>5&jiB;mtZP1i&Mho8$s@TL$gx3uPVhLe5)93z z>nQQ{?L=k3dnY>FBCPy|M@!^-b(s zyZ1B7eur&*MvCanN|?>EHS7U$8n=o~eKu&p{Q8gF1r21({pp>^!kGZPRIB|?WjqnT z{KV?fL8?-$@O<}=DEAG_FPl1f49FoeHX18{_)^n+M@``$bHB;yPJriWh7Z_#fM?eP zLE4XQ0eFU)!LSs_%k=3L{Qf7(FscBQ{E~YEYsd+GOyRKwdS-bd*f(7)j#sDfDoqQ| z?hZ~`oKQ1mA-+h)t_xm{Q&A4W^OhRK4)zgo(|D_HrdOc1nU;bpzPPl&m8b~oWsPkE0(zIVBpr!msj|S z$8%ybZ|AYLR_7Cr0EZVfiQ4&&tc06l;zUYb?mES7%`r`br%bLlebrgSqKa!9C0|{^ zS3R3q*H-?zZ$yiK z59_Sv1O2-?JMRa|?F|AA-BcciG+N;tzf|Zef))Skiw$PEh1ok3(l!3H{&e@+p4z(yV-xoeTPe{QRwAbBm0r zJo~xiaqSMRV;zKb#NA@fNeRzkgbjs!){ zz2o#u6@s+*?$1kaML}OO5rNCLt+N4mbbgF@&JmDzbRNdUo0VW!Z)G^_zP5oKJg{Ir z2;`)As0hph9P<9uZl>Vp&$Lu%`)yi$K8bYVCTek?+8Ix#<95=&_m*8EV-boCLBRrn z_>THl>_H4fJlLaGK|!nx!-pm#y^e2U4_CY2QuZl$p8nxV@Ljd-w-A~k@+c8dIZ;%X#E|CCu7?_&Z05&I z;JpT)=Zx{qNARA`am4+`?rL~XSChzowZ#_R8^$=+o#VWvf+{b|otmxsjnFQ?{(xkYLm12c5O{7MMaLgzfQzAS!HIE=MK~IHGvSg68`MXF%8TD} z2A)eFFnD_8WF^$c2|Ljh^ctSKe_q+r`_+0FttjdbvDqXc!Y9SAud70RUg)hM&e|i0 zzJ$4Ae0ep(eTePqC2e@G`U!m$ILZ&|9Obxqp66pIlap(2P8K{T$CJey4CJ6bgy6{T zvaj&oNF<}OVW%d%XKQ(>Kk)geIBG(tuX}TZggC_bJ7J@w{8E$X%pdC7FgG8F!uN1#SajF zq(Z}e!EN)~?>#<+WH1bKX(=1MWL2Qfk&KkTd+Z zdet%@9#0#-R*^g)g0_#|=G4o@&?f#1Wr;Je4uV(n+Wvc=pnU|mpYQ0s1^tz--$yH8 zvbe1eNBBU%10@_?VEK7pjPExx$2(;x|Bt^=;Vd_`lM~3}`RAfH!Mk7m`#YZ=w}$U{ z%$Mq1@63szp&@qCk|)7Cv=EP(8@7EVX^!~8o z=6O{c3y9LkqomtZU)X z))3+*cZB}p1hh}iYCCs(%p`KeF?i}|a}`o)QqDMg9oA*+Nod{FO%+8eqi4Un9S3zr zp4EZ@8}J=FUqrNY&pfn`Pl=yj<|l}spqVG@g2hmulsCi1KMMAv14+Lx?Xej~Oz&De z7q|)EaYdcIMXd_vyM-EgRGu%aMk=_!;F4=`t^UUh_j+n>L^YD+@Tr)iwyov!qLCJZNxRp8Wl|5tqlmk&u3Kr@5&r#KH6P zz4U!BK6p0N7MAXcpnD6Xs+UzU^hBNQ-O)hkuiv-+{E$Be@ne>8-t~Sm%olZvYlUu7 zkbmmat(zrmwNZ_ytn(e5zY%$XWfwb9h@Z&ri-!je%^<=F*RFjotwElQNd_(NfOw2N zeDK!l&#V1+KpjL@ zKbBxTj07hh()4hG_UTXKYSc0sMZP-KR*}_ekgBn_Mal%`3LZvi?o+^(I{xBY4myC|wY zO=I|dU7`n!YsL1WP0c&^3wbM{RAkdP`Q+Q%@#1m5h-EMxt`FZ(5j8Hw!1eo^B-#B; z^3&VvF1c$ep?!KCcc`mnK>G~7*koY22kkR`VAO733w-y| z&#Ys0;;AYcN!?nj4BkZ_HQ_DS`U&HUKU%)jSZNdy(XK4c1oM?f+-nBt55RcH@Al-Q zb`(QZ=dTLi>BrGeA~QB!kucr^pKqCb9D{g%UnTUAsu?ElSC1k<#V9Us5C!%SWaNue!NL>fvo)^=#_P= zP!%r>jkc}dUsVp{eKnPpUSks4r=)Ldc0LitJA2Mng2p)fKK(k+)C*Md=$FiM^?MCK zUk;H&^RuZi-lb2DSCov8BJEWY9QHlch(x4-gwaXiZGG(ZG&O3tMA7b=Dbd^F82WJM zE2gLa^eGD0=RPhs1@lGIQIWUhd!Rn{;r;2u@-Sbh)a?*DNGF5-d~I-V5+@->I)!H8 z|IAkkAq6QUy_-b#|NT!{y1EKE)yJzr#|MA^E|G=xzMv>N72Nt>zZcZkcb@h!PKNxW zgiKD_g+qT0YT1_^l7;>nenp-AlL+x$ajQl>mIp)2?D*7oTnF=!Q;%ldE`|AmWqp_!Th_!NcP=dx}EMY-t8XjdGpk67GcMl z!aTykJNDXu|F#-nz9_mz^VXLnj$U~**VZYakJ5NM-h9GXx9!hQ89w9A<4~W*jh?Oy zVVK{^O)sS4vmhU8fs^z%#ky#_fN1)wXC$P+h58f+GtBQ>obw#UJtK%z{>hJ)OI1ku z$ZvMm3Ao=0{_RcL+aQkql+)7eze7Oz<8KBuc)@rd8R32L)f(y}!Dmx}*g*S;9F$5R zC_(?%#HD9c?@>onvfCYVcKk-d(iOz6OTu{1aA1>KxG{lP+@3vi9=vnQ-M8;M-#R=` z2o{NLa0=x?mEAKMguIkcE>8M`Z`x9~?W0@g^=_6r5>sYYI>N>?TrK|E&6 z77x?(i=wOZ_YY5J;bO;yL}pmdOOSIC^2> zk%)Ji9@@S4L!V(AeAjB0DHamn5B-(k^RP;m0pe#uMfKFXNSMFE(v_bV_{*R~my#h@ zr(wi?bGDv#FU0er+MMb6tO=y~i0L8Po8X5?08G>J-2e(pZZ z7Zz5jd7fvXzgnjIwoT(62SSHENjb+Ueo*JLLD|L*b38D+ptKmF)^c zHNvz$aVE$NuIEV>{rgF$#8FT8kCrrG9_V`F?dDU?5YMOT)qK|zLt? zyJ?d+HP8_L-e>NeppRVAqsy`X%vTb+u=TJeX$_gtC)sRefqCEAJC#NR;XB=kn5f(P zb_%17F*oV%$!MbeJwqeY0=e7%y5XW+{d^^6o9}dIMm^@`;CW3(M3vF!9LPWQCOTrn zdo<9u2}NDStR%!k((K3lgZXWFCH||0S5{_`K1LfmjOhcib@LUqj4tG#Xy4agAf^ zxx_CGBlmI=57Adce>Iu3a}^scB9|lN(tSbS$Y_BoHgu@yH(b!SEf;G5Ah8gbp9Qn5gFr z=GOAPrRM*vEMrTQ7vV8XTbna_r&qnFwPOK$3EO< zeEH*_x~KkgivQ*3Lj0_j#Kz?ZP;Ki!e`WDO>Y6%ASiJpy7SutQ22~xMeGT!Gbc|gA z86zW_NVb~EpK9dS=jR60-jL7S=wHp6mWiPu0>_99%zEg-nCvsBtstIHGK`SQEg*gb zy;T?)<6GNYTd3)~i0FI^~;cVbK0r8{zY$Z3!1I8D@!!j#O5&pi< z-zPh82joL}lGsMcnk<@gUYkx_V;J#xHoOb(hxqBPda+yR{4^5%E{pRP=t~w`Se|R{ z1kc+nOtNz{Kwr(I81?+#P6FyIvv#*v9NK60;*GdOBE&NTbBdscKeW&2kw0xFIdDIC z@eFA7*rSei>(tJ9#)AFwgxW#Yk8r(Fa9HZ3Vec%`_#mdIQnCg)B5}`(=_2vA@QZomzun~=sxK-LhQAjdEOkM?7ph&A;p-Md zC+QP2hF|HSgAo%wXO2QV|0z{Vs+EEG`Mp*v?&%HVA+F_YkU%r!`{x-2B^984Q$3>V zocF>olIJ@~7jOvXceUNw1b@(XBA5qU|39ul%&EhcxZXl}`)RJvSg(tty8h)^Eff0a z-A12Q+JEL<=9;-)KTi+q)Gj(2=D%iv@t)c;Vnek>z5V@yJ623uj1QxEXA{Om0DeSw z@H6t&!TjE=*D~@9%mY0aXRE1FRE?a?>awh7k>39Oiu8iv2QZIsv75e8;4J|idYnGa zCj;^GDo?m!TpHqMl;5CvObFuVY*#CzT`uIGH3s{%-7ZR~%dJ)^DoqmNq?Ptumkq{O zCh@I|&DI>U)A`+!krc@9rwe$Ks-XYh-ltpqp(>8zk>$}HU3g0Brnt`X;n@uF7?C!-_hd+2GBZUf3zJ zjUTR(6QAO{#nC^-%W@8)2I!l$CpH#2aJ@SJrzihyD~vBDI;qrOCn26s%H8AZZiIM# zoNmjUW2uUE)-*absthBK7j93!djRwM+benOdJ;=W#6`LuoxEy9)lz)MThX2eT&o4O_h)hC%)$i$cR{v+7F5jXGm;R+o3JsDXxz+Owg-F|k(V2glL`57u+Hv{tAa2(CmPPu zKCFexiSY(SG3IaEkM6kwy@z1j_WCgDlfkLcIk>*4xZLzz$Pn(AHD%UgTU}+)0p2Hr z-yMdK8@lS_kxq~g`OdhDIrq#Uw+G0xi-RAKr|*xyBJ6|fU9lSjO+;OBl--moPe2Rw zRq#wE*B3%OuNK%}qYs4kNeDU|?{ypEd5Ntdx|0dse|pvB+#!8YLBI3ak1+iiLM|~B zhdg42`Jz9%t%b{e7O`G0p6}B6fK&}BUy^$Z^Lvch`R_(q;%Fdo{CZ5UK6*+u=x<>t z#E-Stj`GGoaR0R@$cd%=2>d;+_*Jah1nvjv1jNzzCuGrS{yRh1C<*y9L@+5n1o@Er zjG=jz`aEK@Fs+l2R)yHghw@5G!uaTRHQj3(B!==|Svpo1LO=yr?;9@(!uIhv;F z2-GK=pGPO+8_e$lKM(Q=HNf-v9I8hF0lu>63*Vn(gAF8PEontuTMXK#bk;wc`}Y*m zn19?`81!lMw~xKSUy~G^K z8@k%*>Jo4OeSGDOt38-!J>Z+`uW%QxpP9(Ya{C4$|9EBFGEWCV{^=MtEB;yl^*I%g z|A(X@kJ|a~^Lr!()<1gA%DML-e#A$H+{7{#5H^2)$prA;h9&vsT=oNaK2p*9c}g`_ z93AtezI5q_9_p%9c~_4J`PtiFjOh9c+J`8p`||!Sh`(6rwPw;CXzv_%aie|ois)6; zt!3>32^sLenbQ8xd4kNZn&Q9K(}?`)qwD9DzA|d{) z#`(o!kPrDX4+__oEg&V0@l20E-}SYRZ)&67Ks^4mI8@@mFNpG7J*Dz7R|`#QVPaMa zg?y;7S)~*q4f*h}fy?=Y4akQDyG|OMzO%T^m+^(CzV23+MkSSPPW-w(gruBSRwFbn zZ}aPgNoLW&)4*T$p&W-gs*ur(w8#I`fbkXnvg&+^2CpodDzkQwyvLOefj zRzJb=&v|cN+9+2e4fIzT!O8I29M$&Uo7!B}tntx6>+FvA#qyAlsez!@V|36ylU}El zsy!BwLxODX*NmzV(W<%AN3Oy10Muf7SNX~PsPtIa9r}F+D6@_xF>@I5VGq?JPl-Ro zvts5j$G{5YLq$oZYN9XHM}TM2JFQk8^_lkH@Bw|~)Ms|QcloCd-EqPqyNG`d=}{o; z^ct*2s<>?`4YHyCHTK!Giz^nrLDuObTLi|(G<*uqoD&;La>cva9q`|zIsQ*&(% z#;bp8`0aJs!>Ik#Ti;UZVZ>qMv;VpfjCYo^X9^$r&mj|aEce`yYGm}B!4byj|Ep`J L)HDBoUGx6{a^}}A literal 162320 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 Date: Wed, 16 May 2018 20:36:45 -0400 Subject: [PATCH 32/76] 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 906845cb93..911e53c281 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 9da5e36daf5d9a6e489794c625d62283fc27d330 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Mon, 21 May 2018 16:32:44 -0400 Subject: [PATCH 33/76] 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 6d7e598eba..1d630b3d4f 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 fff8b749c3..2b03e1cd0a 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 d053625877..c3b647a822 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 911e53c281..0685542409 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 0000000000..0a0500794b --- /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 012e976afe..fc70a8eeca 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 44a161f470..c68ed4a8c0 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 024e4cb483..7bb5fb6bb7 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 938294ae4d..9b55025b58 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 964ec34a35..1d33491230 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 d804fe0ea0..ce2549f58a 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 34/76] 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 ee445b517f..6dfbc95106 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 35/76] 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 c9c76552bc..7c6fc48673 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 36/76] 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 38b7f21670..ce28fa2d04 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 5c21c3ef4b..4f5b46915a 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 da50007d68..1740fa2abd 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 00ab8fa15b..99a9892a77 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 37/76] 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 ce28fa2d04..a9ffa74d75 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 38/76] 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 4f5b46915a..611efb7fda 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 a21e858fda..c353d8317f 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 39/76] 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 c353d8317f..11a47b1506 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 40/76] 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 c68ed4a8c0..b708974b86 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 ebabe5cdbc..17880f6e1b 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 8fe5e2da1f..806bcc65e8 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 41/76] 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 c3b647a822..7fdd03ad8b 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 0685542409..6efe1cf99e 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 0a0500794b..877f51e4e0 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 b708974b86..d325928a66 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 42/76] 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 1d630b3d4f..82138a83f7 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 dbd4181dae..d75ee4e54a 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 7fdd03ad8b..44f138e104 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 6efe1cf99e..2bf379a6d4 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 fc70a8eeca..d019e8fce6 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 d325928a66..501a43a57d 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 1d33491230..3b01ddfc9d 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 ce2549f58a..fa0a6b2963 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 43/76] 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 a9ffa74d75..2c0f10eae0 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 611efb7fda..391e63f073 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 11a47b1506..af013cbb5b 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 44/76] 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 7c6fc48673..3f53582d5e 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 45/76] 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 0000000000..6091dd1cdb --- /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 46/76] 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 6091dd1cdb..563e51c3b7 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 47/76] 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 563e51c3b7..4d79fc579d 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 48/76] 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 a5341cbedd..74f3429034 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 49/76] 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 fa0a6b2963..e121cae5e7 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 af4525a1ebf3058172739e81c739089e2d40be43 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Fri, 25 May 2018 20:32:17 -0400 Subject: [PATCH 50/76] 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 2bf379a6d4..a51e5b8a80 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 877f51e4e0..7d2c3cd9af 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 d019e8fce6..8f95c4a7d1 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 501a43a57d..96cb2f7548 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 7bb5fb6bb7..f5d0ba272d 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 9b55025b58..3161a24739 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 3b01ddfc9d..464c0c6cfa 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 e121cae5e7..d6643190cd 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 51/76] 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 a51e5b8a80..e79a00264b 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 52/76] 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 44f138e104..457fdf3786 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 f5d0ba272d..0e20d20791 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 d6643190cd..c9564083da 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 53/76] 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 89d5673943..ccf6bf5217 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 82138a83f7..cdbb446172 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 2b03e1cd0a..f55c8e6fd4 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 8f95c4a7d1..d633b67005 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 232d2f6e7d..353d0137af 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 96cb2f7548..e24f3fe253 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 0e20d20791..2d9bf02c58 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 3161a24739..909029aa90 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 464c0c6cfa..02352db7cd 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 7dea2b7df0..1155559fd3 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 54/76] 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 cdbb446172..8cdc7b6f64 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 e79a00264b..dc31edf7e4 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 7d2c3cd9af..b998ad32c9 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 d633b67005..758fc54da7 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 6dfbc95106..ee445b517f 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 51911bacb43297f3230e43639787b6375d7df359 Mon Sep 17 00:00:00 2001 From: guillaume Date: Mon, 28 May 2018 18:36:43 -0400 Subject: [PATCH 55/76] 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 8c12211bca..a17bf5fd91 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 58e4b66dc1..a12b16ab4f 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 cfa3eba3c3..dac2fe14af 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 bd6ef7158d..de3d20a175 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 56/76] 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 0000000000..6f30c68ea6 --- /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 57/76] 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 6f30c68ea6..928c45a289 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 a17bf5fd91..03e25fc0ea 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 9be992c16a..d3b38aa3a7 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 b40ff63447..ebd025f940 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 de3d20a175..45c92062d5 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 58/76] 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 d3b38aa3a7..7bc6b95463 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 ebd025f940..44c57f1607 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 59/76] 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 928c45a289..7bb365fe4c 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 7bc6b95463..f55393b541 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 44c57f1607..49474a925f 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 60/76] 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 f55393b541..f672707f59 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 49474a925f..1c0d0e492a 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 61/76] 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 7bb365fe4c..e37174ca8a 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 f672707f59..a0ef56dcfc 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 62/76] 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 1c0d0e492a..a15cd35fa3 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 63/76] 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 a15cd35fa3..0af5bed0b9 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 64/76] 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 a12b16ab4f..c4b0a41bf1 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 65/76] 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 e37174ca8a..65f73a3da1 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 a0ef56dcfc..ed6db4efbd 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 ab740e61ec..afdfffb74b 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 66/76] 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 65f73a3da1..af7d4c7272 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 61b58d0b95..b2c766e99b 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 05fe97a950..867d30dd63 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 a1768625b2..c19f1c5f69 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 67/76] 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 68/76] 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 ed6db4efbd..ea40d25c7e 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 69/76] 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 af7d4c7272..d96fe156cd 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 ea40d25c7e..ea9880294e 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 0af5bed0b9..71031be022 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 afdfffb74b..1d0dc3a0d5 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 c4b0a41bf1..58e4b66dc1 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 a200e900e1..03f53bbb0a 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 70/76] 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 d96fe156cd..a99565235f 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 03f53bbb0a..fcc076b940 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 71/76] 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 a99565235f..cfca2adf0d 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 03e25fc0ea..6600bec06f 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 b2c766e99b..0ab4302268 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 ea9880294e..4711b7f851 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 71031be022..9ffe740636 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 fcc076b940..a200e900e1 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 867d30dd63..fd230635d5 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 72/76] 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 6600bec06f..03e25fc0ea 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 0ab4302268..a80f7ee265 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 4711b7f851..6918b222e9 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 9ffe740636..24e8843ab0 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 1d0dc3a0d5..f31f5672f4 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 fd230635d5..c66070ad40 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 50803e5085..b39cc7dcac 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 73/76] 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 0000000000..745f2cce5b --- /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 0000000000..9128ed9694 --- /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 74/76] 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 cfca2adf0d..9ed20485c5 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 03e25fc0ea..0c58b27d26 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 a80f7ee265..be274bf8f4 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 6918b222e9..980eb0cde2 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 f31f5672f4..259ba6f64f 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 75/76] 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 9ed20485c5..f9324a8cb8 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 0c58b27d26..a2a5e0e7f2 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 be274bf8f4..0b17b49b57 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 980eb0cde2..a450178134 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 24e8843ab0..a0b2417c8e 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 fddb88b19d..cea2f19976 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 259ba6f64f..6170a44643 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 c19f1c5f69..8cade45b14 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 4b403c6b7bb3decfee10d77bb16c20f744cc81b5 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Mon, 11 Jun 2018 15:00:18 -0400 Subject: [PATCH 76/76] 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 8cdc7b6f64..e4388e7012 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 f55c8e6fd4..a2f72c8337 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 dc31edf7e4..3f36cf299a 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 2d9bf02c58..959ab932e4 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 909029aa90..19749ce4f0 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 0000000000..e2cb773ab1 --- /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 1155559fd3..56e57a854f 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 99c51d7473..1d8cc6ac7b 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 77859ede8c..b4fcc524c6 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 41252ed611..b6f9e3246d 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