def background(self, background): cv.check_type('plot background', background, Iterable, Integral) cv.check_length('plot background', background, 3) for rgb in background: cv.check_greater_than('plot background',rgb, 0, True) cv.check_less_than('plot background', rgb, 256) self._background = background
def get_group_bounds(self, group): """Returns the energy boundaries for the energy group of interest. Parameters ---------- group : Integral The energy group index, starting at 1 for the highest energies Returns ------- 2-tuple The low and high energy bounds for the group in MeV Raises ------ ValueError If the group edges have not yet been set. """ if self.group_edges is None: msg = 'Unable to get energy group bounds for group "{0}" since ' \ 'the group edges have not yet been set'.format(group) raise ValueError(msg) cv.check_greater_than('group', group, 0) cv.check_less_than('group', group, self.num_groups, equality=True) lower = self.group_edges[self.num_groups-group] upper = self.group_edges[self.num_groups-group+1] return lower, upper
def mask_background(self, mask_background): cv.check_type('plot mask background', mask_background, Iterable, Integral) cv.check_length('plot mask background', mask_background, 3) for rgb in mask_background: cv.check_greater_than('plot mask background', rgb, 0, True) cv.check_less_than('plot mask background', rgb, 256) self._mask_background = mask_background
def num_l(self, num_l): if num_l is not None: cv.check_type('num_l', num_l, Integral) cv.check_greater_than('num_l', num_l, 1, equality=True) cv.check_less_than('num_l', num_l, 4, equality=True) # There is an if block in _evaluate that assumes num_l <= 4. self._num_l = num_l
def probability(self, probability): cv.check_type('mixture distribution probabilities', probability, Iterable, Real) for p in probability: cv.check_greater_than('mixture distribution probabilities', p, 0.0, True) self._probability = probability
def p(self, p): if isinstance(p, Real): p = [p] cv.check_type('discrete probabilities', p, Iterable, Real) for pk in p: cv.check_greater_than('discrete probability', pk, 0.0, True) self._p = p
def fit_order(self, fit_order): if fit_order is not None: cv.check_type('fit_order', fit_order, Integral) cv.check_greater_than('fit_order', fit_order, 2, equality=True) # _broaden_wmp_polynomials assumes the curve fit has at least 3 # terms. self._fit_order = fit_order
def colorize(self, geometry, seed=1): """Generate a color scheme for each domain in the plot. This routine may be used to generate random, reproducible color schemes. The colors generated are based upon cell/material IDs in the geometry. Parameters ---------- geometry : openmc.Geometry The geometry for which the plot is defined seed : Integral The random number seed used to generate the color scheme """ cv.check_type('geometry', geometry, openmc.Geometry) cv.check_type('seed', seed, Integral) cv.check_greater_than('seed', seed, 1, equality=True) # Get collections of the domains which will be plotted if self.color_by == 'material': domains = geometry.get_all_materials().values() else: domains = geometry.get_all_cells().values() # Set the seed for the random number generator np.random.seed(seed) # Generate random colors for each feature for domain in domains: self.colors[domain] = np.random.randint(0, 256, (3,))
def albedo(self, albedo): check_type('CMFD mesh albedo', albedo, Iterable, Real) check_length('CMFD mesh albedo', albedo, 6) for a in albedo: check_greater_than('CMFD mesh albedo', a, 0, True) check_less_than('CMFD mesh albedo', a, 1, True) self._albedo = albedo
def bins(self, bins): if bins is None: self.num_bins = 0 elif self._type is None: msg = 'Unable to set bins for Filter to "{0}" since ' \ 'the Filter type has not yet been set'.format(bins) raise ValueError(msg) # If the bin edge is a single value, it is a Cell, Material, etc. ID if not isinstance(bins, Iterable): bins = [bins] # If the bins are in a collection, convert it to a list else: bins = list(bins) if self.type in ['cell', 'cellborn', 'surface', 'material', 'universe', 'distribcell']: check_iterable_type('filter bins', bins, Integral) for edge in bins: check_greater_than('filter bin', edge, 0, equality=True) elif self._type in ['energy', 'energyout']: for edge in bins: if not isinstance(edge, Real): msg = 'Unable to add bin edge "{0}" to a "{1}" Filter ' \ 'since it is a non-integer or floating point ' \ 'value'.format(edge, self.type) raise ValueError(msg) elif edge < 0.: msg = 'Unable to add bin edge "{0}" to a "{1}" Filter ' \ 'since it is a negative value'.format(edge, self.type) raise ValueError(msg) # Check that bin edges are monotonically increasing for index in range(len(bins)): if index > 0 and bins[index] < bins[index-1]: msg = 'Unable to add bin edges "{0}" to a "{1}" Filter ' \ 'since they are not monotonically ' \ 'increasing'.format(bins, self.type) raise ValueError(msg) # mesh filters elif self._type == 'mesh': if not len(bins) == 1: msg = 'Unable to add bins "{0}" to a mesh Filter since ' \ 'only a single mesh can be used per tally'.format(bins) raise ValueError(msg) elif not isinstance(bins[0], Integral): msg = 'Unable to add bin "{0}" to mesh Filter since it ' \ 'is a non-integer'.format(bins[0]) raise ValueError(msg) elif bins[0] < 0: msg = 'Unable to add bin "{0}" to mesh Filter since it ' \ 'is a negative integer'.format(bins[0]) raise ValueError(msg) # If all error checks passed, add bin edges self._bins = np.array(bins)
def id(self, lattice_id): if lattice_id is None: self._id = openmc.universe.AUTO_UNIVERSE_ID openmc.universe.AUTO_UNIVERSE_ID += 1 else: cv.check_type('lattice ID', lattice_id, Integral) cv.check_greater_than('lattice ID', lattice_id, 0, equality=True) self._id = lattice_id
def temperature(self, temperature): cv.check_type('cell temperature', temperature, (Iterable, Real)) if isinstance(temperature, Iterable): cv.check_type('cell temperature', temperature, Iterable, Real) for T in temperature: cv.check_greater_than('cell temperature', T, 0.0, True) else: cv.check_greater_than('cell temperature', temperature, 0.0, True) self._temperature = temperature
def id(self, universe_id): if universe_id is None: global AUTO_UNIVERSE_ID self._id = AUTO_UNIVERSE_ID AUTO_UNIVERSE_ID += 1 else: cv.check_type('universe ID', universe_id, Integral) cv.check_greater_than('universe ID', universe_id, 0, equality=True) self._id = universe_id
def get_condensed_groups(self, coarse_groups): """Return a coarsened version of this EnergyGroups object. This method merges together energy groups in this object into wider energy groups as defined by the list of groups specified by the user, and returns a new, coarse EnergyGroups object. Parameters ---------- coarse_groups : Iterable of 2-tuple The energy groups of interest - a list of 2-tuples, each directly corresponding to one of the new coarse groups. The values in the 2-tuples are upper/lower energy groups used to construct a new coarse group. For example, if [(1,2), (3,4)] was used as the coarse groups, fine groups 1 and 2 would be merged into coarse group 1 while fine groups 3 and 4 would be merged into coarse group 2. Returns ------- EnergyGroups A coarsened version of this EnergyGroups object. Raises ------ ValueError If the group edges have not yet been set. """ cv.check_type('group edges', coarse_groups, Iterable) for group in coarse_groups: cv.check_type('group edges', group, Iterable) cv.check_length('group edges', group, 2) cv.check_greater_than('lower group', group[0], 1, True) cv.check_less_than('lower group', group[0], self.num_groups, True) cv.check_greater_than('upper group', group[0], 1, True) cv.check_less_than('upper group', group[0], self.num_groups, True) cv.check_less_than('lower group', group[0], group[1], False) # Compute the group indices into the coarse group group_bounds = [group[1] for group in coarse_groups] group_bounds.insert(0, coarse_groups[0][0]) # Determine the indices mapping the fine-to-coarse energy groups group_bounds = np.asarray(group_bounds) group_indices = np.flipud(self.num_groups - group_bounds) group_indices[-1] += 1 # Determine the edges between coarse energy groups and sort # in increasing order in case the user passed in unordered groups group_edges = self.group_edges[group_indices] group_edges = np.sort(group_edges) # Create a new condensed EnergyGroups object condensed_groups = EnergyGroups() condensed_groups.group_edges = group_edges return condensed_groups
def id(self, cell_id): if cell_id is None: global AUTO_CELL_ID self._id = AUTO_CELL_ID AUTO_CELL_ID += 1 else: cv.check_type('cell ID', cell_id, Integral) cv.check_greater_than('cell ID', cell_id, 0, equality=True) self._id = cell_id
def id(self, lattice_id): if lattice_id is None: global AUTO_UNIVERSE_ID self._id = AUTO_UNIVERSE_ID AUTO_UNIVERSE_ID += 1 else: cv.check_type('lattice ID', lattice_id, Integral) cv.check_greater_than('lattice ID', lattice_id, 0, equality=True) self._id = lattice_id
def id(self, plot_id): if plot_id is None: global AUTO_PLOT_ID self._id = AUTO_PLOT_ID AUTO_PLOT_ID += 1 else: cv.check_type('plot ID', plot_id, Integral) cv.check_greater_than('plot ID', plot_id, 0, equality=True) self._id = plot_id
def id(self, surface_id): if surface_id is None: global AUTO_SURFACE_ID self._id = AUTO_SURFACE_ID AUTO_SURFACE_ID += 1 else: check_type('surface ID', surface_id, Integral) check_greater_than('surface ID', surface_id, 0) self._id = surface_id
def id(self, mesh_id): if mesh_id is None: global AUTO_MESH_ID self._id = AUTO_MESH_ID AUTO_MESH_ID += 1 else: cv.check_type('mesh ID', mesh_id, Integral) cv.check_greater_than('mesh ID', mesh_id, 0, equality=True) self._id = mesh_id
def id(self, deriv_id): if deriv_id is None: global AUTO_TALLY_DERIV_ID self._id = AUTO_TALLY_DERIV_ID AUTO_TALLY_DERIV_ID += 1 else: cv.check_type('tally derivative ID', deriv_id, Integral) cv.check_greater_than('tally derivative ID', deriv_id, 0, equality=True) self._id = deriv_id
def branching_ratio(self, branching_ratio): cv.check_type('branching ratio', branching_ratio, UFloat) cv.check_greater_than('branching ratio', branching_ratio.nominal_value, 0.0, True) if branching_ratio.nominal_value == 0.0: warn('Decay mode {} of parent {} has a zero branching ratio.' .format(self.modes, self.parent)) cv.check_greater_than('branching ratio uncertainty', branching_ratio.std_dev, 0.0, True) self._branching_ratio = branching_ratio
def id(self, material_id): if material_id is None: global AUTO_MATERIAL_ID self._id = AUTO_MATERIAL_ID AUTO_MATERIAL_ID += 1 else: cv.check_type('material ID', material_id, Integral) cv.check_greater_than('material ID', material_id, 0, equality=True) self._id = material_id
def highlight_domains(self, geometry, domains, seed=1, alpha=0.5, background='gray'): """Use alpha compositing to highlight one or more domains in the plot. This routine generates a color scheme and applies alpha compositing to make all domains except the highlighted ones appear partially transparent. Parameters ---------- geometry : openmc.Geometry The geometry for which the plot is defined domains : Iterable of Integral A collection of the domain IDs to highlight in the plot seed : Integral The random number seed used to generate the color scheme alpha : Real in [0,1] The value to apply in alpha compisiting background : 3-tuple of Integral or 'white' or 'black' or 'gray' The background color to apply in alpha compisiting """ cv.check_iterable_type('domains', domains, Integral) cv.check_type('alpha', alpha, Real) cv.check_greater_than('alpha', alpha, 0., equality=True) cv.check_less_than('alpha', alpha, 1., equality=True) # Get a background (R,G,B) tuple to apply in alpha compositing if isinstance(background, basestring): if background == 'white': background = (255, 255, 255) elif background == 'black': background = (0, 0, 0) elif background == 'gray': background = (160, 160, 160) else: msg = 'The background "{}" is not defined'.format(background) raise ValueError(msg) cv.check_iterable_type('background', background, Integral) # Generate a color scheme self.colorize(geometry, seed) # Apply alpha compositing to the colors for all domains # other than those the user wishes to highlight for domain_id in self.col_spec: if domain_id not in domains: r, g, b = self.col_spec[domain_id] r = int(((1-alpha) * background[0]) + (alpha * r)) g = int(((1-alpha) * background[1]) + (alpha * g)) b = int(((1-alpha) * background[2]) + (alpha * b)) self._col_spec[domain_id] = (r, g, b)
def tabular_legendre(self, tabular_legendre): cv.check_type('tabular_legendre settings', tabular_legendre, Mapping) for key, value in tabular_legendre.items(): cv.check_value('tabular_legendre key', key, ['enable', 'num_points']) if key == 'enable': cv.check_type('enable tabular_legendre', value, bool) elif key == 'num_points': cv.check_type('num_points tabular_legendre', value, Integral) cv.check_greater_than('num_points tabular_legendre', value, 0) self._tabular_legendre = tabular_legendre
def statepoint(self, statepoint): cv.check_type('statepoint options', statepoint, Mapping) for key, value in statepoint.items(): if key == 'batches': cv.check_type('statepoint batches', value, Iterable, Integral) for batch in value: cv.check_greater_than('statepoint batch', batch, 0) else: raise ValueError("Unknown key '{}' encountered when setting " "statepoint options.".format(key)) self._statepoint = statepoint
def mask_background(self, mask_background): cv.check_type('plot mask background', mask_background, Iterable) if isinstance(mask_background, string_types): if mask_background.lower() not in _SVG_COLORS: raise ValueError("'{}' is not a valid color.".format(mask_background)) else: cv.check_length('plot mask_background', mask_background, 3) for rgb in mask_background: cv.check_greater_than('plot mask background', rgb, 0, True) cv.check_less_than('plot mask background', rgb, 256) self._mask_background = mask_background
def legendre_order(self, legendre_order): cv.check_type('legendre_order', legendre_order, Integral) cv.check_greater_than('legendre_order', legendre_order, 0, equality=True) cv.check_less_than('legendre_order', legendre_order, 10, equality=True) if self.correction == 'P0' and legendre_order > 0: msg = 'The P0 correction will be ignored since the scattering ' \ 'order {} is greater than zero'.format(self.legendre_order) warn(msg, RuntimeWarning) self.correction = None self._legendre_order = legendre_order
def highlight_domains(self, geometry, domains, seed=1, alpha=0.5, background='gray'): """Use alpha compositing to highlight one or more domains in the plot. This routine generates a color scheme and applies alpha compositing to make all domains except the highlighted ones appear partially transparent. Parameters ---------- geometry : openmc.Geometry The geometry for which the plot is defined domains : Iterable of openmc.Cell or openmc.Material A collection of the domain IDs to highlight in the plot seed : int The random number seed used to generate the color scheme alpha : float The value between 0 and 1 to apply in alpha compisiting background : 3-tuple of int or str The background color to apply in alpha compisiting """ cv.check_type('domains', domains, Iterable, (openmc.Cell, openmc.Material)) cv.check_type('alpha', alpha, Real) cv.check_greater_than('alpha', alpha, 0., equality=True) cv.check_less_than('alpha', alpha, 1., equality=True) cv.check_type('background', background, Iterable) # Get a background (R,G,B) tuple to apply in alpha compositing if isinstance(background, string_types): if background.lower() not in _SVG_COLORS: raise ValueError("'{}' is not a valid color.".format(background)) background = _SVG_COLORS[background.lower()] # Generate a color scheme self.colorize(geometry, seed) # Apply alpha compositing to the colors for all domains # other than those the user wishes to highlight for domain, color in self.colors.items(): if domain not in domains: if isinstance(color, string_types): color = _SVG_COLORS[color.lower()] r, g, b = color r = int(((1-alpha) * background[0]) + (alpha * r)) g = int(((1-alpha) * background[1]) + (alpha * g)) b = int(((1-alpha) * background[2]) + (alpha * b)) self._colors[domain] = (r, g, b)
def colors(self, colors): cv.check_type('plot colors', colors, Mapping) for key, value in colors.items(): cv.check_type('plot color key', key, (openmc.Cell, openmc.Material)) cv.check_type('plot color value', value, Iterable) if isinstance(value, string_types): if value.lower() not in _SVG_COLORS: raise ValueError("'{}' is not a valid color.".format(value)) else: cv.check_length('plot color (RGB)', value, 3) for component in value: cv.check_type('RGB component', component, Real) cv.check_greater_than('RGB component', component, 0, True) cv.check_less_than('RGB component', component, 255, True) self._colors = colors
def sourcepoint(self, sourcepoint): cv.check_type('sourcepoint options', sourcepoint, Mapping) for key, value in sourcepoint.items(): if key == 'batches': cv.check_type('sourcepoint batches', value, Iterable, Integral) for batch in value: cv.check_greater_than('sourcepoint batch', batch, 0) elif key == 'separate': cv.check_type('sourcepoint separate', value, bool) elif key == 'write': cv.check_type('sourcepoint write', value, bool) elif key == 'overwrite': cv.check_type('sourcepoint overwrite', value, bool) else: raise ValueError("Unknown key '{}' encountered when setting " "sourcepoint options.".format(key)) self._sourcepoint = sourcepoint
def trace(self, trace): cv.check_type('trace', trace, Iterable, Integral) cv.check_length('trace', trace, 3) cv.check_greater_than('trace batch', trace[0], 0) cv.check_greater_than('trace generation', trace[1], 0) cv.check_greater_than('trace particle', trace[2], 0) self._trace = trace
def resonance_scattering(self, res): cv.check_type('resonance scattering settings', res, Mapping) keys = ('enable', 'method', 'energy_min', 'energy_max', 'nuclides') for key, value in res.items(): cv.check_value('resonance scattering dictionary key', key, keys) if key == 'enable': cv.check_type('resonance scattering enable', value, bool) elif key == 'method': cv.check_value('resonance scattering method', value, _RES_SCAT_METHODS) elif key == 'energy_min': name = 'resonance scattering minimum energy' cv.check_type(name, value, Real) cv.check_greater_than(name, value, 0) elif key == 'energy_max': name = 'resonance scattering minimum energy' cv.check_type(name, value, Real) cv.check_greater_than(name, value, 0) elif key == 'nuclides': cv.check_type('resonance scattering nuclides', value, Iterable, string_types) self._resonance_scattering = res
def get_group_indices(self, groups='all'): """Returns the array indices for one or more energy groups. Parameters ---------- groups : str, tuple The energy groups of interest - a tuple of the energy group indices, starting at 1 for the highest energies (default is 'all') Returns ------- numpy.ndarray The ndarray array indices for each energy group of interest Raises ------ ValueError If the group edges have not yet been set, or if a group is requested that is outside the bounds of the number of energy groups. """ if self.group_edges is None: msg = 'Unable to get energy group indices for groups "{0}" since ' \ 'the group edges have not yet been set'.format(groups) raise ValueError(msg) if groups == 'all': return np.arange(self.num_groups) else: indices = np.zeros(len(groups), dtype=np.int) for i, group in enumerate(groups): cv.check_greater_than('group', group, 0) cv.check_less_than('group', group, self.num_groups, equality=True) indices[i] = group - 1 return indices
def add_s_alpha_beta(self, name, fraction=1.0): r"""Add an :math:`S(\alpha,\beta)` table to the material Parameters ---------- name : str Name of the :math:`S(\alpha,\beta)` table fraction : float The fraction of relevant nuclei that are affected by the :math:`S(\alpha,\beta)` table. For example, if the material is a block of carbon that is 60% graphite and 40% amorphous then add a graphite :math:`S(\alpha,\beta)` table with fraction=0.6. """ if self._macroscopic is not None: msg = 'Unable to add an S(a,b) table to Material ID="{}" as a ' \ 'macroscopic data-set has already been added'.format(self._id) raise ValueError(msg) if not isinstance(name, str): msg = 'Unable to add an S(a,b) table to Material ID="{}" with a ' \ 'non-string table name "{}"'.format(self._id, name) raise ValueError(msg) cv.check_type('S(a,b) fraction', fraction, Real) cv.check_greater_than('S(a,b) fraction', fraction, 0.0, True) cv.check_less_than('S(a,b) fraction', fraction, 1.0, True) new_name = openmc.data.get_thermal_name(name) if new_name != name: msg = 'OpenMC S(a,b) tables follow the GND naming convention. ' \ 'Table "{}" is being renamed as "{}".'.format(name, new_name) warnings.warn(msg) self._sab.append((new_name, fraction))
def track(self, track): check_type('track', track, Iterable, Integral) if len(track) % 3 != 0: msg = 'Unable to set the track to "{0}" since its length is ' \ 'not a multiple of 3'.format(track) raise ValueError(msg) for t in zip(track[::3], track[1::3], track[2::3]): check_greater_than('track batch', t[0], 0) check_greater_than('track generation', t[0], 0) check_greater_than('track particle', t[0], 0) self._track = track
def __init__(self, chain_nuclides, n_bmats, cutoff=112.0, thermal_energy=0.0253, fast_energy=500.0e3): check_type("cutoff", cutoff, Real) check_type("thermal_energy", thermal_energy, Real) check_type("fast_energy", fast_energy, Real) check_greater_than("thermal_energy", thermal_energy, 0.0, equality=True) check_greater_than("cutoff", cutoff, thermal_energy, equality=False) check_greater_than("fast_energy", fast_energy, cutoff, equality=False) self.n_bmats = n_bmats super().__init__(chain_nuclides) self._cutoff = cutoff self._thermal_yields = {} self._fast_yields = {} convert_to_constant = set() for name, nuc in self._chain_nuclides.items(): yields = nuc.yield_data energies = nuc.yield_energies thermal = yields.get(thermal_energy) fast = yields.get(fast_energy) if thermal is None or fast is None: if cutoff <= energies[0]: # use lowest energy yields as constant self._constant_yields[name] = yields[energies[0]] convert_to_constant.add(name) continue if cutoff >= energies[-1]: # use highest energy yields as constant self._constant_yields[name] = yields[energies[-1]] convert_to_constant.add(name) continue cutoff_ix = bisect.bisect_left(energies, cutoff) # find closest energy to requested thermal, fast energies if thermal is None: min_E = min(energies[:cutoff_ix], key=lambda e: abs(e - thermal_energy)) thermal = yields[min_E] if fast is None: min_E = min(energies[cutoff_ix:], key=lambda e: abs(e - fast_energy)) fast = yields[min_E] self._thermal_yields[name] = thermal self._fast_yields[name] = fast for name in convert_to_constant: self._chain_nuclides.pop(name)
def cutoff(self, cutoff): if not isinstance(cutoff, Mapping): msg = 'Unable to set cutoff from "{0}" which is not a '\ ' Python dictionary'.format(cutoff) raise ValueError(msg) for key in cutoff: if key == 'weight': cv.check_type('weight cutoff', cutoff['weight'], Real) cv.check_greater_than('weight cutoff', cutoff['weight'], 0.0) elif key == 'weight_avg': cv.check_type('average survival weight', cutoff['weight_avg'], Real) cv.check_greater_than('average survival weight', cutoff['weight_avg'], 0.0) elif key == 'energy': cv.check_type('energy cutoff', cutoff['energy'], Real) cv.check_greater_than('energy cutoff', cutoff['energy'], 0.0) else: msg = 'Unable to set cutoff to "{0}" which is unsupported by '\ 'OpenMC'.format(key) self._cutoff = cutoff
def meshlines(self, meshlines): cv.check_type('plot meshlines', meshlines, dict) if 'type' not in meshlines: msg = 'Unable to set on plot the meshlines "{0}" which ' \ 'does not have a "type" key'.format(meshlines) raise ValueError(msg) elif meshlines['type'] not in ['tally', 'entropy', 'ufs', 'cmfd']: msg = 'Unable to set the meshlines with ' \ 'type "{0}"'.format(meshlines['type']) raise ValueError(msg) if 'id' in meshlines: cv.check_type('plot meshlines id', meshlines['id'], Integral) cv.check_greater_than('plot meshlines id', meshlines['id'], 0, equality=True) if 'linewidth' in meshlines: cv.check_type('plot mesh linewidth', meshlines['linewidth'], Integral) cv.check_greater_than('plot mesh linewidth', meshlines['linewidth'], 0, equality=True) if 'color' in meshlines: cv.check_type('plot meshlines color', meshlines['color'], Iterable, Integral) cv.check_length('plot meshlines color', meshlines['color'], 3) for rgb in meshlines['color']: cv.check_greater_than('plot meshlines color', rgb, 0, True) cv.check_less_than('plot meshlines color', rgb, 256) self._meshlines = meshlines
def spacing(self, spacing): if spacing is not None: cv.check_type('spacing', spacing, Real) cv.check_greater_than('spacing', spacing, 0.0, equality=False) self._spacing = spacing
def __init__(self, operator, timesteps, power=None, power_density=None, timestep_units='s', solver="cram48"): # Check number of stages previously used if operator.prev_res is not None: res = operator.prev_res[-1] if res.data.shape[0] != self._num_stages: raise ValueError( "{} incompatible with previous restart calculation. " "Previous scheme used {} intermediate solutions, while " "this uses {}".format( self.__class__.__name__, res.data.shape[0], self._num_stages)) self.operator = operator self.chain = operator.chain # Determine power and normalize units to W if power is None: if power_density is None: raise ValueError("Either power or power density must be set") if not isinstance(power_density, Iterable): power = power_density * operator.heavy_metal else: power = [p*operator.heavy_metal for p in power_density] if not isinstance(power, Iterable): # Ensure that power is single value if that is the case power = [power] * len(timesteps) if len(power) != len(timesteps): raise ValueError( "Number of time steps ({}) != number of powers ({})".format( len(timesteps), len(power))) # Get list of times / units if isinstance(timesteps[0], Iterable): times, units = zip(*timesteps) else: times = timesteps units = [timestep_units] * len(timesteps) # Determine number of seconds for each timestep seconds = [] for timestep, unit, watts in zip(times, units, power): # Make sure values passed make sense check_type('timestep', timestep, Real) check_greater_than('timestep', timestep, 0.0, False) check_type('timestep units', unit, str) check_type('power', watts, Real) check_greater_than('power', watts, 0.0, True) if unit in ('s', 'sec'): seconds.append(timestep) elif unit in ('min', 'minute'): seconds.append(timestep*_SECONDS_PER_MINUTE) elif unit in ('h', 'hr', 'hour'): seconds.append(timestep*_SECONDS_PER_HOUR) elif unit in ('d', 'day'): seconds.append(timestep*_SECONDS_PER_DAY) elif unit.lower() == 'mwd/kg': watt_days_per_kg = 1e6*timestep kilograms = 1e-3*operator.heavy_metal days = watt_days_per_kg * kilograms / watts seconds.append(days*_SECONDS_PER_DAY) else: raise ValueError("Invalid timestep unit '{}'".format(unit)) self.timesteps = asarray(seconds) self.power = asarray(power) if isinstance(solver, str): # Delay importing of cram module, which requires this file if solver == "cram48": from .cram import CRAM48 self._solver = CRAM48 elif solver == "cram16": from .cram import CRAM16 self._solver = CRAM16 else: raise ValueError( "Solver {} not understood. Expected 'cram48' or " "'cram16'".format(solver)) else: self.solver = solver
def dilute_initial(self, value): check_type("dilute_initial", value, Real) check_greater_than("dilute_initial", value, 0.0, equality=True) self._dilute_initial = value
def _calculate_cexs_nuclide(this, types, temperature=294., sab_name=None, cross_sections=None): """Calculates continuous-energy cross sections of a requested type. Parameters ---------- this : openmc.Nuclide Nuclide object to source data from types : Iterable of str or Integral The type of cross sections to calculate; values can either be those in openmc.PLOT_TYPES or keys from openmc.data.REACTION_MT which correspond to a reaction description e.g '(n,2n)' or integers which correspond to reaction channel (MT) numbers. temperature : float, optional Temperature in Kelvin to plot. If not specified, a default temperature of 294K will be plotted. Note that the nearest temperature in the library for each nuclide will be used as opposed to using any interpolation. sab_name : str, optional Name of S(a,b) library to apply to MT=2 data when applicable. cross_sections : str, optional Location of cross_sections.xml file. Default is None. Returns ------- energy_grid : numpy.ndarray Energies at which cross sections are calculated, in units of eV data : Iterable of Callable Requested cross section functions """ # Load the library library = openmc.data.DataLibrary.from_xml(cross_sections) # Convert temperature to format needed for access in the library strT = f"{int(round(temperature))}K" T = temperature # Now we can create the data sets to be plotted energy_grid = [] xs = [] lib = library.get_by_material(this) if lib is not None: nuc = openmc.data.IncidentNeutron.from_hdf5(lib['path']) # Obtain the nearest temperature if strT in nuc.temperatures: nucT = strT else: delta_T = np.array(nuc.kTs) - T * openmc.data.K_BOLTZMANN closest_index = np.argmin(np.abs(delta_T)) nucT = nuc.temperatures[closest_index] # Prep S(a,b) data if needed if sab_name: sab = openmc.data.ThermalScattering.from_hdf5(sab_name) # Obtain the nearest temperature if strT in sab.temperatures: sabT = strT else: delta_T = np.array(sab.kTs) - T * openmc.data.K_BOLTZMANN closest_index = np.argmin(np.abs(delta_T)) sabT = sab.temperatures[closest_index] # Create an energy grid composed the S(a,b) and the nuclide's grid grid = nuc.energy[nucT] sab_Emax = 0. sab_funcs = [] if sab.elastic is not None: elastic = sab.elastic.xs[sabT] if isinstance(elastic, openmc.data.CoherentElastic): grid = np.union1d(grid, elastic.bragg_edges) if elastic.bragg_edges[-1] > sab_Emax: sab_Emax = elastic.bragg_edges[-1] elif isinstance(elastic, openmc.data.Tabulated1D): grid = np.union1d(grid, elastic.x) if elastic.x[-1] > sab_Emax: sab_Emax = elastic.x[-1] sab_funcs.append(elastic) if sab.inelastic is not None: inelastic = sab.inelastic.xs[sabT] grid = np.union1d(grid, inelastic.x) if inelastic.x[-1] > sab_Emax: sab_Emax = inelastic.x[-1] sab_funcs.append(inelastic) energy_grid = grid else: energy_grid = nuc.energy[nucT] # Parse the types mts = [] ops = [] yields = [] for line in types: if line in PLOT_TYPES: tmp_mts = [mtj for mti in PLOT_TYPES_MT[line] for mtj in nuc.get_reaction_components(mti)] mts.append(tmp_mts) if line.startswith('nu'): yields.append(True) else: yields.append(False) if XI_MT in tmp_mts: ops.append((np.add,) * (len(tmp_mts) - 2) + (np.multiply,)) else: ops.append((np.add,) * (len(tmp_mts) - 1)) elif line in openmc.data.REACTION_MT: mt_number = openmc.data.REACTION_MT[line] cv.check_type('MT in types', mt_number, Integral) cv.check_greater_than('MT in types', mt_number, 0) tmp_mts = nuc.get_reaction_components(mt_number) mts.append(tmp_mts) ops.append((np.add,) * (len(tmp_mts) - 1)) yields.append(False) elif isinstance(line, int): # Not a built-in type, we have to parse it ourselves cv.check_type('MT in types', line, Integral) cv.check_greater_than('MT in types', line, 0) tmp_mts = nuc.get_reaction_components(line) mts.append(tmp_mts) ops.append((np.add,) * (len(tmp_mts) - 1)) yields.append(False) else: raise TypeError("Invalid type", line) for i, mt_set in enumerate(mts): # Get the reaction xs data from the nuclide funcs = [] op = ops[i] for mt in mt_set: if mt == 2: if sab_name: # Then we need to do a piece-wise function of # The S(a,b) and non-thermal data sab_sum = openmc.data.Sum(sab_funcs) pw_funcs = openmc.data.Regions1D( [sab_sum, nuc[mt].xs[nucT]], [sab_Emax]) funcs.append(pw_funcs) else: funcs.append(nuc[mt].xs[nucT]) elif mt in nuc: if yields[i]: # Get the total yield first if available. This will be # used primarily for fission. for prod in chain(nuc[mt].products, nuc[mt].derived_products): if prod.particle == 'neutron' and \ prod.emission_mode == 'total': func = openmc.data.Combination( [nuc[mt].xs[nucT], prod.yield_], [np.multiply]) funcs.append(func) break else: # Total doesn't exist so we have to create from # prompt and delayed. This is used for scatter # multiplication. func = None for prod in chain(nuc[mt].products, nuc[mt].derived_products): if prod.particle == 'neutron' and \ prod.emission_mode != 'total': if func: func = openmc.data.Combination( [prod.yield_, func], [np.add]) else: func = prod.yield_ if func: funcs.append(openmc.data.Combination( [func, nuc[mt].xs[nucT]], [np.multiply])) else: # If func is still None, then there were no # products. In that case, assume the yield is # one as its not provided for some summed # reactions like MT=4 funcs.append(nuc[mt].xs[nucT]) else: funcs.append(nuc[mt].xs[nucT]) elif mt == UNITY_MT: funcs.append(lambda x: 1.) elif mt == XI_MT: awr = nuc.atomic_weight_ratio alpha = ((awr - 1.) / (awr + 1.))**2 xi = 1. + alpha * np.log(alpha) / (1. - alpha) funcs.append(lambda x: xi) else: funcs.append(lambda x: 0.) funcs = funcs if funcs else [lambda x: 0.] xs.append(openmc.data.Combination(funcs, op)) else: raise ValueError(this + " not in library") return energy_grid, xs
def p(self, p): cv.check_type('tabulated probabilities', p, Iterable, Real) if not self._ignore_negative: for pk in p: cv.check_greater_than('tabulated probability', pk, 0.0, True) self._p = p
def _calculate_mgxs_nuc_macro(this, types, library, orders=None, temperature=294.): """Determines the multi-group cross sections of a nuclide or macroscopic object. If the data for the nuclide or macroscopic object in the library is represented as angle-dependent data then this method will return the geometric average cross section over all angles. Parameters ---------- this : openmc.Nuclide or openmc.Macroscopic Object to source data from types : Iterable of str The type of cross sections to calculate; values can either be those in openmc.PLOT_TYPES_MGXS library : openmc.MGXSLibrary MGXS Library containing the data of interest orders : Iterable of Integral, optional The scattering order or delayed group index to use for the corresponding entry in types. Defaults to the 0th order for scattering and the total delayed neutron data. temperature : float, optional Temperature in Kelvin to plot. If not specified, a default temperature of 294K will be plotted. Note that the nearest temperature in the library for each nuclide will be used as opposed to using any interpolation. Returns ------- data : numpy.ndarray Cross sections calculated at the energy grid described by energy_grid """ # Check the parameters and grab order/delayed groups if orders: cv.check_iterable_type('orders', orders, Integral, min_depth=len(types), max_depth=len(types)) else: orders = [None] * len(types) for i, line in enumerate(types): cv.check_type("line", line, str) cv.check_value("line", line, PLOT_TYPES_MGXS) if orders[i]: cv.check_greater_than("order value", orders[i], 0, equality=True) xsdata = library.get_by_name(this) if xsdata is not None: # Obtain the nearest temperature t = np.abs(xsdata.temperatures - temperature).argmin() # Get the data data = np.zeros((len(types), library.energy_groups.num_groups)) for i, line in enumerate(types): if 'fission' in line and not xsdata.fissionable: continue elif line == 'unity': data[i, :] = 1. else: # Now we have to get the cross section data and properly # treat it depending on the requested type. # First get the data in a generic fashion temp_data = getattr(xsdata, _PLOT_MGXS_ATTR[line])[t] shape = temp_data.shape[:] # If we have angular data, then want the geometric # average over all provided angles. Since the angles are # equi-distant, un-weighted averaging will suffice if xsdata.representation == 'angle': temp_data = np.mean(temp_data, axis=(0, 1)) # Now we can look at the shape of the data to identify how # it should be modified to produce an array of values # with groups. if shape in (xsdata.xs_shapes["[G']"], xsdata.xs_shapes["[G]"]): # Then the data is already an array vs groups so copy # and move along data[i, :] = temp_data elif shape == xsdata.xs_shapes["[G][G']"]: # Sum the data over outgoing groups to create our array vs # groups data[i, :] = np.sum(temp_data, axis=1) elif shape == xsdata.xs_shapes["[DG]"]: # Then we have a constant vs groups with a value for each # delayed group. The user-provided value of orders tells us # which delayed group we want. If none are provided, then # we sum all the delayed groups together. if orders[i]: if orders[i] < len(shape[0]): data[i, :] = temp_data[orders[i]] else: data[i, :] = np.sum(temp_data[:]) elif shape in (xsdata.xs_shapes["[DG][G']"], xsdata.xs_shapes["[DG][G]"]): # Then we have an array vs groups with values for each # delayed group. The user-provided value of orders tells us # which delayed group we want. If none are provided, then # we sum all the delayed groups together. if orders[i]: if orders[i] < len(shape[0]): data[i, :] = temp_data[orders[i], :] else: data[i, :] = np.sum(temp_data[:, :], axis=0) elif shape == xsdata.xs_shapes["[DG][G][G']"]: # Then we have a delayed group matrix. We will first # remove the outgoing group dependency temp_data = np.sum(temp_data, axis=-1) # And then proceed in exactly the same manner as the # "[DG][G']" or "[DG][G]" shapes in the previous block. if orders[i]: if orders[i] < len(shape[0]): data[i, :] = temp_data[orders[i], :] else: data[i, :] = np.sum(temp_data[:, :], axis=0) elif shape == xsdata.xs_shapes["[G][G'][Order]"]: # This is a scattering matrix with angular data # First remove the outgoing group dependence temp_data = np.sum(temp_data, axis=1) # The user either provided a specific order or we resort # to the default 0th order if orders[i]: order = orders[i] else: order = 0 # If the order is available, store the data for that order # if it is not available, then the expansion coefficient # is zero and thus we already have the correct value. if order < shape[1]: data[i, :] = temp_data[:, order] else: raise ValueError(f"{this} not present in provided MGXS library") return data
def end_E(self, end_E): if end_E is not None: cv.check_type('end_E', end_E, Real) cv.check_greater_than('end_E', end_E, 0.0, equality=False) self._end_E = end_E
def atomic_weight_ratio(self, atomic_weight_ratio): name = 'N-body phase space atomic weight ratio' cv.check_type(name, atomic_weight_ratio, Real) cv.check_greater_than(name, atomic_weight_ratio, 0.0) self._atomic_weight_ratio = atomic_weight_ratio
def sqrtAWR(self, sqrtAWR): if sqrtAWR is not None: cv.check_type('sqrtAWR', sqrtAWR, Real) cv.check_greater_than('sqrtAWR', sqrtAWR, 0.0, equality=False) self._sqrtAWR = sqrtAWR
def total_mass(self, total_mass): name = 'N-body phase space total mass' cv.check_type(name, total_mass, Real) cv.check_greater_than(name, total_mass, 0.) self._total_mass = total_mass
def start_E(self, start_E): if start_E is not None: cv.check_type('start_E', start_E, Real) cv.check_greater_than('start_E', start_E, 0.0, equality=True) self._start_E = start_E
def pixels(self, pixels): cv.check_type('plot pixels', pixels, Iterable, Integral) cv.check_length('plot pixels', pixels, 2, 3) for dim in pixels: cv.check_greater_than('plot pixels', dim, 0) self._pixels = pixels
def add_element(self, element, percent, percent_type='ao', enrichment=None): """Add a natural element to the material Parameters ---------- element : str Element to add, e.g., 'Zr' percent : float Atom or weight percent percent_type : {'ao', 'wo'}, optional 'ao' for atom percent and 'wo' for weight percent. Defaults to atom percent. enrichment : float, optional Enrichment for U235 in weight percent. For example, input 4.95 for 4.95 weight percent enriched U. Default is None (natural composition). """ cv.check_type('nuclide', element, str) cv.check_type('percent', percent, Real) cv.check_value('percent type', percent_type, {'ao', 'wo'}) if self._macroscopic is not None: msg = 'Unable to add an Element to Material ID="{}" as a ' \ 'macroscopic data-set has already been added'.format(self._id) raise ValueError(msg) if enrichment is not None: if not isinstance(enrichment, Real): msg = 'Unable to add an Element to Material ID="{}" with a ' \ 'non-floating point enrichment value "{}"'\ .format(self._id, enrichment) raise ValueError(msg) elif element != 'U': msg = 'Unable to use enrichment for element {} which is not ' \ 'uranium for Material ID="{}"'.format(element, self._id) raise ValueError(msg) # Check that the enrichment is in the valid range cv.check_less_than('enrichment', enrichment, 100. / 1.008) cv.check_greater_than('enrichment', enrichment, 0., equality=True) if enrichment > 5.0: msg = 'A uranium enrichment of {} was given for Material ID='\ '"{}". OpenMC assumes the U234/U235 mass ratio is '\ 'constant at 0.008, which is only valid at low ' \ 'enrichments. Consider setting the isotopic ' \ 'composition manually for enrichments over 5%.'.\ format(enrichment, self._id) warnings.warn(msg) # Make sure element name is just that if not element.isalpha(): raise ValueError("Element name should be given by the " "element's symbol, e.g., 'Zr'") # Add naturally-occuring isotopes element = openmc.Element(element) for nuclide in element.expand(percent, percent_type, enrichment): self.add_nuclide(*nuclide)
def __init__(self, operator, timesteps, power=None, power_density=None, n_steps=10): check_type("n_steps", n_steps, Integral) check_greater_than("n_steps", n_steps, 0) super().__init__(operator, timesteps, power, power_density) self.n_steps = n_steps
def add_element(self, element, percent, percent_type='ao', enrichment=None, enrichment_target=None, enrichment_type=None): """Add a natural element to the material Parameters ---------- element : str Element to add, e.g., 'Zr' or 'Zirconium' percent : float Atom or weight percent percent_type : {'ao', 'wo'}, optional 'ao' for atom percent and 'wo' for weight percent. Defaults to atom percent. enrichment : float, optional Enrichment of an enrichment_taget nuclide in percent (ao or wo). If enrichment_taget is not supplied then it is enrichment for U235 in weight percent. For example, input 4.95 for 4.95 weight percent enriched U. Default is None (natural composition). enrichment_target: str, optional Single nuclide name to enrich from a natural composition (e.g., 'O16') .. versionadded:: 0.12 enrichment_type: {'ao', 'wo'}, optional 'ao' for enrichment as atom percent and 'wo' for weight percent. Default is: 'ao' for two-isotope enrichment; 'wo' for U enrichment .. versionadded:: 0.12 Notes ----- General enrichment procedure is allowed only for elements composed of two isotopes. If `enrichment_target` is given without `enrichment` natural composition is added to the material. """ cv.check_type('nuclide', element, str) cv.check_type('percent', percent, Real) cv.check_value('percent type', percent_type, {'ao', 'wo'}) # Make sure element name is just that if not element.isalpha(): raise ValueError( "Element name should be given by the " "element's symbol or name, e.g., 'Zr', 'zirconium'") # Allow for element identifier to be given as a symbol or name if len(element) > 2: el = element.lower() element = openmc.data.ELEMENT_SYMBOL.get(el) if element is None: msg = 'Element name "{}" not recognised'.format(el) raise ValueError(msg) else: if element[0].islower(): msg = 'Element name "{}" should start with an uppercase ' \ 'letter'.format(element) raise ValueError(msg) if len(element) == 2 and element[1].isupper(): msg = 'Element name "{}" should end with a lowercase ' \ 'letter'.format(element) raise ValueError(msg) # skips the first entry of ATOMIC_SYMBOL which is n for neutron if element not in list(openmc.data.ATOMIC_SYMBOL.values())[1:]: msg = 'Element name "{}" not recognised'.format(element) raise ValueError(msg) if self._macroscopic is not None: msg = 'Unable to add an Element to Material ID="{}" as a ' \ 'macroscopic data-set has already been added'.format(self._id) raise ValueError(msg) if enrichment is not None and enrichment_target is None: if not isinstance(enrichment, Real): msg = 'Unable to add an Element to Material ID="{}" with a ' \ 'non-floating point enrichment value "{}"'\ .format(self._id, enrichment) raise ValueError(msg) elif element != 'U': msg = 'Unable to use enrichment for element {} which is not ' \ 'uranium for Material ID="{}"'.format(element, self._id) raise ValueError(msg) # Check that the enrichment is in the valid range cv.check_less_than('enrichment', enrichment, 100. / 1.008) cv.check_greater_than('enrichment', enrichment, 0., equality=True) if enrichment > 5.0: msg = 'A uranium enrichment of {} was given for Material ID='\ '"{}". OpenMC assumes the U234/U235 mass ratio is '\ 'constant at 0.008, which is only valid at low ' \ 'enrichments. Consider setting the isotopic ' \ 'composition manually for enrichments over 5%.'.\ format(enrichment, self._id) warnings.warn(msg) # Add naturally-occuring isotopes element = openmc.Element(element) for nuclide in element.expand(percent, percent_type, enrichment, enrichment_target, enrichment_type): self.add_nuclide(*nuclide)
def theta(self, theta): cv.check_type('Maxwell temperature', theta, Real) cv.check_greater_than('Maxwell temperature', theta, 0.0) self._theta = theta
def energy(self, energy): cv.check_type('decay energy', energy, UFloat) cv.check_greater_than('decay energy', energy.nominal_value, 0.0, True) cv.check_greater_than('decay energy uncertainty', energy.std_dev, 0.0, True) self._energy = energy
def a(self, a): cv.check_type('Watt a', a, Real) cv.check_greater_than('Watt a', a, 0.0) self._a = a
def n_particles(self, n_particles): name = 'N-body phase space number of particles' cv.check_type(name, n_particles, Integral) cv.check_greater_than(name, n_particles, 0) self._n_particles = n_particles
def b(self, b): cv.check_type('Watt b', b, Real) cv.check_greater_than('Watt b', b, 0.0) self._b = b
def expand(self, percent, percent_type, enrichment=None, enrichment_target=None, enrichment_type=None, cross_sections=None): """Expand natural element into its naturally-occurring isotopes. An optional cross_sections argument or the :envvar:`OPENMC_CROSS_SECTIONS` environment variable is used to specify a cross_sections.xml file. If the cross_sections.xml file is found, the element is expanded only into the isotopes/nuclides present in cross_sections.xml. If no cross_sections.xml file is found, the element is expanded based on its naturally occurring isotopes. Parameters ---------- percent : float Atom or weight percent percent_type : {'ao', 'wo'} 'ao' for atom percent and 'wo' for weight percent enrichment : float, optional Enrichment of an enrichment_taget nuclide in percent (ao or wo). If enrichment_taget is not supplied then it is enrichment for U235 in weight percent. For example, input 4.95 for 4.95 weight percent enriched U. Default is None (natural composition). enrichment_target: str, optional Single nuclide name to enrich from a natural composition (e.g., 'O16') .. versionadded:: 0.12 enrichment_type: {'ao', 'wo'}, optional 'ao' for enrichment as atom percent and 'wo' for weight percent. Default is: 'ao' for two-isotope enrichment; 'wo' for U enrichment .. versionadded:: 0.12 cross_sections : str, optional Location of cross_sections.xml file. Default is None. Returns ------- isotopes : list Naturally-occurring isotopes of the element. Each item of the list is a tuple consisting of a nuclide string, the atom/weight percent, and the string 'ao' or 'wo'. Raises ------ ValueError No data is available for any of natural isotopes of the element ValueError If only some natural isotopes are available in the cross-section data library and the element is not O, W, or Ta ValueError If a non-naturally-occurring isotope is requested ValueError If enrichment is requested of an element with more than two naturally-occurring isotopes. ValueError If enrichment procedure for Uranium is used when element is not Uranium. ValueError Uranium enrichment is requested with enrichment_type=='ao' Notes ----- When the `enrichment` argument is specified, a correlation from `ORNL/CSD/TM-244 <https://doi.org/10.2172/5561567>`_ is used to calculate the weight fractions of U234, U235, U236, and U238. Namely, the weight fraction of U234 and U236 are taken to be 0.89% and 0.46%, respectively, of the U235 weight fraction. The remainder of the isotopic weight is assigned to U238. When the `enrichment` argument is specified with `enrichment_target`, a general enrichment procedure is used for elements composed of exactly two naturally-occurring isotopes. `enrichment` is interpreted as atom percent by default but can be controlled by the `enrichment_type` argument. """ # Check input if enrichment_type is not None: cv.check_value('enrichment_type', enrichment_type, {'ao', 'wo'}) if enrichment is not None: cv.check_less_than('enrichment', enrichment, 100.0, equality=True) cv.check_greater_than('enrichment', enrichment, 0., equality=True) # Get the nuclides present in nature natural_nuclides = {name for name, abundance in natural_isotopes(self)} # Create dict to store the expanded nuclides and abundances abundances = OrderedDict() # If cross_sections is None, get the cross sections from the # OPENMC_CROSS_SECTIONS environment variable if cross_sections is None: cross_sections = os.environ.get('OPENMC_CROSS_SECTIONS') # If a cross_sections library is present, check natural nuclides # against the nuclides in the library if cross_sections is not None: library_nuclides = set() tree = ET.parse(cross_sections) root = tree.getroot() for child in root.findall('library'): nuclide = child.attrib['materials'] if re.match(r'{}\d+'.format(self), nuclide) and \ '_m' not in nuclide: library_nuclides.add(nuclide) # Get a set of the mutual and absent nuclides. Convert to lists # and sort to avoid different ordering between Python 2 and 3. mutual_nuclides = natural_nuclides.intersection(library_nuclides) absent_nuclides = natural_nuclides.difference(mutual_nuclides) mutual_nuclides = sorted(list(mutual_nuclides)) absent_nuclides = sorted(list(absent_nuclides)) # If all natural nuclides are present in the library, # expand element using all natural nuclides if len(absent_nuclides) == 0: for nuclide in mutual_nuclides: abundances[nuclide] = NATURAL_ABUNDANCE[nuclide] # If no natural elements are present in the library, check if the # 0 nuclide is present. If so, set the abundance to 1 for this # nuclide. Else, raise an error. elif len(mutual_nuclides) == 0: nuclide_0 = self + '0' if nuclide_0 in library_nuclides: abundances[nuclide_0] = 1.0 else: msg = 'Unable to expand element {0} because the cross '\ 'section library provided does not contain any of '\ 'the natural isotopes for that element.'\ .format(self) raise ValueError(msg) # If some, but not all, natural nuclides are in the library, add # the mutual nuclides. For the absent nuclides, add them based on # our knowledge of the common cross section libraries # (ENDF, JEFF, and JENDL) else: # Add the mutual isotopes for nuclide in mutual_nuclides: abundances[nuclide] = NATURAL_ABUNDANCE[nuclide] # Adjust the abundances for the absent nuclides for nuclide in absent_nuclides: if nuclide in ['O17', 'O18'] and 'O16' in mutual_nuclides: abundances['O16'] += NATURAL_ABUNDANCE[nuclide] elif nuclide == 'Ta180' and 'Ta181' in mutual_nuclides: abundances['Ta181'] += NATURAL_ABUNDANCE[nuclide] elif nuclide == 'W180' and 'W182' in mutual_nuclides: abundances['W182'] += NATURAL_ABUNDANCE[nuclide] else: msg = 'Unsure how to partition natural abundance of ' \ 'isotope {0} into other natural isotopes of ' \ 'this element that are present in the cross ' \ 'section library provided. Consider adding ' \ 'the isotopes of this element individually.' raise ValueError(msg) # If a cross_section library is not present, expand the element into # its natural nuclides else: for nuclide in natural_nuclides: abundances[nuclide] = NATURAL_ABUNDANCE[nuclide] # Modify mole fractions if enrichment provided # Old treatment for Uranium if enrichment is not None and enrichment_target is None: # Check that the element is Uranium if self.name != 'U': msg = ('Enrichment procedure for Uranium was requested, ' 'but the isotope is {} not U'.format(self)) raise ValueError(msg) # Check that enrichment_type is not 'ao' if enrichment_type == 'ao': msg = ('Enrichment procedure for Uranium requires that ' 'enrichment value is provided as wo%.') raise ValueError(msg) # Calculate the mass fractions of isotopes abundances['U234'] = 0.0089 * enrichment abundances['U235'] = enrichment abundances['U236'] = 0.0046 * enrichment abundances['U238'] = 100.0 - 1.0135 * enrichment # Convert the mass fractions to mole fractions for nuclide in abundances.keys(): abundances[nuclide] /= atomic_mass(nuclide) # Normalize the mole fractions to one sum_abundances = sum(abundances.values()) for nuclide in abundances.keys(): abundances[nuclide] /= sum_abundances # Modify mole fractions if enrichment provided # New treatment for arbitrary element elif enrichment is not None and enrichment_target is not None: # Provide more informative error message for U235 if enrichment_target == 'U235': msg = ("There is a special procedure for enrichment of U235 " "in U. To invoke it, the arguments 'enrichment_target'" "and 'enrichment_type' should be omitted. Provide " "a value only for 'enrichment' in weight percent.") raise ValueError(msg) # Check if it is two-isotope mixture if len(abundances) != 2: msg = ( 'Element {} does not consist of two naturally-occurring ' 'isotopes. Please enter isotopic abundances manually.'. format(self)) raise ValueError(msg) # Check if the target nuclide is present in the mixture if enrichment_target not in abundances: msg = ( 'The target nuclide {} is not one of the naturally-occurring ' 'isotopes ({})'.format(enrichment_target, list(abundances))) raise ValueError(msg) # If weight percent enrichment is requested convert to mass fractions if enrichment_type == 'wo': # Convert the atomic abundances to weight fractions # Compute the element atomic mass element_am = sum( atomic_mass(nuc) * abundances[nuc] for nuc in abundances) # Convert Molar Fractions to mass fractions for nuclide in abundances: abundances[nuclide] *= atomic_mass(nuclide) / element_am # Normalize to one sum_abundances = sum(abundances.values()) for nuclide in abundances: abundances[nuclide] /= sum_abundances # Enrich the mixture # The procedure is more generic that it needs to be. It allows # to enrich mixtures of more then 2 isotopes, keeping the ratios # of non-enriched nuclides the same as in natural composition # Get fraction of non-enriched isotopes in nat. composition non_enriched = 1.0 - abundances[enrichment_target] tail_fraction = 1.0 - enrichment / 100.0 # Enrich all nuclides # Do bogus operation for enrichment target but overwrite immediatly # to avoid if statement in the loop for nuclide, fraction in abundances.items(): abundances[nuclide] = tail_fraction * fraction / non_enriched abundances[enrichment_target] = enrichment / 100.0 # Convert back to atomic fractions if requested if enrichment_type == 'wo': # Convert the mass fractions to mole fractions for nuclide in abundances: abundances[nuclide] /= atomic_mass(nuclide) # Normalize the mole fractions to one sum_abundances = sum(abundances.values()) for nuclide in abundances: abundances[nuclide] /= sum_abundances # Compute the ratio of the nuclide atomic masses to the element # atomic mass if percent_type == 'wo': # Compute the element atomic mass element_am = 0. for nuclide in abundances.keys(): element_am += atomic_mass(nuclide) * abundances[nuclide] # Convert the molar fractions to mass fractions for nuclide in abundances.keys(): abundances[nuclide] *= atomic_mass(nuclide) / element_am # Normalize the mass fractions to one sum_abundances = sum(abundances.values()) for nuclide in abundances.keys(): abundances[nuclide] /= sum_abundances # Create a list of the isotopes in this element isotopes = [] for nuclide, abundance in abundances.items(): isotopes.append((nuclide, percent * abundance, percent_type)) return isotopes
def level(self, plot_level): cv.check_type('plot level', plot_level, Integral) cv.check_greater_than('plot level', plot_level, 0, equality=True) self._level = plot_level