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 mask_background(self, mask_background): check_type('plot mask background', mask_background, Iterable, Integral) check_length('plot mask background', mask_background, 3) for rgb in mask_background: check_greater_than('plot mask background', rgb, 0, True) 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 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 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 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 : int The energy group index, starting at 1 for the highest energies Returns ------- 2-tuple The low and high energy bounds for the group in eV 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 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 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 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) self._sab.append((name, fraction))
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 get_condensed_library(self, coarse_groups): """Construct an energy-condensed version of this library. This routine condenses each of the multi-group cross sections in the library to a coarse energy group structure. NOTE: This routine must be called after the load_from_statepoint(...) routine loads the tallies from the statepoint into each of the cross sections. Parameters ---------- coarse_groups : openmc.mgxs.EnergyGroups The coarse energy group structure of interest Returns ------- Library A new multi-group cross section library condensed to the group structure of interest Raises ------ ValueError When this method is called before a statepoint has been loaded See also -------- MGXS.get_condensed_xs(coarse_groups) """ if self.sp_filename is None: msg = 'Unable to get a condensed coarse group cross section ' \ 'library since the statepoint has not yet been loaded' raise ValueError(msg) cv.check_type('coarse_groups', coarse_groups, openmc.mgxs.EnergyGroups) cv.check_less_than('coarse groups', coarse_groups.num_groups, self.num_groups, equality=True) cv.check_value('upper coarse energy', coarse_groups.group_edges[-1], [self.energy_groups.group_edges[-1]]) cv.check_value('lower coarse energy', coarse_groups.group_edges[0], [self.energy_groups.group_edges[0]]) # Clone this Library to initialize the condensed version condensed_library = copy.deepcopy(self) condensed_library.energy_groups = coarse_groups # Condense the MGXS for each domain and mgxs type for domain in self.domains: for mgxs_type in self.mgxs_types: mgxs = condensed_library.get_mgxs(domain, mgxs_type) condensed_mgxs = mgxs.get_condensed_xs(coarse_groups) condensed_library.all_mgxs[ domain.id][mgxs_type] = condensed_mgxs return condensed_library
def get_condensed_library(self, coarse_groups): """Construct an energy-condensed version of this library. This routine condenses each of the multi-group cross sections in the library to a coarse energy group structure. NOTE: This routine must be called after the load_from_statepoint(...) routine loads the tallies from the statepoint into each of the cross sections. Parameters ---------- coarse_groups : openmc.mgxs.EnergyGroups The coarse energy group structure of interest Returns ------- Library A new multi-group cross section library condensed to the group structure of interest Raises ------ ValueError When this method is called before a statepoint has been loaded See also -------- MGXS.get_condensed_xs(coarse_groups) """ if self.sp_filename is None: msg = 'Unable to get a condensed coarse group cross section ' \ 'library since the statepoint has not yet been loaded' raise ValueError(msg) cv.check_type('coarse_groups', coarse_groups, openmc.mgxs.EnergyGroups) cv.check_less_than('coarse groups', coarse_groups.num_groups, self.num_groups, equality=True) cv.check_value('upper coarse energy', coarse_groups.group_edges[-1], [self.energy_groups.group_edges[-1]]) cv.check_value('lower coarse energy', coarse_groups.group_edges[0], [self.energy_groups.group_edges[0]]) # Clone this Library to initialize the condensed version condensed_library = copy.deepcopy(self) condensed_library.energy_groups = coarse_groups # Condense the MGXS for each domain and mgxs type for domain in self.domains: for mgxs_type in self.mgxs_types: mgxs = condensed_library.get_mgxs(domain, mgxs_type) condensed_mgxs = mgxs.get_condensed_xs(coarse_groups) condensed_library.all_mgxs[domain.id][mgxs_type] = condensed_mgxs return condensed_library
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, str): 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, str): 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 _check_color(err_string, color): cv.check_type(err_string, color, Iterable) if isinstance(color, str): if color.lower() not in _SVG_COLORS: raise ValueError("'{}' is not a valid color.".format(color)) else: cv.check_length(err_string, color, 3) for rgb in color: cv.check_type(err_string, rgb, Real) cv.check_greater_than('RGB component', rgb, 0, True) cv.check_less_than('RGB component', rgb, 256)
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 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 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 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 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 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 ------- openmc.mgxs.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 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, string_types): 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 add_element(self, element, percent, percent_type='ao', enrichment=None): """Add a natural element to the material Parameters ---------- element : str Element to add 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). """ 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 not isinstance(element, string_types): msg = 'Unable to add an Element to Material ID="{}" with a ' \ 'non-string value "{}"'.format(self._id, element) raise ValueError(msg) if not isinstance(percent, Real): msg = 'Unable to add an Element to Material ID="{}" with a ' \ 'non-floating point value "{}"'.format(self._id, percent) raise ValueError(msg) if percent_type not in ['ao', 'wo']: msg = 'Unable to add an Element to Material ID="{}" with a ' \ 'percent type "{}"'.format(self._id, percent_type) 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) # Add naturally-occuring isotopes element = openmc.Element(element) for nuclide in element.expand(percent, percent_type, enrichment): self._nuclides.append(nuclide)
def verbosity(self, verbosity): cv.check_type('verbosity', verbosity, Integral) cv.check_greater_than('verbosity', verbosity, 1, True) cv.check_less_than('verbosity', verbosity, 10, True) self._verbosity = verbosity
def search_for_keff(model_builder, initial_guess=None, target=1.0, bracket=None, model_args=None, tol=None, bracketed_method='bisect', print_iterations=False, print_output=False, **kwargs): """Function to perform a keff search by modifying a model parametrized by a single independent variable. Parameters ---------- model_builder : collections.Callable Callable function which builds a model according to a passed parameter. This function must return an openmc.model.Model object. initial_guess : Real, optional Initial guess for the parameter to be searched in `model_builder`. One of `guess` or `bracket` must be provided. target : Real, optional keff value to search for, defaults to 1.0. bracket : None or Iterable of Real, optional Bracketing interval to search for the solution; if not provided, a generic non-bracketing method is used. If provided, the brackets are used. Defaults to no brackets provided. One of `guess` or `bracket` must be provided. If both are provided, the bracket will be preferentially used. model_args : dict, optional Keyword-based arguments to pass to the `model_builder` method. Defaults to no arguments. tol : float Tolerance to pass to the search method bracketed_method : {'brentq', 'brenth', 'ridder', 'bisect'}, optional Solution method to use; only applies if `bracket` is set, otherwise the Secant method is used. Defaults to 'bisect'. print_iterations : bool Whether or not to print the guess and the result during the iteration process. Defaults to False. print_output : bool Whether or not to print the OpenMC output during the iterations. Defaults to False. **kwargs All remaining keyword arguments are passed to the root-finding method. Returns ------- zero_value : float Estimated value of the variable parameter where keff is the targeted value guesses : List of Real List of guesses attempted by the search results : List of 2-tuple of Real List of keffs and uncertainties corresponding to the guess attempted by the search """ if initial_guess is not None: cv.check_type('initial_guess', initial_guess, Real) if bracket is not None: cv.check_iterable_type('bracket', bracket, Real) cv.check_length('bracket', bracket, 2) cv.check_less_than('bracket values', bracket[0], bracket[1]) if model_args is None: model_args = {} else: cv.check_type('model_args', model_args, dict) cv.check_type('target', target, Real) cv.check_type('tol', tol, Real) cv.check_value('bracketed_method', bracketed_method, _SCALAR_BRACKETED_METHODS) cv.check_type('print_iterations', print_iterations, bool) cv.check_type('print_output', print_output, bool) cv.check_type('model_builder', model_builder, Callable) # Run the model builder function once to make sure it provides the correct # output type if bracket is not None: model = model_builder(bracket[0], **model_args) elif initial_guess is not None: model = model_builder(initial_guess, **model_args) cv.check_type('model_builder return', model, openmc.model.Model) # Set the iteration data storage variables guesses = [] results = [] # Set the searching function (for easy replacement should a later # generic function be added. search_function = _search_keff if bracket is not None: # Generate our arguments args = {'f': search_function, 'a': bracket[0], 'b': bracket[1]} if tol is not None: args['rtol'] = tol # Set the root finding method if bracketed_method == 'brentq': root_finder = sopt.brentq elif bracketed_method == 'brenth': root_finder = sopt.brenth elif bracketed_method == 'ridder': root_finder = sopt.ridder elif bracketed_method == 'bisect': root_finder = sopt.bisect elif initial_guess is not None: # Generate our arguments args = {'func': search_function, 'x0': initial_guess} if tol is not None: args['tol'] = tol # Set the root finding method root_finder = sopt.newton else: raise ValueError("Either the 'bracket' or 'initial_guess' parameters " "must be set") # Add information to be passed to the searching function args['args'] = (target, model_builder, model_args, print_iterations, print_output, guesses, results) # Create a new dictionary with the arguments from args and kwargs args.update(kwargs) # Perform the search zero_value = root_finder(**args) return zero_value, guesses, results
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 get_xsdata(self, domain, xsdata_name, nuclide='total', xs_type='macro', xs_id='1m', order=None, tabular_legendre=None, tabular_points=33): """Generates an openmc.XSdata object describing a multi-group cross section data set for eventual combination in to an openmc.MGXSLibrary object (i.e., the library). Parameters ---------- domain : openmc.Material or openmc.Cell or openmc.Universe The domain for spatial homogenization xsdata_name : str Name to apply to the "xsdata" entry produced by this method nuclide : str A nuclide name string (e.g., 'U-235'). Defaults to 'total' to obtain a material-wise macroscopic cross section. xs_type: {'macro', 'micro'} Provide the macro or micro cross section in units of cm^-1 or barns. Defaults to 'macro'. If the Library object is not tallied by nuclide this will be set to 'macro' regardless. xs_ids : str Cross section set identifier. Defaults to '1m'. order : int Scattering order for this data entry. Default is None, which will set the XSdata object to use the order of the Library. tabular_legendre : None or bool Flag to denote whether or not the Legendre expansion of the scattering angular distribution is to be converted to a tabular representation by OpenMC. A value of `True` means that it is to be converted while a value of `False` means that it will not be. Defaults to `None` which leaves the default behavior of OpenMC in place (the distribution is converted to a tabular representation). tabular_points : int This parameter is not used unless the ``tabular_legendre`` parameter is set to `True`. In this case, this parameter sets the number of equally-spaced points in the domain of [-1,1] to be used in building the tabular distribution. Default is `33`. Returns ------- xsdata : openmc.XSdata Multi-Group Cross Section data set object. Raises ------ ValueError When the Library object is initialized with insufficient types of cross sections for the Library. See also -------- Library.create_mg_library() """ cv.check_type('domain', domain, (openmc.Material, openmc.Cell, openmc.Cell)) cv.check_type('xsdata_name', xsdata_name, basestring) cv.check_type('nuclide', nuclide, basestring) cv.check_value('xs_type', xs_type, ['macro', 'micro']) cv.check_type('xs_id', xs_id, basestring) cv.check_type('order', order, (type(None), Integral)) if order is not None: cv.check_greater_than('order', order, 0, equality=True) cv.check_less_than('order', order, 10, equality=True) cv.check_type('tabular_legendre', tabular_legendre, (type(None), bool)) if tabular_points is not None: cv.check_greater_than('tabular_points', tabular_points, 1) # Make sure statepoint has been loaded if self._sp_filename is None: msg = 'A StatePoint must be loaded before calling ' \ 'the create_mg_library() function' raise ValueError(msg) # If gathering material-specific data, set the xs_type to macro if not self.by_nuclide: xs_type = 'macro' # Build & add metadata to XSdata object name = xsdata_name if nuclide is not 'total': name += '_' + nuclide name += '.' + xs_id xsdata = openmc.XSdata(name, self.energy_groups) if order is None: # Set the order to the Library's order (the defualt behavior) xsdata.order = self.legendre_order else: # Set the order of the xsdata object to the minimum of # the provided order or the Library's order. xsdata.order = min(order, self.legendre_order) # Set the tabular_legendre option if needed if tabular_legendre is not None: xsdata.tabular_legendre = {'enable': tabular_legendre, 'num_points': tabular_points} if nuclide is not 'total': xsdata.zaid = self._nuclides[nuclide][0] xsdata.awr = self._nuclides[nuclide][1] # Now get xs data itself if 'nu-transport' in self.mgxs_types and self.correction == 'P0': mymgxs = self.get_mgxs(domain, 'nu-transport') xsdata.set_total_mgxs(mymgxs, xs_type=xs_type, nuclide=[nuclide]) elif 'total' in self.mgxs_types: mymgxs = self.get_mgxs(domain, 'total') xsdata.set_total_mgxs(mymgxs, xs_type=xs_type, nuclide=[nuclide]) if 'absorption' in self.mgxs_types: mymgxs = self.get_mgxs(domain, 'absorption') xsdata.set_absorption_mgxs(mymgxs, xs_type=xs_type, nuclide=[nuclide]) if 'fission' in self.mgxs_types: mymgxs = self.get_mgxs(domain, 'fission') xsdata.set_fission_mgxs(mymgxs, xs_type=xs_type, nuclide=[nuclide]) if 'kappa-fission' in self.mgxs_types: mymgxs = self.get_mgxs(domain, 'kappa-fission') xsdata.set_kappa_fission_mgxs(mymgxs, xs_type=xs_type, nuclide=[nuclide]) # For chi and nu-fission we can either have only a nu-fission matrix # provided, or vectors of chi and nu-fission provided if 'nu-fission matrix' in self.mgxs_types: mymgxs = self.get_mgxs(domain, 'nu-fission matrix') xsdata.set_nu_fission_mgxs(mymgxs, xs_type=xs_type, nuclide=[nuclide]) else: if 'chi' in self.mgxs_types: mymgxs = self.get_mgxs(domain, 'chi') xsdata.set_chi_mgxs(mymgxs, xs_type=xs_type, nuclide=[nuclide]) if 'nu-fission' in self.mgxs_types: mymgxs = self.get_mgxs(domain, 'nu-fission') xsdata.set_nu_fission_mgxs(mymgxs, xs_type=xs_type, nuclide=[nuclide]) # If multiplicity matrix is available, prefer that if 'multiplicity matrix' in self.mgxs_types: mymgxs = self.get_mgxs(domain, 'multiplicity matrix') xsdata.set_multiplicity_mgxs(mymgxs, xs_type=xs_type, nuclide=[nuclide]) using_multiplicity = True # multiplicity wil fall back to using scatter and nu-scatter elif ((('scatter matrix' in self.mgxs_types) and ('nu-scatter matrix' in self.mgxs_types))): scatt_mgxs = self.get_mgxs(domain, 'scatter matrix') nuscatt_mgxs = self.get_mgxs(domain, 'nu-scatter matrix') xsdata.set_multiplicity_mgxs(nuscatt_mgxs, scatt_mgxs, xs_type=xs_type, nuclide=[nuclide]) using_multiplicity = True else: using_multiplicity = False if using_multiplicity: nuscatt_mgxs = self.get_mgxs(domain, 'nu-scatter matrix') xsdata.set_scatter_mgxs(nuscatt_mgxs, xs_type=xs_type, nuclide=[nuclide]) else: if 'nu-scatter matrix' in self.mgxs_types: nuscatt_mgxs = self.get_mgxs(domain, 'nu-scatter matrix') xsdata.set_scatter_mgxs(nuscatt_mgxs, xs_type=xs_type, nuclide=[nuclide]) # Since we are not using multiplicity, then # scattering multiplication (nu-scatter) must be # accounted for approximately by using an adjusted # absorption cross section. if 'total' in self.mgxs_types: xsdata.absorption = \ np.subtract(xsdata.total, np.sum(xsdata.scatter[0, :, :], axis=1)) return xsdata
def add_element(self, element, percent, percent_type='ao', enrichment=None): """Add a natural element to the material Parameters ---------- element : openmc.Element or str Element to add 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). """ 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 not isinstance(element, string_types + (openmc.Element, )): msg = 'Unable to add an Element to Material ID="{}" with a ' \ 'non-Element value "{}"'.format(self._id, element) raise ValueError(msg) if not isinstance(percent, Real): msg = 'Unable to add an Element to Material ID="{}" with a ' \ 'non-floating point value "{}"'.format(self._id, percent) raise ValueError(msg) if percent_type not in ['ao', 'wo']: msg = 'Unable to add an Element to Material ID="{}" with a ' \ 'percent type "{}"'.format(self._id, percent_type) raise ValueError(msg) # Copy this Element to separate it from same Element in other Materials if isinstance(element, openmc.Element): element = deepcopy(element) else: element = openmc.Element(element) 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.name != 'U': msg = 'Unable to use enrichment for element {} which is not ' \ 'uranium for Material ID="{}"'.format(element.name, 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) self._elements.append((element, percent, percent_type, enrichment))
def get_bin(self, bin_index): """Returns the filter bin for some filter bin index. Parameters ---------- bin_index : Integral The zero-based index into the filter's array of bins. The bin index for 'material', 'surface', 'cell', 'cellborn', and 'universe' filters corresponds to the ID in the filter's list of bins. For 'distribcell' tallies the bin index necessarily can only be zero since only one cell can be tracked per tally. The bin index for 'energy' and 'energyout' filters corresponds to the energy range of interest in the filter bins of energies. The bin index for 'mesh' filters is the index into the flattened array of (x,y) or (x,y,z) mesh cell bins. Returns ------- bin : 1-, 2-, or 3-tuple of Real The bin in the Tally data array. The bin for 'material', surface', 'cell', 'cellborn', 'universe' and 'distribcell' filters is a 1-tuple of the ID corresponding to the appropriate filter bin. The bin for 'energy' and 'energyout' filters is a 2-tuple of the lower and upper energies bounding the energy interval for the filter bin. The bin for 'mesh' tallies is a 2-tuple or 3-tuple of the x,y or x,y,z mesh cell indices corresponding to the bin in a 2D/3D mesh. See also -------- Filter.get_bin_index() """ cv.check_type('bin_index', bin_index, Integral) cv.check_greater_than('bin_index', bin_index, 0, equality=True) cv.check_less_than('bin_index', bin_index, self.num_bins) if self.type == 'mesh': # Construct 3-tuple of x,y,z cell indices for a 3D mesh if len(self.mesh.dimension) == 3: nx, ny, nz = self.mesh.dimension x = bin_index / (ny * nz) y = (bin_index - (x * ny * nz)) / nz z = bin_index - (x * ny * nz) - (y * nz) filter_bin = (x, y, z) # Construct 2-tuple of x,y cell indices for a 2D mesh else: nx, ny = self.mesh.dimension x = bin_index / ny y = bin_index - (x * ny) filter_bin = (x, y) # Construct 2-tuple of lower, upper energies for energy(out) filters elif self.type in ['energy', 'energyout']: filter_bin = (self.bins[bin_index], self.bins[bin_index + 1]) # Construct 1-tuple of with the cell ID for distribcell filters elif self.type == 'distribcell': filter_bin = (self.bins[0], ) # Construct 1-tuple with domain ID (e.g., material) for other filters else: filter_bin = (self.bins[bin_index], ) return filter_bin
def verbosity(self, verbosity): check_type('verbosity', verbosity, Integral) check_greater_than('verbosity', verbosity, 1, True) check_less_than('verbosity', verbosity, 10, True) self._verbosity = verbosity
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 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 verbosity(self, verbosity): check_type("verbosity", verbosity, Integral) check_greater_than("verbosity", verbosity, 1, True) check_less_than("verbosity", verbosity, 10, True) self._verbosity = verbosity
def get_bin(self, bin_index): """Returns the filter bin for some filter bin index. Parameters ---------- bin_index : Integral The zero-based index into the filter's array of bins. The bin index for 'material', 'surface', 'cell', 'cellborn', and 'universe' filters corresponds to the ID in the filter's list of bins. For 'distribcell' tallies the bin index necessarily can only be zero since only one cell can be tracked per tally. The bin index for 'energy' and 'energyout' filters corresponds to the energy range of interest in the filter bins of energies. The bin index for 'mesh' filters is the index into the flattened array of (x,y) or (x,y,z) mesh cell bins. Returns ------- bin : 1-, 2-, or 3-tuple of Real The bin in the Tally data array. The bin for 'material', surface', 'cell', 'cellborn', 'universe' and 'distribcell' filters is a 1-tuple of the ID corresponding to the appropriate filter bin. The bin for 'energy' and 'energyout' filters is a 2-tuple of the lower and upper energies bounding the energy interval for the filter bin. The bin for 'mesh' tallies is a 2-tuple or 3-tuple of the x,y or x,y,z mesh cell indices corresponding to the bin in a 2D/3D mesh. See also -------- Filter.get_bin_index() """ cv.check_type('bin_index', bin_index, Integral) cv.check_greater_than('bin_index', bin_index, 0, equality=True) cv.check_less_than('bin_index', bin_index, self.num_bins) if self.type == 'mesh': # Construct 3-tuple of x,y,z cell indices for a 3D mesh if len(self.mesh.dimension) == 3: nx, ny, nz = self.mesh.dimension x = bin_index / (ny * nz) y = (bin_index - (x * ny * nz)) / nz z = bin_index - (x * ny * nz) - (y * nz) filter_bin = (x, y, z) # Construct 2-tuple of x,y cell indices for a 2D mesh else: nx, ny = self.mesh.dimension x = bin_index / ny y = bin_index - (x * ny) filter_bin = (x, y) # Construct 2-tuple of lower, upper energies for energy(out) filters elif self.type in ['energy', 'energyout']: filter_bin = (self.bins[bin_index], self.bins[bin_index+1]) # Construct 1-tuple of with the cell ID for distribcell filters elif self.type == 'distribcell': filter_bin = (self.bins[0],) # Construct 1-tuple with domain ID (e.g., material) for other filters else: filter_bin = (self.bins[bin_index],) return filter_bin
def pin(surfaces, items, subdivisions=None, divide_vols=True, **kwargs): """Convenience function for building a fuel pin Parameters ---------- surfaces : iterable of :class:`openmc.Cylinder` Cylinders used to define boundaries between items. All cylinders must be concentric and of the same orientation, e.g. all :class:`openmc.ZCylinder` items : iterable Objects to go between ``surfaces``. These can be anything that can fill a :class:`openmc.Cell`, including :class:`openmc.Material`, or other :class:`openmc.Universe` objects. There must be one more item than surfaces, which will span all space outside the final ring. subdivisions : None or dict of int to int Dictionary describing which rings to subdivide and how many times. Keys are indexes of the annular rings to be divided. Will construct equal area rings divide_vols : bool If this evaluates to ``True``, then volumes of subdivided :class:`openmc.Material` instances will also be divided by the number of divisions. Otherwise the volume of the original material will not be modified before subdivision kwargs: Additional key-word arguments to be passed to :class:`openmc.Universe`, like ``name="Fuel pin"`` Returns ------- :class:`openmc.Universe` Universe of concentric cylinders filled with the desired items """ if "cells" in kwargs: raise SyntaxError( "Cells will be set by this function, not from input arguments.") check_type("items", items, Iterable) check_length("surfaces", surfaces, len(items) - 1, len(items) - 1) # Check that all surfaces are of similar orientation check_type("surface", surfaces[0], Cylinder) surf_type = type(surfaces[0]) check_iterable_type("surfaces", surfaces[1:], surf_type) # Check for increasing radii and equal centers if surf_type is ZCylinder: center_getter = attrgetter("x0", "y0") elif surf_type is YCylinder: center_getter = attrgetter("x0", "z0") elif surf_type is XCylinder: center_getter = attrgetter("z0", "y0") else: raise TypeError("Not configured to interpret {} surfaces".format( surf_type.__name__)) centers = set() prev_rad = 0 for ix, surf in enumerate(surfaces): cur_rad = surf.r if cur_rad <= prev_rad: raise ValueError( "Surfaces do not appear to be increasing in radius. " "Surface {} at index {} has radius {:7.3e} compared to " "previous radius of {:7.5e}".format(surf.id, ix, cur_rad, prev_rad)) prev_rad = cur_rad centers.add(center_getter(surf)) if len(centers) > 1: raise ValueError( "Surfaces do not appear to be concentric. The following " "centers were found: {}".format(centers)) if subdivisions is not None: check_length("subdivisions", subdivisions, 1, len(surfaces)) orig_indexes = list(subdivisions.keys()) check_iterable_type("ring indexes", orig_indexes, int) check_iterable_type("number of divisions", list(subdivisions.values()), int) for ix in orig_indexes: if ix < 0: subdivisions[len(surfaces) + ix] = subdivisions.pop(ix) # Dissallow subdivision on outer most, infinite region check_less_than("outer ring", max(subdivisions), len(surfaces), equality=True) # ensure ability to concatenate if not isinstance(items, list): items = list(items) if not isinstance(surfaces, list): surfaces = list(surfaces) # generate equal area divisions # Adding N - 1 new regions # N - 2 surfaces are made # Original cell is not removed, but not occupies last ring for ring_index in reversed(sorted(subdivisions.keys())): nr = subdivisions[ring_index] new_surfs = [] lower_rad = 0.0 if ring_index == 0 else surfaces[ring_index - 1].r upper_rad = surfaces[ring_index].r area_term = (upper_rad**2 - lower_rad**2) / nr for new_index in range(nr - 1): lower_rad = sqrt(area_term + lower_rad**2) new_surfs.append(surf_type(r=lower_rad)) surfaces = (surfaces[:ring_index] + new_surfs + surfaces[ring_index:]) filler = items[ring_index] if (divide_vols and hasattr(filler, "volume") and filler.volume is not None): filler.volume /= nr items[ring_index:ring_index] = [ filler.clone() for _i in range(nr - 1) ] # Build the universe regions = subdivide(surfaces) cells = [Cell(fill=f, region=r) for r, f in zip(regions, items)] return Universe(cells=cells, **kwargs)