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

openmc-dev / openmc / 23916574395

02 Apr 2026 06:50PM UTC coverage: 81.336% (+0.01%) from 81.324%
23916574395

Pull #3734

github

web-flow
Merge 3420117d8 into d9b30bbbd
Pull Request #3734: Specify temperature from a field (structured mesh only)

17720 of 25601 branches covered (69.22%)

Branch coverage included in aggregate %.

183 of 204 new or added lines in 15 files covered. (89.71%)

69 existing lines in 3 files now uncovered.

58284 of 67843 relevant lines covered (85.91%)

44753955.49 hits per line

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

94.42
/openmc/settings.py
1
from collections.abc import Iterable, Mapping, MutableSequence, Sequence
11✔
2
from enum import Enum
11✔
3
import itertools
11✔
4
from math import ceil
11✔
5
from numbers import Integral, Real
11✔
6
from pathlib import Path
11✔
7
import textwrap
11✔
8
import traceback
11✔
9

10
import lxml.etree as ET
11✔
11
import warnings
11✔
12
import openmc
11✔
13
import openmc.checkvalue as cv
11✔
14
from openmc.checkvalue import PathLike
11✔
15
from openmc.stats.multivariate import MeshSpatial
11✔
16
from ._xml import clean_indentation, get_elem_list, get_text
11✔
17
from .mesh import _read_meshes, RegularMesh, MeshBase
11✔
18
from .source import SourceBase, MeshSource, IndependentSource
11✔
19
from .field import TemperatureField
11✔
20
from .utility_funcs import input_path
11✔
21
from .volume import VolumeCalculation
11✔
22
from .weight_windows import WeightWindows, WeightWindowGenerator, WeightWindowsList
11✔
23

24

25
class RunMode(Enum):
11✔
26
    EIGENVALUE = 'eigenvalue'
11✔
27
    FIXED_SOURCE = 'fixed source'
11✔
28
    PLOT = 'plot'
11✔
29
    VOLUME = 'volume'
11✔
30
    PARTICLE_RESTART = 'particle restart'
11✔
31

32

33
_RES_SCAT_METHODS = {'dbrc', 'rvs'}
11✔
34

35

36
class Settings:
11✔
37
    """Settings used for an OpenMC simulation.
38

39
    Parameters
40
    ----------
41
    **kwargs : dict, optional
42
        Any keyword arguments are used to set attributes on the instance.
43

44
    Attributes
45
    ----------
46
    atomic_relaxation : bool
47
        Whether to simulate the atomic relaxation cascade (fluorescence photons
48
        and Auger electrons) following photoelectric and incoherent scattering
49
        interactions.
50
    batches : int
51
        Number of batches to simulate
52
    confidence_intervals : bool
53
        If True, uncertainties on tally results will be reported as the
54
        half-width of the 95% two-sided confidence interval. If False,
55
        uncertainties on tally results will be reported as the sample standard
56
        deviation.
57
    collision_track : dict
58
        Options for writing collision information. Acceptable keys are:
59

60
        :max_collisions: Maximum number of collisions to be banked per file. (int)
61
        :max_collision_track_files: Maximum number of collision_track files. (int)
62
        :mcpl: Output in the form of an MCPL-file. (bool)
63
        :cell_ids: List of cell IDs to define cells in which collisions should be banked. (list of int)
64
        :universe_ids: List of universe IDs to define universes in which collisions should be banked. (list of int)
65
        :material_ids: List of material IDs to define materials in which collisions should be banked. (list of int)
66
        :nuclides: List of nuclides to define nuclides in which collisions should be banked.
67
                    (ex: ["I135m", "U233"] ). (list of str)
68
        :reactions: List of reaction to define specific reactions that should be banked
69
                    (ex: ["(n,fission)", 2, "(n,2n)"] ). (list of str or int)
70
        :deposited_E_threshold: Number to define the minimum deposited energy during
71
                     per collision to trigger banking. (float)
72
    create_fission_neutrons : bool
73
        Indicate whether fission neutrons should be created or not.
74
    cutoff : dict
75
        Dictionary defining weight cutoff, energy cutoff and time cutoff. The
76
        dictionary may have the following keys, 'weight', 'weight_avg',
77
        'survival_normalization', 'energy_neutron', 'energy_photon',
78
        'energy_electron', 'energy_positron', 'time_neutron', 'time_photon',
79
        'time_electron', and 'time_positron'. Value for 'weight' should be a
80
        float indicating weight cutoff below which particle undergo Russian
81
        roulette. Value for 'weight_avg' should be a float indicating weight
82
        assigned to particles that are not killed after Russian roulette. Value
83
        of energy should be a float indicating energy in eV below which particle
84
        type will be killed. Value of time should be a float in seconds.
85
        Particles will be killed exactly at the specified time. Value for
86
        'survival_normalization' is a bool indicating whether or not the weight
87
        cutoff parameters will be applied relative to the particle's starting
88
        weight or to its current weight.
89
    delayed_photon_scaling : bool
90
        Indicate whether to scale the fission photon yield by (EGP + EGD)/EGP
91
        where EGP is the energy release of prompt photons and EGD is the energy
92
        release of delayed photons.
93

94
        .. versionadded:: 0.12
95
    electron_treatment : {'led', 'ttb'}
96
        Whether to deposit all energy from electrons locally ('led') or create
97
        secondary bremsstrahlung photons ('ttb').
98
    energy_mode : {'continuous-energy', 'multi-group'}
99
        Set whether the calculation should be continuous-energy or multi-group.
100
    entropy_mesh : openmc.RegularMesh
101
        Mesh to be used to calculate Shannon entropy. If the mesh dimensions are
102
        not specified, OpenMC assigns a mesh such that 20 source sites per mesh
103
        cell are to be expected on average.
104
    event_based : bool
105
        Indicate whether to use event-based parallelism instead of the default
106
        history-based parallelism.
107

108
        .. versionadded:: 0.12
109
    free_gas_threshold : float
110
        Energy multiplier (in units of :math:`kT`) below which the free gas
111
        scattering treatment is applied for elastic scattering. If not
112
        specified, a value of 400.0 is used.
113
    generations_per_batch : int
114
        Number of generations per batch
115
    ifp_n_generation : int
116
        Number of generations to consider for the Iterated Fission Probability
117
        method.
118
    max_lost_particles : int
119
        Maximum number of lost particles
120

121
        .. versionadded:: 0.12
122
    rel_max_lost_particles : float
123
        Maximum number of lost particles, relative to the total number of
124
        particles
125

126
        .. versionadded:: 0.12
127
    inactive : int
128
        Number of inactive batches
129
    keff_trigger : dict
130
        Dictionary defining a trigger on eigenvalue. The dictionary must have
131
        two keys, 'type' and 'threshold'. Acceptable values corresponding to
132
        type are 'variance', 'std_dev', and 'rel_err'. The threshold value
133
        should be a float indicating the variance, standard deviation, or
134
        relative error used.
135
    log_grid_bins : int
136
        Number of bins for logarithmic energy grid search
137
    material_cell_offsets : bool
138
        Generate an "offset table" for material cells by default. These tables
139
        are necessary when a particular instance of a cell needs to be tallied.
140

141
        .. versionadded:: 0.12
142
    max_particles_in_flight : int
143
        Number of neutrons to run concurrently when using event-based
144
        parallelism.
145

146
        .. versionadded:: 0.12
147
    max_particle_events : int
148
        Maximum number of allowed particle events per source particle.
149

150
        .. versionadded:: 0.15.0
151
    max_order : None or int
152
        Maximum scattering order to apply globally when in multi-group mode.
153
    max_history_splits : int
154
        Maximum number of times a particle can split during a history
155

156
        .. versionadded:: 0.13
157
    max_secondaries : int
158
        Maximum secondary bank size
159

160
        .. versionadded:: 0.15.3
161
    max_tracks : int
162
        Maximum number of tracks written to a track file (per MPI process).
163

164
        .. versionadded:: 0.13.1
165
    max_write_lost_particles : int
166
        Maximum number of particle restart files (per MPI process) to write for
167
        lost particles.
168

169
        .. versionadded:: 0.14.0
170
    no_reduce : bool
171
        Indicate that all user-defined and global tallies should not be reduced
172
        across processes in a parallel calculation.
173
    output : dict
174
        Dictionary indicating what files to output. Acceptable keys are:
175

176
        :path: String indicating a directory where output files should be
177
               written
178
        :summary: Whether the 'summary.h5' file should be written (bool)
179
        :tallies: Whether the 'tallies.out' file should be written (bool)
180
    particles : int
181
        Number of particles per generation
182
    photon_transport : bool
183
        Whether to use photon transport.
184
    plot_seed : int
185
       Initial seed for randomly generated plot colors.
186
    ptables : bool
187
        Determine whether probability tables are used.
188
    properties_file : PathLike
189
        Location of the properties file to load cell temperatures/densities
190
        and material densities
191
    random_ray : dict
192
        Options for configuring the random ray solver. Acceptable keys are:
193

194
        :distance_inactive:
195
            Indicates the total inactive distance in [cm] a ray should travel
196
        :distance_active:
197
            Indicates the total active distance in [cm] a ray should travel
198
        :ray_source:
199
            Starting ray distribution (must be uniform in space and angle) as
200
            specified by a :class:`openmc.SourceBase` object.
201
        :volume_estimator:
202
            Choice of volume estimator for the random ray solver. Options are
203
            'naive', 'simulation_averaged', or 'hybrid'.
204
            The default is 'hybrid'.
205
        :source_shape:
206
            Assumed shape of the source distribution within each source region.
207
            Options are 'flat' (default), 'linear', or 'linear_xy'.
208
        :volume_normalized_flux_tallies:
209
            Whether to normalize flux tallies by volume (bool). The default is
210
            'False'. When enabled, flux tallies will be reported in units of
211
            cm/cm^3. When disabled, flux tallies will be reported in units of cm
212
            (i.e., total distance traveled by neutrons in the spatial tally
213
            region).
214
        :adjoint:
215
            Whether to run the random ray solver in adjoint mode (bool). The
216
            default is 'False'.
217
        :sample_method:
218
            Sampling method for the ray starting location and direction of
219
            travel. Options are `prng` (default), `halton`, or `s2`. `s2`
220
            modifies the `prng` sampling method such that rays are sampled
221
            with directions (-1, 0, 0) or (1, 0, 0). This is used for verification
222
            against analytic transport benchmarks which are often derivied with
223
            a reduced angular domain.
224
        :source_region_meshes:
225
            List of tuples where each tuple contains a mesh and a list of
226
            domains. Each domain is an instance of openmc.Material, openmc.Cell,
227
            or openmc.Universe. The mesh will be applied to the listed domains
228
            to subdivide source regions so as to improve accuracy and/or conform
229
            with tally meshes.
230
        :diagonal_stabilization_rho:
231
            The rho factor for use with diagonal stabilization. This technique is
232
            applied when negative diagonal (in-group) elements are detected in
233
            the scattering matrix of input MGXS data, which is a common feature
234
            of transport corrected MGXS data. The default is 1.0, which ensures
235
            no negative diagonal elements are present in the iteration matrix and
236
            thus stabilizes the simulation. A value of 0.0 will disable diagonal
237
            stabilization. Values between 0.0 and 1.0 will apply a degree of
238
            stabilization, which may be desirable as stronger diagonal stabilization
239
            also tends to dampen the convergence rate of the solver, thus requiring
240
            more iterations to converge.
241

242
        .. versionadded:: 0.15.0
243
    resonance_scattering : dict
244
        Settings for resonance elastic scattering. Accepted keys are 'enable'
245
        (bool), 'method' (str), 'energy_min' (float), 'energy_max' (float), and
246
        'nuclides' (list). The 'method' can be set to 'dbrc' (Doppler broadening
247
        rejection correction) or 'rvs' (relative velocity sampling). If not
248
        specified, 'rvs' is the default method. The 'energy_min' and
249
        'energy_max' values indicate the minimum and maximum energies above and
250
        below which the resonance elastic scattering method is to be applied.
251
        The 'nuclides' list indicates what nuclides the method should be applied
252
        to. In its absence, the method will be applied to all nuclides with 0 K
253
        elastic scattering data present.
254
    run_mode : {'eigenvalue', 'fixed source', 'plot', 'volume', 'particle restart'}
255
        The type of calculation to perform (default is 'eigenvalue')
256
    seed : int
257
        Seed for the linear congruential pseudorandom number generator
258
    stride : int
259
        Number of random numbers allocated for each source particle history
260
    source : Iterable of openmc.SourceBase
261
        Distribution of source sites in space, angle, and energy
262
    source_rejection_fraction : float
263
        Minimum fraction of source sites that must be accepted when applying
264
        rejection sampling based on constraints. If not specified, the default
265
        value is 0.05.
266
    sourcepoint : dict
267
        Options for writing source points. Acceptable keys are:
268

269
        :batches: list of batches at which to write source
270
        :overwrite: bool indicating whether to overwrite
271
        :separate: bool indicating whether the source should be written as a
272
                   separate file
273
        :write: bool indicating whether or not to write the source
274
        :mcpl: bool indicating whether to write the source as an MCPL file
275
    statepoint : dict
276
        Options for writing state points. Acceptable keys are:
277

278
        :batches: list of batches at which to write statepoint files
279
    surf_source_read : dict
280
        Options for reading surface source points. Acceptable keys are:
281

282
        :path: Path to surface source file (str).
283
    surf_source_write : dict
284
        Options for writing surface source points. Acceptable keys are:
285

286
        :surface_ids: List of surface ids at which crossing particles are to be
287
                   banked (int)
288
        :max_particles: Maximum number of particles to be banked on surfaces per
289
                   process (int)
290
        :max_source_files: Maximum number of surface source files to be created (int)
291
        :mcpl: Output in the form of an MCPL-file (bool)
292
        :cell: Cell ID used to determine if particles crossing identified
293
               surfaces are to be banked. Particles coming from or going to this
294
               declared cell will be banked (int)
295
        :cellfrom: Cell ID used to determine if particles crossing identified
296
                   surfaces are to be banked. Particles coming from this
297
                   declared cell will be banked (int)
298
        :cellto: Cell ID used to determine if particles crossing identified
299
                 surfaces are to be banked. Particles going to this declared
300
                 cell will be banked (int)
301
    surface_grazing_cutoff : float
302
        Surface flux cosine cutoff. If not specified, the default value is
303
        0.001. For more information, see the surface tally section in the theory
304
        manual.
305
    surface_grazing_ratio : float
306
        Surface flux cosine substitution ratio. If not specified, the default
307
        value is 0.5. For more information, see the surface tally section in the
308
        theory manual.
309
    survival_biasing : bool
310
        Indicate whether survival biasing is to be used
311
    tabular_legendre : dict
312
        Determines if a multi-group scattering moment kernel expanded via
313
        Legendre polynomials is to be converted to a tabular distribution or
314
        not. Accepted keys are 'enable' and 'num_points'. The value for 'enable'
315
        is a bool stating whether the conversion to tabular is performed; the
316
        value for 'num_points' sets the number of points to use in the tabular
317
        distribution, should 'enable' be True.
318
    temperature : dict
319
        Defines a default temperature and method for treating intermediate
320
        temperatures at which nuclear data doesn't exist. Accepted keys are
321
        'default', 'method', 'range', 'tolerance', and 'multipole'. The value
322
        for 'default' should be a float representing the default temperature in
323
        Kelvin. The value for 'method' should be 'nearest' or 'interpolation'.
324
        If the method is 'nearest', 'tolerance' indicates a range of temperature
325
        within which cross sections may be used. If the method is
326
        'interpolation', 'tolerance' indicates the range of temperatures outside
327
        of the available cross section temperatures where cross sections will
328
        evaluate to the nearer bound. The value for 'range' should be a pair of
329
        minimum and maximum temperatures which are used to indicate that cross
330
        sections be loaded at all temperatures within the range. 'multipole' is
331
        a boolean indicating whether or not the windowed multipole method should
332
        be used to evaluate resolved resonance cross sections.
333
    temperature_field : openmc.TemperatureField
334
        Temperature field based on a geometric mesh used to specify temperatures
335
        in a model. Temperatures declared from a mesh take precedence over
336
        all other temperature definition (cell, material, and global).
337
    trace : tuple or list
338
        Show detailed information about a single particle, indicated by three
339
        integers: the batch number, generation number, and particle number
340
    track : tuple or list
341
        Specify particles for which track files should be written. Each particle
342
        is identified by a tuple with the batch number, generation number, and
343
        particle number.
344
    trigger_active : bool
345
        Indicate whether tally triggers are used
346
    trigger_batch_interval : int
347
        Number of batches in between convergence checks
348
    trigger_max_batches : int
349
        Maximum number of batches simulated. If this is set, the number of
350
        batches specified via ``batches`` is interpreted as the minimum number
351
        of batches
352
    uniform_source_sampling : bool
353
        Whether to sampling among multiple sources uniformly, applying their
354
        strengths as weights to sampled particles.
355
    ufs_mesh : openmc.RegularMesh
356
        Mesh to be used for redistributing source sites via the uniform fission
357
        site (UFS) method.
358
    use_decay_photons : bool
359
        Produce decay photons from neutron reactions instead of prompt
360
    verbosity : int
361
        Verbosity during simulation between 1 and 10. Verbosity levels are
362
        described in :ref:`verbosity`.
363
    volume_calculations : VolumeCalculation or iterable of VolumeCalculation
364
        Stochastic volume calculation specifications
365
    weight_windows : WeightWindowsList
366
        Weight windows to use for variance reduction
367

368
        .. versionadded:: 0.13
369
    weight_window_checkpoints : dict
370
        Indicates the checkpoints for weight window split/roulettes. Valid keys
371
        include "collision" and "surface". Values must be of type bool.
372

373
        .. versionadded:: 0.14.0
374
    weight_window_generators : WeightWindowGenerator or iterable of WeightWindowGenerator
375
        Weight windows generation parameters to apply during simulation
376

377
        .. versionadded:: 0.14.0
378

379
    create_delayed_neutrons : bool
380
        Whether delayed neutrons are created in fission.
381

382
        .. versionadded:: 0.13.3
383
    weight_windows_on : bool
384
        Whether weight windows are enabled
385

386
        .. versionadded:: 0.13
387

388
    weight_windows_file: Pathlike
389
        Path to a weight window file to load during simulation initialization
390

391
        .. versionadded::0.14.0
392
    write_initial_source : bool
393
        Indicate whether to write the initial source distribution to file
394
    """
395

396
    def __init__(self, **kwargs):
11✔
397
        self._run_mode = RunMode.EIGENVALUE
11✔
398
        self._batches = None
11✔
399
        self._generations_per_batch = None
11✔
400
        self._inactive = None
11✔
401
        self._max_lost_particles = None
11✔
402
        self._rel_max_lost_particles = None
11✔
403
        self._max_write_lost_particles = None
11✔
404
        self._particles = None
11✔
405
        self._keff_trigger = None
11✔
406

407
        # Energy mode subelement
408
        self._energy_mode = None
11✔
409
        self._max_order = None
11✔
410

411
        # Source subelement
412
        self._source = cv.CheckedList(SourceBase, 'source distributions')
11✔
413
        self._source_rejection_fraction = None
11✔
414

415
        self._confidence_intervals = None
11✔
416
        self._electron_treatment = None
11✔
417
        self._photon_transport = None
11✔
418
        self._atomic_relaxation = None
11✔
419
        self._plot_seed = None
11✔
420
        self._ptables = None
11✔
421
        self._properties_file = None
11✔
422
        self._uniform_source_sampling = None
11✔
423
        self._seed = None
11✔
424
        self._stride = None
11✔
425
        self._surface_grazing_cutoff = None
11✔
426
        self._surface_grazing_ratio = None
11✔
427
        self._survival_biasing = None
11✔
428
        self._free_gas_threshold = None
11✔
429

430
        # Shannon entropy mesh
431
        self._entropy_mesh = None
11✔
432

433
        # Temperature field
434
        self._temperature_field = None
11✔
435

436
        # Trigger subelement
437
        self._trigger_active = None
11✔
438
        self._trigger_max_batches = None
11✔
439
        self._trigger_batch_interval = None
11✔
440

441
        self._output = None
11✔
442

443
        # Iterated Fission Probability
444
        self._ifp_n_generation = None
11✔
445

446
        # Collision track feature
447
        self._collision_track = {}
11✔
448

449
        # Output options
450
        self._statepoint = {}
11✔
451
        self._sourcepoint = {}
11✔
452

453
        self._surf_source_read = {}
11✔
454
        self._surf_source_write = {}
11✔
455

456
        self._no_reduce = None
11✔
457

458
        self._verbosity = None
11✔
459

460
        self._trace = None
11✔
461
        self._track = None
11✔
462

463
        self._tabular_legendre = {}
11✔
464

465
        self._temperature = {}
11✔
466

467
        # Cutoff subelement
468
        self._cutoff = None
11✔
469

470
        # Uniform fission source subelement
471
        self._ufs_mesh = None
11✔
472

473
        self._resonance_scattering = {}
11✔
474
        self._volume_calculations = cv.CheckedList(
11✔
475
            VolumeCalculation, 'volume calculations')
476

477
        self._create_fission_neutrons = None
11✔
478
        self._create_delayed_neutrons = None
11✔
479
        self._delayed_photon_scaling = None
11✔
480
        self._material_cell_offsets = None
11✔
481
        self._log_grid_bins = None
11✔
482

483
        self._event_based = None
11✔
484
        self._max_particles_in_flight = None
11✔
485
        self._max_particle_events = None
11✔
486
        self._write_initial_source = None
11✔
487
        self._weight_windows = WeightWindowsList()
11✔
488
        self._weight_window_generators = cv.CheckedList(
11✔
489
            WeightWindowGenerator, 'weight window generators')
490
        self._weight_windows_on = None
11✔
491
        self._weight_windows_file = None
11✔
492
        self._weight_window_checkpoints = {}
11✔
493
        self._max_history_splits = None
11✔
494
        self._max_tracks = None
11✔
495
        self._max_secondaries = None
11✔
496
        self._use_decay_photons = None
11✔
497

498
        self._random_ray = {}
11✔
499

500
        for key, value in kwargs.items():
11✔
501
            setattr(self, key, value)
11✔
502

503
    def __setattr__(self, name: str, value):
11✔
504
        if not name.startswith('_'):
11✔
505
            try:
11✔
506
                getattr(self, name)
11✔
507
            except AttributeError as e:
×
508
                msg, = traceback.format_exception_only(e)
×
509
                msg = msg.strip().split(maxsplit=1)[-1]
×
510
                warnings.warn(msg, stacklevel=2)
×
511
        super().__setattr__(name, value)
11✔
512

513
    @property
11✔
514
    def run_mode(self) -> str:
11✔
515
        return self._run_mode.value
11✔
516

517
    @run_mode.setter
11✔
518
    def run_mode(self, run_mode: str):
11✔
519
        cv.check_value('run mode', run_mode, {x.value for x in RunMode})
11✔
520
        for mode in RunMode:
11✔
521
            if mode.value == run_mode:
11✔
522
                self._run_mode = mode
11✔
523

524
    @property
11✔
525
    def batches(self) -> int:
11✔
526
        return self._batches
11✔
527

528
    @batches.setter
11✔
529
    def batches(self, batches: int):
11✔
530
        cv.check_type('batches', batches, Integral)
11✔
531
        cv.check_greater_than('batches', batches, 0)
11✔
532
        self._batches = batches
11✔
533

534
    @property
11✔
535
    def generations_per_batch(self) -> int:
11✔
536
        return self._generations_per_batch
11✔
537

538
    @generations_per_batch.setter
11✔
539
    def generations_per_batch(self, generations_per_batch: int):
11✔
540
        cv.check_type('generations per batch', generations_per_batch, Integral)
11✔
541
        cv.check_greater_than('generations per batch',
11✔
542
                              generations_per_batch, 0)
543
        self._generations_per_batch = generations_per_batch
11✔
544

545
    @property
11✔
546
    def inactive(self) -> int:
11✔
547
        return self._inactive
11✔
548

549
    @inactive.setter
11✔
550
    def inactive(self, inactive: int):
11✔
551
        cv.check_type('inactive batches', inactive, Integral)
11✔
552
        cv.check_greater_than('inactive batches', inactive, 0, True)
11✔
553
        self._inactive = inactive
11✔
554

555
    @property
11✔
556
    def max_lost_particles(self) -> int:
11✔
557
        return self._max_lost_particles
11✔
558

559
    @max_lost_particles.setter
11✔
560
    def max_lost_particles(self, max_lost_particles: int):
11✔
561
        cv.check_type('max_lost_particles', max_lost_particles, Integral)
11✔
562
        cv.check_greater_than('max_lost_particles', max_lost_particles, 0)
11✔
563
        self._max_lost_particles = max_lost_particles
11✔
564

565
    @property
11✔
566
    def rel_max_lost_particles(self) -> float:
11✔
567
        return self._rel_max_lost_particles
11✔
568

569
    @rel_max_lost_particles.setter
11✔
570
    def rel_max_lost_particles(self, rel_max_lost_particles: float):
11✔
571
        cv.check_type('rel_max_lost_particles', rel_max_lost_particles, Real)
11✔
572
        cv.check_greater_than('rel_max_lost_particles',
11✔
573
                              rel_max_lost_particles, 0)
574
        cv.check_less_than('rel_max_lost_particles', rel_max_lost_particles, 1)
11✔
575
        self._rel_max_lost_particles = rel_max_lost_particles
11✔
576

577
    @property
11✔
578
    def max_write_lost_particles(self) -> int:
11✔
579
        return self._max_write_lost_particles
11✔
580

581
    @max_write_lost_particles.setter
11✔
582
    def max_write_lost_particles(self, max_write_lost_particles: int):
11✔
583
        cv.check_type('max_write_lost_particles',
11✔
584
                      max_write_lost_particles, Integral)
585
        cv.check_greater_than('max_write_lost_particles',
11✔
586
                              max_write_lost_particles, 0)
587
        self._max_write_lost_particles = max_write_lost_particles
11✔
588

589
    @property
11✔
590
    def particles(self) -> int:
11✔
591
        return self._particles
11✔
592

593
    @particles.setter
11✔
594
    def particles(self, particles: int):
11✔
595
        cv.check_type('particles', particles, Integral)
11✔
596
        cv.check_greater_than('particles', particles, 0)
11✔
597
        self._particles = particles
11✔
598

599
    @property
11✔
600
    def keff_trigger(self) -> dict:
11✔
601
        return self._keff_trigger
11✔
602

603
    @keff_trigger.setter
11✔
604
    def keff_trigger(self, keff_trigger: dict):
11✔
605
        if not isinstance(keff_trigger, dict):
11✔
606
            msg = f'Unable to set a trigger on keff from "{keff_trigger}" ' \
×
607
                'which is not a Python dictionary'
608
            raise ValueError(msg)
×
609

610
        elif 'type' not in keff_trigger:
11✔
611
            msg = f'Unable to set a trigger on keff from "{keff_trigger}" ' \
×
612
                'which does not have a "type" key'
613
            raise ValueError(msg)
×
614

615
        elif keff_trigger['type'] not in ['variance', 'std_dev', 'rel_err']:
11✔
616
            msg = 'Unable to set a trigger on keff with ' \
×
617
                  'type "{0}"'.format(keff_trigger['type'])
618
            raise ValueError(msg)
×
619

620
        elif 'threshold' not in keff_trigger:
11✔
621
            msg = f'Unable to set a trigger on keff from "{keff_trigger}" ' \
×
622
                'which does not have a "threshold" key'
623
            raise ValueError(msg)
×
624

625
        elif not isinstance(keff_trigger['threshold'], Real):
11✔
626
            msg = 'Unable to set a trigger on keff with ' \
×
627
                  'threshold "{0}"'.format(keff_trigger['threshold'])
628
            raise ValueError(msg)
×
629

630
        self._keff_trigger = keff_trigger
11✔
631

632
    @property
11✔
633
    def energy_mode(self) -> str:
11✔
634
        return self._energy_mode
11✔
635

636
    @energy_mode.setter
11✔
637
    def energy_mode(self, energy_mode: str):
11✔
638
        cv.check_value('energy mode', energy_mode,
11✔
639
                       ['continuous-energy', 'multi-group'])
640
        self._energy_mode = energy_mode
11✔
641

642
    @property
11✔
643
    def max_order(self) -> int:
11✔
644
        return self._max_order
11✔
645

646
    @max_order.setter
11✔
647
    def max_order(self, max_order: int | None):
11✔
648
        if max_order is not None:
11✔
649
            cv.check_type('maximum scattering order', max_order, Integral)
11✔
650
            cv.check_greater_than('maximum scattering order', max_order, 0,
11✔
651
                                  True)
652
        self._max_order = max_order
11✔
653

654
    @property
11✔
655
    def source(self) -> list[SourceBase]:
11✔
656
        return self._source
11✔
657

658
    @source.setter
11✔
659
    def source(self, source: SourceBase | Iterable[SourceBase]):
11✔
660
        if not isinstance(source, MutableSequence):
11✔
661
            source = [source]
11✔
662
        self._source = cv.CheckedList(
11✔
663
            SourceBase, 'source distributions', source)
664

665
    @property
11✔
666
    def confidence_intervals(self) -> bool:
11✔
667
        return self._confidence_intervals
11✔
668

669
    @confidence_intervals.setter
11✔
670
    def confidence_intervals(self, confidence_intervals: bool):
11✔
671
        cv.check_type('confidence interval', confidence_intervals, bool)
11✔
672
        self._confidence_intervals = confidence_intervals
11✔
673

674
    @property
11✔
675
    def electron_treatment(self) -> str:
11✔
676
        return self._electron_treatment
11✔
677

678
    @electron_treatment.setter
11✔
679
    def electron_treatment(self, electron_treatment: str):
11✔
680
        cv.check_value('electron treatment',
11✔
681
                       electron_treatment, ['led', 'ttb'])
682
        self._electron_treatment = electron_treatment
11✔
683

684
    @property
11✔
685
    def atomic_relaxation(self) -> bool:
11✔
686
        return self._atomic_relaxation
11✔
687

688
    @atomic_relaxation.setter
11✔
689
    def atomic_relaxation(self, atomic_relaxation: bool):
11✔
690
        cv.check_type('atomic relaxation', atomic_relaxation, bool)
11✔
691
        self._atomic_relaxation = atomic_relaxation
11✔
692

693
    @property
11✔
694
    def ptables(self) -> bool:
11✔
695
        return self._ptables
11✔
696

697
    @ptables.setter
11✔
698
    def ptables(self, ptables: bool):
11✔
699
        cv.check_type('probability tables', ptables, bool)
11✔
700
        self._ptables = ptables
11✔
701

702
    @property
11✔
703
    def photon_transport(self) -> bool:
11✔
704
        return self._photon_transport
11✔
705

706
    @photon_transport.setter
11✔
707
    def photon_transport(self, photon_transport: bool):
11✔
708
        cv.check_type('photon transport', photon_transport, bool)
11✔
709
        self._photon_transport = photon_transport
11✔
710

711
    @property
11✔
712
    def uniform_source_sampling(self) -> bool:
11✔
713
        return self._uniform_source_sampling
11✔
714

715
    @uniform_source_sampling.setter
11✔
716
    def uniform_source_sampling(self, uniform_source_sampling: bool):
11✔
717
        cv.check_type('strength as weights', uniform_source_sampling, bool)
11✔
718
        self._uniform_source_sampling = uniform_source_sampling
11✔
719

720
    @property
11✔
721
    def plot_seed(self):
11✔
722
        return self._plot_seed
11✔
723

724
    @plot_seed.setter
11✔
725
    def plot_seed(self, seed):
11✔
726
        cv.check_type('random plot color seed', seed, Integral)
11✔
727
        cv.check_greater_than('random plot color seed', seed, 0)
11✔
728
        self._plot_seed = seed
11✔
729

730
    @property
11✔
731
    def seed(self) -> int:
11✔
732
        return self._seed
11✔
733

734
    @seed.setter
11✔
735
    def seed(self, seed: int):
11✔
736
        cv.check_type('random number generator seed', seed, Integral)
11✔
737
        cv.check_greater_than('random number generator seed', seed, 0)
11✔
738
        self._seed = seed
11✔
739

740
    @property
11✔
741
    def stride(self) -> int:
11✔
742
        return self._stride
11✔
743

744
    @stride.setter
11✔
745
    def stride(self, stride: int):
11✔
746
        cv.check_type('random number generator stride', stride, Integral)
11✔
747
        cv.check_greater_than('random number generator stride', stride, 0)
11✔
748
        self._stride = stride
11✔
749

750
    @property
11✔
751
    def surface_grazing_cutoff(self) -> float:
11✔
752
        return self._surface_grazing_cutoff
11✔
753

754
    @surface_grazing_cutoff.setter
11✔
755
    def surface_grazing_cutoff(self, surface_grazing_cutoff: float):
11✔
756
        cv.check_type('surface grazing cutoff', surface_grazing_cutoff, float)
11✔
757
        cv.check_greater_than('surface grazing cutoff', surface_grazing_cutoff, 0.0)
11✔
758
        cv.check_less_than('surface grazing cutoff', surface_grazing_cutoff, 1.0)
11✔
759
        self._surface_grazing_cutoff = surface_grazing_cutoff
11✔
760

761
    @property
11✔
762
    def surface_grazing_ratio(self) -> float:
11✔
763
        return self._surface_grazing_ratio
11✔
764

765
    @surface_grazing_ratio.setter
11✔
766
    def surface_grazing_ratio(self, surface_grazing_ratio: float):
11✔
767
        cv.check_type('surface grazing ratio', surface_grazing_ratio, float)
11✔
768
        cv.check_greater_than('surface grazing ratio', surface_grazing_ratio, 0.0)
11✔
769
        self._surface_grazing_ratio = surface_grazing_ratio
11✔
770

771
    @property
11✔
772
    def survival_biasing(self) -> bool:
11✔
773
        return self._survival_biasing
11✔
774

775
    @survival_biasing.setter
11✔
776
    def survival_biasing(self, survival_biasing: bool):
11✔
777
        cv.check_type('survival biasing', survival_biasing, bool)
11✔
778
        self._survival_biasing = survival_biasing
11✔
779

780
    @property
11✔
781
    def entropy_mesh(self) -> RegularMesh:
11✔
782
        return self._entropy_mesh
11✔
783

784
    @entropy_mesh.setter
11✔
785
    def entropy_mesh(self, entropy: RegularMesh):
11✔
786
        cv.check_type('entropy mesh', entropy, RegularMesh)
11✔
787
        self._entropy_mesh = entropy
11✔
788
    
789
    @property
11✔
790
    def temperature_field(self) -> TemperatureField:
11✔
791
        return self._temperature_field
11✔
792

793
    @temperature_field.setter
11✔
794
    def temperature_field(self, temperature_field: TemperatureField):
11✔
795
        cv.check_type('temperature field', temperature_field, TemperatureField)
11✔
796
        self._temperature_field = temperature_field
11✔
797

798
    @property
11✔
799
    def trigger_active(self) -> bool:
11✔
800
        return self._trigger_active
11✔
801

802
    @trigger_active.setter
11✔
803
    def trigger_active(self, trigger_active: bool):
11✔
804
        cv.check_type('trigger active', trigger_active, bool)
11✔
805
        self._trigger_active = trigger_active
11✔
806

807
    @property
11✔
808
    def trigger_max_batches(self) -> int:
11✔
809
        return self._trigger_max_batches
11✔
810

811
    @trigger_max_batches.setter
11✔
812
    def trigger_max_batches(self, trigger_max_batches: int):
11✔
813
        cv.check_type('trigger maximum batches', trigger_max_batches, Integral)
11✔
814
        cv.check_greater_than('trigger maximum batches',
11✔
815
                              trigger_max_batches, 0)
816
        self._trigger_max_batches = trigger_max_batches
11✔
817

818
    @property
11✔
819
    def trigger_batch_interval(self) -> int:
11✔
820
        return self._trigger_batch_interval
11✔
821

822
    @trigger_batch_interval.setter
11✔
823
    def trigger_batch_interval(self, trigger_batch_interval: int):
11✔
824
        cv.check_type('trigger batch interval',
11✔
825
                      trigger_batch_interval, Integral)
826
        cv.check_greater_than('trigger batch interval',
11✔
827
                              trigger_batch_interval, 0)
828
        self._trigger_batch_interval = trigger_batch_interval
11✔
829

830
    @property
11✔
831
    def output(self) -> dict:
11✔
832
        return self._output
11✔
833

834
    @output.setter
11✔
835
    def output(self, output: dict):
11✔
836
        cv.check_type('output', output, Mapping)
11✔
837
        for key, value in output.items():
11✔
838
            cv.check_value('output key', key, ('summary', 'tallies', 'path'))
11✔
839
            if key in ('summary', 'tallies'):
11✔
840
                cv.check_type(f"output['{key}']", value, bool)
11✔
841
            else:
842
                cv.check_type("output['path']", value, str)
11✔
843
        self._output = output
11✔
844

845
    @property
11✔
846
    def sourcepoint(self) -> dict:
11✔
847
        return self._sourcepoint
11✔
848

849
    @sourcepoint.setter
11✔
850
    def sourcepoint(self, sourcepoint: dict):
11✔
851
        cv.check_type('sourcepoint options', sourcepoint, Mapping)
11✔
852
        for key, value in sourcepoint.items():
11✔
853
            if key == 'batches':
11✔
854
                cv.check_type('sourcepoint batches', value, Iterable, Integral)
11✔
855
                for batch in value:
11✔
856
                    cv.check_greater_than('sourcepoint batch', batch, 0)
11✔
857
            elif key == 'separate':
11✔
858
                cv.check_type('sourcepoint separate', value, bool)
11✔
859
            elif key == 'write':
11✔
860
                cv.check_type('sourcepoint write', value, bool)
11✔
861
            elif key == 'overwrite':
11✔
862
                cv.check_type('sourcepoint overwrite', value, bool)
11✔
863
            elif key == 'mcpl':
11✔
864
                cv.check_type('sourcepoint mcpl', value, bool)
11✔
865
            else:
866
                raise ValueError(f"Unknown key '{key}' encountered when "
×
867
                                 "setting sourcepoint options.")
868
        self._sourcepoint = sourcepoint
11✔
869

870
    @property
11✔
871
    def statepoint(self) -> dict:
11✔
872
        return self._statepoint
11✔
873

874
    @statepoint.setter
11✔
875
    def statepoint(self, statepoint: dict):
11✔
876
        cv.check_type('statepoint options', statepoint, Mapping)
11✔
877
        for key, value in statepoint.items():
11✔
878
            if key == 'batches':
11✔
879
                cv.check_type('statepoint batches', value, Iterable, Integral)
11✔
880
                for batch in value:
11✔
881
                    cv.check_greater_than('statepoint batch', batch, 0)
11✔
882
            else:
883
                raise ValueError(f"Unknown key '{key}' encountered when "
×
884
                                 "setting statepoint options.")
885
        self._statepoint = statepoint
11✔
886

887
    @property
11✔
888
    def surf_source_read(self) -> dict:
11✔
889
        return self._surf_source_read
11✔
890

891
    @surf_source_read.setter
11✔
892
    def surf_source_read(self, ssr: dict):
11✔
893
        cv.check_type('surface source reading options', ssr, Mapping)
11✔
894
        for key, value in ssr.items():
11✔
895
            cv.check_value('surface source reading key', key,
11✔
896
                           ('path'))
897
            if key == 'path':
11✔
898
                cv.check_type('path to surface source file', value, PathLike)
11✔
899
        self._surf_source_read = dict(ssr)
11✔
900

901
        # Resolve path to surface source file
902
        if 'path' in ssr:
11✔
903
            self._surf_source_read['path'] = input_path(ssr['path'])
11✔
904

905
    @property
11✔
906
    def surf_source_write(self) -> dict:
11✔
907
        return self._surf_source_write
11✔
908

909
    @surf_source_write.setter
11✔
910
    def surf_source_write(self, surf_source_write: dict):
11✔
911
        cv.check_type("surface source writing options",
11✔
912
                      surf_source_write, Mapping)
913
        for key, value in surf_source_write.items():
11✔
914
            cv.check_value(
11✔
915
                "surface source writing key",
916
                key,
917
                ("surface_ids", "max_particles", "max_source_files",
918
                 "mcpl", "cell", "cellfrom", "cellto"),
919
            )
920
            if key == "surface_ids":
11✔
921
                cv.check_type(
11✔
922
                    "surface ids for source banking", value, Iterable, Integral
923
                )
924
                for surf_id in value:
11✔
925
                    cv.check_greater_than(
11✔
926
                        "surface id for source banking", surf_id, 0)
927

928
            elif key == "mcpl":
11✔
929
                cv.check_type("write to an MCPL-format file", value, bool)
11✔
930
            elif key in ("max_particles", "max_source_files", "cell", "cellfrom", "cellto"):
11✔
931
                name = {
11✔
932
                    "max_particles": "maximum particle banks on surfaces per process",
933
                    "max_source_files": "maximun surface source files to be written",
934
                    "cell": "Cell ID for source banking (from or to)",
935
                    "cellfrom": "Cell ID for source banking (from only)",
936
                    "cellto": "Cell ID for source banking (to only)",
937
                }[key]
938
                cv.check_type(name, value, Integral)
11✔
939
                cv.check_greater_than(name, value, 0)
11✔
940

941
        self._surf_source_write = surf_source_write
11✔
942

943
    @property
11✔
944
    def collision_track(self) -> dict:
11✔
945
        return self._collision_track
11✔
946

947
    @collision_track.setter
11✔
948
    def collision_track(self, collision_track: dict):
11✔
949
        cv.check_type('Collision tracking options', collision_track, Mapping)
11✔
950
        for key, value in collision_track.items():
11✔
951
            cv.check_value('collision_track key', key,
11✔
952
                           ('cell_ids', 'reactions', 'universe_ids', 'material_ids', 'nuclides',
953
                            'deposited_E_threshold', 'max_collisions', 'max_collision_track_files', 'mcpl'))
954
            if key == 'cell_ids':
11✔
955
                cv.check_type('cell ids for collision tracking data banking', value,
11✔
956
                              Iterable, Integral)
957
                for cell_id in value:
11✔
958
                    cv.check_greater_than('cell id for collision tracking data banking',
11✔
959
                                          cell_id, 0)
960
            elif key == 'reactions':
11✔
961
                cv.check_type('MT numbers for collision tracking data banking', value,
11✔
962
                              Iterable)
963
                for reaction in value:
11✔
964
                    if isinstance(reaction, int):
11✔
965
                        cv.check_greater_than(
11✔
966
                            'MT number for collision tracking data banking', reaction, 0
967
                        )
968
                    elif isinstance(reaction, str):
11✔
969
                        # check against allowed strings? so far let C++ code handle it
970
                        pass
11✔
971
                    else:
972
                        raise TypeError(
×
973
                            f"MT number for collision tracking data banking must be a positive int or string, "
974
                            f"got {type(reaction).__name__}")
975
            elif key == 'universe_ids':
11✔
976
                cv.check_type('universe ids for collision tracking data banking', value,
11✔
977
                              Iterable, Integral)
978
                for universe_id in value:
11✔
979
                    cv.check_greater_than('universe id for collision tracking data banking',
11✔
980
                                          universe_id, 0)
981
            elif key == 'material_ids':
11✔
982
                cv.check_type('material ids for collision tracking data banking', value,
11✔
983
                              Iterable, Integral)
984
                for material_id in value:
11✔
985
                    cv.check_greater_than('material id for collision tracking data banking',
11✔
986
                                          material_id, 0)
987
            elif key == 'nuclides':
11✔
988
                cv.check_type('nuclides for collision tracking data banking', value,
11✔
989
                              Iterable, str)
990
                for nuclide in value:
11✔
991
                    # If nuclide name doesn't look valid, give a warning
992
                    try:
11✔
993
                        openmc.data.zam(nuclide)
11✔
994
                    except ValueError:
×
995
                        warnings.warn(f"Nuclide {nuclide} is not valid")
×
996
            elif key == 'deposited_E_threshold':
11✔
997
                cv.check_type('Deposited Energy Threshold for collision tracking data banking',
11✔
998
                              value, Real)
999
                cv.check_greater_than('Deposited Energy Threshold for collision tracking data banking',
11✔
1000
                                      value, 0)
1001
            elif key == 'max_collisions':
11✔
1002
                cv.check_type('maximum collisions banks per file',
11✔
1003
                              value, Integral)
1004
                cv.check_greater_than('maximum collisions banks in collision tracking',
11✔
1005
                                      value, 0)
1006
            elif key == 'max_collision_track_files':
11✔
1007
                cv.check_type('maximum collisions banks',
×
1008
                              value, Integral)
1009
                cv.check_greater_than('maximum number of collision_track files ',
×
1010
                                      value, 0)
1011
            elif key == 'mcpl':
11✔
1012
                cv.check_type('write to an MCPL-format file', value, bool)
11✔
1013

1014
        self._collision_track = collision_track
11✔
1015

1016
    @property
11✔
1017
    def no_reduce(self) -> bool:
11✔
1018
        return self._no_reduce
11✔
1019

1020
    @no_reduce.setter
11✔
1021
    def no_reduce(self, no_reduce: bool):
11✔
1022
        cv.check_type('no reduction option', no_reduce, bool)
11✔
1023
        self._no_reduce = no_reduce
11✔
1024

1025
    @property
11✔
1026
    def verbosity(self) -> int:
11✔
1027
        return self._verbosity
11✔
1028

1029
    @verbosity.setter
11✔
1030
    def verbosity(self, verbosity: int):
11✔
1031
        cv.check_type('verbosity', verbosity, Integral)
11✔
1032
        cv.check_greater_than('verbosity', verbosity, 1, True)
11✔
1033
        cv.check_less_than('verbosity', verbosity, 10, True)
11✔
1034
        self._verbosity = verbosity
11✔
1035

1036
    @property
11✔
1037
    def ifp_n_generation(self) -> int:
11✔
1038
        return self._ifp_n_generation
11✔
1039

1040
    @ifp_n_generation.setter
11✔
1041
    def ifp_n_generation(self, ifp_n_generation: int):
11✔
1042
        if ifp_n_generation is not None:
11✔
1043
            cv.check_type("number of generations", ifp_n_generation, Integral)
11✔
1044
            cv.check_greater_than("number of generations", ifp_n_generation, 0)
11✔
1045
        self._ifp_n_generation = ifp_n_generation
11✔
1046

1047
    @property
11✔
1048
    def tabular_legendre(self) -> dict:
11✔
1049
        return self._tabular_legendre
11✔
1050

1051
    @tabular_legendre.setter
11✔
1052
    def tabular_legendre(self, tabular_legendre: dict):
11✔
1053
        cv.check_type('tabular_legendre settings', tabular_legendre, Mapping)
11✔
1054
        for key, value in tabular_legendre.items():
11✔
1055
            cv.check_value('tabular_legendre key', key,
11✔
1056
                           ['enable', 'num_points'])
1057
            if key == 'enable':
11✔
1058
                cv.check_type('enable tabular_legendre', value, bool)
11✔
1059
            elif key == 'num_points':
11✔
1060
                cv.check_type('num_points tabular_legendre', value, Integral)
11✔
1061
                cv.check_greater_than('num_points tabular_legendre', value, 0)
11✔
1062
        self._tabular_legendre = tabular_legendre
11✔
1063

1064
    @property
11✔
1065
    def temperature(self) -> dict:
11✔
1066
        return self._temperature
11✔
1067

1068
    @temperature.setter
11✔
1069
    def temperature(self, temperature: dict):
11✔
1070

1071
        cv.check_type('temperature settings', temperature, Mapping)
11✔
1072
        for key, value in temperature.items():
11✔
1073
            cv.check_value('temperature key', key,
11✔
1074
                           ['default', 'method', 'tolerance', 'multipole',
1075
                            'range'])
1076
            if key == 'default':
11✔
1077
                cv.check_type('default temperature', value, Real)
11✔
1078
            elif key == 'method':
11✔
1079
                cv.check_value('temperature method', value,
11✔
1080
                               ['nearest', 'interpolation'])
1081
            elif key == 'tolerance':
11✔
1082
                cv.check_type('temperature tolerance', value, Real)
11✔
1083
            elif key == 'multipole':
11✔
1084
                cv.check_type('temperature multipole', value, bool)
11✔
1085
            elif key == 'range':
11✔
1086
                cv.check_length('temperature range', value, 2)
11✔
1087
                for T in value:
11✔
1088
                    cv.check_type('temperature', T, Real)
11✔
1089

1090
        self._temperature = temperature
11✔
1091

1092
    @property
11✔
1093
    def properties_file(self) -> PathLike | None:
11✔
1094
        return self._properties_file
11✔
1095

1096
    @properties_file.setter
11✔
1097
    def properties_file(self, value: PathLike | None):
11✔
1098
        if value is None:
11✔
1099
            self._properties_file = None
×
1100
        else:
1101
            cv.check_type('properties file', value, PathLike)
11✔
1102
            self._properties_file = input_path(value)
11✔
1103

1104
    @property
11✔
1105
    def trace(self) -> Iterable:
11✔
1106
        return self._trace
11✔
1107

1108
    @trace.setter
11✔
1109
    def trace(self, trace: Iterable):
11✔
1110
        cv.check_type('trace', trace, Iterable, Integral)
11✔
1111
        cv.check_length('trace', trace, 3)
11✔
1112
        cv.check_greater_than('trace batch', trace[0], 0)
11✔
1113
        cv.check_greater_than('trace generation', trace[1], 0)
11✔
1114
        cv.check_greater_than('trace particle', trace[2], 0)
11✔
1115
        self._trace = trace
11✔
1116

1117
    @property
11✔
1118
    def track(self) -> Iterable[Iterable[int]]:
11✔
1119
        return self._track
11✔
1120

1121
    @track.setter
11✔
1122
    def track(self, track: Iterable[Iterable[int]]):
11✔
1123
        cv.check_type('track', track, Sequence)
11✔
1124
        for t in track:
11✔
1125
            if len(t) != 3:
11✔
1126
                msg = f'Unable to set the track to "{t}" since its length is not 3'
×
1127
                raise ValueError(msg)
×
1128
            cv.check_greater_than('track batch', t[0], 0)
11✔
1129
            cv.check_greater_than('track generation', t[1], 0)
11✔
1130
            cv.check_greater_than('track particle', t[2], 0)
11✔
1131
            cv.check_type('track batch', t[0], Integral)
11✔
1132
            cv.check_type('track generation', t[1], Integral)
11✔
1133
            cv.check_type('track particle', t[2], Integral)
11✔
1134
        self._track = track
11✔
1135

1136
    @property
11✔
1137
    def cutoff(self) -> dict:
11✔
1138
        return self._cutoff
11✔
1139

1140
    @cutoff.setter
11✔
1141
    def cutoff(self, cutoff: dict):
11✔
1142
        if not isinstance(cutoff, Mapping):
11✔
1143
            msg = f'Unable to set cutoff from "{cutoff}" which is not a '\
×
1144
                'Python dictionary'
1145
            raise ValueError(msg)
×
1146
        for key in cutoff:
11✔
1147
            if key == 'weight':
11✔
1148
                cv.check_type('weight cutoff', cutoff[key], Real)
11✔
1149
                cv.check_greater_than('weight cutoff', cutoff[key], 0.0)
11✔
1150
            elif key == 'weight_avg':
11✔
1151
                cv.check_type('average survival weight', cutoff[key], Real)
11✔
1152
                cv.check_greater_than('average survival weight',
11✔
1153
                                      cutoff[key], 0.0)
1154
            elif key == 'survival_normalization':
11✔
1155
                cv.check_type('survival normalization', cutoff[key], bool)
11✔
1156
            elif key in ['energy_neutron', 'energy_photon', 'energy_electron',
11✔
1157
                         'energy_positron']:
1158
                cv.check_type('energy cutoff', cutoff[key], Real)
11✔
1159
                cv.check_greater_than('energy cutoff', cutoff[key], 0.0)
11✔
1160
            else:
1161
                msg = f'Unable to set cutoff to "{key}" which is unsupported ' \
11✔
1162
                    'by OpenMC'
1163

1164
        self._cutoff = cutoff
11✔
1165

1166
    @property
11✔
1167
    def ufs_mesh(self) -> RegularMesh:
11✔
1168
        return self._ufs_mesh
11✔
1169

1170
    @ufs_mesh.setter
11✔
1171
    def ufs_mesh(self, ufs_mesh: RegularMesh):
11✔
1172
        cv.check_type('UFS mesh', ufs_mesh, RegularMesh)
11✔
1173
        cv.check_length('UFS mesh dimension', ufs_mesh.dimension, 3)
11✔
1174
        cv.check_length('UFS mesh lower-left corner', ufs_mesh.lower_left, 3)
11✔
1175
        cv.check_length('UFS mesh upper-right corner', ufs_mesh.upper_right, 3)
11✔
1176
        self._ufs_mesh = ufs_mesh
11✔
1177

1178
    @property
11✔
1179
    def resonance_scattering(self) -> dict:
11✔
1180
        return self._resonance_scattering
11✔
1181

1182
    @resonance_scattering.setter
11✔
1183
    def resonance_scattering(self, res: dict):
11✔
1184
        cv.check_type('resonance scattering settings', res, Mapping)
11✔
1185
        keys = ('enable', 'method', 'energy_min', 'energy_max', 'nuclides')
11✔
1186
        for key, value in res.items():
11✔
1187
            cv.check_value('resonance scattering dictionary key', key, keys)
11✔
1188
            if key == 'enable':
11✔
1189
                cv.check_type('resonance scattering enable', value, bool)
11✔
1190
            elif key == 'method':
11✔
1191
                cv.check_value('resonance scattering method', value,
11✔
1192
                               _RES_SCAT_METHODS)
1193
            elif key == 'energy_min':
11✔
1194
                name = 'resonance scattering minimum energy'
11✔
1195
                cv.check_type(name, value, Real)
11✔
1196
                cv.check_greater_than(name, value, 0)
11✔
1197
            elif key == 'energy_max':
11✔
1198
                name = 'resonance scattering minimum energy'
11✔
1199
                cv.check_type(name, value, Real)
11✔
1200
                cv.check_greater_than(name, value, 0)
11✔
1201
            elif key == 'nuclides':
11✔
1202
                cv.check_type('resonance scattering nuclides', value,
11✔
1203
                              Iterable, str)
1204
        self._resonance_scattering = res
11✔
1205

1206
    @property
11✔
1207
    def volume_calculations(self) -> list[VolumeCalculation]:
11✔
1208
        return self._volume_calculations
11✔
1209

1210
    @volume_calculations.setter
11✔
1211
    def volume_calculations(
11✔
1212
        self, vol_calcs: VolumeCalculation | Iterable[VolumeCalculation]
1213
    ):
1214
        if not isinstance(vol_calcs, MutableSequence):
11✔
1215
            vol_calcs = [vol_calcs]
11✔
1216
        self._volume_calculations = cv.CheckedList(
11✔
1217
            VolumeCalculation, 'stochastic volume calculations', vol_calcs)
1218

1219
    @property
11✔
1220
    def create_fission_neutrons(self) -> bool:
11✔
1221
        return self._create_fission_neutrons
11✔
1222

1223
    @create_fission_neutrons.setter
11✔
1224
    def create_fission_neutrons(self, create_fission_neutrons: bool):
11✔
1225
        cv.check_type('Whether create fission neutrons',
11✔
1226
                      create_fission_neutrons, bool)
1227
        self._create_fission_neutrons = create_fission_neutrons
11✔
1228

1229
    @property
11✔
1230
    def create_delayed_neutrons(self) -> bool:
11✔
1231
        return self._create_delayed_neutrons
11✔
1232

1233
    @create_delayed_neutrons.setter
11✔
1234
    def create_delayed_neutrons(self, create_delayed_neutrons: bool):
11✔
1235
        cv.check_type('Whether create only prompt neutrons',
11✔
1236
                      create_delayed_neutrons, bool)
1237
        self._create_delayed_neutrons = create_delayed_neutrons
11✔
1238

1239
    @property
11✔
1240
    def delayed_photon_scaling(self) -> bool:
11✔
1241
        return self._delayed_photon_scaling
×
1242

1243
    @delayed_photon_scaling.setter
11✔
1244
    def delayed_photon_scaling(self, value: bool):
11✔
1245
        cv.check_type('delayed photon scaling', value, bool)
×
1246
        self._delayed_photon_scaling = value
×
1247

1248
    @property
11✔
1249
    def material_cell_offsets(self) -> bool:
11✔
1250
        return self._material_cell_offsets
×
1251

1252
    @material_cell_offsets.setter
11✔
1253
    def material_cell_offsets(self, value: bool):
11✔
1254
        cv.check_type('material cell offsets', value, bool)
×
1255
        self._material_cell_offsets = value
×
1256

1257
    @property
11✔
1258
    def log_grid_bins(self) -> int:
11✔
1259
        return self._log_grid_bins
11✔
1260

1261
    @log_grid_bins.setter
11✔
1262
    def log_grid_bins(self, log_grid_bins: int):
11✔
1263
        cv.check_type('log grid bins', log_grid_bins, Real)
11✔
1264
        cv.check_greater_than('log grid bins', log_grid_bins, 0)
11✔
1265
        self._log_grid_bins = log_grid_bins
11✔
1266

1267
    @property
11✔
1268
    def event_based(self) -> bool:
11✔
1269
        return self._event_based
×
1270

1271
    @event_based.setter
11✔
1272
    def event_based(self, value: bool):
11✔
1273
        cv.check_type('event based', value, bool)
×
1274
        self._event_based = value
×
1275

1276
    @property
11✔
1277
    def max_particles_in_flight(self) -> int:
11✔
1278
        return self._max_particles_in_flight
×
1279

1280
    @max_particles_in_flight.setter
11✔
1281
    def max_particles_in_flight(self, value: int):
11✔
1282
        cv.check_type('max particles in flight', value, Integral)
×
1283
        cv.check_greater_than('max particles in flight', value, 0)
×
1284
        self._max_particles_in_flight = value
×
1285

1286
    @property
11✔
1287
    def max_particle_events(self) -> int:
11✔
1288
        return self._max_particle_events
11✔
1289

1290
    @max_particle_events.setter
11✔
1291
    def max_particle_events(self, value: int):
11✔
1292
        cv.check_type('max particle events', value, Integral)
11✔
1293
        cv.check_greater_than('max particle events', value, 0)
11✔
1294
        self._max_particle_events = value
11✔
1295

1296
    @property
11✔
1297
    def write_initial_source(self) -> bool:
11✔
1298
        return self._write_initial_source
11✔
1299

1300
    @write_initial_source.setter
11✔
1301
    def write_initial_source(self, value: bool):
11✔
1302
        cv.check_type('write initial source', value, bool)
11✔
1303
        self._write_initial_source = value
11✔
1304

1305
    @property
11✔
1306
    def weight_windows(self) -> WeightWindowsList:
11✔
1307
        return self._weight_windows
11✔
1308

1309
    @weight_windows.setter
11✔
1310
    def weight_windows(self, value: WeightWindows | Sequence[WeightWindows]):
11✔
1311
        if not isinstance(value, Sequence):
11✔
1312
            value = [value]
11✔
1313
        self._weight_windows = WeightWindowsList(value)
11✔
1314

1315
    @property
11✔
1316
    def weight_windows_on(self) -> bool:
11✔
1317
        return self._weight_windows_on
11✔
1318

1319
    @weight_windows_on.setter
11✔
1320
    def weight_windows_on(self, value: bool):
11✔
1321
        cv.check_type('weight windows on', value, bool)
11✔
1322
        self._weight_windows_on = value
11✔
1323

1324
    @property
11✔
1325
    def weight_window_checkpoints(self) -> dict:
11✔
1326
        return self._weight_window_checkpoints
11✔
1327

1328
    @weight_window_checkpoints.setter
11✔
1329
    def weight_window_checkpoints(self, weight_window_checkpoints: dict):
11✔
1330
        for key in weight_window_checkpoints.keys():
11✔
1331
            cv.check_value('weight_window_checkpoints',
11✔
1332
                           key, ('collision', 'surface'))
1333
        self._weight_window_checkpoints = weight_window_checkpoints
11✔
1334

1335
    @property
11✔
1336
    def max_splits(self):
11✔
1337
        raise AttributeError(
×
1338
            'max_splits has been deprecated. Please use max_history_splits instead')
1339

1340
    @property
11✔
1341
    def max_history_splits(self) -> int:
11✔
1342
        return self._max_history_splits
11✔
1343

1344
    @max_history_splits.setter
11✔
1345
    def max_history_splits(self, value: int):
11✔
1346
        cv.check_type('maximum particle splits', value, Integral)
11✔
1347
        cv.check_greater_than('max particle splits', value, 0)
11✔
1348
        self._max_history_splits = value
11✔
1349

1350
    @property
11✔
1351
    def max_secondaries(self) -> int:
11✔
1352
        return self._max_secondaries
11✔
1353

1354
    @max_secondaries.setter
11✔
1355
    def max_secondaries(self, value: int):
11✔
1356
        cv.check_type('maximum secondary bank size', value, Integral)
11✔
1357
        cv.check_greater_than('max secondary bank size', value, 0)
11✔
1358
        self._max_secondaries = value
11✔
1359

1360
    @property
11✔
1361
    def max_tracks(self) -> int:
11✔
1362
        return self._max_tracks
11✔
1363

1364
    @max_tracks.setter
11✔
1365
    def max_tracks(self, value: int):
11✔
1366
        cv.check_type('maximum particle tracks', value, Integral)
11✔
1367
        cv.check_greater_than('maximum particle tracks', value, 0, True)
11✔
1368
        self._max_tracks = value
11✔
1369

1370
    @property
11✔
1371
    def weight_windows_file(self) -> PathLike | None:
11✔
1372
        return self._weight_windows_file
11✔
1373

1374
    @weight_windows_file.setter
11✔
1375
    def weight_windows_file(self, value: PathLike | None):
11✔
1376
        if value is None:
×
1377
            self._weight_windows_file = None
×
1378
        else:
1379
            cv.check_type('weight windows file', value, PathLike)
×
1380
            self._weight_windows_file = input_path(value)
×
1381

1382
    @property
11✔
1383
    def weight_window_generators(self) -> list[WeightWindowGenerator]:
11✔
1384
        return self._weight_window_generators
11✔
1385

1386
    @weight_window_generators.setter
11✔
1387
    def weight_window_generators(self, wwgs):
11✔
1388
        if not isinstance(wwgs, MutableSequence):
11✔
1389
            wwgs = [wwgs]
11✔
1390
        self._weight_window_generators = cv.CheckedList(
11✔
1391
            WeightWindowGenerator, 'weight window generators', wwgs)
1392

1393
    @property
11✔
1394
    def random_ray(self) -> dict:
11✔
1395
        return self._random_ray
11✔
1396

1397
    @random_ray.setter
11✔
1398
    def random_ray(self, random_ray: dict):
11✔
1399
        if not isinstance(random_ray, Mapping):
11✔
1400
            raise ValueError(f'Unable to set random_ray from "{random_ray}" '
×
1401
                             'which is not a dict.')
1402
        for key, value in random_ray.items():
11✔
1403
            if key == 'distance_active':
11✔
1404
                cv.check_type('active ray length', value, Real)
11✔
1405
                cv.check_greater_than('active ray length', value, 0.0)
11✔
1406
            elif key == 'distance_inactive':
11✔
1407
                cv.check_type('inactive ray length', value, Real)
11✔
1408
                cv.check_greater_than('inactive ray length',
11✔
1409
                                      value, 0.0, True)
1410
            elif key == 'ray_source':
11✔
1411
                cv.check_type('random ray source', value, SourceBase)
11✔
1412
            elif key == 'volume_estimator':
11✔
1413
                cv.check_value('volume estimator', value,
11✔
1414
                               ('naive', 'simulation_averaged',
1415
                                'hybrid'))
1416
            elif key == 'source_shape':
11✔
1417
                cv.check_value('source shape', value,
11✔
1418
                               ('flat', 'linear', 'linear_xy'))
1419
            elif key == 'volume_normalized_flux_tallies':
11✔
1420
                cv.check_type('volume normalized flux tallies', value, bool)
11✔
1421
            elif key == 'adjoint':
11✔
1422
                cv.check_type('adjoint', value, bool)
11✔
1423
            elif key == 'source_region_meshes':
11✔
1424
                cv.check_type('source region meshes', value, Iterable)
11✔
1425
                for mesh, domains in value:
11✔
1426
                    cv.check_type('mesh', mesh, MeshBase)
11✔
1427
                    cv.check_type('domains', domains, Iterable)
11✔
1428
                    valid_types = (openmc.Material,
11✔
1429
                                   openmc.Cell, openmc.Universe)
1430
                    for domain in domains:
11✔
1431
                        if not isinstance(domain, valid_types):
11✔
1432
                            raise ValueError(
×
1433
                                f'Invalid domain type: {type(domain)}. Expected '
1434
                                'openmc.Material, openmc.Cell, or openmc.Universe.')
1435
            elif key == 'sample_method':
11✔
1436
                cv.check_value('sample method', value,
11✔
1437
                               ('prng', 'halton', 's2'))
1438
            elif key == 'diagonal_stabilization_rho':
×
1439
                cv.check_type('diagonal stabilization rho', value, Real)
×
1440
                cv.check_greater_than('diagonal stabilization rho',
×
1441
                                      value, 0.0, True)
1442
            else:
1443
                raise ValueError(f'Unable to set random ray to "{key}" which is '
×
1444
                                 'unsupported by OpenMC')
1445

1446
        self._random_ray = random_ray
11✔
1447

1448
    @property
11✔
1449
    def use_decay_photons(self) -> bool:
11✔
1450
        return self._use_decay_photons
11✔
1451

1452
    @use_decay_photons.setter
11✔
1453
    def use_decay_photons(self, value):
11✔
1454
        cv.check_type('use decay photons', value, bool)
11✔
1455
        self._use_decay_photons = value
11✔
1456

1457
    @property
11✔
1458
    def source_rejection_fraction(self) -> float:
11✔
1459
        return self._source_rejection_fraction
11✔
1460

1461
    @source_rejection_fraction.setter
11✔
1462
    def source_rejection_fraction(self, source_rejection_fraction: float):
11✔
1463
        cv.check_type('source_rejection_fraction',
11✔
1464
                      source_rejection_fraction, Real)
1465
        cv.check_greater_than('source_rejection_fraction',
11✔
1466
                              source_rejection_fraction, 0)
1467
        cv.check_less_than('source_rejection_fraction',
11✔
1468
                           source_rejection_fraction, 1)
1469
        self._source_rejection_fraction = source_rejection_fraction
11✔
1470

1471
    @property
11✔
1472
    def free_gas_threshold(self) -> float | None:
11✔
1473
        return self._free_gas_threshold
11✔
1474

1475
    @free_gas_threshold.setter
11✔
1476
    def free_gas_threshold(self, free_gas_threshold: float | None):
11✔
1477
        if free_gas_threshold is not None:
11✔
1478
            cv.check_type('free gas threshold', free_gas_threshold, Real)
11✔
1479
            cv.check_greater_than('free gas threshold', free_gas_threshold, 0.0)
11✔
1480
        self._free_gas_threshold = free_gas_threshold
11✔
1481

1482
    def _create_run_mode_subelement(self, root):
11✔
1483
        elem = ET.SubElement(root, "run_mode")
11✔
1484
        elem.text = self._run_mode.value
11✔
1485

1486
    def _create_batches_subelement(self, root):
11✔
1487
        if self._batches is not None:
11✔
1488
            element = ET.SubElement(root, "batches")
11✔
1489
            element.text = str(self._batches)
11✔
1490

1491
    def _create_generations_per_batch_subelement(self, root):
11✔
1492
        if self._generations_per_batch is not None:
11✔
1493
            element = ET.SubElement(root, "generations_per_batch")
11✔
1494
            element.text = str(self._generations_per_batch)
11✔
1495

1496
    def _create_inactive_subelement(self, root):
11✔
1497
        if self._inactive is not None:
11✔
1498
            element = ET.SubElement(root, "inactive")
11✔
1499
            element.text = str(self._inactive)
11✔
1500

1501
    def _create_max_lost_particles_subelement(self, root):
11✔
1502
        if self._max_lost_particles is not None:
11✔
1503
            element = ET.SubElement(root, "max_lost_particles")
11✔
1504
            element.text = str(self._max_lost_particles)
11✔
1505

1506
    def _create_rel_max_lost_particles_subelement(self, root):
11✔
1507
        if self._rel_max_lost_particles is not None:
11✔
1508
            element = ET.SubElement(root, "rel_max_lost_particles")
11✔
1509
            element.text = str(self._rel_max_lost_particles)
11✔
1510

1511
    def _create_max_write_lost_particles_subelement(self, root):
11✔
1512
        if self._max_write_lost_particles is not None:
11✔
1513
            element = ET.SubElement(root, "max_write_lost_particles")
11✔
1514
            element.text = str(self._max_write_lost_particles)
11✔
1515

1516
    def _create_particles_subelement(self, root):
11✔
1517
        if self._particles is not None:
11✔
1518
            element = ET.SubElement(root, "particles")
11✔
1519
            element.text = str(self._particles)
11✔
1520

1521
    def _create_keff_trigger_subelement(self, root):
11✔
1522
        if self._keff_trigger is not None:
11✔
1523
            element = ET.SubElement(root, "keff_trigger")
11✔
1524
            for key, value in sorted(self._keff_trigger.items()):
11✔
1525
                subelement = ET.SubElement(element, key)
11✔
1526
                subelement.text = str(value).lower()
11✔
1527

1528
    def _create_energy_mode_subelement(self, root):
11✔
1529
        if self._energy_mode is not None:
11✔
1530
            element = ET.SubElement(root, "energy_mode")
11✔
1531
            element.text = str(self._energy_mode)
11✔
1532

1533
    def _create_max_order_subelement(self, root):
11✔
1534
        if self._max_order is not None:
11✔
1535
            element = ET.SubElement(root, "max_order")
11✔
1536
            element.text = str(self._max_order)
11✔
1537

1538
    def _create_source_subelement(self, root, mesh_memo=None):
11✔
1539
        for source in self.source:
11✔
1540
            root.append(source.to_xml_element())
11✔
1541
            if isinstance(source, IndependentSource) and isinstance(source.space, MeshSpatial):
11✔
1542
                path = f"./mesh[@id='{source.space.mesh.id}']"
11✔
1543
                if root.find(path) is None:
11✔
1544
                    root.append(source.space.mesh.to_xml_element())
11✔
1545
            if isinstance(source, MeshSource):
11✔
1546
                path = f"./mesh[@id='{source.mesh.id}']"
11✔
1547
                if root.find(path) is None:
11✔
1548
                    root.append(source.mesh.to_xml_element())
11✔
1549
                    if mesh_memo is not None:
11✔
1550
                        mesh_memo.add(source.mesh.id)
11✔
1551

1552
    def _create_volume_calcs_subelement(self, root):
11✔
1553
        for calc in self.volume_calculations:
11✔
1554
            root.append(calc.to_xml_element())
11✔
1555

1556
    def _create_output_subelement(self, root):
11✔
1557
        if self._output is not None:
11✔
1558
            element = ET.SubElement(root, "output")
11✔
1559
            for key, value in sorted(self._output.items()):
11✔
1560
                subelement = ET.SubElement(element, key)
11✔
1561
                if key in ('summary', 'tallies'):
11✔
1562
                    subelement.text = str(value).lower()
11✔
1563
                else:
1564
                    subelement.text = value
11✔
1565

1566
    def _create_verbosity_subelement(self, root):
11✔
1567
        if self._verbosity is not None:
11✔
1568
            element = ET.SubElement(root, "verbosity")
11✔
1569
            element.text = str(self._verbosity)
11✔
1570

1571
    def _create_statepoint_subelement(self, root):
11✔
1572
        if self._statepoint:
11✔
1573
            element = ET.SubElement(root, "state_point")
11✔
1574
            if 'batches' in self._statepoint:
11✔
1575
                subelement = ET.SubElement(element, "batches")
11✔
1576
                subelement.text = ' '.join(
11✔
1577
                    str(x) for x in self._statepoint['batches'])
1578

1579
    def _create_uniform_source_sampling_subelement(self, root):
11✔
1580
        if self._uniform_source_sampling is not None:
11✔
1581
            element = ET.SubElement(root, "uniform_source_sampling")
11✔
1582
            element.text = str(self._uniform_source_sampling).lower()
11✔
1583

1584
    def _create_sourcepoint_subelement(self, root):
11✔
1585
        if self._sourcepoint:
11✔
1586
            element = ET.SubElement(root, "source_point")
11✔
1587

1588
            if 'batches' in self._sourcepoint:
11✔
1589
                subelement = ET.SubElement(element, "batches")
11✔
1590
                subelement.text = ' '.join(
11✔
1591
                    str(x) for x in self._sourcepoint['batches'])
1592

1593
            if 'separate' in self._sourcepoint:
11✔
1594
                subelement = ET.SubElement(element, "separate")
11✔
1595
                subelement.text = str(self._sourcepoint['separate']).lower()
11✔
1596

1597
            if 'write' in self._sourcepoint:
11✔
1598
                subelement = ET.SubElement(element, "write")
11✔
1599
                subelement.text = str(self._sourcepoint['write']).lower()
11✔
1600

1601
            # Overwrite latest subelement
1602
            if 'overwrite' in self._sourcepoint:
11✔
1603
                subelement = ET.SubElement(element, "overwrite_latest")
11✔
1604
                subelement.text = str(self._sourcepoint['overwrite']).lower()
11✔
1605

1606
            if 'mcpl' in self._sourcepoint:
11✔
1607
                subelement = ET.SubElement(element, "mcpl")
11✔
1608
                subelement.text = str(self._sourcepoint['mcpl']).lower()
11✔
1609

1610
    def _create_surf_source_read_subelement(self, root):
11✔
1611
        if self._surf_source_read:
11✔
1612
            element = ET.SubElement(root, "surf_source_read")
11✔
1613
            if 'path' in self._surf_source_read:
11✔
1614
                subelement = ET.SubElement(element, "path")
11✔
1615
                subelement.text = str(self._surf_source_read['path'])
11✔
1616

1617
    def _create_surf_source_write_subelement(self, root):
11✔
1618
        if self._surf_source_write:
11✔
1619
            element = ET.SubElement(root, "surf_source_write")
11✔
1620
            if "surface_ids" in self._surf_source_write:
11✔
1621
                subelement = ET.SubElement(element, "surface_ids")
11✔
1622
                subelement.text = " ".join(
11✔
1623
                    str(x) for x in self._surf_source_write["surface_ids"]
1624
                )
1625
            if "mcpl" in self._surf_source_write:
11✔
1626
                subelement = ET.SubElement(element, "mcpl")
11✔
1627
                subelement.text = str(self._surf_source_write["mcpl"]).lower()
11✔
1628
            for key in ("max_particles", "max_source_files", "cell", "cellfrom", "cellto"):
11✔
1629
                if key in self._surf_source_write:
11✔
1630
                    subelement = ET.SubElement(element, key)
11✔
1631
                    subelement.text = str(self._surf_source_write[key])
11✔
1632

1633
    def _create_collision_track_subelement(self, root):
11✔
1634
        if self._collision_track:
11✔
1635
            element = ET.SubElement(root, "collision_track")
11✔
1636
            if 'cell_ids' in self._collision_track:
11✔
1637
                subelement = ET.SubElement(element, "cell_ids")
11✔
1638
                subelement.text = ' '.join(
11✔
1639
                    str(x) for x in self._collision_track['cell_ids'])
1640
            if 'reactions' in self._collision_track:
11✔
1641
                subelement = ET.SubElement(element, "reactions")
11✔
1642
                subelement.text = ' '.join(
11✔
1643
                    str(x) for x in self._collision_track['reactions'])
1644
            if 'universe_ids' in self._collision_track:
11✔
1645
                subelement = ET.SubElement(element, "universe_ids")
11✔
1646
                subelement.text = ' '.join(
11✔
1647
                    str(x) for x in self._collision_track['universe_ids'])
1648
            if 'material_ids' in self._collision_track:
11✔
1649
                subelement = ET.SubElement(element, "material_ids")
11✔
1650
                subelement.text = ' '.join(
11✔
1651
                    str(x) for x in self._collision_track['material_ids'])
1652
            if 'nuclides' in self._collision_track:
11✔
1653
                subelement = ET.SubElement(element, "nuclides")
11✔
1654
                subelement.text = ' '.join(
11✔
1655
                    str(x) for x in self._collision_track['nuclides'])
1656
            if 'deposited_E_threshold' in self._collision_track:
11✔
1657
                subelement = ET.SubElement(element, "deposited_E_threshold")
11✔
1658
                subelement.text = str(
11✔
1659
                    self._collision_track['deposited_E_threshold'])
1660
            if 'max_collisions' in self._collision_track:
11✔
1661
                subelement = ET.SubElement(element, "max_collisions")
11✔
1662
                subelement.text = str(self._collision_track['max_collisions'])
11✔
1663
            if 'max_collision_track_files' in self._collision_track:
11✔
1664
                subelement = ET.SubElement(
×
1665
                    element, "max_collision_track_files")
1666
                subelement.text = str(
×
1667
                    self._collision_track['max_collision_track_files'])
1668
            if 'mcpl' in self._collision_track:
11✔
1669
                subelement = ET.SubElement(element, "mcpl")
11✔
1670
                subelement.text = str(self._collision_track['mcpl']).lower()
11✔
1671

1672
    def _create_confidence_intervals(self, root):
11✔
1673
        if self._confidence_intervals is not None:
11✔
1674
            element = ET.SubElement(root, "confidence_intervals")
11✔
1675
            element.text = str(self._confidence_intervals).lower()
11✔
1676

1677
    def _create_electron_treatment_subelement(self, root):
11✔
1678
        if self._electron_treatment is not None:
11✔
1679
            element = ET.SubElement(root, "electron_treatment")
11✔
1680
            element.text = str(self._electron_treatment)
11✔
1681

1682
    def _create_atomic_relaxation_subelement(self, root):
11✔
1683
        if self._atomic_relaxation is not None:
11✔
1684
            element = ET.SubElement(root, "atomic_relaxation")
11✔
1685
            element.text = str(self._atomic_relaxation).lower()
11✔
1686

1687
    def _create_photon_transport_subelement(self, root):
11✔
1688
        if self._photon_transport is not None:
11✔
1689
            element = ET.SubElement(root, "photon_transport")
11✔
1690
            element.text = str(self._photon_transport).lower()
11✔
1691

1692
    def _create_plot_seed_subelement(self, root):
11✔
1693
        if self._plot_seed is not None:
11✔
1694
            element = ET.SubElement(root, "plot_seed")
11✔
1695
            element.text = str(self._plot_seed)
11✔
1696

1697
    def _create_ptables_subelement(self, root):
11✔
1698
        if self._ptables is not None:
11✔
1699
            element = ET.SubElement(root, "ptables")
11✔
1700
            element.text = str(self._ptables).lower()
11✔
1701

1702
    def _create_seed_subelement(self, root):
11✔
1703
        if self._seed is not None:
11✔
1704
            element = ET.SubElement(root, "seed")
11✔
1705
            element.text = str(self._seed)
11✔
1706

1707
    def _create_stride_subelement(self, root):
11✔
1708
        if self._stride is not None:
11✔
1709
            element = ET.SubElement(root, "stride")
11✔
1710
            element.text = str(self._stride)
11✔
1711

1712
    def _create_surface_grazing_cutoff_subelement(self, root):
11✔
1713
        if self._surface_grazing_cutoff is not None:
11✔
1714
            element = ET.SubElement(root, "surface_grazing_cutoff")
11✔
1715
            element.text = str(self._surface_grazing_cutoff)
11✔
1716

1717
    def _create_surface_grazing_ratio_subelement(self, root):
11✔
1718
        if self._surface_grazing_ratio is not None:
11✔
1719
            element = ET.SubElement(root, "surface_grazing_ratio")
11✔
1720
            element.text = str(self._surface_grazing_ratio)
11✔
1721

1722
    def _create_survival_biasing_subelement(self, root):
11✔
1723
        if self._survival_biasing is not None:
11✔
1724
            element = ET.SubElement(root, "survival_biasing")
11✔
1725
            element.text = str(self._survival_biasing).lower()
11✔
1726

1727
    def _create_cutoff_subelement(self, root):
11✔
1728
        if self._cutoff is not None:
11✔
1729
            element = ET.SubElement(root, "cutoff")
11✔
1730
            for key, value in self._cutoff.items():
11✔
1731
                subelement = ET.SubElement(element, key)
11✔
1732
                subelement.text = str(value) if key != 'survival_normalization' \
11✔
1733
                    else str(value).lower()
1734

1735
    def _create_entropy_mesh_subelement(self, root, mesh_memo=None):
11✔
1736
        if self.entropy_mesh is None:
11✔
1737
            return
11✔
1738

1739
        # use default heuristic for entropy mesh if not set by user
1740
        if self.entropy_mesh.dimension is None:
11✔
1741
            if self.particles is None:
×
1742
                raise RuntimeError("Number of particles must be set in order to "
×
1743
                                   "use entropy mesh dimension heuristic")
1744
            else:
1745
                n = ceil((self.particles / 20.0)**(1.0 / 3.0))
×
1746
                d = len(self.entropy_mesh.lower_left)
×
1747
                self.entropy_mesh.dimension = (n,)*d
×
1748

1749
        # add mesh ID to this element
1750
        subelement = ET.SubElement(root, "entropy_mesh")
11✔
1751
        subelement.text = str(self.entropy_mesh.id)
11✔
1752

1753
        # If this mesh has already been written outside the
1754
        # settings element, skip writing it again
1755
        if mesh_memo and self.entropy_mesh.id in mesh_memo:
11✔
1756
            return
×
1757

1758
        # See if a <mesh> element already exists -- if not, add it
1759
        path = f"./mesh[@id='{self.entropy_mesh.id}']"
11✔
1760
        if root.find(path) is None:
11✔
1761
            root.append(self.entropy_mesh.to_xml_element())
11✔
1762
            if mesh_memo is not None:
11✔
1763
                mesh_memo.add(self.entropy_mesh.id)
11✔
1764

1765
    def _create_temperature_field_subelement(self, root, mesh_memo=None):
11✔
1766
        if self.temperature_field is None:
11✔
1767
            return
11✔
1768

1769
        # add mesh ID to this element
1770
        element = ET.SubElement(root, "temperature_field")
11✔
1771
        subelement = ET.SubElement(element, "mesh")
11✔
1772
        subelement.text = str(self.temperature_field.mesh.id)
11✔
1773

1774
        # If this mesh has already been written outside the
1775
        # settings element, skip writing it again
1776
        if mesh_memo and self.temperature_field.mesh.id in mesh_memo:
11✔
NEW
1777
            return
×
1778

1779
        # See if a <mesh> element already exists -- if not, add it
1780
        path = f"./mesh[@id='{self.temperature_field.mesh.id}']"
11✔
1781
        if root.find(path) is None:
11✔
1782
            root.append(self.temperature_field.mesh.to_xml_element())
11✔
1783
            if mesh_memo is not None:
11✔
1784
                mesh_memo.add(self.temperature_field.mesh.id)
11✔
1785

1786
        # Add temperature values
1787
        subelement = ET.SubElement(element, "values")
11✔
1788
        subelement.text = "\n        ".join(
11✔
1789
            textwrap.wrap(" ".join(
1790
                [str(i) for i in self.temperature_field.values]), 80))
1791

1792
    def _create_trigger_subelement(self, root):
11✔
1793
        if self._trigger_active is not None:
11✔
1794
            trigger_element = ET.SubElement(root, "trigger")
11✔
1795
            element = ET.SubElement(trigger_element, "active")
11✔
1796
            element.text = str(self._trigger_active).lower()
11✔
1797

1798
            if self._trigger_max_batches is not None:
11✔
1799
                element = ET.SubElement(trigger_element, "max_batches")
11✔
1800
                element.text = str(self._trigger_max_batches)
11✔
1801

1802
            if self._trigger_batch_interval is not None:
11✔
1803
                element = ET.SubElement(trigger_element, "batch_interval")
11✔
1804
                element.text = str(self._trigger_batch_interval)
11✔
1805

1806
    def _create_no_reduce_subelement(self, root):
11✔
1807
        if self._no_reduce is not None:
11✔
1808
            element = ET.SubElement(root, "no_reduce")
11✔
1809
            element.text = str(self._no_reduce).lower()
11✔
1810

1811
    def _create_ifp_n_generation_subelement(self, root):
11✔
1812
        if self._ifp_n_generation is not None:
11✔
1813
            element = ET.SubElement(root, "ifp_n_generation")
11✔
1814
            element.text = str(self._ifp_n_generation)
11✔
1815

1816
    def _create_tabular_legendre_subelements(self, root):
11✔
1817
        if self.tabular_legendre:
11✔
1818
            element = ET.SubElement(root, "tabular_legendre")
11✔
1819
            subelement = ET.SubElement(element, "enable")
11✔
1820
            subelement.text = str(self._tabular_legendre['enable']).lower()
11✔
1821
            if 'num_points' in self._tabular_legendre:
11✔
1822
                subelement = ET.SubElement(element, "num_points")
11✔
1823
                subelement.text = str(self._tabular_legendre['num_points'])
11✔
1824

1825
    def _create_temperature_subelements(self, root):
11✔
1826
        if self.temperature:
11✔
1827
            for key, value in sorted(self.temperature.items()):
11✔
1828
                element = ET.SubElement(root, f"temperature_{key}")
11✔
1829
                if isinstance(value, bool):
11✔
1830
                    element.text = str(value).lower()
11✔
1831
                elif key == 'range':
11✔
1832
                    element.text = ' '.join(str(T) for T in value)
11✔
1833
                else:
1834
                    element.text = str(value)
11✔
1835

1836
    def _create_properties_file_element(self, root):
11✔
1837
        if self.properties_file is not None:
11✔
1838
            element = ET.Element("properties_file")
11✔
1839
            element.text = str(self.properties_file)
11✔
1840
            root.append(element)
11✔
1841

1842
    def _create_trace_subelement(self, root):
11✔
1843
        if self._trace is not None:
11✔
1844
            element = ET.SubElement(root, "trace")
11✔
1845
            element.text = ' '.join(map(str, self._trace))
11✔
1846

1847
    def _create_track_subelement(self, root):
11✔
1848
        if self._track is not None:
11✔
1849
            element = ET.SubElement(root, "track")
11✔
1850
            element.text = ' '.join(map(str, itertools.chain(*self._track)))
11✔
1851

1852
    def _create_ufs_mesh_subelement(self, root, mesh_memo=None):
11✔
1853
        if self.ufs_mesh is None:
11✔
1854
            return
11✔
1855

1856
        subelement = ET.SubElement(root, "ufs_mesh")
11✔
1857
        subelement.text = str(self.ufs_mesh.id)
11✔
1858

1859
        if mesh_memo and self.ufs_mesh.id in mesh_memo:
11✔
1860
            return
×
1861

1862
        # See if a <mesh> element already exists -- if not, add it
1863
        path = f"./mesh[@id='{self.ufs_mesh.id}']"
11✔
1864
        if root.find(path) is None:
11✔
1865
            root.append(self.ufs_mesh.to_xml_element())
×
1866
            if mesh_memo is not None:
×
1867
                mesh_memo.add(self.ufs_mesh.id)
×
1868

1869
    def _create_use_decay_photons_subelement(self, root):
11✔
1870
        if self._use_decay_photons is not None:
11✔
1871
            element = ET.SubElement(root, "use_decay_photons")
11✔
1872
            element.text = str(self._use_decay_photons).lower()
11✔
1873

1874
    def _create_resonance_scattering_subelement(self, root):
11✔
1875
        res = self.resonance_scattering
11✔
1876
        if res:
11✔
1877
            elem = ET.SubElement(root, 'resonance_scattering')
11✔
1878
            if 'enable' in res:
11✔
1879
                subelem = ET.SubElement(elem, 'enable')
11✔
1880
                subelem.text = str(res['enable']).lower()
11✔
1881
            if 'method' in res:
11✔
1882
                subelem = ET.SubElement(elem, 'method')
11✔
1883
                subelem.text = res['method']
11✔
1884
            if 'energy_min' in res:
11✔
1885
                subelem = ET.SubElement(elem, 'energy_min')
11✔
1886
                subelem.text = str(res['energy_min'])
11✔
1887
            if 'energy_max' in res:
11✔
1888
                subelem = ET.SubElement(elem, 'energy_max')
11✔
1889
                subelem.text = str(res['energy_max'])
11✔
1890
            if 'nuclides' in res:
11✔
1891
                subelem = ET.SubElement(elem, 'nuclides')
11✔
1892
                subelem.text = ' '.join(res['nuclides'])
11✔
1893

1894
    def _create_create_fission_neutrons_subelement(self, root):
11✔
1895
        if self._create_fission_neutrons is not None:
11✔
1896
            elem = ET.SubElement(root, "create_fission_neutrons")
11✔
1897
            elem.text = str(self._create_fission_neutrons).lower()
11✔
1898

1899
    def _create_create_delayed_neutrons_subelement(self, root):
11✔
1900
        if self._create_delayed_neutrons is not None:
11✔
1901
            elem = ET.SubElement(root, "create_delayed_neutrons")
11✔
1902
            elem.text = str(self._create_delayed_neutrons).lower()
11✔
1903

1904
    def _create_delayed_photon_scaling_subelement(self, root):
11✔
1905
        if self._delayed_photon_scaling is not None:
11✔
1906
            elem = ET.SubElement(root, "delayed_photon_scaling")
×
1907
            elem.text = str(self._delayed_photon_scaling).lower()
×
1908

1909
    def _create_event_based_subelement(self, root):
11✔
1910
        if self._event_based is not None:
11✔
1911
            elem = ET.SubElement(root, "event_based")
×
1912
            elem.text = str(self._event_based).lower()
×
1913

1914
    def _create_max_particles_in_flight_subelement(self, root):
11✔
1915
        if self._max_particles_in_flight is not None:
11✔
1916
            elem = ET.SubElement(root, "max_particles_in_flight")
×
1917
            elem.text = str(self._max_particles_in_flight).lower()
×
1918

1919
    def _create_max_events_subelement(self, root):
11✔
1920
        if self._max_particle_events is not None:
11✔
1921
            elem = ET.SubElement(root, "max_particle_events")
11✔
1922
            elem.text = str(self._max_particle_events).lower()
11✔
1923

1924
    def _create_material_cell_offsets_subelement(self, root):
11✔
1925
        if self._material_cell_offsets is not None:
11✔
1926
            elem = ET.SubElement(root, "material_cell_offsets")
×
1927
            elem.text = str(self._material_cell_offsets).lower()
×
1928

1929
    def _create_log_grid_bins_subelement(self, root):
11✔
1930
        if self._log_grid_bins is not None:
11✔
1931
            elem = ET.SubElement(root, "log_grid_bins")
11✔
1932
            elem.text = str(self._log_grid_bins)
11✔
1933

1934
    def _create_write_initial_source_subelement(self, root):
11✔
1935
        if self._write_initial_source is not None:
11✔
1936
            elem = ET.SubElement(root, "write_initial_source")
11✔
1937
            elem.text = str(self._write_initial_source).lower()
11✔
1938

1939
    def _create_weight_windows_subelement(self, root, mesh_memo=None):
11✔
1940
        for ww in self._weight_windows:
11✔
1941
            # Add weight window information
1942
            root.append(ww.to_xml_element())
11✔
1943

1944
            # if this mesh has already been written,
1945
            # skip writing the mesh element
1946
            if mesh_memo and ww.mesh.id in mesh_memo:
11✔
1947
                continue
11✔
1948

1949
            # See if a <mesh> element already exists -- if not, add it
1950
            path = f"./mesh[@id='{ww.mesh.id}']"
11✔
1951
            if root.find(path) is None:
11✔
1952
                root.append(ww.mesh.to_xml_element())
11✔
1953
                if mesh_memo is not None:
11✔
1954
                    mesh_memo.add(ww.mesh.id)
11✔
1955

1956
    def _create_weight_windows_on_subelement(self, root):
11✔
1957
        if self._weight_windows_on is not None:
11✔
1958
            elem = ET.SubElement(root, "weight_windows_on")
11✔
1959
            elem.text = str(self._weight_windows_on).lower()
11✔
1960

1961
    def _create_weight_window_generators_subelement(self, root, mesh_memo=None):
11✔
1962
        if not self.weight_window_generators:
11✔
1963
            return
11✔
1964
        elem = ET.SubElement(root, 'weight_window_generators')
11✔
1965
        for wwg in self.weight_window_generators:
11✔
1966
            elem.append(wwg.to_xml_element())
11✔
1967

1968
        # ensure that mesh elements are created if needed
1969
        for wwg in self.weight_window_generators:
11✔
1970
            if mesh_memo is not None and wwg.mesh.id in mesh_memo:
11✔
1971
                continue
×
1972

1973
            # See if a <mesh> element already exists -- if not, add it
1974
            path = f"./mesh[@id='{wwg.mesh.id}']"
11✔
1975
            if root.find(path) is None:
11✔
1976
                root.append(wwg.mesh.to_xml_element())
11✔
1977
                if mesh_memo is not None:
11✔
1978
                    mesh_memo.add(wwg.mesh.id)
11✔
1979

1980
    def _create_weight_windows_file_element(self, root):
11✔
1981
        if self.weight_windows_file is not None:
11✔
1982
            element = ET.Element("weight_windows_file")
×
1983
            element.text = str(self.weight_windows_file)
×
1984
            root.append(element)
×
1985

1986
    def _create_weight_window_checkpoints_subelement(self, root):
11✔
1987
        if not self._weight_window_checkpoints:
11✔
1988
            return
11✔
1989
        element = ET.SubElement(root, "weight_window_checkpoints")
11✔
1990

1991
        if 'collision' in self._weight_window_checkpoints:
11✔
1992
            subelement = ET.SubElement(element, "collision")
11✔
1993
            subelement.text = str(
11✔
1994
                self._weight_window_checkpoints['collision']).lower()
1995

1996
        if 'surface' in self._weight_window_checkpoints:
11✔
1997
            subelement = ET.SubElement(element, "surface")
11✔
1998
            subelement.text = str(
11✔
1999
                self._weight_window_checkpoints['surface']).lower()
2000

2001
    def _create_max_history_splits_subelement(self, root):
11✔
2002
        if self._max_history_splits is not None:
11✔
2003
            elem = ET.SubElement(root, "max_history_splits")
11✔
2004
            elem.text = str(self._max_history_splits)
11✔
2005

2006
    def _create_max_secondaries_subelement(self, root):
11✔
2007
        if self._max_secondaries is not None:
11✔
2008
            elem = ET.SubElement(root, "max_secondaries")
11✔
2009
            elem.text = str(self._max_secondaries)
11✔
2010

2011
    def _create_max_tracks_subelement(self, root):
11✔
2012
        if self._max_tracks is not None:
11✔
2013
            elem = ET.SubElement(root, "max_tracks")
11✔
2014
            elem.text = str(self._max_tracks)
11✔
2015

2016
    def _create_random_ray_subelement(self, root, mesh_memo=None):
11✔
2017
        if self._random_ray:
11✔
2018
            element = ET.SubElement(root, "random_ray")
11✔
2019
            for key, value in self._random_ray.items():
11✔
2020
                if key == 'ray_source' and isinstance(value, SourceBase):
11✔
2021
                    source_element = value.to_xml_element()
11✔
2022
                    if source_element.find('bias') is not None:
11✔
2023
                        raise RuntimeError(
×
2024
                            "Ray source distributions should not be biased.")
2025
                    element.append(source_element)
11✔
2026

2027
                elif key == 'source_region_meshes':
11✔
2028
                    subelement = ET.SubElement(element, 'source_region_meshes')
11✔
2029
                    for mesh, domains in value:
11✔
2030
                        mesh_elem = ET.SubElement(subelement, 'mesh')
11✔
2031
                        mesh_elem.set('id', str(mesh.id))
11✔
2032
                        for domain in domains:
11✔
2033
                            domain_elem = ET.SubElement(mesh_elem, 'domain')
11✔
2034
                            domain_elem.set('id', str(domain.id))
11✔
2035
                            domain_elem.set(
11✔
2036
                                'type', domain.__class__.__name__.lower())
2037
                        if mesh_memo is not None and mesh.id not in mesh_memo:
11✔
2038
                            domain_elem.set('type', domain.__class__.__name__.lower())
11✔
2039
                        # See if a <mesh> element already exists -- if not, add it
2040
                        path = f"./mesh[@id='{mesh.id}']"
11✔
2041
                        if root.find(path) is None:
11✔
2042
                            root.append(mesh.to_xml_element())
11✔
2043
                            if mesh_memo is not None:
11✔
2044
                                mesh_memo.add(mesh.id)
11✔
2045
                elif isinstance(value, bool):
11✔
2046
                    subelement = ET.SubElement(element, key)
11✔
2047
                    subelement.text = str(value).lower()
11✔
2048
                else:
2049
                    subelement = ET.SubElement(element, key)
11✔
2050
                    subelement.text = str(value)
11✔
2051

2052
    def _create_source_rejection_fraction_subelement(self, root):
11✔
2053
        if self._source_rejection_fraction is not None:
11✔
2054
            element = ET.SubElement(root, "source_rejection_fraction")
11✔
2055
            element.text = str(self._source_rejection_fraction)
11✔
2056

2057
    def _create_free_gas_threshold_subelement(self, root):
11✔
2058
        if self._free_gas_threshold is not None:
11✔
2059
            element = ET.SubElement(root, "free_gas_threshold")
11✔
2060
            element.text = str(self._free_gas_threshold)
11✔
2061

2062
    def _eigenvalue_from_xml_element(self, root):
11✔
2063
        elem = root.find('eigenvalue')
11✔
2064
        if elem is not None:
11✔
2065
            self._run_mode_from_xml_element(elem)
×
2066
            self._particles_from_xml_element(elem)
×
2067
            self._batches_from_xml_element(elem)
×
2068
            self._inactive_from_xml_element(elem)
×
2069
            self._max_lost_particles_from_xml_element(elem)
×
2070
            self._rel_max_lost_particles_from_xml_element(elem)
×
2071
            self._max_write_lost_particles_from_xml_element(elem)
×
2072
            self._generations_per_batch_from_xml_element(elem)
×
2073

2074
    def _run_mode_from_xml_element(self, root):
11✔
2075
        text = get_text(root, 'run_mode')
11✔
2076
        if text is not None:
11✔
2077
            self.run_mode = text
11✔
2078

2079
    def _particles_from_xml_element(self, root):
11✔
2080
        text = get_text(root, 'particles')
11✔
2081
        if text is not None:
11✔
2082
            self.particles = int(text)
11✔
2083

2084
    def _batches_from_xml_element(self, root):
11✔
2085
        text = get_text(root, 'batches')
11✔
2086
        if text is not None:
11✔
2087
            self.batches = int(text)
11✔
2088

2089
    def _inactive_from_xml_element(self, root):
11✔
2090
        text = get_text(root, 'inactive')
11✔
2091
        if text is not None:
11✔
2092
            self.inactive = int(text)
11✔
2093

2094
    def _max_lost_particles_from_xml_element(self, root):
11✔
2095
        text = get_text(root, 'max_lost_particles')
11✔
2096
        if text is not None:
11✔
2097
            self.max_lost_particles = int(text)
11✔
2098

2099
    def _rel_max_lost_particles_from_xml_element(self, root):
11✔
2100
        text = get_text(root, 'rel_max_lost_particles')
11✔
2101
        if text is not None:
11✔
2102
            self.rel_max_lost_particles = float(text)
11✔
2103

2104
    def _max_write_lost_particles_from_xml_element(self, root):
11✔
2105
        text = get_text(root, 'max_write_lost_particles')
11✔
2106
        if text is not None:
11✔
2107
            self.max_write_lost_particles = int(text)
×
2108

2109
    def _generations_per_batch_from_xml_element(self, root):
11✔
2110
        text = get_text(root, 'generations_per_batch')
11✔
2111
        if text is not None:
11✔
2112
            self.generations_per_batch = int(text)
11✔
2113

2114
    def _keff_trigger_from_xml_element(self, root):
11✔
2115
        elem = root.find('keff_trigger')
11✔
2116
        if elem is not None:
11✔
2117
            trigger = get_text(elem, 'type')
11✔
2118
            threshold = float(get_text(elem, 'threshold'))
11✔
2119
            self.keff_trigger = {'type': trigger, 'threshold': threshold}
11✔
2120

2121
    def _source_from_xml_element(self, root, meshes=None):
11✔
2122
        for elem in root.findall('source'):
11✔
2123
            src = SourceBase.from_xml_element(elem, meshes)
11✔
2124
            # add newly constructed source object to the list
2125
            self.source.append(src)
11✔
2126

2127
    def _volume_calcs_from_xml_element(self, root):
11✔
2128
        volume_elems = root.findall("volume_calc")
11✔
2129
        if volume_elems:
11✔
2130
            self.volume_calculations = [VolumeCalculation.from_xml_element(elem)
11✔
2131
                                        for elem in volume_elems]
2132

2133
    def _output_from_xml_element(self, root):
11✔
2134
        elem = root.find('output')
11✔
2135
        if elem is not None:
11✔
2136
            self.output = {}
11✔
2137
            for key in ('summary', 'tallies', 'path'):
11✔
2138
                value = get_text(elem, key)
11✔
2139
                if value is not None:
11✔
2140
                    if key in ('summary', 'tallies'):
11✔
2141
                        value = value in ('true', '1')
11✔
2142
                    self.output[key] = value
11✔
2143

2144
    def _statepoint_from_xml_element(self, root):
11✔
2145
        elem = root.find('state_point')
11✔
2146
        if elem is not None:
11✔
2147
            batches = get_elem_list(elem, "batches", int)
11✔
2148
            if batches is not None:
11✔
2149
                self.statepoint['batches'] = batches
11✔
2150

2151
    def _sourcepoint_from_xml_element(self, root):
11✔
2152
        elem = root.find('source_point')
11✔
2153
        if elem is not None:
11✔
2154
            for key in ('separate', 'write', 'overwrite_latest', 'batches', 'mcpl'):
11✔
2155
                if key in ('separate', 'write', 'mcpl', 'overwrite_latest'):
11✔
2156
                    value = get_text(elem, key) in ('true', '1')
11✔
2157
                    if key == 'overwrite_latest':
11✔
2158
                        key = 'overwrite'
11✔
2159
                else:
2160
                    value = get_elem_list(elem, key, int)
11✔
2161
                if value is not None:
11✔
2162
                    self.sourcepoint[key] = value
11✔
2163

2164
    def _surf_source_read_from_xml_element(self, root):
11✔
2165
        elem = root.find('surf_source_read')
11✔
2166
        if elem is not None:
11✔
2167
            ssr = {}
11✔
2168
            value = get_text(elem, 'path')
11✔
2169
            if value is not None:
11✔
2170
                ssr['path'] = value
11✔
2171
            self.surf_source_read = ssr
11✔
2172

2173
    def _surf_source_write_from_xml_element(self, root):
11✔
2174
        elem = root.find('surf_source_write')
11✔
2175
        if elem is None:
11✔
2176
            return
11✔
2177
        for key in ('surface_ids', 'max_particles', 'max_source_files', 'mcpl', 'cell', 'cellto', 'cellfrom'):
11✔
2178
            if key == 'surface_ids':
11✔
2179
                value = get_elem_list(elem, key, int)
11✔
2180
            else:
2181
                value = get_text(elem, key)
11✔
2182
            if value is not None:
11✔
2183
                if key == 'mcpl':
11✔
2184
                    value = value in ('true', '1')
×
2185
                elif key in ('max_particles', 'max_source_files', 'cell', 'cellfrom', 'cellto'):
11✔
2186
                    value = int(value)
11✔
2187
                self.surf_source_write[key] = value
11✔
2188

2189
    def _collision_track_from_xml_element(self, root):
11✔
2190
        elem = root.find('collision_track')
11✔
2191
        if elem is not None:
11✔
2192
            for key in ('cell_ids', 'reactions', 'universe_ids', 'material_ids', 'nuclides',
11✔
2193
                        'deposited_E_threshold', 'max_collisions', "max_collision_track_files", 'mcpl'):
2194
                value = get_text(elem, key)
11✔
2195
                if value is not None:
11✔
2196
                    if key in ('cell_ids', 'universe_ids', 'material_ids'):
11✔
2197
                        value = [int(x) for x in value.split()]
11✔
2198
                    elif key in ('reactions', 'nuclides'):
11✔
2199
                        value = value.split()
11✔
2200
                    elif key in ('max_collisions', 'max_collision_track_files'):
11✔
2201
                        value = int(value)
11✔
2202
                    elif key == 'deposited_E_threshold':
11✔
2203
                        value = float(value)
11✔
2204
                    elif key == 'mcpl':
11✔
2205
                        value = value in ('true', '1')
11✔
2206
                    self.collision_track[key] = value
11✔
2207

2208
    def _confidence_intervals_from_xml_element(self, root):
11✔
2209
        text = get_text(root, 'confidence_intervals')
11✔
2210
        if text is not None:
11✔
2211
            self.confidence_intervals = text in ('true', '1')
11✔
2212

2213
    def _electron_treatment_from_xml_element(self, root):
11✔
2214
        text = get_text(root, 'electron_treatment')
11✔
2215
        if text is not None:
11✔
2216
            self.electron_treatment = text
11✔
2217

2218
    def _atomic_relaxation_from_xml_element(self, root):
11✔
2219
        text = get_text(root, 'atomic_relaxation')
11✔
2220
        if text is not None:
11✔
2221
            self.atomic_relaxation = text in ('true', '1')
11✔
2222

2223
    def _energy_mode_from_xml_element(self, root):
11✔
2224
        text = get_text(root, 'energy_mode')
11✔
2225
        if text is not None:
11✔
2226
            self.energy_mode = text
11✔
2227

2228
    def _max_order_from_xml_element(self, root):
11✔
2229
        text = get_text(root, 'max_order')
11✔
2230
        if text is not None:
11✔
2231
            self.max_order = int(text)
11✔
2232

2233
    def _photon_transport_from_xml_element(self, root):
11✔
2234
        text = get_text(root, 'photon_transport')
11✔
2235
        if text is not None:
11✔
2236
            self.photon_transport = text in ('true', '1')
11✔
2237

2238
    def _uniform_source_sampling_from_xml_element(self, root):
11✔
2239
        text = get_text(root, 'uniform_source_sampling')
11✔
2240
        if text is not None:
11✔
2241
            self.uniform_source_sampling = text in ('true', '1')
×
2242

2243
    def _plot_seed_from_xml_element(self, root):
11✔
2244
        text = get_text(root, 'plot_seed')
11✔
2245
        if text is not None:
11✔
2246
            self.plot_seed = int(text)
11✔
2247

2248
    def _ptables_from_xml_element(self, root):
11✔
2249
        text = get_text(root, 'ptables')
11✔
2250
        if text is not None:
11✔
2251
            self.ptables = text in ('true', '1')
11✔
2252

2253
    def _seed_from_xml_element(self, root):
11✔
2254
        text = get_text(root, 'seed')
11✔
2255
        if text is not None:
11✔
2256
            self.seed = int(text)
11✔
2257

2258
    def _stride_from_xml_element(self, root):
11✔
2259
        text = get_text(root, 'stride')
11✔
2260
        if text is not None:
11✔
2261
            self.stride = int(text)
×
2262

2263
    def _surface_grazing_cutoff_from_xml_element(self, root):
11✔
2264
        text = get_text(root, 'surface_grazing_cutoff')
11✔
2265
        if text is not None:
11✔
2266
            self.surface_grazing_cutoff = float(text)
11✔
2267

2268
    def _surface_grazing_ratio_from_xml_element(self, root):
11✔
2269
        text = get_text(root, 'surface_grazing_ratio')
11✔
2270
        if text is not None:
11✔
2271
            self.surface_grazing_ratio = float(text)
11✔
2272

2273
    def _survival_biasing_from_xml_element(self, root):
11✔
2274
        text = get_text(root, 'survival_biasing')
11✔
2275
        if text is not None:
11✔
2276
            self.survival_biasing = text in ('true', '1')
11✔
2277

2278
    def _cutoff_from_xml_element(self, root):
11✔
2279
        elem = root.find('cutoff')
11✔
2280
        if elem is not None:
11✔
2281
            self.cutoff = {}
11✔
2282
            for key in ('energy_neutron', 'energy_photon', 'energy_electron',
11✔
2283
                        'energy_positron', 'weight', 'weight_avg', 'time_neutron',
2284
                        'time_photon', 'time_electron', 'time_positron',
2285
                        'survival_normalization'):
2286
                value = get_text(elem, key)
11✔
2287
                if value is not None:
11✔
2288
                    if key == 'survival_normalization':
11✔
2289
                        self.cutoff[key] = value in ('true', '1')
11✔
2290
                    else:
2291
                        self.cutoff[key] = float(value)
11✔
2292

2293
    def _entropy_mesh_from_xml_element(self, root, meshes):
11✔
2294
        text = get_text(root, 'entropy_mesh')
11✔
2295
        if text is None:
11✔
2296
            return
11✔
2297
        mesh_id = int(text)
11✔
2298
        if mesh_id not in meshes:
11✔
2299
            raise ValueError(f'Could not locate mesh with ID "{mesh_id}"')
×
2300
        self.entropy_mesh = meshes[mesh_id]
11✔
2301

2302
    def _temperature_field_from_xml_element(self, root, meshes):
11✔
2303
        elem = root.find('temperature_field')
11✔
2304
        if elem is not None:
11✔
2305
            mesh_id = int(get_text(elem, 'mesh'))
11✔
2306
            if mesh_id not in meshes:
11✔
NEW
2307
                raise ValueError(f'Could not locate mesh with ID "{mesh_id}"')
×
2308
            mesh = meshes[mesh_id]
11✔
2309
            values = [float(x) for x in get_text(elem, 'values').split()]
11✔
2310
            self.temperature_field = TemperatureField(mesh, values)
11✔
2311

2312
    def _trigger_from_xml_element(self, root):
11✔
2313
        elem = root.find('trigger')
11✔
2314
        if elem is not None:
11✔
2315
            self.trigger_active = get_text(elem, 'active') in ('true', '1')
11✔
2316
            text = get_text(elem, 'max_batches')
11✔
2317
            if text is not None:
11✔
2318
                self.trigger_max_batches = int(text)
11✔
2319
            text = get_text(elem, 'batch_interval')
11✔
2320
            if text is not None:
11✔
2321
                self.trigger_batch_interval = int(text)
11✔
2322

2323
    def _no_reduce_from_xml_element(self, root):
11✔
2324
        text = get_text(root, 'no_reduce')
11✔
2325
        if text is not None:
11✔
2326
            self.no_reduce = text in ('true', '1')
11✔
2327

2328
    def _verbosity_from_xml_element(self, root):
11✔
2329
        text = get_text(root, 'verbosity')
11✔
2330
        if text is not None:
11✔
2331
            self.verbosity = int(text)
11✔
2332

2333
    def _ifp_n_generation_from_xml_element(self, root):
11✔
2334
        text = get_text(root, 'ifp_n_generation')
11✔
2335
        if text is not None:
11✔
2336
            self.ifp_n_generation = int(text)
11✔
2337

2338
    def _tabular_legendre_from_xml_element(self, root):
11✔
2339
        elem = root.find('tabular_legendre')
11✔
2340
        if elem is not None:
11✔
2341
            text = get_text(elem, 'enable')
11✔
2342
            self.tabular_legendre['enable'] = text in ('true', '1')
11✔
2343
            text = get_text(elem, 'num_points')
11✔
2344
            if text is not None:
11✔
2345
                self.tabular_legendre['num_points'] = int(text)
11✔
2346

2347
    def _temperature_from_xml_element(self, root):
11✔
2348
        text = get_text(root, 'temperature_default')
11✔
2349
        if text is not None:
11✔
2350
            self.temperature['default'] = float(text)
11✔
2351
        text = get_text(root, 'temperature_tolerance')
11✔
2352
        if text is not None:
11✔
2353
            self.temperature['tolerance'] = float(text)
×
2354
        text = get_text(root, 'temperature_method')
11✔
2355
        if text is not None:
11✔
2356
            self.temperature['method'] = text
11✔
2357
        text = get_elem_list(root, "temperature_range", float)
11✔
2358
        if text is not None:
11✔
2359
            self.temperature['range'] = text
11✔
2360
        text = get_text(root, 'temperature_multipole')
11✔
2361
        if text is not None:
11✔
2362
            self.temperature['multipole'] = text in ('true', '1')
11✔
2363

2364
    def _properties_file_from_xml_element(self, root):
11✔
2365
        text = get_text(root, 'properties_file')
11✔
2366
        if text is not None:
11✔
2367
            self.properties_file = text
11✔
2368

2369
    def _trace_from_xml_element(self, root):
11✔
2370
        text = get_elem_list(root, "trace", int)
11✔
2371
        if text is not None:
11✔
2372
            self.trace = text
11✔
2373

2374
    def _track_from_xml_element(self, root):
11✔
2375
        values = get_elem_list(root, "track", int)
11✔
2376
        if values is not None:
11✔
2377
            self.track = list(zip(values[::3], values[1::3], values[2::3]))
11✔
2378

2379
    def _ufs_mesh_from_xml_element(self, root, meshes):
11✔
2380
        text = get_text(root, 'ufs_mesh')
11✔
2381
        if text is None:
11✔
2382
            return
11✔
2383
        mesh_id = int(text)
11✔
2384
        if mesh_id not in meshes:
11✔
2385
            raise ValueError(f'Could not locate mesh with ID "{mesh_id}"')
×
2386
        self.ufs_mesh = meshes[mesh_id]
11✔
2387

2388
    def _resonance_scattering_from_xml_element(self, root):
11✔
2389
        elem = root.find('resonance_scattering')
11✔
2390
        if elem is not None:
11✔
2391
            keys = ('enable', 'method', 'energy_min', 'energy_max', 'nuclides')
11✔
2392
            for key in keys:
11✔
2393
                if key == 'nuclides':
11✔
2394
                    value = get_elem_list(elem, key, str)
11✔
2395
                else:
2396
                    value = get_text(elem, key)
11✔
2397
                if value is not None:
11✔
2398
                    if key == 'enable':
11✔
2399
                        value = value in ('true', '1')
11✔
2400
                    elif key in ('energy_min', 'energy_max'):
11✔
2401
                        value = float(value)
11✔
2402
                    self.resonance_scattering[key] = value
11✔
2403

2404
    def _create_fission_neutrons_from_xml_element(self, root):
11✔
2405
        text = get_text(root, 'create_fission_neutrons')
11✔
2406
        if text is not None:
11✔
2407
            self.create_fission_neutrons = text in ('true', '1')
11✔
2408

2409
    def _create_delayed_neutrons_from_xml_element(self, root):
11✔
2410
        text = get_text(root, 'create_delayed_neutrons')
11✔
2411
        if text is not None:
11✔
2412
            self.create_delayed_neutrons = text in ('true', '1')
11✔
2413

2414
    def _delayed_photon_scaling_from_xml_element(self, root):
11✔
2415
        text = get_text(root, 'delayed_photon_scaling')
11✔
2416
        if text is not None:
11✔
2417
            self.delayed_photon_scaling = text in ('true', '1')
×
2418

2419
    def _event_based_from_xml_element(self, root):
11✔
2420
        text = get_text(root, 'event_based')
11✔
2421
        if text is not None:
11✔
2422
            self.event_based = text in ('true', '1')
×
2423

2424
    def _max_particles_in_flight_from_xml_element(self, root):
11✔
2425
        text = get_text(root, 'max_particles_in_flight')
11✔
2426
        if text is not None:
11✔
2427
            self.max_particles_in_flight = int(text)
×
2428

2429
    def _max_particle_events_from_xml_element(self, root):
11✔
2430
        text = get_text(root, 'max_particle_events')
11✔
2431
        if text is not None:
11✔
2432
            self.max_particle_events = int(text)
11✔
2433

2434
    def _material_cell_offsets_from_xml_element(self, root):
11✔
2435
        text = get_text(root, 'material_cell_offsets')
11✔
2436
        if text is not None:
11✔
2437
            self.material_cell_offsets = text in ('true', '1')
×
2438

2439
    def _log_grid_bins_from_xml_element(self, root):
11✔
2440
        text = get_text(root, 'log_grid_bins')
11✔
2441
        if text is not None:
11✔
2442
            self.log_grid_bins = int(text)
11✔
2443

2444
    def _write_initial_source_from_xml_element(self, root):
11✔
2445
        text = get_text(root, 'write_initial_source')
11✔
2446
        if text is not None:
11✔
2447
            self.write_initial_source = text in ('true', '1')
11✔
2448

2449
    def _weight_window_generators_from_xml_element(self, root, meshes=None):
11✔
2450
        for elem in root.iter('weight_windows_generator'):
11✔
2451
            wwg = WeightWindowGenerator.from_xml_element(elem, meshes)
11✔
2452
            self.weight_window_generators.append(wwg)
11✔
2453

2454
    def _weight_windows_from_xml_element(self, root, meshes=None):
11✔
2455
        for elem in root.findall('weight_windows'):
11✔
2456
            ww = WeightWindows.from_xml_element(elem, meshes)
11✔
2457
            self.weight_windows.append(ww)
11✔
2458

2459
    def _weight_windows_on_from_xml_element(self, root):
11✔
2460
        text = get_text(root, 'weight_windows_on')
11✔
2461
        if text is not None:
11✔
2462
            self.weight_windows_on = text in ('true', '1')
×
2463

2464
    def _weight_windows_file_from_xml_element(self, root):
11✔
2465
        text = get_text(root, 'weight_windows_file')
11✔
2466
        if text is not None:
11✔
2467
            self.weight_windows_file = text
×
2468

2469
    def _weight_window_checkpoints_from_xml_element(self, root):
11✔
2470
        elem = root.find('weight_window_checkpoints')
11✔
2471
        if elem is None:
11✔
2472
            return
11✔
2473
        for key in ('collision', 'surface'):
11✔
2474
            value = get_text(elem, key)
11✔
2475
            if value is not None:
11✔
2476
                value = value in ('true', '1')
11✔
2477
                self.weight_window_checkpoints[key] = value
11✔
2478

2479
    def _max_history_splits_from_xml_element(self, root):
11✔
2480
        text = get_text(root, 'max_history_splits')
11✔
2481
        if text is not None:
11✔
2482
            self.max_history_splits = int(text)
11✔
2483

2484
    def _max_secondaries_from_xml_element(self, root):
11✔
2485
        text = get_text(root, 'max_secondaries')
11✔
2486
        if text is not None:
11✔
2487
            self.max_secondaries = int(text)
11✔
2488

2489
    def _max_tracks_from_xml_element(self, root):
11✔
2490
        text = get_text(root, 'max_tracks')
11✔
2491
        if text is not None:
11✔
2492
            self.max_tracks = int(text)
11✔
2493

2494
    def _random_ray_from_xml_element(self, root, meshes=None):
11✔
2495
        elem = root.find('random_ray')
11✔
2496
        if elem is not None:
11✔
2497
            self.random_ray = {}
11✔
2498
            for child in elem:
11✔
2499
                if child.tag in ('distance_inactive', 'distance_active', 'diagonal_stabilization_rho'):
11✔
2500
                    self.random_ray[child.tag] = float(child.text)
11✔
2501
                elif child.tag == 'source':
11✔
2502
                    source = SourceBase.from_xml_element(child)
11✔
2503
                    if child.find('bias') is not None:
11✔
2504
                        raise RuntimeError(
×
2505
                            "Ray source distributions should not be biased.")
2506
                    self.random_ray['ray_source'] = source
11✔
2507
                elif child.tag == 'volume_estimator':
11✔
2508
                    self.random_ray['volume_estimator'] = child.text
11✔
2509
                elif child.tag == 'source_shape':
11✔
2510
                    self.random_ray['source_shape'] = child.text
11✔
2511
                elif child.tag == 'volume_normalized_flux_tallies':
11✔
2512
                    self.random_ray['volume_normalized_flux_tallies'] = (
11✔
2513
                        child.text in ('true', '1')
2514
                    )
2515
                elif child.tag == 'adjoint':
11✔
2516
                    self.random_ray['adjoint'] = (
11✔
2517
                        child.text in ('true', '1')
2518
                    )
2519
                elif child.tag == 'sample_method':
11✔
2520
                    self.random_ray['sample_method'] = child.text
11✔
2521
                elif child.tag == 'source_region_meshes':
11✔
2522
                    self.random_ray['source_region_meshes'] = []
11✔
2523
                    for mesh_elem in child.findall('mesh'):
11✔
2524
                        mesh_id = int(get_text(mesh_elem, 'id'))
11✔
2525
                        if meshes and mesh_id in meshes:
11✔
2526
                            mesh = meshes[mesh_id]
11✔
2527
                        else:
2528
                            mesh = MeshBase.from_xml_element(mesh_elem)
×
2529
                        domains = []
11✔
2530
                        for domain_elem in mesh_elem.findall('domain'):
11✔
2531
                            domain_id = int(get_text(domain_elem, "id"))
11✔
2532
                            domain_type = get_text(domain_elem, "type")
11✔
2533
                            if domain_type == 'material':
11✔
2534
                                domain = openmc.Material(domain_id)
×
2535
                            elif domain_type == 'cell':
11✔
2536
                                domain = openmc.Cell(domain_id)
×
2537
                            elif domain_type == 'universe':
11✔
2538
                                domain = openmc.Universe(domain_id)
11✔
2539
                            domains.append(domain)
11✔
2540
                        self.random_ray['source_region_meshes'].append(
11✔
2541
                            (mesh, domains))
2542

2543
    def _use_decay_photons_from_xml_element(self, root):
11✔
2544
        text = get_text(root, 'use_decay_photons')
11✔
2545
        if text is not None:
11✔
2546
            self.use_decay_photons = text in ('true', '1')
×
2547

2548
    def _source_rejection_fraction_from_xml_element(self, root):
11✔
2549
        text = get_text(root, 'source_rejection_fraction')
11✔
2550
        if text is not None:
11✔
2551
            self.source_rejection_fraction = float(text)
11✔
2552

2553
    def _free_gas_threshold_from_xml_element(self, root):
11✔
2554
        text = get_text(root, 'free_gas_threshold')
11✔
2555
        if text is not None:
11✔
2556
            self.free_gas_threshold = float(text)
11✔
2557

2558
    def to_xml_element(self, mesh_memo=None):
11✔
2559
        """Create a 'settings' element to be written to an XML file.
2560

2561
        Parameters
2562
        ----------
2563
        mesh_memo : set of ints
2564
            A set of mesh IDs to keep track of whether a mesh has already been written.
2565
        """
2566
        # Reset xml element tree
2567
        element = ET.Element("settings")
11✔
2568

2569
        self._create_run_mode_subelement(element)
11✔
2570
        self._create_particles_subelement(element)
11✔
2571
        self._create_batches_subelement(element)
11✔
2572
        self._create_inactive_subelement(element)
11✔
2573
        self._create_max_lost_particles_subelement(element)
11✔
2574
        self._create_rel_max_lost_particles_subelement(element)
11✔
2575
        self._create_max_write_lost_particles_subelement(element)
11✔
2576
        self._create_generations_per_batch_subelement(element)
11✔
2577
        self._create_keff_trigger_subelement(element)
11✔
2578
        self._create_source_subelement(element, mesh_memo)
11✔
2579
        self._create_output_subelement(element)
11✔
2580
        self._create_statepoint_subelement(element)
11✔
2581
        self._create_sourcepoint_subelement(element)
11✔
2582
        self._create_surf_source_read_subelement(element)
11✔
2583
        self._create_surf_source_write_subelement(element)
11✔
2584
        self._create_collision_track_subelement(element)
11✔
2585
        self._create_confidence_intervals(element)
11✔
2586
        self._create_electron_treatment_subelement(element)
11✔
2587
        self._create_atomic_relaxation_subelement(element)
11✔
2588
        self._create_energy_mode_subelement(element)
11✔
2589
        self._create_max_order_subelement(element)
11✔
2590
        self._create_photon_transport_subelement(element)
11✔
2591
        self._create_uniform_source_sampling_subelement(element)
11✔
2592
        self._create_plot_seed_subelement(element)
11✔
2593
        self._create_ptables_subelement(element)
11✔
2594
        self._create_seed_subelement(element)
11✔
2595
        self._create_stride_subelement(element)
11✔
2596
        self._create_surface_grazing_cutoff_subelement(element)
11✔
2597
        self._create_surface_grazing_ratio_subelement(element)
11✔
2598
        self._create_survival_biasing_subelement(element)
11✔
2599
        self._create_cutoff_subelement(element)
11✔
2600
        self._create_entropy_mesh_subelement(element, mesh_memo)
11✔
2601
        self._create_temperature_field_subelement(element, mesh_memo)
11✔
2602
        self._create_trigger_subelement(element)
11✔
2603
        self._create_no_reduce_subelement(element)
11✔
2604
        self._create_verbosity_subelement(element)
11✔
2605
        self._create_ifp_n_generation_subelement(element)
11✔
2606
        self._create_tabular_legendre_subelements(element)
11✔
2607
        self._create_temperature_subelements(element)
11✔
2608
        self._create_properties_file_element(element)
11✔
2609
        self._create_trace_subelement(element)
11✔
2610
        self._create_track_subelement(element)
11✔
2611
        self._create_ufs_mesh_subelement(element, mesh_memo)
11✔
2612
        self._create_resonance_scattering_subelement(element)
11✔
2613
        self._create_volume_calcs_subelement(element)
11✔
2614
        self._create_create_fission_neutrons_subelement(element)
11✔
2615
        self._create_create_delayed_neutrons_subelement(element)
11✔
2616
        self._create_delayed_photon_scaling_subelement(element)
11✔
2617
        self._create_event_based_subelement(element)
11✔
2618
        self._create_max_particles_in_flight_subelement(element)
11✔
2619
        self._create_max_events_subelement(element)
11✔
2620
        self._create_material_cell_offsets_subelement(element)
11✔
2621
        self._create_log_grid_bins_subelement(element)
11✔
2622
        self._create_write_initial_source_subelement(element)
11✔
2623
        self._create_weight_windows_subelement(element, mesh_memo)
11✔
2624
        self._create_weight_windows_on_subelement(element)
11✔
2625
        self._create_weight_window_generators_subelement(element, mesh_memo)
11✔
2626
        self._create_weight_windows_file_element(element)
11✔
2627
        self._create_weight_window_checkpoints_subelement(element)
11✔
2628
        self._create_max_history_splits_subelement(element)
11✔
2629
        self._create_max_tracks_subelement(element)
11✔
2630
        self._create_max_secondaries_subelement(element)
11✔
2631
        self._create_random_ray_subelement(element, mesh_memo)
11✔
2632
        self._create_use_decay_photons_subelement(element)
11✔
2633
        self._create_source_rejection_fraction_subelement(element)
11✔
2634
        self._create_free_gas_threshold_subelement(element)
11✔
2635

2636
        # Clean the indentation in the file to be user-readable
2637
        clean_indentation(element)
11✔
2638

2639
        return element
11✔
2640

2641
    def export_to_xml(self, path: PathLike = 'settings.xml'):
11✔
2642
        """Export simulation settings to an XML file.
2643

2644
        Parameters
2645
        ----------
2646
        path : str
2647
            Path to file to write. Defaults to 'settings.xml'.
2648

2649
        """
2650
        root_element = self.to_xml_element()
11✔
2651

2652
        # Check if path is a directory
2653
        p = Path(path)
11✔
2654
        if p.is_dir():
11✔
2655
            p /= 'settings.xml'
11✔
2656

2657
        # Write the XML Tree to the settings.xml file
2658
        tree = ET.ElementTree(root_element)
11✔
2659
        tree.write(str(p), xml_declaration=True, encoding='utf-8')
11✔
2660

2661
    @classmethod
11✔
2662
    def from_xml_element(cls, elem, meshes=None):
11✔
2663
        """Generate settings from XML element
2664

2665
        Parameters
2666
        ----------
2667
        elem : lxml.etree._Element
2668
            XML element
2669
        meshes : dict or None
2670
            A dictionary with mesh IDs as keys and mesh instances as values that
2671
            have already been read from XML. Pre-existing meshes are used
2672
            and new meshes are added to when creating tally objects.
2673

2674
        Returns
2675
        -------
2676
        openmc.Settings
2677
            Settings object
2678

2679
        """
2680
        # read all meshes under the settings node and update
2681
        settings_meshes = _read_meshes(elem)
11✔
2682
        meshes = {} if meshes is None else meshes
11✔
2683
        meshes.update(settings_meshes)
11✔
2684

2685
        settings = cls()
11✔
2686
        settings._eigenvalue_from_xml_element(elem)
11✔
2687
        settings._run_mode_from_xml_element(elem)
11✔
2688
        settings._particles_from_xml_element(elem)
11✔
2689
        settings._batches_from_xml_element(elem)
11✔
2690
        settings._inactive_from_xml_element(elem)
11✔
2691
        settings._max_lost_particles_from_xml_element(elem)
11✔
2692
        settings._rel_max_lost_particles_from_xml_element(elem)
11✔
2693
        settings._max_write_lost_particles_from_xml_element(elem)
11✔
2694
        settings._generations_per_batch_from_xml_element(elem)
11✔
2695
        settings._keff_trigger_from_xml_element(elem)
11✔
2696
        settings._source_from_xml_element(elem, meshes)
11✔
2697
        settings._volume_calcs_from_xml_element(elem)
11✔
2698
        settings._output_from_xml_element(elem)
11✔
2699
        settings._statepoint_from_xml_element(elem)
11✔
2700
        settings._sourcepoint_from_xml_element(elem)
11✔
2701
        settings._surf_source_read_from_xml_element(elem)
11✔
2702
        settings._surf_source_write_from_xml_element(elem)
11✔
2703
        settings._collision_track_from_xml_element(elem)
11✔
2704
        settings._confidence_intervals_from_xml_element(elem)
11✔
2705
        settings._electron_treatment_from_xml_element(elem)
11✔
2706
        settings._atomic_relaxation_from_xml_element(elem)
11✔
2707
        settings._energy_mode_from_xml_element(elem)
11✔
2708
        settings._max_order_from_xml_element(elem)
11✔
2709
        settings._photon_transport_from_xml_element(elem)
11✔
2710
        settings._uniform_source_sampling_from_xml_element(elem)
11✔
2711
        settings._plot_seed_from_xml_element(elem)
11✔
2712
        settings._ptables_from_xml_element(elem)
11✔
2713
        settings._seed_from_xml_element(elem)
11✔
2714
        settings._stride_from_xml_element(elem)
11✔
2715
        settings._surface_grazing_cutoff_from_xml_element(elem)
11✔
2716
        settings._surface_grazing_ratio_from_xml_element(elem)
11✔
2717
        settings._survival_biasing_from_xml_element(elem)
11✔
2718
        settings._cutoff_from_xml_element(elem)
11✔
2719
        settings._entropy_mesh_from_xml_element(elem, meshes)
11✔
2720
        settings._temperature_field_from_xml_element(elem, meshes)
11✔
2721
        settings._trigger_from_xml_element(elem)
11✔
2722
        settings._no_reduce_from_xml_element(elem)
11✔
2723
        settings._verbosity_from_xml_element(elem)
11✔
2724
        settings._ifp_n_generation_from_xml_element(elem)
11✔
2725
        settings._tabular_legendre_from_xml_element(elem)
11✔
2726
        settings._temperature_from_xml_element(elem)
11✔
2727
        settings._properties_file_from_xml_element(elem)
11✔
2728
        settings._trace_from_xml_element(elem)
11✔
2729
        settings._track_from_xml_element(elem)
11✔
2730
        settings._ufs_mesh_from_xml_element(elem, meshes)
11✔
2731
        settings._resonance_scattering_from_xml_element(elem)
11✔
2732
        settings._create_fission_neutrons_from_xml_element(elem)
11✔
2733
        settings._create_delayed_neutrons_from_xml_element(elem)
11✔
2734
        settings._delayed_photon_scaling_from_xml_element(elem)
11✔
2735
        settings._event_based_from_xml_element(elem)
11✔
2736
        settings._max_particles_in_flight_from_xml_element(elem)
11✔
2737
        settings._max_particle_events_from_xml_element(elem)
11✔
2738
        settings._material_cell_offsets_from_xml_element(elem)
11✔
2739
        settings._log_grid_bins_from_xml_element(elem)
11✔
2740
        settings._write_initial_source_from_xml_element(elem)
11✔
2741
        settings._weight_windows_from_xml_element(elem, meshes)
11✔
2742
        settings._weight_windows_on_from_xml_element(elem)
11✔
2743
        settings._weight_windows_file_from_xml_element(elem)
11✔
2744
        settings._weight_window_generators_from_xml_element(elem, meshes)
11✔
2745
        settings._weight_window_checkpoints_from_xml_element(elem)
11✔
2746
        settings._max_history_splits_from_xml_element(elem)
11✔
2747
        settings._max_tracks_from_xml_element(elem)
11✔
2748
        settings._max_secondaries_from_xml_element(elem)
11✔
2749
        settings._random_ray_from_xml_element(elem, meshes)
11✔
2750
        settings._use_decay_photons_from_xml_element(elem)
11✔
2751
        settings._source_rejection_fraction_from_xml_element(elem)
11✔
2752
        settings._free_gas_threshold_from_xml_element(elem)
11✔
2753

2754
        return settings
11✔
2755

2756
    @classmethod
11✔
2757
    def from_xml(cls, path: PathLike = 'settings.xml'):
11✔
2758
        """Generate settings from XML file
2759

2760
        .. versionadded:: 0.13.0
2761

2762
        Parameters
2763
        ----------
2764
        path : str, optional
2765
            Path to settings XML file
2766

2767
        Returns
2768
        -------
2769
        openmc.Settings
2770
            Settings object
2771

2772
        """
2773
        parser = ET.XMLParser(huge_tree=True)
11✔
2774
        tree = ET.parse(path, parser=parser)
11✔
2775
        root = tree.getroot()
11✔
2776
        meshes = _read_meshes(root)
11✔
2777
        return cls.from_xml_element(root, meshes)
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