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

openmc-dev / openmc / 26783873914

01 Jun 2026 09:43PM UTC coverage: 81.362% (+0.03%) from 81.333%
26783873914

Pull #3948

github

web-flow
Merge 6314ea576 into 111eb7706
Pull Request #3948: Fix get_index_in_direction for regular meshes

18027 of 26121 branches covered (69.01%)

Branch coverage included in aggregate %.

43 of 45 new or added lines in 9 files covered. (95.56%)

443 existing lines in 12 files now uncovered.

59175 of 68766 relevant lines covered (86.05%)

48551677.44 hits per line

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

91.0
/openmc/data/decay.py
1
from collections.abc import Iterable
11✔
2
from functools import cached_property
11✔
3
from io import StringIO
11✔
4
from math import log
11✔
5
from warnings import warn
11✔
6

7
import numpy as np
11✔
8
from uncertainties import ufloat, UFloat
11✔
9

10
import openmc
11✔
11
import openmc.checkvalue as cv
11✔
12
from openmc.exceptions import DataError
11✔
13
from openmc.mixin import EqualityMixin
11✔
14
from openmc.stats import Discrete, Tabular, Univariate, combine_distributions
11✔
15
from .data import gnds_name, zam
11✔
16
from .function import INTERPOLATION_SCHEME
11✔
17
from .endf import (
11✔
18
    as_evaluation, get_head_record, get_list_record, get_tab1_record)
19

20

21
# Gives name and (change in A, change in Z) resulting from decay
22
_DECAY_MODES = {
11✔
23
    0: ('gamma', (0, 0)),
24
    1: ('beta-', (0, 1)),
25
    2: ('ec/beta+', (0, -1)),
26
    3: ('IT', (0, 0)),
27
    4: ('alpha', (-4, -2)),
28
    5: ('n', (-1, 0)),
29
    6: ('sf', None),
30
    7: ('p', (-1, -1)),
31
    8: ('e-', (0, 0)),
32
    9: ('xray', (0, 0)),
33
    10: ('unknown', None)
34
}
35

36
_RADIATION_TYPES = {
11✔
37
    0: 'gamma',
38
    1: 'beta-',
39
    2: 'ec/beta+',
40
    4: 'alpha',
41
    5: 'n',
42
    6: 'sf',
43
    7: 'p',
44
    8: 'e-',
45
    9: 'xray',
46
    10: 'anti-neutrino',
47
    11: 'neutrino'
48
}
49

50

51
def get_decay_modes(value):
11✔
52
    """Return sequence of decay modes given an ENDF RTYP value.
53

54
    Parameters
55
    ----------
56
    value : float
57
        ENDF definition of sequence of decay modes
58

59
    Returns
60
    -------
61
    list of str
62
        List of successive decays, e.g. ('beta-', 'neutron')
63

64
    """
65
    if int(value) == 10:
11✔
66
        # The logic below would treat 10.0 as [1, 0] rather than [10] as it
67
        # should, so we handle this case separately
68
        return ['unknown']
11✔
69
    else:
70
        return [_DECAY_MODES[int(x)][0] for x in
11✔
71
                str(value).strip('0').replace('.', '')]
72

73

74
class FissionProductYields(EqualityMixin):
11✔
75
    """Independent and cumulative fission product yields.
76

77
    Parameters
78
    ----------
79
    ev_or_filename : str, openmc.data.endf.Evaluation, or endf.Material
80
        ENDF fission product yield evaluation to read from. If given as a
81
        string, it is assumed to be the filename for the ENDF file.
82

83
    Attributes
84
    ----------
85
    cumulative : list of dict
86
        Cumulative yields for each tabulated energy. Each item in the list is a
87
        dictionary whose keys are nuclide names and values are cumulative
88
        yields. The i-th dictionary corresponds to the i-th incident neutron
89
        energy.
90
    energies : Iterable of float or None
91
        Energies at which fission product yields are tabulated.
92
    independent : list of dict
93
        Independent yields for each tabulated energy. Each item in the list is a
94
        dictionary whose keys are nuclide names and values are independent
95
        yields. The i-th dictionary corresponds to the i-th incident neutron
96
        energy.
97
    nuclide : dict
98
        Properties of the fissioning nuclide.
99

100
    Notes
101
    -----
102
    Neutron fission yields are typically not measured with a monoenergetic
103
    source of neutrons. As such, if the fission yields are given at, e.g.,
104
    0.0253 eV, one should interpret this as meaning that they are derived from a
105
    typical thermal reactor flux spectrum as opposed to a monoenergetic source
106
    at 0.0253 eV.
107

108
    """
109
    def __init__(self, ev_or_filename):
11✔
110
        # Define function that can be used to read both independent and
111
        # cumulative yields
112
        def get_yields(file_obj):
11✔
113
            # Determine number of energies
114
            n_energy = get_head_record(file_obj)[2]
11✔
115
            energies = np.zeros(n_energy)
11✔
116

117
            data = []
11✔
118
            for i in range(n_energy):
11✔
119
                # Determine i-th energy and number of products
120
                items, values = get_list_record(file_obj)
11✔
121
                energies[i] = items[0]
11✔
122
                n_products = items[5]
11✔
123

124
                # Get yields for i-th energy
125
                yields = {}
11✔
126
                for j in range(n_products):
11✔
127
                    Z, A = divmod(int(values[4*j]), 1000)
11✔
128
                    isomeric_state = int(values[4*j + 1])
11✔
129
                    name = gnds_name(Z, A, isomeric_state)
11✔
130
                    yield_j = ufloat(values[4*j + 2], values[4*j + 3])
11✔
131
                    yields[name] = yield_j
11✔
132

133
                data.append(yields)
11✔
134

135
            return energies, data
11✔
136

137
        ev = as_evaluation(ev_or_filename)
11✔
138

139
        # Assign basic nuclide properties
140
        self.nuclide = {
11✔
141
            'name': ev.gnds_name,
142
            'atomic_number': ev.target['atomic_number'],
143
            'mass_number': ev.target['mass_number'],
144
            'isomeric_state': ev.target['isomeric_state']
145
        }
146

147
        # Read independent yields (MF=8, MT=454)
148
        if (8, 454) in ev.section:
11✔
149
            file_obj = StringIO(ev.section[8, 454])
11✔
150
            self.energies, self.independent = get_yields(file_obj)
11✔
151

152
        # Read cumulative yields (MF=8, MT=459)
153
        if (8, 459) in ev.section:
11✔
154
            file_obj = StringIO(ev.section[8, 459])
11✔
155
            energies, self.cumulative = get_yields(file_obj)
11✔
156
            assert np.all(energies == self.energies)
11✔
157

158
    @classmethod
11✔
159
    def from_endf(cls, ev_or_filename):
11✔
160
        """Generate fission product yield data from an ENDF evaluation
161

162
        Parameters
163
        ----------
164
        ev_or_filename : str, openmc.data.endf.Evaluation, or endf.Material
165
            ENDF fission product yield evaluation to read from. If given as a
166
            string, it is assumed to be the filename for the ENDF file.
167

168
        Returns
169
        -------
170
        openmc.data.FissionProductYields
171
            Fission product yield data
172

173
        """
174
        return cls(ev_or_filename)
11✔
175

176

177
class DecayMode(EqualityMixin):
11✔
178
    """Radioactive decay mode.
179

180
    Parameters
181
    ----------
182
    parent : str
183
        Parent decaying nuclide
184
    modes : list of str
185
        Successive decay modes
186
    daughter_state : int
187
        Metastable state of the daughter nuclide
188
    energy : uncertainties.UFloat
189
        Total decay energy in eV available in the decay process.
190
    branching_ratio : uncertainties.UFloat
191
        Fraction of the decay of the parent nuclide which proceeds by this mode.
192

193
    Attributes
194
    ----------
195
    branching_ratio : uncertainties.UFloat
196
        Fraction of the decay of the parent nuclide which proceeds by this mode.
197
    daughter : str
198
        Name of daughter nuclide produced from decay
199
    energy : uncertainties.UFloat
200
        Total decay energy in eV available in the decay process.
201
    modes : list of str
202
        Successive decay modes
203
    parent : str
204
        Parent decaying nuclide
205

206
    """
207

208
    def __init__(self, parent, modes, daughter_state, energy,
11✔
209
                 branching_ratio):
210
        self._daughter_state = daughter_state
11✔
211
        self.parent = parent
11✔
212
        self.modes = modes
11✔
213
        self.energy = energy
11✔
214
        self.branching_ratio = branching_ratio
11✔
215

216
    def __repr__(self):
11✔
217
        return ('<DecayMode: ({}), {} -> {}, {}>'.format(
11✔
218
            ','.join(self.modes), self.parent, self.daughter,
219
            self.branching_ratio))
220

221
    @property
11✔
222
    def branching_ratio(self):
11✔
223
        return self._branching_ratio
11✔
224

225
    @branching_ratio.setter
11✔
226
    def branching_ratio(self, branching_ratio):
11✔
227
        cv.check_type('branching ratio', branching_ratio, UFloat)
11✔
228
        cv.check_greater_than('branching ratio',
11✔
229
                              branching_ratio.nominal_value, 0.0, True)
230
        if branching_ratio.nominal_value == 0.0:
11✔
UNCOV
231
            warn('Decay mode {} of parent {} has a zero branching ratio.'
×
232
                 .format(self.modes, self.parent))
233
        cv.check_greater_than('branching ratio uncertainty',
11✔
234
                              branching_ratio.std_dev, 0.0, True)
235
        self._branching_ratio = branching_ratio
11✔
236

237
    @property
11✔
238
    def daughter(self):
11✔
239
        # Determine atomic number and mass number of parent
240
        Z, A, _ = zam(self.parent)
11✔
241

242
        # Process changes
243
        for mode in self.modes:
11✔
244
            for name, changes in _DECAY_MODES.values():
11✔
245
                if name == mode:
11✔
246
                    if changes is not None:
11✔
247
                        delta_A, delta_Z = changes
11✔
248
                        A += delta_A
11✔
249
                        Z += delta_Z
11✔
250
                        break
11✔
251
                    else:
252
                        return None
11✔
253

254
        return gnds_name(Z, A, self._daughter_state)
11✔
255

256
    @property
11✔
257
    def parent(self):
11✔
258
        return self._parent
11✔
259

260
    @parent.setter
11✔
261
    def parent(self, parent):
11✔
262
        cv.check_type('parent nuclide', parent, str)
11✔
263
        self._parent = parent
11✔
264

265
    @property
11✔
266
    def energy(self):
11✔
267
        return self._energy
11✔
268

269
    @energy.setter
11✔
270
    def energy(self, energy):
11✔
271
        cv.check_type('decay energy', energy, UFloat)
11✔
272
        cv.check_greater_than('decay energy', energy.nominal_value, 0.0, True)
11✔
273
        cv.check_greater_than('decay energy uncertainty',
11✔
274
                              energy.std_dev, 0.0, True)
275
        self._energy = energy
11✔
276

277
    @property
11✔
278
    def modes(self):
11✔
279
        return self._modes
11✔
280

281
    @modes.setter
11✔
282
    def modes(self, modes):
11✔
283
        cv.check_type('decay modes', modes, Iterable, str)
11✔
284
        self._modes = modes
11✔
285

286

287
class Decay(EqualityMixin):
11✔
288
    """Radioactive decay data.
289

290
    Parameters
291
    ----------
292
    ev_or_filename : str, openmc.data.endf.Evaluation, or endf.Material
293
        ENDF radioactive decay data evaluation to read from. If given as a
294
        string, it is assumed to be the filename for the ENDF file.
295

296
    Attributes
297
    ----------
298
    average_energies : dict
299
        Average decay energies in eV of each type of radiation for decay heat
300
        applications.
301
    decay_constant : uncertainties.UFloat
302
        Decay constant in inverse seconds.
303
    decay_energy : uncertainties.UFloat
304
        Average energy in [eV] per decay for decay heat applications
305
    half_life : uncertainties.UFloat
306
        Half-life of the decay in seconds.
307
    modes : list
308
        Decay mode information for each mode of decay.
309
    nuclide : dict
310
        Dictionary describing decaying nuclide with keys 'name',
311
        'excited_state', 'mass', 'stable', 'spin', and 'parity'.
312
    spectra : dict
313
        Resulting radiation spectra for each radiation type.
314
    sources : dict
315
        Radioactive decay source distributions represented as a dictionary
316
        mapping particle types (e.g., 'photon') to instances of
317
        :class:`openmc.stats.Univariate`.
318

319
        .. versionadded:: 0.13.1
320

321
    """
322
    def __init__(self, ev_or_filename):
11✔
323
        ev = as_evaluation(ev_or_filename)
11✔
324

325
        file_obj = StringIO(ev.section[8, 457])
11✔
326

327
        self.nuclide = {}
11✔
328
        self.modes = []
11✔
329
        self.spectra = {}
11✔
330
        self.average_energies = {}
11✔
331

332
        # Get head record
333
        items = get_head_record(file_obj)
11✔
334
        Z, A = divmod(items[0], 1000)
11✔
335
        metastable = items[3]
11✔
336
        self.nuclide['atomic_number'] = Z
11✔
337
        self.nuclide['mass_number'] = A
11✔
338
        self.nuclide['isomeric_state'] = metastable
11✔
339
        self.nuclide['name'] = gnds_name(Z, A, metastable)
11✔
340
        self.nuclide['mass'] = items[1]  # AWR
11✔
341
        self.nuclide['excited_state'] = items[2]  # State of the original nuclide
11✔
342
        self.nuclide['stable'] = (items[4] == 1)  # Nucleus stability flag
11✔
343

344
        # Determine if radioactive/stable
345
        if not self.nuclide['stable']:
11✔
346
            NSP = items[5]  # Number of radiation types
11✔
347

348
            # Half-life and decay energies
349
            items, values = get_list_record(file_obj)
11✔
350
            self.half_life = ufloat(items[0], items[1])
11✔
351
            NC = items[4]//2
11✔
352
            pairs = list(zip(values[::2], values[1::2]))
11✔
353
            ex = self.average_energies
11✔
354
            ex['light'] = ufloat(*pairs[0])
11✔
355
            ex['electromagnetic'] = ufloat(*pairs[1])
11✔
356
            ex['heavy'] = ufloat(*pairs[2])
11✔
357
            if NC == 17:
11✔
UNCOV
358
                ex['beta-'] = ufloat(*pairs[3])
×
UNCOV
359
                ex['beta+'] = ufloat(*pairs[4])
×
UNCOV
360
                ex['auger'] = ufloat(*pairs[5])
×
UNCOV
361
                ex['conversion'] = ufloat(*pairs[6])
×
UNCOV
362
                ex['gamma'] = ufloat(*pairs[7])
×
UNCOV
363
                ex['xray'] = ufloat(*pairs[8])
×
UNCOV
364
                ex['bremsstrahlung'] = ufloat(*pairs[9])
×
365
                ex['annihilation'] = ufloat(*pairs[10])
×
366
                ex['alpha'] = ufloat(*pairs[11])
×
367
                ex['recoil'] = ufloat(*pairs[12])
×
368
                ex['SF'] = ufloat(*pairs[13])
×
369
                ex['neutron'] = ufloat(*pairs[14])
×
370
                ex['proton'] = ufloat(*pairs[15])
×
371
                ex['neutrino'] = ufloat(*pairs[16])
×
372

373
            items, values = get_list_record(file_obj)
11✔
374
            spin = items[0]
11✔
375
            # ENDF-102 specifies that unknown spin should be reported as -77.777
376
            if spin == -77.777:
11✔
377
                self.nuclide['spin'] = None
11✔
378
            else:
379
                self.nuclide['spin'] = spin
11✔
380
            self.nuclide['parity'] = items[1]  # Parity of the nuclide
11✔
381

382
            # Decay mode information
383
            n_modes = items[5]  # Number of decay modes
11✔
384
            for i in range(n_modes):
11✔
385
                decay_type = get_decay_modes(values[6*i])
11✔
386
                isomeric_state = int(values[6*i + 1])
11✔
387
                energy = ufloat(*values[6*i + 2:6*i + 4])
11✔
388
                branching_ratio = ufloat(*values[6*i + 4:6*(i + 1)])
11✔
389

390
                mode = DecayMode(self.nuclide['name'], decay_type, isomeric_state,
11✔
391
                                 energy, branching_ratio)
392
                self.modes.append(mode)
11✔
393

394
            discrete_type = {0.0: None, 1.0: 'allowed', 2.0: 'first-forbidden',
11✔
395
                             3.0: 'second-forbidden', 4.0: 'third-forbidden',
396
                             5.0: 'fourth-forbidden', 6.0: 'fifth-forbidden'}
397

398
            # Read spectra
399
            for i in range(NSP):
11✔
400
                spectrum = {}
11✔
401

402
                items, values = get_list_record(file_obj)
11✔
403
                # Decay radiation type
404
                spectrum['type'] = _RADIATION_TYPES[items[1]]
11✔
405
                # Continuous spectrum flag
406
                spectrum['continuous_flag'] = {0: 'discrete', 1: 'continuous',
11✔
407
                                               2: 'both'}[items[2]]
408
                spectrum['discrete_normalization'] = ufloat(*values[0:2])
11✔
409
                spectrum['energy_average'] = ufloat(*values[2:4])
11✔
410
                spectrum['continuous_normalization'] = ufloat(*values[4:6])
11✔
411

412
                NER = items[5]  # Number of tabulated discrete energies
11✔
413

414
                if not spectrum['continuous_flag'] == 'continuous':
11✔
415
                    # Information about discrete spectrum
416
                    spectrum['discrete'] = []
11✔
417
                    for j in range(NER):
11✔
418
                        items, values = get_list_record(file_obj)
11✔
419
                        di = {}
11✔
420
                        di['energy'] = ufloat(*items[0:2])
11✔
421
                        di['from_mode'] = get_decay_modes(values[0])
11✔
422
                        di['type'] = discrete_type[values[1]]
11✔
423
                        di['intensity'] = ufloat(*values[2:4])
11✔
424
                        if spectrum['type'] == 'ec/beta+':
11✔
425
                            di['positron_intensity'] = ufloat(*values[4:6])
11✔
426
                        elif spectrum['type'] == 'gamma':
11✔
427
                            if len(values) >= 6:
11✔
428
                                di['internal_pair'] = ufloat(*values[4:6])
11✔
429
                            if len(values) >= 8:
11✔
430
                                di['total_internal_conversion'] = ufloat(*values[6:8])
11✔
431
                            if len(values) == 12:
11✔
432
                                di['k_shell_conversion'] = ufloat(*values[8:10])
11✔
433
                                di['l_shell_conversion'] = ufloat(*values[10:12])
11✔
434
                        spectrum['discrete'].append(di)
11✔
435

436
                if not spectrum['continuous_flag'] == 'discrete':
11✔
437
                    # Read continuous spectrum
438
                    ci = {}
11✔
439
                    params, ci['probability'] = get_tab1_record(file_obj)
11✔
440
                    ci['from_mode'] = get_decay_modes(params[0])
11✔
441

442
                    # Read covariance (Ek, Fk) table
443
                    LCOV = params[3]
11✔
444
                    if LCOV != 0:
11✔
UNCOV
445
                        items, values = get_list_record(file_obj)
×
UNCOV
446
                        ci['covariance_lb'] = items[3]
×
UNCOV
447
                        ci['covariance'] = zip(values[0::2], values[1::2])
×
448

449
                    spectrum['continuous'] = ci
11✔
450

451
                # Add spectrum to dictionary
452
                self.spectra[spectrum['type']] = spectrum
11✔
453

454
        else:
455
            items, values = get_list_record(file_obj)
11✔
456
            items, values = get_list_record(file_obj)
11✔
457
            self.nuclide['spin'] = items[0]
11✔
458
            self.nuclide['parity'] = items[1]
11✔
459
            self.half_life = ufloat(float('inf'), float('inf'))
11✔
460

461
    @property
11✔
462
    def decay_constant(self):
11✔
463
        if self.half_life.n == 0.0:
11✔
UNCOV
464
            name = self.nuclide['name']
×
UNCOV
465
            raise ValueError(f"{name} is listed as unstable but has a zero half-life.")
×
466
        return log(2.)/self.half_life
11✔
467

468
    @property
11✔
469
    def decay_energy(self):
11✔
470
        energy = self.average_energies
11✔
471
        if energy:
11✔
472
            return energy['light'] + energy['electromagnetic'] + energy['heavy']
11✔
473
        else:
UNCOV
474
            return ufloat(0, 0)
×
475

476
    @classmethod
11✔
477
    def from_endf(cls, ev_or_filename):
11✔
478
        """Generate radioactive decay data from an ENDF evaluation
479

480
        Parameters
481
        ----------
482
        ev_or_filename : str, openmc.data.endf.Evaluation, or endf.Material
483
            ENDF radioactive decay data evaluation to read from. If given as a
484
            string, it is assumed to be the filename for the ENDF file.
485

486
        Returns
487
        -------
488
        openmc.data.Decay
489
            Radioactive decay data
490

491
        """
492
        return cls(ev_or_filename)
11✔
493

494
    @cached_property
11✔
495
    def sources(self):
11✔
496
        """Radioactive decay source distributions"""
497
        sources = {}
11✔
498
        name = self.nuclide['name']
11✔
499
        decay_constant = self.decay_constant.n
11✔
500
        for particle, spectra in self.spectra.items():
11✔
501
            # Set particle type based on 'particle' above
502
            particle_type = {
11✔
503
                'gamma': 'photon',
504
                'beta-': 'electron',
505
                'ec/beta+': 'positron',
506
                'alpha': 'alpha',
507
                'n': 'neutron',
508
                'sf': 'fragment',
509
                'p': 'proton',
510
                'e-': 'electron',
511
                'xray': 'photon',
512
                'anti-neutrino': 'anti-neutrino',
513
                'neutrino': 'neutrino',
514
            }[particle]
515

516
            if particle_type not in sources:
11✔
517
                sources[particle_type] = []
11✔
518

519
            # Create distribution for discrete
520
            if spectra['continuous_flag'] in ('discrete', 'both'):
11✔
521
                energies = []
11✔
522
                intensities = []
11✔
523
                for discrete_data in spectra['discrete']:
11✔
524
                    energies.append(discrete_data['energy'].n)
11✔
525
                    intensities.append(discrete_data['intensity'].n)
11✔
526
                energies = np.array(energies)
11✔
527
                intensity = spectra['discrete_normalization'].n
11✔
528
                rates = decay_constant * intensity * np.array(intensities)
11✔
529
                dist_discrete = Discrete(energies, rates)
11✔
530
                sources[particle_type].append(dist_discrete)
11✔
531

532
            # Create distribution for continuous
533
            if spectra['continuous_flag'] in ('continuous', 'both'):
11✔
534
                f = spectra['continuous']['probability']
11✔
535
                if len(f.interpolation) > 1:
11✔
UNCOV
536
                    raise NotImplementedError("Multiple interpolation regions: {name}, {particle}")
×
537
                interpolation = INTERPOLATION_SCHEME[f.interpolation[0]]
11✔
538
                if interpolation not in ('histogram', 'linear-linear'):
11✔
UNCOV
539
                    warn(
×
540
                        f"Continuous spectra with {interpolation} interpolation "
541
                        f"({name}, {particle}) encountered.")
542

543
                intensity = spectra['continuous_normalization'].n
11✔
544
                rates = decay_constant * intensity * f.y
11✔
545
                dist_continuous = Tabular(f.x, rates, interpolation)
11✔
546
                sources[particle_type].append(dist_continuous)
11✔
547

548
        # Combine discrete distributions
549
        merged_sources = {}
11✔
550
        for particle_type, dist_list in sources.items():
11✔
551
            merged_sources[particle_type] = combine_distributions(
11✔
552
                dist_list, [1.0]*len(dist_list))
553

554
        return merged_sources
11✔
555

556

557
_DECAY_PHOTON_ENERGY = {}
11✔
558

559

560
def decay_photon_energy(nuclide: str) -> Univariate | None:
11✔
561
    """Get photon energy distribution resulting from the decay of a nuclide
562

563
    This function relies on data stored in a depletion chain. Before calling it
564
    for the first time, you need to ensure that a depletion chain has been
565
    specified in openmc.config['chain_file'].
566

567
    .. versionadded:: 0.13.2
568

569
    Parameters
570
    ----------
571
    nuclide : str
572
        Name of nuclide, e.g., 'Co58'
573

574
    Returns
575
    -------
576
    openmc.stats.Univariate or None
577
        Distribution of energies in [eV] of photons emitted from decay, or None
578
        if no photon source exists. Note that the probabilities represent
579
        intensities, given as [Bq/atom] (in other words, decay constants).
580
    """
581
    if not _DECAY_PHOTON_ENERGY:
11✔
582
        chain_file = openmc.config.get('chain_file')
11✔
583
        if chain_file is None:
11✔
584
            raise DataError(
11✔
585
                "A depletion chain file must be specified with "
586
                "openmc.config['chain_file'] in order to load decay data."
587
            )
588

589
        from openmc.deplete import Chain
11✔
590
        chain = Chain.from_xml(chain_file)
11✔
591
        for nuc in chain.nuclides:
11✔
592
            if 'photon' in nuc.sources:
11✔
593
                _DECAY_PHOTON_ENERGY[nuc.name] = nuc.sources['photon']
11✔
594

595
        # If the chain file contained no sources at all, warn the user
596
        if not _DECAY_PHOTON_ENERGY:
11✔
UNCOV
597
            warn(f"Chain file '{chain_file}' does not have any decay photon "
×
598
                 "sources listed.")
599

600
    return _DECAY_PHOTON_ENERGY.get(nuclide)
11✔
601

602

603
_DECAY_ENERGY = {}
11✔
604

605

606
def decay_energy(nuclide: str):
11✔
607
    """Get decay energy value resulting from the decay of a nuclide
608

609
    This function relies on data stored in a depletion chain. Before calling it
610
    for the first time, you need to ensure that a depletion chain has been
611
    specified in openmc.config['chain_file'].
612

613
    .. versionadded:: 0.13.3
614

615
    Parameters
616
    ----------
617
    nuclide : str
618
        Name of nuclide, e.g., 'H3'
619

620
    Returns
621
    -------
622
    float
623
        Decay energy of nuclide in [eV]. If the nuclide is stable, a value of
624
        0.0 is returned.
625
    """
626
    if not _DECAY_ENERGY:
11✔
627
        chain_file = openmc.config.get('chain_file')
11✔
628
        if chain_file is None:
11✔
UNCOV
629
            raise DataError(
×
630
                "A depletion chain file must be specified with "
631
                "openmc.config['chain_file'] in order to load decay data."
632
            )
633

634
        from openmc.deplete import Chain
11✔
635
        chain = Chain.from_xml(chain_file)
11✔
636
        for nuc in chain.nuclides:
11✔
637
            if nuc.decay_energy:
11✔
638
                _DECAY_ENERGY[nuc.name] = nuc.decay_energy
11✔
639

640
        # If the chain file contained no decay energy, warn the user
641
        if not _DECAY_ENERGY:
11✔
UNCOV
642
            warn(f"Chain file '{chain_file}' does not have any decay energy.")
×
643

644
    return _DECAY_ENERGY.get(nuclide, 0.0)
11✔
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