From ea5ce9fded06fa98aaa9c0bc58ee966ad82dacaf Mon Sep 17 00:00:00 2001 From: Sebastian Schunert Date: Tue, 5 Jun 2018 15:54:36 -0600 Subject: [PATCH] Refactor neutronics pka generators to include averaged number densities as input. Add documentation (#302) --- doc/content/bib/magpie.bib | 8 +++ .../PKAFissionFragmentNeutronics.md | 59 ++++++++++++++--- doc/content/index.md | 4 +- doc/hidden.yml | 1 - .../PKAFissionFragmentNeutronics.h | 11 ---- .../userobjects/PKAGeneratorNeutronicsBase.h | 14 ++++ include/userobjects/PKAGeneratorRecoil.h | 7 -- .../PKAFissionFragmentNeutronics.C | 38 ++--------- src/userobjects/PKAGeneratorNeutronicsBase.C | 64 +++++++++++++++++++ src/userobjects/PKAGeneratorRecoil.C | 38 ++--------- .../coupled_fission_mockup/damage_sub.i | 2 +- .../coupled_fission_mockup/damage_sub_3D.i | 2 +- 12 files changed, 153 insertions(+), 95 deletions(-) create mode 100644 doc/content/bib/magpie.bib diff --git a/doc/content/bib/magpie.bib b/doc/content/bib/magpie.bib new file mode 100644 index 00000000..ef598ba8 --- /dev/null +++ b/doc/content/bib/magpie.bib @@ -0,0 +1,8 @@ + +@inproceedings{SchunertTREATHeatSource, + author={S. Schunert and others}, + title={{Heat Source Characterization In A TREAT Fuel Particle Using Coupled Neutronics Binary Collision Monte-Carlo Calculations}}, + booktitle={{M\&C 2017 International Conference on Mathematics \& Computational Methods Applied to Nuclear Science \& Engineering}}, + month={April}, + year={2017} +} diff --git a/doc/content/documentation/systems/UserObjects/PKAFissionFragmentNeutronics.md b/doc/content/documentation/systems/UserObjects/PKAFissionFragmentNeutronics.md index 388394a6..2c4e06d0 100644 --- a/doc/content/documentation/systems/UserObjects/PKAFissionFragmentNeutronics.md +++ b/doc/content/documentation/systems/UserObjects/PKAFissionFragmentNeutronics.md @@ -1,14 +1,57 @@ - - # PKAFissionFragmentNeutronics -!alert construction title=Undocumented Class -The PKAFissionFragmentNeutronics has not been documented, if you would like to contribute to MOOSE by -writing documentation, please see [/generate.md]. The content contained on this page explains -the typical documentation associated with a MooseObject; however, what is contained is ultimately -determined by what is necessary to make the documentation clear for users. +`PKAFissionFragmentNeutronics` generates primary knock-on atoms (PKA) originating +from fission reactions. The difference to PKAFissionFragmentEmpirical is that +it uses isotopic fission rates computed by neutronics calculations and ENDF data +for sampling fission product species. + +The partial fission rate for isotope $i$ is denoted by $F_i(\vec{r}, \vec{\rho})$. +It is computed by: + +\begin{equation} +F_i(\vec{r}, \vec{\rho}) = \sum\limits_{g=1}^G N_i(\vec{r},\vec{\rho})\sigma_{f,g,i} \phi_g(\vec{r}), +\end{equation} + +where $\vec{r}$ is the location in the macroscopic, neutronics domain and $\vec{\rho}$ +is the location in the microscopic domain. These two locations are separated because their +scale is significantly separated by 3-4 orders of magnitude. Changes with $\vec{r}$ are +understood to be smooth changes of the average composition of the microscopic domain (orders of centimeters), while changes with $\vec{\rho}$ captures compositional changes between grains on the microscopic domain, i.e. on the orders of micro-meters. The notation of separating the spatial dependence into two scales follows homogenization theory. $N_i$ is the number density of isotope $i$, $\sigma_{f,g,i}$ is the microscopic fission cross section of isotope $i$ in group $g$, and $\phi_g$ is the scalar flux in group $g$. + +Denoting the microscopic domain as $\Theta_m$ located at $\vec{r}_m$, we can define a slowly varying average of the number densities: + +\begin{equation} + N_i(\vec{r}) = \frac{1}{\Theta_m} \int_{\Theta_m} N_i(\vec{r},\vec{\rho}) d\vec{\rho}. +\end{equation} + +$N_i(\vec{r})$ is the number density provided to neutronics calculations. The nuclide fission rates computed by the neutronics calculation is consequently the slowly varying average +given by: + +\begin{equation} +F_i (\vec{r}) = \sum\limits_{g=1}^G N_i(\vec{r})\sigma_{f,g,i}. +\end{equation} + +The `PKAFissionFragmentNeutronics` accepts the values of $F_i (\vec{r}_m)$ as the `partial_reaction_rates` parameter. +The fission rate density in the microscopic domain is computed by: +\begin{equation} + F_i (\vec{r}_m, \vec{\rho}) = \frac{N_i(\vec{r}_m, \vec{\rho})}{N_i(\vec{r}_m)} F_i(\vec{r}_m), +\end{equation} +where $N_i(\vec{r}_m, \vec{\rho}) = \gamma_i(\vec{\rho}) / \Omega(\vec{\rho})$, $\Omega(\vec{\rho})$ is the site volume +that is provided to the `MyTRIMRasterizer` and $\gamma_i$ is the $i$-th variable provided to the `MyTRIMRasterizer's` `var` +parameter. Hence, we have: +\begin{equation} + F_i (\vec{r}_m, \vec{\rho}) = \frac{\gamma_i(\vec{\rho})}{\Omega(\vec{r}) N_i(\vec{r}_m)} F_i(\vec{r}_m), +\end{equation} + +`PKAFissionFragmentNeutronics` accepts the values of $N_i(\vec{r}_m)$ in the `averaged_number_densities` parameter. + +The expected number of fissions in a mesh element with index $j$ and volume $V_j$ is given by: + +\begin{equation} +C_i = \Delta t \int_{V_j} F_i (\vec{r}_m, \vec{\rho}) d\vec{\rho}. +\end{equation} -!syntax description /UserObjects/PKAFissionFragmentNeutronics +Non-integer results are rounded up with a probability of the $C_i - \text{int}(C_i)$; otherwise +rounded down. Two PKAs are created from each fission event. The algorithm for sampling their type, energy, and direction of motion is described in [cite:SchunertTREATHeatSource]. !syntax parameters /UserObjects/PKAFissionFragmentNeutronics diff --git a/doc/content/index.md b/doc/content/index.md index 74bebb96..6cdbb41d 100644 --- a/doc/content/index.md +++ b/doc/content/index.md @@ -7,7 +7,7 @@

Mesoscale-Atomistics Glue Program for Integrated Execution

Welcome to the Magpie project. Magpie is an Idaho National Laboratory tool for coupling -atomistic and Monte Carlo methods to the finite element MOOSE framework. Iy currently couples -the MyTRIM binary collision Montecarlo code and teh Sandia SPPARKS Monte Carlo toolkit. +atomistic and Monte Carlo methods to the finite element MOOSE framework. It currently couples +the MyTRIM binary collision Monte-Carlo code and the Sandia SPPARKS Monte Carlo toolkit. diff --git a/doc/hidden.yml b/doc/hidden.yml index 27082f04..3e91eae6 100644 --- a/doc/hidden.yml +++ b/doc/hidden.yml @@ -21,7 +21,6 @@ - /UserObjects/NeutronicsSpectrumSamplerFission - /UserObjects/PKAConstant - /UserObjects/PKAFissionFragmentEmpirical -- /UserObjects/PKAFissionFragmentNeutronics - /UserObjects/PKAFixedPointGenerator - /UserObjects/PKAFunction - /UserObjects/PKAGeneratorAlphaDecay diff --git a/include/userobjects/PKAFissionFragmentNeutronics.h b/include/userobjects/PKAFissionFragmentNeutronics.h index 6536398a..8d77d9c8 100644 --- a/include/userobjects/PKAFissionFragmentNeutronics.h +++ b/include/userobjects/PKAFissionFragmentNeutronics.h @@ -34,17 +34,6 @@ class PKAFissionFragmentNeutronics : public PKAGeneratorNeutronicsBase * will be sampled to determine the initial state of the ions passed to MyTRIM */ DiscreteFissionPKAPDF _pdf; - - /** - * _partial_fission_rates: the fission rate per nuclide is: - * f_i = N_i * (sum_g sigma_{f,g,i} phi_g), - * where i is the nuclide ID and g is the energy group. - * N_i is provided as variable in the Rasterizer, but the (sum_g sigma_{f,g,i} phi_g) must - * be provided as partial fission rate here - * Note: the fission rate densities are in units of [ fissions / ( mesh-length-unit^3 sec ) ] - */ - std::vector _partial_fission_rates; - std::vector _stored_pps; }; #endif //PKAFISSIONFRAGMENTNEUTRONICS_H diff --git a/include/userobjects/PKAGeneratorNeutronicsBase.h b/include/userobjects/PKAGeneratorNeutronicsBase.h index 418bdbc2..48bb678b 100644 --- a/include/userobjects/PKAGeneratorNeutronicsBase.h +++ b/include/userobjects/PKAGeneratorNeutronicsBase.h @@ -31,6 +31,20 @@ class PKAGeneratorNeutronicsBase : public PKAGeneratorBase /// helper function to set pdf based on neutronics information from Mammoth virtual void setPDF(const std::vector & ZAID, const std::vector & energies, const MultiIndex & probabilities) = 0; + +protected: + /** + * The partial reaction rates are reaction rates of a certain type from the neutronics calculation + * for a single nculide, e.g. fission rate of U-235. NOTE: it multiplies the smoothly varying _average_ + * of the U-235 number density already + * + * In Magpie, the rate of PKA creation is obtained by multiplying with the _local_ number density. To obtain a + * proper PKA creation rate, we need to divide by the smooth average. This is what _averaged_number_densities is for. + */ + std::vector _partial_neutronics_reaction_rates; + std::vector _stored_reaction_rates; + std::vector _averaged_number_densities; + std::vector _stored_densities; }; #endif // PKAGENERATORNEUTRONICSBASE_H diff --git a/include/userobjects/PKAGeneratorRecoil.h b/include/userobjects/PKAGeneratorRecoil.h index 445ab21b..3f3e9e93 100644 --- a/include/userobjects/PKAGeneratorRecoil.h +++ b/include/userobjects/PKAGeneratorRecoil.h @@ -33,13 +33,6 @@ class PKAGeneratorRecoil : public PKAGeneratorNeutronicsBase * are sampled */ DiscretePKAPDF _pdf; - - /** - * The partial reaction rates [# reactions / time / volume] of the particular - * recoil reaction for each nuclide - */ - std::vector _partial_recoil_rates; - std::vector _stored_pps; }; #endif //PKAGeneratorRecoil_H diff --git a/src/userobjects/PKAFissionFragmentNeutronics.C b/src/userobjects/PKAFissionFragmentNeutronics.C index e6814d95..c6a1a69d 100644 --- a/src/userobjects/PKAFissionFragmentNeutronics.C +++ b/src/userobjects/PKAFissionFragmentNeutronics.C @@ -17,8 +17,6 @@ template<> InputParameters validParams() { InputParameters params = validParams(); - params.addParam>("partial_fission_rates", "Partial fission rates per unit volume [sum_g sigma_{f,g,i} * phi_g]. " - "Provide number density as variable in rasterizer!"); params.addClassDescription("PKA generator (fission) user object.\n Takes pdf and samples PKAs due to fission."); return params; } @@ -26,32 +24,6 @@ InputParameters validParams() PKAFissionFragmentNeutronics::PKAFissionFragmentNeutronics(const InputParameters & parameters): PKAGeneratorNeutronicsBase(parameters) { - if (isParamValid("partial_fission_rates")) - { - std::vector names = getParam>("partial_fission_rates"); - _partial_fission_rates.resize(names.size()); - _stored_pps.resize(names.size()); - for (unsigned int j = 0; j < names.size(); ++j) - if (_fe_problem.hasPostprocessor(names[j])) - _partial_fission_rates[j] = &getPostprocessorValueByName(names[j]); - else - { - Real real_value = -std::numeric_limits::max(); - std::istringstream ss(names[j]); - - if (ss >> real_value && ss.eof()) - _stored_pps[j] = real_value; - else - mooseError("Illegal entry in partial_fission_rates: ", names[j]); - - _partial_fission_rates[j] = &_stored_pps[j]; - } - } - else - { - _stored_pps = {1.0e-8}; - _partial_fission_rates = {&_stored_pps[0]}; - } } void @@ -66,12 +38,14 @@ PKAFissionFragmentNeutronics::appendPKAs(std::vector & ion_l mooseAssert(dt >= 0, "Passed a negative time window into PKAFissionFragmentNeutronics::appendPKAs"); mooseAssert(vol >= 0, "Passed a negative volume into PKAFissionFragmentNeutronics::appendPKAs"); - if (averaged_data._elements.size() != _partial_fission_rates.size()) - mooseError("Size of averaged_data and partial_fission_rates must be equal"); + if (averaged_data._elements.size() != _partial_neutronics_reaction_rates.size()) + mooseError("Size of averaged_data and partial_reaction_rates must be equal"); - for (unsigned int nuclide = 0; nuclide < _partial_fission_rates.size(); ++nuclide) + for (unsigned int nuclide = 0; nuclide < _partial_neutronics_reaction_rates.size(); ++nuclide) { - unsigned int num_fission = std::floor(recoil_rate_scaling * dt * vol * (*_partial_fission_rates[nuclide]) * averaged_data._elements[nuclide] + getRandomReal()); + unsigned int num_fission = std::floor(recoil_rate_scaling * dt * vol * + (*_partial_neutronics_reaction_rates[nuclide]) / (*_averaged_number_densities[nuclide] * averaged_data._site_volume) + * averaged_data._elements[nuclide] + getRandomReal()); for (unsigned i = 0; i < num_fission; ++i) { diff --git a/src/userobjects/PKAGeneratorNeutronicsBase.C b/src/userobjects/PKAGeneratorNeutronicsBase.C index 80521f80..8fa54de7 100644 --- a/src/userobjects/PKAGeneratorNeutronicsBase.C +++ b/src/userobjects/PKAGeneratorNeutronicsBase.C @@ -15,6 +15,9 @@ template<> InputParameters validParams() { InputParameters params = validParams(); + params.addParam>("partial_reaction_rates", "Partial neutronic reaction rates per unit volume [sum_g xs_{r,g,i} * phi_g], " + "r: reaction type, g: energy group, i: nuclide id.Provide number density as variable in rasterizer!"); + params.addParam>("averaged_number_densities", "The number density of the species averaged over the domain."); params.addClassDescription("PKA generator (neutronics) user object base class.\n Takes pdf and samples PKAs due to various interactions."); return params; } @@ -22,4 +25,65 @@ InputParameters validParams() PKAGeneratorNeutronicsBase::PKAGeneratorNeutronicsBase(const InputParameters & parameters) : PKAGeneratorBase(parameters) { + if (isParamValid("partial_reaction_rates")) + { + std::vector names = getParam>("partial_reaction_rates"); + _partial_neutronics_reaction_rates.resize(names.size()); + _stored_reaction_rates.resize(names.size()); + for (unsigned int j = 0; j < names.size(); ++j) + if (_fe_problem.hasPostprocessor(names[j])) + _partial_neutronics_reaction_rates[j] = &getPostprocessorValueByName(names[j]); + else + { + Real real_value = -std::numeric_limits::max(); + std::istringstream ss(names[j]); + + if (ss >> real_value && ss.eof()) + _stored_reaction_rates[j] = real_value; + else + mooseError("Illegal entry in _partial_neutronics_reaction_rates: ", names[j]); + + _partial_neutronics_reaction_rates[j] = &_stored_reaction_rates[j]; + } + } + else + { + _stored_reaction_rates = {1.0e-8}; + _partial_neutronics_reaction_rates = {&_stored_reaction_rates[0]}; + } + + if (isParamValid("averaged_number_densities")) + { + std::vector names = getParam>("averaged_number_densities"); + _averaged_number_densities.resize(names.size()); + _stored_densities.resize(names.size()); + for (unsigned int j = 0; j < names.size(); ++j) + if (_fe_problem.hasPostprocessor(names[j])) + _averaged_number_densities[j] = &getPostprocessorValueByName(names[j]); + else + { + Real real_value = -std::numeric_limits::max(); + std::istringstream ss(names[j]); + + if (ss >> real_value && ss.eof()) + _stored_densities[j] = real_value; + else + mooseError("Illegal entry in _partial_neutronics_reaction_rates: ", names[j]); + + _averaged_number_densities[j] = &_stored_reaction_rates[j]; + } + } + else + { + _averaged_number_densities.resize(_stored_reaction_rates.size()); + _stored_densities.resize(_stored_reaction_rates.size()); + for (unsigned int j = 0; j < _stored_densities.size(); ++j) + { + _stored_densities[j] = 1; + _averaged_number_densities[j] = &_stored_densities[j]; + } + } + + if (_averaged_number_densities.size() != _partial_neutronics_reaction_rates.size()) + mooseError("partial_reaction_rates and averaged_number_densities must have the same number of entries."); } diff --git a/src/userobjects/PKAGeneratorRecoil.C b/src/userobjects/PKAGeneratorRecoil.C index fd791c40..9c1a2144 100644 --- a/src/userobjects/PKAGeneratorRecoil.C +++ b/src/userobjects/PKAGeneratorRecoil.C @@ -16,8 +16,6 @@ template<> InputParameters validParams() { InputParameters params = validParams(); - params.addParam>("partial_recoil_rates", "Partial recoil rates per unit volume [recoil reaction rate per nuclide]. " - "Provide number density as variable in rasterizer!"); params.addClassDescription("PKA recoil generator user object.\n Takes pdf and samples PKAs due to recoil reaction."); return params; } @@ -25,32 +23,6 @@ InputParameters validParams() PKAGeneratorRecoil::PKAGeneratorRecoil(const InputParameters & parameters): PKAGeneratorNeutronicsBase(parameters) { - if (isParamValid("partial_recoil_rates")) - { - std::vector names = getParam>("partial_recoil_rates"); - _partial_recoil_rates.resize(names.size()); - _stored_pps.resize(names.size()); - for (unsigned int j = 0; j < names.size(); ++j) - if (_fe_problem.hasPostprocessor(names[j])) - _partial_recoil_rates[j] = &getPostprocessorValueByName(names[j]); - else - { - Real real_value = -std::numeric_limits::max(); - std::istringstream ss(names[j]); - - if (ss >> real_value && ss.eof()) - _stored_pps[j] = real_value; - else - mooseError("Illegal entry in partial_recoil_rates: ", names[j]); - - _partial_recoil_rates[j] = &_stored_pps[j]; - } - } - else - { - _stored_pps = {1.0e-8}; - _partial_recoil_rates = {&_stored_pps[0]}; - } } void @@ -68,12 +40,14 @@ PKAGeneratorRecoil::appendPKAs(std::vector & ion_list, Real mooseAssert(dt >= 0, "Passed a negative time window into PKAGeneratorRecoil::appendPKAs"); mooseAssert(vol >= 0, "Passed a negative volume into PKAGeneratorRecoil::appendPKAs"); - if (averaged_data._elements.size() != _partial_recoil_rates.size()) - mooseError("Size of averaged_data and partial_recoil_rates must be equal"); + if (averaged_data._elements.size() != _partial_neutronics_reaction_rates.size()) + mooseError("Size of averaged_data and partial_reaction_rates must be equal"); - for (unsigned int nuclide = 0; nuclide < _partial_recoil_rates.size(); ++nuclide) + for (unsigned int nuclide = 0; nuclide < _partial_neutronics_reaction_rates.size(); ++nuclide) { - unsigned int num_recoils = std::floor(recoil_rate_scaling * dt * vol * (*_partial_recoil_rates[nuclide]) * averaged_data._elements[nuclide] + getRandomReal()); + unsigned int num_recoils = std::floor(recoil_rate_scaling * dt * vol * + (*_partial_neutronics_reaction_rates[nuclide]) / (*_averaged_number_densities[nuclide]) + * averaged_data._elements[nuclide] + getRandomReal()); for (unsigned i = 0; i < num_recoils; ++i) { diff --git a/tests/radiation_damage/coupled_fission_mockup/damage_sub.i b/tests/radiation_damage/coupled_fission_mockup/damage_sub.i index caa11f37..98da393b 100644 --- a/tests/radiation_damage/coupled_fission_mockup/damage_sub.i +++ b/tests/radiation_damage/coupled_fission_mockup/damage_sub.i @@ -137,7 +137,7 @@ [./neutronics_fission_generator] type = PKAFissionFragmentNeutronics relative_density = 1 - partial_fission_rates = '2.0e-11 0 0' + partial_reaction_rates = '2.0e-11 0 0' [../] [./rasterizer] type = MyTRIMRasterizer diff --git a/tests/radiation_damage/coupled_fission_mockup/damage_sub_3D.i b/tests/radiation_damage/coupled_fission_mockup/damage_sub_3D.i index 86492849..40cc18bc 100644 --- a/tests/radiation_damage/coupled_fission_mockup/damage_sub_3D.i +++ b/tests/radiation_damage/coupled_fission_mockup/damage_sub_3D.i @@ -138,7 +138,7 @@ [./neutronics_fission_generator] type = PKAFissionFragmentNeutronics relative_density = 1 - partial_fission_rates = '1.0e-14 0 0' + partial_reaction_rates = '1.0e-14 0 0' [../] [./rasterizer] type = MyTRIMRasterizer