From 3913d55e2cf65cb9d04af4f1d6ca97df221fc4f5 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 8 Mar 2017 11:30:22 -0600 Subject: [PATCH 1/9] Allow color names, rename Plot attributes, add openmc.plot_inline() --- .travis.yml | 2 +- docs/source/conf.py | 3 +- docs/source/pythonapi/index.rst | 1 + docs/source/usersguide/input.rst | 16 +- examples/python/boxes/build-xml.py | 2 +- .../python/lattice/hexagonal/build-xml.py | 4 +- examples/python/lattice/nested/build-xml.py | 2 +- examples/python/lattice/simple/build-xml.py | 2 +- examples/xml/boxes/plots.xml | 2 +- examples/xml/lattice/nested/plots.xml | 2 +- examples/xml/lattice/simple/plots.xml | 3 +- examples/xml/pincell_multigroup/plots.xml | 12 +- openmc/cell.py | 24 +- openmc/executor.py | 47 +++- openmc/plots.py | 216 +++++++++--------- openmc/universe.py | 1 - src/input_xml.F90 | 10 +- src/relaxng/plots.rnc | 6 +- src/relaxng/plots.rng | 8 +- tests/input_set.py | 8 +- tests/test_distribmat/inputs_true.dat | 4 +- tests/test_distribmat/test_distribmat.py | 4 +- tests/test_lattice_hex/plots.xml | 8 +- tests/test_lattice_mixed/plots.xml | 8 +- tests/test_multipole/inputs_true.dat | 4 +- tests/test_multipole/test_multipole.py | 4 +- tests/test_plot/plots.xml | 4 +- tests/test_triso/plots.xml | 2 +- 28 files changed, 219 insertions(+), 190 deletions(-) diff --git a/.travis.yml b/.travis.yml index e80ab4a9c1..b601d1036d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,7 +29,7 @@ before_install: - conda config --set always_yes yes --set changeps1 no - conda update -q conda - conda info -a - - conda create -q -n test-environment python=$TRAVIS_PYTHON_VERSION six numpy scipy h5py=2.5 pandas + - conda create -q -n test-environment python=$TRAVIS_PYTHON_VERSION six numpy scipy h5py=2.5 pandas matplotlib - source activate test-environment # Install GCC, MPICH, HDF5, PHDF5 diff --git a/docs/source/conf.py b/docs/source/conf.py index 89dd585d70..fbbc7cf8b0 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -25,7 +25,8 @@ except ImportError: MOCK_MODULES = ['numpy', 'numpy.polynomial', 'numpy.polynomial.polynomial', - 'h5py', 'pandas', 'uncertainties', 'openmoc'] + 'h5py', 'pandas', 'uncertainties', 'matplotlib.colors', + 'openmoc'] sys.modules.update((mod_name, MagicMock()) for mod_name in MOCK_MODULES) import numpy as np diff --git a/docs/source/pythonapi/index.rst b/docs/source/pythonapi/index.rst index d07cf60d3e..a1ce6b2b18 100644 --- a/docs/source/pythonapi/index.rst +++ b/docs/source/pythonapi/index.rst @@ -188,6 +188,7 @@ Running OpenMC openmc.run openmc.calculate_volumes openmc.plot_geometry + openmc.plot_inline Post-processing --------------- diff --git a/docs/source/usersguide/input.rst b/docs/source/usersguide/input.rst index 284f4f41a5..991c5912a1 100644 --- a/docs/source/usersguide/input.rst +++ b/docs/source/usersguide/input.rst @@ -2050,13 +2050,13 @@ sub-elements: *Default*: "plot" - :color: - Keyword for plot coloring. This can only be either ``cell`` or ``mat``, + :color_by: + Keyword for plot coloring. This can only be either "cell" or "material", which colors regions by cells and materials, respectively. For voxel plots, this determines which id (cell or material) is associated with each position. - *Default*: ``cell`` + *Default*: "cell" :level: Universe depth to plot at (optional). This parameter controls how many @@ -2142,10 +2142,10 @@ attributes or sub-elements. These are not used in "voxel" plots: *Default*: 0 0 0 (black) - :col_spec: + :color: Any number of this optional tag may be included in each ```` element, which can override the default random colors for cells or materials. Each - ``col_spec`` element must contain ``id`` and ``rgb`` sub-elements. + ``color`` element must contain ``id`` and ``rgb`` sub-elements. :id: Specifies the cell or material unique id for the color specification. @@ -2155,11 +2155,11 @@ attributes or sub-elements. These are not used in "voxel" plots: separated by spaces. As an example, if your plot is colored by material and you want material 23 - to be blue, the corresponding ``col_spec`` element would look like: + to be blue, the corresponding ``color`` element would look like: .. code-block:: xml - + *Default*: None @@ -2175,7 +2175,7 @@ attributes or sub-elements. These are not used in "voxel" plots: :background: Color to apply to all cells or materials not in the ``components`` list of - cells or materials to plot. This overrides any ``col_spec`` color + cells or materials to plot. This overrides any ``color`` color specifications. *Default*: None diff --git a/examples/python/boxes/build-xml.py b/examples/python/boxes/build-xml.py index 03e81b073b..d04f0f67a3 100644 --- a/examples/python/boxes/build-xml.py +++ b/examples/python/boxes/build-xml.py @@ -118,7 +118,7 @@ plot = openmc.Plot(plot_id=1) plot.origin = [0, 0, 0] plot.width = [20, 20] plot.pixels = [200, 200] -plot.color = 'cell' +plot.color_by = 'cell' # Instantiate a Plots collection and export to XML plot_file = openmc.Plots([plot]) diff --git a/examples/python/lattice/hexagonal/build-xml.py b/examples/python/lattice/hexagonal/build-xml.py index 8d124930fe..1065e6e069 100644 --- a/examples/python/lattice/hexagonal/build-xml.py +++ b/examples/python/lattice/hexagonal/build-xml.py @@ -132,7 +132,7 @@ plot_xy.filename = 'plot_xy' plot_xy.origin = [0, 0, 0] plot_xy.width = [6, 6] plot_xy.pixels = [400, 400] -plot_xy.color = 'mat' +plot_xy.color_by = 'material' plot_yz = openmc.Plot(plot_id=2) plot_yz.filename = 'plot_yz' @@ -140,7 +140,7 @@ plot_yz.basis = 'yz' plot_yz.origin = [0, 0, 0] plot_yz.width = [8, 8] plot_yz.pixels = [400, 400] -plot_yz.color = 'mat' +plot_yz.color_by = 'material' # Instantiate a Plots collection, add plots, and export to XML plot_file = openmc.Plots((plot_xy, plot_yz)) diff --git a/examples/python/lattice/nested/build-xml.py b/examples/python/lattice/nested/build-xml.py index 363d0db90c..ef74173f54 100644 --- a/examples/python/lattice/nested/build-xml.py +++ b/examples/python/lattice/nested/build-xml.py @@ -138,7 +138,7 @@ plot = openmc.Plot(plot_id=1) plot.origin = [0, 0, 0] plot.width = [4, 4] plot.pixels = [400, 400] -plot.color = 'mat' +plot.color_by = 'material' # Instantiate a Plots object and export to XML plot_file = openmc.Plots([plot]) diff --git a/examples/python/lattice/simple/build-xml.py b/examples/python/lattice/simple/build-xml.py index 354ce9cc49..6222cd366e 100644 --- a/examples/python/lattice/simple/build-xml.py +++ b/examples/python/lattice/simple/build-xml.py @@ -131,7 +131,7 @@ plot = openmc.Plot(plot_id=1) plot.origin = [0, 0, 0] plot.width = [4, 4] plot.pixels = [400, 400] -plot.color = 'mat' +plot.color_by = 'material' # Instantiate a Plots collection and export to XML plot_file = openmc.Plots([plot]) diff --git a/examples/xml/boxes/plots.xml b/examples/xml/boxes/plots.xml index b7a3093d25..0a75f574eb 100644 --- a/examples/xml/boxes/plots.xml +++ b/examples/xml/boxes/plots.xml @@ -1,7 +1,7 @@ - cell + cell 0. 0. 0. 20. 20. 200 200 diff --git a/examples/xml/lattice/nested/plots.xml b/examples/xml/lattice/nested/plots.xml index a8c6fbb7c5..0f92e06211 100644 --- a/examples/xml/lattice/nested/plots.xml +++ b/examples/xml/lattice/nested/plots.xml @@ -1,7 +1,7 @@ - + 0. 0. 0. 4.0 4.0 400 400 diff --git a/examples/xml/lattice/simple/plots.xml b/examples/xml/lattice/simple/plots.xml index a25a5a8280..0f92e06211 100644 --- a/examples/xml/lattice/simple/plots.xml +++ b/examples/xml/lattice/simple/plots.xml @@ -1,11 +1,10 @@ - + 0. 0. 0. 4.0 4.0 400 400 - diff --git a/examples/xml/pincell_multigroup/plots.xml b/examples/xml/pincell_multigroup/plots.xml index cbb8e532c1..92ab1c2cac 100644 --- a/examples/xml/pincell_multigroup/plots.xml +++ b/examples/xml/pincell_multigroup/plots.xml @@ -4,21 +4,21 @@ 1 mat - material + material 0 0 0 1.26 1.26 slice 1000 1000 - - - - + + + + 2 cell - cell + cell 0 0 0 1.26 1.26 slice diff --git a/openmc/cell.py b/openmc/cell.py index 0adc640836..e26af7f999 100644 --- a/openmc/cell.py +++ b/openmc/cell.py @@ -99,7 +99,6 @@ class Cell(object): self.fill = fill self.region = region self._rotation = None - self._rotation_matrix = None self._temperature = None self._translation = None self._paths = [] @@ -197,7 +196,14 @@ class Cell(object): @property def rotation_matrix(self): - return self._rotation_matrix + if self.rotation is not None: + phi, theta, psi = self.rotation*(-pi/180.) + c3, s3 = cos(phi), sin(phi) + c2, s2 = cos(theta), sin(theta) + c1, s1 = cos(psi), sin(psi) + return np.array([[c1*c2, c1*s2*s3 - c3*s1, s1*s3 + c1*c3*s2], + [c2*s1, c1*c3 + s1*s2*s3, c3*s1*s2 - c1*s3], + [-s2, c2*s3, c2*c3]]) @property def temperature(self): @@ -267,23 +273,13 @@ class Cell(object): @rotation.setter def rotation(self, rotation): if not isinstance(self.fill, openmc.Universe): - raise RuntimeError('Cell rotation can only be applied if the cell ' - 'is filled with a Universe') + raise TypeError('Cell rotation can only be applied if the cell ' + 'is filled with a Universe.') cv.check_type('cell rotation', rotation, Iterable, Real) cv.check_length('cell rotation', rotation, 3) self._rotation = np.asarray(rotation) - # Save rotation matrix - phi, theta, psi = self.rotation*(-pi/180.) - c3, s3 = cos(phi), sin(phi) - c2, s2 = cos(theta), sin(theta) - c1, s1 = cos(psi), sin(psi) - self._rotation_matrix = np.array([ - [c1*c2, c1*s2*s3 - c3*s1, s1*s3 + c1*c3*s2], - [c2*s1, c1*c3 + s1*s2*s3, c3*s1*s2 - c1*s3], - [-s2, c2*s3, c2*c3]]) - @translation.setter def translation(self, translation): cv.check_type('cell translation', translation, Iterable, Real) diff --git a/openmc/executor.py b/openmc/executor.py index 1fc00b998c..5181535128 100644 --- a/openmc/executor.py +++ b/openmc/executor.py @@ -1,9 +1,11 @@ from __future__ import print_function +from collections import Iterable import subprocess from numbers import Integral from six import string_types +import openmc from openmc import VolumeCalculation @@ -32,17 +34,56 @@ def plot_geometry(output=True, openmc_exec='openmc', cwd='.'): Parameters ---------- - output : bool + output : bool, optional Capture OpenMC output from standard out - openmc_exec : str + openmc_exec : str, optional Path to OpenMC executable cwd : str, optional - Path to working directory to run in. Defaults to the current working directory. + Path to working directory to run in """ return _run([openmc_exec, '-p'], output, cwd) +def plot_inline(plots, openmc_exec='openmc', cwd='.', convert_exec='convert'): + """Display plots inline in a Jupyter notebook. + + Parameters + ---------- + plots : Iterable of openmc.Plot + Plots to display + openmc_exec : str + Path to OpenMC executable + cwd : str, optional + Path to working directory to run in + convert_exec : str, optional + Command that can convert PPM files into PNG files + + """ + from IPython.display import Image, display + + if not isinstance(plots, Iterable): + plots = [plots] + + # Create plots.xml + openmc.Plots(plots).export_to_xml() + + # Run OpenMC in geometry plotting mode + plot_geometry(False, openmc_exec, cwd) + + images = [] + if plots is not None: + for p in plots: + if p.filename is not None: + ppm_file = '{}.ppm'.format(p.filename) + else: + ppm_file = 'plot_{}.ppm'.format(p.id) + png_file = ppm_file.replace('.ppm', '.png') + subprocess.check_call([convert_exec, ppm_file, png_file]) + images.append(Image(png_file)) + display(*images) + + def calculate_volumes(threads=None, output=True, cwd='.', openmc_exec='openmc', mpi_args=None): """Run stochastic volume calculations in OpenMC. diff --git a/openmc/plots.py b/openmc/plots.py index bbefb10768..3da9960340 100644 --- a/openmc/plots.py +++ b/openmc/plots.py @@ -1,4 +1,4 @@ -from collections import Iterable +from collections import Iterable, Mapping from numbers import Real, Integral from xml.etree import ElementTree as ET import sys @@ -6,6 +6,7 @@ import warnings from six import string_types import numpy as np +from matplotlib.colors import is_color_like, to_rgb import openmc import openmc.checkvalue as cv @@ -50,20 +51,20 @@ class Plot(object): Origin (center) of the plot filename : Path to write the plot to - color : {'cell', 'mat'} + 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 : tuple or list of ndarray + background : Iterable of int or str Color of the background defined by RGB - mask_components : Iterable of int - Unique id numbers of the cells or materials to plot + mask_components : Iterable of openmc.Cell or openmc.Material + The cells or materials to plot mask_background : Iterable of int Color to apply to all cells/materials not listed in mask_components defined by RGB - col_spec : dict + colors : dict Dictionary indicating that certain cells/materials (keys) should be colored with a specific RGB (values) level : int @@ -81,14 +82,14 @@ class Plot(object): self._width = [4.0, 4.0] self._pixels = [1000, 1000] self._origin = [0., 0., 0.] - self._filename = 'plot' - self._color = 'cell' + 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._col_spec = None + self._colors = {} self._level = None self._meshlines = None @@ -117,8 +118,8 @@ class Plot(object): return self._filename @property - def color(self): - return self._color + def color_by(self): + return self._color_by @property def type(self): @@ -141,8 +142,8 @@ class Plot(object): return self._mask_background @property - def col_spec(self): - return self._col_spec + def colors(self): + return self._colors @property def level(self): @@ -193,55 +194,51 @@ class Plot(object): cv.check_type('filename', filename, string_types) self._filename = filename - @color.setter - def color(self, color): - cv.check_type('plot color', color, string_types) - cv.check_value('plot color', color, ['cell', 'mat']) - self._color = color + @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_type('plot type', plottype, string_types) cv.check_value('plot type', plottype, ['slice', 'voxel']) self._type = plottype @basis.setter def basis(self, basis): - cv.check_type('plot basis', basis, string_types) cv.check_value('plot basis', basis, ['xy', 'xz', 'yz']) self._basis = basis @background.setter 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) + cv.check_type('plot background', background, Iterable) + if isinstance(background, string_types): + if not is_color_like(background): + 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 - @col_spec.setter - def col_spec(self, col_spec): - cv.check_type('plot col_spec parameter', col_spec, dict, Integral) + @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 not is_color_like(value): + 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) - for key in col_spec: - if key < 0: - msg = 'Unable to create Plot ID="{0}" with col_spec ID "{1}" ' \ - 'which is less than 0'.format(self._id, key) - raise ValueError(msg) - - elif not isinstance(col_spec[key], Iterable): - msg = 'Unable to create Plot ID="{0}" with col_spec RGB values' \ - ' "{1}" which is not iterable'.format(self._id, col_spec[key]) - raise ValueError(msg) - - elif len(col_spec[key]) != 3: - msg = 'Unable to create Plot ID="{0}" with col_spec RGB ' \ - 'values of length "{1}" since 3 values must be ' \ - 'input'.format(self._id, len(col_spec[key])) - raise ValueError(msg) - - self._col_spec = col_spec + self._colors = colors @mask_components.setter def mask_components(self, mask_components): @@ -300,25 +297,23 @@ class Plot(object): def __repr__(self): string = 'Plot\n' - string += '{0: <16}{1}{2}\n'.format('\tID', '=\t', self._id) - string += '{0: <16}{1}{2}\n'.format('\tName', '=\t', self._name) - string += '{0: <16}{1}{2}\n'.format('\tFilename', '=\t', self._filename) - string += '{0: <16}{1}{2}\n'.format('\tType', '=\t', self._type) - string += '{0: <16}{1}{2}\n'.format('\tBasis', '=\t', self._basis) - string += '{0: <16}{1}{2}\n'.format('\tWidth', '=\t', self._width) - string += '{0: <16}{1}{2}\n'.format('\tOrigin', '=\t', self._origin) - string += '{0: <16}{1}{2}\n'.format('\tPixels', '=\t', self._origin) - string += '{0: <16}{1}{2}\n'.format('\tColor', '=\t', self._color) - string += '{0: <16}{1}{2}\n'.format('\tBackground', '=\t', - self._background) - string += '{0: <16}{1}{2}\n'.format('\tMask components', '=\t', + 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 += '{0: <16}{1}{2}\n'.format('\tMask background', '=\t', + string += '{: <16}=\t{}\n'.format('\tMask background', self._mask_background) - string += '{0: <16}{1}{2}\n'.format('\tCol Spec', '=\t', self._col_spec) - string += '{0: <16}{1}{2}\n'.format('\tLevel', '=\t', self._level) - string += '{0: <16}{1}{2}\n'.format('\tMeshlines', '=\t', - self._meshlines) + 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 def colorize(self, geometry, seed=1): @@ -341,78 +336,71 @@ class Plot(object): cv.check_greater_than('seed', seed, 1, equality=True) # Get collections of the domains which will be plotted - if self.color is 'mat': - domains = geometry.get_all_materials() + if self.color_by == 'material': + domains = geometry.get_all_materials().values() else: - domains = geometry.get_all_cells() + domains = geometry.get_all_cells().values() # Set the seed for the random number generator np.random.seed(seed) # Generate random colors for each feature - self.col_spec = {} - for domain_id in domains: - r = np.random.randint(0, 256) - g = np.random.randint(0, 256) - b = np.random.randint(0, 256) - self.col_spec[domain_id] = (r, g, b) + for domain in domains: + self.colors[domain] = np.random.randint(0, 256, (3,)) 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 + 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 + domains : Iterable of openmc.Cell or openmc.Material A collection of the domain IDs to highlight in the plot - seed : Integral + seed : int The random number seed used to generate the color scheme - alpha : Real in [0,1] + alpha : float The value to apply in alpha compisiting - background : 3-tuple of Integral or 'white' or 'black' or 'gray' + background : 3-tuple of int or str The background color to apply in alpha compisiting """ - cv.check_iterable_type('domains', domains, Integral) + 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 == '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) + try: + background = to_rgb(background) + except ValueError: + raise ValueError("'{}' is not a valid color.".format(background)) # 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] + for domain, color in self.colors.items(): + if domain not in domains: + if isinstance(color, string_types): + color = [int(255*x) for x in to_rgb(color)] + 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._col_spec[domain_id] = (r, g, b) + self._colors[domain] = (r, g, b) - def get_plot_xml(self): + def to_xml_element(self): """Return XML representation of the plot Returns @@ -424,8 +412,9 @@ class Plot(object): element = ET.Element("plot") element.set("id", str(self._id)) - element.set("filename", self._filename) - element.set("color", self._color) + 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': @@ -442,14 +431,18 @@ class Plot(object): if self._background is not None: subelement = ET.SubElement(element, "background") - subelement.text = ' '.join(map(str, self._background)) + color = self._background + if isinstance(color, string_types): + color = [int(255*x) for x in to_rgb(color)] + subelement.text = ' '.join(str(x) for x in color) - if self._col_spec is not None: - for key in self._col_spec: - subelement = ET.SubElement(element, "col_spec") - subelement.set("id", str(key)) - subelement.set("rgb", ' '.join(map( - str, self._col_spec[key]))) + if self._colors: + for domain, color in self._colors.items(): + subelement = ET.SubElement(element, "color") + subelement.set("id", str(domain.id)) + if isinstance(color, string_types): + color = [int(255*x) for x in to_rgb(color)] + subelement.set("rgb", ' '.join(str(x) for x in color)) if self._mask_components is not None: subelement = ET.SubElement(element, "mask") @@ -585,20 +578,21 @@ class Plots(cv.CheckedList): 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 partially transparent. + 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 + domains : Iterable of openmc.Cell or openmc.Material A collection of the domain IDs to highlight in the plot - seed : Integral + seed : int The random number seed used to generate the color scheme - alpha : Real in [0,1] + alpha : float The value to apply in alpha compisiting - background : 3-tuple of Integral or 'white' or 'black' or 'gray' + background : 3-tuple of int or str The background color to apply in alpha compisiting """ @@ -608,7 +602,7 @@ class Plots(cv.CheckedList): def _create_plot_subelements(self): for plot in self: - xml_element = plot.get_plot_xml() + xml_element = plot.to_xml_element() if len(plot.name) > 0: self._plots_file.append(ET.Comment(plot.name)) diff --git a/openmc/universe.py b/openmc/universe.py index 4cc3f482e9..506bacef47 100644 --- a/openmc/universe.py +++ b/openmc/universe.py @@ -225,7 +225,6 @@ class Universe(object): color_by : {'cell', 'material'} Indicate whether the plot should be colored by cell or by material colors : dict - Assigns colors to specific materials or cells. Keys are instances of :class:`Cell` or :class:`Material` and values are RGB 3-tuples or RGBA 4-tuples. Red, green, blue, and alpha should all be floats in diff --git a/src/input_xml.F90 b/src/input_xml.F90 index e7365330cf..3d8e327500 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -4364,8 +4364,8 @@ contains ! Copy plot color type and initialize all colors randomly temp_str = "cell" - if (check_for_node(node_plot, "color")) & - call get_node_value(node_plot, "color", temp_str) + if (check_for_node(node_plot, "color_by")) & + call get_node_value(node_plot, "color_by", temp_str) temp_str = to_lower(temp_str) select case (trim(temp_str)) case ("cell") @@ -4378,7 +4378,7 @@ contains pl % colors(j) % rgb(3) = int(prn()*255) end do - case ("mat", "material") + case ("material") pl % color_by = PLOT_COLOR_MATS allocate(pl % colors(n_materials)) @@ -4393,8 +4393,8 @@ contains // "' in plot " // trim(to_str(pl % id))) end select - ! Get the number of nodes and get a list of them - call get_node_list(node_plot, "col_spec", node_col_list) + ! Get the number of nodes and get a list of them + call get_node_list(node_plot, "color", node_col_list) n_cols = size(node_col_list) ! Copy user specified colors diff --git a/src/relaxng/plots.rnc b/src/relaxng/plots.rnc index 5ef14f2297..e53cc15f72 100644 --- a/src/relaxng/plots.rnc +++ b/src/relaxng/plots.rnc @@ -5,8 +5,8 @@ element plots { attribute filename { xsd:string { maxLength = "50" } })? & (element type { "slice" | "voxel" } | attribute type { "slice" | "voxel" })? & - (element color { ( "cell" | "mat" | "material" ) } | - attribute color { ( "cell" | "mat" | "material" ) })? & + (element color_by { ( "cell" | "material" ) } | + attribute color_by { ( "cell" | "material" ) })? & (element level { xsd:int } | attribute level { xsd:int })? & (element origin { list { xsd:double+ } } | attribute origin { list { xsd:double+ } })? & @@ -18,7 +18,7 @@ element plots { attribute pixels { list { xsd:int+ } })? & (element background { list { xsd:int+ } } | attribute background { list { xsd:int+ } })? & - element col_spec { + element color { (element id { xsd:int } | attribute id { xsd:int }) & (element rgb { list { xsd:int+ } } | attribute rgb { list { xsd:int+ } }) diff --git a/src/relaxng/plots.rng b/src/relaxng/plots.rng index a21f4d141e..55d69008ee 100644 --- a/src/relaxng/plots.rng +++ b/src/relaxng/plots.rng @@ -45,17 +45,15 @@ - + cell - mat material - + cell - mat material @@ -162,7 +160,7 @@ - + diff --git a/tests/input_set.py b/tests/input_set.py index 7971a8a4cc..ce5e3d5bc6 100644 --- a/tests/input_set.py +++ b/tests/input_set.py @@ -471,7 +471,7 @@ class InputSet(object): plot.origin = (125, 125, 0) plot.width = (250, 250) plot.pixels = (3000, 3000) - plot.color = 'mat' + plot.color_by = 'material' self.plots.add_plot(plot) @@ -563,7 +563,7 @@ class PinCellInputSet(object): plot.origin = (0.0, 0.0, 0) plot.width = (1.26, 1.26) plot.pixels = (300, 300) - plot.color = 'mat' + plot.color_by = 'material' self.plots.add_plot(plot) @@ -714,7 +714,7 @@ class AssemblyInputSet(object): plot.origin = (0.0, 0.0, 0) plot.width = (21.42, 21.42) plot.pixels = (300, 300) - plot.color = 'mat' + plot.color_by = 'material' self.plots.add_plot(plot) @@ -793,6 +793,6 @@ class MGInputSet(InputSet): plot.width = (2.5, 2.5) plot.basis = 'xz' plot.pixels = (3000, 3000) - plot.color = 'mat' + plot.color_by = 'material' self.plots.add_plot(plot) diff --git a/tests/test_distribmat/inputs_true.dat b/tests/test_distribmat/inputs_true.dat index 746bb8bef5..2d3e39a65d 100644 --- a/tests/test_distribmat/inputs_true.dat +++ b/tests/test_distribmat/inputs_true.dat @@ -52,12 +52,12 @@ - + 0 0 0 7 7 400 400 - + 0 0 0 7 7 400 400 diff --git a/tests/test_distribmat/test_distribmat.py b/tests/test_distribmat/test_distribmat.py index 6c1c5f79cf..2816bbfbc3 100644 --- a/tests/test_distribmat/test_distribmat.py +++ b/tests/test_distribmat/test_distribmat.py @@ -95,7 +95,7 @@ class DistribmatTestHarness(PyAPITestHarness): plot = openmc.Plot(plot_id=1) plot.basis = 'xy' - plot.color = 'cell' + plot.color_by = 'cell' plot.filename = 'cellplot' plot.origin = (0, 0, 0) plot.width = (7, 7) @@ -104,7 +104,7 @@ class DistribmatTestHarness(PyAPITestHarness): plot = openmc.Plot(plot_id=2) plot.basis = 'xy' - plot.color = 'mat' + plot.color_by = 'material' plot.filename = 'matplot' plot.origin = (0, 0, 0) plot.width = (7, 7) diff --git a/tests/test_lattice_hex/plots.xml b/tests/test_lattice_hex/plots.xml index 651c811443..903d69aef2 100644 --- a/tests/test_lattice_hex/plots.xml +++ b/tests/test_lattice_hex/plots.xml @@ -1,28 +1,28 @@ - + xy_cell 0 0 0 30 30 500 500 - + xy_material 0 0 0 30 30 500 500 - + yz_cell 0 0 0 50 400 500 4000 - + yz_material 0 0 0 5 5 diff --git a/tests/test_lattice_mixed/plots.xml b/tests/test_lattice_mixed/plots.xml index 651c811443..903d69aef2 100644 --- a/tests/test_lattice_mixed/plots.xml +++ b/tests/test_lattice_mixed/plots.xml @@ -1,28 +1,28 @@ - + xy_cell 0 0 0 30 30 500 500 - + xy_material 0 0 0 30 30 500 500 - + yz_cell 0 0 0 50 400 500 4000 - + yz_material 0 0 0 5 5 diff --git a/tests/test_multipole/inputs_true.dat b/tests/test_multipole/inputs_true.dat index 2a1e865ada..ee1ba636e2 100644 --- a/tests/test_multipole/inputs_true.dat +++ b/tests/test_multipole/inputs_true.dat @@ -51,12 +51,12 @@ - + 0 0 0 7 7 400 400 - + 0 0 0 7 7 400 400 diff --git a/tests/test_multipole/test_multipole.py b/tests/test_multipole/test_multipole.py index 85c360e734..33a9295dbc 100644 --- a/tests/test_multipole/test_multipole.py +++ b/tests/test_multipole/test_multipole.py @@ -79,7 +79,7 @@ class MultipoleTestHarness(PyAPITestHarness): plot = openmc.Plot(plot_id=1) plot.basis = 'xy' - plot.color = 'cell' + plot.color_by = 'cell' plot.filename = 'cellplot' plot.origin = (0, 0, 0) plot.width = (7, 7) @@ -88,7 +88,7 @@ class MultipoleTestHarness(PyAPITestHarness): plot = openmc.Plot(plot_id=2) plot.basis = 'xy' - plot.color = 'mat' + plot.color_by = 'material' plot.filename = 'matplot' plot.origin = (0, 0, 0) plot.width = (7, 7) diff --git a/tests/test_plot/plots.xml b/tests/test_plot/plots.xml index d5e03f598f..ecfe69125b 100644 --- a/tests/test_plot/plots.xml +++ b/tests/test_plot/plots.xml @@ -5,7 +5,7 @@ 0. 0. 0. 25 25 200 200 - + @@ -16,7 +16,7 @@ - + 0. 0. 0. 25 25 200 200 diff --git a/tests/test_triso/plots.xml b/tests/test_triso/plots.xml index 60ae7d9d8f..eafe3206b3 100644 --- a/tests/test_triso/plots.xml +++ b/tests/test_triso/plots.xml @@ -1,6 +1,6 @@ - From 61f013855582d993ec604568ef427282a1cb83fd Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 8 Mar 2017 13:42:26 -0600 Subject: [PATCH 2/9] Add Plot.from_geometry() classmethod --- docs/Makefile | 3 +- docs/source/usersguide/input.rst | 2 +- openmc/cell.py | 8 +++ openmc/geometry.py | 7 +++ openmc/plots.py | 97 +++++++++++++++++++++++++------- openmc/region.py | 2 +- openmc/universe.py | 8 +++ openmc/volume.py | 4 +- src/input_xml.F90 | 7 +-- 9 files changed, 107 insertions(+), 31 deletions(-) diff --git a/docs/Makefile b/docs/Makefile index e84aadd33c..2f3c029db1 100644 --- a/docs/Makefile +++ b/docs/Makefile @@ -55,7 +55,8 @@ images: $(PDFS) $(PNGS) clean: -rm -rf $(BUILDDIR)/* - -rm $(PDFS) + -rm -rf $(PDFS) + -rm -rf source/pythonapi/generated/ html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html diff --git a/docs/source/usersguide/input.rst b/docs/source/usersguide/input.rst index 991c5912a1..4ce714b939 100644 --- a/docs/source/usersguide/input.rst +++ b/docs/source/usersguide/input.rst @@ -2178,7 +2178,7 @@ attributes or sub-elements. These are not used in "voxel" plots: cells or materials to plot. This overrides any ``color`` color specifications. - *Default*: None + *Default*: 255 255 255 (white) :meshlines: The ``meshlines`` sub-element allows for plotting the boundaries of a diff --git a/openmc/cell.py b/openmc/cell.py index e26af7f999..e83d01dcd7 100644 --- a/openmc/cell.py +++ b/openmc/cell.py @@ -224,6 +224,14 @@ class Cell(object): 'Call the Geometry.determine_paths() method.') return self._paths + @property + def bounding_box(self): + if self.region is not None: + return self.region.bounding_box + else: + return (np.array([-np.inf, -np.inf, -np.inf]), + np.array([np.inf, np.inf, np.inf])) + @property def num_instances(self): return len(self.paths) diff --git a/openmc/geometry.py b/openmc/geometry.py index 5cb7cf1679..4bb6e23e46 100644 --- a/openmc/geometry.py +++ b/openmc/geometry.py @@ -28,6 +28,9 @@ class Geometry(object): ---------- root_universe : openmc.Universe Root universe which contains all others + bounding_box : 2-tuple of numpy.array + Lower-left and upper-right coordinates of an axis-aligned bounding box + of the universe. """ @@ -41,6 +44,10 @@ class Geometry(object): def root_universe(self): return self._root_universe + @property + def bounding_box(self): + return self.root_universe.bounding_box + @root_universe.setter def root_universe(self, root_universe): check_type('root universe', root_universe, openmc.Universe) diff --git a/openmc/plots.py b/openmc/plots.py index 3da9960340..9e69bf7f89 100644 --- a/openmc/plots.py +++ b/openmc/plots.py @@ -23,12 +23,16 @@ def reset_auto_plot_id(): AUTO_PLOT_ID = 10000 -BASES = ['xy', 'xz', 'yz'] +_BASES = ['xy', 'xz', 'yz'] class Plot(object): - """Definition of a finite region of space to be plotted, either as a slice plot - in two dimensions or as a voxel plot in three dimensions. + """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 `_. Parameters ---------- @@ -58,15 +62,14 @@ class Plot(object): basis : {'xy', 'xz', 'yz'} The basis directions for the plot background : Iterable of int or str - Color of the background defined by RGB + Color of the background mask_components : Iterable of openmc.Cell or openmc.Material The cells or materials to plot - mask_background : Iterable of int + mask_background : Iterable of int or str Color to apply to all cells/materials not listed in mask_components - defined by RGB colors : dict Dictionary indicating that certain cells/materials (keys) should be - colored with a specific RGB (values) + displayed with a particular color. level : int Universe depth to plot at meshlines : dict @@ -80,7 +83,7 @@ class Plot(object): self.id = plot_id self.name = name self._width = [4.0, 4.0] - self._pixels = [1000, 1000] + self._pixels = [400, 400] self._origin = [0., 0., 0.] self._filename = None self._color_by = 'cell' @@ -206,7 +209,7 @@ class Plot(object): @basis.setter def basis(self, basis): - cv.check_value('plot basis', basis, ['xy', 'xz', 'yz']) + cv.check_value('plot basis', basis, _BASES) self._basis = basis @background.setter @@ -242,18 +245,21 @@ class Plot(object): @mask_components.setter def mask_components(self, mask_components): - cv.check_type('plot mask components', mask_components, Iterable, Integral) - for component in mask_components: - cv.check_greater_than('plot mask components', component, 0, True) + 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, 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) + cv.check_type('plot mask background', mask_background, Iterable) + if isinstance(mask_background, string_types): + if not is_color_like(mask_background): + 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 @@ -316,6 +322,51 @@ class Plot(object): string += '{: <16}=\t{}\n'.format('\tMeshlines', self._meshlines) return string + @classmethod + 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 at what z value is 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 + def colorize(self, geometry, seed=1): """Generate a color scheme for each domain in the plot. @@ -446,10 +497,14 @@ class Plot(object): if self._mask_components is not None: subelement = ET.SubElement(element, "mask") - subelement.set("components", ' '.join(map( - str, self._mask_components))) - subelement.set("background", ' '.join(map( - str, self._mask_background))) + 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 = [int(255*x) for x in to_rgb(color)] + subelement.set("background", ' '.join( + str(x) for x in color)) if self._level is not None: subelement = ET.SubElement(element, "level") diff --git a/openmc/region.py b/openmc/region.py index 8fd7e5181d..4494a3f622 100644 --- a/openmc/region.py +++ b/openmc/region.py @@ -301,7 +301,7 @@ class Union(Region): ---------- nodes : tuple of openmc.Region Regions to take the union of - bounding_box : tuple of numpy.array + bounding_box : 2-tuple of numpy.array Lower-left and upper-right coordinates of an axis-aligned bounding box """ diff --git a/openmc/universe.py b/openmc/universe.py index 506bacef47..952f61dac1 100644 --- a/openmc/universe.py +++ b/openmc/universe.py @@ -46,6 +46,9 @@ class Universe(object): Volume of the universe in cm^3. This can either be set manually or calculated in a stochastic volume calculation and added via the :meth:`Universe.add_volume_information` method. + bounding_box : 2-tuple of numpy.array + Lower-left and upper-right coordinates of an axis-aligned bounding box + of the universe. """ @@ -105,6 +108,11 @@ class Universe(object): def volume(self): return self._volume + @property + def bounding_box(self): + regions = [c.region for c in self.cells.values()] + return openmc.Union(*regions).bounding_box + @id.setter def id(self, universe_id): if universe_id is None: diff --git a/openmc/volume.py b/openmc/volume.py index f7bad73b4c..c9e1a5afa6 100644 --- a/openmc/volume.py +++ b/openmc/volume.py @@ -80,9 +80,7 @@ class VolumeCalculation(object): # user-specified one is valid if self.domain_type == 'cell': for c in domains: - if c.region is None: - continue - ll, ur = c.region.bounding_box + ll, ur = c.bounding_box if np.any(np.isinf(ll)) or np.any(np.isinf(ur)): continue if (np.any(np.asarray(lower_left) > ll) or diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 3d8e327500..163dcf9013 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -4634,13 +4634,12 @@ contains end do ! Alter colors based on mask information - do j=1,size(pl % colors) - if (.not. any(j .eq. iarray)) then + do j = 1, size(pl % colors) + if (.not. any(j == iarray)) then if (check_for_node(node_mask, "background")) then call get_node_array(node_mask, "background", pl % colors(j) % rgb) else - call fatal_error("Missing in mask of plot " & - // trim(to_str(pl % id))) + pl % colors(j) % rgb(:) = [255, 255, 255] end if end if end do From 70ce242ed7f56ac9eb071e01a23586da9b87a7c1 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 8 Mar 2017 13:46:52 -0600 Subject: [PATCH 3/9] Allow string colors in Universe.plot() --- openmc/plotter.py | 4 +--- openmc/universe.py | 20 +++++++++++--------- tests/test_triso/inputs_true.dat | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/openmc/plotter.py b/openmc/plotter.py index 9e356b1eda..488de56d87 100644 --- a/openmc/plotter.py +++ b/openmc/plotter.py @@ -3,6 +3,7 @@ from six import string_types from itertools import chain import numpy as np +import matplotlib.pyplot as plt import openmc.checkvalue as cv import openmc.data @@ -121,9 +122,6 @@ def plot_xs(this, types, divisor_types=None, temperature=294., axis=None, generated. """ - - from matplotlib import pyplot as plt - cv.check_type("plot_CE", plot_CE, bool) if isinstance(this, openmc.Nuclide): diff --git a/openmc/universe.py b/openmc/universe.py index 952f61dac1..718825db97 100644 --- a/openmc/universe.py +++ b/openmc/universe.py @@ -1,3 +1,4 @@ +from copy import copy from collections import OrderedDict, Iterable from numbers import Integral, Real import random @@ -5,6 +6,8 @@ import sys from six import string_types import numpy as np +import matplotlib.pyplot as plt +from matplotlib.colors import to_rgba import openmc import openmc.checkvalue as cv @@ -234,9 +237,9 @@ class Universe(object): Indicate whether the plot should be colored by cell or by material colors : dict Assigns colors to specific materials or cells. Keys are instances of - :class:`Cell` or :class:`Material` and values are RGB 3-tuples or - RGBA 4-tuples. Red, green, blue, and alpha should all be floats in - the range [0.0, 1.0], for example: + :class:`Cell` or :class:`Material` and values are RGB 3-tuples, RGBA + 4-tuples, or strings indicating SVG color names. Red, green, blue, + and alpha should all be floats in the range [0.0, 1.0], for example: .. code-block:: python @@ -256,8 +259,6 @@ class Universe(object): :func:`matplotlib.pyplot.imshow`. """ - import matplotlib.pyplot as plt - # Seed the random number generator if seed is not None: random.seed(seed) @@ -267,9 +268,9 @@ class Universe(object): colors = {} else: # Convert to RGBA if necessary - for obj, rgb in colors.items(): - if len(rgb) == 3: - colors[obj] = rgb + (1.0,) + colors = copy(colors) + for obj, color in colors.items(): + colors[obj] = to_rgba(color) if basis == 'xy': x_min = center[0] - 0.5*width[0] @@ -325,7 +326,8 @@ class Universe(object): img[j, i, :] = colors[obj] # Display image - plt.imshow(img, extent=(x_min, x_max, y_min, y_max), **kwargs) + plt.imshow(img, extent=(x_min, x_max, y_min, y_max), + interpolation='nearest', **kwargs) # Show or save the plot if filename is None: diff --git a/tests/test_triso/inputs_true.dat b/tests/test_triso/inputs_true.dat index 43b15b037c..12dd4e7a64 100644 --- a/tests/test_triso/inputs_true.dat +++ b/tests/test_triso/inputs_true.dat @@ -442,7 +442,7 @@ - From 9dec183b539cf6c9cdf090fece72e6a085d0aac9 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 8 Mar 2017 15:00:18 -0600 Subject: [PATCH 4/9] Replace use of matplotlib.colors with _SVG_NAMES dictionary --- .travis.yml | 2 +- docs/source/conf.py | 3 +- openmc/plots.py | 170 +++++++++++++++++++++++++++++++++++++++++--- openmc/plotter.py | 4 +- openmc/universe.py | 15 +++- 5 files changed, 176 insertions(+), 18 deletions(-) diff --git a/.travis.yml b/.travis.yml index b601d1036d..e80ab4a9c1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -29,7 +29,7 @@ before_install: - conda config --set always_yes yes --set changeps1 no - conda update -q conda - conda info -a - - conda create -q -n test-environment python=$TRAVIS_PYTHON_VERSION six numpy scipy h5py=2.5 pandas matplotlib + - conda create -q -n test-environment python=$TRAVIS_PYTHON_VERSION six numpy scipy h5py=2.5 pandas - source activate test-environment # Install GCC, MPICH, HDF5, PHDF5 diff --git a/docs/source/conf.py b/docs/source/conf.py index fbbc7cf8b0..89dd585d70 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -25,8 +25,7 @@ except ImportError: MOCK_MODULES = ['numpy', 'numpy.polynomial', 'numpy.polynomial.polynomial', - 'h5py', 'pandas', 'uncertainties', 'matplotlib.colors', - 'openmoc'] + 'h5py', 'pandas', 'uncertainties', 'openmoc'] sys.modules.update((mod_name, MagicMock()) for mod_name in MOCK_MODULES) import numpy as np diff --git a/openmc/plots.py b/openmc/plots.py index 9e69bf7f89..d95bc59058 100644 --- a/openmc/plots.py +++ b/openmc/plots.py @@ -6,7 +6,6 @@ import warnings from six import string_types import numpy as np -from matplotlib.colors import is_color_like, to_rgb import openmc import openmc.checkvalue as cv @@ -25,6 +24,156 @@ def reset_auto_plot_id(): _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) +} + class Plot(object): """Definition of a finite region of space to be plotted. @@ -216,7 +365,7 @@ class Plot(object): def background(self, background): cv.check_type('plot background', background, Iterable) if isinstance(background, string_types): - if not is_color_like(background): + if background.lower() not in _SVG_COLORS: raise ValueError("'{}' is not a valid color.".format(background)) else: cv.check_length('plot background', background, 3) @@ -232,7 +381,7 @@ class Plot(object): 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 not is_color_like(value): + 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) @@ -253,7 +402,7 @@ class Plot(object): def mask_background(self, mask_background): cv.check_type('plot mask background', mask_background, Iterable) if isinstance(mask_background, string_types): - if not is_color_like(mask_background): + 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) @@ -431,10 +580,9 @@ class Plot(object): # Get a background (R,G,B) tuple to apply in alpha compositing if isinstance(background, string_types): - try: - background = to_rgb(background) - except ValueError: + 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) @@ -444,7 +592,7 @@ class Plot(object): for domain, color in self.colors.items(): if domain not in domains: if isinstance(color, string_types): - color = [int(255*x) for x in to_rgb(color)] + 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)) @@ -484,7 +632,7 @@ class Plot(object): subelement = ET.SubElement(element, "background") color = self._background if isinstance(color, string_types): - color = [int(255*x) for x in to_rgb(color)] + color = _SVG_COLORS[color.lower()] subelement.text = ' '.join(str(x) for x in color) if self._colors: @@ -492,7 +640,7 @@ class Plot(object): subelement = ET.SubElement(element, "color") subelement.set("id", str(domain.id)) if isinstance(color, string_types): - color = [int(255*x) for x in to_rgb(color)] + color = _SVG_COLORS[color.lower()] subelement.set("rgb", ' '.join(str(x) for x in color)) if self._mask_components is not None: @@ -502,7 +650,7 @@ class Plot(object): color = self._mask_background if color is not None: if isinstance(color, string_types): - color = [int(255*x) for x in to_rgb(color)] + color = _SVG_COLORS[color.lower()] subelement.set("background", ' '.join( str(x) for x in color)) diff --git a/openmc/plotter.py b/openmc/plotter.py index 488de56d87..9e356b1eda 100644 --- a/openmc/plotter.py +++ b/openmc/plotter.py @@ -3,7 +3,6 @@ from six import string_types from itertools import chain import numpy as np -import matplotlib.pyplot as plt import openmc.checkvalue as cv import openmc.data @@ -122,6 +121,9 @@ def plot_xs(this, types, divisor_types=None, temperature=294., axis=None, generated. """ + + from matplotlib import pyplot as plt + cv.check_type("plot_CE", plot_CE, bool) if isinstance(this, openmc.Nuclide): diff --git a/openmc/universe.py b/openmc/universe.py index 718825db97..71bb2ab624 100644 --- a/openmc/universe.py +++ b/openmc/universe.py @@ -1,3 +1,4 @@ +from __future__ import division from copy import copy from collections import OrderedDict, Iterable from numbers import Integral, Real @@ -6,11 +7,10 @@ import sys from six import string_types import numpy as np -import matplotlib.pyplot as plt -from matplotlib.colors import to_rgba import openmc import openmc.checkvalue as cv +from openmc.plots import _SVG_COLORS # A static variable for auto-generated Lattice (Universe) IDs @@ -259,6 +259,8 @@ class Universe(object): :func:`matplotlib.pyplot.imshow`. """ + import matplotlib.pyplot as plt + # Seed the random number generator if seed is not None: random.seed(seed) @@ -270,7 +272,14 @@ class Universe(object): # Convert to RGBA if necessary colors = copy(colors) for obj, color in colors.items(): - colors[obj] = to_rgba(color) + if isinstance(color, string_types): + if color.lower() not in _SVG_COLORS: + raise ValueError("'{}' is not a valid color." + .format(color)) + colors[obj] = [x/255 for x in + _SVG_COLORS[color.lower()]] + [1.0] + elif len(color) == 3: + colors[obj] = list(color) + [1.0] if basis == 'xy': x_min = center[0] - 0.5*width[0] From f805fddb6b2efd4ff44b3c491448ddccd57294ad Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 9 Mar 2017 15:48:23 -0600 Subject: [PATCH 5/9] Handle unbounded universe --- openmc/universe.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/openmc/universe.py b/openmc/universe.py index 71bb2ab624..0d5b8395a5 100644 --- a/openmc/universe.py +++ b/openmc/universe.py @@ -113,8 +113,13 @@ class Universe(object): @property def bounding_box(self): - regions = [c.region for c in self.cells.values()] - return openmc.Union(*regions).bounding_box + regions = [c.region for c in self.cells.values() + if c.region is not None] + if regions: + return openmc.Union(*regions).bounding_box + else: + # Infinite bounding box + return openmc.Intersection().bounding_box @id.setter def id(self, universe_id): From 97d99f16258d04c7af071d9d500e19bc6853411a Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 10 Mar 2017 07:13:38 -0600 Subject: [PATCH 6/9] Fix bug in autodetermining root universe (didn't account for lattice outer) --- openmc/summary.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openmc/summary.py b/openmc/summary.py index c30f004cbb..82275007c0 100644 --- a/openmc/summary.py +++ b/openmc/summary.py @@ -49,7 +49,7 @@ class Summary(object): @property def date_and_time(self): - return self._f.attrs['date_and_time'] + return self._f.attrs['date_and_time'].decode() @property def geometry(self): @@ -185,6 +185,8 @@ class Summary(object): for idx in fill._natural_indices: univ = fill.get_universe(idx) fill_univ_ids.add(univ.id) + if fill.outer is not None: + fill_univ_ids.add(fill.outer.id) # Set the fill for the Cell cells[cell_id].fill = fill From 247c2461c55c085ead80480a1c5ab1fc2eaae4d8 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 10 Mar 2017 09:07:17 -0600 Subject: [PATCH 7/9] Fix bugs in Material.remove_nuclide and Material.remove_element --- openmc/material.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/openmc/material.py b/openmc/material.py index 7ffa899a19..cd19225364 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -464,16 +464,16 @@ class Material(object): Nuclide to remove """ + cv.check_type('nuclide', nuclide, string_types + (openmc.Nuclide,)) - if not isinstance(nuclide, openmc.Nuclide): - msg = 'Unable to remove a Nuclide "{}" in Material ID="{}" ' \ - 'since it is not a Nuclide'.format(self._id, nuclide) - raise ValueError(msg) + if isinstance(nuclide, string_types): + nuclide = openmc.Nuclide(nuclide) # If the Material contains the Nuclide, delete it for nuc in self._nuclides: - if nuclide == nuc: + if nuclide == nuc[0]: self._nuclides.remove(nuc) + break def add_macroscopic(self, macroscopic): """Add a macroscopic to the material. This will also set the @@ -625,15 +625,14 @@ class Material(object): Element to remove """ + cv.check_type('element', element, string_types + (openmc.Element,)) - if not isinstance(element, openmc.Element): - msg = 'Unable to remove "{}" in Material ID="{}" ' \ - 'since it is not an Element'.format(self.id, element) - raise ValueError(msg) + if isinstance(element, string_types): + element = openmc.Element(element) # If the Material contains the Element, delete it for elm in self._elements: - if element == elm: + if element == elm[0]: self._elements.remove(elm) def add_s_alpha_beta(self, name): From f342b2a93dc388d392f863ef13fbf7d955eb72a5 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 10 Mar 2017 09:54:58 -0600 Subject: [PATCH 8/9] Change 'center' argument of Universe.plot to 'origin' --- docs/source/usersguide/input.rst | 7 +++---- openmc/universe.py | 36 ++++++++++++++++---------------- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/docs/source/usersguide/input.rst b/docs/source/usersguide/input.rst index 4ce714b939..1326b71d8b 100644 --- a/docs/source/usersguide/input.rst +++ b/docs/source/usersguide/input.rst @@ -2051,10 +2051,9 @@ sub-elements: *Default*: "plot" :color_by: - Keyword for plot coloring. This can only be either "cell" or "material", - which colors regions by cells and materials, respectively. For voxel plots, - this determines which id (cell or material) is associated with each - position. + Keyword for plot coloring. This can be either "cell" or "material", which + colors regions by cells and materials, respectively. For voxel plots, this + determines which id (cell or material) is associated with each position. *Default*: "cell" diff --git a/openmc/universe.py b/openmc/universe.py index 0d5b8395a5..12bef012b9 100644 --- a/openmc/universe.py +++ b/openmc/universe.py @@ -223,15 +223,15 @@ class Universe(object): return [self, cell] + cell.fill.find(p) return [] - def plot(self, center=(0., 0., 0.), width=(1., 1.), pixels=(200, 200), + def plot(self, origin=(0., 0., 0.), width=(1., 1.), pixels=(200, 200), basis='xy', color_by='cell', colors=None, filename=None, seed=None, **kwargs): """Display a slice plot of the universe. Parameters ---------- - center : Iterable of float - Coordinates at the center of the plot + origin : Iterable of float + Coordinates at the origin of the plot width : Iterable of float Width of the plot in each basis direction pixels : Iterable of int @@ -287,23 +287,23 @@ class Universe(object): colors[obj] = list(color) + [1.0] if basis == 'xy': - x_min = center[0] - 0.5*width[0] - x_max = center[0] + 0.5*width[0] - y_min = center[1] - 0.5*width[1] - y_max = center[1] + 0.5*width[1] + x_min = origin[0] - 0.5*width[0] + x_max = origin[0] + 0.5*width[0] + y_min = origin[1] - 0.5*width[1] + y_max = origin[1] + 0.5*width[1] elif basis == 'yz': # The x-axis will correspond to physical y and the y-axis will # correspond to physical z - x_min = center[1] - 0.5*width[0] - x_max = center[1] + 0.5*width[0] - y_min = center[2] - 0.5*width[1] - y_max = center[2] + 0.5*width[1] + x_min = origin[1] - 0.5*width[0] + x_max = origin[1] + 0.5*width[0] + y_min = origin[2] - 0.5*width[1] + y_max = origin[2] + 0.5*width[1] elif basis == 'xz': # The y-axis will correspond to physical z - x_min = center[0] - 0.5*width[0] - x_max = center[0] + 0.5*width[0] - y_min = center[2] - 0.5*width[1] - y_max = center[2] + 0.5*width[1] + x_min = origin[0] - 0.5*width[0] + x_max = origin[0] + 0.5*width[0] + y_min = origin[2] - 0.5*width[1] + y_max = origin[2] + 0.5*width[1] # Determine locations to determine cells at x_coords = np.linspace(x_min, x_max, pixels[0], endpoint=False) + \ @@ -317,11 +317,11 @@ class Universe(object): for i, x in enumerate(x_coords): for j, y in enumerate(y_coords): if basis == 'xy': - path = self.find((x, y, center[2])) + path = self.find((x, y, origin[2])) elif basis == 'yz': - path = self.find((center[0], x, y)) + path = self.find((origin[0], x, y)) elif basis == 'xz': - path = self.find((x, center[1], y)) + path = self.find((x, origin[1], y)) if len(path) > 0: try: From d2c7486cfc14ef86d45dc8bf1808540d59a372a6 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 10 Mar 2017 10:43:01 -0600 Subject: [PATCH 9/9] Address @wbinventor comments on #826 --- openmc/cell.py | 21 +++++++++++++-------- openmc/executor.py | 4 ++++ openmc/plots.py | 8 ++++---- 3 files changed, 21 insertions(+), 12 deletions(-) diff --git a/openmc/cell.py b/openmc/cell.py index e83d01dcd7..b578702bba 100644 --- a/openmc/cell.py +++ b/openmc/cell.py @@ -99,6 +99,7 @@ class Cell(object): self.fill = fill self.region = region self._rotation = None + self._rotation_matrix = None self._temperature = None self._translation = None self._paths = [] @@ -196,14 +197,7 @@ class Cell(object): @property def rotation_matrix(self): - if self.rotation is not None: - phi, theta, psi = self.rotation*(-pi/180.) - c3, s3 = cos(phi), sin(phi) - c2, s2 = cos(theta), sin(theta) - c1, s1 = cos(psi), sin(psi) - return np.array([[c1*c2, c1*s2*s3 - c3*s1, s1*s3 + c1*c3*s2], - [c2*s1, c1*c3 + s1*s2*s3, c3*s1*s2 - c1*s3], - [-s2, c2*s3, c2*c3]]) + return self._rotation_matrix @property def temperature(self): @@ -288,6 +282,17 @@ class Cell(object): cv.check_length('cell rotation', rotation, 3) self._rotation = np.asarray(rotation) + # Save rotation matrix -- the reason we do this instead of having it be + # automatically calculated when the rotation_matrix property is accessed + # is so that plotting on a rotated geometry can be done faster. + phi, theta, psi = self.rotation*(-pi/180.) + c3, s3 = cos(phi), sin(phi) + c2, s2 = cos(theta), sin(theta) + c1, s1 = cos(psi), sin(psi) + return np.array([[c1*c2, c1*s2*s3 - c3*s1, s1*s3 + c1*c3*s2], + [c2*s1, c1*c3 + s1*s2*s3, c3*s1*s2 - c1*s3], + [-s2, c2*s3, c2*c3]]) + @translation.setter def translation(self, translation): cv.check_type('cell translation', translation, Iterable, Real) diff --git a/openmc/executor.py b/openmc/executor.py index 5181535128..51215e8b74 100644 --- a/openmc/executor.py +++ b/openmc/executor.py @@ -48,6 +48,10 @@ def plot_geometry(output=True, openmc_exec='openmc', cwd='.'): def plot_inline(plots, openmc_exec='openmc', cwd='.', convert_exec='convert'): """Display plots inline in a Jupyter notebook. + This function requires that you have a program installed to convert PPM + files to PNG files. Typically, that would be `ImageMagick + `_ which includes a `convert` command. + Parameters ---------- plots : Iterable of openmc.Plot diff --git a/openmc/plots.py b/openmc/plots.py index d95bc59058..2ba5d9758e 100644 --- a/openmc/plots.py +++ b/openmc/plots.py @@ -483,8 +483,8 @@ class Plot(object): 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 at what z value is used in - the origin. + the basis is 'xy', this would indicate the z value used in the + origin. """ cv.check_type('geometry', geometry, openmc.Geometry) @@ -565,7 +565,7 @@ class Plot(object): seed : int The random number seed used to generate the color scheme alpha : float - The value to apply in alpha compisiting + 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 @@ -794,7 +794,7 @@ class Plots(cv.CheckedList): seed : int The random number seed used to generate the color scheme alpha : float - The value to apply in alpha compisiting + 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