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

openmc-dev / openmc / 34416149688

09 Sep 2026 11:15PM UTC coverage: 81.48% (+0.1%) from 81.347%
34416149688

Pull #4087

github

web-flow
Merge 93138e589 into 5260b9a0f
Pull Request #4087: Compute bounding boxes for general planes and tori in C++

18756 of 27197 branches covered (68.96%)

Branch coverage included in aggregate %.

43 of 46 new or added lines in 2 files covered. (93.48%)

959 existing lines in 29 files now uncovered.

60863 of 70519 relevant lines covered (86.31%)

49704801.91 hits per line

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

94.25
/openmc/data/function.py
1
from abc import ABC, abstractmethod
11 ✔
2
from collections.abc import Iterable, Callable
11 ✔
3
from functools import reduce
11 ✔
4
from itertools import zip_longest
11 ✔
5
from math import exp, log
11 ✔
6
from numbers import Real, Integral
11 ✔
7

8
import numpy as np
11 ✔
9

10
import openmc.checkvalue as cv
11 ✔
11
import openmc.data
11 ✔
12
from openmc.mixin import EqualityMixin
11 ✔
13
from .data import EV_PER_MEV
11 ✔
14

15
INTERPOLATION_SCHEME = {1: 'histogram', 2: 'linear-linear', 3: 'linear-log',
11 ✔
16
                        4: 'log-linear', 5: 'log-log'}
17

18

19
def sum_functions(funcs):
11 ✔
20
    """Add tabulated/polynomial functions together.
21

22
    Parameters
23
    ----------
24
    funcs : list of Function1D
25
        Functions to add
26

27
    Returns
28
    -------
29
    Function1D
30
        Sum of polynomial/tabulated functions
31

32
    """
33
    # Copy so we can iterate multiple times
34
    funcs = list(funcs)
11 ✔
35

36
    # Get x values for all tabulated components
37
    xs = []
11 ✔
38
    for f in funcs:
11 ✔
39
        if isinstance(f, Tabulated1D):
11 ✔
40
            xs.append(f.x)
11 ✔
41
            if not np.all(f.interpolation == 2):
11 ✔
42
                raise ValueError('Only linear-linear tabulated functions '
×
43
                                 'can be combined')
44

45
    if xs:
11 ✔
46
        # Take the union of all energies (sorted)
47
        x = reduce(np.union1d, xs)
11 ✔
48

49
        # Evaluate each function and add together. Use a floating-point
50
        # accumulator so that integer-valued grids do not truncate results.
51
        y = np.zeros_like(x, dtype=float)
11 ✔
52
        for f in funcs:
11 ✔
53
            y += f(x)
11 ✔
54
        return Tabulated1D(x, y)
11 ✔
55
    else:
56
        # If no tabulated functions are present, we need to combine the
57
        # polynomials by adding their coefficients
58
        coeffs = [sum(x) for x in zip_longest(*funcs, fillvalue=0.0)]
11 ✔
59
        return Polynomial(coeffs)
11 ✔
60

61

62
class Function1D(EqualityMixin, ABC):
11 ✔
63
    """A function of one independent variable with HDF5 support."""
64
    @abstractmethod
11 ✔
65
    def __call__(self): pass
11 ✔
66

67
    @abstractmethod
11 ✔
68
    def to_hdf5(self, group, name='xy'):
11 ✔
69
        """Write function to an HDF5 group
70

71
        Parameters
72
        ----------
73
        group : h5py.Group
74
            HDF5 group to write to
75
        name : str
76
            Name of the dataset to create
77

78
        """
UNCOV
79
        pass
×
80

81
    @classmethod
11 ✔
82
    def from_hdf5(cls, dataset):
11 ✔
83
        """Generate function from an HDF5 dataset
84

85
        Parameters
86
        ----------
87
        dataset : h5py.Dataset
88
            Dataset to read from
89

90
        Returns
91
        -------
92
        openmc.data.Function1D
93
            Function read from dataset
94

95
        """
96
        for subclass in cls.__subclasses__():
11 ✔
97
            if dataset.attrs['type'].decode() == subclass.__name__:
11 ✔
98
                return subclass.from_hdf5(dataset)
11 ✔
UNCOV
99
        raise ValueError("Unrecognized Function1D class: '"
×
100
                         + dataset.attrs['type'].decode() + "'")
101

102

103
class Tabulated1D(Function1D):
11 ✔
104
    """A one-dimensional tabulated function.
105

106
    This class mirrors the TAB1 type from the ENDF-6 format. A tabulated
107
    function is specified by tabulated (x,y) pairs along with interpolation
108
    rules that determine the values between tabulated pairs.
109

110
    Once an object has been created, it can be used as though it were an actual
111
    function, e.g.:
112

113
    >>> f = Tabulated1D([0, 10], [4, 5])
114
    >>> [f(xi) for xi in numpy.linspace(0, 10, 5)]
115
    [4.0, 4.25, 4.5, 4.75, 5.0]
116

117
    ENDF-6 only defines the function on its tabulated domain. By convention,
118
    OpenMC returns zero when evaluating outside that domain. This behavior is
119
    used when combining threshold reactions on a union energy grid and applies
120
    to both scalar and array arguments.
121

122
    Parameters
123
    ----------
124
    x : Iterable of float
125
        Independent variable
126
    y : Iterable of float
127
        Dependent variable
128
    breakpoints : Iterable of int
129
        Breakpoints for interpolation regions
130
    interpolation : Iterable of int
131
        Interpolation scheme identification number, e.g., 3 means y is linear in
132
        ln(x).
133

134
    Attributes
135
    ----------
136
    x : Iterable of float
137
        Independent variable
138
    y : Iterable of float
139
        Dependent variable
140
    breakpoints : Iterable of int
141
        Breakpoints for interpolation regions
142
    interpolation : Iterable of int
143
        Interpolation scheme identification number, e.g., 3 means y is linear in
144
        ln(x).
145
    n_regions : int
146
        Number of interpolation regions
147
    n_pairs : int
148
        Number of tabulated (x,y) pairs
149

150
    """
151

152
    def __init__(self, x, y, breakpoints=None, interpolation=None):
11 ✔
153
        if breakpoints is None or interpolation is None:
11 ✔
154
            # Single linear-linear interpolation region by default
155
            self.breakpoints = np.array([len(x)])
11 ✔
156
            self.interpolation = np.array([2])
11 ✔
157
        else:
158
            self.breakpoints = np.asarray(breakpoints, dtype=int)
11 ✔
159
            self.interpolation = np.asarray(interpolation, dtype=int)
11 ✔
160

161
        self.x = np.asarray(x)
11 ✔
162
        self.y = np.asarray(y)
11 ✔
163

164
    def __call__(self, x):
11 ✔
165
        # Check if input is scalar
166
        if not isinstance(x, Iterable):
11 ✔
167
            return self._interpolate_scalar(x)
11 ✔
168

169
        x = np.array(x)
11 ✔
170

171
        if not np.all(np.isfinite(x)):
11 ✔
172
            raise ValueError('Interpolation points must be finite')
11 ✔
173

174
        # Create output array.  Use a floating-point dtype so that
175
        # interpolated values are not truncated when the input is an
176
        # integer-valued array.
177
        y = np.zeros_like(x, dtype=float)
11 ✔
178

179
        # Get indices for interpolation
180
        idx = np.searchsorted(self.x, x, side='right') - 1
11 ✔
181

182
        # Loop over interpolation regions
183
        for k in range(len(self.breakpoints)):
11 ✔
184
            # Get indices for the begining and ending of this region
185
            i_begin = self.breakpoints[k-1] - 1 if k > 0 else 0
11 ✔
186
            i_end = self.breakpoints[k] - 1
11 ✔
187

188
            # Figure out which idx values lie within this region
189
            contained = (idx >= i_begin) & (idx < i_end)
11 ✔
190

191
            xk = x[contained]                 # x values in this region
11 ✔
192
            xi = self.x[idx[contained]]       # low edge of corresponding bins
11 ✔
193
            xi1 = self.x[idx[contained] + 1]  # high edge of corresponding bins
11 ✔
194
            yi = self.y[idx[contained]]
11 ✔
195
            yi1 = self.y[idx[contained] + 1]
11 ✔
196

197
            if self.interpolation[k] == 1:
11 ✔
198
                # Histogram
199
                y[contained] = yi
11 ✔
200

201
            elif self.interpolation[k] == 2:
11 ✔
202
                # Linear-linear
203
                y[contained] = yi + (xk - xi)/(xi1 - xi)*(yi1 - yi)
11 ✔
204

205
            elif self.interpolation[k] == 3:
11 ✔
206
                # Linear-log
207
                y[contained] = yi + np.log(xk/xi)/np.log(xi1/xi)*(yi1 - yi)
11 ✔
208

209
            elif self.interpolation[k] == 4:
11 ✔
210
                # Log-linear
211
                y[contained] = yi*np.exp((xk - xi)/(xi1 - xi)*np.log(yi1/yi))
11 ✔
212

213
            elif self.interpolation[k] == 5:
11 ✔
214
                # Log-log
215
                y[contained] = (yi*np.exp(np.log(xk/xi)/np.log(xi1/xi)
11 ✔
216
                                *np.log(yi1/yi)))
217

218
        # In some cases, x values might be outside the tabulated region due only
219
        # to precision, so we check if they're close and set them equal if so.
220
        y[np.isclose(x, self.x[0], rtol=0.0, atol=1e-14)] = self.y[0]
11 ✔
221
        y[np.isclose(x, self.x[-1], rtol=0.0, atol=1e-14)] = self.y[-1]
11 ✔
222

223
        return y
11 ✔
224

225
    def _interpolate_scalar(self, x):
11 ✔
226
        if not np.isfinite(x):
11 ✔
227
            raise ValueError('Interpolation point must be finite')
11 ✔
228

229
        if x < self._x[0]:
11 ✔
230
            if np.isclose(x, self.x[0], rtol=0.0, atol=1e-14):
11 ✔
UNCOV
231
                return self._y[0]
×
232
            return 0.0
11 ✔
233
        elif x > self._x[-1]:
11 ✔
234
            if np.isclose(x, self.x[-1], rtol=0.0, atol=1e-14):
11 ✔
235
                return self._y[-1]
11 ✔
236
            return 0.0
11 ✔
237
        elif x == self._x[0]:
11 ✔
238
            return self._y[0]
11 ✔
239
        elif x == self._x[-1]:
11 ✔
240
            return self._y[-1]
11 ✔
241

242
        # Get the index for interpolation
243
        idx = np.searchsorted(self._x, x, side='right') - 1
11 ✔
244

245
        # Loop over interpolation regions
246
        for b, p in zip(self.breakpoints, self.interpolation):
11 ✔
247
            if idx < b - 1:
11 ✔
248
                break
11 ✔
249

250
        xi = self._x[idx]       # low edge of the corresponding bin
11 ✔
251
        xi1 = self._x[idx + 1]  # high edge of the corresponding bin
11 ✔
252
        yi = self._y[idx]
11 ✔
253
        yi1 = self._y[idx + 1]
11 ✔
254

255
        if p == 1:
11 ✔
256
            # Histogram
257
            return yi
11 ✔
258

259
        elif p == 2:
11 ✔
260
            # Linear-linear
261
            return yi + (x - xi)/(xi1 - xi)*(yi1 - yi)
11 ✔
262

263
        elif p == 3:
11 ✔
264
            # Linear-log
265
            return yi + log(x/xi)/log(xi1/xi)*(yi1 - yi)
11 ✔
266

267
        elif p == 4:
11 ✔
268
            # Log-linear
269
            return yi*exp((x - xi)/(xi1 - xi)*log(yi1/yi))
11 ✔
270

271
        elif p == 5:
11 ✔
272
            # Log-log
273
            return yi*exp(log(x/xi)/log(xi1/xi)*log(yi1/yi))
11 ✔
274

275
    def __len__(self):
11 ✔
UNCOV
276
        return len(self.x)
×
277

278
    @property
11 ✔
279
    def x(self):
11 ✔
280
        return self._x
11 ✔
281

282
    @x.setter
11 ✔
283
    def x(self, x):
11 ✔
284
        cv.check_type('x values', x, Iterable, Real)
11 ✔
285
        self._x = x
11 ✔
286

287
    @property
11 ✔
288
    def y(self):
11 ✔
289
        return self._y
11 ✔
290

291
    @y.setter
11 ✔
292
    def y(self, y):
11 ✔
293
        cv.check_type('y values', y, Iterable, Real)
11 ✔
294
        self._y = y
11 ✔
295

296
    @property
11 ✔
297
    def breakpoints(self):
11 ✔
298
        return self._breakpoints
11 ✔
299

300
    @breakpoints.setter
11 ✔
301
    def breakpoints(self, breakpoints):
11 ✔
302
        cv.check_type('breakpoints', breakpoints, Iterable, Integral)
11 ✔
303
        self._breakpoints = breakpoints
11 ✔
304

305
    @property
11 ✔
306
    def interpolation(self):
11 ✔
307
        return self._interpolation
11 ✔
308

309
    @interpolation.setter
11 ✔
310
    def interpolation(self, interpolation):
11 ✔
311
        cv.check_type('interpolation', interpolation, Iterable, Integral)
11 ✔
312
        self._interpolation = interpolation
11 ✔
313

314
    @property
11 ✔
315
    def n_pairs(self):
11 ✔
UNCOV
316
        return len(self.x)
×
317

318
    @property
11 ✔
319
    def n_regions(self):
11 ✔
320
        return len(self.breakpoints)
11 ✔
321

322
    def integral(self):
11 ✔
323
        """Integral of the tabulated function over its tabulated range.
324

325
        Returns
326
        -------
327
        numpy.ndarray
328
            Array of same length as the tabulated data that represents partial
329
            integrals from the bottom of the range to each tabulated point.
330

331
        """
332

333
        # Create output array
334
        partial_sum = np.zeros(len(self.x) - 1)
11 ✔
335

336
        i_low = 0
11 ✔
337
        for k in range(len(self.breakpoints)):
11 ✔
338
            # Determine which x values are within this interpolation range
339
            i_high = self.breakpoints[k] - 1
11 ✔
340

341
            # Get x values and bounding (x,y) pairs
342
            x0 = self.x[i_low:i_high]
11 ✔
343
            x1 = self.x[i_low + 1:i_high + 1]
11 ✔
344
            y0 = self.y[i_low:i_high]
11 ✔
345
            y1 = self.y[i_low + 1:i_high + 1]
11 ✔
346

347
            if self.interpolation[k] == 1:
11 ✔
348
                # Histogram
UNCOV
349
                partial_sum[i_low:i_high] = y0*(x1 - x0)
×
350

351
            elif self.interpolation[k] == 2:
11 ✔
352
                # Linear-linear
353
                m = (y1 - y0)/(x1 - x0)
11 ✔
354
                partial_sum[i_low:i_high] = (y0 - m*x0)*(x1 - x0) + \
11 ✔
355
                                            m*(x1**2 - x0**2)/2
356

357
            elif self.interpolation[k] == 3:
11 ✔
358
                # Linear-log
UNCOV
359
                logx = np.log(x1/x0)
×
UNCOV
360
                m = (y1 - y0)/logx
×
UNCOV
361
                partial_sum[i_low:i_high] = y0 + m*(x1*(logx - 1) + x0)
×
362

363
            elif self.interpolation[k] == 4:
11 ✔
364
                # Log-linear
UNCOV
365
                m = np.log(y1/y0)/(x1 - x0)
×
UNCOV
366
                partial_sum[i_low:i_high] = y0/m*(np.exp(m*(x1 - x0)) - 1)
×
367

368
            elif self.interpolation[k] == 5:
11 ✔
369
                # Log-log
370
                m = np.log(y1/y0)/np.log(x1/x0)
11 ✔
371
                partial_sum[i_low:i_high] = y0/((m + 1)*x0**m)*(
11 ✔
372
                    x1**(m + 1) - x0**(m + 1))
373

374
            i_low = i_high
11 ✔
375

376
        return np.concatenate(([0.], np.cumsum(partial_sum)))
11 ✔
377

378
    def to_hdf5(self, group, name='xy'):
11 ✔
379
        """Write tabulated function to an HDF5 group
380

381
        Parameters
382
        ----------
383
        group : h5py.Group
384
            HDF5 group to write to
385
        name : str
386
            Name of the dataset to create
387

388
        """
389
        dataset = group.create_dataset(name, data=np.vstack(
11 ✔
390
            [self.x, self.y]))
391
        dataset.attrs['type'] = np.bytes_(type(self).__name__)
11 ✔
392
        dataset.attrs['breakpoints'] = self.breakpoints
11 ✔
393
        dataset.attrs['interpolation'] = self.interpolation
11 ✔
394

395
    @classmethod
11 ✔
396
    def from_hdf5(cls, dataset):
11 ✔
397
        """Generate tabulated function from an HDF5 dataset
398

399
        Parameters
400
        ----------
401
        dataset : h5py.Dataset
402
            Dataset to read from
403

404
        Returns
405
        -------
406
        openmc.data.Tabulated1D
407
            Function read from dataset
408

409
        """
410
        if dataset.attrs['type'].decode() != cls.__name__:
11 ✔
UNCOV
411
            raise ValueError("Expected an HDF5 attribute 'type' equal to '"
×
412
                             + cls.__name__ + "'")
413

414
        x = dataset[0, :]
11 ✔
415
        y = dataset[1, :]
11 ✔
416
        breakpoints = dataset.attrs['breakpoints']
11 ✔
417
        interpolation = dataset.attrs['interpolation']
11 ✔
418
        return cls(x, y, breakpoints, interpolation)
11 ✔
419

420
    @classmethod
11 ✔
421
    def from_ace(cls, ace, idx=0, convert_units=True):
11 ✔
422
        """Create a Tabulated1D object from an ACE table.
423

424
        Parameters
425
        ----------
426
        ace : openmc.data.ace.Table
427
            An ACE table
428
        idx : int
429
            Offset to read from in XSS array (default of zero)
430
        convert_units : bool
431
            If the abscissa represents energy, indicate whether to convert MeV
432
            to eV.
433

434
        Returns
435
        -------
436
        openmc.data.Tabulated1D
437
            Tabulated data object
438

439
        """
440

441
        # Get number of regions and pairs
442
        n_regions = int(ace.xss[idx])
11 ✔
443
        n_pairs = int(ace.xss[idx + 1 + 2*n_regions])
11 ✔
444

445
        # Get interpolation information
446
        idx += 1
11 ✔
447
        if n_regions > 0:
11 ✔
448
            breakpoints = ace.xss[idx:idx + n_regions].astype(int)
11 ✔
449
            interpolation = ace.xss[idx + n_regions:idx + 2*n_regions].astype(int)
11 ✔
450
        else:
451
            # 0 regions implies linear-linear interpolation by default
452
            breakpoints = np.array([n_pairs])
11 ✔
453
            interpolation = np.array([2])
11 ✔
454

455
        # Get (x,y) pairs
456
        idx += 2*n_regions + 1
11 ✔
457
        x = ace.xss[idx:idx + n_pairs].copy()
11 ✔
458
        y = ace.xss[idx + n_pairs:idx + 2*n_pairs].copy()
11 ✔
459

460
        if convert_units:
11 ✔
461
            x *= EV_PER_MEV
11 ✔
462

463
        return Tabulated1D(x, y, breakpoints, interpolation)
11 ✔
464

465

466
class Polynomial(np.polynomial.Polynomial, Function1D):
11 ✔
467
    """A power series class.
468

469
    Parameters
470
    ----------
471
    coef : Iterable of float
472
        Polynomial coefficients in order of increasing degree
473

474
    """
475
    def to_hdf5(self, group, name='xy'):
11 ✔
476
        """Write polynomial function to an HDF5 group
477

478
        Parameters
479
        ----------
480
        group : h5py.Group
481
            HDF5 group to write to
482
        name : str
483
            Name of the dataset to create
484

485
        """
486
        dataset = group.create_dataset(name, data=self.coef)
11 ✔
487
        dataset.attrs['type'] = np.bytes_(type(self).__name__)
11 ✔
488

489
    @classmethod
11 ✔
490
    def from_hdf5(cls, dataset):
11 ✔
491
        """Generate function from an HDF5 dataset
492

493
        Parameters
494
        ----------
495
        dataset : h5py.Dataset
496
            Dataset to read from
497

498
        Returns
499
        -------
500
        openmc.data.Function1D
501
            Function read from dataset
502

503
        """
504
        if dataset.attrs['type'].decode() != cls.__name__:
11 ✔
UNCOV
505
            raise ValueError("Expected an HDF5 attribute 'type' equal to '"
×
506
                             + cls.__name__ + "'")
507
        return cls(dataset[()])
11 ✔
508

509

510
class Combination(EqualityMixin):
11 ✔
511
    """Combination of multiple functions with a user-defined operator
512

513
    This class allows you to create a callable object which represents the
514
    combination of other callable objects by way of a series of user-defined
515
    operators connecting each of the callable objects.
516

517
    Parameters
518
    ----------
519
    functions : Iterable of Callable
520
        Functions to combine according to operations
521
    operations : Iterable of numpy.ufunc
522
        Operations to perform between functions; note that the standard order
523
        of operations will not be followed, but can be simulated by
524
        combinations of Combination objects. The operations parameter must have
525
        a length one less than the number of functions.
526

527

528
    Attributes
529
    ----------
530
    functions : Iterable of Callable
531
        Functions to combine according to operations
532
    operations : Iterable of numpy.ufunc
533
        Operations to perform between functions; note that the standard order
534
        of operations will not be followed, but can be simulated by
535
        combinations of Combination objects. The operations parameter must have
536
        a length one less than the number of functions.
537

538
    """
539

540
    def __init__(self, functions, operations):
11 ✔
541
        self.functions = functions
11 ✔
542
        self.operations = operations
11 ✔
543

544
    def __call__(self, x):
11 ✔
545
        ans = self.functions[0](x)
11 ✔
546
        for i, operation in enumerate(self.operations):
11 ✔
547
            ans = operation(ans, self.functions[i + 1](x))
11 ✔
548
        return ans
11 ✔
549

550
    @property
11 ✔
551
    def functions(self):
11 ✔
552
        return self._functions
11 ✔
553

554
    @functions.setter
11 ✔
555
    def functions(self, functions):
11 ✔
556
        cv.check_type('functions', functions, Iterable, Callable)
11 ✔
557
        self._functions = functions
11 ✔
558

559
    @property
11 ✔
560
    def operations(self):
11 ✔
561
        return self._operations
11 ✔
562

563
    @operations.setter
11 ✔
564
    def operations(self, operations):
11 ✔
565
        cv.check_type('operations', operations, Iterable, np.ufunc)
11 ✔
566
        length = len(self.functions) - 1
11 ✔
567
        cv.check_length('operations', operations, length, length_max=length)
11 ✔
568
        self._operations = operations
11 ✔
569

570

571
class Sum(Function1D):
11 ✔
572
    """Sum of multiple functions.
573

574
    This class allows you to create a callable object which represents the sum
575
    of other callable objects. This is used for redundant reactions whereby the
576
    cross section is defined as the sum of other cross sections.
577

578
    Parameters
579
    ----------
580
    functions : Iterable of Callable
581
        Functions which are to be added together
582

583
    Attributes
584
    ----------
585
    functions : Iterable of Callable
586
        Functions which are to be added together
587

588
    """
589

590
    def __init__(self, functions):
11 ✔
591
        self.functions = list(functions)
11 ✔
592

593
    def __call__(self, x):
11 ✔
594
        return sum(f(x) for f in self.functions)
11 ✔
595

596
    @property
11 ✔
597
    def functions(self):
11 ✔
598
        return self._functions
11 ✔
599

600
    @functions.setter
11 ✔
601
    def functions(self, functions):
11 ✔
602
        cv.check_type('functions', functions, Iterable, Callable)
11 ✔
603
        self._functions = functions
11 ✔
604

605
    def to_hdf5(self, group, name='xy'):
11 ✔
606
        """Write sum of functions to an HDF5 group
607

608
        .. versionadded:: 0.13.1
609

610
        Parameters
611
        ----------
612
        group : h5py.Group
613
            HDF5 group to write to
614
        name : str
615
            Name of the dataset to create
616

617
        """
618
        sum_group = group.create_group(name)
11 ✔
619
        sum_group.attrs['type'] = np.bytes_(type(self).__name__)
11 ✔
620
        sum_group.attrs['n'] = len(self.functions)
11 ✔
621
        for i, f in enumerate(self.functions):
11 ✔
622
            f.to_hdf5(sum_group, f'func_{i+1}')
11 ✔
623

624
    @classmethod
11 ✔
625
    def from_hdf5(cls, group):
11 ✔
626
        """Generate sum of functions from an HDF5 group
627

628
        .. versionadded:: 0.13.1
629

630
        Parameters
631
        ----------
632
        group : h5py.Group
633
            Group to read from
634

635
        Returns
636
        -------
637
        openmc.data.Sum
638
            Functions read from the group
639

640
        """
641
        n = group.attrs['n']
11 ✔
642
        functions = [
11 ✔
643
            Function1D.from_hdf5(group[f'func_{i+1}'])
644
            for i in range(n)
645
        ]
646
        return cls(functions)
11 ✔
647

648

649
class Regions1D(EqualityMixin):
11 ✔
650
    r"""Piecewise composition of multiple functions.
651

652
    This class allows you to create a callable object which is composed
653
    of multiple other callable objects, each applying to a specific interval
654

655
    Parameters
656
    ----------
657
    functions : Iterable of Callable
658
        Functions which are to be combined in a piecewise fashion
659
    breakpoints : Iterable of float
660
        The values of the dependent variable that define the domain of
661
        each function. The `i`\ th and `(i+1)`\ th values are the limits of the
662
        domain of the `i`\ th function. Values must be monotonically increasing.
663

664
    Attributes
665
    ----------
666
    functions : Iterable of Callable
667
        Functions which are to be combined in a piecewise fashion
668
    breakpoints : Iterable of float
669
        The breakpoints between each function
670

671
    """
672

673
    def __init__(self, functions, breakpoints):
11 ✔
674
        self.functions = functions
11 ✔
675
        self.breakpoints = breakpoints
11 ✔
676

677
    def __call__(self, x):
11 ✔
678
        i = np.searchsorted(self.breakpoints, x)
11 ✔
679
        if isinstance(x, Iterable):
11 ✔
680
            ans = np.empty_like(x)
11 ✔
681
            for j in range(len(i)):
11 ✔
682
                ans[j] = self.functions[i[j]](x[j])
11 ✔
683
            return ans
11 ✔
684
        else:
UNCOV
685
            return self.functions[i](x)
×
686

687
    @property
11 ✔
688
    def functions(self):
11 ✔
689
        return self._functions
11 ✔
690

691
    @functions.setter
11 ✔
692
    def functions(self, functions):
11 ✔
693
        cv.check_type('functions', functions, Iterable, Callable)
11 ✔
694
        self._functions = functions
11 ✔
695

696
    @property
11 ✔
697
    def breakpoints(self):
11 ✔
698
        return self._breakpoints
11 ✔
699

700
    @breakpoints.setter
11 ✔
701
    def breakpoints(self, breakpoints):
11 ✔
702
        cv.check_iterable_type('breakpoints', breakpoints, Real)
11 ✔
703
        self._breakpoints = breakpoints
11 ✔
704

705

706
class ResonancesWithBackground(EqualityMixin):
11 ✔
707
    """Cross section in resolved resonance region.
708

709
    Parameters
710
    ----------
711
    resonances : openmc.data.Resonances
712
        Resolved resonance parameter data
713
    background : Callable
714
        Background cross section as a function of energy
715
    mt : int
716
        MT value of the reaction
717

718
    Attributes
719
    ----------
720
    resonances : openmc.data.Resonances
721
        Resolved resonance parameter data
722
    background : Callable
723
        Background cross section as a function of energy
724
    mt : int
725
        MT value of the reaction
726

727
    """
728

729

730
    def __init__(self, resonances, background, mt):
11 ✔
731
        self.resonances = resonances
11 ✔
732
        self.background = background
11 ✔
733
        self.mt = mt
11 ✔
734

735
    @property
11 ✔
736
    def background(self):
11 ✔
UNCOV
737
        return self._background
×
738

739
    @background.setter
11 ✔
740
    def background(self, background):
11 ✔
741
        cv.check_type('background cross section', background, Callable)
11 ✔
742
        self._background = background
11 ✔
743

744
    @property
11 ✔
745
    def mt(self):
11 ✔
UNCOV
746
        return self._mt
×
747

748
    @mt.setter
11 ✔
749
    def mt(self, mt):
11 ✔
750
        cv.check_type('MT value', mt, Integral)
11 ✔
751
        self._mt = mt
11 ✔
752

753
    @property
11 ✔
754
    def resonances(self):
11 ✔
UNCOV
755
        return self._resonances
×
756

757
    @resonances.setter
11 ✔
758
    def resonances(self, resonances):
11 ✔
759
        cv.check_type('resolved resonance parameters', resonances,
11 ✔
760
                      openmc.data.Resonances)
761
        self._resonances = resonances
11 ✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc