Source code for openmc.plots

from collections import Iterable, Mapping
from numbers import Real, Integral
from xml.etree import ElementTree as ET
import sys
import warnings

from six import string_types
import numpy as np

import openmc
import openmc.checkvalue as cv
from openmc.clean_xml import clean_xml_indentation


# A static variable for auto-generated Plot IDs
AUTO_PLOT_ID = 10000


def reset_auto_plot_id():
    """Reset counter for auto-generated plot IDs."""
    global AUTO_PLOT_ID
    AUTO_PLOT_ID = 10000


_BASES = ['xy', 'xz', 'yz']

_SVG_COLORS = {
    'aliceblue': (240, 248, 255),
    'antiquewhite': (250, 235, 215),
    'aqua': (0, 255, 255),
    'aquamarine': (127, 255, 212),
    'azure': (240, 255, 255),
    'beige': (245, 245, 220),
    'bisque': (255, 228, 196),
    'black': (0, 0, 0),
    'blanchedalmond': (255, 235, 205),
    'blue': (0, 0, 255),
    'blueviolet': (138, 43, 226),
    'brown': (165, 42, 42),
    'burlywood': (222, 184, 135),
    'cadetblue': (95, 158, 160),
    'chartreuse': (127, 255, 0),
    'chocolate': (210, 105, 30),
    'coral': (255, 127, 80),
    'cornflowerblue': (100, 149, 237),
    'cornsilk': (255, 248, 220),
    'crimson': (220, 20, 60),
    'cyan': (0, 255, 255),
    'darkblue': (0, 0, 139),
    'darkcyan': (0, 139, 139),
    'darkgoldenrod': (184, 134, 11),
    'darkgray': (169, 169, 169),
    'darkgreen': (0, 100, 0),
    'darkgrey': (169, 169, 169),
    'darkkhaki': (189, 183, 107),
    'darkmagenta': (139, 0, 139),
    'darkolivegreen': (85, 107, 47),
    'darkorange': (255, 140, 0),
    'darkorchid': (153, 50, 204),
    'darkred': (139, 0, 0),
    'darksalmon': (233, 150, 122),
    'darkseagreen': (143, 188, 143),
    'darkslateblue': (72, 61, 139),
    'darkslategray': (47, 79, 79),
    'darkslategrey': (47, 79, 79),
    'darkturquoise': (0, 206, 209),
    'darkviolet': (148, 0, 211),
    'deeppink': (255, 20, 147),
    'deepskyblue': (0, 191, 255),
    'dimgray': (105, 105, 105),
    'dimgrey': (105, 105, 105),
    'dodgerblue': (30, 144, 255),
    'firebrick': (178, 34, 34),
    'floralwhite': (255, 250, 240),
    'forestgreen': (34, 139, 34),
    'fuchsia': (255, 0, 255),
    'gainsboro': (220, 220, 220),
    'ghostwhite': (248, 248, 255),
    'gold': (255, 215, 0),
    'goldenrod': (218, 165, 32),
    'gray': (128, 128, 128),
    'green': (0, 128, 0),
    'greenyellow': (173, 255, 47),
    'grey': (128, 128, 128),
    'honeydew': (240, 255, 240),
    'hotpink': (255, 105, 180),
    'indianred': (205, 92, 92),
    'indigo': (75, 0, 130),
    'ivory': (255, 255, 240),
    'khaki': (240, 230, 140),
    'lavender': (230, 230, 250),
    'lavenderblush': (255, 240, 245),
    'lawngreen': (124, 252, 0),
    'lemonchiffon': (255, 250, 205),
    'lightblue': (173, 216, 230),
    'lightcoral': (240, 128, 128),
    'lightcyan': (224, 255, 255),
    'lightgoldenrodyellow': (250, 250, 210),
    'lightgray': (211, 211, 211),
    'lightgreen': (144, 238, 144),
    'lightgrey': (211, 211, 211),
    'lightpink': (255, 182, 193),
    'lightsalmon': (255, 160, 122),
    'lightseagreen': (32, 178, 170),
    'lightskyblue': (135, 206, 250),
    'lightslategray': (119, 136, 153),
    'lightslategrey': (119, 136, 153),
    'lightsteelblue': (176, 196, 222),
    'lightyellow': (255, 255, 224),
    'lime': (0, 255, 0),
    'limegreen': (50, 205, 50),
    'linen': (250, 240, 230),
    'magenta': (255, 0, 255),
    'maroon': (128, 0, 0),
    'mediumaquamarine': (102, 205, 170),
    'mediumblue': (0, 0, 205),
    'mediumorchid': (186, 85, 211),
    'mediumpurple': (147, 112, 219),
    'mediumseagreen': (60, 179, 113),
    'mediumslateblue': (123, 104, 238),
    'mediumspringgreen': (0, 250, 154),
    'mediumturquoise': (72, 209, 204),
    'mediumvioletred': (199, 21, 133),
    'midnightblue': (25, 25, 112),
    'mintcream': (245, 255, 250),
    'mistyrose': (255, 228, 225),
    'moccasin': (255, 228, 181),
    'navajowhite': (255, 222, 173),
    'navy': (0, 0, 128),
    'oldlace': (253, 245, 230),
    'olive': (128, 128, 0),
    'olivedrab': (107, 142, 35),
    'orange': (255, 165, 0),
    'orangered': (255, 69, 0),
    'orchid': (218, 112, 214),
    'palegoldenrod': (238, 232, 170),
    'palegreen': (152, 251, 152),
    'paleturquoise': (175, 238, 238),
    'palevioletred': (219, 112, 147),
    'papayawhip': (255, 239, 213),
    'peachpuff': (255, 218, 185),
    'peru': (205, 133, 63),
    'pink': (255, 192, 203),
    'plum': (221, 160, 221),
    'powderblue': (176, 224, 230),
    'purple': (128, 0, 128),
    'red': (255, 0, 0),
    'rosybrown': (188, 143, 143),
    'royalblue': (65, 105, 225),
    'saddlebrown': (139, 69, 19),
    'salmon': (250, 128, 114),
    'sandybrown': (244, 164, 96),
    'seagreen': (46, 139, 87),
    'seashell': (255, 245, 238),
    'sienna': (160, 82, 45),
    'silver': (192, 192, 192),
    'skyblue': (135, 206, 235),
    'slateblue': (106, 90, 205),
    'slategray': (112, 128, 144),
    'slategrey': (112, 128, 144),
    'snow': (255, 250, 250),
    'springgreen': (0, 255, 127),
    'steelblue': (70, 130, 180),
    'tan': (210, 180, 140),
    'teal': (0, 128, 128),
    'thistle': (216, 191, 216),
    'tomato': (255, 99, 71),
    'turquoise': (64, 224, 208),
    'violet': (238, 130, 238),
    'wheat': (245, 222, 179),
    'white': (255, 255, 255),
    'whitesmoke': (245, 245, 245),
    'yellow': (255, 255, 0),
    'yellowgreen': (154, 205, 50)
}


[docs]class Plot(object): """Definition of a finite region of space to be plotted. OpenMC is capable of generating two-dimensional slice plots and three-dimensional voxel plots. Colors that are used in plots can be given as RGB tuples, e.g. (255, 255, 255) would be white, or by a string indicating a valid `SVG color <https://www.w3.org/TR/SVG/types.html#ColorKeywords>`_. Parameters ---------- plot_id : int Unique identifier for the plot name : str Name of the plot Attributes ---------- id : int Unique identifier name : str Name of the plot width : Iterable of float Width of the plot in each basis direction pixels : Iterable of int Number of pixels to use in each basis direction origin : tuple or list of ndarray Origin (center) of the plot filename : Path to write the plot to color_by : {'cell', 'material'} Indicate whether the plot should be colored by cell or by material type : {'slice', 'voxel'} The type of the plot basis : {'xy', 'xz', 'yz'} The basis directions for the plot background : Iterable of int or str Color of the background mask_components : Iterable of openmc.Cell or openmc.Material The cells or materials to plot mask_background : Iterable of int or str Color to apply to all cells/materials not listed in mask_components colors : dict Dictionary indicating that certain cells/materials (keys) should be displayed with a particular color. level : int Universe depth to plot at meshlines : dict Dictionary defining type, id, linewidth and color of a regular mesh to be plotted on top of a plot """ def __init__(self, plot_id=None, name=''): # Initialize Plot class attributes self.id = plot_id self.name = name self._width = [4.0, 4.0] self._pixels = [400, 400] self._origin = [0., 0., 0.] self._filename = None self._color_by = 'cell' self._type = 'slice' self._basis = 'xy' self._background = None self._mask_components = None self._mask_background = None self._colors = {} self._level = None self._meshlines = None @property def id(self): return self._id @property def name(self): return self._name @property def width(self): return self._width @property def pixels(self): return self._pixels @property def origin(self): return self._origin @property def filename(self): return self._filename @property def color_by(self): return self._color_by @property def type(self): return self._type @property def basis(self): return self._basis @property def background(self): return self._background @property def mask_components(self): return self._mask_components @property def mask_background(self): return self._mask_background @property def colors(self): return self._colors @property def level(self): return self._level @property def meshlines(self): return self._meshlines @id.setter 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 @name.setter def name(self, name): cv.check_type('plot name', name, string_types) self._name = name @width.setter def width(self, width): cv.check_type('plot width', width, Iterable, Real) cv.check_length('plot width', width, 2, 3) self._width = width @origin.setter def origin(self, origin): cv.check_type('plot origin', origin, Iterable, Real) cv.check_length('plot origin', origin, 3) self._origin = origin @pixels.setter 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 @filename.setter def filename(self, filename): cv.check_type('filename', filename, string_types) self._filename = filename @color_by.setter def color_by(self, color_by): cv.check_value('plot color_by', color_by, ['cell', 'material']) self._color_by = color_by @type.setter def type(self, plottype): cv.check_value('plot type', plottype, ['slice', 'voxel']) self._type = plottype @basis.setter def basis(self, basis): cv.check_value('plot basis', basis, _BASES) self._basis = basis @background.setter def background(self, background): cv.check_type('plot background', background, Iterable) if isinstance(background, string_types): if background.lower() not in _SVG_COLORS: raise ValueError("'{}' is not a valid color.".format(background)) else: 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 @colors.setter 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 @mask_components.setter def mask_components(self, mask_components): cv.check_type('plot mask components', mask_components, Iterable, (openmc.Cell, openmc.Material)) self._mask_components = mask_components @mask_background.setter 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 @level.setter 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 @meshlines.setter 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 __repr__(self): string = 'Plot\n' string += '{: <16}=\t{}\n'.format('\tID', self._id) string += '{: <16}=\t{}\n'.format('\tName', self._name) string += '{: <16}=\t{}\n'.format('\tFilename', self._filename) string += '{: <16}=\t{}\n'.format('\tType', self._type) string += '{: <16}=\t{}\n'.format('\tBasis', self._basis) string += '{: <16}=\t{}\n'.format('\tWidth', self._width) string += '{: <16}=\t{}\n'.format('\tOrigin', self._origin) string += '{: <16}=\t{}\n'.format('\tPixels', self._origin) string += '{: <16}=\t{}\n'.format('\tColor by', self._color) string += '{: <16}=\t{}\n'.format('\tBackground', self._background) string += '{: <16}=\t{}\n'.format('\tMask components', self._mask_components) string += '{: <16}=\t{}\n'.format('\tMask background', self._mask_background) string += '{: <16}=\t{}\n'.format('\tColors', self._colors) string += '{: <16}=\t{}\n'.format('\tLevel', self._level) string += '{: <16}=\t{}\n'.format('\tMeshlines', self._meshlines) return string @classmethod
[docs] def from_geometry(cls, geometry, basis='xy', slice_coord=0.): """Return plot that encompasses a geometry. Parameters ---------- geometry : openmc.Geometry The geometry the base the plot off of basis : {'xy', 'xz', 'yz'} The basis directions for the plot slice_coord : float The level at which the slice plot should be plotted. For example, if the basis is 'xy', this would indicate the z value used in the origin. """ cv.check_type('geometry', geometry, openmc.Geometry) cv.check_value('basis', basis, _BASES) # Decide which axes to keep if basis == 'xy': pick_index = (0, 1) slice_index = 2 elif basis == 'yz': pick_index = (1, 2) slice_index = 0 elif basis == 'xz': pick_index = (0, 2) slice_index = 1 # Get lower-left and upper-right coordinates for desired axes lower_left, upper_right = geometry.bounding_box lower_left = lower_left[np.array(pick_index)] upper_right = upper_right[np.array(pick_index)] if np.any(np.isinf((lower_left, upper_right))): raise ValueError('The geometry does not appear to be bounded ' 'in the {} plane.'.format(basis)) plot = cls() plot.origin = np.insert((lower_left + upper_right)/2, slice_index, slice_coord) plot.width = upper_right - lower_left return plot
[docs] 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,))
[docs] 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)
[docs] def to_xml_element(self): """Return XML representation of the plot Returns ------- element : xml.etree.ElementTree.Element XML element containing plot data """ element = ET.Element("plot") element.set("id", str(self._id)) if self._filename is not None: element.set("filename", self._filename) element.set("color_by", self._color_by) element.set("type", self._type) if self._type is 'slice': element.set("basis", self._basis) subelement = ET.SubElement(element, "origin") subelement.text = ' '.join(map(str, self._origin)) subelement = ET.SubElement(element, "width") subelement.text = ' '.join(map(str, self._width)) subelement = ET.SubElement(element, "pixels") subelement.text = ' '.join(map(str, self._pixels)) if self._background is not None: subelement = ET.SubElement(element, "background") color = self._background if isinstance(color, string_types): color = _SVG_COLORS[color.lower()] subelement.text = ' '.join(str(x) for x in color) if self._colors: for domain, color in sorted(self._colors.items(), key=lambda x: x[0].id): subelement = ET.SubElement(element, "color") subelement.set("id", str(domain.id)) if isinstance(color, string_types): color = _SVG_COLORS[color.lower()] subelement.set("rgb", ' '.join(str(x) for x in color)) if self._mask_components is not None: subelement = ET.SubElement(element, "mask") subelement.set("components", ' '.join( str(d.id) for d in self._mask_components)) color = self._mask_background if color is not None: if isinstance(color, string_types): color = _SVG_COLORS[color.lower()] subelement.set("background", ' '.join( str(x) for x in color)) if self._level is not None: subelement = ET.SubElement(element, "level") subelement.text = str(self._level) if self._meshlines is not None: subelement = ET.SubElement(element, "meshlines") subelement.set("meshtype", self._meshlines['type']) if self._meshlines['id'] is not None: subelement.set("id", str(self._meshlines['id'])) if self._meshlines['linewidth'] is not None: subelement.set("linewidth", str(self._meshlines['linewidth'])) if self._meshlines['color'] is not None: subelement.set("color", ' '.join(map( str, self._meshlines['color']))) return element
[docs]class Plots(cv.CheckedList): """Collection of Plots used for an OpenMC simulation. This class corresponds directly to the plots.xml input file. It can be thought of as a normal Python list where each member is a :class:`Plot`. It behaves like a list as the following example demonstrates: >>> xz_plot = openmc.Plot() >>> big_plot = openmc.Plot() >>> small_plot = openmc.Plot() >>> p = openmc.Plots((xz_plot, big_plot)) >>> p.append(small_plot) >>> small_plot = p.pop() Parameters ---------- plots : Iterable of openmc.Plot Plots to add to the collection """ def __init__(self, plots=None): super(Plots, self).__init__(Plot, 'plots collection') self._plots_file = ET.Element("plots") if plots is not None: self += plots
[docs] def add_plot(self, plot): """Add a plot to the file. .. deprecated:: 0.8 Use :meth:`Plots.append` instead. Parameters ---------- plot : openmc.Plot Plot to add """ warnings.warn("Plots.add_plot(...) has been deprecated and may be " "removed in a future version. Use Plots.append(...) " "instead.", DeprecationWarning) self.append(plot)
[docs] def append(self, plot): """Append plot to collection Parameters ---------- plot : openmc.Plot Plot to append """ super(Plots, self).append(plot)
[docs] def insert(self, index, plot): """Insert plot before index Parameters ---------- index : int Index in list plot : openmc.Plot Plot to insert """ super(Plots, self).insert(index, plot)
[docs] def remove_plot(self, plot): """Remove a plot from the file. .. deprecated:: 0.8 Use :meth:`Plots.remove` instead. Parameters ---------- plot : openmc.Plot Plot to remove """ warnings.warn("Plots.remove_plot(...) has been deprecated and may be " "removed in a future version. Use Plots.remove(...) " "instead.", DeprecationWarning) self.remove(plot)
[docs] def colorize(self, geometry, seed=1): """Generate a consistent color scheme for each domain in each plot. This routine may be used to generate random, reproducible color schemes. The colors generated are based upon cell/material IDs in the geometry. The color schemes will be consistent for all plots in "plots.xml". Parameters ---------- geometry : openmc.Geometry The geometry for which the plots are defined seed : Integral The random number seed used to generate the color scheme """ for plot in self: plot.colorize(geometry, seed)
[docs] 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 """ for plot in self: plot.highlight_domains(geometry, domains, seed, alpha, background)
def _create_plot_subelements(self): for plot in self: xml_element = plot.to_xml_element() if len(plot.name) > 0: self._plots_file.append(ET.Comment(plot.name)) self._plots_file.append(xml_element)
[docs] def export_to_xml(self, path='plots.xml'): """Export plot specifications to an XML file. Parameters ---------- path : str Path to file to write. Defaults to 'plots.xml'. """ # Reset xml element tree self._plots_file.clear() self._create_plot_subelements() # Clean the indentation in the file to be user-readable clean_xml_indentation(self._plots_file) # Write the XML Tree to the plots.xml file tree = ET.ElementTree(self._plots_file) tree.write(path, xml_declaration=True, encoding='utf-8', method="xml")