diff --git a/docs/source/io_formats/settings.rst b/docs/source/io_formats/settings.rst index 3b26eb84b..e9bb5ef53 100644 --- a/docs/source/io_formats/settings.rst +++ b/docs/source/io_formats/settings.rst @@ -220,11 +220,6 @@ The ```` element determines what output files should be written to disk during the run. The sub-elements are described below, where "true" will write out the file and "false" will not. - :cross_sections: - Writes out an ASCII summary file of the cross sections that were read in. - - *Default*: false - :summary: Writes out an HDF5 summary file describing all of the user input files that were read in. @@ -239,15 +234,11 @@ out the file and "false" will not. .. note:: The tally results will always be written to a binary/HDF5 state point file. -------------------------- -```` Element -------------------------- + :path: + Absolute or relative path where all output files should be written to. The + specified path must exist or else OpenMC will abort. -The ```` element specifies an absolute or relative path where all -output files should be written to. The specified path must exist or else OpenMC -will abort. - - *Default*: Current working directory + *Default*: Current working directory ----------------------- ```` Element diff --git a/docs/source/pythonapi/index.rst b/docs/source/pythonapi/index.rst index 700a6e82e..41f190661 100644 --- a/docs/source/pythonapi/index.rst +++ b/docs/source/pythonapi/index.rst @@ -200,6 +200,8 @@ Various classes may be created when performing tally slicing and/or arithmetic: openmc.arithmetic.AggregateNuclide openmc.arithmetic.AggregateFilter +.. _pythonapi_stats: + --------------------------------- :mod:`openmc.stats` -- Statistics --------------------------------- diff --git a/docs/source/usersguide/basics.rst b/docs/source/usersguide/basics.rst index 5a2772da9..50d150573 100644 --- a/docs/source/usersguide/basics.rst +++ b/docs/source/usersguide/basics.rst @@ -156,6 +156,8 @@ Serpent. In the Python API, integer IDs can be assigned but it is not strictly required. When IDs are not explicitly assigned to instances of the OpenMC Python classes, they will be automatically assigned. +.. _result_files: + ----------------------------- Viewing and Analyzing Results ----------------------------- diff --git a/docs/source/usersguide/index.rst b/docs/source/usersguide/index.rst index a3f23dd86..38d04edca 100644 --- a/docs/source/usersguide/index.rst +++ b/docs/source/usersguide/index.rst @@ -18,6 +18,7 @@ essential aspects of using OpenMC to perform simulations. geometry settings tallies + plots scripts processing troubleshoot diff --git a/docs/source/usersguide/plots.rst b/docs/source/usersguide/plots.rst new file mode 100644 index 000000000..e969d4584 --- /dev/null +++ b/docs/source/usersguide/plots.rst @@ -0,0 +1,125 @@ +.. _usersguide_plots: + +====================== +Geometry Visualization +====================== + +OpenMC is capable of producing two-dimensional slice plots of a geometry as well +as three-dimensional voxel plots using the geometry plotting :ref:`run mode +` is a geometry plotting mode. The geometry plotting mode +relies on the presence of a :ref:`plots.xml ` file that indicates what +plots should be created. To create this file, one needs to create one or more +:class:`openmc.Plot` instances, add them to a :class:`openmc.Plots` collection, +and then use the :class:`Plots.export_to_xml` method to write the ``plots.xml`` +file. + +----------- +Slice Plots +----------- + +.. image:: ../_images/atr.png + :width: 300px + +By default, when an instance of :class:`openmc.Plot` is created, it indicates +that a 2D slice plot should be made. You can specify the origin of the plot +(:attr:`Plot.origin`), the width of the plot in each direction +(:attr:`Plot.width`), the number of pixels to use in each direction +(:attr:`Plot.pixels`), and the basis directions for the plot. For example, to +create a x-z plot centered at (5.0, 2.0, 3.0) with a width of (50., 50.) and +400x400 pixels:: + + plot = openmc.Plot() + plot.basis = 'yz' + plot.origin = (5.0, 2.0, 3.0) + plot.width = (50., 50.) + plot.pixels = (400, 400) + +By default, a unique color will be assigned to each cell in the geometry. If you +want your plot to be colored by material instead, change the +:attr:`Plot.color_by` attribute:: + + plot.color_by = 'material' + +If you don't like the random colors assigned, you can also indicate that +particular cells/materials should be given colors of your choosing:: + + plot.colors = { + water: 'blue', + clad: 'black' + } + + # ..or.. + plot.colors = { + water: (0, 0, 255), + clad: (0, 0, 0) + } + +Note that colors can be given as RGB tuples or by a string indicating a valid +`SVG color `_. + +When you're done creating your :class:`openmc.Plot` instances, you need to then +assign them to a :class:`openmc.Plots` collection and export it to XML:: + + plots = openmc.Plots([plot1, plot2, plot3]) + plots.export_to_xml() + + # ..or.. + plots = openmc.Plots() + plots.append(plot1) + plots += [plot2, plot3] + plots.export_to_xml() + +To actually generate the plots, run the :func:`openmc.plot_geometry` +function. Alternatively, run the :ref:`scripts_openmc` executable with the +``--plot`` command-line flag. When that has finished, you will have one or more +``.ppm`` files, i.e., `portable pixmap +`_ files. On some Linux +distributions, these ``.ppm`` files are natively viewable. If you find that +you're unable to open them on your system (or you don't like the fact that they +are not compressed), you may want to consider converting them to another format. +This is easily accomplished with the ``convert`` command available on most Linux +distributions as part of the `ImageMagick +`_ package. (On Debian +derivatives: ``sudo apt install imagemagick``). Images are then converted like: + +.. code-block:: sh + + convert myplot.ppm myplot.png + +Alternatively, if you're working with in a `Jupyter `_ +Notebook or QtConsole, you can use the :func:`openmc.plot_inline` to run OpenMC +in plotting mode and display the resulting plot within the notebook. + +.. _usersguide_voxel: + +----------- +Voxel Plots +----------- + +.. image:: ../_images/3dba.png + :width: 200px + +The :class:`openmc.Plot` class can also be told to generate a 3D voxel plot +instead of a 2D slice plot. Simply change the :attr:`Plot.type` attribute to +'voxel'. In this case, the :attr:`Plot.width` and :attr:`Plot.pixels` attributes +should be three items long, e.g.:: + + vox_plot = openmc.Plot() + vox_plot.type = 'voxel' + vox_plot.width = (100., 100., 50.) + vox_plot.pixels = (400, 400, 200) + +The voxel plot data is written to an :ref:`HDF5 file `. The voxel file +can subsequently be converted into a standard mesh format that can be viewed in +`ParaView `_, `VisIt +`_, etc. This typically +will compress the size of the file significantly. The provided +:ref:`scripts_voxel` script can convert the HDF5 voxel file to VTK or SILO +formats. Once processed into a standard 3D file format, colors and masks can be +defined using the stored ID numbers to better explore the geometry. The process +for doing this will depend on the 3D viewer, but should be straightforward. + +.. note:: 3D voxel plotting can be very computer intensive for the viewing + program (Visit, ParaView, etc.) if the number of voxels is large (>10 + million or so). Thus if you want an accurate picture that renders + smoothly, consider using only one voxel in a certain direction. diff --git a/docs/source/usersguide/processing.rst b/docs/source/usersguide/processing.rst index 93a17a7b8..426b3c425 100644 --- a/docs/source/usersguide/processing.rst +++ b/docs/source/usersguide/processing.rst @@ -34,7 +34,7 @@ Geometry Visualization Geometry plotting is carried out by creating a plots.xml, specifying plots, and running OpenMC with the --plot or -p command-line option (See -:ref:`usersguide_plotting`). +:ref:`scripts_openmc`). Plotting in 2D -------------- diff --git a/docs/source/usersguide/scripts.rst b/docs/source/usersguide/scripts.rst index 411409c99..d1c37fa16 100644 --- a/docs/source/usersguide/scripts.rst +++ b/docs/source/usersguide/scripts.rst @@ -30,7 +30,7 @@ Alternatively, you could run from any directory: Note that in the latter case, any output files will be placed in the present working directory which may be different from ``/home/username/somemodel``. If you're using the Python API, :func:`openmc.run` is equivalent to running -``openmc`` from the command line. OpenMC accepts the following command line +``openmc`` from the command line. ``openmc`` accepts the following command line flags: -c, --volume Run in stochastic volume calculation mode @@ -95,10 +95,8 @@ Input files can be checked before executing OpenMC using the ``openmc-validate-xml`` script which is installed alongside the Python API. Two command line arguments can be set when running ``openmc-validate-xml``: -* ``-i``, ``--input-path`` - Location of OpenMC input files. - *Default*: current working directory -* ``-r``, ``--relaxng-path`` - Location of OpenMC RelaxNG files. - *Default*: None +-i, --input-path Location of OpenMC input files. +-r, --relaxng-path Location of OpenMC RelaxNG files If the RelaxNG path is not set, the script will search for these files because it expects that the user is either running the script located in the install @@ -120,3 +118,22 @@ Message Description --------------------------- ``openmc-voxel-to-silovtk`` --------------------------- + +When OpenMC generates :ref:`voxel plots `, they are in an +:ref:`HDF5 format ` that is not terribly useful by itself. The +``openmc-voxel-to-silovtk`` script converts a voxel HDF5 file to `VTK +`_ or `SILO +`_ file. For VTK, you need +to have the VTK Python bindings installed. For SILO, you need to have `silomesh +`_ installed. To convert a voxel file, +simply provide the path to the file: + +.. code-block:: sh + + openmc-voxel-to-silovtk voxel_1.h5 + +The ``openmc-voxel-to-silovtk`` script also takes the following optional +command-line arguments: + +-o, --output Path to output VTK or SILO file +-s, --silo Flag to convert to SILO instead of VTK diff --git a/docs/source/usersguide/settings.rst b/docs/source/usersguide/settings.rst index c88c32491..ea099e8a6 100644 --- a/docs/source/usersguide/settings.rst +++ b/docs/source/usersguide/settings.rst @@ -3,3 +3,221 @@ ================== Execution Settings ================== + +.. currentmodule:: openmc + +Once you have created the materials and geometry for your simulation, the last +step to have a complete model is to specify execution settings through the +:class:`openmc.Settings` class. At a minimum, you need to specify a :ref:`source +distribution ` and :ref:`how many particles to run +`. Many other execution settings can be set using the +:class:`openmc.Settings` object, but they are generally optional. + +.. _usersguide_run_modes: + +--------- +Run Modes +--------- + +The :attr:`Settings.run_mode` attribute controls what run mode is used when +:ref:`scripts_openmc` is executed. There are five different run modes that can +be specified: + +'eigenvalue' + Runs a :math:`k` eigenvalue simulation. See :ref:`methods_eigenvalue` for a + full description of eigenvalue calculations. In this mode, the + :attr:`Settings.source` specifies a starting source that is only used for the + first fission generation. + +'fixed source' + Runs a fixed-source calculation with a specified external source, specified in + the :attr:`Settings.source` attribute. + +'volume' + Runs a stochastic volume calculation. + +'plot' + Generates slice or voxel plots (see :ref:`usersguide_plots`). + +'particle_restart' + Simulate a single source particle using a particle restart file. + + +So, for example, to specify that OpenMC should be run in fixed source mode, you +would need to instantiate a :class:`openmc.Settings` object and assign the +:attr:`Settings.run_mode` attribute:: + + settings = openmc.Settings() + settings.run_mode = 'fixed source' + +.. _usersguide_particles: + +------------------- +Number of Particles +------------------- + +For a fixed source simulation, the total number of source particle histories +simulated is broken up into a number of *batches*, each corresponding to a +:ref:`realization ` of the tally random variables. Thus, you +need to specify both the number of batches (:attr:`Settings.batches`) as well as +the number of particles per batch (:attr:`Settings.particles`). + +For a :math:`k` eigenvalue simulation, particles are grouped into *fission +generations*, as described in :ref:`methods_eigenvalue`. Successive fission +generations can be combined into a batch for statistical purposes. By default, a +batch will consist of only a single fission generation, but this can be changed +with the :attr:`Settings.generations_per_batch` attribute. For problems with a +high dominance ratio, using multiple generations per batch can help reduce +underprediction of variance, thereby leading to more accurate confidence +intervals. Tallies should not be scored to until the source distribution +converges, as described in :ref:`method-successive-generations`, which may take +many generations. To specify the number of batches that should be discarded +before tallies begin to accumulate, use the :attr:`Settings.inactive` attribute. + +The following example shows how one would simulate 10000 particles per +generation, using 10 generations per batch, 150 total batches, and discarding 5 +batches. Thus, a total of 145 active batches (or 1450 generations) will be used +for accumulating tallies. + +:: + + settings.particles = 10000 + settings.generations_per_batch = 10 + settings.batches = 150 + settings.inactive = 5 + +.. _usersguide_source: + +----------------------------- +External Source Distributions +----------------------------- + +External source distributions can be specified through the +:attr:`Settings.source` attribute. If you have a single external source, you can +create an instance of :class:`openmc.Source` and use it to set the +:attr:`Settings.source` attribute. If you have multiple external sources with +varying source strengths, :attr:`Settings.source` should be set to a list of +:class:`openmc.Source` objects. + +The :class:`openmc.Source` class has three main attributes that one can set: +:attr:`Source.space`, which defines the spatial distribution, +:attr:`Source.angle`, which defines the angular distribution, and +:attr:`Source.energy`, which defines the energy distribution. + +The spatial distribution can be set equal to a sub-class of +:class:`openmc.stats.Spatial`; common choices are :class:`openmc.stats.Point` or +:class:`openmc.stats.Box`. To independently specify distributions in the x, y, +and z coordinates, you can use :class:`openmc.stats.CartesianIndependent`. + +The angular distribution can be set equal to a sub-class of +:class:`openmc.stats.UnitSphere` such as :class:`openmc.stats.Isotropic`, +:class:`openmc.stats.Monodirectional`, or +:class:`openmc.stats.PolarAzimuthal`. By default, if no angular distribution is +specified, an isotropic angular distribution is used. + +The energy distribution can be set equal to any univariate probability +distribution. This could be a probability mass function +(:class:`openmc.stats.Discrete`), a Watt fission spectrum +(:class:`openmc.stats.Watt`), or a tabular distribution +(:class:`openmc.stats.Tabular`). By default, if no energy distribution is +specified, a Watt fission spectrum with :math:`a` = 0.988 MeV and :math:`b` = +2.249 MeV :sup:`-1` is used. + +As an example, to create an isotropic, 10 MeV monoenergetic source uniformly +distributed over a cube centered at the origin with an edge length of 10 cm, one +would run:: + + source = openmc.Source() + source.space = openmc.stats.Box((-5, -5, -5), (5, 5, 5)) + source.angle = openmc.stats.Isotropic() + source.energy = openmc.stats.Discrete([10.0e6], [1.0]) + settings.source = source + +The :class:`openmc.Source` class also has a :attr:`Source.strength` attribute +that indicates the relative strength of a source distribution if multiple are +used. For example, to create two sources, one that should be sampled 70% of the +time and another that should be sampled 30% of the time:: + + src1 = openmc.Source() + src1.strength = 0.7 + ... + + src2 = openmc.Source() + src2.strength = 0.3 + ... + + settings.source = [src1, src2] + +For a full list of all classes related to statistical distributions, see +:ref:`pythonapi_stats`. + + +--------------- +Shannon Entropy +--------------- + +To assess convergence of the source distribution, the scalar Shannon entropy +metric is often used in Monte Carlo codes. OpenMC also allows you to calculate +Shannon entropy at each generation over a specified mesh, created using the +:class:`openmc.Mesh` class. After instantiating a :class:`Mesh`, you need to +specify the lower-left coordinates of the mesh (:attr:`Mesh.lower_left`), the +number of mesh cells in each direction (:attr:`Mesh.dimension`) and either the +upper-right coordinates of the mesh (:attr:`Mesh.upper_right`) or the width of +each mesh cell (:attr:`Mesh.width`). Once you have a mesh, simply assign it to +the :attr:`Settings.entropy_mesh` attribute. + +:: + + entropy_mesh = openmc.Mesh() + entropy_mesh.lower_left = (-50, -50, -25) + entropy_mesh.upper_right = (50, 50, 25) + entropy_mesh.dimension = (8, 8, 8) + + settings.entropy_mesh = entropy_mesh + +If you're unsure of what bounds to use for the entropy mesh, you can try getting +a bounding box for the entire geometry using the :attr:`Geometry.bounding_box` +property:: + + geom = openmc.Geometry() + ... + m = openmc.Mesh() + m.lower_left, m.upper_right = geom.bounding_box + m.dimension = (8, 8, 8) + + settings.entropy_mesh = m + +-------------------------- +Generation of Output Files +-------------------------- + +A number of attributes of the :class:`openmc.Settings` class can be used to +control what files are output and how often. First, there is the +:attr:`Settings.output` attribute which takes a dictionary having keys +'summary', 'tallies', and 'path'. The first two keys controls whether a +``summary.h5`` and ``tallies.out`` file are written, respectively (see +:ref:`result_files` for a description of those files). By default, output files +are written to the current working directory; this can be changed by setting the +'path' key. For example, if you want to disable the ``tallies.out`` file and +write the ``summary.h5`` to a directory called 'results', you'd specify the +:attr:`Settings.output` dictionary as:: + + settings.output = { + 'tallies': False, + 'path': 'results' + } + +Generation of statepoint and source files is handled separately through the +:attr:`Settings.statepoint` and :attr:`Settings.sourcepoint` attributes. Both of +those attributes expect dictionaries and have a 'batches' key which indicates at +which batches statepoints and source files should be written. Note that by +default, the source is written as part of the statepoint file; this behavior can +be changed by the 'separate' and 'write' keys of the +:attr:`Settings.sourcepoint` dictionary, the first of which indicates whether +the source should be written to a separate file and the second of which +indicates whether the source should be written at all. + +As an example, to write a statepoint file every five batches:: + + settings.batches = n + settings.statepoint = {'batches': range(5, n + 5, 5)} diff --git a/openmc/settings.py b/openmc/settings.py index 3a5bd63a2..bb04defcd 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -71,11 +71,12 @@ class Settings(object): Indicate that all user-defined and global tallies should not be reduced across processes in a parallel calculation. output : dict - Dictionary indicating what files to output. Valid keys are 'summary', - 'cross_sections', 'tallies', and 'distribmats'. Values corresponding to - each key should be given as a boolean value. - output_path : str - Path to write output to + Dictionary indicating what files to output. Acceptable keys are: + + :path: String indicating a directory where output files should be + written + :summary: Whether the 'summary.h5' file should be written (bool) + :tallies: Whether the 'tallies.out' file should be written (bool) particles : int Number of particles per generation ptables : bool @@ -193,7 +194,6 @@ class Settings(object): self._trigger_batch_interval = None self._output = None - self._output_path = None # Output options self._statepoint = {} @@ -315,10 +315,6 @@ class Settings(object): def output(self): return self._output - @property - def output_path(self): - return self._output_path - @property def sourcepoint(self): return self._sourcepoint @@ -479,30 +475,15 @@ class Settings(object): @output.setter def output(self, output): - if not isinstance(output, dict): - msg = 'Unable to set output to "{0}" which is not a Python ' \ - 'dictionary of string keys and boolean values'.format(output) - raise ValueError(msg) - - for element in output: - keys = ['summary', 'cross_sections', 'tallies', 'distribmats'] - if element not in keys: - msg = 'Unable to set output to "{0}" which is unsupported by ' \ - 'OpenMC'.format(element) - raise ValueError(msg) - - if not isinstance(output[element], (bool, np.bool)): - msg = 'Unable to set output for "{0}" to a non-boolean ' \ - 'value "{1}"'.format(element, output[element]) - raise ValueError(msg) - + cv.check_type('output', output, Mapping) + for key, value in output.items(): + cv.check_value('output key', key, ('summary', 'tallies', 'path')) + if key in ('summary', 'tallies'): + cv.check_type("output['{}']".format(key), value, bool) + else: + cv.check_type("output['path']", value, string_types) self._output = output - @output_path.setter - def output_path(self, output_path): - cv.check_type('output path', output_path, string_types) - self._output_path = output_path - @verbosity.setter def verbosity(self, verbosity): cv.check_type('verbosity', verbosity, Integral) @@ -876,13 +857,12 @@ class Settings(object): if self._output is not None: element = ET.SubElement(root, "output") - for key in self._output: + for key, value in self._output.items(): subelement = ET.SubElement(element, key) - subelement.text = str(self._output[key]).lower() - - if self._output_path is not None: - element = ET.SubElement(root, "output_path") - element.text = self._output_path + if key in ('summary', 'tallies'): + subelement.text = str(value).lower() + else: + subelement.text = value def _create_verbosity_subelement(self, root): if self._verbosity is not None: diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 9bf78324d..bf981e9f5 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -183,14 +183,6 @@ contains max_order = 0 end if - ! Set output directory if a path has been specified on the - ! element - if (check_for_node(root, "output_path")) then - call get_node_value(root, "output_path", path_output) - if (.not. ends_with(path_output, "/")) & - path_output = trim(path_output) // "/" - end if - ! Check for a trigger node and get trigger information if (check_for_node(root, "trigger")) then node_trigger = root % child("trigger") @@ -308,12 +300,30 @@ contains call get_node_list(root, "source", node_source_list) n = size(node_source_list) - if (run_mode == MODE_EIGENVALUE .or. run_mode == MODE_FIXEDSOURCE) then - if (n == 0) call fatal_error("No source specified in settings XML file.") - end if + if (n == 0) then + ! Default source is isotropic point source at origin with Watt spectrum + allocate(external_source(1)) + external_source % strength = ONE - ! Allocate array for sources - allocate(external_source(n)) + allocate(SpatialPoint :: external_source(1) % space) + select type (space => external_source(1) % space) + type is (SpatialPoint) + space % xyz(:) = [ZERO, ZERO, ZERO] + end select + + allocate(Isotropic :: external_source(1) % angle) + external_source(1) % angle % reference_uvw(:) = [ZERO, ZERO, ONE] + + allocate(Watt :: external_source(1) % energy) + select type(energy => external_source(1) % energy) + type is (Watt) + energy % a = 0.988e6_8 + energy % b = 2.249e-6_8 + end select + else + ! Allocate array for sources + allocate(external_source(n)) + end if ! Read each source do i = 1, n @@ -451,8 +461,12 @@ contains end select else - call fatal_error("No spatial distribution specified for external & - &source.") + ! If no spatial distribution specified, make it a point source + allocate(SpatialPoint :: external_source(i) % space) + select type (space => external_source(i) % space) + type is (SpatialPoint) + space % xyz(:) = [ZERO, ZERO, ZERO] + end select end if ! Determine external source angular distribution @@ -839,6 +853,13 @@ contains if (check_for_node(node_output, "tallies")) then call get_node_value(node_output, "tallies", output_tallies) end if + + ! Set output directory if a path has been specified + if (check_for_node(node_output, "path")) then + call get_node_value(node_output, "path", path_output) + if (.not. ends_with(path_output, "/")) & + path_output = trim(path_output) // "/" + end if end if ! Check for cmfd run diff --git a/src/relaxng/settings.rnc b/src/relaxng/settings.rnc index 2043feb16..a0f100cd8 100644 --- a/src/relaxng/settings.rnc +++ b/src/relaxng/settings.rnc @@ -38,13 +38,10 @@ element settings { element output { (element summary { xsd:boolean } | attribute summary { xsd:boolean })? & - (element cross_sections { xsd:boolean } | - attribute cross_sections { xsd:boolean })? & - (element tallies { xsd:boolean } | attribute tallies { xsd:boolean })? + (element tallies { xsd:boolean } | attribute tallies { xsd:boolean })? & + (element path { xsd:string } | attribute path { xsd:string })? }? & - element output_path { xsd:string { maxLength = "255" } }? & - element particles { xsd:positiveInteger }? & element ptables { xsd:boolean }? & diff --git a/src/relaxng/settings.rng b/src/relaxng/settings.rng index f0c7ddbbf..2a9a170f9 100644 --- a/src/relaxng/settings.rng +++ b/src/relaxng/settings.rng @@ -177,16 +177,6 @@ - - - - - - - - - - @@ -197,16 +187,19 @@ + + + + + + + + + + - - - - 255 - - -