diff --git a/docs/source/pythonapi/examples/mgxs-part-iv.ipynb b/docs/source/pythonapi/examples/mgxs-part-iv.ipynb new file mode 100644 index 0000000000..4b73cf3caa --- /dev/null +++ b/docs/source/pythonapi/examples/mgxs-part-iv.ipynb @@ -0,0 +1,1461 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This Notebook illustrates the use of the openmc.mgxs.Library class specifically for application in OpenMC's multi-group mode. This example notebook follows the same process as was done in MGXS Part III, but instead uses OpenMC as the multi-group solver. This Notebook illustrates the following features:\n", + "\n", + " - Calculation of multi-group cross sections for a fuel assembly\n", + " - Automated creation and storage of MGXS with openmc.mgxs.Library\n", + " - Steady-state pin-by-pin fission rates comparison between continuous-energy and multi-group OpenMC.\n", + "\n", + "Note: This Notebook illustrates the use of Pandas DataFrames to containerize multi-group cross section data. We recommend using Pandas >v0.15.0 or later since OpenMC's Python API leverages the multi-indexing feature included in the most recent releases of Pandas.\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Generate Input Files" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import math\n", + "import pickle\n", + "\n", + "from IPython.display import Image\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "import os\n", + "\n", + "import openmc\n", + "import openmc.mgxs\n", + "\n", + "%matplotlib inline" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "\n", + "First we need to define materials that will be used in the problem. Before defining a material, we must create nuclides that are used in the material." + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# Instantiate some Nuclides\n", + "h1 = openmc.Nuclide('H-1')\n", + "b10 = openmc.Nuclide('B-10')\n", + "o16 = openmc.Nuclide('O-16')\n", + "u235 = openmc.Nuclide('U-235')\n", + "u238 = openmc.Nuclide('U-238')\n", + "zr90 = openmc.Nuclide('Zr-90')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With the nuclides we defined, we will now create three materials for the fuel, water, and cladding of the fuel pins." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# 1.6 enriched fuel\n", + "fuel = openmc.Material(name='1.6% Fuel')\n", + "fuel.set_density('g/cm3', 10.31341)\n", + "fuel.add_nuclide(u235, 3.7503e-4)\n", + "fuel.add_nuclide(u238, 2.2625e-2)\n", + "fuel.add_nuclide(o16, 4.6007e-2)\n", + "\n", + "# zircaloy\n", + "zircaloy = openmc.Material(name='Zircaloy')\n", + "zircaloy.set_density('g/cm3', 6.55)\n", + "zircaloy.add_nuclide(zr90, 7.2758e-3)\n", + "\n", + "# borated water\n", + "water = openmc.Material(name='Borated Water')\n", + "water.set_density('g/cm3', 0.740582)\n", + "water.add_nuclide(h1, 4.9457e-2)\n", + "water.add_nuclide(o16, 2.4732e-2)\n", + "water.add_nuclide(b10, 8.0042e-6)\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With our three materials, we can now create a Materials object that can be exported to an actual XML file." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# Instantiate a Materials object\n", + "materials_file = openmc.Materials((fuel, zircaloy, water))\n", + "materials_file.default_xs = '71c'\n", + "\n", + "# Export to \"materials.xml\"\n", + "materials_file.export_to_xml()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now let's move on to the geometry. This problem will be a square array of fuel pins and control rod guide tubes for which we can use OpenMC's lattice/universe feature. The basic universe will have three regions for the fuel, the clad, and the surrounding coolant. The first step is to create the bounding surfaces for fuel and clad, as well as the outer bounding surfaces of the problem." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# Create cylinders for the fuel and clad\n", + "fuel_outer_radius = openmc.ZCylinder(x0=0.0, y0=0.0, R=0.39218)\n", + "clad_outer_radius = openmc.ZCylinder(x0=0.0, y0=0.0, R=0.45720)\n", + "\n", + "# Create boundary planes to surround the geometry\n", + "min_x = openmc.XPlane(x0=-10.71, boundary_type='reflective')\n", + "max_x = openmc.XPlane(x0=+10.71, boundary_type='reflective')\n", + "min_y = openmc.YPlane(y0=-10.71, boundary_type='reflective')\n", + "max_y = openmc.YPlane(y0=+10.71, boundary_type='reflective')\n", + "min_z = openmc.ZPlane(z0=-10., boundary_type='reflective')\n", + "max_z = openmc.ZPlane(z0=+10., boundary_type='reflective')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With the surfaces defined, we can now construct a fuel pin cell from cells that are defined by intersections of half-spaces created by the surfaces." + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# Create a Universe to encapsulate a fuel pin\n", + "fuel_pin_universe = openmc.Universe(name='1.6% Fuel Pin')\n", + "\n", + "# Create fuel Cell\n", + "fuel_cell = openmc.Cell(name='1.6% Fuel')\n", + "fuel_cell.fill = fuel\n", + "fuel_cell.region = -fuel_outer_radius\n", + "fuel_pin_universe.add_cell(fuel_cell)\n", + "\n", + "# Create a clad Cell\n", + "clad_cell = openmc.Cell(name='1.6% Clad')\n", + "clad_cell.fill = zircaloy\n", + "clad_cell.region = +fuel_outer_radius & -clad_outer_radius\n", + "fuel_pin_universe.add_cell(clad_cell)\n", + "\n", + "# Create a moderator Cell\n", + "moderator_cell = openmc.Cell(name='1.6% Moderator')\n", + "moderator_cell.fill = water\n", + "moderator_cell.region = +clad_outer_radius\n", + "fuel_pin_universe.add_cell(moderator_cell)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Likewise, we can construct a control rod guide tube with the same surfaces." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# Create a Universe to encapsulate a control rod guide tube\n", + "guide_tube_universe = openmc.Universe(name='Guide Tube')\n", + "\n", + "# Create guide tube Cell\n", + "guide_tube_cell = openmc.Cell(name='Guide Tube Water')\n", + "guide_tube_cell.fill = water\n", + "guide_tube_cell.region = -fuel_outer_radius\n", + "guide_tube_universe.add_cell(guide_tube_cell)\n", + "\n", + "# Create a clad Cell\n", + "clad_cell = openmc.Cell(name='Guide Clad')\n", + "clad_cell.fill = zircaloy\n", + "clad_cell.region = +fuel_outer_radius & -clad_outer_radius\n", + "guide_tube_universe.add_cell(clad_cell)\n", + "\n", + "# Create a moderator Cell\n", + "moderator_cell = openmc.Cell(name='Guide Tube Moderator')\n", + "moderator_cell.fill = water\n", + "moderator_cell.region = +clad_outer_radius\n", + "guide_tube_universe.add_cell(moderator_cell)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Using the pin cell universe, we can construct a 17x17 rectangular lattice with a 1.26 cm pitch." + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# Create fuel assembly Lattice\n", + "assembly = openmc.RectLattice(name='1.6% Fuel Assembly')\n", + "assembly.dimension = (17, 17)\n", + "assembly.pitch = (1.26, 1.26)\n", + "assembly.lower_left = [-1.26 * 17. / 2.0] * 2" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we create a NumPy array of fuel pin and guide tube universes for the lattice." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# Create array indices for guide tube locations in lattice\n", + "template_x = np.array([5, 8, 11, 3, 13, 2, 5, 8, 11, 14, 2, 5, 8,\n", + " 11, 14, 2, 5, 8, 11, 14, 3, 13, 5, 8, 11])\n", + "template_y = np.array([2, 2, 2, 3, 3, 5, 5, 5, 5, 5, 8, 8, 8, 8,\n", + " 8, 11, 11, 11, 11, 11, 13, 13, 14, 14, 14])\n", + "\n", + "# Initialize an empty 17x17 array of the lattice universes\n", + "universes = np.empty((17, 17), dtype=openmc.Universe)\n", + "\n", + "# Fill the array with the fuel pin and guide tube universes\n", + "universes[:,:] = fuel_pin_universe\n", + "universes[template_x, template_y] = guide_tube_universe\n", + "\n", + "# Store the array of universes in the lattice\n", + "assembly.universes = universes" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "OpenMC requires that there is a \"root\" universe. Let us create a root cell that is filled by the pin cell universe and then assign it to the root universe." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# Create root Cell\n", + "root_cell = openmc.Cell(name='root cell')\n", + "root_cell.fill = assembly\n", + "\n", + "# Add boundary planes\n", + "root_cell.region = +min_x & -max_x & +min_y & -max_y & +min_z & -max_z\n", + "\n", + "# Create root Universe\n", + "root_universe = openmc.Universe(name='root universe', universe_id=0)\n", + "root_universe.add_cell(root_cell)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We now must create a geometry that is assigned a root universe and export it to XML." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# Create Geometry and set root Universe\n", + "geometry = openmc.Geometry()\n", + "geometry.root_universe = root_universe\n", + "# Export to \"geometry.xml\"\n", + "geometry.export_to_xml()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With the geometry and materials finished, we now just need to define simulation parameters. In this case, we will use 10 inactive batches and 40 active batches each with 2500 particles." + ] + }, + { + "cell_type": "code", + "execution_count": 12, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# OpenMC simulation parameters\n", + "batches = 50\n", + "inactive = 10\n", + "particles = 5000\n", + "\n", + "# Instantiate a Settings object\n", + "settings_file = openmc.Settings()\n", + "settings_file.batches = batches\n", + "settings_file.inactive = inactive\n", + "settings_file.particles = particles\n", + "settings_file.output = {'tallies': False}\n", + "\n", + "# Create an initial uniform spatial source distribution over fissionable zones\n", + "bounds = [-10.71, -10.71, -10, 10.71, 10.71, 10.]\n", + "uniform_dist = openmc.stats.Box(bounds[:3], bounds[3:], only_fissionable=True)\n", + "settings_file.source = openmc.source.Source(space=uniform_dist)\n", + "\n", + "# Export to \"settings.xml\"\n", + "settings_file.export_to_xml()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Let us also create a Plots file that we can use to verify that our fuel assembly geometry was created successfully." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# Instantiate a Plot\n", + "plot = openmc.Plot()\n", + "plot.filename = 'materials-xy'\n", + "plot.origin = [0, 0, 0]\n", + "plot.pixels = [250, 250]\n", + "plot.width = [-10.71*2, -10.71*2]\n", + "plot.color = 'mat'\n", + "\n", + "# Instantiate a Plots object, add Plot, and export to \"plots.xml\"\n", + "plot_file = openmc.Plots([plot])\n", + "plot_file.export_to_xml()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "With the plots.xml file, we can now generate and view the plot. OpenMC outputs plots in .ppm format, which can be converted into a compressed format like .png with the convert utility." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Run openmc in plotting mode\n", + "openmc.plot_geometry(output=False)" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "image/png": "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\n", + "text/plain": [ + "" + ] + }, + "execution_count": 15, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Convert OpenMC's funky ppm to png\n", + "!convert materials-xy.ppm materials-xy.png\n", + "\n", + "# Display the materials plot inline\n", + "Image(filename='materials-xy.png')" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As we can see from the plot, we have a nice array of fuel and guide tube pin cells with fuel, cladding, and water!\n", + "\n", + "# Create an MGXS Library\n", + "\n", + "Now we are ready to generate multi-group cross sections! First, let's define a 2-group structure using the built-in EnergyGroups class." + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# Instantiate a 2-group EnergyGroups object\n", + "groups = openmc.mgxs.EnergyGroups()\n", + "groups.group_edges = np.array([0., 0.625e-6, 20.])" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we will instantiate an openmc.mgxs.Library for the energy groups with our the fuel assembly geometry." + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# Initialize an 2-group MGXS Library for OpenMOC\n", + "mgxs_lib = openmc.mgxs.Library(geometry)\n", + "mgxs_lib.energy_groups = groups" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now, we must specify to the Library which types of cross sections to compute. OpenMC's multi-group mode can accept isotropic flux-weighted cross sections or angle-dependent cross sections, as well as supporting anisotropic scattering represented by either Legendre polynomials, histogram, or tabular angular distributions. At this time the MGXS Library class only supports the generation of isotropic flux-weighted cross sections and P0 scattering, so that is what will be used for this example. Therefore, we will create the following multi-group cross sections needed to run an OpenMC simulation to verify the accuracy of our cross sections: \"transport\", \"absorption\", \"nu-fission\", '\"fission\", \"nu-scatter matrix\", \"scatter matrix\", and \"chi\".\n", + "\"scatter matrix\" is needed in addition to \"nu-scatter matrix\" because OpenMC's multi-group mode can treat scattering multiplication (i.e., (n,xn) reactions)) explicitly instead of adjusting the absorption cross section to maintain neutron balance, and using this explicit treatment would require tallying of both types of scattering matrices." + ] + }, + { + "cell_type": "code", + "execution_count": 18, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# Specify multi-group cross section types to compute\n", + "mgxs_lib.mgxs_types = ['total', 'absorption', 'nu-fission', 'fission',\n", + " 'nu-scatter matrix', 'scatter matrix', 'chi']" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we must specify the type of domain over which we would like the `Library` to compute multi-group cross sections. The domain type corresponds to the type of tally filter to be used in the tallies created to compute multi-group cross sections. At the present time, the `Library` supports \"material,\" \"cell,\" and \"universe\" domain types. In this simple example, we wish to compute multi-group cross sections only for each material andtherefore will use a \"material\" domain type.\n", + "\n", + "**Note:** By default, the `Library` class will instantiate `MGXS` objects for each and every domain (material, cell or universe) in the geometry of interest. However, one may specify a subset of these domains to the `Library.domains` property." + ] + }, + { + "cell_type": "code", + "execution_count": 19, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# Specify a \"cell\" domain type for the cross section tally filters\n", + "mgxs_lib.domain_type = \"material\"\n", + "\n", + "# Specify the cell domains over which to compute multi-group cross sections\n", + "mgxs_lib.domains = geometry.get_all_materials()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will instruct the library to not compute cross sections on a nuclide-by-nuclide basis, and instead to focus on generating material-specific macroscopic cross sections.\n", + "\n", + "**NOTE:** The default value of the `by_nuclide` parameter is `False`, so the following step is not necessary but is included for illustrative purposes." + ] + }, + { + "cell_type": "code", + "execution_count": 20, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# Do not compute cross sections on a nuclide-by-nuclide basis\n", + "mgxs_lib.by_nuclide = False" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we will set the scattering order that we wish to use. For this problem we will use P3 scattering." + ] + }, + { + "cell_type": "code", + "execution_count": 21, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/nelsonag/git/openmc/openmc/mgxs/library.py:320: RuntimeWarning: The P0 correction will be ignored since the scattering order 0 is greater than zero\n", + " warnings.warn(msg, RuntimeWarning)\n" + ] + } + ], + "source": [ + "# Set the Legendre order to 3 for P3 scattering\n", + "mgxs_lib.legendre_order = 3" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now that the `Library` has been setup, lets make sure it contains the types of cross sections which meet the needs of OpenMC's multi-group solver. Note that this step is done automatically when writing the Multi-Group Library file later in the process (as part of the `mgxs_lib.write_mg_library()`), but it is a good practice to also run this before spending all the time running OpenMC to generate the cross sections." + ] + }, + { + "cell_type": "code", + "execution_count": 22, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# Check the library - if no errors are raised, then the library is satisfactory.\n", + "mgxs_lib.check_library_for_openmc_mgxs()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Lastly, we use the `Library` to construct the tallies needed to compute all of the requested multi-group cross sections in each domain and nuclide." + ] + }, + { + "cell_type": "code", + "execution_count": 23, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# Construct all tallies needed for the multi-group cross section library\n", + "mgxs_lib.build_library()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The tallies can now be export to a \"tallies.xml\" input file for OpenMC.\n", + "\n", + "**NOTE:** At this point the `Library` has constructed nearly 100 distinct Tally objects. The overhead to tally in OpenMC scales as O(N) for N tallies, which can become a bottleneck for large tally datasets. To compensate for this, the Python API's `Tally`, `Filter` and `Tallies` classes allow for the smart merging of tallies when possible. The `Library` class supports this runtime optimization with the use of the optional `merge` parameter (`False` by default) for the `Library.add_to_tallies_file(...)` method, as shown below." + ] + }, + { + "cell_type": "code", + "execution_count": 24, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# Create a \"tallies.xml\" file for the MGXS Library\n", + "tallies_file = openmc.Tallies()\n", + "mgxs_lib.add_to_tallies_file(tallies_file, merge=True)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In addition, we instantiate a fission rate mesh tally to compare with the multi-group result." + ] + }, + { + "cell_type": "code", + "execution_count": 25, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# Instantiate a tally Mesh\n", + "mesh = openmc.Mesh()\n", + "mesh.type = 'regular'\n", + "mesh.dimension = [17, 17]\n", + "mesh.lower_left = [-10.71, -10.71]\n", + "mesh.upper_right = [+10.71, +10.71]\n", + "\n", + "# Instantiate tally Filter\n", + "mesh_filter = openmc.Filter()\n", + "mesh_filter.mesh = mesh\n", + "\n", + "# Instantiate the Tally\n", + "tally = openmc.Tally(name='mesh tally')\n", + "tally.filters = [mesh_filter]\n", + "tally.scores = ['fission']\n", + "\n", + "# Add tally to collection\n", + "tallies_file.append(tally)" + ] + }, + { + "cell_type": "code", + "execution_count": 26, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# Export all tallies to a \"tallies.xml\" file\n", + "tallies_file.export_to_xml()" + ] + }, + { + "cell_type": "code", + "execution_count": 27, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + " .d88888b. 888b d888 .d8888b.\n", + " d88P\" \"Y88b 8888b d8888 d88P Y88b\n", + " 888 888 88888b.d88888 888 888\n", + " 888 888 88888b. .d88b. 88888b. 888Y88888P888 888 \n", + " 888 888 888 \"88b d8P Y8b 888 \"88b 888 Y888P 888 888 \n", + " 888 888 888 888 88888888 888 888 888 Y8P 888 888 888\n", + " Y88b. .d88P 888 d88P Y8b. 888 888 888 \" 888 Y88b d88P\n", + " \"Y88888P\" 88888P\" \"Y8888 888 888 888 888 \"Y8888P\"\n", + "__________________888______________________________________________________\n", + " 888\n", + " 888\n", + "\n", + " Copyright: 2011-2016 Massachusetts Institute of Technology\n", + " License: http://openmc.readthedocs.io/en/latest/license.html\n", + " Version: 0.7.1\n", + " Git SHA1: 058ba68895a2f880402fda3d58cfb14b162931d9\n", + " Date/Time: 2016-05-17 21:14:05\n", + " OpenMP Threads: 4\n", + "\n", + " ===========================================================================\n", + " ========================> INITIALIZATION <=========================\n", + " ===========================================================================\n", + "\n", + " Reading settings XML file...\n", + " Reading cross sections XML file...\n", + " Reading geometry XML file...\n", + " Reading materials XML file...\n", + " Reading tallies XML file...\n", + " Building neighboring cells lists for each surface...\n", + " Loading ACE cross section table: 92235.71c\n", + " Loading ACE cross section table: 92238.71c\n", + " Loading ACE cross section table: 8016.71c\n", + " Loading ACE cross section table: 40090.71c\n", + " Loading ACE cross section table: 1001.71c\n", + " Loading ACE cross section table: 5010.71c\n", + " Maximum neutron transport energy: 20.0000 MeV for 92235.71c\n", + " Initializing source particles...\n", + "\n", + " ===========================================================================\n", + " ====================> K EIGENVALUE SIMULATION <====================\n", + " ===========================================================================\n", + "\n", + " Bat./Gen. k Average k \n", + " ========= ======== ==================== \n", + " 1/1 1.05201 \n", + " 2/1 1.02017 \n", + " 3/1 1.02398 \n", + " 4/1 1.02677 \n", + " 5/1 1.01070 \n", + " 6/1 1.02964 \n", + " 7/1 1.02163 \n", + " 8/1 1.04524 \n", + " 9/1 1.00773 \n", + " 10/1 1.01536 \n", + " 11/1 1.02992 \n", + " 12/1 1.03248 1.03120 +/- 0.00128\n", + " 13/1 0.99044 1.01761 +/- 0.01361\n", + " 14/1 1.01484 1.01692 +/- 0.00965\n", + " 15/1 1.01491 1.01652 +/- 0.00748\n", + " 16/1 1.03809 1.02011 +/- 0.00709\n", + " 17/1 1.02536 1.02086 +/- 0.00604\n", + " 18/1 1.03663 1.02283 +/- 0.00559\n", + " 19/1 1.03902 1.02463 +/- 0.00525\n", + " 20/1 1.01557 1.02373 +/- 0.00478\n", + " 21/1 1.01286 1.02274 +/- 0.00443\n", + " 22/1 1.01392 1.02200 +/- 0.00411\n", + " 23/1 1.04439 1.02372 +/- 0.00416\n", + " 24/1 1.04034 1.02491 +/- 0.00403\n", + " 25/1 0.99433 1.02287 +/- 0.00427\n", + " 26/1 1.02720 1.02314 +/- 0.00400\n", + " 27/1 1.03545 1.02387 +/- 0.00383\n", + " 28/1 1.03853 1.02468 +/- 0.00370\n", + " 29/1 1.02735 1.02482 +/- 0.00350\n", + " 30/1 1.02429 1.02480 +/- 0.00332\n", + " 31/1 1.02901 1.02500 +/- 0.00317\n", + " 32/1 1.03296 1.02536 +/- 0.00304\n", + " 33/1 1.03605 1.02582 +/- 0.00294\n", + " 34/1 1.04247 1.02652 +/- 0.00290\n", + " 35/1 1.02088 1.02629 +/- 0.00279\n", + " 36/1 1.03017 1.02644 +/- 0.00269\n", + " 37/1 1.03216 1.02665 +/- 0.00259\n", + " 38/1 1.01459 1.02622 +/- 0.00254\n", + " 39/1 1.03706 1.02659 +/- 0.00248\n", + " 40/1 1.01383 1.02617 +/- 0.00243\n", + " 41/1 0.99043 1.02502 +/- 0.00262\n", + " 42/1 1.02891 1.02514 +/- 0.00254\n", + " 43/1 1.02100 1.02501 +/- 0.00246\n", + " 44/1 0.99546 1.02414 +/- 0.00254\n", + " 45/1 1.01562 1.02390 +/- 0.00248\n", + " 46/1 1.03025 1.02408 +/- 0.00242\n", + " 47/1 0.99409 1.02327 +/- 0.00249\n", + " 48/1 1.04355 1.02380 +/- 0.00248\n", + " 49/1 1.02763 1.02390 +/- 0.00242\n", + " 50/1 0.99426 1.02316 +/- 0.00247\n", + " Creating state point statepoint.50.h5...\n", + "\n", + " ===========================================================================\n", + " ======================> SIMULATION FINISHED <======================\n", + " ===========================================================================\n", + "\n", + "\n", + " =======================> TIMING STATISTICS <=======================\n", + "\n", + " Total time for initialization = 1.4530E+00 seconds\n", + " Reading cross sections = 1.1470E+00 seconds\n", + " Total time in simulation = 1.8747E+01 seconds\n", + " Time in transport only = 1.8639E+01 seconds\n", + " Time in inactive batches = 2.1690E+00 seconds\n", + " Time in active batches = 1.6578E+01 seconds\n", + " Time synchronizing fission bank = 6.0000E-03 seconds\n", + " Sampling source sites = 4.0000E-03 seconds\n", + " SEND/RECV source sites = 2.0000E-03 seconds\n", + " Time accumulating tallies = 0.0000E+00 seconds\n", + " Total time for finalization = 0.0000E+00 seconds\n", + " Total time elapsed = 2.0209E+01 seconds\n", + " Calculation Rate (inactive) = 23052.1 neutrons/second\n", + " Calculation Rate (active) = 12064.2 neutrons/second\n", + "\n", + " ============================> RESULTS <============================\n", + "\n", + " k-effective (Collision) = 1.02389 +/- 0.00235\n", + " k-effective (Track-length) = 1.02316 +/- 0.00247\n", + " k-effective (Absorption) = 1.02494 +/- 0.00180\n", + " Combined k-effective = 1.02429 +/- 0.00140\n", + " Leakage Fraction = 0.00000 +/- 0.00000\n", + "\n" + ] + }, + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 27, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Run OpenMC\n", + "openmc.run()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "To make the files available and not be over-written when running the multi-group calculation, we will now rename the statepoint and summary files." + ] + }, + { + "cell_type": "code", + "execution_count": 28, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# Move the StatePoint File\n", + "ce_spfile = './ce_statepoint.h5'\n", + "os.rename('statepoint.' + str(batches) + '.h5', ce_spfile)\n", + "# Move the Summary file\n", + "ce_sumfile = './ce_summary.h5'\n", + "os.rename('summary.h5', ce_sumfile)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Tally Data Processing\n", + "\n", + "Our simulation ran successfully and created statepoint and summary output files. Let's begin by loading the StatePoint file, but not automatically linking the summary file." + ] + }, + { + "cell_type": "code", + "execution_count": 29, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# Load the statepoint file, but not the summary file, as it is a different filename than expected.\n", + "sp = openmc.StatePoint(ce_spfile, autolink=False)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In addition to the statepoint file, our simulation also created a summary file which encapsulates information about the materials and geometry. This is necessary for the `openmc.mgxs` module to properly process the tally data. We first create a `Summary` object and link it with the statepoint. Normally this would not need to be performed, but since we have renamed our summary file to avoid conflicts with the Multi-Group calculation's summary file, we will load this in explicitly." + ] + }, + { + "cell_type": "code", + "execution_count": 30, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "su = openmc.Summary(ce_sumfile)\n", + "sp.link_with_summary(su)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The statepoint is now ready to be analyzed by the `Library`. We simply have to load the tallies from the statepoint into the `Library` and our `MGXS` objects will compute the cross sections for us under-the-hood." + ] + }, + { + "cell_type": "code", + "execution_count": 31, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# Initialize MGXS Library with OpenMC statepoint data\n", + "mgxs_lib.load_from_statepoint(sp)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The next step will be to prepare the input for OpenMC to use our newly created multi-group data." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Multi-Group OpenMC Calculation" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We will now use the `Library` to produce a multi-group cross section data set for use by the OpenMC multi-group solver. " + ] + }, + { + "cell_type": "code", + "execution_count": 32, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "/home/nelsonag/git/openmc/openmc/tallies.py:1988: RuntimeWarning: invalid value encountered in true_divide\n", + " self_rel_err = data['self']['std. dev.'] / data['self']['mean']\n", + "/home/nelsonag/git/openmc/openmc/tallies.py:1989: RuntimeWarning: invalid value encountered in true_divide\n", + " other_rel_err = data['other']['std. dev.'] / data['other']['mean']\n", + "/home/nelsonag/git/openmc/openmc/tallies.py:1990: RuntimeWarning: invalid value encountered in true_divide\n", + " new_tally._mean = data['self']['mean'] / data['other']['mean']\n" + ] + } + ], + "source": [ + "# Create a MGXS File which can then be written to disk\n", + "mgxs_file = mgxs_lib.create_mg_library(xs_type='macro', xsdata_names=['fuel', 'zircaloy', 'water'],\n", + " xs_ids='2m')\n", + "\n", + "# Write the file to disk using the default filename of `mgxs.xml`\n", + "mgxs_file.export_to_xml()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "OpenMC's multi-group mode uses the same input files as does the continuous-energy mode (materials, geometry, settings, plots ,and tallies file). Differences would include the use of a flag to tell the code to use multi-group transport, a location of the multi-group library file, and any changes needed in the materials.xml and geometry.xml files to re-define materials as necessary (for example, if using a macroscopic cross section library instead of individual microscopic nuclide cross sections as is done in continuous-energy, or if multiple cross sections exist for the same material due to the material existing in varied spectral regions).\n", + "\n", + "Since this example is using material-wise macroscopic cross sections without considering that the neutron energy spectra and thus cross sections may be changing in space, we only need to modify the materials.xml and settings.xml files. If the material names and ids are not otherwise changed, then the geometry.xml file does not need to be modified from its continuous-energy form. The tallies.xml file will be left untouched as it currently contains the tally types that we will need to perform our comparison. \n", + "\n", + "First we will create the new materials.xml file. Continuous-energy cross section nuclidic data sets are named with the nuclide name followed by a cross section identifier. For example, the data for hydrogen is accessed in OpenMC by the name `H-1.71c`. The cross-section identifier (in this case, `71c`) can be used to distinguish between different variants of `H-1` data, such as for different evaluations or temperatures. OpenMC multi-group libraries use the same convention of a name followed by a xs identifier. We will use a cross section identifier here of `2m`. Similar to how continuous-energy cross section libraries are named, the `openmc.Macroscopic` quantities below can either have their `xs_id` included (i.e., `'fuel.2m'`). An alternative is to leave this extension off and simply change the `default_xs` parameter to `.2m`." + ] + }, + { + "cell_type": "code", + "execution_count": 33, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# Instantiate our Macroscopic Data\n", + "fuel_macro = openmc.Macroscopic('fuel')\n", + "zircaloy_macro = openmc.Macroscopic('zircaloy')\n", + "water_macro = openmc.Macroscopic('water')\n", + "\n", + "# Now re-define our materials to use the Multi-Group macroscopic data\n", + "# instead of the continuous-energy data.\n", + "# 1.6 enriched fuel UO2\n", + "fuel = openmc.Material(name='UO2')\n", + "fuel.add_macroscopic(fuel_macro)\n", + "\n", + "# cladding\n", + "zircaloy = openmc.Material(name='Clad')\n", + "zircaloy.add_macroscopic(zircaloy_macro)\n", + "\n", + "# moderator\n", + "water = openmc.Material(name='Water')\n", + "water.add_macroscopic(water_macro)\n", + "\n", + "# Finally, instantiate our Materials object\n", + "materials_file = openmc.Materials((fuel, zircaloy, water))\n", + "materials_file.default_xs = '2m'\n", + "\n", + "# Export to \"materials.xml\"\n", + "materials_file.export_to_xml()\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "No geometry file neeeds to be written as the continuous-energy file is correctly defined for the multi-group case as well." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we can make the changes we need to the settings file.\n", + "These changes are limited to telling OpenMC we will be running a multi-group calculation and pointing to the location of our multi-group cross section file." + ] + }, + { + "cell_type": "code", + "execution_count": 34, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "# Set the location of the cross sections file\n", + "settings_file.cross_sections = './mgxs.xml'\n", + "settings_file.energy_mode = 'multi-group'\n", + "\n", + "# Export to \"settings.xml\"\n", + "settings_file.export_to_xml()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, since we want similar tally data in the end, we will leave our pre-existing `tallies.xml` file for this calculation.\n", + "\n", + "At this point, the problem is set up and we can run the multi-group calculation." + ] + }, + { + "cell_type": "code", + "execution_count": 35, + "metadata": { + "collapsed": false, + "scrolled": true + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + " .d88888b. 888b d888 .d8888b.\n", + " d88P\" \"Y88b 8888b d8888 d88P Y88b\n", + " 888 888 88888b.d88888 888 888\n", + " 888 888 88888b. .d88b. 88888b. 888Y88888P888 888 \n", + " 888 888 888 \"88b d8P Y8b 888 \"88b 888 Y888P 888 888 \n", + " 888 888 888 888 88888888 888 888 888 Y8P 888 888 888\n", + " Y88b. .d88P 888 d88P Y8b. 888 888 888 \" 888 Y88b d88P\n", + " \"Y88888P\" 88888P\" \"Y8888 888 888 888 888 \"Y8888P\"\n", + "__________________888______________________________________________________\n", + " 888\n", + " 888\n", + "\n", + " Copyright: 2011-2016 Massachusetts Institute of Technology\n", + " License: http://openmc.readthedocs.io/en/latest/license.html\n", + " Version: 0.7.1\n", + " Git SHA1: 058ba68895a2f880402fda3d58cfb14b162931d9\n", + " Date/Time: 2016-05-17 21:14:26\n", + " OpenMP Threads: 4\n", + "\n", + " ===========================================================================\n", + " ========================> INITIALIZATION <=========================\n", + " ===========================================================================\n", + "\n", + " Reading settings XML file...\n", + " Reading cross sections XML file...\n", + " Reading geometry XML file...\n", + " Reading materials XML file...\n", + " Reading tallies XML file...\n", + " Building neighboring cells lists for each surface...\n", + " Loading Cross Section Data...\n", + " Loading fuel.2m Data...\n", + " Loading zircaloy.2m Data...\n", + " Loading water.2m Data...\n", + " Initializing source particles...\n", + "\n", + " ===========================================================================\n", + " ====================> K EIGENVALUE SIMULATION <====================\n", + " ===========================================================================\n", + "\n", + " Bat./Gen. k Average k \n", + " ========= ======== ==================== \n", + " 1/1 1.06913 \n", + " 2/1 1.04067 \n", + " 3/1 1.01854 \n", + " 4/1 1.00203 \n", + " 5/1 1.03243 \n", + " 6/1 1.02688 \n", + " 7/1 1.06855 \n", + " 8/1 1.03420 \n", + " 9/1 1.01657 \n", + " 10/1 1.02795 \n", + " 11/1 1.01796 \n", + " 12/1 1.03372 1.02584 +/- 0.00788\n", + " 13/1 1.02433 1.02534 +/- 0.00458\n", + " 14/1 1.01147 1.02187 +/- 0.00474\n", + " 15/1 1.01215 1.01993 +/- 0.00416\n", + " 16/1 1.04088 1.02342 +/- 0.00487\n", + " 17/1 1.04033 1.02583 +/- 0.00477\n", + " 18/1 1.04483 1.02821 +/- 0.00477\n", + " 19/1 1.02870 1.02826 +/- 0.00420\n", + " 20/1 1.01339 1.02678 +/- 0.00404\n", + " 21/1 1.03389 1.02742 +/- 0.00371\n", + " 22/1 1.02535 1.02725 +/- 0.00340\n", + " 23/1 1.00225 1.02533 +/- 0.00367\n", + " 24/1 0.99938 1.02347 +/- 0.00387\n", + " 25/1 1.01620 1.02299 +/- 0.00363\n", + " 26/1 1.03393 1.02367 +/- 0.00347\n", + " 27/1 1.01875 1.02338 +/- 0.00327\n", + " 28/1 1.00305 1.02225 +/- 0.00328\n", + " 29/1 1.01453 1.02185 +/- 0.00313\n", + " 30/1 1.02891 1.02220 +/- 0.00299\n", + " 31/1 0.99612 1.02096 +/- 0.00311\n", + " 32/1 1.04911 1.02224 +/- 0.00323\n", + " 33/1 1.01410 1.02188 +/- 0.00310\n", + " 34/1 0.98979 1.02055 +/- 0.00326\n", + " 35/1 1.00938 1.02010 +/- 0.00316\n", + " 36/1 1.02857 1.02043 +/- 0.00305\n", + " 37/1 1.04095 1.02119 +/- 0.00303\n", + " 38/1 1.02033 1.02115 +/- 0.00292\n", + " 39/1 1.02104 1.02115 +/- 0.00282\n", + " 40/1 1.00854 1.02073 +/- 0.00276\n", + " 41/1 1.00932 1.02036 +/- 0.00269\n", + " 42/1 1.00284 1.01982 +/- 0.00266\n", + " 43/1 1.02489 1.01997 +/- 0.00258\n", + " 44/1 1.03981 1.02055 +/- 0.00257\n", + " 45/1 1.02630 1.02072 +/- 0.00251\n", + " 46/1 1.00133 1.02018 +/- 0.00249\n", + " 47/1 1.02409 1.02028 +/- 0.00243\n", + " 48/1 1.03928 1.02078 +/- 0.00241\n", + " 49/1 1.01226 1.02057 +/- 0.00236\n", + " 50/1 1.03536 1.02094 +/- 0.00233\n", + " Creating state point statepoint.50.h5...\n", + "\n", + " ===========================================================================\n", + " ======================> SIMULATION FINISHED <======================\n", + " ===========================================================================\n", + "\n", + "\n", + " =======================> TIMING STATISTICS <=======================\n", + "\n", + " Total time for initialization = 4.6000E-02 seconds\n", + " Reading cross sections = 8.0000E-03 seconds\n", + " Total time in simulation = 1.4524E+01 seconds\n", + " Time in transport only = 1.4457E+01 seconds\n", + " Time in inactive batches = 1.3350E+00 seconds\n", + " Time in active batches = 1.3189E+01 seconds\n", + " Time synchronizing fission bank = 7.0000E-03 seconds\n", + " Sampling source sites = 5.0000E-03 seconds\n", + " SEND/RECV source sites = 2.0000E-03 seconds\n", + " Time accumulating tallies = 0.0000E+00 seconds\n", + " Total time for finalization = 0.0000E+00 seconds\n", + " Total time elapsed = 1.4579E+01 seconds\n", + " Calculation Rate (inactive) = 37453.2 neutrons/second\n", + " Calculation Rate (active) = 15164.2 neutrons/second\n", + "\n", + " ============================> RESULTS <============================\n", + "\n", + " k-effective (Collision) = 1.02358 +/- 0.00231\n", + " k-effective (Track-length) = 1.02094 +/- 0.00233\n", + " k-effective (Absorption) = 1.02682 +/- 0.00152\n", + " Combined k-effective = 1.02527 +/- 0.00153\n", + " Leakage Fraction = 0.00000 +/- 0.00000\n", + "\n" + ] + }, + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 35, + "metadata": {}, + "output_type": "execute_result" + } + ], + "source": [ + "# Run the Multi-Group OpenMC Simulation\n", + "openmc.run()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Results Comparison\n", + "Now we can compare the multi-group and continuous-energy results.\n", + "\n", + "We will begin by loading the multi-group statepoint file we just finished writing and extracting the calculated keff.\n", + "Since we did not rename the summary file, we do not need to load it separately this time." + ] + }, + { + "cell_type": "code", + "execution_count": 36, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# Load the last statepoint file and keff value\n", + "mgsp = openmc.StatePoint('statepoint.' + str(batches) + '.h5')\n", + "mg_keff = mgsp.k_combined" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, we can load the continuous-energy eigenvalue for comparison." + ] + }, + { + "cell_type": "code", + "execution_count": 37, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [ + "ce_keff = sp.k_combined" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Lets compare the two eigenvalues, including their bias" + ] + }, + { + "cell_type": "code", + "execution_count": 38, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Continuous-Energy keff = 1.024295\n", + "Multi-Group keff = 1.025274\n", + "bias [pcm]: -97.9\n" + ] + } + ], + "source": [ + "bias = 1.0E5 * (ce_keff[0] - mg_keff[0])\n", + "\n", + "print('Continuous-Energy keff = {0:1.6f}'.format(ce_keff[0]))\n", + "print('Multi-Group keff = {0:1.6f}'.format(mg_keff[0]))\n", + "print('bias [pcm]: {0:1.1f}'.format(bias))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "This shows a nontrivial pcm bias between the two methods. Some degree of mismatch is expected simply to the very few histories being used in these example problems. An additional mismatch is always inherent in the practical application of multi-group theory due to the high degree of approximations inherent in that method." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Flux and Pin Power Visualizations" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next we will visualize the mesh tally results obtained from both the Continuous-Energy and Multi-Group OpenMC calculations.\n", + "\n", + "First, we extract volume-integrated fission rates from the Multi-Group calculation's mesh fission rate tally for each pin cell in the fuel assembly." + ] + }, + { + "cell_type": "code", + "execution_count": 39, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# Get the OpenMC fission rate mesh tally data\n", + "mg_mesh_tally = mgsp.get_tally(name='mesh tally')\n", + "mg_fission_rates = mg_mesh_tally.get_values(scores=['fission'])\n", + "\n", + "# Reshape array to 2D for plotting\n", + "mg_fission_rates.shape = (17,17)\n", + "\n", + "# Normalize to the average pin power\n", + "mg_fission_rates /= np.mean(mg_fission_rates)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can do the same for the Continuous-Energy results." + ] + }, + { + "cell_type": "code", + "execution_count": 40, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "# Get the OpenMC fission rate mesh tally data\n", + "ce_mesh_tally = sp.get_tally(name='mesh tally')\n", + "ce_fission_rates = ce_mesh_tally.get_values(scores=['fission'])\n", + "\n", + "# Reshape array to 2D for plotting\n", + "ce_fission_rates.shape = (17,17)\n", + "\n", + "# Normalize to the average pin power\n", + "ce_fission_rates /= np.mean(ce_fission_rates)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now we can easily use Matplotlib to visualize the two fission rates side-by-side." + ] + }, + { + "cell_type": "code", + "execution_count": 41, + "metadata": { + "collapsed": false + }, + "outputs": [ + { + "data": { + "text/plain": [ + "" + ] + }, + "execution_count": 41, + "metadata": {}, + "output_type": "execute_result" + }, + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXQAAADDCAYAAACS2+oqAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAIABJREFUeJztnXeYFeX1xz8HUJSmoIJiA8WOokSwm43RtUSjiQ1LbAnq\nzySYaGzR6IrGHns0tih2YzcxidjWXlAUS2woIoiggoqKorDn98fMwmXZe88su5e7O3w/z7PP3jvv\nd9733HfOnHmnnHnN3RFCCNH2aVdpA4QQQrQMCuhCCJETFNCFECInKKALIUROUEAXQoicoIAuhBA5\nodUFdDN7zcy2rrQdizJm9m8z+0Uz1r/czE5sSZsWRcyszsxWK1Ge231FPriAuHv4B+wLjAK+BD4E\n7ge2yLJuUO+1wPDm1lPJv/Q3zASmp39fAi9V2q4Mdp8CfFdg83TgD5W2qwk2TwOeBDZtwvqPAocs\nBDvfB74FejRY/jJQB6ySsZ7ZwGoFftakfQVYDDgZeDPdxhPSfXe7Sm/LRranfLAF/sIRupkdBZwP\nnA70BFYBLgN+Gq27CHG2u3dL/7q6+0Yt3YCZtW/pOoFbC2zu5u7nlaGNluZWd+8GLAvUArdX1pxG\ncWAcsE/9AjPrDyyRlmXFmmnHncAuwP5Ad6AvcBGwU6ONlcfHIuSDLUlwNOlGcuT8eQnN4sCFJCP3\nicAFwGJp2Q9JRgVHAVNSzUFp2VCSI923JEe7e9Pl44BtCo6GtwEjUs2rwMCCtutIRzDp93lGMWkb\n7wCfAvcAK6TLV03XbdfYkRNYnWRDfQ58DNxS4vcXHTkVtHMAMD6t648F5QYcD4wFPgFuBZZusO4h\n6bq16fIDSEaAnwAn1fcX0Av4GuheUP8P0jbbFxlpXB+NIkr1Rbqtp6RlLwPrNmU7FGzDw4C3ganA\npcHo6PqC7+uQjGKXSb8vDfwztXNq+rl3WnY6MAuYkfrSxenytYGRqf4NYM+C+ncCXk/1E4CjMo7C\nxgF/BJ4vWHYucEJq7yqNjdaAA4EnGvo3GfaVRmzYNvWHFTLYeiwwBviG5DLsOqltn5Hsc7s05hsl\nbP4t8G66Hc7Juj3lg833wWiEvhnQMe2AYpwEDAY2AAakn08qKF8e6Ar0Bn4F/NXMlnL3q4CbSDZ4\nN3fftUj9uwA3A0ulnfPXgrKiox0z2wY4A9gDWAH4gCRghusCpwEPuPvSwErAJSW0WdgCWINkJzvZ\nzNZKlx9JcqazFUn/fEZy9lPI1iQbfHszW4fk9+9D8puWStfD3aeQ7AR7Fay7H4nzz26G7Y32hZlV\nA1sC/dKyvUkcch4ybAeAn5AcfDYE9krrLomZLU4STKaS9BskwejvwMokZ5IzSP3F3U8CngB+k/rb\nMDPrRLIj3Ugy2toHuCztZ4CrgaGejMb6A49EdhXwLNDVzNYys3Yk2+VG4lH3fH7ZhH2lkB8Dz7n7\nRxm0Q4AdSYJRO+A+4L/AcsAw4CYzW6MJNu8GDEz/djWzQzLYUAr5YEYfjAL6MsCn7l5XQrMvcKq7\nT3X3qcCpQOHNjO+A09x9trv/B/gKWKuReorxpLs/4Mnh6gaSA0c9pXaOfYFr3H2Mu39PMjrazMxW\nydDm98CqZraiu3/n7k8H+mPMbJqZfZb+v7agzIGatJ5XSEZCA9KyQ4ET3f2j1MbhwB5pAKhf9xR3\n/8bdZ5I45H3u/oy7zyK5PlrI9aR9n9axD0mfFWPvBnYv34S++J7kQL2umZm7v5UeVBqSZTuc6e5f\nuvsEkoPShpHNJDvKL4E96v3T3ae5+93uPtPdvwbOJDkgFmNnYJy7X+8JL5NcptgjLf8OWM/Murr7\nF2l5U7iBZIffjuQ69qQmrt8clgUm138xs+7pdv7czL5poL3I3SelPrYp0Nndz3b3We7+KPAvCi4f\nZeCstL8mkpy9l1pXPtiCPhgF9KnAsgUBpjF6kxzx6hmfLptTR4MDwgygS9BuIZMLPs8AlgjsKbRr\nfP2XtHOnAitmWPcYkr553sxeNbODAczsBDP70symm1nhSPpcd+/h7t3T/wc3qK/QyQp//6rA3akj\nTwP+R+KkvQr0Exv8pgkFv+kb5h2R3AusY2Z9gGrgc3d/ocTvvK2B3ZMb0TTaF+mOfinJ6GOymf3N\nzBrbrlm2Q7H+KWozyf2c14CN6wvMbEkzu8LM3jezz4HHgKXNrNiBf1Vg0/r+N7PPSHb++v7fnWTk\nNt7MHjWzTUvY1Rg3pvUdRHKwLRupX9b75kokfbxCfbm7f+bu3UlGoYs3WL2oj6WMJ9t+01h9DeNB\nQ+SDLeiDUWB8huS63W4lNB+mRhUamHUk0pQbRI0xA+hU8L3w6D6p0C4z60xyxjGR5NoixdZ194/d\n/VB3XxE4nOQUaDV3P9Pn3rw5opm2Q3Ig3DF15Hqn7tzgNLmwjz4iOeWs/01Lpr+p3u6ZwD9IboLt\nT+nReSaK9UVadqm7bwysR3LWdUwjVZTaDs2xa1pqT42Z1Tv/0SSXtgalp+D1I6P6namhv00guTdR\n2P/d3P03aRsvuvtuJJce7iXp26bY+AHJNeodgbsakXxNcf+dr7qgra4FvjkReBgYZGaNBdOGwaWw\n7kkklwsKWYVkP89qc+H6q9DMMxP5YHYfLBnQ3X06yU2Av5rZrunRp4OZ7WhmZ6WyW4GTzGxZM1sW\n+BPZA8kUkps+TaHQGV8C9jWzdma2A8lN2HpuBg42sw3MrCPJNbRn3X2Cu39K4qD7p+seQnLjJWnA\nbA8zqz96f05y02RBr0OXuix0BXBG/amfmS1nZoVPDzVc9w5gFzPb1MwWI7m81ZAbSEaEu5CMEJtF\nsb4ws43NbLCZdSC5mfYtjfdR0e3QXNvc/S2Sa73HpYu6prZMN7MeQE2DVRr627+ANc1s/9SvF0t/\n19rp533NrJsn9yC+JLmh1VQOIblx2fAyByQ38X6e7lf9SE7fi9GkfcXdHyS5dHBPup0WS7fVZpQ+\nODwHfG1mx6Z9UkVyWeCWJth8jJktbWYrk9wnani9uknIB7P7YHjpwt0vIHlK5SSSO7cfAEcw90bp\n6cALQP314ReAP5eqsuDzNSTXh6aZ2V2NlEfr/47kpuJnJNfp7i6w+xGSg8tdJMG7L8nNn3qGktzd\n/5TkTvVTBWWDgOfMbHr6O4e5+3iKc2x6qjs9Pe39uIi9Db9fRHLUHWlmXwBPk9xUbnRdd/8fyRME\nt5GMOr4g2SYzCzRPkzj86HSEuCAUtlusL7oBV5E8izuOpB/ne+Qsw3Yo1T9ZOA8Ymg4mLiQZPX5K\n0pf/bqC9CNjTzKaa2YXu/hXJpakhJP05CTiLuZckfgGMS0+dDyW5yZyFOb/B3ce5++jGykie0Pie\n5LLitcx/AG7uvvJzkoBxI8k+8h7JfrJ9kTZIrzH/lOTpik9JLmn8wt3fyWgzJD79IjCa5EGGvwd2\nNoZ8MKFJPmjuzb3qISpFeur4Ocld/vEFyx8GbnL3BdmRhFhgzKyOxB/fq7QtiyKtLvVflMbMdk5P\ndzsDfwFeaRDMBwEbkYzihRCLEArobY9dSU7LJpJc959z6mhm15E803pkeidfiIWNTvkriC65CCFE\nTtAIXQghckKHclSaPkJ4IckB4xp3P7sRjU4NRFlx9+a+3Go+5NuiNVDMt1v8koslWZxvk7xLYhLJ\na3eHuPubDXTOcQVtP1kDW9bMW9mIDA1ekEGTJWn51QyaFxr01T01sFvNvMsOjnMVLqg7PdT8/u2/\nlSy/eM2hYR3DHrlq/oUjauDAmjlfV/rxO/NrGvDynDcVFOdqfhVqvqNjqLnCD5tv2fSai+lWM2zO\n9w+fL/VakZRNrcUDepN8+/iC5OgnamCrmjlf+57xv7Ctce+uGxs0OcPPezjev7v84ZP5ls3887l0\nPHFujs7/dbo8rOfcYQ3fRDE/Ay55NtT099dCzU1HNOJvo2pgUM2cr49ftvH8mgZs/ZdSidQpx8R9\nuHXdyFDz+AM7zLvgxhrYv2beZTuWrqO6GkaOLO7b5bjkMhh4x93Hp8+03kpyI0+Ito58W7RqyhHQ\nV2Ted0FMpGnvgRCitSLfFq2aclxDb+xUoPFzlidr5n7uuHQZTCkza1dV2oKmM6Cq0hY0mY5Vm8Si\nF2thdG25Tcnu20/UzP3cBn27/VabV9qEptO7qtIWNI0NqjIKa9M/GDu2tLIcAX0iyQt56lmJYi/n\naXjNvK3RFgP6hlWVtqDJZAroP6hK/uq5prHX3DSb7L5dcM28LdJh6y0qbULTWbGq0hY0jcwBvSr9\ng3794L33ivt2OS65jAL6mdmqlrwAfgjJC/OFaOvIt0WrpsVH6O4+28x+Q5KxWP9o1xst3Y4QCxv5\ntmjtlOU5dHf/L02blUiINoF8W7RmKpb6b2ZOn6DtbWPb7LQZocYv7RRqZg+JJzxfYvnPQ03XpaeH\nmk06PBdq1gkGfhd9fGRYx5E9Lwo1VfZoqHmHNUPNmHlmBmycZ9gs1HxO91CzQoZpMl9pt1lZEouy\nkCQWlZq1MfZZ7LNYs1v8gM1md8bToJ5EnBfxk5PjerocN//z7A2p7hw/r33X9fFbik854PhQ04f3\nQ83hX8TP18/8TeyT/CiWXHRwnDtyZPuVSpZXV6/OyJEHLNTn0IUQQlQABXQhhMgJCuhCCJETFNCF\nECInKKALIUROUEAXQoicoIAuhBA5QQFdCCFyQlkyRTNzblA+M67isJ6lJ4IA6HXGH0KNdY1zUGYe\n2zXUVPF0qOntjb/PqZDzOLFk+Vo9Dwjr6E88UcBmPjrUPMtGoeb358fb4a6jg7f3A+dwbKjZ2f4V\nal4JFWXmlyXKrolXf3x2/Jr1frwbano9Fye62SazQ83sp+PEu5073R5q7iBOGhp1QJyk9iXdQs02\nPBlqNlmqb6j58IY4geuWDLPoXMWhoYZNBpUuXxsYWXzf1whdCCFyggK6EELkBAV0IYTICQroQgiR\nExTQhRAiJyigCyFETlBAF0KInFDZ59D7BOVd4iou/eyYUNNudqnJBlKujI9tH9Ej1CxvF4Sa3f3O\nUOOPlJ7A4vkfx5NX7O53hBoGx88Xb7JUXA0Pxn38s6vjPv7rr44INXeyewaD/pJBUz6GXnlx0bKr\nZg4L1+/EN6FmeTJMgtEr7vNhnBNq9n5ow1Bzhp0Qai71/4SaXtYn1Nzue4aabT6MfXutiaGEtTcZ\nF7f1VtxW+8/j5/3ZMMiHWb10sUboQgiRExTQhRAiJyigCyFETlBAF0KInKCALoQQOUEBXQghcoIC\nuhBC5AQFdCGEyAnm7pVp2MxH1a1bUjPowxfCeuq+6RRqBq3+WKh54fEfhhqfEUpgQAbNIxk0QY7G\nERPjxJm3vV+o2aZdPJHCiY+GEuqeiicIefGPpbc3QHebFmqu5LBQc67V4O6xUWXAzHyduuK+O3Zq\nvF1GLBtPYJLl540Ps/fg+OfjJDU6x5Kj1vtzqDn/8dITtwAM3zpOFjx5XDQ7DvBsLBmxf6w5sDqD\nG10Sx9Hr+u0dak7jTyXLt6Iz17frW9S3NUIXQoicoIAuhBA5QQFdCCFyggK6EELkBAV0IYTICQro\nQgiRExTQhRAiJyigCyFETijLjEVm9j7wBVAHfO/ugxvTDbFbS9Yzunc8SwoWz5QzmPNDzTc/iJMH\nluiSYcaRF+Nj5C37xck8++x/d8nyr/yqsI4HP94t1Fhd3H9+T/ybnjpho1CzJXGiGI/FbW3yw+fi\nespEVt9+o33x2a3s7XgqriHL3BMb80ncV/5VhqSYwbEP2MtxW3957qS4ra3jtk7O8Ltu7BPPWrV/\n39tDzYHfZhjTxpOQQb/4d43n+FAzbs3SyXdrbFl6/XJNQVcHVLl7hjmyhGhTyLdFq6Vcl1ysjHUL\nUUnk26LVUi7HdOABMxtlZkPL1IYQlUC+LVot5brksrm7Tzaz5YAHzewNd3+yTG0JsTCRb4tWS1kC\nurtPTv9/YmZ3A4OB+Zx+Ws1lcz4vWTWIJasGlcMcsQjwWu1UXq+N39TYXLL6Nn5hwZdNwTYtu20i\np8yohW9qARj7Umlpiwd0M+sEtHP3r8ysM1ANnNqYtkfNES3dvFhE6V+1DP2rlpnz/fZTx7Z4G03x\nbex3Ld6+WETpVJX8Af02gvfGDC8qLccIvRdwt5l5Wv9N7j6yDO0IsbCRb4tWTYsHdHcfB2R4gFyI\ntoV8W7R2Kjpj0dW+T0nNMkwN6/mP7xhqetqUULOv3xJqXvCNQ80v3roz1HjHUMKYfqUTQmrr4gcs\nOvq3oebwY26IjamOfWT57caFmsnPrRa31TFuq93bcRIHQ9pVdMaiPWdfV7T89svj2YhmT2gfao48\n88xQM5kVQs1t4w4KNU/3jY9jHYgT7wa/+WqomdU79oG1ur0eat67qH+osfXjtrx3KOHttVYONWs/\nMT6u6JnSLlvdF0YOMc1YJIQQeUcBXQghcoICuhBC5AQFdCGEyAkK6EIIkRMU0IUQIicooAshRE5Q\nQBdCiJxQ0cQiu7V0IsKs9eJE1s36PxJq7iGeuee3XBJqdueOUNPVvww12379aKjp+PvS5XdfFSdU\nrcSEULO0fx5qXmRgqNmqkfdTzWfPyvHLs3y/UMIuZ/0j1Nzfbq+KJhbt7LcVLf/aO4V1rMikUPOl\nxTMfLZshOe9sPzbUjLJGJ2aah/7+WqihfewDK2bIvxmz8hqhprd/FNfDgFCzJDNCzRbvB2/NAtbs\n83KoGft6aXuqu8DIvkosEkKI3KOALoQQOUEBXQghcoICuhBC5AQFdCGEyAkK6EIIkRMU0IUQIico\noAshRE6oaGLRkLprSmp28n+H9exvt8eNXRwft54ftn6oGcyYuK0RcVu3H7BzqNnT7isteDBu57vB\ncV7N4kvFs8ywSdyW/zNuy3pmaGtc3Nabq60aata18RVNLLLziv/WWRvGCXO2TdxX5/PrUHPMXy4N\nNbOPztBNf4q3y6GnXRRqruS3oeb59nFbHWfHiUUDeCvU3MEuoWb3P8VxyE6Lt1e7v4cS9jzk+pLl\nA+jNSe2qlVgkhBB5RwFdCCFyggK6EELkBAV0IYTICQroQgiRExTQhRAiJyigCyFETlBAF0KInBBn\nOJSRt610csD6tnJYxz5114WaW34W2zKdbqHGT20fasaeslJsD/uEmj36lW7rvLFxUslavB1qtqBz\nqLn/uT1DzQyWDDXtOSDU9O27RaiZRO9QAxmmvSkj+x5VPGmuw82zwvXHECfOjLTzQs09R1eHmo38\ntFDz/knxDEp8F0s+XezmUHPXZXE9kzLMxMR68f66x5FxYuX9w38U29M+buuO2TuEmods25LlXSk9\nS5VG6EIIkRMU0IUQIicooAshRE5QQBdCiJyggC6EEDlBAV0IIXKCAroQQuQEBXQhhMgJCzxjkZld\nA+wMTHH3DdJl3YHbgFWB94G93P2LIuv7T+tKJxn88/G9QztmrRLnRp3S57hQs7f9I9S866uHmo+t\nZ6hZzd8LNT++4pmS5XWbhlVw7IDhoea0r08ONUu8Gbd16w9+GmqG7BjMwgS899/lQ83u3BVqxtjm\nCzxjUUv4Nl3rijdwS2xD9U73hpqRN+8aapbYeVrcVreRoea+9+J9ccvVHgo1D3+xfahZ7PRQwvXn\nxsluXdrFs5ntfmzclm8da16JJyHj6bo4se7wA0rPWMT61bQ7bmRZZiy6Fmi4dY4HHnL3tYBHgBOa\nUb8QlUK+LdokCxzQ3f1J4LMGi3cFRqSfRwC7LWj9QlQK+bZoq7T0NfSe7j4FwN0nA8u1cP1CVAr5\ntmj16KaoEELkhJZ+2+IUM+vl7lPMbHng41LiN2vumPN52ap1WbZq3RY2RywqfFU7mq9qR5eziSb5\nNjNr5n5uXwUdqspomsgztZOhdkr6ZfLYktrmBnRL/+q5DzgIOBs4ECh5q37tmj2a2bwQCV2qBtKl\nauCc71NO/Xtzq2yWb9OxprntCwFA1fLJHwDr92P4Q8WfklvgSy5mdjPwNLCmmX1gZgcDZwHbmdlb\nwLbpdyHaFPJt0VZZ4BG6u+9bpKj0G9qFaOXIt0VbZYETi5rdsJk/VLdZSc2Pbn82rmfP2aHGb4pP\nRI7e78+h5vwMjx5Ptu6h5lvvGGr6MLlkuW0V/6a6w+K8Gts/7j+7I0Nb37ZMW3wSt/VIz9J+A7Ct\nPbPAiUXNxcwcu7Jo+bmzXg7rONouDTU3EifX7HdjnISVZbt8/0W8XTo8l8EHquO2pi0et/Xpd/Fs\nZmtmmLXKHovb8qcyuNEfM/j2xXFb9nAg2KgaG16exCIhhBCtCAV0IYTICQroQgiRExTQhRAiJyig\nCyFETlBAF0KInKCALoQQOUEBXQghckJLv5yrSWz37ydLlh+7ZzzjzhkXtQ81NxwZJ2Csbu+GmvHe\nK9R0ILZnrK0Rah7zISXLt3siTmBa7ouGr/Sen7fpF2p67xEnQnXfb2aoqdsp7pvvOocSaqiJRfPN\nT7GQ8V8VLbrSxoSrH3tFnPB39WGLhZoZGV6XNKl9vF3WeDquZ/iOsc0nPxy31WOruK0eB04INR9e\nt2yoWWl63Bb949/1SoY+3ODBuCk7vsRMV0C1Q6lxuEboQgiRExTQhRAiJyigCyFETlBAF0KInKCA\nLoQQOUEBXQghcoICuhBC5AQFdCGEyAkVTSzyJ0vPBPLUTzYP69ht2C2h5l72CjVDiWeImWI9Q83h\nfkWo2XbcU6GGF0sXT9tjibCKV3vEzQzsW3zC2Xr81gwzttxUOiEC4Eb2DjUDiGfzyaIpnbK2EOhf\nvOgIuzxcfeSh1aHmYG4ONSM63xpqDown4uLATf4WaobOin3fJr4Uavy42J6x260Yavpd/mHc1toZ\nfHubeDaimbM3CDU/5U9xW0cH9vQD/lW8WCN0IYTICQroQgiRExTQhRAiJyigCyFETlBAF0KInKCA\nLoQQOUEBXQghcoICuhBC5ISKJhaxSemH6J88dbuwCts1nk3E741nE/n1yfEsQhvyZqj5P+Lko8vf\nODrUsEfpZIYeO8TH4u7LZUiaGBsnTdiDcVvtx8TbYdiAjULNwf+LE2EOX++CUFNxbi/e979/OU7S\nsfvj/nzkhC1CzQFXZ/CBg2MfuO7ieB+6YNjhoWbLlYOMOcAejv1t9VUmhRomxMluNi5uq26b+Lcf\n9OioUFPtI0MN0cRpwYxeGqELIUROUEAXQoicoIAuhBA5QQFdCCFyggK6EELkBAV0IYTICQroQgiR\nExTQhRAiJ5h7nMDQ6Ipm1wA7A1PcfYN02SnAUODjVPZHd/9vkfWdHUs/+H/A/XECxl1f7x5qftc5\nTkR51dYPNdvUPRq39VI8a8sDA7cONevxesnyERwY1vEz7g41q3w9IdRc2PnIUHPiyPNDzYDtnw01\nPXxqqBlTt2GomdZhZdw9Q1bN/LSIb29aYr/aMt7n/nzOUaGmm00PNdv7A6Fm9Ulxks4HvZcNNaMY\nHGp2P/DfoebsEcNCzfr+SqjZ6fXaUPNG/z6hZkmfEWr63PlxqBm052OhZrp3K1m+JV24rl2/or7d\nnBH6tcD2jSw/390Hpn+NOrwQrRz5tmiTLHBAd/cngc8aKVqgUZEQrQX5tmirlOMa+q/N7GUzu9rM\nlipD/UJUCvm2aNW09Mu5LgOGu7ub2enA+cAvi6rfqZn7uUcVLFPVwuaIRYXva5/h+8eeKWcTTfPt\nCTVzP3ergqWqymmbyDEzakfxTe0LALzE4iW1LRrQ3f2Tgq9XAf8sucIaNS3ZvFiEWaxqMxar2mzO\n929Pa9k3MjbZt1euadH2xaJLp6pBdKoaBMBGdGHM8EuKapt7ycUouK5oZssXlP0ceK2Z9QtRKeTb\nos2xwCN0M7sZqAKWMbMPgFOAH5nZhkAd8D5wWAvYKMRCRb4t2ioLHNDdfd9GFl/bDFuEaBXIt0Vb\nZYETi5rdsJkfWXdGSc32FidFXOsHh5p3WS3UjD5ny1CTYcIimJXhybYMsyxxZ6C5bXhYxQ/qtgk1\nl3icxLH5yS+FGr6PJZyT4XevnmWWpYcyNFa9wIlFzcXMnA4lfmu/DJXckaGvMgzH9lprRKh5wmPf\nP8yuDDVZunv4/WeGmu12vi/U/IIbQs0M7xRqDn887p+Tf3hCqPm7HxLXQ7zPDr3rxpLl1T1h5Nbt\nypJYJIQQohWhgC6EEDlBAV0IIXKCAroQQuSEVhPQJ9a+V2kTms5HtZW2oMl8WZvhBmdrY0ZtpS1o\nHnW1lbagycysfa7SJjSZN2o/iUWtiLdqp7R4nQrozWFybaUtaDJf1r5caROazje1lbageXhtpS1o\nMt8poJedXAd0IYQQzaOlX87VJFZibjZ1N7rM8z1ZtmZYR196hJrF6BIb0yuW8M28XyeNg96rNtDM\nzlDP0hk0fYPygSuEVazdyO/+jsXnWd6ZdcJ6BvYOJTArg2ZgBs1K8y+a9Cb0XrtgQbeuYTWjR2do\nq4wM3Gju50kfQu8VCwpXzlDBEhk07WNJX5YJNZ/Tcb5lY2lPv4LlK7DifJqGeIa3Cw/M8I7KfsSi\nHo3klizJR/Ms75ShEwdmCA1Zfnv/RvqwIcvQZ57vS/LBfMsGBrGhXxcYWaK8oolFFWlYLDJUNLFI\niDJSzLcrFtCFEEK0LLqGLoQQOUEBXQghckKrCOhmtoOZvWlmb5vZcZW2Jwtm9r6ZjTGzl8zs+Urb\n0xhmdo2ZTTGzVwqWdTezkWb2lpk90JqmUiti7ylmNtHMRqd/O1TSxqbS1nxbfl0eFpZvVzygm1k7\n4FKSWdbXA/Yxs7VLr9UqqAOq3H0jdx9caWOK0Njs9ccDD7n7WsAjQPwquYVHY/YCnO/uA9O//y5s\noxaUNurb8uvysFB8u+IBHRgMvOPu4939e+BWYNcK25QFo3X0X1GKzF6/K1D/ztARwG4L1agSFLEX\nyPA8XOukLfq2/LoMLCzfbg0bbkVgQsH3iemy1o4DD5jZKDMbWmljmkBPd58C4O6TgeUqbE8Wfm1m\nL5vZ1a3tVDqgLfq2/Hrh0qK+3RoCemNHqLbwLOXm7r4xsBPJRskwQ4ZYAC4DVnf3DYHJwPkVtqcp\ntEXfll/3XIupAAABIElEQVQvPFrct1tDQJ8IrFLwfSVgUoVsyUw6CqifDf5uktPrtsAUM+sFcyY+\n/rjC9pTE3T/xuckSVwGDKmlPE2lzvi2/XniUw7dbQ0AfBfQzs1XNbHFgCBDPQVVBzKyTmXVJP3cG\nqmm9s8DPM3s9Sd8elH4+ELh3YRsUMI+96c5Zz89pvf3cGG3Kt+XXZafsvl3Rd7kAuPtsM/sNySsK\n2gHXuPsbFTYrohdwd5ri3QG4yd1LvWKhIhSZvf4s4HYzOwT4ANizchbOSxF7f2RmG5I8ffE+cFjF\nDGwibdC35ddlYmH5tlL/hRAiJ7SGSy5CCCFaAAV0IYTICQroQgiRExTQhRAiJyigCyFETlBAF0KI\nnKCALoQQOUEBXQghcsL/A1cunn39vbPfAAAAAElFTkSuQmCC\n", + "text/plain": [ + "" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "# Force zeros to be NaNs so their values are not included when matplotlib calculates\n", + "# the color scale\n", + "ce_fission_rates[ce_fission_rates == 0.] = np.nan\n", + "mg_fission_rates[mg_fission_rates == 0.] = np.nan\n", + "\n", + "# Plot the CE fission rates in the left subplot\n", + "fig = plt.subplot(121)\n", + "plt.imshow(ce_fission_rates, interpolation='none', cmap='jet')\n", + "plt.title('Continuous-Energy Fission Rates')\n", + "\n", + "# Plot the MG fission rates in the right subplot\n", + "fig2 = plt.subplot(122)\n", + "plt.imshow(mg_fission_rates, interpolation='none', cmap='jet')\n", + "plt.title('Multi-Group Fission Rates')\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "collapsed": true + }, + "source": [ + "We also see very good agreement between the fission rate distributions, though these should converge closer together with an increasing number of particle histories in both the continuous-energy run to generate the multi-group cross sections, and in the multi-group calculation itself." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": true + }, + "outputs": [], + "source": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.5.1" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/docs/source/pythonapi/examples/mgxs-part-iv.rst b/docs/source/pythonapi/examples/mgxs-part-iv.rst new file mode 100644 index 0000000000..e243255217 --- /dev/null +++ b/docs/source/pythonapi/examples/mgxs-part-iv.rst @@ -0,0 +1,13 @@ +.. _notebook_mgxs_part_iv: + +==================================================== +MGXS Part IV: Multi-Group Mode Cross-Section Library +==================================================== + +.. only:: html + + .. notebook:: mgxs-part-iv.ipynb + +.. only:: latex + + IPython notebooks must be viewed in the online HTML documentation. diff --git a/docs/source/pythonapi/index.rst b/docs/source/pythonapi/index.rst index 89d1b0508b..bf35e75874 100644 --- a/docs/source/pythonapi/index.rst +++ b/docs/source/pythonapi/index.rst @@ -26,6 +26,7 @@ Example Jupyter Notebooks examples/mgxs-part-i examples/mgxs-part-ii examples/mgxs-part-iii + examples/mgxs-part-iv ------------------------------------ :mod:`openmc` -- Basic Functionality diff --git a/docs/source/usersguide/mgxs_library.rst b/docs/source/usersguide/mgxs_library.rst index a5d2ec0d06..98a9e84859 100644 --- a/docs/source/usersguide/mgxs_library.rst +++ b/docs/source/usersguide/mgxs_library.rst @@ -8,10 +8,10 @@ OpenMC can be run in continuous-energy mode or multi-group mode, provided the nuclear data is available. In continuous-energy mode, the ``cross_sections.xml`` file contains necessary meta-data for each data set, including the name and a file system location where the complete library -can be found. In multi-group mode, this ``cross_sections.xml`` file contains +can be found. In multi-group mode, this ``mgxs.xml`` file contains this same meta-data describing the nuclide or material, but also contains the group-wise nuclear data. This portion of the manual describes the format of -the multi-group data library required to be used in the ``cross_sections.xml`` +the multi-group data library required to be used in the ``mgxs.xml`` file. Similar to the other input file types, the multi-group library is provided in @@ -22,9 +22,9 @@ materials. .. _XML: http://www.w3.org/XML/ ------------------------------------------------- -MGXS Library Specification -- cross_sections.xml ------------------------------------------------- +-------------------------------------- +MGXS Library Specification -- mgxs.xml +-------------------------------------- The multi-group library meta-data is contained within the groups_, group_structure_, and inverse_velocities_ elements. @@ -33,7 +33,7 @@ The actual multi-group data itself is contained within the xsdata_ element. .. _groups: ```` Element ----------------------------------- +-------------------- The ```` element has no attributes and simply provides the number of energy groups contained within the library. diff --git a/examples/python/pincell_multigroup/build-xml.py b/examples/python/pincell_multigroup/build-xml.py index c7d6dfc8be..5ac5b376a3 100644 --- a/examples/python/pincell_multigroup/build-xml.py +++ b/examples/python/pincell_multigroup/build-xml.py @@ -1,4 +1,3 @@ -import numpy as np import openmc import openmc.mgxs @@ -12,7 +11,7 @@ inactive = 10 particles = 1000 ############################################################################### -# Exporting to OpenMC mg_cross_sections.xml file +# Exporting to OpenMC mgxs.xml file ############################################################################### # Instantiate the energy group data @@ -22,45 +21,43 @@ groups = openmc.mgxs.EnergyGroups(group_edges=[1E-11, 0.0635E-6, 10.0E-6, # Instantiate the 7-group (C5G7) cross section data uo2_xsdata = openmc.XSdata('UO2.300K', groups) uo2_xsdata.order = 0 -uo2_xsdata.total = np.array([0.1779492, 0.3298048, 0.4803882, 0.5543674, - 0.3118013, 0.3951678, 0.5644058]) -uo2_xsdata.absorption = np.array([8.0248E-03, 3.7174E-03, 2.6769E-02, 9.6236E-02, - 3.0020E-02, 1.1126E-01, 2.8278E-01]) -scatter = [[[0.1275370, 0.0423780, 0.0000094, 0.0000000, 0.0000000, 0.0000000, 0.0000000], - [0.0000000, 0.3244560, 0.0016314, 0.0000000, 0.0000000, 0.0000000, 0.0000000], - [0.0000000, 0.0000000, 0.4509400, 0.0026792, 0.0000000, 0.0000000, 0.0000000], - [0.0000000, 0.0000000, 0.0000000, 0.4525650, 0.0055664, 0.0000000, 0.0000000], - [0.0000000, 0.0000000, 0.0000000, 0.0001253, 0.2714010, 0.0102550, 0.0000000], - [0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0012968, 0.2658020, 0.0168090], - [0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0085458, 0.2730800]]] -uo2_xsdata.scatter = np.array(scatter[:][:]) -uo2_xsdata.fission = np.array([7.21206E-03, 8.19301E-04, 6.45320E-03, - 1.85648E-02, 1.78084E-02, 8.30348E-02, - 2.16004E-01]) -uo2_xsdata.nu_fission = np.array([2.005998E-02, 2.027303E-03, 1.570599E-02, - 4.518301E-02, 4.334208E-02, 2.020901E-01, - 5.257105E-01]) -uo2_xsdata.chi = np.array([5.8791E-01, 4.1176E-01, 3.3906E-04, 1.1761E-07, - 0.0000E+00, 0.0000E+00, 0.0000E+00]) +uo2_xsdata.total = [0.1779492, 0.3298048, 0.4803882, 0.5543674, + 0.3118013, 0.3951678, 0.5644058] +uo2_xsdata.absorption = [8.0248E-03, 3.7174E-03, 2.6769E-02, 9.6236E-02, + 3.0020E-02, 1.1126E-01, 2.8278E-01] +uo2_xsdata.scatter = [[[0.1275370, 0.0423780, 0.0000094, 0.0000000, 0.0000000, 0.0000000, 0.0000000], + [0.0000000, 0.3244560, 0.0016314, 0.0000000, 0.0000000, 0.0000000, 0.0000000], + [0.0000000, 0.0000000, 0.4509400, 0.0026792, 0.0000000, 0.0000000, 0.0000000], + [0.0000000, 0.0000000, 0.0000000, 0.4525650, 0.0055664, 0.0000000, 0.0000000], + [0.0000000, 0.0000000, 0.0000000, 0.0001253, 0.2714010, 0.0102550, 0.0000000], + [0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0012968, 0.2658020, 0.0168090], + [0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0085458, 0.2730800]]] +uo2_xsdata.fission = [7.21206E-03, 8.19301E-04, 6.45320E-03, + 1.85648E-02, 1.78084E-02, 8.30348E-02, + 2.16004E-01] +uo2_xsdata.nu_fission = [2.005998E-02, 2.027303E-03, 1.570599E-02, + 4.518301E-02, 4.334208E-02, 2.020901E-01, + 5.257105E-01] +uo2_xsdata.chi = [5.8791E-01, 4.1176E-01, 3.3906E-04, 1.1761E-07, + 0.0000E+00, 0.0000E+00, 0.0000E+00] h2o_xsdata = openmc.XSdata('LWTR.300K', groups) h2o_xsdata.order = 0 -h2o_xsdata.total = np.array([0.15920605, 0.412969593, 0.59030986, 0.58435, - 0.718, 1.2544497, 2.650379]) -h2o_xsdata.absorption = np.array([6.0105E-04, 1.5793E-05, 3.3716E-04, - 1.9406E-03, 5.7416E-03, 1.5001E-02, - 3.7239E-02]) -scatter = [[[0.0444777, 0.1134000, 0.0007235, 0.0000037, 0.0000001, 0.0000000, 0.0000000], - [0.0000000, 0.2823340, 0.1299400, 0.0006234, 0.0000480, 0.0000074, 0.0000010], - [0.0000000, 0.0000000, 0.3452560, 0.2245700, 0.0169990, 0.0026443, 0.0005034], - [0.0000000, 0.0000000, 0.0000000, 0.0910284, 0.4155100, 0.0637320, 0.0121390], - [0.0000000, 0.0000000, 0.0000000, 0.0000714, 0.1391380, 0.5118200, 0.0612290], - [0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0022157, 0.6999130, 0.5373200], - [0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.1324400, 2.4807000]]] -h2o_xsdata.scatter = np.array(scatter) +h2o_xsdata.total = [0.15920605, 0.412969593, 0.59030986, 0.58435, + 0.718, 1.2544497, 2.650379] +h2o_xsdata.absorption = [6.0105E-04, 1.5793E-05, 3.3716E-04, + 1.9406E-03, 5.7416E-03, 1.5001E-02, + 3.7239E-02] +h2o_xsdata.scatter = [[[0.0444777, 0.1134000, 0.0007235, 0.0000037, 0.0000001, 0.0000000, 0.0000000], + [0.0000000, 0.2823340, 0.1299400, 0.0006234, 0.0000480, 0.0000074, 0.0000010], + [0.0000000, 0.0000000, 0.3452560, 0.2245700, 0.0169990, 0.0026443, 0.0005034], + [0.0000000, 0.0000000, 0.0000000, 0.0910284, 0.4155100, 0.0637320, 0.0121390], + [0.0000000, 0.0000000, 0.0000000, 0.0000714, 0.1391380, 0.5118200, 0.0612290], + [0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0022157, 0.6999130, 0.5373200], + [0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.0000000, 0.1324400, 2.4807000]]] mg_cross_sections_file = openmc.MGXSLibrary(groups) -mg_cross_sections_file.add_xsdatas([uo2_xsdata,h2o_xsdata]) +mg_cross_sections_file.add_xsdatas([uo2_xsdata, h2o_xsdata]) mg_cross_sections_file.export_to_xml() @@ -134,7 +131,7 @@ geometry.export_to_xml() # Instantiate a Settings object, set all runtime parameters, and export to XML settings_file = openmc.Settings() settings_file.energy_mode = "multi-group" -settings_file.cross_sections = "./mg_cross_sections.xml" +settings_file.cross_sections = "./mgxs.xml" settings_file.batches = batches settings_file.inactive = inactive settings_file.particles = particles diff --git a/openmc/material.py b/openmc/material.py index bf66cf11d4..6b2b07f2d4 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -348,7 +348,9 @@ class Material(object): del self._nuclides[nuclide._name] def add_macroscopic(self, macroscopic): - """Add a macroscopic to the material + """Add a macroscopic to the material. This will also set the + density of the material to 1.0, unless it has been otherwise set, + as a default for Macroscopic cross sections. Parameters ---------- @@ -386,6 +388,14 @@ class Material(object): 'Material!'.format(self._id, macroscopic) raise ValueError(msg) + # Generally speaking, the density for a macroscopic object will + # be 1.0. Therefore, lets set density to 1.0 so that the user + # doesnt need to set it unless its needed. + # Of course, if the user has already set a value of density, + # then we will not override it. + if self._density is None: + self.set_density('macro', 1.0) + def remove_macroscopic(self, macroscopic): """Remove a macroscopic from the material diff --git a/openmc/mgxs/library.py b/openmc/mgxs/library.py index ea856d735c..20302b6246 100644 --- a/openmc/mgxs/library.py +++ b/openmc/mgxs/library.py @@ -5,6 +5,9 @@ import pickle import warnings from numbers import Integral from collections import OrderedDict +from warnings import warn + +import numpy as np import openmc import openmc.mgxs @@ -254,7 +257,7 @@ class Library(object): @domain_type.setter def domain_type(self, domain_type): - cv.check_value('domain type', domain_type, tuple(openmc.mgxs.DOMAIN_TYPES)) + cv.check_value('domain type', domain_type, openmc.mgxs.DOMAIN_TYPES) self._domain_type = domain_type @domains.setter @@ -386,7 +389,7 @@ class Library(object): ---------- tallies_file : openmc.Tallies A Tallies collection to add each MGXS' tallies to generate a - "tallies.xml" input file for OpenMC + 'tallies.xml' input file for OpenMC merge : bool Indicate whether tallies should be merged when possible. Defaults to True. @@ -433,6 +436,7 @@ class Library(object): self._sp_filename = statepoint._f.filename self._openmc_geometry = statepoint.summary.openmc_geometry + self._nuclides = statepoint.summary.nuclides if statepoint.run_mode == 'k-eigenvalue': self._keff = statepoint.k_combined[0] @@ -456,7 +460,7 @@ class Library(object): ---------- domain : Material or Cell or Universe or Integral The material, cell, or universe object of interest (or its ID) - mgxs_type : {'total', 'transport', 'absorption', 'capture', 'fission', 'nu-fission', 'kappa-fission', 'scatter', 'nu-scatter', 'scatter matrix', 'nu-scatter matrix', 'chi'} + mgxs_type : {'total', 'transport', 'nu-transport', 'absorption', 'capture', 'fission', 'nu-fission', 'kappa-fission', 'scatter', 'nu-scatter', 'scatter matrix', 'nu-scatter matrix', 'chi'} The type of multi-group cross section object to return Returns @@ -486,7 +490,7 @@ class Library(object): if domain_id == domain.id: break else: - msg = 'Unable to find MGXS for {0} "{1}" in ' \ + msg = 'Unable to find MGXS for "{0}" "{1}" in ' \ 'library'.format(self.domain_type, domain_id) raise ValueError(msg) else: @@ -670,7 +674,7 @@ class Library(object): full_filename = os.path.join(directory, filename) full_filename = full_filename.replace(' ', '-') f = h5py.File(full_filename, 'w') - f.attrs["# groups"] = self.num_groups + f.attrs['# groups'] = self.num_groups f.close() # Export MGXS for each domain and mgxs type to an HDF5 file @@ -747,3 +751,395 @@ class Library(object): # Load and return pickled Library object return pickle.load(open(full_filename, 'rb')) + + def get_xsdata(self, domain, xsdata_name, nuclide='total', xs_type='macro', + xs_id='1m', order=None): + """Generates an openmc.XSdata object describing a multi-group cross section + data set for eventual combination in to an openmc.MGXSLibrary object + (i.e., the library). + + Parameters + ---------- + domain : openmc.Material or openmc.Cell or openmc.Universe + The domain for spatial homogenization + xsdata_name : str + Name to apply to the "xsdata" entry produced by this method + nuclide : str + A nuclide name string (e.g., 'U-235'). Defaults to 'total' to + obtain a material-wise macroscopic cross section. + xs_type: {'macro', 'micro'} + Provide the macro or micro cross section in units of cm^-1 or + barns. Defaults to 'macro'. If the Library object is not tallied by + nuclide this will be set to 'macro' regardless. + xs_ids : str + Cross section set identifier. Defaults to '1m'. + order : Scattering order for this data entry. Default is None, + which will set the XSdata object to use the order of the + Library. + + Returns + ------- + xsdata : openmc.XSdata + Multi-Group Cross Section data set object. + + Raises + ------ + ValueError + When the Library object is initialized with insufficient types of + cross sections for the Library. + + See also + -------- + Library.create_mg_library() + + """ + + cv.check_type('domain', domain, (openmc.Material, openmc.Cell, + openmc.Cell)) + cv.check_type('xsdata_name', xsdata_name, basestring) + cv.check_type('nuclide', nuclide, basestring) + cv.check_value('xs_type', xs_type, ['macro', 'micro']) + cv.check_type('xs_id', xs_id, basestring) + cv.check_type('order', order, (type(None), Integral)) + if order is not None: + cv.check_greater_than('order', order, 0, equality=True) + cv.check_less_than('order', order, 10, equality=True) + + # Make sure statepoint has been loaded + if self._sp_filename is None: + msg = 'A StatePoint must be loaded before calling ' \ + 'the create_mg_library() function' + raise ValueError(msg) + + # If gathering material-specific data, set the xs_type to macro + if not self.by_nuclide: + xs_type = 'macro' + + # Build & add metadata to XSdata object + name = xsdata_name + if nuclide is not 'total': + name += '_' + nuclide + name += '.' + xs_id + xsdata = openmc.XSdata(name, self.energy_groups) + + if order is None: + # Set the order to the Library's order (the defualt behavior) + xsdata.order = self.legendre_order + else: + # Set the order of the xsdata object to the minimum of + # the provided order or the Library's order. + xsdata.order = min(order, self.legendre_order) + + if nuclide is not 'total': + xsdata.zaid = self._nuclides[nuclide][0] + xsdata.awr = self._nuclides[nuclide][1] + + # Now get xs data itself + if 'nu-transport' in self.mgxs_types and self.correction == 'P0': + mymgxs = self.get_mgxs(domain, 'nu-transport') + xsdata.set_total_mgxs(mymgxs, xs_type=xs_type, nuclide=[nuclide]) + elif 'total' in self.mgxs_types: + mymgxs = self.get_mgxs(domain, 'total') + xsdata.set_total_mgxs(mymgxs, xs_type=xs_type, nuclide=[nuclide]) + if 'absorption' in self.mgxs_types: + mymgxs = self.get_mgxs(domain, 'absorption') + xsdata.set_absorption_mgxs(mymgxs, xs_type=xs_type, + nuclide=[nuclide]) + if 'fission' in self.mgxs_types: + mymgxs = self.get_mgxs(domain, 'fission') + xsdata.set_fission_mgxs(mymgxs, xs_type=xs_type, + nuclide=[nuclide]) + if 'kappa-fission' in self.mgxs_types: + mymgxs = self.get_mgxs(domain, 'kappa-fission') + xsdata.set_kappa_fission_mgxs(mymgxs, xs_type=xs_type, + nuclide=[nuclide]) + if 'chi' in self.mgxs_types: + mymgxs = self.get_mgxs(domain, 'chi') + xsdata.set_chi_mgxs(mymgxs, xs_type=xs_type, nuclide=[nuclide]) + if 'nu-fission' in self.mgxs_types: + mymgxs = self.get_mgxs(domain, 'nu-fission') + xsdata.set_nu_fission_mgxs(mymgxs, xs_type=xs_type, + nuclide=[nuclide]) + # multiplicity requires scatter and nu-scatter + if ((('scatter matrix' in self.mgxs_types) and + ('nu-scatter matrix' in self.mgxs_types))): + scatt_mgxs = self.get_mgxs(domain, 'scatter matrix') + nuscatt_mgxs = self.get_mgxs(domain, 'nu-scatter matrix') + xsdata.set_multiplicity_mgxs(nuscatt_mgxs, scatt_mgxs, + xs_type=xs_type, nuclide=[nuclide]) + using_multiplicity = True + else: + using_multiplicity = False + + if using_multiplicity: + nuscatt_mgxs = self.get_mgxs(domain, 'nu-scatter matrix') + xsdata.set_scatter_mgxs(nuscatt_mgxs, xs_type=xs_type, + nuclide=[nuclide]) + else: + if 'nu-scatter matrix' in self.mgxs_types: + nuscatt_mgxs = self.get_mgxs(domain, 'nu-scatter matrix') + xsdata.set_scatter_mgxs(nuscatt_mgxs, xs_type=xs_type, + nuclide=[nuclide]) + + # Since we are not using multiplicity, then + # scattering multiplication (nu-scatter) must be + # accounted for approximately by using an adjusted + # absorption cross section. + if 'total' in self.mgxs_types: + xsdata._absorption = \ + np.subtract(xsdata.total, + np.sum(xsdata.scatter[0, :, :], axis=1)) + + return xsdata + + def create_mg_library(self, xs_type='macro', xsdata_names=None, + xs_ids=None): + """Creates an openmc.MGXSLibrary object to contain the MGXS data for the + Multi-Group mode of OpenMC. + + Parameters + ---------- + xs_type: {'macro', 'micro'} + Provide the macro or micro cross section in units of cm^-1 or + barns. Defaults to 'macro'. If the Library object is not tallied by + nuclide this will be set to 'macro' regardless. + xsdata_names : Iterable of str + List of names to apply to the "xsdata" entries in the + resultant mgxs data file. Defaults to 'set1', 'set2', ... + xs_ids : str or Iterable of str + Cross section set identifier (i.e., '71c') for all + data sets (if only str) or for each individual one + (if iterable of str). Defaults to '1m'. + + Returns + ------- + mgxs_file : openmc.MGXSLibrary + Multi-Group Cross Section File that is ready to be printed to the + file of choice by the user. + + Raises + ------ + ValueError + When the Library object is initialized with insufficient types of + cross sections for the Library. + + See also + -------- + Library.dump_to_file() + + """ + + # Check to ensure the Library contains the correct + # multi-group cross section types + self.check_library_for_openmc_mgxs() + + cv.check_value('xs_type', xs_type, ['macro', 'micro']) + if xsdata_names is not None: + cv.check_iterable_type('xsdata_names', xsdata_names, basestring) + if xs_ids is not None: + if isinstance(xs_ids, basestring): + # If we only have a string lets convert it now to a list + # of strings. + xs_ids = [xs_ids for i in range(len(self.domains))] + else: + cv.check_iterable_type('xs_ids', xs_ids, basestring) + else: + xs_ids = ['1m' for i in range(len(self.domains))] + + # If gathering material-specific data, set the xs_type to macro + if not self.by_nuclide: + xs_type = 'macro' + + # Initialize file + mgxs_file = openmc.MGXSLibrary(self.energy_groups) + + # Create the xsdata object and add it to the mgxs_file + for i, domain in enumerate(self.domains): + if self.by_nuclide: + nuclides = list(domain.get_all_nuclides().keys()) + else: + nuclides = ['total'] + for nuclide in nuclides: + # Build & add metadata to XSdata object + if xsdata_names is None: + xsdata_name = 'set' + str(i + 1) + else: + xsdata_name = xsdata_names[i] + if nuclide is not 'total': + xsdata_name += '_' + nuclide + + xsdata = self.get_xsdata(domain, xsdata_name, nuclide=nuclide, + xs_type=xs_type, xs_id=xs_ids[i]) + + mgxs_file.add_xsdata(xsdata) + + return mgxs_file + + def create_mg_library_and_materials(self, xsdata_names=None, xs_ids=None, + material_ids=None): + """Creates an openmc.MGXSLibrary object to contain the MGXS data for the + Multi-Group mode of OpenMC as well as the associated openmc.Materials + objects. This method cannot be used for Library objects with + `Library.by_nuclide == True` since the materials to output would be + problem dependent and thus any Materials object produced by this method + would not be useful. + + Parameters + ---------- + xsdata_names : Iterable of str + List of names to apply to the "xsdata" entries in the + resultant mgxs data file. Defaults to 'set1', 'set2', ... + xs_ids : str or Iterable of str + Cross section set identifier (i.e., '71c') for all + data sets (if only str) or for each individual one + (if iterable of str). Defaults to '1m'. + material_ids : None or Iterable of Integral + An optional list of material IDs to pass to the materials in + materials_file. Defaults to `None` implying the materials will be + given an ID number which matches the index of the domain in + `self.domains` + + Returns + ------- + mgxs_file : openmc.MGXSLibrary + Multi-Group Cross Section File that is ready to be printed to the + file of choice by the user. + materials_file : openmc.Materials + Materials file ready to be printed with all the macroscopic data + present within this Library. + + Raises + ------ + ValueError + When the Library object is initialized with insufficient types of + cross sections for the Library. + + See also + -------- + Library.create_mg_library() + Library.dump_to_file() + + """ + + # Check to ensure the Library contains the correct + # multi-group cross section types + self.check_library_for_openmc_mgxs() + + if xsdata_names is not None: + cv.check_iterable_type('xsdata_names', xsdata_names, basestring) + if xs_ids is not None: + if isinstance(xs_ids, basestring): + # If we only have a string lets convert it now to a list + # of strings. + xs_ids = [xs_ids for i in range(len(self.domains))] + else: + cv.check_iterable_type('xs_ids', xs_ids, basestring) + else: + xs_ids = ['1m' for i in range(len(self.domains))] + if material_ids is not None: + cv.check_iterable_type('material_ids', material_ids, Integral) + xs_type = 'macro' + + # Initialize files + mgxs_file = openmc.MGXSLibrary(self.energy_groups) + + materials = [] + macroscopics = [] + nuclide = 'total' + # Create the xsdata object and add it to the mgxs_file + for i, domain in enumerate(self.domains): + # Build & add metadata to XSdata object + if xsdata_names is None: + xsdata_name = 'set' + str(i + 1) + else: + xsdata_name = xsdata_names[i] + + xsdata = self.get_xsdata(domain, xsdata_name, nuclide=nuclide, + xs_type=xs_type, xs_id=xs_ids[i]) + + mgxs_file.add_xsdata(xsdata) + + macroscopics.append(openmc.Macroscopic(name=xsdata_name, + xs=xs_ids[i])) + if material_ids is not None: + mat_id = material_ids[i] + else: + mat_id = i + materials.append(openmc.Material(name=xsdata_name + '.' + + xs_ids[i], material_id=mat_id)) + materials[-1].add_macroscopic(macroscopics[-1]) + + materials_file = openmc.Materials(materials) + + return (mgxs_file, materials_file) + + def check_library_for_openmc_mgxs(self): + """This routine will check the MGXS Types within a Library + to ensure the MGXS types provided can be used to create + a MGXS Library for OpenMC's Multi-Group mode. + + The rules to check include: + + - Either total or transport should be present. + + - Both can be available if one wants, but we should + use whatever corresponds to Library.correction (if P0: transport) + + - Absorption and total (or transport) are required. + - A nu-fission cross section and chi values are not required as a + fixed source problem could be the target. + - Fission and kappa-fission are not required as they are only + needed to support tallies the user may wish to request. + - A nu-scatter matrix is required. + + - Having both nu-scatter (of any order) and scatter + (at least isotropic) matrices is preferred + - If only nu-scatter, need total (not transport), to + be used in adjusting absorption + (i.e., reduced_abs = tot - nuscatt) + + See also + -------- + Library.create_mg_library() + + """ + + error_flag = False + # Ensure absorption is present + if 'absorption' not in self.mgxs_types: + error_flag = True + msg = '"absorption" MGXS type is required but not provided.' + warn(msg) + # Ensure nu-scattering matrix is required + if 'nu-scatter matrix' not in self.mgxs_types: + error_flag = True + msg = '"nu-scatter matrix" MGXS type is required but not provided.' + warn(msg) + else: + # Ok, now see the status of scatter + if 'scatter matrix' not in self.mgxs_types: + # We dont have both nu-scatter and scatter, therefore + # we need total, and not transport. + if 'total' not in self.mgxs_types: + error_flag = True + msg = '"total" MGXS type is required if a ' \ + 'scattering matrix is not provided.' + warn(msg) + # Total or transport can be present, but if using + # self.correction=="P0", then we should use transport. + if (((self.correction is "P0") and + ('nu-transport' not in self.mgxs_types))): + error_flag = True + msg = 'A "nu-transport" MGXS type is required since a "P0" ' \ + 'correction is applied, but a "nu-transport" MGXS is ' \ + 'not provided.' + warn(msg) + elif (((self.correction is None) and + ('total' not in self.mgxs_types))): + error_flag = True + msg = '"total" MGXS type is required, but not provided.' + warn(msg) + + if error_flag: + msg = 'Invalid MGXS configuration encountered.' + raise ValueError(msg) diff --git a/openmc/mgxs/mgxs.py b/openmc/mgxs/mgxs.py index 56bb9ea89b..5be84bb2ce 100644 --- a/openmc/mgxs/mgxs.py +++ b/openmc/mgxs/mgxs.py @@ -376,7 +376,7 @@ class MGXS(object): @domain_type.setter def domain_type(self, domain_type): - cv.check_value('domain type', domain_type, tuple(DOMAIN_TYPES)) + cv.check_value('domain type', domain_type, DOMAIN_TYPES) self._domain_type = domain_type @energy_groups.setter diff --git a/openmc/mgxs_library.py b/openmc/mgxs_library.py index d3e49b238e..88ae05808a 100644 --- a/openmc/mgxs_library.py +++ b/openmc/mgxs_library.py @@ -1,19 +1,19 @@ from collections import Iterable from numbers import Real, Integral from xml.etree import ElementTree as ET -import warnings import sys -if sys.version_info[0] >= 3: - basestring = str import numpy as np import openmc -from openmc.mgxs import EnergyGroups +import openmc.mgxs from openmc.checkvalue import check_type, check_value, check_greater_than, \ - check_iterable_type + check_iterable_type from openmc.clean_xml import * +if sys.version_info[0] >= 3: + basestring = str + # Supported incoming particle MGXS angular treatment representations _REPRESENTATIONS = ['isotropic', 'angle'] @@ -99,6 +99,12 @@ class XSdata(object): Unique identifier for the xsdata object alias : str Separate unique identifier for the xsdata object + zaid : int + 1000*(atomic number) + mass number. As an example, the zaid of U-235 + would be 92235. + awr : float + Atomic weight ratio of an isotope. That is, the ratio of the mass + of the isotope to the mass of a single neutron. kT : float Temperature (in units of MeV). energy_groups : openmc.mgxs.EnergyGroups @@ -116,12 +122,27 @@ class XSdata(object): Legendre polynomial form). Dict contains two keys: 'enable' and 'num_points'. 'enable' is a boolean and 'num_points' is the number of points to use, if 'enable' is True. + representation : {'isotropic', 'angle'} + Method used in generating the MGXS (isotropic or angle-dependent flux + weighting). num_azimuthal : int Number of equal width angular bins that the azimuthal angular domain is subdivided into. This only applies when ``representation`` is "angle". num_polar : int Number of equal width angular bins that the polar angular domain is subdivided into. This only applies when ``representation`` is "angle". + vector_shape : iterable of int + Dimensionality of vector multi-group cross sections (e.g., the total + cross section). The return result depends on the value of + ``representation``. + matrix_shape : iterable of int + Dimensionality of matrix multi-group cross sections (e.g., the + fission matrix cross section). The return result depends on the + value of ``representation``. + pn_matrix_shape : iterable of int + Dimensionality of scattering matrix data (e.g., the + scattering matrix cross section). The return result depends on the + value of ``representation``. total : numpy.ndarray Group-wise total cross section ordered by increasing group index (i.e., fast to thermal). If ``representation`` is "isotropic", then the length @@ -133,9 +154,9 @@ class XSdata(object): angles and outer-dimension being the polar angles. absorption : numpy.ndarray Group-wise absorption cross section ordered by increasing group index - (i.e., fast to thermal). If ``representation`` is "isotropic", then the + (i.e., fast to thermal). If ``representation`` is "isotropic", then the length of this list should equal the number of groups described in the - ``groups`` attribute. If ``representation`` is "angle", then the length + ``groups`` attribute. If ``representation`` is "angle", then the length of this list should equal the number of groups times the number of azimuthal angles times the number of polar angles, with the inner-dimension being groups, intermediate-dimension being azimuthal @@ -152,7 +173,7 @@ class XSdata(object): multiplicity : numpy.ndarray Ratio of neutrons produced in scattering collisions to the neutrons which undergo scattering collisions; that is, the multiplicity provides - the code with a scaling factor to account for neutrons being produced in + the code with a scaling factor to account for neutrons produced in (n,xn) reactions. This information is assumed isotropic and therefore does not need to be repeated for every Legendre moment or histogram/tabular bin. This matrix follows the same arrangement as @@ -160,30 +181,30 @@ class XSdata(object): needed to provide the scattering type information. fission : numpy.ndarray Group-wise fission cross section ordered by increasing group index - (i.e., fast to thermal). If ``representation`` is "isotropic", then the + (i.e., fast to thermal). If ``representation`` is "isotropic", then the length of this list should equal the number of groups described in the - ``groups`` attribute. If ``representation`` is "angle", then the length + ``groups`` attribute. If ``representation`` is "angle", then the length of this list should equal the number of groups times the number of azimuthal angles times the number of polar angles, with the inner-dimension being groups, intermediate-dimension being azimuthal angles and outer-dimension being the polar angles. - k_fission : numpy.ndarray - Group-wise kappa-fission cross section ordered by increasing group index - (i.e., fast to thermal). If ``representation`` is "isotropic", then the - length of this list should equal the number of groups described in the - ``groups`` attribute. If ``representation`` is "angle", then the length + kappa_fission : numpy.ndarray + Group-wise kappa-fission cross section ordered by increasing group + index (i.e., fast to thermal). If ``representation`` is "isotropic", + then the length of this list should equal the number of groups in the + ``groups`` attribute. If ``representation`` is "angle", then the length of this list should equal the number of groups times the number of azimuthal angles times the number of polar angles, with the inner-dimension being groups, intermediate-dimension being azimuthal angles and outer-dimension being the polar angles. chi : numpy.ndarray - Group-wise fission spectra ordered by increasing group index (i.e., fast - to thermal). This attribute should be used if making the common + Group-wise fission spectra ordered by increasing group index (i.e., + fast to thermal). This attribute should be used if making the common approximation that the fission spectra does not depend on incoming - energy. If the user does not wish to make this approximation, then this - should not be provided and this information included in the + energy. If the user does not wish to make this approximation, then + this should not be provided and this information included in the ``nu_fission`` element instead. If ``representation`` is "isotropic", - then the length of this list should equal the number of groups described + then the length of this list should equal the number of groups in the ``groups`` element. If ``representation`` is "angle", then the length of this list should equal the number of groups times the number of azimuthal angles times the number of polar angles, with the @@ -191,18 +212,21 @@ class XSdata(object): angles and outer-dimension being the polar angles. nu_fission : numpy.ndarray Group-wise fission production cross section vector (i.e., if ``chi`` is - provided), or is the group-wise fission production matrix. If providing + provided), or is the group-wise fission production matrix. If providing the vector, it should be ordered the same as the ``fission`` data. If providing the matrix, it should be ordered the same as the ``multiplicity`` matrix. """ - def __init__(self, name, energy_groups, representation="isotropic"): + + def __init__(self, name, energy_groups, representation='isotropic'): # Initialize class attributes self._name = name self._energy_groups = energy_groups self._representation = representation self._alias = None + self._zaid = None + self._awr = None self._kT = None self._fissionable = False self._scatt_type = 'legendre' @@ -216,7 +240,7 @@ class XSdata(object): self._multiplicity = None self._fission = None self._nu_fission = None - self._k_fission = None + self._kappa_fission = None self._chi = None self._use_chi = None @@ -236,6 +260,14 @@ class XSdata(object): def alias(self): return self._alias + @property + def zaid(self): + return self._zaid + + @property + def awr(self): + return self._awr + @property def kT(self): return self._kT @@ -285,8 +317,8 @@ class XSdata(object): return self._nu_fission @property - def k_fission(self): - return self._k_fission + def kappa_fission(self): + return self._kappa_fission @property def chi(self): @@ -300,6 +332,34 @@ class XSdata(object): else: return self._order + @property + def vector_shape(self): + if self.representation is 'isotropic': + return (self.energy_groups.num_groups,) + elif self.representation is 'angle': + return (self.num_polar, self.num_azimuthal, + self.energy_groups.num_groups) + + @property + def matrix_shape(self): + if self.representation is 'isotropic': + return (self.energy_groups.num_groups, + self.energy_groups.num_groups) + elif self.representation is 'angle': + return (self.num_polar, self.num_azimuthal, + self.energy_groups.num_groups, + self.energy_groups.num_groups) + + @property + def pn_matrix_shape(self): + if self.representation is 'isotropic': + return (self.num_orders, self.energy_groups.num_groups, + self.energy_groups.num_groups) + elif self.representation is 'angle': + return (self.num_polar, self.num_azimuthal, self.num_orders, + self.energy_groups.num_groups, + self.energy_groups.num_groups) + @name.setter def name(self, name): check_type('name for XSdata', name, basestring) @@ -308,15 +368,12 @@ class XSdata(object): @energy_groups.setter def energy_groups(self, energy_groups): # Check validity of energy_groups - check_type("energy_groups", energy_groups, EnergyGroups) + check_type('energy_groups', energy_groups, openmc.mgxs.EnergyGroups) - # Check that there is one or more groups - if ((energy_groups.num_groups is None) or - (energy_groups.num_groups < 1)): - - msg = 'energy_groups object incorrectly initialized.' + if energy_group.group_edges is None: + msg = 'Unable to assign an EnergyGroups object ' \ + 'with uninitialized group edges' raise ValueError(msg) - self._energy_groups = energy_groups @representation.setter @@ -333,37 +390,52 @@ class XSdata(object): else: self._alias = self._name + @zaid.setter + def zaid(self, zaid): + # Check type and value + check_type('zaid', zaid, Integral) + check_greater_than('zaid', zaid, 0) + self._zaid = zaid + + @awr.setter + def awr(self, awr): + # Check validity of type and that the awr value is > 0 + check_type('awr', awr, Real) + check_greater_than('awr', awr, 0.0) + self._awr = awr + @kT.setter def kT(self, kT): # Check validity of type and that the kT value is >= 0 - check_type("kT", kT, Real) - check_greater_than("kT", kT, 0.0, equality=True) + check_type('kT', kT, Real) + check_greater_than('kT', kT, 0.0, equality=True) self._kT = kT @scatt_type.setter def scatt_type(self, scatt_type): # check to see it is of a valid type and value - check_value("scatt_type", scatt_type, ['legendre', 'histogram', + check_value('scatt_type', scatt_type, ['legendre', 'histogram', 'tabular']) self._scatt_type = scatt_type @order.setter def order(self, order): # Check type and value - check_type("order", order, Integral) - check_greater_than("order", order, 0, equality=True) + check_type('order', order, Integral) + check_greater_than('order', order, 0, equality=True) self._order = order @tabular_legendre.setter def tabular_legendre(self, tabular_legendre): # Check to make sure this is a dict and it has our keys with the # right values. - check_type("tabular_legendre", tabular_legendre, dict) + check_type('tabular_legendre', tabular_legendre, dict) if 'enable' in tabular_legendre: enable = tabular_legendre['enable'] check_type('enable', enable, bool) else: - msg = "enable must be provided in tabular_legendre" + msg = 'The tabular_legendre dict must include a value keyed by ' \ + '"enable"' raise ValueError(msg) if 'num_points' in tabular_legendre: num_points = tabular_legendre['num_points'] @@ -379,200 +451,503 @@ class XSdata(object): @num_polar.setter def num_polar(self, num_polar): # Make sure we have positive ints - check_value("num_polar", num_polar, Integral) - check_greater_than("num_polar", num_polar, 0) + check_value('num_polar', num_polar, Integral) + check_greater_than('num_polar', num_polar, 0) self._num_polar = num_polar @num_azimuthal.setter def num_azimuthal(self, num_azimuthal): - check_value("num_azimuthal", num_azimuthal, Integral) - check_greater_than("num_azimuthal", num_azimuthal, 0) + check_value('num_azimuthal', num_azimuthal, Integral) + check_greater_than('num_azimuthal', num_azimuthal, 0) self._num_azimuthal = num_azimuthal @total.setter def total(self, total): - if self._representation is 'isotropic': - shape = (self._energy_groups.num_groups,) - elif self._representation is 'angle': - shape = (self._num_polar, self._num_azimuthal, - self._energy_groups.num_groups) - # check we have a numpy list - check_type("total", total, np.ndarray, expected_iter_type=Real) - if total.shape == shape: - self._total = np.copy(total) - else: - msg = 'Shape of provided total "{0}" does not match shape ' \ - 'required, "{1}"'.format(total.shape, shape) - raise ValueError(msg) + check_type('total', total, Iterable, expected_iter_type=Real) + # Convert to a numpy array so we can easily get the shape for + # checking + nptotal = np.asarray(total) + check_value('total shape', nptotal.shape, [self.vector_shape]) + + self._total = nptotal @absorption.setter def absorption(self, absorption): - if self._representation is 'isotropic': - shape = (self._energy_groups.num_groups,) - elif self._representation is 'angle': - shape = (self._num_polar, self._num_azimuthal, - self._energy_groups.num_groups) - # check we have a numpy list - check_type("absorption", absorption, np.ndarray, expected_iter_type=Real) - if absorption.shape == shape: - self._absorption = np.copy(absorption) - else: - msg = 'Shape of provided absorption "{0}" does not match shape ' \ - 'required, "{1}"'.format(absorption.shape, shape) - raise ValueError(msg) + check_type('absorption', absorption, Iterable, expected_iter_type=Real) + # Convert to a numpy array so we can easily get the shape for + # checking + npabsorption = np.asarray(absorption) + check_value('absorption shape', npabsorption.shape, + [self.vector_shape]) + + self._absorption = npabsorption @fission.setter def fission(self, fission): - if self._representation is 'isotropic': - shape = (self._energy_groups.num_groups,) - elif self._representation is 'angle': - shape = (self._num_polar, self._num_azimuthal, - self._energy_groups.num_groups) - # check we have a numpy list - check_type("fission", fission, np.ndarray, expected_iter_type=Real) - if fission.shape == shape: - self._fission = np.copy(fission) - if np.sum(self._fission) > 0.0: - self._fissionable = True - else: - msg = 'Shape of provided fission "{0}" does not match shape ' \ - 'required, "{1}"'.format(fission.shape, shape) - raise ValueError(msg) + check_type('fission', fission, Iterable, expected_iter_type=Real) + # Convert to a numpy array so we can easily get the shape for + # checking + npfission = np.asarray(fission) + check_value('fission shape', npfission.shape, [self.vector_shape]) - @k_fission.setter - def k_fission(self, k_fission): - if self._representation is 'isotropic': - shape = (self._energy_groups.num_groups,) - elif self._representation is 'angle': - shape = (self._num_polar, self._num_azimuthal, - self._energy_groups.num_groups) - # check we have a numpy list - check_type("k_fission", k_fission, np.ndarray, expected_iter_type=Real) - if k_fission.shape == shape: - self._k_fission = np.copy(k_fission) - if np.sum(self._k_fission) > 0.0: - self._fissionable = True - else: - msg = 'Shape of provided k_fission "{0}" does not match shape ' \ - 'required, "{1}"'.format(k_fission.shape, shape) - raise ValueError(msg) + self._fission = npfission + + if np.sum(self._fission) > 0.0: + self._fissionable = True + + @kappa_fission.setter + def kappa_fission(self, kappa_fission): + check_type('kappa_fission', kappa_fission, Iterable, + expected_iter_type=Real) + # Convert to a numpy array so we can easily get the shape for + # checking + npkappa_fission = np.asarray(kappa_fission) + check_value('kappa fission shape', npkappa_fission.shape, + [self.vector_shape]) + + self._kappa_fission = npkappa_fission + + if np.sum(self._kappa_fission) > 0.0: + self._fissionable = True @chi.setter def chi(self, chi): - if not self._use_chi: - msg = 'Providing chi when nu_fission already provided as matrix!' - raise ValueError(msg) - if self._representation is 'isotropic': - shape = (self._energy_groups.num_groups,) - elif self._representation is 'angle': - shape = (self._num_polar, self._num_azimuthal, - self._energy_groups.num_groups) - # check we have a numpy list - check_type("chi", chi, np.ndarray, expected_iter_type=Real) - if chi.shape == shape: - self._chi = np.copy(chi) - else: - msg = 'Shape of provided chi "{0}" does not match shape ' \ - 'required, "{1}"'.format(chi.shape, shape) + if self._use_chi is not None: + if not self._use_chi: + msg = 'Providing chi when nu_fission already provided as a' \ + 'matrix' + raise ValueError(msg) + + check_type('chi', chi, Iterable, expected_iter_type=Real) + # Convert to a numpy array so we can easily get the shape for + # checking + npchi = np.asarray(chi) + # Check the shape + if npchi.shape != self.vector_shape: + msg = 'Provided chi iterable does not have the expected shape.' raise ValueError(msg) + + self._chi = npchi + if self._use_chi is not None: self._use_chi = True @scatter.setter def scatter(self, scatter): - if self._representation is 'isotropic': - shape = (self.num_orders, self._energy_groups.num_groups, - self._energy_groups.num_groups) - max_depth = 3 - elif self._representation is 'angle': - shape = (self._num_polar, self._num_azimuthal, self.num_orders, - self._energy_groups.num_groups, - self._energy_groups.num_groups) - max_depth = 5 - # check we have a numpy list - check_iterable_type("scatter", scatter, expected_type=Real, - max_depth=max_depth) - if scatter.shape == shape: - self._scatter = np.copy(scatter) - else: - msg = 'Shape of provided scatter "{0}" does not match shape ' \ - 'required, "{1}"'.format(scatter.shape, shape) - raise ValueError(msg) + # Convert to a numpy array so we can easily get the shape for + # checking + npscatter = np.asarray(scatter) + check_iterable_type('scatter', npscatter, Real, + max_depth=len(npscatter.shape)) + check_value('scatter shape', npscatter.shape, [self.pn_matrix_shape]) + + self._scatter = npscatter @multiplicity.setter def multiplicity(self, multiplicity): - if self._representation is 'isotropic': - shape = (self._energy_groups.num_groups, - self._energy_groups.num_groups) - max_depth = 2 - elif self._representation is 'angle': - shape = (self._num_polar, self._num_azimuthal, - self._energy_groups.num_groups, - self._energy_groups.num_groups) - max_depth = 4 - # check we have a numpy list - check_iterable_type("multiplicity", multiplicity, expected_type=Real, - max_depth=max_depth) - if multiplicity.shape == shape: - self._multiplicity = np.copy(multiplicity) - else: - msg = 'Shape of provided multiplicity "{0}" does not match shape ' \ - 'required, "{1}"'.format(multiplicity.shape, shape) - raise ValueError(msg) + # Convert to a numpy array so we can easily get the shape for + # checking + npmultiplicity = np.asarray(multiplicity) + check_iterable_type('multiplicity', npmultiplicity, Real, + max_depth=len(npmultiplicity.shape)) + check_value('multiplicity shape', npmultiplicity.shape, + [self.matrix_shape]) + + self._multiplicity = npmultiplicity @nu_fission.setter def nu_fission(self, nu_fission): + # The NuFissionXS class does not have the capability to produce + # a fission matrix and therefore if this path is pursued, we know + # chi must be used. # nu_fission can be given as a vector or a matrix # Vector is used when chi also exists. # Matrix is used when chi does not exist. # We have to check that the correct form is given, but only if # chi already has been set. If not, we just check that this is OK - # and set the use_chi flag. + # and set the use_chi flag accordingly - # First lets set our dimensions here since they get used repeatedly - # throughout this code. - if self._representation is 'isotropic': - shape_vec = (self._energy_groups.num_groups,) - shape_mat = (self._energy_groups.num_groups, - self._energy_groups.num_groups) - elif self._representation is 'angle': - shape_vec = (self._num_polar, self._num_azimuthal, - self._energy_groups.num_groups) - shape_mat = (self._num_polar, self._num_azimuthal, - self._energy_groups.num_groups, - self._energy_groups.num_groups) + # Convert to a numpy array so we can easily get the shape for + # checking + npnu_fission = np.asarray(nu_fission) + + check_iterable_type('nu_fission', npnu_fission, Real, + max_depth=len(npnu_fission.shape)) - # Begin by checking the case when chi has already been given and thus - # the rules for filling in nu_fission are set. if self._use_chi is not None: if self._use_chi: - shape = shape_vec + check_value('nu_fission shape', npnu_fission.shape, + [self.vector_shape]) else: - shape = shape_mat - if nu_fission.shape != shape: - msg = "Invalid Shape of Nu_fission!" - raise ValueError(msg) + check_value('nu_fission shape', npnu_fission.shape, + [self.matrix_shape]) else: - # Get shape of nu_fission so we can figure if we need chi or not - if nu_fission.shape == shape_vec: + check_value('nu_fission shape', npnu_fission.shape, + [self.vector_shape, self.matrix_shape]) + # Find out if we have a nu-fission matrix or vector + # and set a flag to allow other methods to check this later. + if npnu_fission.shape == self.vector_shape: self._use_chi = True - shape = shape_vec - elif nu_fission.shape == shape_mat: - self._use_chi = False - shape = shape_mat else: - msg = "Invalid Shape of Nu_fission!" - raise ValueError(msg) + self._use_chi = False - # check we have a numpy list - check_type("nu_fission", nu_fission, np.ndarray, expected_iter_type=Real) - self._nu_fission = np.copy(nu_fission) + self._nu_fission = npnu_fission if np.sum(self._nu_fission) > 0.0: self._fissionable = True + def set_total_mgxs(self, total, nuclide='total', xs_type='macro'): + """This method allows for an openmc.mgxs.TotalXS or + openmc.mgxs.TransportXS to be used to set the total cross section + for this XSdata object. + + Parameters + ---------- + total: openmc.mgxs.TotalXS or openmc.mgxs.TransportXS + MGXS Object containing the total or transport cross section + for the domain of interest. + nuclide : str + Individual nuclide (or 'total' if obtaining material-wise data) + to gather data for. Defaults to 'total'. + xs_type: {'macro', 'micro'} + Provide the macro or micro cross section in units of cm^-1 or + barns. Defaults to 'macro'. + + See also + -------- + openmc.mgxs.Library.create_mg_library() + openmc.mgxs.Library.get_xsdata + + """ + + check_type('total', total, (openmc.mgxs.TotalXS, + openmc.mgxs.TransportXS)) + check_value('energy_groups', total.energy_groups, [self.energy_groups]) + check_value('domain_type', total.domain_type, + ['universe', 'cell', 'material']) + + if self.representation is 'isotropic': + self._total = total.get_xs(nuclides=nuclide, xs_type=xs_type) + elif self.representation is 'angle': + msg = 'Angular-Dependent MGXS have not yet been implemented' + raise ValueError(msg) + + def set_absorption_mgxs(self, absorption, nuclide='total', + xs_type='macro'): + """This method allows for an openmc.mgxs.AbsorptionXS + to be used to set the absorption cross section for this XSdata object. + + Parameters + ---------- + absorption: openmc.mgxs.AbsorptionXS + MGXS Object containing the absorption cross section + for the domain of interest. + nuclide : str + Individual nuclide (or 'total' if obtaining material-wise data) + to gather data for. Defaults to 'total'. + xs_type: {'macro', 'micro'} + Provide the macro or micro cross section in units of cm^-1 or + barns. Defaults to 'macro'. + + See also + -------- + openmc.mgxs.Library.create_mg_library() + openmc.mgxs.Library.get_xsdata + + """ + + check_type('absorption', absorption, openmc.mgxs.AbsorptionXS) + check_value('energy_groups', absorption.energy_groups, + [self.energy_groups]) + check_value('domain_type', absorption.domain_type, + ['universe', 'cell', 'material']) + + if self.representation is 'isotropic': + self._absorption = absorption.get_xs(nuclides=nuclide, + xs_type=xs_type) + elif self.representation is 'angle': + msg = 'Angular-Dependent MGXS have not yet been implemented' + raise ValueError(msg) + + def set_fission_mgxs(self, fission, nuclide='total', xs_type='macro'): + """This method allows for an openmc.mgxs.FissionXS + to be used to set the fission cross section for this XSdata object. + + Parameters + ---------- + fission: openmc.mgxs.FissionXS + MGXS Object containing the fission cross section + for the domain of interest. + nuclide : str + Individual nuclide (or 'total' if obtaining material-wise data) + to gather data for. Defaults to 'total'. + xs_type: {'macro', 'micro'} + Provide the macro or micro cross section in units of cm^-1 or + barns. Defaults to 'macro'. + + See also + -------- + openmc.mgxs.Library.create_mg_library() + openmc.mgxs.Library.get_xsdata + + """ + + check_type('fission', fission, openmc.mgxs.FissionXS) + check_value('energy_groups', fission.energy_groups, + [self.energy_groups]) + check_value('domain_type', fission.domain_type, + ['universe', 'cell', 'material']) + + if self.representation is 'isotropic': + self._fission = fission.get_xs(nuclides=nuclide, + xs_type=xs_type) + elif self.representation is 'angle': + msg = 'Angular-Dependent MGXS have not yet been implemented' + raise ValueError(msg) + + def set_nu_fission_mgxs(self, nu_fission, nuclide='total', + xs_type='macro'): + """This method allows for an openmc.mgxs.NuFissionXS + to be used to set the nu-fission cross section for this XSdata object. + + Parameters + ---------- + nu_fission: openmc.mgxs.NuFissionXS + MGXS Object containing the nu-fission cross section + for the domain of interest. + nuclide : str + Individual nuclide (or 'total' if obtaining material-wise data) + to gather data for. Defaults to 'total'. + xs_type: {'macro', 'micro'} + Provide the macro or micro cross section in units of cm^-1 or + barns. Defaults to 'macro'. + + See also + -------- + openmc.mgxs.Library.create_mg_library() + openmc.mgxs.Library.get_xsdata + + """ + + # The NuFissionXS class does not have the capability to produce + # a fission matrix and therefore if this path is pursued, we know + # chi must be used. + check_type('nu_fission', nu_fission, openmc.mgxs.NuFissionXS) + check_value('energy_groups', nu_fission.energy_groups, + [self.energy_groups]) + check_value('domain_type', nu_fission.domain_type, + ['universe', 'cell', 'material']) + + if self.representation is 'isotropic': + self._nu_fission = nu_fission.get_xs(nuclides=nuclide, + xs_type=xs_type) + elif self.representation is 'angle': + msg = 'Angular-Dependent MGXS have not yet been implemented' + raise ValueError(msg) + + self._use_chi = True + + if np.sum(self._nu_fission) > 0.0: + self._fissionable = True + + def set_kappa_fission_mgxs(self, k_fission, nuclide='total', + xs_type='macro'): + """This method allows for an openmc.mgxs.KappaFissionXS + to be used to set the kappa-fission cross section for this XSdata + object. + + Parameters + ---------- + kappa_fission: openmc.mgxs.KappaFissionXS + MGXS Object containing the kappa-fission cross section + for the domain of interest. + nuclide : str + Individual nuclide (or 'total' if obtaining material-wise data) + to gather data for. Defaults to 'total'. + xs_type: {'macro', 'micro'} + Provide the macro or micro cross section in units of cm^-1 or + barns. Defaults to 'macro'. + + See also + -------- + openmc.mgxs.Library.create_mg_library() + openmc.mgxs.Library.get_xsdata + + """ + + check_type('k_fission', k_fission, openmc.mgxs.KappaFissionXS) + check_value('energy_groups', k_fission.energy_groups, + [self.energy_groups]) + check_value('domain_type', k_fission.domain_type, + ['universe', 'cell', 'material']) + + if self.representation is 'isotropic': + self._kappa_fission = k_fission.get_xs(nuclides=nuclide, + xs_type=xs_type) + elif self.representation is 'angle': + msg = 'Angular-Dependent MGXS have not yet been implemented' + raise ValueError(msg) + + def set_chi_mgxs(self, chi, nuclide='total', xs_type='macro'): + """This method allows for an openmc.mgxs.Chi + to be used to set chi for this XSdata object. + + Parameters + ---------- + chi: openmc.mgxs.Chi + MGXS Object containing chi for the domain of interest. + nuclide : str + Individual nuclide (or 'total' if obtaining material-wise data) + to gather data for. Defaults to 'total'. + xs_type: {'macro', 'micro'} + Provide the macro or micro cross section in units of cm^-1 or + barns. Defaults to 'macro'. + + See also + -------- + openmc.mgxs.Library.create_mg_library() + openmc.mgxs.Library.get_xsdata + + """ + + if self._use_chi is not None: + if not self._use_chi: + msg = 'Providing chi when nu_fission already provided as a ' \ + 'matrix!' + raise ValueError(msg) + + check_type('chi', chi, openmc.mgxs.Chi) + check_value('energy_groups', chi.energy_groups, [self.energy_groups]) + check_value('domain_type', chi.domain_type, + ['universe', 'cell', 'material']) + + if self.representation is 'isotropic': + self._chi = chi.get_xs(nuclides=nuclide, + xs_type=xs_type) + elif self.representation is 'angle': + msg = 'Angular-Dependent MGXS have not yet been implemented' + raise ValueError(msg) + + if self._use_chi is not None: + self._use_chi = True + + def set_scatter_mgxs(self, scatter, nuclide='total', xs_type='macro'): + """This method allows for an openmc.mgxs.ScatterMatrixXS + to be used to set the scatter matrix cross section for this XSdata + object. If the XSdata.order attribute has not yet been set, then + it will be set based on the properties of scatter. + + Parameters + ---------- + scatter: openmc.mgxs.ScatterMatrixXS + MGXS Object containing the scatter matrix cross section + for the domain of interest. + nuclide : str + Individual nuclide (or 'total' if obtaining material-wise data) + to gather data for. Defaults to 'total'. + xs_type: {'macro', 'micro'} + Provide the macro or micro cross section in units of cm^-1 or + barns. Defaults to 'macro'. + + See also + -------- + openmc.mgxs.Library.create_mg_library() + openmc.mgxs.Library.get_xsdata + + """ + + check_type('scatter', scatter, openmc.mgxs.ScatterMatrixXS) + check_value('energy_groups', scatter.energy_groups, + [self.energy_groups]) + check_value('domain_type', scatter.domain_type, + ['universe', 'cell', 'material']) + + if (self.scatt_type != 'legendre'): + msg = 'Anisotropic scattering representations other than ' \ + 'Legendre expansions have not yet been implemented in ' \ + 'openmc.mgxs.' + raise ValueError(msg) + + # If the user has not defined XSdata.order, then we will set + # the order based on the data within scatter. + # Otherwise, we will check to see that XSdata.order to match + # the order of scatter + if self.order is None: + self.order = scatter.legendre_order + else: + check_value('legendre_order', scatter.legendre_order, + [self.order]) + + if self.representation is 'isotropic': + # Get the scattering orders in the outermost dimension + self._scatter = np.zeros((self.num_orders, + self.energy_groups.num_groups, + self.energy_groups.num_groups)) + for moment in range(self.num_orders): + self._scatter[moment, :, :] = scatter.get_xs(nuclides=nuclide, + xs_type=xs_type, + moment=moment) + + elif self.representation is 'angle': + msg = 'Angular-Dependent MGXS have not yet been implemented' + raise ValueError(msg) + + def set_multiplicity_mgxs(self, nuscatter, scatter, nuclide='total', + xs_type='macro'): + """This method allows for an openmc.mgxs.NuScatterMatrixXS and + openmc.mgxs.ScatterMatrixXS to be used to set the scattering + multiplicity for this XSdata object. Multiplicity, + in OpenMC parlance, is a factor used to account for the production + of neutrons introduced by scattering multiplication reactions, i.e., + (n,xn) events. In this sense, the multiplication matrix is simply + defined as the ratio of the nu-scatter and scatter matrices. + + Parameters + ---------- + nuscatter: openmc.mgxs.NuScatterMatrixXS + MGXS Object containing the nu-scattering matrix cross section + for the domain of interest. + scatter: openmc.mgxs.ScatterMatrixXS + MGXS Object containing the scattering matrix cross section + for the domain of interest. + nuclide : str + Individual nuclide (or 'total' if obtaining material-wise data) + to gather data for. Defaults to 'total'. + xs_type: {'macro', 'micro'} + Provide the macro or micro cross section in units of cm^-1 or + barns. Defaults to 'macro'. + + See also + -------- + openmc.mgxs.Library.create_mg_library() + openmc.mgxs.Library.get_xsdata + + """ + + check_type('nuscatter', nuscatter, openmc.mgxs.NuScatterMatrixXS) + check_type('scatter', scatter, openmc.mgxs.ScatterMatrixXS) + check_value('energy_groups', nuscatter.energy_groups, + [self.energy_groups]) + check_value('energy_groups', scatter.energy_groups, + [self.energy_groups]) + check_value('domain_type', nuscatter.domain_type, + ['universe', 'cell', 'material']) + check_value('domain_type', scatter.domain_type, + ['universe', 'cell', 'material']) + + if self.representation is 'isotropic': + nuscatt = nuscatter.get_xs(nuclides=nuclide, + xs_type=xs_type, moment=0) + scatt = scatter.get_xs(nuclides=nuclide, + xs_type=xs_type, moment=0) + self._multiplicity = np.divide(nuscatt, scatt) + elif self.representation is 'angle': + msg = 'Angular-Dependent MGXS have not yet been implemented' + raise ValueError(msg) + self._multiplicity = np.nan_to_num(self._multiplicity) + def _get_xsdata_xml(self): - element = ET.Element("xsdata") - element.set("name", self._name) + element = ET.Element('xsdata') + element.set('name', self._name) if self._alias is not None: subelement = ET.SubElement(element, 'alias') @@ -582,6 +957,18 @@ class XSdata(object): subelement = ET.SubElement(element, 'kT') subelement.text = str(self._kT) + if self._zaid is not None: + subelement = ET.SubElement(element, 'zaid') + subelement.text = str(self._zaid) + + if self._awr is not None: + subelement = ET.SubElement(element, 'awr') + subelement.text = str(self._awr) + + if self._kT is not None: + subelement = ET.SubElement(element, 'kT') + subelement.text = str(self._kT) + if self._fissionable is not None: subelement = ET.SubElement(element, 'fissionable') subelement.text = str(self._fissionable) @@ -609,7 +996,8 @@ class XSdata(object): if self._tabular_legendre is not None: subelement = ET.SubElement(element, 'tabular_legendre') subelement.set('enable', str(self._tabular_legendre['enable'])) - subelement.set('num_points', str(self._tabular_legendre['num_points'])) + subelement.set('num_points', + str(self._tabular_legendre['num_points'])) if self._total is not None: subelement = ET.SubElement(element, 'total') @@ -632,9 +1020,9 @@ class XSdata(object): subelement = ET.SubElement(element, 'fission') subelement.text = ndarray_to_string(self._fission) - if self._k_fission is not None: + if self._kappa_fission is not None: subelement = ET.SubElement(element, 'k_fission') - subelement.text = ndarray_to_string(self._k_fission) + subelement.text = ndarray_to_string(self._kappa_fission) if self._nu_fission is not None: subelement = ET.SubElement(element, 'nu_fission') @@ -649,7 +1037,8 @@ class XSdata(object): class MGXSLibrary(object): """Multi-Group Cross Sections file used for an OpenMC simulation. - Corresponds directly to the MG version of the cross_sections.xml input file. + Corresponds directly to the MG version of the cross_sections.xml input + file. Attributes ---------- @@ -665,7 +1054,7 @@ class MGXSLibrary(object): self._xsdatas = [] self._energy_groups = energy_groups self._inverse_velocities = None - self._cross_sections_file = ET.Element("cross_sections") + self._cross_sections_file = ET.Element('cross_sections') @property def inverse_velocities(self): @@ -684,7 +1073,7 @@ class MGXSLibrary(object): @energy_groups.setter def energy_groups(self, energy_groups): - check_type("energy groups", energy_groups, EnergyGroups) + check_type('energy groups', energy_groups, openmc.mgxs.EnergyGroups) self._energy_groups = energy_groups def add_xsdata(self, xsdata): @@ -696,14 +1085,10 @@ class MGXSLibrary(object): MGXS information to add """ - - # Check the type if not isinstance(xsdata, XSdata): msg = 'Unable to add a non-XSdata "{0}" to the ' \ 'MGXSLibrary instance'.format(xsdata) raise ValueError(msg) - - # Make sure energy groups match. if xsdata.energy_groups != self._energy_groups: msg = 'Energy groups of XSdata do not match that of MGXSLibrary.' raise ValueError(msg) @@ -719,11 +1104,7 @@ class MGXSLibrary(object): XSdatas to add """ - - if not isinstance(xsdatas, Iterable): - msg = 'Unable to create OpenMC xsdatas.xml file from "{0}" which ' \ - 'is not iterable'.format(xsdatas) - raise ValueError(msg) + check_iterable_type('xsdatas', xsdatas, XSdata) for xsdata in xsdatas: self.add_xsdata(xsdata) @@ -747,19 +1128,19 @@ class MGXSLibrary(object): def _create_groups_subelement(self): if self._energy_groups is not None: - element = ET.SubElement(self._cross_sections_file, "groups") + element = ET.SubElement(self._cross_sections_file, 'groups') element.text = str(self._energy_groups.num_groups) def _create_group_structure_subelement(self): if self._energy_groups is not None: element = ET.SubElement(self._cross_sections_file, - "group_structure") + 'group_structure') element.text = ' '.join(map(str, self._energy_groups.group_edges)) def _create_inverse_velocities_subelement(self): if self._inverse_velocities is not None: element = ET.SubElement(self._cross_sections_file, - "inverse_velocities") + 'inverse_velocities') element.text = ' '.join(map(str, self._inverse_velocities)) def _create_xsdata_subelements(self): @@ -767,14 +1148,14 @@ class MGXSLibrary(object): xml_element = xsdata._get_xsdata_xml() self._cross_sections_file.append(xml_element) - def export_to_xml(self, filename='mg_cross_sections.xml'): - """Create an mg_cross_sections.xml file that can be used for a + def export_to_xml(self, filename='mgxs.xml'): + """Create an mgxs.xml file that can be used for a simulation. Parameters ---------- filename : str, optional - filename of file, default is mg_cross_sections.xml + filename of file, default is mgxs.xml """ @@ -793,4 +1174,4 @@ class MGXSLibrary(object): # Write the XML Tree to the xsdatas.xml file tree = ET.ElementTree(self._cross_sections_file) tree.write(filename, xml_declaration=True, - encoding='utf-8', method="xml") + encoding='utf-8', method='xml') diff --git a/openmc/settings.py b/openmc/settings.py index ec38bf54c3..e64935d7aa 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -70,9 +70,10 @@ class Settings(object): deviation. cross_sections : str Indicates the path to an XML cross section listing file (usually named - cross_sections.xml). If it is not set, the :envvar:`CROSS_SECTIONS` - environment variable will be used for continuous-energy calculations - and :envvar:`MG_CROSS_SECTIONS` will be used for multi-group + cross_sections.xml). If it is not set, the + :envvar:`OPENMC_CROSS_SECTIONS` environment variable will be used for + continuous-energy calculations and + :envvar:`OPENMC_MG_CROSS_SECTIONS` will be used for multi-group calculations to find the path to the XML cross section file. energy_grid : {'nuclide', 'logarithm', 'material-union'} Set the method used to search energy grids. diff --git a/openmc/summary.py b/openmc/summary.py index c9aa08f152..6fb6b70009 100644 --- a/openmc/summary.py +++ b/openmc/summary.py @@ -38,8 +38,10 @@ class Summary(object): self._opencg_geometry = None self._read_metadata() + self._read_nuclides() self._read_geometry() self._read_tallies() + self._f.close() @property def openmc_geometry(self): @@ -55,8 +57,8 @@ class Summary(object): def _read_metadata(self): # Read OpenMC version self.version = [self._f['version_major'].value, - self._f['version_minor'].value, - self._f['version_release'].value] + self._f['version_minor'].value, + self._f['version_release'].value] # Read date and time self.date_and_time = self._f['date_and_time'][...] @@ -71,6 +73,17 @@ class Summary(object): self.gen_per_batch = self._f['gen_per_batch'].value self.n_procs = self._f['n_procs'].value + def _read_nuclides(self): + self.nuclides = {} + n_nuclides = self._f['nuclides/n_nuclides_total'].value + names = self._f['nuclides/names'].value + awrs = self._f['nuclides/awrs'].value + zaids = self._f['nuclides/zaids'].value + for n in range(n_nuclides): + name = names[n].decode() + name = name[:name.find('.')] + self.nuclides[name] = (zaids[n], awrs[n]) + def _read_geometry(self): # Read in and initialize the Materials and Geometry self._read_materials() diff --git a/src/input_xml.F90 b/src/input_xml.F90 index 5a4bc8429d..822b2084e5 100644 --- a/src/input_xml.F90 +++ b/src/input_xml.F90 @@ -153,7 +153,7 @@ contains else call get_environment_variable("OPENMC_MG_CROSS_SECTIONS", env_variable) if (len_trim(env_variable) == 0) then - call fatal_error("No cross_sections.xml file was specified in & + call fatal_error("No mgxs.xml file was specified in & &settings.xml or in the OPENMC_MG_CROSS_SECTIONS environment & &variable. OpenMC needs such a file to identify where to & &find the cross section libraries. Please consult the user's & @@ -4523,24 +4523,24 @@ contains subroutine read_mg_cross_sections_xml() integer :: i ! loop index - logical :: file_exists ! does cross_sections.xml exist? + logical :: file_exists ! does mgxs.xml exist? type(XsListing), pointer :: listing => null() type(Node), pointer :: doc => null() type(Node), pointer :: node_xsdata => null() type(NodeList), pointer :: node_xsdata_list => null() real(8), allocatable :: rev_energy_bins(:) - ! Check if cross_sections.xml exists + ! Check if mgxs.xml exists inquire(FILE=path_cross_sections, EXIST=file_exists) if (.not. file_exists) then - ! Could not find cross_sections.xml file + ! Could not find mgxs.xml file call fatal_error("Cross sections XML file '" & // trim(path_cross_sections) // "' does not exist!") end if call write_message("Reading cross sections XML file...", 5) - ! Parse cross_sections.xml file + ! Parse mgxs.xml file call open_xmldoc(doc, path_cross_sections) if (check_for_node(doc, "groups")) then @@ -4588,7 +4588,7 @@ contains ! Allocate xs_listings array if (n_listings == 0) then call fatal_error("At least one element must be present in & - &cross_sections.xml file!") + &mgxs.xml file!") else allocate(xs_listings(n_listings)) end if diff --git a/src/summary.F90 b/src/summary.F90 index 4502058cad..aabf6c22b2 100644 --- a/src/summary.F90 +++ b/src/summary.F90 @@ -68,6 +68,7 @@ contains "description", "Number of generations per batch") end if + call write_nuclides(file_id) call write_geometry(file_id) call write_materials(file_id) if (n_tallies > 0) then @@ -105,6 +106,49 @@ contains end subroutine write_header +!=============================================================================== +! WRITE_NUCLIDES +!=============================================================================== + + subroutine write_nuclides(file_id) + integer(HID_T), intent(in) :: file_id + integer(HID_T) :: nuclide_group + integer :: i + character(12), allocatable :: nucnames(:) + real(8), allocatable :: awrs(:) + integer, allocatable :: zaids(:) + + ! Write useful data from nuclide objects + nuclide_group = create_group(file_id, "nuclides") + call write_dataset(nuclide_group, "n_nuclides_total", n_nuclides_total) + + ! Build array of nuclide names, awrs, and zaids + allocate(nucnames(n_nuclides_total)) + allocate(awrs(n_nuclides_total)) + allocate(zaids(n_nuclides_total)) + do i = 1, n_nuclides_total + if (run_CE) then + nucnames(i) = xs_listings(nuclides(i) % listing) % alias + awrs(i) = nuclides(i) % awr + zaids(i) = nuclides(i) % zaid + else + nucnames(i) = xs_listings(nuclides_MG(i) % obj % listing) % alias + awrs(i) = nuclides_MG(i) % obj % awr + zaids(i) = nuclides_MG(i) % obj % zaid + end if + end do + + ! Write nuclide names, awrs and zaids + call write_dataset(nuclide_group, "names", nucnames) + call write_dataset(nuclide_group, "awrs", awrs) + call write_dataset(nuclide_group, "zaids", zaids) + + call close_group(nuclide_group) + + deallocate(nucnames, awrs, zaids) + + end subroutine write_nuclides + !=============================================================================== ! WRITE_GEOMETRY !===============================================================================