<jupyter_start><jupyter_text>This IPython Notebook illustrates the use of the `openmc.mgxs` module to calculate multi-group cross sections for a heterogeneous fuel pin cell geometry. In particular, this Notebook illustrates the following features:* Creation of multi-group cross sections on a **heterogeneous geometry*** Calculation of cross sections on a **nuclide-by-nuclide basis*** The use of **[tally precision triggers](http://openmc.readthedocs.io/en/latest/io_formats/settings.htmltrigger-element)** with multi-group cross sections* Built-in features for **energy condensation** in downstream data processing* The use of the **`openmc.data`** module to plot continuous-energy vs. multi-group cross sections* **Validation** of multi-group cross sections with **[OpenMOC](https://mit-crpg.github.io/OpenMOC/)****Note:** This Notebook was created using [OpenMOC](https://mit-crpg.github.io/OpenMOC/) to verify the multi-group cross-sections generated by OpenMC. You must install [OpenMOC](https://mit-crpg.github.io/OpenMOC/) on your system in order to run this Notebook in its entirety. In addition, this Notebook illustrates the use of [Pandas](http://pandas.pydata.org/) `DataFrames` to containerize multi-group cross section data.  Generate Input Files<jupyter_code>import numpy as np
import matplotlib.pyplot as plt
plt.style.use('seaborn-dark')

import openmoc

import openmc
import openmc.mgxs as mgxs
import openmc.data
from openmc.openmoc_compatible import get_openmoc_geometry

%matplotlib inline<jupyter_output>/home/romano/miniconda3/envs/python3/lib/python3.6/site-packages/matplotlib/__init__.py:1401: UserWarning:  This call to matplotlib.use() has no effect
because the backend has already been chosen;
matplotlib.use() must be called *before* pylab, matplotlib.pyplot,
or matplotlib.backends is imported for the first time.

  warnings.warn(_use_error_msg)<jupyter_text>First we need to define materials that will be used in the problem. We'll create three distinct materials for water, clad and fuel.<jupyter_code># 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)

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

# zircaloy
zircaloy = openmc.Material(name='Zircaloy')
zircaloy.set_density('g/cm3', 6.55)
zircaloy.add_nuclide('Zr90', 7.2758e-3)<jupyter_output><empty_output><jupyter_text>With our materials, we can now create a `Materials` object that can be exported to an actual XML file.<jupyter_code># Instantiate a Materials collection
materials_file = openmc.Materials([fuel, water, zircaloy])

# Export to "materials.xml"
materials_file.export_to_xml()<jupyter_output><empty_output><jupyter_text>Now let's move on to the geometry. Our problem will have three regions for the fuel, the clad, and the surrounding coolant. The first step is to create the bounding surfaces -- in this case two cylinders and six reflective planes.<jupyter_code># 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=-0.63, boundary_type='reflective')
max_x = openmc.XPlane(x0=+0.63, boundary_type='reflective')
min_y = openmc.YPlane(y0=-0.63, boundary_type='reflective')
max_y = openmc.YPlane(y0=+0.63, boundary_type='reflective')
min_z = openmc.ZPlane(z0=-0.63, boundary_type='reflective')
max_z = openmc.ZPlane(z0=+0.63, boundary_type='reflective')<jupyter_output><empty_output><jupyter_text>With the surfaces defined, we can now create cells that are defined by intersections of half-spaces created by the surfaces.<jupyter_code># Create a Universe to encapsulate a fuel pin
pin_cell_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
pin_cell_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
pin_cell_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
pin_cell_universe.add_cell(moderator_cell)<jupyter_output><empty_output><jupyter_text>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.<jupyter_code># Create root Cell
root_cell = openmc.Cell(name='root cell')
root_cell.region = +min_x & -max_x & +min_y & -max_y
root_cell.fill = pin_cell_universe

# Create root Universe
root_universe = openmc.Universe(universe_id=0, name='root universe')
root_universe.add_cell(root_cell)<jupyter_output><empty_output><jupyter_text>We now must create a geometry that is assigned a root universe and export it to XML.<jupyter_code># Create Geometry and set root Universe
openmc_geometry = openmc.Geometry(root_universe)

# Export to "geometry.xml"
openmc_geometry.export_to_xml()<jupyter_output><empty_output><jupyter_text>Next, we must define simulation parameters. In this case, we will use 10 inactive batches and 40 active batches each with 10,000 particles.<jupyter_code># OpenMC simulation parameters
batches = 50
inactive = 10
particles = 10000

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

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

# Activate tally precision triggers
settings_file.trigger_active = True
settings_file.trigger_max_batches = settings_file.batches * 4

# Export to "settings.xml"
settings_file.export_to_xml()<jupyter_output><empty_output><jupyter_text>Now we are finally ready to make use of the `openmc.mgxs` module to generate multi-group cross sections! First, let's define "coarse" 2-group and "fine" 8-group structures using the built-in `EnergyGroups` class.<jupyter_code># Instantiate a "coarse" 2-group EnergyGroups object
coarse_groups = mgxs.EnergyGroups([0., 0.625, 20.0e6])

# Instantiate a "fine" 8-group EnergyGroups object
fine_groups = mgxs.EnergyGroups([0., 0.058, 0.14, 0.28,
                                 0.625, 4.0, 5.53e3, 821.0e3, 20.0e6])<jupyter_output><empty_output><jupyter_text>Now we will instantiate a variety of `MGXS` objects needed to run an OpenMOC simulation to verify the accuracy of our cross sections. In particular, we define transport, fission, nu-fission, nu-scatter and chi cross sections for each of the three cells in the fuel pin with the 8-group structure as our energy groups.<jupyter_code># Extract all Cells filled by Materials
openmc_cells = openmc_geometry.get_all_material_cells().values()

# Create dictionary to store multi-group cross sections for all cells
xs_library = {}

# Instantiate 8-group cross sections for each cell
for cell in openmc_cells:
    xs_library[cell.id] = {}
    xs_library[cell.id]['transport']  = mgxs.TransportXS(groups=fine_groups)
    xs_library[cell.id]['fission'] = mgxs.FissionXS(groups=fine_groups)
    xs_library[cell.id]['nu-fission'] = mgxs.FissionXS(groups=fine_groups, nu=True)
    xs_library[cell.id]['nu-scatter'] = mgxs.ScatterMatrixXS(groups=fine_groups, nu=True)
    xs_library[cell.id]['chi'] = mgxs.Chi(groups=fine_groups)<jupyter_output><empty_output><jupyter_text>Next, we showcase the use of OpenMC's [tally precision trigger](http://openmc.readthedocs.io/en/latest/io_formats/settings.htmltrigger-element) feature in conjunction with the `openmc.mgxs` module. In particular, we will assign a tally trigger of 1E-2 on the standard deviation for each of the tallies used to compute multi-group cross sections.<jupyter_code># Create a tally trigger for +/- 0.01 on each tally used to compute the multi-group cross sections
tally_trigger = openmc.Trigger('std_dev', 1E-2)

# Add the tally trigger to each of the multi-group cross section tallies
for cell in openmc_cells:
    for mgxs_type in xs_library[cell.id]:
        xs_library[cell.id][mgxs_type].tally_trigger = tally_trigger<jupyter_output><empty_output><jupyter_text>Now, we must loop over all cells to set the cross section domains to the various cells - fuel, clad and moderator - included in the geometry. In addition, we will set each cross section to tally cross sections on a per-nuclide basis through the use of the `MGXS` class' boolean `by_nuclide` instance attribute.<jupyter_code># Instantiate an empty Tallies object
tallies_file = openmc.Tallies()

# Iterate over all cells and cross section types
for cell in openmc_cells:
    for rxn_type in xs_library[cell.id]:

        # Set the cross sections domain to the cell
        xs_library[cell.id][rxn_type].domain = cell
        
        # Tally cross sections by nuclide
        xs_library[cell.id][rxn_type].by_nuclide = True
                
        # Add OpenMC tallies to the tallies file for XML generation
        for tally in xs_library[cell.id][rxn_type].tallies.values():
            tallies_file.append(tally, merge=True)

# Export to "tallies.xml"
tallies_file.export_to_xml()<jupyter_output>/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another CellFilter instance already exists with id=48.
  warn(msg, IDWarning)
/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another CellFilter instance already exists with id=18.
  warn(msg, IDWarning)
/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another EnergyFilter instance already exists with id=2.
  warn(msg, IDWarning)
/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another EnergyoutFilter instance already exists with id=3.
  warn(msg, IDWarning)
/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another CellFilter instance already exists with id=40.
  warn(msg, IDWarning)
/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another CellFilter instance already exists with id=43.
  warn(msg, IDWarning)
/home/romano/openmc/openmc/mixin.py:61: IDWarning: Another EnergyFilter instance already exists with id=13.
  warn(msg, IDWarning)<jupyter_text>Now we a have a complete set of inputs, so we can go ahead and run our simulation.<jupyter_code># Run OpenMC
openmc.run()<jupyter_output>%%%%%%%%%%%%%%%
                          %%%%%%%%%%%%%%%%%%%%%%%%
                       %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                     %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                   %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                  %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
                                   %%%%%%%%%%%%%%%%%%%%%%%%
                                    %%%%%%%%%%%%%%%%%%%%%%%%
                ###############      %%%%%%%%%%%%%%%%%%%%%%%%
               ##################     %%%%%%%%%%%%%%%%%%%%%%%
               ###################     %%%%%%%%%%%%%%%%%%%%%%%
               ####################     %%%%%%%%%%%%%%%%%%%%%%
               #####################     %%%%%%%%%%%%%%%%%%%%%
               ######################     %%%%%%%%%%%%%%%%%%%%
               #######################     %%%%%%%%%%%%%%%%%%
                #######################     %%%%%%%%%%%%%%%%%
                ######################     %%%%%%%%%%[...]<jupyter_text>Tally Data Processing Our simulation ran successfully and created statepoint and summary output files. We begin our analysis by instantiating a `StatePoint` object.<jupyter_code># Load the last statepoint file
sp = openmc.StatePoint('statepoint.082.h5')<jupyter_output><empty_output><jupyter_text>The statepoint is now ready to be analyzed by our multi-group cross sections. We simply have to load the tallies from the `StatePoint` into each object as follows and our `MGXS` objects will compute the cross sections for us under-the-hood.<jupyter_code># Iterate over all cells and cross section types
for cell in openmc_cells:
    for rxn_type in xs_library[cell.id]:
        xs_library[cell.id][rxn_type].load_from_statepoint(sp)<jupyter_output><empty_output><jupyter_text>That's it! Our multi-group cross sections are now ready for the big spotlight. This time we have cross sections in three distinct spatial zones - fuel, clad and moderator - on a per-nuclide basis.  Extracting and Storing MGXS Data Let's first inspect one of our cross sections by printing it to the screen as a microscopic cross section in units of barns.<jupyter_code>nufission = xs_library[fuel_cell.id]['nu-fission']
nufission.print_xs(xs_type='micro', nuclides=['U235', 'U238'])<jupyter_output>Multi-Group XS
	Reaction Type  =	nu-fission
	Domain Type    =	cell
	Domain ID      =	1
	Nuclide        =	U235
	Cross Sections [barns]:
            Group 1 [821000.0   - 20000000.0eV]:	3.30e+00 +/- 2.14e-01%
            Group 2 [5530.0     - 821000.0  eV]:	3.96e+00 +/- 1.33e-01%
            Group 3 [4.0        - 5530.0    eV]:	5.50e+01 +/- 2.29e-01%
            Group 4 [0.625      - 4.0       eV]:	8.85e+01 +/- 3.10e-01%
            Group 5 [0.28       - 0.625     eV]:	2.90e+02 +/- 3.94e-01%
            Group 6 [0.14       - 0.28      eV]:	4.49e+02 +/- 4.12e-01%
            Group 7 [0.058      - 0.14      eV]:	6.87e+02 +/- 3.01e-01%
            Group 8 [0.0        - 0.058     eV]:	1.44e+03 +/- 2.79e-01%

	Nuclide        =	U238
	Cross Sections [barns]:
            Group 1 [821000.0   - 20000000.0eV]:	1.06e+00 +/- 2.53e-01%
            Group 2 [5530.0     - 821000.0  eV]:	1.21e-03 +/- 2.60e-01%
            Group 3 [4.0        - 5530.0    eV]:	5.73e-04 +/- 2.93e+00%
            Group 4 [0.6[...]<jupyter_text>Our multi-group cross sections are capable of summing across all nuclides to provide us with macroscopic cross sections as well.<jupyter_code>nufission = xs_library[fuel_cell.id]['nu-fission']
nufission.print_xs(xs_type='macro', nuclides='sum')<jupyter_output>Multi-Group XS
	Reaction Type  =	nu-fission
	Domain Type    =	cell
	Domain ID      =	1
	Cross Sections [cm^-1]:
            Group 1 [821000.0   - 20000000.0eV]:	2.52e-02 +/- 2.41e-01%
            Group 2 [5530.0     - 821000.0  eV]:	1.51e-03 +/- 1.31e-01%
            Group 3 [4.0        - 5530.0    eV]:	2.07e-02 +/- 2.29e-01%
            Group 4 [0.625      - 4.0       eV]:	3.32e-02 +/- 3.10e-01%
            Group 5 [0.28       - 0.625     eV]:	1.09e-01 +/- 3.94e-01%
            Group 6 [0.14       - 0.28      eV]:	1.69e-01 +/- 4.12e-01%
            Group 7 [0.058      - 0.14      eV]:	2.58e-01 +/- 3.01e-01%
            Group 8 [0.0        - 0.058     eV]:	5.40e-01 +/- 2.79e-01%<jupyter_text>Although a printed report is nice, it is not scalable or flexible. Let's extract the microscopic cross section data for the moderator as a [Pandas](http://pandas.pydata.org/) `DataFrame` .<jupyter_code>nuscatter = xs_library[moderator_cell.id]['nu-scatter']
df = nuscatter.get_pandas_dataframe(xs_type='micro')
df.head(10)<jupyter_output>/home/romano/openmc/openmc/tallies.py:1798: RuntimeWarning: invalid value encountered in true_divide
  self_rel_err = data['self']['std. dev.'] / data['self']['mean']<jupyter_text>Next, we illustate how one can easily take multi-group cross sections and condense them down to a coarser energy group structure. The `MGXS` class includes a `get_condensed_xs(...)` method which takes an `EnergyGroups` parameter with a coarse(r) group structure and returns a new `MGXS` condensed to the coarse groups. We illustrate this process below using the 2-group structure created earlier.<jupyter_code># Extract the 8-group transport cross section for the fuel
fine_xs = xs_library[fuel_cell.id]['transport']

# Condense to the 2-group structure
condensed_xs = fine_xs.get_condensed_xs(coarse_groups)<jupyter_output><empty_output><jupyter_text>Group condensation is as simple as that! We now have a new coarse 2-group `TransportXS` in addition to our original 8-group `TransportXS`. Let's inspect the 2-group `TransportXS` by printing it to the screen and extracting a Pandas `DataFrame` as we have already learned how to do.<jupyter_code>condensed_xs.print_xs()
df = condensed_xs.get_pandas_dataframe(xs_type='micro')
df<jupyter_output><empty_output><jupyter_text>Verification with OpenMOC Now, let's verify our cross sections using OpenMOC. First, we construct an equivalent OpenMOC geometry.<jupyter_code># Create an OpenMOC Geometry from the OpenMC Geometry
openmoc_geometry = get_openmoc_geometry(sp.summary.geometry)<jupyter_output><empty_output><jupyter_text>Next, we we can inject the multi-group cross sections into the equivalent fuel pin cell OpenMOC geometry.<jupyter_code># Get all OpenMOC cells in the gometry
openmoc_cells = openmoc_geometry.getRootUniverse().getAllCells()

# Inject multi-group cross sections into OpenMOC Materials
for cell_id, cell in openmoc_cells.items():
    
    # Ignore the root cell
    if cell.getName() == 'root cell':
        continue
    
    # Get a reference to the Material filling this Cell
    openmoc_material = cell.getFillMaterial()
    
    # Set the number of energy groups for the Material
    openmoc_material.setNumEnergyGroups(fine_groups.num_groups)
    
    # Extract the appropriate cross section objects for this cell
    transport = xs_library[cell_id]['transport']
    nufission = xs_library[cell_id]['nu-fission']
    nuscatter = xs_library[cell_id]['nu-scatter']
    chi = xs_library[cell_id]['chi']
    
    # Inject NumPy arrays of cross section data into the Material
    # NOTE: Sum across nuclides to get macro cross sections needed by OpenMOC
    openmoc_material.setSigmaT(transport.get_xs(nuclides='sum').flatten())
    openmoc_material.setNuSigmaF(nufission.get_xs(nuclides='sum').flatten())
    openmoc_material.setSigmaS(nuscatter.get_xs(nuclides='sum').flatten())
    openmoc_material.setChi(chi.get_xs(nuclides='sum').flatten())<jupyter_output>/home/romano/openmc/openmc/tallies.py:1798: RuntimeWarning: invalid value encountered in true_divide
  self_rel_err = data['self']['std. dev.'] / data['self']['mean']
/home/romano/openmc/openmc/tallies.py:1799: RuntimeWarning: invalid value encountered in true_divide
  other_rel_err = data['other']['std. dev.'] / data['other']['mean']
/home/romano/openmc/openmc/tallies.py:1800: RuntimeWarning: invalid value encountered in true_divide
  new_tally._mean = data['self']['mean'] / data['other']['mean']<jupyter_text>We are now ready to run OpenMOC to verify our cross-sections from OpenMC.<jupyter_code># Generate tracks for OpenMOC
track_generator = openmoc.TrackGenerator(openmoc_geometry, num_azim=128, azim_spacing=0.1)
track_generator.generateTracks()

# Run OpenMOC
solver = openmoc.CPUSolver(track_generator)
solver.computeEigenvalue()<jupyter_output>[  NORMAL ]  Importing ray tracing data from file...
[  NORMAL ]  Computing the eigenvalue...
[  NORMAL ]  Iteration 0:	k_eff = 0.423134	res = 0.000E+00
[  NORMAL ]  Iteration 1:	k_eff = 0.475951	res = 5.769E-01
[  NORMAL ]  Iteration 2:	k_eff = 0.491466	res = 1.248E-01
[  NORMAL ]  Iteration 3:	k_eff = 0.487444	res = 3.260E-02
[  NORMAL ]  Iteration 4:	k_eff = 0.483929	res = 8.184E-03
[  NORMAL ]  Iteration 5:	k_eff = 0.477278	res = 7.213E-03
[  NORMAL ]  Iteration 6:	k_eff = 0.468936	res = 1.374E-02
[  NORMAL ]  Iteration 7:	k_eff = 0.460317	res = 1.748E-02
[  NORMAL ]  Iteration 8:	k_eff = 0.450589	res = 1.838E-02
[  NORMAL ]  Iteration 9:	k_eff = 0.441375	res = 2.113E-02
[  NORMAL ]  Iteration 10:	k_eff = 0.431988	res = 2.045E-02
[  NORMAL ]  Iteration 11:	k_eff = 0.422929	res = 2.127E-02
[  NORMAL ]  Iteration 12:	k_eff = 0.414483	res = 2.097E-02
[  NORMAL ]  Iteration 13:	k_eff = 0.406704	res = 1.997E-02
[  NORMAL ]  Iteration 14:	k_eff = 0.399374	res = 1.877E-02
[  NORMAL ]  Ite[...]<jupyter_text>We report the eigenvalues computed by OpenMC and OpenMOC here together to summarize our results.<jupyter_code># Print report of keff and bias with OpenMC
openmoc_keff = solver.getKeff()
openmc_keff = sp.k_combined[0]
bias = (openmoc_keff - openmc_keff) * 1e5

print('openmc keff = {0:1.6f}'.format(openmc_keff))
print('openmoc keff = {0:1.6f}'.format(openmoc_keff))
print('bias [pcm]: {0:1.1f}'.format(bias))<jupyter_output>openmc keff = 1.224484
openmoc keff = 1.220675
bias [pcm]: -380.9<jupyter_text>As a sanity check, let's run a simulation with the coarse 2-group cross sections to ensure that they also produce a reasonable result.<jupyter_code>openmoc_geometry = get_openmoc_geometry(sp.summary.geometry)
openmoc_cells = openmoc_geometry.getRootUniverse().getAllCells()

# Inject multi-group cross sections into OpenMOC Materials
for cell_id, cell in openmoc_cells.items():
    
    # Ignore the root cell
    if cell.getName() == 'root cell':
        continue
    
    openmoc_material = cell.getFillMaterial()
    openmoc_material.setNumEnergyGroups(coarse_groups.num_groups)
    
    # Extract the appropriate cross section objects for this cell
    transport = xs_library[cell_id]['transport']
    nufission = xs_library[cell_id]['nu-fission']
    nuscatter = xs_library[cell_id]['nu-scatter']
    chi = xs_library[cell_id]['chi']
    
    # Perform group condensation
    transport = transport.get_condensed_xs(coarse_groups)
    nufission = nufission.get_condensed_xs(coarse_groups)
    nuscatter = nuscatter.get_condensed_xs(coarse_groups)
    chi = chi.get_condensed_xs(coarse_groups)
    
    # Inject NumPy arrays of cross section data into the Material
    openmoc_material.setSigmaT(transport.get_xs(nuclides='sum').flatten())
    openmoc_material.setNuSigmaF(nufission.get_xs(nuclides='sum').flatten())
    openmoc_material.setSigmaS(nuscatter.get_xs(nuclides='sum').flatten())
    openmoc_material.setChi(chi.get_xs(nuclides='sum').flatten())
# Generate tracks for OpenMOC
track_generator = openmoc.TrackGenerator(openmoc_geometry, num_azim=128, azim_spacing=0.1)
track_generator.generateTracks()

# Run OpenMOC
solver = openmoc.CPUSolver(track_generator)
solver.computeEigenvalue()
# Print report of keff and bias with OpenMC
openmoc_keff = solver.getKeff()
openmc_keff = sp.k_combined[0]
bias = (openmoc_keff - openmc_keff) * 1e5

print('openmc keff = {0:1.6f}'.format(openmc_keff))
print('openmoc keff = {0:1.6f}'.format(openmoc_keff))
print('bias [pcm]: {0:1.1f}'.format(bias))<jupyter_output>openmc keff = 1.224484
openmoc keff = 1.225211
bias [pcm]: 72.7<jupyter_text>There is a non-trivial bias in both the 2-group and 8-group cases. In the case of a pin cell, one can show that these biases do not converge to <100 pcm with more particle histories. For heterogeneous geometries, additional measures must be taken to address the following three sources of bias:* Appropriate transport-corrected cross sections* Spatial discretization of OpenMOC's mesh* Constant-in-angle multi-group cross sections  Visualizing MGXS Data It is often insightful to generate visual depictions of multi-group cross sections. There are many different types of plots which may be useful for multi-group cross section visualization, only a few of which will be shown here for enrichment and inspiration.One particularly useful visualization is a comparison of the continuous-energy and multi-group cross sections for a particular nuclide and reaction type. We illustrate one option for generating such plots with the use of the `openmc.plotter` module to plot continuous-energy cross sections from the openly available cross section library distributed by NNDC.The MGXS data can also be plotted using the openmc.plot_xs command, however we will do this manually here to show how the openmc.Mgxs.get_xs method can be used to obtain data.<jupyter_code># Create a figure of the U-235 continuous-energy fission cross section 
fig = openmc.plot_xs('U235', ['fission'])

# Get the axis to use for plotting the MGXS
ax = fig.gca()

# Extract energy group bounds and MGXS values to plot
fission = xs_library[fuel_cell.id]['fission']
energy_groups = fission.energy_groups
x = energy_groups.group_edges
y = fission.get_xs(nuclides=['U235'], order_groups='decreasing', xs_type='micro')
y = np.squeeze(y)

# Fix low energy bound
x[0] = 1.e-5

# Extend the mgxs values array for matplotlib's step plot
y = np.insert(y, 0, y[0])

# Create a step plot for the MGXS
ax.plot(x, y, drawstyle='steps', color='r', linewidth=3)

ax.set_title('U-235 Fission Cross Section')
ax.legend(['Continuous', 'Multi-Group'])
ax.set_xlim((x.min(), x.max()))<jupyter_output>/home/romano/openmc/openmc/tallies.py:1798: RuntimeWarning: invalid value encountered in true_divide
  self_rel_err = data['self']['std. dev.'] / data['self']['mean']<jupyter_text>Another useful type of illustration is scattering matrix sparsity structures. First, we extract Pandas `DataFrames` for the H-1 and O-16 scattering matrices.<jupyter_code># Construct a Pandas DataFrame for the microscopic nu-scattering matrix
nuscatter = xs_library[moderator_cell.id]['nu-scatter']
df = nuscatter.get_pandas_dataframe(xs_type='micro')

# Slice DataFrame in two for each nuclide's mean values
h1 = df[df['nuclide'] == 'H1']['mean']
o16 = df[df['nuclide'] == 'O16']['mean']

# Cast DataFrames as NumPy arrays
h1 = h1.values
o16 = o16.values

# Reshape arrays to 2D matrix for plotting
h1.shape = (fine_groups.num_groups, fine_groups.num_groups)
o16.shape = (fine_groups.num_groups, fine_groups.num_groups)<jupyter_output><empty_output><jupyter_text>Matplotlib's `imshow` routine can be used to plot the matrices to illustrate their sparsity structures.<jupyter_code># Create plot of the H-1 scattering matrix
fig = plt.subplot(121)
fig.imshow(h1, interpolation='nearest', cmap='jet')
plt.title('H-1 Scattering Matrix')
plt.xlabel('Group Out')
plt.ylabel('Group In')

# Create plot of the O-16 scattering matrix
fig2 = plt.subplot(122)
fig2.imshow(o16, interpolation='nearest', cmap='jet')
plt.title('O-16 Scattering Matrix')
plt.xlabel('Group Out')
plt.ylabel('Group In')

# Show the plot on screen
plt.show()<jupyter_output><empty_output>
{'fraction_non_alphanumeric': 0.1243440565822496, 'fraction_numerical': 0.039052399422009276, 'mean_word_length': 4.38031914893617, 'pattern_counts': {'":': 0, '<': 103, '<?xml version=': 0, '>': 102, 'https://': 3, 'lorem ipsum': 0, 'www.': 0, 'xml': 8}, 'pii_count': 0, 'substrings_counts': 3, 'word_list_counts': {'cursed_substrings.json': 3, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '28', 'dir': 'jupyter-structured-clean-dedup', 'id': '37965', 'license': 'MIT', 'n_tokens_mistral': 9563, 'n_tokens_neox': 8571, 'n_words': 2427, 'path': 'examples/jupyter/mgxs-part-ii.ipynb', 'repo_name': 'johnnyliu27/openmc', 'size': '26298'}