Add Madland fission-Q support to openmc.data

This commit is contained in:
Sterling Harper 2016-07-29 10:50:08 -05:00
parent c8a188ca26
commit a38f53d26f
4 changed files with 351 additions and 0 deletions

View file

@ -14,3 +14,4 @@ from .nbody import *
from .thermal import *
from .urr import *
from .library import *
from .fission_energy import *

43
openmc/data/endf_utils.py Normal file
View file

@ -0,0 +1,43 @@
"""This module contains a few utility functions for reading ENDF_ data. It is by
no means enough to read an entire ENDF file. For a more complete ENDF reader,
see Pyne_.
.. _ENDF: http://www.nndc.bnl.gov/endf
.. _Pyne: http://www.pyne.io
"""
import re
def read_float(float_string):
"""Parse ENDF 6E11.0 formatted string into a float."""
assert len(float_string) == 11
pattern = '([\s\\-]\d+\\.\d+)([\\+\\-]\d+)'
mantissa, exponent = re.match(pattern, float_string).groups()
return float(mantissa + 'e' + exponent)
def read_CONT_line(line):
"""Parse 80-column line from ENDF CONT record into floats and ints."""
return (read_float(line[0:11]), read_float(line[11:22]), int(line[22:33]),
int(line[33:44]), int(line[44:55]), int(line[55:66]),
int(line[66:70]), int(line[70:72]), int(line[72:75]),
int(line[75:80]))
def identify_nuclide(fname):
"""Read the header of an ENDF file and extract identifying information."""
with open(fname, 'r') as fh:
# Skip the tape id (TPID).
line = fh.readline()
# Read the first HEAD and CONT info.
line = fh.readline()
ZA, AW, LRP, LFI, NLIB, NMOD, MAT, MF, MT, NS = read_CONT_line(line)
line = fh.readline()
ELIS, STA, LIS, LISO, junk, NFOR, MAT, MF, MT, NS = read_CONT_line(line)
# Return dictionary of the most important identifying information.
return {'Z': int(ZA) // 1000,
'A': int(ZA) % 1000,
'LIS': LIS,
'LISO': LISO}

View file

@ -0,0 +1,285 @@
from collections import Callable
import sys
#from warnings import warn
import numpy as np
from numpy.polynomial.polynomial import Polynomial
from .function import Tabulated1D, Sum
from .endf_utils import read_float, read_CONT_line, identify_nuclide
import openmc.checkvalue as cv
if sys.version_info[0] >= 3:
basestring = str
class FissionEnergyRelease(object):
def __init__(self):
self._fragments = None
self._prompt_neutrons = None
self._delayed_neutrons = None
self._prompt_photons = None
self._delayed_photons = None
self._betas = None
self._neutrinos = None
self._form = None
@property
def fragments(self):
return self._fragments
@property
def prompt_neutrons(self):
return self._prompt_neutrons
@property
def delayed_neutrons(self):
return self._delayed_neutrons
@property
def prompt_photons(self):
return self._prompt_photons
@property
def delayed_photons(self):
return self._delayed_photons
@property
def betas(self):
return self._betas
@property
def neutrinos(self):
return self._neutrinos
@property
def recoverable(self):
return Sum([self.fragments, self.prompt_neutrons, self.delayed_neutrons,
self.prompt_photons, self.delayed_photons, self.betas])
@property
def total(self):
return Sum([self.fragments, self.prompt_neutrons, self.delayed_neutrons,
self.prompt_photons, self.delayed_photons, self.betas,
self.neutrinos])
@property
def form(self):
return self._form
@fragments.setter
def fragments(self, energy_release):
cv.check_type('fragments', energy_release, Callable)
self._fragments = energy_release
@prompt_neutrons.setter
def prompt_neutrons(self, energy_release):
cv.check_type('prompt_neutrons', energy_release, Callable)
self._prompt_neutrons = energy_release
@delayed_neutrons.setter
def delayed_neutrons(self, energy_release):
cv.check_type('delayed_neutrons', energy_release, Callable)
self._delayed_neutrons = energy_release
@prompt_photons.setter
def prompt_photons(self, energy_release):
cv.check_type('prompt_photons', energy_release, Callable)
self._prompt_photons = energy_release
@delayed_photons.setter
def delayed_photons(self, energy_release):
cv.check_type('delayed_photons', energy_release, Callable)
self._delayed_photons = energy_release
@betas.setter
def betas(self, energy_release):
cv.check_type('betas', energy_release, Callable)
self._betas = energy_release
@neutrinos.setter
def neutrinos(self, energy_release):
cv.check_type('neutrinos', energy_release, Callable)
self._neutrinos = energy_release
@form.setter
def form(self, form):
cv.check_value('format', form, ('Madland', 'Sher-Beck'))
self._form = form
@classmethod
def from_endf(cls, filename, incident_neutron):
"""Generate fission energy release data from an ENDF file.
Parameters
----------
filename : str
Name of the ENDF file containing fission energy release data
incident_neutron : openmc.data.IncidentNeutron
Corresponding incident neutron dataset
Returns
-------
openmc.data.FissionEnergyRelease
Fission energy release data
"""
# Check to make sure this ENDF file matches the expected isomer.
ident = identify_nuclide(filename)
if ident['Z'] != incident_neutron.atomic_number:
pass
if ident['A'] != incident_neutron.mass_number:
pass
if ident['LISO'] != incident_neutron.metastable:
pass
# Extract the MF=1, MT=458 section.
lines = []
with open(filename, 'r') as fh:
line = fh.readline()
while line != '':
if line[70:75] == ' 1458':
lines.append(line)
line = fh.readline()
# Read the number of coefficients in this LIST record.
NPL = read_CONT_line(lines[1])[4]
# Parse the ENDF LIST into an array.
data = []
for i in range(NPL):
row, column = divmod(i, 6)
data.append(read_float(lines[2 + row][11*column:11*(column+1)]))
# Declare the coefficient names and the order they are given in. The
# LIST contains a value followed immediately by an uncertainty for each
# of these components, times the polynomial order + 1. If we only find
# one value for each of these components, then we need to use the
# Sher-Beck formula for energy dependence. Otherwise, it is a
# polynomial.
labels = ('EFR', 'ENP', 'END', 'EGP', 'EGD', 'EB', 'ENU', 'ER', 'ET')
# Associate each set of values and uncertainties with its label.
value = dict()
uncertainty = dict()
for i in range(len(labels)):
value[labels[i]] = data[2*i::18]
uncertainty[labels[i]] = data[2*i + 1::18]
# In ENDF/B-7.1, data for 2nd-order coefficients were mistakenly not
# converted from MeV to eV. Check for this error and fix it if present.
n_coeffs = len(value['EFR'])
if n_coeffs == 3: # Only check 2nd-order data.
# Check each energy component for the error. If a 1 MeV neutron
# causes a change of more than 100 MeV, we know something is wrong.
error_present = False
for coeffs in value.values():
second_order = coeffs[2]
if abs(second_order) * 1e12 > 1e8:
error_present = True
break
# If we found the error, reduce all 2nd-order coeffs by 10**6.
if error_present:
for coeffs in value.values(): coeffs[2] *= 1e-6
for coeffs in uncertainty.values(): coeffs[2] *= 1e-6
# Perform the sanity check again... just in case.
for coeffs in value.values():
second_order = coeffs[2]
if abs(second_order) * 1e12 > 1e8:
raise ValueError("Encountered a ludicrously large second-"
"order polynomial coefficient.")
# Convert eV to MeV.
for coeffs in value.values():
for i in range(len(coeffs)):
coeffs[i] *= 10**(-6 + 6*i)
for coeffs in uncertainty.values():
for i in range(len(coeffs)):
coeffs[i] *= 10**(-6 + 6*i)
out = cls()
if n_coeffs > 1:
out.form = 'Madland'
out.fragments = Polynomial(value['EFR'])
out.prompt_neutrons = Polynomial(value['ENP'])
out.delayed_neutrons = Polynomial(value['END'])
out.prompt_photons = Polynomial(value['EGP'])
out.delayed_photons = Polynomial(value['EGD'])
out.betas = Polynomial(value['EB'])
out.neutrinos = Polynomial(value['ENU'])
else:
out.form = 'Sher-Beck'
raise NotImplemented
return out
@classmethod
def from_hdf5(cls, group):
"""Generate fission energy release data from an HDF5 group.
Parameters
----------
group : h5py.Group
HDF5 group to read from
Returns
-------
openmc.data.FissionEnergyRelease
Fission energy release data
"""
obj = cls()
if group.attrs['format'] == 'Madland':
obj.fragments = Polynomial(group['fragments'].value)
obj.prompt_neutrons = Polynomial(group['prompt_neutrons'].value)
obj.delayed_neutrons = Polynomial(group['delayed_neutrons'].value)
obj.prompt_photons = Polynomial(group['prompt_photons'].value)
obj.delayed_photons = Polynomial(group['delayed_photons'].value)
obj.betas = Polynomial(group['betas'].value)
obj.neutrinos = Polynomial(group['neutrinos'].value)
elif group.attrs['format'] == 'Sher-Beck':
raise NotImplemented
else:
raise ValueError('Unrecognized energy release format')
return obj
def to_hdf5(self, group):
"""Write energy release data to an HDF5 group
Parameters
----------
group : h5py.Group
HDF5 group to write to
"""
if self.form == 'Madland':
group.attrs['format'] = np.string_('Madland')
group.create_dataset('fragments', data=self.fragments.coef)
group.create_dataset('prompt_neutrons',
data=self.prompt_neutrons.coef)
group.create_dataset('delayed_neutrons',
data=self.delayed_neutrons.coef)
group.create_dataset('prompt_photons',
data=self.prompt_photons.coef)
group.create_dataset('delayed_photons',
data=self.delayed_photons.coef)
group.create_dataset('betas', data=self.betas.coef)
group.create_dataset('neutrinos', data=self.neutrinos.coef)
elif self.form == 'Sher-Beck':
group.attrs['format'] = np.string_('Sher-Beck')
self.fragments.to_hdf5(group, 'fragments')
self.prompt_neutrons.to_hdf5(group, 'prompt_neutrons')
self.delayed_neutrons.to_hdf5(group, 'delayed_neutrons')
self.prompt_photons.to_hdf5(group, 'prompt_photons')
self.delayed_photons.to_hdf5(group, 'delayed_photons')
self.betas.to_hdf5(group, 'betas')
self.neutrinos.to_hdf5(group, 'neutrinos')
else:
raise ValueError('Unrecognized energy release format')

View file

@ -9,6 +9,7 @@ import h5py
from .data import ATOMIC_SYMBOL, SUM_RULES
from .ace import Table, get_table
from .fission_energy import FissionEnergyRelease
from .function import Tabulated1D, Sum
from .product import Product
from .reaction import Reaction, _get_photon_products
@ -81,6 +82,7 @@ class IncidentNeutron(object):
self.temperature = temperature
self._energy = None
self._fission_energy = None
self.reactions = OrderedDict()
self.summed_reactions = OrderedDict()
self.urr = None
@ -126,6 +128,10 @@ class IncidentNeutron(object):
def energy(self):
return self._energy
@property
def fission_energy(self):
return self._fission_energy
@property
def temperature(self):
return self._temperature
@ -186,6 +192,12 @@ class IncidentNeutron(object):
cv.check_type('energy grid', energy, Iterable, Real)
self._energy = energy
@fission_energy.setter
def fission_energy(self, fission_energy):
cv.check_type('fission energy release', fission_energy,
FissionEnergyRelease)
self._fission_energy = fission_energy
@reactions.setter
def reactions(self, reactions):
cv.check_type('reactions', reactions, Mapping)
@ -276,6 +288,11 @@ class IncidentNeutron(object):
urr_group = g.create_group('urr')
self.urr.to_hdf5(urr_group)
# 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)
f.close()
@classmethod
@ -331,6 +348,11 @@ class IncidentNeutron(object):
urr_group = group['urr']
data.urr = ProbabilityTables.from_hdf5(urr_group)
# 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)
return data
@classmethod