Skip to content

Commit d9b30bb

Browse files
GuyStennelsonagpaulromano
authored
Approximate multigroup velocity (#3766)
Co-authored-by: Adam Nelson <1037107+nelsonag@users.noreply.github.com> Co-authored-by: Paul Romano <paul.k.romano@gmail.com>
1 parent 97d9a83 commit d9b30bb

9 files changed

Lines changed: 145 additions & 13 deletions

File tree

docs/source/io_formats/mgxs_library.rst

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,10 @@ Temperature-dependent data, provided for temperature <TTT>K.
133133
This dataset is optional. This is a 1-D vector if `representation`
134134
is "isotropic", or a 3-D vector if `representation` is "angle"
135135
with dimensions of [polar][azimuthal][groups].
136+
When this data is not available, an approximation using the
137+
group energy boundaries is used. For more information see
138+
the particle speed subsection in the multigroup-data section
139+
of the theory manual.
136140

137141
**/<library name>/<TTT>K/scatter_data/**
138142

docs/source/methods/cross_sections.rst

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -289,6 +289,48 @@ sections. This allows flexibility for the model to use highly anisotropic
289289
scattering information in the water while the fuel can be simulated with linear
290290
or even isotropic scattering.
291291

292+
Particle Speed
293+
--------------
294+
295+
When using a multigroup representation of cross sections, the particle speed has
296+
meaning only in an average sense. The particle speed is important when modeling
297+
dynamic behavior. OpenMC calculates the particle speed using the inverse
298+
velocity multigroup data if it is available. If such data is not available,
299+
OpenMC uses an approximate velocity using the group energy bounds in the
300+
following way:
301+
302+
.. math::
303+
304+
\frac{1}{v_g} = \int_{E_{\text{min}}^g}^{E_{\text{max}}^g} \frac{1}{v(E)} \frac{\alpha}{E} dE
305+
306+
Where :math:`E_{\text{min}}^g` and :math:`E_{\text{max}}^g` are the group energy
307+
boundaries for group :math:`g`. :math:`v(E)` is the neutron velocity calculated
308+
using relativistic kinematics, :math:`\alpha` is a normalization constant for the
309+
:math:`\frac{1}{E}` spectrum.
310+
311+
This equation is valid when inside the group boundaries the neutron spectrum
312+
follows a typical :math:`\frac{1}{E}` slowing down spectrum. This assumption is
313+
widely used when generating fine group neutron cross section data libraries from
314+
continuous energy data.
315+
316+
The solution to this equation is:
317+
318+
.. math::
319+
320+
\frac{1}{v_g} = \frac{1}{c \log\left(\frac{E_{\text{max}}^g}{E_{\text{min}}^g}\right)}
321+
\left[ 2(\operatorname{arctanh}(k_{\text{max}}^{-1}) - \operatorname{arctanh}(k_{\text{min}}^{-1}))
322+
- (k_{\text{max}}-k_{\text{min}}) \right]
323+
324+
where :math:`c` is the speed of light and :math:`k_{\text{max}}`,
325+
:math:`k_{\text{min}}` are defined by a change of variables:
326+
327+
.. math::
328+
329+
k = \sqrt{1+\frac{2 m_n c^2}{E}}
330+
331+
where :math:`E` is the particle kinetic energy and :math:`m_n` is the neutron
332+
rest mass.
333+
292334
.. _logarithmic mapping technique:
293335
https://mcnp.lanl.gov/pdf_files/TechReport_2014_LANL_LA-UR-14-24530_Brown.pdf
294336
.. _Hwang: https://doi.org/10.13182/NSE87-A16381

include/openmc/mgxs_interface.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,8 @@ class MgxsInterface {
6161
vector<double> energy_bin_avg_;
6262
vector<double> rev_energy_bins_;
6363
vector<vector<double>> nuc_temps_; // all available temperatures
64+
vector<double>
65+
default_inverse_velocity_; // approximate default inverse-velocity data
6466
};
6567

6668
namespace data {

include/openmc/particle.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@ class Particle : public ParticleData {
3939

4040
double speed() const;
4141

42+
double mass() const;
43+
4244
//! create a secondary particle
4345
//
4446
//! stores the current phase space attributes of the particle in the

openmc/mgxs/groups.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ def group_edges(self):
7878
@group_edges.setter
7979
def group_edges(self, edges):
8080
cv.check_type('group edges', edges, Iterable, Real)
81+
cv.check_increasing('group edges', edges)
8182
cv.check_greater_than('number of group edges', len(edges), 1)
8283
self._group_edges = np.array(edges)
8384

src/mgxs.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -510,6 +510,8 @@ double Mgxs::get_xs(MgxsType xstype, int gin, const int* gout, const double* mu,
510510
break;
511511
case MgxsType::INVERSE_VELOCITY:
512512
val = xs_t->inverse_velocity(a, gin);
513+
if (!(val > 0))
514+
val = data::mg.default_inverse_velocity_[gin];
513515
break;
514516
case MgxsType::DECAY_RATE:
515517
if (dg != nullptr) {

src/mgxs_interface.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,19 @@ void MgxsInterface::read_header(const std::string& path_cross_sections)
237237
"library file!");
238238
}
239239

240+
// Calculate approximate default inverse velocity data
241+
for (int i = 0; i < energy_bins_.size() - 1; ++i) {
242+
double e_min = std::max(energy_bins_[i + 1], 1e-5);
243+
double e_max = energy_bins_[i];
244+
double alpha = 1.0 / (C_LIGHT * std::log(e_max / e_min));
245+
double k_max = std::sqrt(1 + 2.0 * MASS_NEUTRON_EV / e_max);
246+
double k_min = std::sqrt(1 + 2.0 * MASS_NEUTRON_EV / e_min);
247+
double inv_v =
248+
alpha * (2.0 * (std::atanh(1.0 / k_max) - std::atanh(1.0 / k_min)) -
249+
(k_max - k_min));
250+
default_inverse_velocity_.push_back(inv_v);
251+
}
252+
240253
// Close MGXS HDF5 file
241254
file_close(file_id);
242255
}

src/particle.cpp

Lines changed: 20 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -48,26 +48,33 @@ double Particle::speed() const
4848
{
4949
if (settings::run_CE) {
5050
// Determine mass in eV/c^2
51-
double mass;
52-
switch (type().pdg_number()) {
53-
case PDG_NEUTRON:
54-
mass = MASS_NEUTRON_EV;
55-
case PDG_ELECTRON:
56-
case PDG_POSITRON:
57-
mass = MASS_ELECTRON_EV;
58-
default:
59-
mass = this->type().mass() * AMU_EV;
60-
}
51+
double mass = this->mass();
6152

6253
// Equivalent to C * sqrt(1-(m/(m+E))^2) without problem at E<<m:
6354
return C_LIGHT * std::sqrt(this->E() * (this->E() + 2 * mass)) /
6455
(this->E() + mass);
6556
} else {
66-
auto& macro_xs = data::mg.macro_xs_[this->material()];
57+
auto mat = this->material();
58+
if (mat == MATERIAL_VOID)
59+
return 1.0 / data::mg.default_inverse_velocity_[this->g()];
60+
auto& macro_xs = data::mg.macro_xs_[mat];
6761
int macro_t = this->mg_xs_cache().t;
6862
int macro_a = macro_xs.get_angle_index(this->u());
69-
return 1.0 / macro_xs.get_xs(MgxsType::INVERSE_VELOCITY, this->g(), nullptr,
70-
nullptr, nullptr, macro_t, macro_a);
63+
return 1.0 / macro_xs.get_xs(
64+
MgxsType::INVERSE_VELOCITY, this->g(), macro_t, macro_a);
65+
}
66+
}
67+
68+
double Particle::mass() const
69+
{
70+
switch (type().pdg_number()) {
71+
case PDG_NEUTRON:
72+
return MASS_NEUTRON_EV;
73+
case PDG_ELECTRON:
74+
case PDG_POSITRON:
75+
return MASS_ELECTRON_EV;
76+
default:
77+
return this->type().mass() * AMU_EV;
7178
}
7279
}
7380

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import openmc
2+
import numpy as np
3+
import pytest
4+
5+
@pytest.fixture
6+
def one_group_lib():
7+
groups = openmc.mgxs.EnergyGroups([0.0, 20.0e6])
8+
xsdata = openmc.XSdata('slab_mat', groups)
9+
xsdata.order = 0
10+
xsdata.set_total([0.0])
11+
xsdata.set_absorption([0.0])
12+
xsdata.set_scatter_matrix([[[0.0]]])
13+
14+
mg_library = openmc.MGXSLibrary(groups)
15+
mg_library.add_xsdata(xsdata)
16+
name = 'mgxs.h5'
17+
mg_library.export_to_hdf5(name)
18+
yield name
19+
20+
@pytest.fixture
21+
def slab_model(one_group_lib):
22+
model = openmc.Model()
23+
mat = openmc.Material(name='slab_material')
24+
mat.set_density('macro', 1.0)
25+
mat.add_macroscopic('slab_mat')
26+
27+
model.materials = openmc.Materials([mat])
28+
model.materials.cross_sections = one_group_lib
29+
30+
x_min = openmc.XPlane(x0=0.0, boundary_type='vacuum')
31+
x_max = openmc.XPlane(x0=10.0, boundary_type='vacuum')
32+
33+
y_min = openmc.YPlane(y0=-10.0, boundary_type='vacuum')
34+
y_max = openmc.YPlane(y0=10.0, boundary_type='vacuum')
35+
z_min = openmc.ZPlane(z0=-10.0, boundary_type='vacuum')
36+
z_max = openmc.ZPlane(z0=19.0, boundary_type='vacuum')
37+
38+
cell = openmc.Cell(fill=mat, region=+z_min & -x_max & +y_min & -y_max & +z_min & -z_max)
39+
model.geometry = openmc.Geometry([cell])
40+
41+
model.settings = openmc.Settings()
42+
model.settings.energy_mode = 'multi-group'
43+
model.settings.run_mode = 'fixed source'
44+
model.settings.batches = 3
45+
model.settings.particles = 10
46+
47+
source = openmc.IndependentSource()
48+
source.space = openmc.stats.Point((5.0, 0.0, 0.0))
49+
model.settings.source = source
50+
return model
51+
52+
def test_inverse_velocity(run_in_tmpdir, slab_model):
53+
tally = openmc.Tally()
54+
tally.scores = ['flux','inverse-velocity']
55+
slab_model.tallies = [tally]
56+
slab_model.run(apply_tally_results=True)
57+
inverse_velocity = tally.mean.squeeze()[1]/tally.mean.squeeze()[0]
58+
59+
assert inverse_velocity == pytest.approx(1.6144e-5, rel=1e-4)

0 commit comments

Comments
 (0)