Compute fission heating and fission-less heating with njoy
This reverts d81aaeca9 and 81c625928 in the following ways.
Fission heating data, MT318, is pulled from the heatr file
produced when running NJOY. Reaction data that is potentially
temperature dependent is set onto the IncidentNeutron object
by scaling MT318 by the ratio of the fission cross section
used in HEATR and other fission cross sections stored on the
object. This produces potentially many MT318 fission heating
KERMA coefficients on the nuclide.
The fission-less heating coefficient, MT999, if computed by
subtracting MT318 from MT301, total heating coefficients. This
reaction is marked as redundant, as it can easily be reconstructed.
MT318 is allowed to be written to the HDF5 file, while 999 is not.
When reading back in the library, MT999 is rebuilt in exactly
the same manner.
The test_heating test in tests/unit_tests/test_data_neutron.py
has been updated given the changes in this commit.
It is worth noting that the heating coefficients computed in
NJOY only contain prompt neutrons, as
k_{i,j}(E) = sigma_{i,j}(E) * (E + Q - \bar{E})
where k_{i,j} is the kerma coefficient for reaction j of material
i, sigma_{i,j} is the corresponding reaction cross section, E
is the energy of incident particle, Q is the mass-difference
Q value, and \bar{E} is the average energy of secondary particles.
Source: NJOY16 Manual on HEATR
2019-09-03 17:09:15 -05:00
|
|
|
from collections.abc import Mapping, MutableMapping
|
2017-04-12 14:20:18 -05:00
|
|
|
from io import StringIO
|
2017-03-19 18:17:39 -05:00
|
|
|
from math import log10
|
2016-06-09 14:52:58 -05:00
|
|
|
from numbers import Integral, Real
|
2017-04-12 14:20:18 -05:00
|
|
|
import os
|
|
|
|
|
import tempfile
|
2016-07-19 22:32:24 -05:00
|
|
|
from warnings import warn
|
2016-06-09 14:52:58 -05:00
|
|
|
|
|
|
|
|
import numpy as np
|
|
|
|
|
import h5py
|
|
|
|
|
|
2016-10-31 15:37:49 -05:00
|
|
|
from . import HDF5_VERSION, HDF5_VERSION_MAJOR
|
2018-03-11 14:56:37 -05:00
|
|
|
from .ace import Library, Table, get_table, get_metadata
|
2025-12-04 06:53:04 -06:00
|
|
|
from .data import ATOMIC_SYMBOL, K_BOLTZMANN, EV_PER_MEV, gnds_name
|
2019-09-04 14:46:32 -05:00
|
|
|
from .endf import (
|
2026-05-29 21:53:45 -05:00
|
|
|
Evaluation, SUM_RULES, as_evaluation, get_head_record, get_tab1_record,
|
|
|
|
|
get_evaluations)
|
2016-07-29 10:50:08 -05:00
|
|
|
from .fission_energy import FissionEnergyRelease
|
2016-02-24 11:01:12 -06:00
|
|
|
from .function import Tabulated1D, Sum, ResonancesWithBackground
|
2024-08-14 15:34:17 +01:00
|
|
|
from .njoy import make_ace, make_pendf
|
2016-06-09 14:52:58 -05:00
|
|
|
from .product import Product
|
2020-04-15 06:51:00 -05:00
|
|
|
from .reaction import Reaction, _get_photon_products_ace, FISSION_MTS
|
2017-03-24 14:53:25 -05:00
|
|
|
from . import resonance as res
|
2017-12-15 13:03:45 -05:00
|
|
|
from . import resonance_covariance as res_cov
|
2016-06-09 14:52:58 -05:00
|
|
|
from .urr import ProbabilityTables
|
|
|
|
|
import openmc.checkvalue as cv
|
2016-08-19 06:58:17 -04:00
|
|
|
from openmc.mixin import EqualityMixin
|
2016-06-09 14:52:58 -05:00
|
|
|
|
|
|
|
|
|
2017-03-30 09:08:38 -05:00
|
|
|
# Fractions of resonance widths used for reconstructing resonances
|
|
|
|
|
_RESONANCE_ENERGY_GRID = np.logspace(-3, 3, 61)
|
|
|
|
|
|
|
|
|
|
|
2016-08-19 06:58:17 -04:00
|
|
|
class IncidentNeutron(EqualityMixin):
|
2016-06-09 14:52:58 -05:00
|
|
|
"""Continuous-energy neutron interaction data.
|
|
|
|
|
|
2016-11-03 14:58:26 -05:00
|
|
|
This class stores data derived from an ENDF-6 format neutron interaction
|
|
|
|
|
sublibrary. Instances of this class are not normally instantiated by the
|
|
|
|
|
user but rather created using the factory methods
|
|
|
|
|
:meth:`IncidentNeutron.from_hdf5`, :meth:`IncidentNeutron.from_ace`, and
|
2018-06-28 16:19:54 -05:00
|
|
|
:meth:`IncidentNeutron.from_endf`.
|
2016-06-09 14:52:58 -05:00
|
|
|
|
|
|
|
|
Parameters
|
2018-07-17 15:49:02 -05:00
|
|
|
----------
|
2016-06-09 14:52:58 -05:00
|
|
|
name : str
|
2022-09-08 14:25:22 -05:00
|
|
|
Name of the nuclide using the GNDS naming convention
|
2016-06-09 14:52:58 -05:00
|
|
|
atomic_number : int
|
2016-11-03 14:58:26 -05:00
|
|
|
Number of protons in the target nucleus
|
2016-06-09 14:52:58 -05:00
|
|
|
mass_number : int
|
2016-11-03 14:58:26 -05:00
|
|
|
Number of nucleons in the target nucleus
|
2016-06-09 14:52:58 -05:00
|
|
|
metastable : int
|
2016-11-03 14:58:26 -05:00
|
|
|
Metastable state of the target nucleus. A value of zero indicates ground
|
|
|
|
|
state.
|
2016-06-09 14:52:58 -05:00
|
|
|
atomic_weight_ratio : float
|
|
|
|
|
Atomic mass ratio of the target nuclide.
|
2016-08-24 19:57:16 -04:00
|
|
|
kTs : Iterable of float
|
2016-08-21 06:05:19 -04:00
|
|
|
List of temperatures of the target nuclide in the data set.
|
2016-10-24 18:44:51 -05:00
|
|
|
The temperatures have units of eV.
|
2016-06-09 14:52:58 -05:00
|
|
|
|
|
|
|
|
Attributes
|
|
|
|
|
----------
|
|
|
|
|
atomic_number : int
|
2016-11-03 14:58:26 -05:00
|
|
|
Number of protons in the target nucleus
|
2016-06-09 14:52:58 -05:00
|
|
|
atomic_symbol : str
|
|
|
|
|
Atomic symbol of the nuclide, e.g., 'Zr'
|
|
|
|
|
atomic_weight_ratio : float
|
|
|
|
|
Atomic weight ratio of the target nuclide.
|
2016-08-02 14:10:32 -05:00
|
|
|
fission_energy : None or openmc.data.FissionEnergyRelease
|
|
|
|
|
The energy released by fission, tabulated by component (e.g. prompt
|
|
|
|
|
neutrons or beta particles) and dependent on incident neutron energy
|
2016-06-09 14:52:58 -05:00
|
|
|
mass_number : int
|
2016-11-03 14:58:26 -05:00
|
|
|
Number of nucleons in the target nucleus
|
2016-06-09 14:52:58 -05:00
|
|
|
metastable : int
|
2016-11-03 14:58:26 -05:00
|
|
|
Metastable state of the target nucleus. A value of zero indicates ground
|
|
|
|
|
state.
|
2016-06-09 14:52:58 -05:00
|
|
|
name : str
|
2022-09-08 14:25:22 -05:00
|
|
|
Name of the nuclide using the GNDS naming convention
|
2023-08-19 13:15:11 +01:00
|
|
|
reactions : dict
|
2016-06-09 14:52:58 -05:00
|
|
|
Contains the cross sections, secondary angle and energy distributions,
|
|
|
|
|
and other associated data for each reaction. The keys are the MT values
|
|
|
|
|
and the values are Reaction objects.
|
2016-02-24 11:01:12 -06:00
|
|
|
resonances : openmc.data.Resonances or None
|
|
|
|
|
Resonance parameters
|
2017-12-15 13:03:45 -05:00
|
|
|
resonance_covariance : openmc.data.ResonanceCovariance or None
|
|
|
|
|
Covariance for resonance parameters
|
2016-08-25 13:58:53 -05:00
|
|
|
temperatures : list of str
|
2019-12-05 22:22:43 -06:00
|
|
|
List of string representations of the temperatures of the target nuclide
|
2016-08-24 19:57:16 -04:00
|
|
|
in the data set. The temperatures are strings of the temperature,
|
|
|
|
|
rounded to the nearest integer; e.g., '294K'
|
2016-08-21 06:05:19 -04:00
|
|
|
kTs : Iterable of float
|
|
|
|
|
List of temperatures of the target nuclide in the data set.
|
2016-10-24 18:44:51 -05:00
|
|
|
The temperatures have units of eV.
|
2016-08-25 13:58:53 -05:00
|
|
|
urr : dict
|
|
|
|
|
Dictionary whose keys are temperatures (e.g., '294K') and values are
|
|
|
|
|
unresolved resonance region probability tables.
|
2016-06-09 14:52:58 -05:00
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
def __init__(self, name, atomic_number, mass_number, metastable,
|
2016-08-21 06:05:19 -04:00
|
|
|
atomic_weight_ratio, kTs):
|
2016-06-09 14:52:58 -05:00
|
|
|
self.name = name
|
|
|
|
|
self.atomic_number = atomic_number
|
|
|
|
|
self.mass_number = mass_number
|
|
|
|
|
self.metastable = metastable
|
|
|
|
|
self.atomic_weight_ratio = atomic_weight_ratio
|
2016-08-21 06:05:19 -04:00
|
|
|
self.kTs = kTs
|
|
|
|
|
self.energy = {}
|
2016-07-29 10:50:08 -05:00
|
|
|
self._fission_energy = None
|
2023-08-19 13:15:11 +01:00
|
|
|
self.reactions = {}
|
2016-08-25 13:58:53 -05:00
|
|
|
self._urr = {}
|
2016-02-24 11:01:12 -06:00
|
|
|
self._resonances = None
|
2016-06-09 14:52:58 -05:00
|
|
|
|
2016-07-19 22:32:24 -05:00
|
|
|
def __contains__(self, mt):
|
2018-11-09 20:21:11 -06:00
|
|
|
return mt in self.reactions
|
2016-07-19 22:32:24 -05:00
|
|
|
|
|
|
|
|
def __getitem__(self, mt):
|
|
|
|
|
if mt in self.reactions:
|
|
|
|
|
return self.reactions[mt]
|
|
|
|
|
else:
|
2019-10-20 14:11:21 +08:00
|
|
|
# Try to create a redundant cross section
|
|
|
|
|
mts = self.get_reaction_components(mt)
|
|
|
|
|
if len(mts) > 0:
|
|
|
|
|
return self._get_redundant_reaction(mt, mts)
|
|
|
|
|
else:
|
2024-04-29 22:45:37 +01:00
|
|
|
raise KeyError(f'No reaction with MT={mt}.')
|
2016-07-19 22:32:24 -05:00
|
|
|
|
2016-06-09 14:52:58 -05:00
|
|
|
def __repr__(self):
|
2024-04-29 22:45:37 +01:00
|
|
|
return f"<IncidentNeutron: {self.name}>"
|
2016-06-09 14:52:58 -05:00
|
|
|
|
|
|
|
|
def __iter__(self):
|
|
|
|
|
return iter(self.reactions.values())
|
|
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def name(self):
|
|
|
|
|
return self._name
|
|
|
|
|
|
|
|
|
|
@name.setter
|
|
|
|
|
def name(self, name):
|
2017-12-24 16:06:05 +07:00
|
|
|
cv.check_type('name', name, str)
|
2016-06-09 14:52:58 -05:00
|
|
|
self._name = name
|
|
|
|
|
|
|
|
|
|
@property
|
2023-06-17 04:34:02 +01:00
|
|
|
def atomic_number(self):
|
|
|
|
|
return self._atomic_number
|
2016-06-09 14:52:58 -05:00
|
|
|
|
|
|
|
|
@atomic_number.setter
|
|
|
|
|
def atomic_number(self, atomic_number):
|
|
|
|
|
cv.check_type('atomic number', atomic_number, Integral)
|
2016-02-24 11:01:12 -06:00
|
|
|
cv.check_greater_than('atomic number', atomic_number, 0, True)
|
2016-06-09 14:52:58 -05:00
|
|
|
self._atomic_number = atomic_number
|
|
|
|
|
|
2023-06-17 04:34:02 +01:00
|
|
|
@property
|
|
|
|
|
def mass_number(self):
|
|
|
|
|
return self._mass_number
|
|
|
|
|
|
2016-06-09 14:52:58 -05:00
|
|
|
@mass_number.setter
|
|
|
|
|
def mass_number(self, mass_number):
|
|
|
|
|
cv.check_type('mass number', mass_number, Integral)
|
|
|
|
|
cv.check_greater_than('mass number', mass_number, 0, True)
|
|
|
|
|
self._mass_number = mass_number
|
|
|
|
|
|
2023-06-17 04:34:02 +01:00
|
|
|
@property
|
|
|
|
|
def metastable(self):
|
|
|
|
|
return self._metastable
|
|
|
|
|
|
2016-06-09 14:52:58 -05:00
|
|
|
@metastable.setter
|
|
|
|
|
def metastable(self, metastable):
|
|
|
|
|
cv.check_type('metastable', metastable, Integral)
|
|
|
|
|
cv.check_greater_than('metastable', metastable, 0, True)
|
|
|
|
|
self._metastable = metastable
|
|
|
|
|
|
2023-06-17 04:34:02 +01:00
|
|
|
@property
|
|
|
|
|
def atomic_weight_ratio(self):
|
|
|
|
|
return self._atomic_weight_ratio
|
|
|
|
|
|
2016-06-09 14:52:58 -05:00
|
|
|
@atomic_weight_ratio.setter
|
|
|
|
|
def atomic_weight_ratio(self, atomic_weight_ratio):
|
|
|
|
|
cv.check_type('atomic weight ratio', atomic_weight_ratio, Real)
|
|
|
|
|
cv.check_greater_than('atomic weight ratio', atomic_weight_ratio, 0.0)
|
|
|
|
|
self._atomic_weight_ratio = atomic_weight_ratio
|
|
|
|
|
|
2023-06-17 04:34:02 +01:00
|
|
|
@property
|
|
|
|
|
def fission_energy(self):
|
|
|
|
|
return self._fission_energy
|
|
|
|
|
|
2016-07-29 10:50:08 -05:00
|
|
|
@fission_energy.setter
|
|
|
|
|
def fission_energy(self, fission_energy):
|
|
|
|
|
cv.check_type('fission energy release', fission_energy,
|
|
|
|
|
FissionEnergyRelease)
|
|
|
|
|
self._fission_energy = fission_energy
|
|
|
|
|
|
2023-06-17 04:34:02 +01:00
|
|
|
@property
|
|
|
|
|
def reactions(self):
|
|
|
|
|
return self._reactions
|
|
|
|
|
|
2016-06-09 14:52:58 -05:00
|
|
|
@reactions.setter
|
|
|
|
|
def reactions(self, reactions):
|
|
|
|
|
cv.check_type('reactions', reactions, Mapping)
|
|
|
|
|
self._reactions = reactions
|
|
|
|
|
|
2023-06-17 04:34:02 +01:00
|
|
|
@property
|
|
|
|
|
def resonances(self):
|
|
|
|
|
return self._resonances
|
|
|
|
|
|
2016-02-24 11:01:12 -06:00
|
|
|
@resonances.setter
|
|
|
|
|
def resonances(self, resonances):
|
2017-03-24 14:53:25 -05:00
|
|
|
cv.check_type('resonances', resonances, res.Resonances)
|
2016-02-24 11:01:12 -06:00
|
|
|
self._resonances = resonances
|
|
|
|
|
|
2023-06-17 04:34:02 +01:00
|
|
|
@property
|
|
|
|
|
def resonance_covariance(self):
|
|
|
|
|
return self._resonance_covariance
|
|
|
|
|
|
2017-12-15 13:03:45 -05:00
|
|
|
@resonance_covariance.setter
|
|
|
|
|
def resonance_covariance(self, resonance_covariance):
|
2018-07-19 13:10:37 -05:00
|
|
|
cv.check_type('resonance covariance', resonance_covariance,
|
2018-07-23 13:43:07 -05:00
|
|
|
res_cov.ResonanceCovariances)
|
2018-07-19 13:10:37 -05:00
|
|
|
self._resonance_covariance = resonance_covariance
|
2017-12-15 13:03:45 -05:00
|
|
|
|
2023-06-17 04:34:02 +01:00
|
|
|
@property
|
|
|
|
|
def urr(self):
|
|
|
|
|
return self._urr
|
|
|
|
|
|
2016-06-09 14:52:58 -05:00
|
|
|
@urr.setter
|
|
|
|
|
def urr(self, urr):
|
2016-08-25 13:58:53 -05:00
|
|
|
cv.check_type('probability table dictionary', urr, MutableMapping)
|
|
|
|
|
for key, value in urr:
|
2017-12-24 16:06:05 +07:00
|
|
|
cv.check_type('probability table temperature', key, str)
|
2016-08-25 13:58:53 -05:00
|
|
|
cv.check_type('probability tables', value, ProbabilityTables)
|
2016-06-09 14:52:58 -05:00
|
|
|
self._urr = urr
|
|
|
|
|
|
2023-06-17 04:34:02 +01:00
|
|
|
@property
|
|
|
|
|
def temperatures(self):
|
2024-04-29 22:45:37 +01:00
|
|
|
return [f"{int(round(kT / K_BOLTZMANN))}K" for kT in self.kTs]
|
2023-06-17 04:34:02 +01:00
|
|
|
|
|
|
|
|
@property
|
|
|
|
|
def atomic_symbol(self):
|
|
|
|
|
return ATOMIC_SYMBOL[self.atomic_number]
|
|
|
|
|
|
2016-08-21 06:05:19 -04:00
|
|
|
def add_temperature_from_ace(self, ace_or_filename, metastable_scheme='nndc'):
|
2016-09-06 19:16:05 -04:00
|
|
|
"""Append data from an ACE file at a different temperature.
|
2016-08-21 06:05:19 -04:00
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
|
----------
|
|
|
|
|
ace_or_filename : openmc.data.ace.Table or str
|
|
|
|
|
ACE table to read from. If given as a string, it is assumed to be
|
|
|
|
|
the filename for the ACE file.
|
|
|
|
|
metastable_scheme : {'nndc', 'mcnp'}
|
|
|
|
|
Determine how ZAID identifiers are to be interpreted in the case of
|
|
|
|
|
a metastable nuclide. Because the normal ZAID (=1000*Z + A) does not
|
|
|
|
|
encode metastable information, different conventions are used among
|
|
|
|
|
different libraries. In MCNP libraries, the convention is to add 400
|
|
|
|
|
for a metastable nuclide except for Am242m, for which 95242 is
|
|
|
|
|
metastable and 95642 (or 1095242 in newer libraries) is the ground
|
|
|
|
|
state. For NNDC libraries, ZAID is given as 1000*Z + A + 100*m.
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
2016-08-25 11:17:37 -05:00
|
|
|
data = IncidentNeutron.from_ace(ace_or_filename, metastable_scheme)
|
2016-08-21 06:05:19 -04:00
|
|
|
|
2016-08-25 11:17:37 -05:00
|
|
|
# Check if temprature already exists
|
|
|
|
|
strT = data.temperatures[0]
|
|
|
|
|
if strT in self.temperatures:
|
2024-04-29 22:45:37 +01:00
|
|
|
warn(f'Cross sections at T={strT} already exist.')
|
2016-08-25 11:17:37 -05:00
|
|
|
return
|
2016-08-21 06:05:19 -04:00
|
|
|
|
2016-08-25 11:17:37 -05:00
|
|
|
# Check that name matches
|
|
|
|
|
if data.name != self.name:
|
|
|
|
|
raise ValueError('Data provided for an incorrect nuclide.')
|
2016-08-21 06:05:19 -04:00
|
|
|
|
2016-08-25 11:17:37 -05:00
|
|
|
# Add temperature
|
|
|
|
|
self.kTs += data.kTs
|
|
|
|
|
|
|
|
|
|
# Add energy grid
|
|
|
|
|
self.energy[strT] = data.energy[strT]
|
|
|
|
|
|
2018-09-13 07:18:47 -05:00
|
|
|
# Add normal and redundant reactions
|
2018-11-09 20:21:11 -06:00
|
|
|
for mt in data.reactions:
|
2016-09-20 11:35:29 -05:00
|
|
|
if mt in self:
|
|
|
|
|
self[mt].xs[strT] = data[mt].xs[strT]
|
|
|
|
|
else:
|
|
|
|
|
warn("Tried to add cross sections for MT={} at T={} but this "
|
|
|
|
|
"reaction doesn't exist.".format(mt, strT))
|
2016-08-21 06:05:19 -04:00
|
|
|
|
2016-08-25 13:58:53 -05:00
|
|
|
# Add probability tables
|
|
|
|
|
if strT in data.urr:
|
|
|
|
|
self.urr[strT] = data.urr[strT]
|
|
|
|
|
|
2024-08-14 15:34:17 +01:00
|
|
|
def add_elastic_0K_from_endf(self, filename, overwrite=False, **kwargs):
|
2017-03-19 18:17:39 -05:00
|
|
|
"""Append 0K elastic scattering cross section from an ENDF file.
|
|
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
|
----------
|
|
|
|
|
filename : str
|
|
|
|
|
Path to ENDF file
|
2017-03-22 09:37:18 -05:00
|
|
|
overwrite : bool
|
|
|
|
|
If existing 0 K data is present, this flag can be used to indicate
|
|
|
|
|
that it should be overwritten. Otherwise, an exception will be
|
|
|
|
|
thrown.
|
2024-08-14 15:34:17 +01:00
|
|
|
**kwargs
|
|
|
|
|
Keyword arguments passed to :func:`openmc.data.njoy.make_pendf`
|
2017-03-22 09:37:18 -05:00
|
|
|
|
|
|
|
|
Raises
|
|
|
|
|
------
|
|
|
|
|
ValueError
|
|
|
|
|
If 0 K data is already present and the `overwrite` parameter is
|
|
|
|
|
False.
|
2017-03-19 18:17:39 -05:00
|
|
|
|
|
|
|
|
"""
|
2017-03-22 09:37:18 -05:00
|
|
|
# Check for existing data
|
|
|
|
|
if '0K' in self.energy and not overwrite:
|
|
|
|
|
raise ValueError('0 K data already exists for this nuclide.')
|
|
|
|
|
|
2024-08-14 15:34:17 +01:00
|
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
|
|
|
# Set arguments for make_pendf
|
|
|
|
|
pendf_path = os.path.join(tmpdir, 'pendf')
|
|
|
|
|
kwargs.setdefault('output_dir', tmpdir)
|
|
|
|
|
kwargs.setdefault('pendf', pendf_path)
|
|
|
|
|
|
|
|
|
|
# Run NJOY to create a pointwise ENDF file
|
|
|
|
|
make_pendf(filename, **kwargs)
|
|
|
|
|
|
|
|
|
|
# Add 0K elastic scattering cross section
|
|
|
|
|
pendf = Evaluation(pendf_path)
|
|
|
|
|
file_obj = StringIO(pendf.section[3, 2])
|
|
|
|
|
get_head_record(file_obj)
|
|
|
|
|
params, xs = get_tab1_record(file_obj)
|
|
|
|
|
self.energy['0K'] = xs.x
|
|
|
|
|
self[2].xs['0K'] = xs
|
2017-03-19 18:17:39 -05:00
|
|
|
|
2018-11-12 15:19:04 -06:00
|
|
|
def get_reaction_components(self, mt):
|
2018-09-13 07:18:47 -05:00
|
|
|
"""Determine what reactions make up redundant reaction.
|
2016-07-19 22:32:24 -05:00
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
|
----------
|
|
|
|
|
mt : int
|
|
|
|
|
ENDF MT number of the reaction to find components of.
|
|
|
|
|
|
|
|
|
|
Returns
|
|
|
|
|
-------
|
|
|
|
|
mts : list of int
|
2018-09-13 07:18:47 -05:00
|
|
|
ENDF MT numbers of reactions that make up the redundant reaction and
|
2016-07-19 22:32:24 -05:00
|
|
|
have cross sections provided.
|
|
|
|
|
|
|
|
|
|
"""
|
2018-11-12 15:19:04 -06:00
|
|
|
mts = []
|
|
|
|
|
if mt in SUM_RULES:
|
|
|
|
|
for mt_i in SUM_RULES[mt]:
|
|
|
|
|
mts += self.get_reaction_components(mt_i)
|
|
|
|
|
if mts:
|
|
|
|
|
return mts
|
2017-12-14 22:04:42 +07:00
|
|
|
else:
|
2018-11-12 15:19:04 -06:00
|
|
|
return [mt] if mt in self else []
|
2016-07-19 22:32:24 -05:00
|
|
|
|
2017-12-17 19:48:08 -05:00
|
|
|
def export_to_hdf5(self, path, mode='a', libver='earliest'):
|
2016-10-11 14:28:58 -05:00
|
|
|
"""Export incident neutron data to an HDF5 file.
|
2016-06-09 14:52:58 -05:00
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
|
----------
|
|
|
|
|
path : str
|
|
|
|
|
Path to write HDF5 file to
|
2020-04-15 07:00:08 -05:00
|
|
|
mode : {'r+', 'w', 'x', 'a'}
|
2016-06-09 14:52:58 -05:00
|
|
|
Mode that is used to open the HDF5 file. This is the second argument
|
|
|
|
|
to the :class:`h5py.File` constructor.
|
2017-12-17 19:48:08 -05:00
|
|
|
libver : {'earliest', 'latest'}
|
|
|
|
|
Compatibility mode for the HDF5 file. 'latest' will produce files
|
|
|
|
|
that are less backwards compatible but have performance benefits.
|
2016-06-09 14:52:58 -05:00
|
|
|
|
|
|
|
|
"""
|
2016-10-11 14:28:58 -05:00
|
|
|
# If data come from ENDF, don't allow exporting to HDF5
|
|
|
|
|
if hasattr(self, '_evaluation'):
|
|
|
|
|
raise NotImplementedError('Cannot export incident neutron data that '
|
|
|
|
|
'originated from an ENDF file.')
|
2016-06-09 14:52:58 -05:00
|
|
|
|
2016-10-31 15:37:49 -05:00
|
|
|
# Open file and write version
|
2020-04-15 07:29:04 -05:00
|
|
|
with h5py.File(str(path), mode, libver=libver) as f:
|
2024-03-26 11:00:06 -05:00
|
|
|
f.attrs['filetype'] = np.bytes_('data_neutron')
|
2020-04-15 07:29:04 -05:00
|
|
|
f.attrs['version'] = np.array(HDF5_VERSION)
|
|
|
|
|
|
|
|
|
|
# Write basic data
|
|
|
|
|
g = f.create_group(self.name)
|
|
|
|
|
g.attrs['Z'] = self.atomic_number
|
|
|
|
|
g.attrs['A'] = self.mass_number
|
|
|
|
|
g.attrs['metastable'] = self.metastable
|
|
|
|
|
g.attrs['atomic_weight_ratio'] = self.atomic_weight_ratio
|
|
|
|
|
ktg = g.create_group('kTs')
|
|
|
|
|
for i, temperature in enumerate(self.temperatures):
|
|
|
|
|
ktg.create_dataset(temperature, data=self.kTs[i])
|
|
|
|
|
|
|
|
|
|
# Write energy grid
|
|
|
|
|
eg = g.create_group('energy')
|
|
|
|
|
for temperature in self.temperatures:
|
|
|
|
|
eg.create_dataset(temperature, data=self.energy[temperature])
|
|
|
|
|
|
|
|
|
|
# Write 0K energy grid if needed
|
|
|
|
|
if '0K' in self.energy and '0K' not in eg:
|
|
|
|
|
eg.create_dataset('0K', data=self.energy['0K'])
|
|
|
|
|
|
|
|
|
|
# Write reaction data
|
|
|
|
|
rxs_group = g.create_group('reactions')
|
|
|
|
|
for rx in self.reactions.values():
|
|
|
|
|
# Skip writing redundant reaction if it doesn't have photon
|
|
|
|
|
# production or is a summed transmutation reaction. MT=4 is also
|
|
|
|
|
# sometimes needed for probability tables. Also write gas
|
|
|
|
|
# production, heating, and damage energy production.
|
|
|
|
|
if rx.redundant:
|
|
|
|
|
photon_rx = any(p.particle == 'photon' for p in rx.products)
|
|
|
|
|
keep_mts = (4, 16, 103, 104, 105, 106, 107,
|
|
|
|
|
203, 204, 205, 206, 207, 301, 444, 901)
|
|
|
|
|
if not (photon_rx or rx.mt in keep_mts):
|
|
|
|
|
continue
|
|
|
|
|
|
2024-04-29 22:45:37 +01:00
|
|
|
rx_group = rxs_group.create_group(f'reaction_{rx.mt:03}')
|
2020-04-15 07:29:04 -05:00
|
|
|
rx.to_hdf5(rx_group)
|
|
|
|
|
|
|
|
|
|
# Write total nu data if available
|
|
|
|
|
if len(rx.derived_products) > 0 and 'total_nu' not in g:
|
|
|
|
|
tgroup = g.create_group('total_nu')
|
|
|
|
|
rx.derived_products[0].to_hdf5(tgroup)
|
|
|
|
|
|
|
|
|
|
# Write unresolved resonance probability tables
|
|
|
|
|
if self.urr:
|
|
|
|
|
urr_group = g.create_group('urr')
|
|
|
|
|
for temperature, urr in self.urr.items():
|
|
|
|
|
tgroup = urr_group.create_group(temperature)
|
|
|
|
|
urr.to_hdf5(tgroup)
|
|
|
|
|
|
|
|
|
|
# Write fission energy release data
|
|
|
|
|
if self.fission_energy is not None:
|
|
|
|
|
fer_group = g.create_group('fission_energy_release')
|
|
|
|
|
self.fission_energy.to_hdf5(fer_group)
|
2016-06-09 14:52:58 -05:00
|
|
|
|
|
|
|
|
@classmethod
|
2016-07-15 08:21:57 -05:00
|
|
|
def from_hdf5(cls, group_or_filename):
|
2016-06-09 14:52:58 -05:00
|
|
|
"""Generate continuous-energy neutron interaction data from HDF5 group
|
|
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
|
----------
|
|
|
|
|
group_or_filename : h5py.Group or str
|
|
|
|
|
HDF5 group containing interaction data. If given as a string, it is
|
|
|
|
|
assumed to be the filename for the HDF5 file, and the first group is
|
|
|
|
|
used to read from.
|
|
|
|
|
|
|
|
|
|
Returns
|
|
|
|
|
-------
|
2016-07-15 08:21:57 -05:00
|
|
|
openmc.data.IncidentNeutron
|
2016-06-09 14:52:58 -05:00
|
|
|
Continuous-energy neutron interaction data
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
if isinstance(group_or_filename, h5py.Group):
|
|
|
|
|
group = group_or_filename
|
|
|
|
|
else:
|
2018-11-02 07:34:34 -05:00
|
|
|
h5file = h5py.File(str(group_or_filename), 'r')
|
2016-10-31 15:37:49 -05:00
|
|
|
|
|
|
|
|
# Make sure version matches
|
|
|
|
|
if 'version' in h5file.attrs:
|
|
|
|
|
major, minor = h5file.attrs['version']
|
2018-11-13 06:51:03 -06:00
|
|
|
# For now all versions of HDF5 data can be read
|
2016-10-31 15:37:49 -05:00
|
|
|
else:
|
|
|
|
|
raise IOError(
|
|
|
|
|
'HDF5 data does not indicate a version. Your installation of '
|
|
|
|
|
'the OpenMC Python API expects version {}.x data.'
|
|
|
|
|
.format(HDF5_VERSION_MAJOR))
|
|
|
|
|
|
2016-06-09 14:52:58 -05:00
|
|
|
group = list(h5file.values())[0]
|
|
|
|
|
|
|
|
|
|
name = group.name[1:]
|
|
|
|
|
atomic_number = group.attrs['Z']
|
|
|
|
|
mass_number = group.attrs['A']
|
|
|
|
|
metastable = group.attrs['metastable']
|
|
|
|
|
atomic_weight_ratio = group.attrs['atomic_weight_ratio']
|
2016-08-21 13:43:07 -04:00
|
|
|
kTg = group['kTs']
|
|
|
|
|
kTs = []
|
|
|
|
|
for temp in kTg:
|
2019-03-26 22:43:33 -05:00
|
|
|
kTs.append(kTg[temp][()])
|
2016-06-09 14:52:58 -05:00
|
|
|
|
2016-07-15 08:21:57 -05:00
|
|
|
data = cls(name, atomic_number, mass_number, metastable,
|
2016-08-21 06:05:19 -04:00
|
|
|
atomic_weight_ratio, kTs)
|
2016-06-09 14:52:58 -05:00
|
|
|
|
|
|
|
|
# Read energy grid
|
2016-08-21 06:05:19 -04:00
|
|
|
e_group = group['energy']
|
2016-08-25 13:58:53 -05:00
|
|
|
for temperature, dset in e_group.items():
|
2019-03-26 22:43:33 -05:00
|
|
|
data.energy[temperature] = dset[()]
|
2016-06-09 14:52:58 -05:00
|
|
|
|
|
|
|
|
# Read reaction data
|
2016-07-22 15:35:17 -05:00
|
|
|
rxs_group = group['reactions']
|
|
|
|
|
for name, obj in sorted(rxs_group.items()):
|
|
|
|
|
if name.startswith('reaction_'):
|
2016-08-25 13:58:53 -05:00
|
|
|
rx = Reaction.from_hdf5(obj, data.energy)
|
2018-11-09 20:21:11 -06:00
|
|
|
data.reactions[rx.mt] = rx
|
2016-07-22 15:35:17 -05:00
|
|
|
|
|
|
|
|
# Read total nu data if available
|
2020-04-15 06:51:00 -05:00
|
|
|
if rx.mt in FISSION_MTS and 'total_nu' in group:
|
2016-07-22 15:35:17 -05:00
|
|
|
tgroup = group['total_nu']
|
|
|
|
|
rx.derived_products.append(Product.from_hdf5(tgroup))
|
2016-06-09 14:52:58 -05:00
|
|
|
|
|
|
|
|
# Read unresolved resonance probability tables
|
|
|
|
|
if 'urr' in group:
|
|
|
|
|
urr_group = group['urr']
|
2016-08-25 13:58:53 -05:00
|
|
|
for temperature, tgroup in urr_group.items():
|
|
|
|
|
data.urr[temperature] = ProbabilityTables.from_hdf5(tgroup)
|
2016-06-09 14:52:58 -05:00
|
|
|
|
2016-07-29 10:50:08 -05:00
|
|
|
# Read fission energy release data
|
|
|
|
|
if 'fission_energy_release' in group:
|
|
|
|
|
fer_group = group['fission_energy_release']
|
|
|
|
|
data.fission_energy = FissionEnergyRelease.from_hdf5(fer_group)
|
|
|
|
|
|
2016-06-09 14:52:58 -05:00
|
|
|
return data
|
|
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
|
def from_ace(cls, ace_or_filename, metastable_scheme='nndc'):
|
|
|
|
|
"""Generate incident neutron continuous-energy data from an ACE table
|
|
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
|
----------
|
2016-08-21 06:05:19 -04:00
|
|
|
ace_or_filename : openmc.data.ace.Table or str
|
|
|
|
|
ACE table to read from. If the value is a string, it is assumed to
|
|
|
|
|
be the filename for the ACE file.
|
2016-06-09 14:52:58 -05:00
|
|
|
metastable_scheme : {'nndc', 'mcnp'}
|
|
|
|
|
Determine how ZAID identifiers are to be interpreted in the case of
|
|
|
|
|
a metastable nuclide. Because the normal ZAID (=1000*Z + A) does not
|
|
|
|
|
encode metastable information, different conventions are used among
|
|
|
|
|
different libraries. In MCNP libraries, the convention is to add 400
|
|
|
|
|
for a metastable nuclide except for Am242m, for which 95242 is
|
|
|
|
|
metastable and 95642 (or 1095242 in newer libraries) is the ground
|
|
|
|
|
state. For NNDC libraries, ZAID is given as 1000*Z + A + 100*m.
|
|
|
|
|
|
|
|
|
|
Returns
|
|
|
|
|
-------
|
|
|
|
|
openmc.data.IncidentNeutron
|
|
|
|
|
Incident neutron continuous-energy data
|
|
|
|
|
|
|
|
|
|
"""
|
2016-08-21 06:05:19 -04:00
|
|
|
|
|
|
|
|
# First obtain the data for the first provided ACE table/file
|
2016-06-09 14:52:58 -05:00
|
|
|
if isinstance(ace_or_filename, Table):
|
|
|
|
|
ace = ace_or_filename
|
|
|
|
|
else:
|
|
|
|
|
ace = get_table(ace_or_filename)
|
|
|
|
|
|
|
|
|
|
# If mass number hasn't been specified, make an educated guess
|
|
|
|
|
zaid, xs = ace.name.split('.')
|
2019-09-25 12:36:47 -05:00
|
|
|
if not xs.endswith('c'):
|
|
|
|
|
raise TypeError(
|
2024-04-29 22:45:37 +01:00
|
|
|
f"{ace} is not a continuous-energy neutron ACE table.")
|
2016-08-21 06:05:19 -04:00
|
|
|
name, element, Z, mass_number, metastable = \
|
2018-03-11 14:56:37 -05:00
|
|
|
get_metadata(int(zaid), metastable_scheme)
|
2016-08-21 06:05:19 -04:00
|
|
|
|
|
|
|
|
# Assign temperature to the running list
|
2016-10-24 18:44:51 -05:00
|
|
|
kTs = [ace.temperature*EV_PER_MEV]
|
2016-08-21 11:37:39 -04:00
|
|
|
|
2016-07-15 08:21:57 -05:00
|
|
|
data = cls(name, Z, mass_number, metastable,
|
2016-08-21 06:05:19 -04:00
|
|
|
ace.atomic_weight_ratio, kTs)
|
2016-06-09 14:52:58 -05:00
|
|
|
|
2016-08-25 11:17:37 -05:00
|
|
|
# Get string of temperature to use as a dictionary key
|
|
|
|
|
strT = data.temperatures[0]
|
|
|
|
|
|
2016-06-09 14:52:58 -05:00
|
|
|
# Read energy grid
|
|
|
|
|
n_energy = ace.nxs[3]
|
2019-03-07 09:36:38 -06:00
|
|
|
i = ace.jxs[1]
|
|
|
|
|
energy = ace.xss[i : i + n_energy]*EV_PER_MEV
|
2016-08-25 11:17:37 -05:00
|
|
|
data.energy[strT] = energy
|
2019-03-07 09:36:38 -06:00
|
|
|
total_xs = ace.xss[i + n_energy : i + 2*n_energy]
|
|
|
|
|
absorption_xs = ace.xss[i + 2*n_energy : i + 3*n_energy]
|
2019-03-13 06:15:22 -05:00
|
|
|
heating_number = ace.xss[i + 4*n_energy : i + 5*n_energy]*EV_PER_MEV
|
2016-06-09 14:52:58 -05:00
|
|
|
|
2020-04-15 07:29:04 -05:00
|
|
|
# Create redundant reaction for total (MT=1)
|
2016-06-09 14:52:58 -05:00
|
|
|
total = Reaction(1)
|
2016-08-25 11:17:37 -05:00
|
|
|
total.xs[strT] = Tabulated1D(energy, total_xs)
|
2018-09-13 07:18:47 -05:00
|
|
|
total.redundant = True
|
2018-11-09 20:21:11 -06:00
|
|
|
data.reactions[1] = total
|
2016-08-25 11:17:37 -05:00
|
|
|
|
2020-04-15 07:29:04 -05:00
|
|
|
# Create redundant reaction for absorption (MT=101)
|
2016-09-02 17:12:43 -04:00
|
|
|
if np.count_nonzero(absorption_xs) > 0:
|
2018-11-12 07:52:07 -06:00
|
|
|
absorption = Reaction(101)
|
2016-09-02 17:12:43 -04:00
|
|
|
absorption.xs[strT] = Tabulated1D(energy, absorption_xs)
|
2018-09-13 07:18:47 -05:00
|
|
|
absorption.redundant = True
|
2018-11-12 07:52:07 -06:00
|
|
|
data.reactions[101] = absorption
|
2016-06-09 14:52:58 -05:00
|
|
|
|
2020-04-15 07:29:04 -05:00
|
|
|
# Create redundant reaction for heating (MT=301)
|
2019-03-07 09:36:38 -06:00
|
|
|
heating = Reaction(301)
|
2019-03-13 16:13:57 -05:00
|
|
|
heating.xs[strT] = Tabulated1D(energy, heating_number*total_xs)
|
2019-03-07 09:36:38 -06:00
|
|
|
heating.redundant = True
|
|
|
|
|
data.reactions[301] = heating
|
|
|
|
|
|
2016-06-09 14:52:58 -05:00
|
|
|
# Read each reaction
|
|
|
|
|
n_reaction = ace.nxs[4] + 1
|
|
|
|
|
for i in range(n_reaction):
|
|
|
|
|
rx = Reaction.from_ace(ace, i)
|
|
|
|
|
data.reactions[rx.mt] = rx
|
|
|
|
|
|
|
|
|
|
# Some photon production reactions may be assigned to MTs that don't
|
|
|
|
|
# exist, usually MT=4. In this case, we create a new reaction and add
|
|
|
|
|
# them
|
|
|
|
|
n_photon_reactions = ace.nxs[6]
|
|
|
|
|
photon_mts = ace.xss[ace.jxs[13]:ace.jxs[13] +
|
|
|
|
|
n_photon_reactions].astype(int)
|
|
|
|
|
|
|
|
|
|
for mt in np.unique(photon_mts // 1000):
|
2016-07-19 22:32:24 -05:00
|
|
|
if mt not in data:
|
|
|
|
|
if mt not in SUM_RULES:
|
|
|
|
|
warn('Photon production is present for MT={} but no '
|
|
|
|
|
'cross section is given.'.format(mt))
|
|
|
|
|
continue
|
|
|
|
|
|
2018-09-13 07:18:47 -05:00
|
|
|
# Create redundant reaction with appropriate cross section
|
2016-07-19 22:32:24 -05:00
|
|
|
mts = data.get_reaction_components(mt)
|
2016-08-08 06:49:24 -05:00
|
|
|
if len(mts) == 0:
|
|
|
|
|
warn('Photon production is present for MT={} but no '
|
|
|
|
|
'reaction components exist.'.format(mt))
|
|
|
|
|
continue
|
2018-08-23 16:29:40 -05:00
|
|
|
|
2018-09-13 07:18:47 -05:00
|
|
|
# Determine redundant cross section
|
2018-11-06 13:20:33 -06:00
|
|
|
rx = data._get_redundant_reaction(mt, mts)
|
2016-02-24 11:01:12 -06:00
|
|
|
rx.products += _get_photon_products_ace(ace, rx)
|
2018-11-09 20:21:11 -06:00
|
|
|
data.reactions[mt] = rx
|
2016-06-09 14:52:58 -05:00
|
|
|
|
2018-11-06 13:20:33 -06:00
|
|
|
# For transmutation reactions, sometimes only individual levels are
|
|
|
|
|
# present in an ACE file, e.g. MT=600-649 instead of the summation
|
|
|
|
|
# MT=103. In this case, if a user wants to tally (n,p), OpenMC doesn't
|
|
|
|
|
# know about the total cross section. Here, we explicitly create a
|
|
|
|
|
# redundant reaction for this purpose.
|
|
|
|
|
for mt in (16, 103, 104, 105, 106, 107):
|
|
|
|
|
if mt not in data:
|
|
|
|
|
# Determine if any individual levels are present
|
|
|
|
|
mts = data.get_reaction_components(mt)
|
|
|
|
|
if len(mts) == 0:
|
|
|
|
|
continue
|
|
|
|
|
|
|
|
|
|
# Determine redundant cross section
|
|
|
|
|
rx = data._get_redundant_reaction(mt, mts)
|
2018-11-09 20:21:11 -06:00
|
|
|
data.reactions[mt] = rx
|
2018-11-06 13:20:33 -06:00
|
|
|
|
2018-11-12 09:21:35 -06:00
|
|
|
# Make sure redundant cross sections that are present in an ACE file get
|
|
|
|
|
# marked as such
|
|
|
|
|
for rx in data:
|
2018-11-12 15:19:04 -06:00
|
|
|
mts = data.get_reaction_components(rx.mt)
|
|
|
|
|
if mts != [rx.mt]:
|
2018-11-12 09:21:35 -06:00
|
|
|
rx.redundant = True
|
2019-03-07 09:36:38 -06:00
|
|
|
if rx.mt in (203, 204, 205, 206, 207, 444):
|
|
|
|
|
rx.redundant = True
|
2018-11-12 09:21:35 -06:00
|
|
|
|
2016-06-09 14:52:58 -05:00
|
|
|
# Read unresolved resonance probability tables
|
2016-08-25 13:58:53 -05:00
|
|
|
urr = ProbabilityTables.from_ace(ace)
|
|
|
|
|
if urr is not None:
|
|
|
|
|
data.urr[strT] = urr
|
2016-06-09 14:52:58 -05:00
|
|
|
|
|
|
|
|
return data
|
2016-02-24 11:01:12 -06:00
|
|
|
|
|
|
|
|
@classmethod
|
2018-07-19 08:54:22 -05:00
|
|
|
def from_endf(cls, ev_or_filename, covariance=False):
|
2016-02-24 11:01:12 -06:00
|
|
|
"""Generate incident neutron continuous-energy data from an ENDF evaluation
|
|
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
|
----------
|
2026-05-29 21:53:45 -05:00
|
|
|
ev_or_filename : openmc.data.endf.Evaluation, endf.Material, or str
|
2016-02-24 11:01:12 -06:00
|
|
|
ENDF evaluation to read from. If given as a string, it is assumed to
|
|
|
|
|
be the filename for the ENDF file.
|
|
|
|
|
|
2018-07-19 08:54:22 -05:00
|
|
|
covariance : bool
|
2018-07-23 13:43:07 -05:00
|
|
|
Flag to indicate whether or not covariance data from File 32 should be
|
2017-12-15 13:03:45 -05:00
|
|
|
retrieved
|
|
|
|
|
|
2016-02-24 11:01:12 -06:00
|
|
|
Returns
|
|
|
|
|
-------
|
|
|
|
|
openmc.data.IncidentNeutron
|
|
|
|
|
Incident neutron continuous-energy data
|
|
|
|
|
|
|
|
|
|
"""
|
2026-05-29 21:53:45 -05:00
|
|
|
ev = as_evaluation(ev_or_filename)
|
2016-02-24 11:01:12 -06:00
|
|
|
|
|
|
|
|
atomic_number = ev.target['atomic_number']
|
|
|
|
|
mass_number = ev.target['mass_number']
|
|
|
|
|
metastable = ev.target['isomeric_state']
|
|
|
|
|
atomic_weight_ratio = ev.target['mass']
|
|
|
|
|
temperature = ev.target['temperature']
|
|
|
|
|
|
|
|
|
|
# Determine name
|
2025-12-04 06:53:04 -06:00
|
|
|
name = gnds_name(atomic_number, mass_number, metastable)
|
2016-02-24 11:01:12 -06:00
|
|
|
|
|
|
|
|
# Instantiate incident neutron data
|
|
|
|
|
data = cls(name, atomic_number, mass_number, metastable,
|
2019-09-13 07:54:30 -05:00
|
|
|
atomic_weight_ratio, [temperature])
|
2016-02-24 11:01:12 -06:00
|
|
|
|
|
|
|
|
if (2, 151) in ev.section:
|
2017-03-24 14:53:25 -05:00
|
|
|
data.resonances = res.Resonances.from_endf(ev)
|
2016-02-24 11:01:12 -06:00
|
|
|
|
2018-07-19 08:54:22 -05:00
|
|
|
if (32, 151) in ev.section and covariance:
|
2018-07-23 13:43:07 -05:00
|
|
|
data.resonance_covariance = (
|
|
|
|
|
res_cov.ResonanceCovariances.from_endf(ev, data.resonances)
|
|
|
|
|
)
|
2017-12-15 13:03:45 -05:00
|
|
|
|
2016-02-24 11:01:12 -06:00
|
|
|
# Read each reaction
|
|
|
|
|
for mf, mt, nc, mod in ev.reaction_list:
|
|
|
|
|
if mf == 3:
|
|
|
|
|
data.reactions[mt] = Reaction.from_endf(ev, mt)
|
|
|
|
|
|
|
|
|
|
# Replace cross sections for elastic, capture, fission
|
|
|
|
|
try:
|
2017-03-24 14:53:25 -05:00
|
|
|
if any(isinstance(r, res._RESOLVED) for r in data.resonances):
|
2016-02-24 11:01:12 -06:00
|
|
|
for mt in (2, 102, 18):
|
|
|
|
|
if mt in data.reactions:
|
|
|
|
|
rx = data.reactions[mt]
|
|
|
|
|
rx.xs['0K'] = ResonancesWithBackground(
|
|
|
|
|
data.resonances, rx.xs['0K'], mt)
|
|
|
|
|
except ValueError:
|
|
|
|
|
# Thrown if multiple resolved ranges (e.g. Pu239 in ENDF/B-VII.1)
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
# If first-chance, second-chance, etc. fission are present, check
|
|
|
|
|
# whether energy distributions were specified in MF=5. If not, copy the
|
|
|
|
|
# energy distribution from MT=18.
|
|
|
|
|
for mt, rx in data.reactions.items():
|
|
|
|
|
if mt in (19, 20, 21, 38):
|
|
|
|
|
if (5, mt) not in ev.section:
|
2019-09-11 15:38:59 -05:00
|
|
|
if rx.products:
|
|
|
|
|
neutron = data.reactions[18].products[0]
|
|
|
|
|
rx.products[0].applicability = neutron.applicability
|
|
|
|
|
rx.products[0].distribution = neutron.distribution
|
2016-02-24 11:01:12 -06:00
|
|
|
|
|
|
|
|
# Read fission energy release (requires that we already know nu for
|
|
|
|
|
# fission)
|
|
|
|
|
if (1, 458) in ev.section:
|
|
|
|
|
data.fission_energy = FissionEnergyRelease.from_endf(ev, data)
|
|
|
|
|
|
|
|
|
|
data._evaluation = ev
|
|
|
|
|
return data
|
2017-04-12 14:20:18 -05:00
|
|
|
|
|
|
|
|
@classmethod
|
2019-03-11 08:12:52 -05:00
|
|
|
def from_njoy(cls, filename, temperatures=None, evaluation=None, **kwargs):
|
2017-04-12 14:20:18 -05:00
|
|
|
"""Generate incident neutron data by running NJOY.
|
|
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
|
----------
|
|
|
|
|
filename : str
|
2019-03-11 08:12:52 -05:00
|
|
|
Path to ENDF file
|
2017-04-12 14:20:18 -05:00
|
|
|
temperatures : iterable of float
|
2017-04-12 15:13:42 -05:00
|
|
|
Temperatures in Kelvin to produce data at. If omitted, data is
|
|
|
|
|
produced at room temperature (293.6 K)
|
2019-03-11 08:12:52 -05:00
|
|
|
evaluation : openmc.data.endf.Evaluation, optional
|
|
|
|
|
If the ENDF file contains multiple material evaluations, this
|
|
|
|
|
argument indicates which evaluation to use.
|
2017-04-12 14:20:18 -05:00
|
|
|
**kwargs
|
|
|
|
|
Keyword arguments passed to :func:`openmc.data.njoy.make_ace`
|
|
|
|
|
|
|
|
|
|
Returns
|
|
|
|
|
-------
|
|
|
|
|
data : openmc.data.IncidentNeutron
|
|
|
|
|
Incident neutron continuous-energy data
|
2017-04-12 15:13:42 -05:00
|
|
|
|
2017-04-12 14:20:18 -05:00
|
|
|
"""
|
2017-12-24 16:18:22 +07:00
|
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
2017-04-12 14:20:18 -05:00
|
|
|
# Run NJOY to create an ACE library
|
Pass output_dir to make_ace to collect files
The user can now pass a single argument indicating where output
tapes for each njoy module should be written. The default argument
is the current working directory, useful for removing any temporary
files created during testing.
The pendf, broadr, heatr, gaspr, and purr arguments can either
be booleans or strings. A boolean value indicates the module output
should be written into a file named after the module, e.g. heatr output
written to <output_dir>/heatr.
ace and xsdir can be None or strings. A value of None means the
final ace and xsdir files will be written to
<output_dir>/ace and <output_dir>/xsdir, respectively. The temporary
temperature-dependent ace and xsdir files are now always named
"ace_<temp>" and "xsdir_<temp>", as the ace and xsdir arguments can
be more than "ace" and "xsdir".
openmc.data.IncidentNeutron.from_njoy knows to use this output_dir
argument to ensure all created tape files are written into the
temporary directory or the user requested directory, if applicable.
This commit was written because simply writing the broadr, gaspr,
and purr output files to "broadr", "gaspr", and "purr" resulted
in left over files after running tests.
2019-09-04 13:28:06 -05:00
|
|
|
kwargs.setdefault("output_dir", tmpdir)
|
2019-09-05 09:18:28 -05:00
|
|
|
for key in ("acer", "pendf", "heatr", "broadr", "gaspr", "purr"):
|
Pass output_dir to make_ace to collect files
The user can now pass a single argument indicating where output
tapes for each njoy module should be written. The default argument
is the current working directory, useful for removing any temporary
files created during testing.
The pendf, broadr, heatr, gaspr, and purr arguments can either
be booleans or strings. A boolean value indicates the module output
should be written into a file named after the module, e.g. heatr output
written to <output_dir>/heatr.
ace and xsdir can be None or strings. A value of None means the
final ace and xsdir files will be written to
<output_dir>/ace and <output_dir>/xsdir, respectively. The temporary
temperature-dependent ace and xsdir files are now always named
"ace_<temp>" and "xsdir_<temp>", as the ace and xsdir arguments can
be more than "ace" and "xsdir".
openmc.data.IncidentNeutron.from_njoy knows to use this output_dir
argument to ensure all created tape files are written into the
temporary directory or the user requested directory, if applicable.
This commit was written because simply writing the broadr, gaspr,
and purr output files to "broadr", "gaspr", and "purr" resulted
in left over files after running tests.
2019-09-04 13:28:06 -05:00
|
|
|
kwargs.setdefault(key, os.path.join(kwargs["output_dir"], key))
|
2019-03-11 08:12:52 -05:00
|
|
|
kwargs['evaluation'] = evaluation
|
2019-07-10 21:46:15 -05:00
|
|
|
make_ace(filename, temperatures, **kwargs)
|
2017-04-12 14:20:18 -05:00
|
|
|
|
|
|
|
|
# Create instance from ACE tables within library
|
2019-09-05 09:18:28 -05:00
|
|
|
lib = Library(kwargs['acer'])
|
2017-04-12 14:20:18 -05:00
|
|
|
data = cls.from_ace(lib.tables[0])
|
|
|
|
|
for table in lib.tables[1:]:
|
|
|
|
|
data.add_temperature_from_ace(table)
|
|
|
|
|
|
2026-01-02 05:31:46 -05:00
|
|
|
# Use name based on ENDF evaluation. The name assigned by from_ace
|
|
|
|
|
# may be wrong for higher metastable states (e.g., Hf178_m2)
|
|
|
|
|
ev = evaluation if evaluation is not None else Evaluation(filename)
|
|
|
|
|
data.name = ev.gnds_name
|
|
|
|
|
|
2019-09-13 10:59:18 -05:00
|
|
|
# Add 0K elastic scattering cross section
|
|
|
|
|
if '0K' not in data.energy:
|
|
|
|
|
pendf = Evaluation(kwargs['pendf'])
|
|
|
|
|
file_obj = StringIO(pendf.section[3, 2])
|
|
|
|
|
get_head_record(file_obj)
|
|
|
|
|
params, xs = get_tab1_record(file_obj)
|
|
|
|
|
data.energy['0K'] = xs.x
|
|
|
|
|
data[2].xs['0K'] = xs
|
|
|
|
|
|
2017-04-12 14:20:18 -05:00
|
|
|
# Add fission energy release data
|
2017-04-13 09:32:30 -05:00
|
|
|
if (1, 458) in ev.section:
|
2019-09-12 07:24:12 -05:00
|
|
|
data.fission_energy = f = FissionEnergyRelease.from_endf(ev, data)
|
|
|
|
|
else:
|
|
|
|
|
f = None
|
|
|
|
|
|
|
|
|
|
# For energy deposition, we want to store two different KERMAs:
|
|
|
|
|
# one calculated assuming outgoing photons deposit their energy
|
|
|
|
|
# locally, and one calculated assuming they carry their energy
|
|
|
|
|
# away. This requires two HEATR runs (which make_ace does by
|
|
|
|
|
# default). Here, we just need to correct for the fact that NJOY
|
|
|
|
|
# uses a fission heating number of h = EFR, whereas we want:
|
|
|
|
|
#
|
|
|
|
|
# 1) h = EFR + EGP + EGD + EB (for local case)
|
|
|
|
|
# 2) h = EFR + EB (for non-local case)
|
|
|
|
|
#
|
|
|
|
|
# The best way to handle this is to subtract off the fission
|
|
|
|
|
# KERMA that NJOY calculates and add back exactly what we want.
|
|
|
|
|
|
2019-09-13 10:59:18 -05:00
|
|
|
# If NJOY is not run with HEATR at all, skip everything below
|
|
|
|
|
if not kwargs["heatr"]:
|
|
|
|
|
return data
|
2019-09-12 07:24:12 -05:00
|
|
|
|
|
|
|
|
# Helper function to get a cross section from an ENDF file on a
|
|
|
|
|
# given energy grid
|
|
|
|
|
def get_file3_xs(ev, mt, E):
|
2026-02-26 04:14:44 +01:00
|
|
|
xs = ev.section_data[3, mt]['sigma']
|
2019-09-12 07:24:12 -05:00
|
|
|
return xs(E)
|
|
|
|
|
|
2019-09-13 10:59:18 -05:00
|
|
|
heating_local = Reaction(901)
|
|
|
|
|
heating_local.redundant = True
|
|
|
|
|
|
2019-09-12 07:24:12 -05:00
|
|
|
heatr_evals = get_evaluations(kwargs["heatr"])
|
|
|
|
|
heatr_local_evals = get_evaluations(kwargs["heatr"] + "_local")
|
|
|
|
|
|
2022-12-05 23:10:01 -06:00
|
|
|
for ev, ev_local, temp in zip(heatr_evals, heatr_local_evals, data.temperatures):
|
2019-09-12 07:24:12 -05:00
|
|
|
# Get total KERMA (originally from ACE file) and energy grid
|
|
|
|
|
kerma = data.reactions[301].xs[temp]
|
|
|
|
|
E = kerma.x
|
|
|
|
|
|
|
|
|
|
if f is not None:
|
|
|
|
|
# Replace fission KERMA with (EFR + EB)*sigma_f
|
2022-05-03 17:17:12 -05:00
|
|
|
fission = data[18].xs[temp]
|
2019-09-12 07:24:12 -05:00
|
|
|
kerma_fission = get_file3_xs(ev, 318, E)
|
|
|
|
|
kerma.y = kerma.y - kerma_fission + (
|
2019-10-02 21:09:18 -05:00
|
|
|
f.fragments(E) + f.betas(E)) * fission(E)
|
2019-09-12 07:24:12 -05:00
|
|
|
|
|
|
|
|
# For local KERMA, we first need to get the values from the
|
|
|
|
|
# HEATR run with photon energy deposited locally and put
|
|
|
|
|
# them on the same energy grid
|
|
|
|
|
kerma_local = get_file3_xs(ev_local, 301, E)
|
|
|
|
|
|
|
|
|
|
if f is not None:
|
|
|
|
|
# When photons deposit their energy locally, we replace the
|
|
|
|
|
# fission KERMA with (EFR + EGP + EGD + EB)*sigma_f
|
|
|
|
|
kerma_fission_local = get_file3_xs(ev_local, 318, E)
|
|
|
|
|
kerma_local = kerma_local - kerma_fission_local + (
|
|
|
|
|
f.fragments(E) + f.prompt_photons(E)
|
2019-10-02 21:09:18 -05:00
|
|
|
+ f.delayed_photons(E) + f.betas(E))*fission(E)
|
2019-09-12 07:24:12 -05:00
|
|
|
|
|
|
|
|
heating_local.xs[temp] = Tabulated1D(E, kerma_local)
|
|
|
|
|
|
|
|
|
|
data.reactions[901] = heating_local
|
2017-04-12 14:20:18 -05:00
|
|
|
|
|
|
|
|
return data
|
2018-11-06 13:20:33 -06:00
|
|
|
|
|
|
|
|
def _get_redundant_reaction(self, mt, mts):
|
|
|
|
|
"""Create redundant reaction from its components
|
|
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
|
----------
|
|
|
|
|
mt : int
|
|
|
|
|
MT value of the desired reaction
|
|
|
|
|
mts : iterable of int
|
|
|
|
|
MT values of its components
|
|
|
|
|
|
|
|
|
|
Returns
|
|
|
|
|
-------
|
|
|
|
|
openmc.Reaction
|
|
|
|
|
Redundant reaction
|
|
|
|
|
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
rx = Reaction(mt)
|
2019-10-20 14:11:21 +08:00
|
|
|
# Get energy grid
|
|
|
|
|
for strT in self.temperatures:
|
|
|
|
|
energy = self.energy[strT]
|
|
|
|
|
xss = [self.reactions[mt_i].xs[strT] for mt_i in mts]
|
|
|
|
|
idx = min([xs._threshold_idx if hasattr(xs, '_threshold_idx')
|
|
|
|
|
else 0 for xs in xss])
|
|
|
|
|
rx.xs[strT] = Tabulated1D(energy[idx:], Sum(xss)(energy[idx:]))
|
|
|
|
|
rx.xs[strT]._threshold_idx = idx
|
|
|
|
|
|
2018-11-06 13:20:33 -06:00
|
|
|
rx.redundant = True
|
|
|
|
|
|
|
|
|
|
return rx
|