MGXS Part IV: Multi-Group Mode Cross-Section LibraryΒΆ

mgxs-part-iv

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. During this process, this notebook will illustrate the following features:

  • Calculation of multi-group cross sections for a fuel assembly
  • Automated creation and storage of MGXS with openmc.mgxs.Library
  • Steady-state pin-by-pin fission rates comparison between continuous-energy and multi-group OpenMC.

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.

Generate Input Files

In [1]:
import math
import pickle

from IPython.display import Image
import matplotlib.pyplot as plt
import numpy as np
import os

import openmc
import openmc.mgxs

%matplotlib inline

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.

In [2]:
# Instantiate some Nuclides
h1 = openmc.Nuclide('H-1')
b10 = openmc.Nuclide('B-10')
o16 = openmc.Nuclide('O-16')
u235 = openmc.Nuclide('U-235')
u238 = openmc.Nuclide('U-238')
zr90 = openmc.Nuclide('Zr-90')

With the nuclides we defined, we will now create three materials for the fuel, water, and cladding of the fuel pins.

In [3]:
# 1.6 enriched fuel
fuel = openmc.Material(name='1.6% Fuel')
fuel.set_density('g/cm3', 10.31341)
fuel.add_nuclide(u235, 3.7503e-4)
fuel.add_nuclide(u238, 2.2625e-2)
fuel.add_nuclide(o16, 4.6007e-2)

# zircaloy
zircaloy = openmc.Material(name='Zircaloy')
zircaloy.set_density('g/cm3', 6.55)
zircaloy.add_nuclide(zr90, 7.2758e-3)

# borated water
water = openmc.Material(name='Borated Water')
water.set_density('g/cm3', 0.740582)
water.add_nuclide(h1, 4.9457e-2)
water.add_nuclide(o16, 2.4732e-2)
water.add_nuclide(b10, 8.0042e-6)

With our three materials, we can now create a Materials object that can be exported to an actual XML file.

In [4]:
# Instantiate a Materials object
materials_file = openmc.Materials((fuel, zircaloy, water))
materials_file.default_xs = '71c'

# Export to "materials.xml"
materials_file.export_to_xml()

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.

In [5]:
# Create cylinders for the fuel and clad
fuel_outer_radius = openmc.ZCylinder(x0=0.0, y0=0.0, R=0.39218)
clad_outer_radius = openmc.ZCylinder(x0=0.0, y0=0.0, R=0.45720)

# Create boundary planes to surround the geometry
min_x = openmc.XPlane(x0=-10.71, boundary_type='reflective')
max_x = openmc.XPlane(x0=+10.71, boundary_type='reflective')
min_y = openmc.YPlane(y0=-10.71, boundary_type='reflective')
max_y = openmc.YPlane(y0=+10.71, boundary_type='reflective')
min_z = openmc.ZPlane(z0=-10., boundary_type='reflective')
max_z = openmc.ZPlane(z0=+10., boundary_type='reflective')

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.

In [6]:
# Create a Universe to encapsulate a fuel pin
fuel_pin_universe = openmc.Universe(name='1.6% Fuel Pin')

# Create fuel Cell
fuel_cell = openmc.Cell(name='1.6% Fuel')
fuel_cell.fill = fuel
fuel_cell.region = -fuel_outer_radius
fuel_pin_universe.add_cell(fuel_cell)

# Create a clad Cell
clad_cell = openmc.Cell(name='1.6% Clad')
clad_cell.fill = zircaloy
clad_cell.region = +fuel_outer_radius & -clad_outer_radius
fuel_pin_universe.add_cell(clad_cell)

# Create a moderator Cell
moderator_cell = openmc.Cell(name='1.6% Moderator')
moderator_cell.fill = water
moderator_cell.region = +clad_outer_radius
fuel_pin_universe.add_cell(moderator_cell)

Likewise, we can construct a control rod guide tube with the same surfaces.

In [7]:
# Create a Universe to encapsulate a control rod guide tube
guide_tube_universe = openmc.Universe(name='Guide Tube')

# Create guide tube Cell
guide_tube_cell = openmc.Cell(name='Guide Tube Water')
guide_tube_cell.fill = water
guide_tube_cell.region = -fuel_outer_radius
guide_tube_universe.add_cell(guide_tube_cell)

# Create a clad Cell
clad_cell = openmc.Cell(name='Guide Clad')
clad_cell.fill = zircaloy
clad_cell.region = +fuel_outer_radius & -clad_outer_radius
guide_tube_universe.add_cell(clad_cell)

# Create a moderator Cell
moderator_cell = openmc.Cell(name='Guide Tube Moderator')
moderator_cell.fill = water
moderator_cell.region = +clad_outer_radius
guide_tube_universe.add_cell(moderator_cell)

Using the pin cell universe, we can construct a 17x17 rectangular lattice with a 1.26 cm pitch.

In [8]:
# Create fuel assembly Lattice
assembly = openmc.RectLattice(name='1.6% Fuel Assembly')
assembly.pitch = (1.26, 1.26)
assembly.lower_left = [-1.26 * 17. / 2.0] * 2

Next, we create a NumPy array of fuel pin and guide tube universes for the lattice.

In [9]:
# Create array indices for guide tube locations in lattice
template_x = np.array([5, 8, 11, 3, 13, 2, 5, 8, 11, 14, 2, 5, 8,
                       11, 14, 2, 5, 8, 11, 14, 3, 13, 5, 8, 11])
template_y = np.array([2, 2, 2, 3, 3, 5, 5, 5, 5, 5, 8, 8, 8, 8,
                       8, 11, 11, 11, 11, 11, 13, 13, 14, 14, 14])

# Initialize an empty 17x17 array of the lattice universes
universes = np.empty((17, 17), dtype=openmc.Universe)

# Fill the array with the fuel pin and guide tube universes
universes[:,:] = fuel_pin_universe
universes[template_x, template_y] = guide_tube_universe

# Store the array of universes in the lattice
assembly.universes = universes

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.

In [10]:
# Create root Cell
root_cell = openmc.Cell(name='root cell')
root_cell.fill = assembly

# Add boundary planes
root_cell.region = +min_x & -max_x & +min_y & -max_y & +min_z & -max_z

# Create root Universe
root_universe = openmc.Universe(name='root universe', universe_id=0)
root_universe.add_cell(root_cell)

We now must create a geometry that is assigned a root universe and export it to XML.

In [11]:
# Create Geometry and set root Universe
geometry = openmc.Geometry()
geometry.root_universe = root_universe
# Export to "geometry.xml"
geometry.export_to_xml()

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 5000 particles.

In [12]:
# OpenMC simulation parameters
batches = 50
inactive = 10
particles = 5000

# Instantiate a Settings object
settings_file = openmc.Settings()
settings_file.batches = batches
settings_file.inactive = inactive
settings_file.particles = particles
settings_file.output = {'tallies': False}

# Create an initial uniform spatial source distribution over fissionable zones
bounds = [-10.71, -10.71, -10, 10.71, 10.71, 10.]
uniform_dist = openmc.stats.Box(bounds[:3], bounds[3:], only_fissionable=True)
settings_file.source = openmc.source.Source(space=uniform_dist)

# Export to "settings.xml"
settings_file.export_to_xml()

Let us also create a Plots file that we can use to verify that our fuel assembly geometry was created successfully.

In [13]:
# Instantiate a Plot
plot = openmc.Plot()
plot.filename = 'materials-xy'
plot.origin = [0, 0, 0]
plot.pixels = [250, 250]
plot.width = [-10.71*2, -10.71*2]
plot.color = 'mat'

# Instantiate a Plots object, add Plot, and export to "plots.xml"
plot_file = openmc.Plots([plot])
plot_file.export_to_xml()

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.

In [14]:
# Run openmc in plotting mode
openmc.plot_geometry(output=False)
Out[14]:
0
In [15]:
# Convert OpenMC's funky ppm to png
!convert materials-xy.ppm materials-xy.png

# Display the materials plot inline
Image(filename='materials-xy.png')
Out[15]:

As we can see from the plot, we have a nice array of fuel and guide tube pin cells with fuel, cladding, and water!

Create an MGXS Library

Now we are ready to generate multi-group cross sections! First, let's define a 2-group structure using the built-in EnergyGroups class.

In [16]:
# Instantiate a 2-group EnergyGroups object
groups = openmc.mgxs.EnergyGroups()
groups.group_edges = np.array([0., 0.625e-6, 20.])

Next, we will instantiate an openmc.mgxs.Library for the energy groups with our the fuel assembly geometry.

In [17]:
# Initialize an 2-group MGXS Library for OpenMOC
mgxs_lib = openmc.mgxs.Library(geometry)
mgxs_lib.energy_groups = groups

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: "total", "absorption", "nu-fission", '"fission", "nu-scatter matrix", "multiplicity matrix", and "chi". "multiplicity matrix" is needed to provide OpenMC's multi-group mode with additional information needed to accurately treat scattering multiplication (i.e., (n,xn) reactions)) explicitly.

In [18]:
# Specify multi-group cross section types to compute
mgxs_lib.mgxs_types = ['total', 'absorption', 'nu-fission', 'fission',
                       'nu-scatter matrix', 'multiplicity matrix', 'chi']

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.

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.

In [19]:
# Specify a "cell" domain type for the cross section tally filters
mgxs_lib.domain_type = "material"

# Specify the cell domains over which to compute multi-group cross sections
mgxs_lib.domains = geometry.get_all_materials()

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.

NOTE: The default value of the by_nuclide parameter is False, so the following step is not necessary but is included for illustrative purposes.

In [20]:
# Do not compute cross sections on a nuclide-by-nuclide basis
mgxs_lib.by_nuclide = False

Now we will set the scattering order that we wish to use. For this problem we will use P3 scattering. A warning is expected telling us that the default behavior (a P0 correction on the scattering data) is over-ridden by our choice of using a Legendre expansion to treat anisotropic scattering.

In [21]:
# Set the Legendre order to 3 for P3 scattering
mgxs_lib.legendre_order = 3
/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
  warnings.warn(msg, RuntimeWarning)

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.

In [22]:
# Check the library - if no errors are raised, then the library is satisfactory.
mgxs_lib.check_library_for_openmc_mgxs()

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.

In [23]:
# Construct all tallies needed for the multi-group cross section library
mgxs_lib.build_library()

The tallies can now be export to a "tallies.xml" input file for OpenMC.

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.

In [24]:
# Create a "tallies.xml" file for the MGXS Library
tallies_file = openmc.Tallies()
mgxs_lib.add_to_tallies_file(tallies_file, merge=True)

In addition, we instantiate a fission rate mesh tally to compare with the multi-group result.

In [25]:
# Instantiate a tally Mesh
mesh = openmc.Mesh()
mesh.type = 'regular'
mesh.dimension = [17, 17]
mesh.lower_left = [-10.71, -10.71]
mesh.upper_right = [+10.71, +10.71]

# Instantiate tally Filter
mesh_filter = openmc.Filter()
mesh_filter.mesh = mesh

# Instantiate the Tally
tally = openmc.Tally(name='mesh tally')
tally.filters = [mesh_filter]
tally.scores = ['fission']

# Add tally to collection
tallies_file.append(tally, merge=True)

# Export all tallies to a "tallies.xml" file
tallies_file.export_to_xml()

Time to run the calculation and get our results!

In [26]:
# Run OpenMC
openmc.run()
       .d88888b.                             888b     d888  .d8888b.
      d88P" "Y88b                            8888b   d8888 d88P  Y88b
      888     888                            88888b.d88888 888    888
      888     888 88888b.   .d88b.  88888b.  888Y88888P888 888       
      888     888 888 "88b d8P  Y8b 888 "88b 888 Y888P 888 888       
      888     888 888  888 88888888 888  888 888  Y8P  888 888    888
      Y88b. .d88P 888 d88P Y8b.     888  888 888   "   888 Y88b  d88P
       "Y88888P"  88888P"   "Y8888  888  888 888       888  "Y8888P"
__________________888______________________________________________________
                  888
                  888

      Copyright:      2011-2016 Massachusetts Institute of Technology
      License:        http://openmc.readthedocs.io/en/latest/license.html
      Version:        0.7.1
      Git SHA1:       826d5a43d85eaec1b6c7b4ce22e1a8f5e9336a4f
      Date/Time:      2016-06-08 19:33:38
      OpenMP Threads: 4

 ===========================================================================
 ========================>     INITIALIZATION     <=========================
 ===========================================================================

 Reading settings XML file...
 Reading cross sections XML file...
 Reading geometry XML file...
 Reading materials XML file...
 Reading tallies XML file...
 Building neighboring cells lists for each surface...
 Loading ACE cross section table: 92235.71c
 Loading ACE cross section table: 92238.71c
 Loading ACE cross section table: 8016.71c
 Loading ACE cross section table: 40090.71c
 Loading ACE cross section table: 1001.71c
 Loading ACE cross section table: 5010.71c
 Maximum neutron transport energy: 20.0000 MeV for 92235.71c
 Initializing source particles...

 ===========================================================================
 ====================>     K EIGENVALUE SIMULATION     <====================
 ===========================================================================

  Bat./Gen.      k            Average k         
  =========   ========   ====================   
        1/1    1.05201                       
        2/1    1.02017                       
        3/1    1.02398                       
        4/1    1.02677                       
        5/1    1.01070                       
        6/1    1.02964                       
        7/1    1.02163                       
        8/1    1.04524                       
        9/1    1.00773                       
       10/1    1.01536                       
       11/1    1.02992                       
       12/1    1.03248    1.03120 +/- 0.00128
       13/1    0.99044    1.01761 +/- 0.01361
       14/1    1.01484    1.01692 +/- 0.00965
       15/1    1.01491    1.01652 +/- 0.00748
       16/1    1.03809    1.02011 +/- 0.00709
       17/1    1.02536    1.02086 +/- 0.00604
       18/1    1.03663    1.02283 +/- 0.00559
       19/1    1.03902    1.02463 +/- 0.00525
       20/1    1.01557    1.02373 +/- 0.00478
       21/1    1.01286    1.02274 +/- 0.00443
       22/1    1.01392    1.02200 +/- 0.00411
       23/1    1.04439    1.02372 +/- 0.00416
       24/1    1.04034    1.02491 +/- 0.00403
       25/1    0.99433    1.02287 +/- 0.00427
       26/1    1.02720    1.02314 +/- 0.00400
       27/1    1.03545    1.02387 +/- 0.00383
       28/1    1.03853    1.02468 +/- 0.00370
       29/1    1.02735    1.02482 +/- 0.00350
       30/1    1.02429    1.02480 +/- 0.00332
       31/1    1.02901    1.02500 +/- 0.00317
       32/1    1.03296    1.02536 +/- 0.00304
       33/1    1.03605    1.02582 +/- 0.00294
       34/1    1.04247    1.02652 +/- 0.00290
       35/1    1.02088    1.02629 +/- 0.00279
       36/1    1.03017    1.02644 +/- 0.00269
       37/1    1.03216    1.02665 +/- 0.00259
       38/1    1.01459    1.02622 +/- 0.00254
       39/1    1.03706    1.02659 +/- 0.00248
       40/1    1.01383    1.02617 +/- 0.00243
       41/1    0.99043    1.02502 +/- 0.00262
       42/1    1.02891    1.02514 +/- 0.00254
       43/1    1.02100    1.02501 +/- 0.00246
       44/1    0.99546    1.02414 +/- 0.00254
       45/1    1.01562    1.02390 +/- 0.00248
       46/1    1.03025    1.02408 +/- 0.00242
       47/1    0.99409    1.02327 +/- 0.00249
       48/1    1.04355    1.02380 +/- 0.00248
       49/1    1.02763    1.02390 +/- 0.00242
       50/1    0.99426    1.02316 +/- 0.00247
 Creating state point statepoint.50.h5...

 ===========================================================================
 ======================>     SIMULATION FINISHED     <======================
 ===========================================================================


 =======================>     TIMING STATISTICS     <=======================

 Total time for initialization     =  1.4220E+00 seconds
   Reading cross sections          =  1.1320E+00 seconds
 Total time in simulation          =  1.6571E+01 seconds
   Time in transport only          =  1.6501E+01 seconds
   Time in inactive batches        =  2.1010E+00 seconds
   Time in active batches          =  1.4470E+01 seconds
   Time synchronizing fission bank =  5.0000E-03 seconds
     Sampling source sites         =  4.0000E-03 seconds
     SEND/RECV source sites        =  1.0000E-03 seconds
   Time accumulating tallies       =  0.0000E+00 seconds
 Total time for finalization       =  0.0000E+00 seconds
 Total time elapsed                =  1.8002E+01 seconds
 Calculation Rate (inactive)       =  23798.2 neutrons/second
 Calculation Rate (active)         =  13821.7 neutrons/second

 ============================>     RESULTS     <============================

 k-effective (Collision)     =  1.02389 +/-  0.00235
 k-effective (Track-length)  =  1.02316 +/-  0.00247
 k-effective (Absorption)    =  1.02494 +/-  0.00180
 Combined k-effective        =  1.02429 +/-  0.00140
 Leakage Fraction            =  0.00000 +/-  0.00000

Out[26]:
0

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.

In [27]:
# Move the StatePoint File
ce_spfile = './ce_statepoint.h5'
os.rename('statepoint.' + str(batches) + '.h5', ce_spfile)
# Move the Summary file
ce_sumfile = './ce_summary.h5'
os.rename('summary.h5', ce_sumfile)

Tally Data Processing

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.

In [28]:
# Load the statepoint file, but not the summary file, as it is a different filename than expected.
sp = openmc.StatePoint(ce_spfile, autolink=False)

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.

In [29]:
su = openmc.Summary(ce_sumfile)
sp.link_with_summary(su)

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.

In [30]:
# Initialize MGXS Library with OpenMC statepoint data
mgxs_lib.load_from_statepoint(sp)

The next step will be to prepare the input for OpenMC to use our newly created multi-group data.

Multi-Group OpenMC Calculation

We will now use the Library to produce a multi-group cross section data set for use by the OpenMC multi-group solver.
Note that since this simulation included so few histories, it is reasonable to expect some divisions by zero errors. This will show up as a runtime warning in the following step.

In [31]:
# Create a MGXS File which can then be written to disk
mgxs_file = mgxs_lib.create_mg_library(xs_type='macro', xsdata_names=['fuel', 'zircaloy', 'water'],
                                       xs_ids='2m')

# Write the file to disk using the default filename of `mgxs.xml`
mgxs_file.export_to_xml()
/home/nelsonag/git/openmc/openmc/tallies.py:1990: RuntimeWarning: invalid value encountered in true_divide
  self_rel_err = data['self']['std. dev.'] / data['self']['mean']
/home/nelsonag/git/openmc/openmc/tallies.py:1991: RuntimeWarning: invalid value encountered in true_divide
  other_rel_err = data['other']['std. dev.'] / data['other']['mean']
/home/nelsonag/git/openmc/openmc/tallies.py:1992: RuntimeWarning: invalid value encountered in true_divide
  new_tally._mean = data['self']['mean'] / data['other']['mean']

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).

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.

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.

In [32]:
# Instantiate our Macroscopic Data
fuel_macro = openmc.Macroscopic('fuel')
zircaloy_macro = openmc.Macroscopic('zircaloy')
water_macro = openmc.Macroscopic('water')

# Now re-define our materials to use the Multi-Group macroscopic data
# instead of the continuous-energy data.
# 1.6 enriched fuel UO2
fuel = openmc.Material(name='UO2')
fuel.add_macroscopic(fuel_macro)

# cladding
zircaloy = openmc.Material(name='Clad')
zircaloy.add_macroscopic(zircaloy_macro)

# moderator
water = openmc.Material(name='Water')
water.add_macroscopic(water_macro)

# Finally, instantiate our Materials object
materials_file = openmc.Materials((fuel, zircaloy, water))
materials_file.default_xs = '2m'

# Export to "materials.xml"
materials_file.export_to_xml()

No geometry file neeeds to be written as the continuous-energy file is correctly defined for the multi-group case as well.

Next, we can make the changes we need to the settings file. 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.

In [33]:
# Set the location of the cross sections file
settings_file.cross_sections = './mgxs.xml'
settings_file.energy_mode = 'multi-group'

# Export to "settings.xml"
settings_file.export_to_xml()

Finally, since we want similar tally data in the end, we will leave our pre-existing tallies.xml file for this calculation.

At this point, the problem is set up and we can run the multi-group calculation.

In [34]:
# Run the Multi-Group OpenMC Simulation
openmc.run()
       .d88888b.                             888b     d888  .d8888b.
      d88P" "Y88b                            8888b   d8888 d88P  Y88b
      888     888                            88888b.d88888 888    888
      888     888 88888b.   .d88b.  88888b.  888Y88888P888 888       
      888     888 888 "88b d8P  Y8b 888 "88b 888 Y888P 888 888       
      888     888 888  888 88888888 888  888 888  Y8P  888 888    888
      Y88b. .d88P 888 d88P Y8b.     888  888 888   "   888 Y88b  d88P
       "Y88888P"  88888P"   "Y8888  888  888 888       888  "Y8888P"
__________________888______________________________________________________
                  888
                  888

      Copyright:      2011-2016 Massachusetts Institute of Technology
      License:        http://openmc.readthedocs.io/en/latest/license.html
      Version:        0.7.1
      Git SHA1:       826d5a43d85eaec1b6c7b4ce22e1a8f5e9336a4f
      Date/Time:      2016-06-08 19:33:56
      OpenMP Threads: 4

 ===========================================================================
 ========================>     INITIALIZATION     <=========================
 ===========================================================================

 Reading settings XML file...
 Reading cross sections XML file...
 Reading geometry XML file...
 Reading materials XML file...
 Reading tallies XML file...
 Building neighboring cells lists for each surface...
 Loading Cross Section Data...
 Loading fuel.2m Data...
 Loading zircaloy.2m Data...
 Loading water.2m Data...
 Initializing source particles...

 ===========================================================================
 ====================>     K EIGENVALUE SIMULATION     <====================
 ===========================================================================

  Bat./Gen.      k            Average k         
  =========   ========   ====================   
        1/1    0.99367                       
        2/1    1.03173                       
        3/1    1.01999                       
        4/1    1.01421                       
        5/1    1.03980                       
        6/1    1.04540                       
        7/1    1.04199                       
        8/1    1.02680                       
        9/1    1.01267                       
       10/1    1.03420                       
       11/1    1.05773                       
       12/1    1.03475    1.04624 +/- 0.01149
       13/1    1.03632    1.04293 +/- 0.00741
       14/1    0.99297    1.03044 +/- 0.01355
       15/1    1.02413    1.02918 +/- 0.01057
       16/1    1.02359    1.02825 +/- 0.00868
       17/1    0.99913    1.02409 +/- 0.00843
       18/1    1.01493    1.02294 +/- 0.00739
       19/1    1.03010    1.02374 +/- 0.00657
       20/1    1.04890    1.02626 +/- 0.00639
       21/1    1.01267    1.02502 +/- 0.00591
       22/1    1.02637    1.02513 +/- 0.00540
       23/1    1.01374    1.02426 +/- 0.00504
       24/1    1.06661    1.02728 +/- 0.00556
       25/1    1.03212    1.02760 +/- 0.00519
       26/1    1.05433    1.02927 +/- 0.00513
       27/1    0.99891    1.02749 +/- 0.00514
       28/1    1.00616    1.02630 +/- 0.00499
       29/1    1.04583    1.02733 +/- 0.00483
       30/1    1.01512    1.02672 +/- 0.00462
       31/1    0.98104    1.02455 +/- 0.00491
       32/1    1.04202    1.02534 +/- 0.00474
       33/1    1.00779    1.02458 +/- 0.00460
       34/1    1.02450    1.02457 +/- 0.00440
       35/1    0.98882    1.02314 +/- 0.00446
       36/1    1.01541    1.02285 +/- 0.00429
       37/1    1.02050    1.02276 +/- 0.00413
       38/1    1.03573    1.02322 +/- 0.00401
       39/1    1.03649    1.02368 +/- 0.00389
       40/1    1.01434    1.02337 +/- 0.00378
       41/1    1.02345    1.02337 +/- 0.00365
       42/1    1.01900    1.02323 +/- 0.00354
       43/1    1.01450    1.02297 +/- 0.00344
       44/1    1.03127    1.02321 +/- 0.00335
       45/1    1.01598    1.02301 +/- 0.00326
       46/1    1.00851    1.02260 +/- 0.00319
       47/1    1.03406    1.02291 +/- 0.00312
       48/1    1.02373    1.02294 +/- 0.00303
       49/1    1.04066    1.02339 +/- 0.00299
       50/1    1.02011    1.02331 +/- 0.00292
 Creating state point statepoint.50.h5...

 ===========================================================================
 ======================>     SIMULATION FINISHED     <======================
 ===========================================================================


 =======================>     TIMING STATISTICS     <=======================

 Total time for initialization     =  3.1000E-02 seconds
   Reading cross sections          =  5.0000E-03 seconds
 Total time in simulation          =  1.1867E+01 seconds
   Time in transport only          =  1.1830E+01 seconds
   Time in inactive batches        =  1.2670E+00 seconds
   Time in active batches          =  1.0600E+01 seconds
   Time synchronizing fission bank =  7.0000E-03 seconds
     Sampling source sites         =  7.0000E-03 seconds
     SEND/RECV source sites        =  0.0000E+00 seconds
   Time accumulating tallies       =  0.0000E+00 seconds
 Total time for finalization       =  0.0000E+00 seconds
 Total time elapsed                =  1.1907E+01 seconds
 Calculation Rate (inactive)       =  39463.3 neutrons/second
 Calculation Rate (active)         =  18867.9 neutrons/second

 ============================>     RESULTS     <============================

 k-effective (Collision)     =  1.02638 +/-  0.00260
 k-effective (Track-length)  =  1.02331 +/-  0.00292
 k-effective (Absorption)    =  1.02579 +/-  0.00132
 Combined k-effective        =  1.02558 +/-  0.00136
 Leakage Fraction            =  0.00000 +/-  0.00000

Out[34]:
0

Results Comparison

Now we can compare the multi-group and continuous-energy results.

We will begin by loading the multi-group statepoint file we just finished writing and extracting the calculated keff. Since we did not rename the summary file, we do not need to load it separately this time.

In [35]:
# Load the last statepoint file and keff value
mgsp = openmc.StatePoint('statepoint.' + str(batches) + '.h5')
mg_keff = mgsp.k_combined

Next, we can load the continuous-energy eigenvalue for comparison.

In [36]:
ce_keff = sp.k_combined

Lets compare the two eigenvalues, including their bias

In [37]:
bias = 1.0E5 * (ce_keff[0] - mg_keff[0])

print('Continuous-Energy keff = {0:1.6f}'.format(ce_keff[0]))
print('Multi-Group keff = {0:1.6f}'.format(mg_keff[0]))
print('bias [pcm]: {0:1.1f}'.format(bias))
Continuous-Energy keff = 1.024295
Multi-Group keff = 1.025577
bias [pcm]: -128.2

This shows a small but 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.

Flux and Pin Power Visualizations

Next we will visualize the mesh tally results obtained from both the Continuous-Energy and Multi-Group OpenMC calculations.

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.

In [38]:
# Get the OpenMC fission rate mesh tally data
mg_mesh_tally = mgsp.get_tally(name='mesh tally')
mg_fission_rates = mg_mesh_tally.get_values(scores=['fission'])

# Reshape array to 2D for plotting
mg_fission_rates.shape = (17,17)

# Normalize to the average pin power
mg_fission_rates /= np.mean(mg_fission_rates)

Now we can do the same for the Continuous-Energy results.

In [39]:
# Get the OpenMC fission rate mesh tally data
ce_mesh_tally = sp.get_tally(name='mesh tally')
ce_fission_rates = ce_mesh_tally.get_values(scores=['fission'])

# Reshape array to 2D for plotting
ce_fission_rates.shape = (17,17)

# Normalize to the average pin power
ce_fission_rates /= np.mean(ce_fission_rates)

Now we can easily use Matplotlib to visualize the two fission rates side-by-side.

In [40]:
# Force zeros to be NaNs so their values are not included when matplotlib calculates
# the color scale
ce_fission_rates[ce_fission_rates == 0.] = np.nan
mg_fission_rates[mg_fission_rates == 0.] = np.nan

# Plot the CE fission rates in the left subplot
fig = plt.subplot(121)
plt.imshow(ce_fission_rates, interpolation='none', cmap='jet')
plt.title('Continuous-Energy Fission Rates')

# Plot the MG fission rates in the right subplot
fig2 = plt.subplot(122)
plt.imshow(mg_fission_rates, interpolation='none', cmap='jet')
plt.title('Multi-Group Fission Rates')
Out[40]:
<matplotlib.text.Text at 0x7fbaddf68550>

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.

In [ ]: