mirror of
https://github.com/openmc-dev/openmc.git
synced 2026-07-27 13:45:36 -04:00
Move write_tallies to C++
This commit is contained in:
parent
ddaed7311b
commit
8bb0fa7791
6 changed files with 366 additions and 373 deletions
|
|
@ -2,6 +2,7 @@
|
||||||
#define OPENMC_TALLIES_TALLY_H
|
#define OPENMC_TALLIES_TALLY_H
|
||||||
|
|
||||||
#include "openmc/constants.h"
|
#include "openmc/constants.h"
|
||||||
|
#include "openmc/tallies/filter.h"
|
||||||
#include "openmc/tallies/trigger.h"
|
#include "openmc/tallies/trigger.h"
|
||||||
|
|
||||||
#include "pugixml.hpp"
|
#include "pugixml.hpp"
|
||||||
|
|
@ -21,6 +22,8 @@ class Tally {
|
||||||
public:
|
public:
|
||||||
Tally() {}
|
Tally() {}
|
||||||
|
|
||||||
|
void init_from_xml(pugi::xml_node node);
|
||||||
|
|
||||||
void set_scores(pugi::xml_node node);
|
void set_scores(pugi::xml_node node);
|
||||||
|
|
||||||
void set_scores(std::vector<std::string> scores);
|
void set_scores(std::vector<std::string> scores);
|
||||||
|
|
@ -50,6 +53,8 @@ public:
|
||||||
|
|
||||||
int id_; //!< user-defined identifier
|
int id_; //!< user-defined identifier
|
||||||
|
|
||||||
|
std::string name_; //!< user-defined name
|
||||||
|
|
||||||
int type_ {TALLY_VOLUME}; //!< volume, surface current
|
int type_ {TALLY_VOLUME}; //!< volume, surface current
|
||||||
|
|
||||||
//! Event type that contributes to this tally
|
//! Event type that contributes to this tally
|
||||||
|
|
@ -93,6 +98,41 @@ private:
|
||||||
int32_t n_filter_bins_ {0};
|
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
|
// Global variable declarations
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
|
||||||
|
|
@ -473,6 +473,12 @@ contains
|
||||||
type(TallyDerivative), pointer :: deriv
|
type(TallyDerivative), pointer :: deriv
|
||||||
|
|
||||||
interface
|
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)
|
subroutine tally_set_scores(tally_ptr, xml_node) bind(C)
|
||||||
import C_PTR
|
import C_PTR
|
||||||
type(C_PTR), value :: tally_ptr
|
type(C_PTR), value :: tally_ptr
|
||||||
|
|
@ -630,6 +636,8 @@ contains
|
||||||
! Get pointer to tally xml node
|
! Get pointer to tally xml node
|
||||||
node_tal = node_tal_list(i)
|
node_tal = node_tal_list(i)
|
||||||
|
|
||||||
|
call tally_init_from_xml(t % ptr, node_tal % ptr)
|
||||||
|
|
||||||
! Copy and set tally id
|
! Copy and set tally id
|
||||||
if (check_for_node(node_tal, "id")) then
|
if (check_for_node(node_tal, "id")) then
|
||||||
call get_node_value(node_tal, "id", tally_id)
|
call get_node_value(node_tal, "id", tally_id)
|
||||||
|
|
|
||||||
235
src/output.F90
235
src/output.F90
|
|
@ -39,6 +39,9 @@ module output
|
||||||
import Particle
|
import Particle
|
||||||
type(Particle), intent(in) :: p
|
type(Particle), intent(in) :: p
|
||||||
end subroutine
|
end subroutine
|
||||||
|
|
||||||
|
subroutine write_tallies() bind(C)
|
||||||
|
end subroutine
|
||||||
end interface
|
end interface
|
||||||
|
|
||||||
contains
|
contains
|
||||||
|
|
@ -441,238 +444,6 @@ contains
|
||||||
|
|
||||||
end subroutine print_results
|
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
|
! MEAN_STDEV computes the sample mean and standard deviation of the mean of a
|
||||||
! single tally score
|
! single tally score
|
||||||
|
|
|
||||||
182
src/output.cpp
182
src/output.cpp
|
|
@ -2,27 +2,37 @@
|
||||||
|
|
||||||
#include <algorithm> // for std::transform
|
#include <algorithm> // for std::transform
|
||||||
#include <cstring> // for strlen
|
#include <cstring> // for strlen
|
||||||
#include <iomanip> // for setw
|
|
||||||
#include <iostream>
|
|
||||||
#include <sstream>
|
|
||||||
#include <ctime>
|
#include <ctime>
|
||||||
|
#include <iomanip> // for setw, setprecision
|
||||||
|
#include <ios> // for left
|
||||||
|
#include <iostream>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
#include "openmc/capi.h"
|
#include "openmc/capi.h"
|
||||||
#include "openmc/cell.h"
|
#include "openmc/cell.h"
|
||||||
#include "openmc/constants.h"
|
#include "openmc/constants.h"
|
||||||
|
#include "openmc/error.h"
|
||||||
#include "openmc/geometry.h"
|
#include "openmc/geometry.h"
|
||||||
#include "openmc/lattice.h"
|
#include "openmc/lattice.h"
|
||||||
|
#include "openmc/math_functions.h"
|
||||||
#include "openmc/message_passing.h"
|
#include "openmc/message_passing.h"
|
||||||
|
#include "openmc/mgxs_interface.h"
|
||||||
|
#include "openmc/nuclide.h"
|
||||||
#include "openmc/plot.h"
|
#include "openmc/plot.h"
|
||||||
|
#include "openmc/reaction.h"
|
||||||
#include "openmc/settings.h"
|
#include "openmc/settings.h"
|
||||||
#include "openmc/surface.h"
|
#include "openmc/surface.h"
|
||||||
|
#include "openmc/tallies/derivative.h"
|
||||||
|
#include "openmc/tallies/tally.h"
|
||||||
|
|
||||||
namespace openmc {
|
namespace openmc {
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
||||||
void
|
std::string
|
||||||
header(const char* msg, int level) {
|
header(const char* msg) {
|
||||||
// Determine how many times to repeat the '=' character.
|
// Determine how many times to repeat the '=' character.
|
||||||
int n_prefix = (63 - strlen(msg)) / 2;
|
int n_prefix = (63 - strlen(msg)) / 2;
|
||||||
int n_suffix = n_prefix;
|
int n_suffix = n_prefix;
|
||||||
|
|
@ -39,10 +49,18 @@ header(const char* msg, int level) {
|
||||||
out << "> " << upper << " <";
|
out << "> " << upper << " <";
|
||||||
for (int i = 0; i < n_suffix; i++) out << '=';
|
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.
|
// Print header based on verbosity level.
|
||||||
if (settings::verbosity >= level) {
|
if (settings::verbosity >= level)
|
||||||
std::cout << out.str() << "\n\n";
|
std::cout << out << "\n\n";
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
@ -230,4 +248,152 @@ print_overlap_check()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//==============================================================================
|
||||||
|
|
||||||
|
std::pair<double, double>
|
||||||
|
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<int, const char*> 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
|
} // namespace openmc
|
||||||
|
|
|
||||||
|
|
@ -144,11 +144,6 @@ int openmc_simulation_finalize()
|
||||||
simulation::time_active.stop();
|
simulation::time_active.stop();
|
||||||
simulation::time_finalize.start();
|
simulation::time_finalize.start();
|
||||||
|
|
||||||
#pragma omp parallel
|
|
||||||
{
|
|
||||||
simulation::filter_matches.clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deallocate Fortran variables, set tallies to inactive
|
// Deallocate Fortran variables, set tallies to inactive
|
||||||
for (auto& mat : model::materials) {
|
for (auto& mat : model::materials) {
|
||||||
mat->mat_nuclide_index_.clear();
|
mat->mat_nuclide_index_.clear();
|
||||||
|
|
@ -165,6 +160,11 @@ int openmc_simulation_finalize()
|
||||||
// Write tally results to tallies.out
|
// Write tally results to tallies.out
|
||||||
if (settings::output_tallies && mpi::master) write_tallies();
|
if (settings::output_tallies && mpi::master) write_tallies();
|
||||||
|
|
||||||
|
#pragma omp parallel
|
||||||
|
{
|
||||||
|
simulation::filter_matches.clear();
|
||||||
|
}
|
||||||
|
|
||||||
// Deactivate all tallies
|
// Deactivate all tallies
|
||||||
for (int i = 1; i <= n_tallies; ++i) {
|
for (int i = 1; i <= n_tallies; ++i) {
|
||||||
openmc_tally_set_active(i, false);
|
openmc_tally_set_active(i, false);
|
||||||
|
|
|
||||||
|
|
@ -63,124 +63,6 @@ double global_tally_collision;
|
||||||
double global_tally_tracklength;
|
double global_tally_tracklength;
|
||||||
double global_tally_leakage;
|
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
|
int
|
||||||
score_str_to_int(std::string score_str)
|
score_str_to_int(std::string score_str)
|
||||||
{
|
{
|
||||||
|
|
@ -341,6 +223,12 @@ score_str_to_int(std::string score_str)
|
||||||
// Tally object implementation
|
// 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
|
void
|
||||||
Tally::set_filters(const int32_t filter_indices[], int n)
|
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
|
// Non-member functions
|
||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
@ -2511,8 +2516,8 @@ score_analog_tally_ce(Particle* p)
|
||||||
// Initialize an iterator over valid filter bin combinations. If there are
|
// Initialize an iterator over valid filter bin combinations. If there are
|
||||||
// no valid combinations, use a continue statement to ensure we skip the
|
// no valid combinations, use a continue statement to ensure we skip the
|
||||||
// assume_separate break below.
|
// assume_separate break below.
|
||||||
auto filter_iter = FilterBinIter(tally, p, false);
|
auto filter_iter = FilterBinIter(tally, p);
|
||||||
auto end = FilterBinIter(tally, nullptr, true);
|
auto end = FilterBinIter(tally, true);
|
||||||
if (filter_iter == end) continue;
|
if (filter_iter == end) continue;
|
||||||
|
|
||||||
// Loop over filter bins.
|
// 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
|
// Initialize an iterator over valid filter bin combinations. If there are
|
||||||
// no valid combinations, use a continue statement to ensure we skip the
|
// no valid combinations, use a continue statement to ensure we skip the
|
||||||
// assume_separate break below.
|
// assume_separate break below.
|
||||||
auto filter_iter = FilterBinIter(tally, p, false);
|
auto filter_iter = FilterBinIter(tally, p);
|
||||||
auto end = FilterBinIter(tally, nullptr, true);
|
auto end = FilterBinIter(tally, true);
|
||||||
if (filter_iter == end) continue;
|
if (filter_iter == end) continue;
|
||||||
|
|
||||||
// Loop over filter bins.
|
// 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
|
// Initialize an iterator over valid filter bin combinations. If there are
|
||||||
// no valid combinations, use a continue statement to ensure we skip the
|
// no valid combinations, use a continue statement to ensure we skip the
|
||||||
// assume_separate break below.
|
// assume_separate break below.
|
||||||
auto filter_iter = FilterBinIter(tally, p, false);
|
auto filter_iter = FilterBinIter(tally, p);
|
||||||
auto end = FilterBinIter(tally, nullptr, true);
|
auto end = FilterBinIter(tally, true);
|
||||||
if (filter_iter == end) continue;
|
if (filter_iter == end) continue;
|
||||||
|
|
||||||
// Loop over filter bins.
|
// Loop over filter bins.
|
||||||
|
|
@ -2718,8 +2723,8 @@ score_collision_tally(Particle* p)
|
||||||
// Initialize an iterator over valid filter bin combinations. If there are
|
// Initialize an iterator over valid filter bin combinations. If there are
|
||||||
// no valid combinations, use a continue statement to ensure we skip the
|
// no valid combinations, use a continue statement to ensure we skip the
|
||||||
// assume_separate break below.
|
// assume_separate break below.
|
||||||
auto filter_iter = FilterBinIter(tally, p, false);
|
auto filter_iter = FilterBinIter(tally, p);
|
||||||
auto end = FilterBinIter(tally, nullptr, true);
|
auto end = FilterBinIter(tally, true);
|
||||||
if (filter_iter == end) continue;
|
if (filter_iter == end) continue;
|
||||||
|
|
||||||
// Loop over filter bins.
|
// Loop over filter bins.
|
||||||
|
|
@ -2786,8 +2791,8 @@ score_surface_tally_inner(Particle* p, const std::vector<int>& tallies)
|
||||||
// Initialize an iterator over valid filter bin combinations. If there are
|
// Initialize an iterator over valid filter bin combinations. If there are
|
||||||
// no valid combinations, use a continue statement to ensure we skip the
|
// no valid combinations, use a continue statement to ensure we skip the
|
||||||
// assume_separate break below.
|
// assume_separate break below.
|
||||||
auto filter_iter = FilterBinIter(tally, p, false);
|
auto filter_iter = FilterBinIter(tally, p);
|
||||||
auto end = FilterBinIter(tally, nullptr, true);
|
auto end = FilterBinIter(tally, true);
|
||||||
if (filter_iter == end) continue;
|
if (filter_iter == end) continue;
|
||||||
|
|
||||||
// Loop over filter bins.
|
// Loop over filter bins.
|
||||||
|
|
@ -3172,6 +3177,9 @@ extern "C" {
|
||||||
int active_surface_tallies_size()
|
int active_surface_tallies_size()
|
||||||
{return model::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_;}
|
int tally_get_id_c(Tally* tally) {return tally->id_;}
|
||||||
|
|
||||||
void tally_set_id_c(Tally* tally, int id) {tally->id_ = id;}
|
void tally_set_id_c(Tally* tally, int id) {tally->id_ = id;}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue