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

openmc-dev / openmc / 23561323664

25 Mar 2026 08:00PM UTC coverage: 81.332% (+0.006%) from 81.326%
23561323664

Pull #3717

github

web-flow
Merge 11ca70b1f into 6cd39073b
Pull Request #3717: Local adjoint source for Random Ray

17726 of 25626 branches covered (69.17%)

Branch coverage included in aggregate %.

366 of 400 new or added lines in 9 files covered. (91.5%)

2 existing lines in 2 files now uncovered.

58360 of 67924 relevant lines covered (85.92%)

44892866.35 hits per line

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

93.94
/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 traceback
11✔
8

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

22

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

30

31
_RES_SCAT_METHODS = {'dbrc', 'rvs'}
11✔
32

33

34
class Settings:
11✔
35
    """Settings used for an OpenMC simulation.
36

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

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

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

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

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

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

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

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

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

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

154
        .. versionadded:: 0.13
155
    max_secondaries : int
156
        Maximum secondary bank size
157

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

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

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

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

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

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

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

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

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

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

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

370
        .. versionadded:: 0.14.0
371
    weight_window_generators : WeightWindowGenerator or iterable of WeightWindowGenerator
372
        Weight windows generation parameters to apply during simulation
373

374
        .. versionadded:: 0.14.0
375

376
    create_delayed_neutrons : bool
377
        Whether delayed neutrons are created in fission.
378

379
        .. versionadded:: 0.13.3
380
    weight_windows_on : bool
381
        Whether weight windows are enabled
382

383
        .. versionadded:: 0.13
384

385
    weight_windows_file: Pathlike
386
        Path to a weight window file to load during simulation initialization
387

388
        .. versionadded::0.14.0
389
    write_initial_source : bool
390
        Indicate whether to write the initial source distribution to file
391
    """
392

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

404
        # Energy mode subelement
405
        self._energy_mode = None
11✔
406
        self._max_order = None
11✔
407

408
        # Source subelement
409
        self._source = cv.CheckedList(SourceBase, 'source distributions')
11✔
410
        self._source_rejection_fraction = None
11✔
411

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

427
        # Shannon entropy mesh
428
        self._entropy_mesh = None
11✔
429

430
        # Trigger subelement
431
        self._trigger_active = None
11✔
432
        self._trigger_max_batches = None
11✔
433
        self._trigger_batch_interval = None
11✔
434

435
        self._output = None
11✔
436

437
        # Iterated Fission Probability
438
        self._ifp_n_generation = None
11✔
439

440
        # Collision track feature
441
        self._collision_track = {}
11✔
442

443
        # Output options
444
        self._statepoint = {}
11✔
445
        self._sourcepoint = {}
11✔
446

447
        self._surf_source_read = {}
11✔
448
        self._surf_source_write = {}
11✔
449

450
        self._no_reduce = None
11✔
451

452
        self._verbosity = None
11✔
453

454
        self._trace = None
11✔
455
        self._track = None
11✔
456

457
        self._tabular_legendre = {}
11✔
458

459
        self._temperature = {}
11✔
460

461
        # Cutoff subelement
462
        self._cutoff = None
11✔
463

464
        # Uniform fission source subelement
465
        self._ufs_mesh = None
11✔
466

467
        self._resonance_scattering = {}
11✔
468
        self._volume_calculations = cv.CheckedList(
11✔
469
            VolumeCalculation, 'volume calculations')
470

471
        self._create_fission_neutrons = None
11✔
472
        self._create_delayed_neutrons = None
11✔
473
        self._delayed_photon_scaling = None
11✔
474
        self._material_cell_offsets = None
11✔
475
        self._log_grid_bins = None
11✔
476

477
        self._event_based = None
11✔
478
        self._max_particles_in_flight = None
11✔
479
        self._max_particle_events = None
11✔
480
        self._write_initial_source = None
11✔
481
        self._weight_windows = WeightWindowsList()
11✔
482
        self._weight_window_generators = cv.CheckedList(
11✔
483
            WeightWindowGenerator, 'weight window generators')
484
        self._weight_windows_on = None
11✔
485
        self._weight_windows_file = None
11✔
486
        self._weight_window_checkpoints = {}
11✔
487
        self._max_history_splits = None
11✔
488
        self._max_tracks = None
11✔
489
        self._max_secondaries = None
11✔
490
        self._use_decay_photons = None
11✔
491

492
        self._random_ray = {}
11✔
493

494
        for key, value in kwargs.items():
11✔
495
            setattr(self, key, value)
11✔
496

497
    def __setattr__(self, name: str, value):
11✔
498
        if not name.startswith('_'):
11✔
499
            try:
11✔
500
                getattr(self, name)
11✔
501
            except AttributeError as e:
×
502
                msg, = traceback.format_exception_only(e)
×
503
                msg = msg.strip().split(maxsplit=1)[-1]
×
504
                warnings.warn(msg, stacklevel=2)
×
505
        super().__setattr__(name, value)
11✔
506

507
    @property
11✔
508
    def run_mode(self) -> str:
11✔
509
        return self._run_mode.value
11✔
510

511
    @run_mode.setter
11✔
512
    def run_mode(self, run_mode: str):
11✔
513
        cv.check_value('run mode', run_mode, {x.value for x in RunMode})
11✔
514
        for mode in RunMode:
11✔
515
            if mode.value == run_mode:
11✔
516
                self._run_mode = mode
11✔
517

518
    @property
11✔
519
    def batches(self) -> int:
11✔
520
        return self._batches
11✔
521

522
    @batches.setter
11✔
523
    def batches(self, batches: int):
11✔
524
        cv.check_type('batches', batches, Integral)
11✔
525
        cv.check_greater_than('batches', batches, 0)
11✔
526
        self._batches = batches
11✔
527

528
    @property
11✔
529
    def generations_per_batch(self) -> int:
11✔
530
        return self._generations_per_batch
11✔
531

532
    @generations_per_batch.setter
11✔
533
    def generations_per_batch(self, generations_per_batch: int):
11✔
534
        cv.check_type('generations per batch', generations_per_batch, Integral)
11✔
535
        cv.check_greater_than('generations per batch',
11✔
536
                              generations_per_batch, 0)
537
        self._generations_per_batch = generations_per_batch
11✔
538

539
    @property
11✔
540
    def inactive(self) -> int:
11✔
541
        return self._inactive
11✔
542

543
    @inactive.setter
11✔
544
    def inactive(self, inactive: int):
11✔
545
        cv.check_type('inactive batches', inactive, Integral)
11✔
546
        cv.check_greater_than('inactive batches', inactive, 0, True)
11✔
547
        self._inactive = inactive
11✔
548

549
    @property
11✔
550
    def max_lost_particles(self) -> int:
11✔
551
        return self._max_lost_particles
11✔
552

553
    @max_lost_particles.setter
11✔
554
    def max_lost_particles(self, max_lost_particles: int):
11✔
555
        cv.check_type('max_lost_particles', max_lost_particles, Integral)
11✔
556
        cv.check_greater_than('max_lost_particles', max_lost_particles, 0)
11✔
557
        self._max_lost_particles = max_lost_particles
11✔
558

559
    @property
11✔
560
    def rel_max_lost_particles(self) -> float:
11✔
561
        return self._rel_max_lost_particles
11✔
562

563
    @rel_max_lost_particles.setter
11✔
564
    def rel_max_lost_particles(self, rel_max_lost_particles: float):
11✔
565
        cv.check_type('rel_max_lost_particles', rel_max_lost_particles, Real)
11✔
566
        cv.check_greater_than('rel_max_lost_particles',
11✔
567
                              rel_max_lost_particles, 0)
568
        cv.check_less_than('rel_max_lost_particles', rel_max_lost_particles, 1)
11✔
569
        self._rel_max_lost_particles = rel_max_lost_particles
11✔
570

571
    @property
11✔
572
    def max_write_lost_particles(self) -> int:
11✔
573
        return self._max_write_lost_particles
11✔
574

575
    @max_write_lost_particles.setter
11✔
576
    def max_write_lost_particles(self, max_write_lost_particles: int):
11✔
577
        cv.check_type('max_write_lost_particles',
11✔
578
                      max_write_lost_particles, Integral)
579
        cv.check_greater_than('max_write_lost_particles',
11✔
580
                              max_write_lost_particles, 0)
581
        self._max_write_lost_particles = max_write_lost_particles
11✔
582

583
    @property
11✔
584
    def particles(self) -> int:
11✔
585
        return self._particles
11✔
586

587
    @particles.setter
11✔
588
    def particles(self, particles: int):
11✔
589
        cv.check_type('particles', particles, Integral)
11✔
590
        cv.check_greater_than('particles', particles, 0)
11✔
591
        self._particles = particles
11✔
592

593
    @property
11✔
594
    def keff_trigger(self) -> dict:
11✔
595
        return self._keff_trigger
11✔
596

597
    @keff_trigger.setter
11✔
598
    def keff_trigger(self, keff_trigger: dict):
11✔
599
        if not isinstance(keff_trigger, dict):
11✔
600
            msg = f'Unable to set a trigger on keff from "{keff_trigger}" ' \
×
601
                'which is not a Python dictionary'
602
            raise ValueError(msg)
×
603

604
        elif 'type' not in keff_trigger:
11✔
605
            msg = f'Unable to set a trigger on keff from "{keff_trigger}" ' \
×
606
                'which does not have a "type" key'
607
            raise ValueError(msg)
×
608

609
        elif keff_trigger['type'] not in ['variance', 'std_dev', 'rel_err']:
11✔
610
            msg = 'Unable to set a trigger on keff with ' \
×
611
                  'type "{0}"'.format(keff_trigger['type'])
612
            raise ValueError(msg)
×
613

614
        elif 'threshold' not in keff_trigger:
11✔
615
            msg = f'Unable to set a trigger on keff from "{keff_trigger}" ' \
×
616
                'which does not have a "threshold" key'
617
            raise ValueError(msg)
×
618

619
        elif not isinstance(keff_trigger['threshold'], Real):
11✔
620
            msg = 'Unable to set a trigger on keff with ' \
×
621
                  'threshold "{0}"'.format(keff_trigger['threshold'])
622
            raise ValueError(msg)
×
623

624
        self._keff_trigger = keff_trigger
11✔
625

626
    @property
11✔
627
    def energy_mode(self) -> str:
11✔
628
        return self._energy_mode
11✔
629

630
    @energy_mode.setter
11✔
631
    def energy_mode(self, energy_mode: str):
11✔
632
        cv.check_value('energy mode', energy_mode,
11✔
633
                       ['continuous-energy', 'multi-group'])
634
        self._energy_mode = energy_mode
11✔
635

636
    @property
11✔
637
    def max_order(self) -> int:
11✔
638
        return self._max_order
11✔
639

640
    @max_order.setter
11✔
641
    def max_order(self, max_order: int | None):
11✔
642
        if max_order is not None:
11✔
643
            cv.check_type('maximum scattering order', max_order, Integral)
11✔
644
            cv.check_greater_than('maximum scattering order', max_order, 0,
11✔
645
                                  True)
646
        self._max_order = max_order
11✔
647

648
    @property
11✔
649
    def source(self) -> list[SourceBase]:
11✔
650
        return self._source
11✔
651

652
    @source.setter
11✔
653
    def source(self, source: SourceBase | Iterable[SourceBase]):
11✔
654
        if not isinstance(source, MutableSequence):
11✔
655
            source = [source]
11✔
656
        self._source = cv.CheckedList(
11✔
657
            SourceBase, 'source distributions', source)
658

659
    @property
11✔
660
    def confidence_intervals(self) -> bool:
11✔
661
        return self._confidence_intervals
11✔
662

663
    @confidence_intervals.setter
11✔
664
    def confidence_intervals(self, confidence_intervals: bool):
11✔
665
        cv.check_type('confidence interval', confidence_intervals, bool)
11✔
666
        self._confidence_intervals = confidence_intervals
11✔
667

668
    @property
11✔
669
    def electron_treatment(self) -> str:
11✔
670
        return self._electron_treatment
11✔
671

672
    @electron_treatment.setter
11✔
673
    def electron_treatment(self, electron_treatment: str):
11✔
674
        cv.check_value('electron treatment',
11✔
675
                       electron_treatment, ['led', 'ttb'])
676
        self._electron_treatment = electron_treatment
11✔
677

678
    @property
11✔
679
    def atomic_relaxation(self) -> bool:
11✔
680
        return self._atomic_relaxation
11✔
681

682
    @atomic_relaxation.setter
11✔
683
    def atomic_relaxation(self, atomic_relaxation: bool):
11✔
684
        cv.check_type('atomic relaxation', atomic_relaxation, bool)
11✔
685
        self._atomic_relaxation = atomic_relaxation
11✔
686

687
    @property
11✔
688
    def ptables(self) -> bool:
11✔
689
        return self._ptables
11✔
690

691
    @ptables.setter
11✔
692
    def ptables(self, ptables: bool):
11✔
693
        cv.check_type('probability tables', ptables, bool)
11✔
694
        self._ptables = ptables
11✔
695

696
    @property
11✔
697
    def photon_transport(self) -> bool:
11✔
698
        return self._photon_transport
11✔
699

700
    @photon_transport.setter
11✔
701
    def photon_transport(self, photon_transport: bool):
11✔
702
        cv.check_type('photon transport', photon_transport, bool)
11✔
703
        self._photon_transport = photon_transport
11✔
704

705
    @property
11✔
706
    def uniform_source_sampling(self) -> bool:
11✔
707
        return self._uniform_source_sampling
11✔
708

709
    @uniform_source_sampling.setter
11✔
710
    def uniform_source_sampling(self, uniform_source_sampling: bool):
11✔
711
        cv.check_type('strength as weights', uniform_source_sampling, bool)
11✔
712
        self._uniform_source_sampling = uniform_source_sampling
11✔
713

714
    @property
11✔
715
    def plot_seed(self):
11✔
716
        return self._plot_seed
11✔
717

718
    @plot_seed.setter
11✔
719
    def plot_seed(self, seed):
11✔
720
        cv.check_type('random plot color seed', seed, Integral)
11✔
721
        cv.check_greater_than('random plot color seed', seed, 0)
11✔
722
        self._plot_seed = seed
11✔
723

724
    @property
11✔
725
    def seed(self) -> int:
11✔
726
        return self._seed
11✔
727

728
    @seed.setter
11✔
729
    def seed(self, seed: int):
11✔
730
        cv.check_type('random number generator seed', seed, Integral)
11✔
731
        cv.check_greater_than('random number generator seed', seed, 0)
11✔
732
        self._seed = seed
11✔
733

734
    @property
11✔
735
    def stride(self) -> int:
11✔
736
        return self._stride
11✔
737

738
    @stride.setter
11✔
739
    def stride(self, stride: int):
11✔
740
        cv.check_type('random number generator stride', stride, Integral)
11✔
741
        cv.check_greater_than('random number generator stride', stride, 0)
11✔
742
        self._stride = stride
11✔
743

744
    @property
11✔
745
    def surface_grazing_cutoff(self) -> float:
11✔
746
        return self._surface_grazing_cutoff
11✔
747

748
    @surface_grazing_cutoff.setter
11✔
749
    def surface_grazing_cutoff(self, surface_grazing_cutoff: float):
11✔
750
        cv.check_type('surface grazing cutoff', surface_grazing_cutoff, float)
11✔
751
        cv.check_greater_than('surface grazing cutoff', surface_grazing_cutoff, 0.0)
11✔
752
        cv.check_less_than('surface grazing cutoff', surface_grazing_cutoff, 1.0)
11✔
753
        self._surface_grazing_cutoff = surface_grazing_cutoff
11✔
754

755
    @property
11✔
756
    def surface_grazing_ratio(self) -> float:
11✔
757
        return self._surface_grazing_ratio
11✔
758

759
    @surface_grazing_ratio.setter
11✔
760
    def surface_grazing_ratio(self, surface_grazing_ratio: float):
11✔
761
        cv.check_type('surface grazing ratio', surface_grazing_ratio, float)
11✔
762
        cv.check_greater_than('surface grazing ratio', surface_grazing_ratio, 0.0)
11✔
763
        self._surface_grazing_ratio = surface_grazing_ratio
11✔
764

765
    @property
11✔
766
    def survival_biasing(self) -> bool:
11✔
767
        return self._survival_biasing
11✔
768

769
    @survival_biasing.setter
11✔
770
    def survival_biasing(self, survival_biasing: bool):
11✔
771
        cv.check_type('survival biasing', survival_biasing, bool)
11✔
772
        self._survival_biasing = survival_biasing
11✔
773

774
    @property
11✔
775
    def entropy_mesh(self) -> RegularMesh:
11✔
776
        return self._entropy_mesh
11✔
777

778
    @entropy_mesh.setter
11✔
779
    def entropy_mesh(self, entropy: RegularMesh):
11✔
780
        cv.check_type('entropy mesh', entropy, RegularMesh)
11✔
781
        self._entropy_mesh = entropy
11✔
782

783
    @property
11✔
784
    def trigger_active(self) -> bool:
11✔
785
        return self._trigger_active
11✔
786

787
    @trigger_active.setter
11✔
788
    def trigger_active(self, trigger_active: bool):
11✔
789
        cv.check_type('trigger active', trigger_active, bool)
11✔
790
        self._trigger_active = trigger_active
11✔
791

792
    @property
11✔
793
    def trigger_max_batches(self) -> int:
11✔
794
        return self._trigger_max_batches
11✔
795

796
    @trigger_max_batches.setter
11✔
797
    def trigger_max_batches(self, trigger_max_batches: int):
11✔
798
        cv.check_type('trigger maximum batches', trigger_max_batches, Integral)
11✔
799
        cv.check_greater_than('trigger maximum batches',
11✔
800
                              trigger_max_batches, 0)
801
        self._trigger_max_batches = trigger_max_batches
11✔
802

803
    @property
11✔
804
    def trigger_batch_interval(self) -> int:
11✔
805
        return self._trigger_batch_interval
11✔
806

807
    @trigger_batch_interval.setter
11✔
808
    def trigger_batch_interval(self, trigger_batch_interval: int):
11✔
809
        cv.check_type('trigger batch interval',
11✔
810
                      trigger_batch_interval, Integral)
811
        cv.check_greater_than('trigger batch interval',
11✔
812
                              trigger_batch_interval, 0)
813
        self._trigger_batch_interval = trigger_batch_interval
11✔
814

815
    @property
11✔
816
    def output(self) -> dict:
11✔
817
        return self._output
11✔
818

819
    @output.setter
11✔
820
    def output(self, output: dict):
11✔
821
        cv.check_type('output', output, Mapping)
11✔
822
        for key, value in output.items():
11✔
823
            cv.check_value('output key', key, ('summary', 'tallies', 'path'))
11✔
824
            if key in ('summary', 'tallies'):
11✔
825
                cv.check_type(f"output['{key}']", value, bool)
11✔
826
            else:
827
                cv.check_type("output['path']", value, str)
11✔
828
        self._output = output
11✔
829

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

834
    @sourcepoint.setter
11✔
835
    def sourcepoint(self, sourcepoint: dict):
11✔
836
        cv.check_type('sourcepoint options', sourcepoint, Mapping)
11✔
837
        for key, value in sourcepoint.items():
11✔
838
            if key == 'batches':
11✔
839
                cv.check_type('sourcepoint batches', value, Iterable, Integral)
11✔
840
                for batch in value:
11✔
841
                    cv.check_greater_than('sourcepoint batch', batch, 0)
11✔
842
            elif key == 'separate':
11✔
843
                cv.check_type('sourcepoint separate', value, bool)
11✔
844
            elif key == 'write':
11✔
845
                cv.check_type('sourcepoint write', value, bool)
11✔
846
            elif key == 'overwrite':
11✔
847
                cv.check_type('sourcepoint overwrite', value, bool)
11✔
848
            elif key == 'mcpl':
11✔
849
                cv.check_type('sourcepoint mcpl', value, bool)
11✔
850
            else:
851
                raise ValueError(f"Unknown key '{key}' encountered when "
×
852
                                 "setting sourcepoint options.")
853
        self._sourcepoint = sourcepoint
11✔
854

855
    @property
11✔
856
    def statepoint(self) -> dict:
11✔
857
        return self._statepoint
11✔
858

859
    @statepoint.setter
11✔
860
    def statepoint(self, statepoint: dict):
11✔
861
        cv.check_type('statepoint options', statepoint, Mapping)
11✔
862
        for key, value in statepoint.items():
11✔
863
            if key == 'batches':
11✔
864
                cv.check_type('statepoint batches', value, Iterable, Integral)
11✔
865
                for batch in value:
11✔
866
                    cv.check_greater_than('statepoint batch', batch, 0)
11✔
867
            else:
868
                raise ValueError(f"Unknown key '{key}' encountered when "
×
869
                                 "setting statepoint options.")
870
        self._statepoint = statepoint
11✔
871

872
    @property
11✔
873
    def surf_source_read(self) -> dict:
11✔
874
        return self._surf_source_read
11✔
875

876
    @surf_source_read.setter
11✔
877
    def surf_source_read(self, ssr: dict):
11✔
878
        cv.check_type('surface source reading options', ssr, Mapping)
11✔
879
        for key, value in ssr.items():
11✔
880
            cv.check_value('surface source reading key', key,
11✔
881
                           ('path'))
882
            if key == 'path':
11✔
883
                cv.check_type('path to surface source file', value, PathLike)
11✔
884
        self._surf_source_read = dict(ssr)
11✔
885

886
        # Resolve path to surface source file
887
        if 'path' in ssr:
11✔
888
            self._surf_source_read['path'] = input_path(ssr['path'])
11✔
889

890
    @property
11✔
891
    def surf_source_write(self) -> dict:
11✔
892
        return self._surf_source_write
11✔
893

894
    @surf_source_write.setter
11✔
895
    def surf_source_write(self, surf_source_write: dict):
11✔
896
        cv.check_type("surface source writing options",
11✔
897
                      surf_source_write, Mapping)
898
        for key, value in surf_source_write.items():
11✔
899
            cv.check_value(
11✔
900
                "surface source writing key",
901
                key,
902
                ("surface_ids", "max_particles", "max_source_files",
903
                 "mcpl", "cell", "cellfrom", "cellto"),
904
            )
905
            if key == "surface_ids":
11✔
906
                cv.check_type(
11✔
907
                    "surface ids for source banking", value, Iterable, Integral
908
                )
909
                for surf_id in value:
11✔
910
                    cv.check_greater_than(
11✔
911
                        "surface id for source banking", surf_id, 0)
912

913
            elif key == "mcpl":
11✔
914
                cv.check_type("write to an MCPL-format file", value, bool)
11✔
915
            elif key in ("max_particles", "max_source_files", "cell", "cellfrom", "cellto"):
11✔
916
                name = {
11✔
917
                    "max_particles": "maximum particle banks on surfaces per process",
918
                    "max_source_files": "maximun surface source files to be written",
919
                    "cell": "Cell ID for source banking (from or to)",
920
                    "cellfrom": "Cell ID for source banking (from only)",
921
                    "cellto": "Cell ID for source banking (to only)",
922
                }[key]
923
                cv.check_type(name, value, Integral)
11✔
924
                cv.check_greater_than(name, value, 0)
11✔
925

926
        self._surf_source_write = surf_source_write
11✔
927

928
    @property
11✔
929
    def collision_track(self) -> dict:
11✔
930
        return self._collision_track
11✔
931

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

999
        self._collision_track = collision_track
11✔
1000

1001
    @property
11✔
1002
    def no_reduce(self) -> bool:
11✔
1003
        return self._no_reduce
11✔
1004

1005
    @no_reduce.setter
11✔
1006
    def no_reduce(self, no_reduce: bool):
11✔
1007
        cv.check_type('no reduction option', no_reduce, bool)
11✔
1008
        self._no_reduce = no_reduce
11✔
1009

1010
    @property
11✔
1011
    def verbosity(self) -> int:
11✔
1012
        return self._verbosity
11✔
1013

1014
    @verbosity.setter
11✔
1015
    def verbosity(self, verbosity: int):
11✔
1016
        cv.check_type('verbosity', verbosity, Integral)
11✔
1017
        cv.check_greater_than('verbosity', verbosity, 1, True)
11✔
1018
        cv.check_less_than('verbosity', verbosity, 10, True)
11✔
1019
        self._verbosity = verbosity
11✔
1020

1021
    @property
11✔
1022
    def ifp_n_generation(self) -> int:
11✔
1023
        return self._ifp_n_generation
11✔
1024

1025
    @ifp_n_generation.setter
11✔
1026
    def ifp_n_generation(self, ifp_n_generation: int):
11✔
1027
        if ifp_n_generation is not None:
11✔
1028
            cv.check_type("number of generations", ifp_n_generation, Integral)
11✔
1029
            cv.check_greater_than("number of generations", ifp_n_generation, 0)
11✔
1030
        self._ifp_n_generation = ifp_n_generation
11✔
1031

1032
    @property
11✔
1033
    def tabular_legendre(self) -> dict:
11✔
1034
        return self._tabular_legendre
11✔
1035

1036
    @tabular_legendre.setter
11✔
1037
    def tabular_legendre(self, tabular_legendre: dict):
11✔
1038
        cv.check_type('tabular_legendre settings', tabular_legendre, Mapping)
11✔
1039
        for key, value in tabular_legendre.items():
11✔
1040
            cv.check_value('tabular_legendre key', key,
11✔
1041
                           ['enable', 'num_points'])
1042
            if key == 'enable':
11✔
1043
                cv.check_type('enable tabular_legendre', value, bool)
11✔
1044
            elif key == 'num_points':
11✔
1045
                cv.check_type('num_points tabular_legendre', value, Integral)
11✔
1046
                cv.check_greater_than('num_points tabular_legendre', value, 0)
11✔
1047
        self._tabular_legendre = tabular_legendre
11✔
1048

1049
    @property
11✔
1050
    def temperature(self) -> dict:
11✔
1051
        return self._temperature
11✔
1052

1053
    @temperature.setter
11✔
1054
    def temperature(self, temperature: dict):
11✔
1055

1056
        cv.check_type('temperature settings', temperature, Mapping)
11✔
1057
        for key, value in temperature.items():
11✔
1058
            cv.check_value('temperature key', key,
11✔
1059
                           ['default', 'method', 'tolerance', 'multipole',
1060
                            'range'])
1061
            if key == 'default':
11✔
1062
                cv.check_type('default temperature', value, Real)
11✔
1063
            elif key == 'method':
11✔
1064
                cv.check_value('temperature method', value,
11✔
1065
                               ['nearest', 'interpolation'])
1066
            elif key == 'tolerance':
11✔
1067
                cv.check_type('temperature tolerance', value, Real)
11✔
1068
            elif key == 'multipole':
11✔
1069
                cv.check_type('temperature multipole', value, bool)
11✔
1070
            elif key == 'range':
11✔
1071
                cv.check_length('temperature range', value, 2)
11✔
1072
                for T in value:
11✔
1073
                    cv.check_type('temperature', T, Real)
11✔
1074

1075
        self._temperature = temperature
11✔
1076

1077
    @property
11✔
1078
    def properties_file(self) -> PathLike | None:
11✔
1079
        return self._properties_file
11✔
1080

1081
    @properties_file.setter
11✔
1082
    def properties_file(self, value: PathLike | None):
11✔
1083
        if value is None:
11✔
1084
            self._properties_file = None
×
1085
        else:
1086
            cv.check_type('properties file', value, PathLike)
11✔
1087
            self._properties_file = input_path(value)
11✔
1088

1089
    @property
11✔
1090
    def trace(self) -> Iterable:
11✔
1091
        return self._trace
11✔
1092

1093
    @trace.setter
11✔
1094
    def trace(self, trace: Iterable):
11✔
1095
        cv.check_type('trace', trace, Iterable, Integral)
11✔
1096
        cv.check_length('trace', trace, 3)
11✔
1097
        cv.check_greater_than('trace batch', trace[0], 0)
11✔
1098
        cv.check_greater_than('trace generation', trace[1], 0)
11✔
1099
        cv.check_greater_than('trace particle', trace[2], 0)
11✔
1100
        self._trace = trace
11✔
1101

1102
    @property
11✔
1103
    def track(self) -> Iterable[Iterable[int]]:
11✔
1104
        return self._track
11✔
1105

1106
    @track.setter
11✔
1107
    def track(self, track: Iterable[Iterable[int]]):
11✔
1108
        cv.check_type('track', track, Sequence)
11✔
1109
        for t in track:
11✔
1110
            if len(t) != 3:
11✔
1111
                msg = f'Unable to set the track to "{t}" since its length is not 3'
×
1112
                raise ValueError(msg)
×
1113
            cv.check_greater_than('track batch', t[0], 0)
11✔
1114
            cv.check_greater_than('track generation', t[1], 0)
11✔
1115
            cv.check_greater_than('track particle', t[2], 0)
11✔
1116
            cv.check_type('track batch', t[0], Integral)
11✔
1117
            cv.check_type('track generation', t[1], Integral)
11✔
1118
            cv.check_type('track particle', t[2], Integral)
11✔
1119
        self._track = track
11✔
1120

1121
    @property
11✔
1122
    def cutoff(self) -> dict:
11✔
1123
        return self._cutoff
11✔
1124

1125
    @cutoff.setter
11✔
1126
    def cutoff(self, cutoff: dict):
11✔
1127
        if not isinstance(cutoff, Mapping):
11✔
1128
            msg = f'Unable to set cutoff from "{cutoff}" which is not a '\
×
1129
                'Python dictionary'
1130
            raise ValueError(msg)
×
1131
        for key in cutoff:
11✔
1132
            if key == 'weight':
11✔
1133
                cv.check_type('weight cutoff', cutoff[key], Real)
11✔
1134
                cv.check_greater_than('weight cutoff', cutoff[key], 0.0)
11✔
1135
            elif key == 'weight_avg':
11✔
1136
                cv.check_type('average survival weight', cutoff[key], Real)
11✔
1137
                cv.check_greater_than('average survival weight',
11✔
1138
                                      cutoff[key], 0.0)
1139
            elif key == 'survival_normalization':
11✔
1140
                cv.check_type('survival normalization', cutoff[key], bool)
11✔
1141
            elif key in ['energy_neutron', 'energy_photon', 'energy_electron',
11✔
1142
                         'energy_positron']:
1143
                cv.check_type('energy cutoff', cutoff[key], Real)
11✔
1144
                cv.check_greater_than('energy cutoff', cutoff[key], 0.0)
11✔
1145
            else:
1146
                msg = f'Unable to set cutoff to "{key}" which is unsupported ' \
11✔
1147
                    'by OpenMC'
1148

1149
        self._cutoff = cutoff
11✔
1150

1151
    @property
11✔
1152
    def ufs_mesh(self) -> RegularMesh:
11✔
1153
        return self._ufs_mesh
11✔
1154

1155
    @ufs_mesh.setter
11✔
1156
    def ufs_mesh(self, ufs_mesh: RegularMesh):
11✔
1157
        cv.check_type('UFS mesh', ufs_mesh, RegularMesh)
11✔
1158
        cv.check_length('UFS mesh dimension', ufs_mesh.dimension, 3)
11✔
1159
        cv.check_length('UFS mesh lower-left corner', ufs_mesh.lower_left, 3)
11✔
1160
        cv.check_length('UFS mesh upper-right corner', ufs_mesh.upper_right, 3)
11✔
1161
        self._ufs_mesh = ufs_mesh
11✔
1162

1163
    @property
11✔
1164
    def resonance_scattering(self) -> dict:
11✔
1165
        return self._resonance_scattering
11✔
1166

1167
    @resonance_scattering.setter
11✔
1168
    def resonance_scattering(self, res: dict):
11✔
1169
        cv.check_type('resonance scattering settings', res, Mapping)
11✔
1170
        keys = ('enable', 'method', 'energy_min', 'energy_max', 'nuclides')
11✔
1171
        for key, value in res.items():
11✔
1172
            cv.check_value('resonance scattering dictionary key', key, keys)
11✔
1173
            if key == 'enable':
11✔
1174
                cv.check_type('resonance scattering enable', value, bool)
11✔
1175
            elif key == 'method':
11✔
1176
                cv.check_value('resonance scattering method', value,
11✔
1177
                               _RES_SCAT_METHODS)
1178
            elif key == 'energy_min':
11✔
1179
                name = 'resonance scattering minimum energy'
11✔
1180
                cv.check_type(name, value, Real)
11✔
1181
                cv.check_greater_than(name, value, 0)
11✔
1182
            elif key == 'energy_max':
11✔
1183
                name = 'resonance scattering minimum energy'
11✔
1184
                cv.check_type(name, value, Real)
11✔
1185
                cv.check_greater_than(name, value, 0)
11✔
1186
            elif key == 'nuclides':
11✔
1187
                cv.check_type('resonance scattering nuclides', value,
11✔
1188
                              Iterable, str)
1189
        self._resonance_scattering = res
11✔
1190

1191
    @property
11✔
1192
    def volume_calculations(self) -> list[VolumeCalculation]:
11✔
1193
        return self._volume_calculations
11✔
1194

1195
    @volume_calculations.setter
11✔
1196
    def volume_calculations(
11✔
1197
        self, vol_calcs: VolumeCalculation | Iterable[VolumeCalculation]
1198
    ):
1199
        if not isinstance(vol_calcs, MutableSequence):
11✔
1200
            vol_calcs = [vol_calcs]
11✔
1201
        self._volume_calculations = cv.CheckedList(
11✔
1202
            VolumeCalculation, 'stochastic volume calculations', vol_calcs)
1203

1204
    @property
11✔
1205
    def create_fission_neutrons(self) -> bool:
11✔
1206
        return self._create_fission_neutrons
11✔
1207

1208
    @create_fission_neutrons.setter
11✔
1209
    def create_fission_neutrons(self, create_fission_neutrons: bool):
11✔
1210
        cv.check_type('Whether create fission neutrons',
11✔
1211
                      create_fission_neutrons, bool)
1212
        self._create_fission_neutrons = create_fission_neutrons
11✔
1213

1214
    @property
11✔
1215
    def create_delayed_neutrons(self) -> bool:
11✔
1216
        return self._create_delayed_neutrons
11✔
1217

1218
    @create_delayed_neutrons.setter
11✔
1219
    def create_delayed_neutrons(self, create_delayed_neutrons: bool):
11✔
1220
        cv.check_type('Whether create only prompt neutrons',
11✔
1221
                      create_delayed_neutrons, bool)
1222
        self._create_delayed_neutrons = create_delayed_neutrons
11✔
1223

1224
    @property
11✔
1225
    def delayed_photon_scaling(self) -> bool:
11✔
1226
        return self._delayed_photon_scaling
×
1227

1228
    @delayed_photon_scaling.setter
11✔
1229
    def delayed_photon_scaling(self, value: bool):
11✔
1230
        cv.check_type('delayed photon scaling', value, bool)
×
1231
        self._delayed_photon_scaling = value
×
1232

1233
    @property
11✔
1234
    def material_cell_offsets(self) -> bool:
11✔
1235
        return self._material_cell_offsets
×
1236

1237
    @material_cell_offsets.setter
11✔
1238
    def material_cell_offsets(self, value: bool):
11✔
1239
        cv.check_type('material cell offsets', value, bool)
×
1240
        self._material_cell_offsets = value
×
1241

1242
    @property
11✔
1243
    def log_grid_bins(self) -> int:
11✔
1244
        return self._log_grid_bins
11✔
1245

1246
    @log_grid_bins.setter
11✔
1247
    def log_grid_bins(self, log_grid_bins: int):
11✔
1248
        cv.check_type('log grid bins', log_grid_bins, Real)
11✔
1249
        cv.check_greater_than('log grid bins', log_grid_bins, 0)
11✔
1250
        self._log_grid_bins = log_grid_bins
11✔
1251

1252
    @property
11✔
1253
    def event_based(self) -> bool:
11✔
1254
        return self._event_based
×
1255

1256
    @event_based.setter
11✔
1257
    def event_based(self, value: bool):
11✔
1258
        cv.check_type('event based', value, bool)
×
1259
        self._event_based = value
×
1260

1261
    @property
11✔
1262
    def max_particles_in_flight(self) -> int:
11✔
1263
        return self._max_particles_in_flight
×
1264

1265
    @max_particles_in_flight.setter
11✔
1266
    def max_particles_in_flight(self, value: int):
11✔
1267
        cv.check_type('max particles in flight', value, Integral)
×
1268
        cv.check_greater_than('max particles in flight', value, 0)
×
1269
        self._max_particles_in_flight = value
×
1270

1271
    @property
11✔
1272
    def max_particle_events(self) -> int:
11✔
1273
        return self._max_particle_events
11✔
1274

1275
    @max_particle_events.setter
11✔
1276
    def max_particle_events(self, value: int):
11✔
1277
        cv.check_type('max particle events', value, Integral)
11✔
1278
        cv.check_greater_than('max particle events', value, 0)
11✔
1279
        self._max_particle_events = value
11✔
1280

1281
    @property
11✔
1282
    def write_initial_source(self) -> bool:
11✔
1283
        return self._write_initial_source
11✔
1284

1285
    @write_initial_source.setter
11✔
1286
    def write_initial_source(self, value: bool):
11✔
1287
        cv.check_type('write initial source', value, bool)
11✔
1288
        self._write_initial_source = value
11✔
1289

1290
    @property
11✔
1291
    def weight_windows(self) -> WeightWindowsList:
11✔
1292
        return self._weight_windows
11✔
1293

1294
    @weight_windows.setter
11✔
1295
    def weight_windows(self, value: WeightWindows | Sequence[WeightWindows]):
11✔
1296
        if not isinstance(value, Sequence):
11✔
1297
            value = [value]
11✔
1298
        self._weight_windows = WeightWindowsList(value)
11✔
1299

1300
    @property
11✔
1301
    def weight_windows_on(self) -> bool:
11✔
1302
        return self._weight_windows_on
11✔
1303

1304
    @weight_windows_on.setter
11✔
1305
    def weight_windows_on(self, value: bool):
11✔
1306
        cv.check_type('weight windows on', value, bool)
11✔
1307
        self._weight_windows_on = value
11✔
1308

1309
    @property
11✔
1310
    def weight_window_checkpoints(self) -> dict:
11✔
1311
        return self._weight_window_checkpoints
11✔
1312

1313
    @weight_window_checkpoints.setter
11✔
1314
    def weight_window_checkpoints(self, weight_window_checkpoints: dict):
11✔
1315
        for key in weight_window_checkpoints.keys():
11✔
1316
            cv.check_value('weight_window_checkpoints',
11✔
1317
                           key, ('collision', 'surface'))
1318
        self._weight_window_checkpoints = weight_window_checkpoints
11✔
1319

1320
    @property
11✔
1321
    def max_splits(self):
11✔
1322
        raise AttributeError(
×
1323
            'max_splits has been deprecated. Please use max_history_splits instead')
1324

1325
    @property
11✔
1326
    def max_history_splits(self) -> int:
11✔
1327
        return self._max_history_splits
11✔
1328

1329
    @max_history_splits.setter
11✔
1330
    def max_history_splits(self, value: int):
11✔
1331
        cv.check_type('maximum particle splits', value, Integral)
11✔
1332
        cv.check_greater_than('max particle splits', value, 0)
11✔
1333
        self._max_history_splits = value
11✔
1334

1335
    @property
11✔
1336
    def max_secondaries(self) -> int:
11✔
1337
        return self._max_secondaries
11✔
1338

1339
    @max_secondaries.setter
11✔
1340
    def max_secondaries(self, value: int):
11✔
1341
        cv.check_type('maximum secondary bank size', value, Integral)
11✔
1342
        cv.check_greater_than('max secondary bank size', value, 0)
11✔
1343
        self._max_secondaries = value
11✔
1344

1345
    @property
11✔
1346
    def max_tracks(self) -> int:
11✔
1347
        return self._max_tracks
11✔
1348

1349
    @max_tracks.setter
11✔
1350
    def max_tracks(self, value: int):
11✔
1351
        cv.check_type('maximum particle tracks', value, Integral)
11✔
1352
        cv.check_greater_than('maximum particle tracks', value, 0, True)
11✔
1353
        self._max_tracks = value
11✔
1354

1355
    @property
11✔
1356
    def weight_windows_file(self) -> PathLike | None:
11✔
1357
        return self._weight_windows_file
11✔
1358

1359
    @weight_windows_file.setter
11✔
1360
    def weight_windows_file(self, value: PathLike | None):
11✔
1361
        if value is None:
×
1362
            self._weight_windows_file = None
×
1363
        else:
1364
            cv.check_type('weight windows file', value, PathLike)
×
1365
            self._weight_windows_file = input_path(value)
×
1366

1367
    @property
11✔
1368
    def weight_window_generators(self) -> list[WeightWindowGenerator]:
11✔
1369
        return self._weight_window_generators
11✔
1370

1371
    @weight_window_generators.setter
11✔
1372
    def weight_window_generators(self, wwgs):
11✔
1373
        if not isinstance(wwgs, MutableSequence):
11✔
1374
            wwgs = [wwgs]
11✔
1375
        self._weight_window_generators = cv.CheckedList(
11✔
1376
            WeightWindowGenerator, 'weight window generators', wwgs)
1377

1378
    @property
11✔
1379
    def random_ray(self) -> dict:
11✔
1380
        return self._random_ray
11✔
1381

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

1439
        self._random_ray = random_ray
11✔
1440

1441
    @property
11✔
1442
    def use_decay_photons(self) -> bool:
11✔
1443
        return self._use_decay_photons
11✔
1444

1445
    @use_decay_photons.setter
11✔
1446
    def use_decay_photons(self, value):
11✔
1447
        cv.check_type('use decay photons', value, bool)
11✔
1448
        self._use_decay_photons = value
11✔
1449

1450
    @property
11✔
1451
    def source_rejection_fraction(self) -> float:
11✔
1452
        return self._source_rejection_fraction
11✔
1453

1454
    @source_rejection_fraction.setter
11✔
1455
    def source_rejection_fraction(self, source_rejection_fraction: float):
11✔
1456
        cv.check_type('source_rejection_fraction',
11✔
1457
                      source_rejection_fraction, Real)
1458
        cv.check_greater_than('source_rejection_fraction',
11✔
1459
                              source_rejection_fraction, 0)
1460
        cv.check_less_than('source_rejection_fraction',
11✔
1461
                           source_rejection_fraction, 1)
1462
        self._source_rejection_fraction = source_rejection_fraction
11✔
1463

1464
    @property
11✔
1465
    def free_gas_threshold(self) -> float | None:
11✔
1466
        return self._free_gas_threshold
11✔
1467

1468
    @free_gas_threshold.setter
11✔
1469
    def free_gas_threshold(self, free_gas_threshold: float | None):
11✔
1470
        if free_gas_threshold is not None:
11✔
1471
            cv.check_type('free gas threshold', free_gas_threshold, Real)
11✔
1472
            cv.check_greater_than('free gas threshold', free_gas_threshold, 0.0)
11✔
1473
        self._free_gas_threshold = free_gas_threshold
11✔
1474

1475
    def _create_run_mode_subelement(self, root):
11✔
1476
        elem = ET.SubElement(root, "run_mode")
11✔
1477
        elem.text = self._run_mode.value
11✔
1478

1479
    def _create_batches_subelement(self, root):
11✔
1480
        if self._batches is not None:
11✔
1481
            element = ET.SubElement(root, "batches")
11✔
1482
            element.text = str(self._batches)
11✔
1483

1484
    def _create_generations_per_batch_subelement(self, root):
11✔
1485
        if self._generations_per_batch is not None:
11✔
1486
            element = ET.SubElement(root, "generations_per_batch")
11✔
1487
            element.text = str(self._generations_per_batch)
11✔
1488

1489
    def _create_inactive_subelement(self, root):
11✔
1490
        if self._inactive is not None:
11✔
1491
            element = ET.SubElement(root, "inactive")
11✔
1492
            element.text = str(self._inactive)
11✔
1493

1494
    def _create_max_lost_particles_subelement(self, root):
11✔
1495
        if self._max_lost_particles is not None:
11✔
1496
            element = ET.SubElement(root, "max_lost_particles")
11✔
1497
            element.text = str(self._max_lost_particles)
11✔
1498

1499
    def _create_rel_max_lost_particles_subelement(self, root):
11✔
1500
        if self._rel_max_lost_particles is not None:
11✔
1501
            element = ET.SubElement(root, "rel_max_lost_particles")
11✔
1502
            element.text = str(self._rel_max_lost_particles)
11✔
1503

1504
    def _create_max_write_lost_particles_subelement(self, root):
11✔
1505
        if self._max_write_lost_particles is not None:
11✔
1506
            element = ET.SubElement(root, "max_write_lost_particles")
11✔
1507
            element.text = str(self._max_write_lost_particles)
11✔
1508

1509
    def _create_particles_subelement(self, root):
11✔
1510
        if self._particles is not None:
11✔
1511
            element = ET.SubElement(root, "particles")
11✔
1512
            element.text = str(self._particles)
11✔
1513

1514
    def _create_keff_trigger_subelement(self, root):
11✔
1515
        if self._keff_trigger is not None:
11✔
1516
            element = ET.SubElement(root, "keff_trigger")
11✔
1517
            for key, value in sorted(self._keff_trigger.items()):
11✔
1518
                subelement = ET.SubElement(element, key)
11✔
1519
                subelement.text = str(value).lower()
11✔
1520

1521
    def _create_energy_mode_subelement(self, root):
11✔
1522
        if self._energy_mode is not None:
11✔
1523
            element = ET.SubElement(root, "energy_mode")
11✔
1524
            element.text = str(self._energy_mode)
11✔
1525

1526
    def _create_max_order_subelement(self, root):
11✔
1527
        if self._max_order is not None:
11✔
1528
            element = ET.SubElement(root, "max_order")
11✔
1529
            element.text = str(self._max_order)
11✔
1530

1531
    def _create_source_subelement(self, root, mesh_memo=None):
11✔
1532
        for source in self.source:
11✔
1533
            root.append(source.to_xml_element())
11✔
1534
            if isinstance(source, IndependentSource) and isinstance(source.space, MeshSpatial):
11✔
1535
                path = f"./mesh[@id='{source.space.mesh.id}']"
11✔
1536
                if root.find(path) is None:
11✔
1537
                    root.append(source.space.mesh.to_xml_element())
11✔
1538
            if isinstance(source, MeshSource):
11✔
1539
                path = f"./mesh[@id='{source.mesh.id}']"
11✔
1540
                if root.find(path) is None:
11✔
1541
                    root.append(source.mesh.to_xml_element())
11✔
1542
                    if mesh_memo is not None:
11✔
1543
                        mesh_memo.add(source.mesh.id)
11✔
1544

1545
    def _create_volume_calcs_subelement(self, root):
11✔
1546
        for calc in self.volume_calculations:
11✔
1547
            root.append(calc.to_xml_element())
11✔
1548

1549
    def _create_output_subelement(self, root):
11✔
1550
        if self._output is not None:
11✔
1551
            element = ET.SubElement(root, "output")
11✔
1552
            for key, value in sorted(self._output.items()):
11✔
1553
                subelement = ET.SubElement(element, key)
11✔
1554
                if key in ('summary', 'tallies'):
11✔
1555
                    subelement.text = str(value).lower()
11✔
1556
                else:
1557
                    subelement.text = value
11✔
1558

1559
    def _create_verbosity_subelement(self, root):
11✔
1560
        if self._verbosity is not None:
11✔
1561
            element = ET.SubElement(root, "verbosity")
11✔
1562
            element.text = str(self._verbosity)
11✔
1563

1564
    def _create_statepoint_subelement(self, root):
11✔
1565
        if self._statepoint:
11✔
1566
            element = ET.SubElement(root, "state_point")
11✔
1567
            if 'batches' in self._statepoint:
11✔
1568
                subelement = ET.SubElement(element, "batches")
11✔
1569
                subelement.text = ' '.join(
11✔
1570
                    str(x) for x in self._statepoint['batches'])
1571

1572
    def _create_uniform_source_sampling_subelement(self, root):
11✔
1573
        if self._uniform_source_sampling is not None:
11✔
1574
            element = ET.SubElement(root, "uniform_source_sampling")
11✔
1575
            element.text = str(self._uniform_source_sampling).lower()
11✔
1576

1577
    def _create_sourcepoint_subelement(self, root):
11✔
1578
        if self._sourcepoint:
11✔
1579
            element = ET.SubElement(root, "source_point")
11✔
1580

1581
            if 'batches' in self._sourcepoint:
11✔
1582
                subelement = ET.SubElement(element, "batches")
11✔
1583
                subelement.text = ' '.join(
11✔
1584
                    str(x) for x in self._sourcepoint['batches'])
1585

1586
            if 'separate' in self._sourcepoint:
11✔
1587
                subelement = ET.SubElement(element, "separate")
11✔
1588
                subelement.text = str(self._sourcepoint['separate']).lower()
11✔
1589

1590
            if 'write' in self._sourcepoint:
11✔
1591
                subelement = ET.SubElement(element, "write")
11✔
1592
                subelement.text = str(self._sourcepoint['write']).lower()
11✔
1593

1594
            # Overwrite latest subelement
1595
            if 'overwrite' in self._sourcepoint:
11✔
1596
                subelement = ET.SubElement(element, "overwrite_latest")
11✔
1597
                subelement.text = str(self._sourcepoint['overwrite']).lower()
11✔
1598

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

1603
    def _create_surf_source_read_subelement(self, root):
11✔
1604
        if self._surf_source_read:
11✔
1605
            element = ET.SubElement(root, "surf_source_read")
11✔
1606
            if 'path' in self._surf_source_read:
11✔
1607
                subelement = ET.SubElement(element, "path")
11✔
1608
                subelement.text = str(self._surf_source_read['path'])
11✔
1609

1610
    def _create_surf_source_write_subelement(self, root):
11✔
1611
        if self._surf_source_write:
11✔
1612
            element = ET.SubElement(root, "surf_source_write")
11✔
1613
            if "surface_ids" in self._surf_source_write:
11✔
1614
                subelement = ET.SubElement(element, "surface_ids")
11✔
1615
                subelement.text = " ".join(
11✔
1616
                    str(x) for x in self._surf_source_write["surface_ids"]
1617
                )
1618
            if "mcpl" in self._surf_source_write:
11✔
1619
                subelement = ET.SubElement(element, "mcpl")
11✔
1620
                subelement.text = str(self._surf_source_write["mcpl"]).lower()
11✔
1621
            for key in ("max_particles", "max_source_files", "cell", "cellfrom", "cellto"):
11✔
1622
                if key in self._surf_source_write:
11✔
1623
                    subelement = ET.SubElement(element, key)
11✔
1624
                    subelement.text = str(self._surf_source_write[key])
11✔
1625

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

1665
    def _create_confidence_intervals(self, root):
11✔
1666
        if self._confidence_intervals is not None:
11✔
1667
            element = ET.SubElement(root, "confidence_intervals")
11✔
1668
            element.text = str(self._confidence_intervals).lower()
11✔
1669

1670
    def _create_electron_treatment_subelement(self, root):
11✔
1671
        if self._electron_treatment is not None:
11✔
1672
            element = ET.SubElement(root, "electron_treatment")
11✔
1673
            element.text = str(self._electron_treatment)
11✔
1674

1675
    def _create_atomic_relaxation_subelement(self, root):
11✔
1676
        if self._atomic_relaxation is not None:
11✔
1677
            element = ET.SubElement(root, "atomic_relaxation")
11✔
1678
            element.text = str(self._atomic_relaxation).lower()
11✔
1679

1680
    def _create_photon_transport_subelement(self, root):
11✔
1681
        if self._photon_transport is not None:
11✔
1682
            element = ET.SubElement(root, "photon_transport")
11✔
1683
            element.text = str(self._photon_transport).lower()
11✔
1684

1685
    def _create_plot_seed_subelement(self, root):
11✔
1686
        if self._plot_seed is not None:
11✔
1687
            element = ET.SubElement(root, "plot_seed")
11✔
1688
            element.text = str(self._plot_seed)
11✔
1689

1690
    def _create_ptables_subelement(self, root):
11✔
1691
        if self._ptables is not None:
11✔
1692
            element = ET.SubElement(root, "ptables")
11✔
1693
            element.text = str(self._ptables).lower()
11✔
1694

1695
    def _create_seed_subelement(self, root):
11✔
1696
        if self._seed is not None:
11✔
1697
            element = ET.SubElement(root, "seed")
11✔
1698
            element.text = str(self._seed)
11✔
1699

1700
    def _create_stride_subelement(self, root):
11✔
1701
        if self._stride is not None:
11✔
1702
            element = ET.SubElement(root, "stride")
11✔
1703
            element.text = str(self._stride)
11✔
1704

1705
    def _create_surface_grazing_cutoff_subelement(self, root):
11✔
1706
        if self._surface_grazing_cutoff is not None:
11✔
1707
            element = ET.SubElement(root, "surface_grazing_cutoff")
11✔
1708
            element.text = str(self._surface_grazing_cutoff)
11✔
1709

1710
    def _create_surface_grazing_ratio_subelement(self, root):
11✔
1711
        if self._surface_grazing_ratio is not None:
11✔
1712
            element = ET.SubElement(root, "surface_grazing_ratio")
11✔
1713
            element.text = str(self._surface_grazing_ratio)
11✔
1714

1715
    def _create_survival_biasing_subelement(self, root):
11✔
1716
        if self._survival_biasing is not None:
11✔
1717
            element = ET.SubElement(root, "survival_biasing")
11✔
1718
            element.text = str(self._survival_biasing).lower()
11✔
1719

1720
    def _create_cutoff_subelement(self, root):
11✔
1721
        if self._cutoff is not None:
11✔
1722
            element = ET.SubElement(root, "cutoff")
11✔
1723
            for key, value in self._cutoff.items():
11✔
1724
                subelement = ET.SubElement(element, key)
11✔
1725
                subelement.text = str(value) if key != 'survival_normalization' \
11✔
1726
                    else str(value).lower()
1727

1728
    def _create_entropy_mesh_subelement(self, root, mesh_memo=None):
11✔
1729
        if self.entropy_mesh is None:
11✔
1730
            return
11✔
1731

1732
        # use default heuristic for entropy mesh if not set by user
1733
        if self.entropy_mesh.dimension is None:
11✔
1734
            if self.particles is None:
×
1735
                raise RuntimeError("Number of particles must be set in order to "
×
1736
                                   "use entropy mesh dimension heuristic")
1737
            else:
1738
                n = ceil((self.particles / 20.0)**(1.0 / 3.0))
×
1739
                d = len(self.entropy_mesh.lower_left)
×
1740
                self.entropy_mesh.dimension = (n,)*d
×
1741

1742
        # add mesh ID to this element
1743
        subelement = ET.SubElement(root, "entropy_mesh")
11✔
1744
        subelement.text = str(self.entropy_mesh.id)
11✔
1745

1746
        # If this mesh has already been written outside the
1747
        # settings element, skip writing it again
1748
        if mesh_memo and self.entropy_mesh.id in mesh_memo:
11✔
1749
            return
×
1750

1751
        # See if a <mesh> element already exists -- if not, add it
1752
        path = f"./mesh[@id='{self.entropy_mesh.id}']"
11✔
1753
        if root.find(path) is None:
11✔
1754
            root.append(self.entropy_mesh.to_xml_element())
11✔
1755
            if mesh_memo is not None:
11✔
1756
                mesh_memo.add(self.entropy_mesh.id)
11✔
1757

1758
    def _create_trigger_subelement(self, root):
11✔
1759
        if self._trigger_active is not None:
11✔
1760
            trigger_element = ET.SubElement(root, "trigger")
11✔
1761
            element = ET.SubElement(trigger_element, "active")
11✔
1762
            element.text = str(self._trigger_active).lower()
11✔
1763

1764
            if self._trigger_max_batches is not None:
11✔
1765
                element = ET.SubElement(trigger_element, "max_batches")
11✔
1766
                element.text = str(self._trigger_max_batches)
11✔
1767

1768
            if self._trigger_batch_interval is not None:
11✔
1769
                element = ET.SubElement(trigger_element, "batch_interval")
11✔
1770
                element.text = str(self._trigger_batch_interval)
11✔
1771

1772
    def _create_no_reduce_subelement(self, root):
11✔
1773
        if self._no_reduce is not None:
11✔
1774
            element = ET.SubElement(root, "no_reduce")
11✔
1775
            element.text = str(self._no_reduce).lower()
11✔
1776

1777
    def _create_ifp_n_generation_subelement(self, root):
11✔
1778
        if self._ifp_n_generation is not None:
11✔
1779
            element = ET.SubElement(root, "ifp_n_generation")
11✔
1780
            element.text = str(self._ifp_n_generation)
11✔
1781

1782
    def _create_tabular_legendre_subelements(self, root):
11✔
1783
        if self.tabular_legendre:
11✔
1784
            element = ET.SubElement(root, "tabular_legendre")
11✔
1785
            subelement = ET.SubElement(element, "enable")
11✔
1786
            subelement.text = str(self._tabular_legendre['enable']).lower()
11✔
1787
            if 'num_points' in self._tabular_legendre:
11✔
1788
                subelement = ET.SubElement(element, "num_points")
11✔
1789
                subelement.text = str(self._tabular_legendre['num_points'])
11✔
1790

1791
    def _create_temperature_subelements(self, root):
11✔
1792
        if self.temperature:
11✔
1793
            for key, value in sorted(self.temperature.items()):
11✔
1794
                element = ET.SubElement(root, f"temperature_{key}")
11✔
1795
                if isinstance(value, bool):
11✔
1796
                    element.text = str(value).lower()
11✔
1797
                elif key == 'range':
11✔
1798
                    element.text = ' '.join(str(T) for T in value)
11✔
1799
                else:
1800
                    element.text = str(value)
11✔
1801

1802
    def _create_properties_file_element(self, root):
11✔
1803
        if self.properties_file is not None:
11✔
1804
            element = ET.Element("properties_file")
11✔
1805
            element.text = str(self.properties_file)
11✔
1806
            root.append(element)
11✔
1807

1808
    def _create_trace_subelement(self, root):
11✔
1809
        if self._trace is not None:
11✔
1810
            element = ET.SubElement(root, "trace")
11✔
1811
            element.text = ' '.join(map(str, self._trace))
11✔
1812

1813
    def _create_track_subelement(self, root):
11✔
1814
        if self._track is not None:
11✔
1815
            element = ET.SubElement(root, "track")
11✔
1816
            element.text = ' '.join(map(str, itertools.chain(*self._track)))
11✔
1817

1818
    def _create_ufs_mesh_subelement(self, root, mesh_memo=None):
11✔
1819
        if self.ufs_mesh is None:
11✔
1820
            return
11✔
1821

1822
        subelement = ET.SubElement(root, "ufs_mesh")
11✔
1823
        subelement.text = str(self.ufs_mesh.id)
11✔
1824

1825
        if mesh_memo and self.ufs_mesh.id in mesh_memo:
11✔
1826
            return
×
1827

1828
        # See if a <mesh> element already exists -- if not, add it
1829
        path = f"./mesh[@id='{self.ufs_mesh.id}']"
11✔
1830
        if root.find(path) is None:
11✔
1831
            root.append(self.ufs_mesh.to_xml_element())
×
1832
            if mesh_memo is not None:
×
1833
                mesh_memo.add(self.ufs_mesh.id)
×
1834

1835
    def _create_use_decay_photons_subelement(self, root):
11✔
1836
        if self._use_decay_photons is not None:
11✔
1837
            element = ET.SubElement(root, "use_decay_photons")
11✔
1838
            element.text = str(self._use_decay_photons).lower()
11✔
1839

1840
    def _create_resonance_scattering_subelement(self, root):
11✔
1841
        res = self.resonance_scattering
11✔
1842
        if res:
11✔
1843
            elem = ET.SubElement(root, 'resonance_scattering')
11✔
1844
            if 'enable' in res:
11✔
1845
                subelem = ET.SubElement(elem, 'enable')
11✔
1846
                subelem.text = str(res['enable']).lower()
11✔
1847
            if 'method' in res:
11✔
1848
                subelem = ET.SubElement(elem, 'method')
11✔
1849
                subelem.text = res['method']
11✔
1850
            if 'energy_min' in res:
11✔
1851
                subelem = ET.SubElement(elem, 'energy_min')
11✔
1852
                subelem.text = str(res['energy_min'])
11✔
1853
            if 'energy_max' in res:
11✔
1854
                subelem = ET.SubElement(elem, 'energy_max')
11✔
1855
                subelem.text = str(res['energy_max'])
11✔
1856
            if 'nuclides' in res:
11✔
1857
                subelem = ET.SubElement(elem, 'nuclides')
11✔
1858
                subelem.text = ' '.join(res['nuclides'])
11✔
1859

1860
    def _create_create_fission_neutrons_subelement(self, root):
11✔
1861
        if self._create_fission_neutrons is not None:
11✔
1862
            elem = ET.SubElement(root, "create_fission_neutrons")
11✔
1863
            elem.text = str(self._create_fission_neutrons).lower()
11✔
1864

1865
    def _create_create_delayed_neutrons_subelement(self, root):
11✔
1866
        if self._create_delayed_neutrons is not None:
11✔
1867
            elem = ET.SubElement(root, "create_delayed_neutrons")
11✔
1868
            elem.text = str(self._create_delayed_neutrons).lower()
11✔
1869

1870
    def _create_delayed_photon_scaling_subelement(self, root):
11✔
1871
        if self._delayed_photon_scaling is not None:
11✔
1872
            elem = ET.SubElement(root, "delayed_photon_scaling")
×
1873
            elem.text = str(self._delayed_photon_scaling).lower()
×
1874

1875
    def _create_event_based_subelement(self, root):
11✔
1876
        if self._event_based is not None:
11✔
1877
            elem = ET.SubElement(root, "event_based")
×
1878
            elem.text = str(self._event_based).lower()
×
1879

1880
    def _create_max_particles_in_flight_subelement(self, root):
11✔
1881
        if self._max_particles_in_flight is not None:
11✔
1882
            elem = ET.SubElement(root, "max_particles_in_flight")
×
1883
            elem.text = str(self._max_particles_in_flight).lower()
×
1884

1885
    def _create_max_events_subelement(self, root):
11✔
1886
        if self._max_particle_events is not None:
11✔
1887
            elem = ET.SubElement(root, "max_particle_events")
11✔
1888
            elem.text = str(self._max_particle_events).lower()
11✔
1889

1890
    def _create_material_cell_offsets_subelement(self, root):
11✔
1891
        if self._material_cell_offsets is not None:
11✔
1892
            elem = ET.SubElement(root, "material_cell_offsets")
×
1893
            elem.text = str(self._material_cell_offsets).lower()
×
1894

1895
    def _create_log_grid_bins_subelement(self, root):
11✔
1896
        if self._log_grid_bins is not None:
11✔
1897
            elem = ET.SubElement(root, "log_grid_bins")
11✔
1898
            elem.text = str(self._log_grid_bins)
11✔
1899

1900
    def _create_write_initial_source_subelement(self, root):
11✔
1901
        if self._write_initial_source is not None:
11✔
1902
            elem = ET.SubElement(root, "write_initial_source")
11✔
1903
            elem.text = str(self._write_initial_source).lower()
11✔
1904

1905
    def _create_weight_windows_subelement(self, root, mesh_memo=None):
11✔
1906
        for ww in self._weight_windows:
11✔
1907
            # Add weight window information
1908
            root.append(ww.to_xml_element())
11✔
1909

1910
            # if this mesh has already been written,
1911
            # skip writing the mesh element
1912
            if mesh_memo and ww.mesh.id in mesh_memo:
11✔
1913
                continue
11✔
1914

1915
            # See if a <mesh> element already exists -- if not, add it
1916
            path = f"./mesh[@id='{ww.mesh.id}']"
11✔
1917
            if root.find(path) is None:
11✔
1918
                root.append(ww.mesh.to_xml_element())
11✔
1919
                if mesh_memo is not None:
11✔
1920
                    mesh_memo.add(ww.mesh.id)
11✔
1921

1922
    def _create_weight_windows_on_subelement(self, root):
11✔
1923
        if self._weight_windows_on is not None:
11✔
1924
            elem = ET.SubElement(root, "weight_windows_on")
11✔
1925
            elem.text = str(self._weight_windows_on).lower()
11✔
1926

1927
    def _create_weight_window_generators_subelement(self, root, mesh_memo=None):
11✔
1928
        if not self.weight_window_generators:
11✔
1929
            return
11✔
1930
        elem = ET.SubElement(root, 'weight_window_generators')
11✔
1931
        for wwg in self.weight_window_generators:
11✔
1932
            elem.append(wwg.to_xml_element())
11✔
1933

1934
        # ensure that mesh elements are created if needed
1935
        for wwg in self.weight_window_generators:
11✔
1936
            if mesh_memo is not None and wwg.mesh.id in mesh_memo:
11✔
1937
                continue
×
1938

1939
            # See if a <mesh> element already exists -- if not, add it
1940
            path = f"./mesh[@id='{wwg.mesh.id}']"
11✔
1941
            if root.find(path) is None:
11✔
1942
                root.append(wwg.mesh.to_xml_element())
11✔
1943
                if mesh_memo is not None:
11✔
1944
                    mesh_memo.add(wwg.mesh.id)
11✔
1945

1946
    def _create_weight_windows_file_element(self, root):
11✔
1947
        if self.weight_windows_file is not None:
11✔
1948
            element = ET.Element("weight_windows_file")
×
1949
            element.text = str(self.weight_windows_file)
×
1950
            root.append(element)
×
1951

1952
    def _create_weight_window_checkpoints_subelement(self, root):
11✔
1953
        if not self._weight_window_checkpoints:
11✔
1954
            return
11✔
1955
        element = ET.SubElement(root, "weight_window_checkpoints")
11✔
1956

1957
        if 'collision' in self._weight_window_checkpoints:
11✔
1958
            subelement = ET.SubElement(element, "collision")
11✔
1959
            subelement.text = str(
11✔
1960
                self._weight_window_checkpoints['collision']).lower()
1961

1962
        if 'surface' in self._weight_window_checkpoints:
11✔
1963
            subelement = ET.SubElement(element, "surface")
11✔
1964
            subelement.text = str(
11✔
1965
                self._weight_window_checkpoints['surface']).lower()
1966

1967
    def _create_max_history_splits_subelement(self, root):
11✔
1968
        if self._max_history_splits is not None:
11✔
1969
            elem = ET.SubElement(root, "max_history_splits")
11✔
1970
            elem.text = str(self._max_history_splits)
11✔
1971

1972
    def _create_max_secondaries_subelement(self, root):
11✔
1973
        if self._max_secondaries is not None:
11✔
1974
            elem = ET.SubElement(root, "max_secondaries")
11✔
1975
            elem.text = str(self._max_secondaries)
11✔
1976

1977
    def _create_max_tracks_subelement(self, root):
11✔
1978
        if self._max_tracks is not None:
11✔
1979
            elem = ET.SubElement(root, "max_tracks")
11✔
1980
            elem.text = str(self._max_tracks)
11✔
1981

1982
    def _create_random_ray_subelement(self, root, mesh_memo=None):
11✔
1983
        if self._random_ray:
11✔
1984
            element = ET.SubElement(root, "random_ray")
11✔
1985
            for key, value in self._random_ray.items():
11✔
1986
                if key == 'ray_source' and isinstance(value, SourceBase):
11✔
1987
                    subelement = ET.SubElement(element, 'ray_source')
11✔
1988
                    source_element = value.to_xml_element()
11✔
1989
                    if source_element.find('bias') is not None:
11✔
1990
                        raise RuntimeError(
×
1991
                            "Ray source distributions should not be biased.")
1992
                    subelement.append(source_element)
11✔
1993

1994
                elif key == 'source_region_meshes':
11✔
1995
                    subelement = ET.SubElement(element, 'source_region_meshes')
11✔
1996
                    for mesh, domains in value:
11✔
1997
                        mesh_elem = ET.SubElement(subelement, 'mesh')
11✔
1998
                        mesh_elem.set('id', str(mesh.id))
11✔
1999
                        for domain in domains:
11✔
2000
                            domain_elem = ET.SubElement(mesh_elem, 'domain')
11✔
2001
                            domain_elem.set('id', str(domain.id))
11✔
2002
                            domain_elem.set(
11✔
2003
                                'type', domain.__class__.__name__.lower())
2004
                        if mesh_memo is not None and mesh.id not in mesh_memo:
11✔
2005
                            domain_elem.set('type', domain.__class__.__name__.lower())
11✔
2006
                        # See if a <mesh> element already exists -- if not, add it
2007
                        path = f"./mesh[@id='{mesh.id}']"
11✔
2008
                        if root.find(path) is None:
11✔
2009
                            root.append(mesh.to_xml_element())
11✔
2010
                            if mesh_memo is not None:    
11✔
2011
                                mesh_memo.add(mesh.id)
11✔
2012
                elif key == 'adjoint_source':
11✔
2013
                    subelement = ET.SubElement(element, 'adjoint_source')
11✔
2014
                    # Check that all entries are valid SourceBase instances, in case 
2015
                    # the random_ray setter was not used to populate dict entries.
2016
                    if not isinstance(value, MutableSequence):
11✔
2017
                        value = [value]
11✔
2018
                    for source in value:
11✔
2019
                        if not isinstance(source, SourceBase):
11✔
NEW
2020
                            raise ValueError(
×
2021
                                f'Invalid adjoint source type: {type(source)}. '
2022
                                'Expected openmc.SourceBase.')
2023
                        subelement.append(source.to_xml_element())
11✔
2024
                elif isinstance(value, bool):
11✔
2025
                    subelement = ET.SubElement(element, key)
11✔
2026
                    subelement.text = str(value).lower()
11✔
2027
                else:
2028
                    subelement = ET.SubElement(element, key)
11✔
2029
                    subelement.text = str(value)
11✔
2030

2031
    def _create_source_rejection_fraction_subelement(self, root):
11✔
2032
        if self._source_rejection_fraction is not None:
11✔
2033
            element = ET.SubElement(root, "source_rejection_fraction")
11✔
2034
            element.text = str(self._source_rejection_fraction)
11✔
2035

2036
    def _create_free_gas_threshold_subelement(self, root):
11✔
2037
        if self._free_gas_threshold is not None:
11✔
2038
            element = ET.SubElement(root, "free_gas_threshold")
11✔
2039
            element.text = str(self._free_gas_threshold)
11✔
2040

2041
    def _eigenvalue_from_xml_element(self, root):
11✔
2042
        elem = root.find('eigenvalue')
11✔
2043
        if elem is not None:
11✔
2044
            self._run_mode_from_xml_element(elem)
×
2045
            self._particles_from_xml_element(elem)
×
2046
            self._batches_from_xml_element(elem)
×
2047
            self._inactive_from_xml_element(elem)
×
2048
            self._max_lost_particles_from_xml_element(elem)
×
2049
            self._rel_max_lost_particles_from_xml_element(elem)
×
2050
            self._max_write_lost_particles_from_xml_element(elem)
×
2051
            self._generations_per_batch_from_xml_element(elem)
×
2052

2053
    def _run_mode_from_xml_element(self, root):
11✔
2054
        text = get_text(root, 'run_mode')
11✔
2055
        if text is not None:
11✔
2056
            self.run_mode = text
11✔
2057

2058
    def _particles_from_xml_element(self, root):
11✔
2059
        text = get_text(root, 'particles')
11✔
2060
        if text is not None:
11✔
2061
            self.particles = int(text)
11✔
2062

2063
    def _batches_from_xml_element(self, root):
11✔
2064
        text = get_text(root, 'batches')
11✔
2065
        if text is not None:
11✔
2066
            self.batches = int(text)
11✔
2067

2068
    def _inactive_from_xml_element(self, root):
11✔
2069
        text = get_text(root, 'inactive')
11✔
2070
        if text is not None:
11✔
2071
            self.inactive = int(text)
11✔
2072

2073
    def _max_lost_particles_from_xml_element(self, root):
11✔
2074
        text = get_text(root, 'max_lost_particles')
11✔
2075
        if text is not None:
11✔
2076
            self.max_lost_particles = int(text)
11✔
2077

2078
    def _rel_max_lost_particles_from_xml_element(self, root):
11✔
2079
        text = get_text(root, 'rel_max_lost_particles')
11✔
2080
        if text is not None:
11✔
2081
            self.rel_max_lost_particles = float(text)
11✔
2082

2083
    def _max_write_lost_particles_from_xml_element(self, root):
11✔
2084
        text = get_text(root, 'max_write_lost_particles')
11✔
2085
        if text is not None:
11✔
2086
            self.max_write_lost_particles = int(text)
×
2087

2088
    def _generations_per_batch_from_xml_element(self, root):
11✔
2089
        text = get_text(root, 'generations_per_batch')
11✔
2090
        if text is not None:
11✔
2091
            self.generations_per_batch = int(text)
11✔
2092

2093
    def _keff_trigger_from_xml_element(self, root):
11✔
2094
        elem = root.find('keff_trigger')
11✔
2095
        if elem is not None:
11✔
2096
            trigger = get_text(elem, 'type')
11✔
2097
            threshold = float(get_text(elem, 'threshold'))
11✔
2098
            self.keff_trigger = {'type': trigger, 'threshold': threshold}
11✔
2099

2100
    def _source_from_xml_element(self, root, meshes=None):
11✔
2101
        for elem in root.findall('source'):
11✔
2102
            src = SourceBase.from_xml_element(elem, meshes)
11✔
2103
            # add newly constructed source object to the list
2104
            self.source.append(src)
11✔
2105

2106
    def _volume_calcs_from_xml_element(self, root):
11✔
2107
        volume_elems = root.findall("volume_calc")
11✔
2108
        if volume_elems:
11✔
2109
            self.volume_calculations = [VolumeCalculation.from_xml_element(elem)
11✔
2110
                                        for elem in volume_elems]
2111

2112
    def _output_from_xml_element(self, root):
11✔
2113
        elem = root.find('output')
11✔
2114
        if elem is not None:
11✔
2115
            self.output = {}
11✔
2116
            for key in ('summary', 'tallies', 'path'):
11✔
2117
                value = get_text(elem, key)
11✔
2118
                if value is not None:
11✔
2119
                    if key in ('summary', 'tallies'):
11✔
2120
                        value = value in ('true', '1')
11✔
2121
                    self.output[key] = value
11✔
2122

2123
    def _statepoint_from_xml_element(self, root):
11✔
2124
        elem = root.find('state_point')
11✔
2125
        if elem is not None:
11✔
2126
            batches = get_elem_list(elem, "batches", int)
11✔
2127
            if batches is not None:
11✔
2128
                self.statepoint['batches'] = batches
11✔
2129

2130
    def _sourcepoint_from_xml_element(self, root):
11✔
2131
        elem = root.find('source_point')
11✔
2132
        if elem is not None:
11✔
2133
            for key in ('separate', 'write', 'overwrite_latest', 'batches', 'mcpl'):
11✔
2134
                if key in ('separate', 'write', 'mcpl', 'overwrite_latest'):
11✔
2135
                    value = get_text(elem, key) in ('true', '1')
11✔
2136
                    if key == 'overwrite_latest':
11✔
2137
                        key = 'overwrite'
11✔
2138
                else:
2139
                    value = get_elem_list(elem, key, int)
11✔
2140
                if value is not None:
11✔
2141
                    self.sourcepoint[key] = value
11✔
2142

2143
    def _surf_source_read_from_xml_element(self, root):
11✔
2144
        elem = root.find('surf_source_read')
11✔
2145
        if elem is not None:
11✔
2146
            ssr = {}
11✔
2147
            value = get_text(elem, 'path')
11✔
2148
            if value is not None:
11✔
2149
                ssr['path'] = value
11✔
2150
            self.surf_source_read = ssr
11✔
2151

2152
    def _surf_source_write_from_xml_element(self, root):
11✔
2153
        elem = root.find('surf_source_write')
11✔
2154
        if elem is None:
11✔
2155
            return
11✔
2156
        for key in ('surface_ids', 'max_particles', 'max_source_files', 'mcpl', 'cell', 'cellto', 'cellfrom'):
11✔
2157
            if key == 'surface_ids':
11✔
2158
                value = get_elem_list(elem, key, int)
11✔
2159
            else:
2160
                value = get_text(elem, key)
11✔
2161
            if value is not None:
11✔
2162
                if key == 'mcpl':
11✔
2163
                    value = value in ('true', '1')
×
2164
                elif key in ('max_particles', 'max_source_files', 'cell', 'cellfrom', 'cellto'):
11✔
2165
                    value = int(value)
11✔
2166
                self.surf_source_write[key] = value
11✔
2167

2168
    def _collision_track_from_xml_element(self, root):
11✔
2169
        elem = root.find('collision_track')
11✔
2170
        if elem is not None:
11✔
2171
            for key in ('cell_ids', 'reactions', 'universe_ids', 'material_ids', 'nuclides',
11✔
2172
                        'deposited_E_threshold', 'max_collisions', "max_collision_track_files", 'mcpl'):
2173
                value = get_text(elem, key)
11✔
2174
                if value is not None:
11✔
2175
                    if key in ('cell_ids', 'universe_ids', 'material_ids'):
11✔
2176
                        value = [int(x) for x in value.split()]
11✔
2177
                    elif key in ('reactions', 'nuclides'):
11✔
2178
                        value = value.split()
11✔
2179
                    elif key in ('max_collisions', 'max_collision_track_files'):
11✔
2180
                        value = int(value)
11✔
2181
                    elif key == 'deposited_E_threshold':
11✔
2182
                        value = float(value)
11✔
2183
                    elif key == 'mcpl':
11✔
2184
                        value = value in ('true', '1')
11✔
2185
                    self.collision_track[key] = value
11✔
2186

2187
    def _confidence_intervals_from_xml_element(self, root):
11✔
2188
        text = get_text(root, 'confidence_intervals')
11✔
2189
        if text is not None:
11✔
2190
            self.confidence_intervals = text in ('true', '1')
11✔
2191

2192
    def _electron_treatment_from_xml_element(self, root):
11✔
2193
        text = get_text(root, 'electron_treatment')
11✔
2194
        if text is not None:
11✔
2195
            self.electron_treatment = text
11✔
2196

2197
    def _atomic_relaxation_from_xml_element(self, root):
11✔
2198
        text = get_text(root, 'atomic_relaxation')
11✔
2199
        if text is not None:
11✔
2200
            self.atomic_relaxation = text in ('true', '1')
11✔
2201

2202
    def _energy_mode_from_xml_element(self, root):
11✔
2203
        text = get_text(root, 'energy_mode')
11✔
2204
        if text is not None:
11✔
2205
            self.energy_mode = text
11✔
2206

2207
    def _max_order_from_xml_element(self, root):
11✔
2208
        text = get_text(root, 'max_order')
11✔
2209
        if text is not None:
11✔
2210
            self.max_order = int(text)
11✔
2211

2212
    def _photon_transport_from_xml_element(self, root):
11✔
2213
        text = get_text(root, 'photon_transport')
11✔
2214
        if text is not None:
11✔
2215
            self.photon_transport = text in ('true', '1')
11✔
2216

2217
    def _uniform_source_sampling_from_xml_element(self, root):
11✔
2218
        text = get_text(root, 'uniform_source_sampling')
11✔
2219
        if text is not None:
11✔
2220
            self.uniform_source_sampling = text in ('true', '1')
×
2221

2222
    def _plot_seed_from_xml_element(self, root):
11✔
2223
        text = get_text(root, 'plot_seed')
11✔
2224
        if text is not None:
11✔
2225
            self.plot_seed = int(text)
11✔
2226

2227
    def _ptables_from_xml_element(self, root):
11✔
2228
        text = get_text(root, 'ptables')
11✔
2229
        if text is not None:
11✔
2230
            self.ptables = text in ('true', '1')
11✔
2231

2232
    def _seed_from_xml_element(self, root):
11✔
2233
        text = get_text(root, 'seed')
11✔
2234
        if text is not None:
11✔
2235
            self.seed = int(text)
11✔
2236

2237
    def _stride_from_xml_element(self, root):
11✔
2238
        text = get_text(root, 'stride')
11✔
2239
        if text is not None:
11✔
2240
            self.stride = int(text)
×
2241

2242
    def _surface_grazing_cutoff_from_xml_element(self, root):
11✔
2243
        text = get_text(root, 'surface_grazing_cutoff')
11✔
2244
        if text is not None:
11✔
2245
            self.surface_grazing_cutoff = float(text)
11✔
2246

2247
    def _surface_grazing_ratio_from_xml_element(self, root):
11✔
2248
        text = get_text(root, 'surface_grazing_ratio')
11✔
2249
        if text is not None:
11✔
2250
            self.surface_grazing_ratio = float(text)
11✔
2251

2252
    def _survival_biasing_from_xml_element(self, root):
11✔
2253
        text = get_text(root, 'survival_biasing')
11✔
2254
        if text is not None:
11✔
2255
            self.survival_biasing = text in ('true', '1')
11✔
2256

2257
    def _cutoff_from_xml_element(self, root):
11✔
2258
        elem = root.find('cutoff')
11✔
2259
        if elem is not None:
11✔
2260
            self.cutoff = {}
11✔
2261
            for key in ('energy_neutron', 'energy_photon', 'energy_electron',
11✔
2262
                        'energy_positron', 'weight', 'weight_avg', 'time_neutron',
2263
                        'time_photon', 'time_electron', 'time_positron',
2264
                        'survival_normalization'):
2265
                value = get_text(elem, key)
11✔
2266
                if value is not None:
11✔
2267
                    if key == 'survival_normalization':
11✔
2268
                        self.cutoff[key] = value in ('true', '1')
11✔
2269
                    else:
2270
                        self.cutoff[key] = float(value)
11✔
2271

2272
    def _entropy_mesh_from_xml_element(self, root, meshes):
11✔
2273
        text = get_text(root, 'entropy_mesh')
11✔
2274
        if text is None:
11✔
2275
            return
11✔
2276
        mesh_id = int(text)
11✔
2277
        if mesh_id not in meshes:
11✔
2278
            raise ValueError(f'Could not locate mesh with ID "{mesh_id}"')
×
2279
        self.entropy_mesh = meshes[mesh_id]
11✔
2280

2281
    def _trigger_from_xml_element(self, root):
11✔
2282
        elem = root.find('trigger')
11✔
2283
        if elem is not None:
11✔
2284
            self.trigger_active = get_text(elem, 'active') in ('true', '1')
11✔
2285
            text = get_text(elem, 'max_batches')
11✔
2286
            if text is not None:
11✔
2287
                self.trigger_max_batches = int(text)
11✔
2288
            text = get_text(elem, 'batch_interval')
11✔
2289
            if text is not None:
11✔
2290
                self.trigger_batch_interval = int(text)
11✔
2291

2292
    def _no_reduce_from_xml_element(self, root):
11✔
2293
        text = get_text(root, 'no_reduce')
11✔
2294
        if text is not None:
11✔
2295
            self.no_reduce = text in ('true', '1')
11✔
2296

2297
    def _verbosity_from_xml_element(self, root):
11✔
2298
        text = get_text(root, 'verbosity')
11✔
2299
        if text is not None:
11✔
2300
            self.verbosity = int(text)
11✔
2301

2302
    def _ifp_n_generation_from_xml_element(self, root):
11✔
2303
        text = get_text(root, 'ifp_n_generation')
11✔
2304
        if text is not None:
11✔
2305
            self.ifp_n_generation = int(text)
11✔
2306

2307
    def _tabular_legendre_from_xml_element(self, root):
11✔
2308
        elem = root.find('tabular_legendre')
11✔
2309
        if elem is not None:
11✔
2310
            text = get_text(elem, 'enable')
11✔
2311
            self.tabular_legendre['enable'] = text in ('true', '1')
11✔
2312
            text = get_text(elem, 'num_points')
11✔
2313
            if text is not None:
11✔
2314
                self.tabular_legendre['num_points'] = int(text)
11✔
2315

2316
    def _temperature_from_xml_element(self, root):
11✔
2317
        text = get_text(root, 'temperature_default')
11✔
2318
        if text is not None:
11✔
2319
            self.temperature['default'] = float(text)
11✔
2320
        text = get_text(root, 'temperature_tolerance')
11✔
2321
        if text is not None:
11✔
2322
            self.temperature['tolerance'] = float(text)
×
2323
        text = get_text(root, 'temperature_method')
11✔
2324
        if text is not None:
11✔
2325
            self.temperature['method'] = text
11✔
2326
        text = get_elem_list(root, "temperature_range", float)
11✔
2327
        if text is not None:
11✔
2328
            self.temperature['range'] = text
11✔
2329
        text = get_text(root, 'temperature_multipole')
11✔
2330
        if text is not None:
11✔
2331
            self.temperature['multipole'] = text in ('true', '1')
11✔
2332

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

2338
    def _trace_from_xml_element(self, root):
11✔
2339
        text = get_elem_list(root, "trace", int)
11✔
2340
        if text is not None:
11✔
2341
            self.trace = text
11✔
2342

2343
    def _track_from_xml_element(self, root):
11✔
2344
        values = get_elem_list(root, "track", int)
11✔
2345
        if values is not None:
11✔
2346
            self.track = list(zip(values[::3], values[1::3], values[2::3]))
11✔
2347

2348
    def _ufs_mesh_from_xml_element(self, root, meshes):
11✔
2349
        text = get_text(root, 'ufs_mesh')
11✔
2350
        if text is None:
11✔
2351
            return
11✔
2352
        mesh_id = int(text)
11✔
2353
        if mesh_id not in meshes:
11✔
2354
            raise ValueError(f'Could not locate mesh with ID "{mesh_id}"')
×
2355
        self.ufs_mesh = meshes[mesh_id]
11✔
2356

2357
    def _resonance_scattering_from_xml_element(self, root):
11✔
2358
        elem = root.find('resonance_scattering')
11✔
2359
        if elem is not None:
11✔
2360
            keys = ('enable', 'method', 'energy_min', 'energy_max', 'nuclides')
11✔
2361
            for key in keys:
11✔
2362
                if key == 'nuclides':
11✔
2363
                    value = get_elem_list(elem, key, str)
11✔
2364
                else:
2365
                    value = get_text(elem, key)
11✔
2366
                if value is not None:
11✔
2367
                    if key == 'enable':
11✔
2368
                        value = value in ('true', '1')
11✔
2369
                    elif key in ('energy_min', 'energy_max'):
11✔
2370
                        value = float(value)
11✔
2371
                    self.resonance_scattering[key] = value
11✔
2372

2373
    def _create_fission_neutrons_from_xml_element(self, root):
11✔
2374
        text = get_text(root, 'create_fission_neutrons')
11✔
2375
        if text is not None:
11✔
2376
            self.create_fission_neutrons = text in ('true', '1')
11✔
2377

2378
    def _create_delayed_neutrons_from_xml_element(self, root):
11✔
2379
        text = get_text(root, 'create_delayed_neutrons')
11✔
2380
        if text is not None:
11✔
2381
            self.create_delayed_neutrons = text in ('true', '1')
11✔
2382

2383
    def _delayed_photon_scaling_from_xml_element(self, root):
11✔
2384
        text = get_text(root, 'delayed_photon_scaling')
11✔
2385
        if text is not None:
11✔
2386
            self.delayed_photon_scaling = text in ('true', '1')
×
2387

2388
    def _event_based_from_xml_element(self, root):
11✔
2389
        text = get_text(root, 'event_based')
11✔
2390
        if text is not None:
11✔
2391
            self.event_based = text in ('true', '1')
×
2392

2393
    def _max_particles_in_flight_from_xml_element(self, root):
11✔
2394
        text = get_text(root, 'max_particles_in_flight')
11✔
2395
        if text is not None:
11✔
2396
            self.max_particles_in_flight = int(text)
×
2397

2398
    def _max_particle_events_from_xml_element(self, root):
11✔
2399
        text = get_text(root, 'max_particle_events')
11✔
2400
        if text is not None:
11✔
2401
            self.max_particle_events = int(text)
11✔
2402

2403
    def _material_cell_offsets_from_xml_element(self, root):
11✔
2404
        text = get_text(root, 'material_cell_offsets')
11✔
2405
        if text is not None:
11✔
2406
            self.material_cell_offsets = text in ('true', '1')
×
2407

2408
    def _log_grid_bins_from_xml_element(self, root):
11✔
2409
        text = get_text(root, 'log_grid_bins')
11✔
2410
        if text is not None:
11✔
2411
            self.log_grid_bins = int(text)
11✔
2412

2413
    def _write_initial_source_from_xml_element(self, root):
11✔
2414
        text = get_text(root, 'write_initial_source')
11✔
2415
        if text is not None:
11✔
2416
            self.write_initial_source = text in ('true', '1')
11✔
2417

2418
    def _weight_window_generators_from_xml_element(self, root, meshes=None):
11✔
2419
        for elem in root.iter('weight_windows_generator'):
11✔
2420
            wwg = WeightWindowGenerator.from_xml_element(elem, meshes)
11✔
2421
            self.weight_window_generators.append(wwg)
11✔
2422

2423
    def _weight_windows_from_xml_element(self, root, meshes=None):
11✔
2424
        for elem in root.findall('weight_windows'):
11✔
2425
            ww = WeightWindows.from_xml_element(elem, meshes)
11✔
2426
            self.weight_windows.append(ww)
11✔
2427

2428
    def _weight_windows_on_from_xml_element(self, root):
11✔
2429
        text = get_text(root, 'weight_windows_on')
11✔
2430
        if text is not None:
11✔
2431
            self.weight_windows_on = text in ('true', '1')
×
2432

2433
    def _weight_windows_file_from_xml_element(self, root):
11✔
2434
        text = get_text(root, 'weight_windows_file')
11✔
2435
        if text is not None:
11✔
2436
            self.weight_windows_file = text
×
2437

2438
    def _weight_window_checkpoints_from_xml_element(self, root):
11✔
2439
        elem = root.find('weight_window_checkpoints')
11✔
2440
        if elem is None:
11✔
2441
            return
11✔
2442
        for key in ('collision', 'surface'):
11✔
2443
            value = get_text(elem, key)
11✔
2444
            if value is not None:
11✔
2445
                value = value in ('true', '1')
11✔
2446
                self.weight_window_checkpoints[key] = value
11✔
2447

2448
    def _max_history_splits_from_xml_element(self, root):
11✔
2449
        text = get_text(root, 'max_history_splits')
11✔
2450
        if text is not None:
11✔
2451
            self.max_history_splits = int(text)
11✔
2452

2453
    def _max_secondaries_from_xml_element(self, root):
11✔
2454
        text = get_text(root, 'max_secondaries')
11✔
2455
        if text is not None:
11✔
2456
            self.max_secondaries = int(text)
11✔
2457

2458
    def _max_tracks_from_xml_element(self, root):
11✔
2459
        text = get_text(root, 'max_tracks')
11✔
2460
        if text is not None:
11✔
2461
            self.max_tracks = int(text)
11✔
2462

2463
    def _random_ray_from_xml_element(self, root, meshes=None):
11✔
2464
        elem = root.find('random_ray')
11✔
2465
        if elem is not None:
11✔
2466
            self.random_ray = {}
11✔
2467
            for child in elem:
11✔
2468
                if child.tag in ('distance_inactive', 'distance_active', 'diagonal_stabilization_rho'):
11✔
2469
                    self.random_ray[child.tag] = float(child.text)
11✔
2470
                elif child.tag == 'ray_source':
11✔
2471
                    source_element = child.find('source')
11✔
2472
                    source = SourceBase.from_xml_element(source_element)
11✔
2473
                    if child.find('bias') is not None:
11✔
2474
                        raise RuntimeError(
×
2475
                            "Ray source distributions should not be biased.")
2476
                    self.random_ray['ray_source'] = source
11✔
2477
                elif child.tag == 'volume_estimator':
11✔
2478
                    self.random_ray['volume_estimator'] = child.text
11✔
2479
                elif child.tag == 'source_shape':
11✔
2480
                    self.random_ray['source_shape'] = child.text
11✔
2481
                elif child.tag == 'volume_normalized_flux_tallies':
11✔
2482
                    self.random_ray['volume_normalized_flux_tallies'] = (
11✔
2483
                        child.text in ('true', '1')
2484
                    )
2485
                elif child.tag == 'adjoint':
11✔
2486
                    self.random_ray['adjoint'] = (
11✔
2487
                        child.text in ('true', '1')
2488
                    )
2489
                elif child.tag == 'adjoint_source':
11✔
NEW
2490
                    for subelem in child.findall('source'):
×
NEW
2491
                        src = SourceBase.from_xml_element(subelem)
×
2492
                        # add newly constructed source object to the list
NEW
2493
                        self.random_ray['adjoint_source'].append(src)
×
2494
                elif child.tag == 'sample_method':
11✔
2495
                    self.random_ray['sample_method'] = child.text
11✔
2496
                elif child.tag == 'source_region_meshes':
11✔
2497
                    self.random_ray['source_region_meshes'] = []
11✔
2498
                    for mesh_elem in child.findall('mesh'):
11✔
2499
                        mesh_id = int(get_text(mesh_elem, 'id'))
11✔
2500
                        if meshes and mesh_id in meshes:
11✔
2501
                            mesh = meshes[mesh_id]
11✔
2502
                        else:
2503
                            mesh = MeshBase.from_xml_element(mesh_elem)
×
2504
                        domains = []
11✔
2505
                        for domain_elem in mesh_elem.findall('domain'):
11✔
2506
                            domain_id = int(get_text(domain_elem, "id"))
11✔
2507
                            domain_type = get_text(domain_elem, "type")
11✔
2508
                            if domain_type == 'material':
11✔
2509
                                domain = openmc.Material(domain_id)
×
2510
                            elif domain_type == 'cell':
11✔
2511
                                domain = openmc.Cell(domain_id)
×
2512
                            elif domain_type == 'universe':
11✔
2513
                                domain = openmc.Universe(domain_id)
11✔
2514
                            domains.append(domain)
11✔
2515
                        self.random_ray['source_region_meshes'].append(
11✔
2516
                            (mesh, domains))
2517

2518
    def _use_decay_photons_from_xml_element(self, root):
11✔
2519
        text = get_text(root, 'use_decay_photons')
11✔
2520
        if text is not None:
11✔
2521
            self.use_decay_photons = text in ('true', '1')
×
2522

2523
    def _source_rejection_fraction_from_xml_element(self, root):
11✔
2524
        text = get_text(root, 'source_rejection_fraction')
11✔
2525
        if text is not None:
11✔
2526
            self.source_rejection_fraction = float(text)
11✔
2527

2528
    def _free_gas_threshold_from_xml_element(self, root):
11✔
2529
        text = get_text(root, 'free_gas_threshold')
11✔
2530
        if text is not None:
11✔
2531
            self.free_gas_threshold = float(text)
11✔
2532

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

2536
        Parameters
2537
        ----------
2538
        mesh_memo : set of ints
2539
            A set of mesh IDs to keep track of whether a mesh has already been written.
2540
        """
2541
        # Reset xml element tree
2542
        element = ET.Element("settings")
11✔
2543

2544
        self._create_run_mode_subelement(element)
11✔
2545
        self._create_particles_subelement(element)
11✔
2546
        self._create_batches_subelement(element)
11✔
2547
        self._create_inactive_subelement(element)
11✔
2548
        self._create_max_lost_particles_subelement(element)
11✔
2549
        self._create_rel_max_lost_particles_subelement(element)
11✔
2550
        self._create_max_write_lost_particles_subelement(element)
11✔
2551
        self._create_generations_per_batch_subelement(element)
11✔
2552
        self._create_keff_trigger_subelement(element)
11✔
2553
        self._create_source_subelement(element, mesh_memo)
11✔
2554
        self._create_output_subelement(element)
11✔
2555
        self._create_statepoint_subelement(element)
11✔
2556
        self._create_sourcepoint_subelement(element)
11✔
2557
        self._create_surf_source_read_subelement(element)
11✔
2558
        self._create_surf_source_write_subelement(element)
11✔
2559
        self._create_collision_track_subelement(element)
11✔
2560
        self._create_confidence_intervals(element)
11✔
2561
        self._create_electron_treatment_subelement(element)
11✔
2562
        self._create_atomic_relaxation_subelement(element)
11✔
2563
        self._create_energy_mode_subelement(element)
11✔
2564
        self._create_max_order_subelement(element)
11✔
2565
        self._create_photon_transport_subelement(element)
11✔
2566
        self._create_uniform_source_sampling_subelement(element)
11✔
2567
        self._create_plot_seed_subelement(element)
11✔
2568
        self._create_ptables_subelement(element)
11✔
2569
        self._create_seed_subelement(element)
11✔
2570
        self._create_stride_subelement(element)
11✔
2571
        self._create_surface_grazing_cutoff_subelement(element)
11✔
2572
        self._create_surface_grazing_ratio_subelement(element)
11✔
2573
        self._create_survival_biasing_subelement(element)
11✔
2574
        self._create_cutoff_subelement(element)
11✔
2575
        self._create_entropy_mesh_subelement(element, mesh_memo)
11✔
2576
        self._create_trigger_subelement(element)
11✔
2577
        self._create_no_reduce_subelement(element)
11✔
2578
        self._create_verbosity_subelement(element)
11✔
2579
        self._create_ifp_n_generation_subelement(element)
11✔
2580
        self._create_tabular_legendre_subelements(element)
11✔
2581
        self._create_temperature_subelements(element)
11✔
2582
        self._create_properties_file_element(element)
11✔
2583
        self._create_trace_subelement(element)
11✔
2584
        self._create_track_subelement(element)
11✔
2585
        self._create_ufs_mesh_subelement(element, mesh_memo)
11✔
2586
        self._create_resonance_scattering_subelement(element)
11✔
2587
        self._create_volume_calcs_subelement(element)
11✔
2588
        self._create_create_fission_neutrons_subelement(element)
11✔
2589
        self._create_create_delayed_neutrons_subelement(element)
11✔
2590
        self._create_delayed_photon_scaling_subelement(element)
11✔
2591
        self._create_event_based_subelement(element)
11✔
2592
        self._create_max_particles_in_flight_subelement(element)
11✔
2593
        self._create_max_events_subelement(element)
11✔
2594
        self._create_material_cell_offsets_subelement(element)
11✔
2595
        self._create_log_grid_bins_subelement(element)
11✔
2596
        self._create_write_initial_source_subelement(element)
11✔
2597
        self._create_weight_windows_subelement(element, mesh_memo)
11✔
2598
        self._create_weight_windows_on_subelement(element)
11✔
2599
        self._create_weight_window_generators_subelement(element, mesh_memo)
11✔
2600
        self._create_weight_windows_file_element(element)
11✔
2601
        self._create_weight_window_checkpoints_subelement(element)
11✔
2602
        self._create_max_history_splits_subelement(element)
11✔
2603
        self._create_max_tracks_subelement(element)
11✔
2604
        self._create_max_secondaries_subelement(element)
11✔
2605
        self._create_random_ray_subelement(element, mesh_memo)
11✔
2606
        self._create_use_decay_photons_subelement(element)
11✔
2607
        self._create_source_rejection_fraction_subelement(element)
11✔
2608
        self._create_free_gas_threshold_subelement(element)
11✔
2609

2610
        # Clean the indentation in the file to be user-readable
2611
        clean_indentation(element)
11✔
2612

2613
        return element
11✔
2614

2615
    def export_to_xml(self, path: PathLike = 'settings.xml'):
11✔
2616
        """Export simulation settings to an XML file.
2617

2618
        Parameters
2619
        ----------
2620
        path : str
2621
            Path to file to write. Defaults to 'settings.xml'.
2622

2623
        """
2624
        root_element = self.to_xml_element()
11✔
2625

2626
        # Check if path is a directory
2627
        p = Path(path)
11✔
2628
        if p.is_dir():
11✔
2629
            p /= 'settings.xml'
11✔
2630

2631
        # Write the XML Tree to the settings.xml file
2632
        tree = ET.ElementTree(root_element)
11✔
2633
        tree.write(str(p), xml_declaration=True, encoding='utf-8')
11✔
2634

2635
    @classmethod
11✔
2636
    def from_xml_element(cls, elem, meshes=None):
11✔
2637
        """Generate settings from XML element
2638

2639
        Parameters
2640
        ----------
2641
        elem : lxml.etree._Element
2642
            XML element
2643
        meshes : dict or None
2644
            A dictionary with mesh IDs as keys and mesh instances as values that
2645
            have already been read from XML. Pre-existing meshes are used
2646
            and new meshes are added to when creating tally objects.
2647

2648
        Returns
2649
        -------
2650
        openmc.Settings
2651
            Settings object
2652

2653
        """
2654
        # read all meshes under the settings node and update
2655
        settings_meshes = _read_meshes(elem)
11✔
2656
        meshes = {} if meshes is None else meshes
11✔
2657
        meshes.update(settings_meshes)
11✔
2658

2659
        settings = cls()
11✔
2660
        settings._eigenvalue_from_xml_element(elem)
11✔
2661
        settings._run_mode_from_xml_element(elem)
11✔
2662
        settings._particles_from_xml_element(elem)
11✔
2663
        settings._batches_from_xml_element(elem)
11✔
2664
        settings._inactive_from_xml_element(elem)
11✔
2665
        settings._max_lost_particles_from_xml_element(elem)
11✔
2666
        settings._rel_max_lost_particles_from_xml_element(elem)
11✔
2667
        settings._max_write_lost_particles_from_xml_element(elem)
11✔
2668
        settings._generations_per_batch_from_xml_element(elem)
11✔
2669
        settings._keff_trigger_from_xml_element(elem)
11✔
2670
        settings._source_from_xml_element(elem, meshes)
11✔
2671
        settings._volume_calcs_from_xml_element(elem)
11✔
2672
        settings._output_from_xml_element(elem)
11✔
2673
        settings._statepoint_from_xml_element(elem)
11✔
2674
        settings._sourcepoint_from_xml_element(elem)
11✔
2675
        settings._surf_source_read_from_xml_element(elem)
11✔
2676
        settings._surf_source_write_from_xml_element(elem)
11✔
2677
        settings._collision_track_from_xml_element(elem)
11✔
2678
        settings._confidence_intervals_from_xml_element(elem)
11✔
2679
        settings._electron_treatment_from_xml_element(elem)
11✔
2680
        settings._atomic_relaxation_from_xml_element(elem)
11✔
2681
        settings._energy_mode_from_xml_element(elem)
11✔
2682
        settings._max_order_from_xml_element(elem)
11✔
2683
        settings._photon_transport_from_xml_element(elem)
11✔
2684
        settings._uniform_source_sampling_from_xml_element(elem)
11✔
2685
        settings._plot_seed_from_xml_element(elem)
11✔
2686
        settings._ptables_from_xml_element(elem)
11✔
2687
        settings._seed_from_xml_element(elem)
11✔
2688
        settings._stride_from_xml_element(elem)
11✔
2689
        settings._surface_grazing_cutoff_from_xml_element(elem)
11✔
2690
        settings._surface_grazing_ratio_from_xml_element(elem)
11✔
2691
        settings._survival_biasing_from_xml_element(elem)
11✔
2692
        settings._cutoff_from_xml_element(elem)
11✔
2693
        settings._entropy_mesh_from_xml_element(elem, meshes)
11✔
2694
        settings._trigger_from_xml_element(elem)
11✔
2695
        settings._no_reduce_from_xml_element(elem)
11✔
2696
        settings._verbosity_from_xml_element(elem)
11✔
2697
        settings._ifp_n_generation_from_xml_element(elem)
11✔
2698
        settings._tabular_legendre_from_xml_element(elem)
11✔
2699
        settings._temperature_from_xml_element(elem)
11✔
2700
        settings._properties_file_from_xml_element(elem)
11✔
2701
        settings._trace_from_xml_element(elem)
11✔
2702
        settings._track_from_xml_element(elem)
11✔
2703
        settings._ufs_mesh_from_xml_element(elem, meshes)
11✔
2704
        settings._resonance_scattering_from_xml_element(elem)
11✔
2705
        settings._create_fission_neutrons_from_xml_element(elem)
11✔
2706
        settings._create_delayed_neutrons_from_xml_element(elem)
11✔
2707
        settings._delayed_photon_scaling_from_xml_element(elem)
11✔
2708
        settings._event_based_from_xml_element(elem)
11✔
2709
        settings._max_particles_in_flight_from_xml_element(elem)
11✔
2710
        settings._max_particle_events_from_xml_element(elem)
11✔
2711
        settings._material_cell_offsets_from_xml_element(elem)
11✔
2712
        settings._log_grid_bins_from_xml_element(elem)
11✔
2713
        settings._write_initial_source_from_xml_element(elem)
11✔
2714
        settings._weight_windows_from_xml_element(elem, meshes)
11✔
2715
        settings._weight_windows_on_from_xml_element(elem)
11✔
2716
        settings._weight_windows_file_from_xml_element(elem)
11✔
2717
        settings._weight_window_generators_from_xml_element(elem, meshes)
11✔
2718
        settings._weight_window_checkpoints_from_xml_element(elem)
11✔
2719
        settings._max_history_splits_from_xml_element(elem)
11✔
2720
        settings._max_tracks_from_xml_element(elem)
11✔
2721
        settings._max_secondaries_from_xml_element(elem)
11✔
2722
        settings._random_ray_from_xml_element(elem, meshes)
11✔
2723
        settings._use_decay_photons_from_xml_element(elem)
11✔
2724
        settings._source_rejection_fraction_from_xml_element(elem)
11✔
2725
        settings._free_gas_threshold_from_xml_element(elem)
11✔
2726

2727
        return settings
11✔
2728

2729
    @classmethod
11✔
2730
    def from_xml(cls, path: PathLike = 'settings.xml'):
11✔
2731
        """Generate settings from XML file
2732

2733
        .. versionadded:: 0.13.0
2734

2735
        Parameters
2736
        ----------
2737
        path : str, optional
2738
            Path to settings XML file
2739

2740
        Returns
2741
        -------
2742
        openmc.Settings
2743
            Settings object
2744

2745
        """
2746
        parser = ET.XMLParser(huge_tree=True)
11✔
2747
        tree = ET.parse(path, parser=parser)
11✔
2748
        root = tree.getroot()
11✔
2749
        meshes = _read_meshes(root)
11✔
2750
        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