From 122f0f5e54a04c20b0fe4b53577d302c77819b6c Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Wed, 23 Jan 2019 14:23:03 -0500 Subject: [PATCH 01/58] Create C++ Tally object with filter indices --- include/openmc/tallies/filter.h | 4 -- include/openmc/tallies/tally.h | 22 +++++++++ src/output.F90 | 25 +++++----- src/state_point.F90 | 8 ++-- src/tallies/filter.cpp | 15 ------ src/tallies/tally.F90 | 49 ++++++++++++-------- src/tallies/tally.cpp | 62 +++++++++++++++++++++++++ src/tallies/tally_header.F90 | 82 +++++++++++++++++++++------------ src/tallies/trigger.F90 | 18 ++++---- 9 files changed, 193 insertions(+), 92 deletions(-) diff --git a/include/openmc/tallies/filter.h b/include/openmc/tallies/filter.h index fd3be0aafe..fcdca110da 100644 --- a/include/openmc/tallies/filter.h +++ b/include/openmc/tallies/filter.h @@ -93,10 +93,6 @@ extern std::vector> tally_filters; //============================================================================== -extern "C" void free_memory_tally_c(); - -//============================================================================== - // Filter-related Fortran functions that will be called from C++ extern "C" int verify_filter(int32_t index); extern "C" Filter* filter_from_f(int32_t index); diff --git a/include/openmc/tallies/tally.h b/include/openmc/tallies/tally.h index 05b5d141f9..6fc60dc730 100644 --- a/include/openmc/tallies/tally.h +++ b/include/openmc/tallies/tally.h @@ -3,16 +3,36 @@ #include "openmc/constants.h" +#include "pugixml.hpp" #include "xtensor/xtensor.hpp" +#include // for unique_ptr +#include +#include + namespace openmc { +//============================================================================== +//! A user-specified flux-weighted (or current) measurement. +//============================================================================== + +class Tally { +public: + Tally() {} + + std::vector filters_; +}; + //============================================================================== // Global variable declarations //============================================================================== extern "C" double total_weight; +namespace model { + extern std::vector> tallies; +} + // Threadprivate variables extern "C" double global_tally_absorption; extern "C" double global_tally_collision; @@ -44,6 +64,8 @@ adaptor_type<3> tally_results(int idx); extern "C" void reduce_tally_results(); #endif +extern "C" void free_memory_tally_c(); + } // namespace openmc #endif // OPENMC_TALLIES_TALLY_H diff --git a/src/output.F90 b/src/output.F90 index 94a73eacc8..6efd2c6e65 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -459,6 +459,7 @@ contains integer :: filter_index ! index in results array for filters integer :: score_index ! scoring bin index integer :: i_nuclide ! index in nuclides array + integer :: i_filt integer :: unit_tally ! tallies.out file unit integer :: nr ! number of realizations real(8) :: t_value ! t-values for confidence intervals @@ -557,7 +558,7 @@ contains ! to be used for a given tally. ! Initialize bins, filter level, and indentation - do h = 1, size(t % filter) + do h = 1, t % n_filters() filter_bins(t % filter(h)) = 0 end do j = 1 @@ -566,7 +567,7 @@ contains print_bin: do find_bin: do ! Check for no filters - if (size(t % filter) == 0) exit find_bin + if (t % n_filters() == 0) exit find_bin ! Increment bin combination filter_bins(t % filter(j)) = filter_bins(t % filter(j)) + 1 @@ -588,12 +589,13 @@ contains else ! Check if this is last filter - if (j == size(t % filter)) exit find_bin + if (j == t % n_filters()) exit find_bin ! Print current filter information + i_filt = t % filter(j) write(UNIT=unit_tally, FMT='(1X,2A)') repeat(" ", indent), & - trim(filters(t % filter(j)) % obj % & - text_label(filter_bins(t % filter(j)))) + trim(filters(i_filt) % obj % & + text_label(filter_bins(i_filt))) indent = indent + 2 j = j + 1 end if @@ -601,10 +603,11 @@ contains end do find_bin ! Print filter information - if (size(t % filter) > 0) then + if (t % n_filters() > 0) then + i_filt = t % filter(j) write(UNIT=unit_tally, FMT='(1X,2A)') repeat(" ", indent), & - trim(filters(t % filter(j)) % obj % & - text_label(filter_bins(t % filter(j)))) + trim(filters(i_filt) % obj % & + text_label(filter_bins(i_filt))) end if ! Determine scoring index for this bin combination -- note that unlike @@ -612,14 +615,14 @@ contains ! bins below the lowest filter level will be zeros filter_index = 1 - do h = 1, size(t % filter) + do h = 1, t % n_filters() filter_index = filter_index & + (max(filter_bins(t % filter(h)) ,1) - 1) * t % stride(h) end do ! Write results for this filter bin combination score_index = 0 - if (size(t % filter) > 0) indent = indent + 2 + if (t % n_filters() > 0) indent = indent + 2 do n = 1, t % n_nuclide_bins ! Write label for nuclide i_nuclide = t % nuclide_bins(n) @@ -659,7 +662,7 @@ contains end do indent = indent - 2 - if (size(t % filter) == 0) exit print_bin + if (t % n_filters() == 0) exit print_bin end do print_bin diff --git a/src/state_point.F90 b/src/state_point.F90 index d69217d8b7..fba79dd843 100644 --- a/src/state_point.F90 +++ b/src/state_point.F90 @@ -263,11 +263,11 @@ contains call write_dataset(tally_group, "n_realizations", & tally % n_realizations) - call write_dataset(tally_group, "n_filters", size(tally % filter)) - if (size(tally % filter) > 0) then + call write_dataset(tally_group, "n_filters", tally % n_filters()) + if (tally % n_filters() > 0) then ! Write IDs of filters - allocate(id_array(size(tally % filter))) - do j = 1, size(tally % filter) + allocate(id_array(tally % n_filters())) + do j = 1, tally % n_filters() id_array(j) = filters(tally % filter(j)) % obj % id end do call write_dataset(tally_group, "filters", id_array) diff --git a/src/tallies/filter.cpp b/src/tallies/filter.cpp index fd5e38f7ec..c5edb2f864 100644 --- a/src/tallies/filter.cpp +++ b/src/tallies/filter.cpp @@ -43,21 +43,6 @@ namespace model { std::vector> tally_filters; } // namespace model -//============================================================================== -// Non-member functions -//============================================================================== - -void -free_memory_tally_c() -{ - #pragma omp parallel - { - simulation::filter_matches.clear(); - } - - model::tally_filters.clear(); -} - //============================================================================== // Fortran compatibility functions //============================================================================== diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index bca3420e98..50602c15d5 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -2068,7 +2068,7 @@ contains ! Find all valid bins in each filter if they have not already been found ! for a previous tally. - do j = 1, size(t % filter) + do j = 1, t % n_filters() i_filt = t % filter(j) if (.not. filter_matches(i_filt) % bins_present) then call filter_matches(i_filt) % bins_clear() @@ -2095,7 +2095,7 @@ contains filter_weight = ONE ! Determine scoring index and weight for this filter combination - do j = 1, size(t % filter) + do j = 1, t % n_filters() i_filt = t % filter(j) i_bin = filter_matches(i_filt) % i_bin filter_index = filter_index + (filter_matches(i_filt) & @@ -2155,7 +2155,7 @@ contains ! Increment the filter bins, starting with the last filter to find the ! next valid bin combination finished = .true. - do j = size(t % filter), 1, -1 + do j = t % n_filters(), 1, -1 i_filt = t % filter(j) if (filter_matches(i_filt) % i_bin < filter_matches(i_filt) % & bins_size()) then @@ -2214,7 +2214,7 @@ contains ! Find all valid bins in each filter if they have not already been found ! for a previous tally. - do j = 1, size(t % filter) + do j = 1, t % n_filters() i_filt = t % filter(j) if (.not. filter_matches(i_filt) % bins_present) then call filter_matches(i_filt) % bins_clear() @@ -2241,7 +2241,7 @@ contains filter_weight = ONE ! Determine scoring index and weight for this filter combination - do j = 1, size(t % filter) + do j = 1, t % n_filters() i_filt = t % filter(j) i_bin = filter_matches(i_filt) % i_bin filter_index = filter_index + (filter_matches(i_filt) & @@ -2284,7 +2284,7 @@ contains ! Increment the filter bins, starting with the last filter to find the ! next valid bin combination finished = .true. - do j = size(t % filter), 1, -1 + do j = t % n_filters(), 1, -1 i_filt = t % filter(j) if (filter_matches(i_filt) % i_bin < filter_matches(i_filt) % & bins_size()) then @@ -2414,7 +2414,7 @@ contains ! determine scoring index and weight for this filter combination i_filter = 1 - do l = 1, size(t % filter) + do l = 1, t % n_filters() i_filter = i_filter + (filter_matches(t % filter(l)) & % bins_data(filter_matches(t % filter(l)) % i_bin) - 1) * & t % stride(l) @@ -2454,7 +2454,7 @@ contains ! determine scoring index and weight for this filter ! combination - do l = 1, size(t % filter) + do l = 1, t % n_filters() f = t % filter(l) b = filter_matches(f) % i_bin i_filter = i_filter + (filter_matches(f) & @@ -2477,7 +2477,7 @@ contains filter_weight = ONE ! determine scoring index and weight for this filter combination - do l = 1, size(t % filter) + do l = 1, t % n_filters() f = t % filter(l) b = filter_matches(f) % i_bin i_filter = i_filter + (filter_matches(f) % bins_data(b) - 1) & @@ -2526,7 +2526,7 @@ contains ! determine scoring index and weight on the modified matching bins filter_index = 1 - do i = 1, size(t % filter) + do i = 1, t % n_filters() filter_index = filter_index + (filter_matches(t % filter(i)) % & bins_data(filter_matches(t % filter(i)) % i_bin) - 1) * t % stride(i) end do @@ -2579,7 +2579,7 @@ contains ! Find all valid bins in each filter if they have not already been found ! for a previous tally. - do j = 1, size(t % filter) + do j = 1, t % n_filters() i_filt = t % filter(j) if (.not. filter_matches(i_filt) % bins_present) then call filter_matches(i_filt) % bins_clear() @@ -2606,7 +2606,7 @@ contains filter_weight = ONE ! Determine scoring index and weight for this filter combination - do j = 1, size(t % filter) + do j = 1, t % n_filters() i_filt = t % filter(j) i_bin = filter_matches(i_filt) % i_bin filter_index = filter_index + (filter_matches(i_filt) & @@ -2658,7 +2658,7 @@ contains ! Increment the filter bins, starting with the last filter to find the ! next valid bin combination finished = .true. - do j = size(t % filter), 1, -1 + do j = t % n_filters(), 1, -1 i_filt = t % filter(j) if (filter_matches(i_filt) % i_bin < filter_matches(i_filt) % & bins_size()) then @@ -2736,7 +2736,7 @@ contains ! Find all valid bins in each filter if they have not already been found ! for a previous tally. - do j = 1, size(t % filter) + do j = 1, t % n_filters() i_filt = t % filter(j) if (.not. filter_matches(i_filt) % bins_present) then call filter_matches(i_filt) % bins_clear() @@ -2763,7 +2763,7 @@ contains filter_weight = ONE ! Determine scoring index and weight for this filter combination - do j = 1, size(t % filter) + do j = 1, t % n_filters() i_filt = t % filter(j) i_bin = filter_matches(i_filt) % i_bin filter_index = filter_index + (filter_matches(i_filt) & @@ -2815,7 +2815,7 @@ contains ! Increment the filter bins, starting with the last filter to find the ! next valid bin combination finished = .true. - do j = size(t % filter), 1, -1 + do j = t % n_filters(), 1, -1 i_filt = t % filter(j) if (filter_matches(i_filt) % i_bin < filter_matches(i_filt) % & bins_size()) then @@ -2882,7 +2882,7 @@ contains ! Find all valid bins in each filter if they have not already been found ! for a previous tally. - do j = 1, size(t % filter) + do j = 1, t % n_filters() i_filt = t % filter(j) if (.not. filter_matches(i_filt) % bins_present) then call filter_matches(i_filt) % bins_clear() @@ -2909,7 +2909,7 @@ contains filter_weight = ONE ! Determine scoring index and weight for this filter combination - do j = 1, size(t % filter) + do j = 1, t % n_filters() i_filt = t % filter(j) i_bin = filter_matches(i_filt) % i_bin filter_index = filter_index + (filter_matches(i_filt) & @@ -2945,7 +2945,7 @@ contains ! Increment the filter bins, starting with the last filter to find the ! next valid bin combination finished = .true. - do j = size(t % filter), 1, -1 + do j = t % n_filters(), 1, -1 i_filt = t % filter(j) if (filter_matches(i_filt) % i_bin < filter_matches(i_filt) % & bins_size()) then @@ -3836,6 +3836,14 @@ contains integer(C_INT32_T) :: empty(0) character(:), allocatable :: type_ + interface + function tally_pointer(indx) bind(C) result(ptr) + import C_INT, C_PTR + integer(C_INT), value :: indx + type(C_PTR) :: ptr + end function + end interface + ! Convert C string to Fortran string type_ = to_f_string(type) @@ -3853,6 +3861,9 @@ contains call set_errmsg("Unknown tally type: " // trim(type_)) end select + ! Assign the pointer to the C++ tally + tallies(index) % obj % ptr = tally_pointer(index - 1) + ! When a tally is allocated, set it to have 0 filters err = tallies(index) % obj % set_filters(empty) end if diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 7f64832ec3..c8f1b38906 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -2,6 +2,8 @@ #include "openmc/capi.h" #include "openmc/constants.h" +#include "openmc/error.h" +#include "openmc/tallies/filter.h" #include "openmc/message_passing.h" #include "xtensor/xadapt.hpp" @@ -17,6 +19,10 @@ namespace openmc { // Global variable definitions //============================================================================== +namespace model { + std::vector> tallies; +} + double global_tally_absorption; double global_tally_collision; double global_tally_tracklength; @@ -108,4 +114,60 @@ void reduce_tally_results() } #endif +extern "C" void +free_memory_tally_c() +{ + #pragma omp parallel + { + simulation::filter_matches.clear(); + } + + model::tally_filters.clear(); + + model::tallies.clear(); +} + +//============================================================================== +// C-API functions +//============================================================================== + +extern "C" int +openmc_tally_get_filters(int32_t index, int32_t** indices, int* n) +{ + if (index < 1 || index > model::tallies.size()) { + set_errmsg("Index in tallies array is out of bounds."); + return OPENMC_E_OUT_OF_BOUNDS; + } + + *indices = model::tallies[index-1]->filters_.data(); + *n = model::tallies[index-1]->filters_.size(); + return 0; +} + +//============================================================================== +// Fortran compatibility functions +//============================================================================== + +extern "C" { + Tally* tally_pointer(int indx) {return model::tallies[indx].get();} + + void + extend_tallies_c(int n) + { + for (int i = 0; i < n; ++i) + model::tallies.push_back(std::make_unique()); + } + + void + tally_set_filters_c(Tally* tally, int n, int32_t filter_indices[]) + { + tally->filters_.clear(); + tally->filters_.assign(filter_indices, filter_indices + n); + } + + int tally_get_n_filters_c(Tally* tally) {return tally->filters_.size();} + + int32_t tally_get_filter_c(Tally* tally, int i) {return tally->filters_[i];} +} + } // namespace openmc diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index f2339d85d5..9a82f94d8b 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -26,7 +26,6 @@ module tally_header public :: openmc_tally_get_active public :: openmc_tally_get_estimator public :: openmc_tally_get_id - public :: openmc_tally_get_filters public :: openmc_tally_get_n_realizations public :: openmc_tally_get_nuclides public :: openmc_tally_get_scores @@ -48,6 +47,8 @@ module tally_header !=============================================================================== type, public :: TallyObject + type(C_PTR) :: ptr + ! Basic data integer :: id ! user-defined identifier @@ -57,7 +58,6 @@ module tally_header real(8) :: volume ! volume of region logical :: active = .false. logical :: depletion_rx = .false. ! has depletion reactions, e.g. (n,2n) - integer, allocatable :: filter(:) ! index in filters array ! The stride attribute is used for determining the index in the results ! array for a matching_bin combination. Since multiple dimensions are @@ -106,6 +106,8 @@ module tally_header procedure :: read_results_hdf5 => tally_read_results_hdf5 procedure :: write_results_hdf5 => tally_write_results_hdf5 procedure :: set_filters => tally_set_filters + procedure :: n_filters => tally_get_n_filters + procedure :: filter => tally_get_filter end type TallyObject type, public :: TallyContainer @@ -291,6 +293,15 @@ contains integer :: n ! number of filters integer :: stride ! filter stride + interface + subroutine tally_set_filters_c(ptr, n, filter_indices) bind(C) + import C_PTR, C_INT, C_INT32_T + type(C_PTR), value :: ptr + integer(C_INT), value :: n + integer(C_INT32_T) :: filter_indices(n) + end subroutine + end interface + err = 0 this % find_filter(:) = 0 n = size(filter_indices) @@ -362,9 +373,10 @@ contains end do if (err == 0) then - if (allocated(this % filter)) deallocate(this % filter) + call tally_set_filters_c(this % ptr, n, filter_indices) + if (allocated(this % stride)) deallocate(this % stride) - allocate(this % filter(n), this % stride(n)) + allocate(this % stride(n)) ! Filters are traversed in reverse so that the last filter has the ! shortest stride in memory and the first filter has the largest stride @@ -372,7 +384,6 @@ contains do i = n, 1, -1 ! Set filter and stride k = filter_indices(i) - this % filter(i) = k this % stride(i) = stride ! Multiply stride by number of bins in this filter @@ -385,6 +396,34 @@ contains end function tally_set_filters + function tally_get_n_filters(this) result(n) + class(TallyObject) :: this + integer(C_INT) :: n + interface + function tally_get_n_filters_c(tally) result(n) bind(C) + import C_PTR, C_INT, C_INT32_T + type(C_PTR), value :: tally + integer(C_INT) :: n + end function + end interface + n = tally_get_n_filters_c(this % ptr) + end function + + function tally_get_filter(this, i) result(filt) + class(TallyObject) :: this + integer(C_INT) :: i + integer(C_INT32_T) :: filt + interface + function tally_get_filter_c(tally, i) result(filt) bind(C) + import C_PTR, C_INT, C_INT32_T + type(C_PTR), value :: tally + integer(C_INT), value :: i + integer(C_INT32_T) :: filt + end function + end interface + filt = tally_get_filter_c(this % ptr, i-1) + end function + !=============================================================================== ! CONFIGURE_TALLIES initializes several data structures related to tallies. This ! is called after the basic tally data has already been read from the @@ -449,6 +488,14 @@ contains integer :: i type(TallyContainer), allocatable :: temp(:) ! temporary tallies array + interface + subroutine extend_tallies_c() bind(C) + end subroutine + end interface + + ! Extend the C++ tallies array first + call extend_tallies_c() + if (n_tallies == 0) then ! Allocate tallies array allocate(tallies(n)) @@ -557,31 +604,6 @@ contains end function openmc_tally_get_id - function openmc_tally_get_filters(index, filter_indices, n) result(err) bind(C) - ! Return the list of filters assigned to a tally - integer(C_INT32_T), value :: index - type(C_PTR), intent(out) :: filter_indices - integer(C_INT), intent(out) :: n - integer(C_INT) :: err - - if (index >= 1 .and. index <= size(tallies)) then - associate (t => tallies(index) % obj) - if (allocated(t % filter)) then - filter_indices = C_LOC(t % filter(1)) - n = size(t % filter) - err = 0 - else - err = E_ALLOCATE - call set_errmsg("Tally filters have not been allocated yet.") - end if - end associate - else - err = E_OUT_OF_BOUNDS - call set_errmsg('Index in tallies array is out of bounds.') - end if - end function openmc_tally_get_filters - - function openmc_tally_get_n_realizations(index, n) result(err) bind(C) ! Return the number of realizations for a tally integer(C_INT32_T), value :: index diff --git a/src/tallies/trigger.F90 b/src/tallies/trigger.F90 index f5efd04378..c2a917b33e 100644 --- a/src/tallies/trigger.F90 +++ b/src/tallies/trigger.F90 @@ -172,7 +172,7 @@ contains else ! Initialize bins, filter level - do j = 1, size(t % filter) + do j = 1, t % n_filters() call filter_matches(t % filter(j)) % bins_clear() call filter_matches(t % filter(j)) % bins_push_back(0) end do @@ -223,7 +223,7 @@ contains end if end if end do NUCLIDE_LOOP - if (size(t % filter) == 0) exit FILTER_LOOP + if (t % n_filters() == 0) exit FILTER_LOOP end do FILTER_LOOP end if end associate @@ -268,7 +268,7 @@ contains end select ! initialize bins array - do j = 1, size(t % filter) + do j = 1, t % n_filters() call filter_matches(t % filter(j)) % bins_clear() call filter_matches(t % filter(j)) % bins_push_back(1) end do @@ -307,7 +307,7 @@ contains ! Left Surface call filter_matches(i_filter_surf) % bins_set_data(1, OUT_LEFT) filter_index = 1 - do j = 1, size(t % filter) + do j = 1, t % n_filters() filter_index = filter_index + (filter_matches(t % filter(j)) % & bins_data(1) - 1) * t % stride(j) end do @@ -323,7 +323,7 @@ contains ! Right Surface call filter_matches(i_filter_surf) % bins_set_data(1, OUT_RIGHT) filter_index = 1 - do j = 1, size(t % filter) + do j = 1, t % n_filters() filter_index = filter_index + (filter_matches(t % filter(j)) % & bins_data(1) - 1) * t % stride(j) end do @@ -339,7 +339,7 @@ contains ! Back Surface call filter_matches(i_filter_surf) % bins_set_data(1, OUT_BACK) filter_index = 1 - do j = 1, size(t % filter) + do j = 1, t % n_filters() filter_index = filter_index + (filter_matches(t % filter(j)) % & bins_data(1) - 1) * t % stride(j) end do @@ -355,7 +355,7 @@ contains ! Front Surface call filter_matches(i_filter_surf) % bins_set_data(1, OUT_FRONT) filter_index = 1 - do j = 1, size(t % filter) + do j = 1, t % n_filters() filter_index = filter_index + (filter_matches(t % filter(j)) % & bins_data(1) - 1) * t % stride(j) end do @@ -371,7 +371,7 @@ contains ! Bottom Surface call filter_matches(i_filter_surf) % bins_set_data(1, OUT_BOTTOM) filter_index = 1 - do j = 1, size(t % filter) + do j = 1, t % n_filters() filter_index = filter_index + (filter_matches(t % filter(j)) % & bins_data(1) - 1) * t % stride(j) end do @@ -387,7 +387,7 @@ contains ! Top Surface call filter_matches(i_filter_surf) % bins_set_data(1, OUT_TOP) filter_index = 1 - do j = 1, size(t % filter) + do j = 1, t % n_filters() filter_index = filter_index + (filter_matches(t % filter(j)) % & bins_data(1) - 1) * t % stride(j) end do From 5e03599f32a552e9aea3372034c9f03875061bc6 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Wed, 23 Jan 2019 23:41:21 -0500 Subject: [PATCH 02/58] Move tally filter strides to C++ --- include/openmc/capi.h | 2 +- include/openmc/tallies/tally.h | 18 ++++++++- src/tallies/tally.cpp | 70 ++++++++++++++++++++++++++++------ src/tallies/tally_header.F90 | 62 ++++++++++++++++-------------- src/tallies/trigger.F90 | 2 +- 5 files changed, 111 insertions(+), 43 deletions(-) diff --git a/include/openmc/capi.h b/include/openmc/capi.h index aad72a1386..6101ef1adb 100644 --- a/include/openmc/capi.h +++ b/include/openmc/capi.h @@ -104,7 +104,7 @@ extern "C" { int openmc_tally_get_active(int32_t index, bool* active); int openmc_tally_get_estimator(int32_t index, int32_t* estimator); int openmc_tally_get_id(int32_t index, int32_t* id); - int openmc_tally_get_filters(int32_t index, int32_t** indices, int* n); + int openmc_tally_get_filters(int32_t index, const int32_t** indices, int* n); int openmc_tally_get_n_realizations(int32_t index, int32_t* n); int openmc_tally_get_nuclides(int32_t index, int** nuclides, int* n); int openmc_tally_get_scores(int32_t index, int** scores, int* n); diff --git a/include/openmc/tallies/tally.h b/include/openmc/tallies/tally.h index 6fc60dc730..0f68afe3fb 100644 --- a/include/openmc/tallies/tally.h +++ b/include/openmc/tallies/tally.h @@ -20,7 +20,23 @@ class Tally { public: Tally() {} - std::vector filters_; + const std::vector& filters() {return filters_;} + + int32_t filters(int i) const {return filters_[i];} + + void set_filters(const int32_t filter_indices[], int n); + + int32_t strides(int i) const {return strides_[i];} + + int32_t n_filter_bins() const {return n_filter_bins_;} + +private: + std::vector filters_; //< Filter indices in global filters array + + //! Index strides assigned to each filter to support 1D indexing. + std::vector strides_; + + int32_t n_filter_bins_ {0}; }; //============================================================================== diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index c8f1b38906..1d816bdac6 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -28,6 +28,32 @@ double global_tally_collision; double global_tally_tracklength; double global_tally_leakage; +//==============================================================================// Tally object implementation +//============================================================================== + +void +Tally::set_filters(const int32_t filter_indices[], int n) +{ + // Clear old data. + filters_.clear(); + strides_.clear(); + + // Copy in the given filter indices. + filters_.assign(filter_indices, filter_indices + n); + + // Set the strides. Filters are traversed in reverse so that the last filter + // has the shortest stride in memory and the first filter has the longest + // stride. + strides_.resize(n, 0); + int stride = 1; + for (int i = n-1; i >= 0; --i) { + strides_[i] = stride; + //TODO: off-by-one + stride *= model::tally_filters[filters_[i]-1]->n_bins_; + } + n_filter_bins_ = stride; +} + //============================================================================== // Non-member functions //============================================================================== @@ -132,18 +158,39 @@ free_memory_tally_c() //============================================================================== extern "C" int -openmc_tally_get_filters(int32_t index, int32_t** indices, int* n) +openmc_tally_get_filters(int32_t index, const int32_t** indices, int* n) { if (index < 1 || index > model::tallies.size()) { set_errmsg("Index in tallies array is out of bounds."); return OPENMC_E_OUT_OF_BOUNDS; } - *indices = model::tallies[index-1]->filters_.data(); - *n = model::tallies[index-1]->filters_.size(); + *indices = model::tallies[index-1]->filters().data(); + *n = model::tallies[index-1]->filters().size(); return 0; } +/* +// Declared in Fortran +extern "C" int tally_update_find_filter(int32_t index); + +extern "C" int +openmc_tally_set_filters(int32_t index, int n, const int32_t* indices) +{ + // Make sure the index fits in the array bounds. + if (index < 1 || index > model::tallies.size()) { + set_errmsg("Index in tallies array is out of bounds."); + return OPENMC_E_OUT_OF_BOUNDS; + } + + // Set the filters. + model::tallies[index-1]->set_filters(indices, n); + + // Update the find_filter map. + return tally_update_find_filter(index); +} +*/ + //============================================================================== // Fortran compatibility functions //============================================================================== @@ -158,16 +205,17 @@ extern "C" { model::tallies.push_back(std::make_unique()); } - void - tally_set_filters_c(Tally* tally, int n, int32_t filter_indices[]) - { - tally->filters_.clear(); - tally->filters_.assign(filter_indices, filter_indices + n); - } + void tally_set_filters_c(Tally* tally, int n, int32_t filter_indices[]) + {tally->set_filters(filter_indices, n);} - int tally_get_n_filters_c(Tally* tally) {return tally->filters_.size();} + int tally_get_n_filters_c(Tally* tally) {return tally->filters().size();} - int32_t tally_get_filter_c(Tally* tally, int i) {return tally->filters_[i];} + int32_t tally_get_filter_c(Tally* tally, int i) {return tally->filters(i);} + + int32_t tally_get_stride_c(Tally* tally, int i) {return tally->strides(i);} + + int32_t tally_get_n_filter_bins_c(Tally* tally) + {return tally->n_filter_bins();} } } // namespace openmc diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index 9a82f94d8b..85fd776cb5 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -59,13 +59,6 @@ module tally_header logical :: active = .false. logical :: depletion_rx = .false. ! has depletion reactions, e.g. (n,2n) - ! The stride attribute is used for determining the index in the results - ! array for a matching_bin combination. Since multiple dimensions are - ! mapped onto one dimension in the results array, the stride attribute gives - ! the stride for a given filter type within the results array - - integer, allocatable :: stride(:) - ! This array provides a way to lookup what index in the filters array a ! certain filter is. For example, if find_filter(FILTER_CELL) > 0, then the ! value is the index in filters(:). @@ -86,7 +79,6 @@ module tally_header ! second dimension of the array is for the combination of filters ! (e.g. specific cell, specific energy group, etc.) - integer :: n_filter_bins = 1 integer :: total_score_bins real(C_DOUBLE), allocatable :: results(:,:,:) @@ -108,6 +100,8 @@ module tally_header procedure :: set_filters => tally_set_filters procedure :: n_filters => tally_get_n_filters procedure :: filter => tally_get_filter + procedure :: stride => tally_get_stride + procedure :: n_filter_bins => tally_get_n_filter_bins end type TallyObject type, public :: TallyContainer @@ -271,12 +265,12 @@ contains ! If results was already allocated but shape is wrong, then reallocate it ! to the correct shape if (this % total_score_bins /= size(this % results, 2) .or. & - this % n_filter_bins /= size(this % results, 3)) then + this % n_filter_bins() /= size(this % results, 3)) then deallocate(this % results) - allocate(this % results(3, this % total_score_bins, this % n_filter_bins)) + allocate(this % results(3, this % total_score_bins, this % n_filter_bins())) end if else - allocate(this % results(3, this % total_score_bins, this % n_filter_bins)) + allocate(this % results(3, this % total_score_bins, this % n_filter_bins())) end if end subroutine tally_allocate_results @@ -374,24 +368,6 @@ contains if (err == 0) then call tally_set_filters_c(this % ptr, n, filter_indices) - - if (allocated(this % stride)) deallocate(this % stride) - allocate(this % stride(n)) - - ! Filters are traversed in reverse so that the last filter has the - ! shortest stride in memory and the first filter has the largest stride - stride = 1 - do i = n, 1, -1 - ! Set filter and stride - k = filter_indices(i) - this % stride(i) = stride - - ! Multiply stride by number of bins in this filter - stride = stride * filters(k) % obj % n_bins - end do - - ! Set total number of filter bins - this % n_filter_bins = stride end if end function tally_set_filters @@ -424,6 +400,34 @@ contains filt = tally_get_filter_c(this % ptr, i-1) end function + function tally_get_stride(this, i) result(stride) + class(TallyObject) :: this + integer(C_INT) :: i + integer(C_INT32_T) :: stride + interface + function tally_get_stride_c(tally, i) result(stride) bind(C) + import C_PTR, C_INT, C_INT32_T + type(C_PTR), value :: tally + integer(C_INT), value :: i + integer(C_INT32_T) :: stride + end function + end interface + stride = tally_get_stride_c(this % ptr, i-1) + end function + + function tally_get_n_filter_bins(this) result(n_filter_bins) + class(TallyObject) :: this + integer(C_INT32_T) :: n_filter_bins + interface + function tally_get_n_filter_bins_c(tally) result(n_filter_bins) bind(C) + import C_PTR, C_INT, C_INT32_T + type(C_PTR), value :: tally + integer(C_INT32_T) :: n_filter_bins + end function + end interface + n_filter_bins = tally_get_n_filter_bins_c(this % ptr) + end function + !=============================================================================== ! CONFIGURE_TALLIES initializes several data structures related to tallies. This ! is called after the basic tally data has already been read from the diff --git a/src/tallies/trigger.F90 b/src/tallies/trigger.F90 index c2a917b33e..5315c8aaed 100644 --- a/src/tallies/trigger.F90 +++ b/src/tallies/trigger.F90 @@ -177,7 +177,7 @@ contains call filter_matches(t % filter(j)) % bins_push_back(0) end do - FILTER_LOOP: do filter_index = 1, t % n_filter_bins + FILTER_LOOP: do filter_index = 1, t % n_filter_bins() ! Initialize score index score_index = trigger % score_index From d3c41f5f8a0ff5dd4fb84ab20ccf182bfdaf2523 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Fri, 25 Jan 2019 15:01:09 -0500 Subject: [PATCH 03/58] Remove the TallyObject % find_filter attribute --- src/input_xml.F90 | 68 ++++++++++++++++++++++++------------ src/tallies/tally.F90 | 26 +++++++------- src/tallies/tally_header.F90 | 66 +++++++++++++--------------------- src/tallies/trigger.F90 | 6 ++-- 4 files changed, 85 insertions(+), 81 deletions(-) diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 0883776247..a5dae6c823 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -928,6 +928,9 @@ contains integer :: MT ! user-specified MT for score logical :: file_exists ! does tallies.xml file exist? integer, allocatable :: temp_filter(:) ! temporary filter indices + logical :: has_energyout, has_delayedgroup, has_legendre, has_surface, & + has_cell, has_cellfrom, has_meshsurface + integer :: particle_filter_index character(MAX_LINE_LEN) :: filename character(MAX_WORD_LEN) :: word character(MAX_WORD_LEN) :: score_name @@ -1163,6 +1166,30 @@ contains end if deallocate(temp_filter) + ! Check for the presence of certain filter types + has_energyout = (t % energyout_filter > 0) + has_delayedgroup = (t % delayedgroup_filter > 0) + has_legendre = .false. + has_surface = (t % surface_filter > 0) + has_cell = .false. + has_cellfrom = .false. + has_meshsurface = .false. + particle_filter_index = 0 + do j = 1, t % n_filters() + select type (filt => filters(t % filter(j)) % obj) + type is (LegendreFilter) + has_legendre = .true. + type is (CellFilter) + has_cell = .true. + type is (CellfromFilter) + has_cellfrom = .true. + type is (MeshSurfaceFilter) + has_meshsurface = .true. + type is (ParticleFilter) + particle_filter_index = j + end select + end do + ! ======================================================================= ! READ DATA FOR NUCLIDES @@ -1255,8 +1282,7 @@ contains ! Check if delayed group filter is used with any score besides ! delayed-nu-fission or decay-rate if ((score_name /= 'delayed-nu-fission' .and. & - score_name /= 'decay-rate') .and. & - t % find_filter(FILTER_DELAYEDGROUP) > 0) then + score_name /= 'decay-rate') .and. has_delayedgroup) then call fatal_error("Cannot tally " // trim(score_name) // " with a & &delayedgroup filter.") end if @@ -1270,22 +1296,21 @@ contains end if t % score_bins(j) = SCORE_FLUX - if (t % find_filter(FILTER_ENERGYOUT) > 0) then + if (has_energyout) then call fatal_error("Cannot tally flux with an outgoing energy & &filter.") end if case ('total', '(n,total)') t % score_bins(j) = SCORE_TOTAL - if (t % find_filter(FILTER_ENERGYOUT) > 0) then + if (has_energyout) then call fatal_error("Cannot tally total reaction rate with an & &outgoing energy filter.") end if case ('scatter') t % score_bins(j) = SCORE_SCATTER - if (t % find_filter(FILTER_ENERGYOUT) > 0 .or. & - t % find_filter(FILTER_LEGENDRE) > 0) then + if (has_energyout .or. has_legendre) then ! Set tally estimator to analog t % estimator = ESTIMATOR_ANALOG end if @@ -1299,8 +1324,7 @@ contains if (run_CE) then t % estimator = ESTIMATOR_ANALOG else - if (t % find_filter(FILTER_ENERGYOUT) > 0 .or. & - t % find_filter(FILTER_LEGENDRE) > 0) then + if (has_energyout .or. has_legendre) then ! Set tally estimator to analog t % estimator = ESTIMATOR_ANALOG end if @@ -1320,19 +1344,19 @@ contains case ('absorption') t % score_bins(j) = SCORE_ABSORPTION - if (t % find_filter(FILTER_ENERGYOUT) > 0) then + if (has_energyout) then call fatal_error("Cannot tally absorption rate with an outgoing & &energy filter.") end if case ('fission', '18') t % score_bins(j) = SCORE_FISSION - if (t % find_filter(FILTER_ENERGYOUT) > 0) then + if (has_energyout) then call fatal_error("Cannot tally fission rate with an outgoing & &energy filter.") end if case ('nu-fission') t % score_bins(j) = SCORE_NU_FISSION - if (t % find_filter(FILTER_ENERGYOUT) > 0) then + if (has_energyout) then ! Set tally estimator to analog t % estimator = ESTIMATOR_ANALOG end if @@ -1340,13 +1364,13 @@ contains t % score_bins(j) = SCORE_DECAY_RATE case ('delayed-nu-fission') t % score_bins(j) = SCORE_DELAYED_NU_FISSION - if (t % find_filter(FILTER_ENERGYOUT) > 0) then + if (has_energyout) then ! Set tally estimator to analog t % estimator = ESTIMATOR_ANALOG end if case ('prompt-nu-fission') t % score_bins(j) = SCORE_PROMPT_NU_FISSION - if (t % find_filter(FILTER_ENERGYOUT) > 0) then + if (has_energyout) then ! Set tally estimator to analog t % estimator = ESTIMATOR_ANALOG end if @@ -1362,12 +1386,10 @@ contains ! Check which type of current is desired: mesh currents or ! surface currents - if (t % find_filter(FILTER_SURFACE) > 0 .or. & - &t % find_filter(FILTER_CELL) > 0 .or. & - &t % find_filter(FILTER_CELLFROM) > 0) then + if (has_surface .or. has_cell .or. has_cellfrom) then ! Check to make sure that mesh surface currents are not desired as well - if (t % find_filter(FILTER_MESHSURFACE) > 0) then + if (has_meshsurface) then call fatal_error("Cannot tally mesh surface currents & &in the same tally as normal surface currents") end if @@ -1375,7 +1397,7 @@ contains t % type = TALLY_SURFACE t % score_bins(j) = SCORE_CURRENT - else if (t % find_filter(FILTER_MESHSURFACE) > 0) then + else if (has_meshsurface) then t % score_bins(j) = SCORE_CURRENT t % type = TALLY_MESH_SURFACE @@ -1522,7 +1544,7 @@ contains ! Check if tally is compatible with particle type if (photon_transport) then - if (t % find_filter(FILTER_PARTICLE) == 0) then + if (particle_filter_index == 0) then do j = 1, n_scores select case (t % score_bins(j)) case (SCORE_INVERSE_VELOCITY) @@ -1537,7 +1559,7 @@ contains end select end do else - select type(filt => filters(t % find_filter(FILTER_PARTICLE)) % obj) + select type(filt => filters(particle_filter_index) % obj) type is (ParticleFilter) do l = 1, filt % n_bins if (filt % particles(l) == ELECTRON .or. filt % particles(l) == POSITRON) then @@ -1547,8 +1569,8 @@ contains end select end if else - if (t % find_filter(FILTER_PARTICLE) > 0) then - select type(filt => filters(t % find_filter(FILTER_PARTICLE)) % obj) + if (particle_filter_index > 0) then + select type(filt => filters(particle_filter_index) % obj) type is (ParticleFilter) do l = 1, filt % n_bins if (filt % particles(l) /= NEUTRON) then @@ -1592,7 +1614,7 @@ contains if (tally_derivs(t % deriv) % variable == DIFF_NUCLIDE_DENSITY & .or. tally_derivs(t % deriv) % variable == DIFF_TEMPERATURE) then if (any(t % nuclide_bins == -1)) then - if (t % find_filter(FILTER_ENERGYOUT) > 0) then + if (has_energyout) then call fatal_error("Error on tally " // trim(to_str(t % id)) & // ": Cannot use a 'nuclide_density' or 'temperature' & &derivative on a tally with an outgoing energy filter and & diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index 50602c15d5..02dacb2510 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -296,7 +296,7 @@ contains if (t % estimator == ESTIMATOR_ANALOG) then if (survival_biasing .or. p % fission) then - if (t % find_filter(FILTER_ENERGYOUT) > 0) then + if (t % energyout_filter > 0) then ! Normally, we only need to make contributions to one scoring ! bin. However, in the case of fission, since multiple fission ! neutrons were emitted with different energies, multiple @@ -342,7 +342,7 @@ contains if (t % estimator == ESTIMATOR_ANALOG) then if (survival_biasing .or. p % fission) then - if (t % find_filter(FILTER_ENERGYOUT) > 0) then + if (t % energyout_filter > 0) then ! Normally, we only need to make contributions to one scoring ! bin. However, in the case of fission, since multiple fission ! neutrons were emitted with different energies, multiple @@ -406,11 +406,11 @@ contains if (material_xs % absorption == ZERO) cycle SCORE_LOOP ! Set the delayedgroup filter index and the number of delayed group bins - dg_filter = t % find_filter(FILTER_DELAYEDGROUP) + dg_filter = t % delayedgroup_filter if (t % estimator == ESTIMATOR_ANALOG) then if (survival_biasing .or. p % fission) then - if (t % find_filter(FILTER_ENERGYOUT) > 0) then + if (t % energyout_filter > 0) then ! Normally, we only need to make contributions to one scoring ! bin. However, in the case of fission, since multiple fission ! neutrons were emitted with different energies, multiple @@ -600,7 +600,7 @@ contains if (material_xs % absorption == ZERO) cycle SCORE_LOOP ! Set the delayedgroup filter index - dg_filter = t % find_filter(FILTER_DELAYEDGROUP) + dg_filter = t % delayedgroup_filter if (t % estimator == ESTIMATOR_ANALOG) then if (survival_biasing) then @@ -1509,7 +1509,7 @@ contains if (t % estimator == ESTIMATOR_ANALOG) then if (survival_biasing .or. p % fission) then - if (t % find_filter(FILTER_ENERGYOUT) > 0) then + if (t % energyout_filter > 0) then ! Normally, we only need to make contributions to one scoring ! bin. However, in the case of fission, since multiple fission ! neutrons were emitted with different energies, multiple @@ -1563,7 +1563,7 @@ contains if (t % estimator == ESTIMATOR_ANALOG) then if (survival_biasing .or. p % fission) then - if (t % find_filter(FILTER_ENERGYOUT) > 0) then + if (t % energyout_filter > 0) then ! Normally, we only need to make contributions to one scoring ! bin. However, in the case of fission, since multiple fission ! neutrons were emitted with different energies, multiple @@ -1617,11 +1617,11 @@ contains case (SCORE_DELAYED_NU_FISSION) ! Set the delayedgroup filter index and the number of delayed group bins - dg_filter = t % find_filter(FILTER_DELAYEDGROUP) + dg_filter = t % delayedgroup_filter if (t % estimator == ESTIMATOR_ANALOG) then if (survival_biasing .or. p % fission) then - if (t % find_filter(FILTER_ENERGYOUT) > 0) then + if (t % energyout_filter > 0) then ! Normally, we only need to make contributions to one scoring ! bin. However, in the case of fission, since multiple fission ! neutrons were emitted with different energies, multiple @@ -1760,7 +1760,7 @@ contains case (SCORE_DECAY_RATE) ! Set the delayedgroup filter index and the number of delayed group bins - dg_filter = t % find_filter(FILTER_DELAYEDGROUP) + dg_filter = t % delayedgroup_filter if (t % estimator == ESTIMATOR_ANALOG) then if (survival_biasing) then @@ -2350,7 +2350,7 @@ contains integer :: g_out ! energy group of fission bank site ! save original outgoing energy bin and score index - i = t % filter(t % find_filter(FILTER_ENERGYOUT)) + i = t % filter(t % energyout_filter) i_bin = filter_matches(i) % i_bin bin_energyout = filter_matches(i) % bins_data(i_bin) @@ -2429,7 +2429,7 @@ contains else if (score_bin == SCORE_DELAYED_NU_FISSION .and. g /= 0) then ! Get the index of delayed group filter - j = t % find_filter(FILTER_DELAYEDGROUP) + j = t % delayedgroup_filter ! if the delayed group filter is present, tally to corresponding ! delayed group bin if it exists @@ -2519,7 +2519,7 @@ contains integer :: filter_index ! index for matching filter bin combination ! save original delayed group bin - i_filt = t % filter(t % find_filter(FILTER_DELAYEDGROUP)) + i_filt = t % filter(t % delayedgroup_filter) i_bin = filter_matches(i_filt) % i_bin bin_original = filter_matches(i_filt) % bins_data(i_bin) call filter_matches(i_filt) % bins_set_data(i_bin, d_bin) diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index 85fd776cb5..173e4959b8 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -59,11 +59,14 @@ module tally_header logical :: active = .false. logical :: depletion_rx = .false. ! has depletion reactions, e.g. (n,2n) - ! This array provides a way to lookup what index in the filters array a - ! certain filter is. For example, if find_filter(FILTER_CELL) > 0, then the - ! value is the index in filters(:). - - integer :: find_filter(N_FILTER_TYPES) = 0 + ! We need to have quick access to some filters. The following gives indices + ! for various filters that could be in the tally or -1 if they are not + ! present. + integer :: energyin_filter = -1 + integer :: energyout_filter = -1 + integer :: delayedgroup_filter = -1 + integer :: surface_filter = -1 + integer :: mesh_filter = -1 ! Individual nuclides to tally integer :: n_nuclide_bins = 0 @@ -297,7 +300,6 @@ contains end interface err = 0 - this % find_filter(:) = 0 n = size(filter_indices) do i = 1, n k = filter_indices(i) @@ -309,61 +311,41 @@ contains ! Set the filter index in the tally find_filter array select type (filt => filters(k) % obj) - type is (DistribcellFilter) - j = FILTER_DISTRIBCELL - type is (CellFilter) - j = FILTER_CELL - type is (CellFromFilter) - j = FILTER_CELLFROM - type is (CellbornFilter) - j = FILTER_CELLBORN - type is (MaterialFilter) - j = FILTER_MATERIAL - type is (UniverseFilter) - j = FILTER_UNIVERSE + type is (SurfaceFilter) - j = FILTER_SURFACE + this % surface_filter = i + type is (MeshFilter) - j = FILTER_MESH - type is (MeshSurfaceFilter) - j = FILTER_MESHSURFACE + this % mesh_filter = i + type is (EnergyFilter) - j = FILTER_ENERGYIN + this % energyin_filter = i + type is (EnergyoutFilter) - j = FILTER_ENERGYOUT + this % energyout_filter = i this % estimator = ESTIMATOR_ANALOG + type is (DelayedGroupFilter) - j = FILTER_DELAYEDGROUP - type is (MuFilter) - j = FILTER_MU - this % estimator = ESTIMATOR_ANALOG - type is (PolarFilter) - j = FILTER_POLAR - type is (AzimuthalFilter) - j = FILTER_AZIMUTHAL - type is (EnergyFunctionFilter) - j = FILTER_ENERGYFUNCTION + this % delayedgroup_filter = i + type is (LegendreFilter) - j = FILTER_LEGENDRE this % estimator = ESTIMATOR_ANALOG + type is (SphericalHarmonicsFilter) - j = FILTER_SPH_HARMONICS if (filt % cosine() == COSINE_SCATTER) then this % estimator = ESTIMATOR_ANALOG end if + type is (SpatialLegendreFilter) - j = FILTER_SPTL_LEGENDRE this % estimator = ESTIMATOR_COLLISION + type is (ZernikeFilter) - j = FILTER_ZERNIKE this % estimator = ESTIMATOR_COLLISION + type is (ZernikeRadialFilter) - j = FILTER_ZERNIKE_RADIAL this % estimator = ESTIMATOR_COLLISION - type is (ParticleFilter) - j = FILTER_PARTICLE + end select - this % find_filter(j) = i end do if (err == 0) then diff --git a/src/tallies/trigger.F90 b/src/tallies/trigger.F90 index 5315c8aaed..d792b7e980 100644 --- a/src/tallies/trigger.F90 +++ b/src/tallies/trigger.F90 @@ -260,8 +260,8 @@ contains type(RegularMesh) :: m ! surface current mesh ! Get pointer to mesh - i_filter_mesh = t % filter(t % find_filter(FILTER_MESH)) - i_filter_surf = t % filter(t % find_filter(FILTER_SURFACE)) + i_filter_mesh = t % mesh_filter + i_filter_surf = t % surface_filter select type(filt => filters(i_filter_mesh) % obj) type is (MeshFilter) m = meshes(filt % mesh()) @@ -274,7 +274,7 @@ contains end do ! determine how many energyin bins there are - i_filter_ein = t % find_filter(FILTER_ENERGYIN) + i_filter_ein = t % energyin_filter if (i_filter_ein > 0) then print_ebin = .true. n = filters(t % filter(i_filter_ein)) % obj % n_bins From 39bd9b7b69f3e895ee4ef4afd208c7dc759106be Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Fri, 25 Jan 2019 15:36:52 -0500 Subject: [PATCH 04/58] Move indices like tally % filter_energyin to C++ --- include/openmc/tallies/tally.h | 9 ++++ src/input_xml.F90 | 6 +-- src/tallies/tally.F90 | 26 +++++----- src/tallies/tally.cpp | 44 ++++++++++++++++ src/tallies/tally_header.F90 | 92 ++++++++++++++++++++++++++-------- src/tallies/trigger.F90 | 6 +-- 6 files changed, 142 insertions(+), 41 deletions(-) diff --git a/include/openmc/tallies/tally.h b/include/openmc/tallies/tally.h index 0f68afe3fb..5f4deb34c6 100644 --- a/include/openmc/tallies/tally.h +++ b/include/openmc/tallies/tally.h @@ -30,6 +30,15 @@ public: int32_t n_filter_bins() const {return n_filter_bins_;} + // We need to have quick access to some filters. The following gives indices + // for various filters that could be in the tally or C_NONE if they are not + // present. + int energyin_filter_ {C_NONE}; + int energyout_filter_ {C_NONE}; + int delayedgroup_filter_ {C_NONE}; + int surface_filter_ {C_NONE}; + int mesh_filter_ {C_NONE}; + private: std::vector filters_; //< Filter indices in global filters array diff --git a/src/input_xml.F90 b/src/input_xml.F90 index a5dae6c823..304ed9f03b 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -1167,10 +1167,10 @@ contains deallocate(temp_filter) ! Check for the presence of certain filter types - has_energyout = (t % energyout_filter > 0) - has_delayedgroup = (t % delayedgroup_filter > 0) + has_energyout = (t % energyout_filter() > 0) + has_delayedgroup = (t % delayedgroup_filter() > 0) has_legendre = .false. - has_surface = (t % surface_filter > 0) + has_surface = (t % surface_filter() > 0) has_cell = .false. has_cellfrom = .false. has_meshsurface = .false. diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index 02dacb2510..0232ed6027 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -296,7 +296,7 @@ contains if (t % estimator == ESTIMATOR_ANALOG) then if (survival_biasing .or. p % fission) then - if (t % energyout_filter > 0) then + if (t % energyout_filter() > 0) then ! Normally, we only need to make contributions to one scoring ! bin. However, in the case of fission, since multiple fission ! neutrons were emitted with different energies, multiple @@ -342,7 +342,7 @@ contains if (t % estimator == ESTIMATOR_ANALOG) then if (survival_biasing .or. p % fission) then - if (t % energyout_filter > 0) then + if (t % energyout_filter() > 0) then ! Normally, we only need to make contributions to one scoring ! bin. However, in the case of fission, since multiple fission ! neutrons were emitted with different energies, multiple @@ -406,11 +406,11 @@ contains if (material_xs % absorption == ZERO) cycle SCORE_LOOP ! Set the delayedgroup filter index and the number of delayed group bins - dg_filter = t % delayedgroup_filter + dg_filter = t % delayedgroup_filter() if (t % estimator == ESTIMATOR_ANALOG) then if (survival_biasing .or. p % fission) then - if (t % energyout_filter > 0) then + if (t % energyout_filter() > 0) then ! Normally, we only need to make contributions to one scoring ! bin. However, in the case of fission, since multiple fission ! neutrons were emitted with different energies, multiple @@ -600,7 +600,7 @@ contains if (material_xs % absorption == ZERO) cycle SCORE_LOOP ! Set the delayedgroup filter index - dg_filter = t % delayedgroup_filter + dg_filter = t % delayedgroup_filter() if (t % estimator == ESTIMATOR_ANALOG) then if (survival_biasing) then @@ -1509,7 +1509,7 @@ contains if (t % estimator == ESTIMATOR_ANALOG) then if (survival_biasing .or. p % fission) then - if (t % energyout_filter > 0) then + if (t % energyout_filter() > 0) then ! Normally, we only need to make contributions to one scoring ! bin. However, in the case of fission, since multiple fission ! neutrons were emitted with different energies, multiple @@ -1563,7 +1563,7 @@ contains if (t % estimator == ESTIMATOR_ANALOG) then if (survival_biasing .or. p % fission) then - if (t % energyout_filter > 0) then + if (t % energyout_filter() > 0) then ! Normally, we only need to make contributions to one scoring ! bin. However, in the case of fission, since multiple fission ! neutrons were emitted with different energies, multiple @@ -1617,11 +1617,11 @@ contains case (SCORE_DELAYED_NU_FISSION) ! Set the delayedgroup filter index and the number of delayed group bins - dg_filter = t % delayedgroup_filter + dg_filter = t % delayedgroup_filter() if (t % estimator == ESTIMATOR_ANALOG) then if (survival_biasing .or. p % fission) then - if (t % energyout_filter > 0) then + if (t % energyout_filter() > 0) then ! Normally, we only need to make contributions to one scoring ! bin. However, in the case of fission, since multiple fission ! neutrons were emitted with different energies, multiple @@ -1760,7 +1760,7 @@ contains case (SCORE_DECAY_RATE) ! Set the delayedgroup filter index and the number of delayed group bins - dg_filter = t % delayedgroup_filter + dg_filter = t % delayedgroup_filter() if (t % estimator == ESTIMATOR_ANALOG) then if (survival_biasing) then @@ -2350,7 +2350,7 @@ contains integer :: g_out ! energy group of fission bank site ! save original outgoing energy bin and score index - i = t % filter(t % energyout_filter) + i = t % filter(t % energyout_filter()) i_bin = filter_matches(i) % i_bin bin_energyout = filter_matches(i) % bins_data(i_bin) @@ -2429,7 +2429,7 @@ contains else if (score_bin == SCORE_DELAYED_NU_FISSION .and. g /= 0) then ! Get the index of delayed group filter - j = t % delayedgroup_filter + j = t % delayedgroup_filter() ! if the delayed group filter is present, tally to corresponding ! delayed group bin if it exists @@ -2519,7 +2519,7 @@ contains integer :: filter_index ! index for matching filter bin combination ! save original delayed group bin - i_filt = t % filter(t % delayedgroup_filter) + i_filt = t % filter(t % delayedgroup_filter()) i_bin = filter_matches(i_filt) % i_bin bin_original = filter_matches(i_filt) % bins_data(i_bin) call filter_matches(i_filt) % bins_set_data(i_bin, d_bin) diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 1d816bdac6..ada469ee5c 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -4,6 +4,10 @@ #include "openmc/constants.h" #include "openmc/error.h" #include "openmc/tallies/filter.h" +#include "openmc/tallies/filter_energy.h" +#include "openmc/tallies/filter_delayedgroup.h" +#include "openmc/tallies/filter_surface.h" +#include "openmc/tallies/filter_mesh.h" #include "openmc/message_passing.h" #include "xtensor/xadapt.hpp" @@ -41,6 +45,31 @@ Tally::set_filters(const int32_t filter_indices[], int n) // Copy in the given filter indices. filters_.assign(filter_indices, filter_indices + n); + for (int i = 0; i < n; ++i) { + auto i_filt = filters_[i]; + //TODO: off-by-one + if (i_filt < 1 || i_filt > model::tally_filters.size()) + throw std::out_of_range("Index in tally filter array out of bounds."); + + //TODO: off-by-one + const auto* filt = model::tally_filters[i_filt-1].get(); + + //TODO: off-by-one on each index + if (dynamic_cast(filt)) { + if (dynamic_cast(filt)) { + energyout_filter_ = i + 1; + } else { + energyin_filter_ = i + 1; + } + } else if (dynamic_cast(filt)) { + delayedgroup_filter_ = i + 1; + } else if (dynamic_cast(filt)) { + surface_filter_ = i + 1; + } else if (dynamic_cast(filt)) { + mesh_filter_ = i + 1; + } + } + // Set the strides. Filters are traversed in reverse so that the last filter // has the shortest stride in memory and the first filter has the longest // stride. @@ -216,6 +245,21 @@ extern "C" { int32_t tally_get_n_filter_bins_c(Tally* tally) {return tally->n_filter_bins();} + + int tally_get_energyin_filter_c(Tally* tally) + {return tally->energyin_filter_;} + + int tally_get_energyout_filter_c(Tally* tally) + {return tally->energyout_filter_;} + + int tally_get_delayedgroup_filter_c(Tally* tally) + {return tally->delayedgroup_filter_;} + + int tally_get_surface_filter_c(Tally* tally) + {return tally->surface_filter_;} + + int tally_get_mesh_filter_c(Tally* tally) + {return tally->mesh_filter_;} } } // namespace openmc diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index 173e4959b8..f1ae558f5c 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -59,15 +59,6 @@ module tally_header logical :: active = .false. logical :: depletion_rx = .false. ! has depletion reactions, e.g. (n,2n) - ! We need to have quick access to some filters. The following gives indices - ! for various filters that could be in the tally or -1 if they are not - ! present. - integer :: energyin_filter = -1 - integer :: energyout_filter = -1 - integer :: delayedgroup_filter = -1 - integer :: surface_filter = -1 - integer :: mesh_filter = -1 - ! Individual nuclides to tally integer :: n_nuclide_bins = 0 integer, allocatable :: nuclide_bins(:) @@ -105,6 +96,11 @@ module tally_header procedure :: filter => tally_get_filter procedure :: stride => tally_get_stride procedure :: n_filter_bins => tally_get_n_filter_bins + procedure :: energyin_filter => tally_get_energyin_filter + procedure :: energyout_filter => tally_get_energyout_filter + procedure :: delayedgroup_filter => tally_get_delayedgroup_filter + procedure :: surface_filter => tally_get_surface_filter + procedure :: mesh_filter => tally_get_mesh_filter end type TallyObject type, public :: TallyContainer @@ -312,22 +308,9 @@ contains ! Set the filter index in the tally find_filter array select type (filt => filters(k) % obj) - type is (SurfaceFilter) - this % surface_filter = i - - type is (MeshFilter) - this % mesh_filter = i - - type is (EnergyFilter) - this % energyin_filter = i - type is (EnergyoutFilter) - this % energyout_filter = i this % estimator = ESTIMATOR_ANALOG - type is (DelayedGroupFilter) - this % delayedgroup_filter = i - type is (LegendreFilter) this % estimator = ESTIMATOR_ANALOG @@ -410,6 +393,71 @@ contains n_filter_bins = tally_get_n_filter_bins_c(this % ptr) end function + function tally_get_energyin_filter(this) result(filt) + class(TallyObject) :: this + integer(C_INT) :: filt + interface + function tally_get_energyin_filter_c(tally) result(filt) bind(C) + import C_PTR, C_INT + type(C_PTR), value :: tally + integer(C_INT) :: filt + end function + end interface + filt = tally_get_energyin_filter_c(this % ptr) + end function + + function tally_get_energyout_filter(this) result(filt) + class(TallyObject) :: this + integer(C_INT) :: filt + interface + function tally_get_energyout_filter_c(tally) result(filt) bind(C) + import C_PTR, C_INT + type(C_PTR), value :: tally + integer(C_INT) :: filt + end function + end interface + filt = tally_get_energyout_filter_c(this % ptr) + end function + + function tally_get_delayedgroup_filter(this) result(filt) + class(TallyObject) :: this + integer(C_INT) :: filt + interface + function tally_get_delayedgroup_filter_c(tally) result(filt) bind(C) + import C_PTR, C_INT + type(C_PTR), value :: tally + integer(C_INT) :: filt + end function + end interface + filt = tally_get_delayedgroup_filter_c(this % ptr) + end function + + function tally_get_surface_filter(this) result(filt) + class(TallyObject) :: this + integer(C_INT) :: filt + interface + function tally_get_surface_filter_c(tally) result(filt) bind(C) + import C_PTR, C_INT + type(C_PTR), value :: tally + integer(C_INT) :: filt + end function + end interface + filt = tally_get_surface_filter_c(this % ptr) + end function + + function tally_get_mesh_filter(this) result(filt) + class(TallyObject) :: this + integer(C_INT) :: filt + interface + function tally_get_mesh_filter_c(tally) result(filt) bind(C) + import C_PTR, C_INT + type(C_PTR), value :: tally + integer(C_INT) :: filt + end function + end interface + filt = tally_get_mesh_filter_c(this % ptr) + end function + !=============================================================================== ! CONFIGURE_TALLIES initializes several data structures related to tallies. This ! is called after the basic tally data has already been read from the diff --git a/src/tallies/trigger.F90 b/src/tallies/trigger.F90 index d792b7e980..31281a64f0 100644 --- a/src/tallies/trigger.F90 +++ b/src/tallies/trigger.F90 @@ -260,8 +260,8 @@ contains type(RegularMesh) :: m ! surface current mesh ! Get pointer to mesh - i_filter_mesh = t % mesh_filter - i_filter_surf = t % surface_filter + i_filter_mesh = t % mesh_filter() + i_filter_surf = t % surface_filter() select type(filt => filters(i_filter_mesh) % obj) type is (MeshFilter) m = meshes(filt % mesh()) @@ -274,7 +274,7 @@ contains end do ! determine how many energyin bins there are - i_filter_ein = t % energyin_filter + i_filter_ein = t % energyin_filter() if (i_filter_ein > 0) then print_ebin = .true. n = filters(t % filter(i_filter_ein)) % obj % n_bins From 0efcf24d763b69fd74e06a6c8d44d3bf3e5cfe45 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Fri, 25 Jan 2019 15:45:38 -0500 Subject: [PATCH 05/58] Move openmc_tally_set_filters to C++ --- src/input_xml.F90 | 20 ++++++++ src/tallies/tally.F90 | 2 +- src/tallies/tally.cpp | 16 +++--- src/tallies/tally_header.F90 | 96 ++++-------------------------------- 4 files changed, 39 insertions(+), 95 deletions(-) diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 304ed9f03b..b92d5141fc 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -1190,6 +1190,26 @@ contains end select end do + ! Change the tally estimator if a filter demands it + do j = 1, t % n_filters() + select type (filt => filters(t % filter(j)) % obj) + type is (EnergyoutFilter) + t % estimator = ESTIMATOR_ANALOG + type is (LegendreFilter) + t % estimator = ESTIMATOR_ANALOG + type is (SphericalHarmonicsFilter) + if (filt % cosine() == COSINE_SCATTER) then + t % estimator = ESTIMATOR_ANALOG + end if + type is (SpatialLegendreFilter) + t % estimator = ESTIMATOR_COLLISION + type is (ZernikeFilter) + t % estimator = ESTIMATOR_COLLISION + type is (ZernikeRadialFilter) + t % estimator = ESTIMATOR_COLLISION + end select + end do + ! ======================================================================= ! READ DATA FOR NUCLIDES diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index 0232ed6027..2b274364ed 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -3865,7 +3865,7 @@ contains tallies(index) % obj % ptr = tally_pointer(index - 1) ! When a tally is allocated, set it to have 0 filters - err = tallies(index) % obj % set_filters(empty) + err = openmc_tally_set_filters(index, 0, empty) end if else err = E_OUT_OF_BOUNDS diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index ada469ee5c..38ead0f080 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -199,10 +199,6 @@ openmc_tally_get_filters(int32_t index, const int32_t** indices, int* n) return 0; } -/* -// Declared in Fortran -extern "C" int tally_update_find_filter(int32_t index); - extern "C" int openmc_tally_set_filters(int32_t index, int n, const int32_t* indices) { @@ -213,12 +209,16 @@ openmc_tally_set_filters(int32_t index, int n, const int32_t* indices) } // Set the filters. - model::tallies[index-1]->set_filters(indices, n); + try { + //TODO: off-by-one + model::tallies[index-1]->set_filters(indices, n); + } catch (const std::out_of_range& ex) { + set_errmsg(ex.what()); + return OPENMC_E_OUT_OF_BOUNDS; + } - // Update the find_filter map. - return tally_update_find_filter(index); + return 0; } -*/ //============================================================================== // Fortran compatibility functions diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index f1ae558f5c..0b216e3adf 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -40,6 +40,16 @@ module tally_header public :: openmc_tally_set_scores public :: openmc_tally_set_type + interface + function openmc_tally_set_filters(index, n, filter_indices) result(err) bind(C) + import C_INT32_T, C_INT + integer(C_INT32_T), value, intent(in) :: index + integer(C_INT), value, intent(in) :: n + integer(C_INT32_T), intent(in) :: filter_indices(n) + integer(C_INT) :: err + end function openmc_tally_set_filters + end interface + !=============================================================================== ! TALLYOBJECT describes a user-specified tally. The region of phase space to ! tally in is given by the TallyFilters and the results are stored in a @@ -91,7 +101,6 @@ module tally_header procedure :: allocate_results => tally_allocate_results procedure :: read_results_hdf5 => tally_read_results_hdf5 procedure :: write_results_hdf5 => tally_write_results_hdf5 - procedure :: set_filters => tally_set_filters procedure :: n_filters => tally_get_n_filters procedure :: filter => tally_get_filter procedure :: stride => tally_get_stride @@ -274,69 +283,6 @@ contains end subroutine tally_allocate_results - - function tally_set_filters(this, filter_indices) result(err) - class(TallyObject), intent(inout) :: this - integer(C_INT32_T), intent(in) :: filter_indices(:) - integer(C_INT) :: err - - integer :: i ! index in this % filter/stride - integer :: j ! index in this % find_filter - integer :: k ! index in global filters array - integer :: n ! number of filters - integer :: stride ! filter stride - - interface - subroutine tally_set_filters_c(ptr, n, filter_indices) bind(C) - import C_PTR, C_INT, C_INT32_T - type(C_PTR), value :: ptr - integer(C_INT), value :: n - integer(C_INT32_T) :: filter_indices(n) - end subroutine - end interface - - err = 0 - n = size(filter_indices) - do i = 1, n - k = filter_indices(i) - if (k < 1 .or. k > n_filters) then - err = E_OUT_OF_BOUNDS - call set_errmsg("Index in tally filter array out of bounds.") - exit - end if - - ! Set the filter index in the tally find_filter array - select type (filt => filters(k) % obj) - - type is (EnergyoutFilter) - this % estimator = ESTIMATOR_ANALOG - - type is (LegendreFilter) - this % estimator = ESTIMATOR_ANALOG - - type is (SphericalHarmonicsFilter) - if (filt % cosine() == COSINE_SCATTER) then - this % estimator = ESTIMATOR_ANALOG - end if - - type is (SpatialLegendreFilter) - this % estimator = ESTIMATOR_COLLISION - - type is (ZernikeFilter) - this % estimator = ESTIMATOR_COLLISION - - type is (ZernikeRadialFilter) - this % estimator = ESTIMATOR_COLLISION - - end select - end do - - if (err == 0) then - call tally_set_filters_c(this % ptr, n, filter_indices) - end if - - end function tally_set_filters - function tally_get_n_filters(this) result(n) class(TallyObject) :: this integer(C_INT) :: n @@ -800,28 +746,6 @@ contains end function openmc_tally_set_estimator - function openmc_tally_set_filters(index, n, filter_indices) result(err) bind(C) - ! Set the list of filters for a tally - integer(C_INT32_T), value, intent(in) :: index - integer(C_INT), value, intent(in) :: n - integer(C_INT32_T), intent(in) :: filter_indices(n) - integer(C_INT) :: err - - err = 0 - if (index >= 1 .and. index <= n_tallies) then - if (allocated(tallies(index) % obj)) then - err = tallies(index) % obj % set_filters(filter_indices) - else - err = E_ALLOCATE - call set_errmsg("Tally type has not been set yet.") - end if - else - err = E_OUT_OF_BOUNDS - call set_errmsg('Index in tallies array is out of bounds.') - end if - end function openmc_tally_set_filters - - function openmc_tally_set_active(index, active) result(err) bind(C) ! Set the ID of a tally integer(C_INT32_T), value, intent(in) :: index From 06caf67ccf3baef53418b0b28f339898e41551bb Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sat, 26 Jan 2019 12:10:18 -0500 Subject: [PATCH 06/58] Create C++ TallyDerivative struct --- CMakeLists.txt | 1 + include/openmc/constants.h | 23 ------- include/openmc/nuclide.h | 12 ++-- include/openmc/tallies/derivative.h | 51 ++++++++++++++ src/constants.F90 | 22 +----- src/input_xml.F90 | 9 +++ src/tallies/derivative.cpp | 103 ++++++++++++++++++++++++++++ src/tallies/trigger_header.F90 | 2 +- 8 files changed, 172 insertions(+), 51 deletions(-) create mode 100644 include/openmc/tallies/derivative.h create mode 100644 src/tallies/derivative.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index e33979d2c8..4671b75c01 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -419,6 +419,7 @@ add_library(libopenmc SHARED src/string_utils.cpp src/summary.cpp src/surface.cpp + src/tallies/derivative.cpp src/tallies/filter.cpp src/tallies/filter_azimuthal.cpp src/tallies/filter_cellborn.cpp diff --git a/include/openmc/constants.h b/include/openmc/constants.h index 375ebc2767..7fc7dcedec 100644 --- a/include/openmc/constants.h +++ b/include/openmc/constants.h @@ -367,24 +367,6 @@ constexpr int NO_BIN_FOUND {-1}; constexpr int FILTER_UNIVERSE {1}; constexpr int FILTER_MATERIAL {2}; constexpr int FILTER_CELL {3}; -constexpr int FILTER_CELLBORN {4}; -constexpr int FILTER_SURFACE {5}; -constexpr int FILTER_MESH {6}; -constexpr int FILTER_ENERGYIN {7}; -constexpr int FILTER_ENERGYOUT {8}; -constexpr int FILTER_DISTRIBCELL {9}; -constexpr int FILTER_MU {10}; -constexpr int FILTER_POLAR {11}; -constexpr int FILTER_AZIMUTHAL {12}; -constexpr int FILTER_DELAYEDGROUP {13}; -constexpr int FILTER_ENERGYFUNCTION {14}; -constexpr int FILTER_CELLFROM {15}; -constexpr int FILTER_MESHSURFACE {16}; -constexpr int FILTER_LEGENDRE {17}; -constexpr int FILTER_SPH_HARMONICS {18}; -constexpr int FILTER_SPTL_LEGENDRE {19}; -constexpr int FILTER_ZERNIKE {20}; -constexpr int FILTER_PARTICLE {21}; // Mesh types constexpr int MESH_REGULAR {1}; @@ -415,11 +397,6 @@ constexpr int K_ABSORPTION {1}; constexpr int K_TRACKLENGTH {2}; constexpr int LEAKAGE {3}; -// Differential tally independent variables -constexpr int DIFF_DENSITY {1}; -constexpr int DIFF_NUCLIDE_DENSITY {2}; -constexpr int DIFF_TEMPERATURE {3}; - // Miscellaneous constexpr int C_NONE {-1}; constexpr int F90_NONE {0}; //TODO: replace usage of this with C_NONE diff --git a/include/openmc/nuclide.h b/include/openmc/nuclide.h index e551aba613..bc4112281b 100644 --- a/include/openmc/nuclide.h +++ b/include/openmc/nuclide.h @@ -25,10 +25,10 @@ namespace openmc { constexpr double CACHE_INVALID {-1.0}; -//=============================================================================== +//============================================================================== //! Cached microscopic cross sections for a particular nuclide at the current //! energy -//=============================================================================== +//============================================================================== struct NuclideMicroXS { // Microscopic cross sections in barns @@ -63,10 +63,10 @@ struct NuclideMicroXS { //!< * temperature (eV)) }; -//=============================================================================== +//============================================================================== // MATERIALMACROXS contains cached macroscopic cross sections for the material a // particle is traveling through -//=============================================================================== +//============================================================================== struct MaterialMacroXS { double total; //!< macroscopic total xs @@ -82,9 +82,9 @@ struct MaterialMacroXS { double pair_production; //!< macroscopic pair production xs }; -//=============================================================================== +//============================================================================== // Data for a nuclide -//=============================================================================== +//============================================================================== class Nuclide { public: diff --git a/include/openmc/tallies/derivative.h b/include/openmc/tallies/derivative.h new file mode 100644 index 0000000000..585e9702cb --- /dev/null +++ b/include/openmc/tallies/derivative.h @@ -0,0 +1,51 @@ +#ifndef OPENMC_TALLIES_DERIVATIVE_H +#define OPENMC_TALLIES_DERIVATIVE_H + +#include +#include + +#include "pugixml.hpp" + +//============================================================================== +//! Describes a first-order derivative that can be applied to tallies. +//============================================================================== + +namespace openmc { + +struct TallyDerivative { + int id; //!< User-defined identifier + int variable; //!< Independent variable (like temperature) + int diff_material; //!< Material this derivative is applied to + int diff_nuclide; //!< Nuclide this material is applied to + double flux_deriv; //!< Derivative of the current particle's weight + + TallyDerivative() {} + TallyDerivative(pugi::xml_node node); +}; + +} // namespace openmc + +//============================================================================== +// Global variables +//============================================================================== + +// Explicit vector template specialization declaration of threadprivate variable +// outside of the openmc namespace for the picky Intel compiler. +extern template class std::vector; + +namespace openmc { + namespace model { + extern std::vector tally_derivs; + #pragma omp threadprivate(tally_derivs) + + extern std::unordered_map tally_deriv_map; + } + + // Independent variables + //TODO: convert to enum + constexpr int DIFF_DENSITY {1}; + constexpr int DIFF_NUCLIDE_DENSITY {2}; + constexpr int DIFF_TEMPERATURE {3}; +} + +#endif // OPENMC_TALLIES_DERIVATIVE_H diff --git a/src/constants.F90 b/src/constants.F90 index 93a40abdbd..44d91f70ba 100644 --- a/src/constants.F90 +++ b/src/constants.F90 @@ -252,30 +252,10 @@ module constants integer, parameter :: NO_BIN_FOUND = -1 ! Tally filter and map types - integer, parameter :: N_FILTER_TYPES = 22 integer, parameter :: & FILTER_UNIVERSE = 1, & FILTER_MATERIAL = 2, & - FILTER_CELL = 3, & - FILTER_CELLBORN = 4, & - FILTER_SURFACE = 5, & - FILTER_MESH = 6, & - FILTER_ENERGYIN = 7, & - FILTER_ENERGYOUT = 8, & - FILTER_DISTRIBCELL = 9, & - FILTER_MU = 10, & - FILTER_POLAR = 11, & - FILTER_AZIMUTHAL = 12, & - FILTER_DELAYEDGROUP = 13, & - FILTER_ENERGYFUNCTION = 14, & - FILTER_CELLFROM = 15, & - FILTER_MESHSURFACE = 16, & - FILTER_LEGENDRE = 17, & - FILTER_SPH_HARMONICS = 18, & - FILTER_SPTL_LEGENDRE = 19, & - FILTER_ZERNIKE = 20, & - FILTER_ZERNIKE_RADIAL = 21, & - FILTER_PARTICLE = 22 + FILTER_CELL = 3 ! Tally surface current directions integer, parameter :: & diff --git a/src/input_xml.F90 b/src/input_xml.F90 index b92d5141fc..181e52843f 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -949,6 +949,13 @@ contains type(XMLNode), allocatable :: node_deriv_list(:) type(DictEntryCI) :: elem + interface + subroutine read_tally_derivatives(node_ptr) bind(C) + import C_PTR + type(C_PTR) :: node_ptr + end subroutine + end interface + ! Check if tallies.xml exists filename = trim(path_input) // "tallies.xml" inquire(FILE=filename, EXIST=file_exists) @@ -999,6 +1006,8 @@ contains ! ========================================================================== ! READ DATA FOR DERIVATIVES + call read_tally_derivatives(root % ptr) + ! Get pointer list to XML nodes and allocate global array. ! The array is threadprivate so it must be allocated in parallel. call get_node_list(root, "derivative", node_deriv_list) diff --git a/src/tallies/derivative.cpp b/src/tallies/derivative.cpp new file mode 100644 index 0000000000..c78be5973b --- /dev/null +++ b/src/tallies/derivative.cpp @@ -0,0 +1,103 @@ +#include "openmc/error.h" +#include "openmc/nuclide.h" +#include "openmc/settings.h" +#include "openmc/tallies/derivative.h" +#include "openmc/xml_interface.h" + +template class std::vector; + +namespace openmc { + +//============================================================================== +// Global variables +//============================================================================== + +namespace model { + std::vector tally_derivs; + std::unordered_map tally_deriv_map; +} + +//============================================================================== +// TallyDerivative implementation +//============================================================================== + +TallyDerivative::TallyDerivative(pugi::xml_node node) +{ + if (check_for_node(node, "id")) { + id = std::stoi(get_node_value(node, "id")); + } else { + fatal_error("Must specify an ID for elements in the tally " + "XML file"); + } + + if (id <= 0) + fatal_error(" IDs must be an integer greater than zero"); + + std::string variable = get_node_value(node, "variable"); + + if (variable == "density") { + variable = DIFF_DENSITY; + + } else if (variable == "nuclide_density") { + variable = DIFF_NUCLIDE_DENSITY; + + std::string nuclide_name = get_node_value(node, "nuclide"); + bool found = false; + for (auto i = 0; i < data::nuclides.size(); ++i) { + if (data::nuclides[i]->name_ == nuclide_name) { + found = true; + diff_nuclide = i; + } + } + if (!found) { + std::stringstream out; + out << "Could not find the nuclide \"" << nuclide_name + << "\" specified in derivative " << id << " in any material."; + fatal_error(out); + } + + } else if (variable == "temperature") { + variable = DIFF_TEMPERATURE; + + } else { + std::stringstream out; + out << "Unrecognized variable \"" << variable << "\" on derivative " << id; + fatal_error(out); + } + + diff_material = std::stoi(get_node_value(node, "material")); +} + +//============================================================================== +// Non-method functions +//============================================================================== + +extern "C" void +read_tally_derivatives(pugi::xml_node* node) +{ + // Populate the derivatives array. This must be done in parallel because + // the derivatives are threadprivate. + #pragma omp parallel + { + for (auto deriv_node : node->children("derivative")) + model::tally_derivs.push_back(deriv_node); + } + + // Fill the derivative map. + for (auto i = 0; i < model::tally_derivs.size(); ++i) { + auto id = model::tally_derivs[i].id; + auto search = model::tally_deriv_map.find(id); + if (search == model::tally_deriv_map.end()) { + model::tally_deriv_map[id] = i; + } else { + fatal_error("Two or more derivatives use the same unique ID: " + + std::to_string(id)); + } + } + + // Make sure derivatives were not requested for an MG run. + if (!settings::run_CE && !model::tally_derivs.empty()) + fatal_error("Differential tallies not supported in multi-group mode"); +} + +}// namespace openmc diff --git a/src/tallies/trigger_header.F90 b/src/tallies/trigger_header.F90 index 8b3bdbff2d..83d2fa95c0 100644 --- a/src/tallies/trigger_header.F90 +++ b/src/tallies/trigger_header.F90 @@ -2,7 +2,7 @@ module trigger_header use, intrinsic :: ISO_C_BINDING - use constants, only: NONE, N_FILTER_TYPES, ZERO + use constants, only: NONE, ZERO implicit none private From 9f6a7466e24f6bd309bfb37265965eb7180402cd Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sat, 26 Jan 2019 13:53:01 -0500 Subject: [PATCH 07/58] Use interoperable TallyDerivative structs --- include/openmc/tallies/derivative.h | 2 +- include/openmc/tallies/tally.h | 4 +- src/api.F90 | 2 - src/input_xml.F90 | 49 ++++------- src/output.F90 | 8 +- src/state_point.F90 | 17 ++-- src/tallies/derivative.cpp | 35 ++++++-- src/tallies/tally.F90 | 37 +++++---- src/tallies/tally.cpp | 6 ++ src/tallies/tally_derivative_header.F90 | 106 +++++------------------- src/tallies/tally_header.F90 | 31 ++++++- 11 files changed, 139 insertions(+), 158 deletions(-) diff --git a/include/openmc/tallies/derivative.h b/include/openmc/tallies/derivative.h index 585e9702cb..d9202433c3 100644 --- a/include/openmc/tallies/derivative.h +++ b/include/openmc/tallies/derivative.h @@ -12,7 +12,7 @@ namespace openmc { -struct TallyDerivative { +extern "C" struct TallyDerivative { int id; //!< User-defined identifier int variable; //!< Independent variable (like temperature) int diff_material; //!< Material this derivative is applied to diff --git a/include/openmc/tallies/tally.h b/include/openmc/tallies/tally.h index 5f4deb34c6..77e5958ef6 100644 --- a/include/openmc/tallies/tally.h +++ b/include/openmc/tallies/tally.h @@ -39,8 +39,10 @@ public: int surface_filter_ {C_NONE}; int mesh_filter_ {C_NONE}; + int deriv_ {F90_NONE}; //!< Index of a TallyDerivative object for diff tallies. + private: - std::vector filters_; //< Filter indices in global filters array + std::vector filters_; //!< Filter indices in global filters array //! Index strides assigned to each filter to support 1D indexing. std::vector strides_; diff --git a/src/api.F90 b/src/api.F90 index 640ad910a5..cf5670bc9f 100644 --- a/src/api.F90 +++ b/src/api.F90 @@ -66,7 +66,6 @@ contains use settings use simulation_header use surface_header - use tally_derivative_header use tally_filter_header use tally_header use trigger_header @@ -102,7 +101,6 @@ contains call free_memory_mesh() call free_memory_tally() call free_memory_tally_filter() - call free_memory_tally_derivative() call free_memory_bank() call free_memory_cmfd() #ifdef DAGMC diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 181e52843f..87eeac6c37 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -913,6 +913,7 @@ contains integer :: l ! loop over bins integer :: filter_id ! user-specified identifier for filter integer :: tally_id ! user-specified identifier for filter + integer :: deriv_id integer :: i_filt ! index in filters array integer :: i_elem ! index of entry in dictionary integer :: n ! size of arrays in mesh specification @@ -948,6 +949,7 @@ contains type(XMLNode), allocatable :: node_trigger_list(:) type(XMLNode), allocatable :: node_deriv_list(:) type(DictEntryCI) :: elem + type(TallyDerivative), pointer :: deriv interface subroutine read_tally_derivatives(node_ptr) bind(C) @@ -960,12 +962,6 @@ contains filename = trim(path_input) // "tallies.xml" inquire(FILE=filename, EXIST=file_exists) if (.not. file_exists) then - ! We need to allocate tally_derivs to avoid segfaults. Also needs to be - ! done in parallel because tally derivs are threadprivate. -!$omp parallel - allocate(tally_derivs(0)) -!$omp end parallel - ! Since a tallies.xml file is optional, no error is issued here return end if @@ -1008,28 +1004,10 @@ contains call read_tally_derivatives(root % ptr) - ! Get pointer list to XML nodes and allocate global array. - ! The array is threadprivate so it must be allocated in parallel. call get_node_list(root, "derivative", node_deriv_list) -!$omp parallel - allocate(tally_derivs(size(node_deriv_list))) -!$omp end parallel - - ! Make sure this is not an MG run. - if (.not. run_CE .and. size(node_deriv_list) > 0) then - call fatal_error("Differential tallies not supported in multi-group mode") - end if - - ! Read derivative attributes. do i = 1, size(node_deriv_list) -!$omp parallel -!$omp critical (ReadTallyDeriv) - call tally_derivs(i) % from_xml(node_deriv_list(i)) -!$omp end critical (ReadTallyDeriv) -!$omp end parallel - - ! Update tally derivative dictionary - call tally_deriv_dict % set(tally_derivs(i) % id, i) + deriv => tally_deriv_c(i - 1) + call tally_deriv_dict % set(deriv % id, i) end do ! ========================================================================== @@ -1618,13 +1596,13 @@ contains ! Check for a tally derivative. if (check_for_node(node_tal, "derivative")) then - ! Temporarily store the derivative id. - call get_node_value(node_tal, "derivative", t % deriv) + call get_node_value(node_tal, "derivative", deriv_id) ! Find the derivative with the given id, and store it's index. - do j = 1, size(tally_derivs) - if (tally_derivs(j) % id == t % deriv) then - t % deriv = j + do j = 1, n_tally_derivs() + deriv => tally_deriv_c(j - 1) + if (deriv % id == deriv_id) then + call t % set_deriv(j) ! Only analog or collision estimators are supported for differential ! tallies. if (t % estimator == ESTIMATOR_TRACKLENGTH) then @@ -1633,15 +1611,16 @@ contains ! We found the derivative we were looking for; exit the do loop. exit end if - if (j == size(tally_derivs)) then + if (j == n_tally_derivs()) then call fatal_error("Could not find derivative " & - // trim(to_str(t % deriv)) // " specified on tally " & + // trim(to_str(deriv_id)) // " specified on tally " & // trim(to_str(t % id))) end if end do - if (tally_derivs(t % deriv) % variable == DIFF_NUCLIDE_DENSITY & - .or. tally_derivs(t % deriv) % variable == DIFF_TEMPERATURE) then + deriv => tally_deriv_c(t % deriv() - 1) + if (deriv % variable == DIFF_NUCLIDE_DENSITY & + .or. deriv % variable == DIFF_TEMPERATURE) then if (any(t % nuclide_bins == -1)) then if (has_energyout) then call fatal_error("Error on tally " // trim(to_str(t % id)) & diff --git a/src/output.F90 b/src/output.F90 index 6efd2c6e65..04e35c17d4 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -471,6 +471,7 @@ contains ! to be applied at write-time integer, allocatable :: filter_bins(:) character(MAX_WORD_LEN) :: temp_name + type(TallyDerivative), pointer :: deriv ! Skip if there are no tallies if (n_tallies == 0) return @@ -530,8 +531,9 @@ contains endif ! Write derivative information. - if (t % deriv /= NONE) then - associate(deriv => tally_derivs(t % deriv)) + if (t % deriv() /= NONE) then + !associate(deriv => tally_derivs(t % deriv())) + deriv => tally_deriv_c(t % deriv() - 1) select case (deriv % variable) case (DIFF_DENSITY) write(unit=unit_tally, fmt="(' Density derivative Material ',A)") & @@ -548,7 +550,7 @@ contains call fatal_error("Differential tally dependent variable for tally "& // trim(to_str(t % id)) // " not defined in output.F90.") end select - end associate + !end associate end if ! WARNING: Admittedly, the logic for moving for printing results is diff --git a/src/state_point.F90 b/src/state_point.F90 index fba79dd843..f9fde01912 100644 --- a/src/state_point.F90 +++ b/src/state_point.F90 @@ -28,7 +28,7 @@ module state_point use string, only: to_str, count_digits, zero_padded, to_f_string use tally_header use tally_filter_header - use tally_derivative_header, only: tally_derivs + use tally_derivative_header use timer_header implicit none @@ -69,6 +69,7 @@ contains character(len=:, kind=C_CHAR), allocatable :: filename_ character(MAX_WORD_LEN, kind=C_CHAR) :: temp_name logical :: parallel + type(TallyDerivative), pointer :: deriv interface subroutine meshes_to_hdf5(group) bind(C) @@ -174,10 +175,11 @@ contains call meshes_to_hdf5(tallies_group) ! Write information for derivatives. - if (size(tally_derivs) > 0) then + if (n_tally_derivs() > 0) then derivs_group = create_group(tallies_group, "derivatives") - do i = 1, size(tally_derivs) - associate(deriv => tally_derivs(i)) + do i = 1, n_tally_derivs() + !associate(deriv => tally_derivs(i)) + deriv => tally_deriv_c(i - 1) deriv_group = create_group(derivs_group, "derivative " & // trim(to_str(deriv % id))) select case (deriv % variable) @@ -200,7 +202,7 @@ contains &state_point.F90.") end select call close_group(deriv_group) - end associate + !end associate end do call close_group(derivs_group) end if @@ -303,9 +305,10 @@ contains deallocate(str_array) ! Write derivative information. - if (tally % deriv /= NONE) then + if (tally % deriv() /= NONE) then + deriv => tally_deriv_c(tally % deriv() - 1) call write_dataset(tally_group, "derivative", & - tally_derivs(tally % deriv) % id) + deriv % id) end if ! Write scores. diff --git a/src/tallies/derivative.cpp b/src/tallies/derivative.cpp index c78be5973b..66889708ae 100644 --- a/src/tallies/derivative.cpp +++ b/src/tallies/derivative.cpp @@ -33,12 +33,12 @@ TallyDerivative::TallyDerivative(pugi::xml_node node) if (id <= 0) fatal_error(" IDs must be an integer greater than zero"); - std::string variable = get_node_value(node, "variable"); + std::string variable_str = get_node_value(node, "variable"); - if (variable == "density") { + if (variable_str == "density") { variable = DIFF_DENSITY; - } else if (variable == "nuclide_density") { + } else if (variable_str == "nuclide_density") { variable = DIFF_NUCLIDE_DENSITY; std::string nuclide_name = get_node_value(node, "nuclide"); @@ -46,7 +46,8 @@ TallyDerivative::TallyDerivative(pugi::xml_node node) for (auto i = 0; i < data::nuclides.size(); ++i) { if (data::nuclides[i]->name_ == nuclide_name) { found = true; - diff_nuclide = i; + //TODO: off-by-one + diff_nuclide = i + 1; } } if (!found) { @@ -56,12 +57,13 @@ TallyDerivative::TallyDerivative(pugi::xml_node node) fatal_error(out); } - } else if (variable == "temperature") { + } else if (variable_str == "temperature") { variable = DIFF_TEMPERATURE; } else { std::stringstream out; - out << "Unrecognized variable \"" << variable << "\" on derivative " << id; + out << "Unrecognized variable \"" << variable_str + << "\" on derivative " << id; fatal_error(out); } @@ -100,4 +102,25 @@ read_tally_derivatives(pugi::xml_node* node) fatal_error("Differential tallies not supported in multi-group mode"); } +/* +//! Set the flux derivatives on differential tallies to zero. +extern "C" void +zero_flux_derivs_c() +{ + for (auto& deriv : model::tally_derivs) deriv.flux_deriv = 0.; +} +*/ + +//============================================================================== +// Fortran interop +//============================================================================== + +extern "C" int n_tally_derivs() {return model::tally_derivs.size();} + +extern "C" TallyDerivative* +tally_deriv_c(int i) +{ + return &(model::tally_derivs[i]); +} + }// namespace openmc diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index 2b274364ed..3122bf866b 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -17,7 +17,7 @@ module tally use settings use simulation_header use string, only: to_str - use tally_derivative_header, only: tally_derivs + use tally_derivative_header use tally_filter use tally_header @@ -1184,7 +1184,7 @@ contains !######################################################################### ! Add derivative information on score for differential tallies. - if (t % deriv /= NONE) then + if (t % deriv() /= NONE) then call apply_derivative_to_score(p, t, i_nuclide, atom_density, & score_bin, score) end if @@ -2375,7 +2375,7 @@ contains ! Add derivative information for differential tallies. Note that the ! i_nuclide and atom_density arguments do not matter since this is an ! analog estimator. - if (t % deriv /= NONE) then + if (t % deriv() /= NONE) then call apply_derivative_to_score(p, t, 0, ZERO, SCORE_NU_FISSION, score) end if @@ -2992,6 +2992,7 @@ contains integer, intent(in) :: score_bin real(8), intent(inout) :: score + type(TallyDerivative), pointer :: deriv integer :: l logical :: scoring_diff_nuclide real(8) :: flux_deriv @@ -3004,10 +3005,12 @@ contains ! where (1/f * d_f/d_p) is the (logarithmic) flux derivative and p is the ! perturbated variable. - associate(deriv => tally_derivs(t % deriv)) + !associate(deriv => tally_derivs(t % deriv())) + deriv => tally_deriv_c(t % deriv() - 1) flux_deriv = deriv % flux_deriv - select case (tally_derivs(t % deriv) % variable) + !select case (tally_derivs(t % deriv()) % variable) + select case (deriv % variable) !========================================================================= ! Density derivative: @@ -3546,7 +3549,7 @@ contains &analog and collision estimators.") end select end select - end associate + !end associate end subroutine apply_derivative_to_score !=============================================================================== @@ -3558,14 +3561,16 @@ contains type(Particle), intent(in) :: p real(8), intent(in) :: distance ! Neutron flight distance + type(TallyDerivative), pointer :: deriv integer :: i, l real(8) :: dsig_s, dsig_a, dsig_f ! A void material cannot be perturbed so it will not affect flux derivatives if (p % material == MATERIAL_VOID) return - do i = 1, size(tally_derivs) - associate(deriv => tally_derivs(i)) + do i = 1, n_tally_derivs() + !associate(deriv => tally_derivs(i)) + deriv => tally_deriv_c(i - 1) select case (deriv % variable) case (DIFF_DENSITY) @@ -3609,7 +3614,7 @@ contains end if end associate end select - end associate + !end associate end do end subroutine score_track_derivative @@ -3632,14 +3637,16 @@ contains subroutine score_collision_derivative(p) type(Particle), intent(in) :: p + type(TallyDerivative), pointer :: deriv integer :: i, j, l real(8) :: dsig_s, dsig_a, dsig_f ! A void material cannot be perturbed so it will not affect flux derivatives if (p % material == MATERIAL_VOID) return - do i = 1, size(tally_derivs) - associate(deriv => tally_derivs(i)) + do i = 1, n_tally_derivs() + !associate(deriv => tally_derivs(i)) + deriv => tally_deriv_c(i - 1) select case (deriv % variable) case (DIFF_DENSITY) @@ -3702,7 +3709,7 @@ contains end if end associate end select - end associate + !end associate end do end subroutine score_collision_derivative @@ -3712,8 +3719,10 @@ contains subroutine zero_flux_derivs() integer :: i - do i = 1, size(tally_derivs) - tally_derivs(i) % flux_deriv = ZERO + type(TallyDerivative), pointer :: deriv + do i = 1, n_tally_derivs() + deriv => tally_deriv_c(i - 1) + deriv % flux_deriv = ZERO end do end subroutine zero_flux_derivs diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 38ead0f080..169e886d6e 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -3,6 +3,7 @@ #include "openmc/capi.h" #include "openmc/constants.h" #include "openmc/error.h" +#include "openmc/tallies/derivative.h" #include "openmc/tallies/filter.h" #include "openmc/tallies/filter_energy.h" #include "openmc/tallies/filter_delayedgroup.h" @@ -175,6 +176,7 @@ free_memory_tally_c() #pragma omp parallel { simulation::filter_matches.clear(); + model::tally_derivs.clear(); } model::tally_filters.clear(); @@ -260,6 +262,10 @@ extern "C" { int tally_get_mesh_filter_c(Tally* tally) {return tally->mesh_filter_;} + + int tally_get_deriv_c(Tally* tally) {return tally->deriv_;} + + int tally_set_deriv_c(Tally* tally, int deriv) {tally->deriv_ = deriv;} } } // namespace openmc diff --git a/src/tallies/tally_derivative_header.F90 b/src/tallies/tally_derivative_header.F90 index 304ae5e456..efeddbb36c 100644 --- a/src/tallies/tally_derivative_header.F90 +++ b/src/tallies/tally_derivative_header.F90 @@ -1,104 +1,38 @@ module tally_derivative_header - use constants - use dict_header, only: DictIntInt, EMPTY - use error, only: fatal_error - use nuclide_header, only: nuclide_dict - use string, only: to_str, to_lower - use xml_interface + use, intrinsic :: ISO_C_BINDING + + use dict_header, only: DictIntInt implicit none - private - public :: free_memory_tally_derivative !=============================================================================== ! TALLYDERIVATIVE describes a first-order derivative that can be applied to ! tallies. !=============================================================================== - type, public :: TallyDerivative - integer :: id - integer :: variable - integer :: diff_material - integer :: diff_nuclide - real(8) :: flux_deriv - contains - procedure :: from_xml + type, bind(C), public :: TallyDerivative + integer(C_INT) :: id + integer(C_INT) :: variable + integer(C_INT) :: diff_material + integer(C_INT) :: diff_nuclide + real(C_DOUBLE) :: flux_deriv end type TallyDerivative - type(TallyDerivative), public, allocatable :: tally_derivs(:) -!$omp threadprivate(tally_derivs) - ! Dictionary that maps user IDs to indices in 'tally_derivs' type(DictIntInt), public :: tally_deriv_dict -contains + interface + function n_tally_derivs() result(n) bind(C) + import C_INT + integer(C_INT) :: n + end function - subroutine from_xml(this, node) - class(TallyDerivative), intent(inout) :: this - type(XMLNode), intent(in) :: node - - character(MAX_WORD_LEN) :: temp_str - character(MAX_WORD_LEN) :: word - integer :: val - - ! Copy the derivative id. - if (check_for_node(node, "id")) then - call get_node_value(node, "id", this % id) - else - call fatal_error("Must specify an ID for elements in the& - & tally XML file") - end if - - ! Make sure the id is > 0. - if (this % id <= 0) then - call fatal_error(" IDs must be an integer greater than & - &zero") - end if - - ! Make sure this id has not already been used. - if (tally_deriv_dict % has(this % id)) then - call fatal_error("Two or more 's use the same unique & - &ID: " // trim(to_str(this % id))) - end if - - ! Read the independent variable name. - call get_node_value(node, "variable", temp_str) - temp_str = to_lower(temp_str) - - select case(temp_str) - case("density") - this % variable = DIFF_DENSITY - - case("nuclide_density") - this % variable = DIFF_NUCLIDE_DENSITY - - call get_node_value(node, "nuclide", word) - word = trim(to_lower(word)) - val = nuclide_dict % get(word) - if (val == EMPTY) then - call fatal_error("Could not find the nuclide " & - // trim(word) // " specified in derivative " & - // trim(to_str(this % id)) // " in any material.") - end if - this % diff_nuclide = val - - case("temperature") - this % variable = DIFF_TEMPERATURE - end select - - call get_node_value(node, "material", this % diff_material) - - end subroutine from_xml - -!=============================================================================== -! FREE_MEMORY_TALLY_DERIVATIVE deallocates global arrays defined in this module -!=============================================================================== - - subroutine free_memory_tally_derivative() -!$omp parallel - if (allocated(tally_derivs)) deallocate(tally_derivs) -!$omp end parallel - end subroutine free_memory_tally_derivative + function tally_deriv_c(i) result(deriv) bind(C) + import C_INT, TallyDerivative + integer(C_INT), value :: i + type(TallyDerivative), pointer :: deriv + end function + end interface end module tally_derivative_header diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index 0b216e3adf..f4300789e1 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -93,9 +93,6 @@ module tally_header integer :: n_triggers = 0 ! # of triggers type(TriggerObject), allocatable :: triggers(:) ! Array of triggers - ! Index for the TallyDerivative for differential tallies. - integer :: deriv = NONE - contains procedure :: accumulate => tally_accumulate procedure :: allocate_results => tally_allocate_results @@ -110,6 +107,8 @@ module tally_header procedure :: delayedgroup_filter => tally_get_delayedgroup_filter procedure :: surface_filter => tally_get_surface_filter procedure :: mesh_filter => tally_get_mesh_filter + procedure :: deriv => tally_get_deriv + procedure :: set_deriv => tally_set_deriv end type TallyObject type, public :: TallyContainer @@ -404,6 +403,32 @@ contains filt = tally_get_mesh_filter_c(this % ptr) end function + function tally_get_deriv(this) result(deriv) + class(TallyObject) :: this + integer(C_INT) :: deriv + interface + function tally_get_deriv_c(tally) result(deriv) bind(C) + import C_PTR, C_INT + type(C_PTR), value :: tally + integer(C_INT) :: deriv + end function + end interface + deriv = tally_get_deriv_c(this % ptr) + end function + + subroutine tally_set_deriv(this, deriv) + class(TallyObject) :: this + integer(C_INT) :: deriv + interface + subroutine tally_set_deriv_c(tally, deriv) bind(C) + import C_PTR, C_INT + type(C_PTR), value :: tally + integer(C_INT), value :: deriv + end subroutine + end interface + call tally_set_deriv_c(this % ptr, deriv) + end subroutine + !=============================================================================== ! CONFIGURE_TALLIES initializes several data structures related to tallies. This ! is called after the basic tally data has already been read from the From fe6fba2954372bf7deb3bdc34e51bf44be830df7 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sat, 26 Jan 2019 14:03:30 -0500 Subject: [PATCH 08/58] Use 0-based indexing for tally derivatives --- include/openmc/tallies/tally.h | 2 +- src/input_xml.F90 | 11 +++++------ src/output.F90 | 4 ++-- src/state_point.F90 | 8 ++++---- src/tallies/tally.F90 | 18 +++++++++--------- 5 files changed, 21 insertions(+), 22 deletions(-) diff --git a/include/openmc/tallies/tally.h b/include/openmc/tallies/tally.h index 77e5958ef6..e67ebb7843 100644 --- a/include/openmc/tallies/tally.h +++ b/include/openmc/tallies/tally.h @@ -39,7 +39,7 @@ public: int surface_filter_ {C_NONE}; int mesh_filter_ {C_NONE}; - int deriv_ {F90_NONE}; //!< Index of a TallyDerivative object for diff tallies. + int deriv_ {C_NONE}; //!< Index of a TallyDerivative object for diff tallies. private: std::vector filters_; //!< Filter indices in global filters array diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 87eeac6c37..5972f3057d 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -1004,9 +1004,8 @@ contains call read_tally_derivatives(root % ptr) - call get_node_list(root, "derivative", node_deriv_list) - do i = 1, size(node_deriv_list) - deriv => tally_deriv_c(i - 1) + do i = 0, n_tally_derivs() - 1 + deriv => tally_deriv_c(i) call tally_deriv_dict % set(deriv % id, i) end do @@ -1599,8 +1598,8 @@ contains call get_node_value(node_tal, "derivative", deriv_id) ! Find the derivative with the given id, and store it's index. - do j = 1, n_tally_derivs() - deriv => tally_deriv_c(j - 1) + do j = 0, n_tally_derivs() - 1 + deriv => tally_deriv_c(j) if (deriv % id == deriv_id) then call t % set_deriv(j) ! Only analog or collision estimators are supported for differential @@ -1618,7 +1617,7 @@ contains end if end do - deriv => tally_deriv_c(t % deriv() - 1) + deriv => tally_deriv_c(t % deriv()) if (deriv % variable == DIFF_NUCLIDE_DENSITY & .or. deriv % variable == DIFF_TEMPERATURE) then if (any(t % nuclide_bins == -1)) then diff --git a/src/output.F90 b/src/output.F90 index 04e35c17d4..2487153586 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -531,9 +531,9 @@ contains endif ! Write derivative information. - if (t % deriv() /= NONE) then + if (t % deriv() /= C_NONE) then !associate(deriv => tally_derivs(t % deriv())) - deriv => tally_deriv_c(t % deriv() - 1) + deriv => tally_deriv_c(t % deriv()) select case (deriv % variable) case (DIFF_DENSITY) write(unit=unit_tally, fmt="(' Density derivative Material ',A)") & diff --git a/src/state_point.F90 b/src/state_point.F90 index f9fde01912..5dd98b3ea4 100644 --- a/src/state_point.F90 +++ b/src/state_point.F90 @@ -177,9 +177,9 @@ contains ! Write information for derivatives. if (n_tally_derivs() > 0) then derivs_group = create_group(tallies_group, "derivatives") - do i = 1, n_tally_derivs() + do i = 0, n_tally_derivs() - 1 !associate(deriv => tally_derivs(i)) - deriv => tally_deriv_c(i - 1) + deriv => tally_deriv_c(i) deriv_group = create_group(derivs_group, "derivative " & // trim(to_str(deriv % id))) select case (deriv % variable) @@ -305,8 +305,8 @@ contains deallocate(str_array) ! Write derivative information. - if (tally % deriv() /= NONE) then - deriv => tally_deriv_c(tally % deriv() - 1) + if (tally % deriv() /= C_NONE) then + deriv => tally_deriv_c(tally % deriv()) call write_dataset(tally_group, "derivative", & deriv % id) end if diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index 3122bf866b..d3ad9877f1 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -1184,7 +1184,7 @@ contains !######################################################################### ! Add derivative information on score for differential tallies. - if (t % deriv() /= NONE) then + if (t % deriv() /= C_NONE) then call apply_derivative_to_score(p, t, i_nuclide, atom_density, & score_bin, score) end if @@ -2375,7 +2375,7 @@ contains ! Add derivative information for differential tallies. Note that the ! i_nuclide and atom_density arguments do not matter since this is an ! analog estimator. - if (t % deriv() /= NONE) then + if (t % deriv() /= C_NONE) then call apply_derivative_to_score(p, t, 0, ZERO, SCORE_NU_FISSION, score) end if @@ -3006,7 +3006,7 @@ contains ! perturbated variable. !associate(deriv => tally_derivs(t % deriv())) - deriv => tally_deriv_c(t % deriv() - 1) + deriv => tally_deriv_c(t % deriv()) flux_deriv = deriv % flux_deriv !select case (tally_derivs(t % deriv()) % variable) @@ -3568,9 +3568,9 @@ contains ! A void material cannot be perturbed so it will not affect flux derivatives if (p % material == MATERIAL_VOID) return - do i = 1, n_tally_derivs() + do i = 0, n_tally_derivs() - 1 !associate(deriv => tally_derivs(i)) - deriv => tally_deriv_c(i - 1) + deriv => tally_deriv_c(i) select case (deriv % variable) case (DIFF_DENSITY) @@ -3644,9 +3644,9 @@ contains ! A void material cannot be perturbed so it will not affect flux derivatives if (p % material == MATERIAL_VOID) return - do i = 1, n_tally_derivs() + do i = 0, n_tally_derivs() - 1 !associate(deriv => tally_derivs(i)) - deriv => tally_deriv_c(i - 1) + deriv => tally_deriv_c(i) select case (deriv % variable) case (DIFF_DENSITY) @@ -3720,8 +3720,8 @@ contains subroutine zero_flux_derivs() integer :: i type(TallyDerivative), pointer :: deriv - do i = 1, n_tally_derivs() - deriv => tally_deriv_c(i - 1) + do i = 0, n_tally_derivs() - 1 + deriv => tally_deriv_c(i) deriv % flux_deriv = ZERO end do end subroutine zero_flux_derivs From 69376894cc80f935e47df29692ed68c3ef05705c Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sat, 26 Jan 2019 14:32:46 -0500 Subject: [PATCH 09/58] Minor tally derivative cleanup --- src/input_xml.F90 | 6 ------ src/tallies/derivative.cpp | 4 +--- src/tallies/tally.F90 | 18 +++++------------- src/tallies/tally_derivative_header.F90 | 5 ----- 4 files changed, 6 insertions(+), 27 deletions(-) diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 5972f3057d..88c38f9d40 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -947,7 +947,6 @@ contains type(XMLNode), allocatable :: node_tal_list(:) type(XMLNode), allocatable :: node_filt_list(:) type(XMLNode), allocatable :: node_trigger_list(:) - type(XMLNode), allocatable :: node_deriv_list(:) type(DictEntryCI) :: elem type(TallyDerivative), pointer :: deriv @@ -1004,11 +1003,6 @@ contains call read_tally_derivatives(root % ptr) - do i = 0, n_tally_derivs() - 1 - deriv => tally_deriv_c(i) - call tally_deriv_dict % set(deriv % id, i) - end do - ! ========================================================================== ! READ FILTER DATA diff --git a/src/tallies/derivative.cpp b/src/tallies/derivative.cpp index 66889708ae..5908ce3387 100644 --- a/src/tallies/derivative.cpp +++ b/src/tallies/derivative.cpp @@ -102,14 +102,12 @@ read_tally_derivatives(pugi::xml_node* node) fatal_error("Differential tallies not supported in multi-group mode"); } -/* //! Set the flux derivatives on differential tallies to zero. extern "C" void -zero_flux_derivs_c() +zero_flux_derivs() { for (auto& deriv : model::tally_derivs) deriv.flux_deriv = 0.; } -*/ //============================================================================== // Fortran interop diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index d3ad9877f1..57593cb3a8 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -46,6 +46,11 @@ module tally end subroutine score_analog_tally_ end interface + interface + subroutine zero_flux_derivs() bind(C) + end subroutine + end interface + contains !=============================================================================== @@ -3713,19 +3718,6 @@ contains end do end subroutine score_collision_derivative -!=============================================================================== -! ZERO_FLUX_DERIVS Set the flux derivatives on differential tallies to zero. -!=============================================================================== - - subroutine zero_flux_derivs() - integer :: i - type(TallyDerivative), pointer :: deriv - do i = 0, n_tally_derivs() - 1 - deriv => tally_deriv_c(i) - deriv % flux_deriv = ZERO - end do - end subroutine zero_flux_derivs - !=============================================================================== ! ACCUMULATE_TALLIES accumulates the sum of the contributions from each history ! within the batch to a new random variable diff --git a/src/tallies/tally_derivative_header.F90 b/src/tallies/tally_derivative_header.F90 index efeddbb36c..d2ba11d799 100644 --- a/src/tallies/tally_derivative_header.F90 +++ b/src/tallies/tally_derivative_header.F90 @@ -2,8 +2,6 @@ module tally_derivative_header use, intrinsic :: ISO_C_BINDING - use dict_header, only: DictIntInt - implicit none !=============================================================================== @@ -19,9 +17,6 @@ module tally_derivative_header real(C_DOUBLE) :: flux_deriv end type TallyDerivative - ! Dictionary that maps user IDs to indices in 'tally_derivs' - type(DictIntInt), public :: tally_deriv_dict - interface function n_tally_derivs() result(n) bind(C) import C_INT From 8c4a74ec849a546720e2d3cff8d01d5db73c81c9 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sat, 26 Jan 2019 15:13:32 -0500 Subject: [PATCH 10/58] Move tally % estimator and tally % active to C++ --- include/openmc/tallies/tally.h | 12 +++++ src/input_xml.F90 | 40 +++++++-------- src/state_point.F90 | 2 +- src/tallies/tally.F90 | 89 ++++++++++++++++---------------- src/tallies/tally.cpp | 34 +++++++++++++ src/tallies/tally_header.F90 | 93 ++++++++++++++++++---------------- 6 files changed, 162 insertions(+), 108 deletions(-) diff --git a/include/openmc/tallies/tally.h b/include/openmc/tallies/tally.h index e67ebb7843..4b0514fd6a 100644 --- a/include/openmc/tallies/tally.h +++ b/include/openmc/tallies/tally.h @@ -30,6 +30,11 @@ public: int32_t n_filter_bins() const {return n_filter_bins_;} + //! Event type that contributes to this tally + int estimator_ {ESTIMATOR_TRACKLENGTH}; + + bool active_ {false}; + // We need to have quick access to some filters. The following gives indices // for various filters that could be in the tally or C_NONE if they are not // present. @@ -58,6 +63,13 @@ extern "C" double total_weight; namespace model { extern std::vector> tallies; + + extern std::vector active_analog_tallies; + extern std::vector active_tracklength_tallies; + extern std::vector active_meshsurf_tallies; + extern std::vector active_collision_tallies; + extern std::vector active_tallies; + extern std::vector active_surface_tallies; } // Threadprivate variables diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 88c38f9d40..454956b46e 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -1174,19 +1174,19 @@ contains do j = 1, t % n_filters() select type (filt => filters(t % filter(j)) % obj) type is (EnergyoutFilter) - t % estimator = ESTIMATOR_ANALOG + call t % set_estimator(ESTIMATOR_ANALOG) type is (LegendreFilter) - t % estimator = ESTIMATOR_ANALOG + call t % set_estimator(ESTIMATOR_ANALOG) type is (SphericalHarmonicsFilter) if (filt % cosine() == COSINE_SCATTER) then - t % estimator = ESTIMATOR_ANALOG + call t % set_estimator(ESTIMATOR_ANALOG) end if type is (SpatialLegendreFilter) - t % estimator = ESTIMATOR_COLLISION + call t % set_estimator(ESTIMATOR_COLLISION) type is (ZernikeFilter) - t % estimator = ESTIMATOR_COLLISION + call t % set_estimator(ESTIMATOR_COLLISION) type is (ZernikeRadialFilter) - t % estimator = ESTIMATOR_COLLISION + call t % set_estimator(ESTIMATOR_COLLISION) end select end do @@ -1312,7 +1312,7 @@ contains t % score_bins(j) = SCORE_SCATTER if (has_energyout .or. has_legendre) then ! Set tally estimator to analog - t % estimator = ESTIMATOR_ANALOG + call t % set_estimator(ESTIMATOR_ANALOG) end if case ('nu-scatter') @@ -1322,11 +1322,11 @@ contains ! (MG mode has all data available without a collision being ! necessary) if (run_CE) then - t % estimator = ESTIMATOR_ANALOG + call t % set_estimator(ESTIMATOR_ANALOG) else if (has_energyout .or. has_legendre) then ! Set tally estimator to analog - t % estimator = ESTIMATOR_ANALOG + call t % set_estimator(ESTIMATOR_ANALOG) end if end if @@ -1358,7 +1358,7 @@ contains t % score_bins(j) = SCORE_NU_FISSION if (has_energyout) then ! Set tally estimator to analog - t % estimator = ESTIMATOR_ANALOG + call t % set_estimator(ESTIMATOR_ANALOG) end if case ('decay-rate') t % score_bins(j) = SCORE_DECAY_RATE @@ -1366,13 +1366,13 @@ contains t % score_bins(j) = SCORE_DELAYED_NU_FISSION if (has_energyout) then ! Set tally estimator to analog - t % estimator = ESTIMATOR_ANALOG + call t % set_estimator(ESTIMATOR_ANALOG) end if case ('prompt-nu-fission') t % score_bins(j) = SCORE_PROMPT_NU_FISSION if (has_energyout) then ! Set tally estimator to analog - t % estimator = ESTIMATOR_ANALOG + call t % set_estimator(ESTIMATOR_ANALOG) end if case ('kappa-fission') t % score_bins(j) = SCORE_KAPPA_FISSION @@ -1563,7 +1563,7 @@ contains type is (ParticleFilter) do l = 1, filt % n_bins if (filt % particles(l) == ELECTRON .or. filt % particles(l) == POSITRON) then - t % estimator = ESTIMATOR_ANALOG + call t % set_estimator(ESTIMATOR_ANALOG) end if end do end select @@ -1598,8 +1598,8 @@ contains call t % set_deriv(j) ! Only analog or collision estimators are supported for differential ! tallies. - if (t % estimator == ESTIMATOR_TRACKLENGTH) then - t % estimator = ESTIMATOR_COLLISION + if (t % estimator() == ESTIMATOR_TRACKLENGTH) then + call t % set_estimator(ESTIMATOR_COLLISION) end if ! We found the derivative we were looking for; exit the do loop. exit @@ -1807,29 +1807,29 @@ contains call get_node_value(node_tal, "estimator", temp_str) select case(trim(temp_str)) case ('analog') - t % estimator = ESTIMATOR_ANALOG + call t % set_estimator(ESTIMATOR_ANALOG) case ('tracklength', 'track-length', 'pathlength', 'path-length') ! If the estimator was set to an analog estimator, this means the ! tally needs post-collision information - if (t % estimator == ESTIMATOR_ANALOG) then + if (t % estimator() == ESTIMATOR_ANALOG) then call fatal_error("Cannot use track-length estimator for tally " & // to_str(t % id)) end if ! Set estimator to track-length estimator - t % estimator = ESTIMATOR_TRACKLENGTH + call t % set_estimator(ESTIMATOR_TRACKLENGTH) case ('collision') ! If the estimator was set to an analog estimator, this means the ! tally needs post-collision information - if (t % estimator == ESTIMATOR_ANALOG) then + if (t % estimator() == ESTIMATOR_ANALOG) then call fatal_error("Cannot use collision estimator for tally " & // to_str(t % id)) end if ! Set estimator to collision estimator - t % estimator = ESTIMATOR_COLLISION + call t % set_estimator(ESTIMATOR_COLLISION) case default call fatal_error("Invalid estimator '" // trim(temp_str) & diff --git a/src/state_point.F90 b/src/state_point.F90 index 5dd98b3ea4..4fbc097954 100644 --- a/src/state_point.F90 +++ b/src/state_point.F90 @@ -254,7 +254,7 @@ contains ! Write the name for this tally call write_dataset(tally_group, "name", tally % name) - select case(tally % estimator) + select case(tally % estimator()) case (ESTIMATOR_ANALOG) call write_dataset(tally_group, "estimator", "analog") case (ESTIMATOR_TRACKLENGTH) diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index 57593cb3a8..8420c02cf2 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -125,7 +125,7 @@ contains case (SCORE_FLUX) - if (t % estimator == ESTIMATOR_ANALOG) then + if (t % estimator() == ESTIMATOR_ANALOG) then ! All events score to a flux bin. We actually use a collision ! estimator in place of an analog one since there is no way to count ! 'events' exactly for the flux @@ -150,7 +150,7 @@ contains case (SCORE_TOTAL) - if (t % estimator == ESTIMATOR_ANALOG) then + if (t % estimator() == ESTIMATOR_ANALOG) then ! All events will score to the total reaction rate. We can just ! use the weight of the particle entering the collision as the ! score @@ -172,7 +172,7 @@ contains case (SCORE_INVERSE_VELOCITY) - if (t % estimator == ESTIMATOR_ANALOG) then + if (t % estimator() == ESTIMATOR_ANALOG) then ! All events score to an inverse velocity bin. We actually use a ! collision estimator in place of an analog one since there is no way ! to count 'events' exactly for the inverse velocity @@ -197,7 +197,7 @@ contains case (SCORE_SCATTER) - if (t % estimator == ESTIMATOR_ANALOG) then + if (t % estimator() == ESTIMATOR_ANALOG) then ! Skip any event where the particle didn't scatter if (p % event /= EVENT_SCATTER) cycle SCORE_LOOP ! Since only scattering events make it here, again we can use @@ -239,7 +239,7 @@ contains case (SCORE_ABSORPTION) - if (t % estimator == ESTIMATOR_ANALOG) then + if (t % estimator() == ESTIMATOR_ANALOG) then if (survival_biasing) then ! No absorption events actually occur if survival biasing is on -- ! just use weight absorbed in survival biasing @@ -265,7 +265,7 @@ contains if (material_xs % absorption == ZERO) cycle SCORE_LOOP - if (t % estimator == ESTIMATOR_ANALOG) then + if (t % estimator() == ESTIMATOR_ANALOG) then if (survival_biasing) then ! No fission events occur if survival biasing is on -- need to ! calculate fraction of absorptions that would have resulted in @@ -299,7 +299,7 @@ contains if (material_xs % absorption == ZERO) cycle SCORE_LOOP - if (t % estimator == ESTIMATOR_ANALOG) then + if (t % estimator() == ESTIMATOR_ANALOG) then if (survival_biasing .or. p % fission) then if (t % energyout_filter() > 0) then ! Normally, we only need to make contributions to one scoring @@ -345,7 +345,7 @@ contains if (material_xs % absorption == ZERO) cycle SCORE_LOOP - if (t % estimator == ESTIMATOR_ANALOG) then + if (t % estimator() == ESTIMATOR_ANALOG) then if (survival_biasing .or. p % fission) then if (t % energyout_filter() > 0) then ! Normally, we only need to make contributions to one scoring @@ -413,7 +413,7 @@ contains ! Set the delayedgroup filter index and the number of delayed group bins dg_filter = t % delayedgroup_filter() - if (t % estimator == ESTIMATOR_ANALOG) then + if (t % estimator() == ESTIMATOR_ANALOG) then if (survival_biasing .or. p % fission) then if (t % energyout_filter() > 0) then ! Normally, we only need to make contributions to one scoring @@ -607,7 +607,7 @@ contains ! Set the delayedgroup filter index dg_filter = t % delayedgroup_filter() - if (t % estimator == ESTIMATOR_ANALOG) then + if (t % estimator() == ESTIMATOR_ANALOG) then if (survival_biasing) then ! No fission events occur if survival biasing is on -- need to ! calculate fraction of absorptions that would have resulted in @@ -890,7 +890,7 @@ contains score = ZERO - if (t % estimator == ESTIMATOR_ANALOG) then + if (t % estimator() == ESTIMATOR_ANALOG) then if (survival_biasing) then ! No fission events occur if survival biasing is on -- need to ! calculate fraction of absorptions that would have resulted in @@ -955,7 +955,7 @@ contains score = ONE case (ELASTIC) - if (t % estimator == ESTIMATOR_ANALOG) then + if (t % estimator() == ESTIMATOR_ANALOG) then ! Check if event MT matches if (p % event_MT /= ELASTIC) cycle SCORE_LOOP score = p % last_wgt * flux @@ -991,7 +991,7 @@ contains score = ZERO - if (t % estimator == ESTIMATOR_ANALOG) then + if (t % estimator() == ESTIMATOR_ANALOG) then if (survival_biasing) then ! No fission events occur if survival biasing is on -- need to ! calculate fraction of absorptions that would have resulted in @@ -1063,7 +1063,7 @@ contains end if case (N_2N, N_3N, N_4N, N_GAMMA, N_P, N_A) - if (t % estimator == ESTIMATOR_ANALOG) then + if (t % estimator() == ESTIMATOR_ANALOG) then ! Check if event MT matches if (p % event_MT /= score_bin) cycle SCORE_LOOP score = p % last_wgt * flux @@ -1102,7 +1102,7 @@ contains end if case default - if (t % estimator == ESTIMATOR_ANALOG) then + if (t % estimator() == ESTIMATOR_ANALOG) then ! Any other score is assumed to be a MT number. Thus, we just need ! to check if it matches the MT number of the event if (p % event_MT /= score_bin) cycle SCORE_LOOP @@ -1228,8 +1228,8 @@ contains ! to tally with. ! Set the direction and group to use with get_xs - if (t % estimator == ESTIMATOR_ANALOG .or. & - t % estimator == ESTIMATOR_COLLISION) then + if (t % estimator() == ESTIMATOR_ANALOG .or. & + t % estimator() == ESTIMATOR_COLLISION) then if (survival_biasing) then @@ -1289,7 +1289,7 @@ contains case (SCORE_FLUX) - if (t % estimator == ESTIMATOR_ANALOG) then + if (t % estimator() == ESTIMATOR_ANALOG) then ! All events score to a flux bin. We actually use a collision ! estimator in place of an analog one since there is no way to count ! 'events' exactly for the flux @@ -1310,7 +1310,7 @@ contains case (SCORE_TOTAL) - if (t % estimator == ESTIMATOR_ANALOG) then + if (t % estimator() == ESTIMATOR_ANALOG) then ! All events will score to the total reaction rate. We can just ! use the weight of the particle entering the collision as the ! score @@ -1339,8 +1339,8 @@ contains case (SCORE_INVERSE_VELOCITY) - if (t % estimator == ESTIMATOR_ANALOG .or. & - t % estimator == ESTIMATOR_COLLISION) then + if (t % estimator() == ESTIMATOR_ANALOG .or. & + t % estimator() == ESTIMATOR_COLLISION) then ! All events score to an inverse velocity bin. We actually use a ! collision estimator in place of an analog one since there is no way ! to count 'events' exactly for the inverse velocity @@ -1375,7 +1375,7 @@ contains case (SCORE_SCATTER) - if (t % estimator == ESTIMATOR_ANALOG) then + if (t % estimator() == ESTIMATOR_ANALOG) then ! Skip any event where the particle didn't scatter if (p % event /= EVENT_SCATTER) then cycle SCORE_LOOP @@ -1413,7 +1413,7 @@ contains case (SCORE_NU_SCATTER) - if (t % estimator == ESTIMATOR_ANALOG) then + if (t % estimator() == ESTIMATOR_ANALOG) then ! Skip any event where the particle didn't scatter if (p % event /= EVENT_SCATTER) then cycle SCORE_LOOP @@ -1448,7 +1448,7 @@ contains case (SCORE_ABSORPTION) - if (t % estimator == ESTIMATOR_ANALOG) then + if (t % estimator() == ESTIMATOR_ANALOG) then if (survival_biasing) then ! No absorption events actually occur if survival biasing is on -- ! just use weight absorbed in survival biasing @@ -1477,7 +1477,7 @@ contains case (SCORE_FISSION) - if (t % estimator == ESTIMATOR_ANALOG) then + if (t % estimator() == ESTIMATOR_ANALOG) then if (survival_biasing) then ! No fission events occur if survival biasing is on -- need to ! calculate fraction of absorptions that would have resulted in @@ -1512,7 +1512,7 @@ contains case (SCORE_NU_FISSION) - if (t % estimator == ESTIMATOR_ANALOG) then + if (t % estimator() == ESTIMATOR_ANALOG) then if (survival_biasing .or. p % fission) then if (t % energyout_filter() > 0) then ! Normally, we only need to make contributions to one scoring @@ -1566,7 +1566,7 @@ contains case (SCORE_PROMPT_NU_FISSION) - if (t % estimator == ESTIMATOR_ANALOG) then + if (t % estimator() == ESTIMATOR_ANALOG) then if (survival_biasing .or. p % fission) then if (t % energyout_filter() > 0) then ! Normally, we only need to make contributions to one scoring @@ -1624,7 +1624,7 @@ contains ! Set the delayedgroup filter index and the number of delayed group bins dg_filter = t % delayedgroup_filter() - if (t % estimator == ESTIMATOR_ANALOG) then + if (t % estimator() == ESTIMATOR_ANALOG) then if (survival_biasing .or. p % fission) then if (t % energyout_filter() > 0) then ! Normally, we only need to make contributions to one scoring @@ -1767,7 +1767,7 @@ contains ! Set the delayedgroup filter index and the number of delayed group bins dg_filter = t % delayedgroup_filter() - if (t % estimator == ESTIMATOR_ANALOG) then + if (t % estimator() == ESTIMATOR_ANALOG) then if (survival_biasing) then ! No fission events occur if survival biasing is on -- need to ! calculate fraction of absorptions that would have resulted in @@ -1944,7 +1944,7 @@ contains case (SCORE_KAPPA_FISSION) - if (t % estimator == ESTIMATOR_ANALOG) then + if (t % estimator() == ESTIMATOR_ANALOG) then if (survival_biasing) then ! No fission events occur if survival biasing is on -- need to ! calculate fraction of absorptions that would have resulted in @@ -2078,7 +2078,7 @@ contains if (.not. filter_matches(i_filt) % bins_present) then call filter_matches(i_filt) % bins_clear() call filter_matches(i_filt) % weights_clear() - call filters(i_filt) % obj % get_all_bins(p, t % estimator, & + call filters(i_filt) % obj % get_all_bins(p, t % estimator(), & filter_matches(i_filt)) filter_matches(i_filt) % bins_present = .true. end if @@ -2224,7 +2224,7 @@ contains if (.not. filter_matches(i_filt) % bins_present) then call filter_matches(i_filt) % bins_clear() call filter_matches(i_filt) % weights_clear() - call filters(i_filt) % obj % get_all_bins(p, t % estimator, & + call filters(i_filt) % obj % get_all_bins(p, t % estimator(), & filter_matches(i_filt)) filter_matches(i_filt) % bins_present = .true. end if @@ -2589,7 +2589,7 @@ contains if (.not. filter_matches(i_filt) % bins_present) then call filter_matches(i_filt) % bins_clear() call filter_matches(i_filt) % weights_clear() - call filters(i_filt) % obj % get_all_bins(p, t % estimator, & + call filters(i_filt) % obj % get_all_bins(p, t % estimator(), & filter_matches(i_filt)) filter_matches(i_filt) % bins_present = .true. end if @@ -2746,7 +2746,7 @@ contains if (.not. filter_matches(i_filt) % bins_present) then call filter_matches(i_filt) % bins_clear() call filter_matches(i_filt) % weights_clear() - call filters(i_filt) % obj % get_all_bins(p, t % estimator, & + call filters(i_filt) % obj % get_all_bins(p, t % estimator(), & filter_matches(i_filt)) filter_matches(i_filt) % bins_present = .true. end if @@ -2892,7 +2892,7 @@ contains if (.not. filter_matches(i_filt) % bins_present) then call filter_matches(i_filt) % bins_clear() call filter_matches(i_filt) % weights_clear() - call filters(i_filt) % obj % get_all_bins(p, t % estimator, & + call filters(i_filt) % obj % get_all_bins(p, t % estimator(), & filter_matches(i_filt)) filter_matches(i_filt) % bins_present = .true. end if @@ -3026,7 +3026,7 @@ contains ! (1 / c) * (d_c / d_rho) = 1 / rho case (DIFF_DENSITY) - select case (t % estimator) + select case (t % estimator()) case (ESTIMATOR_ANALOG) @@ -3089,7 +3089,7 @@ contains ! where i is the perturbed nuclide. case (DIFF_NUCLIDE_DENSITY) - select case (t % estimator) + select case (t % estimator()) case (ESTIMATOR_ANALOG) @@ -3226,7 +3226,7 @@ contains ! resonance range and requires multipole data. case (DIFF_TEMPERATURE) - select case (t % estimator) + select case (t % estimator()) case (ESTIMATOR_ANALOG) @@ -3786,7 +3786,9 @@ contains subroutine setup_active_tallies() bind(C) - integer :: i ! loop counter + integer :: i + integer(C_INT) :: err + logical(C_BOOL) :: active call active_tallies % clear() call active_analog_tallies % clear() @@ -3797,17 +3799,18 @@ contains do i = 1, n_tallies associate (t => tallies(i) % obj) - if (t % active) then + err = openmc_tally_get_active(i, active) + if (active) then ! Add tally to active tallies call active_tallies % push_back(i) ! Check what type of tally this is and add it to the appropriate list if (t % type == TALLY_VOLUME) then - if (t % estimator == ESTIMATOR_ANALOG) then + if (t % estimator() == ESTIMATOR_ANALOG) then call active_analog_tallies % push_back(i) - elseif (t % estimator == ESTIMATOR_TRACKLENGTH) then + elseif (t % estimator() == ESTIMATOR_TRACKLENGTH) then call active_tracklength_tallies % push_back(i) - elseif (t % estimator == ESTIMATOR_COLLISION) then + elseif (t % estimator() == ESTIMATOR_COLLISION) then call active_collision_tallies % push_back(i) end if elseif (t % type == TALLY_MESH_SURFACE) then diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 169e886d6e..cbc7b92f00 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -26,6 +26,13 @@ namespace openmc { namespace model { std::vector> tallies; + + std::vector active_analog_tallies; + std::vector active_tracklength_tallies; + std::vector active_meshsurf_tallies; + std::vector active_collision_tallies; + std::vector active_tallies; + std::vector active_surface_tallies; } double global_tally_absorption; @@ -188,6 +195,28 @@ free_memory_tally_c() // C-API functions //============================================================================== +extern "C" int +openmc_tally_get_active(int32_t index, bool* active) +{ + if (index < 1 || index > model::tallies.size()) { + set_errmsg("Index in tallies array is out of bounds."); + return OPENMC_E_OUT_OF_BOUNDS; + } + //TODO: off-by-one + *active = model::tallies[index-1]->active_; +} + +extern "C" int +openmc_tally_set_active(int32_t index, bool active) +{ + if (index < 1 || index > model::tallies.size()) { + set_errmsg("Index in tallies array is out of bounds."); + return OPENMC_E_OUT_OF_BOUNDS; + } + //TODO: off-by-one + model::tallies[index-1]->active_ = active; +} + extern "C" int openmc_tally_get_filters(int32_t index, const int32_t** indices, int* n) { @@ -196,6 +225,7 @@ openmc_tally_get_filters(int32_t index, const int32_t** indices, int* n) return OPENMC_E_OUT_OF_BOUNDS; } + //TODO: off-by-one *indices = model::tallies[index-1]->filters().data(); *n = model::tallies[index-1]->filters().size(); return 0; @@ -236,6 +266,10 @@ extern "C" { model::tallies.push_back(std::make_unique()); } + int tally_get_estimator_c(Tally* tally) {return tally->estimator_;} + + void tally_set_estimator_c(Tally* tally, int e) {tally->estimator_ = e;} + void tally_set_filters_c(Tally* tally, int n, int32_t filter_indices[]) {tally->set_filters(filter_indices, n);} diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index f4300789e1..aa929e8c44 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -47,7 +47,21 @@ module tally_header integer(C_INT), value, intent(in) :: n integer(C_INT32_T), intent(in) :: filter_indices(n) integer(C_INT) :: err - end function openmc_tally_set_filters + end function + + function openmc_tally_set_active(index, active) result(err) bind(C) + import C_INT32_T, C_BOOL, C_INT + integer(C_INT32_T), value, intent(in) :: index + logical(C_BOOL), value, intent(in) :: active + integer(C_INT) :: err + end function + + function openmc_tally_get_active(index, active) result(err) bind(C) + import C_INT32_T, C_BOOL, C_INT + integer(C_INT32_T), value :: index + logical(C_BOOL), intent(out) :: active + integer(C_INT) :: err + end function end interface !=============================================================================== @@ -64,9 +78,9 @@ module tally_header integer :: id ! user-defined identifier character(len=104) :: name = "" ! user-defined name integer :: type = TALLY_VOLUME ! volume, surface current - integer :: estimator = ESTIMATOR_TRACKLENGTH ! collision, track-length + !integer :: estimator = ESTIMATOR_TRACKLENGTH ! collision, track-length real(8) :: volume ! volume of region - logical :: active = .false. + !logical :: active = .false. logical :: depletion_rx = .false. ! has depletion reactions, e.g. (n,2n) ! Individual nuclides to tally @@ -98,6 +112,8 @@ module tally_header procedure :: allocate_results => tally_allocate_results procedure :: read_results_hdf5 => tally_read_results_hdf5 procedure :: write_results_hdf5 => tally_write_results_hdf5 + procedure :: estimator => tally_get_estimator + procedure :: set_estimator => tally_set_estimator procedure :: n_filters => tally_get_n_filters procedure :: filter => tally_get_filter procedure :: stride => tally_get_stride @@ -282,6 +298,32 @@ contains end subroutine tally_allocate_results + function tally_get_estimator(this) result(e) + class(TallyObject) :: this + integer(C_INT) :: e + interface + function tally_get_estimator_c(tally) result(e) bind(C) + import C_PTR, C_INT + type(C_PTR), value :: tally + integer(C_INT) :: e + end function + end interface + e = tally_get_estimator_c(this % ptr) + end function + + subroutine tally_set_estimator(this, e) + class(TallyObject) :: this + integer(C_INT) :: e + interface + subroutine tally_set_estimator_c(tally, e) bind(C) + import C_PTR, C_INT + type(C_PTR), value :: tally + integer(C_INT), value :: e + end subroutine + end interface + call tally_set_estimator_c(this % ptr, e) + end subroutine + function tally_get_n_filters(this) result(n) class(TallyObject) :: this integer(C_INT) :: n @@ -561,22 +603,6 @@ contains end function openmc_global_tallies - function openmc_tally_get_active(index, active) result(err) bind(C) - ! Return whether a tally is active - integer(C_INT32_T), value :: index - logical(C_BOOL), intent(out) :: active - integer(C_INT) :: err - - if (index >= 1 .and. index <= size(tallies)) then - active = tallies(index) % obj % active - err = 0 - else - err = E_OUT_OF_BOUNDS - call set_errmsg('Index in tallies array is out of bounds.') - end if - end function openmc_tally_get_active - - function openmc_tally_get_estimator(index, estimator) result(err) bind(C) ! Return the type of estimator of a tally integer(C_INT32_T), value :: index @@ -584,7 +610,7 @@ contains integer(C_INT) :: err if (index >= 1 .and. index <= size(tallies)) then - estimator = tallies(index) % obj % estimator + estimator = tallies(index) % obj % estimator() err = 0 else err = E_OUT_OF_BOUNDS @@ -755,11 +781,11 @@ contains if (index >= 1 .and. index <= size(tallies)) then select case (estimator_) case ('analog') - tallies(index) % obj % estimator = ESTIMATOR_ANALOG + call tallies(index) % obj % set_estimator(ESTIMATOR_ANALOG) case ('tracklength') - tallies(index) % obj % estimator = ESTIMATOR_TRACKLENGTH + call tallies(index) % obj % set_estimator(ESTIMATOR_TRACKLENGTH) case ('collision') - tallies(index) % obj % estimator = ESTIMATOR_COLLISION + call tallies(index) % obj % set_estimator(ESTIMATOR_COLLISION) case default err = E_INVALID_ARGUMENT call set_errmsg("Unknown tally estimator: " // trim(estimator_)) @@ -771,27 +797,6 @@ contains end function openmc_tally_set_estimator - function openmc_tally_set_active(index, active) result(err) bind(C) - ! Set the ID of a tally - integer(C_INT32_T), value, intent(in) :: index - logical(C_BOOL), value, intent(in) :: active - integer(C_INT) :: err - - if (index >= 1 .and. index <= n_tallies) then - if (allocated(tallies(index) % obj)) then - tallies(index) % obj % active = active - err = 0 - else - err = E_ALLOCATE - call set_errmsg("Tally type has not been set yet.") - end if - else - err = E_OUT_OF_BOUNDS - call set_errmsg('Index in tallies array is out of bounds.') - end if - end function openmc_tally_set_active - - function openmc_tally_set_id(index, id) result(err) bind(C) ! Set the ID of a tally integer(C_INT32_T), value, intent(in) :: index From e99fd9e7e4ea97bdbfbf0e8942cb44aa751de134 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sat, 26 Jan 2019 16:26:03 -0500 Subject: [PATCH 11/58] Move most active_tally lists to C++ --- include/openmc/tallies/tally.h | 8 +- src/input_xml.F90 | 4 +- src/state_point.F90 | 2 +- src/tallies/tally.F90 | 46 ++++------ src/tallies/tally.cpp | 115 ++++++++++++++++++++++++- src/tallies/tally_header.F90 | 153 ++++++++++++++++----------------- src/tallies/trigger.F90 | 2 +- src/tracking.F90 | 12 +-- tests/unit_tests/test_capi.py | 1 + 9 files changed, 219 insertions(+), 124 deletions(-) diff --git a/include/openmc/tallies/tally.h b/include/openmc/tallies/tally.h index 4b0514fd6a..0022fc8966 100644 --- a/include/openmc/tallies/tally.h +++ b/include/openmc/tallies/tally.h @@ -30,6 +30,8 @@ public: int32_t n_filter_bins() const {return n_filter_bins_;} + int type_ {TALLY_VOLUME}; //!< volume, surface current + //! Event type that contributes to this tally int estimator_ {ESTIMATOR_TRACKLENGTH}; @@ -64,12 +66,12 @@ extern "C" double total_weight; namespace model { extern std::vector> tallies; + extern std::vector active_tallies; extern std::vector active_analog_tallies; extern std::vector active_tracklength_tallies; - extern std::vector active_meshsurf_tallies; extern std::vector active_collision_tallies; - extern std::vector active_tallies; - extern std::vector active_surface_tallies; + //extern std::vector active_meshsurf_tallies; + //extern std::vector active_surface_tallies; } // Threadprivate variables diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 454956b46e..aaab3a460a 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -1394,12 +1394,12 @@ contains &in the same tally as normal surface currents") end if - t % type = TALLY_SURFACE + call t % set_type(TALLY_SURFACE) t % score_bins(j) = SCORE_CURRENT else if (has_meshsurface) then t % score_bins(j) = SCORE_CURRENT - t % type = TALLY_MESH_SURFACE + call t % set_type(TALLY_MESH_SURFACE) ! Check to make sure that current is the only desired response ! for this tally diff --git a/src/state_point.F90 b/src/state_point.F90 index 4fbc097954..85df5b6dfe 100644 --- a/src/state_point.F90 +++ b/src/state_point.F90 @@ -346,7 +346,7 @@ contains call write_dataset(file_id, "global_tallies", global_tallies) ! Write tallies - if (active_tallies % size() > 0) then + if (active_tallies_size() > 0) then ! Indicate that tallies are on call write_attribute(file_id, "tallies_present", 1) diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index 8420c02cf2..226491c0e6 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -2066,9 +2066,9 @@ contains ! A loop over all tallies is necessary because we need to simultaneously ! determine different filter bins for the same tally in order to score to it - TALLY_LOOP: do i = 1, active_analog_tallies % size() + TALLY_LOOP: do i = 1, active_analog_tallies_size() ! Get index of tally and pointer to tally - i_tally = active_analog_tallies % data(i) + i_tally = active_analog_tallies_data(i) associate (t => tallies(i_tally) % obj) ! Find all valid bins in each filter if they have not already been found @@ -2212,9 +2212,9 @@ contains ! A loop over all tallies is necessary because we need to simultaneously ! determine different filter bins for the same tally in order to score to it - TALLY_LOOP: do i = 1, active_analog_tallies % size() + TALLY_LOOP: do i = 1, active_analog_tallies_size() ! Get index of tally and pointer to tally - i_tally = active_analog_tallies % data(i) + i_tally = active_analog_tallies_data(i) associate (t => tallies(i_tally) % obj) ! Find all valid bins in each filter if they have not already been found @@ -2577,9 +2577,9 @@ contains ! A loop over all tallies is necessary because we need to simultaneously ! determine different filter bins for the same tally in order to score to it - TALLY_LOOP: do i = 1, active_tracklength_tallies % size() + TALLY_LOOP: do i = 1, active_tracklength_tallies_size() ! Get index of tally and pointer to tally - i_tally = active_tracklength_tallies % data(i) + i_tally = active_tracklength_tallies_data(i) associate (t => tallies(i_tally) % obj) ! Find all valid bins in each filter if they have not already been found @@ -2734,9 +2734,9 @@ contains ! A loop over all tallies is necessary because we need to simultaneously ! determine different filter bins for the same tally in order to score to it - TALLY_LOOP: do i = 1, active_collision_tallies % size() + TALLY_LOOP: do i = 1, active_collision_tallies_size() ! Get index of tally and pointer to tally - i_tally = active_collision_tallies % data(i) + i_tally = active_collision_tallies_data(i) associate (t => tallies(i_tally) % obj) ! Find all valid bins in each filter if they have not already been found @@ -3774,8 +3774,8 @@ contains end if ! Accumulate results for each tally - do i = 1, active_tallies % size() - call tallies(active_tallies % data(i)) % obj % accumulate() + do i = 1, active_tallies_size() + call tallies(active_tallies_data(i)) % obj % accumulate() end do end subroutine accumulate_tallies @@ -3790,10 +3790,13 @@ contains integer(C_INT) :: err logical(C_BOOL) :: active - call active_tallies % clear() - call active_analog_tallies % clear() - call active_collision_tallies % clear() - call active_tracklength_tallies % clear() + interface + subroutine setup_active_tallies_c() bind(C) + end subroutine + end interface + + call setup_active_tallies_c() + call active_surface_tallies % clear() call active_meshsurf_tallies % clear() @@ -3801,21 +3804,10 @@ contains associate (t => tallies(i) % obj) err = openmc_tally_get_active(i, active) if (active) then - ! Add tally to active tallies - call active_tallies % push_back(i) - ! Check what type of tally this is and add it to the appropriate list - if (t % type == TALLY_VOLUME) then - if (t % estimator() == ESTIMATOR_ANALOG) then - call active_analog_tallies % push_back(i) - elseif (t % estimator() == ESTIMATOR_TRACKLENGTH) then - call active_tracklength_tallies % push_back(i) - elseif (t % estimator() == ESTIMATOR_COLLISION) then - call active_collision_tallies % push_back(i) - end if - elseif (t % type == TALLY_MESH_SURFACE) then + if (t % type() == TALLY_MESH_SURFACE) then call active_meshsurf_tallies % push_back(i) - elseif (t % type == TALLY_SURFACE) then + elseif (t % type() == TALLY_SURFACE) then call active_surface_tallies % push_back(i) end if diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index cbc7b92f00..a93de78b30 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -27,12 +27,12 @@ namespace openmc { namespace model { std::vector> tallies; + std::vector active_tallies; std::vector active_analog_tallies; std::vector active_tracklength_tallies; - std::vector active_meshsurf_tallies; std::vector active_collision_tallies; - std::vector active_tallies; - std::vector active_surface_tallies; + //std::vector active_meshsurf_tallies; + //std::vector active_surface_tallies; } double global_tally_absorption; @@ -177,6 +177,48 @@ void reduce_tally_results() } #endif +extern "C" void +setup_active_tallies_c() +{ + model::active_tallies.clear(); + model::active_analog_tallies.clear(); + model::active_tracklength_tallies.clear(); + model::active_collision_tallies.clear(); + //model::active_meshsurf_tallies.clear(); + //model::active_surface_tallies.clear(); + + //TODO: off-by-one all through here + for (auto i = 0; i < model::tallies.size(); ++i) { + const auto& tally {*model::tallies[i]}; + + if (tally.active_) { + model::active_tallies.push_back(i + 1); + switch (tally.type_) { + + case TALLY_VOLUME: + switch (tally.estimator_) { + case ESTIMATOR_ANALOG: + model::active_analog_tallies.push_back(i + 1); + break; + case ESTIMATOR_TRACKLENGTH: + model::active_tracklength_tallies.push_back(i + 1); + break; + case ESTIMATOR_COLLISION: + model::active_collision_tallies.push_back(i + 1); + } + break; + + //case TALLY_MESH_SURFACE: + // model::active_meshsurf_tallies.push_back(i + 1); + // break; + + //case TALLY_SURFACE: + // model::active_surface_tallies.push_back(i + 1); + } + } + } +} + extern "C" void free_memory_tally_c() { @@ -189,12 +231,51 @@ free_memory_tally_c() model::tally_filters.clear(); model::tallies.clear(); + + model::active_tallies.clear(); + model::active_analog_tallies.clear(); + model::active_tracklength_tallies.clear(); + model::active_collision_tallies.clear(); + //model::active_meshsurf_tallies.clear(); + //model::active_surface_tallies.clear(); } //============================================================================== // C-API functions //============================================================================== +extern "C" int +openmc_tally_get_type(int32_t index, int32_t* type) +{ + if (index < 1 || index > model::tallies.size()) { + set_errmsg("Index in tallies array is out of bounds."); + return OPENMC_E_OUT_OF_BOUNDS; + } + //TODO: off-by-one + *type = model::tallies[index-1]->type_; +} + +extern "C" int +openmc_tally_set_type(int32_t index, const char* type) +{ + if (index < 1 || index > model::tallies.size()) { + set_errmsg("Index in tallies array is out of bounds."); + return OPENMC_E_OUT_OF_BOUNDS; + } + if (strcmp(type, "volume") == 0) { + model::tallies[index-1]->type_ = TALLY_VOLUME; + } else if (strcmp(type, "mesh-surface") == 0) { + model::tallies[index-1]->type_ = TALLY_MESH_SURFACE; + } else if (strcmp(type, "surface") == 0) { + model::tallies[index-1]->type_ = TALLY_SURFACE; + } else { + std::stringstream errmsg; + errmsg << "Unknown tally type: " << type; + set_errmsg(errmsg); + return OPENMC_E_INVALID_ARGUMENT; + } +} + extern "C" int openmc_tally_get_active(int32_t index, bool* active) { @@ -266,6 +347,34 @@ extern "C" { model::tallies.push_back(std::make_unique()); } + int active_tallies_data(int i) + {return model::active_tallies[i-1];} + + int active_tallies_size() + {return model::active_tallies.size();} + + int active_analog_tallies_data(int i) + {return model::active_analog_tallies[i-1];} + + int active_analog_tallies_size() + {return model::active_analog_tallies.size();} + + int active_tracklength_tallies_data(int i) + {return model::active_tracklength_tallies[i-1];} + + int active_tracklength_tallies_size() + {return model::active_tracklength_tallies.size();} + + int active_collision_tallies_data(int i) + {return model::active_collision_tallies[i-1];} + + int active_collision_tallies_size() + {return model::active_collision_tallies.size();} + + int tally_get_type_c(Tally* tally) {return tally->type_;} + + void tally_set_type_c(Tally* tally, int type) {tally->type_ = type;} + int tally_get_estimator_c(Tally* tally) {return tally->estimator_;} void tally_set_estimator_c(Tally* tally, int e) {tally->estimator_ = e;} diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index aa929e8c44..2e57d243bb 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -16,29 +16,6 @@ module tally_header use trigger_header, only: TriggerObject implicit none - private - public :: allocate_tally_results - public :: free_memory_tally - public :: openmc_extend_tallies - public :: openmc_get_tally_index - public :: openmc_get_tally_next_id - public :: openmc_global_tallies - public :: openmc_tally_get_active - public :: openmc_tally_get_estimator - public :: openmc_tally_get_id - public :: openmc_tally_get_n_realizations - public :: openmc_tally_get_nuclides - public :: openmc_tally_get_scores - public :: openmc_tally_get_type - public :: openmc_tally_reset - public :: openmc_tally_results - public :: openmc_tally_set_active - public :: openmc_tally_set_estimator - public :: openmc_tally_set_filters - public :: openmc_tally_set_id - public :: openmc_tally_set_nuclides - public :: openmc_tally_set_scores - public :: openmc_tally_set_type interface function openmc_tally_set_filters(index, n, filter_indices) result(err) bind(C) @@ -62,6 +39,50 @@ module tally_header logical(C_BOOL), intent(out) :: active integer(C_INT) :: err end function + + function active_tallies_size() result(size) bind(C) + import C_INT + integer(C_INT) :: size + end function + + function active_tallies_data(i) result(tally) bind(C) + import C_INT + integer(C_INT), value :: i + integer(C_INT) :: tally + end function + + function active_analog_tallies_size() result(size) bind(C) + import C_INT + integer(C_INT) :: size + end function + + function active_analog_tallies_data(i) result(tally) bind(C) + import C_INT + integer(C_INT), value :: i + integer(C_INT) :: tally + end function + + function active_tracklength_tallies_size() result(size) bind(C) + import C_INT + integer(C_INT) :: size + end function + + function active_tracklength_tallies_data(i) result(tally) bind(C) + import C_INT + integer(C_INT), value :: i + integer(C_INT) :: tally + end function + + function active_collision_tallies_size() result(size) bind(C) + import C_INT + integer(C_INT) :: size + end function + + function active_collision_tallies_data(i) result(tally) bind(C) + import C_INT + integer(C_INT), value :: i + integer(C_INT) :: tally + end function end interface !=============================================================================== @@ -77,10 +98,7 @@ module tally_header integer :: id ! user-defined identifier character(len=104) :: name = "" ! user-defined name - integer :: type = TALLY_VOLUME ! volume, surface current - !integer :: estimator = ESTIMATOR_TRACKLENGTH ! collision, track-length real(8) :: volume ! volume of region - !logical :: active = .false. logical :: depletion_rx = .false. ! has depletion reactions, e.g. (n,2n) ! Individual nuclides to tally @@ -112,6 +130,8 @@ module tally_header procedure :: allocate_results => tally_allocate_results procedure :: read_results_hdf5 => tally_read_results_hdf5 procedure :: write_results_hdf5 => tally_write_results_hdf5 + procedure :: type => tally_get_type + procedure :: set_type => tally_set_type procedure :: estimator => tally_get_estimator procedure :: set_estimator => tally_set_estimator procedure :: n_filters => tally_get_n_filters @@ -163,11 +183,7 @@ module tally_header !$omp& global_tally_tracklength, global_tally_leakage) ! Active tally lists - type(VectorInt), public :: active_analog_tallies - type(VectorInt), public :: active_tracklength_tallies type(VectorInt), public :: active_meshsurf_tallies - type(VectorInt), public :: active_collision_tallies - type(VectorInt), public :: active_tallies type(VectorInt), public :: active_surface_tallies ! Normalization for statistics @@ -298,6 +314,32 @@ contains end subroutine tally_allocate_results + function tally_get_type(this) result(t) + class(TallyObject) :: this + integer(C_INT) :: t + interface + function tally_get_type_c(tally) result(t) bind(C) + import C_PTR, C_INT + type(C_PTR), value :: tally + integer(C_INT) :: t + end function + end interface + t = tally_get_type_c(this % ptr) + end function + + subroutine tally_set_type(this, t) + class(TallyObject) :: this + integer(C_INT) :: t + interface + subroutine tally_set_type_c(tally, t) bind(C) + import C_PTR, C_INT + type(C_PTR), value :: tally + integer(C_INT), value :: t + end subroutine + end interface + call tally_set_type_c(this % ptr, t) + end subroutine + function tally_get_estimator(this) result(e) class(TallyObject) :: this integer(C_INT) :: e @@ -513,12 +555,8 @@ contains if (allocated(global_tallies)) deallocate(global_tallies) ! Deallocate tally node lists - call active_analog_tallies % clear() - call active_tracklength_tallies % clear() call active_meshsurf_tallies % clear() - call active_collision_tallies % clear() call active_surface_tallies % clear() - call active_tallies % clear() end subroutine free_memory_tally !=============================================================================== @@ -701,22 +739,6 @@ contains end function openmc_tally_get_scores - function openmc_tally_get_type(index, type) result(err) bind(C) - ! Return the type of a tally - integer(C_INT32_T), value :: index - integer(C_INT32_T), intent(out) :: type - integer(C_INT) :: err - - if (index >= 1 .and. index <= size(tallies)) then - type = tallies(index) % obj % type - err = 0 - else - err = E_OUT_OF_BOUNDS - call set_errmsg('Index in tallies array is out of bounds.') - end if - end function openmc_tally_get_type - - function openmc_tally_reset(index) result(err) bind(C) ! Reset tally results and number of realizations integer(C_INT32_T), intent(in), value :: index @@ -1048,37 +1070,6 @@ contains end function openmc_tally_set_scores - function openmc_tally_set_type(index, type) result(err) bind(C) - ! Update the type of a tally that is already allocated - integer(C_INT32_T), value, intent(in) :: index - character(kind=C_CHAR), intent(in) :: type(*) - integer(C_INT) :: err - - character(:), allocatable :: type_ - - ! Convert C string to Fortran string - type_ = to_f_string(type) - - err = 0 - if (index >= 1 .and. index <= size(tallies)) then - select case (type_) - case ('volume') - tallies(index) % obj % type = TALLY_VOLUME - case ('mesh-surface') - tallies(index) % obj % type = TALLY_MESH_SURFACE - case ('surface') - tallies(index) % obj % type = TALLY_SURFACE - case default - err = E_INVALID_ARGUMENT - call set_errmsg("Unknown tally type: " // trim(type_)) - end select - else - err = E_OUT_OF_BOUNDS - call set_errmsg("Index in tally array is out of bounds.") - end if - end function openmc_tally_set_type - - subroutine openmc_get_tally_next_id(id) bind(C) ! Returns an ID number that has not been used by any other tallies. integer(C_INT32_T), intent(out) :: id diff --git a/src/tallies/trigger.F90 b/src/tallies/trigger.F90 index 31281a64f0..4c1efcc2ec 100644 --- a/src/tallies/trigger.F90 +++ b/src/tallies/trigger.F90 @@ -166,7 +166,7 @@ contains trigger % variance = ZERO ! Mesh current tally triggers require special treatment - if (t % type == TALLY_MESH_SURFACE) then + if (t % type() == TALLY_MESH_SURFACE) then call compute_tally_current(t, trigger) else diff --git a/src/tracking.F90 b/src/tracking.F90 index bf8332852d..f540687f35 100644 --- a/src/tracking.F90 +++ b/src/tracking.F90 @@ -86,7 +86,7 @@ contains endif ! Every particle starts with no accumulated flux derivative. - if (active_tallies % size() > 0) call zero_flux_derivs() + if (active_tallies_size() > 0) call zero_flux_derivs() EVENT_LOOP: do ! Set the random number stream @@ -172,7 +172,7 @@ contains end do ! Score track-length tallies - if (active_tracklength_tallies % size() > 0) then + if (active_tracklength_tallies_size() > 0) then call score_tracklength_tally(p, distance) end if @@ -183,7 +183,7 @@ contains end if ! Score flux derivative accumulators for differential tallies. - if (active_tallies % size() > 0) call score_track_derivative(p, distance) + if (active_tallies_size() > 0) call score_track_derivative(p, distance) if (d_collision > d_boundary) then ! ==================================================================== @@ -241,8 +241,8 @@ contains ! Score collision estimator tallies -- this is done after a collision ! has occurred rather than before because we need information on the ! outgoing energy for any tallies with an outgoing energy filter - if (active_collision_tallies % size() > 0) call score_collision_tally(p) - if (active_analog_tallies % size() > 0) call score_analog_tally(p) + if (active_collision_tallies_size() > 0) call score_collision_tally(p) + if (active_analog_tallies_size() > 0) call score_analog_tally(p) ! Reset banked weight during collision p % n_bank = 0 @@ -273,7 +273,7 @@ contains end do ! Score flux derivative accumulators for differential tallies. - if (active_tallies % size() > 0) call score_collision_derivative(p) + if (active_tallies_size() > 0) call score_collision_derivative(p) end if ! If particle has too many events, display warning and kill it diff --git a/tests/unit_tests/test_capi.py b/tests/unit_tests/test_capi.py index f5185e23fa..42c0639cb0 100644 --- a/tests/unit_tests/test_capi.py +++ b/tests/unit_tests/test_capi.py @@ -162,6 +162,7 @@ def test_tally_mapping(capi_init): def test_tally(capi_init): t = openmc.capi.tallies[1] + assert t.type == 'volume' assert len(t.filters) == 2 assert isinstance(t.filters[0], openmc.capi.MaterialFilter) assert isinstance(t.filters[1], openmc.capi.EnergyFilter) From f4d7c81bd44a5dd6071dd0b77eef96bc38966198 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sun, 27 Jan 2019 14:29:23 -0500 Subject: [PATCH 12/58] Move tally % nuclide_bins to C++ --- include/openmc/tallies/tally.h | 19 +++++ src/input_xml.F90 | 41 ++++++----- src/output.F90 | 2 +- src/state_point.F90 | 4 +- src/tallies/tally.F90 | 14 ++-- src/tallies/tally.cpp | 30 ++++++++ src/tallies/tally_header.F90 | 124 ++++++++++++++++++++++----------- src/tallies/trigger.F90 | 2 +- 8 files changed, 164 insertions(+), 72 deletions(-) diff --git a/include/openmc/tallies/tally.h b/include/openmc/tallies/tally.h index 0022fc8966..6ffd07be34 100644 --- a/include/openmc/tallies/tally.h +++ b/include/openmc/tallies/tally.h @@ -20,6 +20,9 @@ class Tally { public: Tally() {} + //---------------------------------------------------------------------------- + // Methods for getting and setting filter/stride data. + const std::vector& filters() {return filters_;} int32_t filters(int i) const {return filters_[i];} @@ -30,13 +33,26 @@ public: int32_t n_filter_bins() const {return n_filter_bins_;} + //---------------------------------------------------------------------------- + // Major public data members. + int type_ {TALLY_VOLUME}; //!< volume, surface current //! Event type that contributes to this tally int estimator_ {ESTIMATOR_TRACKLENGTH}; + //! Whether this tally is currently being updated bool active_ {false}; + //! Index of each nuclide to be tallied. -1 indicates total material. + std::vector nuclides_; + + //! True if this tally has a bin for every nuclide in the problem + bool all_nuclides_ {false}; + + //---------------------------------------------------------------------------- + // Miscellaneous public members. + // We need to have quick access to some filters. The following gives indices // for various filters that could be in the tally or C_NONE if they are not // present. @@ -49,6 +65,9 @@ public: int deriv_ {C_NONE}; //!< Index of a TallyDerivative object for diff tallies. private: + //---------------------------------------------------------------------------- + // Private data. + std::vector filters_; //!< Filter indices in global filters array //! Index strides assigned to each filter to support 1D indexing. diff --git a/src/input_xml.F90 b/src/input_xml.F90 index aaab3a460a..70d20041b0 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -949,6 +949,8 @@ contains type(XMLNode), allocatable :: node_trigger_list(:) type(DictEntryCI) :: elem type(TallyDerivative), pointer :: deriv + integer, allocatable :: nuclide_bins(:) + logical :: all_nuclides interface subroutine read_tally_derivatives(node_ptr) bind(C) @@ -1193,6 +1195,7 @@ contains ! ======================================================================= ! READ DATA FOR NUCLIDES + all_nuclides = .false. if (check_for_node(node_tal, "nuclides")) then ! Allocate a temporary string array for nuclides and copy values over @@ -1201,27 +1204,23 @@ contains if (trim(sarray(1)) == 'all') then ! Handle special case all - allocate(t % nuclide_bins(n_nuclides + 1)) + allocate(nuclide_bins(n_nuclides + 1)) ! Set bins to 1, 2, 3, ..., n_nuclides, -1 - t % nuclide_bins(1:n_nuclides) = & - (/ (j, j=1, n_nuclides) /) - t % nuclide_bins(n_nuclides + 1) = -1 - - ! Set number of nuclide bins - t % n_nuclide_bins = n_nuclides + 1 + nuclide_bins(1:n_nuclides) = (/ (j, j=1, n_nuclides) /) + nuclide_bins(n_nuclides + 1) = -1 ! Set flag so we can treat this case specially - t % all_nuclides = .true. + all_nuclides = .true. else ! Any other case, e.g. U-235 Pu-239 n_words = node_word_count(node_tal, "nuclides") - allocate(t % nuclide_bins(n_words)) + allocate(nuclide_bins(n_words)) do j = 1, n_words ! Check if total material was specified if (trim(sarray(j)) == 'total') then - t % nuclide_bins(j) = -1 + nuclide_bins(j) = -1 cycle end if @@ -1236,11 +1235,8 @@ contains end if ! Set bin to index in nuclides array - t % nuclide_bins(j) = nuclide_dict % get(word) + nuclide_bins(j) = nuclide_dict % get(word) end do - - ! Set number of nuclide bins - t % n_nuclide_bins = n_words end if ! Deallocate temporary string array @@ -1249,11 +1245,14 @@ contains else ! No were specified -- create only one bin will be added ! for the total material. - allocate(t % nuclide_bins(1)) - t % nuclide_bins(1) = -1 - t % n_nuclide_bins = 1 + allocate(nuclide_bins(1)) + nuclide_bins(1) = -1 end if + ! Set the tally nuclides array + call t % set_nuclide_bins(nuclide_bins, all_nuclides) + deallocate(nuclide_bins) + ! ======================================================================= ! READ DATA FOR SCORES @@ -1290,7 +1289,7 @@ contains select case (trim(score_name)) case ('flux') ! Prohibit user from tallying flux for an individual nuclide - if (.not. (t % n_nuclide_bins == 1 .and. & + if (.not. (t % n_nuclide_bins() == 1 .and. & t % nuclide_bins(1) == -1)) then call fatal_error("Cannot tally flux for an individual nuclide.") end if @@ -1614,8 +1613,8 @@ contains deriv => tally_deriv_c(t % deriv()) if (deriv % variable == DIFF_NUCLIDE_DENSITY & .or. deriv % variable == DIFF_TEMPERATURE) then - if (any(t % nuclide_bins == -1)) then - if (has_energyout) then + do j = 1, t % n_nuclide_bins() + if (has_energyout .and. t % nuclide_bins(j) == -1) then call fatal_error("Error on tally " // trim(to_str(t % id)) & // ": Cannot use a 'nuclide_density' or 'temperature' & &derivative on a tally with an outgoing energy filter and & @@ -1626,7 +1625,7 @@ contains ! (e.g. pertrubing moderator but only tallying fuel), but this ! case would be hard to check for by only reading inputs. end if - end if + end do end if end if diff --git a/src/output.F90 b/src/output.F90 index 2487153586..c4e0c34776 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -625,7 +625,7 @@ contains ! Write results for this filter bin combination score_index = 0 if (t % n_filters() > 0) indent = indent + 2 - do n = 1, t % n_nuclide_bins + do n = 1, t % n_nuclide_bins() ! Write label for nuclide i_nuclide = t % nuclide_bins(n) if (i_nuclide == -1) then diff --git a/src/state_point.F90 b/src/state_point.F90 index 85df5b6dfe..6de1db7732 100644 --- a/src/state_point.F90 +++ b/src/state_point.F90 @@ -277,8 +277,8 @@ contains end if ! Set up nuclide bin array and then write - allocate(str_array(tally % n_nuclide_bins)) - NUCLIDE_LOOP: do j = 1, tally % n_nuclide_bins + allocate(str_array(tally % n_nuclide_bins())) + NUCLIDE_LOOP: do j = 1, tally % n_nuclide_bins() if (tally % nuclide_bins(j) > 0) then if (run_CE) then i_xs = index(nuclides(tally % nuclide_bins(j)) % name, '.') diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index 226491c0e6..12d9b9477e 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -2114,12 +2114,12 @@ contains ! Check for nuclide bins k = 0 - NUCLIDE_LOOP: do while (k < t % n_nuclide_bins) + NUCLIDE_LOOP: do while (k < t % n_nuclide_bins()) ! Increment the index in the list of nuclide bins k = k + 1 - if (t % all_nuclides) then + if (t % all_nuclides()) then ! In the case that the user has requested to tally all nuclides, we ! can take advantage of the fact that we know exactly how nuclide ! bins correspond to nuclide indices. @@ -2259,7 +2259,7 @@ contains ! Nuclide logic ! Check for nuclide bins - NUCLIDE_LOOP: do k = 1, t % n_nuclide_bins + NUCLIDE_LOOP: do k = 1, t % n_nuclide_bins() ! Get index of nuclide in nuclides array i_nuclide = t % nuclide_bins(k) @@ -2623,13 +2623,13 @@ contains ! ====================================================================== ! Nuclide logic - if (t % all_nuclides) then + if (t % all_nuclides()) then if (p % material /= MATERIAL_VOID) then call score_all_nuclides(p, t, flux * filter_weight, filter_index) end if else - NUCLIDE_BIN_LOOP: do k = 1, t % n_nuclide_bins + NUCLIDE_BIN_LOOP: do k = 1, t % n_nuclide_bins() ! Get index of nuclide in nuclides array i_nuclide = t % nuclide_bins(k) @@ -2780,13 +2780,13 @@ contains ! ====================================================================== ! Nuclide logic - if (t % all_nuclides) then + if (t % all_nuclides()) then if (p % material /= MATERIAL_VOID) then call score_all_nuclides(p, t, flux * filter_weight, filter_index) end if else - NUCLIDE_BIN_LOOP: do k = 1, t % n_nuclide_bins + NUCLIDE_BIN_LOOP: do k = 1, t % n_nuclide_bins() ! Get index of nuclide in nuclides array i_nuclide = t % nuclide_bins(k) diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index a93de78b30..81d248ec8b 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -333,6 +333,20 @@ openmc_tally_set_filters(int32_t index, int n, const int32_t* indices) return 0; } +extern "C" int +openmc_tally_get_nuclides(int32_t index, int** nuclides, int* n) +{ + // Make sure the index fits in the array bounds. + if (index < 1 || index > model::tallies.size()) { + set_errmsg("Index in tallies array is out of bounds."); + return OPENMC_E_OUT_OF_BOUNDS; + } + + //TODO: off-by-one + *n = model::tallies[index-1]->nuclides_.size(); + *nuclides = model::tallies[index-1]->nuclides_.data(); +} + //============================================================================== // Fortran compatibility functions //============================================================================== @@ -391,6 +405,22 @@ extern "C" { int32_t tally_get_n_filter_bins_c(Tally* tally) {return tally->n_filter_bins();} + int tally_get_n_nuclide_bins_c(Tally* tally) + {return tally->nuclides_.size();} + + int tally_get_nuclide_bins_c(Tally* tally, int i) + {return tally->nuclides_[i-1];} + + void + tally_set_nuclide_bins_c(Tally* tally, int n, int bins[], bool all_nuclides) + { + tally->nuclides_.clear(); + tally->nuclides_.assign(bins, bins + n); + tally->all_nuclides_ = all_nuclides; + } + + bool tally_get_all_nuclides_c(Tally* tally) {return tally->all_nuclides_;} + int tally_get_energyin_filter_c(Tally* tally) {return tally->energyin_filter_;} diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index 2e57d243bb..ca90c40879 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -40,6 +40,14 @@ module tally_header integer(C_INT) :: err end function + function openmc_tally_get_nuclides(index, nuclides, n) result(err) bind(C) + import C_INT32_T, C_PTR, C_INT + integer(C_INT32_T), value :: index + type(C_PTR), intent(out) :: nuclides + integer(C_INT), intent(out) :: n + integer(C_INT) :: err + end function openmc_tally_get_nuclides + function active_tallies_size() result(size) bind(C) import C_INT integer(C_INT) :: size @@ -101,11 +109,6 @@ module tally_header real(8) :: volume ! volume of region logical :: depletion_rx = .false. ! has depletion reactions, e.g. (n,2n) - ! Individual nuclides to tally - integer :: n_nuclide_bins = 0 - integer, allocatable :: nuclide_bins(:) - logical :: all_nuclides = .false. - ! Values to score, e.g. flux, absorption, etc. integer :: n_score_bins = 0 integer, allocatable :: score_bins(:) @@ -138,6 +141,10 @@ module tally_header procedure :: filter => tally_get_filter procedure :: stride => tally_get_stride procedure :: n_filter_bins => tally_get_n_filter_bins + procedure :: n_nuclide_bins => tally_get_n_nuclide_bins + procedure :: nuclide_bins => tally_get_nuclide_bins + procedure :: set_nuclide_bins => tally_set_nuclide_bins + procedure :: all_nuclides => tally_get_all_nuclides procedure :: energyin_filter => tally_get_energyin_filter procedure :: energyout_filter => tally_get_energyout_filter procedure :: delayedgroup_filter => tally_get_delayedgroup_filter @@ -289,16 +296,15 @@ contains subroutine tally_allocate_results(this) class(TallyObject), intent(inout) :: this + integer, parameter :: default_nuclide_bins(1) = (/-1/) ! If no nuclides were specified, add a single bin for total material - if (.not. allocated(this % nuclide_bins)) then - allocate(this % nuclide_bins(1)) - this % nuclide_bins(1) = -1 - this % n_nuclide_bins = 1 + if (this % n_nuclide_bins() == 0) then + call this % set_nuclide_bins(default_nuclide_bins) end if ! Set total number of filter and scoring bins - this % total_score_bins = this % n_score_bins * this % n_nuclide_bins + this % total_score_bins = this % n_score_bins * this % n_nuclide_bins() if (allocated(this % results)) then ! If results was already allocated but shape is wrong, then reallocate it @@ -422,6 +428,68 @@ contains n_filter_bins = tally_get_n_filter_bins_c(this % ptr) end function + function tally_get_n_nuclide_bins(this) result(n) + class(TallyObject) :: this + integer(C_INT) :: n + interface + function tally_get_n_nuclide_bins_c(tally) result(n) bind(C) + import C_PTR, C_INT + type(C_PTR), value :: tally + integer(C_INT) :: n + end function + end interface + n = tally_get_n_nuclide_bins_c(this % ptr) + end function + + function tally_get_nuclide_bins(this, i) result(nuclide) + class(TallyObject) :: this + integer(C_INT) :: i, nuclide + interface + function tally_get_nuclide_bins_c(tally, i) result(nuclide) bind(C) + import C_PTR, C_INT + type(C_PTR), value :: tally + integer(C_INT), value :: i + integer(C_INT) :: nuclide + end function + end interface + nuclide = tally_get_nuclide_bins_c(this % ptr, i) + end function + + subroutine tally_set_nuclide_bins(this, bins, all_nuclides) + class(TallyObject) :: this + integer :: bins(:) + logical, optional :: all_nuclides + logical(C_BOOL) :: all_ + interface + subroutine tally_set_nuclide_bins_c(this, n, bins, all_nuclides) bind(C) + import C_PTR, C_INT, C_BOOL + type(C_PTR), value :: this + integer(C_INT), value :: n + integer(C_INT) :: bins(n) + logical(C_BOOL), value :: all_nuclides + end subroutine + end interface + if (present(all_nuclides)) then + all_ = logical(all_nuclides, kind=C_BOOL) + else + all_ = .false._C_BOOL + end if + call tally_set_nuclide_bins_c(this % ptr, size(bins), bins, all_) + end subroutine + + function tally_get_all_nuclides(this) result(all_nuc) + class(TallyObject) :: this + logical(C_BOOL) :: all_nuc + interface + function tally_get_all_nuclides_c(tally) result(all_nuc) bind(C) + import C_PTR, C_BOOL + type(C_PTR), value :: tally + logical(C_BOOL) :: all_nuc + end function + end interface + all_nuc = tally_get_all_nuclides_c(this % ptr) + end function + function tally_get_energyin_filter(this) result(filt) class(TallyObject) :: this integer(C_INT) :: filt @@ -689,31 +757,6 @@ contains end function openmc_tally_get_n_realizations - function openmc_tally_get_nuclides(index, nuclides, n) result(err) bind(C) - ! Return the list of nuclides assigned to a tally - integer(C_INT32_T), value :: index - type(C_PTR), intent(out) :: nuclides - integer(C_INT), intent(out) :: n - integer(C_INT) :: err - - if (index >= 1 .and. index <= size(tallies)) then - associate (t => tallies(index) % obj) - if (allocated(t % nuclide_bins)) then - nuclides = C_LOC(t % nuclide_bins(1)) - n = size(t % nuclide_bins) - err = 0 - else - err = E_ALLOCATE - call set_errmsg("Tally nuclides have not been allocated yet.") - end if - end associate - else - err = E_OUT_OF_BOUNDS - call set_errmsg('Index in tallies array is out of bounds.') - end if - end function openmc_tally_get_nuclides - - function openmc_tally_get_scores(index, scores, n) result(err) bind(C) ! Return the list of nuclides assigned to a tally integer(C_INT32_T), value :: index @@ -856,6 +899,7 @@ contains type(C_PTR), intent(in) :: nuclides(n) integer(C_INT) :: err + integer, allocatable :: bins(:) integer :: i character(C_CHAR), pointer :: string(:) character(len=:, kind=C_CHAR), allocatable :: nuclide_ @@ -863,9 +907,7 @@ contains err = E_UNASSIGNED if (index >= 1 .and. index <= size(tallies)) then associate (t => tallies(index) % obj) - if (allocated(t % nuclide_bins)) deallocate(t % nuclide_bins) - allocate(t % nuclide_bins(n)) - t % n_nuclide_bins = n + allocate(bins(n)) do i = 1, n ! Convert C string to Fortran string @@ -874,10 +916,10 @@ contains select case (nuclide_) case ('total') - t % nuclide_bins(i) = -1 + bins(i) = -1 case default if (nuclide_dict % has(nuclide_)) then - t % nuclide_bins(i) = nuclide_dict % get(nuclide_) + bins(i) = nuclide_dict % get(nuclide_) else err = E_DATA call set_errmsg("Nuclide '" // trim(to_f_string(string)) // & @@ -887,6 +929,8 @@ contains end select end do + call t % set_nuclide_bins(bins) + err = 0 end associate else diff --git a/src/tallies/trigger.F90 b/src/tallies/trigger.F90 index 4c1efcc2ec..56ed00aba6 100644 --- a/src/tallies/trigger.F90 +++ b/src/tallies/trigger.F90 @@ -183,7 +183,7 @@ contains score_index = trigger % score_index ! Initialize score bin index - NUCLIDE_LOOP: do n = 1, t % n_nuclide_bins + NUCLIDE_LOOP: do n = 1, t % n_nuclide_bins() call get_trigger_uncertainty(std_dev, rel_err, & score_index, filter_index, t) From ba479565c592ff171b26e54b53b4f7f1fd588779 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sun, 27 Jan 2019 17:09:21 -0500 Subject: [PATCH 13/58] Move score_collision_tally to C++ --- include/openmc/tallies/filter.h | 2 + include/openmc/tallies/tally.h | 2 +- src/tallies/tally.F90 | 268 +++++++++----------------------- src/tallies/tally.cpp | 168 +++++++++++++++++++- 4 files changed, 243 insertions(+), 197 deletions(-) diff --git a/include/openmc/tallies/filter.h b/include/openmc/tallies/filter.h index fcdca110da..96efa68204 100644 --- a/include/openmc/tallies/filter.h +++ b/include/openmc/tallies/filter.h @@ -22,6 +22,8 @@ class FilterMatch public: std::vector bins_; std::vector weights_; + int i_bin_; + bool bins_present_ {false}; }; } // namespace openmc diff --git a/include/openmc/tallies/tally.h b/include/openmc/tallies/tally.h index 6ffd07be34..dbeecbee24 100644 --- a/include/openmc/tallies/tally.h +++ b/include/openmc/tallies/tally.h @@ -23,7 +23,7 @@ public: //---------------------------------------------------------------------------- // Methods for getting and setting filter/stride data. - const std::vector& filters() {return filters_;} + const std::vector& filters() const {return filters_;} int32_t filters(int i) const {return filters_[i];} diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index 12d9b9477e..ec7756766a 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -27,17 +27,16 @@ module tally procedure(score_analog_tally_), pointer :: score_analog_tally => null() abstract interface - subroutine score_general_(p, t, start_index, filter_index, i_nuclide, & + subroutine score_general_(p, i_tally, start_index, filter_index, i_nuclide, & atom_density, flux) - import Particle - import TallyObject - type(Particle), intent(in) :: p - type(TallyObject), intent(inout) :: t - integer, intent(in) :: start_index - integer, intent(in) :: i_nuclide - integer, intent(in) :: filter_index ! for % results - real(8), intent(in) :: flux ! flux estimate - real(8), intent(in) :: atom_density ! atom/b-cm + import Particle, C_INT, C_DOUBLE + type(Particle), intent(in) :: p + integer(C_INT), intent(in), value :: i_tally + integer(C_INT), intent(in), value :: start_index + integer(C_INT), intent(in), value :: i_nuclide + integer(C_INT), intent(in), value :: filter_index ! for % results + real(C_DOUBLE), intent(in), value :: flux ! flux estimate + real(C_DOUBLE), intent(in), value :: atom_density ! atom/b-cm end subroutine score_general_ subroutine score_analog_tally_(p) @@ -47,6 +46,11 @@ module tally end interface interface + subroutine score_collision_tally(p) bind(C) + import Particle + type(Particle) :: p + end subroutine + subroutine zero_flux_derivs() bind(C) end subroutine end interface @@ -76,15 +80,15 @@ contains ! analog tallies. !=============================================================================== - subroutine score_general_ce(p, t, start_index, filter_index, i_nuclide, & - atom_density, flux) - type(Particle), intent(in) :: p - type(TallyObject), intent(inout) :: t - integer, intent(in) :: start_index - integer, intent(in) :: i_nuclide - integer, intent(in) :: filter_index ! for % results - real(8), intent(in) :: flux ! flux estimate - real(8), intent(in) :: atom_density ! atom/b-cm + subroutine score_general_ce(p, i_tally, start_index, filter_index, i_nuclide, & + atom_density, flux) bind(C) + type(Particle), intent(in) :: p + integer(C_INT), intent(in), value :: i_tally + integer(C_INT), intent(in), value :: start_index + integer(C_INT), intent(in), value :: i_nuclide + integer(C_INT), intent(in), value :: filter_index ! for % results + real(C_DOUBLE), intent(in), value :: flux ! flux estimate + real(C_DOUBLE), intent(in), value :: atom_density ! atom/b-cm integer :: i ! loop index for scoring bins integer :: l ! loop index for nuclides in material @@ -107,6 +111,8 @@ contains real(8) :: E ! particle energy real(8) :: xs ! cross section + associate (t => tallies(i_tally) % obj) + ! Pre-collision energy of particle E = p % last_E @@ -1201,17 +1207,18 @@ contains t % results(RESULT_VALUE, score_index, filter_index) + score end do SCORE_LOOP + end associate end subroutine score_general_ce - subroutine score_general_mg(p, t, start_index, filter_index, i_nuclide, & - atom_density, flux) - type(Particle), intent(in) :: p - type(TallyObject), intent(inout) :: t - integer, intent(in) :: start_index - integer, intent(in) :: i_nuclide - integer, intent(in) :: filter_index ! for % results - real(8), intent(in) :: flux ! flux estimate - real(8), intent(in) :: atom_density ! atom/b-cm + subroutine score_general_mg(p, i_tally, start_index, filter_index, i_nuclide, & + atom_density, flux) bind(C) + type(Particle), intent(in) :: p + integer(C_INT), intent(in), value :: i_tally + integer(C_INT), intent(in), value :: start_index + integer(C_INT), intent(in), value :: i_nuclide + integer(C_INT), intent(in), value :: filter_index ! for % results + real(C_DOUBLE), intent(in), value :: flux ! flux estimate + real(C_DOUBLE), intent(in), value :: atom_density ! atom/b-cm integer :: i ! loop index for scoring bins integer :: q ! loop index for scoring bins @@ -1227,6 +1234,8 @@ contains integer :: p_g ! Particle group to use for getting info ! to tally with. + associate (t => tallies(i_tally) % obj) + ! Set the direction and group to use with get_xs if (t % estimator() == ESTIMATOR_ANALOG .or. & t % estimator() == ESTIMATOR_COLLISION) then @@ -1992,6 +2001,7 @@ contains t % results(RESULT_VALUE, score_index, filter_index) + score end do SCORE_LOOP + end associate end subroutine score_general_mg !=============================================================================== @@ -1999,18 +2009,20 @@ contains ! the user requests all. !=============================================================================== - subroutine score_all_nuclides(p, t, flux, filter_index) + subroutine score_all_nuclides(p, i_tally, flux, filter_index) bind(C) type(Particle), intent(in) :: p - type(TallyObject), intent(inout) :: t - real(8), intent(in) :: flux - integer, intent(in) :: filter_index + integer(C_INT), intent(in), value :: i_tally + real(C_DOUBLE), intent(in), value :: flux + integer(C_INT), intent(in), value :: filter_index integer :: i ! loop index for nuclides in material integer :: i_nuclide ! index in nuclides array real(8) :: atom_density ! atom density of single nuclide in atom/b-cm type(Material), pointer :: mat + associate (t => tallies(i_tally) % obj) + ! Get pointer to current material. We need this in order to determine what ! nuclides are in the material mat => materials(p % material) @@ -2026,7 +2038,7 @@ contains atom_density = mat % atom_density(i) ! Determine score for each bin - call score_general(p, t, (i_nuclide-1)*t % n_score_bins, filter_index, & + call score_general(p, i_tally, (i_nuclide-1)*t % n_score_bins, filter_index, & i_nuclide, atom_density, flux) end do NUCLIDE_LOOP @@ -2038,9 +2050,10 @@ contains atom_density = ZERO ! Determine score for each bin - call score_general(p, t, n_nuclides*t % n_score_bins, filter_index, & + call score_general(p, i_tally, n_nuclides*t % n_score_bins, filter_index, & i_nuclide, atom_density, flux) + end associate end subroutine score_all_nuclides !=============================================================================== @@ -2149,7 +2162,7 @@ contains end if ! Determine score for each bin - call score_general(p, t, (k-1)*t % n_score_bins, filter_index, & + call score_general(p, i_tally, (k-1)*t % n_score_bins, filter_index, & i_nuclide, ZERO, filter_weight) end do NUCLIDE_LOOP @@ -2279,7 +2292,7 @@ contains atom_density = ZERO end if - call score_general(p, t, (k-1)*t % n_score_bins, filter_index, & + call score_general(p, i_tally, (k-1)*t % n_score_bins, filter_index, & i_nuclide, atom_density, filter_weight) end do NUCLIDE_LOOP @@ -2625,7 +2638,7 @@ contains if (t % all_nuclides()) then if (p % material /= MATERIAL_VOID) then - call score_all_nuclides(p, t, flux * filter_weight, filter_index) + call score_all_nuclides(p, i_tally, flux * filter_weight, filter_index) end if else @@ -2650,7 +2663,7 @@ contains end if ! Determine score for each bin - call score_general(p, t, (k-1)*t % n_score_bins, filter_index, & + call score_general(p, i_tally, (k-1)*t % n_score_bins, filter_index, & i_nuclide, atom_density, flux * filter_weight) end do NUCLIDE_BIN_LOOP @@ -2696,163 +2709,6 @@ contains end subroutine score_tracklength_tally -!=============================================================================== -! SCORE_COLLISION_TALLY calculates fluxes and reaction rates based on the -! 1/Sigma_t estimate of the flux. This is triggered after every collision. It -! is invalid for tallies that require post-collison information because it can -! score reactions that didn't actually occur, and we don't a priori know what -! the outcome will be for reactions that we didn't sample. -!=============================================================================== - - subroutine score_collision_tally(p) - - type(Particle), intent(in) :: p - - integer :: i - integer :: i_tally - integer :: i_filt - integer :: i_bin - integer :: j ! loop index for scoring bins - integer :: k ! loop index for nuclide bins - integer :: filter_index ! single index for single bin - integer :: i_nuclide ! index in nuclides array (from bins) - real(8) :: flux ! collision estimate of flux - real(8) :: atom_density ! atom density of single nuclide - ! in atom/b-cm - real(8) :: filter_weight ! combined weight of all filters - logical :: finished ! found all valid bin combinations - type(Material), pointer :: mat - - ! Determine collision estimate of flux - if (survival_biasing) then - ! We need to account for the fact that some weight was already absorbed - flux = (p % last_wgt + p % absorb_wgt) / material_xs % total - else - flux = p % last_wgt / material_xs % total - end if - - ! A loop over all tallies is necessary because we need to simultaneously - ! determine different filter bins for the same tally in order to score to it - - TALLY_LOOP: do i = 1, active_collision_tallies_size() - ! Get index of tally and pointer to tally - i_tally = active_collision_tallies_data(i) - associate (t => tallies(i_tally) % obj) - - ! Find all valid bins in each filter if they have not already been found - ! for a previous tally. - do j = 1, t % n_filters() - i_filt = t % filter(j) - if (.not. filter_matches(i_filt) % bins_present) then - call filter_matches(i_filt) % bins_clear() - call filter_matches(i_filt) % weights_clear() - call filters(i_filt) % obj % get_all_bins(p, t % estimator(), & - filter_matches(i_filt)) - filter_matches(i_filt) % bins_present = .true. - end if - ! If there are no valid bins for this filter, then there is nothing to - ! score and we can move on to the next tally. - if (filter_matches(i_filt) % bins_size() == 0) cycle TALLY_LOOP - - ! Set the index of the bin used in the first filter combination - filter_matches(i_filt) % i_bin = 1 - end do - - ! ======================================================================== - ! Loop until we've covered all valid bins on each of the filters. - - FILTER_LOOP: do - - ! Reset scoring index and weight - filter_index = 1 - filter_weight = ONE - - ! Determine scoring index and weight for this filter combination - do j = 1, t % n_filters() - i_filt = t % filter(j) - i_bin = filter_matches(i_filt) % i_bin - filter_index = filter_index + (filter_matches(i_filt) & - % bins_data(i_bin) - 1) * t % stride(j) - filter_weight = filter_weight * filter_matches(i_filt) & - % weights_data(i_bin) - end do - - ! ====================================================================== - ! Nuclide logic - - if (t % all_nuclides()) then - if (p % material /= MATERIAL_VOID) then - call score_all_nuclides(p, t, flux * filter_weight, filter_index) - end if - else - - NUCLIDE_BIN_LOOP: do k = 1, t % n_nuclide_bins() - ! Get index of nuclide in nuclides array - i_nuclide = t % nuclide_bins(k) - - if (i_nuclide > 0) then - if (p % material /= MATERIAL_VOID) then - ! Get pointer to current material - mat => materials(p % material) - - ! Determine index of nuclide in Material % atom_density array - j = mat % mat_nuclide_index(i_nuclide) - if (j == 0) cycle NUCLIDE_BIN_LOOP - - ! Copy corresponding atom density - atom_density = mat % atom_density(j) - else - atom_density = ZERO - end if - end if - - ! Determine score for each bin - call score_general(p, t, (k-1)*t % n_score_bins, filter_index, & - i_nuclide, atom_density, flux * filter_weight) - - end do NUCLIDE_BIN_LOOP - - end if - - ! ====================================================================== - ! Filter logic - - ! Increment the filter bins, starting with the last filter to find the - ! next valid bin combination - finished = .true. - do j = t % n_filters(), 1, -1 - i_filt = t % filter(j) - if (filter_matches(i_filt) % i_bin < filter_matches(i_filt) % & - bins_size()) then - filter_matches(i_filt) % i_bin = filter_matches(i_filt) % i_bin + 1 - finished = .false. - exit - else - filter_matches(i_filt) % i_bin = 1 - end if - end do - - ! Once we have finished all valid bins for each of the filters, exit - ! the loop. - if (finished) exit FILTER_LOOP - - end do FILTER_LOOP - - ! If the user has specified that we can assume all tallies are spatially - ! separate, this implies that once a tally has been scored to, we needn't - ! check the others. This cuts down on overhead when there are many - ! tallies specified - - if (assume_separate) exit TALLY_LOOP - - end associate - end do TALLY_LOOP - - ! Reset filter matches flag - filter_matches(:) % bins_present = .false. - - end subroutine score_collision_tally - !=============================================================================== ! score_surface_tally is called at every surface crossing and can be used to ! tally total or partial currents between two cells @@ -3869,4 +3725,26 @@ contains end if end function openmc_tally_allocate +!=============================================================================== +! Functions for C++ interop +!=============================================================================== + + function material_nuclide_index(i_material, i_nuclide) result(i) bind(C) + integer(C_INT), value :: i_material, i_nuclide + integer(C_INT) :: i + i = materials(i_material) % mat_nuclide_index(i_nuclide) + end function + + function material_atom_density(i_material, i) result(dens) bind(C) + integer(C_INT), value :: i_material, i + real(C_DOUBLE) :: dens + dens = materials(i_material) % atom_density(i) + end function + + function tally_get_n_score_bins(i_tally) result(n) bind(C) + integer(C_INT), value :: i_tally + integer(C_INT) :: n + n = tallies(i_tally) % obj % n_score_bins + end function + end module tally diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 81d248ec8b..01661141f3 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -3,13 +3,15 @@ #include "openmc/capi.h" #include "openmc/constants.h" #include "openmc/error.h" +#include "openmc/message_passing.h" +#include "openmc/nuclide.h" +#include "openmc/settings.h" #include "openmc/tallies/derivative.h" #include "openmc/tallies/filter.h" #include "openmc/tallies/filter_energy.h" #include "openmc/tallies/filter_delayedgroup.h" #include "openmc/tallies/filter_surface.h" #include "openmc/tallies/filter_mesh.h" -#include "openmc/message_passing.h" #include "xtensor/xadapt.hpp" #include "xtensor/xbuilder.hpp" // for empty_like @@ -20,6 +22,27 @@ namespace openmc { +//============================================================================== +// Functions defined in Fortran +//============================================================================== + +extern "C" void +score_general_ce(Particle* p, int i_tally, int start_index, int filter_index, + int i_nuclide, double atom_density, double flux); + +extern "C" void +score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, + int i_nuclide, double atom_density, double flux); + +extern "C" void +score_all_nuclides(Particle* p, int i_tally, double flux, int filter_index); + +extern "C" int material_nuclide_index(int i_material, int i_nuclide); + +extern "C" double material_atom_density(int i_material, int i); + +extern "C" int tally_get_n_score_bins(int i_tally); + //============================================================================== // Global variable definitions //============================================================================== @@ -120,6 +143,139 @@ adaptor_type<3> tally_results(int idx) return xt::adapt(results, size, xt::no_ownership(), shape); } +//! Score tallies using a 1 / Sigma_t estimate of the flux. +// +//! This is triggered after every collision. It is invalid for tallies that +//! require post-collison information because it can score reactions that didn't +//! actually occur, and we don't a priori know what the outcome will be for +//! reactions that we didn't sample. + +extern "C" void +score_collision_tally(Particle* p) +{ + // Determine the collision estimate of the flux + double flux; + if (!settings::survival_biasing) { + flux = p->last_wgt / simulation::material_xs.total; + } else { + flux = (p->last_wgt + p->absorb_wgt) / simulation::material_xs.total; + } + + for (auto i_tally : model::active_collision_tallies) { + //TODO: off-by-one + const Tally& tally {*model::tallies[i_tally-1]}; + auto n_score_bins = tally_get_n_score_bins(i_tally); + + //-------------------------------------------------------------------------- + // Loop through all relevant filters and find the filter bins and weights + // for this event. + + // Find all valid bins in each filter if they have not already been found + // for a previous tally. + for (auto i_filt : tally.filters()) { + //TODO: off-by-one + auto& match {simulation::filter_matches[i_filt-1]}; + if (!match.bins_present_) { + match.bins_.clear(); + match.weights_.clear(); + //TODO: off-by-one + model::tally_filters[i_filt-1] + ->get_all_bins(p, tally.estimator_, match); + match.bins_present_ = true; + } + + // If there are no valid bins for this filter, then there is nothing to + // score so we can move on to the next tally. + if (match.bins_.size() == 0) goto next_tally; + + // Set the index of the bin used in the first filter combination + match.i_bin_ = 1; + } + + //-------------------------------------------------------------------------- + // Loop over filter bins and nuclide bins + + for (bool filter_loop_done = false; !filter_loop_done; ) { + + //------------------------------------------------------------------------ + // Filter logic + + // Determine scoring index and weight for the current filter combination + int filter_index = 1; + double filter_weight = 1.; + for (auto i = 0; i < tally.filters().size(); ++i) { + auto i_filt = tally.filters(i); + //TODO: off-by-one + auto& match {simulation::filter_matches[i_filt-1]}; + auto i_bin = match.i_bin_; + //TODO: off-by-one + filter_index += (match.bins_[i_bin-1] - 1) * tally.strides(i); + filter_weight *= match.weights_[i_bin-1]; + } + + //------------------------------------------------------------------------ + // Nuclide logic + + if (tally.all_nuclides_) { + score_all_nuclides(p, i_tally, flux*filter_weight, filter_index); + } else { + for (auto i = 0; i < tally.nuclides_.size(); ++i) { + auto i_nuclide = tally.nuclides_[i]; + + double atom_density = 0.; + if (i_nuclide > 0) { + auto j = material_nuclide_index(p->material, i_nuclide); + if (j == 0) continue; + atom_density = material_atom_density(p->material, j); + } + + //TODO: consider replacing this "if" with pointers or templates + if (settings::run_CE) { + score_general_ce(p, i_tally, i*n_score_bins, filter_index, + i_nuclide, atom_density, flux*filter_weight); + } else { + score_general_mg(p, i_tally, i*n_score_bins, filter_index, + i_nuclide, atom_density, flux*filter_weight); + } + } + } + + //------------------------------------------------------------------------ + // Further filter logic + + // Increment the filter bins, starting with the last filter to find the + // next valid bin combination + filter_loop_done = true; + for (int i = tally.filters().size()-1; i >= 0; --i) { + auto i_filt = tally.filters(i); + //TODO: off-by-one + auto& match {simulation::filter_matches[i_filt-1]}; + if (match.i_bin_ < match.bins_.size()) { + ++match.i_bin_; + filter_loop_done = false; + break; + } else { + match.i_bin_ = 1; + } + } + } + + // If the user has specified that we can assume all tallies are spatially + // separate, this implies that once a tally has been scored to, we needn't + // check the others. This cuts down on overhead when there are many + // tallies specified + if (settings::assume_separate) break; + +next_tally: + ; + } + + // Reset all the filter matches for the next tally event. + for (auto& match : simulation::filter_matches) + match.bins_present_ = false; +} + + #ifdef OPENMC_MPI void reduce_tally_results() { @@ -253,6 +409,8 @@ openmc_tally_get_type(int32_t index, int32_t* type) } //TODO: off-by-one *type = model::tallies[index-1]->type_; + + return 0; } extern "C" int @@ -274,6 +432,8 @@ openmc_tally_set_type(int32_t index, const char* type) set_errmsg(errmsg); return OPENMC_E_INVALID_ARGUMENT; } + + return 0; } extern "C" int @@ -285,6 +445,8 @@ openmc_tally_get_active(int32_t index, bool* active) } //TODO: off-by-one *active = model::tallies[index-1]->active_; + + return 0; } extern "C" int @@ -296,6 +458,8 @@ openmc_tally_set_active(int32_t index, bool active) } //TODO: off-by-one model::tallies[index-1]->active_ = active; + + return 0; } extern "C" int @@ -345,6 +509,8 @@ openmc_tally_get_nuclides(int32_t index, int** nuclides, int* n) //TODO: off-by-one *n = model::tallies[index-1]->nuclides_.size(); *nuclides = model::tallies[index-1]->nuclides_.data(); + + return 0; } //============================================================================== From c3664ab125b2d174c98ea6623497da47c7508e3b Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sun, 27 Jan 2019 20:29:17 -0500 Subject: [PATCH 14/58] Move score_tracklength_tallies to C++ --- src/tallies/filter.cpp | 8 ++ src/tallies/tally.F90 | 199 ++++------------------------ src/tallies/tally.cpp | 129 ++++++++++++++++++ src/tallies/tally_filter_header.F90 | 31 ++++- 4 files changed, 192 insertions(+), 175 deletions(-) diff --git a/src/tallies/filter.cpp b/src/tallies/filter.cpp index c5edb2f864..7c641f8545 100644 --- a/src/tallies/filter.cpp +++ b/src/tallies/filter.cpp @@ -50,6 +50,14 @@ std::vector> tally_filters; extern "C" { // filter_match_point moved to simulation.cpp + int + filter_match_get_i_bin(FilterMatch* match) + {return match->i_bin_;} + + void + filter_match_set_i_bin(FilterMatch* match, int i) + {match->i_bin_ = i;} + void filter_match_bins_push_back(FilterMatch* match, int val) {match->bins_.push_back(val);} diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index ec7756766a..ce4355151c 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -46,6 +46,12 @@ module tally end interface interface + subroutine score_tracklength_tally(p, distance) bind(C) + import Particle, C_DOUBLE + type(Particle) :: p + real(C_DOUBLE), value :: distance + end subroutine + subroutine score_collision_tally(p) bind(C) import Particle type(Particle) :: p @@ -2100,7 +2106,7 @@ contains if (filter_matches(i_filt) % bins_size() == 0) cycle TALLY_LOOP ! Set the index of the bin used in the first filter combination - filter_matches(i_filt) % i_bin = 1 + call filter_matches(i_filt) % set_i_bin(1) end do ! ======================================================================== @@ -2115,7 +2121,7 @@ contains ! Determine scoring index and weight for this filter combination do j = 1, t % n_filters() i_filt = t % filter(j) - i_bin = filter_matches(i_filt) % i_bin + i_bin = filter_matches(i_filt) % i_bin() filter_index = filter_index + (filter_matches(i_filt) & % bins_data(i_bin) - 1) * t % stride(j) filter_weight = filter_weight * filter_matches(i_filt) & @@ -2175,13 +2181,13 @@ contains finished = .true. do j = t % n_filters(), 1, -1 i_filt = t % filter(j) - if (filter_matches(i_filt) % i_bin < filter_matches(i_filt) % & + if (filter_matches(i_filt) % i_bin() < filter_matches(i_filt) % & bins_size()) then - filter_matches(i_filt) % i_bin = filter_matches(i_filt) % i_bin + 1 + call filter_matches(i_filt) % set_i_bin(filter_matches(i_filt) % i_bin() + 1) finished = .false. exit else - filter_matches(i_filt) % i_bin = 1 + call filter_matches(i_filt) % set_i_bin(1) end if end do @@ -2246,7 +2252,7 @@ contains if (filter_matches(i_filt) % bins_size() == 0) cycle TALLY_LOOP ! Set the index of the bin used in the first filter combination - filter_matches(i_filt) % i_bin = 1 + call filter_matches(i_filt) % set_i_bin(1) end do ! ======================================================================== @@ -2261,7 +2267,7 @@ contains ! Determine scoring index and weight for this filter combination do j = 1, t % n_filters() i_filt = t % filter(j) - i_bin = filter_matches(i_filt) % i_bin + i_bin = filter_matches(i_filt) % i_bin() filter_index = filter_index + (filter_matches(i_filt) & % bins_data(i_bin) - 1) * t % stride(j) filter_weight = filter_weight * filter_matches(i_filt) & @@ -2304,13 +2310,13 @@ contains finished = .true. do j = t % n_filters(), 1, -1 i_filt = t % filter(j) - if (filter_matches(i_filt) % i_bin < filter_matches(i_filt) % & + if (filter_matches(i_filt) % i_bin() < filter_matches(i_filt) % & bins_size()) then - filter_matches(i_filt) % i_bin = filter_matches(i_filt) % i_bin + 1 + call filter_matches(i_filt) % set_i_bin(filter_matches(i_filt) % i_bin() + 1) finished = .false. exit else - filter_matches(i_filt) % i_bin = 1 + call filter_matches(i_filt) % set_i_bin(1) end if end do @@ -2369,7 +2375,7 @@ contains ! save original outgoing energy bin and score index i = t % filter(t % energyout_filter()) - i_bin = filter_matches(i) % i_bin + i_bin = filter_matches(i) % i_bin() bin_energyout = filter_matches(i) % bins_data(i_bin) ! declare the energyout filter type @@ -2434,7 +2440,7 @@ contains i_filter = 1 do l = 1, t % n_filters() i_filter = i_filter + (filter_matches(t % filter(l)) & - % bins_data(filter_matches(t % filter(l)) % i_bin) - 1) * & + % bins_data(filter_matches(t % filter(l)) % i_bin()) - 1) * & t % stride(l) end do @@ -2474,7 +2480,7 @@ contains ! combination do l = 1, t % n_filters() f = t % filter(l) - b = filter_matches(f) % i_bin + b = filter_matches(f) % i_bin() i_filter = i_filter + (filter_matches(f) & % bins_data(b) - 1) * t % stride(l) filter_weight = filter_weight * filter_matches(f) & @@ -2497,7 +2503,7 @@ contains ! determine scoring index and weight for this filter combination do l = 1, t % n_filters() f = t % filter(l) - b = filter_matches(f) % i_bin + b = filter_matches(f) % i_bin() i_filter = i_filter + (filter_matches(f) % bins_data(b) - 1) & * t % stride(l) filter_weight = filter_weight * filter_matches(f) & @@ -2538,7 +2544,7 @@ contains ! save original delayed group bin i_filt = t % filter(t % delayedgroup_filter()) - i_bin = filter_matches(i_filt) % i_bin + i_bin = filter_matches(i_filt) % i_bin() bin_original = filter_matches(i_filt) % bins_data(i_bin) call filter_matches(i_filt) % bins_set_data(i_bin, d_bin) @@ -2546,7 +2552,7 @@ contains filter_index = 1 do i = 1, t % n_filters() filter_index = filter_index + (filter_matches(t % filter(i)) % & - bins_data(filter_matches(t % filter(i)) % i_bin) - 1) * t % stride(i) + bins_data(filter_matches(t % filter(i)) % i_bin()) - 1) * t % stride(i) end do !$omp atomic @@ -2558,157 +2564,6 @@ contains end subroutine score_fission_delayed_dg -!=============================================================================== -! SCORE_TRACKLENGTH_TALLY calculates fluxes and reaction rates based on the -! track-length estimate of the flux. This is triggered at every event (surface -! crossing, lattice crossing, or collision) and thus cannot be done for tallies -! that require post-collision information. -!=============================================================================== - - subroutine score_tracklength_tally(p, distance) - - type(Particle), intent(in) :: p - real(8), intent(in) :: distance - - integer :: i - integer :: i_tally - integer :: i_filt - integer :: i_bin - integer :: j ! loop index for scoring bins - integer :: k ! loop index for nuclide bins - integer :: filter_index ! single index for single bin - integer :: i_nuclide ! index in nuclides array (from bins) - real(8) :: flux ! tracklength estimate of flux - real(8) :: atom_density ! atom density of single nuclide in atom/b-cm - real(8) :: filter_weight ! combined weight of all filters - logical :: finished ! found all valid bin combinations - type(Material), pointer :: mat - - ! Determine track-length estimate of flux - flux = p % wgt * distance - - ! A loop over all tallies is necessary because we need to simultaneously - ! determine different filter bins for the same tally in order to score to it - - TALLY_LOOP: do i = 1, active_tracklength_tallies_size() - ! Get index of tally and pointer to tally - i_tally = active_tracklength_tallies_data(i) - associate (t => tallies(i_tally) % obj) - - ! Find all valid bins in each filter if they have not already been found - ! for a previous tally. - do j = 1, t % n_filters() - i_filt = t % filter(j) - if (.not. filter_matches(i_filt) % bins_present) then - call filter_matches(i_filt) % bins_clear() - call filter_matches(i_filt) % weights_clear() - call filters(i_filt) % obj % get_all_bins(p, t % estimator(), & - filter_matches(i_filt)) - filter_matches(i_filt) % bins_present = .true. - end if - ! If there are no valid bins for this filter, then there is nothing to - ! score and we can move on to the next tally. - if (filter_matches(i_filt) % bins_size() == 0) cycle TALLY_LOOP - - ! Set the index of the bin used in the first filter combination - filter_matches(i_filt) % i_bin = 1 - end do - - ! ======================================================================== - ! Loop until we've covered all valid bins on each of the filters. - - FILTER_LOOP: do - - ! Reset scoring index and weight - filter_index = 1 - filter_weight = ONE - - ! Determine scoring index and weight for this filter combination - do j = 1, t % n_filters() - i_filt = t % filter(j) - i_bin = filter_matches(i_filt) % i_bin - filter_index = filter_index + (filter_matches(i_filt) & - % bins_data(i_bin) - 1) * t % stride(j) - filter_weight = filter_weight * filter_matches(i_filt) & - % weights_data(i_bin) - end do - - ! ====================================================================== - ! Nuclide logic - - if (t % all_nuclides()) then - if (p % material /= MATERIAL_VOID) then - call score_all_nuclides(p, i_tally, flux * filter_weight, filter_index) - end if - else - - NUCLIDE_BIN_LOOP: do k = 1, t % n_nuclide_bins() - ! Get index of nuclide in nuclides array - i_nuclide = t % nuclide_bins(k) - - if (i_nuclide > 0) then - if (p % material /= MATERIAL_VOID) then - ! Get pointer to current material - mat => materials(p % material) - - ! Determine index of nuclide in Material % atom_density array - j = mat % mat_nuclide_index(i_nuclide) - if (j == 0) cycle NUCLIDE_BIN_LOOP - - ! Copy corresponding atom density - atom_density = mat % atom_density(j) - else - atom_density = ZERO - end if - end if - - ! Determine score for each bin - call score_general(p, i_tally, (k-1)*t % n_score_bins, filter_index, & - i_nuclide, atom_density, flux * filter_weight) - - end do NUCLIDE_BIN_LOOP - - end if - - ! ====================================================================== - ! Filter logic - - ! Increment the filter bins, starting with the last filter to find the - ! next valid bin combination - finished = .true. - do j = t % n_filters(), 1, -1 - i_filt = t % filter(j) - if (filter_matches(i_filt) % i_bin < filter_matches(i_filt) % & - bins_size()) then - filter_matches(i_filt) % i_bin = filter_matches(i_filt) % i_bin + 1 - finished = .false. - exit - else - filter_matches(i_filt) % i_bin = 1 - end if - end do - - ! Once we have finished all valid bins for each of the filters, exit - ! the loop. - if (finished) exit FILTER_LOOP - - end do FILTER_LOOP - - ! If the user has specified that we can assume all tallies are spatially - ! separate, this implies that once a tally has been scored to, we needn't - ! check the others. This cuts down on overhead when there are many - ! tallies specified - - if (assume_separate) exit TALLY_LOOP - - end associate - end do TALLY_LOOP - - ! Reset filter matches flag - filter_matches(:) % bins_present = .false. - - end subroutine score_tracklength_tally - !=============================================================================== ! score_surface_tally is called at every surface crossing and can be used to ! tally total or partial currents between two cells @@ -2757,7 +2612,7 @@ contains if (filter_matches(i_filt) % bins_size() == 0) cycle TALLY_LOOP ! Set the index of the bin used in the first filter combination - filter_matches(i_filt) % i_bin = 1 + call filter_matches(i_filt) % set_i_bin(1) end do ! ======================================================================== @@ -2772,7 +2627,7 @@ contains ! Determine scoring index and weight for this filter combination do j = 1, t % n_filters() i_filt = t % filter(j) - i_bin = filter_matches(i_filt) % i_bin + i_bin = filter_matches(i_filt) % i_bin() filter_index = filter_index + (filter_matches(i_filt) & % bins_data(i_bin) - 1) * t % stride(j) filter_weight = filter_weight * filter_matches(i_filt) & @@ -2808,13 +2663,13 @@ contains finished = .true. do j = t % n_filters(), 1, -1 i_filt = t % filter(j) - if (filter_matches(i_filt) % i_bin < filter_matches(i_filt) % & + if (filter_matches(i_filt) % i_bin() < filter_matches(i_filt) % & bins_size()) then - filter_matches(i_filt) % i_bin = filter_matches(i_filt) % i_bin + 1 + call filter_matches(i_filt) % set_i_bin(filter_matches(i_filt) % i_bin() + 1) finished = .false. exit else - filter_matches(i_filt) % i_bin = 1 + call filter_matches(i_filt) % set_i_bin(1) end if end do diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 01661141f3..32bc14329f 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -143,6 +143,135 @@ adaptor_type<3> tally_results(int idx) return xt::adapt(results, size, xt::no_ownership(), shape); } +//! Score tallies using a tracklength estimate of the flux. +// +//! This is triggered at every event (surface crossing, lattice crossing, or +//! collision) and thus cannot be done for tallies that require post-collision +//! information. + +extern "C" void +score_tracklength_tally(Particle* p, double distance) +{ + // Determine the tracklength estimate of the flux + double flux = p->wgt * distance; + + for (auto i_tally : model::active_tracklength_tallies) { + //TODO: off-by-one + const Tally& tally {*model::tallies[i_tally-1]}; + auto n_score_bins = tally_get_n_score_bins(i_tally); + + //-------------------------------------------------------------------------- + // Loop through all relevant filters and find the filter bins and weights + // for this event. + + // Find all valid bins in each filter if they have not already been found + // for a previous tally. + for (auto i_filt : tally.filters()) { + //TODO: off-by-one + auto& match {simulation::filter_matches[i_filt-1]}; + if (!match.bins_present_) { + match.bins_.clear(); + match.weights_.clear(); + //TODO: off-by-one + model::tally_filters[i_filt-1] + ->get_all_bins(p, tally.estimator_, match); + match.bins_present_ = true; + } + + // If there are no valid bins for this filter, then there is nothing to + // score so we can move on to the next tally. + if (match.bins_.size() == 0) goto next_tally; + + // Set the index of the bin used in the first filter combination + match.i_bin_ = 1; + } + + //-------------------------------------------------------------------------- + // Loop over filter bins and nuclide bins + + for (bool filter_loop_done = false; !filter_loop_done; ) { + + //------------------------------------------------------------------------ + // Filter logic + + // Determine scoring index and weight for the current filter combination + int filter_index = 1; + double filter_weight = 1.; + for (auto i = 0; i < tally.filters().size(); ++i) { + auto i_filt = tally.filters(i); + //TODO: off-by-one + auto& match {simulation::filter_matches[i_filt-1]}; + auto i_bin = match.i_bin_; + //TODO: off-by-one + filter_index += (match.bins_[i_bin-1] - 1) * tally.strides(i); + filter_weight *= match.weights_[i_bin-1]; + } + + //------------------------------------------------------------------------ + // Nuclide logic + + if (tally.all_nuclides_) { + if (p->material != MATERIAL_VOID) + score_all_nuclides(p, i_tally, flux*filter_weight, filter_index); + } else { + for (auto i = 0; i < tally.nuclides_.size(); ++i) { + auto i_nuclide = tally.nuclides_[i]; + + double atom_density = 0.; + if (i_nuclide > 0) { + if (p->material != MATERIAL_VOID) { + auto j = material_nuclide_index(p->material, i_nuclide); + if (j == 0) continue; + atom_density = material_atom_density(p->material, j); + } + } + + //TODO: consider replacing this "if" with pointers or templates + if (settings::run_CE) { + score_general_ce(p, i_tally, i*n_score_bins, filter_index, + i_nuclide, atom_density, flux*filter_weight); + } else { + score_general_mg(p, i_tally, i*n_score_bins, filter_index, + i_nuclide, atom_density, flux*filter_weight); + } + } + } + + //------------------------------------------------------------------------ + // Further filter logic + + // Increment the filter bins, starting with the last filter to find the + // next valid bin combination + filter_loop_done = true; + for (int i = tally.filters().size()-1; i >= 0; --i) { + auto i_filt = tally.filters(i); + //TODO: off-by-one + auto& match {simulation::filter_matches[i_filt-1]}; + if (match.i_bin_ < match.bins_.size()) { + ++match.i_bin_; + filter_loop_done = false; + break; + } else { + match.i_bin_ = 1; + } + } + } + + // If the user has specified that we can assume all tallies are spatially + // separate, this implies that once a tally has been scored to, we needn't + // check the others. This cuts down on overhead when there are many + // tallies specified + if (settings::assume_separate) break; + +next_tally: + ; + } + + // Reset all the filter matches for the next tally event. + for (auto& match : simulation::filter_matches) + match.bins_present_ = false; +} + //! Score tallies using a 1 / Sigma_t estimate of the flux. // //! This is triggered after every collision. It is invalid for tallies that diff --git a/src/tallies/tally_filter_header.F90 b/src/tallies/tally_filter_header.F90 index 0d2ada1684..5a8784ae08 100644 --- a/src/tallies/tally_filter_header.F90 +++ b/src/tallies/tally_filter_header.F90 @@ -36,13 +36,12 @@ module tally_filter_header type, public :: TallyFilterMatch type(C_PTR) :: ptr - ! Index of the bin and weight being used in the current filter combination - integer :: i_bin - ! Indicates whether all valid bins for this filter have been found logical :: bins_present = .false. contains + procedure :: i_bin + procedure :: set_i_bin procedure :: bins_push_back procedure :: weights_push_back procedure :: bins_clear @@ -150,6 +149,32 @@ contains ! TallyFilterMatch implementation !=============================================================================== + function i_bin(this) result(i) + class(TallyFilterMatch) :: this + integer :: i + interface + function filter_match_get_i_bin(ptr) result(i) bind(C) + import C_PTR, C_INT + type(C_PTR), value :: ptr + integer(C_INT) :: i + end function + end interface + i = filter_match_get_i_bin(this % ptr) + end function + + subroutine set_i_bin(this, i) + class(TallyFilterMatch) :: this + integer :: i + interface + subroutine filter_match_set_i_bin(ptr, i) bind(C) + import C_PTR, C_INT + type(C_PTR), value :: ptr + integer(C_INT), value :: i + end subroutine + end interface + call filter_match_set_i_bin(this % ptr, i) + end subroutine + subroutine bins_push_back(this, val) class(TallyFilterMatch), intent(inout) :: this integer, intent(in) :: val From 67b2185860b6d949d5f758353c3fc3137851f757 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sun, 27 Jan 2019 21:30:18 -0500 Subject: [PATCH 15/58] Move score_surface_tally to C++ --- include/openmc/tallies/tally.h | 4 +- src/tallies/tally.F90 | 151 +++----------------------------- src/tallies/tally.cpp | 154 +++++++++++++++++++++++++++++---- src/tallies/tally_header.F90 | 24 ++--- src/tracking.F90 | 22 +++-- 5 files changed, 168 insertions(+), 187 deletions(-) diff --git a/include/openmc/tallies/tally.h b/include/openmc/tallies/tally.h index dbeecbee24..a41c82b61a 100644 --- a/include/openmc/tallies/tally.h +++ b/include/openmc/tallies/tally.h @@ -89,8 +89,8 @@ namespace model { extern std::vector active_analog_tallies; extern std::vector active_tracklength_tallies; extern std::vector active_collision_tallies; - //extern std::vector active_meshsurf_tallies; - //extern std::vector active_surface_tallies; + extern std::vector active_meshsurf_tallies; + extern std::vector active_surface_tallies; } // Threadprivate variables diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index ce4355151c..893a21472a 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -57,6 +57,16 @@ module tally type(Particle) :: p end subroutine + subroutine score_meshsurface_tally(p) bind(C) + import Particle + type(Particle) :: p + end subroutine + + subroutine score_surface_tally(p) bind(C) + import Particle + type(Particle) :: p + end subroutine + subroutine zero_flux_derivs() bind(C) end subroutine end interface @@ -2564,137 +2574,6 @@ contains end subroutine score_fission_delayed_dg -!=============================================================================== -! score_surface_tally is called at every surface crossing and can be used to -! tally total or partial currents between two cells -!=============================================================================== - - subroutine score_surface_tally(p, tally_vec) - type(Particle), intent(in) :: p - type(VectorInt), intent(in) :: tally_vec - - integer :: i - integer :: i_tally - integer :: i_filt - integer :: i_bin - integer :: q ! loop index for scoring bins - integer :: k ! working index for expand and score - integer :: score_bin ! scoring bin, e.g. SCORE_FLUX - integer :: score_index ! scoring bin index - integer :: j ! loop index for scoring bins - integer :: filter_index ! single index for single bin - real(8) :: flux ! collision estimate of flux - real(8) :: filter_weight ! combined weight of all filters - real(8) :: score ! analog tally score - logical :: finished ! found all valid bin combinations - - ! No collision, so no weight change when survival biasing - flux = p % wgt - - TALLY_LOOP: do i = 1, tally_vec % size() - ! Get index of tally and pointer to tally - i_tally = tally_vec % data(i) - associate (t => tallies(i_tally) % obj) - - ! Find all valid bins in each filter if they have not already been found - ! for a previous tally. - do j = 1, t % n_filters() - i_filt = t % filter(j) - if (.not. filter_matches(i_filt) % bins_present) then - call filter_matches(i_filt) % bins_clear() - call filter_matches(i_filt) % weights_clear() - call filters(i_filt) % obj % get_all_bins(p, t % estimator(), & - filter_matches(i_filt)) - filter_matches(i_filt) % bins_present = .true. - end if - ! If there are no valid bins for this filter, then there is nothing to - ! score and we can move on to the next tally. - if (filter_matches(i_filt) % bins_size() == 0) cycle TALLY_LOOP - - ! Set the index of the bin used in the first filter combination - call filter_matches(i_filt) % set_i_bin(1) - end do - - ! ======================================================================== - ! Loop until we've covered all valid bins on each of the filters. - - FILTER_LOOP: do - - ! Reset scoring index and weight - filter_index = 1 - filter_weight = ONE - - ! Determine scoring index and weight for this filter combination - do j = 1, t % n_filters() - i_filt = t % filter(j) - i_bin = filter_matches(i_filt) % i_bin() - filter_index = filter_index + (filter_matches(i_filt) & - % bins_data(i_bin) - 1) * t % stride(j) - filter_weight = filter_weight * filter_matches(i_filt) & - % weights_data(i_bin) - end do - - ! Determine score - score = flux * filter_weight - - ! Currently only one score type - k = 0 - SCORE_LOOP: do q = 1, t % n_score_bins - k = k + 1 - - ! determine what type of score bin - score_bin = t % score_bins(q) - - ! determine scoring bin index, no offset from nuclide bins - score_index = q - - ! Expand score if necessary and add to tally results. -!$omp atomic - t % results(RESULT_VALUE, score_index, filter_index) = & - t % results(RESULT_VALUE, score_index, filter_index) + score - - end do SCORE_LOOP - - ! ====================================================================== - ! Filter logic - - ! Increment the filter bins, starting with the last filter to find the - ! next valid bin combination - finished = .true. - do j = t % n_filters(), 1, -1 - i_filt = t % filter(j) - if (filter_matches(i_filt) % i_bin() < filter_matches(i_filt) % & - bins_size()) then - call filter_matches(i_filt) % set_i_bin(filter_matches(i_filt) % i_bin() + 1) - finished = .false. - exit - else - call filter_matches(i_filt) % set_i_bin(1) - end if - end do - - ! Once we have finished all valid bins for each of the filters, exit - ! the loop. - if (finished) exit FILTER_LOOP - - end do FILTER_LOOP - - end associate - - ! If the user has specified that we can assume all tallies are spatially - ! separate, this implies that once a tally has been scored to, we needn't - ! check the others. This cuts down on overhead when there are many - ! tallies specified - - if (assume_separate) exit TALLY_LOOP - - end do TALLY_LOOP - - ! Reset filter matches flag - filter_matches(:) % bins_present = .false. - - end subroutine score_surface_tally - !=============================================================================== ! APPLY_DERIVATIVE_TO_SCORE multiply the given score by its relative derivative !=============================================================================== @@ -3508,20 +3387,10 @@ contains call setup_active_tallies_c() - call active_surface_tallies % clear() - call active_meshsurf_tallies % clear() - do i = 1, n_tallies associate (t => tallies(i) % obj) err = openmc_tally_get_active(i, active) if (active) then - ! Check what type of tally this is and add it to the appropriate list - if (t % type() == TALLY_MESH_SURFACE) then - call active_meshsurf_tallies % push_back(i) - elseif (t % type() == TALLY_SURFACE) then - call active_surface_tallies % push_back(i) - end if - ! Check if tally contains depletion reactions and if so, set flag if (t % depletion_rx) need_depletion_rx = .true. end if diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 32bc14329f..94cf6d357a 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -54,8 +54,8 @@ namespace model { std::vector active_analog_tallies; std::vector active_tracklength_tallies; std::vector active_collision_tallies; - //std::vector active_meshsurf_tallies; - //std::vector active_surface_tallies; + std::vector active_meshsurf_tallies; + std::vector active_surface_tallies; } double global_tally_absorption; @@ -404,6 +404,130 @@ next_tally: match.bins_present_ = false; } +//! Score surface or mesh-surface tallies for particle currents. + +static void +score_surface_tally_inner(Particle* p, const std::vector& tallies) +{ + // No collision, so no weight change when survival biasing + double flux = p->wgt; + + for (auto i_tally : tallies) { + //TODO: off-by-one + const Tally& tally {*model::tallies[i_tally-1]}; + auto n_score_bins = tally_get_n_score_bins(i_tally); + auto results = tally_results(i_tally); + + //-------------------------------------------------------------------------- + // Loop through all relevant filters and find the filter bins and weights + // for this event. + + // Find all valid bins in each filter if they have not already been found + // for a previous tally. + for (auto i_filt : tally.filters()) { + //TODO: off-by-one + auto& match {simulation::filter_matches[i_filt-1]}; + if (!match.bins_present_) { + match.bins_.clear(); + match.weights_.clear(); + //TODO: off-by-one + model::tally_filters[i_filt-1] + ->get_all_bins(p, tally.estimator_, match); + match.bins_present_ = true; + } + + // If there are no valid bins for this filter, then there is nothing to + // score so we can move on to the next tally. + if (match.bins_.size() == 0) goto next_tally; + + // Set the index of the bin used in the first filter combination + match.i_bin_ = 1; + } + + //-------------------------------------------------------------------------- + // Loop over filter bins and nuclide bins + + for (bool filter_loop_done = false; !filter_loop_done; ) { + + //------------------------------------------------------------------------ + // Filter logic + + // Determine scoring index and weight for the current filter combination + int filter_index = 1; + double filter_weight = 1.; + for (auto i = 0; i < tally.filters().size(); ++i) { + auto i_filt = tally.filters(i); + //TODO: off-by-one + auto& match {simulation::filter_matches[i_filt-1]}; + auto i_bin = match.i_bin_; + //TODO: off-by-one + filter_index += (match.bins_[i_bin-1] - 1) * tally.strides(i); + filter_weight *= match.weights_[i_bin-1]; + } + + //------------------------------------------------------------------------ + // Scoring loop + + // There is only one score type for current tallies so there is no need + // for a further scoring function. + double score = flux * filter_weight; + for (auto score_index = 1; score_index < n_score_bins+1; ++score_index) { + //TODO: off-by-one + #pragma omp atomic + results(filter_index-1, score_index-1, RESULT_VALUE) += score; + } + + //------------------------------------------------------------------------ + // Further filter logic + + // Increment the filter bins, starting with the last filter to find the + // next valid bin combination + filter_loop_done = true; + for (int i = tally.filters().size()-1; i >= 0; --i) { + auto i_filt = tally.filters(i); + //TODO: off-by-one + auto& match {simulation::filter_matches[i_filt-1]}; + if (match.i_bin_ < match.bins_.size()) { + ++match.i_bin_; + filter_loop_done = false; + break; + } else { + match.i_bin_ = 1; + } + } + } + + // If the user has specified that we can assume all tallies are spatially + // separate, this implies that once a tally has been scored to, we needn't + // check the others. This cuts down on overhead when there are many + // tallies specified + if (settings::assume_separate) break; + +next_tally: + ; + } + + // Reset all the filter matches for the next tally event. + for (auto& match : simulation::filter_matches) + match.bins_present_ = false; +} + +//! Score mesh-surface tallies for particle currents. + +extern "C" void +score_meshsurface_tally(Particle* p) +{ + score_surface_tally_inner(p, model::active_meshsurf_tallies); +} + +//! Score surface tallies for particle currents. + +extern "C" void +score_surface_tally(Particle* p) +{ + score_surface_tally_inner(p, model::active_surface_tallies); +} + #ifdef OPENMC_MPI void reduce_tally_results() @@ -469,8 +593,8 @@ setup_active_tallies_c() model::active_analog_tallies.clear(); model::active_tracklength_tallies.clear(); model::active_collision_tallies.clear(); - //model::active_meshsurf_tallies.clear(); - //model::active_surface_tallies.clear(); + model::active_meshsurf_tallies.clear(); + model::active_surface_tallies.clear(); //TODO: off-by-one all through here for (auto i = 0; i < model::tallies.size(); ++i) { @@ -493,12 +617,12 @@ setup_active_tallies_c() } break; - //case TALLY_MESH_SURFACE: - // model::active_meshsurf_tallies.push_back(i + 1); - // break; + case TALLY_MESH_SURFACE: + model::active_meshsurf_tallies.push_back(i + 1); + break; - //case TALLY_SURFACE: - // model::active_surface_tallies.push_back(i + 1); + case TALLY_SURFACE: + model::active_surface_tallies.push_back(i + 1); } } } @@ -668,18 +792,18 @@ extern "C" { int active_analog_tallies_size() {return model::active_analog_tallies.size();} - int active_tracklength_tallies_data(int i) - {return model::active_tracklength_tallies[i-1];} - int active_tracklength_tallies_size() {return model::active_tracklength_tallies.size();} - int active_collision_tallies_data(int i) - {return model::active_collision_tallies[i-1];} - int active_collision_tallies_size() {return model::active_collision_tallies.size();} + int active_meshsurf_tallies_size() + {return model::active_meshsurf_tallies.size();} + + int active_surface_tallies_size() + {return model::active_surface_tallies.size();} + int tally_get_type_c(Tally* tally) {return tally->type_;} void tally_set_type_c(Tally* tally, int type) {tally->type_ = type;} diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index ca90c40879..b8be994248 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -75,21 +75,19 @@ module tally_header integer(C_INT) :: size end function - function active_tracklength_tallies_data(i) result(tally) bind(C) - import C_INT - integer(C_INT), value :: i - integer(C_INT) :: tally - end function - function active_collision_tallies_size() result(size) bind(C) import C_INT integer(C_INT) :: size end function - function active_collision_tallies_data(i) result(tally) bind(C) + function active_meshsurf_tallies_size() result(size) bind(C) import C_INT - integer(C_INT), value :: i - integer(C_INT) :: tally + integer(C_INT) :: size + end function + + function active_surface_tallies_size() result(size) bind(C) + import C_INT + integer(C_INT) :: size end function end interface @@ -189,10 +187,6 @@ module tally_header !$omp threadprivate(global_tally_collision, global_tally_absorption, & !$omp& global_tally_tracklength, global_tally_leakage) - ! Active tally lists - type(VectorInt), public :: active_meshsurf_tallies - type(VectorInt), public :: active_surface_tallies - ! Normalization for statistics integer(C_INT32_T), public, bind(C) :: n_realizations = 0 ! # of independent realizations real(C_DOUBLE), public, bind(C) :: total_weight ! total starting particle weight in realization @@ -621,10 +615,6 @@ contains largest_tally_id = 0 if (allocated(global_tallies)) deallocate(global_tallies) - - ! Deallocate tally node lists - call active_meshsurf_tallies % clear() - call active_surface_tallies % clear() end subroutine free_memory_tally !=============================================================================== diff --git a/src/tracking.F90 b/src/tracking.F90 index f540687f35..d16c368ea9 100644 --- a/src/tracking.F90 +++ b/src/tracking.F90 @@ -24,6 +24,7 @@ module tracking use tally_header use tally, only: score_analog_tally, score_tracklength_tally, & score_collision_tally, score_surface_tally, & + score_meshsurface_tally, & score_track_derivative, zero_flux_derivs, & score_collision_derivative use track_output, only: initialize_particle_track, write_particle_track, & @@ -210,8 +211,7 @@ contains p % event = EVENT_SURFACE end if ! Score cell to cell partial currents - if(active_surface_tallies % size() > 0) & - call score_surface_tally(p, active_surface_tallies) + if (active_surface_tallies_size() > 0) call score_surface_tally(p) else ! ==================================================================== ! PARTICLE HAS COLLISION @@ -226,8 +226,7 @@ contains ! since the direction of the particle will change and we need to use the ! pre-collision direction to figure out what mesh surfaces were crossed - if (active_meshsurf_tallies % size() > 0) & - call score_surface_tally(p, active_meshsurf_tallies) + if (active_meshsurf_tallies_size() > 0) call score_meshsurface_tally(p) ! Clear surface component p % surface = ERROR_INT @@ -346,12 +345,12 @@ contains ! forward slightly so that if the mesh boundary is on the surface, it is ! still processed - if (active_meshsurf_tallies % size() > 0) then + if (active_meshsurf_tallies_size() > 0) then ! TODO: Find a better solution to score surface currents than ! physically moving the particle forward slightly p % coord(1) % xyz = p % coord(1) % xyz + TINY_BIT * p % coord(1) % uvw - call score_surface_tally(p, active_meshsurf_tallies) + call score_meshsurface_tally(p) end if ! Score to global leakage tally @@ -382,14 +381,13 @@ contains ! the particle slightly back in case the surface crossing is coincident ! with a mesh boundary - if(active_surface_tallies % size() > 0) & - call score_surface_tally(p, active_surface_tallies) + if (active_surface_tallies_size() > 0) call score_surface_tally(p) - if (active_meshsurf_tallies % size() > 0) then + if (active_meshsurf_tallies_size() > 0) then xyz = p % coord(1) % xyz p % coord(1) % xyz = p % coord(1) % xyz - TINY_BIT * p % coord(1) % uvw - call score_surface_tally(p, active_meshsurf_tallies) + call score_meshsurface_tally(p) p % coord(1) % xyz = xyz end if @@ -443,10 +441,10 @@ contains ! Score surface currents since reflection causes the direction of the ! particle to change -- artificially move the particle slightly back in ! case the surface crossing is coincident with a mesh boundary - if (active_meshsurf_tallies % size() > 0) then + if (active_meshsurf_tallies_size() > 0) then xyz = p % coord(1) % xyz p % coord(1) % xyz = p % coord(1) % xyz - TINY_BIT * p % coord(1) % uvw - call score_surface_tally(p, active_meshsurf_tallies) + call score_meshsurface_tally(p) p % coord(1) % xyz = xyz end if From 6c0e28f069b6284180ea995cf791f750c99988a7 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Wed, 30 Jan 2019 22:08:02 -0500 Subject: [PATCH 16/58] Move score_analog_tally_ce to C++ --- src/tallies/tally.F90 | 149 ++---------------------------------------- src/tallies/tally.cpp | 126 ++++++++++++++++++++++++++++++++++- 2 files changed, 130 insertions(+), 145 deletions(-) diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index 893a21472a..29fd2bf1e0 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -46,6 +46,11 @@ module tally end interface interface + subroutine score_analog_tally_ce(p) bind(C) + import Particle + type(Particle), intent(in) :: p + end subroutine + subroutine score_tracklength_tally(p, distance) bind(C) import Particle, C_DOUBLE type(Particle) :: p @@ -2078,150 +2083,6 @@ contains ! triggered at every collision, not every event !=============================================================================== - subroutine score_analog_tally_ce(p) - - type(Particle), intent(in) :: p - - integer :: i, j - integer :: i_tally - integer :: i_filt - integer :: i_bin - integer :: k ! loop index for nuclide bins - integer :: filter_index ! single index for single bin - integer :: i_nuclide ! index in nuclides array - real(8) :: filter_weight ! combined weight of all filters - logical :: finished ! found all valid bin combinations - - ! A loop over all tallies is necessary because we need to simultaneously - ! determine different filter bins for the same tally in order to score to it - - TALLY_LOOP: do i = 1, active_analog_tallies_size() - ! Get index of tally and pointer to tally - i_tally = active_analog_tallies_data(i) - associate (t => tallies(i_tally) % obj) - - ! Find all valid bins in each filter if they have not already been found - ! for a previous tally. - do j = 1, t % n_filters() - i_filt = t % filter(j) - if (.not. filter_matches(i_filt) % bins_present) then - call filter_matches(i_filt) % bins_clear() - call filter_matches(i_filt) % weights_clear() - call filters(i_filt) % obj % get_all_bins(p, t % estimator(), & - filter_matches(i_filt)) - filter_matches(i_filt) % bins_present = .true. - end if - ! If there are no valid bins for this filter, then there is nothing to - ! score and we can move on to the next tally. - if (filter_matches(i_filt) % bins_size() == 0) cycle TALLY_LOOP - - ! Set the index of the bin used in the first filter combination - call filter_matches(i_filt) % set_i_bin(1) - end do - - ! ======================================================================== - ! Loop until we've covered all valid bins on each of the filters. - - FILTER_LOOP: do - - ! Reset scoring index and weight - filter_index = 1 - filter_weight = ONE - - ! Determine scoring index and weight for this filter combination - do j = 1, t % n_filters() - i_filt = t % filter(j) - i_bin = filter_matches(i_filt) % i_bin() - filter_index = filter_index + (filter_matches(i_filt) & - % bins_data(i_bin) - 1) * t % stride(j) - filter_weight = filter_weight * filter_matches(i_filt) & - % weights_data(i_bin) - end do - - ! ====================================================================== - ! Nuclide logic - - ! Check for nuclide bins - k = 0 - NUCLIDE_LOOP: do while (k < t % n_nuclide_bins()) - - ! Increment the index in the list of nuclide bins - k = k + 1 - - if (t % all_nuclides()) then - ! In the case that the user has requested to tally all nuclides, we - ! can take advantage of the fact that we know exactly how nuclide - ! bins correspond to nuclide indices. - if (k == 1) then - ! If we just entered, set the nuclide bin index to the index in - ! the nuclides array since this will match the index in the - ! nuclide bin array. - k = p % event_nuclide - elseif (k == p % event_nuclide + 1) then - ! After we've tallied the individual nuclide bin, we also need - ! to contribute to the total material bin which is the last bin - k = n_nuclides + 1 - else - ! After we've tallied in both the individual nuclide bin and the - ! total material bin, we're done - exit - end if - - else - ! If the user has explicitly specified nuclides (or specified - ! none), we need to search through the nuclide bin list one by - ! one. First we need to get the value of the nuclide bin - i_nuclide = t % nuclide_bins(k) - - ! Now compare the value against that of the colliding nuclide. - if (i_nuclide /= p % event_nuclide .and. i_nuclide /= -1) cycle - end if - - ! Determine score for each bin - call score_general(p, i_tally, (k-1)*t % n_score_bins, filter_index, & - i_nuclide, ZERO, filter_weight) - - end do NUCLIDE_LOOP - - ! ====================================================================== - ! Filter logic - - ! Increment the filter bins, starting with the last filter to find the - ! next valid bin combination - finished = .true. - do j = t % n_filters(), 1, -1 - i_filt = t % filter(j) - if (filter_matches(i_filt) % i_bin() < filter_matches(i_filt) % & - bins_size()) then - call filter_matches(i_filt) % set_i_bin(filter_matches(i_filt) % i_bin() + 1) - finished = .false. - exit - else - call filter_matches(i_filt) % set_i_bin(1) - end if - end do - - ! Once we have finished all valid bins for each of the filters, exit - ! the loop. - if (finished) exit FILTER_LOOP - - end do FILTER_LOOP - - ! If the user has specified that we can assume all tallies are spatially - ! separate, this implies that once a tally has been scored to, we needn't - ! check the others. This cuts down on overhead when there are many - ! tallies specified - - if (assume_separate) exit TALLY_LOOP - - end associate - end do TALLY_LOOP - - ! Reset filter matches flag - filter_matches(:) % bins_present = .false. - - end subroutine score_analog_tally_ce - subroutine score_analog_tally_mg(p) type(Particle), intent(in) :: p diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 94cf6d357a..97793a070d 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -143,6 +143,129 @@ adaptor_type<3> tally_results(int idx) return xt::adapt(results, size, xt::no_ownership(), shape); } +//! Score tallies based on a simple count of events. +// +//! Analog tallies ar etriggered at every collision, not every event. + +extern "C" void +score_analog_tally_ce(Particle* p) +{ + for (auto i_tally : model::active_analog_tallies) { + //TODO: off-by-one + const Tally& tally {*model::tallies[i_tally-1]}; + auto n_score_bins = tally_get_n_score_bins(i_tally); + + //-------------------------------------------------------------------------- + // Loop through all relevant filters and find the filter bins and weights + // for this event. + + // Find all valid bins in each filter if they have not already been found + // for a previous tally. + for (auto i_filt : tally.filters()) { + //TODO: off-by-one + auto& match {simulation::filter_matches[i_filt-1]}; + if (!match.bins_present_) { + match.bins_.clear(); + match.weights_.clear(); + //TODO: off-by-one + model::tally_filters[i_filt-1] + ->get_all_bins(p, tally.estimator_, match); + match.bins_present_ = true; + } + + // If there are no valid bins for this filter, then there is nothing to + // score so we can move on to the next tally. + if (match.bins_.size() == 0) goto next_tally; + + // Set the index of the bin used in the first filter combination + match.i_bin_ = 1; + } + + //-------------------------------------------------------------------------- + // Loop over filter bins and nuclide bins + + for (bool filter_loop_done = false; !filter_loop_done; ) { + + //------------------------------------------------------------------------ + // Filter logic + + // Determine scoring index and weight for the current filter combination + int filter_index = 1; + double filter_weight = 1.; + for (auto i = 0; i < tally.filters().size(); ++i) { + auto i_filt = tally.filters(i); + //TODO: off-by-one + auto& match {simulation::filter_matches[i_filt-1]}; + auto i_bin = match.i_bin_; + //TODO: off-by-one + filter_index += (match.bins_[i_bin-1] - 1) * tally.strides(i); + filter_weight *= match.weights_[i_bin-1]; + } + + //------------------------------------------------------------------------ + // Nuclide logic + + if (!tally.all_nuclides_) { + for (auto i = 0; i < tally.nuclides_.size(); ++i) { + auto i_nuclide = tally.nuclides_[i]; + + // Tally this event in the present nuclide bin if that bin represents + // the event nuclide or the total material. + if (i_nuclide == p->event_nuclide || i_nuclide == -1) + score_general_ce(p, i_tally, i*n_score_bins, filter_index, + -1, -1., filter_weight); + } + } else { + // In the case that the user has requested to tally all nuclides, we + // can take advantage of the fact that we know exactly how nuclide + // bins correspond to nuclide indices. First, tally the nuclide. + // Note that the i_nuclide and flux arguments for score_general are + // not used for analog tallies. + auto i = p->event_nuclide; + score_general_ce(p, i_tally, i*n_score_bins, filter_index, + -1, -1., filter_weight); + + // Now tally the total material. + i = tally.nuclides_.size(); + score_general_ce(p, i_tally, i*n_score_bins, filter_index, + -1, -1., filter_weight); + } + + //------------------------------------------------------------------------ + // Further filter logic + + // Increment the filter bins, starting with the last filter to find the + // next valid bin combination + filter_loop_done = true; + for (int i = tally.filters().size()-1; i >= 0; --i) { + auto i_filt = tally.filters(i); + //TODO: off-by-one + auto& match {simulation::filter_matches[i_filt-1]}; + if (match.i_bin_ < match.bins_.size()) { + ++match.i_bin_; + filter_loop_done = false; + break; + } else { + match.i_bin_ = 1; + } + } + } + + // If the user has specified that we can assume all tallies are spatially + // separate, this implies that once a tally has been scored to, we needn't + // check the others. This cuts down on overhead when there are many + // tallies specified + if (settings::assume_separate) break; + +next_tally: + ; + } + + // Reset all the filter matches for the next tally event. + for (auto& match : simulation::filter_matches) + match.bins_present_ = false; +} + //! Score tallies using a tracklength estimate of the flux. // //! This is triggered at every event (surface crossing, lattice crossing, or @@ -277,7 +400,8 @@ next_tally: //! This is triggered after every collision. It is invalid for tallies that //! require post-collison information because it can score reactions that didn't //! actually occur, and we don't a priori know what the outcome will be for -//! reactions that we didn't sample. +//! reactions that we didn't sample. It is assumed the material is not void +//! since collisions do not occur in voids. extern "C" void score_collision_tally(Particle* p) From abe9bf4a6ff9ba8f5b8c41e546fe1cc86da68e3d Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Wed, 30 Jan 2019 22:18:54 -0500 Subject: [PATCH 17/58] Move score_analog_tally_mg to C++ --- src/tallies/tally.F90 | 140 ++---------------------------------------- src/tallies/tally.cpp | 119 +++++++++++++++++++++++++++++++++-- 2 files changed, 120 insertions(+), 139 deletions(-) diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index 29fd2bf1e0..7c037fcebe 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -51,6 +51,11 @@ module tally type(Particle), intent(in) :: p end subroutine + subroutine score_analog_tally_mg(p) bind(C) + import Particle + type(Particle), intent(in) :: p + end subroutine + subroutine score_tracklength_tally(p, distance) bind(C) import Particle, C_DOUBLE type(Particle) :: p @@ -2077,141 +2082,6 @@ contains end associate end subroutine score_all_nuclides -!=============================================================================== -! SCORE_ANALOG_TALLY keeps track of how many events occur in a specified cell, -! energy range, etc. Note that since these are "analog" tallies, they are only -! triggered at every collision, not every event -!=============================================================================== - - subroutine score_analog_tally_mg(p) - - type(Particle), intent(in) :: p - - integer :: i, j - integer :: i_tally - integer :: i_filt - integer :: i_bin - integer :: k ! loop index for nuclide bins - integer :: filter_index ! single index for single bin - integer :: i_nuclide ! index in nuclides array - real(8) :: filter_weight ! combined weight of all filters - real(8) :: atom_density - logical :: finished ! found all valid bin combinations - type(Material), pointer :: mat - - ! A loop over all tallies is necessary because we need to simultaneously - ! determine different filter bins for the same tally in order to score to it - - TALLY_LOOP: do i = 1, active_analog_tallies_size() - ! Get index of tally and pointer to tally - i_tally = active_analog_tallies_data(i) - associate (t => tallies(i_tally) % obj) - - ! Find all valid bins in each filter if they have not already been found - ! for a previous tally. - do j = 1, t % n_filters() - i_filt = t % filter(j) - if (.not. filter_matches(i_filt) % bins_present) then - call filter_matches(i_filt) % bins_clear() - call filter_matches(i_filt) % weights_clear() - call filters(i_filt) % obj % get_all_bins(p, t % estimator(), & - filter_matches(i_filt)) - filter_matches(i_filt) % bins_present = .true. - end if - ! If there are no valid bins for this filter, then there is nothing to - ! score and we can move on to the next tally. - if (filter_matches(i_filt) % bins_size() == 0) cycle TALLY_LOOP - - ! Set the index of the bin used in the first filter combination - call filter_matches(i_filt) % set_i_bin(1) - end do - - ! ======================================================================== - ! Loop until we've covered all valid bins on each of the filters. - - FILTER_LOOP: do - - ! Reset scoring index and weight - filter_index = 1 - filter_weight = ONE - - ! Determine scoring index and weight for this filter combination - do j = 1, t % n_filters() - i_filt = t % filter(j) - i_bin = filter_matches(i_filt) % i_bin() - filter_index = filter_index + (filter_matches(i_filt) & - % bins_data(i_bin) - 1) * t % stride(j) - filter_weight = filter_weight * filter_matches(i_filt) & - % weights_data(i_bin) - end do - - ! ====================================================================== - ! Nuclide logic - - ! Check for nuclide bins - NUCLIDE_LOOP: do k = 1, t % n_nuclide_bins() - ! Get index of nuclide in nuclides array - i_nuclide = t % nuclide_bins(k) - - if (i_nuclide > 0) then - if (p % material /= MATERIAL_VOID) then - ! Get pointer to current material - mat => materials(p % material) - - ! Determine index of nuclide in Material % atom_density array - j = mat % mat_nuclide_index(i_nuclide) - if (j == 0) cycle NUCLIDE_LOOP - - ! Copy corresponding atom density - atom_density = mat % atom_density(j) - end if - else - atom_density = ZERO - end if - - call score_general(p, i_tally, (k-1)*t % n_score_bins, filter_index, & - i_nuclide, atom_density, filter_weight) - end do NUCLIDE_LOOP - - ! ====================================================================== - ! Filter logic - - ! Increment the filter bins, starting with the last filter to find the - ! next valid bin combination - finished = .true. - do j = t % n_filters(), 1, -1 - i_filt = t % filter(j) - if (filter_matches(i_filt) % i_bin() < filter_matches(i_filt) % & - bins_size()) then - call filter_matches(i_filt) % set_i_bin(filter_matches(i_filt) % i_bin() + 1) - finished = .false. - exit - else - call filter_matches(i_filt) % set_i_bin(1) - end if - end do - - ! Once we have finished all valid bins for each of the filters, exit - ! the loop. - if (finished) exit FILTER_LOOP - - end do FILTER_LOOP - - ! If the user has specified that we can assume all tallies are spatially - ! separate, this implies that once a tally has been scored to, we needn't - ! check the others. This cuts down on overhead when there are many - ! tallies specified - - if (assume_separate) exit TALLY_LOOP - - end associate - end do TALLY_LOOP - - ! Reset filter matches flag - filter_matches(:) % bins_present = .false. - - end subroutine score_analog_tally_mg - !=============================================================================== ! SCORE_FISSION_EOUT handles a special case where we need to store neutron ! production rate with an outgoing energy filter (think of a fission matrix). In diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 97793a070d..104885edd7 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -143,7 +143,7 @@ adaptor_type<3> tally_results(int idx) return xt::adapt(results, size, xt::no_ownership(), shape); } -//! Score tallies based on a simple count of events. +//! Score tallies based on a simple count of events (for continuous energy). // //! Analog tallies ar etriggered at every collision, not every event. @@ -210,7 +210,9 @@ score_analog_tally_ce(Particle* p) auto i_nuclide = tally.nuclides_[i]; // Tally this event in the present nuclide bin if that bin represents - // the event nuclide or the total material. + // the event nuclide or the total material. Note that the i_nuclide + // and flux arguments for score_general are not used for analog + // tallies. if (i_nuclide == p->event_nuclide || i_nuclide == -1) score_general_ce(p, i_tally, i*n_score_bins, filter_index, -1, -1., filter_weight); @@ -219,8 +221,6 @@ score_analog_tally_ce(Particle* p) // In the case that the user has requested to tally all nuclides, we // can take advantage of the fact that we know exactly how nuclide // bins correspond to nuclide indices. First, tally the nuclide. - // Note that the i_nuclide and flux arguments for score_general are - // not used for analog tallies. auto i = p->event_nuclide; score_general_ce(p, i_tally, i*n_score_bins, filter_index, -1, -1., filter_weight); @@ -266,6 +266,117 @@ next_tally: match.bins_present_ = false; } +//! Score tallies based on a simple count of events (for multigroup). +// +//! Analog tallies ar etriggered at every collision, not every event. + +extern "C" void +score_analog_tally_mg(Particle* p) +{ + for (auto i_tally : model::active_analog_tallies) { + //TODO: off-by-one + const Tally& tally {*model::tallies[i_tally-1]}; + auto n_score_bins = tally_get_n_score_bins(i_tally); + + //-------------------------------------------------------------------------- + // Loop through all relevant filters and find the filter bins and weights + // for this event. + + // Find all valid bins in each filter if they have not already been found + // for a previous tally. + for (auto i_filt : tally.filters()) { + //TODO: off-by-one + auto& match {simulation::filter_matches[i_filt-1]}; + if (!match.bins_present_) { + match.bins_.clear(); + match.weights_.clear(); + //TODO: off-by-one + model::tally_filters[i_filt-1] + ->get_all_bins(p, tally.estimator_, match); + match.bins_present_ = true; + } + + // If there are no valid bins for this filter, then there is nothing to + // score so we can move on to the next tally. + if (match.bins_.size() == 0) goto next_tally; + + // Set the index of the bin used in the first filter combination + match.i_bin_ = 1; + } + + //-------------------------------------------------------------------------- + // Loop over filter bins and nuclide bins + + for (bool filter_loop_done = false; !filter_loop_done; ) { + + //------------------------------------------------------------------------ + // Filter logic + + // Determine scoring index and weight for the current filter combination + int filter_index = 1; + double filter_weight = 1.; + for (auto i = 0; i < tally.filters().size(); ++i) { + auto i_filt = tally.filters(i); + //TODO: off-by-one + auto& match {simulation::filter_matches[i_filt-1]}; + auto i_bin = match.i_bin_; + //TODO: off-by-one + filter_index += (match.bins_[i_bin-1] - 1) * tally.strides(i); + filter_weight *= match.weights_[i_bin-1]; + } + + //------------------------------------------------------------------------ + // Nuclide logic + + for (auto i = 0; i < tally.nuclides_.size(); ++i) { + auto i_nuclide = tally.nuclides_[i]; + + double atom_density = 0.; + if (i_nuclide > 0) { + auto j = material_nuclide_index(p->material, i_nuclide); + if (j == 0) continue; + atom_density = material_atom_density(p->material, j); + } + + score_general_mg(p, i_tally, i*n_score_bins, filter_index, + i_nuclide, atom_density, filter_weight); + } + + //------------------------------------------------------------------------ + // Further filter logic + + // Increment the filter bins, starting with the last filter to find the + // next valid bin combination + filter_loop_done = true; + for (int i = tally.filters().size()-1; i >= 0; --i) { + auto i_filt = tally.filters(i); + //TODO: off-by-one + auto& match {simulation::filter_matches[i_filt-1]}; + if (match.i_bin_ < match.bins_.size()) { + ++match.i_bin_; + filter_loop_done = false; + break; + } else { + match.i_bin_ = 1; + } + } + } + + // If the user has specified that we can assume all tallies are spatially + // separate, this implies that once a tally has been scored to, we needn't + // check the others. This cuts down on overhead when there are many + // tallies specified + if (settings::assume_separate) break; + +next_tally: + ; + } + + // Reset all the filter matches for the next tally event. + for (auto& match : simulation::filter_matches) + match.bins_present_ = false; +} + //! Score tallies using a tracklength estimate of the flux. // //! This is triggered at every event (surface crossing, lattice crossing, or From a90d8f3e867c7bbfea635b0906a97dc7700207d1 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Thu, 31 Jan 2019 12:09:09 -0500 Subject: [PATCH 18/58] Simplify tally triggers --- src/input_xml.F90 | 4 - src/tallies/trigger.F90 | 287 +++++++++++++++++---------------- src/tallies/trigger_header.F90 | 1 - 3 files changed, 148 insertions(+), 144 deletions(-) diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 70d20041b0..c657b9722b 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -1728,10 +1728,7 @@ contains call trigger_scores % next_entry(elem, i_elem) if (i_elem == 0) exit - score_name = trim(elem % key) - ! Store the score name and index in the trigger - t % triggers(trig_ind) % score_name = score_name t % triggers(trig_ind) % score_index = elem % value ! Set the trigger convergence threshold type @@ -1758,7 +1755,6 @@ contains else ! Store the score name and index - t % triggers(trig_ind) % score_name = trim(score_name) t % triggers(trig_ind) % score_index = & trigger_scores % get(trim(score_name)) diff --git a/src/tallies/trigger.F90 b/src/tallies/trigger.F90 index 56ed00aba6..785e7b97b0 100644 --- a/src/tallies/trigger.F90 +++ b/src/tallies/trigger.F90 @@ -8,6 +8,7 @@ module trigger use constants use eigenvalue, only: openmc_get_keff + use endf, only: reaction_name use error, only: warning, write_message use string, only: to_str use mesh_header, only: RegularMesh, meshes @@ -33,44 +34,50 @@ contains implicit none - ! Variables to reflect distance to trigger convergence criteria - real(8) :: max_ratio ! max uncertainty/thresh ratio - integer :: tally_id ! id for tally with max ratio - character(len=52) :: name ! "eigenvalue" or tally score + real(8) :: keff_ratio ! uncertainty/threshold ratio for keff + real(8) :: tally_ratio ! max tally uncertainty/threshold ratio + integer :: tally_id ! id for tally with max ratio + integer :: score ! tally score with max ratio + integer :: n_pred_batches ! predicted # batches to satisfy all triggers - integer :: n_pred_batches ! predicted # batches to satisfy all triggers + if (.not. master) return ! Checks if current_batch is one for which the triggers must be checked if (current_batch < n_batches .or. (.not. trigger_on)) return if (mod((current_batch - n_batches), n_batch_interval) /= 0 .and. & current_batch /= n_max_batches) return - ! Check the trigger and output the result - call check_tally_triggers(max_ratio, tally_id, name) + ! By default, assume all triggers are satisfied + satisfy_triggers = .true. - ! When trigger threshold is reached, write information + ! Check the eigenvalue and tally triggers + call check_keff_trigger(keff_ratio) + call check_tally_triggers(tally_ratio, tally_id, score) + + ! Alert the user if the triggers are satisfied if (satisfy_triggers) then call write_message("Triggers satisfied for batch " // & trim(to_str(current_batch)), 7) - - ! When trigger is not reached write convergence info for user - elseif (name == "eigenvalue") then - call write_message("Triggers unsatisfied, max unc./thresh. is " // & - trim(to_str(max_ratio)) // " for " // trim(name), 7) - else - call write_message("Triggers unsatisfied, max unc./thresh. is " // & - trim(to_str(max_ratio)) // " for " // trim(name) // & - " in tally " // trim(to_str(tally_id)), 7) + return end if - ! If batch_interval is not set, estimate batches till triggers are satisfied - if (pred_batches .and. .not. satisfy_triggers) then + ! Specify which trigger is unsatisfied + if (keff_ratio >= tally_ratio) then + call write_message("Triggers unsatisfied, max unc./thresh. is " // & + trim(to_str(keff_ratio)) // " for eigenvalue", 7) + else + call write_message("Triggers unsatisfied, max unc./thresh. is " // & + trim(to_str(tally_ratio)) // " for " // trim(reaction_name(score)) & + // " in tally " // trim(to_str(tally_id)), 7) + end if + ! Estimate batches till triggers are satisfied + if (pred_batches) then ! Estimate the number of remaining batches to convergence ! The prediction uses the fact that tally variances are proportional ! to 1/N where N is the number of the batches/particles n_batch_interval = int((current_batch-n_inactive) * & - (max_ratio ** 2)) + n_inactive-n_batches + 1 + (max(keff_ratio, tally_ratio) ** 2)) + n_inactive-n_batches + 1 n_pred_batches = n_batch_interval + n_batches ! Write the predicted number of batches for the user @@ -85,18 +92,56 @@ contains end if end subroutine check_triggers - !=============================================================================== -! CHECK_TALLY_TRIGGERS checks whether uncertainties are below the threshold, -! and finds the maximum uncertainty/threshold ratio for all triggers +! CHECK_KEFF_TRIGGER computes the uncertainty/threshold ratio for the eigenvalue +! trigger and updates the global satisfy_tiggers variable if the trigger is +! unsatisfied. !=============================================================================== - subroutine check_tally_triggers(max_ratio, tally_id, name) + subroutine check_keff_trigger(ratio) + real(8), intent(out) :: ratio + integer(C_INT) :: err + real(C_DOUBLE) :: k_combined(2) + real(8) :: uncertainty + + ratio = 0 + if (run_mode == MODE_EIGENVALUE) then + if (keff_trigger % trigger_type /= 0) then + err = openmc_get_keff(k_combined) + select case (keff_trigger % trigger_type) + case(VARIANCE) + uncertainty = k_combined(2) ** 2 + case(STANDARD_DEVIATION) + uncertainty = k_combined(2) + case default + uncertainty = k_combined(2) / k_combined(1) + end select + + ! If uncertainty is above threshold, store uncertainty ratio + if (uncertainty > keff_trigger % threshold) then + satisfy_triggers = .false. + if (keff_trigger % trigger_type == VARIANCE) then + ratio = sqrt(uncertainty / keff_trigger % threshold) + else + ratio = uncertainty / keff_trigger % threshold + end if + end if + end if + end if + end subroutine check_keff_trigger + +!=============================================================================== +! CHECK_TALLY_TRIGGERS computes the uncertainty/threshold ratio for all tally +! triggers and updates the global satisfy_tiggers variable if any trigger is +! unsatisfied. +!=============================================================================== + + subroutine check_tally_triggers(max_ratio, tally_id, score) ! Variables to reflect distance to trigger convergence criteria - real(8), intent(inout) :: max_ratio ! max uncertainty/thresh ratio - integer, intent(inout) :: tally_id ! id for tally with max ratio - character(len=52), intent(inout) :: name ! "eigenvalue" or tally score + real(8), intent(out) :: max_ratio ! max uncertainty/thresh ratio + integer, intent(out) :: tally_id ! id for tally with max ratio + integer, intent(out) :: score integer :: i ! index in tallies array integer :: j ! index in tally filters @@ -114,126 +159,90 @@ contains ! Initialize tally trigger maximum uncertainty ratio to zero max_ratio = 0 - if (master) then + ! Compute uncertainties for all tallies, scores with triggers + TALLY_LOOP: do i = 1, n_tallies + associate (t => tallies(i) % obj) - ! By default, assume all triggers are satisfied - satisfy_triggers = .true. - - ! Check eigenvalue trigger - if (run_mode == MODE_EIGENVALUE) then - if (keff_trigger % trigger_type /= 0) then - err = openmc_get_keff(k_combined) - select case (keff_trigger % trigger_type) - case(VARIANCE) - uncertainty = k_combined(2) ** 2 - case(STANDARD_DEVIATION) - uncertainty = k_combined(2) - case default - uncertainty = k_combined(2) / k_combined(1) - end select - - ! If uncertainty is above threshold, store uncertainty ratio - if (uncertainty > keff_trigger % threshold) then - satisfy_triggers = .false. - if (keff_trigger % trigger_type == VARIANCE) then - ratio = sqrt(uncertainty / keff_trigger % threshold) - else - ratio = uncertainty / keff_trigger % threshold - end if - if (max_ratio < ratio) then - max_ratio = ratio - name = "eigenvalue" - end if - end if - end if + ! Cycle through if only one batch has been simumlate + if (t % n_realizations == 1) then + cycle TALLY_LOOP end if - ! Compute uncertainties for all tallies, scores with triggers - TALLY_LOOP: do i = 1, n_tallies - associate (t => tallies(i) % obj) + TRIGGER_LOOP: do s = 1, t % n_triggers + associate (trigger => t % triggers(s)) - ! Cycle through if only one batch has been simumlate - if (t % n_realizations == 1) then - cycle TALLY_LOOP + ! Initialize trigger uncertainties to zero + trigger % std_dev = ZERO + trigger % rel_err = ZERO + trigger % variance = ZERO + + ! Mesh current tally triggers require special treatment + if (t % type() == TALLY_MESH_SURFACE) then + call compute_tally_current(t, trigger) + + else + + ! Initialize bins, filter level + do j = 1, t % n_filters() + call filter_matches(t % filter(j)) % bins_clear() + call filter_matches(t % filter(j)) % bins_push_back(0) + end do + + FILTER_LOOP: do filter_index = 1, t % n_filter_bins() + + ! Initialize score index + score_index = trigger % score_index + + ! Initialize score bin index + NUCLIDE_LOOP: do n = 1, t % n_nuclide_bins() + + call get_trigger_uncertainty(std_dev, rel_err, & + score_index, filter_index, t) + + if (trigger % variance < variance) then + trigger % variance = std_dev ** 2 + end if + if (trigger % std_dev < std_dev) then + trigger % std_dev = std_dev + end if + if (trigger % rel_err < rel_err) then + trigger % rel_err = rel_err + end if + + select case (trigger % type) + case(VARIANCE) + uncertainty = trigger % variance + case(STANDARD_DEVIATION) + uncertainty = trigger % std_dev + case default + uncertainty = trigger % rel_err + end select + + if (uncertainty > trigger % threshold) then + satisfy_triggers = .false. + + if (trigger % type == VARIANCE) then + ratio = sqrt(uncertainty / trigger % threshold) + else + ratio = uncertainty / trigger % threshold + end if + + if (max_ratio < ratio) then + max_ratio = ratio + score = t % score_bins(trigger % score_index) + tally_id = t % id + end if + end if + end do NUCLIDE_LOOP + if (t % n_filters() == 0) exit FILTER_LOOP + end do FILTER_LOOP end if - - TRIGGER_LOOP: do s = 1, t % n_triggers - associate (trigger => t % triggers(s)) - - ! Initialize trigger uncertainties to zero - trigger % std_dev = ZERO - trigger % rel_err = ZERO - trigger % variance = ZERO - - ! Mesh current tally triggers require special treatment - if (t % type() == TALLY_MESH_SURFACE) then - call compute_tally_current(t, trigger) - - else - - ! Initialize bins, filter level - do j = 1, t % n_filters() - call filter_matches(t % filter(j)) % bins_clear() - call filter_matches(t % filter(j)) % bins_push_back(0) - end do - - FILTER_LOOP: do filter_index = 1, t % n_filter_bins() - - ! Initialize score index - score_index = trigger % score_index - - ! Initialize score bin index - NUCLIDE_LOOP: do n = 1, t % n_nuclide_bins() - - call get_trigger_uncertainty(std_dev, rel_err, & - score_index, filter_index, t) - - if (trigger % variance < variance) then - trigger % variance = std_dev ** 2 - end if - if (trigger % std_dev < std_dev) then - trigger % std_dev = std_dev - end if - if (trigger % rel_err < rel_err) then - trigger % rel_err = rel_err - end if - - select case (t % triggers(s) % type) - case(VARIANCE) - uncertainty = trigger % variance - case(STANDARD_DEVIATION) - uncertainty = trigger % std_dev - case default - uncertainty = trigger % rel_err - end select - - if (uncertainty > t % triggers(s) % threshold) then - satisfy_triggers = .false. - - if (t % triggers(s) % type == VARIANCE) then - ratio = sqrt(uncertainty / t % triggers(s) % threshold) - else - ratio = uncertainty / t % triggers(s) % threshold - end if - - if (max_ratio < ratio) then - max_ratio = ratio - name = t % triggers(s) % score_name - tally_id = t % id - end if - end if - end do NUCLIDE_LOOP - if (t % n_filters() == 0) exit FILTER_LOOP - end do FILTER_LOOP - end if - end associate - end do TRIGGER_LOOP end associate - end do TALLY_LOOP - end if + end do TRIGGER_LOOP + end associate + end do TALLY_LOOP end subroutine check_tally_triggers - !=============================================================================== ! COMPUTE_TALLY_CURRENT computes the current for a mesh current tally with ! precision trigger(s). diff --git a/src/tallies/trigger_header.F90 b/src/tallies/trigger_header.F90 index 83d2fa95c0..ccf497d1da 100644 --- a/src/tallies/trigger_header.F90 +++ b/src/tallies/trigger_header.F90 @@ -14,7 +14,6 @@ module trigger_header type, public :: TriggerObject integer :: type ! "variance", "std_dev" or "rel_err" real(8) :: threshold ! a convergence threshold - character(len=52) :: score_name ! the name of the score integer :: score_index ! the index of the score real(8) :: variance = ZERO ! temp variance container real(8) :: std_dev = ZERO ! temp std. dev. container From a07ea809f296b289a373811cbd2afa9bf323fd72 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Thu, 31 Jan 2019 13:39:33 -0500 Subject: [PATCH 19/58] Remove some tally-related C++/Fortran glue-code This commit also removes some tally trigger special-case code for meshsurface current tallies. That code was broken and is also no longer needed. --- CMakeLists.txt | 3 +- include/openmc/tallies/tally.h | 3 - include/openmc/tallies/trigger.h | 36 ++++ src/input_xml.F90 | 4 +- src/output.F90 | 2 - src/settings.cpp | 8 +- src/tallies/filter.cpp | 24 --- src/tallies/tally.cpp | 24 +-- src/tallies/tally_filter.F90 | 2 - src/tallies/tally_filter_header.F90 | 90 +------- src/tallies/tally_filter_mesh.F90 | 38 ---- src/tallies/tally_filter_meshsurface.F90 | 22 -- src/tallies/tally_header.F90 | 48 ----- src/tallies/trigger.F90 | 250 ++--------------------- src/tallies/trigger.cpp | 32 +++ src/tallies/trigger_header.F90 | 14 +- 16 files changed, 106 insertions(+), 494 deletions(-) create mode 100644 include/openmc/tallies/trigger.h delete mode 100644 src/tallies/tally_filter_mesh.F90 delete mode 100644 src/tallies/tally_filter_meshsurface.F90 create mode 100644 src/tallies/trigger.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 4671b75c01..8fb3f9c0d4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -359,8 +359,6 @@ add_library(libopenmc SHARED src/tallies/tally_filter_delayedgroup.F90 src/tallies/tally_filter_distribcell.F90 src/tallies/tally_filter_energy.F90 - src/tallies/tally_filter_mesh.F90 - src/tallies/tally_filter_meshsurface.F90 src/tallies/tally_filter_particle.F90 src/tallies/tally_filter_sph_harm.F90 src/tallies/tally_header.F90 @@ -442,6 +440,7 @@ add_library(libopenmc SHARED src/tallies/filter_universe.cpp src/tallies/filter_zernike.cpp src/tallies/tally.cpp + src/tallies/trigger.cpp src/timer.cpp src/thermal.cpp src/wmp.cpp diff --git a/include/openmc/tallies/tally.h b/include/openmc/tallies/tally.h index a41c82b61a..d4bbfbaa11 100644 --- a/include/openmc/tallies/tally.h +++ b/include/openmc/tallies/tally.h @@ -56,11 +56,8 @@ public: // We need to have quick access to some filters. The following gives indices // for various filters that could be in the tally or C_NONE if they are not // present. - int energyin_filter_ {C_NONE}; int energyout_filter_ {C_NONE}; int delayedgroup_filter_ {C_NONE}; - int surface_filter_ {C_NONE}; - int mesh_filter_ {C_NONE}; int deriv_ {C_NONE}; //!< Index of a TallyDerivative object for diff tallies. diff --git a/include/openmc/tallies/trigger.h b/include/openmc/tallies/trigger.h new file mode 100644 index 0000000000..56c15f7bc2 --- /dev/null +++ b/include/openmc/tallies/trigger.h @@ -0,0 +1,36 @@ +#ifndef OPENMC_TALLIES_TRIGGER_H +#define OPENMC_TALLIES_TRIGGER_H + +#include + +#include "pugixml.hpp" + +namespace openmc { + +//! Stops the simulation early if a desired tally uncertainty is reached. + +extern "C" struct Trigger +{ + int type; //!< variance, std_dev, or rel_err + double threshold; //!< uncertainty value below which trigger is satisfied + int score_index; //!< index of the relevant score in the tally's arrays + double variance {0.}; + double std_dev {0.}; + double rel_err {0.}; +}; + +//! Stops the simulation early if a desired k-effective uncertainty is reached. + +extern "C" struct KTrigger +{ + int type{0}; + double threshold {0.}; +}; + +//TODO: consider a different namespace +namespace settings { + extern "C" KTrigger keff_trigger; +} + +} // namespace openmc +#endif // OPENMC_TALLIES_TRIGGER_H diff --git a/src/input_xml.F90 b/src/input_xml.F90 index c657b9722b..160acd263b 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -1152,9 +1152,9 @@ contains has_energyout = (t % energyout_filter() > 0) has_delayedgroup = (t % delayedgroup_filter() > 0) has_legendre = .false. - has_surface = (t % surface_filter() > 0) has_cell = .false. has_cellfrom = .false. + has_surface = .false. has_meshsurface = .false. particle_filter_index = 0 do j = 1, t % n_filters() @@ -1165,6 +1165,8 @@ contains has_cell = .true. type is (CellfromFilter) has_cellfrom = .true. + type is (SurfaceFilter) + has_surface = .true. type is (MeshSurfaceFilter) has_meshsurface = .true. type is (ParticleFilter) diff --git a/src/output.F90 b/src/output.F90 index c4e0c34776..5895cd59c8 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -20,8 +20,6 @@ module output use tally_header use tally_derivative_header use tally_filter - use tally_filter_mesh, only: MeshFilter - use tally_filter_header, only: TallyFilterMatch use timer_header implicit none diff --git a/src/settings.cpp b/src/settings.cpp index dbbe9c6dad..a7543d5e61 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -25,6 +25,7 @@ #include "openmc/simulation.h" #include "openmc/source.h" #include "openmc/string_utils.h" +#include "openmc/tallies/trigger.h" #include "openmc/xml_interface.h" namespace openmc { @@ -106,13 +107,6 @@ int verbosity {7}; double weight_cutoff {0.25}; double weight_survive {1.0}; -// TODO: Move to separate file -struct KTrigger { - int type; - double threshold; -}; -extern "C" KTrigger keff_trigger; - } // namespace settings //============================================================================== diff --git a/src/tallies/filter.cpp b/src/tallies/filter.cpp index 7c641f8545..f331e63123 100644 --- a/src/tallies/filter.cpp +++ b/src/tallies/filter.cpp @@ -54,30 +54,6 @@ extern "C" { filter_match_get_i_bin(FilterMatch* match) {return match->i_bin_;} - void - filter_match_set_i_bin(FilterMatch* match, int i) - {match->i_bin_ = i;} - - void - filter_match_bins_push_back(FilterMatch* match, int val) - {match->bins_.push_back(val);} - - void - filter_match_weights_push_back(FilterMatch* match, double val) - {match->weights_.push_back(val);} - - void - filter_match_bins_clear(FilterMatch* match) - {match->bins_.clear();} - - void - filter_match_weights_clear(FilterMatch* match) - {match->weights_.clear();} - - int - filter_match_bins_size(FilterMatch* match) - {return match->bins_.size();} - int filter_match_bins_data(FilterMatch* match, int indx) {return match->bins_[indx-1];} diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 104885edd7..8357472537 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -86,18 +86,10 @@ Tally::set_filters(const int32_t filter_indices[], int n) const auto* filt = model::tally_filters[i_filt-1].get(); //TODO: off-by-one on each index - if (dynamic_cast(filt)) { - if (dynamic_cast(filt)) { - energyout_filter_ = i + 1; - } else { - energyin_filter_ = i + 1; - } + if (dynamic_cast(filt)) { + energyout_filter_ = i + 1; } else if (dynamic_cast(filt)) { delayedgroup_filter_ = i + 1; - } else if (dynamic_cast(filt)) { - surface_filter_ = i + 1; - } else if (dynamic_cast(filt)) { - mesh_filter_ = i + 1; } } @@ -1021,9 +1013,6 @@ extern "C" { int active_tallies_size() {return model::active_tallies.size();} - int active_analog_tallies_data(int i) - {return model::active_analog_tallies[i-1];} - int active_analog_tallies_size() {return model::active_analog_tallies.size();} @@ -1075,21 +1064,12 @@ extern "C" { bool tally_get_all_nuclides_c(Tally* tally) {return tally->all_nuclides_;} - int tally_get_energyin_filter_c(Tally* tally) - {return tally->energyin_filter_;} - int tally_get_energyout_filter_c(Tally* tally) {return tally->energyout_filter_;} int tally_get_delayedgroup_filter_c(Tally* tally) {return tally->delayedgroup_filter_;} - int tally_get_surface_filter_c(Tally* tally) - {return tally->surface_filter_;} - - int tally_get_mesh_filter_c(Tally* tally) - {return tally->mesh_filter_;} - int tally_get_deriv_c(Tally* tally) {return tally->deriv_;} int tally_set_deriv_c(Tally* tally, int deriv) {tally->deriv_ = deriv;} diff --git a/src/tallies/tally_filter.F90 b/src/tallies/tally_filter.F90 index a1fc01dab2..231fe55256 100644 --- a/src/tallies/tally_filter.F90 +++ b/src/tallies/tally_filter.F90 @@ -10,8 +10,6 @@ module tally_filter use tally_filter_delayedgroup use tally_filter_distribcell use tally_filter_energy - use tally_filter_mesh - use tally_filter_meshsurface use tally_filter_particle use tally_filter_sph_harm diff --git a/src/tallies/tally_filter_header.F90 b/src/tallies/tally_filter_header.F90 index 5a8784ae08..2e1e7d6609 100644 --- a/src/tallies/tally_filter_header.F90 +++ b/src/tallies/tally_filter_header.F90 @@ -35,18 +35,8 @@ module tally_filter_header type, public :: TallyFilterMatch type(C_PTR) :: ptr - - ! Indicates whether all valid bins for this filter have been found - logical :: bins_present = .false. - contains procedure :: i_bin - procedure :: set_i_bin - procedure :: bins_push_back - procedure :: weights_push_back - procedure :: bins_clear - procedure :: weights_clear - procedure :: bins_size procedure :: bins_data procedure :: weights_data procedure :: bins_set_data @@ -98,6 +88,12 @@ module tally_filter_header type, public, extends(TallyFilter) :: MaterialFilter end type + type, public, extends(TallyFilter) :: MeshFilter + end type + + type, public, extends(TallyFilter) :: MeshSurfaceFilter + end type + type, public, extends(TallyFilter) :: MuFilter end type @@ -162,80 +158,6 @@ contains i = filter_match_get_i_bin(this % ptr) end function - subroutine set_i_bin(this, i) - class(TallyFilterMatch) :: this - integer :: i - interface - subroutine filter_match_set_i_bin(ptr, i) bind(C) - import C_PTR, C_INT - type(C_PTR), value :: ptr - integer(C_INT), value :: i - end subroutine - end interface - call filter_match_set_i_bin(this % ptr, i) - end subroutine - - subroutine bins_push_back(this, val) - class(TallyFilterMatch), intent(inout) :: this - integer, intent(in) :: val - interface - subroutine filter_match_bins_push_back(ptr, val) bind(C) - import C_PTR, C_INT - type(C_PTR), value :: ptr - integer(C_INT), intent(in), value :: val - end subroutine - end interface - call filter_match_bins_push_back(this % ptr, val) - end subroutine bins_push_back - - subroutine weights_push_back(this, val) - class(TallyFilterMatch), intent(inout) :: this - real(8), intent(in) :: val - interface - subroutine filter_match_weights_push_back(ptr, val) bind(C) - import C_PTR, C_DOUBLE - type(C_PTR), value :: ptr - real(C_DOUBLE), intent(in), value :: val - end subroutine - end interface - call filter_match_weights_push_back(this % ptr, val) - end subroutine weights_push_back - - subroutine bins_clear(this) - class(TallyFilterMatch), intent(inout) :: this - interface - subroutine filter_match_bins_clear(ptr) bind(C) - import C_PTR - type(C_PTR), value :: ptr - end subroutine - end interface - call filter_match_bins_clear(this % ptr) - end subroutine bins_clear - - subroutine weights_clear(this) - class(TallyFilterMatch), intent(inout) :: this - interface - subroutine filter_match_weights_clear(ptr) bind(C) - import C_PTR - type(C_PTR), value :: ptr - end subroutine - end interface - call filter_match_weights_clear(this % ptr) - end subroutine weights_clear - - function bins_size(this) result(len) - class(TallyFilterMatch), intent(inout) :: this - integer :: len - interface - function filter_match_bins_size(ptr) bind(C) result(len) - import C_PTR, C_INT - type(C_PTR), value :: ptr - integer(C_INT) :: len - end function - end interface - len = filter_match_bins_size(this % ptr) - end function bins_size - function bins_data(this, indx) result(val) class(TallyFilterMatch), intent(inout) :: this integer, intent(in) :: indx diff --git a/src/tallies/tally_filter_mesh.F90 b/src/tallies/tally_filter_mesh.F90 deleted file mode 100644 index 626ed4b156..0000000000 --- a/src/tallies/tally_filter_mesh.F90 +++ /dev/null @@ -1,38 +0,0 @@ -module tally_filter_mesh - - use, intrinsic :: ISO_C_BINDING - - use tally_filter_header - - implicit none - - interface - function openmc_mesh_filter_set_mesh(index, index_mesh) result(err) bind(C) - import C_INT32_T, C_INT - integer(C_INT32_T), value, intent(in) :: index - integer(C_INT32_T), value, intent(in) :: index_mesh - integer(C_INT) :: err - end function - end interface - - type, public, extends(TallyFilter) :: MeshFilter - contains - procedure :: mesh => get_mesh - end type MeshFilter - -contains - - function get_mesh(this) result(mesh) - class(MeshFilter) :: this - integer :: mesh - interface - function mesh_filter_get_mesh(filt) result(index_mesh) bind(C) - import C_PTR, C_INT - type(C_PTR), value :: filt - integer(C_INT) :: index_mesh - end function mesh_filter_get_mesh - end interface - mesh = mesh_filter_get_mesh(this % ptr) - end function get_mesh - -end module tally_filter_mesh diff --git a/src/tallies/tally_filter_meshsurface.F90 b/src/tallies/tally_filter_meshsurface.F90 deleted file mode 100644 index 574d2c8b7e..0000000000 --- a/src/tallies/tally_filter_meshsurface.F90 +++ /dev/null @@ -1,22 +0,0 @@ -module tally_filter_meshsurface - - use, intrinsic :: ISO_C_BINDING - - use tally_filter_header - - implicit none - - interface - function openmc_meshsurface_filter_set_mesh(index, index_mesh) result(err) & - bind(C) - import C_INT32_T, C_INT - integer(C_INT32_T), value, intent(in) :: index - integer(C_INT32_T), value, intent(in) :: index_mesh - integer(C_INT) :: err - end function - end interface - - type, extends(TallyFilter) :: MeshSurfaceFilter - end type MeshSurfaceFilter - -end module tally_filter_meshsurface diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index b8be994248..3e76100277 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -64,12 +64,6 @@ module tally_header integer(C_INT) :: size end function - function active_analog_tallies_data(i) result(tally) bind(C) - import C_INT - integer(C_INT), value :: i - integer(C_INT) :: tally - end function - function active_tracklength_tallies_size() result(size) bind(C) import C_INT integer(C_INT) :: size @@ -143,11 +137,8 @@ module tally_header procedure :: nuclide_bins => tally_get_nuclide_bins procedure :: set_nuclide_bins => tally_set_nuclide_bins procedure :: all_nuclides => tally_get_all_nuclides - procedure :: energyin_filter => tally_get_energyin_filter procedure :: energyout_filter => tally_get_energyout_filter procedure :: delayedgroup_filter => tally_get_delayedgroup_filter - procedure :: surface_filter => tally_get_surface_filter - procedure :: mesh_filter => tally_get_mesh_filter procedure :: deriv => tally_get_deriv procedure :: set_deriv => tally_set_deriv end type TallyObject @@ -484,19 +475,6 @@ contains all_nuc = tally_get_all_nuclides_c(this % ptr) end function - function tally_get_energyin_filter(this) result(filt) - class(TallyObject) :: this - integer(C_INT) :: filt - interface - function tally_get_energyin_filter_c(tally) result(filt) bind(C) - import C_PTR, C_INT - type(C_PTR), value :: tally - integer(C_INT) :: filt - end function - end interface - filt = tally_get_energyin_filter_c(this % ptr) - end function - function tally_get_energyout_filter(this) result(filt) class(TallyObject) :: this integer(C_INT) :: filt @@ -523,32 +501,6 @@ contains filt = tally_get_delayedgroup_filter_c(this % ptr) end function - function tally_get_surface_filter(this) result(filt) - class(TallyObject) :: this - integer(C_INT) :: filt - interface - function tally_get_surface_filter_c(tally) result(filt) bind(C) - import C_PTR, C_INT - type(C_PTR), value :: tally - integer(C_INT) :: filt - end function - end interface - filt = tally_get_surface_filter_c(this % ptr) - end function - - function tally_get_mesh_filter(this) result(filt) - class(TallyObject) :: this - integer(C_INT) :: filt - interface - function tally_get_mesh_filter_c(tally) result(filt) bind(C) - import C_PTR, C_INT - type(C_PTR), value :: tally - integer(C_INT) :: filt - end function - end interface - filt = tally_get_mesh_filter_c(this % ptr) - end function - function tally_get_deriv(this) result(deriv) class(TallyObject) :: this integer(C_INT) :: deriv diff --git a/src/tallies/trigger.F90 b/src/tallies/trigger.F90 index 785e7b97b0..93068f4f5d 100644 --- a/src/tallies/trigger.F90 +++ b/src/tallies/trigger.F90 @@ -2,27 +2,32 @@ module trigger use, intrinsic :: ISO_C_BINDING -#ifdef OPENMC_MPI - use message_passing -#endif - use constants use eigenvalue, only: openmc_get_keff use endf, only: reaction_name use error, only: warning, write_message use string, only: to_str - use mesh_header, only: RegularMesh, meshes use message_passing, only: master use settings use simulation_header use trigger_header use tally, only: TallyObject - use tally_filter_mesh, only: MeshFilter - use tally_filter_header, only: filter_matches, filters use tally_header, only: tallies, n_tallies implicit none + interface + subroutine get_tally_uncertainty(i_tally, score_index, filter_index, & + std_dev, rel_err) bind(C) + import C_INT, C_DOUBLE + integer(C_INT), value :: i_tally + integer(C_INT), value :: score_index + integer(C_INT), value :: filter_index + real(C_DOUBLE) :: std_dev + real(C_DOUBLE) :: rel_err + end subroutine + end interface + contains !=============================================================================== @@ -171,22 +176,10 @@ contains TRIGGER_LOOP: do s = 1, t % n_triggers associate (trigger => t % triggers(s)) - ! Initialize trigger uncertainties to zero - trigger % std_dev = ZERO - trigger % rel_err = ZERO - trigger % variance = ZERO - - ! Mesh current tally triggers require special treatment - if (t % type() == TALLY_MESH_SURFACE) then - call compute_tally_current(t, trigger) - - else - - ! Initialize bins, filter level - do j = 1, t % n_filters() - call filter_matches(t % filter(j)) % bins_clear() - call filter_matches(t % filter(j)) % bins_push_back(0) - end do + ! Initialize trigger uncertainties to zero + trigger % std_dev = ZERO + trigger % rel_err = ZERO + trigger % variance = ZERO FILTER_LOOP: do filter_index = 1, t % n_filter_bins() @@ -196,8 +189,8 @@ contains ! Initialize score bin index NUCLIDE_LOOP: do n = 1, t % n_nuclide_bins() - call get_trigger_uncertainty(std_dev, rel_err, & - score_index, filter_index, t) + call get_tally_uncertainty(i, score_index, filter_index, & + std_dev, rel_err) if (trigger % variance < variance) then trigger % variance = std_dev ** 2 @@ -236,217 +229,10 @@ contains end do NUCLIDE_LOOP if (t % n_filters() == 0) exit FILTER_LOOP end do FILTER_LOOP - end if end associate end do TRIGGER_LOOP end associate end do TALLY_LOOP end subroutine check_tally_triggers -!=============================================================================== -! COMPUTE_TALLY_CURRENT computes the current for a mesh current tally with -! precision trigger(s). -!=============================================================================== - - subroutine compute_tally_current(t, trigger) - type(TallyObject), intent(in) :: t ! mesh current tally - type(TriggerObject), intent(inout) :: trigger ! mesh current tally trigger - - integer :: i ! mesh index - integer :: j ! loop index for tally filters - integer :: ijk(3) ! indices of mesh cells - integer :: n_dim ! number of mesh dimensions - integer :: n_cells ! number of mesh cells - integer :: l ! index for energy - integer :: i_filter_mesh ! index for mesh filter - integer :: i_filter_ein ! index for incoming energy filter - integer :: i_filter_surf ! index for surface filter - integer :: n ! number of incoming energy bins - integer :: filter_index ! index in results array for filters - logical :: print_ebin ! should incoming energy bin be displayed? - real(8) :: rel_err = ZERO ! temporary relative error of result - real(8) :: std_dev = ZERO ! temporary standard deviration of result - type(RegularMesh) :: m ! surface current mesh - - ! Get pointer to mesh - i_filter_mesh = t % mesh_filter() - i_filter_surf = t % surface_filter() - select type(filt => filters(i_filter_mesh) % obj) - type is (MeshFilter) - m = meshes(filt % mesh()) - end select - - ! initialize bins array - do j = 1, t % n_filters() - call filter_matches(t % filter(j)) % bins_clear() - call filter_matches(t % filter(j)) % bins_push_back(1) - end do - - ! determine how many energyin bins there are - i_filter_ein = t % energyin_filter() - if (i_filter_ein > 0) then - print_ebin = .true. - n = filters(t % filter(i_filter_ein)) % obj % n_bins - i_filter_ein = t % filter(i_filter_ein) - else - print_ebin = .false. - n = 1 - end if - - ! Get the dimensions and number of cells in the mesh - n_dim = m % n_dimension() - n_cells = 1 - do j = 1, n_dim - n_cells = n_cells * m % dimension(j) - end do - - ! Loop over all the mesh cells - do i = 1, n_cells - - ! Get the indices for this cell - call m % get_indices_from_bin(i, ijk) - call filter_matches(i_filter_mesh) % bins_set_data(1, i) - - do l = 1, n - - if (print_ebin) then - call filter_matches(i_filter_ein) % bins_set_data(1, l) - end if - - ! Left Surface - call filter_matches(i_filter_surf) % bins_set_data(1, OUT_LEFT) - filter_index = 1 - do j = 1, t % n_filters() - filter_index = filter_index + (filter_matches(t % filter(j)) % & - bins_data(1) - 1) * t % stride(j) - end do - call get_trigger_uncertainty(std_dev, rel_err, 1, filter_index, t) - if (trigger % std_dev < std_dev) then - trigger % std_dev = std_dev - end if - if (trigger % rel_err < rel_err) then - trigger % rel_err = rel_err - end if - trigger % variance = std_dev**2 - - ! Right Surface - call filter_matches(i_filter_surf) % bins_set_data(1, OUT_RIGHT) - filter_index = 1 - do j = 1, t % n_filters() - filter_index = filter_index + (filter_matches(t % filter(j)) % & - bins_data(1) - 1) * t % stride(j) - end do - call get_trigger_uncertainty(std_dev, rel_err, 1, filter_index, t) - if (trigger % std_dev < std_dev) then - trigger % std_dev = std_dev - end if - if (trigger % rel_err < rel_err) then - trigger % rel_err = rel_err - end if - trigger % variance = trigger % std_dev**2 - - ! Back Surface - call filter_matches(i_filter_surf) % bins_set_data(1, OUT_BACK) - filter_index = 1 - do j = 1, t % n_filters() - filter_index = filter_index + (filter_matches(t % filter(j)) % & - bins_data(1) - 1) * t % stride(j) - end do - call get_trigger_uncertainty(std_dev, rel_err, 1, filter_index, t) - if (trigger % std_dev < std_dev) then - trigger % std_dev = std_dev - end if - if (trigger % rel_err < rel_err) then - trigger % rel_err = rel_err - end if - trigger % variance = trigger % std_dev**2 - - ! Front Surface - call filter_matches(i_filter_surf) % bins_set_data(1, OUT_FRONT) - filter_index = 1 - do j = 1, t % n_filters() - filter_index = filter_index + (filter_matches(t % filter(j)) % & - bins_data(1) - 1) * t % stride(j) - end do - call get_trigger_uncertainty(std_dev, rel_err, 1, filter_index, t) - if (trigger % std_dev < std_dev) then - trigger % std_dev = std_dev - end if - if (trigger % rel_err < rel_err) then - trigger % rel_err = rel_err - end if - trigger % variance = trigger % std_dev**2 - - ! Bottom Surface - call filter_matches(i_filter_surf) % bins_set_data(1, OUT_BOTTOM) - filter_index = 1 - do j = 1, t % n_filters() - filter_index = filter_index + (filter_matches(t % filter(j)) % & - bins_data(1) - 1) * t % stride(j) - end do - call get_trigger_uncertainty(std_dev, rel_err, 1, filter_index, t) - if (trigger % std_dev < std_dev) then - trigger % std_dev = std_dev - end if - if (trigger % rel_err < rel_err) then - trigger % rel_err = rel_err - end if - trigger % variance = trigger % std_dev**2 - - ! Top Surface - call filter_matches(i_filter_surf) % bins_set_data(1, OUT_TOP) - filter_index = 1 - do j = 1, t % n_filters() - filter_index = filter_index + (filter_matches(t % filter(j)) % & - bins_data(1) - 1) * t % stride(j) - end do - call get_trigger_uncertainty(std_dev, rel_err, 1, filter_index, t) - if (trigger % std_dev < std_dev) then - trigger % std_dev = std_dev - end if - if (trigger % rel_err < rel_err) then - trigger % rel_err = rel_err - end if - trigger % variance = trigger % std_dev**2 - - end do - end do - - end subroutine compute_tally_current - -!=============================================================================== -! GET_TRIGGER_UNCERTAINTY computes the standard deviation and relative error -! for a single tally bin for CHECK_TALLY_TRIGGERS. -!=============================================================================== - - subroutine get_trigger_uncertainty(std_dev, rel_err, score_index, & - filter_index, t) - - real(8), intent(inout) :: std_dev ! tally standard deviation - real(8), intent(inout) :: rel_err ! tally relative error - integer, intent(in) :: score_index ! tally results score index - integer, intent(in) :: filter_index ! tally results filter index - type(TallyObject), intent(in) :: t ! tally - - integer :: n ! number of realizations - real(8) :: mean ! tally mean - real(8) :: tally_sum, tally_sum_sq ! results for a single tally bin - - n = t % n_realizations - tally_sum = t % results(RESULT_SUM, score_index, filter_index) - tally_sum_sq = t % results(RESULT_SUM_SQ, score_index, filter_index) - - ! Compute the tally mean and standard deviation - mean = tally_sum / n - std_dev = sqrt((tally_sum_sq / n - mean * mean) / (n - 1)) - - ! Compute the relative error if the mean is non-zero - if (mean == ZERO) then - rel_err = ZERO - else - rel_err = std_dev / mean - end if - - end subroutine get_trigger_uncertainty - end module trigger diff --git a/src/tallies/trigger.cpp b/src/tallies/trigger.cpp new file mode 100644 index 0000000000..d3069d9a09 --- /dev/null +++ b/src/tallies/trigger.cpp @@ -0,0 +1,32 @@ +#include "openmc/tallies/trigger.h" + +#include + +#include "openmc/capi.h" +#include "openmc/tallies/tally.h" + +namespace openmc { + +extern "C" void +get_tally_uncertainty(int i_tally, int score_index, int filter_index, + double* std_dev, double* rel_err) +{ + int n; + int err = openmc_tally_get_n_realizations(i_tally, &n); + + auto results = tally_results(i_tally); + //TODO: off-by-one + auto sum = results(filter_index-1, score_index-1, RESULT_SUM); + auto sum_sq = results(filter_index-1, score_index-1, RESULT_SUM_SQ); + + auto mean = sum / n; + *std_dev = std::sqrt((sum_sq/n - mean*mean) / (n-1)); + + if (mean > 0) { + *rel_err = *std_dev / mean; + } else { + *rel_err = 0.; + } +} + +} // namespace openmc diff --git a/src/tallies/trigger_header.F90 b/src/tallies/trigger_header.F90 index ccf497d1da..c370483cea 100644 --- a/src/tallies/trigger_header.F90 +++ b/src/tallies/trigger_header.F90 @@ -11,13 +11,13 @@ module trigger_header ! TRIGGEROBJECT stores the variance, relative error and standard deviation ! for some user-specified trigger. !=============================================================================== - type, public :: TriggerObject - integer :: type ! "variance", "std_dev" or "rel_err" - real(8) :: threshold ! a convergence threshold - integer :: score_index ! the index of the score - real(8) :: variance = ZERO ! temp variance container - real(8) :: std_dev = ZERO ! temp std. dev. container - real(8) :: rel_err = ZERO ! temp rel. err. container + type, public, bind(C) :: TriggerObject + integer(C_INT) :: type ! "variance", "std_dev" or "rel_err" + real(C_DOUBLE) :: threshold ! a convergence threshold + integer(C_INT) :: score_index ! the index of the score + real(C_DOUBLE) :: variance = ZERO ! temp variance container + real(C_DOUBLE) :: std_dev = ZERO ! temp std. dev. container + real(C_DOUBLE) :: rel_err = ZERO ! temp rel. err. container end type TriggerObject !=============================================================================== From b66a4d28b9ced77b1e1b88c7bbeb2225627841cd Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Thu, 31 Jan 2019 16:18:46 -0500 Subject: [PATCH 20/58] Move most tally trigger math to C++ --- src/tallies/trigger.F90 | 176 +++++++--------------------------------- src/tallies/trigger.cpp | 128 +++++++++++++++++++++++++++-- 2 files changed, 148 insertions(+), 156 deletions(-) diff --git a/src/tallies/trigger.F90 b/src/tallies/trigger.F90 index 93068f4f5d..4aea523268 100644 --- a/src/tallies/trigger.F90 +++ b/src/tallies/trigger.F90 @@ -17,14 +17,16 @@ module trigger implicit none interface - subroutine get_tally_uncertainty(i_tally, score_index, filter_index, & - std_dev, rel_err) bind(C) - import C_INT, C_DOUBLE - integer(C_INT), value :: i_tally - integer(C_INT), value :: score_index - integer(C_INT), value :: filter_index - real(C_DOUBLE) :: std_dev - real(C_DOUBLE) :: rel_err + function check_keff_trigger() bind(C) result(ratio) + import C_DOUBLE + real(C_DOUBLE) :: ratio + end function + + subroutine check_tally_triggers(ratio, tally_id, score) bind(C) + import C_DOUBLE, C_INT + real(C_DOUBLE) :: ratio + integer(C_INT) :: tally_id + integer(C_INT) :: score end subroutine end interface @@ -52,13 +54,16 @@ contains if (mod((current_batch - n_batches), n_batch_interval) /= 0 .and. & current_batch /= n_max_batches) return - ! By default, assume all triggers are satisfied - satisfy_triggers = .true. - ! Check the eigenvalue and tally triggers - call check_keff_trigger(keff_ratio) + keff_ratio = check_keff_trigger() call check_tally_triggers(tally_ratio, tally_id, score) + if (max(keff_ratio, tally_ratio) <= ONE) then + satisfy_triggers = .true. + else + satisfy_triggers = .false. + end if + ! Alert the user if the triggers are satisfied if (satisfy_triggers) then call write_message("Triggers satisfied for batch " // & @@ -97,142 +102,17 @@ contains end if end subroutine check_triggers -!=============================================================================== -! CHECK_KEFF_TRIGGER computes the uncertainty/threshold ratio for the eigenvalue -! trigger and updates the global satisfy_tiggers variable if the trigger is -! unsatisfied. -!=============================================================================== + function n_tally_triggers(i_tally) bind(C) result(n) + integer(C_INT), value :: i_tally + integer(C_INT) :: n + n = tallies(i_tally) % obj % n_triggers + end function - subroutine check_keff_trigger(ratio) - real(8), intent(out) :: ratio - integer(C_INT) :: err - real(C_DOUBLE) :: k_combined(2) - real(8) :: uncertainty - - ratio = 0 - if (run_mode == MODE_EIGENVALUE) then - if (keff_trigger % trigger_type /= 0) then - err = openmc_get_keff(k_combined) - select case (keff_trigger % trigger_type) - case(VARIANCE) - uncertainty = k_combined(2) ** 2 - case(STANDARD_DEVIATION) - uncertainty = k_combined(2) - case default - uncertainty = k_combined(2) / k_combined(1) - end select - - ! If uncertainty is above threshold, store uncertainty ratio - if (uncertainty > keff_trigger % threshold) then - satisfy_triggers = .false. - if (keff_trigger % trigger_type == VARIANCE) then - ratio = sqrt(uncertainty / keff_trigger % threshold) - else - ratio = uncertainty / keff_trigger % threshold - end if - end if - end if - end if - end subroutine check_keff_trigger - -!=============================================================================== -! CHECK_TALLY_TRIGGERS computes the uncertainty/threshold ratio for all tally -! triggers and updates the global satisfy_tiggers variable if any trigger is -! unsatisfied. -!=============================================================================== - - subroutine check_tally_triggers(max_ratio, tally_id, score) - - ! Variables to reflect distance to trigger convergence criteria - real(8), intent(out) :: max_ratio ! max uncertainty/thresh ratio - integer, intent(out) :: tally_id ! id for tally with max ratio - integer, intent(out) :: score - - integer :: i ! index in tallies array - integer :: j ! index in tally filters - integer :: n ! loop index for nuclides - integer :: s ! loop index for triggers - integer :: filter_index ! index in results array for filters - integer :: score_index ! scoring bin index - integer(C_INT) :: err - real(8) :: uncertainty ! trigger uncertainty - real(8) :: std_dev = ZERO ! trigger standard deviation - real(8) :: rel_err = ZERO ! trigger relative error - real(8) :: ratio ! ratio of the uncertainty/trigger threshold - real(C_DOUBLE) :: k_combined(2) - - ! Initialize tally trigger maximum uncertainty ratio to zero - max_ratio = 0 - - ! Compute uncertainties for all tallies, scores with triggers - TALLY_LOOP: do i = 1, n_tallies - associate (t => tallies(i) % obj) - - ! Cycle through if only one batch has been simumlate - if (t % n_realizations == 1) then - cycle TALLY_LOOP - end if - - TRIGGER_LOOP: do s = 1, t % n_triggers - associate (trigger => t % triggers(s)) - - ! Initialize trigger uncertainties to zero - trigger % std_dev = ZERO - trigger % rel_err = ZERO - trigger % variance = ZERO - - FILTER_LOOP: do filter_index = 1, t % n_filter_bins() - - ! Initialize score index - score_index = trigger % score_index - - ! Initialize score bin index - NUCLIDE_LOOP: do n = 1, t % n_nuclide_bins() - - call get_tally_uncertainty(i, score_index, filter_index, & - std_dev, rel_err) - - if (trigger % variance < variance) then - trigger % variance = std_dev ** 2 - end if - if (trigger % std_dev < std_dev) then - trigger % std_dev = std_dev - end if - if (trigger % rel_err < rel_err) then - trigger % rel_err = rel_err - end if - - select case (trigger % type) - case(VARIANCE) - uncertainty = trigger % variance - case(STANDARD_DEVIATION) - uncertainty = trigger % std_dev - case default - uncertainty = trigger % rel_err - end select - - if (uncertainty > trigger % threshold) then - satisfy_triggers = .false. - - if (trigger % type == VARIANCE) then - ratio = sqrt(uncertainty / trigger % threshold) - else - ratio = uncertainty / trigger % threshold - end if - - if (max_ratio < ratio) then - max_ratio = ratio - score = t % score_bins(trigger % score_index) - tally_id = t % id - end if - end if - end do NUCLIDE_LOOP - if (t % n_filters() == 0) exit FILTER_LOOP - end do FILTER_LOOP - end associate - end do TRIGGER_LOOP - end associate - end do TALLY_LOOP - end subroutine check_tally_triggers + function get_tally_trigger(i_tally, i_trig) bind(C) result(trigger) + integer(C_INT), value :: i_tally + integer(C_INT), value :: i_trig + type(C_PTR) :: trigger + trigger = C_LOC(tallies(i_tally) % obj % triggers(i_trig)) + end function end module trigger diff --git a/src/tallies/trigger.cpp b/src/tallies/trigger.cpp index d3069d9a09..6a6fe231d3 100644 --- a/src/tallies/trigger.cpp +++ b/src/tallies/trigger.cpp @@ -1,15 +1,18 @@ #include "openmc/tallies/trigger.h" #include +#include // for std::pair #include "openmc/capi.h" +#include "openmc/constants.h" +#include "openmc/message_passing.h" +#include "openmc/settings.h" #include "openmc/tallies/tally.h" namespace openmc { -extern "C" void -get_tally_uncertainty(int i_tally, int score_index, int filter_index, - double* std_dev, double* rel_err) +static std::pair +get_tally_uncertainty(int i_tally, int score_index, int filter_index) { int n; int err = openmc_tally_get_n_realizations(i_tally, &n); @@ -20,13 +23,122 @@ get_tally_uncertainty(int i_tally, int score_index, int filter_index, auto sum_sq = results(filter_index-1, score_index-1, RESULT_SUM_SQ); auto mean = sum / n; - *std_dev = std::sqrt((sum_sq/n - mean*mean) / (n-1)); + double std_dev = std::sqrt((sum_sq/n - mean*mean) / (n-1)); + double rel_err = (mean != 0.) ? std_dev / std::abs(mean) : 0.; - if (mean > 0) { - *rel_err = *std_dev / mean; - } else { - *rel_err = 0.; + return {std_dev, rel_err}; +} + +// Functions defined in F90. +extern "C" int n_tally_triggers(int i_tally); +extern "C" Trigger* get_tally_trigger(int i_tally, int i_trig); + +//! Finds the limiting limiting tally trigger. +// +//! param[out] ratio The uncertainty/threshold ratio for the most limiting +//! tally trigger +//! param[out] tally_id The ID number of the most limiting tally +//! param[out] score The most limiting tally score bin + +extern "C" void +check_tally_triggers(double* ratio, int* tally_id, int* score) +{ + *ratio = 0.; + //TODO: off-by-one + for (auto i_tally = 1; i_tally < model::tallies.size()+1; ++i_tally) { + const Tally& t {*model::tallies[i_tally-1]}; + + // Ignore tallies with less than two realizations. + int n_reals; + int err = openmc_tally_get_n_realizations(i_tally, &n_reals); + if (n_reals < 2) continue; + + //TODO: off-by-one + for (auto i_trig = 1; i_trig < n_tally_triggers(i_tally)+1; ++i_trig) { + auto& trigger {*get_tally_trigger(i_tally, i_trig)}; + + trigger.std_dev = 0.; + trigger.rel_err = 0.; + trigger.variance = 0.; + + const auto& results = tally_results(i_tally); + //TODO: off-by-one + for (auto filter_index = 1; filter_index < results.shape()[0]+1; + ++filter_index) { + //TODO: off-by-one + for (auto score_index = 1; score_index < results.shape()[1]+1; + ++score_index) { + auto uncert_pair = get_tally_uncertainty(i_tally, score_index, + filter_index); + double std_dev = uncert_pair.first; + double rel_err = uncert_pair.second; + if (trigger.std_dev < std_dev) { + trigger.std_dev = std_dev; + trigger.variance = std_dev * std_dev; + } + if (trigger.rel_err < rel_err) { + trigger.rel_err = rel_err; + } + + double uncertainty; + switch (trigger.type) { + case VARIANCE: + uncertainty = trigger.variance; + break; + case STANDARD_DEVIATION: + uncertainty = trigger.std_dev; + break; + case RELATIVE_ERROR: + uncertainty = trigger.rel_err; + } + + double this_ratio = uncertainty / trigger.threshold; + if (trigger.type == VARIANCE) { + this_ratio = std::sqrt(*ratio); + } + + if (this_ratio > *ratio) { + *ratio = this_ratio; + int* scores; + int junk; + err = openmc_tally_get_scores(i_tally, &scores, &junk); + //TODO: off-by-one + *score = scores[trigger.score_index-1]; + err = openmc_tally_get_id(i_tally, tally_id); + } + } + } + } } } +//! Computes the uncertainty/threshold ratio for the eigenvalue trigger. + +extern "C" double +check_keff_trigger() +{ + if (settings::run_mode != RUN_MODE_EIGENVALUE) return 0.; + if (settings::keff_trigger.type == 0) return 0.; + + double k_combined[2]; + int err = openmc_get_keff(k_combined); + + double uncertainty = 0.; + switch (settings::keff_trigger.type) { + case VARIANCE: + uncertainty = k_combined[1] * k_combined[1]; + break; + case STANDARD_DEVIATION: + uncertainty = k_combined[1]; + break; + case RELATIVE_ERROR: + uncertainty = k_combined[1] / k_combined[0]; + } + + double ratio = uncertainty / settings::keff_trigger.threshold; + if (settings::keff_trigger.type == VARIANCE) + ratio = std::sqrt(ratio); + return ratio; +} + } // namespace openmc From 5b8806c839bb080e4b0a944bd07f832094feaa3a Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Fri, 1 Feb 2019 13:03:00 -0500 Subject: [PATCH 21/58] Move check_triggers to C++ --- src/simulation.cpp | 2 +- src/tallies/trigger.F90 | 95 +---------------------------------------- src/tallies/trigger.cpp | 84 +++++++++++++++++++++++++++++++----- 3 files changed, 76 insertions(+), 105 deletions(-) diff --git a/src/simulation.cpp b/src/simulation.cpp index 3591953cb8..4565a6391d 100644 --- a/src/simulation.cpp +++ b/src/simulation.cpp @@ -16,6 +16,7 @@ #include "openmc/timer.h" #include "openmc/tallies/filter.h" #include "openmc/tallies/tally.h" +#include "openmc/tallies/trigger.h" #include @@ -27,7 +28,6 @@ namespace openmc { // data/functions from Fortran side extern "C" void accumulate_tallies(); extern "C" void allocate_tally_results(); -extern "C" void check_triggers(); extern "C" void init_tally_routines(); extern "C" void load_state_point(); extern "C" void print_batch_keff(); diff --git a/src/tallies/trigger.F90 b/src/tallies/trigger.F90 index 4aea523268..d65b14c8f7 100644 --- a/src/tallies/trigger.F90 +++ b/src/tallies/trigger.F90 @@ -2,106 +2,13 @@ module trigger use, intrinsic :: ISO_C_BINDING - use constants - use eigenvalue, only: openmc_get_keff - use endf, only: reaction_name - use error, only: warning, write_message - use string, only: to_str - use message_passing, only: master - use settings - use simulation_header use trigger_header - use tally, only: TallyObject - use tally_header, only: tallies, n_tallies + use tally_header, only: tallies implicit none - interface - function check_keff_trigger() bind(C) result(ratio) - import C_DOUBLE - real(C_DOUBLE) :: ratio - end function - - subroutine check_tally_triggers(ratio, tally_id, score) bind(C) - import C_DOUBLE, C_INT - real(C_DOUBLE) :: ratio - integer(C_INT) :: tally_id - integer(C_INT) :: score - end subroutine - end interface - contains -!=============================================================================== -! CHECK_TRIGGERS checks any user-specified precision triggers' for convergence -! and predicts the number of remainining batches to convergence. -!=============================================================================== - - subroutine check_triggers() bind(C) - - implicit none - - real(8) :: keff_ratio ! uncertainty/threshold ratio for keff - real(8) :: tally_ratio ! max tally uncertainty/threshold ratio - integer :: tally_id ! id for tally with max ratio - integer :: score ! tally score with max ratio - integer :: n_pred_batches ! predicted # batches to satisfy all triggers - - if (.not. master) return - - ! Checks if current_batch is one for which the triggers must be checked - if (current_batch < n_batches .or. (.not. trigger_on)) return - if (mod((current_batch - n_batches), n_batch_interval) /= 0 .and. & - current_batch /= n_max_batches) return - - ! Check the eigenvalue and tally triggers - keff_ratio = check_keff_trigger() - call check_tally_triggers(tally_ratio, tally_id, score) - - if (max(keff_ratio, tally_ratio) <= ONE) then - satisfy_triggers = .true. - else - satisfy_triggers = .false. - end if - - ! Alert the user if the triggers are satisfied - if (satisfy_triggers) then - call write_message("Triggers satisfied for batch " // & - trim(to_str(current_batch)), 7) - return - end if - - ! Specify which trigger is unsatisfied - if (keff_ratio >= tally_ratio) then - call write_message("Triggers unsatisfied, max unc./thresh. is " // & - trim(to_str(keff_ratio)) // " for eigenvalue", 7) - else - call write_message("Triggers unsatisfied, max unc./thresh. is " // & - trim(to_str(tally_ratio)) // " for " // trim(reaction_name(score)) & - // " in tally " // trim(to_str(tally_id)), 7) - end if - - ! Estimate batches till triggers are satisfied - if (pred_batches) then - ! Estimate the number of remaining batches to convergence - ! The prediction uses the fact that tally variances are proportional - ! to 1/N where N is the number of the batches/particles - n_batch_interval = int((current_batch-n_inactive) * & - (max(keff_ratio, tally_ratio) ** 2)) + n_inactive-n_batches + 1 - n_pred_batches = n_batch_interval + n_batches - - ! Write the predicted number of batches for the user - if (n_pred_batches > n_max_batches) then - call warning("The estimated number of batches is " // & - trim(to_str(n_pred_batches)) // & - " -- greater than max batches. ") - else - call write_message("The estimated number of batches is " // & - trim(to_str(n_pred_batches)), 7) - end if - end if - end subroutine check_triggers - function n_tally_triggers(i_tally) bind(C) result(n) integer(C_INT), value :: i_tally integer(C_INT) :: n diff --git a/src/tallies/trigger.cpp b/src/tallies/trigger.cpp index 6a6fe231d3..79926e9343 100644 --- a/src/tallies/trigger.cpp +++ b/src/tallies/trigger.cpp @@ -1,12 +1,15 @@ #include "openmc/tallies/trigger.h" #include +#include #include // for std::pair #include "openmc/capi.h" #include "openmc/constants.h" -#include "openmc/message_passing.h" +#include "openmc/error.h" +#include "openmc/reaction.h" #include "openmc/settings.h" +#include "openmc/simulation.h" #include "openmc/tallies/tally.h" namespace openmc { @@ -40,10 +43,10 @@ extern "C" Trigger* get_tally_trigger(int i_tally, int i_trig); //! param[out] tally_id The ID number of the most limiting tally //! param[out] score The most limiting tally score bin -extern "C" void -check_tally_triggers(double* ratio, int* tally_id, int* score) +void +check_tally_triggers(double& ratio, int& tally_id, int& score) { - *ratio = 0.; + ratio = 0.; //TODO: off-by-one for (auto i_tally = 1; i_tally < model::tallies.size()+1; ++i_tally) { const Tally& t {*model::tallies[i_tally-1]}; @@ -94,17 +97,17 @@ check_tally_triggers(double* ratio, int* tally_id, int* score) double this_ratio = uncertainty / trigger.threshold; if (trigger.type == VARIANCE) { - this_ratio = std::sqrt(*ratio); + this_ratio = std::sqrt(ratio); } - if (this_ratio > *ratio) { - *ratio = this_ratio; + if (this_ratio > ratio) { + ratio = this_ratio; int* scores; int junk; err = openmc_tally_get_scores(i_tally, &scores, &junk); //TODO: off-by-one - *score = scores[trigger.score_index-1]; - err = openmc_tally_get_id(i_tally, tally_id); + score = scores[trigger.score_index-1]; + err = openmc_tally_get_id(i_tally, &tally_id); } } } @@ -114,7 +117,7 @@ check_tally_triggers(double* ratio, int* tally_id, int* score) //! Computes the uncertainty/threshold ratio for the eigenvalue trigger. -extern "C" double +double check_keff_trigger() { if (settings::run_mode != RUN_MODE_EIGENVALUE) return 0.; @@ -141,4 +144,65 @@ check_keff_trigger() return ratio; } +//! See if tally and eigenvalue uncertainties are under trigger thresholds. + +void +check_triggers() +{ + // Make some aliases. + const auto current_batch {simulation::current_batch}; + const auto n_batches {settings::n_batches}; + const auto interval {settings::trigger_batch_interval}; + + // See if the current batch is one for which the triggers must be checked. + if (!settings::trigger_on) return; + if (current_batch < n_batches) return; + if (((current_batch - n_batches) % interval) != 0) return; + + // Check the eigenvalue and tally triggers. + double keff_ratio = check_keff_trigger(); + double tally_ratio; + int tally_id, score; + check_tally_triggers(tally_ratio, tally_id, score); + + // If all the triggers are satisfied, alert the user and return. + if (std::max(keff_ratio, tally_ratio) <= 1.) { + simulation::satisfy_triggers = true; + write_message("Triggers satisfied for batch " + + std::to_string(current_batch), 7); + return; + } + + // At least one trigger is unsatisfied. Let the user know which one. + simulation::satisfy_triggers = false; + std::stringstream msg; + msg << "Triggers unsatisfied, max unc./thresh. is "; + if (keff_ratio >= tally_ratio) { + msg << keff_ratio << " for eigenvalue"; + } else { + msg << tally_ratio << " for " << reaction_name(score) << " in tally " + << tally_id; + } + write_message(msg, 7); + + // Estimate batches til triggers are satisfied. + if (settings::trigger_predict) { + // This calculation assumes tally variance is proportional to 1/N where N is + // the number of batches. + auto max_ratio = std::max(keff_ratio, tally_ratio); + auto n_active = current_batch - settings::n_inactive; + auto n_pred_batches = static_cast(n_active * max_ratio * max_ratio) + + settings::n_inactive + 1; + + std::stringstream msg; + msg << "The estimated number of batches is " << n_pred_batches; + if (n_pred_batches > settings::n_max_batches) { + msg << " --- greater than max batches"; + warning(msg); + } else { + write_message(msg, 7); + } + } +} + } // namespace openmc From 3ea876235e294025e5ba3c2292526df5bdb35170 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sat, 2 Feb 2019 12:07:24 -0500 Subject: [PATCH 22/58] Finish moving triggers to C++ --- CMakeLists.txt | 2 - include/openmc/tallies/tally.h | 8 ++ include/openmc/tallies/trigger.h | 25 ++++- src/api.F90 | 1 - src/constants.F90 | 6 - src/input_xml.F90 | 187 ++----------------------------- src/settings.F90 | 2 - src/simulation_header.F90 | 5 - src/tallies/tally.cpp | 91 ++++++++++++++- src/tallies/tally_header.F90 | 5 - src/tallies/trigger.F90 | 25 ----- src/tallies/trigger.cpp | 24 ++-- src/tallies/trigger_header.F90 | 33 ------ 13 files changed, 142 insertions(+), 272 deletions(-) delete mode 100644 src/tallies/trigger.F90 delete mode 100644 src/tallies/trigger_header.F90 diff --git a/CMakeLists.txt b/CMakeLists.txt index 8fb3f9c0d4..4efc2e38b4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -362,8 +362,6 @@ add_library(libopenmc SHARED src/tallies/tally_filter_particle.F90 src/tallies/tally_filter_sph_harm.F90 src/tallies/tally_header.F90 - src/tallies/trigger.F90 - src/tallies/trigger_header.F90 src/bank.cpp src/bremsstrahlung.cpp src/dagmc.cpp diff --git a/include/openmc/tallies/tally.h b/include/openmc/tallies/tally.h index d4bbfbaa11..93ddcd3904 100644 --- a/include/openmc/tallies/tally.h +++ b/include/openmc/tallies/tally.h @@ -2,6 +2,7 @@ #define OPENMC_TALLIES_TALLY_H #include "openmc/constants.h" +#include "openmc/tallies/trigger.h" #include "pugixml.hpp" #include "xtensor/xtensor.hpp" @@ -33,6 +34,11 @@ public: int32_t n_filter_bins() const {return n_filter_bins_;} + //---------------------------------------------------------------------------- + // Other methods. + + void init_triggers(pugi::xml_node node, int i_tally); + //---------------------------------------------------------------------------- // Major public data members. @@ -59,6 +65,8 @@ public: int energyout_filter_ {C_NONE}; int delayedgroup_filter_ {C_NONE}; + std::vector triggers_; + int deriv_ {C_NONE}; //!< Index of a TallyDerivative object for diff tallies. private: diff --git a/include/openmc/tallies/trigger.h b/include/openmc/tallies/trigger.h index 56c15f7bc2..458c2925a0 100644 --- a/include/openmc/tallies/trigger.h +++ b/include/openmc/tallies/trigger.h @@ -7,10 +7,17 @@ namespace openmc { +//============================================================================== +// Structs +//============================================================================== + //! Stops the simulation early if a desired tally uncertainty is reached. -extern "C" struct Trigger +struct Trigger { + Trigger(int type_, double threshold_, int score_index_) + : type(type_), threshold(threshold_), score_index(score_index_) {} + int type; //!< variance, std_dev, or rel_err double threshold; //!< uncertainty value below which trigger is satisfied int score_index; //!< index of the relevant score in the tally's arrays @@ -21,16 +28,26 @@ extern "C" struct Trigger //! Stops the simulation early if a desired k-effective uncertainty is reached. -extern "C" struct KTrigger +struct KTrigger { - int type{0}; + int type {0}; double threshold {0.}; }; +//============================================================================== +// Global variable declarations +//============================================================================== + //TODO: consider a different namespace namespace settings { - extern "C" KTrigger keff_trigger; + extern KTrigger keff_trigger; } +//============================================================================== +// Non-memeber functions +//============================================================================== + +void check_triggers(); + } // namespace openmc #endif // OPENMC_TALLIES_TRIGGER_H diff --git a/src/api.F90 b/src/api.F90 index cf5670bc9f..b170ac3385 100644 --- a/src/api.F90 +++ b/src/api.F90 @@ -68,7 +68,6 @@ contains use surface_header use tally_filter_header use tally_header - use trigger_header use volume_header interface diff --git a/src/constants.F90 b/src/constants.F90 index 44d91f70ba..45ad98100d 100644 --- a/src/constants.F90 +++ b/src/constants.F90 @@ -272,12 +272,6 @@ module constants OUT_TOP = 11, & ! z max IN_TOP = 12 ! z max - ! Tally trigger types and threshold - integer, parameter :: & - VARIANCE = 1, & - RELATIVE_ERROR = 2, & - STANDARD_DEVIATION = 3 - ! Global tally parameters integer, parameter :: N_GLOBAL_TALLIES = 4 integer, parameter :: & diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 160acd263b..a31f174c7e 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -36,7 +36,6 @@ module input_xml use tally_filter_header use tally_filter use timer_header, only: time_read_xs - use trigger_header use volume_header use xml_interface @@ -915,17 +914,12 @@ contains integer :: tally_id ! user-specified identifier for filter integer :: deriv_id integer :: i_filt ! index in filters array - integer :: i_elem ! index of entry in dictionary integer :: n ! size of arrays in mesh specification integer :: n_words ! number of words read integer :: n_filter ! number of filters integer :: n_scores ! number of scores - integer :: n_user_trig ! number of user-specified tally triggers - integer :: trig_ind ! index of triggers array for each tally - integer :: user_trig_ind ! index of user-specified triggers for each tally integer :: i_start, i_end integer(C_INT) :: err - real(8) :: threshold ! trigger convergence threshold integer :: MT ! user-specified MT for score logical :: file_exists ! does tallies.xml file exist? integer, allocatable :: temp_filter(:) ! temporary filter indices @@ -937,22 +931,25 @@ contains character(MAX_WORD_LEN) :: score_name character(MAX_WORD_LEN) :: temp_str character(MAX_WORD_LEN), allocatable :: sarray(:) - type(DictCharInt) :: trigger_scores type(TallyFilterContainer), pointer :: f type(XMLDocument) :: doc type(XMLNode) :: root type(XMLNode) :: node_tal type(XMLNode) :: node_filt - type(XMLNode) :: node_trigger type(XMLNode), allocatable :: node_tal_list(:) type(XMLNode), allocatable :: node_filt_list(:) - type(XMLNode), allocatable :: node_trigger_list(:) - type(DictEntryCI) :: elem type(TallyDerivative), pointer :: deriv integer, allocatable :: nuclide_bins(:) logical :: all_nuclides interface + subroutine tally_init_triggers(tally_ptr, i_tally, xml_node) bind(C) + import C_PTR, C_INT + type(C_PTR), value :: tally_ptr + integer(C_INT), value :: i_tally + type(C_PTR) :: xml_node + end subroutine + subroutine read_tally_derivatives(node_ptr) bind(C) import C_PTR type(C_PTR) :: node_ptr @@ -1262,15 +1259,6 @@ contains n_words = node_word_count(node_tal, "scores") allocate(sarray(n_words)) call get_node_array(node_tal, "scores", sarray) - - ! Append the score to the list of possible trigger scores - do j = 1, n_words - sarray(j) = to_lower(sarray(j)) - score_name = trim(sarray(j)) - - if (trigger_on) call trigger_scores % set(trim(score_name), j) - - end do n_scores = n_words ! Allocate score storage accordingly @@ -1633,166 +1621,7 @@ contains ! If settings.xml trigger is turned on, create tally triggers if (trigger_on) then - - ! Get list of trigger nodes for this tally - call get_node_list(node_tal, "trigger", node_trigger_list) - - ! Initialize the number of triggers - n_user_trig = size(node_trigger_list) - - ! Count the number of triggers needed for all scores including "all" - t % n_triggers = 0 - COUNT_TRIGGERS: do user_trig_ind = 1, n_user_trig - - ! Get pointer to trigger node - node_trigger = node_trigger_list(user_trig_ind) - - ! Get scores for this trigger - if (check_for_node(node_trigger, "scores")) then - n_words = node_word_count(node_trigger, "scores") - allocate(sarray(n_words)) - call get_node_array(node_trigger, "scores", sarray) - else - n_words = 1 - allocate(sarray(n_words)) - sarray(1) = "all" - end if - - ! Count the number of scores for this trigger - do j = 1, n_words - score_name = trim(to_lower(sarray(j))) - - if (score_name == "all") then - t % n_triggers = t % n_triggers + trigger_scores % size() - else - t % n_triggers = t % n_triggers + 1 - end if - - end do - - deallocate(sarray) - - end do COUNT_TRIGGERS - - ! Allocate array of triggers for this tally - if (t % n_triggers > 0) then - allocate(t % triggers(t % n_triggers)) - end if - - ! Initialize overall trigger index for this tally to zero - trig_ind = 1 - - ! Create triggers for all scores specified on each trigger - TRIGGER_LOOP: do user_trig_ind = 1, n_user_trig - - ! Get pointer to trigger node - node_trigger = node_trigger_list(user_trig_ind) - - ! Get the trigger type - "variance", "std_dev" or "rel_err" - if (check_for_node(node_trigger, "type")) then - call get_node_value(node_trigger, "type", temp_str) - temp_str = to_lower(temp_str) - else - call fatal_error("Must specify trigger type for tally " // & - trim(to_str(t % id)) // " in tally XML file.") - end if - - ! Get the convergence threshold for the trigger - if (check_for_node(node_trigger, "threshold")) then - call get_node_value(node_trigger, "threshold", threshold) - else - call fatal_error("Must specify trigger threshold for tally " // & - trim(to_str(t % id)) // " in tally XML file.") - end if - - ! Get list scores for this trigger - if (check_for_node(node_trigger, "scores")) then - n_words = node_word_count(node_trigger, "scores") - allocate(sarray(n_words)) - call get_node_array(node_trigger, "scores", sarray) - else - n_words = 1 - allocate(sarray(n_words)) - sarray(1) = "all" - end if - - ! Create a trigger for each score - SCORE_LOOP: do j = 1, n_words - score_name = trim(to_lower(sarray(j))) - - ! Expand "all" to include TriggerObjects for each score in tally - if (score_name == "all") then - - ! Loop over all tally scores - i_elem = 0 - do - ! Move to next score - call trigger_scores % next_entry(elem, i_elem) - if (i_elem == 0) exit - - ! Store the score name and index in the trigger - t % triggers(trig_ind) % score_index = elem % value - - ! Set the trigger convergence threshold type - select case (temp_str) - case ('std_dev') - t % triggers(trig_ind) % type = STANDARD_DEVIATION - case ('variance') - t % triggers(trig_ind) % type = VARIANCE - case ('rel_err') - t % triggers(trig_ind) % type = RELATIVE_ERROR - case default - call fatal_error("Unknown trigger type " // & - trim(temp_str) // " in tally " // trim(to_str(t % id))) - end select - - ! Store the trigger convergence threshold - t % triggers(trig_ind) % threshold = threshold - - ! Increment the overall trigger index - trig_ind = trig_ind + 1 - end do - - ! Scores other than the "all" placeholder - else - - ! Store the score name and index - t % triggers(trig_ind) % score_index = & - trigger_scores % get(trim(score_name)) - - ! Check if an invalid score was set for the trigger - if (t % triggers(trig_ind) % score_index == 0) then - call fatal_error("The trigger score " // trim(score_name) // & - " is not set for tally " // trim(to_str(t % id))) - end if - - ! Store the trigger convergence threshold - t % triggers(trig_ind) % threshold = threshold - - ! Set the trigger convergence threshold type - select case (temp_str) - case ('std_dev') - t % triggers(trig_ind) % type = STANDARD_DEVIATION - case ('variance') - t % triggers(trig_ind) % type = VARIANCE - case ('rel_err') - t % triggers(trig_ind) % type = RELATIVE_ERROR - case default - call fatal_error("Unknown trigger type " // trim(temp_str) // & - " in tally " // trim(to_str(t % id))) - end select - - ! Increment the overall trigger index - trig_ind = trig_ind + 1 - end if - end do SCORE_LOOP - - ! Deallocate the list of tally scores used to create triggers - deallocate(sarray) - end do TRIGGER_LOOP - - ! Deallocate dictionary of scores/indices used to populate triggers - call trigger_scores % clear() + call tally_init_triggers(t % ptr, i_start + i - 1, node_tal % ptr) end if ! ======================================================================= diff --git a/src/settings.F90 b/src/settings.F90 index f0b6a131fd..37cab546e1 100644 --- a/src/settings.F90 +++ b/src/settings.F90 @@ -51,8 +51,6 @@ module settings integer(C_INT32_T), bind(C) :: gen_per_batch ! # of generations per batch integer(C_INT), bind(C) :: n_max_batches ! max # of batches - integer(C_INT), bind(C, name='trigger_batch_interval') :: n_batch_interval ! batch interval for triggers - logical(C_BOOL), bind(C, name='trigger_predict') :: pred_batches ! predict batches for triggers logical(C_BOOL), bind(C) :: trigger_on ! flag for turning triggers on/off logical(C_BOOL), bind(C) :: entropy_on diff --git a/src/simulation_header.F90 b/src/simulation_header.F90 index 1df4b0a4e3..b72377f85d 100644 --- a/src/simulation_header.F90 +++ b/src/simulation_header.F90 @@ -25,11 +25,6 @@ module simulation_header integer(C_INT), bind(C) :: total_gen ! total number of generations simulated logical(C_BOOL), bind(C) :: need_depletion_rx ! need to calculate depletion reaction rx? - ! ============================================================================ - ! TALLY PRECISION TRIGGER VARIABLES - - logical(C_BOOL), bind(C) :: satisfy_triggers ! whether triggers are satisfied - integer(C_INT64_T), bind(C) :: work ! number of particles per processor ! ============================================================================ diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 8357472537..f217efd110 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -12,6 +12,7 @@ #include "openmc/tallies/filter_delayedgroup.h" #include "openmc/tallies/filter_surface.h" #include "openmc/tallies/filter_mesh.h" +#include "openmc/xml_interface.h" #include "xtensor/xadapt.hpp" #include "xtensor/xbuilder.hpp" // for empty_like @@ -19,6 +20,8 @@ #include #include +#include +#include namespace openmc { @@ -63,7 +66,8 @@ double global_tally_collision; double global_tally_tracklength; double global_tally_leakage; -//==============================================================================// Tally object implementation +//============================================================================== +// Tally object implementation //============================================================================== void @@ -106,6 +110,88 @@ Tally::set_filters(const int32_t filter_indices[], int n) n_filter_bins_ = stride; } +void +Tally::init_triggers(pugi::xml_node node, int i_tally) +{ + //TODO: use id_ attribute when it's available in C++ + int id_; + auto err = openmc_tally_get_id(i_tally, &id_); + + for (auto trigger_node: node.children("trigger")) { + // Read the trigger type. + int trigger_type; + if (check_for_node(trigger_node, "type")) { + auto type_str = get_node_value(trigger_node, "type"); + if (type_str == "std_dev") { + trigger_type = STANDARD_DEVIATION; + } else if (type_str == "variance") { + trigger_type = VARIANCE; + } else if (type_str == "rel_err") { + trigger_type = RELATIVE_ERROR; + } else { + std::stringstream msg; + msg << "Unknown trigger type \"" << type_str << "\" in tally " << id_; + fatal_error(msg); + } + } else { + std::stringstream msg; + msg << "Must specify trigger type for tally " << id_ + << " in tally XML file"; + fatal_error(msg); + } + + // Read the trigger threshold. + double threshold; + if (check_for_node(trigger_node, "threshold")) { + threshold = std::stod(get_node_value(trigger_node, "threshold")); + } else { + std::stringstream msg; + msg << "Must specify trigger threshold for tally " << id_ + << " in tally XML file"; + fatal_error(msg); + } + + // Read the trigger scores. + std::vector trigger_scores; + if (check_for_node(trigger_node, "scores")) { + trigger_scores = get_node_array(trigger_node, "scores"); + } else { + trigger_scores.push_back("all"); + } + + //TODO: change this when tally scores are moved to C++ + // Get access to the tally's scores. + int* tally_scores; + int n_tally_scores; + auto err = openmc_tally_get_scores(i_tally, &tally_scores, &n_tally_scores); + + // Parse the trigger scores and populate the triggers_ vector. + for (auto score_str : trigger_scores) { + if (score_str == "all") { + triggers_.reserve(triggers_.size() + n_tally_scores); + for (auto i_score = 0; i_score < n_tally_scores; ++i_score) { + //TODO: off-by-one + triggers_.emplace_back(trigger_type, threshold, i_score+1); + } + } else { + int i_score = 0; + for (; i_score < n_tally_scores; ++i_score) { + if (reaction_name(tally_scores[i_score]) == score_str) break; + } + if (i_score == n_tally_scores) { + std::stringstream msg; + msg << "Could not find the score \"" << score_str << "\" in tally " + << id_ << " but it was listed in a trigger on that tally"; + fatal_error(msg); + } + //TODO: off-by-one + triggers_.emplace_back(trigger_type, threshold, i_score+1); + std::cout << i_score+1 << "\n"; + } + } + } +} + //============================================================================== // Non-member functions //============================================================================== @@ -1070,6 +1156,9 @@ extern "C" { int tally_get_delayedgroup_filter_c(Tally* tally) {return tally->delayedgroup_filter_;} + void tally_init_triggers(Tally* tally, int i_tally, pugi::xml_node* node) + {tally->init_triggers(*node, i_tally);} + int tally_get_deriv_c(Tally* tally) {return tally->deriv_;} int tally_set_deriv_c(Tally* tally, int deriv) {tally->deriv_ = deriv;} diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index 3e76100277..8a5e946642 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -13,7 +13,6 @@ module tally_header use string, only: to_lower, to_f_string, str_to_int, to_str use tally_filter_header, only: TallyFilterContainer, filters, n_filters use tally_filter - use trigger_header, only: TriggerObject implicit none @@ -116,10 +115,6 @@ module tally_header ! Number of realizations of tally random variables integer :: n_realizations = 0 - ! Tally precision triggers - integer :: n_triggers = 0 ! # of triggers - type(TriggerObject), allocatable :: triggers(:) ! Array of triggers - contains procedure :: accumulate => tally_accumulate procedure :: allocate_results => tally_allocate_results diff --git a/src/tallies/trigger.F90 b/src/tallies/trigger.F90 deleted file mode 100644 index d65b14c8f7..0000000000 --- a/src/tallies/trigger.F90 +++ /dev/null @@ -1,25 +0,0 @@ -module trigger - - use, intrinsic :: ISO_C_BINDING - - use trigger_header - use tally_header, only: tallies - - implicit none - -contains - - function n_tally_triggers(i_tally) bind(C) result(n) - integer(C_INT), value :: i_tally - integer(C_INT) :: n - n = tallies(i_tally) % obj % n_triggers - end function - - function get_tally_trigger(i_tally, i_trig) bind(C) result(trigger) - integer(C_INT), value :: i_tally - integer(C_INT), value :: i_trig - type(C_PTR) :: trigger - trigger = C_LOC(tallies(i_tally) % obj % triggers(i_trig)) - end function - -end module trigger diff --git a/src/tallies/trigger.cpp b/src/tallies/trigger.cpp index 79926e9343..0bc783b963 100644 --- a/src/tallies/trigger.cpp +++ b/src/tallies/trigger.cpp @@ -14,6 +14,18 @@ namespace openmc { +//============================================================================== +// Global variable definitions +//============================================================================== + +namespace settings { + KTrigger keff_trigger; +} + +//============================================================================== +// Non-memeber functions +//============================================================================== + static std::pair get_tally_uncertainty(int i_tally, int score_index, int filter_index) { @@ -32,10 +44,6 @@ get_tally_uncertainty(int i_tally, int score_index, int filter_index) return {std_dev, rel_err}; } -// Functions defined in F90. -extern "C" int n_tally_triggers(int i_tally); -extern "C" Trigger* get_tally_trigger(int i_tally, int i_trig); - //! Finds the limiting limiting tally trigger. // //! param[out] ratio The uncertainty/threshold ratio for the most limiting @@ -49,17 +57,15 @@ check_tally_triggers(double& ratio, int& tally_id, int& score) ratio = 0.; //TODO: off-by-one for (auto i_tally = 1; i_tally < model::tallies.size()+1; ++i_tally) { - const Tally& t {*model::tallies[i_tally-1]}; + //TODO: can I mike trigger and t const? + Tally& t {*model::tallies[i_tally-1]}; // Ignore tallies with less than two realizations. int n_reals; int err = openmc_tally_get_n_realizations(i_tally, &n_reals); if (n_reals < 2) continue; - //TODO: off-by-one - for (auto i_trig = 1; i_trig < n_tally_triggers(i_tally)+1; ++i_trig) { - auto& trigger {*get_tally_trigger(i_tally, i_trig)}; - + for (auto& trigger : t.triggers_) { trigger.std_dev = 0.; trigger.rel_err = 0.; trigger.variance = 0.; diff --git a/src/tallies/trigger_header.F90 b/src/tallies/trigger_header.F90 deleted file mode 100644 index c370483cea..0000000000 --- a/src/tallies/trigger_header.F90 +++ /dev/null @@ -1,33 +0,0 @@ -module trigger_header - - use, intrinsic :: ISO_C_BINDING - - use constants, only: NONE, ZERO - - implicit none - private - -!=============================================================================== -! TRIGGEROBJECT stores the variance, relative error and standard deviation -! for some user-specified trigger. -!=============================================================================== - type, public, bind(C) :: TriggerObject - integer(C_INT) :: type ! "variance", "std_dev" or "rel_err" - real(C_DOUBLE) :: threshold ! a convergence threshold - integer(C_INT) :: score_index ! the index of the score - real(C_DOUBLE) :: variance = ZERO ! temp variance container - real(C_DOUBLE) :: std_dev = ZERO ! temp std. dev. container - real(C_DOUBLE) :: rel_err = ZERO ! temp rel. err. container - end type TriggerObject - -!=============================================================================== -! KTRIGGER describes a user-specified precision trigger for k-effective -!=============================================================================== - type, public, bind(C) :: KTrigger - integer(C_INT) :: trigger_type = 0 - real(C_DOUBLE) :: threshold = ZERO - end type KTrigger - - type(KTrigger), public, bind(C) :: keff_trigger ! trigger for k-effective - -end module trigger_header From 9fe05ad9c89475aefcf07d1d1854809b4bec2115 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sat, 2 Feb 2019 12:46:43 -0500 Subject: [PATCH 23/58] Clean up tally triggers --- include/openmc/constants.h | 5 --- include/openmc/tallies/trigger.h | 16 ++++---- src/settings.cpp | 6 +-- src/tallies/tally.cpp | 15 +++----- src/tallies/trigger.cpp | 64 +++++++++++++------------------- 5 files changed, 42 insertions(+), 64 deletions(-) diff --git a/include/openmc/constants.h b/include/openmc/constants.h index 7fc7dcedec..b55ea061f5 100644 --- a/include/openmc/constants.h +++ b/include/openmc/constants.h @@ -385,11 +385,6 @@ constexpr int IN_BOTTOM {10}; // z min constexpr int OUT_TOP {11}; // z max constexpr int IN_TOP {12}; // z max -// Tally trigger types and threshold -constexpr int VARIANCE {1}; -constexpr int RELATIVE_ERROR {2}; -constexpr int STANDARD_DEVIATION {3}; - // Global tally parameters constexpr int N_GLOBAL_TALLIES {4}; constexpr int K_COLLISION {0}; diff --git a/include/openmc/tallies/trigger.h b/include/openmc/tallies/trigger.h index 458c2925a0..f4f44e20ad 100644 --- a/include/openmc/tallies/trigger.h +++ b/include/openmc/tallies/trigger.h @@ -8,29 +8,27 @@ namespace openmc { //============================================================================== -// Structs +// Type definitions //============================================================================== +enum class TriggerMetric { + variance, relative_error, standard_deviation, not_active +}; + //! Stops the simulation early if a desired tally uncertainty is reached. struct Trigger { - Trigger(int type_, double threshold_, int score_index_) - : type(type_), threshold(threshold_), score_index(score_index_) {} - - int type; //!< variance, std_dev, or rel_err + TriggerMetric metric; double threshold; //!< uncertainty value below which trigger is satisfied int score_index; //!< index of the relevant score in the tally's arrays - double variance {0.}; - double std_dev {0.}; - double rel_err {0.}; }; //! Stops the simulation early if a desired k-effective uncertainty is reached. struct KTrigger { - int type {0}; + TriggerMetric metric {TriggerMetric::not_active}; double threshold {0.}; }; diff --git a/src/settings.cpp b/src/settings.cpp index a7543d5e61..0457ea13ff 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -155,11 +155,11 @@ void get_run_parameters(pugi::xml_node node_base) if (check_for_node(node_keff_trigger, "type")) { auto temp = get_node_value(node_keff_trigger, "type", true, true); if (temp == "std_dev") { - keff_trigger.type = STANDARD_DEVIATION; + keff_trigger.metric = TriggerMetric::standard_deviation; } else if (temp == "variance") { - keff_trigger.type = VARIANCE; + keff_trigger.metric = TriggerMetric::variance; } else if (temp == "rel_err") { - keff_trigger.type = RELATIVE_ERROR; + keff_trigger.metric = TriggerMetric::relative_error; } else { fatal_error("Unrecognized keff trigger type " + temp); } diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index f217efd110..81d84456a4 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -119,15 +119,15 @@ Tally::init_triggers(pugi::xml_node node, int i_tally) for (auto trigger_node: node.children("trigger")) { // Read the trigger type. - int trigger_type; + TriggerMetric metric; if (check_for_node(trigger_node, "type")) { auto type_str = get_node_value(trigger_node, "type"); if (type_str == "std_dev") { - trigger_type = STANDARD_DEVIATION; + metric = TriggerMetric::standard_deviation; } else if (type_str == "variance") { - trigger_type = VARIANCE; + metric = TriggerMetric::variance; } else if (type_str == "rel_err") { - trigger_type = RELATIVE_ERROR; + metric = TriggerMetric::relative_error; } else { std::stringstream msg; msg << "Unknown trigger type \"" << type_str << "\" in tally " << id_; @@ -170,8 +170,7 @@ Tally::init_triggers(pugi::xml_node node, int i_tally) if (score_str == "all") { triggers_.reserve(triggers_.size() + n_tally_scores); for (auto i_score = 0; i_score < n_tally_scores; ++i_score) { - //TODO: off-by-one - triggers_.emplace_back(trigger_type, threshold, i_score+1); + triggers_.push_back({metric, threshold, i_score}); } } else { int i_score = 0; @@ -184,9 +183,7 @@ Tally::init_triggers(pugi::xml_node node, int i_tally) << id_ << " but it was listed in a trigger on that tally"; fatal_error(msg); } - //TODO: off-by-one - triggers_.emplace_back(trigger_type, threshold, i_score+1); - std::cout << i_score+1 << "\n"; + triggers_.push_back({metric, threshold, i_score}); } } } diff --git a/src/tallies/trigger.cpp b/src/tallies/trigger.cpp index 0bc783b963..d8f3ed4b7a 100644 --- a/src/tallies/trigger.cpp +++ b/src/tallies/trigger.cpp @@ -33,9 +33,8 @@ get_tally_uncertainty(int i_tally, int score_index, int filter_index) int err = openmc_tally_get_n_realizations(i_tally, &n); auto results = tally_results(i_tally); - //TODO: off-by-one - auto sum = results(filter_index-1, score_index-1, RESULT_SUM); - auto sum_sq = results(filter_index-1, score_index-1, RESULT_SUM_SQ); + auto sum = results(filter_index, score_index, RESULT_SUM); + auto sum_sq = results(filter_index, score_index, RESULT_SUM_SQ); auto mean = sum / n; double std_dev = std::sqrt((sum_sq/n - mean*mean) / (n-1)); @@ -57,62 +56,51 @@ check_tally_triggers(double& ratio, int& tally_id, int& score) ratio = 0.; //TODO: off-by-one for (auto i_tally = 1; i_tally < model::tallies.size()+1; ++i_tally) { - //TODO: can I mike trigger and t const? - Tally& t {*model::tallies[i_tally-1]}; + const Tally& t {*model::tallies[i_tally-1]}; // Ignore tallies with less than two realizations. int n_reals; int err = openmc_tally_get_n_realizations(i_tally, &n_reals); if (n_reals < 2) continue; - for (auto& trigger : t.triggers_) { - trigger.std_dev = 0.; - trigger.rel_err = 0.; - trigger.variance = 0.; - + for (const auto& trigger : t.triggers_) { const auto& results = tally_results(i_tally); - //TODO: off-by-one - for (auto filter_index = 1; filter_index < results.shape()[0]+1; + for (auto filter_index = 0; filter_index < results.shape()[0]; ++filter_index) { - //TODO: off-by-one - for (auto score_index = 1; score_index < results.shape()[1]+1; - ++score_index) { + for (auto score_index = 0; score_index < results.shape()[1]; + ++score_index) { + // Compute the tally uncertainty metrics. auto uncert_pair = get_tally_uncertainty(i_tally, score_index, filter_index); double std_dev = uncert_pair.first; double rel_err = uncert_pair.second; - if (trigger.std_dev < std_dev) { - trigger.std_dev = std_dev; - trigger.variance = std_dev * std_dev; - } - if (trigger.rel_err < rel_err) { - trigger.rel_err = rel_err; - } + // Pick out the relevant uncertainty metric for this trigger. double uncertainty; - switch (trigger.type) { - case VARIANCE: - uncertainty = trigger.variance; + switch (trigger.metric) { + case TriggerMetric::variance: + uncertainty = std_dev * std_dev; break; - case STANDARD_DEVIATION: - uncertainty = trigger.std_dev; + case TriggerMetric::standard_deviation: + uncertainty = std_dev; break; - case RELATIVE_ERROR: - uncertainty = trigger.rel_err; + case TriggerMetric::relative_error: + uncertainty = rel_err; } + // Compute the uncertainty / threshold ratio. double this_ratio = uncertainty / trigger.threshold; - if (trigger.type == VARIANCE) { + if (trigger.metric == TriggerMetric::variance) { this_ratio = std::sqrt(ratio); } + // If this is the most uncertain value, set the output variables. if (this_ratio > ratio) { ratio = this_ratio; int* scores; int junk; err = openmc_tally_get_scores(i_tally, &scores, &junk); - //TODO: off-by-one - score = scores[trigger.score_index-1]; + score = scores[trigger.score_index]; err = openmc_tally_get_id(i_tally, &tally_id); } } @@ -127,25 +115,25 @@ double check_keff_trigger() { if (settings::run_mode != RUN_MODE_EIGENVALUE) return 0.; - if (settings::keff_trigger.type == 0) return 0.; + if (settings::keff_trigger.metric == TriggerMetric::not_active) return 0.; double k_combined[2]; int err = openmc_get_keff(k_combined); double uncertainty = 0.; - switch (settings::keff_trigger.type) { - case VARIANCE: + switch (settings::keff_trigger.metric) { + case TriggerMetric::variance: uncertainty = k_combined[1] * k_combined[1]; break; - case STANDARD_DEVIATION: + case TriggerMetric::standard_deviation: uncertainty = k_combined[1]; break; - case RELATIVE_ERROR: + case TriggerMetric::relative_error: uncertainty = k_combined[1] / k_combined[0]; } double ratio = uncertainty / settings::keff_trigger.threshold; - if (settings::keff_trigger.type == VARIANCE) + if (settings::keff_trigger.metric == TriggerMetric::variance) ratio = std::sqrt(ratio); return ratio; } From 1ee3224ac1b28ee27039d0030dee40fddf997a1d Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Mon, 4 Feb 2019 22:12:38 -0500 Subject: [PATCH 24/58] Reduce tally code duplication --- src/tallies/tally.cpp | 534 +++++++++++++++--------------------------- 1 file changed, 183 insertions(+), 351 deletions(-) diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 81d84456a4..6f568e6861 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -66,6 +66,129 @@ double global_tally_collision; double global_tally_tracklength; double global_tally_leakage; +//============================================================================== +//! An iterator over all combinations of a tally's matching filter bins. +// +//! This iterator handles two distinct tasks. First, it maps the N-dimensional +//! space created by the indices of N filters onto a 1D sequence. In other +//! words, it provides a single number that uniquely identifies a combination of +//! bins for many filters. Second, it handles the task of finding each all +//! valid combinations of filter bins given that each filter can have 1 or 2 or +//! many bins that are valid for the current tally event. +//============================================================================== + +class FilterBinIter +{ +public: + FilterBinIter(const Tally& tally, Particle* p, bool end) + : tally_{tally} + { + // Handle the special case for an iterator that points to the end. + if (end) { + index_ = -1; + return; + } + + // Find all valid bins in each relevant filter if they have not already been + // found for this event. + for (auto i_filt : tally_.filters()) { + //TODO: off-by-one + auto& match {simulation::filter_matches[i_filt-1]}; + if (!match.bins_present_) { + match.bins_.clear(); + match.weights_.clear(); + //TODO: off-by-one + model::tally_filters[i_filt-1] + ->get_all_bins(p, tally_.estimator_, match); + match.bins_present_ = true; + } + + // If there are no valid bins for this filter, then there are no valid + // filter bin combinations so all iterators are end iterators. + if (match.bins_.size() == 0) { + index_ = -1; + return; + } + + // Set the index of the bin used in the first filter combination + match.i_bin_ = 1; + } + + // Compute the initial index and weight. + compute_index_weight(); + } + + bool + operator==(const FilterBinIter& other) + { + return index_ == other.index_; + } + + bool + operator!=(const FilterBinIter& other) + { + return !(*this == other); + } + + FilterBinIter& + operator++() + { + // Find the next valid combination of filter bins. To do this, we search + // backwards through the filters until we find the first filter whose bins + // can be incremented. + bool done_looping = true; + for (int i = tally_.filters().size()-1; i >= 0; --i) { + auto i_filt = tally_.filters(i); + //TODO: off-by-one + auto& match {simulation::filter_matches[i_filt-1]}; + if (match.i_bin_< match.bins_.size()) { + // The bin for this filter can be incremented. Increment it and do not + // touch any of the remaining filters. + ++match.i_bin_; + done_looping = false; + break; + } else { + // This bin cannot be incremented so reset it and continue to the next + // filter. + match.i_bin_ = 1; + } + } + + if (done_looping) { + // We have visited every valid combination. All done! + index_ = -1; + } else { + // The loop found a new valid combination. Compute the corresponding + // index and weight. + compute_index_weight(); + } + + return *this; + } + + int index_ {1}; + double weight_ {1.}; + +private: + void + compute_index_weight() + { + index_ = 1; + weight_ = 1.; + for (auto i = 0; i < tally_.filters().size(); ++i) { + auto i_filt = tally_.filters(i); + //TODO: off-by-one + auto& match {simulation::filter_matches[i_filt-1]}; + auto i_bin = match.i_bin_; + //TODO: off-by-one + index_ += (match.bins_[i_bin-1] - 1) * tally_.strides(i); + weight_ *= match.weights_[i_bin-1]; + } + } + + const Tally& tally_; +}; + //============================================================================== // Tally object implementation //============================================================================== @@ -230,56 +353,19 @@ score_analog_tally_ce(Particle* p) const Tally& tally {*model::tallies[i_tally-1]}; auto n_score_bins = tally_get_n_score_bins(i_tally); - //-------------------------------------------------------------------------- - // Loop through all relevant filters and find the filter bins and weights - // for this event. + // Initialize an iterator over valid filter bin combinations. If there are + // no valid combinations, use a continue statement to ensure we skip the + // assume_separate break below. + auto filter_iter = FilterBinIter(tally, p, false); + auto end = FilterBinIter(tally, nullptr, true); + if (filter_iter == end) continue; - // Find all valid bins in each filter if they have not already been found - // for a previous tally. - for (auto i_filt : tally.filters()) { - //TODO: off-by-one - auto& match {simulation::filter_matches[i_filt-1]}; - if (!match.bins_present_) { - match.bins_.clear(); - match.weights_.clear(); - //TODO: off-by-one - model::tally_filters[i_filt-1] - ->get_all_bins(p, tally.estimator_, match); - match.bins_present_ = true; - } - - // If there are no valid bins for this filter, then there is nothing to - // score so we can move on to the next tally. - if (match.bins_.size() == 0) goto next_tally; - - // Set the index of the bin used in the first filter combination - match.i_bin_ = 1; - } - - //-------------------------------------------------------------------------- - // Loop over filter bins and nuclide bins - - for (bool filter_loop_done = false; !filter_loop_done; ) { - - //------------------------------------------------------------------------ - // Filter logic - - // Determine scoring index and weight for the current filter combination - int filter_index = 1; - double filter_weight = 1.; - for (auto i = 0; i < tally.filters().size(); ++i) { - auto i_filt = tally.filters(i); - //TODO: off-by-one - auto& match {simulation::filter_matches[i_filt-1]}; - auto i_bin = match.i_bin_; - //TODO: off-by-one - filter_index += (match.bins_[i_bin-1] - 1) * tally.strides(i); - filter_weight *= match.weights_[i_bin-1]; - } - - //------------------------------------------------------------------------ - // Nuclide logic + // Loop over filter bins. + for (; filter_iter != end; ++filter_iter) { + auto filter_index = filter_iter.index_; + auto filter_weight = filter_iter.weight_; + // Loop over nuclide bins. if (!tally.all_nuclides_) { for (auto i = 0; i < tally.nuclides_.size(); ++i) { auto i_nuclide = tally.nuclides_[i]; @@ -292,6 +378,7 @@ score_analog_tally_ce(Particle* p) score_general_ce(p, i_tally, i*n_score_bins, filter_index, -1, -1., filter_weight); } + } else { // In the case that the user has requested to tally all nuclides, we // can take advantage of the fact that we know exactly how nuclide @@ -305,25 +392,6 @@ score_analog_tally_ce(Particle* p) score_general_ce(p, i_tally, i*n_score_bins, filter_index, -1, -1., filter_weight); } - - //------------------------------------------------------------------------ - // Further filter logic - - // Increment the filter bins, starting with the last filter to find the - // next valid bin combination - filter_loop_done = true; - for (int i = tally.filters().size()-1; i >= 0; --i) { - auto i_filt = tally.filters(i); - //TODO: off-by-one - auto& match {simulation::filter_matches[i_filt-1]}; - if (match.i_bin_ < match.bins_.size()) { - ++match.i_bin_; - filter_loop_done = false; - break; - } else { - match.i_bin_ = 1; - } - } } // If the user has specified that we can assume all tallies are spatially @@ -331,9 +399,6 @@ score_analog_tally_ce(Particle* p) // check the others. This cuts down on overhead when there are many // tallies specified if (settings::assume_separate) break; - -next_tally: - ; } // Reset all the filter matches for the next tally event. @@ -353,56 +418,19 @@ score_analog_tally_mg(Particle* p) const Tally& tally {*model::tallies[i_tally-1]}; auto n_score_bins = tally_get_n_score_bins(i_tally); - //-------------------------------------------------------------------------- - // Loop through all relevant filters and find the filter bins and weights - // for this event. + // Initialize an iterator over valid filter bin combinations. If there are + // no valid combinations, use a continue statement to ensure we skip the + // assume_separate break below. + auto filter_iter = FilterBinIter(tally, p, false); + auto end = FilterBinIter(tally, nullptr, true); + if (filter_iter == end) continue; - // Find all valid bins in each filter if they have not already been found - // for a previous tally. - for (auto i_filt : tally.filters()) { - //TODO: off-by-one - auto& match {simulation::filter_matches[i_filt-1]}; - if (!match.bins_present_) { - match.bins_.clear(); - match.weights_.clear(); - //TODO: off-by-one - model::tally_filters[i_filt-1] - ->get_all_bins(p, tally.estimator_, match); - match.bins_present_ = true; - } - - // If there are no valid bins for this filter, then there is nothing to - // score so we can move on to the next tally. - if (match.bins_.size() == 0) goto next_tally; - - // Set the index of the bin used in the first filter combination - match.i_bin_ = 1; - } - - //-------------------------------------------------------------------------- - // Loop over filter bins and nuclide bins - - for (bool filter_loop_done = false; !filter_loop_done; ) { - - //------------------------------------------------------------------------ - // Filter logic - - // Determine scoring index and weight for the current filter combination - int filter_index = 1; - double filter_weight = 1.; - for (auto i = 0; i < tally.filters().size(); ++i) { - auto i_filt = tally.filters(i); - //TODO: off-by-one - auto& match {simulation::filter_matches[i_filt-1]}; - auto i_bin = match.i_bin_; - //TODO: off-by-one - filter_index += (match.bins_[i_bin-1] - 1) * tally.strides(i); - filter_weight *= match.weights_[i_bin-1]; - } - - //------------------------------------------------------------------------ - // Nuclide logic + // Loop over filter bins. + for (; filter_iter != end; ++filter_iter) { + auto filter_index = filter_iter.index_; + auto filter_weight = filter_iter.weight_; + // Loop over nuclide bins. for (auto i = 0; i < tally.nuclides_.size(); ++i) { auto i_nuclide = tally.nuclides_[i]; @@ -416,25 +444,6 @@ score_analog_tally_mg(Particle* p) score_general_mg(p, i_tally, i*n_score_bins, filter_index, i_nuclide, atom_density, filter_weight); } - - //------------------------------------------------------------------------ - // Further filter logic - - // Increment the filter bins, starting with the last filter to find the - // next valid bin combination - filter_loop_done = true; - for (int i = tally.filters().size()-1; i >= 0; --i) { - auto i_filt = tally.filters(i); - //TODO: off-by-one - auto& match {simulation::filter_matches[i_filt-1]}; - if (match.i_bin_ < match.bins_.size()) { - ++match.i_bin_; - filter_loop_done = false; - break; - } else { - match.i_bin_ = 1; - } - } } // If the user has specified that we can assume all tallies are spatially @@ -442,9 +451,6 @@ score_analog_tally_mg(Particle* p) // check the others. This cuts down on overhead when there are many // tallies specified if (settings::assume_separate) break; - -next_tally: - ; } // Reset all the filter matches for the next tally event. @@ -469,59 +475,23 @@ score_tracklength_tally(Particle* p, double distance) const Tally& tally {*model::tallies[i_tally-1]}; auto n_score_bins = tally_get_n_score_bins(i_tally); - //-------------------------------------------------------------------------- - // Loop through all relevant filters and find the filter bins and weights - // for this event. + // Initialize an iterator over valid filter bin combinations. If there are + // no valid combinations, use a continue statement to ensure we skip the + // assume_separate break below. + auto filter_iter = FilterBinIter(tally, p, false); + auto end = FilterBinIter(tally, nullptr, true); + if (filter_iter == end) continue; - // Find all valid bins in each filter if they have not already been found - // for a previous tally. - for (auto i_filt : tally.filters()) { - //TODO: off-by-one - auto& match {simulation::filter_matches[i_filt-1]}; - if (!match.bins_present_) { - match.bins_.clear(); - match.weights_.clear(); - //TODO: off-by-one - model::tally_filters[i_filt-1] - ->get_all_bins(p, tally.estimator_, match); - match.bins_present_ = true; - } - - // If there are no valid bins for this filter, then there is nothing to - // score so we can move on to the next tally. - if (match.bins_.size() == 0) goto next_tally; - - // Set the index of the bin used in the first filter combination - match.i_bin_ = 1; - } - - //-------------------------------------------------------------------------- - // Loop over filter bins and nuclide bins - - for (bool filter_loop_done = false; !filter_loop_done; ) { - - //------------------------------------------------------------------------ - // Filter logic - - // Determine scoring index and weight for the current filter combination - int filter_index = 1; - double filter_weight = 1.; - for (auto i = 0; i < tally.filters().size(); ++i) { - auto i_filt = tally.filters(i); - //TODO: off-by-one - auto& match {simulation::filter_matches[i_filt-1]}; - auto i_bin = match.i_bin_; - //TODO: off-by-one - filter_index += (match.bins_[i_bin-1] - 1) * tally.strides(i); - filter_weight *= match.weights_[i_bin-1]; - } - - //------------------------------------------------------------------------ - // Nuclide logic + // Loop over filter bins. + for (; filter_iter != end; ++filter_iter) { + auto filter_index = filter_iter.index_; + auto filter_weight = filter_iter.weight_; + // Loop over nuclide bins. if (tally.all_nuclides_) { if (p->material != MATERIAL_VOID) score_all_nuclides(p, i_tally, flux*filter_weight, filter_index); + } else { for (auto i = 0; i < tally.nuclides_.size(); ++i) { auto i_nuclide = tally.nuclides_[i]; @@ -546,24 +516,6 @@ score_tracklength_tally(Particle* p, double distance) } } - //------------------------------------------------------------------------ - // Further filter logic - - // Increment the filter bins, starting with the last filter to find the - // next valid bin combination - filter_loop_done = true; - for (int i = tally.filters().size()-1; i >= 0; --i) { - auto i_filt = tally.filters(i); - //TODO: off-by-one - auto& match {simulation::filter_matches[i_filt-1]}; - if (match.i_bin_ < match.bins_.size()) { - ++match.i_bin_; - filter_loop_done = false; - break; - } else { - match.i_bin_ = 1; - } - } } // If the user has specified that we can assume all tallies are spatially @@ -571,9 +523,6 @@ score_tracklength_tally(Particle* p, double distance) // check the others. This cuts down on overhead when there are many // tallies specified if (settings::assume_separate) break; - -next_tally: - ; } // Reset all the filter matches for the next tally event. @@ -605,58 +554,22 @@ score_collision_tally(Particle* p) const Tally& tally {*model::tallies[i_tally-1]}; auto n_score_bins = tally_get_n_score_bins(i_tally); - //-------------------------------------------------------------------------- - // Loop through all relevant filters and find the filter bins and weights - // for this event. + // Initialize an iterator over valid filter bin combinations. If there are + // no valid combinations, use a continue statement to ensure we skip the + // assume_separate break below. + auto filter_iter = FilterBinIter(tally, p, false); + auto end = FilterBinIter(tally, nullptr, true); + if (filter_iter == end) continue; - // Find all valid bins in each filter if they have not already been found - // for a previous tally. - for (auto i_filt : tally.filters()) { - //TODO: off-by-one - auto& match {simulation::filter_matches[i_filt-1]}; - if (!match.bins_present_) { - match.bins_.clear(); - match.weights_.clear(); - //TODO: off-by-one - model::tally_filters[i_filt-1] - ->get_all_bins(p, tally.estimator_, match); - match.bins_present_ = true; - } - - // If there are no valid bins for this filter, then there is nothing to - // score so we can move on to the next tally. - if (match.bins_.size() == 0) goto next_tally; - - // Set the index of the bin used in the first filter combination - match.i_bin_ = 1; - } - - //-------------------------------------------------------------------------- - // Loop over filter bins and nuclide bins - - for (bool filter_loop_done = false; !filter_loop_done; ) { - - //------------------------------------------------------------------------ - // Filter logic - - // Determine scoring index and weight for the current filter combination - int filter_index = 1; - double filter_weight = 1.; - for (auto i = 0; i < tally.filters().size(); ++i) { - auto i_filt = tally.filters(i); - //TODO: off-by-one - auto& match {simulation::filter_matches[i_filt-1]}; - auto i_bin = match.i_bin_; - //TODO: off-by-one - filter_index += (match.bins_[i_bin-1] - 1) * tally.strides(i); - filter_weight *= match.weights_[i_bin-1]; - } - - //------------------------------------------------------------------------ - // Nuclide logic + // Loop over filter bins. + for (; filter_iter != end; ++filter_iter) { + auto filter_index = filter_iter.index_; + auto filter_weight = filter_iter.weight_; + // Loop over nuclide bins. if (tally.all_nuclides_) { score_all_nuclides(p, i_tally, flux*filter_weight, filter_index); + } else { for (auto i = 0; i < tally.nuclides_.size(); ++i) { auto i_nuclide = tally.nuclides_[i]; @@ -678,25 +591,6 @@ score_collision_tally(Particle* p) } } } - - //------------------------------------------------------------------------ - // Further filter logic - - // Increment the filter bins, starting with the last filter to find the - // next valid bin combination - filter_loop_done = true; - for (int i = tally.filters().size()-1; i >= 0; --i) { - auto i_filt = tally.filters(i); - //TODO: off-by-one - auto& match {simulation::filter_matches[i_filt-1]}; - if (match.i_bin_ < match.bins_.size()) { - ++match.i_bin_; - filter_loop_done = false; - break; - } else { - match.i_bin_ = 1; - } - } } // If the user has specified that we can assume all tallies are spatially @@ -704,9 +598,6 @@ score_collision_tally(Particle* p) // check the others. This cuts down on overhead when there are many // tallies specified if (settings::assume_separate) break; - -next_tally: - ; } // Reset all the filter matches for the next tally event. @@ -728,56 +619,19 @@ score_surface_tally_inner(Particle* p, const std::vector& tallies) auto n_score_bins = tally_get_n_score_bins(i_tally); auto results = tally_results(i_tally); - //-------------------------------------------------------------------------- - // Loop through all relevant filters and find the filter bins and weights - // for this event. + // Initialize an iterator over valid filter bin combinations. If there are + // no valid combinations, use a continue statement to ensure we skip the + // assume_separate break below. + auto filter_iter = FilterBinIter(tally, p, false); + auto end = FilterBinIter(tally, nullptr, true); + if (filter_iter == end) continue; - // Find all valid bins in each filter if they have not already been found - // for a previous tally. - for (auto i_filt : tally.filters()) { - //TODO: off-by-one - auto& match {simulation::filter_matches[i_filt-1]}; - if (!match.bins_present_) { - match.bins_.clear(); - match.weights_.clear(); - //TODO: off-by-one - model::tally_filters[i_filt-1] - ->get_all_bins(p, tally.estimator_, match); - match.bins_present_ = true; - } - - // If there are no valid bins for this filter, then there is nothing to - // score so we can move on to the next tally. - if (match.bins_.size() == 0) goto next_tally; - - // Set the index of the bin used in the first filter combination - match.i_bin_ = 1; - } - - //-------------------------------------------------------------------------- - // Loop over filter bins and nuclide bins - - for (bool filter_loop_done = false; !filter_loop_done; ) { - - //------------------------------------------------------------------------ - // Filter logic - - // Determine scoring index and weight for the current filter combination - int filter_index = 1; - double filter_weight = 1.; - for (auto i = 0; i < tally.filters().size(); ++i) { - auto i_filt = tally.filters(i); - //TODO: off-by-one - auto& match {simulation::filter_matches[i_filt-1]}; - auto i_bin = match.i_bin_; - //TODO: off-by-one - filter_index += (match.bins_[i_bin-1] - 1) * tally.strides(i); - filter_weight *= match.weights_[i_bin-1]; - } - - //------------------------------------------------------------------------ - // Scoring loop + // Loop over filter bins. + for (; filter_iter != end; ++filter_iter) { + auto filter_index = filter_iter.index_; + auto filter_weight = filter_iter.weight_; + // Loop over scores. // There is only one score type for current tallies so there is no need // for a further scoring function. double score = flux * filter_weight; @@ -786,25 +640,6 @@ score_surface_tally_inner(Particle* p, const std::vector& tallies) #pragma omp atomic results(filter_index-1, score_index-1, RESULT_VALUE) += score; } - - //------------------------------------------------------------------------ - // Further filter logic - - // Increment the filter bins, starting with the last filter to find the - // next valid bin combination - filter_loop_done = true; - for (int i = tally.filters().size()-1; i >= 0; --i) { - auto i_filt = tally.filters(i); - //TODO: off-by-one - auto& match {simulation::filter_matches[i_filt-1]}; - if (match.i_bin_ < match.bins_.size()) { - ++match.i_bin_; - filter_loop_done = false; - break; - } else { - match.i_bin_ = 1; - } - } } // If the user has specified that we can assume all tallies are spatially @@ -812,9 +647,6 @@ score_surface_tally_inner(Particle* p, const std::vector& tallies) // check the others. This cuts down on overhead when there are many // tallies specified if (settings::assume_separate) break; - -next_tally: - ; } // Reset all the filter matches for the next tally event. @@ -955,8 +787,8 @@ free_memory_tally_c() model::active_analog_tallies.clear(); model::active_tracklength_tallies.clear(); model::active_collision_tallies.clear(); - //model::active_meshsurf_tallies.clear(); - //model::active_surface_tallies.clear(); + model::active_meshsurf_tallies.clear(); + model::active_surface_tallies.clear(); } //============================================================================== From 391c7536d48484107e3af1a765fa3d57c05f8e2c Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Mon, 4 Feb 2019 22:32:00 -0500 Subject: [PATCH 25/58] Move tally id to C++ --- include/openmc/tallies/tally.h | 2 ++ src/input_xml.F90 | 18 +++++++++--------- src/output.F90 | 6 +++--- src/state_point.F90 | 8 ++++---- src/tallies/tally.F90 | 14 +++++++------- src/tallies/tally.cpp | 8 ++++---- src/tallies/tally_header.F90 | 33 ++++++++++++++++++++++++++++++--- 7 files changed, 59 insertions(+), 30 deletions(-) diff --git a/include/openmc/tallies/tally.h b/include/openmc/tallies/tally.h index 93ddcd3904..6c27d9655a 100644 --- a/include/openmc/tallies/tally.h +++ b/include/openmc/tallies/tally.h @@ -42,6 +42,8 @@ public: //---------------------------------------------------------------------------- // Major public data members. + int id_; //!< user-defined identifier + int type_ {TALLY_VOLUME}; //!< volume, surface current //! Event type that contributes to this tally diff --git a/src/input_xml.F90 b/src/input_xml.F90 index a31f174c7e..a9192ba3e4 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -1133,7 +1133,7 @@ contains else call fatal_error("Could not find filter " & // trim(to_str(temp_filter(j))) // " specified on tally " & - // trim(to_str(t % id))) + // trim(to_str(t % id()))) end if ! Store the index of the filter @@ -1230,7 +1230,7 @@ contains if (.not. nuclide_dict % has(word)) then call fatal_error("Could not find the nuclide " & // trim(word) // " specified in tally " & - // trim(to_str(t % id)) // " in any material.") + // trim(to_str(t % id())) // " in any material.") end if ! Set bin to index in nuclides array @@ -1526,7 +1526,7 @@ contains if (t % score_bins(j) == t % score_bins(k)) then call fatal_error("Duplicate score of type '" // trim(& reaction_name(t % score_bins(j))) // "' found in tally " & - // trim(to_str(t % id))) + // trim(to_str(t % id()))) end if end do end do @@ -1573,7 +1573,7 @@ contains end if else call fatal_error("No specified on tally " & - // trim(to_str(t % id)) // ".") + // trim(to_str(t % id())) // ".") end if ! Check for a tally derivative. @@ -1596,7 +1596,7 @@ contains if (j == n_tally_derivs()) then call fatal_error("Could not find derivative " & // trim(to_str(deriv_id)) // " specified on tally " & - // trim(to_str(t % id))) + // trim(to_str(t % id()))) end if end do @@ -1605,7 +1605,7 @@ contains .or. deriv % variable == DIFF_TEMPERATURE) then do j = 1, t % n_nuclide_bins() if (has_energyout .and. t % nuclide_bins(j) == -1) then - call fatal_error("Error on tally " // trim(to_str(t % id)) & + call fatal_error("Error on tally " // trim(to_str(t % id())) & // ": Cannot use a 'nuclide_density' or 'temperature' & &derivative on a tally with an outgoing energy filter and & &'total' nuclide rate. Instead, tally each nuclide in the & @@ -1640,7 +1640,7 @@ contains ! tally needs post-collision information if (t % estimator() == ESTIMATOR_ANALOG) then call fatal_error("Cannot use track-length estimator for tally " & - // to_str(t % id)) + // to_str(t % id())) end if ! Set estimator to track-length estimator @@ -1651,7 +1651,7 @@ contains ! tally needs post-collision information if (t % estimator() == ESTIMATOR_ANALOG) then call fatal_error("Cannot use collision estimator for tally " & - // to_str(t % id)) + // to_str(t % id())) end if ! Set estimator to collision estimator @@ -1659,7 +1659,7 @@ contains case default call fatal_error("Invalid estimator '" // trim(temp_str) & - // "' on tally " // to_str(t % id)) + // "' on tally " // to_str(t % id())) end select end if diff --git a/src/output.F90 b/src/output.F90 index 5895cd59c8..7bbf2094db 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -522,9 +522,9 @@ contains ! Write header block if (t % name == "") then - call header("TALLY " // trim(to_str(t % id)), 1, unit=unit_tally) + call header("TALLY " // trim(to_str(t % id())), 1, unit=unit_tally) else - call header("TALLY " // trim(to_str(t % id)) // ": " & + call header("TALLY " // trim(to_str(t % id())) // ": " & // trim(t % name), 1, unit=unit_tally) endif @@ -546,7 +546,7 @@ contains &A)") to_str(deriv % diff_material) case default call fatal_error("Differential tally dependent variable for tally "& - // trim(to_str(t % id)) // " not defined in output.F90.") + // trim(to_str(t % id())) // " not defined in output.F90.") end select !end associate end if diff --git a/src/state_point.F90 b/src/state_point.F90 index 6de1db7732..fca7954ab0 100644 --- a/src/state_point.F90 +++ b/src/state_point.F90 @@ -238,7 +238,7 @@ contains ! Write array of tally IDs allocate(id_array(n_tallies)) do i = 1, n_tallies - id_array(i) = tallies(i) % obj % id + id_array(i) = tallies(i) % obj % id() end do call write_attribute(tallies_group, "ids", id_array) deallocate(id_array) @@ -249,7 +249,7 @@ contains ! Get pointer to tally associate (tally => tallies(i) % obj) tally_group = create_group(tallies_group, "tally " // & - trim(to_str(tally % id))) + trim(to_str(tally % id()))) ! Write the name for this tally call write_dataset(tally_group, "name", tally % name) @@ -357,7 +357,7 @@ contains associate (tally => tallies(i) % obj) ! Write sum and sum_sq for each bin tally_group = open_group(tallies_group, "tally " & - // to_str(tally % id)) + // to_str(tally % id())) call tally % write_results_hdf5(tally_group) call close_group(tally_group) end associate @@ -560,7 +560,7 @@ contains associate (t => tallies(i) % obj) ! Read sum, sum_sq, and N for each bin tally_group = open_group(tallies_group, "tally " // & - trim(to_str(t % id))) + trim(to_str(t % id()))) call t % read_results_hdf5(tally_group) call read_dataset(t % n_realizations, tally_group, & "n_realizations") diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index 7c037fcebe..05ee7ac92f 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -1212,7 +1212,7 @@ contains else call fatal_error("Invalid score type on tally " & - // to_str(t % id) // ".") + // to_str(t % id()) // ".") end if end if @@ -2367,7 +2367,7 @@ contains case default call fatal_error('Tally derivative not defined for a score on & - &tally ' // trim(to_str(t % id))) + &tally ' // trim(to_str(t % id()))) end select case (ESTIMATOR_COLLISION) @@ -2388,7 +2388,7 @@ contains case default call fatal_error('Tally derivative not defined for a score on & - &tally ' // trim(to_str(t % id))) + &tally ' // trim(to_str(t % id()))) end select case default @@ -2438,7 +2438,7 @@ contains case default call fatal_error('Tally derivative not defined for a score on & - &tally ' // trim(to_str(t % id))) + &tally ' // trim(to_str(t % id()))) end select case (ESTIMATOR_COLLISION) @@ -2525,7 +2525,7 @@ contains case default call fatal_error('Tally derivative not defined for a score on & - &tally ' // trim(to_str(t % id))) + &tally ' // trim(to_str(t % id()))) end select case default @@ -2678,7 +2678,7 @@ contains case default call fatal_error('Tally derivative not defined for a score on & - &tally ' // trim(to_str(t % id))) + &tally ' // trim(to_str(t % id()))) end select case (ESTIMATOR_COLLISION) @@ -2867,7 +2867,7 @@ contains case default call fatal_error('Tally derivative not defined for a score on & - &tally ' // trim(to_str(t % id))) + &tally ' // trim(to_str(t % id()))) end select case default diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 6f568e6861..2e8bccc3cb 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -236,10 +236,6 @@ Tally::set_filters(const int32_t filter_indices[], int n) void Tally::init_triggers(pugi::xml_node node, int i_tally) { - //TODO: use id_ attribute when it's available in C++ - int id_; - auto err = openmc_tally_get_id(i_tally, &id_); - for (auto trigger_node: node.children("trigger")) { // Read the trigger type. TriggerMetric metric; @@ -943,6 +939,10 @@ extern "C" { int active_surface_tallies_size() {return model::active_surface_tallies.size();} + int tally_get_id_c(Tally* tally) {return tally->id_;} + + void tally_set_id_c(Tally* tally, int id) {tally->id_ = id;} + int tally_get_type_c(Tally* tally) {return tally->type_;} void tally_set_type_c(Tally* tally, int type) {tally->type_ = type;} diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index 8a5e946642..275aa0df32 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -95,7 +95,6 @@ module tally_header ! Basic data - integer :: id ! user-defined identifier character(len=104) :: name = "" ! user-defined name real(8) :: volume ! volume of region logical :: depletion_rx = .false. ! has depletion reactions, e.g. (n,2n) @@ -120,6 +119,8 @@ module tally_header procedure :: allocate_results => tally_allocate_results procedure :: read_results_hdf5 => tally_read_results_hdf5 procedure :: write_results_hdf5 => tally_write_results_hdf5 + procedure :: id => tally_get_id + procedure :: set_id => tally_set_id procedure :: type => tally_get_type procedure :: set_type => tally_set_type procedure :: estimator => tally_get_estimator @@ -300,6 +301,32 @@ contains end subroutine tally_allocate_results + function tally_get_id(this) result(t) + class(TallyObject) :: this + integer(C_INT) :: t + interface + function tally_get_id_c(tally) result(t) bind(C) + import C_PTR, C_INT + type(C_PTR), value :: tally + integer(C_INT) :: t + end function + end interface + t = tally_get_id_c(this % ptr) + end function + + subroutine tally_set_id(this, t) + class(TallyObject) :: this + integer(C_INT) :: t + interface + subroutine tally_set_id_c(tally, t) bind(C) + import C_PTR, C_INT + type(C_PTR), value :: tally + integer(C_INT), value :: t + end subroutine + end interface + call tally_set_id_c(this % ptr, t) + end subroutine + function tally_get_type(this) result(t) class(TallyObject) :: this integer(C_INT) :: t @@ -669,7 +696,7 @@ contains integer(C_INT) :: err if (index >= 1 .and. index <= size(tallies)) then - id = tallies(index) % obj % id + id = tallies(index) % obj % id() err = 0 else err = E_OUT_OF_BOUNDS @@ -812,7 +839,7 @@ contains // to_str(id)) err = E_INVALID_ID else - tallies(index) % obj % id = id + call tallies(index) % obj % set_id(id) call tally_dict % set(id, index) if (id > largest_tally_id) largest_tally_id = id From ed628b96d2e91743ee614af0fdf1c0cc0eabf0c0 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Tue, 5 Feb 2019 01:54:12 -0500 Subject: [PATCH 26/58] Move tally scores to C++ --- include/openmc/tallies/tally.h | 10 + src/input_xml.F90 | 281 +------------------------ src/output.F90 | 2 +- src/state_point.F90 | 6 +- src/tallies/tally.F90 | 16 +- src/tallies/tally.cpp | 374 +++++++++++++++++++++++++++++++-- src/tallies/tally_header.F90 | 250 +++++----------------- src/tallies/trigger.cpp | 7 +- 8 files changed, 432 insertions(+), 514 deletions(-) diff --git a/include/openmc/tallies/tally.h b/include/openmc/tallies/tally.h index 6c27d9655a..b9b97f64e2 100644 --- a/include/openmc/tallies/tally.h +++ b/include/openmc/tallies/tally.h @@ -21,6 +21,10 @@ class Tally { public: Tally() {} + void set_scores(pugi::xml_node node); + + void set_scores(std::vector scores); + //---------------------------------------------------------------------------- // Methods for getting and setting filter/stride data. @@ -37,6 +41,8 @@ public: //---------------------------------------------------------------------------- // Other methods. + void init_scores(pugi::xml_node node); + void init_triggers(pugi::xml_node node, int i_tally); //---------------------------------------------------------------------------- @@ -52,6 +58,8 @@ public: //! Whether this tally is currently being updated bool active_ {false}; + std::vector scores_; //!< Filter integrands (e.g. flux, fission) + //! Index of each nuclide to be tallied. -1 indicates total material. std::vector nuclides_; @@ -67,6 +75,8 @@ public: int energyout_filter_ {C_NONE}; int delayedgroup_filter_ {C_NONE}; + bool depletion_rx_ {false}; //!< Has depletion reactions (e.g. (n,2n)) + std::vector triggers_; int deriv_ {C_NONE}; //!< Index of a TallyDerivative object for diff tallies. diff --git a/src/input_xml.F90 b/src/input_xml.F90 index a9192ba3e4..e1aa1b5033 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -943,6 +943,12 @@ contains logical :: all_nuclides interface + subroutine tally_init_scores(tally_ptr, xml_node) bind(C) + import C_PTR + type(C_PTR), value :: tally_ptr + type(C_PTR) :: xml_node + end subroutine + subroutine tally_init_triggers(tally_ptr, i_tally, xml_node) bind(C) import C_PTR, C_INT type(C_PTR), value :: tally_ptr @@ -1255,281 +1261,10 @@ contains ! ======================================================================= ! READ DATA FOR SCORES + call tally_init_scores(t % ptr, node_tal % ptr) + if (check_for_node(node_tal, "scores")) then n_words = node_word_count(node_tal, "scores") - allocate(sarray(n_words)) - call get_node_array(node_tal, "scores", sarray) - n_scores = n_words - - ! Allocate score storage accordingly - allocate(t % score_bins(n_scores)) - - ! Check the validity of the scores and their filters - do j = 1, n_scores - score_name = sarray(j) - - ! Check if delayed group filter is used with any score besides - ! delayed-nu-fission or decay-rate - if ((score_name /= 'delayed-nu-fission' .and. & - score_name /= 'decay-rate') .and. has_delayedgroup) then - call fatal_error("Cannot tally " // trim(score_name) // " with a & - &delayedgroup filter.") - end if - - select case (trim(score_name)) - case ('flux') - ! Prohibit user from tallying flux for an individual nuclide - if (.not. (t % n_nuclide_bins() == 1 .and. & - t % nuclide_bins(1) == -1)) then - call fatal_error("Cannot tally flux for an individual nuclide.") - end if - - t % score_bins(j) = SCORE_FLUX - if (has_energyout) then - call fatal_error("Cannot tally flux with an outgoing energy & - &filter.") - end if - - case ('total', '(n,total)') - t % score_bins(j) = SCORE_TOTAL - if (has_energyout) then - call fatal_error("Cannot tally total reaction rate with an & - &outgoing energy filter.") - end if - - case ('scatter') - t % score_bins(j) = SCORE_SCATTER - if (has_energyout .or. has_legendre) then - ! Set tally estimator to analog - call t % set_estimator(ESTIMATOR_ANALOG) - end if - - case ('nu-scatter') - t % score_bins(j) = SCORE_NU_SCATTER - - ! Set tally estimator to analog for CE mode - ! (MG mode has all data available without a collision being - ! necessary) - if (run_CE) then - call t % set_estimator(ESTIMATOR_ANALOG) - else - if (has_energyout .or. has_legendre) then - ! Set tally estimator to analog - call t % set_estimator(ESTIMATOR_ANALOG) - end if - end if - - case ('n2n', '(n,2n)') - t % score_bins(j) = N_2N - t % depletion_rx = .true. - - case ('n3n', '(n,3n)') - t % score_bins(j) = N_3N - t % depletion_rx = .true. - - case ('n4n', '(n,4n)') - t % score_bins(j) = N_4N - t % depletion_rx = .true. - - case ('absorption') - t % score_bins(j) = SCORE_ABSORPTION - if (has_energyout) then - call fatal_error("Cannot tally absorption rate with an outgoing & - &energy filter.") - end if - case ('fission', '18') - t % score_bins(j) = SCORE_FISSION - if (has_energyout) then - call fatal_error("Cannot tally fission rate with an outgoing & - &energy filter.") - end if - case ('nu-fission') - t % score_bins(j) = SCORE_NU_FISSION - if (has_energyout) then - ! Set tally estimator to analog - call t % set_estimator(ESTIMATOR_ANALOG) - end if - case ('decay-rate') - t % score_bins(j) = SCORE_DECAY_RATE - case ('delayed-nu-fission') - t % score_bins(j) = SCORE_DELAYED_NU_FISSION - if (has_energyout) then - ! Set tally estimator to analog - call t % set_estimator(ESTIMATOR_ANALOG) - end if - case ('prompt-nu-fission') - t % score_bins(j) = SCORE_PROMPT_NU_FISSION - if (has_energyout) then - ! Set tally estimator to analog - call t % set_estimator(ESTIMATOR_ANALOG) - end if - case ('kappa-fission') - t % score_bins(j) = SCORE_KAPPA_FISSION - case ('inverse-velocity') - t % score_bins(j) = SCORE_INVERSE_VELOCITY - case ('fission-q-prompt') - t % score_bins(j) = SCORE_FISS_Q_PROMPT - case ('fission-q-recoverable') - t % score_bins(j) = SCORE_FISS_Q_RECOV - case ('current') - - ! Check which type of current is desired: mesh currents or - ! surface currents - if (has_surface .or. has_cell .or. has_cellfrom) then - - ! Check to make sure that mesh surface currents are not desired as well - if (has_meshsurface) then - call fatal_error("Cannot tally mesh surface currents & - &in the same tally as normal surface currents") - end if - - call t % set_type(TALLY_SURFACE) - t % score_bins(j) = SCORE_CURRENT - - else if (has_meshsurface) then - t % score_bins(j) = SCORE_CURRENT - call t % set_type(TALLY_MESH_SURFACE) - - ! Check to make sure that current is the only desired response - ! for this tally - if (n_words > 1) then - call fatal_error("Cannot tally other scores in the & - &same tally as surface currents") - end if - else - call fatal_error("Cannot tally currents without surface & - &type filters") - end if - - case ('events') - t % score_bins(j) = SCORE_EVENTS - case ('elastic', '(n,elastic)') - t % score_bins(j) = ELASTIC - case ('(n,2nd)') - t % score_bins(j) = N_2ND - case ('(n,na)') - t % score_bins(j) = N_2NA - case ('(n,n3a)') - t % score_bins(j) = N_N3A - case ('(n,2na)') - t % score_bins(j) = N_2NA - case ('(n,3na)') - t % score_bins(j) = N_3NA - case ('(n,np)') - t % score_bins(j) = N_NP - case ('(n,n2a)') - t % score_bins(j) = N_N2A - case ('(n,2n2a)') - t % score_bins(j) = N_2N2A - case ('(n,nd)') - t % score_bins(j) = N_ND - case ('(n,nt)') - t % score_bins(j) = N_NT - case ('(n,nHe-3)') - t % score_bins(j) = N_N3HE - case ('(n,nd2a)') - t % score_bins(j) = N_ND2A - case ('(n,nt2a)') - t % score_bins(j) = N_NT2A - case ('(n,3nf)') - t % score_bins(j) = N_3NF - case ('(n,2np)') - t % score_bins(j) = N_2NP - case ('(n,3np)') - t % score_bins(j) = N_3NP - case ('(n,n2p)') - t % score_bins(j) = N_N2P - case ('(n,npa)') - t % score_bins(j) = N_NPA - case ('(n,n1)') - t % score_bins(j) = N_N1 - case ('(n,nc)') - t % score_bins(j) = N_NC - case ('(n,gamma)') - t % score_bins(j) = N_GAMMA - t % depletion_rx = .true. - case ('(n,p)') - t % score_bins(j) = N_P - t % depletion_rx = .true. - case ('(n,d)') - t % score_bins(j) = N_D - case ('(n,t)') - t % score_bins(j) = N_T - case ('(n,3He)') - t % score_bins(j) = N_3HE - case ('(n,a)') - t % score_bins(j) = N_A - t % depletion_rx = .true. - case ('(n,2a)') - t % score_bins(j) = N_2A - case ('(n,3a)') - t % score_bins(j) = N_3A - case ('(n,2p)') - t % score_bins(j) = N_2P - case ('(n,pa)') - t % score_bins(j) = N_PA - case ('(n,t2a)') - t % score_bins(j) = N_T2A - case ('(n,d2a)') - t % score_bins(j) = N_D2A - case ('(n,pd)') - t % score_bins(j) = N_PD - case ('(n,pt)') - t % score_bins(j) = N_PT - case ('(n,da)') - t % score_bins(j) = N_DA - - case default - ! First look for deprecated scores - if (starts_with(trim(score_name), 'scatter-') .or. & - starts_with(trim(score_name), 'nu-scatter-') .or. & - starts_with(trim(score_name), 'total-y') .or. & - starts_with(trim(score_name), 'flux-y')) then - call fatal_error(trim(score_name) // " is no longer available.") - end if - - ! Assume that user has specified an MT number - MT = int(str_to_int(score_name)) - - if (MT /= ERROR_INT) then - ! Specified score was an integer - if (MT > 1) then - t % score_bins(j) = MT - else - call fatal_error("Invalid MT on : " // trim(score_name)) - end if - - else - ! Specified score was not an integer - call fatal_error("Unknown scoring function: " // trim(score_name)) - end if - - end select - - ! Do a check at the end (instead of for every case) to make sure - ! the tallies are compatible with MG mode where we have less detailed - ! nuclear data - if (.not. run_CE .and. t % score_bins(j) > 0) then - call fatal_error("Cannot tally " // trim(score_name) // & - " reaction rate in multi-group mode") - end if - end do - - t % n_score_bins = n_scores - - ! Deallocate temporary string array of scores - deallocate(sarray) - - ! Check that no duplicate scores exist - do j = 1, n_scores - 1 - do k = j + 1, n_scores - if (t % score_bins(j) == t % score_bins(k)) then - call fatal_error("Duplicate score of type '" // trim(& - reaction_name(t % score_bins(j))) // "' found in tally " & - // trim(to_str(t % id()))) - end if - end do - end do ! Check if tally is compatible with particle type if (photon_transport) then diff --git a/src/output.F90 b/src/output.F90 index 7bbf2094db..0a3781599e 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -641,7 +641,7 @@ contains end if indent = indent + 2 - do k = 1, t % n_score_bins + do k = 1, t % n_score_bins() score_index = score_index + 1 associate(r => t % results(RESULT_SUM:RESULT_SUM_SQ, :, :)) diff --git a/src/state_point.F90 b/src/state_point.F90 index fca7954ab0..72c2e090b6 100644 --- a/src/state_point.F90 +++ b/src/state_point.F90 @@ -312,9 +312,9 @@ contains end if ! Write scores. - call write_dataset(tally_group, "n_score_bins", tally % n_score_bins) - allocate(str_array(size(tally % score_bins))) - do j = 1, size(tally % score_bins) + call write_dataset(tally_group, "n_score_bins", tally % n_score_bins()) + allocate(str_array(tally % n_score_bins())) + do j = 1, tally % n_score_bins() str_array(j) = reaction_name(tally % score_bins(j)) end do call write_dataset(tally_group, "score_bins", str_array) diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index 05ee7ac92f..9d45eae87f 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -142,7 +142,7 @@ contains ! Pre-collision energy of particle E = p % last_E - SCORE_LOOP: do i = 1, t % n_score_bins + SCORE_LOOP: do i = 1, t % n_score_bins() ! determine what type of score bin score_bin = t % score_bins(i) @@ -1308,7 +1308,7 @@ contains end if i = 0 - SCORE_LOOP: do q = 1, t % n_score_bins + SCORE_LOOP: do q = 1, t % n_score_bins() i = i + 1 ! determine what type of score bin @@ -2064,7 +2064,7 @@ contains atom_density = mat % atom_density(i) ! Determine score for each bin - call score_general(p, i_tally, (i_nuclide-1)*t % n_score_bins, filter_index, & + call score_general(p, i_tally, (i_nuclide-1)*t % n_score_bins(), filter_index, & i_nuclide, atom_density, flux) end do NUCLIDE_LOOP @@ -2076,7 +2076,7 @@ contains atom_density = ZERO ! Determine score for each bin - call score_general(p, i_tally, n_nuclides*t % n_score_bins, filter_index, & + call score_general(p, i_tally, n_nuclides*t % n_score_bins(), filter_index, & i_nuclide, atom_density, flux) end associate @@ -3123,7 +3123,7 @@ contains err = openmc_tally_get_active(i, active) if (active) then ! Check if tally contains depletion reactions and if so, set flag - if (t % depletion_rx) need_depletion_rx = .true. + if (t % depletion_rx()) need_depletion_rx = .true. end if end associate end do @@ -3196,10 +3196,4 @@ contains dens = materials(i_material) % atom_density(i) end function - function tally_get_n_score_bins(i_tally) result(n) bind(C) - integer(C_INT), value :: i_tally - integer(C_INT) :: n - n = tallies(i_tally) % obj % n_score_bins - end function - end module tally diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 2e8bccc3cb..1199f6ba62 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -8,10 +8,14 @@ #include "openmc/settings.h" #include "openmc/tallies/derivative.h" #include "openmc/tallies/filter.h" -#include "openmc/tallies/filter_energy.h" +#include "openmc/tallies/filter_cell.h" +#include "openmc/tallies/filter_cellfrom.h" #include "openmc/tallies/filter_delayedgroup.h" -#include "openmc/tallies/filter_surface.h" +#include "openmc/tallies/filter_energy.h" +#include "openmc/tallies/filter_legendre.h" #include "openmc/tallies/filter_mesh.h" +#include "openmc/tallies/filter_meshsurface.h" +#include "openmc/tallies/filter_surface.h" #include "openmc/xml_interface.h" #include "xtensor/xadapt.hpp" @@ -44,8 +48,6 @@ extern "C" int material_nuclide_index(int i_material, int i_nuclide); extern "C" double material_atom_density(int i_material, int i); -extern "C" int tally_get_n_score_bins(int i_tally); - //============================================================================== // Global variable definitions //============================================================================== @@ -189,6 +191,162 @@ private: const Tally& tally_; }; +int +score_str_to_int(std::string score_str) +{ + if (score_str == "flux") + return SCORE_FLUX; + + if (score_str == "total" || score_str == "(n,total)") + return SCORE_TOTAL; + + if (score_str == "scatter") + return SCORE_SCATTER; + + if (score_str == "nu-scatter") + return SCORE_NU_SCATTER; + + if (score_str == "absorption") + return SCORE_ABSORPTION; + + if (score_str == "fission" || score_str == "18") + return SCORE_FISSION; + + if (score_str == "nu-fission") + return SCORE_NU_FISSION; + + if (score_str == "decay-rate") + return SCORE_DECAY_RATE; + + if (score_str == "delayed-nu-fission") + return SCORE_DELAYED_NU_FISSION; + + if (score_str == "prompt-nu-fission") + return SCORE_PROMPT_NU_FISSION; + + if (score_str == "kappa-fission") + return SCORE_KAPPA_FISSION; + + if (score_str == "inverse-velocity") + return SCORE_INVERSE_VELOCITY; + + if (score_str == "fission-q-prompt") + return SCORE_FISS_Q_PROMPT; + + if (score_str == "fission-q-recoverable") + return SCORE_FISS_Q_RECOV; + + if (score_str == "current") + return SCORE_CURRENT; + + if (score_str == "events") + return SCORE_EVENTS; + + if (score_str == "elastic" || score_str == "(n,elastic)") + return ELASTIC; + + if (score_str == "n2n" || score_str == "(n,2n)") + return N_2N; + + if (score_str == "n3n" || score_str == "(n,3n)") + return N_3N; + + if (score_str == "n4n" || score_str == "(n,4n)") + return N_4N; + + if (score_str == "(n,2nd)") + return N_2ND; + if (score_str == "(n,na)") + return N_2NA; + if (score_str == "(n,n3a)") + return N_N3A; + if (score_str == "(n,2na)") + return N_2NA; + if (score_str == "(n,3na)") + return N_3NA; + if (score_str == "(n,np)") + return N_NP; + if (score_str == "(n,n2a)") + return N_N2A; + if (score_str == "(n,2n2a)") + return N_2N2A; + if (score_str == "(n,nd)") + return N_ND; + if (score_str == "(n,nt)") + return N_NT; + if (score_str == "(n,nHe-3)") + return N_N3HE; + if (score_str == "(n,nd2a)") + return N_ND2A; + if (score_str == "(n,nt2a)") + return N_NT2A; + if (score_str == "(n,3nf)") + return N_3NF; + if (score_str == "(n,2np)") + return N_2NP; + if (score_str == "(n,3np)") + return N_3NP; + if (score_str == "(n,n2p)") + return N_N2P; + if (score_str == "(n,npa)") + return N_NPA; + if (score_str == "(n,n1)") + return N_N1; + if (score_str == "(n,nc)") + return N_NC; + if (score_str == "(n,gamma)") + return N_GAMMA; + if (score_str == "(n,p)") + return N_P; + if (score_str == "(n,d)") + return N_D; + if (score_str == "(n,t)") + return N_T; + if (score_str == "(n,3He)") + return N_3HE; + if (score_str == "(n,a)") + return N_A; + if (score_str == "(n,2a)") + return N_2A; + if (score_str == "(n,3a)") + return N_3A; + if (score_str == "(n,2p)") + return N_2P; + if (score_str == "(n,pa)") + return N_PA; + if (score_str == "(n,t2a)") + return N_T2A; + if (score_str == "(n,d2a)") + return N_D2A; + if (score_str == "(n,pd)") + return N_PD; + if (score_str == "(n,pt)") + return N_PT; + if (score_str == "(n,da)") + return N_DA; + + // So far we have not identified this score string. Check to see if it is a + // deprecated score. + if (score_str.rfind("scatter-", 0) == 0 + || score_str.rfind("nu-scatter-", 0) == 0 + || score_str.rfind("total-y", 0) == 0 + || score_str.rfind("flux-y", 0) == 0) + fatal_error(score_str + " is no longer an available score"); + + + // Assume the given string is a reaction MT number. Make sure it's a natural + // number then return. + int MT; + try { + MT = std::stoi(score_str); + } catch (const std::invalid_argument& ex) { + throw std::invalid_argument("Invalid tally score \"" + score_str + "\""); + } + if (MT < 1) + throw std::invalid_argument("Invalid tally score \"" + score_str + "\""); + return MT; +} + //============================================================================== // Tally object implementation //============================================================================== @@ -233,6 +391,147 @@ Tally::set_filters(const int32_t filter_indices[], int n) n_filter_bins_ = stride; } +void +Tally::set_scores(pugi::xml_node node) +{ + if (!check_for_node(node, "scores")) + fatal_error("No scores specified on tally " + std::to_string(id_)); + + auto scores = get_node_array(node, "scores"); + set_scores(scores); +} + +void +Tally::set_scores(std::vector scores) +{ + // Reset state and prepare for the new scores. + scores_.clear(); + depletion_rx_ = false; + scores_.reserve(scores.size()); + + // Check for the presence of certain restrictive filters. + bool energyout_present = energyout_filter_ != C_NONE; + //bool delayedgroup_present = false; + bool legendre_present = false; + bool cell_present = false; + bool cellfrom_present = false; + bool surface_present = false; + bool meshsurface_present = false; + for (auto i_filt : filters_) { + //TODO: off-by-one + const auto* filt {model::tally_filters[i_filt-1].get()}; + if (dynamic_cast(filt)) { + legendre_present = true; + } else if (dynamic_cast(filt)) { + cellfrom_present = true; + } else if (dynamic_cast(filt)) { + cell_present = true; + } else if (dynamic_cast(filt)) { + surface_present = true; + } else if (dynamic_cast(filt)) { + meshsurface_present = true; + } + } + + // Iterate over the given scores. + for (auto score_str : scores) { + // Make sure a delayed group filter wasn't used with an incompatible score. + bool has_delayedgroup = delayedgroup_filter_ != C_NONE; + if (delayedgroup_filter_ != C_NONE) { + if (score_str != "delayed-nu-fission" && score_str != "decay-rate") + fatal_error("Cannot tally " + score_str + "with a delayedgroup filter"); + } + + auto score = score_str_to_int(score_str); + + switch (score) { + case SCORE_FLUX: + if (!nuclides_.empty()) + if (!(nuclides_.size() == 1 && nuclides_[0] == -1)) + fatal_error("Cannot tally flux for an individual nuclide."); + if (energyout_present) + fatal_error("Cannot tally flux with an outgoing energy filter."); + break; + + case SCORE_TOTAL: + case SCORE_ABSORPTION: + case SCORE_FISSION: + if (energyout_present) + fatal_error("Cannot tally " + score_str + " reaction rate with an " + "outgoing energy filter"); + break; + + case SCORE_SCATTER: + if (legendre_present) + estimator_ = ESTIMATOR_ANALOG; + case SCORE_NU_FISSION: + case SCORE_DELAYED_NU_FISSION: + case SCORE_PROMPT_NU_FISSION: + if (energyout_present) + estimator_ = ESTIMATOR_ANALOG; + break; + + case SCORE_NU_SCATTER: + if (settings::run_CE) { + estimator_ = ESTIMATOR_ANALOG; + } else { + if (energyout_present || legendre_present) + estimator_ = ESTIMATOR_ANALOG; + } + break; + + case N_2N: + case N_3N: + case N_4N: + case N_GAMMA: + case N_P: + case N_A: + depletion_rx_ = true; + break; + + case SCORE_CURRENT: + // Check which type of current is desired: mesh or surface currents. + if (surface_present || cell_present || cellfrom_present) { + if (meshsurface_present) + fatal_error("Cannot tally mesh surface currents in the same tally as " + "normal surface currents"); + type_ = TALLY_SURFACE; + } else if (meshsurface_present) { + type_ = TALLY_MESH_SURFACE; + } else { + fatal_error("Cannot tally currents without surface type filters"); + } + break; + } + + scores_.push_back(score); + } + + // Make sure that no duplicate scores exist. + for (auto it1 = scores_.begin(); it1 != scores_.end(); ++it1) { + for (auto it2 = it1 + 1; it2 != scores_.end(); ++it2) { + if (*it1 == *it2) + fatal_error("Duplicate score of type \"" + reaction_name(*it1) + + "\" found in tally " + std::to_string(id_)); + } + } + + // Make sure all scores are compatible with multigroup mode. + if (!settings::run_CE) { + for (auto sc : scores_) + if (sc > 0) + fatal_error("Cannot tally " + reaction_name(sc) + " reaction rate " + "in multi-group mode"); + } + + // Make sure current scores are not mixed in with volumetric scores. + if (type_ == TALLY_SURFACE || type_ == TALLY_MESH_SURFACE) { + if (scores_.size() != 1) + fatal_error("Cannot tally other scores in the same tally as surface " + "currents"); + } +} + void Tally::init_triggers(pugi::xml_node node, int i_tally) { @@ -347,7 +646,6 @@ score_analog_tally_ce(Particle* p) for (auto i_tally : model::active_analog_tallies) { //TODO: off-by-one const Tally& tally {*model::tallies[i_tally-1]}; - auto n_score_bins = tally_get_n_score_bins(i_tally); // Initialize an iterator over valid filter bin combinations. If there are // no valid combinations, use a continue statement to ensure we skip the @@ -371,7 +669,7 @@ score_analog_tally_ce(Particle* p) // and flux arguments for score_general are not used for analog // tallies. if (i_nuclide == p->event_nuclide || i_nuclide == -1) - score_general_ce(p, i_tally, i*n_score_bins, filter_index, + score_general_ce(p, i_tally, i*tally.scores_.size(), filter_index, -1, -1., filter_weight); } @@ -380,12 +678,12 @@ score_analog_tally_ce(Particle* p) // can take advantage of the fact that we know exactly how nuclide // bins correspond to nuclide indices. First, tally the nuclide. auto i = p->event_nuclide; - score_general_ce(p, i_tally, i*n_score_bins, filter_index, + score_general_ce(p, i_tally, i*tally.scores_.size(), filter_index, -1, -1., filter_weight); // Now tally the total material. i = tally.nuclides_.size(); - score_general_ce(p, i_tally, i*n_score_bins, filter_index, + score_general_ce(p, i_tally, i*tally.scores_.size(), filter_index, -1, -1., filter_weight); } } @@ -412,7 +710,6 @@ score_analog_tally_mg(Particle* p) for (auto i_tally : model::active_analog_tallies) { //TODO: off-by-one const Tally& tally {*model::tallies[i_tally-1]}; - auto n_score_bins = tally_get_n_score_bins(i_tally); // Initialize an iterator over valid filter bin combinations. If there are // no valid combinations, use a continue statement to ensure we skip the @@ -437,7 +734,7 @@ score_analog_tally_mg(Particle* p) atom_density = material_atom_density(p->material, j); } - score_general_mg(p, i_tally, i*n_score_bins, filter_index, + score_general_mg(p, i_tally, i*tally.scores_.size(), filter_index, i_nuclide, atom_density, filter_weight); } } @@ -469,7 +766,6 @@ score_tracklength_tally(Particle* p, double distance) for (auto i_tally : model::active_tracklength_tallies) { //TODO: off-by-one const Tally& tally {*model::tallies[i_tally-1]}; - auto n_score_bins = tally_get_n_score_bins(i_tally); // Initialize an iterator over valid filter bin combinations. If there are // no valid combinations, use a continue statement to ensure we skip the @@ -503,10 +799,10 @@ score_tracklength_tally(Particle* p, double distance) //TODO: consider replacing this "if" with pointers or templates if (settings::run_CE) { - score_general_ce(p, i_tally, i*n_score_bins, filter_index, + score_general_ce(p, i_tally, i*tally.scores_.size(), filter_index, i_nuclide, atom_density, flux*filter_weight); } else { - score_general_mg(p, i_tally, i*n_score_bins, filter_index, + score_general_mg(p, i_tally, i*tally.scores_.size(), filter_index, i_nuclide, atom_density, flux*filter_weight); } } @@ -548,7 +844,6 @@ score_collision_tally(Particle* p) for (auto i_tally : model::active_collision_tallies) { //TODO: off-by-one const Tally& tally {*model::tallies[i_tally-1]}; - auto n_score_bins = tally_get_n_score_bins(i_tally); // Initialize an iterator over valid filter bin combinations. If there are // no valid combinations, use a continue statement to ensure we skip the @@ -579,10 +874,10 @@ score_collision_tally(Particle* p) //TODO: consider replacing this "if" with pointers or templates if (settings::run_CE) { - score_general_ce(p, i_tally, i*n_score_bins, filter_index, + score_general_ce(p, i_tally, i*tally.scores_.size(), filter_index, i_nuclide, atom_density, flux*filter_weight); } else { - score_general_mg(p, i_tally, i*n_score_bins, filter_index, + score_general_mg(p, i_tally, i*tally.scores_.size(), filter_index, i_nuclide, atom_density, flux*filter_weight); } } @@ -612,7 +907,6 @@ score_surface_tally_inner(Particle* p, const std::vector& tallies) for (auto i_tally : tallies) { //TODO: off-by-one const Tally& tally {*model::tallies[i_tally-1]}; - auto n_score_bins = tally_get_n_score_bins(i_tally); auto results = tally_results(i_tally); // Initialize an iterator over valid filter bin combinations. If there are @@ -631,7 +925,8 @@ score_surface_tally_inner(Particle* p, const std::vector& tallies) // There is only one score type for current tallies so there is no need // for a further scoring function. double score = flux * filter_weight; - for (auto score_index = 1; score_index < n_score_bins+1; ++score_index) { + for (auto score_index = 1; score_index < tally.scores_.size()+1; + ++score_index) { //TODO: off-by-one #pragma omp atomic results(filter_index-1, score_index-1, RESULT_VALUE) += score; @@ -853,6 +1148,40 @@ openmc_tally_set_active(int32_t index, bool active) return 0; } +extern "C" int +openmc_tally_get_scores(int32_t index, int** scores, int* n) +{ + if (index < 1 || index > model::tallies.size()) { + set_errmsg("Index in tallies array is out of bounds."); + return OPENMC_E_OUT_OF_BOUNDS; + } + + //TODO: off-by-one + *scores = model::tallies[index-1]->scores_.data(); + *n = model::tallies[index-1]->scores_.size(); + return 0; +} + +extern "C" int +openmc_tally_set_scores(int32_t index, int n, const char** scores) +{ + if (index < 1 || index > model::tallies.size()) { + set_errmsg("Index in tallies array is out of bounds."); + return OPENMC_E_OUT_OF_BOUNDS; + } + + std::vector scores_str(scores, scores+n); + try { + //TODO: off-by-one + model::tallies[index-1]->set_scores(scores_str); + } catch (const std::invalid_argument& ex) { + set_errmsg(ex.what()); + return OPENMC_E_INVALID_ARGUMENT; + } + + return 0; +} + extern "C" int openmc_tally_get_filters(int32_t index, const int32_t** indices, int* n) { @@ -951,6 +1280,12 @@ extern "C" { void tally_set_estimator_c(Tally* tally, int e) {tally->estimator_ = e;} + bool tally_get_depletion_rx_c(Tally* tally) {return tally->depletion_rx_;} + + int tally_get_n_scores_c(Tally* tally) {return tally->scores_.size();} + + int tally_get_score_c(Tally* tally, int i) {return tally->scores_[i];} + void tally_set_filters_c(Tally* tally, int n, int32_t filter_indices[]) {tally->set_filters(filter_indices, n);} @@ -985,6 +1320,9 @@ extern "C" { int tally_get_delayedgroup_filter_c(Tally* tally) {return tally->delayedgroup_filter_;} + void tally_init_scores(Tally* tally, pugi::xml_node* node) + {tally->set_scores(*node);} + void tally_init_triggers(Tally* tally, int i_tally, pugi::xml_node* node) {tally->init_triggers(*node, i_tally);} diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index 275aa0df32..60e339e2b7 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -97,11 +97,10 @@ module tally_header character(len=104) :: name = "" ! user-defined name real(8) :: volume ! volume of region - logical :: depletion_rx = .false. ! has depletion reactions, e.g. (n,2n) ! Values to score, e.g. flux, absorption, etc. - integer :: n_score_bins = 0 - integer, allocatable :: score_bins(:) + !integer :: n_score_bins = 0 + !integer, allocatable :: score_bins_(:) ! Results for each bin -- the first dimension of the array is for scores ! (e.g. flux, total reaction rate, fission reaction rate, etc.) and the @@ -125,6 +124,9 @@ module tally_header procedure :: set_type => tally_set_type procedure :: estimator => tally_get_estimator procedure :: set_estimator => tally_set_estimator + procedure :: depletion_rx => tally_get_depletion_rx + procedure :: n_score_bins => tally_get_n_score_bins + procedure :: score_bins => tally_get_score_bin procedure :: n_filters => tally_get_n_filters procedure :: filter => tally_get_filter procedure :: stride => tally_get_stride @@ -285,7 +287,7 @@ contains end if ! Set total number of filter and scoring bins - this % total_score_bins = this % n_score_bins * this % n_nuclide_bins() + this % total_score_bins = this % n_score_bins() * this % n_nuclide_bins() if (allocated(this % results)) then ! If results was already allocated but shape is wrong, then reallocate it @@ -379,6 +381,47 @@ contains call tally_set_estimator_c(this % ptr, e) end subroutine + function tally_get_depletion_rx(this) result(drx) + class(TallyObject) :: this + logical(C_BOOL) :: drx + interface + function tally_get_depletion_rx_c(tally) result(drx) bind(C) + import C_PTR, C_BOOL + type(C_PTR), value :: tally + logical(C_BOOl) :: drx + end function + end interface + drx = tally_get_depletion_rx_c(this % ptr) + end function + + function tally_get_n_score_bins(this) result(n) + class(TallyObject) :: this + integer(C_INT) :: n + interface + function tally_get_n_scores_c(tally) result(n) bind(C) + import C_PTR, C_INT + type(C_PTR), value :: tally + integer(C_INT) :: n + end function + end interface + n = tally_get_n_scores_c(this % ptr) + end function + + function tally_get_score_bin(this, i) result(filt) + class(TallyObject) :: this + integer(C_INT) :: i + integer(C_INT32_T) :: filt + interface + function tally_get_score_c(tally, i) result(filt) bind(C) + import C_PTR, C_INT + type(C_PTR), value :: tally + integer(C_INT), value :: i + integer(C_INT) :: filt + end function + end interface + filt = tally_get_score_c(this % ptr, i-1) + end function + function tally_get_n_filters(this) result(n) class(TallyObject) :: this integer(C_INT) :: n @@ -721,31 +764,6 @@ contains end function openmc_tally_get_n_realizations - function openmc_tally_get_scores(index, scores, n) result(err) bind(C) - ! Return the list of nuclides assigned to a tally - integer(C_INT32_T), value :: index - type(C_PTR), intent(out) :: scores - integer(C_INT), intent(out) :: n - integer(C_INT) :: err - - if (index >= 1 .and. index <= size(tallies)) then - associate (t => tallies(index) % obj) - if (allocated(t % score_bins)) then - scores = C_LOC(t % score_bins(1)) - n = size(t % score_bins) - err = 0 - else - err = E_ALLOCATE - call set_errmsg("Tally scores have not been allocated yet.") - end if - end associate - else - err = E_OUT_OF_BOUNDS - call set_errmsg('Index in tallies array is out of bounds.') - end if - end function openmc_tally_get_scores - - function openmc_tally_reset(index) result(err) bind(C) ! Reset tally results and number of realizations integer(C_INT32_T), intent(in), value :: index @@ -904,180 +922,6 @@ contains end function openmc_tally_set_nuclides - function openmc_tally_set_scores(index, n, scores) result(err) bind(C) - ! Sets the scores in the tally - integer(C_INT32_T), value :: index - integer(C_INT), value :: n - type(C_PTR), intent(in) :: scores(n) - integer(C_INT) :: err - - integer :: i - integer :: MT - character(C_CHAR), pointer :: string(:) - character(len=:, kind=C_CHAR), allocatable :: score_ - logical :: depletion_rx - - err = E_UNASSIGNED - depletion_rx = .false. - if (index >= 1 .and. index <= size(tallies)) then - associate (t => tallies(index) % obj) - if (allocated(t % score_bins)) deallocate(t % score_bins) - allocate(t % score_bins(n)) - t % n_score_bins = n - - do i = 1, n - ! Convert C string to Fortran string - call c_f_pointer(scores(i), string, [20]) - score_ = to_lower(to_f_string(string)) - - select case (score_) - case ('flux') - t % score_bins(i) = SCORE_FLUX - case ('total', '(n,total)') - t % score_bins(i) = SCORE_TOTAL - case ('scatter') - t % score_bins(i) = SCORE_SCATTER - case ('nu-scatter') - t % score_bins(i) = SCORE_NU_SCATTER - case ('(n,2n)') - t % score_bins(i) = N_2N - depletion_rx = .true. - case ('(n,3n)') - t % score_bins(i) = N_3N - depletion_rx = .true. - case ('(n,4n)') - t % score_bins(i) = N_4N - depletion_rx = .true. - case ('absorption') - t % score_bins(i) = SCORE_ABSORPTION - case ('fission', '18') - t % score_bins(i) = SCORE_FISSION - case ('nu-fission') - t % score_bins(i) = SCORE_NU_FISSION - case ('decay-rate') - t % score_bins(i) = SCORE_DECAY_RATE - case ('delayed-nu-fission') - t % score_bins(i) = SCORE_DELAYED_NU_FISSION - case ('prompt-nu-fission') - t % score_bins(i) = SCORE_PROMPT_NU_FISSION - case ('kappa-fission') - t % score_bins(i) = SCORE_KAPPA_FISSION - case ('inverse-velocity') - t % score_bins(i) = SCORE_INVERSE_VELOCITY - case ('fission-q-prompt') - t % score_bins(i) = SCORE_FISS_Q_PROMPT - case ('fission-q-recoverable') - t % score_bins(i) = SCORE_FISS_Q_RECOV - case ('current') - t % score_bins(i) = SCORE_CURRENT - case ('events') - t % score_bins(i) = SCORE_EVENTS - case ('elastic', '(n,elastic)') - t % score_bins(i) = ELASTIC - case ('(n,2nd)') - t % score_bins(i) = N_2ND - case ('(n,na)') - t % score_bins(i) = N_2NA - case ('(n,n3a)') - t % score_bins(i) = N_N3A - case ('(n,2na)') - t % score_bins(i) = N_2NA - case ('(n,3na)') - t % score_bins(i) = N_3NA - case ('(n,np)') - t % score_bins(i) = N_NP - case ('(n,n2a)') - t % score_bins(i) = N_N2A - case ('(n,2n2a)') - t % score_bins(i) = N_2N2A - case ('(n,nd)') - t % score_bins(i) = N_ND - case ('(n,nt)') - t % score_bins(i) = N_NT - case ('(n,nHe-3)') - t % score_bins(i) = N_N3HE - case ('(n,nd2a)') - t % score_bins(i) = N_ND2A - case ('(n,nt2a)') - t % score_bins(i) = N_NT2A - case ('(n,3nf)') - t % score_bins(i) = N_3NF - case ('(n,2np)') - t % score_bins(i) = N_2NP - case ('(n,3np)') - t % score_bins(i) = N_3NP - case ('(n,n2p)') - t % score_bins(i) = N_N2P - case ('(n,npa)') - t % score_bins(i) = N_NPA - case ('(n,n1)') - t % score_bins(i) = N_N1 - case ('(n,nc)') - t % score_bins(i) = N_NC - case ('(n,gamma)') - t % score_bins(i) = N_GAMMA - depletion_rx = .true. - case ('(n,p)') - t % score_bins(i) = N_P - depletion_rx = .true. - case ('(n,d)') - t % score_bins(i) = N_D - case ('(n,t)') - t % score_bins(i) = N_T - case ('(n,3He)') - t % score_bins(i) = N_3HE - case ('(n,a)') - t % score_bins(i) = N_A - depletion_rx = .true. - case ('(n,2a)') - t % score_bins(i) = N_2A - case ('(n,3a)') - t % score_bins(i) = N_3A - case ('(n,2p)') - t % score_bins(i) = N_2P - case ('(n,pa)') - t % score_bins(i) = N_PA - case ('(n,t2a)') - t % score_bins(i) = N_T2A - case ('(n,d2a)') - t % score_bins(i) = N_D2A - case ('(n,pd)') - t % score_bins(i) = N_PD - case ('(n,pt)') - t % score_bins(i) = N_PT - case ('(n,da)') - t % score_bins(i) = N_DA - case default - ! Assume that user has specified an MT number - MT = int(str_to_int(score_)) - - if (MT /= ERROR_INT) then - ! Specified score was an integer - if (MT > 1) then - t % score_bins(i) = MT - else - err = E_INVALID_ARGUMENT - call set_errmsg("Negative MT number cannot be used as a score.") - end if - - else - err = E_INVALID_ARGUMENT - call set_errmsg("Unknown score: " // trim(score_) // ".") - end if - - end select - end do - - err = 0 - t % depletion_rx = depletion_rx - end associate - else - err = E_OUT_OF_BOUNDS - call set_errmsg('Index in tallies array is out of bounds.') - end if - end function openmc_tally_set_scores - - subroutine openmc_get_tally_next_id(id) bind(C) ! Returns an ID number that has not been used by any other tallies. integer(C_INT32_T), intent(out) :: id diff --git a/src/tallies/trigger.cpp b/src/tallies/trigger.cpp index d8f3ed4b7a..ac69545c61 100644 --- a/src/tallies/trigger.cpp +++ b/src/tallies/trigger.cpp @@ -97,11 +97,8 @@ check_tally_triggers(double& ratio, int& tally_id, int& score) // If this is the most uncertain value, set the output variables. if (this_ratio > ratio) { ratio = this_ratio; - int* scores; - int junk; - err = openmc_tally_get_scores(i_tally, &scores, &junk); - score = scores[trigger.score_index]; - err = openmc_tally_get_id(i_tally, &tally_id); + score = t.scores_[trigger.score_index]; + tally_id = t.id_; } } } From 675ed32b346c6a31c4b4e2c4edbcdcd99a7d2d05 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Tue, 5 Feb 2019 14:41:22 -0500 Subject: [PATCH 27/58] Directly access C++ material data in tally.cpp --- src/material.cpp | 7 +------ src/material_header.F90 | 8 -------- src/tallies/tally.cpp | 35 ++++++++++++++++++++++------------- 3 files changed, 23 insertions(+), 27 deletions(-) diff --git a/src/material.cpp b/src/material.cpp index 51b273ca98..510cea966f 100644 --- a/src/material.cpp +++ b/src/material.cpp @@ -636,7 +636,7 @@ void Material::init_nuclide_index() int n = settings::run_CE ? data::nuclides.size() : data::nuclides_MG.size(); mat_nuclide_index_.resize(n); - std::fill(mat_nuclide_index_.begin(), mat_nuclide_index_.end(), -1); + std::fill(mat_nuclide_index_.begin(), mat_nuclide_index_.end(), C_NONE); for (int i = 0; i < nuclide_.size(); ++i) { mat_nuclide_index_[nuclide_[i]] = i; } @@ -1135,11 +1135,6 @@ extern "C" { return model::materials[i_mat - 1]->density_gpcc_; } - int material_nuclide_index(int32_t i_mat, int i_nuc) - { - return model::materials[i_mat - 1]->mat_nuclide_index_[i_nuc - 1] + 1; - } - void material_calculate_xs(const Particle* p) { model::materials[p->material - 1]->calculate_xs(*p); diff --git a/src/material_header.F90 b/src/material_header.F90 index 81baeb861e..cda79ec60e 100644 --- a/src/material_header.F90 +++ b/src/material_header.F90 @@ -11,7 +11,6 @@ module material_header public :: material_id public :: material_nuclide public :: material_nuclide_size - public :: material_nuclide_index public :: material_atom_density public :: material_density_gpcc @@ -40,13 +39,6 @@ module material_header integer(C_INT) :: n end function - function material_nuclide_index(i_mat, i_nuc) bind(C) result(idx) - import C_INT32_T, C_INT - integer(C_INT32_T), value :: i_mat - integer(C_INT), value :: i_nuc - integer(C_INT) :: idx - end function - function material_atom_density(i_mat, idx) bind(C) result(density) import C_INT32_T, C_INT, C_DOUBLE integer(C_INT32_T), value :: i_mat diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 1199f6ba62..febabb0e39 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -3,6 +3,7 @@ #include "openmc/capi.h" #include "openmc/constants.h" #include "openmc/error.h" +#include "openmc/material.h" #include "openmc/message_passing.h" #include "openmc/nuclide.h" #include "openmc/settings.h" @@ -44,10 +45,6 @@ score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, extern "C" void score_all_nuclides(Particle* p, int i_tally, double flux, int filter_index); -extern "C" int material_nuclide_index(int i_material, int i_nuclide); - -extern "C" double material_atom_density(int i_material, int i); - //============================================================================== // Global variable definitions //============================================================================== @@ -729,9 +726,13 @@ score_analog_tally_mg(Particle* p) double atom_density = 0.; if (i_nuclide > 0) { - auto j = material_nuclide_index(p->material, i_nuclide); - if (j == 0) continue; - atom_density = material_atom_density(p->material, j); + //TODO: off-by-one + auto j = model::materials[p->material-1] + ->mat_nuclide_index_[i_nuclide-1]; + if (j == C_NONE) continue; + //atom_density = material_atom_density(p->material, j); + //TODO: off-by-one + atom_density = model::materials[p->material-1]->atom_density_(j); } score_general_mg(p, i_tally, i*tally.scores_.size(), filter_index, @@ -791,9 +792,13 @@ score_tracklength_tally(Particle* p, double distance) double atom_density = 0.; if (i_nuclide > 0) { if (p->material != MATERIAL_VOID) { - auto j = material_nuclide_index(p->material, i_nuclide); - if (j == 0) continue; - atom_density = material_atom_density(p->material, j); + //TODO: off-by-one + auto j = model::materials[p->material-1] + ->mat_nuclide_index_[i_nuclide-1]; + if (j == C_NONE) continue; + //atom_density = material_atom_density(p->material, j); + //TODO: off-by-one + atom_density = model::materials[p->material-1]->atom_density_(j); } } @@ -867,9 +872,13 @@ score_collision_tally(Particle* p) double atom_density = 0.; if (i_nuclide > 0) { - auto j = material_nuclide_index(p->material, i_nuclide); - if (j == 0) continue; - atom_density = material_atom_density(p->material, j); + //TODO: off-by-one + auto j = model::materials[p->material-1] + ->mat_nuclide_index_[i_nuclide-1]; + if (j == C_NONE) continue; + //atom_density = material_atom_density(p->material, j); + //TODO: off-by-one + atom_density = model::materials[p->material-1]->atom_density_(j); } //TODO: consider replacing this "if" with pointers or templates From 22f8749225ec39dfa11f85600c62998531bdf030 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Tue, 5 Feb 2019 16:54:17 -0500 Subject: [PATCH 28/58] Read tally nuclides from XML in C++ --- include/openmc/tallies/tally.h | 4 +- src/input_xml.F90 | 98 ++++------------------------------ src/tallies/tally.cpp | 45 +++++++++++++++- 3 files changed, 57 insertions(+), 90 deletions(-) diff --git a/include/openmc/tallies/tally.h b/include/openmc/tallies/tally.h index b9b97f64e2..fa9b1acca8 100644 --- a/include/openmc/tallies/tally.h +++ b/include/openmc/tallies/tally.h @@ -25,6 +25,8 @@ public: void set_scores(std::vector scores); + void set_nuclides(pugi::xml_node node); + //---------------------------------------------------------------------------- // Methods for getting and setting filter/stride data. @@ -41,8 +43,6 @@ public: //---------------------------------------------------------------------------- // Other methods. - void init_scores(pugi::xml_node node); - void init_triggers(pugi::xml_node node, int i_tally); //---------------------------------------------------------------------------- diff --git a/src/input_xml.F90 b/src/input_xml.F90 index d714f57eba..5cd951435e 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -447,7 +447,6 @@ contains integer :: i ! loop over user-specified tallies integer :: j ! loop over words - integer :: k ! another loop index integer :: l ! loop over bins integer :: filter_id ! user-specified identifier for filter integer :: tally_id ! user-specified identifier for filter @@ -456,20 +455,14 @@ contains integer :: n ! size of arrays in mesh specification integer :: n_words ! number of words read integer :: n_filter ! number of filters - integer :: n_scores ! number of scores integer :: i_start, i_end integer(C_INT) :: err - integer :: MT ! user-specified MT for score logical :: file_exists ! does tallies.xml file exist? integer, allocatable :: temp_filter(:) ! temporary filter indices - logical :: has_energyout, has_delayedgroup, has_legendre, has_surface, & - has_cell, has_cellfrom, has_meshsurface + logical :: has_energyout integer :: particle_filter_index character(MAX_LINE_LEN) :: filename - character(MAX_WORD_LEN) :: word - character(MAX_WORD_LEN) :: score_name character(MAX_WORD_LEN) :: temp_str - character(MAX_WORD_LEN), allocatable :: sarray(:) type(TallyFilterContainer), pointer :: f type(XMLDocument) :: doc type(XMLNode) :: root @@ -478,11 +471,15 @@ contains type(XMLNode), allocatable :: node_tal_list(:) type(XMLNode), allocatable :: node_filt_list(:) type(TallyDerivative), pointer :: deriv - integer, allocatable :: nuclide_bins(:) - logical :: all_nuclides interface - subroutine tally_init_scores(tally_ptr, xml_node) bind(C) + subroutine tally_set_scores(tally_ptr, xml_node) bind(C) + import C_PTR + type(C_PTR), value :: tally_ptr + type(C_PTR) :: xml_node + end subroutine + + subroutine tally_set_nuclides(tally_ptr, xml_node) bind(C) import C_PTR type(C_PTR), value :: tally_ptr type(C_PTR) :: xml_node @@ -692,25 +689,9 @@ contains ! Check for the presence of certain filter types has_energyout = (t % energyout_filter() > 0) - has_delayedgroup = (t % delayedgroup_filter() > 0) - has_legendre = .false. - has_cell = .false. - has_cellfrom = .false. - has_surface = .false. - has_meshsurface = .false. particle_filter_index = 0 do j = 1, t % n_filters() select type (filt => filters(t % filter(j)) % obj) - type is (LegendreFilter) - has_legendre = .true. - type is (CellFilter) - has_cell = .true. - type is (CellfromFilter) - has_cellfrom = .true. - type is (SurfaceFilter) - has_surface = .true. - type is (MeshSurfaceFilter) - has_meshsurface = .true. type is (ParticleFilter) particle_filter_index = j end select @@ -739,69 +720,12 @@ contains ! ======================================================================= ! READ DATA FOR NUCLIDES - all_nuclides = .false. - if (check_for_node(node_tal, "nuclides")) then - - ! Allocate a temporary string array for nuclides and copy values over - allocate(sarray(node_word_count(node_tal, "nuclides"))) - call get_node_array(node_tal, "nuclides", sarray) - - if (trim(sarray(1)) == 'all') then - ! Handle special case all - allocate(nuclide_bins(n_nuclides + 1)) - - ! Set bins to 1, 2, 3, ..., n_nuclides, -1 - nuclide_bins(1:n_nuclides) = (/ (j, j=1, n_nuclides) /) - nuclide_bins(n_nuclides + 1) = -1 - - ! Set flag so we can treat this case specially - all_nuclides = .true. - else - ! Any other case, e.g. U-235 Pu-239 - n_words = node_word_count(node_tal, "nuclides") - allocate(nuclide_bins(n_words)) - do j = 1, n_words - - ! Check if total material was specified - if (trim(sarray(j)) == 'total') then - nuclide_bins(j) = -1 - cycle - end if - - ! If a specific nuclide was specified - word = sarray(j) - - ! Search through nuclides - k = nuclide_map_get(to_c_string(word)) - if (k == -1) then - call fatal_error("Could not find the nuclide " & - // trim(word) // " specified in tally " & - // trim(to_str(t % id())) // " in any material.") - end if - - ! Set bin to index in nuclides array - nuclide_bins(j) = k - end do - end if - - ! Deallocate temporary string array - deallocate(sarray) - - else - ! No were specified -- create only one bin will be added - ! for the total material. - allocate(nuclide_bins(1)) - nuclide_bins(1) = -1 - end if - - ! Set the tally nuclides array - call t % set_nuclide_bins(nuclide_bins, all_nuclides) - deallocate(nuclide_bins) + call tally_set_nuclides(t % ptr, node_tal % ptr) ! ======================================================================= ! READ DATA FOR SCORES - call tally_init_scores(t % ptr, node_tal % ptr) + call tally_set_scores(t % ptr, node_tal % ptr) if (check_for_node(node_tal, "scores")) then n_words = node_word_count(node_tal, "scores") @@ -809,7 +733,7 @@ contains ! Check if tally is compatible with particle type if (photon_transport) then if (particle_filter_index == 0) then - do j = 1, n_scores + do j = 1, t % n_score_bins() select case (t % score_bins(j)) case (SCORE_INVERSE_VELOCITY) call fatal_error("Particle filter must be used with photon & diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index febabb0e39..9bec0b59c6 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -529,6 +529,46 @@ Tally::set_scores(std::vector scores) } } +void +Tally::set_nuclides(pugi::xml_node node) +{ + // By default, we tally just the total material rates. + if (!check_for_node(node, "nuclides")) { + nuclides_.push_back(-1); + return; + } + + if (get_node_value(node, "nuclides") == "all") { + // This tally should bin every nuclide in the problem. It should also bin + // the total material rates. To achieve this, set the nuclides_ vector to + // 1, 2, 3, ..., -1. + nuclides_.reserve(data::nuclides.size() + 1); + //TODO: off-by-one + for (auto i = 1; i < data::nuclides.size()+1; ++i) + nuclides_.push_back(i); + nuclides_.push_back(-1); + all_nuclides_ = true; + + } else { + // The user provided specifics nuclides. Parse it as an array with either + // "total" or a nuclide name like "U-235" in each position. + auto words = get_node_array(node, "nuclides"); + for (auto word : words) { + if (word == "total") { + nuclides_.push_back(-1); + } else { + auto search = data::nuclide_map.find(word); + if (search == data::nuclide_map.end()) + fatal_error("Could not find the nuclide " + word + + " specified in tally " + std::to_string(id_) + + " in any material"); + //TODO: off-by-one + nuclides_.push_back(search->second + 1); + } + } + } +} + void Tally::init_triggers(pugi::xml_node node, int i_tally) { @@ -1329,9 +1369,12 @@ extern "C" { int tally_get_delayedgroup_filter_c(Tally* tally) {return tally->delayedgroup_filter_;} - void tally_init_scores(Tally* tally, pugi::xml_node* node) + void tally_set_scores(Tally* tally, pugi::xml_node* node) {tally->set_scores(*node);} + void tally_set_nuclides(Tally* tally, pugi::xml_node* node) + {tally->set_nuclides(*node);} + void tally_init_triggers(Tally* tally, int i_tally, pugi::xml_node* node) {tally->init_triggers(*node, i_tally);} From 19e7923d0844033bbb03dfc5ddbdd5246e986f08 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Tue, 5 Feb 2019 17:06:31 -0500 Subject: [PATCH 29/58] Use 0-based indexing for nuclides in tallies --- openmc/capi/tally.py | 2 +- src/output.F90 | 4 +- src/state_point.F90 | 10 +- src/tallies/tally.F90 | 252 +++++++++++++++++------------------ src/tallies/tally.cpp | 18 +-- src/tallies/tally_header.F90 | 2 +- 6 files changed, 144 insertions(+), 144 deletions(-) diff --git a/openmc/capi/tally.py b/openmc/capi/tally.py index a414ead456..1115457cb6 100644 --- a/openmc/capi/tally.py +++ b/openmc/capi/tally.py @@ -278,7 +278,7 @@ class Tally(_FortranObjectWithID): nucs = POINTER(c_int)() n = c_int() _dll.openmc_tally_get_nuclides(self._index, nucs, n) - return [Nuclide(nucs[i]).name if nucs[i] > 0 else 'total' + return [Nuclide(nucs[i]+1).name if nucs[i] >= 0 else 'total' for i in range(n.value)] @nuclides.setter diff --git a/src/output.F90 b/src/output.F90 index ea06645a86..11d128f9ed 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -632,9 +632,9 @@ contains else if (run_CE) then write(UNIT=unit_tally, FMT='(1X,2A,1X,A)') repeat(" ", indent), & - trim(nuclides(i_nuclide) % name) + trim(nuclides(i_nuclide+1) % name) else - call get_name_c(i_nuclide, len(temp_name), temp_name) + call get_name_c(i_nuclide+1, len(temp_name), temp_name) write(UNIT=unit_tally, FMT='(1X,2A,1X,A)') repeat(" ", indent), & trim(temp_name) end if diff --git a/src/state_point.F90 b/src/state_point.F90 index 163bc11219..f89b81af86 100644 --- a/src/state_point.F90 +++ b/src/state_point.F90 @@ -279,16 +279,16 @@ contains ! Set up nuclide bin array and then write allocate(str_array(tally % n_nuclide_bins())) NUCLIDE_LOOP: do j = 1, tally % n_nuclide_bins() - if (tally % nuclide_bins(j) > 0) then + if (tally % nuclide_bins(j) >= 0) then if (run_CE) then - i_xs = index(nuclides(tally % nuclide_bins(j)) % name, '.') + i_xs = index(nuclides(tally % nuclide_bins(j)+1) % name, '.') if (i_xs > 0) then - str_array(j) = nuclides(tally % nuclide_bins(j)) % name(1 : i_xs-1) + str_array(j) = nuclides(tally % nuclide_bins(j)+1) % name(1 : i_xs-1) else - str_array(j) = nuclides(tally % nuclide_bins(j)) % name + str_array(j) = nuclides(tally % nuclide_bins(j)+1) % name end if else - call get_name_c(tally % nuclide_bins(j), len(temp_name), & + call get_name_c(tally % nuclide_bins(j)+1, len(temp_name), & temp_name) i_xs = index(temp_name, '.') if (i_xs > 0) then diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index fde57084d6..9985633776 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -196,8 +196,8 @@ contains end if else - if (i_nuclide > 0) then - score = micro_xs(i_nuclide) % total * atom_density * flux + if (i_nuclide >= 0) then + score = micro_xs(i_nuclide+1) % total * atom_density * flux else score = material_xs % total * flux end if @@ -239,9 +239,9 @@ contains score = p % last_wgt * flux else - if (i_nuclide > 0) then - score = (micro_xs(i_nuclide) % total & - - micro_xs(i_nuclide) % absorption) * atom_density * flux + if (i_nuclide >= 0) then + score = (micro_xs(i_nuclide+1) % total & + - micro_xs(i_nuclide+1) % absorption) * atom_density * flux else score = (material_xs % total - material_xs % absorption) * flux end if @@ -286,8 +286,8 @@ contains end if else - if (i_nuclide > 0) then - score = micro_xs(i_nuclide) % absorption * atom_density * flux + if (i_nuclide >= 0) then + score = micro_xs(i_nuclide+1) % absorption * atom_density * flux else score = material_xs % absorption * flux end if @@ -320,8 +320,8 @@ contains end if else - if (i_nuclide > 0) then - score = micro_xs(i_nuclide) % fission * atom_density * flux + if (i_nuclide >= 0) then + score = micro_xs(i_nuclide+1) % fission * atom_density * flux else score = material_xs % fission * flux end if @@ -366,8 +366,8 @@ contains end if else - if (i_nuclide > 0) then - score = micro_xs(i_nuclide) % nu_fission * atom_density * flux + if (i_nuclide >= 0) then + score = micro_xs(i_nuclide+1) % nu_fission * atom_density * flux else score = material_xs % nu_fission * flux end if @@ -414,8 +414,8 @@ contains end if else - if (i_nuclide > 0) then - score = micro_xs(i_nuclide) % fission * nuclides(i_nuclide) % & + if (i_nuclide >= 0) then + score = micro_xs(i_nuclide+1) % fission * nuclides(i_nuclide+1) % & nu(E, EMISSION_PROMPT) * atom_density * flux else @@ -540,7 +540,7 @@ contains else ! Check if tally is on a single nuclide - if (i_nuclide > 0) then + if (i_nuclide >= 0) then ! Check if the delayed group filter is present if (dg_filter > 0) then @@ -555,10 +555,10 @@ contains d = filt % groups(d_bin) ! Compute the yield for this delayed group - yield = nuclides(i_nuclide) % nu(E, EMISSION_DELAYED, d) + yield = nuclides(i_nuclide+1) % nu(E, EMISSION_DELAYED, d) ! Compute the score and tally to bin - score = micro_xs(i_nuclide) % fission * yield * & + score = micro_xs(i_nuclide+1) % fission * yield * & atom_density * flux call score_fission_delayed_dg(t, d_bin, score, score_index) end do @@ -568,7 +568,7 @@ contains ! If the delayed group filter is not present, compute the score ! by multiplying the delayed-nu-fission macro xs by the flux - score = micro_xs(i_nuclide) % fission * nuclides(i_nuclide) % & + score = micro_xs(i_nuclide+1) % fission * nuclides(i_nuclide+1) % & nu(E, EMISSION_DELAYED) * atom_density * flux end if @@ -771,7 +771,7 @@ contains else ! Check if tally is on a single nuclide - if (i_nuclide > 0) then + if (i_nuclide >= 0) then ! Check if the delayed group filter is present if (dg_filter > 0) then @@ -786,13 +786,13 @@ contains d = filt % groups(d_bin) ! Compute the yield for this delayed group - yield = nuclides(i_nuclide) % nu(E, EMISSION_DELAYED, d) + yield = nuclides(i_nuclide+1) % nu(E, EMISSION_DELAYED, d) - associate (rxn => nuclides(i_nuclide) % & - reactions(nuclides(i_nuclide) % index_fission(1))) + associate (rxn => nuclides(i_nuclide+1) % & + reactions(nuclides(i_nuclide+1) % index_fission(1))) ! Compute the score and tally to bin - score = micro_xs(i_nuclide) % fission * yield * flux * & + score = micro_xs(i_nuclide+1) % fission * yield * flux * & atom_density * rxn % product_decay_rate(1 + d) end associate @@ -817,8 +817,8 @@ contains ! array to be exceeded. Hence, we use the size of this array ! and not the MAX_DELAYED_GROUPS constant for this loop. do d = 1, rxn % products_size() - 2 - score = score + micro_xs(i_nuclide) % fission * flux * & - nuclides(i_nuclide) % nu(E, EMISSION_DELAYED) * & + score = score + micro_xs(i_nuclide+1) % fission * flux * & + nuclides(i_nuclide+1) % nu(E, EMISSION_DELAYED) * & atom_density * rxn % product_decay_rate(1 + d) end do end associate @@ -954,11 +954,11 @@ contains end if else - if (i_nuclide > 0) then - associate (nuc => nuclides(i_nuclide)) + if (i_nuclide >= 0) then + associate (nuc => nuclides(i_nuclide+1)) if (nuc % fissionable) then score = nuc % reactions(nuc % index_fission(1)) % Q_value * & - micro_xs(i_nuclide) % fission * atom_density * flux + micro_xs(i_nuclide+1) % fission * atom_density * flux end if end associate else @@ -994,11 +994,11 @@ contains score = p % last_wgt * flux else - if (i_nuclide > 0) then - if (micro_xs(i_nuclide) % elastic == CACHE_INVALID) then - call nuclides(i_nuclide) % calculate_elastic_xs() + if (i_nuclide >= 0) then + if (micro_xs(i_nuclide+1) % elastic == CACHE_INVALID) then + call nuclides(i_nuclide+1) % calculate_elastic_xs() end if - score = micro_xs(i_nuclide) % elastic * atom_density * flux + score = micro_xs(i_nuclide+1) % elastic * atom_density * flux else score = ZERO if (p % material /= MATERIAL_VOID) then @@ -1064,15 +1064,15 @@ contains end if else - if (i_nuclide > 0) then - associate (nuc => nuclides(i_nuclide)) + if (i_nuclide >= 0) then + associate (nuc => nuclides(i_nuclide+1)) if (score_bin == SCORE_FISS_Q_PROMPT) then xs = nuclide_fission_q_prompt(nuc % ptr, E) else if (score_bin == SCORE_FISS_Q_RECOV) then xs = nuclide_fission_q_recov(nuc % ptr, E) end if - score = micro_xs(i_nuclide) % fission * atom_density * flux * xs + score = micro_xs(i_nuclide+1) % fission * atom_density * flux * xs end associate else if (p % material /= MATERIAL_VOID) then @@ -1118,8 +1118,8 @@ contains m = 6 end select - if (i_nuclide > 0) then - score = micro_xs(i_nuclide) % reaction(m) * atom_density * flux + if (i_nuclide >= 0) then + score = micro_xs(i_nuclide+1) % reaction(m) * atom_density * flux else score = ZERO if (p % material /= MATERIAL_VOID) then @@ -1148,17 +1148,17 @@ contains ! Set default score score = ZERO - if (i_nuclide > 0) then - m = nuclides(i_nuclide) % reaction_index(score_bin) + if (i_nuclide >= 0) then + m = nuclides(i_nuclide+1) % reaction_index(score_bin) if (m /= 0) then ! Retrieve temperature and energy grid index and interpolation ! factor - i_temp = micro_xs(i_nuclide) % index_temp + 1 + i_temp = micro_xs(i_nuclide+1) % index_temp + 1 if (i_temp > 0) then - i_energy = micro_xs(i_nuclide) % index_grid - f = micro_xs(i_nuclide) % interp_factor + i_energy = micro_xs(i_nuclide+1) % index_grid + f = micro_xs(i_nuclide+1) % interp_factor - associate (rx => nuclides(i_nuclide) % reactions(m)) + associate (rx => nuclides(i_nuclide+1) % reactions(m)) threshold = rx % xs_threshold(i_temp) if (i_energy >= threshold) then score = ((ONE - f) * rx % xs(i_temp, i_energy - & @@ -1300,10 +1300,10 @@ contains call set_macro_angle_index_c(p % material, p_uvw) ! Do same for nucxs, point it to the microscopic nuclide data of interest - if (i_nuclide > 0) then + if (i_nuclide >= 0) then ! And since we haven't calculated this temperature index yet, do so now - call set_nuclide_temperature_index_c(i_nuclide, p % sqrtkT) - call set_nuclide_angle_index_c(i_nuclide, p_uvw) + call set_nuclide_temperature_index_c(i_nuclide+1, p % sqrtkT) + call set_nuclide_angle_index_c(i_nuclide+1, p_uvw) end if i = 0 @@ -1356,15 +1356,15 @@ contains score = p % last_wgt end if - if (i_nuclide > 0) then + if (i_nuclide >= 0) then score = score * flux * atom_density * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_TOTAL, p_g) / & + get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_TOTAL, p_g) / & get_macro_xs_c(p % material, MG_GET_XS_TOTAL, p_g) end if else - if (i_nuclide > 0) then - score = get_nuclide_xs_c(i_nuclide, MG_GET_XS_TOTAL, p_g) * & + if (i_nuclide >= 0) then + score = get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_TOTAL, p_g) * & atom_density * flux else score = material_xs % total * flux @@ -1386,8 +1386,8 @@ contains score = p % last_wgt end if - if (i_nuclide > 0) then - score = score * flux * get_nuclide_xs_c(i_nuclide, & + if (i_nuclide >= 0) then + score = score * flux * get_nuclide_xs_c(i_nuclide+1, & MG_GET_XS_INVERSE_VELOCITY, p_g) / & get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) else @@ -1398,8 +1398,8 @@ contains else - if (i_nuclide > 0) then - score = flux * get_nuclide_xs_c(i_nuclide, & + if (i_nuclide >= 0) then + score = flux * get_nuclide_xs_c(i_nuclide+1, & MG_GET_XS_INVERSE_VELOCITY, p_g) else score = flux * get_macro_xs_c(p % material, & @@ -1423,18 +1423,18 @@ contains ! Since we transport based on material data, the angle selected ! was not selected from the f(mu) for the nuclide. Therefore ! adjust the score by the actual probability for that nuclide. - if (i_nuclide > 0) then + if (i_nuclide >= 0) then score = score * atom_density * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_SCATTER_FMU_MULT, & + get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_SCATTER_FMU_MULT, & p % last_g, p % g, MU=p % mu) / & get_macro_xs_c(p % material, MG_GET_XS_SCATTER_FMU_MULT, & p % last_g, p % g, MU=p % mu) end if else - if (i_nuclide > 0) then + if (i_nuclide >= 0) then score = atom_density * flux * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_SCATTER_MULT, & + get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_SCATTER_MULT, & p_g, MU=p % mu) else ! Get the scattering x/s and take away @@ -1461,18 +1461,18 @@ contains ! Since we transport based on material data, the angle selected ! was not selected from the f(mu) for the nuclide. Therefore ! adjust the score by the actual probability for that nuclide. - if (i_nuclide > 0) then + if (i_nuclide >= 0) then score = score * atom_density * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_SCATTER_FMU, & + get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_SCATTER_FMU, & p % last_g, p % g, MU=p % mu) / & get_macro_xs_c(p % material, MG_GET_XS_SCATTER_FMU, & p % last_g, p % g, MU=p % mu) end if else - if (i_nuclide > 0) then + if (i_nuclide >= 0) then score = atom_density * flux * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_SCATTER, p_g) + get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_SCATTER, p_g) else ! Get the scattering x/s, which includes multiplication score = flux * & @@ -1494,15 +1494,15 @@ contains ! can just use the particle's weight entering the collision score = p % last_wgt * flux end if - if (i_nuclide > 0) then + if (i_nuclide >= 0) then score = score * atom_density * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_ABSORPTION, p_g) / & + get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_ABSORPTION, p_g) / & get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) end if else - if (i_nuclide > 0) then + if (i_nuclide >= 0) then score = atom_density * flux * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_ABSORPTION, p_g) + get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_ABSORPTION, p_g) else score = material_xs % absorption * flux end if @@ -1525,9 +1525,9 @@ contains ! fission reaction rate score = p % last_wgt * flux end if - if (i_nuclide > 0) then + if (i_nuclide >= 0) then score = score * atom_density * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_FISSION, p_g) / & + get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_FISSION, p_g) / & get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) else score = score * & @@ -1535,8 +1535,8 @@ contains get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) end if else - if (i_nuclide > 0) then - score = get_nuclide_xs_c(i_nuclide, MG_GET_XS_FISSION, p_g) * & + if (i_nuclide >= 0) then + score = get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_FISSION, p_g) * & atom_density * flux else score = get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) * flux @@ -1563,9 +1563,9 @@ contains ! calculate fraction of absorptions that would have resulted in ! nu-fission score = p % absorb_wgt * flux - if (i_nuclide > 0) then + if (i_nuclide >= 0) then score = score * atom_density * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_NU_FISSION, p_g) / & + get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_NU_FISSION, p_g) / & get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) else score = score * & @@ -1581,16 +1581,16 @@ contains ! bank. Since this was weighted by 1/keff, we multiply by keff ! to get the proper score. score = keff * p % wgt_bank * flux - if (i_nuclide > 0) then + if (i_nuclide >= 0) then score = score * atom_density * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_FISSION, p_g) / & + get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_FISSION, p_g) / & get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) end if end if else - if (i_nuclide > 0) then - score = get_nuclide_xs_c(i_nuclide, MG_GET_XS_NU_FISSION, p_g) * & + if (i_nuclide >= 0) then + score = get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_NU_FISSION, p_g) * & atom_density * flux else score = get_macro_xs_c(p % material, MG_GET_XS_NU_FISSION, p_g) * flux @@ -1617,9 +1617,9 @@ contains ! calculate fraction of absorptions that would have resulted in ! nu-fission score = p % absorb_wgt * flux - if (i_nuclide > 0) then + if (i_nuclide >= 0) then score = score * atom_density * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_PROMPT_NU_FISSION, p_g) / & + get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_PROMPT_NU_FISSION, p_g) / & get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) else score = score * & @@ -1636,16 +1636,16 @@ contains ! to get the proper score. score = keff * p % wgt_bank * (ONE - sum(p % n_delayed_bank) & / real(p % n_bank, 8)) * flux - if (i_nuclide > 0) then + if (i_nuclide >= 0) then score = score * atom_density * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_FISSION, p_g) / & + get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_FISSION, p_g) / & get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) end if end if else - if (i_nuclide > 0) then - score = get_nuclide_xs_c(i_nuclide, MG_GET_XS_PROMPT_NU_FISSION, p_g) * & + if (i_nuclide >= 0) then + score = get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_PROMPT_NU_FISSION, p_g) * & atom_density * flux else score = get_macro_xs_c(p % material, MG_GET_XS_PROMPT_NU_FISSION, p_g) * flux @@ -1688,9 +1688,9 @@ contains d = filt % groups(d_bin) score = p % absorb_wgt * flux - if (i_nuclide > 0) then + if (i_nuclide >= 0) then score = score * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & + get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) else score = score * & @@ -1704,9 +1704,9 @@ contains end select else score = p % absorb_wgt * flux - if (i_nuclide > 0) then + if (i_nuclide >= 0) then score = score * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g) / & + get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, p_g) / & get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) else score = score * & @@ -1739,9 +1739,9 @@ contains score = keff * p % wgt_bank / p % n_bank * & p % n_delayed_bank(d) * flux - if (i_nuclide > 0) then + if (i_nuclide >= 0) then score = score * atom_density * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_FISSION, p_g) / & + get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_FISSION, p_g) / & get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) end if @@ -1751,9 +1751,9 @@ contains end select else score = keff * p % wgt_bank / p % n_bank * sum(p % n_delayed_bank) * flux - if (i_nuclide > 0) then + if (i_nuclide >= 0) then score = score * atom_density * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_FISSION, p_g) / & + get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_FISSION, p_g) / & get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) end if end if @@ -1772,9 +1772,9 @@ contains ! Get the delayed group for this bin d = filt % groups(d_bin) - if (i_nuclide > 0) then + if (i_nuclide >= 0) then score = atom_density * flux * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) + get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) else score = flux * & get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) @@ -1785,9 +1785,9 @@ contains cycle SCORE_LOOP end select else - if (i_nuclide > 0) then + if (i_nuclide >= 0) then score = atom_density * flux * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g) + get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, p_g) else score = flux * & @@ -1820,10 +1820,10 @@ contains d = filt % groups(d_bin) score = p % absorb_wgt * flux - if (i_nuclide > 0) then + if (i_nuclide >= 0) then score = score * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & + get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) else score = score * & @@ -1845,10 +1845,10 @@ contains ! the fraction of the delayed-nu-fission xs to the absorption xs ! for all delayed groups. do d = 1, num_delayed_groups - if (i_nuclide > 0) then + if (i_nuclide >= 0) then score = score + p % absorb_wgt * flux * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & + get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) else score = score + p % absorb_wgt * flux * & @@ -1880,11 +1880,11 @@ contains if (g /= 0) then ! determine score based on bank site weight and keff. - if (i_nuclide > 0) then + if (i_nuclide >= 0) then score = score + keff * atom_density * & fission_bank_wgt(n_bank - p % n_bank + k) * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_FISSION, p_g) / & + get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_FISSION, p_g) / & get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) * flux else score = score + keff * & @@ -1940,10 +1940,10 @@ contains ! Get the delayed group for this bin d = filt % groups(d_bin) - if (i_nuclide > 0) then + if (i_nuclide >= 0) then score = atom_density * flux * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) + get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) else score = flux * & get_macro_xs_c(p % material, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & @@ -1962,10 +1962,10 @@ contains ! the fraction of the delayed-nu-fission xs to the absorption xs ! for all delayed groups. do d = 1, num_delayed_groups - if (i_nuclide > 0) then + if (i_nuclide >= 0) then score = score + atom_density * flux * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) + get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & + get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) else score = score + flux * & get_macro_xs_c(p % material, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & @@ -1992,9 +1992,9 @@ contains ! fission reaction rate score = p % last_wgt * flux end if - if (i_nuclide > 0) then + if (i_nuclide >= 0) then score = score * atom_density * & - get_nuclide_xs_c(i_nuclide, MG_GET_XS_KAPPA_FISSION, p_g) / & + get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_KAPPA_FISSION, p_g) / & get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) else score = score * & @@ -2002,8 +2002,8 @@ contains get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) end if else - if (i_nuclide > 0) then - score = get_nuclide_xs_c(i_nuclide, MG_GET_XS_KAPPA_FISSION, p_g) * & + if (i_nuclide >= 0) then + score = get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_KAPPA_FISSION, p_g) * & atom_density * flux else score = flux * & @@ -2054,11 +2054,11 @@ contains ! Determine index in nuclides array and atom density for i-th nuclide in ! current material - i_nuclide = material_nuclide(p % material, i) + i_nuclide = material_nuclide(p % material, i) - 1 atom_density = material_atom_density(p % material, i) ! Determine score for each bin - call score_general(p, i_tally, (i_nuclide-1)*t % n_score_bins(), filter_index, & + call score_general(p, i_tally, i_nuclide*t % n_score_bins(), filter_index, & i_nuclide, atom_density, flux) end do NUCLIDE_LOOP @@ -2437,7 +2437,7 @@ contains case (ESTIMATOR_COLLISION) scoring_diff_nuclide = & (material_id(p % material) == deriv % diff_material) & - .and. (i_nuclide == deriv % diff_nuclide) + .and. (i_nuclide+1 == deriv % diff_nuclide) select case (score_bin) @@ -2694,14 +2694,14 @@ contains .and. material_xs % total > ZERO) then dsig_s = ZERO dsig_a = ZERO - associate (nuc => nuclides(i_nuclide)) + associate (nuc => nuclides(i_nuclide+1)) if (multipole_in_range(nuc % ptr, p % last_E)) then call multipole_deriv_eval(nuc % ptr, p % last_E, & p % sqrtkT, dsig_s, dsig_a, dsig_f) end if end associate score = score * (flux_deriv & - + (dsig_s + dsig_a) / micro_xs(i_nuclide) % total) + + (dsig_s + dsig_a) / micro_xs(i_nuclide+1) % total) else score = score * flux_deriv end if @@ -2729,15 +2729,15 @@ contains .and. (material_xs % total - material_xs % absorption) > ZERO)& then dsig_s = ZERO - associate (nuc => nuclides(i_nuclide)) + associate (nuc => nuclides(i_nuclide+1)) if (multipole_in_range(nuc % ptr, p % last_E)) then call multipole_deriv_eval(nuc % ptr, p % last_E, & p % sqrtkT, dsig_s, dsig_a, dsig_f) end if end associate score = score * (flux_deriv + dsig_s & - / (micro_xs(i_nuclide) % total & - - micro_xs(i_nuclide) % absorption)) + / (micro_xs(i_nuclide+1) % total & + - micro_xs(i_nuclide+1) % absorption)) else score = score * flux_deriv end if @@ -2763,14 +2763,14 @@ contains else if (material_id(p % material) == deriv % diff_material & .and. material_xs % absorption > ZERO) then dsig_a = ZERO - associate (nuc => nuclides(i_nuclide)) + associate (nuc => nuclides(i_nuclide+1)) if (multipole_in_range(nuc % ptr, p % last_E)) then call multipole_deriv_eval(nuc % ptr, p % last_E, & p % sqrtkT, dsig_s, dsig_a, dsig_f) end if end associate score = score * (flux_deriv & - + dsig_a / micro_xs(i_nuclide) % absorption) + + dsig_a / micro_xs(i_nuclide+1) % absorption) else score = score * flux_deriv end if @@ -2796,14 +2796,14 @@ contains else if (material_id(p % material) == deriv % diff_material & .and. material_xs % fission > ZERO) then dsig_f = ZERO - associate (nuc => nuclides(i_nuclide)) + associate (nuc => nuclides(i_nuclide+1)) if (multipole_in_range(nuc % ptr, p % last_E)) then call multipole_deriv_eval(nuc % ptr, p % last_E, & p % sqrtkT, dsig_s, dsig_a, dsig_f) end if end associate score = score * (flux_deriv & - + dsig_f / micro_xs(i_nuclide) % fission) + + dsig_f / micro_xs(i_nuclide+1) % fission) else score = score * flux_deriv end if @@ -2831,14 +2831,14 @@ contains else if (material_id(p % material) == deriv % diff_material & .and. material_xs % nu_fission > ZERO) then dsig_f = ZERO - associate (nuc => nuclides(i_nuclide)) + associate (nuc => nuclides(i_nuclide+1)) if (multipole_in_range(nuc % ptr, p % last_E)) then call multipole_deriv_eval(nuc % ptr, p % last_E, & p % sqrtkT, dsig_s, dsig_a, dsig_f) end if end associate score = score * (flux_deriv & - + dsig_f / micro_xs(i_nuclide) % fission) + + dsig_f / micro_xs(i_nuclide+1) % fission) else score = score * flux_deriv end if diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 9bec0b59c6..1d6313fd89 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -562,8 +562,7 @@ Tally::set_nuclides(pugi::xml_node node) fatal_error("Could not find the nuclide " + word + " specified in tally " + std::to_string(id_) + " in any material"); - //TODO: off-by-one - nuclides_.push_back(search->second + 1); + nuclides_.push_back(search->second); } } } @@ -705,7 +704,8 @@ score_analog_tally_ce(Particle* p) // the event nuclide or the total material. Note that the i_nuclide // and flux arguments for score_general are not used for analog // tallies. - if (i_nuclide == p->event_nuclide || i_nuclide == -1) + //TODO: off-by-one + if (i_nuclide == p->event_nuclide-1 || i_nuclide == -1) score_general_ce(p, i_tally, i*tally.scores_.size(), filter_index, -1, -1., filter_weight); } @@ -765,10 +765,10 @@ score_analog_tally_mg(Particle* p) auto i_nuclide = tally.nuclides_[i]; double atom_density = 0.; - if (i_nuclide > 0) { + if (i_nuclide >= 0) { //TODO: off-by-one auto j = model::materials[p->material-1] - ->mat_nuclide_index_[i_nuclide-1]; + ->mat_nuclide_index_[i_nuclide]; if (j == C_NONE) continue; //atom_density = material_atom_density(p->material, j); //TODO: off-by-one @@ -830,11 +830,11 @@ score_tracklength_tally(Particle* p, double distance) auto i_nuclide = tally.nuclides_[i]; double atom_density = 0.; - if (i_nuclide > 0) { + if (i_nuclide >= 0) { if (p->material != MATERIAL_VOID) { //TODO: off-by-one auto j = model::materials[p->material-1] - ->mat_nuclide_index_[i_nuclide-1]; + ->mat_nuclide_index_[i_nuclide]; if (j == C_NONE) continue; //atom_density = material_atom_density(p->material, j); //TODO: off-by-one @@ -911,10 +911,10 @@ score_collision_tally(Particle* p) auto i_nuclide = tally.nuclides_[i]; double atom_density = 0.; - if (i_nuclide > 0) { + if (i_nuclide >= 0) { //TODO: off-by-one auto j = model::materials[p->material-1] - ->mat_nuclide_index_[i_nuclide-1]; + ->mat_nuclide_index_[i_nuclide]; if (j == C_NONE) continue; //atom_density = material_atom_density(p->material, j); //TODO: off-by-one diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index d0bfbb8dac..8a81801680 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -903,7 +903,7 @@ contains case default idx = nuclide_map_get(to_c_string(nuclide_)) if (idx /= -1) then - bins(i) = idx + bins(i) = idx - 1 else err = E_DATA call set_errmsg("Nuclide '" // trim(to_f_string(string)) // & From 53a0dc8d972c7be0aecea37d97fc309f1ca1491f Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Tue, 5 Feb 2019 17:58:45 -0500 Subject: [PATCH 30/58] Move openmc_tally_set_nuclides to C++ --- include/openmc/tallies/tally.h | 2 +- src/tallies/tally.cpp | 74 +++++++++++++++++--------- src/tallies/tally_header.F90 | 97 ---------------------------------- 3 files changed, 49 insertions(+), 124 deletions(-) diff --git a/include/openmc/tallies/tally.h b/include/openmc/tallies/tally.h index fa9b1acca8..2091ece0eb 100644 --- a/include/openmc/tallies/tally.h +++ b/include/openmc/tallies/tally.h @@ -61,7 +61,7 @@ public: std::vector scores_; //!< Filter integrands (e.g. flux, fission) //! Index of each nuclide to be tallied. -1 indicates total material. - std::vector nuclides_; + std::vector nuclides_ {-1}; //! True if this tally has a bin for every nuclide in the problem bool all_nuclides_ {false}; diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 1d6313fd89..c14290efcf 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -532,6 +532,8 @@ Tally::set_scores(std::vector scores) void Tally::set_nuclides(pugi::xml_node node) { + nuclides_.clear(); + // By default, we tally just the total material rates. if (!check_for_node(node, "nuclides")) { nuclides_.push_back(-1); @@ -1231,6 +1233,52 @@ openmc_tally_set_scores(int32_t index, int n, const char** scores) return 0; } +extern "C" int +openmc_tally_get_nuclides(int32_t index, int** nuclides, int* n) +{ + // Make sure the index fits in the array bounds. + if (index < 1 || index > model::tallies.size()) { + set_errmsg("Index in tallies array is out of bounds."); + return OPENMC_E_OUT_OF_BOUNDS; + } + + //TODO: off-by-one + *n = model::tallies[index-1]->nuclides_.size(); + *nuclides = model::tallies[index-1]->nuclides_.data(); + + return 0; +} + +extern "C" int +openmc_tally_set_nuclides(int32_t index, int n, const char** nuclides) +{ + // Make sure the index fits in the array bounds. + if (index < 1 || index > model::tallies.size()) { + set_errmsg("Index in tallies array is out of bounds."); + return OPENMC_E_OUT_OF_BOUNDS; + } + + std::vector words(nuclides, nuclides+n); + std::vector nucs; + for (auto word : words){ + if (word == "total") { + nucs.push_back(-1); + } else { + auto search = data::nuclide_map.find(word); + if (search == data::nuclide_map.end()) { + set_errmsg("Nuclide \"" + word + "\" has not been loaded yet"); + return OPENMC_E_DATA; + } + nucs.push_back(search->second); + } + } + + //TODO: off-by-one + model::tallies[index-1]->nuclides_ = nucs; + + return 0; +} + extern "C" int openmc_tally_get_filters(int32_t index, const int32_t** indices, int* n) { @@ -1266,22 +1314,6 @@ openmc_tally_set_filters(int32_t index, int n, const int32_t* indices) return 0; } -extern "C" int -openmc_tally_get_nuclides(int32_t index, int** nuclides, int* n) -{ - // Make sure the index fits in the array bounds. - if (index < 1 || index > model::tallies.size()) { - set_errmsg("Index in tallies array is out of bounds."); - return OPENMC_E_OUT_OF_BOUNDS; - } - - //TODO: off-by-one - *n = model::tallies[index-1]->nuclides_.size(); - *nuclides = model::tallies[index-1]->nuclides_.data(); - - return 0; -} - //============================================================================== // Fortran compatibility functions //============================================================================== @@ -1353,16 +1385,6 @@ extern "C" { int tally_get_nuclide_bins_c(Tally* tally, int i) {return tally->nuclides_[i-1];} - void - tally_set_nuclide_bins_c(Tally* tally, int n, int bins[], bool all_nuclides) - { - tally->nuclides_.clear(); - tally->nuclides_.assign(bins, bins + n); - tally->all_nuclides_ = all_nuclides; - } - - bool tally_get_all_nuclides_c(Tally* tally) {return tally->all_nuclides_;} - int tally_get_energyout_filter_c(Tally* tally) {return tally->energyout_filter_;} diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index 8a81801680..d857a726ef 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -98,10 +98,6 @@ module tally_header character(len=104) :: name = "" ! user-defined name real(8) :: volume ! volume of region - ! Values to score, e.g. flux, absorption, etc. - !integer :: n_score_bins = 0 - !integer, allocatable :: score_bins_(:) - ! Results for each bin -- the first dimension of the array is for scores ! (e.g. flux, total reaction rate, fission reaction rate, etc.) and the ! second dimension of the array is for the combination of filters @@ -133,8 +129,6 @@ module tally_header procedure :: n_filter_bins => tally_get_n_filter_bins procedure :: n_nuclide_bins => tally_get_n_nuclide_bins procedure :: nuclide_bins => tally_get_nuclide_bins - procedure :: set_nuclide_bins => tally_set_nuclide_bins - procedure :: all_nuclides => tally_get_all_nuclides procedure :: energyout_filter => tally_get_energyout_filter procedure :: delayedgroup_filter => tally_get_delayedgroup_filter procedure :: deriv => tally_get_deriv @@ -279,12 +273,6 @@ contains subroutine tally_allocate_results(this) class(TallyObject), intent(inout) :: this - integer, parameter :: default_nuclide_bins(1) = (/-1/) - - ! If no nuclides were specified, add a single bin for total material - if (this % n_nuclide_bins() == 0) then - call this % set_nuclide_bins(default_nuclide_bins) - end if ! Set total number of filter and scoring bins this % total_score_bins = this % n_score_bins() * this % n_nuclide_bins() @@ -505,41 +493,6 @@ contains nuclide = tally_get_nuclide_bins_c(this % ptr, i) end function - subroutine tally_set_nuclide_bins(this, bins, all_nuclides) - class(TallyObject) :: this - integer :: bins(:) - logical, optional :: all_nuclides - logical(C_BOOL) :: all_ - interface - subroutine tally_set_nuclide_bins_c(this, n, bins, all_nuclides) bind(C) - import C_PTR, C_INT, C_BOOL - type(C_PTR), value :: this - integer(C_INT), value :: n - integer(C_INT) :: bins(n) - logical(C_BOOL), value :: all_nuclides - end subroutine - end interface - if (present(all_nuclides)) then - all_ = logical(all_nuclides, kind=C_BOOL) - else - all_ = .false._C_BOOL - end if - call tally_set_nuclide_bins_c(this % ptr, size(bins), bins, all_) - end subroutine - - function tally_get_all_nuclides(this) result(all_nuc) - class(TallyObject) :: this - logical(C_BOOL) :: all_nuc - interface - function tally_get_all_nuclides_c(tally) result(all_nuc) bind(C) - import C_PTR, C_BOOL - type(C_PTR), value :: tally - logical(C_BOOL) :: all_nuc - end function - end interface - all_nuc = tally_get_all_nuclides_c(this % ptr) - end function - function tally_get_energyout_filter(this) result(filt) class(TallyObject) :: this integer(C_INT) :: filt @@ -874,56 +827,6 @@ contains end function openmc_tally_set_id - function openmc_tally_set_nuclides(index, n, nuclides) result(err) bind(C) - ! Sets the nuclides in the tally which results should be scored for - integer(C_INT32_T), value :: index - integer(C_INT), value :: n - type(C_PTR), intent(in) :: nuclides(n) - integer(C_INT) :: err - - integer, allocatable :: bins(:) - integer :: i - integer :: idx - character(C_CHAR), pointer :: string(:) - character(len=:, kind=C_CHAR), allocatable :: nuclide_ - - err = E_UNASSIGNED - if (index >= 1 .and. index <= size(tallies)) then - associate (t => tallies(index) % obj) - allocate(bins(n)) - - do i = 1, n - ! Convert C string to Fortran string - call c_f_pointer(nuclides(i), string, [10]) - nuclide_ = to_f_string(string) - - select case (nuclide_) - case ('total') - bins(i) = -1 - case default - idx = nuclide_map_get(to_c_string(nuclide_)) - if (idx /= -1) then - bins(i) = idx - 1 - else - err = E_DATA - call set_errmsg("Nuclide '" // trim(to_f_string(string)) // & - "' has not been loaded yet.") - return - end if - end select - end do - - call t % set_nuclide_bins(bins) - - err = 0 - end associate - else - err = E_OUT_OF_BOUNDS - call set_errmsg('Index in tallies array is out of bounds.') - end if - end function openmc_tally_set_nuclides - - subroutine openmc_get_tally_next_id(id) bind(C) ! Returns an ID number that has not been used by any other tallies. integer(C_INT32_T), intent(out) :: id From 3377241f4dccf34e35024d482b6aaeee0931d14c Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Tue, 5 Feb 2019 18:42:56 -0500 Subject: [PATCH 31/58] Use 0-based indexing for Tally.filters_ values --- openmc/capi/tally.py | 4 ++-- src/input_xml.F90 | 6 +++--- src/output.F90 | 16 +++++++-------- src/state_point.F90 | 2 +- src/tallies/tally.F90 | 46 +++++++++++++++++++++---------------------- src/tallies/tally.cpp | 26 ++++++++---------------- 6 files changed, 45 insertions(+), 55 deletions(-) diff --git a/openmc/capi/tally.py b/openmc/capi/tally.py index 1115457cb6..df8ef9d08e 100644 --- a/openmc/capi/tally.py +++ b/openmc/capi/tally.py @@ -254,13 +254,13 @@ class Tally(_FortranObjectWithID): filt_idx = POINTER(c_int32)() n = c_int() _dll.openmc_tally_get_filters(self._index, filt_idx, n) - return [_get_filter(filt_idx[i]) for i in range(n.value)] + return [_get_filter(filt_idx[i]+1) for i in range(n.value)] @filters.setter def filters(self, filters): # Get filter indices as int32_t[] n = len(filters) - indices = (c_int32*n)(*(f._index for f in filters)) + indices = (c_int32*n)(*(f._index-1 for f in filters)) _dll.openmc_tally_set_filters(self._index, n, indices) diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 5cd951435e..aa38f7db6e 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -679,7 +679,7 @@ contains end if ! Store the index of the filter - temp_filter(j) = i_filt + temp_filter(j) = i_filt - 1 end do ! Set the filters @@ -691,7 +691,7 @@ contains has_energyout = (t % energyout_filter() > 0) particle_filter_index = 0 do j = 1, t % n_filters() - select type (filt => filters(t % filter(j)) % obj) + select type (filt => filters(t % filter(j) + 1) % obj) type is (ParticleFilter) particle_filter_index = j end select @@ -699,7 +699,7 @@ contains ! Change the tally estimator if a filter demands it do j = 1, t % n_filters() - select type (filt => filters(t % filter(j)) % obj) + select type (filt => filters(t % filter(j) + 1) % obj) type is (EnergyoutFilter) call t % set_estimator(ESTIMATOR_ANALOG) type is (LegendreFilter) diff --git a/src/output.F90 b/src/output.F90 index 11d128f9ed..02f5ec3166 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -559,7 +559,7 @@ contains ! Initialize bins, filter level, and indentation do h = 1, t % n_filters() - filter_bins(t % filter(h)) = 0 + filter_bins(t % filter(h) + 1) = 0 end do j = 1 indent = 0 @@ -570,17 +570,17 @@ contains if (t % n_filters() == 0) exit find_bin ! Increment bin combination - filter_bins(t % filter(j)) = filter_bins(t % filter(j)) + 1 + filter_bins(t % filter(j) + 1) = filter_bins(t % filter(j) + 1) + 1 ! ================================================================= ! REACHED END OF BINS FOR THIS FILTER, MOVE TO NEXT FILTER - if (filter_bins(t % filter(j)) > & - filters(t % filter(j)) % obj % n_bins) then + if (filter_bins(t % filter(j) + 1) > & + filters(t % filter(j) + 1) % obj % n_bins) then ! If this is the first filter, then exit if (j == 1) exit print_bin - filter_bins(t % filter(j)) = 0 + filter_bins(t % filter(j) + 1) = 0 j = j - 1 indent = indent - 2 @@ -592,7 +592,7 @@ contains if (j == t % n_filters()) exit find_bin ! Print current filter information - i_filt = t % filter(j) + i_filt = t % filter(j) + 1 write(UNIT=unit_tally, FMT='(1X,2A)') repeat(" ", indent), & trim(filters(i_filt) % obj % & text_label(filter_bins(i_filt))) @@ -604,7 +604,7 @@ contains ! Print filter information if (t % n_filters() > 0) then - i_filt = t % filter(j) + i_filt = t % filter(j) + 1 write(UNIT=unit_tally, FMT='(1X,2A)') repeat(" ", indent), & trim(filters(i_filt) % obj % & text_label(filter_bins(i_filt))) @@ -617,7 +617,7 @@ contains filter_index = 1 do h = 1, t % n_filters() filter_index = filter_index & - + (max(filter_bins(t % filter(h)) ,1) - 1) * t % stride(h) + + (max(filter_bins(t % filter(h)+1) ,1) - 1) * t % stride(h) end do ! Write results for this filter bin combination diff --git a/src/state_point.F90 b/src/state_point.F90 index f89b81af86..8ca3464c1a 100644 --- a/src/state_point.F90 +++ b/src/state_point.F90 @@ -270,7 +270,7 @@ contains ! Write IDs of filters allocate(id_array(tally % n_filters())) do j = 1, tally % n_filters() - id_array(j) = filters(tally % filter(j)) % obj % id + id_array(j) = filters(tally % filter(j) + 1) % obj % id end do call write_dataset(tally_group, "filters", id_array) deallocate(id_array) diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index 9985633776..5e34ab3279 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -467,7 +467,7 @@ contains ! Check if the delayed group filter is present if (dg_filter > 0) then - select type(filt => filters(t % filter(dg_filter)) % obj) + select type(filt => filters(t % filter(dg_filter) + 1) % obj) type is (DelayedGroupFilter) ! Loop over all delayed group bins and tally to them @@ -512,7 +512,7 @@ contains ! Check if the delayed group filter is present if (dg_filter > 0) then - select type(filt => filters(t % filter(dg_filter)) % obj) + select type(filt => filters(t % filter(dg_filter) + 1) % obj) type is (DelayedGroupFilter) ! Loop over all delayed group bins and tally to them @@ -544,7 +544,7 @@ contains ! Check if the delayed group filter is present if (dg_filter > 0) then - select type(filt => filters(t % filter(dg_filter)) % obj) + select type(filt => filters(t % filter(dg_filter) + 1) % obj) type is (DelayedGroupFilter) ! Loop over all delayed group bins and tally to them @@ -577,7 +577,7 @@ contains ! Check if the delayed group filter is present if (dg_filter > 0) then - select type(filt => filters(t % filter(dg_filter)) % obj) + select type(filt => filters(t % filter(dg_filter) + 1) % obj) type is (DelayedGroupFilter) ! Loop over all nuclides in the current material @@ -650,7 +650,7 @@ contains ! Check if the delayed group filter is present if (dg_filter > 0) then - select type(filt => filters(t % filter(dg_filter)) % obj) + select type(filt => filters(t % filter(dg_filter) + 1) % obj) type is (DelayedGroupFilter) ! Loop over all delayed group bins and tally to them @@ -745,7 +745,7 @@ contains if (dg_filter > 0) then ! declare the delayed group filter type - select type(filt => filters(t % filter(dg_filter)) % obj) + select type(filt => filters(t % filter(dg_filter) + 1) % obj) type is (DelayedGroupFilter) ! loop over delayed group bins until the corresponding bin @@ -775,7 +775,7 @@ contains ! Check if the delayed group filter is present if (dg_filter > 0) then - select type(filt => filters(t % filter(dg_filter)) % obj) + select type(filt => filters(t % filter(dg_filter) + 1) % obj) type is (DelayedGroupFilter) ! Loop over all delayed group bins and tally to them @@ -829,7 +829,7 @@ contains ! Check if the delayed group filter is present if (dg_filter > 0) then - select type(filt => filters(t % filter(dg_filter)) % obj) + select type(filt => filters(t % filter(dg_filter) + 1) % obj) type is (DelayedGroupFilter) ! Loop over all nuclides in the current material @@ -1677,7 +1677,7 @@ contains if (get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) > ZERO) then if (dg_filter > 0) then - select type(filt => filters(t % filter(dg_filter)) % obj) + select type(filt => filters(t % filter(dg_filter) + 1) % obj) type is (DelayedGroupFilter) ! Loop over all delayed group bins and tally to them @@ -1726,7 +1726,7 @@ contains ! Check if the delayed group filter is present if (dg_filter > 0) then - select type(filt => filters(t % filter(dg_filter)) % obj) + select type(filt => filters(t % filter(dg_filter) + 1) % obj) type is (DelayedGroupFilter) ! Loop over all delayed group bins and tally to them @@ -1762,7 +1762,7 @@ contains ! Check if the delayed group filter is present if (dg_filter > 0) then - select type(filt => filters(t % filter(dg_filter)) % obj) + select type(filt => filters(t % filter(dg_filter) + 1) % obj) type is (DelayedGroupFilter) ! Loop over all delayed group bins and tally to them @@ -1809,7 +1809,7 @@ contains if (get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) > ZERO) then if (dg_filter > 0) then - select type(filt => filters(t % filter(dg_filter)) % obj) + select type(filt => filters(t % filter(dg_filter) + 1) % obj) type is (DelayedGroupFilter) ! Loop over all delayed group bins and tally to them @@ -1897,7 +1897,7 @@ contains if (dg_filter > 0) then ! declare the delayed group filter type - select type(filt => filters(t % filter(dg_filter)) % obj) + select type(filt => filters(t % filter(dg_filter) + 1) % obj) type is (DelayedGroupFilter) ! loop over delayed group bins until the corresponding bin @@ -1930,7 +1930,7 @@ contains ! Check if the delayed group filter is present if (dg_filter > 0) then - select type(filt => filters(t % filter(dg_filter)) % obj) + select type(filt => filters(t % filter(dg_filter) + 1) % obj) type is (DelayedGroupFilter) ! Loop over all delayed group bins and tally to them @@ -2109,7 +2109,7 @@ contains integer :: g_out ! energy group of fission bank site ! save original outgoing energy bin and score index - i = t % filter(t % energyout_filter()) + i = t % filter(t % energyout_filter()) + 1 i_bin = filter_matches(i) % i_bin() bin_energyout = filter_matches(i) % bins_data(i_bin) @@ -2174,8 +2174,8 @@ contains ! determine scoring index and weight for this filter combination i_filter = 1 do l = 1, t % n_filters() - i_filter = i_filter + (filter_matches(t % filter(l)) & - % bins_data(filter_matches(t % filter(l)) % i_bin()) - 1) * & + i_filter = i_filter + (filter_matches(t % filter(l) + 1) & + % bins_data(filter_matches(t % filter(l) + 1) % i_bin()) - 1) * & t % stride(l) end do @@ -2195,7 +2195,7 @@ contains if (j > 0) then ! declare the delayed group filter type - select type(dg_filt => filters(t % filter(j)) % obj) + select type(dg_filt => filters(t % filter(j) + 1) % obj) type is (DelayedGroupFilter) ! loop over delayed group bins until the corresponding bin is @@ -2214,7 +2214,7 @@ contains ! determine scoring index and weight for this filter ! combination do l = 1, t % n_filters() - f = t % filter(l) + f = t % filter(l) + 1 b = filter_matches(f) % i_bin() i_filter = i_filter + (filter_matches(f) & % bins_data(b) - 1) * t % stride(l) @@ -2237,7 +2237,7 @@ contains ! determine scoring index and weight for this filter combination do l = 1, t % n_filters() - f = t % filter(l) + f = t % filter(l) + 1 b = filter_matches(f) % i_bin() i_filter = i_filter + (filter_matches(f) % bins_data(b) - 1) & * t % stride(l) @@ -2278,7 +2278,7 @@ contains integer :: filter_index ! index for matching filter bin combination ! save original delayed group bin - i_filt = t % filter(t % delayedgroup_filter()) + i_filt = t % filter(t % delayedgroup_filter()) + 1 i_bin = filter_matches(i_filt) % i_bin() bin_original = filter_matches(i_filt) % bins_data(i_bin) call filter_matches(i_filt) % bins_set_data(i_bin, d_bin) @@ -2286,8 +2286,8 @@ contains ! determine scoring index and weight on the modified matching bins filter_index = 1 do i = 1, t % n_filters() - filter_index = filter_index + (filter_matches(t % filter(i)) % & - bins_data(filter_matches(t % filter(i)) % i_bin()) - 1) * t % stride(i) + filter_index = filter_index + (filter_matches(t % filter(i) + 1) % & + bins_data(filter_matches(t % filter(i) + 1) % i_bin()) - 1) * t % stride(i) end do !$omp atomic diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index c14290efcf..9c38e4009f 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -91,14 +91,11 @@ public: // Find all valid bins in each relevant filter if they have not already been // found for this event. for (auto i_filt : tally_.filters()) { - //TODO: off-by-one - auto& match {simulation::filter_matches[i_filt-1]}; + auto& match {simulation::filter_matches[i_filt]}; if (!match.bins_present_) { match.bins_.clear(); match.weights_.clear(); - //TODO: off-by-one - model::tally_filters[i_filt-1] - ->get_all_bins(p, tally_.estimator_, match); + model::tally_filters[i_filt]->get_all_bins(p, tally_.estimator_, match); match.bins_present_ = true; } @@ -138,8 +135,7 @@ public: bool done_looping = true; for (int i = tally_.filters().size()-1; i >= 0; --i) { auto i_filt = tally_.filters(i); - //TODO: off-by-one - auto& match {simulation::filter_matches[i_filt-1]}; + auto& match {simulation::filter_matches[i_filt]}; if (match.i_bin_< match.bins_.size()) { // The bin for this filter can be incremented. Increment it and do not // touch any of the remaining filters. @@ -176,8 +172,7 @@ private: weight_ = 1.; for (auto i = 0; i < tally_.filters().size(); ++i) { auto i_filt = tally_.filters(i); - //TODO: off-by-one - auto& match {simulation::filter_matches[i_filt-1]}; + auto& match {simulation::filter_matches[i_filt]}; auto i_bin = match.i_bin_; //TODO: off-by-one index_ += (match.bins_[i_bin-1] - 1) * tally_.strides(i); @@ -360,14 +355,11 @@ Tally::set_filters(const int32_t filter_indices[], int n) for (int i = 0; i < n; ++i) { auto i_filt = filters_[i]; - //TODO: off-by-one - if (i_filt < 1 || i_filt > model::tally_filters.size()) + if (i_filt < 0 || i_filt >= model::tally_filters.size()) throw std::out_of_range("Index in tally filter array out of bounds."); - //TODO: off-by-one - const auto* filt = model::tally_filters[i_filt-1].get(); - //TODO: off-by-one on each index + const auto* filt = model::tally_filters[i_filt].get(); if (dynamic_cast(filt)) { energyout_filter_ = i + 1; } else if (dynamic_cast(filt)) { @@ -382,8 +374,7 @@ Tally::set_filters(const int32_t filter_indices[], int n) int stride = 1; for (int i = n-1; i >= 0; --i) { strides_[i] = stride; - //TODO: off-by-one - stride *= model::tally_filters[filters_[i]-1]->n_bins_; + stride *= model::tally_filters[filters_[i]]->n_bins_; } n_filter_bins_ = stride; } @@ -415,8 +406,7 @@ Tally::set_scores(std::vector scores) bool surface_present = false; bool meshsurface_present = false; for (auto i_filt : filters_) { - //TODO: off-by-one - const auto* filt {model::tally_filters[i_filt-1].get()}; + const auto* filt {model::tally_filters[i_filt].get()}; if (dynamic_cast(filt)) { legendre_present = true; } else if (dynamic_cast(filt)) { From cb87aeff7c3441fb10b02d93cc7e0d2113bc537b Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Tue, 5 Feb 2019 20:23:08 -0500 Subject: [PATCH 32/58] Fix tally nuclide indexing error --- src/tallies/tally.cpp | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 9c38e4009f..efd46e5a19 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -533,10 +533,9 @@ Tally::set_nuclides(pugi::xml_node node) if (get_node_value(node, "nuclides") == "all") { // This tally should bin every nuclide in the problem. It should also bin // the total material rates. To achieve this, set the nuclides_ vector to - // 1, 2, 3, ..., -1. + // 0, 1, 2, ..., -1. nuclides_.reserve(data::nuclides.size() + 1); - //TODO: off-by-one - for (auto i = 1; i < data::nuclides.size()+1; ++i) + for (auto i = 0; i < data::nuclides.size(); ++i) nuclides_.push_back(i); nuclides_.push_back(-1); all_nuclides_ = true; From c24a9c18b19c21d2eb3d564af35dd1b07ec54fac Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Tue, 5 Feb 2019 20:57:13 -0500 Subject: [PATCH 33/58] Move score_all_nuclides to C++ --- src/tallies/tally.F90 | 47 ------------------------------------------- src/tallies/tally.cpp | 41 ++++++++++++++++++++++++++++++++++--- 2 files changed, 38 insertions(+), 50 deletions(-) diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index 5e34ab3279..d6278f38f9 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -2029,53 +2029,6 @@ contains end associate end subroutine score_general_mg -!=============================================================================== -! SCORE_ALL_NUCLIDES tallies individual nuclide reaction rates specifically when -! the user requests all. -!=============================================================================== - - subroutine score_all_nuclides(p, i_tally, flux, filter_index) bind(C) - - type(Particle), intent(in) :: p - integer(C_INT), intent(in), value :: i_tally - real(C_DOUBLE), intent(in), value :: flux - integer(C_INT), intent(in), value :: filter_index - - integer :: i ! loop index for nuclides in material - integer :: i_nuclide ! index in nuclides array - real(8) :: atom_density ! atom density of single nuclide in atom/b-cm - - associate (t => tallies(i_tally) % obj) - - ! ========================================================================== - ! SCORE ALL INDIVIDUAL NUCLIDE REACTION RATES - - NUCLIDE_LOOP: do i = 1, material_nuclide_size(p % material) - - ! Determine index in nuclides array and atom density for i-th nuclide in - ! current material - i_nuclide = material_nuclide(p % material, i) - 1 - atom_density = material_atom_density(p % material, i) - - ! Determine score for each bin - call score_general(p, i_tally, i_nuclide*t % n_score_bins(), filter_index, & - i_nuclide, atom_density, flux) - - end do NUCLIDE_LOOP - - ! ========================================================================== - ! SCORE TOTAL MATERIAL REACTION RATES - - i_nuclide = -1 - atom_density = ZERO - - ! Determine score for each bin - call score_general(p, i_tally, n_nuclides*t % n_score_bins(), filter_index, & - i_nuclide, atom_density, flux) - - end associate - end subroutine score_all_nuclides - !=============================================================================== ! SCORE_FISSION_EOUT handles a special case where we need to store neutron ! production rate with an outgoing energy filter (think of a fission matrix). In diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index efd46e5a19..b078773c5f 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -42,9 +42,6 @@ extern "C" void score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, int i_nuclide, double atom_density, double flux); -extern "C" void -score_all_nuclides(Particle* p, int i_tally, double flux, int filter_index); - //============================================================================== // Global variable definitions //============================================================================== @@ -663,6 +660,44 @@ adaptor_type<3> tally_results(int idx) return xt::adapt(results, size, xt::no_ownership(), shape); } +//! Tally rates for when the user requests a tally on all nuclides. + +void +score_all_nuclides(Particle* p, int i_tally, double flux, int filter_index) +{ + //TODO: off-by-one + const Tally& tally {*model::tallies[i_tally-1]}; + const Material& material {*model::materials[p->material-1]}; + + // Score all individual nuclide reaction rates. + for (auto i = 0; i < material.nuclide_.size(); ++i) { + auto i_nuclide = material.nuclide_[i]; + auto atom_density = material.atom_density_(i); + + //TODO: consider replacing this "if" with pointers or templates + if (settings::run_CE) { + score_general_ce(p, i_tally, i_nuclide*tally.scores_.size(), filter_index, + i_nuclide, atom_density, flux); + } else { + score_general_mg(p, i_tally, i_nuclide*tally.scores_.size(), filter_index, + i_nuclide, atom_density, flux); + } + } + + // Score total material reaction rates. + int i_nuclide = -1; + double atom_density = 0.; + auto n_nuclides = data::nuclides.size(); + //TODO: consider replacing this "if" with pointers or templates + if (settings::run_CE) { + score_general_ce(p, i_tally, n_nuclides*tally.scores_.size(), filter_index, + i_nuclide, atom_density, flux); + } else { + score_general_mg(p, i_tally, n_nuclides*tally.scores_.size(), filter_index, + i_nuclide, atom_density, flux); + } +} + //! Score tallies based on a simple count of events (for continuous energy). // //! Analog tallies ar etriggered at every collision, not every event. From 64121662afb041e97407ba50921ea2de632a6260 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Tue, 5 Feb 2019 22:49:49 -0500 Subject: [PATCH 34/58] Move score_fission_delayed_dg to C++ --- src/tallies/filter_energy.cpp | 2 +- src/tallies/tally.F90 | 98 +++++++++++++---------------------- src/tallies/tally.cpp | 39 ++++++++++++++ 3 files changed, 75 insertions(+), 64 deletions(-) diff --git a/src/tallies/filter_energy.cpp b/src/tallies/filter_energy.cpp index 65339b519b..8ea574964d 100644 --- a/src/tallies/filter_energy.cpp +++ b/src/tallies/filter_energy.cpp @@ -120,7 +120,7 @@ extern "C" bool energy_filter_matches_transport_groups(EnergyFilter* filt) {return filt->matches_transport_groups_;} extern "C" int -energy_filter_search(EnergyFilter* filt, double val) +energy_filter_search(const EnergyFilter* filt, double val) { if (val < filt->bins_.front() || val > filt->bins_.back()) { return -1; diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index d6278f38f9..360ac0ce82 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -47,6 +47,14 @@ module tally end interface interface + subroutine score_fission_delayed_dg(i_tally, d_bin, score, score_index) bind(C) + import C_INT, C_DOUBLE + integer(C_INT), value :: i_tally + integer(C_INT), value :: d_bin + real(C_DOUBLE), value :: score + integer(C_INT), value :: score_index + end subroutine + subroutine score_analog_tally_ce(p) bind(C) import Particle type(Particle), intent(in) :: p @@ -340,7 +348,7 @@ contains ! neutrons were emitted with different energies, multiple ! outgoing energy bins may have been scored to. The following ! logic treats this special case and results to multiple bins - call score_fission_eout(p, t, score_index, score_bin) + call score_fission_eout(p, i_tally, score_index, score_bin) cycle SCORE_LOOP end if end if @@ -386,7 +394,7 @@ contains ! neutrons were emitted with different energies, multiple ! outgoing energy bins may have been scored to. The following ! logic treats this special case and results to multiple bins - call score_fission_eout(p, t, score_index, score_bin) + call score_fission_eout(p, i_tally, score_index, score_bin) cycle SCORE_LOOP end if end if @@ -454,7 +462,7 @@ contains ! neutrons were emitted with different energies, multiple ! outgoing energy bins may have been scored to. The following ! logic treats this special case and results to multiple bins - call score_fission_eout(p, t, score_index, score_bin) + call score_fission_eout(p, i_tally, score_index, score_bin) cycle SCORE_LOOP end if end if @@ -485,7 +493,7 @@ contains score = p % absorb_wgt * yield & * micro_xs(p % event_nuclide) % fission & / micro_xs(p % event_nuclide) % absorption * flux - call score_fission_delayed_dg(t, d_bin, score, score_index) + call score_fission_delayed_dg(i_tally, d_bin, score, score_index) end do cycle SCORE_LOOP end select @@ -526,7 +534,7 @@ contains score = keff * p % wgt_bank / p % n_bank * & p % n_delayed_bank(d) * flux - call score_fission_delayed_dg(t, d_bin, score, score_index) + call score_fission_delayed_dg(i_tally, d_bin, score, score_index) end do cycle SCORE_LOOP end select @@ -560,7 +568,7 @@ contains ! Compute the score and tally to bin score = micro_xs(i_nuclide+1) % fission * yield * & atom_density * flux - call score_fission_delayed_dg(t, d_bin, score, score_index) + call score_fission_delayed_dg(i_tally, d_bin, score, score_index) end do cycle SCORE_LOOP end select @@ -603,7 +611,7 @@ contains ! Compute the score and tally to bin score = micro_xs(i_nuc) % fission * yield & * atom_density_ * flux - call score_fission_delayed_dg(t, d_bin, score, & + call score_fission_delayed_dg(i_tally, d_bin, score, & score_index) end do end do @@ -676,7 +684,7 @@ contains end associate ! Tally to bin - call score_fission_delayed_dg(t, d_bin, score, score_index) + call score_fission_delayed_dg(i_tally, d_bin, score, score_index) end do cycle SCORE_LOOP end select @@ -756,7 +764,7 @@ contains ! check whether the delayed group of the particle is equal ! to the delayed group of this bin if (d == g) then - call score_fission_delayed_dg(t, d_bin, score, & + call score_fission_delayed_dg(i_tally, d_bin, score, & score_index) end if end do @@ -797,7 +805,7 @@ contains end associate ! Tally to bin - call score_fission_delayed_dg(t, d_bin, score, score_index) + call score_fission_delayed_dg(i_tally, d_bin, score, score_index) end do cycle SCORE_LOOP end select @@ -865,7 +873,7 @@ contains end associate ! Tally to bin - call score_fission_delayed_dg(t, d_bin, score, & + call score_fission_delayed_dg(i_tally, d_bin, score, & score_index) end do end if @@ -1554,7 +1562,7 @@ contains ! neutrons were emitted with different energies, multiple ! outgoing energy bins may have been scored to. The following ! logic treats this special case and results to multiple bins - call score_fission_eout(p, t, score_index, score_bin) + call score_fission_eout(p, i_tally, score_index, score_bin) cycle SCORE_LOOP end if end if @@ -1608,7 +1616,7 @@ contains ! neutrons were emitted with different energies, multiple ! outgoing energy bins may have been scored to. The following ! logic treats this special case and results to multiple bins - call score_fission_eout(p, t, score_index, score_bin) + call score_fission_eout(p, i_tally, score_index, score_bin) cycle SCORE_LOOP end if end if @@ -1666,7 +1674,7 @@ contains ! neutrons were emitted with different energies, multiple ! outgoing energy bins may have been scored to. The following ! logic treats this special case and results to multiple bins - call score_fission_eout(p, t, score_index, score_bin) + call score_fission_eout(p, i_tally, score_index, score_bin) cycle SCORE_LOOP end if end if @@ -1698,7 +1706,7 @@ contains get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) end if - call score_fission_delayed_dg(t, d_bin, score, score_index) + call score_fission_delayed_dg(i_tally, d_bin, score, score_index) end do cycle SCORE_LOOP end select @@ -1745,7 +1753,7 @@ contains get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) end if - call score_fission_delayed_dg(t, d_bin, score, score_index) + call score_fission_delayed_dg(i_tally, d_bin, score, score_index) end do cycle SCORE_LOOP end select @@ -1780,7 +1788,7 @@ contains get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) end if - call score_fission_delayed_dg(t, d_bin, score, score_index) + call score_fission_delayed_dg(i_tally, d_bin, score, score_index) end do cycle SCORE_LOOP end select @@ -1832,7 +1840,7 @@ contains get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) end if - call score_fission_delayed_dg(t, d_bin, score, score_index) + call score_fission_delayed_dg(i_tally, d_bin, score, score_index) end do cycle SCORE_LOOP end select @@ -1908,7 +1916,7 @@ contains ! check whether the delayed group of the particle is equal ! to the delayed group of this bin if (d == g) then - call score_fission_delayed_dg(t, d_bin, score, & + call score_fission_delayed_dg(i_tally, d_bin, score, & score_index) end if end do @@ -1950,7 +1958,7 @@ contains get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) end if - call score_fission_delayed_dg(t, d_bin, score, score_index) + call score_fission_delayed_dg(i_tally, d_bin, score, score_index) end do cycle SCORE_LOOP end select @@ -2036,10 +2044,10 @@ contains ! neutrons produced with different energies. !=============================================================================== - subroutine score_fission_eout(p, t, i_score, score_bin) + subroutine score_fission_eout(p, i_tally, i_score, score_bin) type(Particle), intent(in) :: p - type(TallyObject), intent(inout) :: t + integer, intent(in) :: i_tally integer, intent(in) :: i_score ! index for score integer, intent(in) :: score_bin @@ -2061,6 +2069,8 @@ contains real(8) :: E_out ! energy of fission bank site integer :: g_out ! energy group of fission bank site + associate (t => tallies(i_tally) % obj) + ! save original outgoing energy bin and score index i = t % filter(t % energyout_filter()) + 1 i_bin = filter_matches(i) % i_bin() @@ -2175,7 +2185,7 @@ contains % weights_data(b) end do - call score_fission_delayed_dg(t, d_bin, & + call score_fission_delayed_dg(i_tally, d_bin, & score * filter_weight, i_score) end if end do @@ -2210,48 +2220,10 @@ contains ! reset outgoing energy bin and score index call filter_matches(i) % bins_set_data(i_bin, bin_energyout) + end associate + end subroutine score_fission_eout -!=============================================================================== -! SCORE_FISSION_DELAYED_DG helper function used to increment the tally when a -! delayed group filter is present. -!=============================================================================== - - subroutine score_fission_delayed_dg(t, d_bin, score, score_index) - - type(TallyObject), intent(inout) :: t - integer, intent(in) :: d_bin ! delayed group bin index - real(8), intent(in) :: score ! actual score - integer, intent(in) :: score_index ! index for score - - integer :: i ! loop over tally filters - integer :: i_filt ! index in filters array - integer :: i_bin ! index of matching filter bin - integer :: bin_original ! original bin index - integer :: filter_index ! index for matching filter bin combination - - ! save original delayed group bin - i_filt = t % filter(t % delayedgroup_filter()) + 1 - i_bin = filter_matches(i_filt) % i_bin() - bin_original = filter_matches(i_filt) % bins_data(i_bin) - call filter_matches(i_filt) % bins_set_data(i_bin, d_bin) - - ! determine scoring index and weight on the modified matching bins - filter_index = 1 - do i = 1, t % n_filters() - filter_index = filter_index + (filter_matches(t % filter(i) + 1) % & - bins_data(filter_matches(t % filter(i) + 1) % i_bin()) - 1) * t % stride(i) - end do - -!$omp atomic - t % results(RESULT_VALUE, score_index, filter_index) = & - t % results(RESULT_VALUE, score_index, filter_index) + score - - ! reset original delayed group bin - call filter_matches(i_filt) % bins_set_data(i_bin, bin_original) - - end subroutine score_fission_delayed_dg - !=============================================================================== ! APPLY_DERIVATIVE_TO_SCORE multiply the given score by its relative derivative !=============================================================================== diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index b078773c5f..79552db1ff 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -5,8 +5,10 @@ #include "openmc/error.h" #include "openmc/material.h" #include "openmc/message_passing.h" +#include "openmc/mgxs_interface.h" #include "openmc/nuclide.h" #include "openmc/settings.h" +#include "openmc/simulation.h" #include "openmc/tallies/derivative.h" #include "openmc/tallies/filter.h" #include "openmc/tallies/filter_cell.h" @@ -42,6 +44,9 @@ extern "C" void score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, int i_nuclide, double atom_density, double flux); +extern "C" int +energy_filter_search(const EnergyFilter* filt, double val); + //============================================================================== // Global variable definitions //============================================================================== @@ -660,6 +665,40 @@ adaptor_type<3> tally_results(int idx) return xt::adapt(results, size, xt::no_ownership(), shape); } +//! Helper function used to increment tallies with a delayed group filter. + +extern "C" void +score_fission_delayed_dg(int i_tally, int d_bin, double score, int score_index) +{ + // Save the original delayed group bin + //TODO: off-by-one + const Tally& tally {*model::tallies[i_tally-1]}; + auto i_filt = tally.filters(tally.delayedgroup_filter_-1); + auto& dg_match {simulation::filter_matches[i_filt]}; + auto i_bin = dg_match.i_bin_; + auto original_bin = dg_match.bins_[i_bin-1]; + dg_match.bins_[i_bin-1] = d_bin; + + // Determine the filter scoring index + auto filter_index = 1; + for (auto i = 0; i < tally.filters().size(); ++i) { + auto i_filt = tally.filters(i); + auto& match {simulation::filter_matches[i_filt]}; + auto i_bin = match.i_bin_; + //TODO: off-by-one + filter_index += (match.bins_[i_bin-1] - 1) * tally.strides(i); + } + + // Update the tally result + auto results = tally_results(i_tally); + //TODO: off-by-one + #pragma omp atomic + results(filter_index-1, score_index-1, RESULT_VALUE) += score; + + // Reset the original delayed group bin + dg_match.bins_[i_bin-1] = original_bin; +} + //! Tally rates for when the user requests a tally on all nuclides. void From ee4f4e84aa2c1730b71ac7a2a28a88bb411bdfbb Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Tue, 5 Feb 2019 23:16:41 -0500 Subject: [PATCH 35/58] Move score_fission_eout to C++ --- src/tallies/tally.F90 | 217 ++++-------------------------------------- src/tallies/tally.cpp | 145 ++++++++++++++++++++++++++++ 2 files changed, 165 insertions(+), 197 deletions(-) diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index 360ac0ce82..4dd2d9ae24 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -55,6 +55,14 @@ module tally integer(C_INT), value :: score_index end subroutine + subroutine score_fission_eout(p, i_tally, i_score, score_bin) bind(C) + import Particle, C_INT + type(Particle) :: p + integer(C_INT), value :: i_tally + integer(C_INT), value :: i_score + integer(C_INT), value :: score_bin + end subroutine + subroutine score_analog_tally_ce(p) bind(C) import Particle type(Particle), intent(in) :: p @@ -1229,7 +1237,7 @@ contains ! Add derivative information on score for differential tallies. if (t % deriv() /= C_NONE) then - call apply_derivative_to_score(p, t, i_nuclide, atom_density, & + call apply_derivative_to_score(p, i_tally, i_nuclide, atom_density, & score_bin, score) end if @@ -2037,205 +2045,18 @@ contains end associate end subroutine score_general_mg -!=============================================================================== -! SCORE_FISSION_EOUT handles a special case where we need to store neutron -! production rate with an outgoing energy filter (think of a fission matrix). In -! this case, we may need to score to multiple bins if there were multiple -! neutrons produced with different energies. -!=============================================================================== - - subroutine score_fission_eout(p, i_tally, i_score, score_bin) - - type(Particle), intent(in) :: p - integer, intent(in) :: i_tally - integer, intent(in) :: i_score ! index for score - integer, intent(in) :: score_bin - - integer :: i ! index of outgoing energy filter - integer :: j ! index of delayedgroup filter - integer :: d ! delayed group - integer :: g ! another delayed group - integer :: d_bin ! delayed group bin index - integer :: k ! loop index for bank sites - integer :: l ! loop index for tally filters - integer :: f ! index in filters array - integer :: b ! index of filter bin - integer :: i_match ! matching bin index on energyout filter - integer :: i_bin ! index of matching filter bin - integer :: bin_energyout ! original outgoing energy bin - integer :: i_filter ! index for matching filter bin combination - real(8) :: filter_weight ! combined weight of all filters - real(8) :: score ! actual score - real(8) :: E_out ! energy of fission bank site - integer :: g_out ! energy group of fission bank site - - associate (t => tallies(i_tally) % obj) - - ! save original outgoing energy bin and score index - i = t % filter(t % energyout_filter()) + 1 - i_bin = filter_matches(i) % i_bin() - bin_energyout = filter_matches(i) % bins_data(i_bin) - - ! declare the energyout filter type - select type(eo_filt => filters(i) % obj) - type is (EnergyoutFilter) - - ! Since the creation of fission sites is weighted such that it is - ! expected to create n_particles sites, we need to multiply the - ! score by keff to get the true nu-fission rate. Otherwise, the sum - ! of all nu-fission rates would be ~1.0. - - ! loop over number of particles banked - do k = 1, p % n_bank - - ! get the delayed group - g = fission_bank_delayed_group(n_bank - p % n_bank + k) - - ! determine score based on bank site weight and keff - score = keff * fission_bank_wgt(n_bank - p % n_bank + k) - - ! Add derivative information for differential tallies. Note that the - ! i_nuclide and atom_density arguments do not matter since this is an - ! analog estimator. - if (t % deriv() /= C_NONE) then - call apply_derivative_to_score(p, t, 0, ZERO, SCORE_NU_FISSION, score) - end if - - if (.not. run_CE .and. eo_filt % matches_transport_groups) then - - ! determine outgoing energy group from fission bank - g_out = int(fission_bank_E(n_bank - p % n_bank + k)) - - ! modify the value so that g_out = 1 corresponds to the highest - ! energy bin - g_out = eo_filt % n_bins - g_out + 1 - - ! change outgoing energy bin - call filter_matches(i) % bins_set_data(i_bin, g_out) - - else - - ! determine outgoing energy from fission bank - if (run_CE) then - E_out = fission_bank_E(n_bank - p % n_bank + k) - else - E_out = energy_bin_avg(int(fission_bank_E(n_bank - p % n_bank + k))) - end if - - ! If this outgoing energy falls within the energyout filter's range, - ! set the appropriate filter_matches bin. - i_match = eo_filt % search(E_out) - if (i_match == -1) cycle - call filter_matches(i) % bins_set_data(i_bin, i_match) - - end if - - ! Case for tallying prompt neutrons - if (score_bin == SCORE_NU_FISSION .or. & - (score_bin == SCORE_PROMPT_NU_FISSION .and. g == 0)) then - - ! determine scoring index and weight for this filter combination - i_filter = 1 - do l = 1, t % n_filters() - i_filter = i_filter + (filter_matches(t % filter(l) + 1) & - % bins_data(filter_matches(t % filter(l) + 1) % i_bin()) - 1) * & - t % stride(l) - end do - - ! Add score to tally -!$omp atomic - t % results(RESULT_VALUE, i_score, i_filter) = & - t % results(RESULT_VALUE, i_score, i_filter) + score - - ! Case for tallying delayed emissions - else if (score_bin == SCORE_DELAYED_NU_FISSION .and. g /= 0) then - - ! Get the index of delayed group filter - j = t % delayedgroup_filter() - - ! if the delayed group filter is present, tally to corresponding - ! delayed group bin if it exists - if (j > 0) then - - ! declare the delayed group filter type - select type(dg_filt => filters(t % filter(j) + 1) % obj) - type is (DelayedGroupFilter) - - ! loop over delayed group bins until the corresponding bin is - ! found - do d_bin = 1, dg_filt % n_bins - d = dg_filt % groups(d_bin) - - ! check whether the delayed group of the particle is equal to - ! the delayed group of this bin - if (d == g) then - - ! Reset scoring index and filter weight - i_filter = 1 - filter_weight = ONE - - ! determine scoring index and weight for this filter - ! combination - do l = 1, t % n_filters() - f = t % filter(l) + 1 - b = filter_matches(f) % i_bin() - i_filter = i_filter + (filter_matches(f) & - % bins_data(b) - 1) * t % stride(l) - filter_weight = filter_weight * filter_matches(f) & - % weights_data(b) - end do - - call score_fission_delayed_dg(i_tally, d_bin, & - score * filter_weight, i_score) - end if - end do - end select - - ! if the delayed group filter is not present, add score to tally - else - - ! Reset scoring index and filter weight - i_filter = 1 - filter_weight = ONE - - ! determine scoring index and weight for this filter combination - do l = 1, t % n_filters() - f = t % filter(l) + 1 - b = filter_matches(f) % i_bin() - i_filter = i_filter + (filter_matches(f) % bins_data(b) - 1) & - * t % stride(l) - filter_weight = filter_weight * filter_matches(f) & - % weights_data(b) - end do - - ! Add score to tally -!$omp atomic - t % results(RESULT_VALUE, i_score, i_filter) = & - t % results(RESULT_VALUE, i_score, i_filter) + score * filter_weight - end if - end if - end do - end select - - ! reset outgoing energy bin and score index - call filter_matches(i) % bins_set_data(i_bin, bin_energyout) - - end associate - - end subroutine score_fission_eout - !=============================================================================== ! APPLY_DERIVATIVE_TO_SCORE multiply the given score by its relative derivative !=============================================================================== - subroutine apply_derivative_to_score(p, t, i_nuclide, atom_density, & - score_bin, score) - type(Particle), intent(in) :: p - type(TallyObject), intent(in) :: t - integer, intent(in) :: i_nuclide - real(8), intent(in) :: atom_density ! atom/b-cm - integer, intent(in) :: score_bin - real(8), intent(inout) :: score + subroutine apply_derivative_to_score(p, i_tally, i_nuclide, atom_density, & + score_bin, score) bind(C) + type(Particle), intent(in) :: p + integer(C_INT), value, intent(in) :: i_tally + integer(C_INT), value, intent(in) :: i_nuclide + real(C_DOUBLE), value, intent(in) :: atom_density ! atom/b-cm + integer(C_INT), value, intent(in) :: score_bin + real(C_DOUBLE), intent(inout) :: score type(TallyDerivative), pointer :: deriv integer :: l @@ -2244,6 +2065,8 @@ contains real(8) :: flux_deriv real(8) :: dsig_s, dsig_a, dsig_f, cum_dsig + associate (t => tallies(i_tally) % obj) + if (score == ZERO) return ! If our score was previously c then the new score is @@ -2778,7 +2601,7 @@ contains &analog and collision estimators.") end select end select - !end associate + end associate end subroutine apply_derivative_to_score !=============================================================================== diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 79552db1ff..ea948ae401 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -44,6 +44,10 @@ extern "C" void score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, int i_nuclide, double atom_density, double flux); +extern "C" void +apply_derivative_to_score(Particle* p, int i_tally, int i_nuclide, + double atom_density, int score_bin, double* score); + extern "C" int energy_filter_search(const EnergyFilter* filt, double val); @@ -699,6 +703,147 @@ score_fission_delayed_dg(int i_tally, int d_bin, double score, int score_index) dg_match.bins_[i_bin-1] = original_bin; } +//! Helper function for nu-fission tallies with energyout filters. +// +//! In this case, we may need to score to multiple bins if there were multiple +//! neutrons produced with different energies. + +extern "C" void +score_fission_eout(Particle* p, int i_tally, int i_score, int score_bin) +{ + //TODO: off-by-one + const Tally& tally {*model::tallies[i_tally-1]}; + auto results = tally_results(i_tally); + auto i_eout_filt = tally.filters()[tally.energyout_filter_-1]; + auto i_bin = simulation::filter_matches[i_eout_filt].i_bin_; + auto bin_energyout = simulation::filter_matches[i_eout_filt].bins_[i_bin-1]; + + const EnergyoutFilter& eo_filt + {*dynamic_cast(model::tally_filters[i_eout_filt].get())}; + + // Note that the score below is weighted by keff. Since the creation of + // fission sites is weighted such that it is expected to create n_particles + // sites, we need to multiply the score by keff to get the true nu-fission + // rate. Otherwise, the sum of all nu-fission rates would be ~1.0. + + // loop over number of particles banked + for (auto i = 0; i < p->n_bank; ++i) { + auto i_bank = simulation::n_bank - p->n_bank + i; + const auto& bank = simulation::fission_bank[i_bank]; + + // get the delayed group + auto g = bank.delayed_group; + + // determine score based on bank site weight and keff + double score = simulation::keff * bank.wgt; + + // Add derivative information for differential tallies. Note that the + // i_nuclide and atom_density arguments do not matter since this is an + // analog estimator. + if (tally.deriv_ != C_NONE) + apply_derivative_to_score(p, i_tally, 0, 0., SCORE_NU_FISSION, &score); + + if (!settings::run_CE && eo_filt.matches_transport_groups_) { + + // determine outgoing energy group from fission bank + auto g_out = static_cast(bank.E); + + // modify the value so that g_out = 1 corresponds to the highest energy + // bin + g_out = eo_filt.n_bins_ - g_out + 1; + + // change outgoing energy bin + simulation::filter_matches[i_eout_filt].bins_[i_bin-1] = g_out; + + } else { + + double E_out; + if (settings::run_CE) { + E_out = bank.E; + } else { + E_out = data::energy_bin_avg[static_cast(bank.E)]; + } + + //TODO: do this without the extern "C" function + auto i_match = energy_filter_search(&eo_filt, E_out); + if (i_match == -1) continue; + simulation::filter_matches[i_eout_filt].bins_[i_bin-1] = i_match; + + } + + // Case for tallying prompt neutrons + if (score_bin == SCORE_NU_FISSION + || (score_bin == SCORE_PROMPT_NU_FISSION && g == 0)) { + + // Find the filter scoring index for this filter combination + //TODO: should this include a weight? + int filter_index = 1; + for (auto j = 0; j < tally.filters().size(); ++j) { + auto i_filt = tally.filters(j); + auto& match {simulation::filter_matches[i_filt]}; + auto i_bin = match.i_bin_; + filter_index += (match.bins_[i_bin-1] - 1) * tally.strides(j); + } + + // Update tally results + #pragma omp atomic + results(filter_index-1, i_score-1, RESULT_VALUE) += score; + + } else if (score_bin == SCORE_DELAYED_NU_FISSION && g != 0) { + + // Get the index of the delayed group filter + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + + // If the delayed group filter is present, tally to corresponding delayed + // group bin if it exists + if (i_dg_filt >= 0) { + const DelayedGroupFilter& dg_filt {*dynamic_cast( + model::tally_filters[i_dg_filt].get())}; + + // Loop over delayed group bins until the corresponding bin is found + for (auto d_bin = 0; d_bin < dg_filt.n_bins_; ++d_bin) { + if (dg_filt.groups_[d_bin] == g) { + // Find the filter index and weight for this filter combination + int filter_index = 1; + double filter_weight = 1.; + for (auto j = 0; j < tally.filters().size(); ++j) { + auto i_filt = tally.filters(j); + auto& match {simulation::filter_matches[i_filt]}; + auto i_bin = match.i_bin_; + filter_index += (match.bins_[i_bin-1] - 1) * tally.strides(j); + filter_weight *= match.weights_[i_bin-1]; + } + + score_fission_delayed_dg(i_tally, d_bin+1, score*filter_weight, + i_score); + } + } + + // If the delayed group filter is not present, add score to tally + } else { + + // Find the filter index and weight for this filter combination + int filter_index = 1; + double filter_weight = 1.; + for (auto j = 0; j < tally.filters().size(); ++j) { + auto i_filt = tally.filters(j); + auto& match {simulation::filter_matches[i_filt]}; + auto i_bin = match.i_bin_; + filter_index += (match.bins_[i_bin-1] - 1) * tally.strides(j); + filter_weight *= match.weights_[i_bin-1]; + } + + // Update tally results + #pragma omp atomic + results(filter_index-1, i_score-1, RESULT_VALUE) += score*filter_weight; + } + } + } + + // Reset outgoing energy bin and score index + simulation::filter_matches[i_eout_filt].bins_[i_bin-1] = bin_energyout; +} + //! Tally rates for when the user requests a tally on all nuclides. void From 355ce75f0ac38456c3e63b9d65f4b4cac307aff3 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Wed, 6 Feb 2019 13:39:57 -0500 Subject: [PATCH 36/58] Start moving score_general_ce to C++ --- src/tallies/tally.F90 | 473 +++--------------------------------------- src/tallies/tally.cpp | 460 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 484 insertions(+), 449 deletions(-) diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index 4dd2d9ae24..cc3b169e42 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -47,6 +47,18 @@ module tally end interface interface + subroutine score_general_ce_c(p, i_tally, start_index, filter_index, & + i_nuclide, atom_density, flux) bind(C) + import Particle, C_INT, C_DOUBLE + type(Particle) :: p + integer(C_INT), value :: i_tally + integer(C_INT), value :: start_index + integer(C_INT), value :: filter_index + integer(C_INT), value :: i_nuclide + real(C_DOUBLE), value :: atom_density + real(C_DOUBLE), value :: flux + end subroutine + subroutine score_fission_delayed_dg(i_tally, d_bin, score, score_index) bind(C) import C_INT, C_DOUBLE integer(C_INT), value :: i_tally @@ -154,6 +166,9 @@ contains real(8) :: E ! particle energy real(8) :: xs ! cross section + call score_general_ce_c(p, i_tally, start_index, filter_index, & + i_nuclide, atom_density, flux) + associate (t => tallies(i_tally) % obj) ! Pre-collision energy of particle @@ -174,480 +189,42 @@ contains case (SCORE_FLUX) - if (t % estimator() == ESTIMATOR_ANALOG) then - ! All events score to a flux bin. We actually use a collision - ! estimator in place of an analog one since there is no way to count - ! 'events' exactly for the flux - if (survival_biasing) then - ! We need to account for the fact that some weight was already - ! absorbed - score = p % last_wgt + p % absorb_wgt - else - score = p % last_wgt - end if - - if (p % type == NEUTRON .or. p % type == PHOTON) then - score = score / material_xs % total * flux - else - score = ZERO - end if - - else - ! For flux, we need no cross section - score = flux - end if + cycle SCORE_LOOP case (SCORE_TOTAL) - if (t % estimator() == ESTIMATOR_ANALOG) then - ! All events will score to the total reaction rate. We can just - ! use the weight of the particle entering the collision as the - ! score - if (survival_biasing) then - ! We need to account for the fact that some weight was already - ! absorbed - score = (p % last_wgt + p % absorb_wgt) * flux - else - score = p % last_wgt * flux - end if - - else - if (i_nuclide >= 0) then - score = micro_xs(i_nuclide+1) % total * atom_density * flux - else - score = material_xs % total * flux - end if - end if + cycle SCORE_LOOP case (SCORE_INVERSE_VELOCITY) - if (t % estimator() == ESTIMATOR_ANALOG) then - ! All events score to an inverse velocity bin. We actually use a - ! collision estimator in place of an analog one since there is no way - ! to count 'events' exactly for the inverse velocity - if (survival_biasing) then - ! We need to account for the fact that some weight was already - ! absorbed - score = p % last_wgt + p % absorb_wgt - else - score = p % last_wgt - end if - - ! Score the flux weighted inverse velocity with velocity in units of - ! cm/s - score = score / material_xs % total & - / (sqrt(TWO * E / MASS_NEUTRON_EV) * C_LIGHT * 100.0_8) * flux - - else - ! For inverse velocity, we don't need a cross section. The velocity is - ! in units of cm/s. - score = flux / (sqrt(TWO * E / MASS_NEUTRON_EV) * C_LIGHT * 100.0_8) - end if + cycle SCORE_LOOP case (SCORE_SCATTER) - if (t % estimator() == ESTIMATOR_ANALOG) then - ! Skip any event where the particle didn't scatter - if (p % event /= EVENT_SCATTER) cycle SCORE_LOOP - ! Since only scattering events make it here, again we can use - ! the weight entering the collision as the estimator for the - ! reaction rate - score = p % last_wgt * flux - - else - if (i_nuclide >= 0) then - score = (micro_xs(i_nuclide+1) % total & - - micro_xs(i_nuclide+1) % absorption) * atom_density * flux - else - score = (material_xs % total - material_xs % absorption) * flux - end if - end if + cycle SCORE_LOOP case (SCORE_NU_SCATTER) - ! Only analog estimators are available. - ! Skip any event where the particle didn't scatter - if (p % event /= EVENT_SCATTER) cycle SCORE_LOOP - ! For scattering production, we need to use the pre-collision weight - ! times the yield as the estimate for the number of neutrons exiting a - ! reaction with neutrons in the exit channel - if (p % event_MT == ELASTIC .or. p % event_MT == N_LEVEL .or. & - (p % event_MT >= N_N1 .and. p % event_MT <= N_NC)) then - ! Don't waste time on very common reactions we know have - ! multiplicities of one. - score = p % last_wgt * flux - else - m = nuclides(p % event_nuclide) % reaction_index(p % event_MT) - - ! Get yield and apply to score - associate (rxn => nuclides(p % event_nuclide) % reactions(m)) - score = p % last_wgt * flux & - * rxn % product_yield(1, E) - end associate - end if + cycle SCORE_LOOP case (SCORE_ABSORPTION) - if (t % estimator() == ESTIMATOR_ANALOG) then - if (survival_biasing) then - ! No absorption events actually occur if survival biasing is on -- - ! just use weight absorbed in survival biasing - score = p % absorb_wgt * flux - else - ! Skip any event where the particle wasn't absorbed - if (p % event == EVENT_SCATTER) cycle SCORE_LOOP - ! All fission and absorption events will contribute here, so we - ! can just use the particle's weight entering the collision - score = p % last_wgt * flux - end if - - else - if (i_nuclide >= 0) then - score = micro_xs(i_nuclide+1) % absorption * atom_density * flux - else - score = material_xs % absorption * flux - end if - end if + cycle SCORE_LOOP case (SCORE_FISSION) - - if (material_xs % absorption == ZERO) cycle SCORE_LOOP - - if (t % estimator() == ESTIMATOR_ANALOG) then - if (survival_biasing) then - ! No fission events occur if survival biasing is on -- need to - ! calculate fraction of absorptions that would have resulted in - ! fission - if (micro_xs(p % event_nuclide) % absorption > ZERO) then - score = p % absorb_wgt * micro_xs(p % event_nuclide) % fission & - / micro_xs(p % event_nuclide) % absorption * flux - else - score = ZERO - end if - else - ! Skip any non-absorption events - if (p % event == EVENT_SCATTER) cycle SCORE_LOOP - ! All fission events will contribute, so again we can use - ! particle's weight entering the collision as the estimate for the - ! fission reaction rate - score = p % last_wgt * micro_xs(p % event_nuclide) % fission & - / micro_xs(p % event_nuclide) % absorption * flux - end if - - else - if (i_nuclide >= 0) then - score = micro_xs(i_nuclide+1) % fission * atom_density * flux - else - score = material_xs % fission * flux - end if - end if + cycle SCORE_LOOP case (SCORE_NU_FISSION) - - if (material_xs % absorption == ZERO) cycle SCORE_LOOP - - if (t % estimator() == ESTIMATOR_ANALOG) then - if (survival_biasing .or. p % fission) then - if (t % energyout_filter() > 0) then - ! Normally, we only need to make contributions to one scoring - ! bin. However, in the case of fission, since multiple fission - ! neutrons were emitted with different energies, multiple - ! outgoing energy bins may have been scored to. The following - ! logic treats this special case and results to multiple bins - call score_fission_eout(p, i_tally, score_index, score_bin) - cycle SCORE_LOOP - end if - end if - if (survival_biasing) then - ! No fission events occur if survival biasing is on -- need to - ! calculate fraction of absorptions that would have resulted in - ! nu-fission - if (micro_xs(p % event_nuclide) % absorption > ZERO) then - score = p % absorb_wgt * micro_xs(p % event_nuclide) % & - nu_fission / micro_xs(p % event_nuclide) % absorption * flux - else - score = ZERO - end if - else - ! Skip any non-fission events - if (.not. p % fission) cycle SCORE_LOOP - ! If there is no outgoing energy filter, than we only need to - ! score to one bin. For the score to be 'analog', we need to - ! score the number of particles that were banked in the fission - ! bank. Since this was weighted by 1/keff, we multiply by keff - ! to get the proper score. - score = keff * p % wgt_bank * flux - end if - - else - if (i_nuclide >= 0) then - score = micro_xs(i_nuclide+1) % nu_fission * atom_density * flux - else - score = material_xs % nu_fission * flux - end if - end if + cycle SCORE_LOOP case (SCORE_PROMPT_NU_FISSION) - - if (material_xs % absorption == ZERO) cycle SCORE_LOOP - - if (t % estimator() == ESTIMATOR_ANALOG) then - if (survival_biasing .or. p % fission) then - if (t % energyout_filter() > 0) then - ! Normally, we only need to make contributions to one scoring - ! bin. However, in the case of fission, since multiple fission - ! neutrons were emitted with different energies, multiple - ! outgoing energy bins may have been scored to. The following - ! logic treats this special case and results to multiple bins - call score_fission_eout(p, i_tally, score_index, score_bin) - cycle SCORE_LOOP - end if - end if - if (survival_biasing) then - ! No fission events occur if survival biasing is on -- need to - ! calculate fraction of absorptions that would have resulted in - ! prompt-nu-fission - if (micro_xs(p % event_nuclide) % absorption > ZERO) then - score = p % absorb_wgt * micro_xs(p % event_nuclide) % fission & - * nuclides(p % event_nuclide) % nu(E, EMISSION_PROMPT) & - / micro_xs(p % event_nuclide) % absorption * flux - else - score = ZERO - end if - else - ! Skip any non-fission events - if (.not. p % fission) cycle SCORE_LOOP - ! If there is no outgoing energy filter, than we only need to - ! score to one bin. For the score to be 'analog', we need to - ! score the number of particles that were banked in the fission - ! bank as prompt neutrons. Since this was weighted by 1/keff, we - ! multiply by keff to get the proper score. - score = keff * p % wgt_bank * (ONE - sum(p % n_delayed_bank) & - / real(p % n_bank, 8)) * flux - end if - - else - if (i_nuclide >= 0) then - score = micro_xs(i_nuclide+1) % fission * nuclides(i_nuclide+1) % & - nu(E, EMISSION_PROMPT) * atom_density * flux - else - - score = ZERO - - ! Loop over all nuclides in the current material - if (p % material /= MATERIAL_VOID) then - do l = 1, material_nuclide_size(p % material) - - ! Get atom density - atom_density_ = material_atom_density(p % material, l) - - ! Get index in nuclides array - i_nuc = material_nuclide(p % material, l) - - ! Accumulate the contribution from each nuclide - score = score + micro_xs(i_nuc) % fission * nuclides(i_nuc) % & - nu(E, EMISSION_PROMPT) * atom_density_ * flux - end do - end if - end if - end if + cycle SCORE_LOOP case (SCORE_DELAYED_NU_FISSION) - - if (material_xs % absorption == ZERO) cycle SCORE_LOOP - - ! Set the delayedgroup filter index and the number of delayed group bins - dg_filter = t % delayedgroup_filter() - - if (t % estimator() == ESTIMATOR_ANALOG) then - if (survival_biasing .or. p % fission) then - if (t % energyout_filter() > 0) then - ! Normally, we only need to make contributions to one scoring - ! bin. However, in the case of fission, since multiple fission - ! neutrons were emitted with different energies, multiple - ! outgoing energy bins may have been scored to. The following - ! logic treats this special case and results to multiple bins - call score_fission_eout(p, i_tally, score_index, score_bin) - cycle SCORE_LOOP - end if - end if - if (survival_biasing) then - ! No fission events occur if survival biasing is on -- need to - ! calculate fraction of absorptions that would have resulted in - ! delayed-nu-fission - if (micro_xs(p % event_nuclide) % absorption > ZERO .and. & - nuclides(p % event_nuclide) % fissionable) then - - ! Check if the delayed group filter is present - if (dg_filter > 0) then - select type(filt => filters(t % filter(dg_filter) + 1) % obj) - type is (DelayedGroupFilter) - - ! Loop over all delayed group bins and tally to them - ! individually - do d_bin = 1, filt % n_bins - - ! Get the delayed group for this bin - d = filt % groups(d_bin) - - ! Compute the yield for this delayed group - yield = nuclides(p % event_nuclide) & - % nu(E, EMISSION_DELAYED, d) - - ! Compute the score and tally to bin - score = p % absorb_wgt * yield & - * micro_xs(p % event_nuclide) % fission & - / micro_xs(p % event_nuclide) % absorption * flux - call score_fission_delayed_dg(i_tally, d_bin, score, score_index) - end do - cycle SCORE_LOOP - end select - else - ! If the delayed group filter is not present, compute the score - ! by multiplying the absorbed weight by the fraction of the - ! delayed-nu-fission xs to the absorption xs - score = p % absorb_wgt * micro_xs(p % event_nuclide) % fission & - * nuclides(p % event_nuclide) % nu(E, EMISSION_DELAYED) & - / micro_xs(p % event_nuclide) % absorption * flux - end if - end if - else - ! Skip any non-fission events - if (.not. p % fission) cycle SCORE_LOOP - ! If there is no outgoing energy filter, than we only need to - ! score to one bin. For the score to be 'analog', we need to - ! score the number of particles that were banked in the fission - ! bank. Since this was weighted by 1/keff, we multiply by keff - ! to get the proper score. Loop over the neutrons produced from - ! fission and check which ones are delayed. If a delayed neutron is - ! encountered, add its contribution to the fission bank to the - ! score. - - ! Check if the delayed group filter is present - if (dg_filter > 0) then - select type(filt => filters(t % filter(dg_filter) + 1) % obj) - type is (DelayedGroupFilter) - - ! Loop over all delayed group bins and tally to them - ! individually - do d_bin = 1, filt % n_bins - - ! Get the delayed group for this bin - d = filt % groups(d_bin) - - ! Compute the score and tally to bin - score = keff * p % wgt_bank / p % n_bank * & - p % n_delayed_bank(d) * flux - - call score_fission_delayed_dg(i_tally, d_bin, score, score_index) - end do - cycle SCORE_LOOP - end select - else - - ! Add the contribution from all delayed groups - score = keff * p % wgt_bank / p % n_bank * & - sum(p % n_delayed_bank) * flux - end if - end if - else - - ! Check if tally is on a single nuclide - if (i_nuclide >= 0) then - - ! Check if the delayed group filter is present - if (dg_filter > 0) then - select type(filt => filters(t % filter(dg_filter) + 1) % obj) - type is (DelayedGroupFilter) - - ! Loop over all delayed group bins and tally to them - ! individually - do d_bin = 1, filt % n_bins - - ! Get the delayed group for this bin - d = filt % groups(d_bin) - - ! Compute the yield for this delayed group - yield = nuclides(i_nuclide+1) % nu(E, EMISSION_DELAYED, d) - - ! Compute the score and tally to bin - score = micro_xs(i_nuclide+1) % fission * yield * & - atom_density * flux - call score_fission_delayed_dg(i_tally, d_bin, score, score_index) - end do - cycle SCORE_LOOP - end select - else - - ! If the delayed group filter is not present, compute the score - ! by multiplying the delayed-nu-fission macro xs by the flux - score = micro_xs(i_nuclide+1) % fission * nuclides(i_nuclide+1) % & - nu(E, EMISSION_DELAYED) * atom_density * flux - end if - - ! Tally is on total nuclides - else - - ! Check if the delayed group filter is present - if (dg_filter > 0) then - select type(filt => filters(t % filter(dg_filter) + 1) % obj) - type is (DelayedGroupFilter) - - ! Loop over all nuclides in the current material - if (p % material /= MATERIAL_VOID) then - do l = 1, material_nuclide_size(p % material) - - ! Get atom density - atom_density_ = material_atom_density(p % material, l) - - ! Get index in nuclides array - i_nuc = material_nuclide(p % material, l) - - ! Loop over all delayed group bins and tally to them - ! individually - do d_bin = 1, filt % n_bins - - ! Get the delayed group for this bin - d = filt % groups(d_bin) - - ! Get the yield for the desired nuclide and delayed group - yield = nuclides(i_nuc) % nu(E, EMISSION_DELAYED, d) - - ! Compute the score and tally to bin - score = micro_xs(i_nuc) % fission * yield & - * atom_density_ * flux - call score_fission_delayed_dg(i_tally, d_bin, score, & - score_index) - end do - end do - end if - cycle SCORE_LOOP - end select - else - - score = ZERO - - ! Loop over all nuclides in the current material - if (p % material /= MATERIAL_VOID) then - do l = 1, material_nuclide_size(p % material) - - ! Get atom density - atom_density_ = material_atom_density(p % material, l) - - ! Get index in nuclides array - i_nuc = material_nuclide(p % material, l) - - ! Accumulate the contribution from each nuclide - score = score + micro_xs(i_nuc) % fission * nuclides(i_nuc) %& - nu(E, EMISSION_DELAYED) * atom_density_ * flux - end do - end if - end if - end if - end if + cycle SCORE_LOOP case (SCORE_DECAY_RATE) diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index ea948ae401..6e205daff2 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -7,6 +7,7 @@ #include "openmc/message_passing.h" #include "openmc/mgxs_interface.h" #include "openmc/nuclide.h" +#include "openmc/reaction_product.h" #include "openmc/settings.h" #include "openmc/simulation.h" #include "openmc/tallies/derivative.h" @@ -51,6 +52,8 @@ apply_derivative_to_score(Particle* p, int i_tally, int i_nuclide, extern "C" int energy_filter_search(const EnergyFilter* filt, double val); +extern "C" double get_precursor_decay_rate(int i_nuclide, int d); + //============================================================================== // Global variable definitions //============================================================================== @@ -814,7 +817,7 @@ score_fission_eout(Particle* p, int i_tally, int i_score, int score_bin) filter_weight *= match.weights_[i_bin-1]; } - score_fission_delayed_dg(i_tally, d_bin+1, score*filter_weight, + score_fission_delayed_dg(i_tally, d_bin+1, score*filter_weight, i_score); } } @@ -844,6 +847,461 @@ score_fission_eout(Particle* p, int i_tally, int i_score, int score_bin) simulation::filter_matches[i_eout_filt].bins_[i_bin-1] = bin_energyout; } +extern "C" void +score_general_ce_c(Particle* p, int i_tally, int start_index, int filter_index, + int i_nuclide, double atom_density, double flux) +{ + //TODO: off-by-one + const Tally& tally {*model::tallies[i_tally-1]}; + auto results = tally_results(i_tally); + + // Get the pre-collision energy of the particle. + auto E = p->last_E; + + for (auto i = 0; i < tally.scores_.size(); ++i) { + auto score_bin = tally.scores_[i]; + auto score_index = start_index + i; + + double score; + + //TODO: off-by-one throughout on p->event_nuclide + //TODO: off-by-one throughout on score_index in calls to score_fission_eout + //TODO: off-by-one throughout on p->material + + switch (score_bin) { + + + case SCORE_FLUX: + if (tally.estimator_ == ESTIMATOR_ANALOG) { + // All events score to a flux bin. We actually use a collision estimator + // in place of an analog one since there is no way to count 'events' + // exactly for the flux + if (settings::survival_biasing) { + // We need to account for the fact that some weight was already + // absorbed + score = p->last_wgt + p->absorb_wgt; + } else { + score = p->last_wgt; + } + + if (p->type == static_cast(ParticleType::neutron) || + p->type == static_cast(ParticleType::photon)) { + score *= flux / simulation::material_xs.total; + } else { + score = 0.; + } + } else { + score = flux; + } + break; + + + case SCORE_TOTAL: + if (tally.estimator_ == ESTIMATOR_ANALOG) { + // All events will score to the total reaction rate. We can just use + // use the weight of the particle entering the collision as the score + if (settings::survival_biasing) { + // We need to account for the fact that some weight was already + // absorbed + score = (p->last_wgt + p->absorb_wgt) * flux; + } else { + score = p->last_wgt * flux; + } + + } else { + if (i_nuclide >= 0) { + score = simulation::micro_xs[i_nuclide].total * atom_density * flux; + } else { + score = simulation::material_xs.total * flux; + } + } + break; + + + case SCORE_INVERSE_VELOCITY: + if (tally.estimator_ == ESTIMATOR_ANALOG) { + // All events score to an inverse velocity bin. We actually use a + // collision estimator in place of an analog one since there is no way + // to count 'events' exactly for the inverse velocity + if (settings::survival_biasing) { + // We need to account for the fact that some weight was already + // absorbed + score = (p->last_wgt + p->absorb_wgt); + } else { + score = p->last_wgt; + } + score *= flux / simulation::material_xs.total; + } else { + score = flux; + } + // Score inverse velocity in units of s/cm. + score /= std::sqrt(2. * E / MASS_NEUTRON_EV) * C_LIGHT * 100.; + break; + + + case SCORE_SCATTER: + if (tally.estimator_ == ESTIMATOR_ANALOG) { + // Skip any event where the particle didn't scatter + if (p->event != EVENT_SCATTER) continue; + // Since only scattering events make it here, again we can use the + // weight entering the collision as the estimator for the reaction rate + score = p->last_wgt * flux; + } else { + if (i_nuclide >= 0) { + score = (simulation::micro_xs[i_nuclide].total + - simulation::micro_xs[i_nuclide].absorption) * atom_density * flux; + } else { + score = (simulation::material_xs.total + - simulation::material_xs.absorption) * flux; + } + } + break; + + + case SCORE_NU_SCATTER: + // Only analog estimators are available. + // Skip any event where the particle didn't scatter + if (p->event != EVENT_SCATTER) continue; + // For scattering production, we need to use the pre-collision weight + // times the yield as the estimate for the number of neutrons exiting a + // reaction with neutrons in the exit channel + if (p->event_MT == ELASTIC || p->event_MT == N_LEVEL || + (p->event_MT >= N_N1 && p->event_MT <= N_NC)) { + // Don't waste time on very common reactions we know have + // multiplicities of one. + score = p->last_wgt * flux; + } else { + // Get yield and apply to score + auto m = + data::nuclides[p->event_nuclide-1]->reaction_index_[p->event_MT]; + const auto& rxn {*data::nuclides[p->event_nuclide-1]->reactions_[m]}; + score = p->last_wgt * flux * (*rxn.products_[0].yield_)(E); + } + break; + + + case SCORE_ABSORPTION: + if (tally.estimator_ == ESTIMATOR_ANALOG) { + if (settings::survival_biasing) { + // No absorption events actually occur if survival biasing is on -- + // just use weight absorbed in survival biasing + score = p->absorb_wgt * flux; + } else { + // Skip any event where the particle wasn't absorbed + if (p->event == EVENT_SCATTER) continue; + // All fission and absorption events will contribute here, so we + // can just use the particle's weight entering the collision + score = p->last_wgt * flux; + } + } else { + if (i_nuclide >= 0) { + score = simulation::micro_xs[i_nuclide].absorption * atom_density + * flux; + } else { + score = simulation::material_xs.absorption * flux; + } + } + break; + + + case SCORE_FISSION: + if (simulation::material_xs.absorption == 0) continue; + if (tally.estimator_ == ESTIMATOR_ANALOG) { + if (settings::survival_biasing) { + // No fission events occur if survival biasing is on -- need to + // calculate fraction of absorptions that would have resulted in + // fission + if (simulation::micro_xs[p->event_nuclide-1].absorption > 0) { + score = p->absorb_wgt + * simulation::micro_xs[p->event_nuclide-1].fission + / simulation::micro_xs[p->event_nuclide-1].absorption * flux; + } else { + score = 0.; + } + } else { + // Skip any non-absorption events + if (p->event == EVENT_SCATTER) continue; + // All fission events will contribute, so again we can use particle's + // weight entering the collision as the estimate for the fission + // reaction rate + score = p->last_wgt + * simulation::micro_xs[p->event_nuclide-1].fission + / simulation::micro_xs[p->event_nuclide-1].absorption * flux; + } + } else { + if (i_nuclide >= 0) { + score = simulation::micro_xs[i_nuclide].fission * atom_density * flux; + } else { + score = simulation::material_xs.fission * flux; + } + } + break; + + + case SCORE_NU_FISSION: + if (simulation::material_xs.absorption == 0) continue; + if (tally.estimator_ == ESTIMATOR_ANALOG) { + if (settings::survival_biasing || p->fission) { + if (tally.energyout_filter_ > 0) { + // Fission has multiple outgoing neutrons so this helper function + // is used to handle scoring the multiple filter bins. + score_fission_eout(p, i_tally, score_index+1, score_bin); + continue; + } + } + if (settings::survival_biasing) { + // No fission events occur if survival biasing is on -- need to + // calculate fraction of absorptions that would have resulted in + // nu-fission + if (simulation::micro_xs[p->event_nuclide-1].absorption > 0) { + score = p->absorb_wgt + * simulation::micro_xs[p->event_nuclide-1].nu_fission + / simulation::micro_xs[p->event_nuclide-1].absorption * flux; + } else { + score = 0.; + } + } else { + // Skip any non-fission events + if (!p->fission) continue; + // If there is no outgoing energy filter, than we only need to score + // to one bin. For the score to be 'analog', we need to score the + // number of particles that were banked in the fission bank. Since + // this was weighted by 1/keff, we multiply by keff to get the proper + // score. + score = simulation::keff * p->wgt_bank * flux; + } + } else { + if (i_nuclide >= 0) { + score = simulation::micro_xs[i_nuclide].nu_fission * atom_density + * flux; + } else { + score = simulation::material_xs.nu_fission * flux; + } + } + break; + + + case SCORE_PROMPT_NU_FISSION: + if (simulation::material_xs.absorption == 0) continue; + if (tally.estimator_ == ESTIMATOR_ANALOG) { + if (settings::survival_biasing || p->fission) { + if (tally.energyout_filter_ > 0) { + // Fission has multiple outgoing neutrons so this helper function + // is used to handle scoring the multiple filter bins. + score_fission_eout(p, i_tally, score_index+1, score_bin); + continue; + } + } + if (settings::survival_biasing) { + // No fission events occur if survival biasing is on -- need to + // calculate fraction of absorptions that would have resulted in + // prompt-nu-fission + if (simulation::micro_xs[p->event_nuclide-1].absorption > 0) { + score = p->absorb_wgt + * simulation::micro_xs[p->event_nuclide-1].fission + * data::nuclides[p->event_nuclide-1] + ->nu(E, ReactionProduct::EmissionMode::prompt) + / simulation::micro_xs[p->event_nuclide-1].absorption * flux; + } else { + score = 0.; + } + } else { + // Skip any non-fission events + if (!p->fission) continue; + // If there is no outgoing energy filter, than we only need to score + // to one bin. For the score to be 'analog', we need to score the + // number of particles that were banked in the fission bank. Since + // this was weighted by 1/keff, we multiply by keff to get the proper + // score. + auto n_delayed = std::accumulate(p->n_delayed_bank, + p->n_delayed_bank+MAX_DELAYED_GROUPS, 0); + auto prompt_frac = 1. - n_delayed / static_cast(p->n_bank); + score = simulation::keff * p->wgt_bank * prompt_frac * flux; + } + } else { + if (i_nuclide >= 0) { + score = simulation::micro_xs[i_nuclide].fission + * data::nuclides[i_nuclide] + ->nu(E, ReactionProduct::EmissionMode::prompt) + * atom_density * flux; + } else { + score = 0.; + // Add up contributions from each nuclide in the material. + if (p->material != MATERIAL_VOID) { + const Material& material {*model::materials[p->material-1]}; + for (auto i = 0; i < material.nuclide_.size(); ++i) { + auto j_nuclide = material.nuclide_[i]; + auto atom_density = material.atom_density_(i); + score += simulation::micro_xs[j_nuclide].fission + * data::nuclides[j_nuclide] + ->nu(E, ReactionProduct::EmissionMode::prompt) + * atom_density * flux; + } + } + } + } + break; + + + case SCORE_DELAYED_NU_FISSION: + if (simulation::material_xs.absorption == 0) continue; + if (tally.estimator_ == ESTIMATOR_ANALOG) { + if (settings::survival_biasing || p->fission) { + if (tally.energyout_filter_ > 0) { + // Fission has multiple outgoing neutrons so this helper function + // is used to handle scoring the multiple filter bins. + score_fission_eout(p, i_tally, score_index+1, score_bin); + continue; + } + } + if (settings::survival_biasing) { + // No fission events occur if survival biasing is on -- need to + // calculate fraction of absorptions that would have resulted in + // delayed-nu-fission + if (simulation::micro_xs[p->event_nuclide-1].absorption > 0 + && data::nuclides[p->event_nuclide-1]->fissionable_) { + if (tally.delayedgroup_filter_ > 0) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + const DelayedGroupFilter& filt + {*dynamic_cast( + model::tally_filters[i_dg_filt].get())}; + // Tally each delayed group bin individually + for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { + auto d = filt.groups_[d_bin]; + auto yield = data::nuclides[p->event_nuclide-1] + ->nu(E, ReactionProduct::EmissionMode::delayed, d); + score = p->absorb_wgt * yield + * simulation::micro_xs[p->event_nuclide-1].fission + / simulation::micro_xs[p->event_nuclide-1].absorption * flux; + score_fission_delayed_dg(i_tally, d_bin+1, score, + score_index+1); + } + continue; + } else { + // If the delayed group filter is not present, compute the score + // by multiplying the absorbed weight by the fraction of the + // delayed-nu-fission xs to the absorption xs + score = p->absorb_wgt + * simulation::micro_xs[p->event_nuclide-1].fission + * data::nuclides[p->event_nuclide-1] + ->nu(E, ReactionProduct::EmissionMode::delayed) + / simulation::micro_xs[p->event_nuclide-1].absorption *flux; + } + } + } else { + // Skip any non-fission events + if (!p->fission) continue; + // If there is no outgoing energy filter, than we only need to score + // to one bin. For the score to be 'analog', we need to score the + // number of particles that were banked in the fission bank. Since + // this was weighted by 1/keff, we multiply by keff to get the proper + // score. Loop over the neutrons produced from fission and check which + // ones are delayed. If a delayed neutron is encountered, add its + // contribution to the fission bank to the score. + if (tally.delayedgroup_filter_ > 0) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + const DelayedGroupFilter& filt + {*dynamic_cast( + model::tally_filters[i_dg_filt].get())}; + // Tally each delayed group bin individually + for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { + auto d = filt.groups_[d_bin]; + score = simulation::keff * p->wgt_bank / p->n_bank + * p->n_delayed_bank[d-1] * flux; + score_fission_delayed_dg(i_tally, d_bin+1, score, score_index+1); + } + continue; + } else { + // Add the contribution from all delayed groups + auto n_delayed = std::accumulate(p->n_delayed_bank, + p->n_delayed_bank+MAX_DELAYED_GROUPS, 0); + score = simulation::keff * p->wgt_bank / p->n_bank * n_delayed + * flux; + } + } + } else { + if (i_nuclide >= 0) { + if (tally.delayedgroup_filter_ > 0) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + const DelayedGroupFilter& filt + {*dynamic_cast( + model::tally_filters[i_dg_filt].get())}; + // Tally each delayed group bin individually + for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { + auto d = filt.groups_[d_bin]; + auto yield = data::nuclides[i_nuclide] + ->nu(E, ReactionProduct::EmissionMode::delayed, d); + score = simulation::micro_xs[i_nuclide].fission * yield + * atom_density * flux; + score_fission_delayed_dg(i_tally, d_bin+1, score, score_index+1); + } + continue; + } else { + // If the delayed group filter is not present, compute the score + // by multiplying the delayed-nu-fission macro xs by the flux + score = simulation::micro_xs[i_nuclide].fission + * data::nuclides[i_nuclide] + ->nu(E, ReactionProduct::EmissionMode::delayed) + * atom_density * flux; + } + } else { + if (tally.delayedgroup_filter_ > 0) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + const DelayedGroupFilter& filt + {*dynamic_cast( + model::tally_filters[i_dg_filt].get())}; + if (p->material != MATERIAL_VOID) { + const Material& material {*model::materials[p->material-1]}; + for (auto i = 0; i < material.nuclide_.size(); ++i) { + auto j_nuclide = material.nuclide_[i]; + auto atom_density = material.atom_density_(i); + // Tally each delayed group bin individually + for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { + auto d = filt.groups_[d_bin]; + auto yield = data::nuclides[j_nuclide] + ->nu(E, ReactionProduct::EmissionMode::delayed, d); + score = simulation::micro_xs[j_nuclide].fission * yield + * atom_density * flux; + score_fission_delayed_dg(i_tally, d_bin+1, score, + score_index+1); + } + } + } + continue; + } else { + score = 0.; + if (p->material != MATERIAL_VOID) { + const Material& material {*model::materials[p->material-1]}; + for (auto i = 0; i < material.nuclide_.size(); ++i) { + auto j_nuclide = material.nuclide_[i]; + auto atom_density = material.atom_density_(i); + score += simulation::micro_xs[j_nuclide].fission + * data::nuclides[j_nuclide] + ->nu(E, ReactionProduct::EmissionMode::delayed) + * atom_density * flux; + } + } + } + } + } + break; + + + default: + continue; + } + + // Add derivative information on score for differnetial tallies. + if (tally.deriv_ != C_NONE) + apply_derivative_to_score(p, i_tally, i_nuclide, atom_density, score_bin, + &score); + + // Update tally results + #pragma omp atomic + results(filter_index-1, score_index, RESULT_VALUE) += score; + } +} + //! Tally rates for when the user requests a tally on all nuclides. void From 1c1fa6d753f187ded9209f208e9bb7342e721adc Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Wed, 6 Feb 2019 21:03:57 -0500 Subject: [PATCH 37/58] Finish moving score_general_ce to C++ --- src/nuclide.cpp | 2 +- src/tallies/tally.F90 | 709 +----------------------------------------- src/tallies/tally.cpp | 455 ++++++++++++++++++++++++++- 3 files changed, 462 insertions(+), 704 deletions(-) diff --git a/src/nuclide.cpp b/src/nuclide.cpp index b64a1681ab..b507b9130e 100644 --- a/src/nuclide.cpp +++ b/src/nuclide.cpp @@ -274,7 +274,7 @@ void Nuclide::create_derived() xs_.emplace_back(shape, 0.0); } - reaction_index_.fill(-1); + reaction_index_.fill(C_NONE); for (int i = 0; i < reactions_.size(); ++i) { const auto& rx {reactions_[i]}; diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index cc3b169e42..57560910aa 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -47,16 +47,16 @@ module tally end interface interface - subroutine score_general_ce_c(p, i_tally, start_index, filter_index, & + subroutine score_general_ce(p, i_tally, start_index, filter_index, & i_nuclide, atom_density, flux) bind(C) import Particle, C_INT, C_DOUBLE - type(Particle) :: p - integer(C_INT), value :: i_tally - integer(C_INT), value :: start_index - integer(C_INT), value :: filter_index - integer(C_INT), value :: i_nuclide - real(C_DOUBLE), value :: atom_density - real(C_DOUBLE), value :: flux + type(Particle), intent(in) :: p + integer(C_INT), intent(in), value :: i_tally + integer(C_INT), intent(in), value :: start_index + integer(C_INT), intent(in), value :: filter_index + integer(C_INT), intent(in), value :: i_nuclide + real(C_DOUBLE), intent(in), value :: atom_density + real(C_DOUBLE), intent(in), value :: flux end subroutine subroutine score_fission_delayed_dg(i_tally, d_bin, score, score_index) bind(C) @@ -135,699 +135,6 @@ contains ! analog tallies. !=============================================================================== - subroutine score_general_ce(p, i_tally, start_index, filter_index, i_nuclide, & - atom_density, flux) bind(C) - type(Particle), intent(in) :: p - integer(C_INT), intent(in), value :: i_tally - integer(C_INT), intent(in), value :: start_index - integer(C_INT), intent(in), value :: i_nuclide - integer(C_INT), intent(in), value :: filter_index ! for % results - real(C_DOUBLE), intent(in), value :: flux ! flux estimate - real(C_DOUBLE), intent(in), value :: atom_density ! atom/b-cm - - integer :: i ! loop index for scoring bins - integer :: l ! loop index for nuclides in material - integer :: m ! loop index for reactions - integer :: i_temp ! temperature index - integer :: i_nuc ! index in nuclides array (from material) - integer :: i_energy ! index in nuclide energy grid - integer :: score_bin ! scoring bin, e.g. SCORE_FLUX - integer :: score_index ! scoring bin index - integer :: d ! delayed neutron index - integer :: g ! delayed neutron index - integer :: k ! loop index for bank sites - integer :: d_bin ! delayed group bin index - integer :: dg_filter ! index of delayed group filter - integer :: threshold ! threshold energy index - real(8) :: yield ! delayed neutron yield - real(8) :: atom_density_ ! atom/b-cm - real(8) :: f ! interpolation factor - real(8) :: score ! analog tally score - real(8) :: E ! particle energy - real(8) :: xs ! cross section - - call score_general_ce_c(p, i_tally, start_index, filter_index, & - i_nuclide, atom_density, flux) - - associate (t => tallies(i_tally) % obj) - - ! Pre-collision energy of particle - E = p % last_E - - SCORE_LOOP: do i = 1, t % n_score_bins() - - ! determine what type of score bin - score_bin = t % score_bins(i) - - ! determine scoring bin index - score_index = start_index + i - - !######################################################################### - ! Determine appropirate scoring value. - - select case(score_bin) - - - case (SCORE_FLUX) - cycle SCORE_LOOP - - - case (SCORE_TOTAL) - cycle SCORE_LOOP - - - case (SCORE_INVERSE_VELOCITY) - cycle SCORE_LOOP - - - case (SCORE_SCATTER) - cycle SCORE_LOOP - - - case (SCORE_NU_SCATTER) - cycle SCORE_LOOP - - - case (SCORE_ABSORPTION) - cycle SCORE_LOOP - - - case (SCORE_FISSION) - cycle SCORE_LOOP - - - case (SCORE_NU_FISSION) - cycle SCORE_LOOP - - - case (SCORE_PROMPT_NU_FISSION) - cycle SCORE_LOOP - - case (SCORE_DELAYED_NU_FISSION) - cycle SCORE_LOOP - - case (SCORE_DECAY_RATE) - - if (material_xs % absorption == ZERO) cycle SCORE_LOOP - - ! Set the delayedgroup filter index - dg_filter = t % delayedgroup_filter() - - if (t % estimator() == ESTIMATOR_ANALOG) then - if (survival_biasing) then - ! No fission events occur if survival biasing is on -- need to - ! calculate fraction of absorptions that would have resulted in - ! delayed-nu-fission - if (micro_xs(p % event_nuclide) % absorption > ZERO .and. & - nuclides(p % event_nuclide) % fissionable) then - - ! Check if the delayed group filter is present - if (dg_filter > 0) then - select type(filt => filters(t % filter(dg_filter) + 1) % obj) - type is (DelayedGroupFilter) - - ! Loop over all delayed group bins and tally to them - ! individually - do d_bin = 1, filt % n_bins - - ! Get the delayed group for this bin - d = filt % groups(d_bin) - - ! Compute the yield for this delayed group - yield = nuclides(p % event_nuclide) & - % nu(E, EMISSION_DELAYED, d) - - associate (rxn => nuclides(p % event_nuclide) % & - reactions(nuclides(p % event_nuclide) % & - index_fission(1))) - - ! Compute the score - score = p % absorb_wgt * yield * & - micro_xs(p % event_nuclide) % fission & - / micro_xs(p % event_nuclide) % absorption & - * rxn % product_decay_rate(1 + d) * flux - end associate - - ! Tally to bin - call score_fission_delayed_dg(i_tally, d_bin, score, score_index) - end do - cycle SCORE_LOOP - end select - else - - ! If the delayed group filter is not present, compute the score - ! by accumulating the absorbed weight times the decay rate times - ! the fraction of the delayed-nu-fission xs to the absorption xs - ! for all delayed groups. - score = ZERO - - associate (rxn => nuclides(p % event_nuclide) % & - reactions(nuclides(p % event_nuclide) % index_fission(1))) - - ! We need to be careful not to overshoot the number of delayed - ! groups since this could cause the range of the - ! rxn % products array to be exceeded. Hence, we use the size - ! of this array and not the MAX_DELAYED_GROUPS constant for - ! this loop. - do d = 1, rxn % products_size() - 2 - score = score + rxn % product_decay_rate(1 + d) * & - p % absorb_wgt & - * micro_xs(p % event_nuclide) % fission & - * nuclides(p % event_nuclide) % & - nu(E, EMISSION_DELAYED, d) & - / micro_xs(p % event_nuclide) % absorption * flux - end do - end associate - end if - end if - else - - ! Skip any non-fission events - if (.not. p % fission) cycle SCORE_LOOP - ! If there is no outgoing energy filter, than we only need to - ! score to one bin. For the score to be 'analog', we need to - ! score the number of particles that were banked in the fission - ! bank. Since this was weighted by 1/keff, we multiply by keff - ! to get the proper score. Loop over the neutrons produced from - ! fission and check which ones are delayed. If a delayed neutron is - ! encountered, add its contribution to the fission bank to the - ! score. - - score = ZERO - - ! loop over number of particles banked - do k = 1, p % n_bank - - ! get the delayed group - g = fission_bank_delayed_group(n_bank - p % n_bank + k) - - ! Case for tallying delayed emissions - if (g /= 0) then - - ! Accumulate the decay rate times delayed nu fission score - associate (rxn => nuclides(p % event_nuclide) % & - reactions(nuclides(p % event_nuclide) % index_fission(1))) - - ! determine score based on bank site weight and keff. - score = score + keff * fission_bank_wgt(n_bank - p % n_bank + k) & - * rxn % product_decay_rate(1 + g) * flux - end associate - - ! if the delayed group filter is present, tally to corresponding - ! delayed group bin if it exists - if (dg_filter > 0) then - - ! declare the delayed group filter type - select type(filt => filters(t % filter(dg_filter) + 1) % obj) - type is (DelayedGroupFilter) - - ! loop over delayed group bins until the corresponding bin - ! is found - do d_bin = 1, filt % n_bins - d = filt % groups(d_bin) - - ! check whether the delayed group of the particle is equal - ! to the delayed group of this bin - if (d == g) then - call score_fission_delayed_dg(i_tally, d_bin, score, & - score_index) - end if - end do - end select - - ! Reset the score to zero - score = ZERO - end if - end if - end do - end if - else - - ! Check if tally is on a single nuclide - if (i_nuclide >= 0) then - - ! Check if the delayed group filter is present - if (dg_filter > 0) then - select type(filt => filters(t % filter(dg_filter) + 1) % obj) - type is (DelayedGroupFilter) - - ! Loop over all delayed group bins and tally to them - ! individually - do d_bin = 1, filt % n_bins - - ! Get the delayed group for this bin - d = filt % groups(d_bin) - - ! Compute the yield for this delayed group - yield = nuclides(i_nuclide+1) % nu(E, EMISSION_DELAYED, d) - - associate (rxn => nuclides(i_nuclide+1) % & - reactions(nuclides(i_nuclide+1) % index_fission(1))) - - ! Compute the score and tally to bin - score = micro_xs(i_nuclide+1) % fission * yield * flux * & - atom_density * rxn % product_decay_rate(1 + d) - end associate - - ! Tally to bin - call score_fission_delayed_dg(i_tally, d_bin, score, score_index) - end do - cycle SCORE_LOOP - end select - else - - ! If the delayed group filter is not present, compute the score - ! by accumulating the absorbed weight times the decay rate times - ! the fraction of the delayed-nu-fission xs to the absorption xs - ! for all delayed groups. - score = ZERO - - associate (rxn => nuclides(p % event_nuclide) % & - reactions(nuclides(p % event_nuclide) % index_fission(1))) - - ! We need to be careful not to overshoot the number of delayed - ! groups since this could cause the range of the rxn % products - ! array to be exceeded. Hence, we use the size of this array - ! and not the MAX_DELAYED_GROUPS constant for this loop. - do d = 1, rxn % products_size() - 2 - score = score + micro_xs(i_nuclide+1) % fission * flux * & - nuclides(i_nuclide+1) % nu(E, EMISSION_DELAYED) * & - atom_density * rxn % product_decay_rate(1 + d) - end do - end associate - end if - - ! Tally is on total nuclides - else - - ! Check if the delayed group filter is present - if (dg_filter > 0) then - select type(filt => filters(t % filter(dg_filter) + 1) % obj) - type is (DelayedGroupFilter) - - ! Loop over all nuclides in the current material - if (p % material /= MATERIAL_VOID) then - do l = 1, material_nuclide_size(p % material) - - ! Get atom density - atom_density_ = material_atom_density(p % material, l) - - ! Get index in nuclides array - i_nuc = material_nuclide(p % material, l) - - if (nuclides(i_nuc) % fissionable) then - - ! Loop over all delayed group bins and tally to them - ! individually - do d_bin = 1, filt % n_bins - - ! Get the delayed group for this bin - d = filt % groups(d_bin) - - ! Get the yield for the desired nuclide and delayed - ! group - yield = nuclides(i_nuc) % nu(E, EMISSION_DELAYED, d) - - associate (rxn => nuclides(i_nuc) % & - reactions(nuclides(i_nuc) % index_fission(1))) - - ! Compute the score - score = micro_xs(i_nuc) % fission * yield * flux * & - atom_density_ & - * rxn % product_decay_rate(1 + d) - end associate - - ! Tally to bin - call score_fission_delayed_dg(i_tally, d_bin, score, & - score_index) - end do - end if - end do - end if - cycle SCORE_LOOP - end select - else - - score = ZERO - - ! Loop over all nuclides in the current material - if (p % material /= MATERIAL_VOID) then - do l = 1, material_nuclide_size(p % material) - - ! Get atom density - atom_density_ = material_atom_density(p % material, l) - - ! Get index in nuclides array - i_nuc = material_nuclide(p % material, l) - - if (nuclides(i_nuc) % fissionable) then - - associate (rxn => nuclides(i_nuc) % & - reactions(nuclides(i_nuc) % index_fission(1))) - - ! We need to be careful not to overshoot the number of - ! delayed groups since this could cause the range of the - ! rxn % products array to be exceeded. Hence, we use the - ! size of this array and not the MAX_DELAYED_GROUPS - ! constant for this loop. - do d = 1, rxn % products_size() - 2 - - ! Accumulate the contribution from each nuclide - score = score + micro_xs(i_nuc) % fission & - * nuclides(i_nuc) % nu(E, EMISSION_DELAYED) & - * atom_density_ * flux & - * rxn % product_decay_rate(1 + d) - end do - end associate - end if - end do - end if - end if - end if - end if - - case (SCORE_KAPPA_FISSION) - - if (material_xs % absorption == ZERO) cycle SCORE_LOOP - - ! Determine kappa-fission cross section on the fly. The ENDF standard - ! (ENDF-102) states that MT 18 stores the fission energy as the Q_value - ! (fission(1)) - - score = ZERO - - if (t % estimator() == ESTIMATOR_ANALOG) then - if (survival_biasing) then - ! No fission events occur if survival biasing is on -- need to - ! calculate fraction of absorptions that would have resulted in - ! fission scaled by kappa-fission - associate (nuc => nuclides(p % event_nuclide)) - if (micro_xs(p % event_nuclide) % absorption > ZERO .and. & - nuc % fissionable) then - score = p % absorb_wgt * & - nuc % reactions(nuc % index_fission(1)) % Q_value * & - micro_xs(p % event_nuclide) % fission / & - micro_xs(p % event_nuclide) % absorption * flux - end if - end associate - else - ! Skip any non-absorption events - if (p % event == EVENT_SCATTER) cycle SCORE_LOOP - ! All fission events will contribute, so again we can use - ! particle's weight entering the collision as the estimate for - ! the fission energy production rate - associate (nuc => nuclides(p % event_nuclide)) - if (nuc % fissionable) then - score = p % last_wgt * & - nuc % reactions(nuc % index_fission(1)) % Q_value * & - micro_xs(p % event_nuclide) % fission / & - micro_xs(p % event_nuclide) % absorption * flux - end if - end associate - end if - - else - if (i_nuclide >= 0) then - associate (nuc => nuclides(i_nuclide+1)) - if (nuc % fissionable) then - score = nuc % reactions(nuc % index_fission(1)) % Q_value * & - micro_xs(i_nuclide+1) % fission * atom_density * flux - end if - end associate - else - if (p % material == MATERIAL_VOID) then - score = ZERO - else - do l = 1, material_nuclide_size(p % material) - ! Determine atom density and index of nuclide - atom_density_ = material_atom_density(p % material, l) - i_nuc = material_nuclide(p % material, l) - - ! If nuclide is fissionable, accumulate kappa fission - associate(nuc => nuclides(i_nuc)) - if (nuc % fissionable) then - score = score + & - nuc % reactions(nuc % index_fission(1)) % Q_value * & - micro_xs(i_nuc) % fission * atom_density_ * flux - end if - end associate - end do - end if - end if - end if - - case (SCORE_EVENTS) - ! Simply count number of scoring events - score = ONE - - case (ELASTIC) - if (t % estimator() == ESTIMATOR_ANALOG) then - ! Check if event MT matches - if (p % event_MT /= ELASTIC) cycle SCORE_LOOP - score = p % last_wgt * flux - - else - if (i_nuclide >= 0) then - if (micro_xs(i_nuclide+1) % elastic == CACHE_INVALID) then - call nuclides(i_nuclide+1) % calculate_elastic_xs() - end if - score = micro_xs(i_nuclide+1) % elastic * atom_density * flux - else - score = ZERO - if (p % material /= MATERIAL_VOID) then - do l = 1, material_nuclide_size(p % material) - ! Get atom density - atom_density_ = material_atom_density(p % material, l) - - ! Get index in nuclides array - i_nuc = material_nuclide(p % material, l) - if (micro_xs(i_nuc) % elastic == CACHE_INVALID) then - call nuclides(i_nuc) % calculate_elastic_xs() - end if - - score = score + micro_xs(i_nuc) % elastic * atom_density_ * flux - end do - end if - end if - end if - - case (SCORE_FISS_Q_PROMPT, SCORE_FISS_Q_RECOV) - - if (material_xs % absorption == ZERO) cycle SCORE_LOOP - - score = ZERO - - if (t % estimator() == ESTIMATOR_ANALOG) then - if (survival_biasing) then - ! No fission events occur if survival biasing is on -- need to - ! calculate fraction of absorptions that would have resulted in - ! fission scaled by Q-value - associate (nuc => nuclides(p % event_nuclide)) - if (micro_xs(p % event_nuclide) % absorption > ZERO) then - if (score_bin == SCORE_FISS_Q_PROMPT) then - xs = nuclide_fission_q_prompt(nuc % ptr, p % last_E) - else if (score_bin == SCORE_FISS_Q_RECOV) then - xs = nuclide_fission_q_recov(nuc % ptr, p % last_E) - end if - - score = p % absorb_wgt * xs * flux & - * micro_xs(p % event_nuclide) % fission & - / micro_xs(p % event_nuclide) % absorption - end if - end associate - else - ! Skip any non-absorption events - if (p % event == EVENT_SCATTER) cycle SCORE_LOOP - ! All fission events will contribute, so again we can use - ! particle's weight entering the collision as the estimate for - ! the fission energy production rate - associate (nuc => nuclides(p % event_nuclide)) - if (micro_xs(p % event_nuclide) % absorption > ZERO) then - if (score_bin == SCORE_FISS_Q_PROMPT) then - xs = nuclide_fission_q_prompt(nuc % ptr, p % last_E) - else if (score_bin == SCORE_FISS_Q_RECOV) then - xs = nuclide_fission_q_recov(nuc % ptr, p % last_E) - end if - - score = p % last_wgt * xs * flux & - * micro_xs(p % event_nuclide) % fission & - / micro_xs(p % event_nuclide) % absorption - end if - end associate - end if - - else - if (i_nuclide >= 0) then - associate (nuc => nuclides(i_nuclide+1)) - if (score_bin == SCORE_FISS_Q_PROMPT) then - xs = nuclide_fission_q_prompt(nuc % ptr, E) - else if (score_bin == SCORE_FISS_Q_RECOV) then - xs = nuclide_fission_q_recov(nuc % ptr, E) - end if - - score = micro_xs(i_nuclide+1) % fission * atom_density * flux * xs - end associate - else - if (p % material /= MATERIAL_VOID) then - do l = 1, material_nuclide_size(p % material) - atom_density_ = material_atom_density(p % material, l) - i_nuc = material_nuclide(p % material, l) - - associate (nuc => nuclides(i_nuc)) - if (score_bin == SCORE_FISS_Q_PROMPT) then - xs = nuclide_fission_q_prompt(nuc % ptr, E) - else if (score_bin == SCORE_FISS_Q_RECOV) then - xs = nuclide_fission_q_recov(nuc % ptr, E) - end if - - score = score + micro_xs(i_nuc) % fission * atom_density_ & - * flux * xs - end associate - end do - end if - end if - end if - - case (N_2N, N_3N, N_4N, N_GAMMA, N_P, N_A) - if (t % estimator() == ESTIMATOR_ANALOG) then - ! Check if event MT matches - if (p % event_MT /= score_bin) cycle SCORE_LOOP - score = p % last_wgt * flux - - else - ! Determine index in NuclideMicroXS % reaction array - select case (score_bin) - case (N_GAMMA) - m = 1 - case (N_P) - m = 2 - case (N_A) - m = 3 - case (N_2N) - m = 4 - case (N_3N) - m = 5 - case (N_4N) - m = 6 - end select - - if (i_nuclide >= 0) then - score = micro_xs(i_nuclide+1) % reaction(m) * atom_density * flux - else - score = ZERO - if (p % material /= MATERIAL_VOID) then - do l = 1, material_nuclide_size(p % material) - i_nuc = material_nuclide(p % material, l) - atom_density_ = material_atom_density(p % material, l) - score = score + micro_xs(i_nuc) % reaction(m) * atom_density_ * flux - end do - end if - end if - end if - - case default - if (t % estimator() == ESTIMATOR_ANALOG) then - ! Any other score is assumed to be a MT number. Thus, we just need - ! to check if it matches the MT number of the event - if (p % event_MT /= score_bin) cycle SCORE_LOOP - score = p % last_wgt * flux - - else - ! Any other cross section has to be calculated on-the-fly. For - ! cross sections that are used often (e.g. n2n, ngamma, etc. for - ! depletion), it might make sense to optimize this section or - ! pre-calculate cross sections - if (score_bin > 1) then - ! Set default score - score = ZERO - - if (i_nuclide >= 0) then - m = nuclides(i_nuclide+1) % reaction_index(score_bin) - if (m /= 0) then - ! Retrieve temperature and energy grid index and interpolation - ! factor - i_temp = micro_xs(i_nuclide+1) % index_temp + 1 - if (i_temp > 0) then - i_energy = micro_xs(i_nuclide+1) % index_grid - f = micro_xs(i_nuclide+1) % interp_factor - - associate (rx => nuclides(i_nuclide+1) % reactions(m)) - threshold = rx % xs_threshold(i_temp) - if (i_energy >= threshold) then - score = ((ONE - f) * rx % xs(i_temp, i_energy - & - threshold + 1) + f * rx % xs(i_temp, i_energy - & - threshold + 2)) * atom_density * flux - end if - end associate - else - ! This block is reached if multipole is turned on and we're in - ! the resolved range. Assume xs is zero. - score = ZERO - end if - end if - - else - if (p % material /= MATERIAL_VOID) then - do l = 1, material_nuclide_size(p % material) - ! Get atom density - atom_density_ = material_atom_density(p % material, l) - - ! Get index in nuclides array - i_nuc = material_nuclide(p % material, l) - - m = nuclides(i_nuc) % reaction_index(score_bin) - if (m /= 0) then - ! Retrieve temperature and energy grid index and - ! interpolation factor - i_temp = micro_xs(i_nuc) % index_temp + 1 - if (i_temp > 0) then - i_energy = micro_xs(i_nuc) % index_grid - f = micro_xs(i_nuc) % interp_factor - - associate (rx => nuclides(i_nuc) % reactions(m)) - threshold = rx % xs_threshold(i_temp) - if (i_energy >= threshold) then - score = score + ((ONE - f) * rx % xs(i_temp, i_energy - & - threshold + 1) + f * rx % xs(i_temp, i_energy - & - threshold + 2)) * atom_density_ * flux - end if - end associate - else - ! This block is reached if multipole is turned on and - ! we're in the resolved range. Assume xs is zero. - score = ZERO - end if - end if - end do - end if - end if - - else - call fatal_error("Invalid score type on tally " & - // to_str(t % id()) // ".") - end if - end if - - end select - - !######################################################################### - ! Add derivative information on score for differential tallies. - - if (t % deriv() /= C_NONE) then - call apply_derivative_to_score(p, i_tally, i_nuclide, atom_density, & - score_bin, score) - end if - - !######################################################################### - ! Expand score if necessary and add to tally results. -!$omp atomic - t % results(RESULT_VALUE, score_index, filter_index) = & - t % results(RESULT_VALUE, score_index, filter_index) + score - - end do SCORE_LOOP - end associate - end subroutine score_general_ce - subroutine score_general_mg(p, i_tally, start_index, filter_index, i_nuclide, & atom_density, flux) bind(C) type(Particle), intent(in) :: p diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 6e205daff2..b48fd46216 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -848,7 +848,7 @@ score_fission_eout(Particle* p, int i_tally, int i_score, int score_bin) } extern "C" void -score_general_ce_c(Particle* p, int i_tally, int start_index, int filter_index, +score_general_ce(Particle* p, int i_tally, int start_index, int filter_index, int i_nuclide, double atom_density, double flux) { //TODO: off-by-one @@ -1287,8 +1287,459 @@ score_general_ce_c(Particle* p, int i_tally, int start_index, int filter_index, break; + case SCORE_DECAY_RATE: + if (simulation::material_xs.absorption == 0) continue; + if (tally.estimator_ == ESTIMATOR_ANALOG) { + if (settings::survival_biasing) { + // No fission events occur if survival biasing is on -- need to + // calculate fraction of absorptions that would have resulted in + // delayed-nu-fission + const auto& nuc {*data::nuclides[p->event_nuclide-1]}; + if (simulation::micro_xs[p->event_nuclide-1].absorption > 0 + && nuc.fissionable_) { + const auto& rxn {*nuc.fission_rx_[0]}; + if (tally.delayedgroup_filter_ > 0) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + const DelayedGroupFilter& filt + {*dynamic_cast( + model::tally_filters[i_dg_filt].get())}; + // Tally each delayed group bin individually + for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { + auto d = filt.groups_[d_bin]; + auto yield + = nuc.nu(E, ReactionProduct::EmissionMode::delayed, d); + auto rate = rxn.products_[d].decay_rate_; + score = p->absorb_wgt * yield + * simulation::micro_xs[p->event_nuclide-1].fission + / simulation::micro_xs[p->event_nuclide-1].absorption + * rate * flux; + score_fission_delayed_dg(i_tally, d_bin+1, score, + score_index+1); + } + continue; + } else { + // If the delayed group filter is not present, compute the score + // by multiplying the absorbed weight by the fraction of the + // delayed-nu-fission xs to the absorption xs for all delayed + // groups + score = 0.; + // We need to be careful not to overshoot the number of + // delayed groups since this could cause the range of the + // rxn.products_ array to be exceeded. Hence, we use the size + // of this array and not the MAX_DELAYED_GROUPS constant for + // this loop. + for (auto d = 0; d < rxn.products_.size() - 2; ++d) + { + auto yield + = nuc.nu(E, ReactionProduct::EmissionMode::delayed, d+1); + auto rate = rxn.products_[d+1].decay_rate_; + score += rate * p->absorb_wgt + * simulation::micro_xs[p->event_nuclide-1].fission * yield + / simulation::micro_xs[p->event_nuclide-1].absorption * flux; + } + } + } + } else { + // Skip any non-fission events + if (!p->fission) continue; + // If there is no outgoing energy filter, than we only need to score + // to one bin. For the score to be 'analog', we need to score the + // number of particles that were banked in the fission bank. Since + // this was weighted by 1/keff, we multiply by keff to get the proper + // score. Loop over the neutrons produced from fission and check which + // ones are delayed. If a delayed neutron is encountered, add its + // contribution to the fission bank to the score. + score = 0.; + for (auto i = 0; i < p->n_bank; ++i) { + auto i_bank = simulation::n_bank - p->n_bank + i; + const auto& bank = simulation::fission_bank[i_bank]; + auto g = bank.delayed_group; + if (g != 0) { + const auto& nuc {*data::nuclides[p->event_nuclide-1]}; + const auto& rxn {*nuc.fission_rx_[0]}; + auto rate = rxn.products_[g].decay_rate_; + //score += simulation::keff * bank.wgt * rate * flux; + if (tally.delayedgroup_filter_ > 0) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + const DelayedGroupFilter& filt + {*dynamic_cast( + model::tally_filters[i_dg_filt].get())}; + // Find the corresponding filter bin and then score + for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { + auto d = filt.groups_[d_bin]; + if (d == g) + score_fission_delayed_dg(i_tally, d_bin+1, score, + score_index+1); + } + score = 0.; + } + } + } + } + } else { + if (i_nuclide >= 0) { + const auto& nuc {*data::nuclides[i_nuclide]}; + const auto& rxn {*nuc.fission_rx_[0]}; + if (tally.delayedgroup_filter_ > 0) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + const DelayedGroupFilter& filt + {*dynamic_cast( + model::tally_filters[i_dg_filt].get())}; + // Tally each delayed group bin individually + for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { + auto d = filt.groups_[d_bin]; + auto yield + = nuc.nu(E, ReactionProduct::EmissionMode::delayed, d); + auto rate = rxn.products_[d].decay_rate_; + score = simulation::micro_xs[i_nuclide].fission * yield * flux + * atom_density * rate; + score_fission_delayed_dg(i_tally, d_bin+1, score, score_index+1); + } + continue; + } else { + score = 0.; + // We need to be careful not to overshoot the number of + // delayed groups since this could cause the range of the + // rxn.products_ array to be exceeded. Hence, we use the size + // of this array and not the MAX_DELAYED_GROUPS constant for + // this loop. + for (auto d = 0; d < rxn.products_.size() - 2; ++d) + { + auto yield + = nuc.nu(E, ReactionProduct::EmissionMode::delayed, d+1); + auto rate = rxn.products_[d+1].decay_rate_; + score += simulation::micro_xs[i_nuclide].fission * flux + * yield * atom_density * rate; + } + } + } else { + if (tally.delayedgroup_filter_ > 0) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + const DelayedGroupFilter& filt + {*dynamic_cast( + model::tally_filters[i_dg_filt].get())}; + if (p->material != MATERIAL_VOID) { + const Material& material {*model::materials[p->material-1]}; + for (auto i = 0; i < material.nuclide_.size(); ++i) { + auto j_nuclide = material.nuclide_[i]; + auto atom_density = material.atom_density_(i); + const auto& nuc {*data::nuclides[j_nuclide]}; + if (nuc.fissionable_) { + const auto& rxn {*nuc.fission_rx_[0]}; + // Tally each delayed group bin individually + for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { + auto d = filt.groups_[d_bin]; + auto yield + = nuc.nu(E, ReactionProduct::EmissionMode::delayed, d); + auto rate = rxn.products_[d].decay_rate_; + score = simulation::micro_xs[j_nuclide].fission * yield + * flux * atom_density * rate; + score_fission_delayed_dg(i_tally, d_bin+1, score, + score_index+1); + } + } + } + } + continue; + } else { + score = 0.; + if (p->material != MATERIAL_VOID) { + const Material& material {*model::materials[p->material-1]}; + for (auto i = 0; i < material.nuclide_.size(); ++i) { + auto j_nuclide = material.nuclide_[i]; + auto atom_density = material.atom_density_(i); + const auto& nuc {*data::nuclides[j_nuclide]}; + if (nuc.fissionable_) { + const auto& rxn {*nuc.fission_rx_[0]}; + // We need to be careful not to overshoot the number of + // delayed groups since this could cause the range of the + // rxn.products_ array to be exceeded. Hence, we use the size + // of this array and not the MAX_DELAYED_GROUPS constant for + // this loop. + for (auto d = 0; d < rxn.products_.size() - 2; ++d) + { + auto yield + = nuc.nu(E, ReactionProduct::EmissionMode::delayed, d+1); + auto rate = rxn.products_[d+1].decay_rate_; + score += simulation::micro_xs[j_nuclide].fission + * yield * atom_density * flux * rate; + } + } + } + } + } + } + } + break; + + + case SCORE_KAPPA_FISSION: + if (simulation::material_xs.absorption == 0.) continue; + score = 0.; + // Kappa-fission values are determined from the Q-value listed for the + // fission cross section. + if (tally.estimator_ == ESTIMATOR_ANALOG) { + if (settings::survival_biasing) { + // No fission events occur if survival biasing is on -- need to + // calculate fraction of absorptions that would have resulted in + // fission scaled by the Q-value + const auto& nuc {*data::nuclides[p->event_nuclide-1]}; + if (simulation::micro_xs[p->event_nuclide-1].absorption > 0 + && nuc.fissionable_) { + const auto& rxn {*nuc.fission_rx_[0]}; + score = p->absorb_wgt * rxn.q_value_ + * simulation::micro_xs[p->event_nuclide-1].fission + / simulation::micro_xs[p->event_nuclide-1].absorption * flux; + } + } else { + // Skip any non-absorption events + if (p->event == EVENT_SCATTER) continue; + // All fission events will contribute, so again we can use particle's + // weight entering the collision as the estimate for the fission + // reaction rate + const auto& nuc {*data::nuclides[p->event_nuclide-1]}; + if (simulation::micro_xs[p->event_nuclide-1].absorption > 0 + && nuc.fissionable_) { + const auto& rxn {*nuc.fission_rx_[0]}; + score = p->last_wgt * rxn.q_value_ + * simulation::micro_xs[p->event_nuclide-1].fission + / simulation::micro_xs[p->event_nuclide-1].absorption * flux; + } + } + } else { + if (i_nuclide >= 0) { + const auto& nuc {*data::nuclides[i_nuclide]}; + if (nuc.fissionable_) { + const auto& rxn {*nuc.fission_rx_[0]}; + score = rxn.q_value_ * simulation::micro_xs[i_nuclide].fission + * atom_density * flux; + } + } else { + if (p->material != MATERIAL_VOID) { + const Material& material {*model::materials[p->material-1]}; + for (auto i = 0; i < material.nuclide_.size(); ++i) { + auto j_nuclide = material.nuclide_[i]; + auto atom_density = material.atom_density_(i); + const auto& nuc {*data::nuclides[j_nuclide]}; + if (nuc.fissionable_) { + const auto& rxn {*nuc.fission_rx_[0]}; + score += rxn.q_value_ * simulation::micro_xs[j_nuclide].fission + * atom_density * flux; + } + } + } + } + } + break; + + + case SCORE_EVENTS: + // Simply count the number of scoring events + score = 1.; + break; + + + case ELASTIC: + if (tally.estimator_ == ESTIMATOR_ANALOG) { + // Check if event MT matches + if (p->event_MT != ELASTIC) continue; + score = p->last_wgt * flux; + } else { + if (i_nuclide >= 0) { + if (simulation::micro_xs[i_nuclide].elastic == CACHE_INVALID) + data::nuclides[i_nuclide]->calculate_elastic_xs(); + score = simulation::micro_xs[i_nuclide].elastic * atom_density * flux; + } else { + score = 0.; + if (p->material != MATERIAL_VOID) { + const Material& material {*model::materials[p->material-1]}; + for (auto i = 0; i < material.nuclide_.size(); ++i) { + auto j_nuclide = material.nuclide_[i]; + auto atom_density = material.atom_density_(i); + if (simulation::micro_xs[j_nuclide].elastic == CACHE_INVALID) + data::nuclides[j_nuclide]->calculate_elastic_xs(); + score += simulation::micro_xs[j_nuclide].elastic * atom_density + * flux; + } + } + } + } + break; + + case SCORE_FISS_Q_PROMPT: + case SCORE_FISS_Q_RECOV: + //continue; + if (simulation::material_xs.absorption == 0.) continue; + score = 0.; + if (tally.estimator_ == ESTIMATOR_ANALOG) { + if (settings::survival_biasing) { + // No fission events occur if survival biasing is on -- need to + // calculate fraction of absorptions that would have resulted in + // fission scaled by the Q-value + const auto& nuc {*data::nuclides[p->event_nuclide-1]}; + if (simulation::micro_xs[p->event_nuclide-1].absorption > 0) { + double q_value = 0.; + if (score_bin == SCORE_FISS_Q_PROMPT) { + if (nuc.fission_q_prompt_) + q_value = (*nuc.fission_q_prompt_)(p->last_E); + } else if (score_bin == SCORE_FISS_Q_RECOV) { + if (nuc.fission_q_recov_) + q_value = (*nuc.fission_q_recov_)(p->last_E); + } + score = p->absorb_wgt * q_value + * simulation::micro_xs[p->event_nuclide-1].fission + / simulation::micro_xs[p->event_nuclide-1].absorption * flux; + } + } else { + // Skip any non-absorption events + if (p->event == EVENT_SCATTER) continue; + // All fission events will contribute, so again we can use particle's + // weight entering the collision as the estimate for the fission + // reaction rate + const auto& nuc {*data::nuclides[p->event_nuclide-1]}; + if (simulation::micro_xs[p->event_nuclide-1].absorption > 0) { + double q_value = 0.; + if (score_bin == SCORE_FISS_Q_PROMPT) { + if (nuc.fission_q_prompt_) + q_value = (*nuc.fission_q_prompt_)(p->last_E); + } else if (score_bin == SCORE_FISS_Q_RECOV) { + if (nuc.fission_q_recov_) + q_value = (*nuc.fission_q_recov_)(p->last_E); + } + score = p->last_wgt * q_value + * simulation::micro_xs[p->event_nuclide-1].fission + / simulation::micro_xs[p->event_nuclide-1].absorption * flux; + } + } + } else { + if (i_nuclide >= 0) { + const auto& nuc {*data::nuclides[i_nuclide]}; + double q_value = 0.; + if (score_bin == SCORE_FISS_Q_PROMPT) { + if (nuc.fission_q_prompt_) + q_value = (*nuc.fission_q_prompt_)(p->last_E); + } else if (score_bin == SCORE_FISS_Q_RECOV) { + if (nuc.fission_q_recov_) + q_value = (*nuc.fission_q_recov_)(p->last_E); + } + score = q_value * simulation::micro_xs[i_nuclide].fission + * atom_density * flux; + } else { + if (p->material != MATERIAL_VOID) { + const Material& material {*model::materials[p->material-1]}; + for (auto i = 0; i < material.nuclide_.size(); ++i) { + auto j_nuclide = material.nuclide_[i]; + auto atom_density = material.atom_density_(i); + const auto& nuc {*data::nuclides[j_nuclide]}; + double q_value = 0.; + if (score_bin == SCORE_FISS_Q_PROMPT) { + if (nuc.fission_q_prompt_) + q_value = (*nuc.fission_q_prompt_)(p->last_E); + } else if (score_bin == SCORE_FISS_Q_RECOV) { + if (nuc.fission_q_recov_) + q_value = (*nuc.fission_q_recov_)(p->last_E); + } + score += q_value * simulation::micro_xs[j_nuclide].fission + * atom_density * flux; + } + } + } + } + break; + + + case N_2N: + case N_3N: + case N_4N: + case N_GAMMA: + case N_P: + case N_A: + if (tally.estimator_ == ESTIMATOR_ANALOG) { + // Check if the event MT matches + if (p->event_MT != score_bin) continue; + score = p->last_wgt * flux; + } else { + int m; + switch (score_bin) { + case N_GAMMA: m = 0; break; + case N_P: m = 1; break; + case N_A: m = 2; break; + case N_2N: m = 3; break; + case N_3N: m = 4; break; + case N_4N: m = 5; break; + } + if (i_nuclide >= 0) { + score = simulation::micro_xs[i_nuclide].reaction[m] * atom_density + * flux; + } else { + score = 0.; + if (p->material != MATERIAL_VOID) { + const Material& material {*model::materials[p->material-1]}; + for (auto i = 0; i < material.nuclide_.size(); ++i) { + auto j_nuclide = material.nuclide_[i]; + auto atom_density = material.atom_density_(i); + score += simulation::micro_xs[j_nuclide].reaction[m] + * atom_density * flux; + } + } + } + } + break; + + default: - continue; + if (tally.estimator_ == ESTIMATOR_ANALOG) { + // Any other score is assumed to be a MT number. Thus, we just need + // to check if it matches the MT number of the event + if (p->event_MT != score_bin) continue; + score = p->last_wgt*flux; + } else { + // Any other cross section has to be calculated on-the-fly + if (score_bin < 2) fatal_error("Invalid score type on tally " + + std::to_string(tally.id_)); + score = 0.; + if (i_nuclide >= 0) { + const auto& nuc {*data::nuclides[i_nuclide]}; + auto m = nuc.reaction_index_[score_bin]; + if (m == C_NONE) continue; + const auto& rxn {*nuc.reactions_[m]}; + auto i_temp = simulation::micro_xs[i_nuclide].index_temp; + if (i_temp >= 0) { // Can be false due to multipole + auto i_grid = simulation::micro_xs[i_nuclide].index_grid - 1; + auto f = simulation::micro_xs[i_nuclide].interp_factor; + const auto& xs {rxn.xs_[i_temp]}; + auto threshold = xs.threshold - 1; + if (i_grid >= xs.threshold) { + score = ((1.0 - f) * xs.value[i_grid-threshold] + + f * xs.value[i_grid-threshold+1]) * atom_density * flux; + } + } + } else { + if (p->material != MATERIAL_VOID) { + const Material& material {*model::materials[p->material-1]}; + for (auto i = 0; i < material.nuclide_.size(); ++i) { + auto j_nuclide = material.nuclide_[i]; + auto atom_density = material.atom_density_(i); + const auto& nuc {*data::nuclides[j_nuclide]}; + auto m = nuc.reaction_index_[score_bin]; + if (m == C_NONE) continue; + const auto& rxn {*nuc.reactions_[m]}; + auto i_temp = simulation::micro_xs[j_nuclide].index_temp; + if (i_temp >= 0) { // Can be false due to multipole + auto i_grid = simulation::micro_xs[j_nuclide].index_grid - 1; + auto f = simulation::micro_xs[j_nuclide].interp_factor; + const auto& xs {rxn.xs_[i_temp]}; + auto threshold = xs.threshold - 1; + if (i_grid >= threshold) { + score += ((1.0 - f) * xs.value[i_grid-threshold] + + f * xs.value[i_grid-threshold+1]) * atom_density + * flux; + } + } + } + } + } + } } // Add derivative information on score for differnetial tallies. From 1ee5c3bf64235b7d2857caa6bd0ebfcfddfec9bc Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Wed, 6 Feb 2019 23:38:21 -0500 Subject: [PATCH 38/58] Begin moving score_general_mg to C++ --- src/tallies/tally.F90 | 275 +++------------------------------- src/tallies/tally.cpp | 336 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 354 insertions(+), 257 deletions(-) diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index 57560910aa..eee8591af6 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -59,6 +59,18 @@ module tally real(C_DOUBLE), intent(in), value :: flux end subroutine + subroutine score_general_mg_c(p, i_tally, start_index, filter_index, & + i_nuclide, atom_density, flux) bind(C) + import Particle, C_INT, C_DOUBLE + type(Particle), intent(in) :: p + integer(C_INT), intent(in), value :: i_tally + integer(C_INT), intent(in), value :: start_index + integer(C_INT), intent(in), value :: filter_index + integer(C_INT), intent(in), value :: i_nuclide + real(C_DOUBLE), intent(in), value :: atom_density + real(C_DOUBLE), intent(in), value :: flux + end subroutine + subroutine score_fission_delayed_dg(i_tally, d_bin, score, score_index) bind(C) import C_INT, C_DOUBLE integer(C_INT), value :: i_tally @@ -206,6 +218,9 @@ contains call set_nuclide_angle_index_c(i_nuclide+1, p_uvw) end if + call score_general_mg_c(p, i_tally, start_index, filter_index, i_nuclide, & + atom_density, flux) + i = 0 SCORE_LOOP: do q = 1, t % n_score_bins() i = i + 1 @@ -223,279 +238,35 @@ contains case (SCORE_FLUX) - if (t % estimator() == ESTIMATOR_ANALOG) then - ! All events score to a flux bin. We actually use a collision - ! estimator in place of an analog one since there is no way to count - ! 'events' exactly for the flux - - if (survival_biasing) then - ! We need to account for the fact that some weight was already - ! absorbed - score = p % last_wgt + p % absorb_wgt - else - score = p % last_wgt - end if - score = score / material_xs % total * flux - - else - ! For flux, we need no cross section - score = flux - end if + cycle SCORE_LOOP case (SCORE_TOTAL) - if (t % estimator() == ESTIMATOR_ANALOG) then - ! All events will score to the total reaction rate. We can just - ! use the weight of the particle entering the collision as the - ! score - if (survival_biasing) then - ! We need to account for the fact that some weight was already - ! absorbed - score = p % last_wgt + p % absorb_wgt - else - score = p % last_wgt - end if - - if (i_nuclide >= 0) then - score = score * flux * atom_density * & - get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_TOTAL, p_g) / & - get_macro_xs_c(p % material, MG_GET_XS_TOTAL, p_g) - end if - - else - if (i_nuclide >= 0) then - score = get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_TOTAL, p_g) * & - atom_density * flux - else - score = material_xs % total * flux - end if - end if + cycle SCORE_LOOP case (SCORE_INVERSE_VELOCITY) - if (t % estimator() == ESTIMATOR_ANALOG .or. & - t % estimator() == ESTIMATOR_COLLISION) then - ! All events score to an inverse velocity bin. We actually use a - ! collision estimator in place of an analog one since there is no way - ! to count 'events' exactly for the inverse velocity - if (survival_biasing) then - ! We need to account for the fact that some weight was already - ! absorbed - score = p % last_wgt + p % absorb_wgt - else - score = p % last_wgt - end if - - if (i_nuclide >= 0) then - score = score * flux * get_nuclide_xs_c(i_nuclide+1, & - MG_GET_XS_INVERSE_VELOCITY, p_g) / & - get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) - else - score = score * flux * get_macro_xs_c(p % material, & - MG_GET_XS_INVERSE_VELOCITY, p_g) / & - get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) - end if - - else - - if (i_nuclide >= 0) then - score = flux * get_nuclide_xs_c(i_nuclide+1, & - MG_GET_XS_INVERSE_VELOCITY, p_g) - else - score = flux * get_macro_xs_c(p % material, & - MG_GET_XS_INVERSE_VELOCITY, p_g) - end if - end if + cycle SCORE_LOOP case (SCORE_SCATTER) - if (t % estimator() == ESTIMATOR_ANALOG) then - ! Skip any event where the particle didn't scatter - if (p % event /= EVENT_SCATTER) then - cycle SCORE_LOOP - end if - - ! Since only scattering events make it here, again we can use - ! the weight entering the collision as the estimator for the - ! reaction rate - score = p % last_wgt * flux - - ! Since we transport based on material data, the angle selected - ! was not selected from the f(mu) for the nuclide. Therefore - ! adjust the score by the actual probability for that nuclide. - if (i_nuclide >= 0) then - score = score * atom_density * & - get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_SCATTER_FMU_MULT, & - p % last_g, p % g, MU=p % mu) / & - get_macro_xs_c(p % material, MG_GET_XS_SCATTER_FMU_MULT, & - p % last_g, p % g, MU=p % mu) - end if - - else - if (i_nuclide >= 0) then - score = atom_density * flux * & - get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_SCATTER_MULT, & - p_g, MU=p % mu) - else - ! Get the scattering x/s and take away - ! the multiplication baked in to sigS - score = flux * & - get_macro_xs_c(p % material, MG_GET_XS_SCATTER_MULT, & - p_g, MU=p % mu) - end if - end if + cycle SCORE_LOOP case (SCORE_NU_SCATTER) - if (t % estimator() == ESTIMATOR_ANALOG) then - ! Skip any event where the particle didn't scatter - if (p % event /= EVENT_SCATTER) then - cycle SCORE_LOOP - end if - - ! For scattering production, we need to use the pre-collision - ! weight times the multiplicity as the estimate for the number of - ! neutrons exiting a reaction with neutrons in the exit channel - score = p % wgt * flux - - ! Since we transport based on material data, the angle selected - ! was not selected from the f(mu) for the nuclide. Therefore - ! adjust the score by the actual probability for that nuclide. - if (i_nuclide >= 0) then - score = score * atom_density * & - get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_SCATTER_FMU, & - p % last_g, p % g, MU=p % mu) / & - get_macro_xs_c(p % material, MG_GET_XS_SCATTER_FMU, & - p % last_g, p % g, MU=p % mu) - end if - - else - if (i_nuclide >= 0) then - score = atom_density * flux * & - get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_SCATTER, p_g) - else - ! Get the scattering x/s, which includes multiplication - score = flux * & - get_macro_xs_c(p % material, MG_GET_XS_SCATTER, p_g) - end if - end if + cycle SCORE_LOOP case (SCORE_ABSORPTION) - if (t % estimator() == ESTIMATOR_ANALOG) then - if (survival_biasing) then - ! No absorption events actually occur if survival biasing is on -- - ! just use weight absorbed in survival biasing - score = p % absorb_wgt * flux - else - ! Skip any event where the particle wasn't absorbed - if (p % event == EVENT_SCATTER) cycle SCORE_LOOP - ! All fission and absorption events will contribute here, so we - ! can just use the particle's weight entering the collision - score = p % last_wgt * flux - end if - if (i_nuclide >= 0) then - score = score * atom_density * & - get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_ABSORPTION, p_g) / & - get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) - end if - else - if (i_nuclide >= 0) then - score = atom_density * flux * & - get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_ABSORPTION, p_g) - else - score = material_xs % absorption * flux - end if - end if + cycle SCORE_LOOP case (SCORE_FISSION) - - if (t % estimator() == ESTIMATOR_ANALOG) then - if (survival_biasing) then - ! No fission events occur if survival biasing is on -- need to - ! calculate fraction of absorptions that would have resulted in - ! fission - score = p % absorb_wgt * flux - else - ! Skip any non-absorption events - if (p % event == EVENT_SCATTER) cycle SCORE_LOOP - ! All fission events will contribute, so again we can use - ! particle's weight entering the collision as the estimate for the - ! fission reaction rate - score = p % last_wgt * flux - end if - if (i_nuclide >= 0) then - score = score * atom_density * & - get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_FISSION, p_g) / & - get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) - else - score = score * & - get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) / & - get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) - end if - else - if (i_nuclide >= 0) then - score = get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_FISSION, p_g) * & - atom_density * flux - else - score = get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) * flux - end if - end if + cycle SCORE_LOOP case (SCORE_NU_FISSION) - - if (t % estimator() == ESTIMATOR_ANALOG) then - if (survival_biasing .or. p % fission) then - if (t % energyout_filter() > 0) then - ! Normally, we only need to make contributions to one scoring - ! bin. However, in the case of fission, since multiple fission - ! neutrons were emitted with different energies, multiple - ! outgoing energy bins may have been scored to. The following - ! logic treats this special case and results to multiple bins - call score_fission_eout(p, i_tally, score_index, score_bin) - cycle SCORE_LOOP - end if - end if - if (survival_biasing) then - ! No fission events occur if survival biasing is on -- need to - ! calculate fraction of absorptions that would have resulted in - ! nu-fission - score = p % absorb_wgt * flux - if (i_nuclide >= 0) then - score = score * atom_density * & - get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_NU_FISSION, p_g) / & - get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) - else - score = score * & - get_macro_xs_c(p % material, MG_GET_XS_NU_FISSION, p_g) / & - get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) - end if - else - ! Skip any non-fission events - if (.not. p % fission) cycle SCORE_LOOP - ! If there is no outgoing energy filter, than we only need to - ! score to one bin. For the score to be 'analog', we need to - ! score the number of particles that were banked in the fission - ! bank. Since this was weighted by 1/keff, we multiply by keff - ! to get the proper score. - score = keff * p % wgt_bank * flux - if (i_nuclide >= 0) then - score = score * atom_density * & - get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_FISSION, p_g) / & - get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) - end if - end if - - else - if (i_nuclide >= 0) then - score = get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_NU_FISSION, p_g) * & - atom_density * flux - else - score = get_macro_xs_c(p % material, MG_GET_XS_NU_FISSION, p_g) * flux - end if - end if + cycle SCORE_LOOP case (SCORE_PROMPT_NU_FISSION) diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index b48fd46216..4e7fdf3c3f 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -37,10 +37,6 @@ namespace openmc { // Functions defined in Fortran //============================================================================== -extern "C" void -score_general_ce(Particle* p, int i_tally, int start_index, int filter_index, - int i_nuclide, double atom_density, double flux); - extern "C" void score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, int i_nuclide, double atom_density, double flux); @@ -54,6 +50,19 @@ energy_filter_search(const EnergyFilter* filt, double val); extern "C" double get_precursor_decay_rate(int i_nuclide, int d); +//============================================================================== +// MG mode helper functions +//TODO: move these or remove the need for them +//============================================================================== + +double +get_nuclide_xs(int index, int xstype, int gin) +{return get_nuclide_xs_c(index, xstype, gin, nullptr, nullptr, nullptr);} + +double +get_macro_xs(int index, int xstype, int gin) +{return get_macro_xs_c(index, xstype, gin, nullptr, nullptr, nullptr);} + //============================================================================== // Global variable definitions //============================================================================== @@ -926,7 +935,7 @@ score_general_ce(Particle* p, int i_tally, int start_index, int filter_index, if (settings::survival_biasing) { // We need to account for the fact that some weight was already // absorbed - score = (p->last_wgt + p->absorb_wgt); + score = p->last_wgt + p->absorb_wgt; } else { score = p->last_wgt; } @@ -1753,6 +1762,323 @@ score_general_ce(Particle* p, int i_tally, int start_index, int filter_index, } } +extern "C" void +score_general_mg_c(Particle* p, int i_tally, int start_index, int filter_index, + int i_nuclide, double atom_density, double flux) +{ + //TODO: off-by-one + const Tally& tally {*model::tallies[i_tally-1]}; + auto results = tally_results(i_tally); + + // Set the direction and group to use with get_xs + double* p_uvw; + int p_g; + if (tally.estimator_ == ESTIMATOR_ANALOG + || tally.estimator_ == ESTIMATOR_COLLISION) { + + if (settings::survival_biasing) { + + // Then we either are alive and had a scatter (and so g changed), + // or are dead and g did not change + if (p->alive) { + p_uvw = p->last_uvw; + p_g = p->last_g; + } else { + p_uvw = p->coord[p->n_coord-1].uvw; + p_g = p->g; + } + } else if (p->event == EVENT_SCATTER) { + + // Then the energy group has been changed by the scattering routine + // meaning gin is now in p % last_g + p_uvw = p->last_uvw; + p_g = p->last_g; + } else { + + // No scatter, no change in g. + p_uvw = p->coord[p->n_coord-1].uvw; + p_g = p->g; + } + } else { + + // No actual collision so g has not changed. + p_uvw = p->coord[p->n_coord-1].uvw; + p_g = p->g; + } + + //TODO: set_macro_angle_index + //TODO: set_nuclide_temperature_index + //TODO: est_nuclide_angle_index + + for (auto i = 0; i < tally.scores_.size(); ++i) { + auto score_bin = tally.scores_[i]; + auto score_index = start_index + i; + + double score; + + switch (score_bin) { + + + case SCORE_FLUX: + if (tally.estimator_ == ESTIMATOR_ANALOG) { + // All events score to a flux bin. We actually use a collision estimator + // in place of an analog one since there is no way to count 'events' + // exactly for the flux + if (settings::survival_biasing) { + // We need to account for the fact that some weight was already + // absorbed + score = p->last_wgt + p->absorb_wgt; + } else { + score = p->last_wgt; + } + score *= flux / simulation::material_xs.total; + } else { + score = flux; + } + break; + + + case SCORE_TOTAL: + if (tally.estimator_ == ESTIMATOR_ANALOG) { + // All events will score to the total reaction rate. We can just use + // use the weight of the particle entering the collision as the score + if (settings::survival_biasing) { + // We need to account for the fact that some weight was already + // absorbed + score = p->last_wgt + p->absorb_wgt; + } else { + score = p->last_wgt; + } + //TODO: should flux be multiplied in above instead of below? + if (i_nuclide >= 0) { + score *= flux * atom_density + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_TOTAL, p_g) + / get_macro_xs(p->material, MG_GET_XS_TOTAL, p_g); + } + } else { + if (i_nuclide >= 0) { + score = get_nuclide_xs(i_nuclide+1, MG_GET_XS_TOTAL, p_g) + * atom_density * flux; + } else { + score = simulation::material_xs.total * flux; + } + } + break; + + + case SCORE_INVERSE_VELOCITY: + if (tally.estimator_ == ESTIMATOR_ANALOG + || tally.estimator_ == ESTIMATOR_COLLISION) { + // All events score to an inverse velocity bin. We actually use a + // collision estimator in place of an analog one since there is no way + // to count 'events' exactly for the inverse velocity + if (settings::survival_biasing) { + // We need to account for the fact that some weight was already + // absorbed + score = p->last_wgt + p->absorb_wgt; + } else { + score = p->last_wgt; + } + if (i_nuclide >= 0) { + score *= flux + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_INVERSE_VELOCITY, p_g) + / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); + } else { + score *= flux + * get_macro_xs(p->material, MG_GET_XS_INVERSE_VELOCITY, p_g) + / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); + } + } else { + if (i_nuclide >= 0) { + score = flux + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_INVERSE_VELOCITY, p_g); + } else { + score = flux + * get_macro_xs(p->material, MG_GET_XS_INVERSE_VELOCITY, p_g); + } + } + break; + + + case SCORE_SCATTER: + if (tally.estimator_ == ESTIMATOR_ANALOG) { + // Skip any event where the particle didn't scatter + if (p->event != EVENT_SCATTER) continue; + // Since only scattering events make it here, again we can use the + // weight entering the collision as the estimator for the reaction rate + score = p->last_wgt * flux; + if (i_nuclide >= 0) { + score *= atom_density + * get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_SCATTER_FMU_MULT, + p->last_g, &p->g, &p->mu, nullptr) + / get_macro_xs_c(p->material, MG_GET_XS_SCATTER_FMU_MULT, + p->last_g, &p->g, &p->mu, nullptr); + } + } else { + if (i_nuclide >= 0) { + score = atom_density * flux * get_nuclide_xs_c( + i_nuclide+1, MG_GET_XS_SCATTER_MULT, p_g, nullptr, &p->mu, nullptr); + } else { + score = flux * get_macro_xs_c( + p->material, MG_GET_XS_SCATTER_MULT, p_g, nullptr, &p->mu, nullptr); + } + } + break; + + + case SCORE_NU_SCATTER: + if (tally.estimator_ == ESTIMATOR_ANALOG) { + // Skip any event where the particle didn't scatter + if (p->event != EVENT_SCATTER) continue; + // For scattering production, we need to use the pre-collision weight + // times the multiplicity as the estimate for the number of neutrons + // exiting a reaction with neutrons in the exit channel + score = p->wgt * flux; + // Since we transport based on material data, the angle selected + // was not selected from the f(mu) for the nuclide. Therefore + // adjust the score by the actual probability for that nuclide. + if (i_nuclide >= 0) { + score *= atom_density + * get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_SCATTER_FMU, + p->last_g, &p->g, &p->mu, nullptr) + / get_macro_xs_c(p->material, MG_GET_XS_SCATTER_FMU, + p->last_g, &p->g, &p->mu, nullptr); + } + } else { + if (i_nuclide >= 0) { + score = atom_density * flux * get_nuclide_xs( + i_nuclide+1, MG_GET_XS_SCATTER, p_g); + } else { + score = flux * get_macro_xs(p->material, MG_GET_XS_SCATTER, p_g); + } + } + break; + + + case SCORE_ABSORPTION: + if (tally.estimator_ == ESTIMATOR_ANALOG) { + if (settings::survival_biasing) { + // No absorption events actually occur if survival biasing is on -- + // just use weight absorbed in survival biasing + score = p->absorb_wgt * flux; + } else { + // Skip any event where the particle wasn't absorbed + if (p->event == EVENT_SCATTER) continue; + // All fission and absorption events will contribute here, so we + // can just use the particle's weight entering the collision + score = p->last_wgt * flux; + } + if (i_nuclide >= 0) { + score *= atom_density + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_ABSORPTION, p_g) + / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); + } + } else { + if (i_nuclide >= 0) { + score = atom_density * flux + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_ABSORPTION, p_g); + } else { + score = simulation::material_xs.absorption * flux; + } + } + break; + + + case SCORE_FISSION: + if (tally.estimator_ == ESTIMATOR_ANALOG) { + if (settings::survival_biasing) { + // No fission events occur if survival biasing is on -- need to + // calculate fraction of absorptions that would have resulted in + // fission + score = p->absorb_wgt * flux; + } else { + // Skip any non-absorption events + if (p->event == EVENT_SCATTER) continue; + // All fission events will contribute, so again we can use particle's + // weight entering the collision as the estimate for the fission + // reaction rate + score = p->last_wgt * flux; + } + if (i_nuclide >= 0) { + score *= atom_density + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_FISSION, p_g) + / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); + } else { + score *= + get_macro_xs(p->material, MG_GET_XS_FISSION, p_g) + / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); + } + } else { + if (i_nuclide >= 0) { + score = get_nuclide_xs(i_nuclide+1, MG_GET_XS_FISSION, p_g) + * atom_density * flux; + } else { + score = get_macro_xs(p->material, MG_GET_XS_FISSION, p_g) * flux; + } + } + break; + + + case SCORE_NU_FISSION: + if (tally.estimator_ == ESTIMATOR_ANALOG) { + if (settings::survival_biasing || p->fission) { + if (tally.energyout_filter_ > 0) { + // Fission has multiple outgoing neutrons so this helper function + // is used to handle scoring the multiple filter bins. + score_fission_eout(p, i_tally, score_index+1, score_bin); + continue; + } + } + if (settings::survival_biasing) { + // No fission events occur if survival biasing is on -- need to + // calculate fraction of absorptions that would have resulted in + // nu-fission + score = p->absorb_wgt * flux; + if (i_nuclide >= 0) { + score *= atom_density + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_NU_FISSION, p_g) + / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); + } else { + score *= + get_macro_xs(p->material, MG_GET_XS_NU_FISSION, p_g) + / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); + } + } else { + // Skip any non-fission events + if (!p->fission) continue; + // If there is no outgoing energy filter, than we only need to score + // to one bin. For the score to be 'analog', we need to score the + // number of particles that were banked in the fission bank. Since + // this was weighted by 1/keff, we multiply by keff to get the proper + // score. + score = simulation::keff * p->wgt_bank * flux; + if (i_nuclide >= 0) { + score *= atom_density + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_FISSION, p_g) + / get_macro_xs(p->material, MG_GET_XS_FISSION, p_g); + } + } + } else { + if (i_nuclide >= 0) { + score = get_nuclide_xs(i_nuclide+1, MG_GET_XS_NU_FISSION, p_g) + * atom_density * flux; + } else { + score = get_macro_xs(p->material, MG_GET_XS_NU_FISSION, p_g) * flux; + } + } + break; + + + default: + continue; + } + + // Update tally results + #pragma omp atomic + results(filter_index-1, score_index, RESULT_VALUE) += score; + } +} + //! Tally rates for when the user requests a tally on all nuclides. void From 0d7df88edd182103bd4a4fbb0de7c8fefc8bbc9e Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sun, 10 Feb 2019 13:06:33 -0500 Subject: [PATCH 39/58] Finish moving score_general_mg to C++ --- src/tallies/tally.F90 | 555 +----------------------------------------- src/tallies/tally.cpp | 396 +++++++++++++++++++++++++++++- 2 files changed, 393 insertions(+), 558 deletions(-) diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index 4238f33273..4ac508da01 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -59,7 +59,7 @@ module tally real(C_DOUBLE), intent(in), value :: flux end subroutine - subroutine score_general_mg_c(p, i_tally, start_index, filter_index, & + subroutine score_general_mg(p, i_tally, start_index, filter_index, & i_nuclide, atom_density, flux) bind(C) import Particle, C_INT, C_DOUBLE type(Particle), intent(in) :: p @@ -147,559 +147,6 @@ contains ! analog tallies. !=============================================================================== - subroutine score_general_mg(p, i_tally, start_index, filter_index, i_nuclide, & - atom_density, flux) bind(C) - type(Particle), intent(in) :: p - integer(C_INT), intent(in), value :: i_tally - integer(C_INT), intent(in), value :: start_index - integer(C_INT), intent(in), value :: i_nuclide - integer(C_INT), intent(in), value :: filter_index ! for % results - real(C_DOUBLE), intent(in), value :: flux ! flux estimate - real(C_DOUBLE), intent(in), value :: atom_density ! atom/b-cm - - integer :: i ! loop index for scoring bins - integer :: q ! loop index for scoring bins - integer :: score_bin ! scoring bin, e.g. SCORE_FLUX - integer :: score_index ! scoring bin index - integer :: d ! delayed neutron index - integer :: g ! delayed neutron index - integer :: k ! loop index for bank sites - integer :: d_bin ! delayed group bin index - integer :: dg_filter ! index of delayed group filter - real(8) :: score ! analog tally score - real(8) :: p_uvw(3) ! Particle's current uvw - integer :: p_g ! Particle group to use for getting info - ! to tally with. - - associate (t => tallies(i_tally) % obj) - - ! Set the direction and group to use with get_xs - if (t % estimator() == ESTIMATOR_ANALOG .or. & - t % estimator() == ESTIMATOR_COLLISION) then - - if (survival_biasing) then - - ! Then we either are alive and had a scatter (and so g changed), - ! or are dead and g did not change - if (p % alive) then - p_uvw = p % last_uvw - p_g = p % last_g - else - p_uvw = p % coord(p % n_coord) % uvw - p_g = p % g - end if - else if (p % event == EVENT_SCATTER) then - - ! Then the energy group has been changed by the scattering routine - ! meaning gin is now in p % last_g - p_uvw = p % last_uvw - p_g = p % last_g - else - - ! No scatter, no change in g. - p_uvw = p % coord(p % n_coord) % uvw - p_g = p % g - end if - else - - ! No actual collision so g has not changed. - p_uvw = p % coord(p % n_coord) % uvw - p_g = p % g - end if - - ! To significantly reduce de-referencing, point matxs to the - ! macroscopic Mgxs for the material of interest - call set_macro_angle_index_c(p % material, p_uvw) - - ! Do same for nucxs, point it to the microscopic nuclide data of interest - if (i_nuclide >= 0) then - ! And since we haven't calculated this temperature index yet, do so now - call set_nuclide_temperature_index_c(i_nuclide+1, p % sqrtkT) - call set_nuclide_angle_index_c(i_nuclide+1, p_uvw) - end if - - call score_general_mg_c(p, i_tally, start_index, filter_index, i_nuclide, & - atom_density, flux) - - i = 0 - SCORE_LOOP: do q = 1, t % n_score_bins() - i = i + 1 - - ! determine what type of score bin - score_bin = t % score_bins(i) - - ! determine scoring bin index - score_index = start_index + i - - !######################################################################### - ! Determine appropirate scoring value. - - select case(score_bin) - - - case (SCORE_FLUX) - cycle SCORE_LOOP - - - case (SCORE_TOTAL) - cycle SCORE_LOOP - - - case (SCORE_INVERSE_VELOCITY) - cycle SCORE_LOOP - - - case (SCORE_SCATTER) - cycle SCORE_LOOP - - - case (SCORE_NU_SCATTER) - cycle SCORE_LOOP - - - case (SCORE_ABSORPTION) - cycle SCORE_LOOP - - - case (SCORE_FISSION) - cycle SCORE_LOOP - - - case (SCORE_NU_FISSION) - cycle SCORE_LOOP - - - case (SCORE_PROMPT_NU_FISSION) - - if (t % estimator() == ESTIMATOR_ANALOG) then - if (survival_biasing .or. p % fission) then - if (t % energyout_filter() > 0) then - ! Normally, we only need to make contributions to one scoring - ! bin. However, in the case of fission, since multiple fission - ! neutrons were emitted with different energies, multiple - ! outgoing energy bins may have been scored to. The following - ! logic treats this special case and results to multiple bins - call score_fission_eout(p, i_tally, score_index, score_bin) - cycle SCORE_LOOP - end if - end if - if (survival_biasing) then - ! No fission events occur if survival biasing is on -- need to - ! calculate fraction of absorptions that would have resulted in - ! nu-fission - score = p % absorb_wgt * flux - if (i_nuclide >= 0) then - score = score * atom_density * & - get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_PROMPT_NU_FISSION, p_g) / & - get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) - else - score = score * & - get_macro_xs_c(p % material, MG_GET_XS_PROMPT_NU_FISSION, p_g) / & - get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) - end if - else - ! Skip any non-fission events - if (.not. p % fission) cycle SCORE_LOOP - ! If there is no outgoing energy filter, than we only need to - ! score to one bin. For the score to be 'analog', we need to - ! score the number of particles that were banked in the fission - ! bank. Since this was weighted by 1/keff, we multiply by keff - ! to get the proper score. - score = keff * p % wgt_bank * (ONE - sum(p % n_delayed_bank) & - / real(p % n_bank, 8)) * flux - if (i_nuclide >= 0) then - score = score * atom_density * & - get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_FISSION, p_g) / & - get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) - end if - end if - - else - if (i_nuclide >= 0) then - score = get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_PROMPT_NU_FISSION, p_g) * & - atom_density * flux - else - score = get_macro_xs_c(p % material, MG_GET_XS_PROMPT_NU_FISSION, p_g) * flux - end if - end if - - - case (SCORE_DELAYED_NU_FISSION) - - ! Set the delayedgroup filter index and the number of delayed group bins - dg_filter = t % delayedgroup_filter() - - if (t % estimator() == ESTIMATOR_ANALOG) then - if (survival_biasing .or. p % fission) then - if (t % energyout_filter() > 0) then - ! Normally, we only need to make contributions to one scoring - ! bin. However, in the case of fission, since multiple fission - ! neutrons were emitted with different energies, multiple - ! outgoing energy bins may have been scored to. The following - ! logic treats this special case and results to multiple bins - call score_fission_eout(p, i_tally, score_index, score_bin) - cycle SCORE_LOOP - end if - end if - if (survival_biasing) then - ! No fission events occur if survival biasing is on -- need to - ! calculate fraction of absorptions that would have resulted in - ! nu-fission - if (get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) > ZERO) then - - if (dg_filter > 0) then - select type(filt => filters(t % filter(dg_filter) + 1) % obj) - type is (DelayedGroupFilter) - - ! Loop over all delayed group bins and tally to them - ! individually - do d_bin = 1, filt % n_bins - - ! Get the delayed group for this bin - d = filt % groups(d_bin) - - score = p % absorb_wgt * flux - if (i_nuclide >= 0) then - score = score * & - get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & - get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) - else - score = score * & - get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & - get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) - end if - - call score_fission_delayed_dg(i_tally, d_bin, score, score_index) - end do - cycle SCORE_LOOP - end select - else - score = p % absorb_wgt * flux - if (i_nuclide >= 0) then - score = score * & - get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, p_g) / & - get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) - else - score = score * & - get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g) / & - get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) - end if - end if - end if - else - ! Skip any non-fission events - if (.not. p % fission) cycle SCORE_LOOP - ! If there is no outgoing energy filter, than we only need to - ! score to one bin. For the score to be 'analog', we need to - ! score the number of particles that were banked in the fission - ! bank. Since this was weighted by 1/keff, we multiply by keff - ! to get the proper score. - - ! Check if the delayed group filter is present - if (dg_filter > 0) then - select type(filt => filters(t % filter(dg_filter) + 1) % obj) - type is (DelayedGroupFilter) - - ! Loop over all delayed group bins and tally to them - ! individually - do d_bin = 1, filt % n_bins - - ! Get the delayed group for this bin - d = filt % groups(d_bin) - - score = keff * p % wgt_bank / p % n_bank * & - p % n_delayed_bank(d) * flux - - if (i_nuclide >= 0) then - score = score * atom_density * & - get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_FISSION, p_g) / & - get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) - end if - - call score_fission_delayed_dg(i_tally, d_bin, score, score_index) - end do - cycle SCORE_LOOP - end select - else - score = keff * p % wgt_bank / p % n_bank * sum(p % n_delayed_bank) * flux - if (i_nuclide >= 0) then - score = score * atom_density * & - get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_FISSION, p_g) / & - get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) - end if - end if - end if - else - - ! Check if the delayed group filter is present - if (dg_filter > 0) then - select type(filt => filters(t % filter(dg_filter) + 1) % obj) - type is (DelayedGroupFilter) - - ! Loop over all delayed group bins and tally to them - ! individually - do d_bin = 1, filt % n_bins - - ! Get the delayed group for this bin - d = filt % groups(d_bin) - - if (i_nuclide >= 0) then - score = atom_density * flux * & - get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) - else - score = flux * & - get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) - end if - - call score_fission_delayed_dg(i_tally, d_bin, score, score_index) - end do - cycle SCORE_LOOP - end select - else - if (i_nuclide >= 0) then - score = atom_density * flux * & - get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, p_g) - - else - score = flux * & - get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g) - end if - end if - end if - - case (SCORE_DECAY_RATE) - - ! Set the delayedgroup filter index and the number of delayed group bins - dg_filter = t % delayedgroup_filter() - - if (t % estimator() == ESTIMATOR_ANALOG) then - if (survival_biasing) then - ! No fission events occur if survival biasing is on -- need to - ! calculate fraction of absorptions that would have resulted in - ! nu-fission - if (get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) > ZERO) then - - if (dg_filter > 0) then - select type(filt => filters(t % filter(dg_filter) + 1) % obj) - type is (DelayedGroupFilter) - - ! Loop over all delayed group bins and tally to them - ! individually - do d_bin = 1, filt % n_bins - - ! Get the delayed group for this bin - d = filt % groups(d_bin) - - score = p % absorb_wgt * flux - if (i_nuclide >= 0) then - score = score * & - get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & - get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & - get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) - else - score = score * & - get_macro_xs_c(p % material, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & - get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & - get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) - end if - - call score_fission_delayed_dg(i_tally, d_bin, score, score_index) - end do - cycle SCORE_LOOP - end select - else - - score = ZERO - - ! If the delayed group filter is not present, compute the score - ! by accumulating the absorbed weight times the decay rate times - ! the fraction of the delayed-nu-fission xs to the absorption xs - ! for all delayed groups. - do d = 1, num_delayed_groups - if (i_nuclide >= 0) then - score = score + p % absorb_wgt * flux * & - get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & - get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & - get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) - else - score = score + p % absorb_wgt * flux * & - get_macro_xs_c(p % material, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & - get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) / & - get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) - end if - end do - end if - end if - else - ! Skip any non-fission events - if (.not. p % fission) cycle SCORE_LOOP - ! If there is no outgoing energy filter, than we only need to - ! score to one bin. For the score to be 'analog', we need to - ! score the number of particles that were banked in the fission - ! bank. Since this was weighted by 1/keff, we multiply by keff - ! to get the proper score. - - score = ZERO - - ! loop over number of particles banked - do k = 1, p % n_bank - - ! get the delayed group - g = fission_bank_delayed_group(n_bank - p % n_bank + k) - - ! Case for tallying delayed emissions - if (g /= 0) then - - ! determine score based on bank site weight and keff. - if (i_nuclide >= 0) then - score = score + keff * atom_density * & - fission_bank_wgt(n_bank - p % n_bank + k) * & - get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DECAY_RATE, p_g, DG=g) * & - get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_FISSION, p_g) / & - get_macro_xs_c(p % material, MG_GET_XS_FISSION, p_g) * flux - else - score = score + keff * & - fission_bank_wgt(n_bank - p % n_bank + k) * & - get_macro_xs_c(p % material, MG_GET_XS_DECAY_RATE, p_g, DG=g) * flux - end if - - ! if the delayed group filter is present, tally to corresponding - ! delayed group bin if it exists - if (dg_filter > 0) then - - ! declare the delayed group filter type - select type(filt => filters(t % filter(dg_filter) + 1) % obj) - type is (DelayedGroupFilter) - - ! loop over delayed group bins until the corresponding bin - ! is found - do d_bin = 1, filt % n_bins - d = filt % groups(d_bin) - - ! check whether the delayed group of the particle is equal - ! to the delayed group of this bin - if (d == g) then - call score_fission_delayed_dg(i_tally, d_bin, score, & - score_index) - end if - end do - end select - - ! Reset the score to zero - score = ZERO - end if - end if - end do - - ! If the delayed group filter is present, cycle because the - ! score_fission_delayed_dg(...) has already tallied the score - if (dg_filter > 0) then - cycle SCORE_LOOP - end if - end if - else - - ! Check if the delayed group filter is present - if (dg_filter > 0) then - select type(filt => filters(t % filter(dg_filter) + 1) % obj) - type is (DelayedGroupFilter) - - ! Loop over all delayed group bins and tally to them - ! individually - do d_bin = 1, filt % n_bins - - ! Get the delayed group for this bin - d = filt % groups(d_bin) - - if (i_nuclide >= 0) then - score = atom_density * flux * & - get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & - get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) - else - score = flux * & - get_macro_xs_c(p % material, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & - get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) - end if - - call score_fission_delayed_dg(i_tally, d_bin, score, score_index) - end do - cycle SCORE_LOOP - end select - else - score = ZERO - - ! If the delayed group filter is not present, compute the score - ! by accumulating the absorbed weight times the decay rate times - ! the fraction of the delayed-nu-fission xs to the absorption xs - ! for all delayed groups. - do d = 1, num_delayed_groups - if (i_nuclide >= 0) then - score = score + atom_density * flux * & - get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & - get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) - else - score = score + flux * & - get_macro_xs_c(p % material, MG_GET_XS_DECAY_RATE, p_g, DG=d) * & - get_macro_xs_c(p % material, MG_GET_XS_DELAYED_NU_FISSION, p_g, DG=d) - end if - end do - end if - end if - - - case (SCORE_KAPPA_FISSION) - - if (t % estimator() == ESTIMATOR_ANALOG) then - if (survival_biasing) then - ! No fission events occur if survival biasing is on -- need to - ! calculate fraction of absorptions that would have resulted in - ! fission - score = p % absorb_wgt * flux - else - ! Skip any non-absorption events - if (p % event == EVENT_SCATTER) cycle SCORE_LOOP - ! All fission events will contribute, so again we can use - ! particle's weight entering the collision as the estimate for the - ! fission reaction rate - score = p % last_wgt * flux - end if - if (i_nuclide >= 0) then - score = score * atom_density * & - get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_KAPPA_FISSION, p_g) / & - get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) - else - score = score * & - get_macro_xs_c(p % material, MG_GET_XS_KAPPA_FISSION, p_g) / & - get_macro_xs_c(p % material, MG_GET_XS_ABSORPTION, p_g) - end if - else - if (i_nuclide >= 0) then - score = get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_KAPPA_FISSION, p_g) * & - atom_density * flux - else - score = flux * & - get_macro_xs_c(p % material, MG_GET_XS_KAPPA_FISSION, p_g) - - end if - end if - - - case (SCORE_EVENTS) - ! Simply count number of scoring events - score = ONE - - end select - - !######################################################################### - ! Expand score if necessary and add to tally results. -!$omp atomic - t % results(RESULT_VALUE, score_index, filter_index) = & - t % results(RESULT_VALUE, score_index, filter_index) + score - - end do SCORE_LOOP - end associate - end subroutine score_general_mg - !=============================================================================== ! APPLY_DERIVATIVE_TO_SCORE multiply the given score by its relative derivative !=============================================================================== diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 46d5ff60ac..d62224d635 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -1761,13 +1761,16 @@ score_general_ce(Particle* p, int i_tally, int start_index, int filter_index, } extern "C" void -score_general_mg_c(Particle* p, int i_tally, int start_index, int filter_index, +score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, int i_nuclide, double atom_density, double flux) { //TODO: off-by-one const Tally& tally {*model::tallies[i_tally-1]}; auto results = tally_results(i_tally); + //TODO: off-by-one throughout on score_index in calls to score_fission_eout + //TODO: off-by-one throughout on p->material + // Set the direction and group to use with get_xs double* p_uvw; int p_g; @@ -1804,9 +1807,16 @@ score_general_mg_c(Particle* p, int i_tally, int start_index, int filter_index, p_g = p->g; } - //TODO: set_macro_angle_index - //TODO: set_nuclide_temperature_index - //TODO: est_nuclide_angle_index + // To significantly reduce de-referencing, point matxs to the macroscopic + // Mgxs for the material of interest + data::macro_xs[p->material - 1].set_angle_index(p_uvw); + + // Do same for nucxs, point it to the microscopic nuclide data of interest + if (i_nuclide >= 0) { + // And since we haven't calculated this temperature index yet, do so now + data::nuclides_MG[i_nuclide].set_temperature_index(p->sqrtkT); + data::nuclides_MG[i_nuclide].set_angle_index(p_uvw); + } for (auto i = 0; i < tally.scores_.size(); ++i) { auto score_bin = tally.scores_[i]; @@ -2067,6 +2077,384 @@ score_general_mg_c(Particle* p, int i_tally, int start_index, int filter_index, break; + case SCORE_PROMPT_NU_FISSION: + if (tally.estimator_ == ESTIMATOR_ANALOG) { + if (settings::survival_biasing || p->fission) { + if (tally.energyout_filter_ > 0) { + // Fission has multiple outgoing neutrons so this helper function + // is used to handle scoring the multiple filter bins. + score_fission_eout(p, i_tally, score_index+1, score_bin); + continue; + } + } + if (settings::survival_biasing) { + // No fission events occur if survival biasing is on -- need to + // calculate fraction of absorptions that would have resulted in + // prompt-nu-fission + score = p->absorb_wgt * flux; + if (i_nuclide >= 0) { + score *= atom_density + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_PROMPT_NU_FISSION, p_g) + / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); + } else { + score *= + get_macro_xs(p->material, MG_GET_XS_PROMPT_NU_FISSION, p_g) + / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); + } + } else { + // Skip any non-fission events + if (!p->fission) continue; + // If there is no outgoing energy filter, than we only need to score + // to one bin. For the score to be 'analog', we need to score the + // number of particles that were banked in the fission bank. Since + // this was weighted by 1/keff, we multiply by keff to get the proper + // score. + auto n_delayed = std::accumulate(p->n_delayed_bank, + p->n_delayed_bank+MAX_DELAYED_GROUPS, 0); + auto prompt_frac = 1. - n_delayed / static_cast(p->n_bank); + score = simulation::keff * p->wgt_bank * prompt_frac * flux; + if (i_nuclide >= 0) { + score *= atom_density + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_FISSION, p_g) + / get_macro_xs(p->material, MG_GET_XS_FISSION, p_g); + } + } + } else { + if (i_nuclide >= 0) { + score = get_nuclide_xs(i_nuclide+1, MG_GET_XS_PROMPT_NU_FISSION, p_g) + * atom_density * flux; + } else { + score = get_macro_xs(p->material, MG_GET_XS_PROMPT_NU_FISSION, p_g) + * flux; + } + } + break; + + + case SCORE_DELAYED_NU_FISSION: + if (tally.estimator_ == ESTIMATOR_ANALOG) { + if (settings::survival_biasing || p->fission) { + if (tally.energyout_filter_ > 0) { + // Fission has multiple outgoing neutrons so this helper function + // is used to handle scoring the multiple filter bins. + score_fission_eout(p, i_tally, score_index+1, score_bin); + continue; + } + } + if (settings::survival_biasing) { + // No fission events occur if survival biasing is on -- need to + // calculate fraction of absorptions that would have resulted in + // delayed-nu-fission + if (get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g) > 0) { + if (tally.delayedgroup_filter_ > 0) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + const DelayedGroupFilter& filt + {*dynamic_cast( + model::tally_filters[i_dg_filt].get())}; + // Tally each delayed group bin individually + for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { + auto d = filt.groups_[d_bin]; + score = p->absorb_wgt * flux; + if (i_nuclide >= 0) { + score *= + get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, + p_g, nullptr, nullptr, &d) + / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); + } else { + score *= + get_macro_xs_c(p->material, MG_GET_XS_DELAYED_NU_FISSION, + p_g, nullptr, nullptr, &d) + / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); + } + score_fission_delayed_dg(i_tally, d_bin+1, score, + score_index+1); + } + continue; + } else { + // If the delayed group filter is not present, compute the score + // by multiplying the absorbed weight by the fraction of the + // delayed-nu-fission xs to the absorption xs + score = p->absorb_wgt * flux; + if (i_nuclide >= 0) { + score *= + get_nuclide_xs(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, p_g) + / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); + } else { + score *= + get_macro_xs(p->material, MG_GET_XS_DELAYED_NU_FISSION, p_g) + / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); + } + } + } + } else { + // Skip any non-fission events + if (!p->fission) continue; + // If there is no outgoing energy filter, than we only need to score + // to one bin. For the score to be 'analog', we need to score the + // number of particles that were banked in the fission bank. Since + // this was weighted by 1/keff, we multiply by keff to get the proper + // score. Loop over the neutrons produced from fission and check which + // ones are delayed. If a delayed neutron is encountered, add its + // contribution to the fission bank to the score. + if (tally.delayedgroup_filter_ > 0) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + const DelayedGroupFilter& filt + {*dynamic_cast( + model::tally_filters[i_dg_filt].get())}; + // Tally each delayed group bin individually + for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { + auto d = filt.groups_[d_bin]; + score = simulation::keff * p->wgt_bank / p->n_bank + * p->n_delayed_bank[d-1] * flux; + if (i_nuclide >= 0) { + score *= atom_density + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_FISSION, p_g) + / get_macro_xs(p->material, MG_GET_XS_FISSION, p_g); + } + score_fission_delayed_dg(i_tally, d_bin+1, score, score_index+1); + } + continue; + } else { + // Add the contribution from all delayed groups + auto n_delayed = std::accumulate(p->n_delayed_bank, + p->n_delayed_bank+MAX_DELAYED_GROUPS, 0); + score = simulation::keff * p->wgt_bank / p->n_bank * n_delayed + * flux; + if (i_nuclide >= 0) { + score *= atom_density + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_FISSION, p_g) + / get_macro_xs(p->material, MG_GET_XS_FISSION, p_g); + } + } + } + } else { + if (tally.delayedgroup_filter_ > 0) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + const DelayedGroupFilter& filt + {*dynamic_cast( + model::tally_filters[i_dg_filt].get())}; + // Tally each delayed group bin individually + for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { + auto d = filt.groups_[d_bin]; + if (i_nuclide >= 0) { + score = flux * atom_density + * get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, + p_g, nullptr, nullptr, &d); + } else { + score = flux + * get_macro_xs_c(p->material, MG_GET_XS_DELAYED_NU_FISSION, + p_g, nullptr, nullptr, &d); + } + score_fission_delayed_dg(i_tally, d_bin+1, score, score_index+1); + } + continue; + } else { + if (i_nuclide >= 0) { + score = flux * atom_density + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, p_g); + } else { + score = flux + * get_macro_xs(p->material, MG_GET_XS_DELAYED_NU_FISSION, p_g); + } + } + } + break; + + + case SCORE_DECAY_RATE: + if (tally.estimator_ == ESTIMATOR_ANALOG) { + if (settings::survival_biasing) { + // No fission events occur if survival biasing is on -- need to + // calculate fraction of absorptions that would have resulted in + // delayed-nu-fission + if (get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g) > 0) { + if (tally.delayedgroup_filter_ > 0) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + const DelayedGroupFilter& filt + {*dynamic_cast( + model::tally_filters[i_dg_filt].get())}; + // Tally each delayed group bin individually + for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { + auto d = filt.groups_[d_bin]; + score = p->absorb_wgt * flux; + if (i_nuclide >= 0) { + score *= + get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DECAY_RATE, + p_g, nullptr, nullptr, &d) + *get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, + p_g, nullptr, nullptr, &d) + / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); + } else { + score *= + get_macro_xs_c(p->material, MG_GET_XS_DECAY_RATE, + p_g, nullptr, nullptr, &d) + * get_macro_xs_c(p->material, MG_GET_XS_DELAYED_NU_FISSION, + p_g, nullptr, nullptr, &d) + / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); + } + score_fission_delayed_dg(i_tally, d_bin+1, score, + score_index+1); + } + continue; + } else { + // If the delayed group filter is not present, compute the score + // by multiplying the absorbed weight by the fraction of the + // delayed-nu-fission xs to the absorption xs for all delayed + // groups + score = 0.; + for (auto d = 0; d < data::num_delayed_groups; ++d) { + if (i_nuclide >= 0) { + score += p->absorb_wgt * flux + * get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DECAY_RATE, + p_g, nullptr, nullptr, &d) + *get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, + p_g, nullptr, nullptr, &d) + / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); + } else { + score += p->absorb_wgt * flux + * get_macro_xs_c(p->material, MG_GET_XS_DECAY_RATE, + p_g, nullptr, nullptr, &d) + * get_macro_xs_c(p->material, MG_GET_XS_DELAYED_NU_FISSION, + p_g, nullptr, nullptr, &d) + / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); + } + } + } + } + } else { + // Skip any non-fission events + if (!p->fission) continue; + // If there is no outgoing energy filter, than we only need to score + // to one bin. For the score to be 'analog', we need to score the + // number of particles that were banked in the fission bank. Since + // this was weighted by 1/keff, we multiply by keff to get the proper + // score. Loop over the neutrons produced from fission and check which + // ones are delayed. If a delayed neutron is encountered, add its + // contribution to the fission bank to the score. + score = 0.; + for (auto i = 0; i < p->n_bank; ++i) { + auto i_bank = simulation::n_bank - p->n_bank + i; + const auto& bank = simulation::fission_bank[i_bank]; + auto g = bank.delayed_group; + if (g != 0) { + if (i_nuclide >= 0) { + score += simulation::keff * atom_density * bank.wgt * flux + * get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DECAY_RATE, p_g, + nullptr, nullptr, &g) + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_FISSION, p_g) + / get_macro_xs(p->material, MG_GET_XS_FISSION, p_g); + } else { + score += simulation::keff * bank.wgt * flux + * get_macro_xs_c(p->material, MG_GET_XS_DECAY_RATE, p_g, + nullptr, nullptr, &g); + } + if (tally.delayedgroup_filter_ > 0) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + const DelayedGroupFilter& filt + {*dynamic_cast( + model::tally_filters[i_dg_filt].get())}; + // Find the corresponding filter bin and then score + for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { + auto d = filt.groups_[d_bin]; + if (d == g) + score_fission_delayed_dg(i_tally, d_bin+1, score, + score_index+1); + } + score = 0.; + } + } + } + if (tally.delayedgroup_filter_ > 0) continue; + } + } else { + if (tally.delayedgroup_filter_ > 0) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + const DelayedGroupFilter& filt + {*dynamic_cast( + model::tally_filters[i_dg_filt].get())}; + // Tally each delayed group bin individually + for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { + auto d = filt.groups_[d_bin]; + if (i_nuclide >= 0) { + score += atom_density * flux + * get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DECAY_RATE, + p_g, nullptr, nullptr, &d) + * get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, + p_g, nullptr, nullptr, &d); + } else { + score += flux + * get_macro_xs_c(p->material, MG_GET_XS_DECAY_RATE, + p_g, nullptr, nullptr, &d) + * get_macro_xs_c(p->material, MG_GET_XS_DELAYED_NU_FISSION, + p_g, nullptr, nullptr, &d); + } + score_fission_delayed_dg(i_tally, d_bin+1, score, score_index+1); + } + continue; + } else { + score = 0.; + for (auto d = 0; d < data::num_delayed_groups; ++d) { + if (i_nuclide >= 0) { + score += atom_density * flux + * get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DECAY_RATE, + p_g, nullptr, nullptr, &d) + * get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, + p_g, nullptr, nullptr, &d); + } else { + score += flux + * get_macro_xs_c(p->material, MG_GET_XS_DECAY_RATE, + p_g, nullptr, nullptr, &d) + * get_macro_xs_c(p->material, MG_GET_XS_DELAYED_NU_FISSION, + p_g, nullptr, nullptr, &d); + } + } + } + } + break; + + + case SCORE_KAPPA_FISSION: + if (tally.estimator_ == ESTIMATOR_ANALOG) { + if (settings::survival_biasing) { + // No fission events occur if survival biasing is on -- need to + // calculate fraction of absorptions that would have resulted in + // fission scaled by the Q-value + score = p->absorb_wgt * flux; + } else { + // Skip any non-absorption events + if (p->event == EVENT_SCATTER) continue; + // All fission events will contribute, so again we can use particle's + // weight entering the collision as the estimate for the fission + // reaction rate + score = p->last_wgt * flux; + } + if (i_nuclide >= 0) { + score *= atom_density + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_KAPPA_FISSION, p_g) + / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); + } else { + score *= + get_macro_xs(p->material, MG_GET_XS_KAPPA_FISSION, p_g) + / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); + } + } else { + if (i_nuclide >= 0) { + score = get_nuclide_xs(i_nuclide+1, MG_GET_XS_KAPPA_FISSION, p_g) + * atom_density * flux; + } else { + score = get_macro_xs(p->material, MG_GET_XS_KAPPA_FISSION, p_g) + * flux; + } + } + break; + + + case SCORE_EVENTS: + // Simply count the number of scoring events + score = 1.; + break; + + default: continue; } From b3191e3c9cbacbb0db8d96a7f2ab075212904807 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sun, 10 Feb 2019 13:25:26 -0500 Subject: [PATCH 40/58] Use 0-based indexing for tally score indices --- src/tallies/tally.F90 | 63 ------------------------------------ src/tallies/tally.cpp | 75 +++++++++++++++++++++++-------------------- 2 files changed, 40 insertions(+), 98 deletions(-) diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index 4ac508da01..a83a9c21be 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -24,22 +24,9 @@ module tally implicit none - procedure(score_general_), pointer :: score_general => null() procedure(score_analog_tally_), pointer :: score_analog_tally => null() abstract interface - subroutine score_general_(p, i_tally, start_index, filter_index, i_nuclide, & - atom_density, flux) - import Particle, C_INT, C_DOUBLE - type(Particle), intent(in) :: p - integer(C_INT), intent(in), value :: i_tally - integer(C_INT), intent(in), value :: start_index - integer(C_INT), intent(in), value :: i_nuclide - integer(C_INT), intent(in), value :: filter_index ! for % results - real(C_DOUBLE), intent(in), value :: flux ! flux estimate - real(C_DOUBLE), intent(in), value :: atom_density ! atom/b-cm - end subroutine score_general_ - subroutine score_analog_tally_(p) import Particle type(Particle), intent(in) :: p @@ -47,46 +34,6 @@ module tally end interface interface - subroutine score_general_ce(p, i_tally, start_index, filter_index, & - i_nuclide, atom_density, flux) bind(C) - import Particle, C_INT, C_DOUBLE - type(Particle), intent(in) :: p - integer(C_INT), intent(in), value :: i_tally - integer(C_INT), intent(in), value :: start_index - integer(C_INT), intent(in), value :: filter_index - integer(C_INT), intent(in), value :: i_nuclide - real(C_DOUBLE), intent(in), value :: atom_density - real(C_DOUBLE), intent(in), value :: flux - end subroutine - - subroutine score_general_mg(p, i_tally, start_index, filter_index, & - i_nuclide, atom_density, flux) bind(C) - import Particle, C_INT, C_DOUBLE - type(Particle), intent(in) :: p - integer(C_INT), intent(in), value :: i_tally - integer(C_INT), intent(in), value :: start_index - integer(C_INT), intent(in), value :: filter_index - integer(C_INT), intent(in), value :: i_nuclide - real(C_DOUBLE), intent(in), value :: atom_density - real(C_DOUBLE), intent(in), value :: flux - end subroutine - - subroutine score_fission_delayed_dg(i_tally, d_bin, score, score_index) bind(C) - import C_INT, C_DOUBLE - integer(C_INT), value :: i_tally - integer(C_INT), value :: d_bin - real(C_DOUBLE), value :: score - integer(C_INT), value :: score_index - end subroutine - - subroutine score_fission_eout(p, i_tally, i_score, score_bin) bind(C) - import Particle, C_INT - type(Particle) :: p - integer(C_INT), value :: i_tally - integer(C_INT), value :: i_score - integer(C_INT), value :: score_bin - end subroutine - subroutine score_analog_tally_ce(p) bind(C) import Particle type(Particle), intent(in) :: p @@ -131,22 +78,12 @@ contains subroutine init_tally_routines() bind(C) if (run_CE) then - score_general => score_general_ce score_analog_tally => score_analog_tally_ce else - score_general => score_general_mg score_analog_tally => score_analog_tally_mg end if end subroutine init_tally_routines -!=============================================================================== -! SCORE_GENERAL* adds scores to the tally array for the given filter and -! nuclide. This function is called by all volume tallies. For analog tallies, -! the flux estimate depends on the score type so the flux argument is really -! just used for filter weights. The atom_density argument is not used for -! analog tallies. -!=============================================================================== - !=============================================================================== ! APPLY_DERIVATIVE_TO_SCORE multiply the given score by its relative derivative !=============================================================================== diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index d62224d635..0be89de6e6 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -37,10 +37,6 @@ namespace openmc { // Functions defined in Fortran //============================================================================== -extern "C" void -score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, - int i_nuclide, double atom_density, double flux); - extern "C" void apply_derivative_to_score(Particle* p, int i_tally, int i_nuclide, double atom_density, int score_bin, double* score); @@ -683,7 +679,7 @@ adaptor_type<3> tally_results(int idx) //! Helper function used to increment tallies with a delayed group filter. -extern "C" void +void score_fission_delayed_dg(int i_tally, int d_bin, double score, int score_index) { // Save the original delayed group bin @@ -709,7 +705,7 @@ score_fission_delayed_dg(int i_tally, int d_bin, double score, int score_index) auto results = tally_results(i_tally); //TODO: off-by-one #pragma omp atomic - results(filter_index-1, score_index-1, RESULT_VALUE) += score; + results(filter_index-1, score_index, RESULT_VALUE) += score; // Reset the original delayed group bin dg_match.bins_[i_bin-1] = original_bin; @@ -720,7 +716,7 @@ score_fission_delayed_dg(int i_tally, int d_bin, double score, int score_index) //! In this case, we may need to score to multiple bins if there were multiple //! neutrons produced with different energies. -extern "C" void +void score_fission_eout(Particle* p, int i_tally, int i_score, int score_bin) { //TODO: off-by-one @@ -799,7 +795,7 @@ score_fission_eout(Particle* p, int i_tally, int i_score, int score_bin) // Update tally results #pragma omp atomic - results(filter_index-1, i_score-1, RESULT_VALUE) += score; + results(filter_index-1, i_score, RESULT_VALUE) += score; } else if (score_bin == SCORE_DELAYED_NU_FISSION && g != 0) { @@ -847,7 +843,7 @@ score_fission_eout(Particle* p, int i_tally, int i_score, int score_bin) // Update tally results #pragma omp atomic - results(filter_index-1, i_score-1, RESULT_VALUE) += score*filter_weight; + results(filter_index-1, i_score, RESULT_VALUE) += score*filter_weight; } } } @@ -856,7 +852,13 @@ score_fission_eout(Particle* p, int i_tally, int i_score, int score_bin) simulation::filter_matches[i_eout_filt].bins_[i_bin-1] = bin_energyout; } -extern "C" void +//! Update tally results for continuous-energy tallies with any estimator. +// +//! For analog tallies, the flux estimate depends on the score type so the flux +//! argument is really just used for filter weights. The atom_density argument +//! is not used for analog tallies. + +void score_general_ce(Particle* p, int i_tally, int start_index, int filter_index, int i_nuclide, double atom_density, double flux) { @@ -874,7 +876,6 @@ score_general_ce(Particle* p, int i_tally, int start_index, int filter_index, double score; //TODO: off-by-one throughout on p->event_nuclide - //TODO: off-by-one throughout on score_index in calls to score_fission_eout //TODO: off-by-one throughout on p->material switch (score_bin) { @@ -1054,7 +1055,7 @@ score_general_ce(Particle* p, int i_tally, int start_index, int filter_index, if (tally.energyout_filter_ > 0) { // Fission has multiple outgoing neutrons so this helper function // is used to handle scoring the multiple filter bins. - score_fission_eout(p, i_tally, score_index+1, score_bin); + score_fission_eout(p, i_tally, score_index, score_bin); continue; } } @@ -1097,7 +1098,7 @@ score_general_ce(Particle* p, int i_tally, int start_index, int filter_index, if (tally.energyout_filter_ > 0) { // Fission has multiple outgoing neutrons so this helper function // is used to handle scoring the multiple filter bins. - score_fission_eout(p, i_tally, score_index+1, score_bin); + score_fission_eout(p, i_tally, score_index, score_bin); continue; } } @@ -1159,7 +1160,7 @@ score_general_ce(Particle* p, int i_tally, int start_index, int filter_index, if (tally.energyout_filter_ > 0) { // Fission has multiple outgoing neutrons so this helper function // is used to handle scoring the multiple filter bins. - score_fission_eout(p, i_tally, score_index+1, score_bin); + score_fission_eout(p, i_tally, score_index, score_bin); continue; } } @@ -1183,7 +1184,7 @@ score_general_ce(Particle* p, int i_tally, int start_index, int filter_index, * simulation::micro_xs[p->event_nuclide-1].fission / simulation::micro_xs[p->event_nuclide-1].absorption * flux; score_fission_delayed_dg(i_tally, d_bin+1, score, - score_index+1); + score_index); } continue; } else { @@ -1217,7 +1218,7 @@ score_general_ce(Particle* p, int i_tally, int start_index, int filter_index, auto d = filt.groups_[d_bin]; score = simulation::keff * p->wgt_bank / p->n_bank * p->n_delayed_bank[d-1] * flux; - score_fission_delayed_dg(i_tally, d_bin+1, score, score_index+1); + score_fission_delayed_dg(i_tally, d_bin+1, score, score_index); } continue; } else { @@ -1242,7 +1243,7 @@ score_general_ce(Particle* p, int i_tally, int start_index, int filter_index, ->nu(E, ReactionProduct::EmissionMode::delayed, d); score = simulation::micro_xs[i_nuclide].fission * yield * atom_density * flux; - score_fission_delayed_dg(i_tally, d_bin+1, score, score_index+1); + score_fission_delayed_dg(i_tally, d_bin+1, score, score_index); } continue; } else { @@ -1272,7 +1273,7 @@ score_general_ce(Particle* p, int i_tally, int start_index, int filter_index, score = simulation::micro_xs[j_nuclide].fission * yield * atom_density * flux; score_fission_delayed_dg(i_tally, d_bin+1, score, - score_index+1); + score_index); } } } @@ -1323,7 +1324,7 @@ score_general_ce(Particle* p, int i_tally, int start_index, int filter_index, / simulation::micro_xs[p->event_nuclide-1].absorption * rate * flux; score_fission_delayed_dg(i_tally, d_bin+1, score, - score_index+1); + score_index); } continue; } else { @@ -1377,7 +1378,7 @@ score_general_ce(Particle* p, int i_tally, int start_index, int filter_index, auto d = filt.groups_[d_bin]; if (d == g) score_fission_delayed_dg(i_tally, d_bin+1, score, - score_index+1); + score_index); } score = 0.; } @@ -1402,7 +1403,7 @@ score_general_ce(Particle* p, int i_tally, int start_index, int filter_index, auto rate = rxn.products_[d].decay_rate_; score = simulation::micro_xs[i_nuclide].fission * yield * flux * atom_density * rate; - score_fission_delayed_dg(i_tally, d_bin+1, score, score_index+1); + score_fission_delayed_dg(i_tally, d_bin+1, score, score_index); } continue; } else { @@ -1443,7 +1444,7 @@ score_general_ce(Particle* p, int i_tally, int start_index, int filter_index, score = simulation::micro_xs[j_nuclide].fission * yield * flux * atom_density * rate; score_fission_delayed_dg(i_tally, d_bin+1, score, - score_index+1); + score_index); } } } @@ -1760,7 +1761,12 @@ score_general_ce(Particle* p, int i_tally, int start_index, int filter_index, } } -extern "C" void +//! Update tally results for multigroup tallies with any estimator. +// +//! For analog tallies, the flux estimate depends on the score type so the flux +//! argument is really just used for filter weights. + +void score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, int i_nuclide, double atom_density, double flux) { @@ -1768,7 +1774,6 @@ score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, const Tally& tally {*model::tallies[i_tally-1]}; auto results = tally_results(i_tally); - //TODO: off-by-one throughout on score_index in calls to score_fission_eout //TODO: off-by-one throughout on p->material // Set the direction and group to use with get_xs @@ -2033,7 +2038,7 @@ score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, if (tally.energyout_filter_ > 0) { // Fission has multiple outgoing neutrons so this helper function // is used to handle scoring the multiple filter bins. - score_fission_eout(p, i_tally, score_index+1, score_bin); + score_fission_eout(p, i_tally, score_index, score_bin); continue; } } @@ -2083,7 +2088,7 @@ score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, if (tally.energyout_filter_ > 0) { // Fission has multiple outgoing neutrons so this helper function // is used to handle scoring the multiple filter bins. - score_fission_eout(p, i_tally, score_index+1, score_bin); + score_fission_eout(p, i_tally, score_index, score_bin); continue; } } @@ -2137,7 +2142,7 @@ score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, if (tally.energyout_filter_ > 0) { // Fission has multiple outgoing neutrons so this helper function // is used to handle scoring the multiple filter bins. - score_fission_eout(p, i_tally, score_index+1, score_bin); + score_fission_eout(p, i_tally, score_index, score_bin); continue; } } @@ -2167,7 +2172,7 @@ score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); } score_fission_delayed_dg(i_tally, d_bin+1, score, - score_index+1); + score_index); } continue; } else { @@ -2211,7 +2216,7 @@ score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, * get_nuclide_xs(i_nuclide+1, MG_GET_XS_FISSION, p_g) / get_macro_xs(p->material, MG_GET_XS_FISSION, p_g); } - score_fission_delayed_dg(i_tally, d_bin+1, score, score_index+1); + score_fission_delayed_dg(i_tally, d_bin+1, score, score_index); } continue; } else { @@ -2245,7 +2250,7 @@ score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, * get_macro_xs_c(p->material, MG_GET_XS_DELAYED_NU_FISSION, p_g, nullptr, nullptr, &d); } - score_fission_delayed_dg(i_tally, d_bin+1, score, score_index+1); + score_fission_delayed_dg(i_tally, d_bin+1, score, score_index); } continue; } else { @@ -2293,7 +2298,7 @@ score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); } score_fission_delayed_dg(i_tally, d_bin+1, score, - score_index+1); + score_index); } continue; } else { @@ -2358,7 +2363,7 @@ score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, auto d = filt.groups_[d_bin]; if (d == g) score_fission_delayed_dg(i_tally, d_bin+1, score, - score_index+1); + score_index); } score = 0.; } @@ -2388,7 +2393,7 @@ score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, * get_macro_xs_c(p->material, MG_GET_XS_DELAYED_NU_FISSION, p_g, nullptr, nullptr, &d); } - score_fission_delayed_dg(i_tally, d_bin+1, score, score_index+1); + score_fission_delayed_dg(i_tally, d_bin+1, score, score_index); } continue; } else { @@ -2805,11 +2810,11 @@ score_surface_tally_inner(Particle* p, const std::vector& tallies) // There is only one score type for current tallies so there is no need // for a further scoring function. double score = flux * filter_weight; - for (auto score_index = 1; score_index < tally.scores_.size()+1; + for (auto score_index = 0; score_index < tally.scores_.size(); ++score_index) { //TODO: off-by-one #pragma omp atomic - results(filter_index-1, score_index-1, RESULT_VALUE) += score; + results(filter_index-1, score_index, RESULT_VALUE) += score; } } From 2286b245bb556ee465cec3ac32f6f3ae8e46e143 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sun, 10 Feb 2019 14:09:15 -0500 Subject: [PATCH 41/58] Remove C++/Fortran interop code --- CMakeLists.txt | 2 - include/openmc/mgxs_interface.h | 21 ++-- src/mgxs_interface.F90 | 47 -------- src/mgxs_interface.cpp | 31 +----- src/tallies/filter_delayedgroup.cpp | 7 -- src/tallies/filter_energy.cpp | 18 --- src/tallies/tally.cpp | 127 ++++++++++------------ src/tallies/tally_filter.F90 | 2 - src/tallies/tally_filter_delayedgroup.F90 | 38 ------- src/tallies/tally_filter_energy.F90 | 91 ---------------- src/tallies/tally_filter_header.F90 | 9 ++ 11 files changed, 81 insertions(+), 312 deletions(-) delete mode 100644 src/tallies/tally_filter_delayedgroup.F90 delete mode 100644 src/tallies/tally_filter_energy.F90 diff --git a/CMakeLists.txt b/CMakeLists.txt index 480f6f772e..b174b069c4 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -350,9 +350,7 @@ add_library(libopenmc SHARED src/tallies/tally_derivative_header.F90 src/tallies/tally_filter.F90 src/tallies/tally_filter_header.F90 - src/tallies/tally_filter_delayedgroup.F90 src/tallies/tally_filter_distribcell.F90 - src/tallies/tally_filter_energy.F90 src/tallies/tally_filter_particle.F90 src/tallies/tally_filter_sph_harm.F90 src/tallies/tally_header.F90 diff --git a/include/openmc/mgxs_interface.h b/include/openmc/mgxs_interface.h index a6c9e19c1f..413cdad621 100644 --- a/include/openmc/mgxs_interface.h +++ b/include/openmc/mgxs_interface.h @@ -51,20 +51,19 @@ extern "C" void calculate_xs_c(int i_mat, int gin, double sqrtkT, const double uvw[3], double& total_xs, double& abs_xs, double& nu_fiss_xs); -extern "C" double -get_nuclide_xs_c(int index, int xstype, int gin, int* gout, double* mu, int* dg); +double +get_nuclide_xs(int index, int xstype, int gin, int* gout, double* mu, int* dg); -extern "C" double -get_macro_xs_c(int index, int xstype, int gin, int* gout, double* mu, int* dg); +inline double +get_nuclide_xs(int index, int xstype, int gin) +{return get_nuclide_xs(index, xstype, gin, nullptr, nullptr, nullptr);} -extern "C" void -set_nuclide_angle_index_c(int index, const double uvw[3]); +double +get_macro_xs(int index, int xstype, int gin, int* gout, double* mu, int* dg); -extern "C" void -set_macro_angle_index_c(int index, const double uvw[3]); - -extern "C" void -set_nuclide_temperature_index_c(int index, double sqrtkT); +inline double +get_macro_xs(int index, int xstype, int gin) +{return get_macro_xs(index, xstype, gin, nullptr, nullptr, nullptr);} //============================================================================== // General Mgxs methods diff --git a/src/mgxs_interface.F90 b/src/mgxs_interface.F90 index 88b3c20e01..9daffc6d13 100644 --- a/src/mgxs_interface.F90 +++ b/src/mgxs_interface.F90 @@ -35,53 +35,6 @@ module mgxs_interface real(C_DOUBLE) :: awr end function get_awr_c - function get_nuclide_xs_c(index, xstype, gin, gout, mu, dg) result(val) & - bind(C) - use ISO_C_BINDING - implicit none - integer(C_INT), value, intent(in) :: index - integer(C_INT), value, intent(in) :: xstype - integer(C_INT), value, intent(in) :: gin - integer(C_INT), optional, intent(in) :: gout - real(C_DOUBLE), optional, intent(in) :: mu - integer(C_INT), optional, intent(in) :: dg - real(C_DOUBLE) :: val - end function get_nuclide_xs_c - - function get_macro_xs_c(index, xstype, gin, gout, mu, dg) result(val) & - bind(C) - use ISO_C_BINDING - implicit none - integer(C_INT), value, intent(in) :: index - integer(C_INT), value, intent(in) :: xstype - integer(C_INT), value, intent(in) :: gin - integer(C_INT), optional, intent(in) :: gout - real(C_DOUBLE), optional, intent(in) :: mu - integer(C_INT), optional, intent(in) :: dg - real(C_DOUBLE) :: val - end function get_macro_xs_c - - subroutine set_nuclide_angle_index_c(index, uvw) bind(C) - use ISO_C_BINDING - implicit none - integer(C_INT), value, intent(in) :: index - real(C_DOUBLE), intent(in) :: uvw(1:3) - end subroutine set_nuclide_angle_index_c - - subroutine set_macro_angle_index_c(index, uvw) bind(C) - use ISO_C_BINDING - implicit none - integer(C_INT), value, intent(in) :: index - real(C_DOUBLE), intent(in) :: uvw(1:3) - end subroutine set_macro_angle_index_c - - subroutine set_nuclide_temperature_index_c(index, sqrtkT) bind(C) - use ISO_C_BINDING - implicit none - integer(C_INT), value, intent(in) :: index - real(C_DOUBLE), value, intent(in) :: sqrtkT - end subroutine set_nuclide_temperature_index_c - end interface ! Number of energy groups diff --git a/src/mgxs_interface.cpp b/src/mgxs_interface.cpp index df08a6bb1b..546e921005 100644 --- a/src/mgxs_interface.cpp +++ b/src/mgxs_interface.cpp @@ -231,7 +231,7 @@ calculate_xs_c(int i_mat, int gin, double sqrtkT, const double uvw[3], //============================================================================== double -get_nuclide_xs_c(int index, int xstype, int gin, int* gout, double* mu, int* dg) +get_nuclide_xs(int index, int xstype, int gin, int* gout, double* mu, int* dg) { int gout_c; int* gout_c_p; @@ -255,7 +255,7 @@ get_nuclide_xs_c(int index, int xstype, int gin, int* gout, double* mu, int* dg) //============================================================================== double -get_macro_xs_c(int index, int xstype, int gin, int* gout, double* mu, int* dg) +get_macro_xs(int index, int xstype, int gin, int* gout, double* mu, int* dg) { int gout_c; int* gout_c_p; @@ -276,33 +276,6 @@ get_macro_xs_c(int index, int xstype, int gin, int* gout, double* mu, int* dg) return data::macro_xs[index - 1].get_xs(xstype, gin - 1, gout_c_p, mu, dg_c_p); } -//============================================================================== - -void -set_nuclide_angle_index_c(int index, const double uvw[3]) -{ - // Update the values - data::nuclides_MG[index - 1].set_angle_index(uvw); -} - -//============================================================================== - -void -set_macro_angle_index_c(int index, const double uvw[3]) -{ - // Update the values - data::macro_xs[index - 1].set_angle_index(uvw); -} - -//============================================================================== - -void -set_nuclide_temperature_index_c(int index, double sqrtkT) -{ - // Update the values - data::nuclides_MG[index - 1].set_temperature_index(sqrtkT); -} - //============================================================================== // General Mgxs methods //============================================================================== diff --git a/src/tallies/filter_delayedgroup.cpp b/src/tallies/filter_delayedgroup.cpp index 2b63027cf4..f1876fae9c 100644 --- a/src/tallies/filter_delayedgroup.cpp +++ b/src/tallies/filter_delayedgroup.cpp @@ -48,11 +48,4 @@ DelayedGroupFilter::text_label(int bin) const return "Delayed Group " + std::to_string(groups_[bin-1]); } -//============================================================================== -// Fortran interoperability -//============================================================================== - -extern "C" int delayedgroup_filter_groups(DelayedGroupFilter* filt, int i) -{return filt->groups_[i-1];} - } // namespace openmc diff --git a/src/tallies/filter_energy.cpp b/src/tallies/filter_energy.cpp index 8ea574964d..5a45dbb037 100644 --- a/src/tallies/filter_energy.cpp +++ b/src/tallies/filter_energy.cpp @@ -112,24 +112,6 @@ EnergyoutFilter::text_label(int bin) const return out.str(); } -//============================================================================== -// Fortran interoperability -//============================================================================== - -extern "C" bool energy_filter_matches_transport_groups(EnergyFilter* filt) -{return filt->matches_transport_groups_;} - -extern "C" int -energy_filter_search(const EnergyFilter* filt, double val) -{ - if (val < filt->bins_.front() || val > filt->bins_.back()) { - return -1; - } else { - //TODO: off-by-one - return lower_bound_index(filt->bins_.begin(), filt->bins_.end(), val) + 1; - } -} - //============================================================================== // C-API functions //============================================================================== diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 0be89de6e6..3b94b70e3e 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -8,6 +8,7 @@ #include "openmc/mgxs_interface.h" #include "openmc/nuclide.h" #include "openmc/reaction_product.h" +#include "openmc/search.h" #include "openmc/settings.h" #include "openmc/simulation.h" #include "openmc/tallies/derivative.h" @@ -46,19 +47,6 @@ energy_filter_search(const EnergyFilter* filt, double val); extern "C" double get_precursor_decay_rate(int i_nuclide, int d); -//============================================================================== -// MG mode helper functions -//TODO: move these or remove the need for them -//============================================================================== - -double -get_nuclide_xs(int index, int xstype, int gin) -{return get_nuclide_xs_c(index, xstype, gin, nullptr, nullptr, nullptr);} - -double -get_macro_xs(int index, int xstype, int gin) -{return get_macro_xs_c(index, xstype, gin, nullptr, nullptr, nullptr);} - //============================================================================== // Global variable definitions //============================================================================== @@ -772,10 +760,15 @@ score_fission_eout(Particle* p, int i_tally, int i_score, int score_bin) E_out = data::energy_bin_avg[static_cast(bank.E)]; } - //TODO: do this without the extern "C" function - auto i_match = energy_filter_search(&eo_filt, E_out); - if (i_match == -1) continue; - simulation::filter_matches[i_eout_filt].bins_[i_bin-1] = i_match; + // Set EnergyoutFilter bin index + if (E_out < eo_filt.bins_.front() || E_out > eo_filt.bins_.back()) { + continue; + } else { + //TODO: off-by-one + auto i_match = lower_bound_index(eo_filt.bins_.begin(), + eo_filt.bins_.end(), E_out) + 1; + simulation::filter_matches[i_eout_filt].bins_[i_bin-1] = i_match; + } } @@ -1922,17 +1915,17 @@ score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, score = p->last_wgt * flux; if (i_nuclide >= 0) { score *= atom_density - * get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_SCATTER_FMU_MULT, - p->last_g, &p->g, &p->mu, nullptr) - / get_macro_xs_c(p->material, MG_GET_XS_SCATTER_FMU_MULT, - p->last_g, &p->g, &p->mu, nullptr); + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_SCATTER_FMU_MULT, + p->last_g, &p->g, &p->mu, nullptr) + / get_macro_xs(p->material, MG_GET_XS_SCATTER_FMU_MULT, + p->last_g, &p->g, &p->mu, nullptr); } } else { if (i_nuclide >= 0) { - score = atom_density * flux * get_nuclide_xs_c( + score = atom_density * flux * get_nuclide_xs( i_nuclide+1, MG_GET_XS_SCATTER_MULT, p_g, nullptr, &p->mu, nullptr); } else { - score = flux * get_macro_xs_c( + score = flux * get_macro_xs( p->material, MG_GET_XS_SCATTER_MULT, p_g, nullptr, &p->mu, nullptr); } } @@ -1952,10 +1945,10 @@ score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, // adjust the score by the actual probability for that nuclide. if (i_nuclide >= 0) { score *= atom_density - * get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_SCATTER_FMU, - p->last_g, &p->g, &p->mu, nullptr) - / get_macro_xs_c(p->material, MG_GET_XS_SCATTER_FMU, - p->last_g, &p->g, &p->mu, nullptr); + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_SCATTER_FMU, + p->last_g, &p->g, &p->mu, nullptr) + / get_macro_xs(p->material, MG_GET_XS_SCATTER_FMU, + p->last_g, &p->g, &p->mu, nullptr); } } else { if (i_nuclide >= 0) { @@ -2162,12 +2155,12 @@ score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, score = p->absorb_wgt * flux; if (i_nuclide >= 0) { score *= - get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, + get_nuclide_xs(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, p_g, nullptr, nullptr, &d) / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); } else { score *= - get_macro_xs_c(p->material, MG_GET_XS_DELAYED_NU_FISSION, + get_macro_xs(p->material, MG_GET_XS_DELAYED_NU_FISSION, p_g, nullptr, nullptr, &d) / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); } @@ -2243,12 +2236,12 @@ score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, auto d = filt.groups_[d_bin]; if (i_nuclide >= 0) { score = flux * atom_density - * get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, - p_g, nullptr, nullptr, &d); + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, + p_g, nullptr, nullptr, &d); } else { score = flux - * get_macro_xs_c(p->material, MG_GET_XS_DELAYED_NU_FISSION, - p_g, nullptr, nullptr, &d); + * get_macro_xs(p->material, MG_GET_XS_DELAYED_NU_FISSION, + p_g, nullptr, nullptr, &d); } score_fission_delayed_dg(i_tally, d_bin+1, score, score_index); } @@ -2284,17 +2277,17 @@ score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, score = p->absorb_wgt * flux; if (i_nuclide >= 0) { score *= - get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DECAY_RATE, + get_nuclide_xs(i_nuclide+1, MG_GET_XS_DECAY_RATE, + p_g, nullptr, nullptr, &d) + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, p_g, nullptr, nullptr, &d) - *get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, - p_g, nullptr, nullptr, &d) / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); } else { score *= - get_macro_xs_c(p->material, MG_GET_XS_DECAY_RATE, + get_macro_xs(p->material, MG_GET_XS_DECAY_RATE, + p_g, nullptr, nullptr, &d) + * get_macro_xs(p->material, MG_GET_XS_DELAYED_NU_FISSION, p_g, nullptr, nullptr, &d) - * get_macro_xs_c(p->material, MG_GET_XS_DELAYED_NU_FISSION, - p_g, nullptr, nullptr, &d) / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); } score_fission_delayed_dg(i_tally, d_bin+1, score, @@ -2310,17 +2303,17 @@ score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, for (auto d = 0; d < data::num_delayed_groups; ++d) { if (i_nuclide >= 0) { score += p->absorb_wgt * flux - * get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DECAY_RATE, - p_g, nullptr, nullptr, &d) - *get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, - p_g, nullptr, nullptr, &d) + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_DECAY_RATE, + p_g, nullptr, nullptr, &d) + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, + p_g, nullptr, nullptr, &d) / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); } else { score += p->absorb_wgt * flux - * get_macro_xs_c(p->material, MG_GET_XS_DECAY_RATE, - p_g, nullptr, nullptr, &d) - * get_macro_xs_c(p->material, MG_GET_XS_DELAYED_NU_FISSION, - p_g, nullptr, nullptr, &d) + * get_macro_xs(p->material, MG_GET_XS_DECAY_RATE, + p_g, nullptr, nullptr, &d) + * get_macro_xs(p->material, MG_GET_XS_DELAYED_NU_FISSION, + p_g, nullptr, nullptr, &d) / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); } } @@ -2344,14 +2337,14 @@ score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, if (g != 0) { if (i_nuclide >= 0) { score += simulation::keff * atom_density * bank.wgt * flux - * get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DECAY_RATE, p_g, - nullptr, nullptr, &g) + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_DECAY_RATE, p_g, + nullptr, nullptr, &g) * get_nuclide_xs(i_nuclide+1, MG_GET_XS_FISSION, p_g) / get_macro_xs(p->material, MG_GET_XS_FISSION, p_g); } else { score += simulation::keff * bank.wgt * flux - * get_macro_xs_c(p->material, MG_GET_XS_DECAY_RATE, p_g, - nullptr, nullptr, &g); + * get_macro_xs(p->material, MG_GET_XS_DECAY_RATE, p_g, + nullptr, nullptr, &g); } if (tally.delayedgroup_filter_ > 0) { auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; @@ -2382,16 +2375,16 @@ score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, auto d = filt.groups_[d_bin]; if (i_nuclide >= 0) { score += atom_density * flux - * get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DECAY_RATE, - p_g, nullptr, nullptr, &d) - * get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, - p_g, nullptr, nullptr, &d); + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_DECAY_RATE, + p_g, nullptr, nullptr, &d) + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, + p_g, nullptr, nullptr, &d); } else { score += flux - * get_macro_xs_c(p->material, MG_GET_XS_DECAY_RATE, - p_g, nullptr, nullptr, &d) - * get_macro_xs_c(p->material, MG_GET_XS_DELAYED_NU_FISSION, - p_g, nullptr, nullptr, &d); + * get_macro_xs(p->material, MG_GET_XS_DECAY_RATE, + p_g, nullptr, nullptr, &d) + * get_macro_xs(p->material, MG_GET_XS_DELAYED_NU_FISSION, + p_g, nullptr, nullptr, &d); } score_fission_delayed_dg(i_tally, d_bin+1, score, score_index); } @@ -2401,16 +2394,16 @@ score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, for (auto d = 0; d < data::num_delayed_groups; ++d) { if (i_nuclide >= 0) { score += atom_density * flux - * get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DECAY_RATE, - p_g, nullptr, nullptr, &d) - * get_nuclide_xs_c(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, - p_g, nullptr, nullptr, &d); + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_DECAY_RATE, + p_g, nullptr, nullptr, &d) + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, + p_g, nullptr, nullptr, &d); } else { score += flux - * get_macro_xs_c(p->material, MG_GET_XS_DECAY_RATE, - p_g, nullptr, nullptr, &d) - * get_macro_xs_c(p->material, MG_GET_XS_DELAYED_NU_FISSION, - p_g, nullptr, nullptr, &d); + * get_macro_xs(p->material, MG_GET_XS_DECAY_RATE, + p_g, nullptr, nullptr, &d) + * get_macro_xs(p->material, MG_GET_XS_DELAYED_NU_FISSION, + p_g, nullptr, nullptr, &d); } } } diff --git a/src/tallies/tally_filter.F90 b/src/tallies/tally_filter.F90 index 231fe55256..58b6a196e0 100644 --- a/src/tallies/tally_filter.F90 +++ b/src/tallies/tally_filter.F90 @@ -7,9 +7,7 @@ module tally_filter use tally_filter_header ! Inherit other filters - use tally_filter_delayedgroup use tally_filter_distribcell - use tally_filter_energy use tally_filter_particle use tally_filter_sph_harm diff --git a/src/tallies/tally_filter_delayedgroup.F90 b/src/tallies/tally_filter_delayedgroup.F90 deleted file mode 100644 index deebb0b1b0..0000000000 --- a/src/tallies/tally_filter_delayedgroup.F90 +++ /dev/null @@ -1,38 +0,0 @@ -module tally_filter_delayedgroup - - use, intrinsic :: ISO_C_BINDING - - use tally_filter_header - - implicit none - private - -!=============================================================================== -! DELAYEDGROUPFILTER bins outgoing fission neutrons in their delayed groups. -! The get_all_bins functionality is not actually used. The bins are manually -! iterated over in the scoring subroutines. -!=============================================================================== - - type, public, extends(TallyFilter) :: DelayedGroupFilter - contains - procedure :: groups - end type DelayedGroupFilter - -contains - - function groups(this, i) result(group) - class(DelayedGroupFilter), intent(in) :: this - integer, intent(in) :: i - integer :: group - interface - function delayedgroup_filter_groups(filt, i) result(group) bind(C) - import C_PTR, C_INT - type(C_PTR), value :: filt - integer(C_INT), value :: i - integer(C_INT) :: group - end function - end interface - group = delayedgroup_filter_groups(this % ptr, i) - end function groups - -end module tally_filter_delayedgroup diff --git a/src/tallies/tally_filter_energy.F90 b/src/tallies/tally_filter_energy.F90 deleted file mode 100644 index 851de7ee8f..0000000000 --- a/src/tallies/tally_filter_energy.F90 +++ /dev/null @@ -1,91 +0,0 @@ -module tally_filter_energy - - use, intrinsic :: ISO_C_BINDING - - use tally_filter_header - use xml_interface - - implicit none - private - public :: openmc_energy_filter_get_bins - public :: openmc_energy_filter_set_bins - - interface - function openmc_energy_filter_get_bins(index, energies, n) result(err) bind(C) - import C_INT32_T, C_PTR, C_INT - integer(C_INT32_T), value :: index - type(C_PTR), intent(out) :: energies - integer(C_INT32_T), intent(out) :: n - integer(C_INT) :: err - end function - function openmc_energy_filter_set_bins(index, n, energies) result(err) bind(C) - import C_INT32_T, C_DOUBLE, C_INT - integer(C_INT32_T), value, intent(in) :: index - integer(C_INT32_T), value, intent(in) :: n - real(C_DOUBLE), intent(in) :: energies(n) - integer(C_INT) :: err - end function - end interface - -!=============================================================================== -! ENERGYFILTER bins the incident neutron energy. -!=============================================================================== - - type, public, extends(TallyFilter) :: EnergyFilter - ! True if transport group number can be used directly to get bin number - logical :: matches_transport_groups = .false. - contains - procedure :: from_xml => from_xml_energy - procedure :: search - end type EnergyFilter - -!=============================================================================== -! ENERGYOUTFILTER bins the outgoing neutron energy. Only scattering events use -! the get_all_bins functionality. Nu-fission tallies manually iterate over the -! filter bins. -!=============================================================================== - - type, public, extends(EnergyFilter) :: EnergyoutFilter - end type EnergyoutFilter - -contains - -!=============================================================================== -! EnergyFilter methods -!=============================================================================== - - subroutine from_xml_energy(this, node) - class(EnergyFilter), intent(inout) :: this - type(XMLNode), intent(in) :: node - - interface - function energy_filter_matches_transport_groups(filt) result(matches) & - bind(C) - import C_PTR, C_BOOL - type(C_PTR), value :: filt - logical(C_BOOL) :: matches - end function - end interface - - call this % from_xml_cpp(node) - this % n_bins = this % n_bins_cpp() - this % matches_transport_groups = & - energy_filter_matches_transport_groups(this % ptr) - end subroutine from_xml_energy - - function search(this, val) result(bin) - class(EnergyFilter), intent(in) :: this - real(8), intent(in) :: val - integer :: bin - interface - function energy_filter_search(filt, val) result(bin) bind(C) - import C_PTR, C_DOUBLE, C_INT - type(C_PTR), value :: filt - real(C_DOUBLE), value :: val - integer(C_INT) :: bin - end function - end interface - bin = energy_filter_search(this % ptr, val) - end function search - -end module tally_filter_energy diff --git a/src/tallies/tally_filter_header.F90 b/src/tallies/tally_filter_header.F90 index 2e1e7d6609..2c7daac3ba 100644 --- a/src/tallies/tally_filter_header.F90 +++ b/src/tallies/tally_filter_header.F90 @@ -79,6 +79,15 @@ module tally_filter_header type, public, extends(CellFilter) :: CellFromFilter end type + type, public, extends(TallyFilter) :: DelayedGroupFilter + end type + + type, public, extends(TallyFilter) :: EnergyFilter + end type + + type, public, extends(EnergyFilter) :: EnergyoutFilter + end type + type, public, extends(TallyFilter) :: EnergyFunctionFilter end type From cdb10ef2ccc3e5c28dd4956048c13c1edb1d8ec2 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sun, 10 Feb 2019 18:43:48 -0500 Subject: [PATCH 42/58] Move score_*_derivative to C++ --- include/openmc/nuclide.h | 2 + src/nuclide.cpp | 2 +- src/tallies/derivative.cpp | 133 ++++++++++++++++++++++++++++++ src/tallies/tally.F90 | 161 +++---------------------------------- 4 files changed, 147 insertions(+), 151 deletions(-) diff --git a/include/openmc/nuclide.h b/include/openmc/nuclide.h index dd4dfc678e..be81dd917b 100644 --- a/include/openmc/nuclide.h +++ b/include/openmc/nuclide.h @@ -176,6 +176,8 @@ private: //! Checks for the right version of nuclear data within HDF5 files void check_data_version(hid_t file_id); +extern "C" bool multipole_in_range(const Nuclide* nuc, double E); + //============================================================================== // Global variables //============================================================================== diff --git a/src/nuclide.cpp b/src/nuclide.cpp index b507b9130e..057b7c1130 100644 --- a/src/nuclide.cpp +++ b/src/nuclide.cpp @@ -990,7 +990,7 @@ extern "C" void multipole_deriv_eval(Nuclide* nuc, double E, double sqrtkT, std::tie(*sig_s, *sig_a, *sig_f) = nuc->multipole_->evaluate_deriv(E, sqrtkT); } -extern "C" bool multipole_in_range(Nuclide* nuc, double E) +extern "C" bool multipole_in_range(const Nuclide* nuc, double E) { return nuc->multipole_ && E >= nuc->multipole_->E_min_&& E <= nuc->multipole_->E_max_; diff --git a/src/tallies/derivative.cpp b/src/tallies/derivative.cpp index 5908ce3387..9c9571bb28 100644 --- a/src/tallies/derivative.cpp +++ b/src/tallies/derivative.cpp @@ -1,9 +1,12 @@ #include "openmc/error.h" +#include "openmc/material.h" #include "openmc/nuclide.h" #include "openmc/settings.h" #include "openmc/tallies/derivative.h" #include "openmc/xml_interface.h" +#include + template class std::vector; namespace openmc { @@ -102,6 +105,136 @@ read_tally_derivatives(pugi::xml_node* node) fatal_error("Differential tallies not supported in multi-group mode"); } +//! Adjust diff tally flux derivatives for a particle tracking event. + +extern "C" void +score_track_derivative(Particle* p, double distance) +{ + // A void material cannot be perturbed so it will not affect flux derivatives. + if (p->material == MATERIAL_VOID) return; + //TODO: off-by-one + const Material& material {*model::materials[p->material-1]}; + + for (auto& deriv : model::tally_derivs) { + if (deriv.diff_material != material.id_) continue; + + switch (deriv.variable) { + + case DIFF_DENSITY: + // phi is proportional to e^(-Sigma_tot * dist) + // (1 / phi) * (d_phi / d_rho) = - (d_Sigma_tot / d_rho) * dist + // (1 / phi) * (d_phi / d_rho) = - Sigma_tot / rho * dist + deriv.flux_deriv -= distance * simulation::material_xs.total + / material.density_gpcc_; + break; + + case DIFF_NUCLIDE_DENSITY: + // phi is proportional to e^(-Sigma_tot * dist) + // (1 / phi) * (d_phi / d_N) = - (d_Sigma_tot / d_N) * dist + // (1 / phi) * (d_phi / d_N) = - sigma_tot * dist + //TODO: off-by-one + deriv.flux_deriv -= distance + * simulation::micro_xs[deriv.diff_nuclide-1].total; + break; + + case DIFF_TEMPERATURE: + for (auto i = 0; i < material.nuclide_.size(); ++i) { + const auto& nuc {*data::nuclides[material.nuclide_[i]]}; + //TODO: off-by-one + if (multipole_in_range(&nuc, p->last_E)) { + // phi is proportional to e^(-Sigma_tot * dist) + // (1 / phi) * (d_phi / d_T) = - (d_Sigma_tot / d_T) * dist + // (1 / phi) * (d_phi / d_T) = - N (d_sigma_tot / d_T) * dist + double dsig_s, dsig_a, dsig_f; + std::tie(dsig_s, dsig_a, dsig_f) + = nuc.multipole_->evaluate_deriv(p->E, p->sqrtkT); + deriv.flux_deriv -= distance * (dsig_s + dsig_a) + * material.atom_density_(i); + } + } + break; + } + } +} + +//! Adjust diff tally flux derivatives for a particle scattering event. +// +//! Note that this subroutine will be called after absorption events in +//! addition to scattering events, but any flux derivatives scored after an +//! absorption will never be tallied. The paricle will be killed before any +//! further tallies are scored. + +extern "C" void +score_collision_derivative(Particle* p) +{ + // A void material cannot be perturbed so it will not affect flux derivatives. + if (p->material == MATERIAL_VOID) return; + //TODO: off-by-one + const Material& material {*model::materials[p->material-1]}; + + for (auto& deriv : model::tally_derivs) { + if (deriv.diff_material != material.id_) continue; + + switch (deriv.variable) { + + case DIFF_DENSITY: + // phi is proportional to Sigma_s + // (1 / phi) * (d_phi / d_rho) = (d_Sigma_s / d_rho) / Sigma_s + // (1 / phi) * (d_phi / d_rho) = 1 / rho + deriv.flux_deriv += 1. / material.density_gpcc_; + break; + + case DIFF_NUCLIDE_DENSITY: + //TODO: off-by-one throughout on diff_nuclide + if (p->event_nuclide != deriv.diff_nuclide) continue; + // Find the index in this material for the diff_nuclide. + int i; + for (i = 0; i < material.nuclide_.size(); ++i) + if (material.nuclide_[i] == deriv.diff_nuclide - 1) break; + // Make sure we found the nuclide. + if (material.nuclide_[i] != deriv.diff_nuclide - 1) { + std::stringstream err_msg; + err_msg << "Could not find nuclide " + << data::nuclides[deriv.diff_nuclide-1]->name_ << " in material " + << material.id_ << " for tally derivative " << deriv.id; + fatal_error(err_msg); + } + // phi is proportional to Sigma_s + // (1 / phi) * (d_phi / d_N) = (d_Sigma_s / d_N) / Sigma_s + // (1 / phi) * (d_phi / d_N) = sigma_s / Sigma_s + // (1 / phi) * (d_phi / d_N) = 1 / N + deriv.flux_deriv += 1. / material.atom_density_(i); + break; + + case DIFF_TEMPERATURE: + // Loop over the material's nuclides until we find the event nuclide. + for (auto i_nuc : material.nuclide_) { + const auto& nuc {*data::nuclides[i_nuc]}; + //TODO: off-by-one + if (i_nuc == p->event_nuclide - 1 + && multipole_in_range(&nuc, p->last_E)) { + // phi is proportional to Sigma_s + // (1 / phi) * (d_phi / d_T) = (d_Sigma_s / d_T) / Sigma_s + // (1 / phi) * (d_phi / d_T) = (d_sigma_s / d_T) / sigma_s + const auto& micro_xs {simulation::micro_xs[i_nuc]}; + double dsig_s, dsig_a, dsig_f; + std::tie(dsig_s, dsig_a, dsig_f) + = nuc.multipole_->evaluate_deriv(p->last_E, p->sqrtkT); + deriv.flux_deriv += dsig_s / (micro_xs.total - micro_xs.absorption); + // Note that this is an approximation! The real scattering cross + // section is + // Sigma_s(E'->E, uvw'->uvw) = Sigma_s(E') * P(E'->E, uvw'->uvw). + // We are assuming that d_P(E'->E, uvw'->uvw) / d_T = 0 and only + // computing d_S(E') / d_T. Using this approximation in the vicinity + // of low-energy resonances causes errors (~2-5% for PWR pincell + // eigenvalue derivatives). + } + } + break; + } + } +} + //! Set the flux derivatives on differential tallies to zero. extern "C" void zero_flux_derivs() diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index a83a9c21be..854aab7515 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -65,6 +65,17 @@ module tally type(Particle) :: p end subroutine + subroutine score_track_derivative(p, distance) bind(C) + import Particle, C_DOUBLE + type(Particle) :: p + real(C_DOUBLE), value :: distance + end subroutine + + subroutine score_collision_derivative(p) bind(C) + import Particle + type(Particle) :: p + end subroutine + subroutine zero_flux_derivs() bind(C) end subroutine end interface @@ -643,156 +654,6 @@ contains end associate end subroutine apply_derivative_to_score -!=============================================================================== -! SCORE_TRACK_DERIVATIVE Adjust flux derivatives on differential tallies to -! account for a neutron travelling through a perturbed material. -!=============================================================================== - - subroutine score_track_derivative(p, distance) - type(Particle), intent(in) :: p - real(8), intent(in) :: distance ! Neutron flight distance - - type(TallyDerivative), pointer :: deriv - integer :: i, l - real(8) :: dsig_s, dsig_a, dsig_f - - ! A void material cannot be perturbed so it will not affect flux derivatives - if (p % material == MATERIAL_VOID) return - - do i = 0, n_tally_derivs() - 1 - !associate(deriv => tally_derivs(i)) - deriv => tally_deriv_c(i) - select case (deriv % variable) - - case (DIFF_DENSITY) - if (material_id(p % material) == deriv % diff_material) then - ! phi is proportional to e^(-Sigma_tot * dist) - ! (1 / phi) * (d_phi / d_rho) = - (d_Sigma_tot / d_rho) * dist - ! (1 / phi) * (d_phi / d_rho) = - Sigma_tot / rho * dist - deriv % flux_deriv = deriv % flux_deriv & - - distance * material_xs % total / material_density_gpcc(p % material) - end if - - case (DIFF_NUCLIDE_DENSITY) - if (material_id(p % material) == deriv % diff_material) then - ! phi is proportional to e^(-Sigma_tot * dist) - ! (1 / phi) * (d_phi / d_N) = - (d_Sigma_tot / d_N) * dist - ! (1 / phi) * (d_phi / d_N) = - sigma_tot * dist - deriv % flux_deriv = deriv % flux_deriv & - - distance * micro_xs(deriv % diff_nuclide) % total - end if - - case (DIFF_TEMPERATURE) - if (material_id(p % material) == deriv % diff_material) then - do l=1, material_nuclide_size(p % material) - associate (nuc => nuclides(material_nuclide(p % material, l))) - if (multipole_in_range(nuc % ptr, p % E)) then - ! phi is proportional to e^(-Sigma_tot * dist) - ! (1 / phi) * (d_phi / d_T) = - (d_Sigma_tot / d_T) * dist - ! (1 / phi) * (d_phi / d_T) = - N (d_sigma_tot / d_T) * dist - call multipole_deriv_eval(nuc % ptr, p % E, & - p % sqrtkT, dsig_s, dsig_a, dsig_f) - deriv % flux_deriv = deriv % flux_deriv & - - distance * (dsig_s + dsig_a) * material_atom_density(p % material, l) - end if - end associate - end do - end if - end select - !end associate - end do - end subroutine score_track_derivative - -!=============================================================================== -! SCORE_COLLISION_DERIVATIVE Adjust flux derivatives on differential tallies to -! account for a neutron scattering in the perturbed material. Note that this -! subroutine will be called after absorption events in addition to scattering -! events, but any flux derivatives scored after an absorption will never be -! tallied. This is because the order of operations is -! 1. Particle is moved. -! 2. score_track_derivative is called. -! 3. Collision physics are computed, and the particle is labeled absorbed. -! 4. Analog- and collision-estimated tallies are scored. -! 5. This subroutine is called. -! 6. Particle is killed and no more tallies are scored. -! Hence, it is safe to assume that only derivative of the scattering cross -! section need to be computed here. -!=============================================================================== - - subroutine score_collision_derivative(p) - type(Particle), intent(in) :: p - - type(TallyDerivative), pointer :: deriv - integer :: i, j, l, i_nuc - real(8) :: dsig_s, dsig_a, dsig_f - - ! A void material cannot be perturbed so it will not affect flux derivatives - if (p % material == MATERIAL_VOID) return - - do i = 0, n_tally_derivs() - 1 - !associate(deriv => tally_derivs(i)) - deriv => tally_deriv_c(i) - select case (deriv % variable) - - case (DIFF_DENSITY) - if (material_id(p % material) == deriv % diff_material) then - ! phi is proportional to Sigma_s - ! (1 / phi) * (d_phi / d_rho) = (d_Sigma_s / d_rho) / Sigma_s - ! (1 / phi) * (d_phi / d_rho) = 1 / rho - deriv % flux_deriv = deriv % flux_deriv & - + ONE / material_density_gpcc(p % material) - end if - - case (DIFF_NUCLIDE_DENSITY) - if (material_id(p % material) == deriv % diff_material & - .and. p % event_nuclide == deriv % diff_nuclide) then - ! Find the index in this material for the diff_nuclide. - do j = 1, material_nuclide_size(p % material) - if (material_nuclide(p % material, j) == deriv % diff_nuclide) exit - end do - ! Make sure we found the nuclide. - if (material_nuclide(p % material, j) /= deriv % diff_nuclide) then - call fatal_error("Couldn't find the right nuclide.") - end if - ! phi is proportional to Sigma_s - ! (1 / phi) * (d_phi / d_N) = (d_Sigma_s / d_N) / Sigma_s - ! (1 / phi) * (d_phi / d_N) = sigma_s / Sigma_s - ! (1 / phi) * (d_phi / d_N) = 1 / N - deriv % flux_deriv = deriv % flux_deriv & - + ONE / material_atom_density(p % material, j) - end if - - case (DIFF_TEMPERATURE) - if (material_id(p % material) == deriv % diff_material) then - do l=1, material_nuclide_size(p % material) - i_nuc = material_nuclide(p % material, l) - associate (nuc => nuclides(i_nuc)) - if (i_nuc == p % event_nuclide .and. & - multipole_in_range(nuc % ptr, p % last_E)) then - ! phi is proportional to Sigma_s - ! (1 / phi) * (d_phi / d_T) = (d_Sigma_s / d_T) / Sigma_s - ! (1 / phi) * (d_phi / d_T) = (d_sigma_s / d_T) / sigma_s - call multipole_deriv_eval(nuc % ptr, p % last_E, & - p % sqrtkT, dsig_s, dsig_a, dsig_f) - deriv % flux_deriv = deriv % flux_deriv + dsig_s& - / (micro_xs(i_nuc) % total & - - micro_xs(i_nuc) % absorption) - ! Note that this is an approximation! The real scattering - ! cross section is Sigma_s(E'->E, uvw'->uvw) = - ! Sigma_s(E') * P(E'->E, uvw'->uvw). We are assuming that - ! d_P(E'->E, uvw'->uvw) / d_T = 0 and only computing - ! d_S(E') / d_T. Using this approximation in the vicinity - ! of low-energy resonances causes errors (~2-5% for PWR - ! pincell eigenvalue derivatives). - end if - end associate - end do - end if - end select - !end associate - end do - end subroutine score_collision_derivative - !=============================================================================== ! ACCUMULATE_TALLIES accumulates the sum of the contributions from each history ! within the batch to a new random variable From e29931fef7f61453db1b60cf93ba4ca59f55c962 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sun, 10 Feb 2019 20:17:04 -0500 Subject: [PATCH 43/58] Start moving apply_derivative_to_score to C++ --- src/tallies/derivative.cpp | 204 +++++++++++++++++++++++++++++++++ src/tallies/tally.F90 | 224 ++++++------------------------------- 2 files changed, 237 insertions(+), 191 deletions(-) diff --git a/src/tallies/derivative.cpp b/src/tallies/derivative.cpp index 9c9571bb28..fd8d080ad4 100644 --- a/src/tallies/derivative.cpp +++ b/src/tallies/derivative.cpp @@ -2,6 +2,7 @@ #include "openmc/material.h" #include "openmc/nuclide.h" #include "openmc/settings.h" +#include "openmc/tallies/tally.h" #include "openmc/tallies/derivative.h" #include "openmc/xml_interface.h" @@ -105,6 +106,209 @@ read_tally_derivatives(pugi::xml_node* node) fatal_error("Differential tallies not supported in multi-group mode"); } +extern "C" void +apply_derivative_to_score_c(Particle* p, int i_tally, int i_nuclide, + double atom_density, int score_bin, double* score) +{ + //TODO: off-by-one + const Tally& tally {*model::tallies[i_tally-1]}; + + if (*score == 0) return; + + // If our score was previously c then the new score is + // c * (1/f * d_f/d_p + 1/c * d_c/d_p) + // where (1/f * d_f/d_p) is the (logarithmic) flux derivative and p is the + // perturbated variable. + + const auto& deriv {model::tally_derivs[tally.deriv_]}; + auto flux_deriv = deriv.flux_deriv; + + // Handle special cases where we know that d_c/d_p must be zero. + if (score_bin == SCORE_FLUX) { + *score *= flux_deriv; + return; + } else if (p->material == MATERIAL_VOID) { + *score *= flux_deriv; + return; + } + //TODO: off-by-one + const Material& material {*model::materials[p->material-1]}; + if (material.id_ != deriv.diff_material) { + *score *= flux_deriv; + return; + } + + switch (deriv.variable) { + + //============================================================================ + // Density derivative: + // c = Sigma_MT + // c = sigma_MT * N + // c = sigma_MT * rho * const + // d_c / d_rho = sigma_MT * const + // (1 / c) * (d_c / d_rho) = 1 / rho + + case DIFF_DENSITY: + switch (tally.estimator_) { + + case ESTIMATOR_ANALOG: + case ESTIMATOR_COLLISION: + switch (score_bin) { + + case SCORE_TOTAL: + case SCORE_SCATTER: + case SCORE_ABSORPTION: + case SCORE_FISSION: + case SCORE_NU_FISSION: + *score *= flux_deriv + 1. / material.density_gpcc_; + break; + + default: + fatal_error("Tally derivative not defined for a score on tally " + + std::to_string(tally.id_)); + } + break; + + default: + fatal_error("Differential tallies are only implemented for analog and " + "collision estimators."); + } + break; + + //============================================================================ + // Nuclide density derivative: + // If we are scoring a reaction rate for a single nuclide then + // c = Sigma_MT_i + // c = sigma_MT_i * N_i + // d_c / d_N_i = sigma_MT_i + // (1 / c) * (d_c / d_N_i) = 1 / N_i + // If the score is for the total material (i_nuclide = -1) + // c = Sum_i(Sigma_MT_i) + // d_c / d_N_i = sigma_MT_i + // (1 / c) * (d_c / d_N) = sigma_MT_i / Sigma_MT + // where i is the perturbed nuclide. + + case DIFF_NUCLIDE_DENSITY: + //TODO: off-by-one throughout on diff_nuclide + switch (tally.estimator_) { + + case ESTIMATOR_ANALOG: + if (p->event_nuclide != deriv.diff_nuclide) { + *score *= flux_deriv; + return; + } + + switch (score_bin) { + + case SCORE_TOTAL: + case SCORE_SCATTER: + case SCORE_ABSORPTION: + case SCORE_FISSION: + case SCORE_NU_FISSION: + { + // Find the index of the perturbed nuclide. + int i; + for (i = 0; i < material.nuclide_.size(); ++i) + if (material.nuclide_[i] == deriv.diff_nuclide - 1) break; + *score *= flux_deriv + 1. / material.atom_density_(i); + } + break; + + default: + fatal_error("Tally derivative not defined for a score on tally " + + std::to_string(tally.id_)); + } + break; + + case ESTIMATOR_COLLISION: + switch (score_bin) { + + case SCORE_TOTAL: + if (i_nuclide == -1 && simulation::material_xs.total) { + *score *= flux_deriv + + simulation::micro_xs[deriv.diff_nuclide-1].total + / simulation::material_xs.total; + } else if (i_nuclide == deriv.diff_nuclide-1 + && simulation::micro_xs[i_nuclide].total) { + *score *= flux_deriv + 1. / atom_density; + } else { + *score *= flux_deriv; + } + break; + + case SCORE_SCATTER: + if (i_nuclide == -1 && (simulation::material_xs.total + - simulation::material_xs.absorption)) { + *score *= flux_deriv + + (simulation::micro_xs[deriv.diff_nuclide-1].total + - simulation::micro_xs[deriv.diff_nuclide-1].absorption) + / (simulation::material_xs.total + - simulation::material_xs.absorption); + } else if (i_nuclide == deriv.diff_nuclide-1) { + *score *= flux_deriv + 1. / atom_density; + } else { + *score *= flux_deriv; + } + break; + + case SCORE_ABSORPTION: + if (i_nuclide == -1 && simulation::material_xs.absorption) { + *score *= flux_deriv + + simulation::micro_xs[deriv.diff_nuclide-1].absorption + / simulation::material_xs.absorption; + } else if (i_nuclide == deriv.diff_nuclide-1 + && simulation::micro_xs[i_nuclide].absorption) { + *score *= flux_deriv + 1. / atom_density; + } else { + *score *= flux_deriv; + } + break; + + case SCORE_FISSION: + if (i_nuclide == -1 && simulation::material_xs.fission) { + *score *= flux_deriv + + simulation::micro_xs[deriv.diff_nuclide-1].fission + / simulation::material_xs.fission; + } else if (i_nuclide == deriv.diff_nuclide-1 + && simulation::micro_xs[i_nuclide].fission) { + *score *= flux_deriv + 1. / atom_density; + } else { + *score *= flux_deriv; + } + break; + + case SCORE_NU_FISSION: + if (i_nuclide == -1 && simulation::material_xs.nu_fission) { + *score *= flux_deriv + + simulation::micro_xs[deriv.diff_nuclide-1].nu_fission + / simulation::material_xs.nu_fission; + } else if (i_nuclide == deriv.diff_nuclide-1 + && simulation::micro_xs[i_nuclide].nu_fission) { + *score *= flux_deriv + 1. / atom_density; + } else { + *score *= flux_deriv; + } + break; + + default: + fatal_error("Tally derivative not defined for a score on tally " + + std::to_string(tally.id_)); + } + break; + + default: + fatal_error("Differential tallies are only implemented for analog and " + "collision estimators."); + } + break; + + //============================================================================ + + case DIFF_TEMPERATURE: + break; + } +} + //! Adjust diff tally flux derivatives for a particle tracking event. extern "C" void diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index 854aab7515..c5efaabf8e 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -115,6 +115,22 @@ contains real(8) :: flux_deriv real(8) :: dsig_s, dsig_a, dsig_f, cum_dsig + interface + subroutine apply_derivative_to_score_c(p, i_tally, i_nuclide, & + atom_density, score_bin, score) bind(C) + import Particle, C_INT, C_DOUBLE + type(Particle), intent(in) :: p + integer(C_INT), value, intent(in) :: i_tally + integer(C_INT), value, intent(in) :: i_nuclide + real(C_DOUBLE), value, intent(in) :: atom_density + integer(C_INT), value, intent(in) :: score_bin + real(C_DOUBLE), intent(inout) :: score + end subroutine + end interface + + call apply_derivative_to_score_c(p, i_tally, i_nuclide, atom_density, & + score_bin, score) + associate (t => tallies(i_tally) % obj) if (score == ZERO) return @@ -128,201 +144,26 @@ contains deriv => tally_deriv_c(t % deriv()) flux_deriv = deriv % flux_deriv + if (p % material == MATERIAL_VOID) then + return + else if (material_id(p % material) /= deriv % diff_material) then + return + end if + + if (deriv % variable == DIFF_NUCLIDE_DENSITY) then + if (t % estimator() == ESTIMATOR_ANALOG) then + if (p % event_nuclide /= deriv % diff_nuclide) return + end if + end if + !select case (tally_derivs(t % deriv()) % variable) select case (deriv % variable) - !========================================================================= - ! Density derivative: - ! c = Sigma_MT - ! c = sigma_MT * N - ! c = sigma_MT * rho * const - ! d_c / d_rho = sigma_MT * const - ! (1 / c) * (d_c / d_rho) = 1 / rho - case (DIFF_DENSITY) - select case (t % estimator()) - - case (ESTIMATOR_ANALOG) - - select case (score_bin) - - case (SCORE_FLUX) - score = score * flux_deriv - - case (SCORE_TOTAL, SCORE_SCATTER, SCORE_ABSORPTION, SCORE_FISSION, & - SCORE_NU_FISSION) - if (material_id(p % material) == deriv % diff_material) then - score = score * (flux_deriv + ONE & - / material_density_gpcc(p % material)) - else - score = score * flux_deriv - end if - - case default - call fatal_error('Tally derivative not defined for a score on & - &tally ' // trim(to_str(t % id()))) - end select - - case (ESTIMATOR_COLLISION) - - select case (score_bin) - - case (SCORE_FLUX) - score = score * flux_deriv - - case (SCORE_TOTAL, SCORE_SCATTER, SCORE_ABSORPTION, SCORE_FISSION, & - SCORE_NU_FISSION) - if (material_id(p % material) == deriv % diff_material) then - score = score * (flux_deriv + ONE & - / material_density_gpcc(p % material)) - else - score = score * flux_deriv - end if - - case default - call fatal_error('Tally derivative not defined for a score on & - &tally ' // trim(to_str(t % id()))) - end select - - case default - call fatal_error("Differential tallies are only implemented for & - &analog and collision estimators.") - end select - - !========================================================================= - ! Nuclide density derivative: - ! If we are scoring a reaction rate for a single nuclide then - ! c = Sigma_MT_i - ! c = sigma_MT_i * N_i - ! d_c / d_N_i = sigma_MT_i - ! (1 / c) * (d_c / d_N_i) = 1 / N_i - ! If the score is for the total material (i_nuclide = -1) - ! c = Sum_i(Sigma_MT_i) - ! d_c / d_N_i = sigma_MT_i - ! (1 / c) * (d_c / d_N) = sigma_MT_i / Sigma_MT - ! where i is the perturbed nuclide. + return case (DIFF_NUCLIDE_DENSITY) - select case (t % estimator()) - - case (ESTIMATOR_ANALOG) - - select case (score_bin) - - case (SCORE_FLUX) - score = score * flux_deriv - - case (SCORE_TOTAL, SCORE_SCATTER, SCORE_ABSORPTION, SCORE_FISSION, & - SCORE_NU_FISSION) - if (material_id(p % material) == deriv % diff_material & - .and. p % event_nuclide == deriv % diff_nuclide) then - ! Search for the index of the perturbed nuclide. - do l = 1, material_nuclide_size(p % material) - if (material_nuclide(p % material, l) == deriv % diff_nuclide) exit - end do - - score = score * (flux_deriv & - + ONE / material_atom_density(p % material, l)) - else - score = score * flux_deriv - end if - - case default - call fatal_error('Tally derivative not defined for a score on & - &tally ' // trim(to_str(t % id()))) - end select - - case (ESTIMATOR_COLLISION) - scoring_diff_nuclide = & - (material_id(p % material) == deriv % diff_material) & - .and. (i_nuclide+1 == deriv % diff_nuclide) - - select case (score_bin) - - case (SCORE_FLUX) - score = score * flux_deriv - - case (SCORE_TOTAL) - if (i_nuclide == -1 .and. & - material_id(p % material) == deriv % diff_material .and. & - material_xs % total /= ZERO) then - score = score * (flux_deriv & - + micro_xs(deriv % diff_nuclide) % total & - / material_xs % total) - else if (scoring_diff_nuclide .and. & - micro_xs(deriv % diff_nuclide) % total /= ZERO) then - score = score * (flux_deriv + ONE / atom_density) - else - score = score * flux_deriv - end if - - case (SCORE_SCATTER) - if (i_nuclide == -1 .and. & - material_id(p % material) == deriv % diff_material .and. & - material_xs % total - material_xs % absorption /= ZERO) then - score = score * (flux_deriv & - + (micro_xs(deriv % diff_nuclide) % total & - - micro_xs(deriv % diff_nuclide) % absorption) & - / (material_xs % total - material_xs % absorption)) - else if (scoring_diff_nuclide .and. & - (micro_xs(deriv % diff_nuclide) % total & - - micro_xs(deriv % diff_nuclide) % absorption) /= ZERO) then - score = score * (flux_deriv + ONE / atom_density) - else - score = score * flux_deriv - end if - - case (SCORE_ABSORPTION) - if (i_nuclide == -1 .and. & - material_id(p % material) == deriv % diff_material .and. & - material_xs % absorption /= ZERO) then - score = score * (flux_deriv & - + micro_xs(deriv % diff_nuclide) % absorption & - / material_xs % absorption ) - else if (scoring_diff_nuclide .and. & - micro_xs(deriv % diff_nuclide) % absorption /= ZERO) then - score = score * (flux_deriv + ONE / atom_density) - else - score = score * flux_deriv - end if - - case (SCORE_FISSION) - if (i_nuclide == -1 .and. & - material_id(p % material) == deriv % diff_material .and. & - material_xs % fission /= ZERO) then - score = score * (flux_deriv & - + micro_xs(deriv % diff_nuclide) % fission & - / material_xs % fission) - else if (scoring_diff_nuclide .and. & - micro_xs(deriv % diff_nuclide) % fission /= ZERO) then - score = score * (flux_deriv + ONE / atom_density) - else - score = score * flux_deriv - end if - - case (SCORE_NU_FISSION) - if (i_nuclide == -1 .and. & - material_id(p % material) == deriv % diff_material .and. & - material_xs % nu_fission /= ZERO) then - score = score * (flux_deriv & - + micro_xs(deriv % diff_nuclide) % nu_fission & - / material_xs % nu_fission) - else if (scoring_diff_nuclide .and. & - micro_xs(deriv % diff_nuclide) % nu_fission /= ZERO) then - score = score * (flux_deriv + ONE / atom_density) - else - score = score * flux_deriv - end if - - case default - call fatal_error('Tally derivative not defined for a score on & - &tally ' // trim(to_str(t % id()))) - end select - - case default - call fatal_error("Differential tallies are only implemented for & - &analog and collision estimators.") - end select + return !========================================================================= ! Temperature derivative: @@ -345,7 +186,7 @@ contains select case (score_bin) case (SCORE_FLUX) - score = score * flux_deriv + !score = score * flux_deriv case (SCORE_TOTAL) if (material_id(p % material) == deriv % diff_material .and. & @@ -467,7 +308,8 @@ contains select case (score_bin) case (SCORE_FLUX) - score = score * flux_deriv + !score = score * flux_deriv + return case (SCORE_TOTAL) if (i_nuclide == -1 .and. & From ddaed7311becc90c4ff06cf5d1e3f606abf67c49 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Sun, 10 Feb 2019 21:45:54 -0500 Subject: [PATCH 44/58] Finish moving apply_derivative_to_score to C++ --- include/openmc/tallies/derivative.h | 12 + src/tallies/derivative.cpp | 267 +++++++++++++++++- src/tallies/tally.F90 | 401 ---------------------------- src/tallies/tally.cpp | 4 - 4 files changed, 275 insertions(+), 409 deletions(-) diff --git a/include/openmc/tallies/derivative.h b/include/openmc/tallies/derivative.h index d9202433c3..da9fa60a6e 100644 --- a/include/openmc/tallies/derivative.h +++ b/include/openmc/tallies/derivative.h @@ -1,6 +1,8 @@ #ifndef OPENMC_TALLIES_DERIVATIVE_H #define OPENMC_TALLIES_DERIVATIVE_H +#include "openmc/particle.h" + #include #include @@ -23,6 +25,16 @@ extern "C" struct TallyDerivative { TallyDerivative(pugi::xml_node node); }; +//============================================================================== +// Non-method functions +//============================================================================== + +//! Scale the given score by its logarithmic derivative + +void +apply_derivative_to_score(Particle* p, int i_tally, int i_nuclide, + double atom_density, int score_bin, double* score); + } // namespace openmc //============================================================================== diff --git a/src/tallies/derivative.cpp b/src/tallies/derivative.cpp index fd8d080ad4..b0ff83ea3a 100644 --- a/src/tallies/derivative.cpp +++ b/src/tallies/derivative.cpp @@ -106,8 +106,8 @@ read_tally_derivatives(pugi::xml_node* node) fatal_error("Differential tallies not supported in multi-group mode"); } -extern "C" void -apply_derivative_to_score_c(Particle* p, int i_tally, int i_nuclide, +void +apply_derivative_to_score(Particle* p, int i_tally, int i_nuclide, double atom_density, int score_bin, double* score) { //TODO: off-by-one @@ -303,8 +303,268 @@ apply_derivative_to_score_c(Particle* p, int i_tally, int i_nuclide, break; //============================================================================ + // Temperature derivative: + // If we are scoring a reaction rate for a single nuclide then + // c = Sigma_MT_i + // c = sigma_MT_i * N_i + // d_c / d_T = (d_sigma_Mt_i / d_T) * N_i + // (1 / c) * (d_c / d_T) = (d_sigma_MT_i / d_T) / sigma_MT_i + // If the score is for the total material (i_nuclide = -1) + // (1 / c) * (d_c / d_T) = Sum_i((d_sigma_MT_i / d_T) * N_i) / Sigma_MT_i + // where i is the perturbed nuclide. The d_sigma_MT_i / d_T term is + // computed by multipole_deriv_eval. It only works for the resolved + // resonance range and requires multipole data. case DIFF_TEMPERATURE: + switch (tally.estimator_) { + + case ESTIMATOR_ANALOG: + { + // Find the index of the event nuclide. + int i; + for (i = 0; i < material.nuclide_.size(); ++i) + if (material.nuclide_[i] == p->event_nuclide-1) break; + + const auto& nuc {*data::nuclides[p->event_nuclide-1]}; + if (!multipole_in_range(&nuc, p->last_E)) { + *score *= flux_deriv; + break; + } + + switch (score_bin) { + + case SCORE_TOTAL: + if (simulation::micro_xs[p->event_nuclide-1].total) { + double dsig_s, dsig_a, dsig_f; + std::tie(dsig_s, dsig_a, dsig_f) + = nuc.multipole_->evaluate_deriv(p->last_E, p->sqrtkT); + *score *= flux_deriv + (dsig_s + dsig_a) * material.atom_density_(i) + / simulation::material_xs.total; + } else { + *score *= flux_deriv; + } + break; + + case SCORE_SCATTER: + if (simulation::micro_xs[p->event_nuclide-1].total + - simulation::micro_xs[p->event_nuclide-1].absorption) { + double dsig_s, dsig_a, dsig_f; + std::tie(dsig_s, dsig_a, dsig_f) + = nuc.multipole_->evaluate_deriv(p->last_E, p->sqrtkT); + *score *= flux_deriv + dsig_s * material.atom_density_(i) + / (simulation::material_xs.total + - simulation::material_xs.absorption); + } else { + *score *= flux_deriv; + } + break; + + case SCORE_ABSORPTION: + if (simulation::micro_xs[p->event_nuclide-1].absorption) { + double dsig_s, dsig_a, dsig_f; + std::tie(dsig_s, dsig_a, dsig_f) + = nuc.multipole_->evaluate_deriv(p->last_E, p->sqrtkT); + *score *= flux_deriv + dsig_a * material.atom_density_(i) + / simulation::material_xs.absorption; + } else { + *score *= flux_deriv; + } + break; + + case SCORE_FISSION: + if (simulation::micro_xs[p->event_nuclide-1].fission) { + double dsig_s, dsig_a, dsig_f; + std::tie(dsig_s, dsig_a, dsig_f) + = nuc.multipole_->evaluate_deriv(p->last_E, p->sqrtkT); + *score *= flux_deriv + dsig_f * material.atom_density_(i) + / simulation::material_xs.fission; + } else { + *score *= flux_deriv; + } + break; + + case SCORE_NU_FISSION: + if (simulation::micro_xs[p->event_nuclide-1].fission) { + double nu = simulation::micro_xs[p->event_nuclide-1].nu_fission + / simulation::micro_xs[p->event_nuclide-1].fission; + double dsig_s, dsig_a, dsig_f; + std::tie(dsig_s, dsig_a, dsig_f) + = nuc.multipole_->evaluate_deriv(p->last_E, p->sqrtkT); + *score *= flux_deriv + nu * dsig_f * material.atom_density_(i) + / simulation::material_xs.nu_fission; + } else { + *score *= flux_deriv; + } + break; + + default: + fatal_error("Tally derivative not defined for a score on tally " + + std::to_string(tally.id_)); + } + } + break; + + case ESTIMATOR_COLLISION: + if (i_nuclide != -1) { + const auto& nuc {*data::nuclides[i_nuclide]}; + if (!multipole_in_range(&nuc, p->last_E)) { + *score *= flux_deriv; + return; + } + } + + switch (score_bin) { + + case SCORE_TOTAL: + if (i_nuclide == -1 && simulation::material_xs.total) { + double cum_dsig = 0; + for (auto i = 0; i < material.nuclide_.size(); ++i) { + auto i_nuc = material.nuclide_[i]; + const auto& nuc {*data::nuclides[i_nuc]}; + if (multipole_in_range(&nuc, p->last_E) + && simulation::micro_xs[i_nuc].total) { + double dsig_s, dsig_a, dsig_f; + std::tie(dsig_s, dsig_a, dsig_f) + = nuc.multipole_->evaluate_deriv(p->last_E, p->sqrtkT); + cum_dsig += (dsig_s + dsig_a) * material.atom_density_(i); + } + } + *score *= flux_deriv + cum_dsig / simulation::material_xs.total; + } else if (simulation::micro_xs[i_nuclide].total) { + const auto& nuc {*data::nuclides[i_nuclide]}; + double dsig_s, dsig_a, dsig_f; + std::tie(dsig_s, dsig_a, dsig_f) + = nuc.multipole_->evaluate_deriv(p->last_E, p->sqrtkT); + *score *= flux_deriv + + (dsig_s + dsig_a) / simulation::micro_xs[i_nuclide].total; + } else { + *score *= flux_deriv; + } + break; + + case SCORE_SCATTER: + if (i_nuclide == -1 && (simulation::material_xs.total + - simulation::material_xs.absorption)) { + double cum_dsig = 0; + for (auto i = 0; i < material.nuclide_.size(); ++i) { + auto i_nuc = material.nuclide_[i]; + const auto& nuc {*data::nuclides[i_nuc]}; + if (multipole_in_range(&nuc, p->last_E) + && (simulation::micro_xs[i_nuc].total + - simulation::micro_xs[i_nuc].absorption)) { + double dsig_s, dsig_a, dsig_f; + std::tie(dsig_s, dsig_a, dsig_f) + = nuc.multipole_->evaluate_deriv(p->last_E, p->sqrtkT); + cum_dsig += dsig_s * material.atom_density_(i); + } + } + *score *= flux_deriv + cum_dsig / (simulation::material_xs.total + - simulation::material_xs.absorption); + } else if (simulation::micro_xs[i_nuclide].total + - simulation::micro_xs[i_nuclide].absorption) { + const auto& nuc {*data::nuclides[i_nuclide]}; + double dsig_s, dsig_a, dsig_f; + std::tie(dsig_s, dsig_a, dsig_f) + = nuc.multipole_->evaluate_deriv(p->last_E, p->sqrtkT); + *score *= flux_deriv + dsig_s / (simulation::micro_xs[i_nuclide].total + - simulation::micro_xs[i_nuclide].absorption); + } else { + *score *= flux_deriv; + } + break; + + case SCORE_ABSORPTION: + if (i_nuclide == -1 && simulation::material_xs.absorption) { + double cum_dsig = 0; + for (auto i = 0; i < material.nuclide_.size(); ++i) { + auto i_nuc = material.nuclide_[i]; + const auto& nuc {*data::nuclides[i_nuc]}; + if (multipole_in_range(&nuc, p->last_E) + && simulation::micro_xs[i_nuc].absorption) { + double dsig_s, dsig_a, dsig_f; + std::tie(dsig_s, dsig_a, dsig_f) + = nuc.multipole_->evaluate_deriv(p->last_E, p->sqrtkT); + cum_dsig += dsig_a * material.atom_density_(i); + } + } + *score *= flux_deriv + cum_dsig / simulation::material_xs.absorption; + } else if (simulation::micro_xs[i_nuclide].absorption) { + const auto& nuc {*data::nuclides[i_nuclide]}; + double dsig_s, dsig_a, dsig_f; + std::tie(dsig_s, dsig_a, dsig_f) + = nuc.multipole_->evaluate_deriv(p->last_E, p->sqrtkT); + *score *= flux_deriv + + dsig_a / simulation::micro_xs[i_nuclide].absorption; + } else { + *score *= flux_deriv; + } + break; + + case SCORE_FISSION: + if (i_nuclide == -1 && simulation::material_xs.fission) { + double cum_dsig = 0; + for (auto i = 0; i < material.nuclide_.size(); ++i) { + auto i_nuc = material.nuclide_[i]; + const auto& nuc {*data::nuclides[i_nuc]}; + if (multipole_in_range(&nuc, p->last_E) + && simulation::micro_xs[i_nuc].fission) { + double dsig_s, dsig_a, dsig_f; + std::tie(dsig_s, dsig_a, dsig_f) + = nuc.multipole_->evaluate_deriv(p->last_E, p->sqrtkT); + cum_dsig += dsig_f * material.atom_density_(i); + } + } + *score *= flux_deriv + cum_dsig / simulation::material_xs.fission; + } else if (simulation::micro_xs[i_nuclide].fission) { + const auto& nuc {*data::nuclides[i_nuclide]}; + double dsig_s, dsig_a, dsig_f; + std::tie(dsig_s, dsig_a, dsig_f) + = nuc.multipole_->evaluate_deriv(p->last_E, p->sqrtkT); + *score *= flux_deriv + + dsig_f / simulation::micro_xs[i_nuclide].fission; + } else { + *score *= flux_deriv; + } + break; + + case SCORE_NU_FISSION: + if (i_nuclide == -1 && simulation::material_xs.nu_fission) { + double cum_dsig = 0; + for (auto i = 0; i < material.nuclide_.size(); ++i) { + auto i_nuc = material.nuclide_[i]; + const auto& nuc {*data::nuclides[i_nuc]}; + if (multipole_in_range(&nuc, p->last_E) + && simulation::micro_xs[i_nuc].fission) { + double nu = simulation::micro_xs[i_nuc].nu_fission + / simulation::micro_xs[i_nuc].fission; + double dsig_s, dsig_a, dsig_f; + std::tie(dsig_s, dsig_a, dsig_f) + = nuc.multipole_->evaluate_deriv(p->last_E, p->sqrtkT); + cum_dsig += nu * dsig_f * material.atom_density_(i); + } + } + *score *= flux_deriv + cum_dsig / simulation::material_xs.nu_fission; + } else if (simulation::micro_xs[i_nuclide].fission) { + const auto& nuc {*data::nuclides[i_nuclide]}; + double dsig_s, dsig_a, dsig_f; + std::tie(dsig_s, dsig_a, dsig_f) + = nuc.multipole_->evaluate_deriv(p->last_E, p->sqrtkT); + *score *= flux_deriv + + dsig_f / simulation::micro_xs[i_nuclide].fission; + } else { + *score *= flux_deriv; + } + break; + + default: + break; + } + break; + + default: + fatal_error("Differential tallies are only implemented for analog and " + "collision estimators."); + } break; } } @@ -344,7 +604,6 @@ score_track_derivative(Particle* p, double distance) case DIFF_TEMPERATURE: for (auto i = 0; i < material.nuclide_.size(); ++i) { const auto& nuc {*data::nuclides[material.nuclide_[i]]}; - //TODO: off-by-one if (multipole_in_range(&nuc, p->last_E)) { // phi is proportional to e^(-Sigma_tot * dist) // (1 / phi) * (d_phi / d_T) = - (d_Sigma_tot / d_T) * dist @@ -427,7 +686,7 @@ score_collision_derivative(Particle* p) deriv.flux_deriv += dsig_s / (micro_xs.total - micro_xs.absorption); // Note that this is an approximation! The real scattering cross // section is - // Sigma_s(E'->E, uvw'->uvw) = Sigma_s(E') * P(E'->E, uvw'->uvw). + // Sigma_s(E'->E, uvw'->uvw) = Sigma_s(E') * P(E'->E, uvw'->uvw). // We are assuming that d_P(E'->E, uvw'->uvw) / d_T = 0 and only // computing d_S(E') / d_T. Using this approximation in the vicinity // of low-energy resonances causes errors (~2-5% for PWR pincell diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index c5efaabf8e..d4fb204554 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -95,407 +95,6 @@ contains end if end subroutine init_tally_routines -!=============================================================================== -! APPLY_DERIVATIVE_TO_SCORE multiply the given score by its relative derivative -!=============================================================================== - - subroutine apply_derivative_to_score(p, i_tally, i_nuclide, atom_density, & - score_bin, score) bind(C) - type(Particle), intent(in) :: p - integer(C_INT), value, intent(in) :: i_tally - integer(C_INT), value, intent(in) :: i_nuclide - real(C_DOUBLE), value, intent(in) :: atom_density ! atom/b-cm - integer(C_INT), value, intent(in) :: score_bin - real(C_DOUBLE), intent(inout) :: score - - type(TallyDerivative), pointer :: deriv - integer :: l - integer :: i_nuc - logical :: scoring_diff_nuclide - real(8) :: flux_deriv - real(8) :: dsig_s, dsig_a, dsig_f, cum_dsig - - interface - subroutine apply_derivative_to_score_c(p, i_tally, i_nuclide, & - atom_density, score_bin, score) bind(C) - import Particle, C_INT, C_DOUBLE - type(Particle), intent(in) :: p - integer(C_INT), value, intent(in) :: i_tally - integer(C_INT), value, intent(in) :: i_nuclide - real(C_DOUBLE), value, intent(in) :: atom_density - integer(C_INT), value, intent(in) :: score_bin - real(C_DOUBLE), intent(inout) :: score - end subroutine - end interface - - call apply_derivative_to_score_c(p, i_tally, i_nuclide, atom_density, & - score_bin, score) - - associate (t => tallies(i_tally) % obj) - - if (score == ZERO) return - - ! If our score was previously c then the new score is - ! c * (1/f * d_f/d_p + 1/c * d_c/d_p) - ! where (1/f * d_f/d_p) is the (logarithmic) flux derivative and p is the - ! perturbated variable. - - !associate(deriv => tally_derivs(t % deriv())) - deriv => tally_deriv_c(t % deriv()) - flux_deriv = deriv % flux_deriv - - if (p % material == MATERIAL_VOID) then - return - else if (material_id(p % material) /= deriv % diff_material) then - return - end if - - if (deriv % variable == DIFF_NUCLIDE_DENSITY) then - if (t % estimator() == ESTIMATOR_ANALOG) then - if (p % event_nuclide /= deriv % diff_nuclide) return - end if - end if - - !select case (tally_derivs(t % deriv()) % variable) - select case (deriv % variable) - - case (DIFF_DENSITY) - return - - case (DIFF_NUCLIDE_DENSITY) - return - - !========================================================================= - ! Temperature derivative: - ! If we are scoring a reaction rate for a single nuclide then - ! c = Sigma_MT_i - ! c = sigma_MT_i * N_i - ! d_c / d_T = (d_sigma_Mt_i / d_T) * N_i - ! (1 / c) * (d_c / d_T) = (d_sigma_MT_i / d_T) / sigma_MT_i - ! If the score is for the total material (i_nuclide = -1) - ! (1 / c) * (d_c / d_T) = Sum_i((d_sigma_MT_i / d_T) * N_i) / Sigma_MT_i - ! where i is the perturbed nuclide. The d_sigma_MT_i / d_T term is - ! computed by multipole_deriv_eval. It only works for the resolved - ! resonance range and requires multipole data. - - case (DIFF_TEMPERATURE) - select case (t % estimator()) - - case (ESTIMATOR_ANALOG) - - select case (score_bin) - - case (SCORE_FLUX) - !score = score * flux_deriv - - case (SCORE_TOTAL) - if (material_id(p % material) == deriv % diff_material .and. & - micro_xs(p % event_nuclide) % total > ZERO) then - ! Search for the index of the perturbed nuclide. - do l = 1, material_nuclide_size(p % material) - if (material_nuclide(p % material, l) == p % event_nuclide) exit - end do - - dsig_s = ZERO - dsig_a = ZERO - associate (nuc => nuclides(p % event_nuclide)) - if (multipole_in_range(nuc % ptr, p % last_E)) then - call multipole_deriv_eval(nuc % ptr, p % last_E, & - p % sqrtkT, dsig_s, dsig_a, dsig_f) - end if - end associate - score = score * (flux_deriv & - + (dsig_s + dsig_a) * material_atom_density(p % material, l) & - / material_xs % total) - else - score = score * flux_deriv - end if - - case (SCORE_SCATTER) - if (material_id(p % material) == deriv % diff_material .and. & - (micro_xs(p % event_nuclide) % total & - - micro_xs(p % event_nuclide) % absorption) > ZERO) then - ! Search for the index of the perturbed nuclide. - do l = 1, material_nuclide_size(p % material) - if (material_nuclide(p % material, l) == p % event_nuclide) exit - end do - - dsig_s = ZERO - associate (nuc => nuclides(p % event_nuclide)) - if (multipole_in_range(nuc % ptr, p % last_E)) then - call multipole_deriv_eval(nuc % ptr, p % last_E, & - p % sqrtkT, dsig_s, dsig_a, dsig_f) - end if - end associate - score = score * (flux_deriv + dsig_s * material_atom_density(p % material, l) / & - (material_xs % total - material_xs % absorption)) - else - score = score * flux_deriv - end if - - case (SCORE_ABSORPTION) - if (material_id(p % material) == deriv % diff_material .and. & - micro_xs(p % event_nuclide) % absorption > ZERO) then - ! Search for the index of the perturbed nuclide. - do l = 1, material_nuclide_size(p % material) - if (material_nuclide(p % material, l) == p % event_nuclide) exit - end do - - dsig_a = ZERO - associate (nuc => nuclides(p % event_nuclide)) - if (multipole_in_range(nuc % ptr, p % last_E)) then - call multipole_deriv_eval(nuc % ptr, p % last_E, & - p % sqrtkT, dsig_s, dsig_a, dsig_f) - end if - end associate - score = score * (flux_deriv + dsig_a * material_atom_density(p % material, l) & - / material_xs % absorption) - else - score = score * flux_deriv - end if - - case (SCORE_FISSION) - if (material_id(p % material) == deriv % diff_material .and. & - micro_xs(p % event_nuclide) % fission > ZERO) then - ! Search for the index of the perturbed nuclide. - do l = 1, material_nuclide_size(p % material) - if (material_nuclide(p % material, l) == p % event_nuclide) exit - end do - - dsig_f = ZERO - associate (nuc => nuclides(p % event_nuclide)) - if (multipole_in_range(nuc % ptr, p % last_E)) then - call multipole_deriv_eval(nuc % ptr, p % last_E, & - p % sqrtkT, dsig_s, dsig_a, dsig_f) - end if - end associate - score = score * (flux_deriv & - + dsig_f * material_atom_density(p % material, l) / material_xs % fission) - else - score = score * flux_deriv - end if - - case (SCORE_NU_FISSION) - if (material_id(p % material) == deriv % diff_material .and. & - micro_xs(p % event_nuclide) % nu_fission > ZERO) then - ! Search for the index of the perturbed nuclide. - do l = 1, material_nuclide_size(p % material) - if (material_nuclide(p % material, l) == p % event_nuclide) exit - end do - - dsig_f = ZERO - associate (nuc => nuclides(p % event_nuclide)) - if (multipole_in_range(nuc % ptr, p % last_E)) then - call multipole_deriv_eval(nuc % ptr, p % last_E, & - p % sqrtkT, dsig_s, dsig_a, dsig_f) - end if - end associate - score = score * (flux_deriv & - + dsig_f * material_atom_density(p % material, l) / material_xs % nu_fission& - * micro_xs(p % event_nuclide) % nu_fission & - / micro_xs(p % event_nuclide) % fission) - else - score = score * flux_deriv - end if - - case default - call fatal_error('Tally derivative not defined for a score on & - &tally ' // trim(to_str(t % id()))) - end select - - case (ESTIMATOR_COLLISION) - - select case (score_bin) - - case (SCORE_FLUX) - !score = score * flux_deriv - return - - case (SCORE_TOTAL) - if (i_nuclide == -1 .and. & - material_id(p % material) == deriv % diff_material .and. & - material_xs % total > ZERO) then - cum_dsig = ZERO - do l = 1, material_nuclide_size(p % material) - i_nuc = material_nuclide(p % material, l) - associate (nuc => nuclides(i_nuc)) - if (multipole_in_range(nuc % ptr, p % last_E) .and. & - micro_xs(i_nuc) % total > ZERO) then - call multipole_deriv_eval(nuc % ptr, p % last_E, & - p % sqrtkT, dsig_s, dsig_a, dsig_f) - cum_dsig = cum_dsig + (dsig_s + dsig_a) & - * material_atom_density(p % material, l) - end if - end associate - end do - score = score * (flux_deriv & - + cum_dsig / material_xs % total) - else if (material_id(p % material) == deriv % diff_material & - .and. material_xs % total > ZERO) then - dsig_s = ZERO - dsig_a = ZERO - associate (nuc => nuclides(i_nuclide+1)) - if (multipole_in_range(nuc % ptr, p % last_E)) then - call multipole_deriv_eval(nuc % ptr, p % last_E, & - p % sqrtkT, dsig_s, dsig_a, dsig_f) - end if - end associate - score = score * (flux_deriv & - + (dsig_s + dsig_a) / micro_xs(i_nuclide+1) % total) - else - score = score * flux_deriv - end if - - case (SCORE_SCATTER) - if (i_nuclide == -1 .and. & - material_id(p % material) == deriv % diff_material .and. & - (material_xs % total - material_xs % absorption) > ZERO) then - cum_dsig = ZERO - do l = 1, material_nuclide_size(p % material) - i_nuc = material_nuclide(p % material, l) - associate (nuc => nuclides(i_nuc)) - if (multipole_in_range(nuc % ptr, p % last_E) .and. & - (micro_xs(i_nuc) % total & - - micro_xs(i_nuc) % absorption) > ZERO) then - call multipole_deriv_eval(nuc % ptr, p % last_E, & - p % sqrtkT, dsig_s, dsig_a, dsig_f) - cum_dsig = cum_dsig + dsig_s * material_atom_density(p % material, l) - end if - end associate - end do - score = score * (flux_deriv + cum_dsig & - / (material_xs % total - material_xs % absorption)) - else if ( material_id(p % material) == deriv % diff_material & - .and. (material_xs % total - material_xs % absorption) > ZERO)& - then - dsig_s = ZERO - associate (nuc => nuclides(i_nuclide+1)) - if (multipole_in_range(nuc % ptr, p % last_E)) then - call multipole_deriv_eval(nuc % ptr, p % last_E, & - p % sqrtkT, dsig_s, dsig_a, dsig_f) - end if - end associate - score = score * (flux_deriv + dsig_s & - / (micro_xs(i_nuclide+1) % total & - - micro_xs(i_nuclide+1) % absorption)) - else - score = score * flux_deriv - end if - - case (SCORE_ABSORPTION) - if (i_nuclide == -1 .and. & - material_id(p % material) == deriv % diff_material .and. & - material_xs % absorption > ZERO) then - cum_dsig = ZERO - do l = 1, material_nuclide_size(p % material) - i_nuc = material_nuclide(p % material, l) - associate (nuc => nuclides(i_nuc)) - if (multipole_in_range(nuc % ptr, p % last_E) .and. & - micro_xs(i_nuc) % absorption > ZERO) then - call multipole_deriv_eval(nuc % ptr, p % last_E, & - p % sqrtkT, dsig_s, dsig_a, dsig_f) - cum_dsig = cum_dsig + dsig_a * material_atom_density(p % material, l) - end if - end associate - end do - score = score * (flux_deriv & - + cum_dsig / material_xs % absorption) - else if (material_id(p % material) == deriv % diff_material & - .and. material_xs % absorption > ZERO) then - dsig_a = ZERO - associate (nuc => nuclides(i_nuclide+1)) - if (multipole_in_range(nuc % ptr, p % last_E)) then - call multipole_deriv_eval(nuc % ptr, p % last_E, & - p % sqrtkT, dsig_s, dsig_a, dsig_f) - end if - end associate - score = score * (flux_deriv & - + dsig_a / micro_xs(i_nuclide+1) % absorption) - else - score = score * flux_deriv - end if - - case (SCORE_FISSION) - if (i_nuclide == -1 .and. & - material_id(p % material) == deriv % diff_material .and. & - material_xs % fission > ZERO) then - cum_dsig = ZERO - do l = 1, material_nuclide_size(p % material) - i_nuc = material_nuclide(p % material, l) - associate (nuc => nuclides(i_nuc)) - if (multipole_in_range(nuc % ptr, p % last_E) .and. & - micro_xs(i_nuc) % fission > ZERO) then - call multipole_deriv_eval(nuc % ptr, p % last_E, & - p % sqrtkT, dsig_s, dsig_a, dsig_f) - cum_dsig = cum_dsig + dsig_f * material_atom_density(p % material, l) - end if - end associate - end do - score = score * (flux_deriv & - + cum_dsig / material_xs % fission) - else if (material_id(p % material) == deriv % diff_material & - .and. material_xs % fission > ZERO) then - dsig_f = ZERO - associate (nuc => nuclides(i_nuclide+1)) - if (multipole_in_range(nuc % ptr, p % last_E)) then - call multipole_deriv_eval(nuc % ptr, p % last_E, & - p % sqrtkT, dsig_s, dsig_a, dsig_f) - end if - end associate - score = score * (flux_deriv & - + dsig_f / micro_xs(i_nuclide+1) % fission) - else - score = score * flux_deriv - end if - - case (SCORE_NU_FISSION) - if (i_nuclide == -1 .and. & - material_id(p % material) == deriv % diff_material .and. & - material_xs % nu_fission > ZERO) then - cum_dsig = ZERO - do l = 1, material_nuclide_size(p % material) - i_nuc = material_nuclide(p % material, l) - associate (nuc => nuclides(i_nuc)) - if (multipole_in_range(nuc % ptr, p % last_E) .and. & - micro_xs(i_nuc) % nu_fission > ZERO) then - call multipole_deriv_eval(nuc % ptr, p % last_E, & - p % sqrtkT, dsig_s, dsig_a, dsig_f) - cum_dsig = cum_dsig + dsig_f * material_atom_density(p % material, l) & - * micro_xs(i_nuc) % nu_fission & - / micro_xs(i_nuc) % fission - end if - end associate - end do - score = score * (flux_deriv & - + cum_dsig / material_xs % nu_fission) - else if (material_id(p % material) == deriv % diff_material & - .and. material_xs % nu_fission > ZERO) then - dsig_f = ZERO - associate (nuc => nuclides(i_nuclide+1)) - if (multipole_in_range(nuc % ptr, p % last_E)) then - call multipole_deriv_eval(nuc % ptr, p % last_E, & - p % sqrtkT, dsig_s, dsig_a, dsig_f) - end if - end associate - score = score * (flux_deriv & - + dsig_f / micro_xs(i_nuclide+1) % fission) - else - score = score * flux_deriv - end if - - case default - call fatal_error('Tally derivative not defined for a score on & - &tally ' // trim(to_str(t % id()))) - end select - - case default - call fatal_error("Differential tallies are only implemented for & - &analog and collision estimators.") - end select - end select - end associate - end subroutine apply_derivative_to_score - !=============================================================================== ! ACCUMULATE_TALLIES accumulates the sum of the contributions from each history ! within the batch to a new random variable diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 3b94b70e3e..704bedaa50 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -38,10 +38,6 @@ namespace openmc { // Functions defined in Fortran //============================================================================== -extern "C" void -apply_derivative_to_score(Particle* p, int i_tally, int i_nuclide, - double atom_density, int score_bin, double* score); - extern "C" int energy_filter_search(const EnergyFilter* filt, double val); From 8bb0fa7791d995f02a94de19f64db683c527c951 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Mon, 11 Feb 2019 13:58:12 -0500 Subject: [PATCH 45/58] Move write_tallies to C++ --- include/openmc/tallies/tally.h | 40 +++++ src/input_xml.F90 | 8 + src/output.F90 | 235 +---------------------------- src/output.cpp | 182 ++++++++++++++++++++++- src/simulation.cpp | 10 +- src/tallies/tally.cpp | 264 +++++++++++++++++---------------- 6 files changed, 366 insertions(+), 373 deletions(-) diff --git a/include/openmc/tallies/tally.h b/include/openmc/tallies/tally.h index 2091ece0eb..74adc7ed6f 100644 --- a/include/openmc/tallies/tally.h +++ b/include/openmc/tallies/tally.h @@ -2,6 +2,7 @@ #define OPENMC_TALLIES_TALLY_H #include "openmc/constants.h" +#include "openmc/tallies/filter.h" #include "openmc/tallies/trigger.h" #include "pugixml.hpp" @@ -21,6 +22,8 @@ class Tally { public: Tally() {} + void init_from_xml(pugi::xml_node node); + void set_scores(pugi::xml_node node); void set_scores(std::vector scores); @@ -50,6 +53,8 @@ public: int id_; //!< user-defined identifier + std::string name_; //!< user-defined name + int type_ {TALLY_VOLUME}; //!< volume, surface current //! Event type that contributes to this tally @@ -93,6 +98,41 @@ private: int32_t n_filter_bins_ {0}; }; +//============================================================================== +//! An iterator over all combinations of a tally's matching filter bins. +// +//! This iterator handles two distinct tasks. First, it maps the N-dimensional +//! space created by the indices of N filters onto a 1D sequence. In other +//! words, it provides a single number that uniquely identifies a combination of +//! bins for many filters. Second, it handles the task of finding each all +//! valid combinations of filter bins given that each filter can have 1 or 2 or +//! many bins that are valid for the current tally event. +//============================================================================== + +class FilterBinIter +{ +public: + FilterBinIter(const Tally& tally, Particle* p); + + FilterBinIter(const Tally& tally, bool end); + + bool operator==(const FilterBinIter& other) const + {return index_ == other.index_;} + + bool operator!=(const FilterBinIter& other) const + {return !(*this == other);} + + FilterBinIter& operator++(); + + int index_ {1}; + double weight_ {1.}; + +private: + void compute_index_weight(); + + const Tally& tally_; +}; + //============================================================================== // Global variable declarations //============================================================================== diff --git a/src/input_xml.F90 b/src/input_xml.F90 index aa38f7db6e..ec4440a127 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -473,6 +473,12 @@ contains type(TallyDerivative), pointer :: deriv interface + subroutine tally_init_from_xml(tally_ptr, xml_node) bind(C) + import C_PTR + type(C_PTR), value :: tally_ptr + type(C_PTR) :: xml_node + end subroutine + subroutine tally_set_scores(tally_ptr, xml_node) bind(C) import C_PTR type(C_PTR), value :: tally_ptr @@ -630,6 +636,8 @@ contains ! Get pointer to tally xml node node_tal = node_tal_list(i) + call tally_init_from_xml(t % ptr, node_tal % ptr) + ! Copy and set tally id if (check_for_node(node_tal, "id")) then call get_node_value(node_tal, "id", tally_id) diff --git a/src/output.F90 b/src/output.F90 index 02f5ec3166..aa66ae0170 100644 --- a/src/output.F90 +++ b/src/output.F90 @@ -39,6 +39,9 @@ module output import Particle type(Particle), intent(in) :: p end subroutine + + subroutine write_tallies() bind(C) + end subroutine end interface contains @@ -441,238 +444,6 @@ contains end subroutine print_results -!=============================================================================== -! WRITE_TALLIES creates an output file and writes out the mean values of all -! tallies and their standard deviations -!=============================================================================== - - subroutine write_tallies() bind(C) - - integer :: i ! index in tallies array - integer :: j ! level in tally hierarchy - integer :: k ! loop index for scoring bins - integer :: n ! loop index for nuclides - integer :: h ! loop index for tally filters - integer :: indent ! number of spaces to preceed output - integer :: filter_index ! index in results array for filters - integer :: score_index ! scoring bin index - integer :: i_nuclide ! index in nuclides array - integer :: i_filt - integer :: unit_tally ! tallies.out file unit - integer :: nr ! number of realizations - real(8) :: t_value ! t-values for confidence intervals - real(8) :: alpha ! significance level for CI - real(8) :: x(2) ! mean and standard deviation - character(MAX_FILE_LEN) :: filename ! name of output file - character(36) :: score_names(N_SCORE_TYPES) ! names of scoring function - character(36) :: score_name ! names of scoring function - ! to be applied at write-time - integer, allocatable :: filter_bins(:) - character(MAX_WORD_LEN) :: temp_name - type(TallyDerivative), pointer :: deriv - - ! Skip if there are no tallies - if (n_tallies == 0) return - - allocate(filter_bins(n_filters)) - - ! Initialize names for scores - score_names(abs(SCORE_FLUX)) = "Flux" - score_names(abs(SCORE_TOTAL)) = "Total Reaction Rate" - score_names(abs(SCORE_SCATTER)) = "Scattering Rate" - score_names(abs(SCORE_NU_SCATTER)) = "Scattering Production Rate" - score_names(abs(SCORE_ABSORPTION)) = "Absorption Rate" - score_names(abs(SCORE_FISSION)) = "Fission Rate" - score_names(abs(SCORE_NU_FISSION)) = "Nu-Fission Rate" - score_names(abs(SCORE_KAPPA_FISSION)) = "Kappa-Fission Rate" - score_names(abs(SCORE_EVENTS)) = "Events" - score_names(abs(SCORE_DECAY_RATE)) = "Decay Rate" - score_names(abs(SCORE_DELAYED_NU_FISSION)) = "Delayed-Nu-Fission Rate" - score_names(abs(SCORE_PROMPT_NU_FISSION)) = "Prompt-Nu-Fission Rate" - score_names(abs(SCORE_INVERSE_VELOCITY)) = "Flux-Weighted Inverse Velocity" - score_names(abs(SCORE_FISS_Q_PROMPT)) = "Prompt fission power" - score_names(abs(SCORE_FISS_Q_RECOV)) = "Recoverable fission power" - score_names(abs(SCORE_CURRENT)) = "Current" - - ! Create filename for tally output - filename = trim(path_output) // "tallies.out" - - ! Open tally file for writing - open(FILE=filename, NEWUNIT=unit_tally, STATUS='replace', ACTION='write') - - ! Calculate t-value for confidence intervals - if (confidence_intervals) then - alpha = ONE - CONFIDENCE_LEVEL - t_value = t_percentile(ONE - alpha/TWO, n_realizations - 1) - else - t_value = ONE - end if - - TALLY_LOOP: do i = 1, n_tallies - associate (t => tallies(i) % obj) - nr = t % n_realizations - - if (confidence_intervals) then - ! Calculate t-value for confidence intervals - alpha = ONE - CONFIDENCE_LEVEL - t_value = t_percentile(ONE - alpha/TWO, nr - 1) - else - t_value = ONE - end if - - ! Write header block - if (t % name == "") then - call header("TALLY " // trim(to_str(t % id())), 1, unit=unit_tally) - else - call header("TALLY " // trim(to_str(t % id())) // ": " & - // trim(t % name), 1, unit=unit_tally) - endif - - ! Write derivative information. - if (t % deriv() /= C_NONE) then - !associate(deriv => tally_derivs(t % deriv())) - deriv => tally_deriv_c(t % deriv()) - select case (deriv % variable) - case (DIFF_DENSITY) - write(unit=unit_tally, fmt="(' Density derivative Material ',A)") & - to_str(deriv % diff_material) - case (DIFF_NUCLIDE_DENSITY) - write(unit=unit_tally, fmt="(' Nuclide density derivative & - &Material ',A,' Nuclide ',A)") & - trim(to_str(deriv % diff_material)), & - trim(nuclides(deriv % diff_nuclide) % name) - case (DIFF_TEMPERATURE) - write(unit=unit_tally, fmt="(' Temperature derivative Material ',& - &A)") to_str(deriv % diff_material) - case default - call fatal_error("Differential tally dependent variable for tally "& - // trim(to_str(t % id())) // " not defined in output.F90.") - end select - !end associate - end if - - ! WARNING: Admittedly, the logic for moving for printing results is - ! extremely confusing and took quite a bit of time to get correct. The - ! logic is structured this way since it is not practical to have a do - ! loop for each filter variable (given that only a few filters are likely - ! to be used for a given tally. - - ! Initialize bins, filter level, and indentation - do h = 1, t % n_filters() - filter_bins(t % filter(h) + 1) = 0 - end do - j = 1 - indent = 0 - - print_bin: do - find_bin: do - ! Check for no filters - if (t % n_filters() == 0) exit find_bin - - ! Increment bin combination - filter_bins(t % filter(j) + 1) = filter_bins(t % filter(j) + 1) + 1 - - ! ================================================================= - ! REACHED END OF BINS FOR THIS FILTER, MOVE TO NEXT FILTER - - if (filter_bins(t % filter(j) + 1) > & - filters(t % filter(j) + 1) % obj % n_bins) then - ! If this is the first filter, then exit - if (j == 1) exit print_bin - - filter_bins(t % filter(j) + 1) = 0 - j = j - 1 - indent = indent - 2 - - ! ================================================================= - ! VALID BIN -- WRITE FILTER INFORMATION OR EXIT TO WRITE RESULTS - - else - ! Check if this is last filter - if (j == t % n_filters()) exit find_bin - - ! Print current filter information - i_filt = t % filter(j) + 1 - write(UNIT=unit_tally, FMT='(1X,2A)') repeat(" ", indent), & - trim(filters(i_filt) % obj % & - text_label(filter_bins(i_filt))) - indent = indent + 2 - j = j + 1 - end if - - end do find_bin - - ! Print filter information - if (t % n_filters() > 0) then - i_filt = t % filter(j) + 1 - write(UNIT=unit_tally, FMT='(1X,2A)') repeat(" ", indent), & - trim(filters(i_filt) % obj % & - text_label(filter_bins(i_filt))) - end if - - ! Determine scoring index for this bin combination -- note that unlike - ! in the score_tally subroutine, we have to use max(bins,1) since all - ! bins below the lowest filter level will be zeros - - filter_index = 1 - do h = 1, t % n_filters() - filter_index = filter_index & - + (max(filter_bins(t % filter(h)+1) ,1) - 1) * t % stride(h) - end do - - ! Write results for this filter bin combination - score_index = 0 - if (t % n_filters() > 0) indent = indent + 2 - do n = 1, t % n_nuclide_bins() - ! Write label for nuclide - i_nuclide = t % nuclide_bins(n) - if (i_nuclide == -1) then - write(UNIT=unit_tally, FMT='(1X,2A,1X,A)') repeat(" ", indent), & - "Total Material" - else - if (run_CE) then - write(UNIT=unit_tally, FMT='(1X,2A,1X,A)') repeat(" ", indent), & - trim(nuclides(i_nuclide+1) % name) - else - call get_name_c(i_nuclide+1, len(temp_name), temp_name) - write(UNIT=unit_tally, FMT='(1X,2A,1X,A)') repeat(" ", indent), & - trim(temp_name) - end if - end if - - indent = indent + 2 - do k = 1, t % n_score_bins() - score_index = score_index + 1 - - associate(r => t % results(RESULT_SUM:RESULT_SUM_SQ, :, :)) - - if (t % score_bins(k) > 0) then - score_name = reaction_name(t % score_bins(k)) - else - score_name = score_names(abs(t % score_bins(k))) - end if - x(:) = mean_stdev(r(:, score_index, filter_index), nr) - write(UNIT=unit_tally, FMT='(1X,2A,1X,A,"+/- ",A)') & - repeat(" ", indent), score_name, & - to_str(x(1)), trim(to_str(t_value * x(2))) - end associate - end do - indent = indent - 2 - - end do - indent = indent - 2 - - if (t % n_filters() == 0) exit print_bin - - end do print_bin - - end associate - end do TALLY_LOOP - - close(UNIT=unit_tally) - - end subroutine write_tallies - !=============================================================================== ! MEAN_STDEV computes the sample mean and standard deviation of the mean of a ! single tally score diff --git a/src/output.cpp b/src/output.cpp index 16b96d4be2..9a64fd41be 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -2,27 +2,37 @@ #include // for std::transform #include // for strlen -#include // for setw -#include -#include #include +#include // for setw, setprecision +#include // for left +#include +#include +#include +#include #include "openmc/capi.h" #include "openmc/cell.h" #include "openmc/constants.h" +#include "openmc/error.h" #include "openmc/geometry.h" #include "openmc/lattice.h" +#include "openmc/math_functions.h" #include "openmc/message_passing.h" +#include "openmc/mgxs_interface.h" +#include "openmc/nuclide.h" #include "openmc/plot.h" +#include "openmc/reaction.h" #include "openmc/settings.h" #include "openmc/surface.h" +#include "openmc/tallies/derivative.h" +#include "openmc/tallies/tally.h" namespace openmc { //============================================================================== -void -header(const char* msg, int level) { +std::string +header(const char* msg) { // Determine how many times to repeat the '=' character. int n_prefix = (63 - strlen(msg)) / 2; int n_suffix = n_prefix; @@ -39,10 +49,18 @@ header(const char* msg, int level) { out << "> " << upper << " <"; for (int i = 0; i < n_suffix; i++) out << '='; + return out.str(); +} + +std::string header(const std::string& msg) {return header(msg.c_str());} + +void +header(const char* msg, int level) { + auto out = header(msg); + // Print header based on verbosity level. - if (settings::verbosity >= level) { - std::cout << out.str() << "\n\n"; - } + if (settings::verbosity >= level) + std::cout << out << "\n\n"; } //============================================================================== @@ -230,4 +248,152 @@ print_overlap_check() } } +//============================================================================== + +std::pair +mean_stdev(double sum, double sum_sq, int n) +{ + double mean, std_dev; + mean = sum / n; + if (n > 1) { + std_dev = std::sqrt((sum_sq / n - mean*mean) / (n - 1)); + } else { + std_dev = 0; + } + return {mean, std_dev}; +} + +const std::unordered_map score_names = { + {SCORE_FLUX, "Flux"}, + {SCORE_TOTAL, "Total Reaction Rate"}, + {SCORE_SCATTER, "Scattering Rate"}, + {SCORE_NU_SCATTER, "Scattering Production Rate"}, + {SCORE_ABSORPTION, "Absorption Rate"}, + {SCORE_FISSION, "Fission Rate"}, + {SCORE_NU_FISSION, "Nu-Fission Rate"}, + {SCORE_KAPPA_FISSION, "Kappa-Fission Rate"}, + {SCORE_EVENTS, "Events"}, + {SCORE_DECAY_RATE, "Decay Rate"}, + {SCORE_DELAYED_NU_FISSION, "Delayed-Nu-Fission Rate"}, + {SCORE_PROMPT_NU_FISSION, "Prompt-Nu-Fission Rate"}, + {SCORE_INVERSE_VELOCITY, "Flux-Weighted Inverse Velocity"}, + {SCORE_FISS_Q_PROMPT, "Prompt fission power"}, + {SCORE_FISS_Q_RECOV, "Recoverable fission power"}, + {SCORE_CURRENT, "Current"}, +}; + +//! Create an ASCII output file showing all tally results. + +extern "C" void +write_tallies() +{ + if (model::tallies.empty()) return; + + // Open the tallies.out file. + std::ofstream tallies_out; + tallies_out.open("tallies.out", std::ios::out | std::ios::trunc); + tallies_out << std::setprecision(6); + + // Loop over each tally. + for (auto i_tally = 0; i_tally < model::tallies.size(); ++i_tally) { + const auto& tally {*model::tallies[i_tally]}; + auto results = tally_results(i_tally+1); + // TODO: get this directly from the tally object when it's been translated + int32_t n_realizations; + auto err = openmc_tally_get_n_realizations(i_tally+1, &n_realizations); + + // Calculate t-value for confidence intervals + double t_value = 1; + if (settings::confidence_intervals) { + auto alpha = 1 - CONFIDENCE_LEVEL; + t_value = t_percentile_c(1 - alpha*0.5, n_realizations - 1); + } + + // Write header block. + std::string tally_header("TALLY " + std::to_string(tally.id_)); + if (!tally.name_.empty()) tally_header += ": " + tally.name_; + tallies_out << "\n" << header(tally_header) << "\n\n"; + + // Write derivative information. + if (tally.deriv_ != C_NONE) { + const auto& deriv {model::tally_derivs[tally.deriv_]}; + switch (deriv.variable) { + case DIFF_DENSITY: + tallies_out << " Density derivative Material " + << std::to_string(deriv.diff_material) << "\n"; + break; + case DIFF_NUCLIDE_DENSITY: + tallies_out << " Nuclide density derivative Material " + << std::to_string(deriv.diff_material) << " Nuclide " + // TODO: off-by-one + << data::nuclides[deriv.diff_nuclide-1]->name_ << "\n"; + break; + case DIFF_TEMPERATURE: + tallies_out << " Temperature derivative Material " + << std::to_string(deriv.diff_material) << "\n"; + break; + default: + fatal_error("Differential tally dependent variable for tally " + + std::to_string(tally.id_) + " not defined in output.cpp"); + } + } + + // Loop over all filter bin combinations. + auto filter_iter = FilterBinIter(tally, false); + auto end = FilterBinIter(tally, true); + for (; filter_iter != end; ++filter_iter) { + auto filter_index = filter_iter.index_; + + // Print info about this combination of filter bins. The stride check + // prevents redundant output. + int indent = 0; + for (auto i = 0; i < tally.filters().size(); ++i) { + if ((filter_index-1) % tally.strides(i) == 0) { + auto i_filt = tally.filters(i); + const auto& filt {*model::tally_filters[i_filt]}; + auto& match {simulation::filter_matches[i_filt]}; + tallies_out << std::string(indent+1, ' ') + << filt.text_label(match.i_bin_) << "\n"; + } + indent += 2; + } + + // Loop over all nuclide and score combinations. + int score_index = 0; + for (auto i_nuclide : tally.nuclides_) { + // Write label for this nuclide bin. + if (i_nuclide == -1) { + tallies_out << std::string(indent+1, ' ') << "Total Material\n"; + } else { + if (settings::run_CE) { + tallies_out << std::string(indent+1, ' ') + << data::nuclides[i_nuclide]->name_ << "\n"; + } else { + tallies_out << std::string(indent+1, ' ') + << data::nuclides_MG[i_nuclide].name << "\n"; + } + } + + // Write the score, mean, and uncertainty. + indent += 2; + for (auto score : tally.scores_) { + std::string score_name = score > 0 ? reaction_name(score) + : score_names.at(score); + double mean, stdev; + //TODO: off-by-one + std::tie(mean, stdev) = mean_stdev( + results(filter_index-1, score_index, RESULT_SUM), + results(filter_index-1, score_index, RESULT_SUM_SQ), + n_realizations); + tallies_out << std::string(indent+1, ' ') << std::left + << std::setw(36) << score_name << " " << mean << " +/- " + << t_value * stdev << "\n"; + score_index += 1; + } + indent -= 2; + } + } + } +} + } // namespace openmc diff --git a/src/simulation.cpp b/src/simulation.cpp index fbbcca9da9..978f57e0f0 100644 --- a/src/simulation.cpp +++ b/src/simulation.cpp @@ -144,11 +144,6 @@ int openmc_simulation_finalize() simulation::time_active.stop(); simulation::time_finalize.start(); -#pragma omp parallel - { - simulation::filter_matches.clear(); - } - // Deallocate Fortran variables, set tallies to inactive for (auto& mat : model::materials) { mat->mat_nuclide_index_.clear(); @@ -165,6 +160,11 @@ int openmc_simulation_finalize() // Write tally results to tallies.out if (settings::output_tallies && mpi::master) write_tallies(); +#pragma omp parallel + { + simulation::filter_matches.clear(); + } + // Deactivate all tallies for (int i = 1; i <= n_tallies; ++i) { openmc_tally_set_active(i, false); diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 704bedaa50..f1d4029791 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -63,124 +63,6 @@ double global_tally_collision; double global_tally_tracklength; double global_tally_leakage; -//============================================================================== -//! An iterator over all combinations of a tally's matching filter bins. -// -//! This iterator handles two distinct tasks. First, it maps the N-dimensional -//! space created by the indices of N filters onto a 1D sequence. In other -//! words, it provides a single number that uniquely identifies a combination of -//! bins for many filters. Second, it handles the task of finding each all -//! valid combinations of filter bins given that each filter can have 1 or 2 or -//! many bins that are valid for the current tally event. -//============================================================================== - -class FilterBinIter -{ -public: - FilterBinIter(const Tally& tally, Particle* p, bool end) - : tally_{tally} - { - // Handle the special case for an iterator that points to the end. - if (end) { - index_ = -1; - return; - } - - // Find all valid bins in each relevant filter if they have not already been - // found for this event. - for (auto i_filt : tally_.filters()) { - auto& match {simulation::filter_matches[i_filt]}; - if (!match.bins_present_) { - match.bins_.clear(); - match.weights_.clear(); - model::tally_filters[i_filt]->get_all_bins(p, tally_.estimator_, match); - match.bins_present_ = true; - } - - // If there are no valid bins for this filter, then there are no valid - // filter bin combinations so all iterators are end iterators. - if (match.bins_.size() == 0) { - index_ = -1; - return; - } - - // Set the index of the bin used in the first filter combination - match.i_bin_ = 1; - } - - // Compute the initial index and weight. - compute_index_weight(); - } - - bool - operator==(const FilterBinIter& other) - { - return index_ == other.index_; - } - - bool - operator!=(const FilterBinIter& other) - { - return !(*this == other); - } - - FilterBinIter& - operator++() - { - // Find the next valid combination of filter bins. To do this, we search - // backwards through the filters until we find the first filter whose bins - // can be incremented. - bool done_looping = true; - for (int i = tally_.filters().size()-1; i >= 0; --i) { - auto i_filt = tally_.filters(i); - auto& match {simulation::filter_matches[i_filt]}; - if (match.i_bin_< match.bins_.size()) { - // The bin for this filter can be incremented. Increment it and do not - // touch any of the remaining filters. - ++match.i_bin_; - done_looping = false; - break; - } else { - // This bin cannot be incremented so reset it and continue to the next - // filter. - match.i_bin_ = 1; - } - } - - if (done_looping) { - // We have visited every valid combination. All done! - index_ = -1; - } else { - // The loop found a new valid combination. Compute the corresponding - // index and weight. - compute_index_weight(); - } - - return *this; - } - - int index_ {1}; - double weight_ {1.}; - -private: - void - compute_index_weight() - { - index_ = 1; - weight_ = 1.; - for (auto i = 0; i < tally_.filters().size(); ++i) { - auto i_filt = tally_.filters(i); - auto& match {simulation::filter_matches[i_filt]}; - auto i_bin = match.i_bin_; - //TODO: off-by-one - index_ += (match.bins_[i_bin-1] - 1) * tally_.strides(i); - weight_ *= match.weights_[i_bin-1]; - } - } - - const Tally& tally_; -}; - int score_str_to_int(std::string score_str) { @@ -341,6 +223,12 @@ score_str_to_int(std::string score_str) // Tally object implementation //============================================================================== +void +Tally::init_from_xml(pugi::xml_node node) +{ + if (check_for_node(node, "name")) name_ = get_node_value(node, "name"); +} + void Tally::set_filters(const int32_t filter_indices[], int n) { @@ -632,6 +520,123 @@ Tally::init_triggers(pugi::xml_node node, int i_tally) } } +//============================================================================== +// FilterBinIter implementation +//============================================================================== + +FilterBinIter::FilterBinIter(const Tally& tally, Particle* p) + : tally_{tally} +{ + // Find all valid bins in each relevant filter if they have not already been + // found for this event. + for (auto i_filt : tally_.filters()) { + auto& match {simulation::filter_matches[i_filt]}; + if (!match.bins_present_) { + match.bins_.clear(); + match.weights_.clear(); + model::tally_filters[i_filt]->get_all_bins(p, tally_.estimator_, match); + match.bins_present_ = true; + } + + // If there are no valid bins for this filter, then there are no valid + // filter bin combinations so all iterators are end iterators. + if (match.bins_.size() == 0) { + index_ = -1; + return; + } + + // Set the index of the bin used in the first filter combination + match.i_bin_ = 1; + } + + // Compute the initial index and weight. + compute_index_weight(); +} + +FilterBinIter::FilterBinIter(const Tally& tally, bool end) + : tally_{tally} +{ + // Handle the special case for an iterator that points to the end. + if (end) { + index_ = -1; + return; + } + + for (auto i_filt : tally_.filters()) { + auto& match {simulation::filter_matches[i_filt]}; + if (!match.bins_present_) { + match.bins_.clear(); + match.weights_.clear(); + for (auto i = 0; i < model::tally_filters[i_filt]->n_bins_; ++i) { + // TODO: off-by-one + match.bins_.push_back(i+1); + match.weights_.push_back(1.0); + } + match.bins_present_ = true; + } + + if (match.bins_.size() == 0) { + index_ = -1; + return; + } + + match.i_bin_ = 1; + } + + // Compute the initial index and weight. + compute_index_weight(); +} + +FilterBinIter& +FilterBinIter::operator++() +{ + // Find the next valid combination of filter bins. To do this, we search + // backwards through the filters until we find the first filter whose bins + // can be incremented. + bool done_looping = true; + for (int i = tally_.filters().size()-1; i >= 0; --i) { + auto i_filt = tally_.filters(i); + auto& match {simulation::filter_matches[i_filt]}; + if (match.i_bin_< match.bins_.size()) { + // The bin for this filter can be incremented. Increment it and do not + // touch any of the remaining filters. + ++match.i_bin_; + done_looping = false; + break; + } else { + // This bin cannot be incremented so reset it and continue to the next + // filter. + match.i_bin_ = 1; + } + } + + if (done_looping) { + // We have visited every valid combination. All done! + index_ = -1; + } else { + // The loop found a new valid combination. Compute the corresponding + // index and weight. + compute_index_weight(); + } + + return *this; +} + +void +FilterBinIter::compute_index_weight() +{ + index_ = 1; + weight_ = 1.; + for (auto i = 0; i < tally_.filters().size(); ++i) { + auto i_filt = tally_.filters(i); + auto& match {simulation::filter_matches[i_filt]}; + auto i_bin = match.i_bin_; + //TODO: off-by-one + index_ += (match.bins_[i_bin-1] - 1) * tally_.strides(i); + weight_ *= match.weights_[i_bin-1]; + } +} + //============================================================================== // Non-member functions //============================================================================== @@ -2511,8 +2516,8 @@ score_analog_tally_ce(Particle* p) // Initialize an iterator over valid filter bin combinations. If there are // no valid combinations, use a continue statement to ensure we skip the // assume_separate break below. - auto filter_iter = FilterBinIter(tally, p, false); - auto end = FilterBinIter(tally, nullptr, true); + auto filter_iter = FilterBinIter(tally, p); + auto end = FilterBinIter(tally, true); if (filter_iter == end) continue; // Loop over filter bins. @@ -2576,8 +2581,8 @@ score_analog_tally_mg(Particle* p) // Initialize an iterator over valid filter bin combinations. If there are // no valid combinations, use a continue statement to ensure we skip the // assume_separate break below. - auto filter_iter = FilterBinIter(tally, p, false); - auto end = FilterBinIter(tally, nullptr, true); + auto filter_iter = FilterBinIter(tally, p); + auto end = FilterBinIter(tally, true); if (filter_iter == end) continue; // Loop over filter bins. @@ -2636,8 +2641,8 @@ score_tracklength_tally(Particle* p, double distance) // Initialize an iterator over valid filter bin combinations. If there are // no valid combinations, use a continue statement to ensure we skip the // assume_separate break below. - auto filter_iter = FilterBinIter(tally, p, false); - auto end = FilterBinIter(tally, nullptr, true); + auto filter_iter = FilterBinIter(tally, p); + auto end = FilterBinIter(tally, true); if (filter_iter == end) continue; // Loop over filter bins. @@ -2718,8 +2723,8 @@ score_collision_tally(Particle* p) // Initialize an iterator over valid filter bin combinations. If there are // no valid combinations, use a continue statement to ensure we skip the // assume_separate break below. - auto filter_iter = FilterBinIter(tally, p, false); - auto end = FilterBinIter(tally, nullptr, true); + auto filter_iter = FilterBinIter(tally, p); + auto end = FilterBinIter(tally, true); if (filter_iter == end) continue; // Loop over filter bins. @@ -2786,8 +2791,8 @@ score_surface_tally_inner(Particle* p, const std::vector& tallies) // Initialize an iterator over valid filter bin combinations. If there are // no valid combinations, use a continue statement to ensure we skip the // assume_separate break below. - auto filter_iter = FilterBinIter(tally, p, false); - auto end = FilterBinIter(tally, nullptr, true); + auto filter_iter = FilterBinIter(tally, p); + auto end = FilterBinIter(tally, true); if (filter_iter == end) continue; // Loop over filter bins. @@ -3172,6 +3177,9 @@ extern "C" { int active_surface_tallies_size() {return model::active_surface_tallies.size();} + void tally_init_from_xml(Tally* tally, pugi::xml_node* node) + {tally->init_from_xml(*node);} + int tally_get_id_c(Tally* tally) {return tally->id_;} void tally_set_id_c(Tally* tally, int id) {tally->id_ = id;} From 743db3c02e32c38055ad434674f7e871a3d4229a Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Mon, 11 Feb 2019 14:47:34 -0500 Subject: [PATCH 46/58] Separate tally scoring from Tally object methods --- CMakeLists.txt | 1 + include/openmc/tallies/tally.h | 36 - include/openmc/tallies/tally_scoring.h | 50 + src/output.cpp | 2 + src/tallies/tally.cpp | 2303 ----------------------- src/tallies/tally_scoring.cpp | 2317 ++++++++++++++++++++++++ 6 files changed, 2370 insertions(+), 2339 deletions(-) create mode 100644 include/openmc/tallies/tally_scoring.h create mode 100644 src/tallies/tally_scoring.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b174b069c4..61a53b1938 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -430,6 +430,7 @@ add_library(libopenmc SHARED src/tallies/filter_universe.cpp src/tallies/filter_zernike.cpp src/tallies/tally.cpp + src/tallies/tally_scoring.cpp src/tallies/trigger.cpp src/timer.cpp src/thermal.cpp diff --git a/include/openmc/tallies/tally.h b/include/openmc/tallies/tally.h index 74adc7ed6f..11ad1124be 100644 --- a/include/openmc/tallies/tally.h +++ b/include/openmc/tallies/tally.h @@ -2,7 +2,6 @@ #define OPENMC_TALLIES_TALLY_H #include "openmc/constants.h" -#include "openmc/tallies/filter.h" #include "openmc/tallies/trigger.h" #include "pugixml.hpp" @@ -98,41 +97,6 @@ private: int32_t n_filter_bins_ {0}; }; -//============================================================================== -//! An iterator over all combinations of a tally's matching filter bins. -// -//! This iterator handles two distinct tasks. First, it maps the N-dimensional -//! space created by the indices of N filters onto a 1D sequence. In other -//! words, it provides a single number that uniquely identifies a combination of -//! bins for many filters. Second, it handles the task of finding each all -//! valid combinations of filter bins given that each filter can have 1 or 2 or -//! many bins that are valid for the current tally event. -//============================================================================== - -class FilterBinIter -{ -public: - FilterBinIter(const Tally& tally, Particle* p); - - FilterBinIter(const Tally& tally, bool end); - - bool operator==(const FilterBinIter& other) const - {return index_ == other.index_;} - - bool operator!=(const FilterBinIter& other) const - {return !(*this == other);} - - FilterBinIter& operator++(); - - int index_ {1}; - double weight_ {1.}; - -private: - void compute_index_weight(); - - const Tally& tally_; -}; - //============================================================================== // Global variable declarations //============================================================================== diff --git a/include/openmc/tallies/tally_scoring.h b/include/openmc/tallies/tally_scoring.h new file mode 100644 index 0000000000..0db43f08c6 --- /dev/null +++ b/include/openmc/tallies/tally_scoring.h @@ -0,0 +1,50 @@ +#ifndef OPENMC_TALLIES_TALLY_SCORING_H +#define OPENMC_TALLIES_TALLY_SCORING_H + +#include "openmc/particle.h" +#include "openmc/tallies/tally.h" + +namespace openmc { + +//============================================================================== +//! An iterator over all combinations of a tally's matching filter bins. +// +//! This iterator handles two distinct tasks. First, it maps the N-dimensional +//! space created by the indices of N filters onto a 1D sequence. In other +//! words, it provides a single number that uniquely identifies a combination of +//! bins for many filters. Second, it handles the task of finding each all +//! valid combinations of filter bins given that each filter can have 1 or 2 or +//! many bins that are valid for the current tally event. +//============================================================================== + +class FilterBinIter +{ +public: + FilterBinIter(const Tally& tally, Particle* p); + + FilterBinIter(const Tally& tally, bool end); + + bool operator==(const FilterBinIter& other) const + {return index_ == other.index_;} + + bool operator!=(const FilterBinIter& other) const + {return !(*this == other);} + + FilterBinIter& operator++(); + + int index_ {1}; + double weight_ {1.}; + +private: + void compute_index_weight(); + + const Tally& tally_; +}; + +//============================================================================== +// Non-member functions +//============================================================================== + +} // namespace openmc + +#endif // OPENMC_TALLIES_TALLY_SCORING_H diff --git a/src/output.cpp b/src/output.cpp index 9a64fd41be..3dac0238b4 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -25,7 +25,9 @@ #include "openmc/settings.h" #include "openmc/surface.h" #include "openmc/tallies/derivative.h" +#include "openmc/tallies/filter.h" #include "openmc/tallies/tally.h" +#include "openmc/tallies/tally_scoring.h" namespace openmc { diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index f1d4029791..5343b7cec0 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -3,12 +3,10 @@ #include "openmc/capi.h" #include "openmc/constants.h" #include "openmc/error.h" -#include "openmc/material.h" #include "openmc/message_passing.h" #include "openmc/mgxs_interface.h" #include "openmc/nuclide.h" #include "openmc/reaction_product.h" -#include "openmc/search.h" #include "openmc/settings.h" #include "openmc/simulation.h" #include "openmc/tallies/derivative.h" @@ -34,15 +32,6 @@ namespace openmc { -//============================================================================== -// Functions defined in Fortran -//============================================================================== - -extern "C" int -energy_filter_search(const EnergyFilter* filt, double val); - -extern "C" double get_precursor_decay_rate(int i_nuclide, int d); - //============================================================================== // Global variable definitions //============================================================================== @@ -520,123 +509,6 @@ Tally::init_triggers(pugi::xml_node node, int i_tally) } } -//============================================================================== -// FilterBinIter implementation -//============================================================================== - -FilterBinIter::FilterBinIter(const Tally& tally, Particle* p) - : tally_{tally} -{ - // Find all valid bins in each relevant filter if they have not already been - // found for this event. - for (auto i_filt : tally_.filters()) { - auto& match {simulation::filter_matches[i_filt]}; - if (!match.bins_present_) { - match.bins_.clear(); - match.weights_.clear(); - model::tally_filters[i_filt]->get_all_bins(p, tally_.estimator_, match); - match.bins_present_ = true; - } - - // If there are no valid bins for this filter, then there are no valid - // filter bin combinations so all iterators are end iterators. - if (match.bins_.size() == 0) { - index_ = -1; - return; - } - - // Set the index of the bin used in the first filter combination - match.i_bin_ = 1; - } - - // Compute the initial index and weight. - compute_index_weight(); -} - -FilterBinIter::FilterBinIter(const Tally& tally, bool end) - : tally_{tally} -{ - // Handle the special case for an iterator that points to the end. - if (end) { - index_ = -1; - return; - } - - for (auto i_filt : tally_.filters()) { - auto& match {simulation::filter_matches[i_filt]}; - if (!match.bins_present_) { - match.bins_.clear(); - match.weights_.clear(); - for (auto i = 0; i < model::tally_filters[i_filt]->n_bins_; ++i) { - // TODO: off-by-one - match.bins_.push_back(i+1); - match.weights_.push_back(1.0); - } - match.bins_present_ = true; - } - - if (match.bins_.size() == 0) { - index_ = -1; - return; - } - - match.i_bin_ = 1; - } - - // Compute the initial index and weight. - compute_index_weight(); -} - -FilterBinIter& -FilterBinIter::operator++() -{ - // Find the next valid combination of filter bins. To do this, we search - // backwards through the filters until we find the first filter whose bins - // can be incremented. - bool done_looping = true; - for (int i = tally_.filters().size()-1; i >= 0; --i) { - auto i_filt = tally_.filters(i); - auto& match {simulation::filter_matches[i_filt]}; - if (match.i_bin_< match.bins_.size()) { - // The bin for this filter can be incremented. Increment it and do not - // touch any of the remaining filters. - ++match.i_bin_; - done_looping = false; - break; - } else { - // This bin cannot be incremented so reset it and continue to the next - // filter. - match.i_bin_ = 1; - } - } - - if (done_looping) { - // We have visited every valid combination. All done! - index_ = -1; - } else { - // The loop found a new valid combination. Compute the corresponding - // index and weight. - compute_index_weight(); - } - - return *this; -} - -void -FilterBinIter::compute_index_weight() -{ - index_ = 1; - weight_ = 1.; - for (auto i = 0; i < tally_.filters().size(); ++i) { - auto i_filt = tally_.filters(i); - auto& match {simulation::filter_matches[i_filt]}; - auto i_bin = match.i_bin_; - //TODO: off-by-one - index_ += (match.bins_[i_bin-1] - 1) * tally_.strides(i); - weight_ *= match.weights_[i_bin-1]; - } -} - //============================================================================== // Non-member functions //============================================================================== @@ -666,2181 +538,6 @@ adaptor_type<3> tally_results(int idx) return xt::adapt(results, size, xt::no_ownership(), shape); } -//! Helper function used to increment tallies with a delayed group filter. - -void -score_fission_delayed_dg(int i_tally, int d_bin, double score, int score_index) -{ - // Save the original delayed group bin - //TODO: off-by-one - const Tally& tally {*model::tallies[i_tally-1]}; - auto i_filt = tally.filters(tally.delayedgroup_filter_-1); - auto& dg_match {simulation::filter_matches[i_filt]}; - auto i_bin = dg_match.i_bin_; - auto original_bin = dg_match.bins_[i_bin-1]; - dg_match.bins_[i_bin-1] = d_bin; - - // Determine the filter scoring index - auto filter_index = 1; - for (auto i = 0; i < tally.filters().size(); ++i) { - auto i_filt = tally.filters(i); - auto& match {simulation::filter_matches[i_filt]}; - auto i_bin = match.i_bin_; - //TODO: off-by-one - filter_index += (match.bins_[i_bin-1] - 1) * tally.strides(i); - } - - // Update the tally result - auto results = tally_results(i_tally); - //TODO: off-by-one - #pragma omp atomic - results(filter_index-1, score_index, RESULT_VALUE) += score; - - // Reset the original delayed group bin - dg_match.bins_[i_bin-1] = original_bin; -} - -//! Helper function for nu-fission tallies with energyout filters. -// -//! In this case, we may need to score to multiple bins if there were multiple -//! neutrons produced with different energies. - -void -score_fission_eout(Particle* p, int i_tally, int i_score, int score_bin) -{ - //TODO: off-by-one - const Tally& tally {*model::tallies[i_tally-1]}; - auto results = tally_results(i_tally); - auto i_eout_filt = tally.filters()[tally.energyout_filter_-1]; - auto i_bin = simulation::filter_matches[i_eout_filt].i_bin_; - auto bin_energyout = simulation::filter_matches[i_eout_filt].bins_[i_bin-1]; - - const EnergyoutFilter& eo_filt - {*dynamic_cast(model::tally_filters[i_eout_filt].get())}; - - // Note that the score below is weighted by keff. Since the creation of - // fission sites is weighted such that it is expected to create n_particles - // sites, we need to multiply the score by keff to get the true nu-fission - // rate. Otherwise, the sum of all nu-fission rates would be ~1.0. - - // loop over number of particles banked - for (auto i = 0; i < p->n_bank; ++i) { - auto i_bank = simulation::n_bank - p->n_bank + i; - const auto& bank = simulation::fission_bank[i_bank]; - - // get the delayed group - auto g = bank.delayed_group; - - // determine score based on bank site weight and keff - double score = simulation::keff * bank.wgt; - - // Add derivative information for differential tallies. Note that the - // i_nuclide and atom_density arguments do not matter since this is an - // analog estimator. - if (tally.deriv_ != C_NONE) - apply_derivative_to_score(p, i_tally, 0, 0., SCORE_NU_FISSION, &score); - - if (!settings::run_CE && eo_filt.matches_transport_groups_) { - - // determine outgoing energy group from fission bank - auto g_out = static_cast(bank.E); - - // modify the value so that g_out = 1 corresponds to the highest energy - // bin - g_out = eo_filt.n_bins_ - g_out + 1; - - // change outgoing energy bin - simulation::filter_matches[i_eout_filt].bins_[i_bin-1] = g_out; - - } else { - - double E_out; - if (settings::run_CE) { - E_out = bank.E; - } else { - E_out = data::energy_bin_avg[static_cast(bank.E)]; - } - - // Set EnergyoutFilter bin index - if (E_out < eo_filt.bins_.front() || E_out > eo_filt.bins_.back()) { - continue; - } else { - //TODO: off-by-one - auto i_match = lower_bound_index(eo_filt.bins_.begin(), - eo_filt.bins_.end(), E_out) + 1; - simulation::filter_matches[i_eout_filt].bins_[i_bin-1] = i_match; - } - - } - - // Case for tallying prompt neutrons - if (score_bin == SCORE_NU_FISSION - || (score_bin == SCORE_PROMPT_NU_FISSION && g == 0)) { - - // Find the filter scoring index for this filter combination - //TODO: should this include a weight? - int filter_index = 1; - for (auto j = 0; j < tally.filters().size(); ++j) { - auto i_filt = tally.filters(j); - auto& match {simulation::filter_matches[i_filt]}; - auto i_bin = match.i_bin_; - filter_index += (match.bins_[i_bin-1] - 1) * tally.strides(j); - } - - // Update tally results - #pragma omp atomic - results(filter_index-1, i_score, RESULT_VALUE) += score; - - } else if (score_bin == SCORE_DELAYED_NU_FISSION && g != 0) { - - // Get the index of the delayed group filter - auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; - - // If the delayed group filter is present, tally to corresponding delayed - // group bin if it exists - if (i_dg_filt >= 0) { - const DelayedGroupFilter& dg_filt {*dynamic_cast( - model::tally_filters[i_dg_filt].get())}; - - // Loop over delayed group bins until the corresponding bin is found - for (auto d_bin = 0; d_bin < dg_filt.n_bins_; ++d_bin) { - if (dg_filt.groups_[d_bin] == g) { - // Find the filter index and weight for this filter combination - int filter_index = 1; - double filter_weight = 1.; - for (auto j = 0; j < tally.filters().size(); ++j) { - auto i_filt = tally.filters(j); - auto& match {simulation::filter_matches[i_filt]}; - auto i_bin = match.i_bin_; - filter_index += (match.bins_[i_bin-1] - 1) * tally.strides(j); - filter_weight *= match.weights_[i_bin-1]; - } - - score_fission_delayed_dg(i_tally, d_bin+1, score*filter_weight, - i_score); - } - } - - // If the delayed group filter is not present, add score to tally - } else { - - // Find the filter index and weight for this filter combination - int filter_index = 1; - double filter_weight = 1.; - for (auto j = 0; j < tally.filters().size(); ++j) { - auto i_filt = tally.filters(j); - auto& match {simulation::filter_matches[i_filt]}; - auto i_bin = match.i_bin_; - filter_index += (match.bins_[i_bin-1] - 1) * tally.strides(j); - filter_weight *= match.weights_[i_bin-1]; - } - - // Update tally results - #pragma omp atomic - results(filter_index-1, i_score, RESULT_VALUE) += score*filter_weight; - } - } - } - - // Reset outgoing energy bin and score index - simulation::filter_matches[i_eout_filt].bins_[i_bin-1] = bin_energyout; -} - -//! Update tally results for continuous-energy tallies with any estimator. -// -//! For analog tallies, the flux estimate depends on the score type so the flux -//! argument is really just used for filter weights. The atom_density argument -//! is not used for analog tallies. - -void -score_general_ce(Particle* p, int i_tally, int start_index, int filter_index, - int i_nuclide, double atom_density, double flux) -{ - //TODO: off-by-one - const Tally& tally {*model::tallies[i_tally-1]}; - auto results = tally_results(i_tally); - - // Get the pre-collision energy of the particle. - auto E = p->last_E; - - for (auto i = 0; i < tally.scores_.size(); ++i) { - auto score_bin = tally.scores_[i]; - auto score_index = start_index + i; - - double score; - - //TODO: off-by-one throughout on p->event_nuclide - //TODO: off-by-one throughout on p->material - - switch (score_bin) { - - - case SCORE_FLUX: - if (tally.estimator_ == ESTIMATOR_ANALOG) { - // All events score to a flux bin. We actually use a collision estimator - // in place of an analog one since there is no way to count 'events' - // exactly for the flux - if (settings::survival_biasing) { - // We need to account for the fact that some weight was already - // absorbed - score = p->last_wgt + p->absorb_wgt; - } else { - score = p->last_wgt; - } - - if (p->type == static_cast(ParticleType::neutron) || - p->type == static_cast(ParticleType::photon)) { - score *= flux / simulation::material_xs.total; - } else { - score = 0.; - } - } else { - score = flux; - } - break; - - - case SCORE_TOTAL: - if (tally.estimator_ == ESTIMATOR_ANALOG) { - // All events will score to the total reaction rate. We can just use - // use the weight of the particle entering the collision as the score - if (settings::survival_biasing) { - // We need to account for the fact that some weight was already - // absorbed - score = (p->last_wgt + p->absorb_wgt) * flux; - } else { - score = p->last_wgt * flux; - } - - } else { - if (i_nuclide >= 0) { - score = simulation::micro_xs[i_nuclide].total * atom_density * flux; - } else { - score = simulation::material_xs.total * flux; - } - } - break; - - - case SCORE_INVERSE_VELOCITY: - if (tally.estimator_ == ESTIMATOR_ANALOG) { - // All events score to an inverse velocity bin. We actually use a - // collision estimator in place of an analog one since there is no way - // to count 'events' exactly for the inverse velocity - if (settings::survival_biasing) { - // We need to account for the fact that some weight was already - // absorbed - score = p->last_wgt + p->absorb_wgt; - } else { - score = p->last_wgt; - } - score *= flux / simulation::material_xs.total; - } else { - score = flux; - } - // Score inverse velocity in units of s/cm. - score /= std::sqrt(2. * E / MASS_NEUTRON_EV) * C_LIGHT * 100.; - break; - - - case SCORE_SCATTER: - if (tally.estimator_ == ESTIMATOR_ANALOG) { - // Skip any event where the particle didn't scatter - if (p->event != EVENT_SCATTER) continue; - // Since only scattering events make it here, again we can use the - // weight entering the collision as the estimator for the reaction rate - score = p->last_wgt * flux; - } else { - if (i_nuclide >= 0) { - score = (simulation::micro_xs[i_nuclide].total - - simulation::micro_xs[i_nuclide].absorption) * atom_density * flux; - } else { - score = (simulation::material_xs.total - - simulation::material_xs.absorption) * flux; - } - } - break; - - - case SCORE_NU_SCATTER: - // Only analog estimators are available. - // Skip any event where the particle didn't scatter - if (p->event != EVENT_SCATTER) continue; - // For scattering production, we need to use the pre-collision weight - // times the yield as the estimate for the number of neutrons exiting a - // reaction with neutrons in the exit channel - if (p->event_MT == ELASTIC || p->event_MT == N_LEVEL || - (p->event_MT >= N_N1 && p->event_MT <= N_NC)) { - // Don't waste time on very common reactions we know have - // multiplicities of one. - score = p->last_wgt * flux; - } else { - // Get yield and apply to score - auto m = - data::nuclides[p->event_nuclide-1]->reaction_index_[p->event_MT]; - const auto& rxn {*data::nuclides[p->event_nuclide-1]->reactions_[m]}; - score = p->last_wgt * flux * (*rxn.products_[0].yield_)(E); - } - break; - - - case SCORE_ABSORPTION: - if (tally.estimator_ == ESTIMATOR_ANALOG) { - if (settings::survival_biasing) { - // No absorption events actually occur if survival biasing is on -- - // just use weight absorbed in survival biasing - score = p->absorb_wgt * flux; - } else { - // Skip any event where the particle wasn't absorbed - if (p->event == EVENT_SCATTER) continue; - // All fission and absorption events will contribute here, so we - // can just use the particle's weight entering the collision - score = p->last_wgt * flux; - } - } else { - if (i_nuclide >= 0) { - score = simulation::micro_xs[i_nuclide].absorption * atom_density - * flux; - } else { - score = simulation::material_xs.absorption * flux; - } - } - break; - - - case SCORE_FISSION: - if (simulation::material_xs.absorption == 0) continue; - if (tally.estimator_ == ESTIMATOR_ANALOG) { - if (settings::survival_biasing) { - // No fission events occur if survival biasing is on -- need to - // calculate fraction of absorptions that would have resulted in - // fission - if (simulation::micro_xs[p->event_nuclide-1].absorption > 0) { - score = p->absorb_wgt - * simulation::micro_xs[p->event_nuclide-1].fission - / simulation::micro_xs[p->event_nuclide-1].absorption * flux; - } else { - score = 0.; - } - } else { - // Skip any non-absorption events - if (p->event == EVENT_SCATTER) continue; - // All fission events will contribute, so again we can use particle's - // weight entering the collision as the estimate for the fission - // reaction rate - score = p->last_wgt - * simulation::micro_xs[p->event_nuclide-1].fission - / simulation::micro_xs[p->event_nuclide-1].absorption * flux; - } - } else { - if (i_nuclide >= 0) { - score = simulation::micro_xs[i_nuclide].fission * atom_density * flux; - } else { - score = simulation::material_xs.fission * flux; - } - } - break; - - - case SCORE_NU_FISSION: - if (simulation::material_xs.absorption == 0) continue; - if (tally.estimator_ == ESTIMATOR_ANALOG) { - if (settings::survival_biasing || p->fission) { - if (tally.energyout_filter_ > 0) { - // Fission has multiple outgoing neutrons so this helper function - // is used to handle scoring the multiple filter bins. - score_fission_eout(p, i_tally, score_index, score_bin); - continue; - } - } - if (settings::survival_biasing) { - // No fission events occur if survival biasing is on -- need to - // calculate fraction of absorptions that would have resulted in - // nu-fission - if (simulation::micro_xs[p->event_nuclide-1].absorption > 0) { - score = p->absorb_wgt - * simulation::micro_xs[p->event_nuclide-1].nu_fission - / simulation::micro_xs[p->event_nuclide-1].absorption * flux; - } else { - score = 0.; - } - } else { - // Skip any non-fission events - if (!p->fission) continue; - // If there is no outgoing energy filter, than we only need to score - // to one bin. For the score to be 'analog', we need to score the - // number of particles that were banked in the fission bank. Since - // this was weighted by 1/keff, we multiply by keff to get the proper - // score. - score = simulation::keff * p->wgt_bank * flux; - } - } else { - if (i_nuclide >= 0) { - score = simulation::micro_xs[i_nuclide].nu_fission * atom_density - * flux; - } else { - score = simulation::material_xs.nu_fission * flux; - } - } - break; - - - case SCORE_PROMPT_NU_FISSION: - if (simulation::material_xs.absorption == 0) continue; - if (tally.estimator_ == ESTIMATOR_ANALOG) { - if (settings::survival_biasing || p->fission) { - if (tally.energyout_filter_ > 0) { - // Fission has multiple outgoing neutrons so this helper function - // is used to handle scoring the multiple filter bins. - score_fission_eout(p, i_tally, score_index, score_bin); - continue; - } - } - if (settings::survival_biasing) { - // No fission events occur if survival biasing is on -- need to - // calculate fraction of absorptions that would have resulted in - // prompt-nu-fission - if (simulation::micro_xs[p->event_nuclide-1].absorption > 0) { - score = p->absorb_wgt - * simulation::micro_xs[p->event_nuclide-1].fission - * data::nuclides[p->event_nuclide-1] - ->nu(E, ReactionProduct::EmissionMode::prompt) - / simulation::micro_xs[p->event_nuclide-1].absorption * flux; - } else { - score = 0.; - } - } else { - // Skip any non-fission events - if (!p->fission) continue; - // If there is no outgoing energy filter, than we only need to score - // to one bin. For the score to be 'analog', we need to score the - // number of particles that were banked in the fission bank. Since - // this was weighted by 1/keff, we multiply by keff to get the proper - // score. - auto n_delayed = std::accumulate(p->n_delayed_bank, - p->n_delayed_bank+MAX_DELAYED_GROUPS, 0); - auto prompt_frac = 1. - n_delayed / static_cast(p->n_bank); - score = simulation::keff * p->wgt_bank * prompt_frac * flux; - } - } else { - if (i_nuclide >= 0) { - score = simulation::micro_xs[i_nuclide].fission - * data::nuclides[i_nuclide] - ->nu(E, ReactionProduct::EmissionMode::prompt) - * atom_density * flux; - } else { - score = 0.; - // Add up contributions from each nuclide in the material. - if (p->material != MATERIAL_VOID) { - const Material& material {*model::materials[p->material-1]}; - for (auto i = 0; i < material.nuclide_.size(); ++i) { - auto j_nuclide = material.nuclide_[i]; - auto atom_density = material.atom_density_(i); - score += simulation::micro_xs[j_nuclide].fission - * data::nuclides[j_nuclide] - ->nu(E, ReactionProduct::EmissionMode::prompt) - * atom_density * flux; - } - } - } - } - break; - - - case SCORE_DELAYED_NU_FISSION: - if (simulation::material_xs.absorption == 0) continue; - if (tally.estimator_ == ESTIMATOR_ANALOG) { - if (settings::survival_biasing || p->fission) { - if (tally.energyout_filter_ > 0) { - // Fission has multiple outgoing neutrons so this helper function - // is used to handle scoring the multiple filter bins. - score_fission_eout(p, i_tally, score_index, score_bin); - continue; - } - } - if (settings::survival_biasing) { - // No fission events occur if survival biasing is on -- need to - // calculate fraction of absorptions that would have resulted in - // delayed-nu-fission - if (simulation::micro_xs[p->event_nuclide-1].absorption > 0 - && data::nuclides[p->event_nuclide-1]->fissionable_) { - if (tally.delayedgroup_filter_ > 0) { - auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; - const DelayedGroupFilter& filt - {*dynamic_cast( - model::tally_filters[i_dg_filt].get())}; - // Tally each delayed group bin individually - for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { - auto d = filt.groups_[d_bin]; - auto yield = data::nuclides[p->event_nuclide-1] - ->nu(E, ReactionProduct::EmissionMode::delayed, d); - score = p->absorb_wgt * yield - * simulation::micro_xs[p->event_nuclide-1].fission - / simulation::micro_xs[p->event_nuclide-1].absorption * flux; - score_fission_delayed_dg(i_tally, d_bin+1, score, - score_index); - } - continue; - } else { - // If the delayed group filter is not present, compute the score - // by multiplying the absorbed weight by the fraction of the - // delayed-nu-fission xs to the absorption xs - score = p->absorb_wgt - * simulation::micro_xs[p->event_nuclide-1].fission - * data::nuclides[p->event_nuclide-1] - ->nu(E, ReactionProduct::EmissionMode::delayed) - / simulation::micro_xs[p->event_nuclide-1].absorption *flux; - } - } - } else { - // Skip any non-fission events - if (!p->fission) continue; - // If there is no outgoing energy filter, than we only need to score - // to one bin. For the score to be 'analog', we need to score the - // number of particles that were banked in the fission bank. Since - // this was weighted by 1/keff, we multiply by keff to get the proper - // score. Loop over the neutrons produced from fission and check which - // ones are delayed. If a delayed neutron is encountered, add its - // contribution to the fission bank to the score. - if (tally.delayedgroup_filter_ > 0) { - auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; - const DelayedGroupFilter& filt - {*dynamic_cast( - model::tally_filters[i_dg_filt].get())}; - // Tally each delayed group bin individually - for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { - auto d = filt.groups_[d_bin]; - score = simulation::keff * p->wgt_bank / p->n_bank - * p->n_delayed_bank[d-1] * flux; - score_fission_delayed_dg(i_tally, d_bin+1, score, score_index); - } - continue; - } else { - // Add the contribution from all delayed groups - auto n_delayed = std::accumulate(p->n_delayed_bank, - p->n_delayed_bank+MAX_DELAYED_GROUPS, 0); - score = simulation::keff * p->wgt_bank / p->n_bank * n_delayed - * flux; - } - } - } else { - if (i_nuclide >= 0) { - if (tally.delayedgroup_filter_ > 0) { - auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; - const DelayedGroupFilter& filt - {*dynamic_cast( - model::tally_filters[i_dg_filt].get())}; - // Tally each delayed group bin individually - for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { - auto d = filt.groups_[d_bin]; - auto yield = data::nuclides[i_nuclide] - ->nu(E, ReactionProduct::EmissionMode::delayed, d); - score = simulation::micro_xs[i_nuclide].fission * yield - * atom_density * flux; - score_fission_delayed_dg(i_tally, d_bin+1, score, score_index); - } - continue; - } else { - // If the delayed group filter is not present, compute the score - // by multiplying the delayed-nu-fission macro xs by the flux - score = simulation::micro_xs[i_nuclide].fission - * data::nuclides[i_nuclide] - ->nu(E, ReactionProduct::EmissionMode::delayed) - * atom_density * flux; - } - } else { - if (tally.delayedgroup_filter_ > 0) { - auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; - const DelayedGroupFilter& filt - {*dynamic_cast( - model::tally_filters[i_dg_filt].get())}; - if (p->material != MATERIAL_VOID) { - const Material& material {*model::materials[p->material-1]}; - for (auto i = 0; i < material.nuclide_.size(); ++i) { - auto j_nuclide = material.nuclide_[i]; - auto atom_density = material.atom_density_(i); - // Tally each delayed group bin individually - for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { - auto d = filt.groups_[d_bin]; - auto yield = data::nuclides[j_nuclide] - ->nu(E, ReactionProduct::EmissionMode::delayed, d); - score = simulation::micro_xs[j_nuclide].fission * yield - * atom_density * flux; - score_fission_delayed_dg(i_tally, d_bin+1, score, - score_index); - } - } - } - continue; - } else { - score = 0.; - if (p->material != MATERIAL_VOID) { - const Material& material {*model::materials[p->material-1]}; - for (auto i = 0; i < material.nuclide_.size(); ++i) { - auto j_nuclide = material.nuclide_[i]; - auto atom_density = material.atom_density_(i); - score += simulation::micro_xs[j_nuclide].fission - * data::nuclides[j_nuclide] - ->nu(E, ReactionProduct::EmissionMode::delayed) - * atom_density * flux; - } - } - } - } - } - break; - - - case SCORE_DECAY_RATE: - if (simulation::material_xs.absorption == 0) continue; - if (tally.estimator_ == ESTIMATOR_ANALOG) { - if (settings::survival_biasing) { - // No fission events occur if survival biasing is on -- need to - // calculate fraction of absorptions that would have resulted in - // delayed-nu-fission - const auto& nuc {*data::nuclides[p->event_nuclide-1]}; - if (simulation::micro_xs[p->event_nuclide-1].absorption > 0 - && nuc.fissionable_) { - const auto& rxn {*nuc.fission_rx_[0]}; - if (tally.delayedgroup_filter_ > 0) { - auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; - const DelayedGroupFilter& filt - {*dynamic_cast( - model::tally_filters[i_dg_filt].get())}; - // Tally each delayed group bin individually - for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { - auto d = filt.groups_[d_bin]; - auto yield - = nuc.nu(E, ReactionProduct::EmissionMode::delayed, d); - auto rate = rxn.products_[d].decay_rate_; - score = p->absorb_wgt * yield - * simulation::micro_xs[p->event_nuclide-1].fission - / simulation::micro_xs[p->event_nuclide-1].absorption - * rate * flux; - score_fission_delayed_dg(i_tally, d_bin+1, score, - score_index); - } - continue; - } else { - // If the delayed group filter is not present, compute the score - // by multiplying the absorbed weight by the fraction of the - // delayed-nu-fission xs to the absorption xs for all delayed - // groups - score = 0.; - // We need to be careful not to overshoot the number of - // delayed groups since this could cause the range of the - // rxn.products_ array to be exceeded. Hence, we use the size - // of this array and not the MAX_DELAYED_GROUPS constant for - // this loop. - for (auto d = 0; d < rxn.products_.size() - 2; ++d) { - auto yield - = nuc.nu(E, ReactionProduct::EmissionMode::delayed, d+1); - auto rate = rxn.products_[d+1].decay_rate_; - score += rate * p->absorb_wgt - * simulation::micro_xs[p->event_nuclide-1].fission * yield - / simulation::micro_xs[p->event_nuclide-1].absorption * flux; - } - } - } - } else { - // Skip any non-fission events - if (!p->fission) continue; - // If there is no outgoing energy filter, than we only need to score - // to one bin. For the score to be 'analog', we need to score the - // number of particles that were banked in the fission bank. Since - // this was weighted by 1/keff, we multiply by keff to get the proper - // score. Loop over the neutrons produced from fission and check which - // ones are delayed. If a delayed neutron is encountered, add its - // contribution to the fission bank to the score. - score = 0.; - for (auto i = 0; i < p->n_bank; ++i) { - auto i_bank = simulation::n_bank - p->n_bank + i; - const auto& bank = simulation::fission_bank[i_bank]; - auto g = bank.delayed_group; - if (g != 0) { - const auto& nuc {*data::nuclides[p->event_nuclide-1]}; - const auto& rxn {*nuc.fission_rx_[0]}; - auto rate = rxn.products_[g].decay_rate_; - score += simulation::keff * bank.wgt * rate * flux; - if (tally.delayedgroup_filter_ > 0) { - auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; - const DelayedGroupFilter& filt - {*dynamic_cast( - model::tally_filters[i_dg_filt].get())}; - // Find the corresponding filter bin and then score - for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { - auto d = filt.groups_[d_bin]; - if (d == g) - score_fission_delayed_dg(i_tally, d_bin+1, score, - score_index); - } - score = 0.; - } - } - } - } - } else { - if (i_nuclide >= 0) { - const auto& nuc {*data::nuclides[i_nuclide]}; - if (!nuc.fissionable_) continue; - const auto& rxn {*nuc.fission_rx_[0]}; - if (tally.delayedgroup_filter_ > 0) { - auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; - const DelayedGroupFilter& filt - {*dynamic_cast( - model::tally_filters[i_dg_filt].get())}; - // Tally each delayed group bin individually - for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { - auto d = filt.groups_[d_bin]; - auto yield - = nuc.nu(E, ReactionProduct::EmissionMode::delayed, d); - auto rate = rxn.products_[d].decay_rate_; - score = simulation::micro_xs[i_nuclide].fission * yield * flux - * atom_density * rate; - score_fission_delayed_dg(i_tally, d_bin+1, score, score_index); - } - continue; - } else { - score = 0.; - // We need to be careful not to overshoot the number of - // delayed groups since this could cause the range of the - // rxn.products_ array to be exceeded. Hence, we use the size - // of this array and not the MAX_DELAYED_GROUPS constant for - // this loop. - for (auto d = 0; d < rxn.products_.size() - 2; ++d) { - auto yield - = nuc.nu(E, ReactionProduct::EmissionMode::delayed, d+1); - auto rate = rxn.products_[d+1].decay_rate_; - score += simulation::micro_xs[i_nuclide].fission * flux - * yield * atom_density * rate; - } - } - } else { - if (tally.delayedgroup_filter_ > 0) { - auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; - const DelayedGroupFilter& filt - {*dynamic_cast( - model::tally_filters[i_dg_filt].get())}; - if (p->material != MATERIAL_VOID) { - const Material& material {*model::materials[p->material-1]}; - for (auto i = 0; i < material.nuclide_.size(); ++i) { - auto j_nuclide = material.nuclide_[i]; - auto atom_density = material.atom_density_(i); - const auto& nuc {*data::nuclides[j_nuclide]}; - if (nuc.fissionable_) { - const auto& rxn {*nuc.fission_rx_[0]}; - // Tally each delayed group bin individually - for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { - auto d = filt.groups_[d_bin]; - auto yield - = nuc.nu(E, ReactionProduct::EmissionMode::delayed, d); - auto rate = rxn.products_[d].decay_rate_; - score = simulation::micro_xs[j_nuclide].fission * yield - * flux * atom_density * rate; - score_fission_delayed_dg(i_tally, d_bin+1, score, - score_index); - } - } - } - } - continue; - } else { - score = 0.; - if (p->material != MATERIAL_VOID) { - const Material& material {*model::materials[p->material-1]}; - for (auto i = 0; i < material.nuclide_.size(); ++i) { - auto j_nuclide = material.nuclide_[i]; - auto atom_density = material.atom_density_(i); - const auto& nuc {*data::nuclides[j_nuclide]}; - if (nuc.fissionable_) { - const auto& rxn {*nuc.fission_rx_[0]}; - // We need to be careful not to overshoot the number of - // delayed groups since this could cause the range of the - // rxn.products_ array to be exceeded. Hence, we use the size - // of this array and not the MAX_DELAYED_GROUPS constant for - // this loop. - for (auto d = 0; d < rxn.products_.size() - 2; ++d) { - auto yield - = nuc.nu(E, ReactionProduct::EmissionMode::delayed, d+1); - auto rate = rxn.products_[d+1].decay_rate_; - score += simulation::micro_xs[j_nuclide].fission - * yield * atom_density * flux * rate; - } - } - } - } - } - } - } - break; - - - case SCORE_KAPPA_FISSION: - if (simulation::material_xs.absorption == 0.) continue; - score = 0.; - // Kappa-fission values are determined from the Q-value listed for the - // fission cross section. - if (tally.estimator_ == ESTIMATOR_ANALOG) { - if (settings::survival_biasing) { - // No fission events occur if survival biasing is on -- need to - // calculate fraction of absorptions that would have resulted in - // fission scaled by the Q-value - const auto& nuc {*data::nuclides[p->event_nuclide-1]}; - if (simulation::micro_xs[p->event_nuclide-1].absorption > 0 - && nuc.fissionable_) { - const auto& rxn {*nuc.fission_rx_[0]}; - score = p->absorb_wgt * rxn.q_value_ - * simulation::micro_xs[p->event_nuclide-1].fission - / simulation::micro_xs[p->event_nuclide-1].absorption * flux; - } - } else { - // Skip any non-absorption events - if (p->event == EVENT_SCATTER) continue; - // All fission events will contribute, so again we can use particle's - // weight entering the collision as the estimate for the fission - // reaction rate - const auto& nuc {*data::nuclides[p->event_nuclide-1]}; - if (simulation::micro_xs[p->event_nuclide-1].absorption > 0 - && nuc.fissionable_) { - const auto& rxn {*nuc.fission_rx_[0]}; - score = p->last_wgt * rxn.q_value_ - * simulation::micro_xs[p->event_nuclide-1].fission - / simulation::micro_xs[p->event_nuclide-1].absorption * flux; - } - } - } else { - if (i_nuclide >= 0) { - const auto& nuc {*data::nuclides[i_nuclide]}; - if (nuc.fissionable_) { - const auto& rxn {*nuc.fission_rx_[0]}; - score = rxn.q_value_ * simulation::micro_xs[i_nuclide].fission - * atom_density * flux; - } - } else { - if (p->material != MATERIAL_VOID) { - const Material& material {*model::materials[p->material-1]}; - for (auto i = 0; i < material.nuclide_.size(); ++i) { - auto j_nuclide = material.nuclide_[i]; - auto atom_density = material.atom_density_(i); - const auto& nuc {*data::nuclides[j_nuclide]}; - if (nuc.fissionable_) { - const auto& rxn {*nuc.fission_rx_[0]}; - score += rxn.q_value_ * simulation::micro_xs[j_nuclide].fission - * atom_density * flux; - } - } - } - } - } - break; - - - case SCORE_EVENTS: - // Simply count the number of scoring events - score = 1.; - break; - - - case ELASTIC: - if (tally.estimator_ == ESTIMATOR_ANALOG) { - // Check if event MT matches - if (p->event_MT != ELASTIC) continue; - score = p->last_wgt * flux; - } else { - if (i_nuclide >= 0) { - if (simulation::micro_xs[i_nuclide].elastic == CACHE_INVALID) - data::nuclides[i_nuclide]->calculate_elastic_xs(); - score = simulation::micro_xs[i_nuclide].elastic * atom_density * flux; - } else { - score = 0.; - if (p->material != MATERIAL_VOID) { - const Material& material {*model::materials[p->material-1]}; - for (auto i = 0; i < material.nuclide_.size(); ++i) { - auto j_nuclide = material.nuclide_[i]; - auto atom_density = material.atom_density_(i); - if (simulation::micro_xs[j_nuclide].elastic == CACHE_INVALID) - data::nuclides[j_nuclide]->calculate_elastic_xs(); - score += simulation::micro_xs[j_nuclide].elastic * atom_density - * flux; - } - } - } - } - break; - - case SCORE_FISS_Q_PROMPT: - case SCORE_FISS_Q_RECOV: - //continue; - if (simulation::material_xs.absorption == 0.) continue; - score = 0.; - if (tally.estimator_ == ESTIMATOR_ANALOG) { - if (settings::survival_biasing) { - // No fission events occur if survival biasing is on -- need to - // calculate fraction of absorptions that would have resulted in - // fission scaled by the Q-value - const auto& nuc {*data::nuclides[p->event_nuclide-1]}; - if (simulation::micro_xs[p->event_nuclide-1].absorption > 0) { - double q_value = 0.; - if (score_bin == SCORE_FISS_Q_PROMPT) { - if (nuc.fission_q_prompt_) - q_value = (*nuc.fission_q_prompt_)(p->last_E); - } else if (score_bin == SCORE_FISS_Q_RECOV) { - if (nuc.fission_q_recov_) - q_value = (*nuc.fission_q_recov_)(p->last_E); - } - score = p->absorb_wgt * q_value - * simulation::micro_xs[p->event_nuclide-1].fission - / simulation::micro_xs[p->event_nuclide-1].absorption * flux; - } - } else { - // Skip any non-absorption events - if (p->event == EVENT_SCATTER) continue; - // All fission events will contribute, so again we can use particle's - // weight entering the collision as the estimate for the fission - // reaction rate - const auto& nuc {*data::nuclides[p->event_nuclide-1]}; - if (simulation::micro_xs[p->event_nuclide-1].absorption > 0) { - double q_value = 0.; - if (score_bin == SCORE_FISS_Q_PROMPT) { - if (nuc.fission_q_prompt_) - q_value = (*nuc.fission_q_prompt_)(p->last_E); - } else if (score_bin == SCORE_FISS_Q_RECOV) { - if (nuc.fission_q_recov_) - q_value = (*nuc.fission_q_recov_)(p->last_E); - } - score = p->last_wgt * q_value - * simulation::micro_xs[p->event_nuclide-1].fission - / simulation::micro_xs[p->event_nuclide-1].absorption * flux; - } - } - } else { - if (i_nuclide >= 0) { - const auto& nuc {*data::nuclides[i_nuclide]}; - double q_value = 0.; - if (score_bin == SCORE_FISS_Q_PROMPT) { - if (nuc.fission_q_prompt_) - q_value = (*nuc.fission_q_prompt_)(p->last_E); - } else if (score_bin == SCORE_FISS_Q_RECOV) { - if (nuc.fission_q_recov_) - q_value = (*nuc.fission_q_recov_)(p->last_E); - } - score = q_value * simulation::micro_xs[i_nuclide].fission - * atom_density * flux; - } else { - if (p->material != MATERIAL_VOID) { - const Material& material {*model::materials[p->material-1]}; - for (auto i = 0; i < material.nuclide_.size(); ++i) { - auto j_nuclide = material.nuclide_[i]; - auto atom_density = material.atom_density_(i); - const auto& nuc {*data::nuclides[j_nuclide]}; - double q_value = 0.; - if (score_bin == SCORE_FISS_Q_PROMPT) { - if (nuc.fission_q_prompt_) - q_value = (*nuc.fission_q_prompt_)(p->last_E); - } else if (score_bin == SCORE_FISS_Q_RECOV) { - if (nuc.fission_q_recov_) - q_value = (*nuc.fission_q_recov_)(p->last_E); - } - score += q_value * simulation::micro_xs[j_nuclide].fission - * atom_density * flux; - } - } - } - } - break; - - - case N_2N: - case N_3N: - case N_4N: - case N_GAMMA: - case N_P: - case N_A: - if (tally.estimator_ == ESTIMATOR_ANALOG) { - // Check if the event MT matches - if (p->event_MT != score_bin) continue; - score = p->last_wgt * flux; - } else { - int m; - switch (score_bin) { - case N_GAMMA: m = 0; break; - case N_P: m = 1; break; - case N_A: m = 2; break; - case N_2N: m = 3; break; - case N_3N: m = 4; break; - case N_4N: m = 5; break; - } - if (i_nuclide >= 0) { - score = simulation::micro_xs[i_nuclide].reaction[m] * atom_density - * flux; - } else { - score = 0.; - if (p->material != MATERIAL_VOID) { - const Material& material {*model::materials[p->material-1]}; - for (auto i = 0; i < material.nuclide_.size(); ++i) { - auto j_nuclide = material.nuclide_[i]; - auto atom_density = material.atom_density_(i); - score += simulation::micro_xs[j_nuclide].reaction[m] - * atom_density * flux; - } - } - } - } - break; - - - default: - if (tally.estimator_ == ESTIMATOR_ANALOG) { - // Any other score is assumed to be a MT number. Thus, we just need - // to check if it matches the MT number of the event - if (p->event_MT != score_bin) continue; - score = p->last_wgt*flux; - } else { - // Any other cross section has to be calculated on-the-fly - if (score_bin < 2) fatal_error("Invalid score type on tally " - + std::to_string(tally.id_)); - score = 0.; - if (i_nuclide >= 0) { - const auto& nuc {*data::nuclides[i_nuclide]}; - auto m = nuc.reaction_index_[score_bin]; - if (m == C_NONE) continue; - const auto& rxn {*nuc.reactions_[m]}; - auto i_temp = simulation::micro_xs[i_nuclide].index_temp; - if (i_temp >= 0) { // Can be false due to multipole - auto i_grid = simulation::micro_xs[i_nuclide].index_grid - 1; - auto f = simulation::micro_xs[i_nuclide].interp_factor; - const auto& xs {rxn.xs_[i_temp]}; - auto threshold = xs.threshold - 1; - if (i_grid >= xs.threshold) { - score = ((1.0 - f) * xs.value[i_grid-threshold] - + f * xs.value[i_grid-threshold+1]) * atom_density * flux; - } - } - } else { - if (p->material != MATERIAL_VOID) { - const Material& material {*model::materials[p->material-1]}; - for (auto i = 0; i < material.nuclide_.size(); ++i) { - auto j_nuclide = material.nuclide_[i]; - auto atom_density = material.atom_density_(i); - const auto& nuc {*data::nuclides[j_nuclide]}; - auto m = nuc.reaction_index_[score_bin]; - if (m == C_NONE) continue; - const auto& rxn {*nuc.reactions_[m]}; - auto i_temp = simulation::micro_xs[j_nuclide].index_temp; - if (i_temp >= 0) { // Can be false due to multipole - auto i_grid = simulation::micro_xs[j_nuclide].index_grid - 1; - auto f = simulation::micro_xs[j_nuclide].interp_factor; - const auto& xs {rxn.xs_[i_temp]}; - auto threshold = xs.threshold - 1; - if (i_grid >= threshold) { - score += ((1.0 - f) * xs.value[i_grid-threshold] - + f * xs.value[i_grid-threshold+1]) * atom_density - * flux; - } - } - } - } - } - } - } - - // Add derivative information on score for differnetial tallies. - if (tally.deriv_ != C_NONE) - apply_derivative_to_score(p, i_tally, i_nuclide, atom_density, score_bin, - &score); - - // Update tally results - #pragma omp atomic - results(filter_index-1, score_index, RESULT_VALUE) += score; - } -} - -//! Update tally results for multigroup tallies with any estimator. -// -//! For analog tallies, the flux estimate depends on the score type so the flux -//! argument is really just used for filter weights. - -void -score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, - int i_nuclide, double atom_density, double flux) -{ - //TODO: off-by-one - const Tally& tally {*model::tallies[i_tally-1]}; - auto results = tally_results(i_tally); - - //TODO: off-by-one throughout on p->material - - // Set the direction and group to use with get_xs - double* p_uvw; - int p_g; - if (tally.estimator_ == ESTIMATOR_ANALOG - || tally.estimator_ == ESTIMATOR_COLLISION) { - - if (settings::survival_biasing) { - - // Then we either are alive and had a scatter (and so g changed), - // or are dead and g did not change - if (p->alive) { - p_uvw = p->last_uvw; - p_g = p->last_g; - } else { - p_uvw = p->coord[p->n_coord-1].uvw; - p_g = p->g; - } - } else if (p->event == EVENT_SCATTER) { - - // Then the energy group has been changed by the scattering routine - // meaning gin is now in p % last_g - p_uvw = p->last_uvw; - p_g = p->last_g; - } else { - - // No scatter, no change in g. - p_uvw = p->coord[p->n_coord-1].uvw; - p_g = p->g; - } - } else { - - // No actual collision so g has not changed. - p_uvw = p->coord[p->n_coord-1].uvw; - p_g = p->g; - } - - // To significantly reduce de-referencing, point matxs to the macroscopic - // Mgxs for the material of interest - data::macro_xs[p->material - 1].set_angle_index(p_uvw); - - // Do same for nucxs, point it to the microscopic nuclide data of interest - if (i_nuclide >= 0) { - // And since we haven't calculated this temperature index yet, do so now - data::nuclides_MG[i_nuclide].set_temperature_index(p->sqrtkT); - data::nuclides_MG[i_nuclide].set_angle_index(p_uvw); - } - - for (auto i = 0; i < tally.scores_.size(); ++i) { - auto score_bin = tally.scores_[i]; - auto score_index = start_index + i; - - double score; - - switch (score_bin) { - - - case SCORE_FLUX: - if (tally.estimator_ == ESTIMATOR_ANALOG) { - // All events score to a flux bin. We actually use a collision estimator - // in place of an analog one since there is no way to count 'events' - // exactly for the flux - if (settings::survival_biasing) { - // We need to account for the fact that some weight was already - // absorbed - score = p->last_wgt + p->absorb_wgt; - } else { - score = p->last_wgt; - } - score *= flux / simulation::material_xs.total; - } else { - score = flux; - } - break; - - - case SCORE_TOTAL: - if (tally.estimator_ == ESTIMATOR_ANALOG) { - // All events will score to the total reaction rate. We can just use - // use the weight of the particle entering the collision as the score - if (settings::survival_biasing) { - // We need to account for the fact that some weight was already - // absorbed - score = p->last_wgt + p->absorb_wgt; - } else { - score = p->last_wgt; - } - //TODO: should flux be multiplied in above instead of below? - if (i_nuclide >= 0) { - score *= flux * atom_density - * get_nuclide_xs(i_nuclide+1, MG_GET_XS_TOTAL, p_g) - / get_macro_xs(p->material, MG_GET_XS_TOTAL, p_g); - } - } else { - if (i_nuclide >= 0) { - score = get_nuclide_xs(i_nuclide+1, MG_GET_XS_TOTAL, p_g) - * atom_density * flux; - } else { - score = simulation::material_xs.total * flux; - } - } - break; - - - case SCORE_INVERSE_VELOCITY: - if (tally.estimator_ == ESTIMATOR_ANALOG - || tally.estimator_ == ESTIMATOR_COLLISION) { - // All events score to an inverse velocity bin. We actually use a - // collision estimator in place of an analog one since there is no way - // to count 'events' exactly for the inverse velocity - if (settings::survival_biasing) { - // We need to account for the fact that some weight was already - // absorbed - score = p->last_wgt + p->absorb_wgt; - } else { - score = p->last_wgt; - } - if (i_nuclide >= 0) { - score *= flux - * get_nuclide_xs(i_nuclide+1, MG_GET_XS_INVERSE_VELOCITY, p_g) - / get_macro_xs(p->material, MG_GET_XS_TOTAL, p_g); - } else { - score *= flux - * get_macro_xs(p->material, MG_GET_XS_INVERSE_VELOCITY, p_g) - / get_macro_xs(p->material, MG_GET_XS_TOTAL, p_g); - } - } else { - if (i_nuclide >= 0) { - score = flux - * get_nuclide_xs(i_nuclide+1, MG_GET_XS_INVERSE_VELOCITY, p_g); - } else { - score = flux - * get_macro_xs(p->material, MG_GET_XS_INVERSE_VELOCITY, p_g); - } - } - break; - - - case SCORE_SCATTER: - if (tally.estimator_ == ESTIMATOR_ANALOG) { - // Skip any event where the particle didn't scatter - if (p->event != EVENT_SCATTER) continue; - // Since only scattering events make it here, again we can use the - // weight entering the collision as the estimator for the reaction rate - score = p->last_wgt * flux; - if (i_nuclide >= 0) { - score *= atom_density - * get_nuclide_xs(i_nuclide+1, MG_GET_XS_SCATTER_FMU_MULT, - p->last_g, &p->g, &p->mu, nullptr) - / get_macro_xs(p->material, MG_GET_XS_SCATTER_FMU_MULT, - p->last_g, &p->g, &p->mu, nullptr); - } - } else { - if (i_nuclide >= 0) { - score = atom_density * flux * get_nuclide_xs( - i_nuclide+1, MG_GET_XS_SCATTER_MULT, p_g, nullptr, &p->mu, nullptr); - } else { - score = flux * get_macro_xs( - p->material, MG_GET_XS_SCATTER_MULT, p_g, nullptr, &p->mu, nullptr); - } - } - break; - - - case SCORE_NU_SCATTER: - if (tally.estimator_ == ESTIMATOR_ANALOG) { - // Skip any event where the particle didn't scatter - if (p->event != EVENT_SCATTER) continue; - // For scattering production, we need to use the pre-collision weight - // times the multiplicity as the estimate for the number of neutrons - // exiting a reaction with neutrons in the exit channel - score = p->wgt * flux; - // Since we transport based on material data, the angle selected - // was not selected from the f(mu) for the nuclide. Therefore - // adjust the score by the actual probability for that nuclide. - if (i_nuclide >= 0) { - score *= atom_density - * get_nuclide_xs(i_nuclide+1, MG_GET_XS_SCATTER_FMU, - p->last_g, &p->g, &p->mu, nullptr) - / get_macro_xs(p->material, MG_GET_XS_SCATTER_FMU, - p->last_g, &p->g, &p->mu, nullptr); - } - } else { - if (i_nuclide >= 0) { - score = atom_density * flux * get_nuclide_xs( - i_nuclide+1, MG_GET_XS_SCATTER, p_g); - } else { - score = flux * get_macro_xs(p->material, MG_GET_XS_SCATTER, p_g); - } - } - break; - - - case SCORE_ABSORPTION: - if (tally.estimator_ == ESTIMATOR_ANALOG) { - if (settings::survival_biasing) { - // No absorption events actually occur if survival biasing is on -- - // just use weight absorbed in survival biasing - score = p->absorb_wgt * flux; - } else { - // Skip any event where the particle wasn't absorbed - if (p->event == EVENT_SCATTER) continue; - // All fission and absorption events will contribute here, so we - // can just use the particle's weight entering the collision - score = p->last_wgt * flux; - } - if (i_nuclide >= 0) { - score *= atom_density - * get_nuclide_xs(i_nuclide+1, MG_GET_XS_ABSORPTION, p_g) - / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); - } - } else { - if (i_nuclide >= 0) { - score = atom_density * flux - * get_nuclide_xs(i_nuclide+1, MG_GET_XS_ABSORPTION, p_g); - } else { - score = simulation::material_xs.absorption * flux; - } - } - break; - - - case SCORE_FISSION: - if (tally.estimator_ == ESTIMATOR_ANALOG) { - if (settings::survival_biasing) { - // No fission events occur if survival biasing is on -- need to - // calculate fraction of absorptions that would have resulted in - // fission - score = p->absorb_wgt * flux; - } else { - // Skip any non-absorption events - if (p->event == EVENT_SCATTER) continue; - // All fission events will contribute, so again we can use particle's - // weight entering the collision as the estimate for the fission - // reaction rate - score = p->last_wgt * flux; - } - if (i_nuclide >= 0) { - score *= atom_density - * get_nuclide_xs(i_nuclide+1, MG_GET_XS_FISSION, p_g) - / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); - } else { - score *= - get_macro_xs(p->material, MG_GET_XS_FISSION, p_g) - / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); - } - } else { - if (i_nuclide >= 0) { - score = get_nuclide_xs(i_nuclide+1, MG_GET_XS_FISSION, p_g) - * atom_density * flux; - } else { - score = get_macro_xs(p->material, MG_GET_XS_FISSION, p_g) * flux; - } - } - break; - - - case SCORE_NU_FISSION: - if (tally.estimator_ == ESTIMATOR_ANALOG) { - if (settings::survival_biasing || p->fission) { - if (tally.energyout_filter_ > 0) { - // Fission has multiple outgoing neutrons so this helper function - // is used to handle scoring the multiple filter bins. - score_fission_eout(p, i_tally, score_index, score_bin); - continue; - } - } - if (settings::survival_biasing) { - // No fission events occur if survival biasing is on -- need to - // calculate fraction of absorptions that would have resulted in - // nu-fission - score = p->absorb_wgt * flux; - if (i_nuclide >= 0) { - score *= atom_density - * get_nuclide_xs(i_nuclide+1, MG_GET_XS_NU_FISSION, p_g) - / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); - } else { - score *= - get_macro_xs(p->material, MG_GET_XS_NU_FISSION, p_g) - / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); - } - } else { - // Skip any non-fission events - if (!p->fission) continue; - // If there is no outgoing energy filter, than we only need to score - // to one bin. For the score to be 'analog', we need to score the - // number of particles that were banked in the fission bank. Since - // this was weighted by 1/keff, we multiply by keff to get the proper - // score. - score = simulation::keff * p->wgt_bank * flux; - if (i_nuclide >= 0) { - score *= atom_density - * get_nuclide_xs(i_nuclide+1, MG_GET_XS_FISSION, p_g) - / get_macro_xs(p->material, MG_GET_XS_FISSION, p_g); - } - } - } else { - if (i_nuclide >= 0) { - score = get_nuclide_xs(i_nuclide+1, MG_GET_XS_NU_FISSION, p_g) - * atom_density * flux; - } else { - score = get_macro_xs(p->material, MG_GET_XS_NU_FISSION, p_g) * flux; - } - } - break; - - - case SCORE_PROMPT_NU_FISSION: - if (tally.estimator_ == ESTIMATOR_ANALOG) { - if (settings::survival_biasing || p->fission) { - if (tally.energyout_filter_ > 0) { - // Fission has multiple outgoing neutrons so this helper function - // is used to handle scoring the multiple filter bins. - score_fission_eout(p, i_tally, score_index, score_bin); - continue; - } - } - if (settings::survival_biasing) { - // No fission events occur if survival biasing is on -- need to - // calculate fraction of absorptions that would have resulted in - // prompt-nu-fission - score = p->absorb_wgt * flux; - if (i_nuclide >= 0) { - score *= atom_density - * get_nuclide_xs(i_nuclide+1, MG_GET_XS_PROMPT_NU_FISSION, p_g) - / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); - } else { - score *= - get_macro_xs(p->material, MG_GET_XS_PROMPT_NU_FISSION, p_g) - / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); - } - } else { - // Skip any non-fission events - if (!p->fission) continue; - // If there is no outgoing energy filter, than we only need to score - // to one bin. For the score to be 'analog', we need to score the - // number of particles that were banked in the fission bank. Since - // this was weighted by 1/keff, we multiply by keff to get the proper - // score. - auto n_delayed = std::accumulate(p->n_delayed_bank, - p->n_delayed_bank+MAX_DELAYED_GROUPS, 0); - auto prompt_frac = 1. - n_delayed / static_cast(p->n_bank); - score = simulation::keff * p->wgt_bank * prompt_frac * flux; - if (i_nuclide >= 0) { - score *= atom_density - * get_nuclide_xs(i_nuclide+1, MG_GET_XS_FISSION, p_g) - / get_macro_xs(p->material, MG_GET_XS_FISSION, p_g); - } - } - } else { - if (i_nuclide >= 0) { - score = get_nuclide_xs(i_nuclide+1, MG_GET_XS_PROMPT_NU_FISSION, p_g) - * atom_density * flux; - } else { - score = get_macro_xs(p->material, MG_GET_XS_PROMPT_NU_FISSION, p_g) - * flux; - } - } - break; - - - case SCORE_DELAYED_NU_FISSION: - if (tally.estimator_ == ESTIMATOR_ANALOG) { - if (settings::survival_biasing || p->fission) { - if (tally.energyout_filter_ > 0) { - // Fission has multiple outgoing neutrons so this helper function - // is used to handle scoring the multiple filter bins. - score_fission_eout(p, i_tally, score_index, score_bin); - continue; - } - } - if (settings::survival_biasing) { - // No fission events occur if survival biasing is on -- need to - // calculate fraction of absorptions that would have resulted in - // delayed-nu-fission - if (get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g) > 0) { - if (tally.delayedgroup_filter_ > 0) { - auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; - const DelayedGroupFilter& filt - {*dynamic_cast( - model::tally_filters[i_dg_filt].get())}; - // Tally each delayed group bin individually - for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { - auto d = filt.groups_[d_bin]; - score = p->absorb_wgt * flux; - if (i_nuclide >= 0) { - score *= - get_nuclide_xs(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, - p_g, nullptr, nullptr, &d) - / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); - } else { - score *= - get_macro_xs(p->material, MG_GET_XS_DELAYED_NU_FISSION, - p_g, nullptr, nullptr, &d) - / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); - } - score_fission_delayed_dg(i_tally, d_bin+1, score, - score_index); - } - continue; - } else { - // If the delayed group filter is not present, compute the score - // by multiplying the absorbed weight by the fraction of the - // delayed-nu-fission xs to the absorption xs - score = p->absorb_wgt * flux; - if (i_nuclide >= 0) { - score *= - get_nuclide_xs(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, p_g) - / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); - } else { - score *= - get_macro_xs(p->material, MG_GET_XS_DELAYED_NU_FISSION, p_g) - / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); - } - } - } - } else { - // Skip any non-fission events - if (!p->fission) continue; - // If there is no outgoing energy filter, than we only need to score - // to one bin. For the score to be 'analog', we need to score the - // number of particles that were banked in the fission bank. Since - // this was weighted by 1/keff, we multiply by keff to get the proper - // score. Loop over the neutrons produced from fission and check which - // ones are delayed. If a delayed neutron is encountered, add its - // contribution to the fission bank to the score. - if (tally.delayedgroup_filter_ > 0) { - auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; - const DelayedGroupFilter& filt - {*dynamic_cast( - model::tally_filters[i_dg_filt].get())}; - // Tally each delayed group bin individually - for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { - auto d = filt.groups_[d_bin]; - score = simulation::keff * p->wgt_bank / p->n_bank - * p->n_delayed_bank[d-1] * flux; - if (i_nuclide >= 0) { - score *= atom_density - * get_nuclide_xs(i_nuclide+1, MG_GET_XS_FISSION, p_g) - / get_macro_xs(p->material, MG_GET_XS_FISSION, p_g); - } - score_fission_delayed_dg(i_tally, d_bin+1, score, score_index); - } - continue; - } else { - // Add the contribution from all delayed groups - auto n_delayed = std::accumulate(p->n_delayed_bank, - p->n_delayed_bank+MAX_DELAYED_GROUPS, 0); - score = simulation::keff * p->wgt_bank / p->n_bank * n_delayed - * flux; - if (i_nuclide >= 0) { - score *= atom_density - * get_nuclide_xs(i_nuclide+1, MG_GET_XS_FISSION, p_g) - / get_macro_xs(p->material, MG_GET_XS_FISSION, p_g); - } - } - } - } else { - if (tally.delayedgroup_filter_ > 0) { - auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; - const DelayedGroupFilter& filt - {*dynamic_cast( - model::tally_filters[i_dg_filt].get())}; - // Tally each delayed group bin individually - for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { - auto d = filt.groups_[d_bin]; - if (i_nuclide >= 0) { - score = flux * atom_density - * get_nuclide_xs(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, - p_g, nullptr, nullptr, &d); - } else { - score = flux - * get_macro_xs(p->material, MG_GET_XS_DELAYED_NU_FISSION, - p_g, nullptr, nullptr, &d); - } - score_fission_delayed_dg(i_tally, d_bin+1, score, score_index); - } - continue; - } else { - if (i_nuclide >= 0) { - score = flux * atom_density - * get_nuclide_xs(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, p_g); - } else { - score = flux - * get_macro_xs(p->material, MG_GET_XS_DELAYED_NU_FISSION, p_g); - } - } - } - break; - - - case SCORE_DECAY_RATE: - if (tally.estimator_ == ESTIMATOR_ANALOG) { - if (settings::survival_biasing) { - // No fission events occur if survival biasing is on -- need to - // calculate fraction of absorptions that would have resulted in - // delayed-nu-fission - if (get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g) > 0) { - if (tally.delayedgroup_filter_ > 0) { - auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; - const DelayedGroupFilter& filt - {*dynamic_cast( - model::tally_filters[i_dg_filt].get())}; - // Tally each delayed group bin individually - for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { - auto d = filt.groups_[d_bin]; - score = p->absorb_wgt * flux; - if (i_nuclide >= 0) { - score *= - get_nuclide_xs(i_nuclide+1, MG_GET_XS_DECAY_RATE, - p_g, nullptr, nullptr, &d) - * get_nuclide_xs(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, - p_g, nullptr, nullptr, &d) - / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); - } else { - score *= - get_macro_xs(p->material, MG_GET_XS_DECAY_RATE, - p_g, nullptr, nullptr, &d) - * get_macro_xs(p->material, MG_GET_XS_DELAYED_NU_FISSION, - p_g, nullptr, nullptr, &d) - / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); - } - score_fission_delayed_dg(i_tally, d_bin+1, score, - score_index); - } - continue; - } else { - // If the delayed group filter is not present, compute the score - // by multiplying the absorbed weight by the fraction of the - // delayed-nu-fission xs to the absorption xs for all delayed - // groups - score = 0.; - for (auto d = 0; d < data::num_delayed_groups; ++d) { - if (i_nuclide >= 0) { - score += p->absorb_wgt * flux - * get_nuclide_xs(i_nuclide+1, MG_GET_XS_DECAY_RATE, - p_g, nullptr, nullptr, &d) - * get_nuclide_xs(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, - p_g, nullptr, nullptr, &d) - / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); - } else { - score += p->absorb_wgt * flux - * get_macro_xs(p->material, MG_GET_XS_DECAY_RATE, - p_g, nullptr, nullptr, &d) - * get_macro_xs(p->material, MG_GET_XS_DELAYED_NU_FISSION, - p_g, nullptr, nullptr, &d) - / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); - } - } - } - } - } else { - // Skip any non-fission events - if (!p->fission) continue; - // If there is no outgoing energy filter, than we only need to score - // to one bin. For the score to be 'analog', we need to score the - // number of particles that were banked in the fission bank. Since - // this was weighted by 1/keff, we multiply by keff to get the proper - // score. Loop over the neutrons produced from fission and check which - // ones are delayed. If a delayed neutron is encountered, add its - // contribution to the fission bank to the score. - score = 0.; - for (auto i = 0; i < p->n_bank; ++i) { - auto i_bank = simulation::n_bank - p->n_bank + i; - const auto& bank = simulation::fission_bank[i_bank]; - auto g = bank.delayed_group; - if (g != 0) { - if (i_nuclide >= 0) { - score += simulation::keff * atom_density * bank.wgt * flux - * get_nuclide_xs(i_nuclide+1, MG_GET_XS_DECAY_RATE, p_g, - nullptr, nullptr, &g) - * get_nuclide_xs(i_nuclide+1, MG_GET_XS_FISSION, p_g) - / get_macro_xs(p->material, MG_GET_XS_FISSION, p_g); - } else { - score += simulation::keff * bank.wgt * flux - * get_macro_xs(p->material, MG_GET_XS_DECAY_RATE, p_g, - nullptr, nullptr, &g); - } - if (tally.delayedgroup_filter_ > 0) { - auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; - const DelayedGroupFilter& filt - {*dynamic_cast( - model::tally_filters[i_dg_filt].get())}; - // Find the corresponding filter bin and then score - for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { - auto d = filt.groups_[d_bin]; - if (d == g) - score_fission_delayed_dg(i_tally, d_bin+1, score, - score_index); - } - score = 0.; - } - } - } - if (tally.delayedgroup_filter_ > 0) continue; - } - } else { - if (tally.delayedgroup_filter_ > 0) { - auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; - const DelayedGroupFilter& filt - {*dynamic_cast( - model::tally_filters[i_dg_filt].get())}; - // Tally each delayed group bin individually - for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { - auto d = filt.groups_[d_bin]; - if (i_nuclide >= 0) { - score += atom_density * flux - * get_nuclide_xs(i_nuclide+1, MG_GET_XS_DECAY_RATE, - p_g, nullptr, nullptr, &d) - * get_nuclide_xs(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, - p_g, nullptr, nullptr, &d); - } else { - score += flux - * get_macro_xs(p->material, MG_GET_XS_DECAY_RATE, - p_g, nullptr, nullptr, &d) - * get_macro_xs(p->material, MG_GET_XS_DELAYED_NU_FISSION, - p_g, nullptr, nullptr, &d); - } - score_fission_delayed_dg(i_tally, d_bin+1, score, score_index); - } - continue; - } else { - score = 0.; - for (auto d = 0; d < data::num_delayed_groups; ++d) { - if (i_nuclide >= 0) { - score += atom_density * flux - * get_nuclide_xs(i_nuclide+1, MG_GET_XS_DECAY_RATE, - p_g, nullptr, nullptr, &d) - * get_nuclide_xs(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, - p_g, nullptr, nullptr, &d); - } else { - score += flux - * get_macro_xs(p->material, MG_GET_XS_DECAY_RATE, - p_g, nullptr, nullptr, &d) - * get_macro_xs(p->material, MG_GET_XS_DELAYED_NU_FISSION, - p_g, nullptr, nullptr, &d); - } - } - } - } - break; - - - case SCORE_KAPPA_FISSION: - if (tally.estimator_ == ESTIMATOR_ANALOG) { - if (settings::survival_biasing) { - // No fission events occur if survival biasing is on -- need to - // calculate fraction of absorptions that would have resulted in - // fission scaled by the Q-value - score = p->absorb_wgt * flux; - } else { - // Skip any non-absorption events - if (p->event == EVENT_SCATTER) continue; - // All fission events will contribute, so again we can use particle's - // weight entering the collision as the estimate for the fission - // reaction rate - score = p->last_wgt * flux; - } - if (i_nuclide >= 0) { - score *= atom_density - * get_nuclide_xs(i_nuclide+1, MG_GET_XS_KAPPA_FISSION, p_g) - / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); - } else { - score *= - get_macro_xs(p->material, MG_GET_XS_KAPPA_FISSION, p_g) - / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); - } - } else { - if (i_nuclide >= 0) { - score = get_nuclide_xs(i_nuclide+1, MG_GET_XS_KAPPA_FISSION, p_g) - * atom_density * flux; - } else { - score = get_macro_xs(p->material, MG_GET_XS_KAPPA_FISSION, p_g) - * flux; - } - } - break; - - - case SCORE_EVENTS: - // Simply count the number of scoring events - score = 1.; - break; - - - default: - continue; - } - - // Update tally results - #pragma omp atomic - results(filter_index-1, score_index, RESULT_VALUE) += score; - } -} - -//! Tally rates for when the user requests a tally on all nuclides. - -void -score_all_nuclides(Particle* p, int i_tally, double flux, int filter_index) -{ - //TODO: off-by-one - const Tally& tally {*model::tallies[i_tally-1]}; - const Material& material {*model::materials[p->material-1]}; - - // Score all individual nuclide reaction rates. - for (auto i = 0; i < material.nuclide_.size(); ++i) { - auto i_nuclide = material.nuclide_[i]; - auto atom_density = material.atom_density_(i); - - //TODO: consider replacing this "if" with pointers or templates - if (settings::run_CE) { - score_general_ce(p, i_tally, i_nuclide*tally.scores_.size(), filter_index, - i_nuclide, atom_density, flux); - } else { - score_general_mg(p, i_tally, i_nuclide*tally.scores_.size(), filter_index, - i_nuclide, atom_density, flux); - } - } - - // Score total material reaction rates. - int i_nuclide = -1; - double atom_density = 0.; - auto n_nuclides = data::nuclides.size(); - //TODO: consider replacing this "if" with pointers or templates - if (settings::run_CE) { - score_general_ce(p, i_tally, n_nuclides*tally.scores_.size(), filter_index, - i_nuclide, atom_density, flux); - } else { - score_general_mg(p, i_tally, n_nuclides*tally.scores_.size(), filter_index, - i_nuclide, atom_density, flux); - } -} - -//! Score tallies based on a simple count of events (for continuous energy). -// -//! Analog tallies ar etriggered at every collision, not every event. - -extern "C" void -score_analog_tally_ce(Particle* p) -{ - for (auto i_tally : model::active_analog_tallies) { - //TODO: off-by-one - const Tally& tally {*model::tallies[i_tally-1]}; - - // Initialize an iterator over valid filter bin combinations. If there are - // no valid combinations, use a continue statement to ensure we skip the - // assume_separate break below. - auto filter_iter = FilterBinIter(tally, p); - auto end = FilterBinIter(tally, true); - if (filter_iter == end) continue; - - // Loop over filter bins. - for (; filter_iter != end; ++filter_iter) { - auto filter_index = filter_iter.index_; - auto filter_weight = filter_iter.weight_; - - // Loop over nuclide bins. - if (!tally.all_nuclides_) { - for (auto i = 0; i < tally.nuclides_.size(); ++i) { - auto i_nuclide = tally.nuclides_[i]; - - // Tally this event in the present nuclide bin if that bin represents - // the event nuclide or the total material. Note that the i_nuclide - // and flux arguments for score_general are not used for analog - // tallies. - //TODO: off-by-one - if (i_nuclide == p->event_nuclide-1 || i_nuclide == -1) - score_general_ce(p, i_tally, i*tally.scores_.size(), filter_index, - -1, -1., filter_weight); - } - - } else { - // In the case that the user has requested to tally all nuclides, we - // can take advantage of the fact that we know exactly how nuclide - // bins correspond to nuclide indices. First, tally the nuclide. - auto i = p->event_nuclide; - score_general_ce(p, i_tally, i*tally.scores_.size(), filter_index, - -1, -1., filter_weight); - - // Now tally the total material. - i = tally.nuclides_.size(); - score_general_ce(p, i_tally, i*tally.scores_.size(), filter_index, - -1, -1., filter_weight); - } - } - - // If the user has specified that we can assume all tallies are spatially - // separate, this implies that once a tally has been scored to, we needn't - // check the others. This cuts down on overhead when there are many - // tallies specified - if (settings::assume_separate) break; - } - - // Reset all the filter matches for the next tally event. - for (auto& match : simulation::filter_matches) - match.bins_present_ = false; -} - -//! Score tallies based on a simple count of events (for multigroup). -// -//! Analog tallies ar etriggered at every collision, not every event. - -extern "C" void -score_analog_tally_mg(Particle* p) -{ - for (auto i_tally : model::active_analog_tallies) { - //TODO: off-by-one - const Tally& tally {*model::tallies[i_tally-1]}; - - // Initialize an iterator over valid filter bin combinations. If there are - // no valid combinations, use a continue statement to ensure we skip the - // assume_separate break below. - auto filter_iter = FilterBinIter(tally, p); - auto end = FilterBinIter(tally, true); - if (filter_iter == end) continue; - - // Loop over filter bins. - for (; filter_iter != end; ++filter_iter) { - auto filter_index = filter_iter.index_; - auto filter_weight = filter_iter.weight_; - - // Loop over nuclide bins. - for (auto i = 0; i < tally.nuclides_.size(); ++i) { - auto i_nuclide = tally.nuclides_[i]; - - double atom_density = 0.; - if (i_nuclide >= 0) { - //TODO: off-by-one - auto j = model::materials[p->material-1] - ->mat_nuclide_index_[i_nuclide]; - if (j == C_NONE) continue; - //atom_density = material_atom_density(p->material, j); - //TODO: off-by-one - atom_density = model::materials[p->material-1]->atom_density_(j); - } - - score_general_mg(p, i_tally, i*tally.scores_.size(), filter_index, - i_nuclide, atom_density, filter_weight); - } - } - - // If the user has specified that we can assume all tallies are spatially - // separate, this implies that once a tally has been scored to, we needn't - // check the others. This cuts down on overhead when there are many - // tallies specified - if (settings::assume_separate) break; - } - - // Reset all the filter matches for the next tally event. - for (auto& match : simulation::filter_matches) - match.bins_present_ = false; -} - -//! Score tallies using a tracklength estimate of the flux. -// -//! This is triggered at every event (surface crossing, lattice crossing, or -//! collision) and thus cannot be done for tallies that require post-collision -//! information. - -extern "C" void -score_tracklength_tally(Particle* p, double distance) -{ - // Determine the tracklength estimate of the flux - double flux = p->wgt * distance; - - for (auto i_tally : model::active_tracklength_tallies) { - //TODO: off-by-one - const Tally& tally {*model::tallies[i_tally-1]}; - - // Initialize an iterator over valid filter bin combinations. If there are - // no valid combinations, use a continue statement to ensure we skip the - // assume_separate break below. - auto filter_iter = FilterBinIter(tally, p); - auto end = FilterBinIter(tally, true); - if (filter_iter == end) continue; - - // Loop over filter bins. - for (; filter_iter != end; ++filter_iter) { - auto filter_index = filter_iter.index_; - auto filter_weight = filter_iter.weight_; - - // Loop over nuclide bins. - if (tally.all_nuclides_) { - if (p->material != MATERIAL_VOID) - score_all_nuclides(p, i_tally, flux*filter_weight, filter_index); - - } else { - for (auto i = 0; i < tally.nuclides_.size(); ++i) { - auto i_nuclide = tally.nuclides_[i]; - - double atom_density = 0.; - if (i_nuclide >= 0) { - if (p->material != MATERIAL_VOID) { - //TODO: off-by-one - auto j = model::materials[p->material-1] - ->mat_nuclide_index_[i_nuclide]; - if (j == C_NONE) continue; - //atom_density = material_atom_density(p->material, j); - //TODO: off-by-one - atom_density = model::materials[p->material-1]->atom_density_(j); - } - } - - //TODO: consider replacing this "if" with pointers or templates - if (settings::run_CE) { - score_general_ce(p, i_tally, i*tally.scores_.size(), filter_index, - i_nuclide, atom_density, flux*filter_weight); - } else { - score_general_mg(p, i_tally, i*tally.scores_.size(), filter_index, - i_nuclide, atom_density, flux*filter_weight); - } - } - } - - } - - // If the user has specified that we can assume all tallies are spatially - // separate, this implies that once a tally has been scored to, we needn't - // check the others. This cuts down on overhead when there are many - // tallies specified - if (settings::assume_separate) break; - } - - // Reset all the filter matches for the next tally event. - for (auto& match : simulation::filter_matches) - match.bins_present_ = false; -} - -//! Score tallies using a 1 / Sigma_t estimate of the flux. -// -//! This is triggered after every collision. It is invalid for tallies that -//! require post-collison information because it can score reactions that didn't -//! actually occur, and we don't a priori know what the outcome will be for -//! reactions that we didn't sample. It is assumed the material is not void -//! since collisions do not occur in voids. - -extern "C" void -score_collision_tally(Particle* p) -{ - // Determine the collision estimate of the flux - double flux; - if (!settings::survival_biasing) { - flux = p->last_wgt / simulation::material_xs.total; - } else { - flux = (p->last_wgt + p->absorb_wgt) / simulation::material_xs.total; - } - - for (auto i_tally : model::active_collision_tallies) { - //TODO: off-by-one - const Tally& tally {*model::tallies[i_tally-1]}; - - // Initialize an iterator over valid filter bin combinations. If there are - // no valid combinations, use a continue statement to ensure we skip the - // assume_separate break below. - auto filter_iter = FilterBinIter(tally, p); - auto end = FilterBinIter(tally, true); - if (filter_iter == end) continue; - - // Loop over filter bins. - for (; filter_iter != end; ++filter_iter) { - auto filter_index = filter_iter.index_; - auto filter_weight = filter_iter.weight_; - - // Loop over nuclide bins. - if (tally.all_nuclides_) { - score_all_nuclides(p, i_tally, flux*filter_weight, filter_index); - - } else { - for (auto i = 0; i < tally.nuclides_.size(); ++i) { - auto i_nuclide = tally.nuclides_[i]; - - double atom_density = 0.; - if (i_nuclide >= 0) { - //TODO: off-by-one - auto j = model::materials[p->material-1] - ->mat_nuclide_index_[i_nuclide]; - if (j == C_NONE) continue; - //atom_density = material_atom_density(p->material, j); - //TODO: off-by-one - atom_density = model::materials[p->material-1]->atom_density_(j); - } - - //TODO: consider replacing this "if" with pointers or templates - if (settings::run_CE) { - score_general_ce(p, i_tally, i*tally.scores_.size(), filter_index, - i_nuclide, atom_density, flux*filter_weight); - } else { - score_general_mg(p, i_tally, i*tally.scores_.size(), filter_index, - i_nuclide, atom_density, flux*filter_weight); - } - } - } - } - - // If the user has specified that we can assume all tallies are spatially - // separate, this implies that once a tally has been scored to, we needn't - // check the others. This cuts down on overhead when there are many - // tallies specified - if (settings::assume_separate) break; - } - - // Reset all the filter matches for the next tally event. - for (auto& match : simulation::filter_matches) - match.bins_present_ = false; -} - -//! Score surface or mesh-surface tallies for particle currents. - -static void -score_surface_tally_inner(Particle* p, const std::vector& tallies) -{ - // No collision, so no weight change when survival biasing - double flux = p->wgt; - - for (auto i_tally : tallies) { - //TODO: off-by-one - const Tally& tally {*model::tallies[i_tally-1]}; - auto results = tally_results(i_tally); - - // Initialize an iterator over valid filter bin combinations. If there are - // no valid combinations, use a continue statement to ensure we skip the - // assume_separate break below. - auto filter_iter = FilterBinIter(tally, p); - auto end = FilterBinIter(tally, true); - if (filter_iter == end) continue; - - // Loop over filter bins. - for (; filter_iter != end; ++filter_iter) { - auto filter_index = filter_iter.index_; - auto filter_weight = filter_iter.weight_; - - // Loop over scores. - // There is only one score type for current tallies so there is no need - // for a further scoring function. - double score = flux * filter_weight; - for (auto score_index = 0; score_index < tally.scores_.size(); - ++score_index) { - //TODO: off-by-one - #pragma omp atomic - results(filter_index-1, score_index, RESULT_VALUE) += score; - } - } - - // If the user has specified that we can assume all tallies are spatially - // separate, this implies that once a tally has been scored to, we needn't - // check the others. This cuts down on overhead when there are many - // tallies specified - if (settings::assume_separate) break; - } - - // Reset all the filter matches for the next tally event. - for (auto& match : simulation::filter_matches) - match.bins_present_ = false; -} - -//! Score mesh-surface tallies for particle currents. - -extern "C" void -score_meshsurface_tally(Particle* p) -{ - score_surface_tally_inner(p, model::active_meshsurf_tallies); -} - -//! Score surface tallies for particle currents. - -extern "C" void -score_surface_tally(Particle* p) -{ - score_surface_tally_inner(p, model::active_surface_tallies); -} - - #ifdef OPENMC_MPI void reduce_tally_results() { diff --git a/src/tallies/tally_scoring.cpp b/src/tallies/tally_scoring.cpp new file mode 100644 index 0000000000..991b4cc279 --- /dev/null +++ b/src/tallies/tally_scoring.cpp @@ -0,0 +1,2317 @@ +#include "openmc/tallies/tally_scoring.h" + +#include "openmc/capi.h" +#include "openmc/constants.h" +#include "openmc/error.h" +#include "openmc/material.h" +#include "openmc/mgxs_interface.h" +#include "openmc/nuclide.h" +#include "openmc/reaction_product.h" +#include "openmc/search.h" +#include "openmc/settings.h" +#include "openmc/simulation.h" +#include "openmc/tallies/derivative.h" +#include "openmc/tallies/filter.h" +#include "openmc/tallies/filter_delayedgroup.h" +#include "openmc/tallies/filter_energy.h" + +#include + +namespace openmc { + +//============================================================================== +// FilterBinIter implementation +//============================================================================== + +FilterBinIter::FilterBinIter(const Tally& tally, Particle* p) + : tally_{tally} +{ + // Find all valid bins in each relevant filter if they have not already been + // found for this event. + for (auto i_filt : tally_.filters()) { + auto& match {simulation::filter_matches[i_filt]}; + if (!match.bins_present_) { + match.bins_.clear(); + match.weights_.clear(); + model::tally_filters[i_filt]->get_all_bins(p, tally_.estimator_, match); + match.bins_present_ = true; + } + + // If there are no valid bins for this filter, then there are no valid + // filter bin combinations so all iterators are end iterators. + if (match.bins_.size() == 0) { + index_ = -1; + return; + } + + // Set the index of the bin used in the first filter combination + match.i_bin_ = 1; + } + + // Compute the initial index and weight. + compute_index_weight(); +} + +FilterBinIter::FilterBinIter(const Tally& tally, bool end) + : tally_{tally} +{ + // Handle the special case for an iterator that points to the end. + if (end) { + index_ = -1; + return; + } + + for (auto i_filt : tally_.filters()) { + auto& match {simulation::filter_matches[i_filt]}; + if (!match.bins_present_) { + match.bins_.clear(); + match.weights_.clear(); + for (auto i = 0; i < model::tally_filters[i_filt]->n_bins_; ++i) { + // TODO: off-by-one + match.bins_.push_back(i+1); + match.weights_.push_back(1.0); + } + match.bins_present_ = true; + } + + if (match.bins_.size() == 0) { + index_ = -1; + return; + } + + match.i_bin_ = 1; + } + + // Compute the initial index and weight. + compute_index_weight(); +} + +FilterBinIter& +FilterBinIter::operator++() +{ + // Find the next valid combination of filter bins. To do this, we search + // backwards through the filters until we find the first filter whose bins + // can be incremented. + bool done_looping = true; + for (int i = tally_.filters().size()-1; i >= 0; --i) { + auto i_filt = tally_.filters(i); + auto& match {simulation::filter_matches[i_filt]}; + if (match.i_bin_< match.bins_.size()) { + // The bin for this filter can be incremented. Increment it and do not + // touch any of the remaining filters. + ++match.i_bin_; + done_looping = false; + break; + } else { + // This bin cannot be incremented so reset it and continue to the next + // filter. + match.i_bin_ = 1; + } + } + + if (done_looping) { + // We have visited every valid combination. All done! + index_ = -1; + } else { + // The loop found a new valid combination. Compute the corresponding + // index and weight. + compute_index_weight(); + } + + return *this; +} + +void +FilterBinIter::compute_index_weight() +{ + index_ = 1; + weight_ = 1.; + for (auto i = 0; i < tally_.filters().size(); ++i) { + auto i_filt = tally_.filters(i); + auto& match {simulation::filter_matches[i_filt]}; + auto i_bin = match.i_bin_; + //TODO: off-by-one + index_ += (match.bins_[i_bin-1] - 1) * tally_.strides(i); + weight_ *= match.weights_[i_bin-1]; + } +} + +//============================================================================== +// Non-member functions +//============================================================================== + +//! Helper function used to increment tallies with a delayed group filter. + +void +score_fission_delayed_dg(int i_tally, int d_bin, double score, int score_index) +{ + // Save the original delayed group bin + //TODO: off-by-one + const Tally& tally {*model::tallies[i_tally-1]}; + auto i_filt = tally.filters(tally.delayedgroup_filter_-1); + auto& dg_match {simulation::filter_matches[i_filt]}; + auto i_bin = dg_match.i_bin_; + auto original_bin = dg_match.bins_[i_bin-1]; + dg_match.bins_[i_bin-1] = d_bin; + + // Determine the filter scoring index + auto filter_index = 1; + for (auto i = 0; i < tally.filters().size(); ++i) { + auto i_filt = tally.filters(i); + auto& match {simulation::filter_matches[i_filt]}; + auto i_bin = match.i_bin_; + //TODO: off-by-one + filter_index += (match.bins_[i_bin-1] - 1) * tally.strides(i); + } + + // Update the tally result + auto results = tally_results(i_tally); + //TODO: off-by-one + #pragma omp atomic + results(filter_index-1, score_index, RESULT_VALUE) += score; + + // Reset the original delayed group bin + dg_match.bins_[i_bin-1] = original_bin; +} + +//! Helper function for nu-fission tallies with energyout filters. +// +//! In this case, we may need to score to multiple bins if there were multiple +//! neutrons produced with different energies. + +void +score_fission_eout(Particle* p, int i_tally, int i_score, int score_bin) +{ + //TODO: off-by-one + const Tally& tally {*model::tallies[i_tally-1]}; + auto results = tally_results(i_tally); + auto i_eout_filt = tally.filters()[tally.energyout_filter_-1]; + auto i_bin = simulation::filter_matches[i_eout_filt].i_bin_; + auto bin_energyout = simulation::filter_matches[i_eout_filt].bins_[i_bin-1]; + + const EnergyoutFilter& eo_filt + {*dynamic_cast(model::tally_filters[i_eout_filt].get())}; + + // Note that the score below is weighted by keff. Since the creation of + // fission sites is weighted such that it is expected to create n_particles + // sites, we need to multiply the score by keff to get the true nu-fission + // rate. Otherwise, the sum of all nu-fission rates would be ~1.0. + + // loop over number of particles banked + for (auto i = 0; i < p->n_bank; ++i) { + auto i_bank = simulation::n_bank - p->n_bank + i; + const auto& bank = simulation::fission_bank[i_bank]; + + // get the delayed group + auto g = bank.delayed_group; + + // determine score based on bank site weight and keff + double score = simulation::keff * bank.wgt; + + // Add derivative information for differential tallies. Note that the + // i_nuclide and atom_density arguments do not matter since this is an + // analog estimator. + if (tally.deriv_ != C_NONE) + apply_derivative_to_score(p, i_tally, 0, 0., SCORE_NU_FISSION, &score); + + if (!settings::run_CE && eo_filt.matches_transport_groups_) { + + // determine outgoing energy group from fission bank + auto g_out = static_cast(bank.E); + + // modify the value so that g_out = 1 corresponds to the highest energy + // bin + g_out = eo_filt.n_bins_ - g_out + 1; + + // change outgoing energy bin + simulation::filter_matches[i_eout_filt].bins_[i_bin-1] = g_out; + + } else { + + double E_out; + if (settings::run_CE) { + E_out = bank.E; + } else { + E_out = data::energy_bin_avg[static_cast(bank.E)]; + } + + // Set EnergyoutFilter bin index + if (E_out < eo_filt.bins_.front() || E_out > eo_filt.bins_.back()) { + continue; + } else { + //TODO: off-by-one + auto i_match = lower_bound_index(eo_filt.bins_.begin(), + eo_filt.bins_.end(), E_out) + 1; + simulation::filter_matches[i_eout_filt].bins_[i_bin-1] = i_match; + } + + } + + // Case for tallying prompt neutrons + if (score_bin == SCORE_NU_FISSION + || (score_bin == SCORE_PROMPT_NU_FISSION && g == 0)) { + + // Find the filter scoring index for this filter combination + //TODO: should this include a weight? + int filter_index = 1; + for (auto j = 0; j < tally.filters().size(); ++j) { + auto i_filt = tally.filters(j); + auto& match {simulation::filter_matches[i_filt]}; + auto i_bin = match.i_bin_; + filter_index += (match.bins_[i_bin-1] - 1) * tally.strides(j); + } + + // Update tally results + #pragma omp atomic + results(filter_index-1, i_score, RESULT_VALUE) += score; + + } else if (score_bin == SCORE_DELAYED_NU_FISSION && g != 0) { + + // Get the index of the delayed group filter + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + + // If the delayed group filter is present, tally to corresponding delayed + // group bin if it exists + if (i_dg_filt >= 0) { + const DelayedGroupFilter& dg_filt {*dynamic_cast( + model::tally_filters[i_dg_filt].get())}; + + // Loop over delayed group bins until the corresponding bin is found + for (auto d_bin = 0; d_bin < dg_filt.n_bins_; ++d_bin) { + if (dg_filt.groups_[d_bin] == g) { + // Find the filter index and weight for this filter combination + int filter_index = 1; + double filter_weight = 1.; + for (auto j = 0; j < tally.filters().size(); ++j) { + auto i_filt = tally.filters(j); + auto& match {simulation::filter_matches[i_filt]}; + auto i_bin = match.i_bin_; + filter_index += (match.bins_[i_bin-1] - 1) * tally.strides(j); + filter_weight *= match.weights_[i_bin-1]; + } + + score_fission_delayed_dg(i_tally, d_bin+1, score*filter_weight, + i_score); + } + } + + // If the delayed group filter is not present, add score to tally + } else { + + // Find the filter index and weight for this filter combination + int filter_index = 1; + double filter_weight = 1.; + for (auto j = 0; j < tally.filters().size(); ++j) { + auto i_filt = tally.filters(j); + auto& match {simulation::filter_matches[i_filt]}; + auto i_bin = match.i_bin_; + filter_index += (match.bins_[i_bin-1] - 1) * tally.strides(j); + filter_weight *= match.weights_[i_bin-1]; + } + + // Update tally results + #pragma omp atomic + results(filter_index-1, i_score, RESULT_VALUE) += score*filter_weight; + } + } + } + + // Reset outgoing energy bin and score index + simulation::filter_matches[i_eout_filt].bins_[i_bin-1] = bin_energyout; +} + +//! Update tally results for continuous-energy tallies with any estimator. +// +//! For analog tallies, the flux estimate depends on the score type so the flux +//! argument is really just used for filter weights. The atom_density argument +//! is not used for analog tallies. + +void +score_general_ce(Particle* p, int i_tally, int start_index, int filter_index, + int i_nuclide, double atom_density, double flux) +{ + //TODO: off-by-one + const Tally& tally {*model::tallies[i_tally-1]}; + auto results = tally_results(i_tally); + + // Get the pre-collision energy of the particle. + auto E = p->last_E; + + for (auto i = 0; i < tally.scores_.size(); ++i) { + auto score_bin = tally.scores_[i]; + auto score_index = start_index + i; + + double score; + + //TODO: off-by-one throughout on p->event_nuclide + //TODO: off-by-one throughout on p->material + + switch (score_bin) { + + + case SCORE_FLUX: + if (tally.estimator_ == ESTIMATOR_ANALOG) { + // All events score to a flux bin. We actually use a collision estimator + // in place of an analog one since there is no way to count 'events' + // exactly for the flux + if (settings::survival_biasing) { + // We need to account for the fact that some weight was already + // absorbed + score = p->last_wgt + p->absorb_wgt; + } else { + score = p->last_wgt; + } + + if (p->type == static_cast(ParticleType::neutron) || + p->type == static_cast(ParticleType::photon)) { + score *= flux / simulation::material_xs.total; + } else { + score = 0.; + } + } else { + score = flux; + } + break; + + + case SCORE_TOTAL: + if (tally.estimator_ == ESTIMATOR_ANALOG) { + // All events will score to the total reaction rate. We can just use + // use the weight of the particle entering the collision as the score + if (settings::survival_biasing) { + // We need to account for the fact that some weight was already + // absorbed + score = (p->last_wgt + p->absorb_wgt) * flux; + } else { + score = p->last_wgt * flux; + } + + } else { + if (i_nuclide >= 0) { + score = simulation::micro_xs[i_nuclide].total * atom_density * flux; + } else { + score = simulation::material_xs.total * flux; + } + } + break; + + + case SCORE_INVERSE_VELOCITY: + if (tally.estimator_ == ESTIMATOR_ANALOG) { + // All events score to an inverse velocity bin. We actually use a + // collision estimator in place of an analog one since there is no way + // to count 'events' exactly for the inverse velocity + if (settings::survival_biasing) { + // We need to account for the fact that some weight was already + // absorbed + score = p->last_wgt + p->absorb_wgt; + } else { + score = p->last_wgt; + } + score *= flux / simulation::material_xs.total; + } else { + score = flux; + } + // Score inverse velocity in units of s/cm. + score /= std::sqrt(2. * E / MASS_NEUTRON_EV) * C_LIGHT * 100.; + break; + + + case SCORE_SCATTER: + if (tally.estimator_ == ESTIMATOR_ANALOG) { + // Skip any event where the particle didn't scatter + if (p->event != EVENT_SCATTER) continue; + // Since only scattering events make it here, again we can use the + // weight entering the collision as the estimator for the reaction rate + score = p->last_wgt * flux; + } else { + if (i_nuclide >= 0) { + score = (simulation::micro_xs[i_nuclide].total + - simulation::micro_xs[i_nuclide].absorption) * atom_density * flux; + } else { + score = (simulation::material_xs.total + - simulation::material_xs.absorption) * flux; + } + } + break; + + + case SCORE_NU_SCATTER: + // Only analog estimators are available. + // Skip any event where the particle didn't scatter + if (p->event != EVENT_SCATTER) continue; + // For scattering production, we need to use the pre-collision weight + // times the yield as the estimate for the number of neutrons exiting a + // reaction with neutrons in the exit channel + if (p->event_MT == ELASTIC || p->event_MT == N_LEVEL || + (p->event_MT >= N_N1 && p->event_MT <= N_NC)) { + // Don't waste time on very common reactions we know have + // multiplicities of one. + score = p->last_wgt * flux; + } else { + // Get yield and apply to score + auto m = + data::nuclides[p->event_nuclide-1]->reaction_index_[p->event_MT]; + const auto& rxn {*data::nuclides[p->event_nuclide-1]->reactions_[m]}; + score = p->last_wgt * flux * (*rxn.products_[0].yield_)(E); + } + break; + + + case SCORE_ABSORPTION: + if (tally.estimator_ == ESTIMATOR_ANALOG) { + if (settings::survival_biasing) { + // No absorption events actually occur if survival biasing is on -- + // just use weight absorbed in survival biasing + score = p->absorb_wgt * flux; + } else { + // Skip any event where the particle wasn't absorbed + if (p->event == EVENT_SCATTER) continue; + // All fission and absorption events will contribute here, so we + // can just use the particle's weight entering the collision + score = p->last_wgt * flux; + } + } else { + if (i_nuclide >= 0) { + score = simulation::micro_xs[i_nuclide].absorption * atom_density + * flux; + } else { + score = simulation::material_xs.absorption * flux; + } + } + break; + + + case SCORE_FISSION: + if (simulation::material_xs.absorption == 0) continue; + if (tally.estimator_ == ESTIMATOR_ANALOG) { + if (settings::survival_biasing) { + // No fission events occur if survival biasing is on -- need to + // calculate fraction of absorptions that would have resulted in + // fission + if (simulation::micro_xs[p->event_nuclide-1].absorption > 0) { + score = p->absorb_wgt + * simulation::micro_xs[p->event_nuclide-1].fission + / simulation::micro_xs[p->event_nuclide-1].absorption * flux; + } else { + score = 0.; + } + } else { + // Skip any non-absorption events + if (p->event == EVENT_SCATTER) continue; + // All fission events will contribute, so again we can use particle's + // weight entering the collision as the estimate for the fission + // reaction rate + score = p->last_wgt + * simulation::micro_xs[p->event_nuclide-1].fission + / simulation::micro_xs[p->event_nuclide-1].absorption * flux; + } + } else { + if (i_nuclide >= 0) { + score = simulation::micro_xs[i_nuclide].fission * atom_density * flux; + } else { + score = simulation::material_xs.fission * flux; + } + } + break; + + + case SCORE_NU_FISSION: + if (simulation::material_xs.absorption == 0) continue; + if (tally.estimator_ == ESTIMATOR_ANALOG) { + if (settings::survival_biasing || p->fission) { + if (tally.energyout_filter_ > 0) { + // Fission has multiple outgoing neutrons so this helper function + // is used to handle scoring the multiple filter bins. + score_fission_eout(p, i_tally, score_index, score_bin); + continue; + } + } + if (settings::survival_biasing) { + // No fission events occur if survival biasing is on -- need to + // calculate fraction of absorptions that would have resulted in + // nu-fission + if (simulation::micro_xs[p->event_nuclide-1].absorption > 0) { + score = p->absorb_wgt + * simulation::micro_xs[p->event_nuclide-1].nu_fission + / simulation::micro_xs[p->event_nuclide-1].absorption * flux; + } else { + score = 0.; + } + } else { + // Skip any non-fission events + if (!p->fission) continue; + // If there is no outgoing energy filter, than we only need to score + // to one bin. For the score to be 'analog', we need to score the + // number of particles that were banked in the fission bank. Since + // this was weighted by 1/keff, we multiply by keff to get the proper + // score. + score = simulation::keff * p->wgt_bank * flux; + } + } else { + if (i_nuclide >= 0) { + score = simulation::micro_xs[i_nuclide].nu_fission * atom_density + * flux; + } else { + score = simulation::material_xs.nu_fission * flux; + } + } + break; + + + case SCORE_PROMPT_NU_FISSION: + if (simulation::material_xs.absorption == 0) continue; + if (tally.estimator_ == ESTIMATOR_ANALOG) { + if (settings::survival_biasing || p->fission) { + if (tally.energyout_filter_ > 0) { + // Fission has multiple outgoing neutrons so this helper function + // is used to handle scoring the multiple filter bins. + score_fission_eout(p, i_tally, score_index, score_bin); + continue; + } + } + if (settings::survival_biasing) { + // No fission events occur if survival biasing is on -- need to + // calculate fraction of absorptions that would have resulted in + // prompt-nu-fission + if (simulation::micro_xs[p->event_nuclide-1].absorption > 0) { + score = p->absorb_wgt + * simulation::micro_xs[p->event_nuclide-1].fission + * data::nuclides[p->event_nuclide-1] + ->nu(E, ReactionProduct::EmissionMode::prompt) + / simulation::micro_xs[p->event_nuclide-1].absorption * flux; + } else { + score = 0.; + } + } else { + // Skip any non-fission events + if (!p->fission) continue; + // If there is no outgoing energy filter, than we only need to score + // to one bin. For the score to be 'analog', we need to score the + // number of particles that were banked in the fission bank. Since + // this was weighted by 1/keff, we multiply by keff to get the proper + // score. + auto n_delayed = std::accumulate(p->n_delayed_bank, + p->n_delayed_bank+MAX_DELAYED_GROUPS, 0); + auto prompt_frac = 1. - n_delayed / static_cast(p->n_bank); + score = simulation::keff * p->wgt_bank * prompt_frac * flux; + } + } else { + if (i_nuclide >= 0) { + score = simulation::micro_xs[i_nuclide].fission + * data::nuclides[i_nuclide] + ->nu(E, ReactionProduct::EmissionMode::prompt) + * atom_density * flux; + } else { + score = 0.; + // Add up contributions from each nuclide in the material. + if (p->material != MATERIAL_VOID) { + const Material& material {*model::materials[p->material-1]}; + for (auto i = 0; i < material.nuclide_.size(); ++i) { + auto j_nuclide = material.nuclide_[i]; + auto atom_density = material.atom_density_(i); + score += simulation::micro_xs[j_nuclide].fission + * data::nuclides[j_nuclide] + ->nu(E, ReactionProduct::EmissionMode::prompt) + * atom_density * flux; + } + } + } + } + break; + + + case SCORE_DELAYED_NU_FISSION: + if (simulation::material_xs.absorption == 0) continue; + if (tally.estimator_ == ESTIMATOR_ANALOG) { + if (settings::survival_biasing || p->fission) { + if (tally.energyout_filter_ > 0) { + // Fission has multiple outgoing neutrons so this helper function + // is used to handle scoring the multiple filter bins. + score_fission_eout(p, i_tally, score_index, score_bin); + continue; + } + } + if (settings::survival_biasing) { + // No fission events occur if survival biasing is on -- need to + // calculate fraction of absorptions that would have resulted in + // delayed-nu-fission + if (simulation::micro_xs[p->event_nuclide-1].absorption > 0 + && data::nuclides[p->event_nuclide-1]->fissionable_) { + if (tally.delayedgroup_filter_ > 0) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + const DelayedGroupFilter& filt + {*dynamic_cast( + model::tally_filters[i_dg_filt].get())}; + // Tally each delayed group bin individually + for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { + auto d = filt.groups_[d_bin]; + auto yield = data::nuclides[p->event_nuclide-1] + ->nu(E, ReactionProduct::EmissionMode::delayed, d); + score = p->absorb_wgt * yield + * simulation::micro_xs[p->event_nuclide-1].fission + / simulation::micro_xs[p->event_nuclide-1].absorption * flux; + score_fission_delayed_dg(i_tally, d_bin+1, score, + score_index); + } + continue; + } else { + // If the delayed group filter is not present, compute the score + // by multiplying the absorbed weight by the fraction of the + // delayed-nu-fission xs to the absorption xs + score = p->absorb_wgt + * simulation::micro_xs[p->event_nuclide-1].fission + * data::nuclides[p->event_nuclide-1] + ->nu(E, ReactionProduct::EmissionMode::delayed) + / simulation::micro_xs[p->event_nuclide-1].absorption *flux; + } + } + } else { + // Skip any non-fission events + if (!p->fission) continue; + // If there is no outgoing energy filter, than we only need to score + // to one bin. For the score to be 'analog', we need to score the + // number of particles that were banked in the fission bank. Since + // this was weighted by 1/keff, we multiply by keff to get the proper + // score. Loop over the neutrons produced from fission and check which + // ones are delayed. If a delayed neutron is encountered, add its + // contribution to the fission bank to the score. + if (tally.delayedgroup_filter_ > 0) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + const DelayedGroupFilter& filt + {*dynamic_cast( + model::tally_filters[i_dg_filt].get())}; + // Tally each delayed group bin individually + for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { + auto d = filt.groups_[d_bin]; + score = simulation::keff * p->wgt_bank / p->n_bank + * p->n_delayed_bank[d-1] * flux; + score_fission_delayed_dg(i_tally, d_bin+1, score, score_index); + } + continue; + } else { + // Add the contribution from all delayed groups + auto n_delayed = std::accumulate(p->n_delayed_bank, + p->n_delayed_bank+MAX_DELAYED_GROUPS, 0); + score = simulation::keff * p->wgt_bank / p->n_bank * n_delayed + * flux; + } + } + } else { + if (i_nuclide >= 0) { + if (tally.delayedgroup_filter_ > 0) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + const DelayedGroupFilter& filt + {*dynamic_cast( + model::tally_filters[i_dg_filt].get())}; + // Tally each delayed group bin individually + for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { + auto d = filt.groups_[d_bin]; + auto yield = data::nuclides[i_nuclide] + ->nu(E, ReactionProduct::EmissionMode::delayed, d); + score = simulation::micro_xs[i_nuclide].fission * yield + * atom_density * flux; + score_fission_delayed_dg(i_tally, d_bin+1, score, score_index); + } + continue; + } else { + // If the delayed group filter is not present, compute the score + // by multiplying the delayed-nu-fission macro xs by the flux + score = simulation::micro_xs[i_nuclide].fission + * data::nuclides[i_nuclide] + ->nu(E, ReactionProduct::EmissionMode::delayed) + * atom_density * flux; + } + } else { + if (tally.delayedgroup_filter_ > 0) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + const DelayedGroupFilter& filt + {*dynamic_cast( + model::tally_filters[i_dg_filt].get())}; + if (p->material != MATERIAL_VOID) { + const Material& material {*model::materials[p->material-1]}; + for (auto i = 0; i < material.nuclide_.size(); ++i) { + auto j_nuclide = material.nuclide_[i]; + auto atom_density = material.atom_density_(i); + // Tally each delayed group bin individually + for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { + auto d = filt.groups_[d_bin]; + auto yield = data::nuclides[j_nuclide] + ->nu(E, ReactionProduct::EmissionMode::delayed, d); + score = simulation::micro_xs[j_nuclide].fission * yield + * atom_density * flux; + score_fission_delayed_dg(i_tally, d_bin+1, score, + score_index); + } + } + } + continue; + } else { + score = 0.; + if (p->material != MATERIAL_VOID) { + const Material& material {*model::materials[p->material-1]}; + for (auto i = 0; i < material.nuclide_.size(); ++i) { + auto j_nuclide = material.nuclide_[i]; + auto atom_density = material.atom_density_(i); + score += simulation::micro_xs[j_nuclide].fission + * data::nuclides[j_nuclide] + ->nu(E, ReactionProduct::EmissionMode::delayed) + * atom_density * flux; + } + } + } + } + } + break; + + + case SCORE_DECAY_RATE: + if (simulation::material_xs.absorption == 0) continue; + if (tally.estimator_ == ESTIMATOR_ANALOG) { + if (settings::survival_biasing) { + // No fission events occur if survival biasing is on -- need to + // calculate fraction of absorptions that would have resulted in + // delayed-nu-fission + const auto& nuc {*data::nuclides[p->event_nuclide-1]}; + if (simulation::micro_xs[p->event_nuclide-1].absorption > 0 + && nuc.fissionable_) { + const auto& rxn {*nuc.fission_rx_[0]}; + if (tally.delayedgroup_filter_ > 0) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + const DelayedGroupFilter& filt + {*dynamic_cast( + model::tally_filters[i_dg_filt].get())}; + // Tally each delayed group bin individually + for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { + auto d = filt.groups_[d_bin]; + auto yield + = nuc.nu(E, ReactionProduct::EmissionMode::delayed, d); + auto rate = rxn.products_[d].decay_rate_; + score = p->absorb_wgt * yield + * simulation::micro_xs[p->event_nuclide-1].fission + / simulation::micro_xs[p->event_nuclide-1].absorption + * rate * flux; + score_fission_delayed_dg(i_tally, d_bin+1, score, + score_index); + } + continue; + } else { + // If the delayed group filter is not present, compute the score + // by multiplying the absorbed weight by the fraction of the + // delayed-nu-fission xs to the absorption xs for all delayed + // groups + score = 0.; + // We need to be careful not to overshoot the number of + // delayed groups since this could cause the range of the + // rxn.products_ array to be exceeded. Hence, we use the size + // of this array and not the MAX_DELAYED_GROUPS constant for + // this loop. + for (auto d = 0; d < rxn.products_.size() - 2; ++d) { + auto yield + = nuc.nu(E, ReactionProduct::EmissionMode::delayed, d+1); + auto rate = rxn.products_[d+1].decay_rate_; + score += rate * p->absorb_wgt + * simulation::micro_xs[p->event_nuclide-1].fission * yield + / simulation::micro_xs[p->event_nuclide-1].absorption * flux; + } + } + } + } else { + // Skip any non-fission events + if (!p->fission) continue; + // If there is no outgoing energy filter, than we only need to score + // to one bin. For the score to be 'analog', we need to score the + // number of particles that were banked in the fission bank. Since + // this was weighted by 1/keff, we multiply by keff to get the proper + // score. Loop over the neutrons produced from fission and check which + // ones are delayed. If a delayed neutron is encountered, add its + // contribution to the fission bank to the score. + score = 0.; + for (auto i = 0; i < p->n_bank; ++i) { + auto i_bank = simulation::n_bank - p->n_bank + i; + const auto& bank = simulation::fission_bank[i_bank]; + auto g = bank.delayed_group; + if (g != 0) { + const auto& nuc {*data::nuclides[p->event_nuclide-1]}; + const auto& rxn {*nuc.fission_rx_[0]}; + auto rate = rxn.products_[g].decay_rate_; + score += simulation::keff * bank.wgt * rate * flux; + if (tally.delayedgroup_filter_ > 0) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + const DelayedGroupFilter& filt + {*dynamic_cast( + model::tally_filters[i_dg_filt].get())}; + // Find the corresponding filter bin and then score + for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { + auto d = filt.groups_[d_bin]; + if (d == g) + score_fission_delayed_dg(i_tally, d_bin+1, score, + score_index); + } + score = 0.; + } + } + } + } + } else { + if (i_nuclide >= 0) { + const auto& nuc {*data::nuclides[i_nuclide]}; + if (!nuc.fissionable_) continue; + const auto& rxn {*nuc.fission_rx_[0]}; + if (tally.delayedgroup_filter_ > 0) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + const DelayedGroupFilter& filt + {*dynamic_cast( + model::tally_filters[i_dg_filt].get())}; + // Tally each delayed group bin individually + for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { + auto d = filt.groups_[d_bin]; + auto yield + = nuc.nu(E, ReactionProduct::EmissionMode::delayed, d); + auto rate = rxn.products_[d].decay_rate_; + score = simulation::micro_xs[i_nuclide].fission * yield * flux + * atom_density * rate; + score_fission_delayed_dg(i_tally, d_bin+1, score, score_index); + } + continue; + } else { + score = 0.; + // We need to be careful not to overshoot the number of + // delayed groups since this could cause the range of the + // rxn.products_ array to be exceeded. Hence, we use the size + // of this array and not the MAX_DELAYED_GROUPS constant for + // this loop. + for (auto d = 0; d < rxn.products_.size() - 2; ++d) { + auto yield + = nuc.nu(E, ReactionProduct::EmissionMode::delayed, d+1); + auto rate = rxn.products_[d+1].decay_rate_; + score += simulation::micro_xs[i_nuclide].fission * flux + * yield * atom_density * rate; + } + } + } else { + if (tally.delayedgroup_filter_ > 0) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + const DelayedGroupFilter& filt + {*dynamic_cast( + model::tally_filters[i_dg_filt].get())}; + if (p->material != MATERIAL_VOID) { + const Material& material {*model::materials[p->material-1]}; + for (auto i = 0; i < material.nuclide_.size(); ++i) { + auto j_nuclide = material.nuclide_[i]; + auto atom_density = material.atom_density_(i); + const auto& nuc {*data::nuclides[j_nuclide]}; + if (nuc.fissionable_) { + const auto& rxn {*nuc.fission_rx_[0]}; + // Tally each delayed group bin individually + for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { + auto d = filt.groups_[d_bin]; + auto yield + = nuc.nu(E, ReactionProduct::EmissionMode::delayed, d); + auto rate = rxn.products_[d].decay_rate_; + score = simulation::micro_xs[j_nuclide].fission * yield + * flux * atom_density * rate; + score_fission_delayed_dg(i_tally, d_bin+1, score, + score_index); + } + } + } + } + continue; + } else { + score = 0.; + if (p->material != MATERIAL_VOID) { + const Material& material {*model::materials[p->material-1]}; + for (auto i = 0; i < material.nuclide_.size(); ++i) { + auto j_nuclide = material.nuclide_[i]; + auto atom_density = material.atom_density_(i); + const auto& nuc {*data::nuclides[j_nuclide]}; + if (nuc.fissionable_) { + const auto& rxn {*nuc.fission_rx_[0]}; + // We need to be careful not to overshoot the number of + // delayed groups since this could cause the range of the + // rxn.products_ array to be exceeded. Hence, we use the size + // of this array and not the MAX_DELAYED_GROUPS constant for + // this loop. + for (auto d = 0; d < rxn.products_.size() - 2; ++d) { + auto yield + = nuc.nu(E, ReactionProduct::EmissionMode::delayed, d+1); + auto rate = rxn.products_[d+1].decay_rate_; + score += simulation::micro_xs[j_nuclide].fission + * yield * atom_density * flux * rate; + } + } + } + } + } + } + } + break; + + + case SCORE_KAPPA_FISSION: + if (simulation::material_xs.absorption == 0.) continue; + score = 0.; + // Kappa-fission values are determined from the Q-value listed for the + // fission cross section. + if (tally.estimator_ == ESTIMATOR_ANALOG) { + if (settings::survival_biasing) { + // No fission events occur if survival biasing is on -- need to + // calculate fraction of absorptions that would have resulted in + // fission scaled by the Q-value + const auto& nuc {*data::nuclides[p->event_nuclide-1]}; + if (simulation::micro_xs[p->event_nuclide-1].absorption > 0 + && nuc.fissionable_) { + const auto& rxn {*nuc.fission_rx_[0]}; + score = p->absorb_wgt * rxn.q_value_ + * simulation::micro_xs[p->event_nuclide-1].fission + / simulation::micro_xs[p->event_nuclide-1].absorption * flux; + } + } else { + // Skip any non-absorption events + if (p->event == EVENT_SCATTER) continue; + // All fission events will contribute, so again we can use particle's + // weight entering the collision as the estimate for the fission + // reaction rate + const auto& nuc {*data::nuclides[p->event_nuclide-1]}; + if (simulation::micro_xs[p->event_nuclide-1].absorption > 0 + && nuc.fissionable_) { + const auto& rxn {*nuc.fission_rx_[0]}; + score = p->last_wgt * rxn.q_value_ + * simulation::micro_xs[p->event_nuclide-1].fission + / simulation::micro_xs[p->event_nuclide-1].absorption * flux; + } + } + } else { + if (i_nuclide >= 0) { + const auto& nuc {*data::nuclides[i_nuclide]}; + if (nuc.fissionable_) { + const auto& rxn {*nuc.fission_rx_[0]}; + score = rxn.q_value_ * simulation::micro_xs[i_nuclide].fission + * atom_density * flux; + } + } else { + if (p->material != MATERIAL_VOID) { + const Material& material {*model::materials[p->material-1]}; + for (auto i = 0; i < material.nuclide_.size(); ++i) { + auto j_nuclide = material.nuclide_[i]; + auto atom_density = material.atom_density_(i); + const auto& nuc {*data::nuclides[j_nuclide]}; + if (nuc.fissionable_) { + const auto& rxn {*nuc.fission_rx_[0]}; + score += rxn.q_value_ * simulation::micro_xs[j_nuclide].fission + * atom_density * flux; + } + } + } + } + } + break; + + + case SCORE_EVENTS: + // Simply count the number of scoring events + score = 1.; + break; + + + case ELASTIC: + if (tally.estimator_ == ESTIMATOR_ANALOG) { + // Check if event MT matches + if (p->event_MT != ELASTIC) continue; + score = p->last_wgt * flux; + } else { + if (i_nuclide >= 0) { + if (simulation::micro_xs[i_nuclide].elastic == CACHE_INVALID) + data::nuclides[i_nuclide]->calculate_elastic_xs(); + score = simulation::micro_xs[i_nuclide].elastic * atom_density * flux; + } else { + score = 0.; + if (p->material != MATERIAL_VOID) { + const Material& material {*model::materials[p->material-1]}; + for (auto i = 0; i < material.nuclide_.size(); ++i) { + auto j_nuclide = material.nuclide_[i]; + auto atom_density = material.atom_density_(i); + if (simulation::micro_xs[j_nuclide].elastic == CACHE_INVALID) + data::nuclides[j_nuclide]->calculate_elastic_xs(); + score += simulation::micro_xs[j_nuclide].elastic * atom_density + * flux; + } + } + } + } + break; + + case SCORE_FISS_Q_PROMPT: + case SCORE_FISS_Q_RECOV: + //continue; + if (simulation::material_xs.absorption == 0.) continue; + score = 0.; + if (tally.estimator_ == ESTIMATOR_ANALOG) { + if (settings::survival_biasing) { + // No fission events occur if survival biasing is on -- need to + // calculate fraction of absorptions that would have resulted in + // fission scaled by the Q-value + const auto& nuc {*data::nuclides[p->event_nuclide-1]}; + if (simulation::micro_xs[p->event_nuclide-1].absorption > 0) { + double q_value = 0.; + if (score_bin == SCORE_FISS_Q_PROMPT) { + if (nuc.fission_q_prompt_) + q_value = (*nuc.fission_q_prompt_)(p->last_E); + } else if (score_bin == SCORE_FISS_Q_RECOV) { + if (nuc.fission_q_recov_) + q_value = (*nuc.fission_q_recov_)(p->last_E); + } + score = p->absorb_wgt * q_value + * simulation::micro_xs[p->event_nuclide-1].fission + / simulation::micro_xs[p->event_nuclide-1].absorption * flux; + } + } else { + // Skip any non-absorption events + if (p->event == EVENT_SCATTER) continue; + // All fission events will contribute, so again we can use particle's + // weight entering the collision as the estimate for the fission + // reaction rate + const auto& nuc {*data::nuclides[p->event_nuclide-1]}; + if (simulation::micro_xs[p->event_nuclide-1].absorption > 0) { + double q_value = 0.; + if (score_bin == SCORE_FISS_Q_PROMPT) { + if (nuc.fission_q_prompt_) + q_value = (*nuc.fission_q_prompt_)(p->last_E); + } else if (score_bin == SCORE_FISS_Q_RECOV) { + if (nuc.fission_q_recov_) + q_value = (*nuc.fission_q_recov_)(p->last_E); + } + score = p->last_wgt * q_value + * simulation::micro_xs[p->event_nuclide-1].fission + / simulation::micro_xs[p->event_nuclide-1].absorption * flux; + } + } + } else { + if (i_nuclide >= 0) { + const auto& nuc {*data::nuclides[i_nuclide]}; + double q_value = 0.; + if (score_bin == SCORE_FISS_Q_PROMPT) { + if (nuc.fission_q_prompt_) + q_value = (*nuc.fission_q_prompt_)(p->last_E); + } else if (score_bin == SCORE_FISS_Q_RECOV) { + if (nuc.fission_q_recov_) + q_value = (*nuc.fission_q_recov_)(p->last_E); + } + score = q_value * simulation::micro_xs[i_nuclide].fission + * atom_density * flux; + } else { + if (p->material != MATERIAL_VOID) { + const Material& material {*model::materials[p->material-1]}; + for (auto i = 0; i < material.nuclide_.size(); ++i) { + auto j_nuclide = material.nuclide_[i]; + auto atom_density = material.atom_density_(i); + const auto& nuc {*data::nuclides[j_nuclide]}; + double q_value = 0.; + if (score_bin == SCORE_FISS_Q_PROMPT) { + if (nuc.fission_q_prompt_) + q_value = (*nuc.fission_q_prompt_)(p->last_E); + } else if (score_bin == SCORE_FISS_Q_RECOV) { + if (nuc.fission_q_recov_) + q_value = (*nuc.fission_q_recov_)(p->last_E); + } + score += q_value * simulation::micro_xs[j_nuclide].fission + * atom_density * flux; + } + } + } + } + break; + + + case N_2N: + case N_3N: + case N_4N: + case N_GAMMA: + case N_P: + case N_A: + if (tally.estimator_ == ESTIMATOR_ANALOG) { + // Check if the event MT matches + if (p->event_MT != score_bin) continue; + score = p->last_wgt * flux; + } else { + int m; + switch (score_bin) { + case N_GAMMA: m = 0; break; + case N_P: m = 1; break; + case N_A: m = 2; break; + case N_2N: m = 3; break; + case N_3N: m = 4; break; + case N_4N: m = 5; break; + } + if (i_nuclide >= 0) { + score = simulation::micro_xs[i_nuclide].reaction[m] * atom_density + * flux; + } else { + score = 0.; + if (p->material != MATERIAL_VOID) { + const Material& material {*model::materials[p->material-1]}; + for (auto i = 0; i < material.nuclide_.size(); ++i) { + auto j_nuclide = material.nuclide_[i]; + auto atom_density = material.atom_density_(i); + score += simulation::micro_xs[j_nuclide].reaction[m] + * atom_density * flux; + } + } + } + } + break; + + + default: + if (tally.estimator_ == ESTIMATOR_ANALOG) { + // Any other score is assumed to be a MT number. Thus, we just need + // to check if it matches the MT number of the event + if (p->event_MT != score_bin) continue; + score = p->last_wgt*flux; + } else { + // Any other cross section has to be calculated on-the-fly + if (score_bin < 2) fatal_error("Invalid score type on tally " + + std::to_string(tally.id_)); + score = 0.; + if (i_nuclide >= 0) { + const auto& nuc {*data::nuclides[i_nuclide]}; + auto m = nuc.reaction_index_[score_bin]; + if (m == C_NONE) continue; + const auto& rxn {*nuc.reactions_[m]}; + auto i_temp = simulation::micro_xs[i_nuclide].index_temp; + if (i_temp >= 0) { // Can be false due to multipole + auto i_grid = simulation::micro_xs[i_nuclide].index_grid - 1; + auto f = simulation::micro_xs[i_nuclide].interp_factor; + const auto& xs {rxn.xs_[i_temp]}; + auto threshold = xs.threshold - 1; + if (i_grid >= xs.threshold) { + score = ((1.0 - f) * xs.value[i_grid-threshold] + + f * xs.value[i_grid-threshold+1]) * atom_density * flux; + } + } + } else { + if (p->material != MATERIAL_VOID) { + const Material& material {*model::materials[p->material-1]}; + for (auto i = 0; i < material.nuclide_.size(); ++i) { + auto j_nuclide = material.nuclide_[i]; + auto atom_density = material.atom_density_(i); + const auto& nuc {*data::nuclides[j_nuclide]}; + auto m = nuc.reaction_index_[score_bin]; + if (m == C_NONE) continue; + const auto& rxn {*nuc.reactions_[m]}; + auto i_temp = simulation::micro_xs[j_nuclide].index_temp; + if (i_temp >= 0) { // Can be false due to multipole + auto i_grid = simulation::micro_xs[j_nuclide].index_grid - 1; + auto f = simulation::micro_xs[j_nuclide].interp_factor; + const auto& xs {rxn.xs_[i_temp]}; + auto threshold = xs.threshold - 1; + if (i_grid >= threshold) { + score += ((1.0 - f) * xs.value[i_grid-threshold] + + f * xs.value[i_grid-threshold+1]) * atom_density + * flux; + } + } + } + } + } + } + } + + // Add derivative information on score for differnetial tallies. + if (tally.deriv_ != C_NONE) + apply_derivative_to_score(p, i_tally, i_nuclide, atom_density, score_bin, + &score); + + // Update tally results + #pragma omp atomic + results(filter_index-1, score_index, RESULT_VALUE) += score; + } +} + +//! Update tally results for multigroup tallies with any estimator. +// +//! For analog tallies, the flux estimate depends on the score type so the flux +//! argument is really just used for filter weights. + +void +score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, + int i_nuclide, double atom_density, double flux) +{ + //TODO: off-by-one + const Tally& tally {*model::tallies[i_tally-1]}; + auto results = tally_results(i_tally); + + //TODO: off-by-one throughout on p->material + + // Set the direction and group to use with get_xs + double* p_uvw; + int p_g; + if (tally.estimator_ == ESTIMATOR_ANALOG + || tally.estimator_ == ESTIMATOR_COLLISION) { + + if (settings::survival_biasing) { + + // Then we either are alive and had a scatter (and so g changed), + // or are dead and g did not change + if (p->alive) { + p_uvw = p->last_uvw; + p_g = p->last_g; + } else { + p_uvw = p->coord[p->n_coord-1].uvw; + p_g = p->g; + } + } else if (p->event == EVENT_SCATTER) { + + // Then the energy group has been changed by the scattering routine + // meaning gin is now in p % last_g + p_uvw = p->last_uvw; + p_g = p->last_g; + } else { + + // No scatter, no change in g. + p_uvw = p->coord[p->n_coord-1].uvw; + p_g = p->g; + } + } else { + + // No actual collision so g has not changed. + p_uvw = p->coord[p->n_coord-1].uvw; + p_g = p->g; + } + + // To significantly reduce de-referencing, point matxs to the macroscopic + // Mgxs for the material of interest + data::macro_xs[p->material - 1].set_angle_index(p_uvw); + + // Do same for nucxs, point it to the microscopic nuclide data of interest + if (i_nuclide >= 0) { + // And since we haven't calculated this temperature index yet, do so now + data::nuclides_MG[i_nuclide].set_temperature_index(p->sqrtkT); + data::nuclides_MG[i_nuclide].set_angle_index(p_uvw); + } + + for (auto i = 0; i < tally.scores_.size(); ++i) { + auto score_bin = tally.scores_[i]; + auto score_index = start_index + i; + + double score; + + switch (score_bin) { + + + case SCORE_FLUX: + if (tally.estimator_ == ESTIMATOR_ANALOG) { + // All events score to a flux bin. We actually use a collision estimator + // in place of an analog one since there is no way to count 'events' + // exactly for the flux + if (settings::survival_biasing) { + // We need to account for the fact that some weight was already + // absorbed + score = p->last_wgt + p->absorb_wgt; + } else { + score = p->last_wgt; + } + score *= flux / simulation::material_xs.total; + } else { + score = flux; + } + break; + + + case SCORE_TOTAL: + if (tally.estimator_ == ESTIMATOR_ANALOG) { + // All events will score to the total reaction rate. We can just use + // use the weight of the particle entering the collision as the score + if (settings::survival_biasing) { + // We need to account for the fact that some weight was already + // absorbed + score = p->last_wgt + p->absorb_wgt; + } else { + score = p->last_wgt; + } + //TODO: should flux be multiplied in above instead of below? + if (i_nuclide >= 0) { + score *= flux * atom_density + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_TOTAL, p_g) + / get_macro_xs(p->material, MG_GET_XS_TOTAL, p_g); + } + } else { + if (i_nuclide >= 0) { + score = get_nuclide_xs(i_nuclide+1, MG_GET_XS_TOTAL, p_g) + * atom_density * flux; + } else { + score = simulation::material_xs.total * flux; + } + } + break; + + + case SCORE_INVERSE_VELOCITY: + if (tally.estimator_ == ESTIMATOR_ANALOG + || tally.estimator_ == ESTIMATOR_COLLISION) { + // All events score to an inverse velocity bin. We actually use a + // collision estimator in place of an analog one since there is no way + // to count 'events' exactly for the inverse velocity + if (settings::survival_biasing) { + // We need to account for the fact that some weight was already + // absorbed + score = p->last_wgt + p->absorb_wgt; + } else { + score = p->last_wgt; + } + if (i_nuclide >= 0) { + score *= flux + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_INVERSE_VELOCITY, p_g) + / get_macro_xs(p->material, MG_GET_XS_TOTAL, p_g); + } else { + score *= flux + * get_macro_xs(p->material, MG_GET_XS_INVERSE_VELOCITY, p_g) + / get_macro_xs(p->material, MG_GET_XS_TOTAL, p_g); + } + } else { + if (i_nuclide >= 0) { + score = flux + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_INVERSE_VELOCITY, p_g); + } else { + score = flux + * get_macro_xs(p->material, MG_GET_XS_INVERSE_VELOCITY, p_g); + } + } + break; + + + case SCORE_SCATTER: + if (tally.estimator_ == ESTIMATOR_ANALOG) { + // Skip any event where the particle didn't scatter + if (p->event != EVENT_SCATTER) continue; + // Since only scattering events make it here, again we can use the + // weight entering the collision as the estimator for the reaction rate + score = p->last_wgt * flux; + if (i_nuclide >= 0) { + score *= atom_density + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_SCATTER_FMU_MULT, + p->last_g, &p->g, &p->mu, nullptr) + / get_macro_xs(p->material, MG_GET_XS_SCATTER_FMU_MULT, + p->last_g, &p->g, &p->mu, nullptr); + } + } else { + if (i_nuclide >= 0) { + score = atom_density * flux * get_nuclide_xs( + i_nuclide+1, MG_GET_XS_SCATTER_MULT, p_g, nullptr, &p->mu, nullptr); + } else { + score = flux * get_macro_xs( + p->material, MG_GET_XS_SCATTER_MULT, p_g, nullptr, &p->mu, nullptr); + } + } + break; + + + case SCORE_NU_SCATTER: + if (tally.estimator_ == ESTIMATOR_ANALOG) { + // Skip any event where the particle didn't scatter + if (p->event != EVENT_SCATTER) continue; + // For scattering production, we need to use the pre-collision weight + // times the multiplicity as the estimate for the number of neutrons + // exiting a reaction with neutrons in the exit channel + score = p->wgt * flux; + // Since we transport based on material data, the angle selected + // was not selected from the f(mu) for the nuclide. Therefore + // adjust the score by the actual probability for that nuclide. + if (i_nuclide >= 0) { + score *= atom_density + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_SCATTER_FMU, + p->last_g, &p->g, &p->mu, nullptr) + / get_macro_xs(p->material, MG_GET_XS_SCATTER_FMU, + p->last_g, &p->g, &p->mu, nullptr); + } + } else { + if (i_nuclide >= 0) { + score = atom_density * flux * get_nuclide_xs( + i_nuclide+1, MG_GET_XS_SCATTER, p_g); + } else { + score = flux * get_macro_xs(p->material, MG_GET_XS_SCATTER, p_g); + } + } + break; + + + case SCORE_ABSORPTION: + if (tally.estimator_ == ESTIMATOR_ANALOG) { + if (settings::survival_biasing) { + // No absorption events actually occur if survival biasing is on -- + // just use weight absorbed in survival biasing + score = p->absorb_wgt * flux; + } else { + // Skip any event where the particle wasn't absorbed + if (p->event == EVENT_SCATTER) continue; + // All fission and absorption events will contribute here, so we + // can just use the particle's weight entering the collision + score = p->last_wgt * flux; + } + if (i_nuclide >= 0) { + score *= atom_density + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_ABSORPTION, p_g) + / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); + } + } else { + if (i_nuclide >= 0) { + score = atom_density * flux + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_ABSORPTION, p_g); + } else { + score = simulation::material_xs.absorption * flux; + } + } + break; + + + case SCORE_FISSION: + if (tally.estimator_ == ESTIMATOR_ANALOG) { + if (settings::survival_biasing) { + // No fission events occur if survival biasing is on -- need to + // calculate fraction of absorptions that would have resulted in + // fission + score = p->absorb_wgt * flux; + } else { + // Skip any non-absorption events + if (p->event == EVENT_SCATTER) continue; + // All fission events will contribute, so again we can use particle's + // weight entering the collision as the estimate for the fission + // reaction rate + score = p->last_wgt * flux; + } + if (i_nuclide >= 0) { + score *= atom_density + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_FISSION, p_g) + / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); + } else { + score *= + get_macro_xs(p->material, MG_GET_XS_FISSION, p_g) + / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); + } + } else { + if (i_nuclide >= 0) { + score = get_nuclide_xs(i_nuclide+1, MG_GET_XS_FISSION, p_g) + * atom_density * flux; + } else { + score = get_macro_xs(p->material, MG_GET_XS_FISSION, p_g) * flux; + } + } + break; + + + case SCORE_NU_FISSION: + if (tally.estimator_ == ESTIMATOR_ANALOG) { + if (settings::survival_biasing || p->fission) { + if (tally.energyout_filter_ > 0) { + // Fission has multiple outgoing neutrons so this helper function + // is used to handle scoring the multiple filter bins. + score_fission_eout(p, i_tally, score_index, score_bin); + continue; + } + } + if (settings::survival_biasing) { + // No fission events occur if survival biasing is on -- need to + // calculate fraction of absorptions that would have resulted in + // nu-fission + score = p->absorb_wgt * flux; + if (i_nuclide >= 0) { + score *= atom_density + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_NU_FISSION, p_g) + / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); + } else { + score *= + get_macro_xs(p->material, MG_GET_XS_NU_FISSION, p_g) + / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); + } + } else { + // Skip any non-fission events + if (!p->fission) continue; + // If there is no outgoing energy filter, than we only need to score + // to one bin. For the score to be 'analog', we need to score the + // number of particles that were banked in the fission bank. Since + // this was weighted by 1/keff, we multiply by keff to get the proper + // score. + score = simulation::keff * p->wgt_bank * flux; + if (i_nuclide >= 0) { + score *= atom_density + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_FISSION, p_g) + / get_macro_xs(p->material, MG_GET_XS_FISSION, p_g); + } + } + } else { + if (i_nuclide >= 0) { + score = get_nuclide_xs(i_nuclide+1, MG_GET_XS_NU_FISSION, p_g) + * atom_density * flux; + } else { + score = get_macro_xs(p->material, MG_GET_XS_NU_FISSION, p_g) * flux; + } + } + break; + + + case SCORE_PROMPT_NU_FISSION: + if (tally.estimator_ == ESTIMATOR_ANALOG) { + if (settings::survival_biasing || p->fission) { + if (tally.energyout_filter_ > 0) { + // Fission has multiple outgoing neutrons so this helper function + // is used to handle scoring the multiple filter bins. + score_fission_eout(p, i_tally, score_index, score_bin); + continue; + } + } + if (settings::survival_biasing) { + // No fission events occur if survival biasing is on -- need to + // calculate fraction of absorptions that would have resulted in + // prompt-nu-fission + score = p->absorb_wgt * flux; + if (i_nuclide >= 0) { + score *= atom_density + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_PROMPT_NU_FISSION, p_g) + / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); + } else { + score *= + get_macro_xs(p->material, MG_GET_XS_PROMPT_NU_FISSION, p_g) + / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); + } + } else { + // Skip any non-fission events + if (!p->fission) continue; + // If there is no outgoing energy filter, than we only need to score + // to one bin. For the score to be 'analog', we need to score the + // number of particles that were banked in the fission bank. Since + // this was weighted by 1/keff, we multiply by keff to get the proper + // score. + auto n_delayed = std::accumulate(p->n_delayed_bank, + p->n_delayed_bank+MAX_DELAYED_GROUPS, 0); + auto prompt_frac = 1. - n_delayed / static_cast(p->n_bank); + score = simulation::keff * p->wgt_bank * prompt_frac * flux; + if (i_nuclide >= 0) { + score *= atom_density + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_FISSION, p_g) + / get_macro_xs(p->material, MG_GET_XS_FISSION, p_g); + } + } + } else { + if (i_nuclide >= 0) { + score = get_nuclide_xs(i_nuclide+1, MG_GET_XS_PROMPT_NU_FISSION, p_g) + * atom_density * flux; + } else { + score = get_macro_xs(p->material, MG_GET_XS_PROMPT_NU_FISSION, p_g) + * flux; + } + } + break; + + + case SCORE_DELAYED_NU_FISSION: + if (tally.estimator_ == ESTIMATOR_ANALOG) { + if (settings::survival_biasing || p->fission) { + if (tally.energyout_filter_ > 0) { + // Fission has multiple outgoing neutrons so this helper function + // is used to handle scoring the multiple filter bins. + score_fission_eout(p, i_tally, score_index, score_bin); + continue; + } + } + if (settings::survival_biasing) { + // No fission events occur if survival biasing is on -- need to + // calculate fraction of absorptions that would have resulted in + // delayed-nu-fission + if (get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g) > 0) { + if (tally.delayedgroup_filter_ > 0) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + const DelayedGroupFilter& filt + {*dynamic_cast( + model::tally_filters[i_dg_filt].get())}; + // Tally each delayed group bin individually + for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { + auto d = filt.groups_[d_bin]; + score = p->absorb_wgt * flux; + if (i_nuclide >= 0) { + score *= + get_nuclide_xs(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, + p_g, nullptr, nullptr, &d) + / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); + } else { + score *= + get_macro_xs(p->material, MG_GET_XS_DELAYED_NU_FISSION, + p_g, nullptr, nullptr, &d) + / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); + } + score_fission_delayed_dg(i_tally, d_bin+1, score, + score_index); + } + continue; + } else { + // If the delayed group filter is not present, compute the score + // by multiplying the absorbed weight by the fraction of the + // delayed-nu-fission xs to the absorption xs + score = p->absorb_wgt * flux; + if (i_nuclide >= 0) { + score *= + get_nuclide_xs(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, p_g) + / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); + } else { + score *= + get_macro_xs(p->material, MG_GET_XS_DELAYED_NU_FISSION, p_g) + / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); + } + } + } + } else { + // Skip any non-fission events + if (!p->fission) continue; + // If there is no outgoing energy filter, than we only need to score + // to one bin. For the score to be 'analog', we need to score the + // number of particles that were banked in the fission bank. Since + // this was weighted by 1/keff, we multiply by keff to get the proper + // score. Loop over the neutrons produced from fission and check which + // ones are delayed. If a delayed neutron is encountered, add its + // contribution to the fission bank to the score. + if (tally.delayedgroup_filter_ > 0) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + const DelayedGroupFilter& filt + {*dynamic_cast( + model::tally_filters[i_dg_filt].get())}; + // Tally each delayed group bin individually + for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { + auto d = filt.groups_[d_bin]; + score = simulation::keff * p->wgt_bank / p->n_bank + * p->n_delayed_bank[d-1] * flux; + if (i_nuclide >= 0) { + score *= atom_density + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_FISSION, p_g) + / get_macro_xs(p->material, MG_GET_XS_FISSION, p_g); + } + score_fission_delayed_dg(i_tally, d_bin+1, score, score_index); + } + continue; + } else { + // Add the contribution from all delayed groups + auto n_delayed = std::accumulate(p->n_delayed_bank, + p->n_delayed_bank+MAX_DELAYED_GROUPS, 0); + score = simulation::keff * p->wgt_bank / p->n_bank * n_delayed + * flux; + if (i_nuclide >= 0) { + score *= atom_density + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_FISSION, p_g) + / get_macro_xs(p->material, MG_GET_XS_FISSION, p_g); + } + } + } + } else { + if (tally.delayedgroup_filter_ > 0) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + const DelayedGroupFilter& filt + {*dynamic_cast( + model::tally_filters[i_dg_filt].get())}; + // Tally each delayed group bin individually + for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { + auto d = filt.groups_[d_bin]; + if (i_nuclide >= 0) { + score = flux * atom_density + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, + p_g, nullptr, nullptr, &d); + } else { + score = flux + * get_macro_xs(p->material, MG_GET_XS_DELAYED_NU_FISSION, + p_g, nullptr, nullptr, &d); + } + score_fission_delayed_dg(i_tally, d_bin+1, score, score_index); + } + continue; + } else { + if (i_nuclide >= 0) { + score = flux * atom_density + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, p_g); + } else { + score = flux + * get_macro_xs(p->material, MG_GET_XS_DELAYED_NU_FISSION, p_g); + } + } + } + break; + + + case SCORE_DECAY_RATE: + if (tally.estimator_ == ESTIMATOR_ANALOG) { + if (settings::survival_biasing) { + // No fission events occur if survival biasing is on -- need to + // calculate fraction of absorptions that would have resulted in + // delayed-nu-fission + if (get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g) > 0) { + if (tally.delayedgroup_filter_ > 0) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + const DelayedGroupFilter& filt + {*dynamic_cast( + model::tally_filters[i_dg_filt].get())}; + // Tally each delayed group bin individually + for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { + auto d = filt.groups_[d_bin]; + score = p->absorb_wgt * flux; + if (i_nuclide >= 0) { + score *= + get_nuclide_xs(i_nuclide+1, MG_GET_XS_DECAY_RATE, + p_g, nullptr, nullptr, &d) + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, + p_g, nullptr, nullptr, &d) + / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); + } else { + score *= + get_macro_xs(p->material, MG_GET_XS_DECAY_RATE, + p_g, nullptr, nullptr, &d) + * get_macro_xs(p->material, MG_GET_XS_DELAYED_NU_FISSION, + p_g, nullptr, nullptr, &d) + / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); + } + score_fission_delayed_dg(i_tally, d_bin+1, score, + score_index); + } + continue; + } else { + // If the delayed group filter is not present, compute the score + // by multiplying the absorbed weight by the fraction of the + // delayed-nu-fission xs to the absorption xs for all delayed + // groups + score = 0.; + for (auto d = 0; d < data::num_delayed_groups; ++d) { + if (i_nuclide >= 0) { + score += p->absorb_wgt * flux + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_DECAY_RATE, + p_g, nullptr, nullptr, &d) + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, + p_g, nullptr, nullptr, &d) + / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); + } else { + score += p->absorb_wgt * flux + * get_macro_xs(p->material, MG_GET_XS_DECAY_RATE, + p_g, nullptr, nullptr, &d) + * get_macro_xs(p->material, MG_GET_XS_DELAYED_NU_FISSION, + p_g, nullptr, nullptr, &d) + / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); + } + } + } + } + } else { + // Skip any non-fission events + if (!p->fission) continue; + // If there is no outgoing energy filter, than we only need to score + // to one bin. For the score to be 'analog', we need to score the + // number of particles that were banked in the fission bank. Since + // this was weighted by 1/keff, we multiply by keff to get the proper + // score. Loop over the neutrons produced from fission and check which + // ones are delayed. If a delayed neutron is encountered, add its + // contribution to the fission bank to the score. + score = 0.; + for (auto i = 0; i < p->n_bank; ++i) { + auto i_bank = simulation::n_bank - p->n_bank + i; + const auto& bank = simulation::fission_bank[i_bank]; + auto g = bank.delayed_group; + if (g != 0) { + if (i_nuclide >= 0) { + score += simulation::keff * atom_density * bank.wgt * flux + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_DECAY_RATE, p_g, + nullptr, nullptr, &g) + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_FISSION, p_g) + / get_macro_xs(p->material, MG_GET_XS_FISSION, p_g); + } else { + score += simulation::keff * bank.wgt * flux + * get_macro_xs(p->material, MG_GET_XS_DECAY_RATE, p_g, + nullptr, nullptr, &g); + } + if (tally.delayedgroup_filter_ > 0) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + const DelayedGroupFilter& filt + {*dynamic_cast( + model::tally_filters[i_dg_filt].get())}; + // Find the corresponding filter bin and then score + for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { + auto d = filt.groups_[d_bin]; + if (d == g) + score_fission_delayed_dg(i_tally, d_bin+1, score, + score_index); + } + score = 0.; + } + } + } + if (tally.delayedgroup_filter_ > 0) continue; + } + } else { + if (tally.delayedgroup_filter_ > 0) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + const DelayedGroupFilter& filt + {*dynamic_cast( + model::tally_filters[i_dg_filt].get())}; + // Tally each delayed group bin individually + for (auto d_bin = 0; d_bin < filt.n_bins_; ++d_bin) { + auto d = filt.groups_[d_bin]; + if (i_nuclide >= 0) { + score += atom_density * flux + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_DECAY_RATE, + p_g, nullptr, nullptr, &d) + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, + p_g, nullptr, nullptr, &d); + } else { + score += flux + * get_macro_xs(p->material, MG_GET_XS_DECAY_RATE, + p_g, nullptr, nullptr, &d) + * get_macro_xs(p->material, MG_GET_XS_DELAYED_NU_FISSION, + p_g, nullptr, nullptr, &d); + } + score_fission_delayed_dg(i_tally, d_bin+1, score, score_index); + } + continue; + } else { + score = 0.; + for (auto d = 0; d < data::num_delayed_groups; ++d) { + if (i_nuclide >= 0) { + score += atom_density * flux + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_DECAY_RATE, + p_g, nullptr, nullptr, &d) + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_DELAYED_NU_FISSION, + p_g, nullptr, nullptr, &d); + } else { + score += flux + * get_macro_xs(p->material, MG_GET_XS_DECAY_RATE, + p_g, nullptr, nullptr, &d) + * get_macro_xs(p->material, MG_GET_XS_DELAYED_NU_FISSION, + p_g, nullptr, nullptr, &d); + } + } + } + } + break; + + + case SCORE_KAPPA_FISSION: + if (tally.estimator_ == ESTIMATOR_ANALOG) { + if (settings::survival_biasing) { + // No fission events occur if survival biasing is on -- need to + // calculate fraction of absorptions that would have resulted in + // fission scaled by the Q-value + score = p->absorb_wgt * flux; + } else { + // Skip any non-absorption events + if (p->event == EVENT_SCATTER) continue; + // All fission events will contribute, so again we can use particle's + // weight entering the collision as the estimate for the fission + // reaction rate + score = p->last_wgt * flux; + } + if (i_nuclide >= 0) { + score *= atom_density + * get_nuclide_xs(i_nuclide+1, MG_GET_XS_KAPPA_FISSION, p_g) + / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); + } else { + score *= + get_macro_xs(p->material, MG_GET_XS_KAPPA_FISSION, p_g) + / get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g); + } + } else { + if (i_nuclide >= 0) { + score = get_nuclide_xs(i_nuclide+1, MG_GET_XS_KAPPA_FISSION, p_g) + * atom_density * flux; + } else { + score = get_macro_xs(p->material, MG_GET_XS_KAPPA_FISSION, p_g) + * flux; + } + } + break; + + + case SCORE_EVENTS: + // Simply count the number of scoring events + score = 1.; + break; + + + default: + continue; + } + + // Update tally results + #pragma omp atomic + results(filter_index-1, score_index, RESULT_VALUE) += score; + } +} + +//! Tally rates for when the user requests a tally on all nuclides. + +void +score_all_nuclides(Particle* p, int i_tally, double flux, int filter_index) +{ + //TODO: off-by-one + const Tally& tally {*model::tallies[i_tally-1]}; + const Material& material {*model::materials[p->material-1]}; + + // Score all individual nuclide reaction rates. + for (auto i = 0; i < material.nuclide_.size(); ++i) { + auto i_nuclide = material.nuclide_[i]; + auto atom_density = material.atom_density_(i); + + //TODO: consider replacing this "if" with pointers or templates + if (settings::run_CE) { + score_general_ce(p, i_tally, i_nuclide*tally.scores_.size(), filter_index, + i_nuclide, atom_density, flux); + } else { + score_general_mg(p, i_tally, i_nuclide*tally.scores_.size(), filter_index, + i_nuclide, atom_density, flux); + } + } + + // Score total material reaction rates. + int i_nuclide = -1; + double atom_density = 0.; + auto n_nuclides = data::nuclides.size(); + //TODO: consider replacing this "if" with pointers or templates + if (settings::run_CE) { + score_general_ce(p, i_tally, n_nuclides*tally.scores_.size(), filter_index, + i_nuclide, atom_density, flux); + } else { + score_general_mg(p, i_tally, n_nuclides*tally.scores_.size(), filter_index, + i_nuclide, atom_density, flux); + } +} + +//! Score tallies based on a simple count of events (for continuous energy). +// +//! Analog tallies ar etriggered at every collision, not every event. + +extern "C" void +score_analog_tally_ce(Particle* p) +{ + for (auto i_tally : model::active_analog_tallies) { + //TODO: off-by-one + const Tally& tally {*model::tallies[i_tally-1]}; + + // Initialize an iterator over valid filter bin combinations. If there are + // no valid combinations, use a continue statement to ensure we skip the + // assume_separate break below. + auto filter_iter = FilterBinIter(tally, p); + auto end = FilterBinIter(tally, true); + if (filter_iter == end) continue; + + // Loop over filter bins. + for (; filter_iter != end; ++filter_iter) { + auto filter_index = filter_iter.index_; + auto filter_weight = filter_iter.weight_; + + // Loop over nuclide bins. + if (!tally.all_nuclides_) { + for (auto i = 0; i < tally.nuclides_.size(); ++i) { + auto i_nuclide = tally.nuclides_[i]; + + // Tally this event in the present nuclide bin if that bin represents + // the event nuclide or the total material. Note that the i_nuclide + // and flux arguments for score_general are not used for analog + // tallies. + //TODO: off-by-one + if (i_nuclide == p->event_nuclide-1 || i_nuclide == -1) + score_general_ce(p, i_tally, i*tally.scores_.size(), filter_index, + -1, -1., filter_weight); + } + + } else { + // In the case that the user has requested to tally all nuclides, we + // can take advantage of the fact that we know exactly how nuclide + // bins correspond to nuclide indices. First, tally the nuclide. + auto i = p->event_nuclide; + score_general_ce(p, i_tally, i*tally.scores_.size(), filter_index, + -1, -1., filter_weight); + + // Now tally the total material. + i = tally.nuclides_.size(); + score_general_ce(p, i_tally, i*tally.scores_.size(), filter_index, + -1, -1., filter_weight); + } + } + + // If the user has specified that we can assume all tallies are spatially + // separate, this implies that once a tally has been scored to, we needn't + // check the others. This cuts down on overhead when there are many + // tallies specified + if (settings::assume_separate) break; + } + + // Reset all the filter matches for the next tally event. + for (auto& match : simulation::filter_matches) + match.bins_present_ = false; +} + +//! Score tallies based on a simple count of events (for multigroup). +// +//! Analog tallies ar etriggered at every collision, not every event. + +extern "C" void +score_analog_tally_mg(Particle* p) +{ + for (auto i_tally : model::active_analog_tallies) { + //TODO: off-by-one + const Tally& tally {*model::tallies[i_tally-1]}; + + // Initialize an iterator over valid filter bin combinations. If there are + // no valid combinations, use a continue statement to ensure we skip the + // assume_separate break below. + auto filter_iter = FilterBinIter(tally, p); + auto end = FilterBinIter(tally, true); + if (filter_iter == end) continue; + + // Loop over filter bins. + for (; filter_iter != end; ++filter_iter) { + auto filter_index = filter_iter.index_; + auto filter_weight = filter_iter.weight_; + + // Loop over nuclide bins. + for (auto i = 0; i < tally.nuclides_.size(); ++i) { + auto i_nuclide = tally.nuclides_[i]; + + double atom_density = 0.; + if (i_nuclide >= 0) { + //TODO: off-by-one + auto j = model::materials[p->material-1] + ->mat_nuclide_index_[i_nuclide]; + if (j == C_NONE) continue; + //atom_density = material_atom_density(p->material, j); + //TODO: off-by-one + atom_density = model::materials[p->material-1]->atom_density_(j); + } + + score_general_mg(p, i_tally, i*tally.scores_.size(), filter_index, + i_nuclide, atom_density, filter_weight); + } + } + + // If the user has specified that we can assume all tallies are spatially + // separate, this implies that once a tally has been scored to, we needn't + // check the others. This cuts down on overhead when there are many + // tallies specified + if (settings::assume_separate) break; + } + + // Reset all the filter matches for the next tally event. + for (auto& match : simulation::filter_matches) + match.bins_present_ = false; +} + +//! Score tallies using a tracklength estimate of the flux. +// +//! This is triggered at every event (surface crossing, lattice crossing, or +//! collision) and thus cannot be done for tallies that require post-collision +//! information. + +extern "C" void +score_tracklength_tally(Particle* p, double distance) +{ + // Determine the tracklength estimate of the flux + double flux = p->wgt * distance; + + for (auto i_tally : model::active_tracklength_tallies) { + //TODO: off-by-one + const Tally& tally {*model::tallies[i_tally-1]}; + + // Initialize an iterator over valid filter bin combinations. If there are + // no valid combinations, use a continue statement to ensure we skip the + // assume_separate break below. + auto filter_iter = FilterBinIter(tally, p); + auto end = FilterBinIter(tally, true); + if (filter_iter == end) continue; + + // Loop over filter bins. + for (; filter_iter != end; ++filter_iter) { + auto filter_index = filter_iter.index_; + auto filter_weight = filter_iter.weight_; + + // Loop over nuclide bins. + if (tally.all_nuclides_) { + if (p->material != MATERIAL_VOID) + score_all_nuclides(p, i_tally, flux*filter_weight, filter_index); + + } else { + for (auto i = 0; i < tally.nuclides_.size(); ++i) { + auto i_nuclide = tally.nuclides_[i]; + + double atom_density = 0.; + if (i_nuclide >= 0) { + if (p->material != MATERIAL_VOID) { + //TODO: off-by-one + auto j = model::materials[p->material-1] + ->mat_nuclide_index_[i_nuclide]; + if (j == C_NONE) continue; + //atom_density = material_atom_density(p->material, j); + //TODO: off-by-one + atom_density = model::materials[p->material-1]->atom_density_(j); + } + } + + //TODO: consider replacing this "if" with pointers or templates + if (settings::run_CE) { + score_general_ce(p, i_tally, i*tally.scores_.size(), filter_index, + i_nuclide, atom_density, flux*filter_weight); + } else { + score_general_mg(p, i_tally, i*tally.scores_.size(), filter_index, + i_nuclide, atom_density, flux*filter_weight); + } + } + } + + } + + // If the user has specified that we can assume all tallies are spatially + // separate, this implies that once a tally has been scored to, we needn't + // check the others. This cuts down on overhead when there are many + // tallies specified + if (settings::assume_separate) break; + } + + // Reset all the filter matches for the next tally event. + for (auto& match : simulation::filter_matches) + match.bins_present_ = false; +} + +//! Score tallies using a 1 / Sigma_t estimate of the flux. +// +//! This is triggered after every collision. It is invalid for tallies that +//! require post-collison information because it can score reactions that didn't +//! actually occur, and we don't a priori know what the outcome will be for +//! reactions that we didn't sample. It is assumed the material is not void +//! since collisions do not occur in voids. + +extern "C" void +score_collision_tally(Particle* p) +{ + // Determine the collision estimate of the flux + double flux; + if (!settings::survival_biasing) { + flux = p->last_wgt / simulation::material_xs.total; + } else { + flux = (p->last_wgt + p->absorb_wgt) / simulation::material_xs.total; + } + + for (auto i_tally : model::active_collision_tallies) { + //TODO: off-by-one + const Tally& tally {*model::tallies[i_tally-1]}; + + // Initialize an iterator over valid filter bin combinations. If there are + // no valid combinations, use a continue statement to ensure we skip the + // assume_separate break below. + auto filter_iter = FilterBinIter(tally, p); + auto end = FilterBinIter(tally, true); + if (filter_iter == end) continue; + + // Loop over filter bins. + for (; filter_iter != end; ++filter_iter) { + auto filter_index = filter_iter.index_; + auto filter_weight = filter_iter.weight_; + + // Loop over nuclide bins. + if (tally.all_nuclides_) { + score_all_nuclides(p, i_tally, flux*filter_weight, filter_index); + + } else { + for (auto i = 0; i < tally.nuclides_.size(); ++i) { + auto i_nuclide = tally.nuclides_[i]; + + double atom_density = 0.; + if (i_nuclide >= 0) { + //TODO: off-by-one + auto j = model::materials[p->material-1] + ->mat_nuclide_index_[i_nuclide]; + if (j == C_NONE) continue; + //atom_density = material_atom_density(p->material, j); + //TODO: off-by-one + atom_density = model::materials[p->material-1]->atom_density_(j); + } + + //TODO: consider replacing this "if" with pointers or templates + if (settings::run_CE) { + score_general_ce(p, i_tally, i*tally.scores_.size(), filter_index, + i_nuclide, atom_density, flux*filter_weight); + } else { + score_general_mg(p, i_tally, i*tally.scores_.size(), filter_index, + i_nuclide, atom_density, flux*filter_weight); + } + } + } + } + + // If the user has specified that we can assume all tallies are spatially + // separate, this implies that once a tally has been scored to, we needn't + // check the others. This cuts down on overhead when there are many + // tallies specified + if (settings::assume_separate) break; + } + + // Reset all the filter matches for the next tally event. + for (auto& match : simulation::filter_matches) + match.bins_present_ = false; +} + +//! Score surface or mesh-surface tallies for particle currents. + +static void +score_surface_tally_inner(Particle* p, const std::vector& tallies) +{ + // No collision, so no weight change when survival biasing + double flux = p->wgt; + + for (auto i_tally : tallies) { + //TODO: off-by-one + const Tally& tally {*model::tallies[i_tally-1]}; + auto results = tally_results(i_tally); + + // Initialize an iterator over valid filter bin combinations. If there are + // no valid combinations, use a continue statement to ensure we skip the + // assume_separate break below. + auto filter_iter = FilterBinIter(tally, p); + auto end = FilterBinIter(tally, true); + if (filter_iter == end) continue; + + // Loop over filter bins. + for (; filter_iter != end; ++filter_iter) { + auto filter_index = filter_iter.index_; + auto filter_weight = filter_iter.weight_; + + // Loop over scores. + // There is only one score type for current tallies so there is no need + // for a further scoring function. + double score = flux * filter_weight; + for (auto score_index = 0; score_index < tally.scores_.size(); + ++score_index) { + //TODO: off-by-one + #pragma omp atomic + results(filter_index-1, score_index, RESULT_VALUE) += score; + } + } + + // If the user has specified that we can assume all tallies are spatially + // separate, this implies that once a tally has been scored to, we needn't + // check the others. This cuts down on overhead when there are many + // tallies specified + if (settings::assume_separate) break; + } + + // Reset all the filter matches for the next tally event. + for (auto& match : simulation::filter_matches) + match.bins_present_ = false; +} + +//! Score mesh-surface tallies for particle currents. + +extern "C" void +score_meshsurface_tally(Particle* p) +{ + score_surface_tally_inner(p, model::active_meshsurf_tallies); +} + +//! Score surface tallies for particle currents. + +extern "C" void +score_surface_tally(Particle* p) +{ + score_surface_tally_inner(p, model::active_surface_tallies); +} + +} // namespace openmc From 8b3be55f0e181ca3dc297d6de9358531f461d0e1 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Mon, 11 Feb 2019 15:34:55 -0500 Subject: [PATCH 47/58] Use more 0-based indexing for tallies --- src/output.cpp | 2 +- src/simulation.cpp | 2 +- src/state_point.cpp | 8 ++- src/tallies/derivative.cpp | 3 +- src/tallies/tally.F90 | 2 +- src/tallies/tally.cpp | 31 +++++----- src/tallies/tally_header.F90 | 14 ----- src/tallies/tally_scoring.cpp | 106 +++++++++++++++------------------- src/tallies/trigger.cpp | 11 ++-- 9 files changed, 77 insertions(+), 102 deletions(-) diff --git a/src/output.cpp b/src/output.cpp index 3dac0238b4..2a7a1c6a66 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -299,7 +299,7 @@ write_tallies() // Loop over each tally. for (auto i_tally = 0; i_tally < model::tallies.size(); ++i_tally) { const auto& tally {*model::tallies[i_tally]}; - auto results = tally_results(i_tally+1); + auto results = tally_results(i_tally); // TODO: get this directly from the tally object when it's been translated int32_t n_realizations; auto err = openmc_tally_get_n_realizations(i_tally+1, &n_realizations); diff --git a/src/simulation.cpp b/src/simulation.cpp index 978f57e0f0..89ad54c4f6 100644 --- a/src/simulation.cpp +++ b/src/simulation.cpp @@ -546,7 +546,7 @@ void calculate_work() #ifdef OPENMC_MPI void broadcast_results() { // Broadcast tally results so that each process has access to results - for (int i = 1; i <= n_tallies; ++i) { + for (int i = 0; i < n_tallies; ++i) { // Create a new datatype that consists of all values for a given filter // bin and then use that to broadcast. This is done to minimize the // chance of the 'count' argument of MPI_BCAST exceeding 2**31 diff --git a/src/state_point.cpp b/src/state_point.cpp index a808e3ef59..aa1fc0e65e 100644 --- a/src/state_point.cpp +++ b/src/state_point.cpp @@ -249,10 +249,11 @@ void write_tally_results_nr(hid_t file_id) write_dataset(file_id, "global_tallies", gt); } - for (int i = 1; i <= n_tallies; ++i) { + for (int i = 0; i < n_tallies; ++i) { // Skip any tallies that are not active bool active; - openmc_tally_get_active(i, &active); + // TODO: off-by-one + openmc_tally_get_active(i+1, &active); if (!active) continue; if (mpi::master && !object_exists(file_id, "tallies_present")) { @@ -270,7 +271,8 @@ void write_tally_results_nr(hid_t file_id) if (mpi::master) { // Open group for tally int id; - openmc_tally_get_id(i, &id); + // TODO: off-by-one + openmc_tally_get_id(i+1, &id); std::string groupname {"tally " + std::to_string(id)}; hid_t tally_group = open_group(tallies_group, groupname.c_str()); diff --git a/src/tallies/derivative.cpp b/src/tallies/derivative.cpp index b0ff83ea3a..df65b0751a 100644 --- a/src/tallies/derivative.cpp +++ b/src/tallies/derivative.cpp @@ -110,8 +110,7 @@ void apply_derivative_to_score(Particle* p, int i_tally, int i_nuclide, double atom_density, int score_bin, double* score) { - //TODO: off-by-one - const Tally& tally {*model::tallies[i_tally-1]}; + const Tally& tally {*model::tallies[i_tally]}; if (*score == 0) return; diff --git a/src/tallies/tally.F90 b/src/tallies/tally.F90 index d4fb204554..6d2c83ab22 100644 --- a/src/tallies/tally.F90 +++ b/src/tallies/tally.F90 @@ -152,7 +152,7 @@ contains ! Accumulate results for each tally do i = 1, active_tallies_size() - call tallies(active_tallies_data(i)) % obj % accumulate() + call tallies(active_tallies_data(i)+1) % obj % accumulate() end do end subroutine accumulate_tallies diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 5343b7cec0..5f6287b890 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -233,12 +233,12 @@ Tally::set_filters(const int32_t filter_indices[], int n) if (i_filt < 0 || i_filt >= model::tally_filters.size()) throw std::out_of_range("Index in tally filter array out of bounds."); - //TODO: off-by-one on each index + // Keep track of indices for special filters. const auto* filt = model::tally_filters[i_filt].get(); if (dynamic_cast(filt)) { - energyout_filter_ = i + 1; + energyout_filter_ = i; } else if (dynamic_cast(filt)) { - delayedgroup_filter_ = i + 1; + delayedgroup_filter_ = i; } } @@ -274,7 +274,6 @@ Tally::set_scores(std::vector scores) // Check for the presence of certain restrictive filters. bool energyout_present = energyout_filter_ != C_NONE; - //bool delayedgroup_present = false; bool legendre_present = false; bool cell_present = false; bool cellfrom_present = false; @@ -531,7 +530,8 @@ adaptor_type<3> tally_results(int idx) // Get pointer to tally results double* results; std::array shape; - openmc_tally_results(idx, &results, shape.data()); + // TODO: off-by-one + openmc_tally_results(idx+1, &results, shape.data()); // Adapt array into xtensor with no ownership std::size_t size {shape[0] * shape[1] * shape[2]}; @@ -541,10 +541,11 @@ adaptor_type<3> tally_results(int idx) #ifdef OPENMC_MPI void reduce_tally_results() { - for (int i = 1; i <= n_tallies; ++i) { + for (int i = 0; i < n_tallies; ++i) { // Skip any tallies that are not active bool active; - openmc_tally_get_active(i, &active); + // TODO: off-by-one + openmc_tally_get_active(i+1, &active); if (!active) continue; // Get view of accumulated tally values @@ -605,33 +606,32 @@ setup_active_tallies_c() model::active_meshsurf_tallies.clear(); model::active_surface_tallies.clear(); - //TODO: off-by-one all through here for (auto i = 0; i < model::tallies.size(); ++i) { const auto& tally {*model::tallies[i]}; if (tally.active_) { - model::active_tallies.push_back(i + 1); + model::active_tallies.push_back(i); switch (tally.type_) { case TALLY_VOLUME: switch (tally.estimator_) { case ESTIMATOR_ANALOG: - model::active_analog_tallies.push_back(i + 1); + model::active_analog_tallies.push_back(i); break; case ESTIMATOR_TRACKLENGTH: - model::active_tracklength_tallies.push_back(i + 1); + model::active_tracklength_tallies.push_back(i); break; case ESTIMATOR_COLLISION: - model::active_collision_tallies.push_back(i + 1); + model::active_collision_tallies.push_back(i); } break; case TALLY_MESH_SURFACE: - model::active_meshsurf_tallies.push_back(i + 1); + model::active_meshsurf_tallies.push_back(i); break; case TALLY_SURFACE: - model::active_surface_tallies.push_back(i + 1); + model::active_surface_tallies.push_back(i); } } } @@ -916,9 +916,6 @@ extern "C" { int tally_get_energyout_filter_c(Tally* tally) {return tally->energyout_filter_;} - int tally_get_delayedgroup_filter_c(Tally* tally) - {return tally->delayedgroup_filter_;} - void tally_set_scores(Tally* tally, pugi::xml_node* node) {tally->set_scores(*node);} diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index d857a726ef..8784b63b43 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -130,7 +130,6 @@ module tally_header procedure :: n_nuclide_bins => tally_get_n_nuclide_bins procedure :: nuclide_bins => tally_get_nuclide_bins procedure :: energyout_filter => tally_get_energyout_filter - procedure :: delayedgroup_filter => tally_get_delayedgroup_filter procedure :: deriv => tally_get_deriv procedure :: set_deriv => tally_set_deriv end type TallyObject @@ -506,19 +505,6 @@ contains filt = tally_get_energyout_filter_c(this % ptr) end function - function tally_get_delayedgroup_filter(this) result(filt) - class(TallyObject) :: this - integer(C_INT) :: filt - interface - function tally_get_delayedgroup_filter_c(tally) result(filt) bind(C) - import C_PTR, C_INT - type(C_PTR), value :: tally - integer(C_INT) :: filt - end function - end interface - filt = tally_get_delayedgroup_filter_c(this % ptr) - end function - function tally_get_deriv(this) result(deriv) class(TallyObject) :: this integer(C_INT) :: deriv diff --git a/src/tallies/tally_scoring.cpp b/src/tallies/tally_scoring.cpp index 991b4cc279..770ab78681 100644 --- a/src/tallies/tally_scoring.cpp +++ b/src/tallies/tally_scoring.cpp @@ -146,9 +146,8 @@ void score_fission_delayed_dg(int i_tally, int d_bin, double score, int score_index) { // Save the original delayed group bin - //TODO: off-by-one - const Tally& tally {*model::tallies[i_tally-1]}; - auto i_filt = tally.filters(tally.delayedgroup_filter_-1); + const Tally& tally {*model::tallies[i_tally]}; + auto i_filt = tally.filters(tally.delayedgroup_filter_); auto& dg_match {simulation::filter_matches[i_filt]}; auto i_bin = dg_match.i_bin_; auto original_bin = dg_match.bins_[i_bin-1]; @@ -182,10 +181,9 @@ score_fission_delayed_dg(int i_tally, int d_bin, double score, int score_index) void score_fission_eout(Particle* p, int i_tally, int i_score, int score_bin) { - //TODO: off-by-one - const Tally& tally {*model::tallies[i_tally-1]}; + const Tally& tally {*model::tallies[i_tally]}; auto results = tally_results(i_tally); - auto i_eout_filt = tally.filters()[tally.energyout_filter_-1]; + auto i_eout_filt = tally.filters()[tally.energyout_filter_]; auto i_bin = simulation::filter_matches[i_eout_filt].i_bin_; auto bin_energyout = simulation::filter_matches[i_eout_filt].bins_[i_bin-1]; @@ -268,7 +266,7 @@ score_fission_eout(Particle* p, int i_tally, int i_score, int score_bin) } else if (score_bin == SCORE_DELAYED_NU_FISSION && g != 0) { // Get the index of the delayed group filter - auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_]; // If the delayed group filter is present, tally to corresponding delayed // group bin if it exists @@ -330,8 +328,7 @@ void score_general_ce(Particle* p, int i_tally, int start_index, int filter_index, int i_nuclide, double atom_density, double flux) { - //TODO: off-by-one - const Tally& tally {*model::tallies[i_tally-1]}; + const Tally& tally {*model::tallies[i_tally]}; auto results = tally_results(i_tally); // Get the pre-collision energy of the particle. @@ -520,7 +517,7 @@ score_general_ce(Particle* p, int i_tally, int start_index, int filter_index, if (simulation::material_xs.absorption == 0) continue; if (tally.estimator_ == ESTIMATOR_ANALOG) { if (settings::survival_biasing || p->fission) { - if (tally.energyout_filter_ > 0) { + if (tally.energyout_filter_ != C_NONE) { // Fission has multiple outgoing neutrons so this helper function // is used to handle scoring the multiple filter bins. score_fission_eout(p, i_tally, score_index, score_bin); @@ -563,7 +560,7 @@ score_general_ce(Particle* p, int i_tally, int start_index, int filter_index, if (simulation::material_xs.absorption == 0) continue; if (tally.estimator_ == ESTIMATOR_ANALOG) { if (settings::survival_biasing || p->fission) { - if (tally.energyout_filter_ > 0) { + if (tally.energyout_filter_ != C_NONE) { // Fission has multiple outgoing neutrons so this helper function // is used to handle scoring the multiple filter bins. score_fission_eout(p, i_tally, score_index, score_bin); @@ -625,7 +622,7 @@ score_general_ce(Particle* p, int i_tally, int start_index, int filter_index, if (simulation::material_xs.absorption == 0) continue; if (tally.estimator_ == ESTIMATOR_ANALOG) { if (settings::survival_biasing || p->fission) { - if (tally.energyout_filter_ > 0) { + if (tally.energyout_filter_ != C_NONE) { // Fission has multiple outgoing neutrons so this helper function // is used to handle scoring the multiple filter bins. score_fission_eout(p, i_tally, score_index, score_bin); @@ -638,8 +635,8 @@ score_general_ce(Particle* p, int i_tally, int start_index, int filter_index, // delayed-nu-fission if (simulation::micro_xs[p->event_nuclide-1].absorption > 0 && data::nuclides[p->event_nuclide-1]->fissionable_) { - if (tally.delayedgroup_filter_ > 0) { - auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + if (tally.delayedgroup_filter_ != C_NONE) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_]; const DelayedGroupFilter& filt {*dynamic_cast( model::tally_filters[i_dg_filt].get())}; @@ -676,8 +673,8 @@ score_general_ce(Particle* p, int i_tally, int start_index, int filter_index, // score. Loop over the neutrons produced from fission and check which // ones are delayed. If a delayed neutron is encountered, add its // contribution to the fission bank to the score. - if (tally.delayedgroup_filter_ > 0) { - auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + if (tally.delayedgroup_filter_ != C_NONE) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_]; const DelayedGroupFilter& filt {*dynamic_cast( model::tally_filters[i_dg_filt].get())}; @@ -699,8 +696,8 @@ score_general_ce(Particle* p, int i_tally, int start_index, int filter_index, } } else { if (i_nuclide >= 0) { - if (tally.delayedgroup_filter_ > 0) { - auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + if (tally.delayedgroup_filter_ != C_NONE) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_]; const DelayedGroupFilter& filt {*dynamic_cast( model::tally_filters[i_dg_filt].get())}; @@ -723,8 +720,8 @@ score_general_ce(Particle* p, int i_tally, int start_index, int filter_index, * atom_density * flux; } } else { - if (tally.delayedgroup_filter_ > 0) { - auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + if (tally.delayedgroup_filter_ != C_NONE) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_]; const DelayedGroupFilter& filt {*dynamic_cast( model::tally_filters[i_dg_filt].get())}; @@ -776,8 +773,8 @@ score_general_ce(Particle* p, int i_tally, int start_index, int filter_index, if (simulation::micro_xs[p->event_nuclide-1].absorption > 0 && nuc.fissionable_) { const auto& rxn {*nuc.fission_rx_[0]}; - if (tally.delayedgroup_filter_ > 0) { - auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + if (tally.delayedgroup_filter_ != C_NONE) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_]; const DelayedGroupFilter& filt {*dynamic_cast( model::tally_filters[i_dg_filt].get())}; @@ -836,8 +833,8 @@ score_general_ce(Particle* p, int i_tally, int start_index, int filter_index, const auto& rxn {*nuc.fission_rx_[0]}; auto rate = rxn.products_[g].decay_rate_; score += simulation::keff * bank.wgt * rate * flux; - if (tally.delayedgroup_filter_ > 0) { - auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + if (tally.delayedgroup_filter_ != C_NONE) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_]; const DelayedGroupFilter& filt {*dynamic_cast( model::tally_filters[i_dg_filt].get())}; @@ -858,8 +855,8 @@ score_general_ce(Particle* p, int i_tally, int start_index, int filter_index, const auto& nuc {*data::nuclides[i_nuclide]}; if (!nuc.fissionable_) continue; const auto& rxn {*nuc.fission_rx_[0]}; - if (tally.delayedgroup_filter_ > 0) { - auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + if (tally.delayedgroup_filter_ != C_NONE) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_]; const DelayedGroupFilter& filt {*dynamic_cast( model::tally_filters[i_dg_filt].get())}; @@ -890,8 +887,8 @@ score_general_ce(Particle* p, int i_tally, int start_index, int filter_index, } } } else { - if (tally.delayedgroup_filter_ > 0) { - auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + if (tally.delayedgroup_filter_ != C_NONE) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_]; const DelayedGroupFilter& filt {*dynamic_cast( model::tally_filters[i_dg_filt].get())}; @@ -1238,8 +1235,7 @@ void score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, int i_nuclide, double atom_density, double flux) { - //TODO: off-by-one - const Tally& tally {*model::tallies[i_tally-1]}; + const Tally& tally {*model::tallies[i_tally]}; auto results = tally_results(i_tally); //TODO: off-by-one throughout on p->material @@ -1503,7 +1499,7 @@ score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, case SCORE_NU_FISSION: if (tally.estimator_ == ESTIMATOR_ANALOG) { if (settings::survival_biasing || p->fission) { - if (tally.energyout_filter_ > 0) { + if (tally.energyout_filter_ != C_NONE) { // Fission has multiple outgoing neutrons so this helper function // is used to handle scoring the multiple filter bins. score_fission_eout(p, i_tally, score_index, score_bin); @@ -1553,7 +1549,7 @@ score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, case SCORE_PROMPT_NU_FISSION: if (tally.estimator_ == ESTIMATOR_ANALOG) { if (settings::survival_biasing || p->fission) { - if (tally.energyout_filter_ > 0) { + if (tally.energyout_filter_ != C_NONE) { // Fission has multiple outgoing neutrons so this helper function // is used to handle scoring the multiple filter bins. score_fission_eout(p, i_tally, score_index, score_bin); @@ -1607,7 +1603,7 @@ score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, case SCORE_DELAYED_NU_FISSION: if (tally.estimator_ == ESTIMATOR_ANALOG) { if (settings::survival_biasing || p->fission) { - if (tally.energyout_filter_ > 0) { + if (tally.energyout_filter_ != C_NONE) { // Fission has multiple outgoing neutrons so this helper function // is used to handle scoring the multiple filter bins. score_fission_eout(p, i_tally, score_index, score_bin); @@ -1619,8 +1615,8 @@ score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, // calculate fraction of absorptions that would have resulted in // delayed-nu-fission if (get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g) > 0) { - if (tally.delayedgroup_filter_ > 0) { - auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + if (tally.delayedgroup_filter_ != C_NONE) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_]; const DelayedGroupFilter& filt {*dynamic_cast( model::tally_filters[i_dg_filt].get())}; @@ -1669,8 +1665,8 @@ score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, // score. Loop over the neutrons produced from fission and check which // ones are delayed. If a delayed neutron is encountered, add its // contribution to the fission bank to the score. - if (tally.delayedgroup_filter_ > 0) { - auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + if (tally.delayedgroup_filter_ != C_NONE) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_]; const DelayedGroupFilter& filt {*dynamic_cast( model::tally_filters[i_dg_filt].get())}; @@ -1701,8 +1697,8 @@ score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, } } } else { - if (tally.delayedgroup_filter_ > 0) { - auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + if (tally.delayedgroup_filter_ != C_NONE) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_]; const DelayedGroupFilter& filt {*dynamic_cast( model::tally_filters[i_dg_filt].get())}; @@ -1741,8 +1737,8 @@ score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, // calculate fraction of absorptions that would have resulted in // delayed-nu-fission if (get_macro_xs(p->material, MG_GET_XS_ABSORPTION, p_g) > 0) { - if (tally.delayedgroup_filter_ > 0) { - auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + if (tally.delayedgroup_filter_ != C_NONE) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_]; const DelayedGroupFilter& filt {*dynamic_cast( model::tally_filters[i_dg_filt].get())}; @@ -1821,8 +1817,8 @@ score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, * get_macro_xs(p->material, MG_GET_XS_DECAY_RATE, p_g, nullptr, nullptr, &g); } - if (tally.delayedgroup_filter_ > 0) { - auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + if (tally.delayedgroup_filter_ != C_NONE) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_]; const DelayedGroupFilter& filt {*dynamic_cast( model::tally_filters[i_dg_filt].get())}; @@ -1837,11 +1833,11 @@ score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, } } } - if (tally.delayedgroup_filter_ > 0) continue; + if (tally.delayedgroup_filter_ != C_NONE) continue; } } else { - if (tally.delayedgroup_filter_ > 0) { - auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_-1]; + if (tally.delayedgroup_filter_ != C_NONE) { + auto i_dg_filt = tally.filters()[tally.delayedgroup_filter_]; const DelayedGroupFilter& filt {*dynamic_cast( model::tally_filters[i_dg_filt].get())}; @@ -1943,8 +1939,7 @@ score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, void score_all_nuclides(Particle* p, int i_tally, double flux, int filter_index) { - //TODO: off-by-one - const Tally& tally {*model::tallies[i_tally-1]}; + const Tally& tally {*model::tallies[i_tally]}; const Material& material {*model::materials[p->material-1]}; // Score all individual nuclide reaction rates. @@ -1984,8 +1979,7 @@ extern "C" void score_analog_tally_ce(Particle* p) { for (auto i_tally : model::active_analog_tallies) { - //TODO: off-by-one - const Tally& tally {*model::tallies[i_tally-1]}; + const Tally& tally {*model::tallies[i_tally]}; // Initialize an iterator over valid filter bin combinations. If there are // no valid combinations, use a continue statement to ensure we skip the @@ -2049,8 +2043,7 @@ extern "C" void score_analog_tally_mg(Particle* p) { for (auto i_tally : model::active_analog_tallies) { - //TODO: off-by-one - const Tally& tally {*model::tallies[i_tally-1]}; + const Tally& tally {*model::tallies[i_tally]}; // Initialize an iterator over valid filter bin combinations. If there are // no valid combinations, use a continue statement to ensure we skip the @@ -2109,8 +2102,7 @@ score_tracklength_tally(Particle* p, double distance) double flux = p->wgt * distance; for (auto i_tally : model::active_tracklength_tallies) { - //TODO: off-by-one - const Tally& tally {*model::tallies[i_tally-1]}; + const Tally& tally {*model::tallies[i_tally]}; // Initialize an iterator over valid filter bin combinations. If there are // no valid combinations, use a continue statement to ensure we skip the @@ -2191,8 +2183,7 @@ score_collision_tally(Particle* p) } for (auto i_tally : model::active_collision_tallies) { - //TODO: off-by-one - const Tally& tally {*model::tallies[i_tally-1]}; + const Tally& tally {*model::tallies[i_tally]}; // Initialize an iterator over valid filter bin combinations. If there are // no valid combinations, use a continue statement to ensure we skip the @@ -2258,8 +2249,7 @@ score_surface_tally_inner(Particle* p, const std::vector& tallies) double flux = p->wgt; for (auto i_tally : tallies) { - //TODO: off-by-one - const Tally& tally {*model::tallies[i_tally-1]}; + const Tally& tally {*model::tallies[i_tally]}; auto results = tally_results(i_tally); // Initialize an iterator over valid filter bin combinations. If there are diff --git a/src/tallies/trigger.cpp b/src/tallies/trigger.cpp index ac69545c61..25bb0c8c04 100644 --- a/src/tallies/trigger.cpp +++ b/src/tallies/trigger.cpp @@ -30,7 +30,8 @@ static std::pair get_tally_uncertainty(int i_tally, int score_index, int filter_index) { int n; - int err = openmc_tally_get_n_realizations(i_tally, &n); + //TODO: off-by-one + int err = openmc_tally_get_n_realizations(i_tally+1, &n); auto results = tally_results(i_tally); auto sum = results(filter_index, score_index, RESULT_SUM); @@ -54,13 +55,13 @@ void check_tally_triggers(double& ratio, int& tally_id, int& score) { ratio = 0.; - //TODO: off-by-one - for (auto i_tally = 1; i_tally < model::tallies.size()+1; ++i_tally) { - const Tally& t {*model::tallies[i_tally-1]}; + for (auto i_tally = 0; i_tally < model::tallies.size(); ++i_tally) { + const Tally& t {*model::tallies[i_tally]}; // Ignore tallies with less than two realizations. int n_reals; - int err = openmc_tally_get_n_realizations(i_tally, &n_reals); + //TODO: off-by-one + int err = openmc_tally_get_n_realizations(i_tally+1, &n_reals); if (n_reals < 2) continue; for (const auto& trigger : t.triggers_) { From a8a7d9112f831225fb9154001ffe5895185d88f4 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Mon, 11 Feb 2019 15:59:36 -0500 Subject: [PATCH 48/58] Use 0-based indexing for FilterMatch.i_bin_ --- src/output.cpp | 3 +- src/simulation.F90 | 11 +-- src/tallies/filter.cpp | 16 ---- src/tallies/tally_filter_header.F90 | 127 ---------------------------- src/tallies/tally_scoring.cpp | 38 ++++----- 5 files changed, 22 insertions(+), 173 deletions(-) diff --git a/src/output.cpp b/src/output.cpp index 2a7a1c6a66..efc4ba5196 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -355,7 +355,8 @@ write_tallies() const auto& filt {*model::tally_filters[i_filt]}; auto& match {simulation::filter_matches[i_filt]}; tallies_out << std::string(indent+1, ' ') - << filt.text_label(match.i_bin_) << "\n"; + // TODO: off-by-one + << filt.text_label(match.i_bin_+1) << "\n"; } indent += 2; } diff --git a/src/simulation.F90 b/src/simulation.F90 index 3de296f9db..54cd9e6d51 100644 --- a/src/simulation.F90 +++ b/src/simulation.F90 @@ -4,7 +4,6 @@ module simulation use nuclide_header, only: micro_xs, n_nuclides use photon_header, only: micro_photon_xs, n_elements - use tally_filter_header, only: filter_matches, n_filters, filter_match_pointer implicit none private @@ -23,12 +22,6 @@ contains ! Allocate array for microscopic cross section cache allocate(micro_xs(n_nuclides)) allocate(micro_photon_xs(n_elements)) - - ! Allocate array for matching filter bins - allocate(filter_matches(n_filters)) - do i = 1, n_filters - filter_matches(i) % ptr = filter_match_pointer(i - 1) - end do !$omp end parallel end subroutine @@ -39,12 +32,10 @@ contains !=============================================================================== subroutine simulation_finalize_f() bind(C) - ! Free up simulation-specific memory !$omp parallel - deallocate(micro_xs, micro_photon_xs, filter_matches) + deallocate(micro_xs, micro_photon_xs) !$omp end parallel - end subroutine end module simulation diff --git a/src/tallies/filter.cpp b/src/tallies/filter.cpp index f331e63123..3733ebbf2e 100644 --- a/src/tallies/filter.cpp +++ b/src/tallies/filter.cpp @@ -50,22 +50,6 @@ std::vector> tally_filters; extern "C" { // filter_match_point moved to simulation.cpp - int - filter_match_get_i_bin(FilterMatch* match) - {return match->i_bin_;} - - int - filter_match_bins_data(FilterMatch* match, int indx) - {return match->bins_[indx-1];} - - double - filter_match_weights_data(FilterMatch* match, int indx) - {return match->weights_[indx-1];} - - void - filter_match_bins_set_data(FilterMatch* match, int indx, int val) - {match->bins_[indx-1] = val;} - Filter* allocate_filter(const char* type) { diff --git a/src/tallies/tally_filter_header.F90 b/src/tallies/tally_filter_header.F90 index 2c7daac3ba..65a223bef2 100644 --- a/src/tallies/tally_filter_header.F90 +++ b/src/tallies/tally_filter_header.F90 @@ -19,28 +19,6 @@ module tally_filter_header public :: openmc_filter_set_id public :: openmc_get_filter_index public :: openmc_get_filter_next_id - public :: filter_match_pointer - - interface - function filter_match_pointer(indx) bind(C) result(ptr) - import C_PTR, C_INT - integer(C_INT), intent(in), value :: indx - type(C_PTR) :: ptr - end function filter_match_pointer - end interface - -!=============================================================================== -! TALLYFILTERMATCH stores every valid bin and weight for a filter -!=============================================================================== - - type, public :: TallyFilterMatch - type(C_PTR) :: ptr - contains - procedure :: i_bin - procedure :: bins_data - procedure :: weights_data - procedure :: bins_set_data - end type TallyFilterMatch !=============================================================================== ! TALLYFILTER describes a filter that limits what events score to a tally. For @@ -54,9 +32,7 @@ module tally_filter_header type(C_PTR) :: ptr contains procedure :: from_xml - procedure :: get_all_bins procedure :: to_statepoint - procedure :: text_label procedure :: initialize procedure :: n_bins_cpp procedure :: from_xml_cpp @@ -138,8 +114,6 @@ module tally_filter_header integer(C_INT32_T), public, bind(C) :: n_filters = 0 ! # of filters type(TallyFilterContainer), public, allocatable, target :: filters(:) - type(TallyFilterMatch), public, allocatable :: filter_matches(:) -!$omp threadprivate(filter_matches) ! Dictionary that maps user IDs to indices in 'filters' type(DictIntInt), public :: filter_dict @@ -150,68 +124,6 @@ module tally_filter_header contains -!=============================================================================== -! TallyFilterMatch implementation -!=============================================================================== - - function i_bin(this) result(i) - class(TallyFilterMatch) :: this - integer :: i - interface - function filter_match_get_i_bin(ptr) result(i) bind(C) - import C_PTR, C_INT - type(C_PTR), value :: ptr - integer(C_INT) :: i - end function - end interface - i = filter_match_get_i_bin(this % ptr) - end function - - function bins_data(this, indx) result(val) - class(TallyFilterMatch), intent(inout) :: this - integer, intent(in) :: indx - integer :: val - interface - function filter_match_bins_data(ptr, indx) bind(C) result(val) - import C_PTR, C_INT - type(C_PTR), value :: ptr - integer(C_INT), intent(in), value :: indx - integer(C_INT) :: val - end function - end interface - val = filter_match_bins_data(this % ptr, indx) - end function bins_data - - function weights_data(this, indx) result(val) - class(TallyFilterMatch), intent(inout) :: this - integer, intent(in) :: indx - real(8) :: val - interface - function filter_match_weights_data(ptr, indx) bind(C) result(val) - import C_PTR, C_INT, C_DOUBLE - type(C_PTR), value :: ptr - integer(C_INT), intent(in), value :: indx - real(C_DOUBLE) :: val - end function - end interface - val = filter_match_weights_data(this % ptr, indx) - end function weights_data - - subroutine bins_set_data(this, indx, val) - class(TallyFilterMatch), intent(inout) :: this - integer, intent(in) :: indx - integer, intent(in) :: val - interface - subroutine filter_match_bins_set_data(ptr, indx, val) bind(C) - import C_PTR, C_INT - type(C_PTR), value :: ptr - integer(C_INT), value, intent(in) :: indx - integer(C_INT), value, intent(in) :: val - end subroutine - end interface - call filter_match_bins_set_data(this % ptr, indx, val) - end subroutine bins_set_data - !=============================================================================== ! TallyFilter implementation !=============================================================================== @@ -223,23 +135,6 @@ contains this % n_bins = this % n_bins_cpp() end subroutine from_xml - subroutine get_all_bins(this, p, estimator, match) - class(TallyFilter), intent(in) :: this - type(Particle), intent(in) :: p - integer, intent(in) :: estimator - type(TallyFilterMatch), intent(inout) :: match - interface - subroutine filter_get_all_bins(filt, p, estimator, match) bind(C) - import C_PTR, Particle, C_INT - type(C_PTR), value :: filt - type(Particle), intent(in) :: p - integer(C_INT), intent(in), value :: estimator - type(C_PTR), value :: match - end subroutine filter_get_all_bins - end interface - call filter_get_all_bins(this % ptr, p, estimator, match % ptr) - end subroutine get_all_bins - subroutine to_statepoint(this, filter_group) class(TallyFilter), intent(in) :: this integer(HID_T), intent(in) :: filter_group @@ -253,28 +148,6 @@ contains call filter_to_statepoint(this % ptr, filter_group) end subroutine to_statepoint - function text_label(this, bin) result(label) - class(TallyFilter), intent(in) :: this - integer, intent(in) :: bin - character(MAX_LINE_LEN) :: label - character(kind=C_CHAR) :: label_(MAX_LINE_LEN+1) - integer :: i - interface - subroutine filter_text_label(filt, bin, label) bind(C) - import C_PTR, C_INT, C_CHAR - type(C_PTR), value :: filt - integer(C_INT), value :: bin - character(kind=C_CHAR) :: label(*) - end subroutine filter_text_label - end interface - call filter_text_label(this % ptr, bin, label_) - label = " " - do i = 1, MAX_LINE_LEN - if (label_(i) == C_NULL_CHAR) exit - label(i:i) = label_(i) - end do - end function text_label - subroutine initialize(this) class(TallyFilter), intent(inout) :: this call this % initialize_cpp() diff --git a/src/tallies/tally_scoring.cpp b/src/tallies/tally_scoring.cpp index 770ab78681..77bfcd2738 100644 --- a/src/tallies/tally_scoring.cpp +++ b/src/tallies/tally_scoring.cpp @@ -45,7 +45,7 @@ FilterBinIter::FilterBinIter(const Tally& tally, Particle* p) } // Set the index of the bin used in the first filter combination - match.i_bin_ = 1; + match.i_bin_ = 0; } // Compute the initial index and weight. @@ -79,7 +79,7 @@ FilterBinIter::FilterBinIter(const Tally& tally, bool end) return; } - match.i_bin_ = 1; + match.i_bin_ = 0; } // Compute the initial index and weight. @@ -96,7 +96,7 @@ FilterBinIter::operator++() for (int i = tally_.filters().size()-1; i >= 0; --i) { auto i_filt = tally_.filters(i); auto& match {simulation::filter_matches[i_filt]}; - if (match.i_bin_< match.bins_.size()) { + if (match.i_bin_ < match.bins_.size()-1) { // The bin for this filter can be incremented. Increment it and do not // touch any of the remaining filters. ++match.i_bin_; @@ -105,7 +105,7 @@ FilterBinIter::operator++() } else { // This bin cannot be incremented so reset it and continue to the next // filter. - match.i_bin_ = 1; + match.i_bin_ = 0; } } @@ -131,8 +131,8 @@ FilterBinIter::compute_index_weight() auto& match {simulation::filter_matches[i_filt]}; auto i_bin = match.i_bin_; //TODO: off-by-one - index_ += (match.bins_[i_bin-1] - 1) * tally_.strides(i); - weight_ *= match.weights_[i_bin-1]; + index_ += (match.bins_[i_bin] - 1) * tally_.strides(i); + weight_ *= match.weights_[i_bin]; } } @@ -150,8 +150,8 @@ score_fission_delayed_dg(int i_tally, int d_bin, double score, int score_index) auto i_filt = tally.filters(tally.delayedgroup_filter_); auto& dg_match {simulation::filter_matches[i_filt]}; auto i_bin = dg_match.i_bin_; - auto original_bin = dg_match.bins_[i_bin-1]; - dg_match.bins_[i_bin-1] = d_bin; + auto original_bin = dg_match.bins_[i_bin]; + dg_match.bins_[i_bin] = d_bin; // Determine the filter scoring index auto filter_index = 1; @@ -160,7 +160,7 @@ score_fission_delayed_dg(int i_tally, int d_bin, double score, int score_index) auto& match {simulation::filter_matches[i_filt]}; auto i_bin = match.i_bin_; //TODO: off-by-one - filter_index += (match.bins_[i_bin-1] - 1) * tally.strides(i); + filter_index += (match.bins_[i_bin] - 1) * tally.strides(i); } // Update the tally result @@ -170,7 +170,7 @@ score_fission_delayed_dg(int i_tally, int d_bin, double score, int score_index) results(filter_index-1, score_index, RESULT_VALUE) += score; // Reset the original delayed group bin - dg_match.bins_[i_bin-1] = original_bin; + dg_match.bins_[i_bin] = original_bin; } //! Helper function for nu-fission tallies with energyout filters. @@ -185,7 +185,7 @@ score_fission_eout(Particle* p, int i_tally, int i_score, int score_bin) auto results = tally_results(i_tally); auto i_eout_filt = tally.filters()[tally.energyout_filter_]; auto i_bin = simulation::filter_matches[i_eout_filt].i_bin_; - auto bin_energyout = simulation::filter_matches[i_eout_filt].bins_[i_bin-1]; + auto bin_energyout = simulation::filter_matches[i_eout_filt].bins_[i_bin]; const EnergyoutFilter& eo_filt {*dynamic_cast(model::tally_filters[i_eout_filt].get())}; @@ -222,7 +222,7 @@ score_fission_eout(Particle* p, int i_tally, int i_score, int score_bin) g_out = eo_filt.n_bins_ - g_out + 1; // change outgoing energy bin - simulation::filter_matches[i_eout_filt].bins_[i_bin-1] = g_out; + simulation::filter_matches[i_eout_filt].bins_[i_bin] = g_out; } else { @@ -240,7 +240,7 @@ score_fission_eout(Particle* p, int i_tally, int i_score, int score_bin) //TODO: off-by-one auto i_match = lower_bound_index(eo_filt.bins_.begin(), eo_filt.bins_.end(), E_out) + 1; - simulation::filter_matches[i_eout_filt].bins_[i_bin-1] = i_match; + simulation::filter_matches[i_eout_filt].bins_[i_bin] = i_match; } } @@ -256,7 +256,7 @@ score_fission_eout(Particle* p, int i_tally, int i_score, int score_bin) auto i_filt = tally.filters(j); auto& match {simulation::filter_matches[i_filt]}; auto i_bin = match.i_bin_; - filter_index += (match.bins_[i_bin-1] - 1) * tally.strides(j); + filter_index += (match.bins_[i_bin] - 1) * tally.strides(j); } // Update tally results @@ -284,8 +284,8 @@ score_fission_eout(Particle* p, int i_tally, int i_score, int score_bin) auto i_filt = tally.filters(j); auto& match {simulation::filter_matches[i_filt]}; auto i_bin = match.i_bin_; - filter_index += (match.bins_[i_bin-1] - 1) * tally.strides(j); - filter_weight *= match.weights_[i_bin-1]; + filter_index += (match.bins_[i_bin] - 1) * tally.strides(j); + filter_weight *= match.weights_[i_bin]; } score_fission_delayed_dg(i_tally, d_bin+1, score*filter_weight, @@ -303,8 +303,8 @@ score_fission_eout(Particle* p, int i_tally, int i_score, int score_bin) auto i_filt = tally.filters(j); auto& match {simulation::filter_matches[i_filt]}; auto i_bin = match.i_bin_; - filter_index += (match.bins_[i_bin-1] - 1) * tally.strides(j); - filter_weight *= match.weights_[i_bin-1]; + filter_index += (match.bins_[i_bin] - 1) * tally.strides(j); + filter_weight *= match.weights_[i_bin]; } // Update tally results @@ -315,7 +315,7 @@ score_fission_eout(Particle* p, int i_tally, int i_score, int score_bin) } // Reset outgoing energy bin and score index - simulation::filter_matches[i_eout_filt].bins_[i_bin-1] = bin_energyout; + simulation::filter_matches[i_eout_filt].bins_[i_bin] = bin_energyout; } //! Update tally results for continuous-energy tallies with any estimator. From 49daad3f949819867279169618cd7cbca30980d7 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Mon, 11 Feb 2019 20:22:48 -0500 Subject: [PATCH 49/58] Cleanup C++ tallies work --- include/openmc/tallies/tally.h | 6 +++--- include/openmc/tallies/tally_scoring.h | 11 ++++++++--- include/openmc/tallies/trigger.h | 6 +++--- src/input_xml.F90 | 3 ++- src/tallies/tally.cpp | 19 ++++++------------- src/tallies/tally_header.F90 | 16 ---------------- src/tallies/trigger.cpp | 6 +++--- 7 files changed, 25 insertions(+), 42 deletions(-) diff --git a/include/openmc/tallies/tally.h b/include/openmc/tallies/tally.h index 11ad1124be..0bb2127c0e 100644 --- a/include/openmc/tallies/tally.h +++ b/include/openmc/tallies/tally.h @@ -50,11 +50,11 @@ public: //---------------------------------------------------------------------------- // Major public data members. - int id_; //!< user-defined identifier + int id_; //!< User-defined identifier - std::string name_; //!< user-defined name + std::string name_; //!< User-defined name - int type_ {TALLY_VOLUME}; //!< volume, surface current + int type_ {TALLY_VOLUME}; //!< e.g. volume, surface current //! Event type that contributes to this tally int estimator_ {ESTIMATOR_TRACKLENGTH}; diff --git a/include/openmc/tallies/tally_scoring.h b/include/openmc/tallies/tally_scoring.h index 0db43f08c6..ea415c514b 100644 --- a/include/openmc/tallies/tally_scoring.h +++ b/include/openmc/tallies/tally_scoring.h @@ -12,16 +12,21 @@ namespace openmc { //! This iterator handles two distinct tasks. First, it maps the N-dimensional //! space created by the indices of N filters onto a 1D sequence. In other //! words, it provides a single number that uniquely identifies a combination of -//! bins for many filters. Second, it handles the task of finding each all -//! valid combinations of filter bins given that each filter can have 1 or 2 or -//! many bins that are valid for the current tally event. +//! bins for many filters. Second, it handles the task of finding each valid +//! combination of filter bins given that each filter can have 1 or 2 or many +//! bins that are valid for the current tally event. //============================================================================== class FilterBinIter { public: + + //! Construct an iterator over bins that match a given particle's state. FilterBinIter(const Tally& tally, Particle* p); + //! Construct an iterator over all filter bin combinations. + // + //! \param end if true, the returned iterator indicates the end of a loop. FilterBinIter(const Tally& tally, bool end); bool operator==(const FilterBinIter& other) const diff --git a/include/openmc/tallies/trigger.h b/include/openmc/tallies/trigger.h index f4f44e20ad..e670297ed1 100644 --- a/include/openmc/tallies/trigger.h +++ b/include/openmc/tallies/trigger.h @@ -19,9 +19,9 @@ enum class TriggerMetric { struct Trigger { - TriggerMetric metric; - double threshold; //!< uncertainty value below which trigger is satisfied - int score_index; //!< index of the relevant score in the tally's arrays + TriggerMetric metric; //!< The type of uncertainty (e.g. std dev) measured + double threshold; //!< Uncertainty value below which trigger is satisfied + int score_index; //!< Index of the relevant score in the tally's arrays }; //! Stops the simulation early if a desired k-effective uncertainty is reached. diff --git a/src/input_xml.F90 b/src/input_xml.F90 index ec4440a127..40795200d8 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -828,7 +828,8 @@ contains ! If settings.xml trigger is turned on, create tally triggers if (trigger_on) then - call tally_init_triggers(t % ptr, i_start + i - 1, node_tal % ptr) + !TODO: off-by-one + call tally_init_triggers(t % ptr, i_start + i - 1 - 1, node_tal % ptr) end if ! ======================================================================= diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 5f6287b890..e9bee044df 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -478,25 +478,20 @@ Tally::init_triggers(pugi::xml_node node, int i_tally) trigger_scores.push_back("all"); } - //TODO: change this when tally scores are moved to C++ - // Get access to the tally's scores. - int* tally_scores; - int n_tally_scores; - auto err = openmc_tally_get_scores(i_tally, &tally_scores, &n_tally_scores); - // Parse the trigger scores and populate the triggers_ vector. + const auto& tally {*model::tallies[i_tally]}; for (auto score_str : trigger_scores) { if (score_str == "all") { - triggers_.reserve(triggers_.size() + n_tally_scores); - for (auto i_score = 0; i_score < n_tally_scores; ++i_score) { + triggers_.reserve(triggers_.size() + tally.scores_.size()); + for (auto i_score = 0; i_score < tally.scores_.size(); ++i_score) { triggers_.push_back({metric, threshold, i_score}); } } else { int i_score = 0; - for (; i_score < n_tally_scores; ++i_score) { - if (reaction_name(tally_scores[i_score]) == score_str) break; + for (; i_score < tally.scores_.size(); ++i_score) { + if (reaction_name(tally.scores_[i_score]) == score_str) break; } - if (i_score == n_tally_scores) { + if (i_score == tally.scores_.size()) { std::stringstream msg; msg << "Could not find the score \"" << score_str << "\" in tally " << id_ << " but it was listed in a trigger on that tally"; @@ -902,8 +897,6 @@ extern "C" { int32_t tally_get_filter_c(Tally* tally, int i) {return tally->filters(i);} - int32_t tally_get_stride_c(Tally* tally, int i) {return tally->strides(i);} - int32_t tally_get_n_filter_bins_c(Tally* tally) {return tally->n_filter_bins();} diff --git a/src/tallies/tally_header.F90 b/src/tallies/tally_header.F90 index 8784b63b43..14ac94107f 100644 --- a/src/tallies/tally_header.F90 +++ b/src/tallies/tally_header.F90 @@ -125,7 +125,6 @@ module tally_header procedure :: score_bins => tally_get_score_bin procedure :: n_filters => tally_get_n_filters procedure :: filter => tally_get_filter - procedure :: stride => tally_get_stride procedure :: n_filter_bins => tally_get_n_filter_bins procedure :: n_nuclide_bins => tally_get_n_nuclide_bins procedure :: nuclide_bins => tally_get_nuclide_bins @@ -437,21 +436,6 @@ contains filt = tally_get_filter_c(this % ptr, i-1) end function - function tally_get_stride(this, i) result(stride) - class(TallyObject) :: this - integer(C_INT) :: i - integer(C_INT32_T) :: stride - interface - function tally_get_stride_c(tally, i) result(stride) bind(C) - import C_PTR, C_INT, C_INT32_T - type(C_PTR), value :: tally - integer(C_INT), value :: i - integer(C_INT32_T) :: stride - end function - end interface - stride = tally_get_stride_c(this % ptr, i-1) - end function - function tally_get_n_filter_bins(this) result(n_filter_bins) class(TallyObject) :: this integer(C_INT32_T) :: n_filter_bins diff --git a/src/tallies/trigger.cpp b/src/tallies/trigger.cpp index 25bb0c8c04..6cd99f1330 100644 --- a/src/tallies/trigger.cpp +++ b/src/tallies/trigger.cpp @@ -23,7 +23,7 @@ namespace settings { } //============================================================================== -// Non-memeber functions +// Non-member functions //============================================================================== static std::pair @@ -44,7 +44,7 @@ get_tally_uncertainty(int i_tally, int score_index, int filter_index) return {std_dev, rel_err}; } -//! Finds the limiting limiting tally trigger. +//! Find the limiting limiting tally trigger. // //! param[out] ratio The uncertainty/threshold ratio for the most limiting //! tally trigger @@ -107,7 +107,7 @@ check_tally_triggers(double& ratio, int& tally_id, int& score) } } -//! Computes the uncertainty/threshold ratio for the eigenvalue trigger. +//! Compute the uncertainty/threshold ratio for the eigenvalue trigger. double check_keff_trigger() From e554ef9c6d885da1c6f79e2a85d4f298c52bc28c Mon Sep 17 00:00:00 2001 From: Jonathan Shimwell Date: Tue, 12 Feb 2019 14:24:20 +0000 Subject: [PATCH 50/58] added context as an argument for downloading --- openmc/_utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openmc/_utils.py b/openmc/_utils.py index d16f556231..4a2f548f6a 100644 --- a/openmc/_utils.py +++ b/openmc/_utils.py @@ -7,7 +7,7 @@ from urllib.request import urlopen _BLOCK_SIZE = 16384 -def download(url, checksum=None): +def download(url, checksum=None, context=None): """Download file from a URL Parameters @@ -16,6 +16,8 @@ def download(url, checksum=None): URL from which to download checksum : str or None MD5 checksum to check against + context : ssl.SSLContext instance or None + For example ssl._create_unverified_context() Returns ------- @@ -23,7 +25,7 @@ def download(url, checksum=None): Name of file written locally """ - req = urlopen(url) + req = urlopen(url, context=context) # Get file size from header file_size = req.length From 59501916ccfc2d979b55b1043aee23e354a469d0 Mon Sep 17 00:00:00 2001 From: Jonathan Shimwell Date: Tue, 12 Feb 2019 15:02:51 +0000 Subject: [PATCH 51/58] added **kwargs to allow flexible downloading --- openmc/_utils.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/openmc/_utils.py b/openmc/_utils.py index 4a2f548f6a..ee3f0d4932 100644 --- a/openmc/_utils.py +++ b/openmc/_utils.py @@ -7,7 +7,7 @@ from urllib.request import urlopen _BLOCK_SIZE = 16384 -def download(url, checksum=None, context=None): +def download(url, checksum=None, **kwargs): """Download file from a URL Parameters @@ -16,8 +16,11 @@ def download(url, checksum=None, context=None): URL from which to download checksum : str or None MD5 checksum to check against - context : ssl.SSLContext instance or None - For example ssl._create_unverified_context() + **kwargs : dict + Optional arguements passed to urlopen() + context: ssl.SSLContext object + Describes the various SSL options, + e.g. context=ssl._create_unverified_context() Returns ------- @@ -25,8 +28,8 @@ def download(url, checksum=None, context=None): Name of file written locally """ - req = urlopen(url, context=context) - + req = urlopen(url, **kwargs) + print(kwargs) # Get file size from header file_size = req.length From d2d30ae97db073de4fa37de783bb86aaf096dce8 Mon Sep 17 00:00:00 2001 From: Jonathan Shimwell Date: Tue, 12 Feb 2019 15:04:40 +0000 Subject: [PATCH 52/58] removed print statment not needed --- openmc/_utils.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openmc/_utils.py b/openmc/_utils.py index ee3f0d4932..1f5632a8da 100644 --- a/openmc/_utils.py +++ b/openmc/_utils.py @@ -29,7 +29,6 @@ def download(url, checksum=None, **kwargs): """ req = urlopen(url, **kwargs) - print(kwargs) # Get file size from header file_size = req.length From b350e7dcd613999cdea8dca14ac8462b5ec3e9e3 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Tue, 12 Feb 2019 13:14:39 -0500 Subject: [PATCH 53/58] Move tally filter IDs to C++ --- include/openmc/tallies/filter.h | 2 ++ src/state_point.F90 | 6 +++--- src/tallies/filter.cpp | 4 ++++ src/tallies/tally_filter_header.F90 | 27 ++++++++++++++++++++++++--- 4 files changed, 33 insertions(+), 6 deletions(-) diff --git a/include/openmc/tallies/filter.h b/include/openmc/tallies/filter.h index 96efa68204..b601685943 100644 --- a/include/openmc/tallies/filter.h +++ b/include/openmc/tallies/filter.h @@ -72,6 +72,8 @@ public: virtual void initialize() {} + int32_t id_; + int n_bins_; }; diff --git a/src/state_point.F90 b/src/state_point.F90 index c96a395a6e..5baf1d7cf4 100644 --- a/src/state_point.F90 +++ b/src/state_point.F90 @@ -90,7 +90,7 @@ contains ! Write IDs of filters allocate(id_array(n_filters)) do i = 1, n_filters - id_array(i) = filters(i) % obj % id + id_array(i) = filters(i) % obj % id() end do call write_attribute(filters_group, "ids", id_array) deallocate(id_array) @@ -98,7 +98,7 @@ contains ! Write filter information FILTER_LOOP: do i = 1, n_filters filter_group = create_group(filters_group, "filter " // & - trim(to_str(filters(i) % obj % id))) + trim(to_str(filters(i) % obj % id()))) call filters(i) % obj % to_statepoint(filter_group) call close_group(filter_group) end do FILTER_LOOP @@ -145,7 +145,7 @@ contains ! Write IDs of filters allocate(id_array(tally % n_filters())) do j = 1, tally % n_filters() - id_array(j) = filters(tally % filter(j) + 1) % obj % id + id_array(j) = filters(tally % filter(j) + 1) % obj % id() end do call write_dataset(tally_group, "filters", id_array) deallocate(id_array) diff --git a/src/tallies/filter.cpp b/src/tallies/filter.cpp index 3733ebbf2e..7670fddf42 100644 --- a/src/tallies/filter.cpp +++ b/src/tallies/filter.cpp @@ -104,6 +104,10 @@ extern "C" { return model::tally_filters.back().get(); } + int32_t filter_get_id(Filter* filt) {return filt->id_;} + + void filter_set_id(Filter* filt, int32_t id) {filt->id_ = id;} + void filter_from_xml(Filter* filt, pugi::xml_node* node) {filt->from_xml(*node);} diff --git a/src/tallies/tally_filter_header.F90 b/src/tallies/tally_filter_header.F90 index 65a223bef2..b67df2ab20 100644 --- a/src/tallies/tally_filter_header.F90 +++ b/src/tallies/tally_filter_header.F90 @@ -27,10 +27,10 @@ module tally_filter_header !=============================================================================== type, public, abstract :: TallyFilter - integer :: id integer :: n_bins = 0 type(C_PTR) :: ptr contains + procedure :: id => filter_get_id_f90 procedure :: from_xml procedure :: to_statepoint procedure :: initialize @@ -128,6 +128,19 @@ contains ! TallyFilter implementation !=============================================================================== + function filter_get_id_f90(this) result(id) + class(TallyFilter) :: this + integer(C_INT32_T) :: id + interface + function filter_get_id(filt) result(id) bind(C) + import C_PTR, C_INT32_T + type(C_PTR), value :: filt + integer(C_INT32_T) :: id + end function + end interface + id = filter_get_id(this % ptr) + end function + subroutine from_xml(this, node) class(TallyFilter), intent(inout) :: this type(XMLNode), intent(in) :: node @@ -285,7 +298,7 @@ contains integer(C_INT) :: err if (index >= 1 .and. index <= n_filters) then - id = filters(index) % obj % id + id = filters(index) % obj % id() err = 0 else err = E_OUT_OF_BOUNDS @@ -300,9 +313,17 @@ contains integer(C_INT32_T), value, intent(in) :: id integer(C_INT) :: err + interface + subroutine filter_set_id(filt, id) bind(C) + import C_PTR, C_INT32_T + type(C_PTR), value :: filt + integer(C_INT32_T), value :: id + end subroutine + end interface + if (index >= 1 .and. index <= n_filters) then if (allocated(filters(index) % obj)) then - filters(index) % obj % id = id + call filter_set_id(filters(index) % obj % ptr, id) call filter_dict % set(id, index) if (id > largest_filter_id) largest_filter_id = id From 558259e4a09f54ac31ff8136add6e4e21df610c8 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Tue, 12 Feb 2019 15:50:54 -0500 Subject: [PATCH 54/58] Write tally info to statepoint from C++ --- src/simulation.F90 | 2 - src/state_point.F90 | 168 +------------------------------------------- src/state_point.cpp | 131 +++++++++++++++++++++++++++++++++- 3 files changed, 130 insertions(+), 171 deletions(-) diff --git a/src/simulation.F90 b/src/simulation.F90 index 54cd9e6d51..f088481dd6 100644 --- a/src/simulation.F90 +++ b/src/simulation.F90 @@ -16,8 +16,6 @@ contains subroutine simulation_init_f() bind(C) - integer :: i - !$omp parallel ! Allocate array for microscopic cross section cache allocate(micro_xs(n_nuclides)) diff --git a/src/state_point.F90 b/src/state_point.F90 index 5baf1d7cf4..33daef4c33 100644 --- a/src/state_point.F90 +++ b/src/state_point.F90 @@ -13,19 +13,10 @@ module state_point use, intrinsic :: ISO_C_BINDING - use constants - use endf, only: reaction_name - use error, only: fatal_error, warning, write_message use hdf5_interface - use message_passing - use mgxs_interface - use nuclide_header, only: nuclides use settings - use simulation_header use string, only: to_str use tally_header - use tally_filter_header - use tally_derivative_header implicit none @@ -38,167 +29,12 @@ contains subroutine statepoint_write_f(file_id) bind(C) integer(HID_T), value :: file_id - integer :: i, j - integer :: i_xs - integer, allocatable :: id_array(:) - integer(HID_T) :: tallies_group, tally_group, & - filters_group, filter_group, derivs_group, & - deriv_group - character(MAX_WORD_LEN), allocatable :: str_array(:) - character(MAX_WORD_LEN, kind=C_CHAR) :: temp_name - type(TallyDerivative), pointer :: deriv + integer :: i + integer(HID_T) :: tallies_group, tally_group ! Open tallies group tallies_group = open_group(file_id, "tallies") - ! Write information for derivatives. - if (n_tally_derivs() > 0) then - derivs_group = create_group(tallies_group, "derivatives") - do i = 0, n_tally_derivs() - 1 - deriv => tally_deriv_c(i) - deriv_group = create_group(derivs_group, "derivative " & - // trim(to_str(deriv % id))) - select case (deriv % variable) - case (DIFF_DENSITY) - call write_dataset(deriv_group, "independent variable", "density") - call write_dataset(deriv_group, "material", deriv % diff_material) - case (DIFF_NUCLIDE_DENSITY) - call write_dataset(deriv_group, "independent variable", & - "nuclide_density") - call write_dataset(deriv_group, "material", deriv % diff_material) - call write_dataset(deriv_group, "nuclide", & - nuclides(deriv % diff_nuclide) % name) - case (DIFF_TEMPERATURE) - call write_dataset(deriv_group, "independent variable", & - "temperature") - call write_dataset(deriv_group, "material", deriv % diff_material) - case default - call fatal_error("Independent variable for derivative " & - // trim(to_str(deriv % id)) // " not defined in & - &state_point.F90.") - end select - call close_group(deriv_group) - end do - call close_group(derivs_group) - end if - - ! Write number of filters - filters_group = create_group(tallies_group, "filters") - call write_attribute(filters_group, "n_filters", n_filters) - - if (n_filters > 0) then - ! Write IDs of filters - allocate(id_array(n_filters)) - do i = 1, n_filters - id_array(i) = filters(i) % obj % id() - end do - call write_attribute(filters_group, "ids", id_array) - deallocate(id_array) - - ! Write filter information - FILTER_LOOP: do i = 1, n_filters - filter_group = create_group(filters_group, "filter " // & - trim(to_str(filters(i) % obj % id()))) - call filters(i) % obj % to_statepoint(filter_group) - call close_group(filter_group) - end do FILTER_LOOP - end if - - call close_group(filters_group) - - ! Write number of tallies - call write_attribute(tallies_group, "n_tallies", n_tallies) - - if (n_tallies > 0) then - ! Write array of tally IDs - allocate(id_array(n_tallies)) - do i = 1, n_tallies - id_array(i) = tallies(i) % obj % id() - end do - call write_attribute(tallies_group, "ids", id_array) - deallocate(id_array) - - ! Write all tally information except results - TALLY_METADATA: do i = 1, n_tallies - - ! Get pointer to tally - associate (tally => tallies(i) % obj) - tally_group = create_group(tallies_group, "tally " // & - trim(to_str(tally % id()))) - - ! Write the name for this tally - call write_dataset(tally_group, "name", tally % name) - - select case(tally % estimator()) - case (ESTIMATOR_ANALOG) - call write_dataset(tally_group, "estimator", "analog") - case (ESTIMATOR_TRACKLENGTH) - call write_dataset(tally_group, "estimator", "tracklength") - case (ESTIMATOR_COLLISION) - call write_dataset(tally_group, "estimator", "collision") - end select - call write_dataset(tally_group, "n_realizations", & - tally % n_realizations) - - call write_dataset(tally_group, "n_filters", tally % n_filters()) - if (tally % n_filters() > 0) then - ! Write IDs of filters - allocate(id_array(tally % n_filters())) - do j = 1, tally % n_filters() - id_array(j) = filters(tally % filter(j) + 1) % obj % id() - end do - call write_dataset(tally_group, "filters", id_array) - deallocate(id_array) - end if - - ! Set up nuclide bin array and then write - allocate(str_array(tally % n_nuclide_bins())) - NUCLIDE_LOOP: do j = 1, tally % n_nuclide_bins() - if (tally % nuclide_bins(j) >= 0) then - if (run_CE) then - i_xs = index(nuclides(tally % nuclide_bins(j)+1) % name, '.') - if (i_xs > 0) then - str_array(j) = nuclides(tally % nuclide_bins(j)+1) % name(1 : i_xs-1) - else - str_array(j) = nuclides(tally % nuclide_bins(j)+1) % name - end if - else - call get_name_c(tally % nuclide_bins(j)+1, len(temp_name), & - temp_name) - i_xs = index(temp_name, '.') - if (i_xs > 0) then - str_array(j) = trim(temp_name(1 : i_xs-1)) - else - str_array(j) = trim(temp_name) - end if - end if - else - str_array(j) = 'total' - end if - end do NUCLIDE_LOOP - call write_dataset(tally_group, "nuclides", str_array) - deallocate(str_array) - - ! Write derivative information. - if (tally % deriv() /= C_NONE) then - deriv => tally_deriv_c(tally % deriv()) - call write_dataset(tally_group, "derivative", deriv % id) - end if - - ! Write scores. - call write_dataset(tally_group, "n_score_bins", tally % n_score_bins()) - allocate(str_array(tally % n_score_bins())) - do j = 1, tally % n_score_bins() - str_array(j) = reaction_name(tally % score_bins(j)) - end do - call write_dataset(tally_group, "score_bins", str_array) - deallocate(str_array) - - call close_group(tally_group) - end associate - end do TALLY_METADATA - end if - if (reduce_tallies) then ! Write global tallies call write_dataset(file_id, "global_tallies", global_tallies) diff --git a/src/state_point.cpp b/src/state_point.cpp index 107ecdaaf9..f86a7b1312 100644 --- a/src/state_point.cpp +++ b/src/state_point.cpp @@ -17,9 +17,12 @@ #include "openmc/hdf5_interface.h" #include "openmc/mesh.h" #include "openmc/message_passing.h" +#include "openmc/mgxs_interface.h" +#include "openmc/nuclide.h" #include "openmc/output.h" #include "openmc/settings.h" #include "openmc/simulation.h" +#include "openmc/tallies/derivative.h" #include "openmc/tallies/filter.h" #include "openmc/tallies/tally.h" #include "openmc/timer.h" @@ -101,13 +104,135 @@ openmc_statepoint_write(const char* filename, bool* write_source) write_attribute(file_id, "source_present", write_source_); // Write out information for eigenvalue run - if (settings::run_mode == RUN_MODE_EIGENVALUE) write_eigenvalue_hdf5(file_id); + if (settings::run_mode == RUN_MODE_EIGENVALUE) + write_eigenvalue_hdf5(file_id); - // Write tally-related data hid_t tallies_group = create_group(file_id, "tallies"); + + // Write meshes meshes_to_hdf5(tallies_group); - statepoint_write_f(file_id); + + // Write information for derivatives + if (!model::tally_derivs.empty()) { + hid_t derivs_group = create_group(tallies_group, "derivatives"); + for (const auto& deriv : model::tally_derivs) { + hid_t deriv_group = create_group(derivs_group, + "derivative " + std::to_string(deriv.id)); + write_dataset(deriv_group, "material", deriv.diff_material); + if (deriv.variable == DIFF_DENSITY) { + write_dataset(deriv_group, "independent variable", "density"); + } else if (deriv.variable == DIFF_NUCLIDE_DENSITY) { + write_dataset(deriv_group, "independent variable", "nuclide_density"); + //TODO: off-by-one + write_dataset(deriv_group, "nuclide", + data::nuclides[deriv.diff_nuclide-1]->name_); + } else if (deriv.variable == DIFF_TEMPERATURE) { + write_dataset(deriv_group, "independent variable", "temperature"); + } else { + fatal_error("Independent variable for derivative " + + std::to_string(deriv.id) + " not defined in state_point.cpp"); + } + close_group(deriv_group); + } + close_group(derivs_group); + } + + // Write information for filters + hid_t filters_group = create_group(tallies_group, "filters"); + write_attribute(filters_group, "n_filters", model::tally_filters.size()); + if (!model::tally_filters.empty()) { + // Write filter IDs + std::vector filter_ids; + filter_ids.reserve(model::tally_filters.size()); + for (const auto& filt : model::tally_filters) + filter_ids.push_back(filt->id_); + write_attribute(filters_group, "ids", filter_ids); + + // Write info for each filter + for (const auto& filt : model::tally_filters) { + hid_t filter_group = create_group(filters_group, + "filter " + std::to_string(filt->id_)); + filt->to_statepoint(filter_group); + close_group(filter_group); + } + } + close_group(filters_group); + + // Write information for tallies + write_attribute(tallies_group, "n_tallies", model::tallies.size()); + if (!model::tallies.empty()) { + // Write tally IDs + std::vector tally_ids; + tally_ids.reserve(model::tallies.size()); + for (const auto& tally : model::tallies) + tally_ids.push_back(tally->id_); + write_attribute(tallies_group, "ids", tally_ids); + + // Write all tally information except results + //TODO: use these two lines when openmc_get_n_realizations isn't needed + //for (const auto& tally_ptr : model::tallies) { + // const auto& tally {*tally_ptr}; + for (auto i_tally = 0; i_tally < model::tallies.size(); ++i_tally) { + const auto& tally {*model::tallies[i_tally]}; + hid_t tally_group = create_group(tallies_group, + "tally " + std::to_string(tally.id_)); + + write_dataset(tally_group, "name", tally.name_); + + if (tally.estimator_ == ESTIMATOR_ANALOG) { + write_dataset(tally_group, "estimator", "analog"); + } else if (tally.estimator_ == ESTIMATOR_TRACKLENGTH) { + write_dataset(tally_group, "estimator", "tracklength"); + } else if (tally.estimator_ == ESTIMATOR_COLLISION) { + write_dataset(tally_group, "estimator", "collision"); + } + + // TODO: get this directly from the tally object when it's moved to C++ + int32_t n_realizations; + auto err = openmc_tally_get_n_realizations(i_tally+1, &n_realizations); + write_dataset(tally_group, "n_realizations", n_realizations); + + // Write the ID of each filter attached to this tally + write_dataset(tally_group, "n_filters", tally.filters().size()); + if (!tally.filters().empty()) { + std::vector filter_ids; + filter_ids.reserve(tally.filters().size()); + for (auto i_filt : tally.filters()) + filter_ids.push_back(model::tally_filters[i_filt]->id_); + write_dataset(tally_group, "filters", filter_ids); + } + + // Write the nuclides this tally scores + std::vector nuclides; + for (auto i_nuclide : tally.nuclides_) { + if (i_nuclide == -1) { + nuclides.push_back("total"); + } else { + if (settings::run_CE) { + nuclides.push_back(data::nuclides[i_nuclide]->name_); + } else { + nuclides.push_back(data::nuclides_MG[i_nuclide].name); + } + } + } + write_dataset(tally_group, "nuclides", nuclides); + + if (tally.deriv_ != C_NONE) write_dataset(tally_group, "derivative", + model::tally_derivs[tally.deriv_].id); + + // Write the tally score bins + std::vector scores; + for (auto sc : tally.scores_) scores.push_back(reaction_name(sc)); + write_dataset(tally_group, "n_score_bins", scores.size()); + write_dataset(tally_group, "score_bins", scores); + + close_group(tally_group); + } + + } + close_group(tallies_group); + statepoint_write_f(file_id); } // Check for the no-tally-reduction method From 8b61bc1a77320312f2a001b5b66e37b2555150b3 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Tue, 12 Feb 2019 17:10:25 -0500 Subject: [PATCH 55/58] Respond to @paulromano comments on #1162 --- include/openmc/mgxs.h | 3 +- include/openmc/mgxs_interface.h | 6 +- include/openmc/tallies/derivative.h | 29 +++--- include/openmc/tallies/tally.h | 18 ++-- include/openmc/tallies/tally_scoring.h | 2 +- include/openmc/tallies/trigger.h | 3 +- src/mgxs.cpp | 3 +- src/mgxs_interface.cpp | 14 +-- src/tallies/derivative.cpp | 133 +++++++++++++------------ src/tallies/tally.cpp | 1 - src/tallies/tally_scoring.cpp | 35 +++---- src/tallies/trigger.cpp | 3 +- 12 files changed, 130 insertions(+), 120 deletions(-) diff --git a/include/openmc/mgxs.h b/include/openmc/mgxs.h index 9d680434ce..145fbcf058 100644 --- a/include/openmc/mgxs.h +++ b/include/openmc/mgxs.h @@ -134,7 +134,8 @@ class Mgxs { //! @param dg delayed group index; use nullptr if irrelevant. //! @return Requested cross section value. double - get_xs(int xstype, int gin, int* gout, double* mu, int* dg); + get_xs(int xstype, int gin, const int* gout, const double* mu, + const int* dg); //! \brief Samples the fission neutron energy and if prompt or delayed. //! diff --git a/include/openmc/mgxs_interface.h b/include/openmc/mgxs_interface.h index 413cdad621..5f5f219e67 100644 --- a/include/openmc/mgxs_interface.h +++ b/include/openmc/mgxs_interface.h @@ -52,14 +52,16 @@ calculate_xs_c(int i_mat, int gin, double sqrtkT, const double uvw[3], double& total_xs, double& abs_xs, double& nu_fiss_xs); double -get_nuclide_xs(int index, int xstype, int gin, int* gout, double* mu, int* dg); +get_nuclide_xs(int index, int xstype, int gin, const int* gout, + const double* mu, const int* dg); inline double get_nuclide_xs(int index, int xstype, int gin) {return get_nuclide_xs(index, xstype, gin, nullptr, nullptr, nullptr);} double -get_macro_xs(int index, int xstype, int gin, int* gout, double* mu, int* dg); +get_macro_xs(int index, int xstype, int gin, const int* gout, + const double* mu, const int* dg); inline double get_macro_xs(int index, int xstype, int gin) diff --git a/include/openmc/tallies/derivative.h b/include/openmc/tallies/derivative.h index da9fa60a6e..deee9d7aa9 100644 --- a/include/openmc/tallies/derivative.h +++ b/include/openmc/tallies/derivative.h @@ -14,7 +14,7 @@ namespace openmc { -extern "C" struct TallyDerivative { +struct TallyDerivative { int id; //!< User-defined identifier int variable; //!< Independent variable (like temperature) int diff_material; //!< Material this derivative is applied to @@ -32,8 +32,8 @@ extern "C" struct TallyDerivative { //! Scale the given score by its logarithmic derivative void -apply_derivative_to_score(Particle* p, int i_tally, int i_nuclide, - double atom_density, int score_bin, double* score); +apply_derivative_to_score(const Particle* p, int i_tally, int i_nuclide, + double atom_density, int score_bin, double& score); } // namespace openmc @@ -46,18 +46,19 @@ apply_derivative_to_score(Particle* p, int i_tally, int i_nuclide, extern template class std::vector; namespace openmc { - namespace model { - extern std::vector tally_derivs; - #pragma omp threadprivate(tally_derivs) - extern std::unordered_map tally_deriv_map; - } +namespace model { +extern std::vector tally_derivs; +#pragma omp threadprivate(tally_derivs) +extern std::unordered_map tally_deriv_map; +} // namespace model - // Independent variables - //TODO: convert to enum - constexpr int DIFF_DENSITY {1}; - constexpr int DIFF_NUCLIDE_DENSITY {2}; - constexpr int DIFF_TEMPERATURE {3}; -} +// Independent variables +//TODO: convert to enum +constexpr int DIFF_DENSITY {1}; +constexpr int DIFF_NUCLIDE_DENSITY {2}; +constexpr int DIFF_TEMPERATURE {3}; + +} // namespace openmc #endif // OPENMC_TALLIES_DERIVATIVE_H diff --git a/include/openmc/tallies/tally.h b/include/openmc/tallies/tally.h index 0bb2127c0e..464ec9f3d9 100644 --- a/include/openmc/tallies/tally.h +++ b/include/openmc/tallies/tally.h @@ -104,15 +104,17 @@ private: extern "C" double total_weight; namespace model { - extern std::vector> tallies; - extern std::vector active_tallies; - extern std::vector active_analog_tallies; - extern std::vector active_tracklength_tallies; - extern std::vector active_collision_tallies; - extern std::vector active_meshsurf_tallies; - extern std::vector active_surface_tallies; -} +extern std::vector> tallies; + +extern std::vector active_tallies; +extern std::vector active_analog_tallies; +extern std::vector active_tracklength_tallies; +extern std::vector active_collision_tallies; +extern std::vector active_meshsurf_tallies; +extern std::vector active_surface_tallies; + +} // namespace model // Threadprivate variables extern "C" double global_tally_absorption; diff --git a/include/openmc/tallies/tally_scoring.h b/include/openmc/tallies/tally_scoring.h index ea415c514b..8aa659325a 100644 --- a/include/openmc/tallies/tally_scoring.h +++ b/include/openmc/tallies/tally_scoring.h @@ -22,7 +22,7 @@ class FilterBinIter public: //! Construct an iterator over bins that match a given particle's state. - FilterBinIter(const Tally& tally, Particle* p); + FilterBinIter(const Tally& tally, const Particle* p); //! Construct an iterator over all filter bin combinations. // diff --git a/include/openmc/tallies/trigger.h b/include/openmc/tallies/trigger.h index e670297ed1..a11759114a 100644 --- a/include/openmc/tallies/trigger.h +++ b/include/openmc/tallies/trigger.h @@ -17,8 +17,7 @@ enum class TriggerMetric { //! Stops the simulation early if a desired tally uncertainty is reached. -struct Trigger -{ +struct Trigger { TriggerMetric metric; //!< The type of uncertainty (e.g. std dev) measured double threshold; //!< Uncertainty value below which trigger is satisfied int score_index; //!< Index of the relevant score in the tally's arrays diff --git a/src/mgxs.cpp b/src/mgxs.cpp index 08c4f78941..5b37d8ad54 100644 --- a/src/mgxs.cpp +++ b/src/mgxs.cpp @@ -424,7 +424,8 @@ Mgxs::combine(const std::vector& micros, const std::vector& scala //============================================================================== double -Mgxs::get_xs(int xstype, int gin, int* gout, double* mu, int* dg) +Mgxs::get_xs(int xstype, int gin, const int* gout, const double* mu, + const int* dg) { // This method assumes that the temperature and angle indices are set #ifdef _OPENMP diff --git a/src/mgxs_interface.cpp b/src/mgxs_interface.cpp index 546e921005..f239dcedcb 100644 --- a/src/mgxs_interface.cpp +++ b/src/mgxs_interface.cpp @@ -231,12 +231,13 @@ calculate_xs_c(int i_mat, int gin, double sqrtkT, const double uvw[3], //============================================================================== double -get_nuclide_xs(int index, int xstype, int gin, int* gout, double* mu, int* dg) +get_nuclide_xs(int index, int xstype, int gin, const int* gout, + const double* mu, const int* dg) { int gout_c; - int* gout_c_p; + const int* gout_c_p; int dg_c; - int* dg_c_p; + const int* dg_c_p; if (gout != nullptr) { gout_c = *gout - 1; gout_c_p = &gout_c; @@ -255,12 +256,13 @@ get_nuclide_xs(int index, int xstype, int gin, int* gout, double* mu, int* dg) //============================================================================== double -get_macro_xs(int index, int xstype, int gin, int* gout, double* mu, int* dg) +get_macro_xs(int index, int xstype, int gin, const int* gout, + const double* mu, const int* dg) { int gout_c; - int* gout_c_p; + const int* gout_c_p; int dg_c; - int* dg_c_p; + const int* dg_c_p; if (gout != nullptr) { gout_c = *gout - 1; gout_c_p = &gout_c; diff --git a/src/tallies/derivative.cpp b/src/tallies/derivative.cpp index df65b0751a..b82db28e64 100644 --- a/src/tallies/derivative.cpp +++ b/src/tallies/derivative.cpp @@ -1,9 +1,10 @@ +#include "openmc/tallies/derivative.h" + #include "openmc/error.h" #include "openmc/material.h" #include "openmc/nuclide.h" #include "openmc/settings.h" #include "openmc/tallies/tally.h" -#include "openmc/tallies/derivative.h" #include "openmc/xml_interface.h" #include @@ -107,12 +108,12 @@ read_tally_derivatives(pugi::xml_node* node) } void -apply_derivative_to_score(Particle* p, int i_tally, int i_nuclide, - double atom_density, int score_bin, double* score) +apply_derivative_to_score(const Particle* p, int i_tally, int i_nuclide, + double atom_density, int score_bin, double& score) { const Tally& tally {*model::tallies[i_tally]}; - if (*score == 0) return; + if (score == 0.0) return; // If our score was previously c then the new score is // c * (1/f * d_f/d_p + 1/c * d_c/d_p) @@ -124,16 +125,16 @@ apply_derivative_to_score(Particle* p, int i_tally, int i_nuclide, // Handle special cases where we know that d_c/d_p must be zero. if (score_bin == SCORE_FLUX) { - *score *= flux_deriv; + score *= flux_deriv; return; } else if (p->material == MATERIAL_VOID) { - *score *= flux_deriv; + score *= flux_deriv; return; } //TODO: off-by-one const Material& material {*model::materials[p->material-1]}; if (material.id_ != deriv.diff_material) { - *score *= flux_deriv; + score *= flux_deriv; return; } @@ -159,7 +160,7 @@ apply_derivative_to_score(Particle* p, int i_tally, int i_nuclide, case SCORE_ABSORPTION: case SCORE_FISSION: case SCORE_NU_FISSION: - *score *= flux_deriv + 1. / material.density_gpcc_; + score *= flux_deriv + 1. / material.density_gpcc_; break; default: @@ -193,7 +194,7 @@ apply_derivative_to_score(Particle* p, int i_tally, int i_nuclide, case ESTIMATOR_ANALOG: if (p->event_nuclide != deriv.diff_nuclide) { - *score *= flux_deriv; + score *= flux_deriv; return; } @@ -209,7 +210,7 @@ apply_derivative_to_score(Particle* p, int i_tally, int i_nuclide, int i; for (i = 0; i < material.nuclide_.size(); ++i) if (material.nuclide_[i] == deriv.diff_nuclide - 1) break; - *score *= flux_deriv + 1. / material.atom_density_(i); + score *= flux_deriv + 1. / material.atom_density_(i); } break; @@ -223,69 +224,69 @@ apply_derivative_to_score(Particle* p, int i_tally, int i_nuclide, switch (score_bin) { case SCORE_TOTAL: - if (i_nuclide == -1 && simulation::material_xs.total) { - *score *= flux_deriv + if (i_nuclide == -1 && simulation::material_xs.total > 0.0) { + score *= flux_deriv + simulation::micro_xs[deriv.diff_nuclide-1].total / simulation::material_xs.total; } else if (i_nuclide == deriv.diff_nuclide-1 && simulation::micro_xs[i_nuclide].total) { - *score *= flux_deriv + 1. / atom_density; + score *= flux_deriv + 1. / atom_density; } else { - *score *= flux_deriv; + score *= flux_deriv; } break; case SCORE_SCATTER: if (i_nuclide == -1 && (simulation::material_xs.total - - simulation::material_xs.absorption)) { - *score *= flux_deriv + - simulation::material_xs.absorption) > 0.0) { + score *= flux_deriv + (simulation::micro_xs[deriv.diff_nuclide-1].total - simulation::micro_xs[deriv.diff_nuclide-1].absorption) / (simulation::material_xs.total - simulation::material_xs.absorption); } else if (i_nuclide == deriv.diff_nuclide-1) { - *score *= flux_deriv + 1. / atom_density; + score *= flux_deriv + 1. / atom_density; } else { - *score *= flux_deriv; + score *= flux_deriv; } break; case SCORE_ABSORPTION: - if (i_nuclide == -1 && simulation::material_xs.absorption) { - *score *= flux_deriv + if (i_nuclide == -1 && simulation::material_xs.absorption > 0.0) { + score *= flux_deriv + simulation::micro_xs[deriv.diff_nuclide-1].absorption / simulation::material_xs.absorption; } else if (i_nuclide == deriv.diff_nuclide-1 && simulation::micro_xs[i_nuclide].absorption) { - *score *= flux_deriv + 1. / atom_density; + score *= flux_deriv + 1. / atom_density; } else { - *score *= flux_deriv; + score *= flux_deriv; } break; case SCORE_FISSION: - if (i_nuclide == -1 && simulation::material_xs.fission) { - *score *= flux_deriv + if (i_nuclide == -1 && simulation::material_xs.fission > 0.0) { + score *= flux_deriv + simulation::micro_xs[deriv.diff_nuclide-1].fission / simulation::material_xs.fission; } else if (i_nuclide == deriv.diff_nuclide-1 && simulation::micro_xs[i_nuclide].fission) { - *score *= flux_deriv + 1. / atom_density; + score *= flux_deriv + 1. / atom_density; } else { - *score *= flux_deriv; + score *= flux_deriv; } break; case SCORE_NU_FISSION: - if (i_nuclide == -1 && simulation::material_xs.nu_fission) { - *score *= flux_deriv + if (i_nuclide == -1 && simulation::material_xs.nu_fission > 0.0) { + score *= flux_deriv + simulation::micro_xs[deriv.diff_nuclide-1].nu_fission / simulation::material_xs.nu_fission; } else if (i_nuclide == deriv.diff_nuclide-1 && simulation::micro_xs[i_nuclide].nu_fission) { - *score *= flux_deriv + 1. / atom_density; + score *= flux_deriv + 1. / atom_density; } else { - *score *= flux_deriv; + score *= flux_deriv; } break; @@ -326,7 +327,7 @@ apply_derivative_to_score(Particle* p, int i_tally, int i_nuclide, const auto& nuc {*data::nuclides[p->event_nuclide-1]}; if (!multipole_in_range(&nuc, p->last_E)) { - *score *= flux_deriv; + score *= flux_deriv; break; } @@ -337,10 +338,10 @@ apply_derivative_to_score(Particle* p, int i_tally, int i_nuclide, double dsig_s, dsig_a, dsig_f; std::tie(dsig_s, dsig_a, dsig_f) = nuc.multipole_->evaluate_deriv(p->last_E, p->sqrtkT); - *score *= flux_deriv + (dsig_s + dsig_a) * material.atom_density_(i) + score *= flux_deriv + (dsig_s + dsig_a) * material.atom_density_(i) / simulation::material_xs.total; } else { - *score *= flux_deriv; + score *= flux_deriv; } break; @@ -350,11 +351,11 @@ apply_derivative_to_score(Particle* p, int i_tally, int i_nuclide, double dsig_s, dsig_a, dsig_f; std::tie(dsig_s, dsig_a, dsig_f) = nuc.multipole_->evaluate_deriv(p->last_E, p->sqrtkT); - *score *= flux_deriv + dsig_s * material.atom_density_(i) + score *= flux_deriv + dsig_s * material.atom_density_(i) / (simulation::material_xs.total - simulation::material_xs.absorption); } else { - *score *= flux_deriv; + score *= flux_deriv; } break; @@ -363,10 +364,10 @@ apply_derivative_to_score(Particle* p, int i_tally, int i_nuclide, double dsig_s, dsig_a, dsig_f; std::tie(dsig_s, dsig_a, dsig_f) = nuc.multipole_->evaluate_deriv(p->last_E, p->sqrtkT); - *score *= flux_deriv + dsig_a * material.atom_density_(i) + score *= flux_deriv + dsig_a * material.atom_density_(i) / simulation::material_xs.absorption; } else { - *score *= flux_deriv; + score *= flux_deriv; } break; @@ -375,10 +376,10 @@ apply_derivative_to_score(Particle* p, int i_tally, int i_nuclide, double dsig_s, dsig_a, dsig_f; std::tie(dsig_s, dsig_a, dsig_f) = nuc.multipole_->evaluate_deriv(p->last_E, p->sqrtkT); - *score *= flux_deriv + dsig_f * material.atom_density_(i) + score *= flux_deriv + dsig_f * material.atom_density_(i) / simulation::material_xs.fission; } else { - *score *= flux_deriv; + score *= flux_deriv; } break; @@ -389,10 +390,10 @@ apply_derivative_to_score(Particle* p, int i_tally, int i_nuclide, double dsig_s, dsig_a, dsig_f; std::tie(dsig_s, dsig_a, dsig_f) = nuc.multipole_->evaluate_deriv(p->last_E, p->sqrtkT); - *score *= flux_deriv + nu * dsig_f * material.atom_density_(i) + score *= flux_deriv + nu * dsig_f * material.atom_density_(i) / simulation::material_xs.nu_fission; } else { - *score *= flux_deriv; + score *= flux_deriv; } break; @@ -405,9 +406,9 @@ apply_derivative_to_score(Particle* p, int i_tally, int i_nuclide, case ESTIMATOR_COLLISION: if (i_nuclide != -1) { - const auto& nuc {*data::nuclides[i_nuclide]}; - if (!multipole_in_range(&nuc, p->last_E)) { - *score *= flux_deriv; + const auto& nuc {data::nuclides[i_nuclide]}; + if (!multipole_in_range(nuc.get(), p->last_E)) { + score *= flux_deriv; return; } } @@ -415,7 +416,7 @@ apply_derivative_to_score(Particle* p, int i_tally, int i_nuclide, switch (score_bin) { case SCORE_TOTAL: - if (i_nuclide == -1 && simulation::material_xs.total) { + if (i_nuclide == -1 && simulation::material_xs.total > 0.0) { double cum_dsig = 0; for (auto i = 0; i < material.nuclide_.size(); ++i) { auto i_nuc = material.nuclide_[i]; @@ -428,16 +429,16 @@ apply_derivative_to_score(Particle* p, int i_tally, int i_nuclide, cum_dsig += (dsig_s + dsig_a) * material.atom_density_(i); } } - *score *= flux_deriv + cum_dsig / simulation::material_xs.total; + score *= flux_deriv + cum_dsig / simulation::material_xs.total; } else if (simulation::micro_xs[i_nuclide].total) { const auto& nuc {*data::nuclides[i_nuclide]}; double dsig_s, dsig_a, dsig_f; std::tie(dsig_s, dsig_a, dsig_f) = nuc.multipole_->evaluate_deriv(p->last_E, p->sqrtkT); - *score *= flux_deriv + score *= flux_deriv + (dsig_s + dsig_a) / simulation::micro_xs[i_nuclide].total; } else { - *score *= flux_deriv; + score *= flux_deriv; } break; @@ -457,7 +458,7 @@ apply_derivative_to_score(Particle* p, int i_tally, int i_nuclide, cum_dsig += dsig_s * material.atom_density_(i); } } - *score *= flux_deriv + cum_dsig / (simulation::material_xs.total + score *= flux_deriv + cum_dsig / (simulation::material_xs.total - simulation::material_xs.absorption); } else if (simulation::micro_xs[i_nuclide].total - simulation::micro_xs[i_nuclide].absorption) { @@ -465,15 +466,15 @@ apply_derivative_to_score(Particle* p, int i_tally, int i_nuclide, double dsig_s, dsig_a, dsig_f; std::tie(dsig_s, dsig_a, dsig_f) = nuc.multipole_->evaluate_deriv(p->last_E, p->sqrtkT); - *score *= flux_deriv + dsig_s / (simulation::micro_xs[i_nuclide].total + score *= flux_deriv + dsig_s / (simulation::micro_xs[i_nuclide].total - simulation::micro_xs[i_nuclide].absorption); } else { - *score *= flux_deriv; + score *= flux_deriv; } break; case SCORE_ABSORPTION: - if (i_nuclide == -1 && simulation::material_xs.absorption) { + if (i_nuclide == -1 && simulation::material_xs.absorption > 0.0) { double cum_dsig = 0; for (auto i = 0; i < material.nuclide_.size(); ++i) { auto i_nuc = material.nuclide_[i]; @@ -486,21 +487,21 @@ apply_derivative_to_score(Particle* p, int i_tally, int i_nuclide, cum_dsig += dsig_a * material.atom_density_(i); } } - *score *= flux_deriv + cum_dsig / simulation::material_xs.absorption; + score *= flux_deriv + cum_dsig / simulation::material_xs.absorption; } else if (simulation::micro_xs[i_nuclide].absorption) { const auto& nuc {*data::nuclides[i_nuclide]}; double dsig_s, dsig_a, dsig_f; std::tie(dsig_s, dsig_a, dsig_f) = nuc.multipole_->evaluate_deriv(p->last_E, p->sqrtkT); - *score *= flux_deriv + score *= flux_deriv + dsig_a / simulation::micro_xs[i_nuclide].absorption; } else { - *score *= flux_deriv; + score *= flux_deriv; } break; case SCORE_FISSION: - if (i_nuclide == -1 && simulation::material_xs.fission) { + if (i_nuclide == -1 && simulation::material_xs.fission > 0.0) { double cum_dsig = 0; for (auto i = 0; i < material.nuclide_.size(); ++i) { auto i_nuc = material.nuclide_[i]; @@ -513,21 +514,21 @@ apply_derivative_to_score(Particle* p, int i_tally, int i_nuclide, cum_dsig += dsig_f * material.atom_density_(i); } } - *score *= flux_deriv + cum_dsig / simulation::material_xs.fission; + score *= flux_deriv + cum_dsig / simulation::material_xs.fission; } else if (simulation::micro_xs[i_nuclide].fission) { const auto& nuc {*data::nuclides[i_nuclide]}; double dsig_s, dsig_a, dsig_f; std::tie(dsig_s, dsig_a, dsig_f) = nuc.multipole_->evaluate_deriv(p->last_E, p->sqrtkT); - *score *= flux_deriv + score *= flux_deriv + dsig_f / simulation::micro_xs[i_nuclide].fission; } else { - *score *= flux_deriv; + score *= flux_deriv; } break; case SCORE_NU_FISSION: - if (i_nuclide == -1 && simulation::material_xs.nu_fission) { + if (i_nuclide == -1 && simulation::material_xs.nu_fission > 0.0) { double cum_dsig = 0; for (auto i = 0; i < material.nuclide_.size(); ++i) { auto i_nuc = material.nuclide_[i]; @@ -542,16 +543,16 @@ apply_derivative_to_score(Particle* p, int i_tally, int i_nuclide, cum_dsig += nu * dsig_f * material.atom_density_(i); } } - *score *= flux_deriv + cum_dsig / simulation::material_xs.nu_fission; + score *= flux_deriv + cum_dsig / simulation::material_xs.nu_fission; } else if (simulation::micro_xs[i_nuclide].fission) { const auto& nuc {*data::nuclides[i_nuclide]}; double dsig_s, dsig_a, dsig_f; std::tie(dsig_s, dsig_a, dsig_f) = nuc.multipole_->evaluate_deriv(p->last_E, p->sqrtkT); - *score *= flux_deriv + score *= flux_deriv + dsig_f / simulation::micro_xs[i_nuclide].fission; } else { - *score *= flux_deriv; + score *= flux_deriv; } break; @@ -571,7 +572,7 @@ apply_derivative_to_score(Particle* p, int i_tally, int i_nuclide, //! Adjust diff tally flux derivatives for a particle tracking event. extern "C" void -score_track_derivative(Particle* p, double distance) +score_track_derivative(const Particle* p, double distance) { // A void material cannot be perturbed so it will not affect flux derivatives. if (p->material == MATERIAL_VOID) return; @@ -627,7 +628,7 @@ score_track_derivative(Particle* p, double distance) //! further tallies are scored. extern "C" void -score_collision_derivative(Particle* p) +score_collision_derivative(const Particle* p) { // A void material cannot be perturbed so it will not affect flux derivatives. if (p->material == MATERIAL_VOID) return; @@ -713,7 +714,7 @@ extern "C" int n_tally_derivs() {return model::tally_derivs.size();} extern "C" TallyDerivative* tally_deriv_c(int i) { - return &(model::tally_derivs[i]); + return &model::tally_derivs[i]; } }// namespace openmc diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index e9bee044df..4bfb15effb 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -637,7 +637,6 @@ free_memory_tally_c() { #pragma omp parallel { - simulation::filter_matches.clear(); model::tally_derivs.clear(); } diff --git a/src/tallies/tally_scoring.cpp b/src/tallies/tally_scoring.cpp index 77bfcd2738..8c509a0aae 100644 --- a/src/tallies/tally_scoring.cpp +++ b/src/tallies/tally_scoring.cpp @@ -23,7 +23,7 @@ namespace openmc { // FilterBinIter implementation //============================================================================== -FilterBinIter::FilterBinIter(const Tally& tally, Particle* p) +FilterBinIter::FilterBinIter(const Tally& tally, const Particle* p) : tally_{tally} { // Find all valid bins in each relevant filter if they have not already been @@ -179,7 +179,7 @@ score_fission_delayed_dg(int i_tally, int d_bin, double score, int score_index) //! neutrons produced with different energies. void -score_fission_eout(Particle* p, int i_tally, int i_score, int score_bin) +score_fission_eout(const Particle* p, int i_tally, int i_score, int score_bin) { const Tally& tally {*model::tallies[i_tally]}; auto results = tally_results(i_tally); @@ -210,7 +210,7 @@ score_fission_eout(Particle* p, int i_tally, int i_score, int score_bin) // i_nuclide and atom_density arguments do not matter since this is an // analog estimator. if (tally.deriv_ != C_NONE) - apply_derivative_to_score(p, i_tally, 0, 0., SCORE_NU_FISSION, &score); + apply_derivative_to_score(p, i_tally, 0, 0., SCORE_NU_FISSION, score); if (!settings::run_CE && eo_filt.matches_transport_groups_) { @@ -325,8 +325,8 @@ score_fission_eout(Particle* p, int i_tally, int i_score, int score_bin) //! is not used for analog tallies. void -score_general_ce(Particle* p, int i_tally, int start_index, int filter_index, - int i_nuclide, double atom_density, double flux) +score_general_ce(const Particle* p, int i_tally, int start_index, + int filter_index, int i_nuclide, double atom_density, double flux) { const Tally& tally {*model::tallies[i_tally]}; auto results = tally_results(i_tally); @@ -1218,7 +1218,7 @@ score_general_ce(Particle* p, int i_tally, int start_index, int filter_index, // Add derivative information on score for differnetial tallies. if (tally.deriv_ != C_NONE) apply_derivative_to_score(p, i_tally, i_nuclide, atom_density, score_bin, - &score); + score); // Update tally results #pragma omp atomic @@ -1232,8 +1232,8 @@ score_general_ce(Particle* p, int i_tally, int start_index, int filter_index, //! argument is really just used for filter weights. void -score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, - int i_nuclide, double atom_density, double flux) +score_general_mg(const Particle* p, int i_tally, int start_index, + int filter_index, int i_nuclide, double atom_density, double flux) { const Tally& tally {*model::tallies[i_tally]}; auto results = tally_results(i_tally); @@ -1241,7 +1241,7 @@ score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, //TODO: off-by-one throughout on p->material // Set the direction and group to use with get_xs - double* p_uvw; + const double* p_uvw; int p_g; if (tally.estimator_ == ESTIMATOR_ANALOG || tally.estimator_ == ESTIMATOR_COLLISION) { @@ -1937,7 +1937,8 @@ score_general_mg(Particle* p, int i_tally, int start_index, int filter_index, //! Tally rates for when the user requests a tally on all nuclides. void -score_all_nuclides(Particle* p, int i_tally, double flux, int filter_index) +score_all_nuclides(const Particle* p, int i_tally, double flux, + int filter_index) { const Tally& tally {*model::tallies[i_tally]}; const Material& material {*model::materials[p->material-1]}; @@ -1976,7 +1977,7 @@ score_all_nuclides(Particle* p, int i_tally, double flux, int filter_index) //! Analog tallies ar etriggered at every collision, not every event. extern "C" void -score_analog_tally_ce(Particle* p) +score_analog_tally_ce(const Particle* p) { for (auto i_tally : model::active_analog_tallies) { const Tally& tally {*model::tallies[i_tally]}; @@ -2040,7 +2041,7 @@ score_analog_tally_ce(Particle* p) //! Analog tallies ar etriggered at every collision, not every event. extern "C" void -score_analog_tally_mg(Particle* p) +score_analog_tally_mg(const Particle* p) { for (auto i_tally : model::active_analog_tallies) { const Tally& tally {*model::tallies[i_tally]}; @@ -2096,7 +2097,7 @@ score_analog_tally_mg(Particle* p) //! information. extern "C" void -score_tracklength_tally(Particle* p, double distance) +score_tracklength_tally(const Particle* p, double distance) { // Determine the tracklength estimate of the flux double flux = p->wgt * distance; @@ -2172,7 +2173,7 @@ score_tracklength_tally(Particle* p, double distance) //! since collisions do not occur in voids. extern "C" void -score_collision_tally(Particle* p) +score_collision_tally(const Particle* p) { // Determine the collision estimate of the flux double flux; @@ -2243,7 +2244,7 @@ score_collision_tally(Particle* p) //! Score surface or mesh-surface tallies for particle currents. static void -score_surface_tally_inner(Particle* p, const std::vector& tallies) +score_surface_tally_inner(const Particle* p, const std::vector& tallies) { // No collision, so no weight change when survival biasing double flux = p->wgt; @@ -2291,7 +2292,7 @@ score_surface_tally_inner(Particle* p, const std::vector& tallies) //! Score mesh-surface tallies for particle currents. extern "C" void -score_meshsurface_tally(Particle* p) +score_meshsurface_tally(const Particle* p) { score_surface_tally_inner(p, model::active_meshsurf_tallies); } @@ -2299,7 +2300,7 @@ score_meshsurface_tally(Particle* p) //! Score surface tallies for particle currents. extern "C" void -score_surface_tally(Particle* p) +score_surface_tally(const Particle* p) { score_surface_tally_inner(p, model::active_surface_tallies); } diff --git a/src/tallies/trigger.cpp b/src/tallies/trigger.cpp index 6cd99f1330..a53cef817e 100644 --- a/src/tallies/trigger.cpp +++ b/src/tallies/trigger.cpp @@ -26,7 +26,7 @@ namespace settings { // Non-member functions //============================================================================== -static std::pair +std::pair get_tally_uncertainty(int i_tally, int score_index, int filter_index) { int n; @@ -87,6 +87,7 @@ check_tally_triggers(double& ratio, int& tally_id, int& score) break; case TriggerMetric::relative_error: uncertainty = rel_err; + break; } // Compute the uncertainty / threshold ratio. From 2bc8b9a5ffca8351ca1bd547188580e97e094695 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Wed, 13 Feb 2019 01:05:50 -0500 Subject: [PATCH 56/58] Avoid implicit construction of TallyDerivative --- include/openmc/tallies/derivative.h | 2 +- src/tallies/derivative.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/include/openmc/tallies/derivative.h b/include/openmc/tallies/derivative.h index deee9d7aa9..3237fb3bab 100644 --- a/include/openmc/tallies/derivative.h +++ b/include/openmc/tallies/derivative.h @@ -22,7 +22,7 @@ struct TallyDerivative { double flux_deriv; //!< Derivative of the current particle's weight TallyDerivative() {} - TallyDerivative(pugi::xml_node node); + explicit TallyDerivative(pugi::xml_node node); }; //============================================================================== diff --git a/src/tallies/derivative.cpp b/src/tallies/derivative.cpp index b82db28e64..600cd2876f 100644 --- a/src/tallies/derivative.cpp +++ b/src/tallies/derivative.cpp @@ -87,7 +87,7 @@ read_tally_derivatives(pugi::xml_node* node) #pragma omp parallel { for (auto deriv_node : node->children("derivative")) - model::tally_derivs.push_back(deriv_node); + model::tally_derivs.emplace_back(deriv_node); } // Fill the derivative map. From 866e75a8413da9f284df400db1e0dc2b791429e9 Mon Sep 17 00:00:00 2001 From: Jonathan Shimwell Date: Wed, 13 Feb 2019 11:52:10 +0000 Subject: [PATCH 57/58] removed contex related comments --- openmc/_utils.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/openmc/_utils.py b/openmc/_utils.py index 1f5632a8da..e5fc4be991 100644 --- a/openmc/_utils.py +++ b/openmc/_utils.py @@ -17,10 +17,7 @@ def download(url, checksum=None, **kwargs): checksum : str or None MD5 checksum to check against **kwargs : dict - Optional arguements passed to urlopen() - context: ssl.SSLContext object - Describes the various SSL options, - e.g. context=ssl._create_unverified_context() + Keyword arguments passed to :func:urllib.request.urlopen Returns ------- From 02205aa336e2b8f869c1ec50efaee8a867ca8a8f Mon Sep 17 00:00:00 2001 From: Jonathan Shimwell Date: Wed, 13 Feb 2019 11:53:50 +0000 Subject: [PATCH 58/58] reworded keword comments --- openmc/_utils.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/openmc/_utils.py b/openmc/_utils.py index e5fc4be991..15781282c2 100644 --- a/openmc/_utils.py +++ b/openmc/_utils.py @@ -16,8 +16,7 @@ def download(url, checksum=None, **kwargs): URL from which to download checksum : str or None MD5 checksum to check against - **kwargs : dict - Keyword arguments passed to :func:urllib.request.urlopen + Keyword arguments passed to :func:urllib.request.urlopen Returns -------