Source code for openmc.data.endf

"""Module for parsing and manipulating data from ENDF evaluations.

All the classes and functions in this module are based on document
ENDF-102 titled "Data Formats and Procedures for the Evaluated Nuclear
Data File ENDF-6". The latest version from June 2009 can be found at
http://www-nds.iaea.org/ndspub/documents/endf/endf102/endf102.pdf

"""
from __future__ import print_function, division, unicode_literals

import io
import re
import os
from math import pi
from collections import OrderedDict, Iterable

from six import string_types
import numpy as np
from numpy.polynomial.polynomial import Polynomial

from .data import ATOMIC_SYMBOL
from .function import Tabulated1D, INTERPOLATION_SCHEME
from openmc.stats.univariate import Uniform, Tabular, Legendre


LIBRARIES = {0: 'ENDF/B', 1: 'ENDF/A', 2: 'JEFF', 3: 'EFF',
             4: 'ENDF/B High Energy', 5: 'CENDL', 6: 'JENDL',
             31: 'INDL/V', 32: 'INDL/A', 33: 'FENDL', 34: 'IRDF',
             35: 'BROND', 36: 'INGDB-90', 37: 'FENDL/A', 41: 'BROND'}

SUM_RULES = {1: [2, 3],
             3: [4, 5, 11, 16, 17, 22, 23, 24, 25, 27, 28, 29, 30, 32, 33, 34, 35,
                 36, 37, 41, 42, 44, 45, 152, 153, 154, 156, 157, 158, 159, 160,
                 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, 172,
                 173, 174, 175, 176, 177, 178, 179, 180, 181, 183, 184, 185,
                 186, 187, 188, 189, 190, 194, 195, 196, 198, 199, 200],
             4: list(range(50, 92)),
             16: list(range(875, 892)),
             18: [19, 20, 21, 38],
             27: [18, 101],
             101: [102, 103, 104, 105, 106, 107, 108, 109, 111, 112, 113, 114,
                   115, 116, 117, 155, 182, 191, 192, 193, 197],
             103: list(range(600, 650)),
             104: list(range(650, 700)),
             105: list(range(700, 750)),
             106: list(range(750, 800)),
             107: list(range(800, 850))}

ENDF_FLOAT_RE = re.compile(r'([\s\-\+]?\d*\.\d+)([\+\-]\d+)')


[docs]def float_endf(s): """Convert string of floating point number in ENDF to float. The ENDF-6 format uses an 'e-less' floating point number format, e.g. -1.23481+10. Trying to convert using the float built-in won't work because of the lack of an 'e'. This function allows such strings to be converted while still allowing numbers that are not in exponential notation to be converted as well. Parameters ---------- s : str Floating-point number from an ENDF file Returns ------- float The number """ return float(ENDF_FLOAT_RE.sub(r'\1e\2', s))
[docs]def get_text_record(file_obj): """Return data from a TEXT record in an ENDF-6 file. Parameters ---------- file_obj : file-like object ENDF-6 file to read from Returns ------- str Text within the TEXT record """ return file_obj.readline()[:66]
[docs]def get_cont_record(file_obj, skipC=False): """Return data from a CONT record in an ENDF-6 file. Parameters ---------- file_obj : file-like object ENDF-6 file to read from skipC : bool Determine whether to skip the first two quantities (C1, C2) of the CONT record. Returns ------- list The six items within the CONT record """ line = file_obj.readline() if skipC: C1 = None C2 = None else: C1 = float_endf(line[:11]) C2 = float_endf(line[11:22]) L1 = int(line[22:33]) L2 = int(line[33:44]) N1 = int(line[44:55]) N2 = int(line[55:66]) return [C1, C2, L1, L2, N1, N2]
[docs]def get_head_record(file_obj): """Return data from a HEAD record in an ENDF-6 file. Parameters ---------- file_obj : file-like object ENDF-6 file to read from Returns ------- list The six items within the HEAD record """ line = file_obj.readline() ZA = int(float_endf(line[:11])) AWR = float_endf(line[11:22]) L1 = int(line[22:33]) L2 = int(line[33:44]) N1 = int(line[44:55]) N2 = int(line[55:66]) return [ZA, AWR, L1, L2, N1, N2]
def get_list_record(file_obj): """Return data from a LIST record in an ENDF-6 file. Parameters ---------- file_obj : file-like object ENDF-6 file to read from Returns ------- list The six items within the header list The values within the list """ # determine how many items are in list items = get_cont_record(file_obj) NPL = items[4] # read items b = [] for i in range((NPL - 1)//6 + 1): line = file_obj.readline() n = min(6, NPL - 6*i) for j in range(n): b.append(float_endf(line[11*j:11*(j + 1)])) return (items, b)
[docs]def get_tab1_record(file_obj): """Return data from a TAB1 record in an ENDF-6 file. Parameters ---------- file_obj : file-like object ENDF-6 file to read from Returns ------- list The six items within the header openmc.data.Tabulated1D The tabulated function """ # Determine how many interpolation regions and total points there are line = file_obj.readline() C1 = float_endf(line[:11]) C2 = float_endf(line[11:22]) L1 = int(line[22:33]) L2 = int(line[33:44]) n_regions = int(line[44:55]) n_pairs = int(line[55:66]) params = [C1, C2, L1, L2] # Read the interpolation region data, namely NBT and INT breakpoints = np.zeros(n_regions, dtype=int) interpolation = np.zeros(n_regions, dtype=int) m = 0 for i in range((n_regions - 1)//3 + 1): line = file_obj.readline() to_read = min(3, n_regions - m) for j in range(to_read): breakpoints[m] = int(line[0:11]) interpolation[m] = int(line[11:22]) line = line[22:] m += 1 # Read tabulated pairs x(n) and y(n) x = np.zeros(n_pairs) y = np.zeros(n_pairs) m = 0 for i in range((n_pairs - 1)//3 + 1): line = file_obj.readline() to_read = min(3, n_pairs - m) for j in range(to_read): x[m] = float_endf(line[:11]) y[m] = float_endf(line[11:22]) line = line[22:] m += 1 return params, Tabulated1D(x, y, breakpoints, interpolation)
[docs]def get_tab2_record(file_obj): # Determine how many interpolation regions and total points there are params = get_cont_record(file_obj) n_regions = params[4] # Read the interpolation region data, namely NBT and INT breakpoints = np.zeros(n_regions, dtype=int) interpolation = np.zeros(n_regions, dtype=int) m = 0 for i in range((n_regions - 1)//3 + 1): line = file_obj.readline() to_read = min(3, n_regions - m) for j in range(to_read): breakpoints[m] = int(line[0:11]) interpolation[m] = int(line[11:22]) line = line[22:] m += 1 return params, Tabulated2D(breakpoints, interpolation)
[docs]def get_evaluations(filename): """Return a list of all evaluations within an ENDF file. Parameters ---------- filename : str Path to ENDF-6 formatted file Returns ------- list A list of :class:`openmc.data.endf.Evaluation` instances. """ evaluations = [] with open(filename, 'r') as fh: while True: pos = fh.tell() line = fh.readline() if line[66:70] == ' -1': break fh.seek(pos) evaluations.append(Evaluation(fh)) return evaluations
[docs]class Evaluation(object): """ENDF material evaluation with multiple files/sections Parameters ---------- filename_or_obj : str or file-like Path to ENDF file to read or an open file positioned at the start of an ENDF material Attributes ---------- info : dict Miscallaneous information about the evaluation. target : dict Information about the target material, such as its mass, isomeric state, whether it's stable, and whether it's fissionable. projectile : dict Information about the projectile such as its mass. reaction_list : list of 4-tuples List of sections in the evaluation. The entries of the tuples are the file (MF), section (MT), number of records (NC), and modification indicator (MOD). """ def __init__(self, filename_or_obj): if isinstance(filename_or_obj, string_types): fh = open(filename_or_obj, 'r') else: fh = filename_or_obj self.section = {} self.info = {} self.target = {} self.projectile = {} self.reaction_list = [] # Determine MAT number for this evaluation MF = 0 while MF == 0: position = fh.tell() line = fh.readline() MF = int(line[70:72]) self.material = int(line[66:70]) fh.seek(position) while True: # Find next section while True: position = fh.tell() line = fh.readline() MAT = int(line[66:70]) MF = int(line[70:72]) MT = int(line[72:75]) if MT > 0 or MAT == 0: fh.seek(position) break # If end of material reached, exit loop if MAT == 0: fh.readline() break section_data = '' while True: line = fh.readline() if line[72:75] == ' 0': break else: section_data += line self.section[MF, MT] = section_data self._read_header() def _read_header(self): file_obj = io.StringIO(self.section[1, 451]) # Information about target/projectile items = get_head_record(file_obj) Z, A = divmod(items[0], 1000) self.target['atomic_number'] = Z self.target['mass_number'] = A self.target['mass'] = items[1] self._LRP = items[2] self.target['fissionable'] = (items[3] == 1) try: global LIBRARIES library = LIBRARIES[items[4]] except KeyError: library = 'Unknown' self.info['modification'] = items[5] # Control record 1 items = get_cont_record(file_obj) self.target['excitation_energy'] = items[0] self.target['stable'] = (int(items[1]) == 0) self.target['state'] = items[2] self.target['isomeric_state'] = m = items[3] self.info['format'] = items[5] assert self.info['format'] == 6 # Set correct excited state for Am242_m1, which is wrong in ENDF/B-VII.1 if Z == 95 and A == 242 and m == 1: self.target['state'] = 2 # Control record 2 items = get_cont_record(file_obj) self.projectile['mass'] = items[0] self.info['energy_max'] = items[1] library_release = items[2] self.info['sublibrary'] = items[4] library_version = items[5] self.info['library'] = (library, library_version, library_release) # Control record 3 items = get_cont_record(file_obj) self.target['temperature'] = items[0] self.info['derived'] = (items[2] > 0) NWD = items[4] NXC = items[5] # Text records text = [get_text_record(file_obj) for i in range(NWD)] if len(text) >= 5: self.target['zsymam'] = text[0][0:11] self.info['laboratory'] = text[0][11:22] self.info['date'] = text[0][22:32] self.info['author'] = text[0][32:66] self.info['reference'] = text[1][1:22] self.info['date_distribution'] = text[1][22:32] self.info['date_release'] = text[1][33:43] self.info['date_entry'] = text[1][55:63] self.info['identifier'] = text[2:5] self.info['description'] = text[5:] # File numbers, reaction designations, and number of records for i in range(NXC): line = file_obj.readline() mf = int(line[22:33]) mt = int(line[33:44]) nc = int(line[44:55]) try: mod = int(line[55:66]) except ValueError: # In JEFF 3.2, a few isotopes of U have MOD values that are # missing. This prevents failure on these isotopes. mod = 0 self.reaction_list.append((mf, mt, nc, mod)) @property def gnd_name(self): symbol = ATOMIC_SYMBOL[self.target['atomic_number']] A = self.target['mass_number'] m = self.target['isomeric_state'] if m > 0: return '{}{}_m{}'.format(symbol, A, m) else: return '{}{}'.format(symbol, A)
class Tabulated2D(object): """Metadata for a two-dimensional function. This is a dummy class that is not really used other than to store the interpolation information for a two-dimensional function. Once we refactor to adopt GND-like data containers, this will probably be removed or extended. Parameters ---------- breakpoints : Iterable of int Breakpoints for interpolation regions interpolation : Iterable of int Interpolation scheme identification number, e.g., 3 means y is linear in ln(x). """ def __init__(self, breakpoints, interpolation): self.breakpoints = breakpoints self.interpolation = interpolation