• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

openmc-dev / openmc / 22007957997

14 Feb 2026 12:50AM UTC coverage: 81.717% (-0.05%) from 81.763%
22007957997

Pull #3765

github

web-flow
Merge 6111ffe2e into 19c0aafdc
Pull Request #3765: Store atomic mass in ParticleType.

17526 of 24626 branches covered (71.17%)

Branch coverage included in aggregate %.

6 of 7 new or added lines in 2 files covered. (85.71%)

1820 existing lines in 34 files now uncovered.

56937 of 66497 relevant lines covered (85.62%)

44324210.02 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.85
/openmc/data/endf.py
1
"""Module for parsing and manipulating data from ENDF evaluations.
2

3
All the classes and functions in this module are based on document ENDF-102
4
titled "Data Formats and Procedures for the Evaluated Nuclear Data File ENDF-6".
5
The version from September 2023 can be found at
6
https://www.nndc.bnl.gov/endfdocs/ENDF-102-2023.pdf
7

8
"""
9
import io
10✔
10
from pathlib import PurePath
10✔
11
import re
10✔
12

13
from .data import gnds_name
10✔
14
from .function import Tabulated1D
10✔
15
from endf.material import _LIBRARY, _SUBLIBRARY
10✔
16
from endf.incident_neutron import SUM_RULES
10✔
17
from endf.records import (
10✔
18
    float_endf,
19
    py_float_endf,
20
    int_endf,
21
    get_text_record,
22
    get_cont_record,
23
    get_head_record,
24
    get_list_record,
25
    get_tab1_record as _get_tab1_record,
26
    get_tab2_record,
27
    get_intg_record,
28
)
29

30

31
def get_tab1_record(file_obj):
10✔
32
    """Return data from a TAB1 record in an ENDF-6 file.
33

34
    This wraps the endf package's get_tab1_record to return an
35
    openmc.data.Tabulated1D (which has HDF5 support and is a Function1D)
36
    instead of endf.Tabulated1D.
37
    """
38
    params, tab = _get_tab1_record(file_obj)
10✔
39
    return params, Tabulated1D(tab.x, tab.y, tab.breakpoints, tab.interpolation)
10✔
40

41

42
def get_evaluations(filename):
10✔
43
    """Return a list of all evaluations within an ENDF file.
44

45
    Parameters
46
    ----------
47
    filename : str
48
        Path to ENDF-6 formatted file
49

50
    Returns
51
    -------
52
    list
53
        A list of :class:`openmc.data.endf.Evaluation` instances.
54

55
    """
56
    evaluations = []
10✔
57
    with open(str(filename), 'r') as fh:
10✔
58
        while True:
10✔
59
            pos = fh.tell()
10✔
60
            line = fh.readline()
10✔
61
            if line[66:70] == '  -1':
10✔
62
                break
10✔
63
            fh.seek(pos)
10✔
64
            evaluations.append(Evaluation(fh))
10✔
65
    return evaluations
10✔
66

67

68
class Evaluation:
10✔
69
    """ENDF material evaluation with multiple files/sections
70

71
    Parameters
72
    ----------
73
    filename_or_obj : str or file-like
74
        Path to ENDF file to read or an open file positioned at the start of an
75
        ENDF material
76

77
    Attributes
78
    ----------
79
    info : dict
80
        Miscellaneous information about the evaluation.
81
    target : dict
82
        Information about the target material, such as its mass, isomeric state,
83
        whether it's stable, and whether it's fissionable.
84
    projectile : dict
85
        Information about the projectile such as its mass.
86
    reaction_list : list of 4-tuples
87
        List of sections in the evaluation. The entries of the tuples are the
88
        file (MF), section (MT), number of records (NC), and modification
89
        indicator (MOD).
90

91
    """
92
    def __init__(self, filename_or_obj):
10✔
93
        if isinstance(filename_or_obj, (str, PurePath)):
10✔
94
            fh = open(str(filename_or_obj), 'r')
10✔
95
            need_to_close = True
10✔
96
        else:
97
            fh = filename_or_obj
10✔
98
            need_to_close = False
10✔
99
        self.section = {}
10✔
100
        self.info = {}
10✔
101
        self.target = {}
10✔
102
        self.projectile = {}
10✔
103
        self.reaction_list = []
10✔
104

105
        # Skip TPID record. Evaluators sometimes put in TPID records that are
106
        # ill-formated because they lack MF/MT values or put them in the wrong
107
        # columns.
108
        if fh.tell() == 0:
10✔
109
            fh.readline()
10✔
110
        MF = 0
10✔
111

112
        # Determine MAT number for this evaluation
113
        while MF == 0:
10✔
114
            position = fh.tell()
10✔
115
            line = fh.readline()
10✔
116
            MF = int(line[70:72])
10✔
117
        self.material = int(line[66:70])
10✔
118
        fh.seek(position)
10✔
119

120
        while True:
10✔
121
            # Find next section
122
            while True:
10✔
123
                position = fh.tell()
10✔
124
                line = fh.readline()
10✔
125
                MAT = int(line[66:70])
10✔
126
                MF = int(line[70:72])
10✔
127
                MT = int(line[72:75])
10✔
128
                if MT > 0 or MAT == 0:
10✔
129
                    fh.seek(position)
10✔
130
                    break
10✔
131

132
            # If end of material reached, exit loop
133
            if MAT == 0:
10✔
134
                fh.readline()
10✔
135
                break
10✔
136

137
            section_data = ''
10✔
138
            while True:
10✔
139
                line = fh.readline()
10✔
140
                if line[72:75] == '  0':
10✔
141
                    break
10✔
142
                else:
143
                    section_data += line
10✔
144
            self.section[MF, MT] = section_data
10✔
145

146
        if need_to_close:
10✔
147
            fh.close()
10✔
148

149
        self._read_header()
10✔
150

151
    def __repr__(self):
10✔
UNCOV
152
        name = self.target['zsymam'].replace(' ', '')
×
UNCOV
153
        return f"<{self.info['sublibrary']} for {name} {self.info['library']}>"
×
154

155
    def _read_header(self):
10✔
156
        file_obj = io.StringIO(self.section[1, 451])
10✔
157

158
        # Information about target/projectile
159
        items = get_head_record(file_obj)
10✔
160
        Z, A = divmod(items[0], 1000)
10✔
161
        self.target['atomic_number'] = Z
10✔
162
        self.target['mass_number'] = A
10✔
163
        self.target['mass'] = items[1]
10✔
164
        self._LRP = items[2]
10✔
165
        self.target['fissionable'] = (items[3] == 1)
10✔
166
        try:
10✔
167
            library = _LIBRARY[items[4]]
10✔
168
        except KeyError:
10✔
169
            library = 'Unknown'
10✔
170
        self.info['modification'] = items[5]
10✔
171

172
        # Control record 1
173
        items = get_cont_record(file_obj)
10✔
174
        self.target['excitation_energy'] = items[0]
10✔
175
        self.target['stable'] = (int(items[1]) == 0)
10✔
176
        self.target['state'] = items[2]
10✔
177
        self.target['isomeric_state'] = m = items[3]
10✔
178
        self.info['format'] = items[5]
10✔
179
        assert self.info['format'] == 6
10✔
180

181
        # Set correct excited state for Am242_m1, which is wrong in ENDF/B-VII.1
182
        if Z == 95 and A == 242 and m == 1:
10✔
183
            self.target['state'] = 2
10✔
184

185
        # Control record 2
186
        items = get_cont_record(file_obj)
10✔
187
        self.projectile['mass'] = items[0]
10✔
188
        self.info['energy_max'] = items[1]
10✔
189
        library_release = items[2]
10✔
190
        self.info['sublibrary'] = _SUBLIBRARY[items[4]]
10✔
191
        library_version = items[5]
10✔
192
        self.info['library'] = (library, library_version, library_release)
10✔
193

194
        # Control record 3
195
        items = get_cont_record(file_obj)
10✔
196
        self.target['temperature'] = items[0]
10✔
197
        self.info['derived'] = (items[2] > 0)
10✔
198
        NWD = items[4]
10✔
199
        NXC = items[5]
10✔
200

201
        # Text records
202
        text = [get_text_record(file_obj) for i in range(NWD)]
10✔
203
        if len(text) >= 5:
10✔
204
            self.target['zsymam'] = text[0][0:11]
10✔
205
            self.info['laboratory'] = text[0][11:22]
10✔
206
            self.info['date'] = text[0][22:32]
10✔
207
            self.info['author'] = text[0][32:66]
10✔
208
            self.info['reference'] = text[1][1:22]
10✔
209
            self.info['date_distribution'] = text[1][22:32]
10✔
210
            self.info['date_release'] = text[1][33:43]
10✔
211
            self.info['date_entry'] = text[1][55:63]
10✔
212
            self.info['identifier'] = text[2:5]
10✔
213
            self.info['description'] = text[5:]
10✔
214
        else:
215
            self.target['zsymam'] = 'Unknown'
10✔
216

217
        # File numbers, reaction designations, and number of records
218
        for i in range(NXC):
10✔
219
            _, _, mf, mt, nc, mod = get_cont_record(file_obj, skip_c=True)
10✔
220
            self.reaction_list.append((mf, mt, nc, mod))
10✔
221

222
    @property
10✔
223
    def gnds_name(self):
10✔
224
        return gnds_name(self.target['atomic_number'],
10✔
225
                         self.target['mass_number'],
226
                         self.target['isomeric_state'])
227

228

229
class Tabulated2D:
10✔
230
    """Metadata for a two-dimensional function.
231

232
    This is a dummy class that is not really used other than to store the
233
    interpolation information for a two-dimensional function. Once we refactor
234
    to adopt GNDS-like data containers, this will probably be removed or
235
    extended.
236

237
    Parameters
238
    ----------
239
    breakpoints : Iterable of int
240
        Breakpoints for interpolation regions
241
    interpolation : Iterable of int
242
        Interpolation scheme identification number, e.g., 3 means y is linear in
243
        ln(x).
244

245
    """
246
    def __init__(self, breakpoints, interpolation):
10✔
UNCOV
247
        self.breakpoints = breakpoints
×
UNCOV
248
        self.interpolation = interpolation
×
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc