Merge pull request #1603 from paulromano/photon-tally-fix

Bug fix for tallying reaction rates in coupled neutron-photon runs
This commit is contained in:
Amanda Lund 2020-07-14 09:28:44 -05:00 committed by GitHub
commit 80ddc6e379
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 174 additions and 49 deletions

View file

@ -108,9 +108,11 @@ The following tables show all valid scores:
+----------------------+---------------------------------------------------+
|Score | Description |
+======================+===================================================+
|absorption |Total absorption rate. This accounts for all |
| |reactions which do not produce secondary neutrons |
| |as well as fission. |
|absorption |Total absorption rate. For incident neutrons, this |
| |accounts for all reactions that do not produce |
| |secondary neutrons as well as fission. For incident|
| |photons, this includes photoelectric and pair |
| |production. |
+----------------------+---------------------------------------------------+
|elastic |Elastic scattering reaction rate. |
+----------------------+---------------------------------------------------+
@ -194,6 +196,14 @@ The following tables show all valid scores:
+----------------------+---------------------------------------------------+
|(n,da) |(n,d\ :math:`\alpha`\ ) reaction rate. |
+----------------------+---------------------------------------------------+
|coherent-scatter |Coherent (Rayleigh) scattering reaction rate. |
+----------------------+---------------------------------------------------+
|incoherent-scatter |Incoherent (Compton) scattering reaction rate. |
+----------------------+---------------------------------------------------+
|photoelectric |Photoelectric absorption reaction rate. |
+----------------------+---------------------------------------------------+
|pair-production |Pair production reaction rate. |
+----------------------+---------------------------------------------------+
|*Arbitrary integer* |An arbitrary integer is interpreted to mean the |
| |reaction rate for a reaction with a given ENDF MT |
| |number. |

View file

@ -159,11 +159,11 @@ const std::unordered_map<int, std::string> REACTION_NAME_MAP {
{N_XA, "(n,Xa)"},
{HEATING, "heating"},
{DAMAGE_ENERGY, "damage-energy"},
{COHERENT, "coherent scatter"},
{INCOHERENT, "incoherent scatter"},
{PAIR_PROD_ELEC, "pair production, electron"},
{PAIR_PROD, "pair production"},
{PAIR_PROD_NUC, "pair production, nuclear"},
{COHERENT, "coherent-scatter"},
{INCOHERENT, "incoherent-scatter"},
{PAIR_PROD_ELEC, "pair-production-electron"},
{PAIR_PROD, "pair-production"},
{PAIR_PROD_NUC, "pair-production-nuclear"},
{PHOTOELECTRIC, "photoelectric"},
{N_PC, "(n,pc)"},
{N_DC, "(n,dc)"},

View file

@ -216,6 +216,14 @@ score_str_to_int(std::string score_str)
return N_XA;
if (score_str == "damage-energy")
return DAMAGE_ENERGY;
if (score_str == "coherent-scatter")
return COHERENT;
if (score_str == "incoherent-scatter")
return INCOHERENT;
if (score_str == "pair-production")
return PAIR_PROD;
if (score_str == "photoelectric")
return PHOTOELECTRIC;
// So far we have not identified this score string. Check to see if it is a
// deprecated score.

View file

@ -530,6 +530,8 @@ score_general_ce(Particle& p, int i_tally, int start_index, int filter_index,
// Get the pre-collision energy of the particle.
auto E = p.E_last_;
using Type = Particle::Type;
for (auto i = 0; i < tally.scores_.size(); ++i) {
auto score_bin = tally.scores_[i];
auto score_index = start_index + i;
@ -549,8 +551,7 @@ score_general_ce(Particle& p, int i_tally, int start_index, int filter_index,
score = p.wgt_last_;
}
if (p.type_ == Particle::Type::neutron ||
p.type_ == Particle::Type::photon) {
if (p.type_ == Type::neutron || p.type_ == Type::photon) {
score *= flux / p.macro_xs_.total;
} else {
score = 0.;
@ -575,9 +576,9 @@ score_general_ce(Particle& p, int i_tally, int start_index, int filter_index,
} else {
if (i_nuclide >= 0) {
if (p.type_ == Particle::Type::neutron) {
if (p.type_ == Type::neutron) {
score = p.neutron_xs_[i_nuclide].total * atom_density * flux;
} else if (p.type_ == Particle::Type::photon) {
} else if (p.type_ == Type::photon) {
score = p.photon_xs_[i_nuclide].total * atom_density * flux;
}
} else {
@ -588,6 +589,8 @@ score_general_ce(Particle& p, int i_tally, int start_index, int filter_index,
case SCORE_INVERSE_VELOCITY:
if (p.type_ != Type::neutron) continue;
if (tally.estimator_ == TallyEstimator::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
@ -609,6 +612,8 @@ score_general_ce(Particle& p, int i_tally, int start_index, int filter_index,
case SCORE_SCATTER:
if (p.type_ != Type::neutron && p.type_ != Type::photon) continue;
if (tally.estimator_ == TallyEstimator::ANALOG) {
// Skip any event where the particle didn't scatter
if (p.event_ != TallyEvent::SCATTER) continue;
@ -617,17 +622,27 @@ score_general_ce(Particle& p, int i_tally, int start_index, int filter_index,
score = p.wgt_last_ * flux;
} else {
if (i_nuclide >= 0) {
score = (p.neutron_xs_[i_nuclide].total
- p.neutron_xs_[i_nuclide].absorption) * atom_density * flux;
if (p.type_ == Type::neutron) {
const auto& micro = p.neutron_xs_[i_nuclide];
score = (micro.total - micro.absorption) * atom_density * flux;
} else {
const auto& micro = p.photon_xs_[i_nuclide];
score = (micro.coherent + micro.incoherent) * atom_density * flux;
}
} else {
score = (p.macro_xs_.total
- p.macro_xs_.absorption) * flux;
if (p.type_ == Type::neutron) {
score = (p.macro_xs_.total - p.macro_xs_.absorption) * flux;
} else {
score = (p.macro_xs_.coherent + p.macro_xs_.incoherent) * flux;
}
}
}
break;
case SCORE_NU_SCATTER:
if (p.type_ != Type::neutron) continue;
// Only analog estimators are available.
// Skip any event where the particle didn't scatter
if (p.event_ != TallyEvent::SCATTER) continue;
@ -649,6 +664,8 @@ score_general_ce(Particle& p, int i_tally, int start_index, int filter_index,
case SCORE_ABSORPTION:
if (p.type_ != Type::neutron && p.type_ != Type::photon) continue;
if (tally.estimator_ == TallyEstimator::ANALOG) {
if (settings::survival_biasing) {
// No absorption events actually occur if survival biasing is on --
@ -663,10 +680,18 @@ score_general_ce(Particle& p, int i_tally, int start_index, int filter_index,
}
} else {
if (i_nuclide >= 0) {
score = p.neutron_xs_[i_nuclide].absorption * atom_density
* flux;
if (p.type_ == Type::neutron) {
score = p.neutron_xs_[i_nuclide].absorption * atom_density * flux;
} else {
const auto& xs = p.photon_xs_[i_nuclide];
score = (xs.total - xs.coherent - xs.incoherent) * atom_density * flux;
}
} else {
score = p.macro_xs_.absorption * flux;
if (p.type_ == Type::neutron) {
score = p.macro_xs_.absorption * flux;
} else {
score = (p.macro_xs_.photoelectric + p.macro_xs_.pair_production) * flux;
}
}
}
break;
@ -1205,6 +1230,8 @@ score_general_ce(Particle& p, int i_tally, int start_index, int filter_index,
case ELASTIC:
if (p.type_ != Type::neutron) continue;
if (tally.estimator_ == TallyEstimator::ANALOG) {
// Check if event MT matches
if (p.event_mt_ != ELASTIC) continue;
@ -1244,6 +1271,8 @@ score_general_ce(Particle& p, int i_tally, int start_index, int filter_index,
case N_GAMMA:
case N_P:
case N_A:
if (p.type_ != Type::neutron) continue;
if (tally.estimator_ == TallyEstimator::ANALOG) {
// Check if the event MT matches
if (p.event_mt_ != score_bin) continue;
@ -1277,8 +1306,46 @@ score_general_ce(Particle& p, int i_tally, int start_index, int filter_index,
break;
case COHERENT:
case INCOHERENT:
case PHOTOELECTRIC:
case PAIR_PROD:
if (p.type_ != Type::photon) continue;
if (tally.estimator_ == TallyEstimator::ANALOG) {
if (score_bin == PHOTOELECTRIC) {
// Photoelectric events are assigned an MT value corresponding to the
// shell cross section. Also, photons below the energy cutoff are
// assumed to have been absorbed via photoelectric absorption
if ((p.event_mt_ < 534 || p.event_mt_ > 572) &&
p.event_mt_ != REACTION_NONE) continue;
} else {
if (p.event_mt_ != score_bin) continue;
}
score = p.wgt_last_ * flux;
} else {
if (i_nuclide >= 0) {
const auto& micro = p.photon_xs_[i_nuclide];
double xs =
(score_bin == COHERENT) ? micro.coherent :
(score_bin == INCOHERENT) ? micro.incoherent :
(score_bin == PHOTOELECTRIC) ? micro.photoelectric :
micro.pair_production;
score = xs * atom_density * flux;
} else {
double xs =
(score_bin == COHERENT) ? p.macro_xs_.coherent :
(score_bin == INCOHERENT) ? p.macro_xs_.incoherent :
(score_bin == PHOTOELECTRIC) ? p.macro_xs_.photoelectric :
p.macro_xs_.pair_production;
score = xs * flux;
}
}
break;
case HEATING:
if (p.type_ == Particle::Type::neutron) {
if (p.type_ == Type::neutron) {
score = score_neutron_heating(p, tally, flux, HEATING,
i_nuclide, atom_density);
} else {
@ -1301,7 +1368,7 @@ score_general_ce(Particle& p, int i_tally, int start_index, int filter_index,
// The default block is really only meant for redundant neutron reactions
// (e.g. 444, 901)
if (p.type_ != Particle::Type::neutron) continue;
if (p.type_ != Type::neutron) continue;
if (tally.estimator_ == TallyEstimator::ANALOG) {

View file

@ -50,19 +50,19 @@
<tally id="2">
<filters>2</filters>
<nuclides>Al27 total</nuclides>
<scores>total</scores>
<scores>total (n,gamma)</scores>
<estimator>tracklength</estimator>
</tally>
<tally id="3">
<filters>2</filters>
<nuclides>Al27 total</nuclides>
<scores>total heating</scores>
<scores>total heating (n,gamma)</scores>
<estimator>collision</estimator>
</tally>
<tally id="4">
<filters>2</filters>
<nuclides>Al27 total</nuclides>
<scores>total heating</scores>
<scores>total heating (n,gamma)</scores>
<estimator>analog</estimator>
</tally>
</tallies>

View file

@ -10,10 +10,16 @@ tally 1:
tally 2:
1.249805E+00
1.562013E+00
4.284760E-04
1.835917E-07
1.249805E+00
1.562013E+00
4.284760E-04
1.835917E-07
8.281718E-01
6.858685E-01
0.000000E+00
0.000000E+00
8.281718E-01
6.858685E-01
0.000000E+00
@ -24,52 +30,84 @@ tally 2:
0.000000E+00
0.000000E+00
0.000000E+00
0.000000E+00
0.000000E+00
0.000000E+00
0.000000E+00
0.000000E+00
0.000000E+00
0.000000E+00
0.000000E+00
0.000000E+00
0.000000E+00
tally 3:
1.248100E+00
1.557754E+00
2.024868E+06
4.100089E+12
4.283447E-04
1.834792E-07
1.248100E+00
1.557754E+00
2.024868E+06
4.100089E+12
8.242000E-01
6.793056E-01
5.827819E+03
3.396348E+07
4.283447E-04
1.834792E-07
8.242000E-01
6.793056E-01
5.827819E+03
3.396348E+07
0.000000E+00
0.000000E+00
8.242000E-01
6.793056E-01
5.827819E+03
3.396348E+07
0.000000E+00
0.000000E+00
1.834863E+05
3.366722E+10
0.000000E+00
0.000000E+00
1.834863E+05
3.366722E+10
0.000000E+00
0.000000E+00
5.938849E+03
3.526993E+07
0.000000E+00
0.000000E+00
1.834863E+05
3.366722E+10
0.000000E+00
0.000000E+00
0.000000E+00
0.000000E+00
5.938849E+03
3.526993E+07
0.000000E+00
0.000000E+00
0.000000E+00
0.000000E+00
5.938849E+03
3.526993E+07
0.000000E+00
0.000000E+00
tally 4:
1.248100E+00
1.557754E+00
2.024868E+06
4.100089E+12
0.000000E+00
0.000000E+00
1.248100E+00
1.557754E+00
2.024868E+06
4.100089E+12
0.000000E+00
0.000000E+00
2.308000E-01
5.326864E-02
5.822937E+03
3.390660E+07
0.000000E+00
0.000000E+00
8.242000E-01
6.793056E-01
5.827819E+03
@ -80,6 +118,10 @@ tally 4:
0.000000E+00
0.000000E+00
0.000000E+00
0.000000E+00
0.000000E+00
0.000000E+00
0.000000E+00
1.834863E+05
3.366722E+10
0.000000E+00
@ -88,5 +130,11 @@ tally 4:
0.000000E+00
0.000000E+00
0.000000E+00
0.000000E+00
0.000000E+00
0.000000E+00
0.000000E+00
5.938849E+03
3.526993E+07
0.000000E+00
0.000000E+00

View file

@ -48,17 +48,17 @@ def model():
current_tally.scores = ['current']
tally_tracklength = openmc.Tally()
tally_tracklength.filters = [particle_filter]
tally_tracklength.scores = ['total'] # heating doesn't work with tracklength
tally_tracklength.scores = ['total', '(n,gamma)'] # heating doesn't work with tracklength
tally_tracklength.nuclides = ['Al27', 'total']
tally_tracklength.estimator = 'tracklength'
tally_collision = openmc.Tally()
tally_collision.filters = [particle_filter]
tally_collision.scores = ['total', 'heating']
tally_collision.scores = ['total', 'heating', '(n,gamma)']
tally_collision.nuclides = ['Al27', 'total']
tally_collision.estimator = 'collision'
tally_analog = openmc.Tally()
tally_analog.filters = [particle_filter]
tally_analog.scores = ['total', 'heating']
tally_analog.scores = ['total', 'heating', '(n,gamma)']
tally_analog.nuclides = ['Al27', 'total']
tally_analog.estimator = 'analog'
model.tallies.extend([current_tally, tally_tracklength,

View file

@ -40,6 +40,6 @@
</filter>
<tally id="1">
<filters>1</filters>
<scores>flux</scores>
<scores>flux (n,gamma)</scores>
</tally>
</tallies>

View file

@ -1,3 +1,5 @@
tally 1:
sum = 2.275713E+02
sum_sq = 5.178870E+04
2.275713E+02
5.178870E+04
0.000000E+00
0.000000E+00

View file

@ -41,20 +41,10 @@ class SourceTestHarness(PyAPITestHarness):
particle_filter = openmc.ParticleFilter('photon')
tally = openmc.Tally()
tally.filters = [particle_filter]
tally.scores = ['flux']
tally.scores = ['flux', '(n,gamma)']
tallies = openmc.Tallies([tally])
tallies.export_to_xml()
def _get_results(self):
with openmc.StatePoint(self._sp_name) as sp:
outstr = ''
t = sp.get_tally()
outstr += 'tally {}:\n'.format(t.id)
outstr += 'sum = {:12.6E}\n'.format(t.sum[0, 0, 0])
outstr += 'sum_sq = {:12.6E}\n'.format(t.sum_sq[0, 0, 0])
return outstr
def test_photon_source():
harness = SourceTestHarness('statepoint.1.h5')