This notebook demonstrates how systematic analysis of tally scores is possible using Pandas dataframes. A dataframe can be automatically generated using the Tally.get_pandas_dataframe(...) method. Furthermore, by linking the tally data in a statepoint file with geometry and material information from a summary file, the dataframe can be shown with user-supplied labels.

Note: that this Notebook was created using the latest Pandas v0.16.1. Everything in the Notebook will wun with older versions of Pandas, but the multi-indexing option in >v0.15.0 makes the tables look prettier.


In [1]:
import glob
from IPython.display import Image
import matplotlib.pylab as pylab
import scipy.stats
import numpy as np

import openmc
from openmc.statepoint import StatePoint
from openmc.summary import Summary

%matplotlib inline

Generate Input Files

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


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)

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

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

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


In [4]:
# Instantiate a MaterialsFile, add Materials
materials_file = openmc.MaterialsFile()
materials_file.add_material(fuel)
materials_file.add_material(water)
materials_file.add_material(zircaloy)
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, which we can use OpenMC's lattice/universe feature for. 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
# Use both reflective and vacuum boundaries to make life interesting
min_x = openmc.XPlane(x0=-10.71, boundary_type='reflective')
max_x = openmc.XPlane(x0=+10.71, boundary_type='vacuum')
min_y = openmc.YPlane(y0=-10.71, boundary_type='vacuum')
max_y = openmc.YPlane(y0=+10.71, boundary_type='reflective')
min_z = openmc.ZPlane(z0=-10.71, boundary_type='reflective')
max_z = openmc.ZPlane(z0=+10.71, boundary_type='reflective')

With the surfaces defined, we can now create cells that are defined by intersections of half-spaces created by the surfaces.


In [6]:
# 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.add_surface(fuel_outer_radius, halfspace=-1)
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.add_surface(fuel_outer_radius, halfspace=+1)
clad_cell.add_surface(clad_outer_radius, halfspace=-1)
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.add_surface(clad_outer_radius, halfspace=+1)
pin_cell_universe.add_cell(moderator_cell)

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


In [7]:
# Create fuel assembly Lattice
assembly = openmc.RectLattice(name='1.6% Fuel - 0BA')
assembly.dimension = (17, 17)
assembly.pitch = (1.26, 1.26)
assembly.lower_left = [-1.26 * 17. / 2.0] * 2
assembly.universes = [[pin_cell_universe] * 17] * 17

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 [8]:
# Create root Cell
root_cell = openmc.Cell(name='root cell')
root_cell.fill = assembly

# Add boundary planes
root_cell.add_surface(min_x, halfspace=+1)
root_cell.add_surface(max_x, halfspace=-1)
root_cell.add_surface(min_y, halfspace=+1)
root_cell.add_surface(max_y, halfspace=-1)
root_cell.add_surface(min_z, halfspace=+1)
root_cell.add_surface(max_z, halfspace=-1)

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

We now must create a geometry that is assigned a root universe, put the geometry into a geometry file, and export it to XML.


In [9]:
# Create Geometry and set root Universe
geometry = openmc.Geometry()
geometry.root_universe = root_universe

In [10]:
# Instantiate a GeometryFile
geometry_file = openmc.GeometryFile()
geometry_file.geometry = geometry

# Export to "geometry.xml"
geometry_file.export_to_xml()

With the geometry and materials finished, we now just need to define simulation parameters. In this case, we will use 5 inactive batches and 15 minimum active batches each with 2500 particles. We also tell OpenMC to turn tally triggers on, which means it will keep running until some criterion on the uncertainty of tallies is reached.


In [11]:
# OpenMC simulation parameters
min_batches = 20
max_batches = 200
inactive = 5
particles = 2500

# Instantiate a SettingsFile
settings_file = openmc.SettingsFile()
settings_file.batches = min_batches
settings_file.inactive = inactive
settings_file.particles = particles
settings_file.output = {'tallies': False, 'summary': True}
settings_file.trigger_active = True
settings_file.trigger_max_batches = max_batches
source_bounds = [-10.71, -10.71, -10, 10.71, 10.71, 10.]
settings_file.set_source_space('box', source_bounds)

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

Let us also create a plot file that we can use to verify that our pin cell geometry was created successfully.


In [12]:
# Instantiate a Plot
plot = openmc.Plot(plot_id=1)
plot.filename = 'materials-xy'
plot.origin = [0, 0, 0]
plot.width = [21.5, 21.5]
plot.pixels = [250, 250]
plot.color = 'mat'

# Instantiate a PlotsFile, add Plot, and export to "plots.xml"
plot_file = openmc.PlotsFile()
plot_file.add_plot(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 [13]:
# Run openmc in plotting mode
executor = openmc.Executor()
executor.plot_geometry(output=False)

In [14]:
# 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[14]:

As we can see from the plot, we have a nice array of pin cells with fuel, cladding, and water! Before we run our simulation, we need to tell the code what we want to tally. The following code shows how to create a variety of tallies.


In [15]:
# Instantiate an empty TalliesFile
tallies_file = openmc.TalliesFile()
tallies_file._tallies = []

Instantiate a fission rate mesh Tally


In [16]:
# Instantiate a tally Mesh
mesh = openmc.Mesh(mesh_id=1)
mesh.type = 'rectangular'
mesh.dimension = [17, 17]
mesh.lower_left = [-10.71, -10.71]
mesh.width = [1.26, 1.26]

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

# Instantiate energy Filter
energy_filter = openmc.Filter()
energy_filter.type = 'energy'
energy_filter.bins = np.array([0, 0.625e-6, 20.])

# Instantiate the Tally
tally = openmc.Tally(name='mesh tally')
tally.add_filter(mesh_filter)
tally.add_filter(energy_filter)
tally.add_score('fission')
tally.add_score('nu-fission')

# Add mesh and Tally to TalliesFile
tallies_file.add_mesh(mesh)
tallies_file.add_tally(tally)

Instantiate a cell Tally with nuclides


In [17]:
# Instantiate tally Filter
cell_filter = openmc.Filter(type='cell', bins=[fuel_cell.id])

# Instantiate the tally
tally = openmc.Tally(name='cell tally')
tally.add_filter(cell_filter)
tally.add_score('scatter-y2')
tally.add_nuclide(u235)
tally.add_nuclide(u238)

# Add mesh and tally to TalliesFile
tallies_file.add_tally(tally)

Create a "distribcell" Tally. The distribcell filter allows us to tally multiple repeated instances of the same cell throughout the geometry.


In [18]:
# Instantiate tally Filter
distribcell_filter = openmc.Filter(type='distribcell', bins=[moderator_cell.id])

# Instantiate tally Trigger for kicks
trigger = openmc.Trigger(trigger_type='std_dev', threshold=5e-5)
trigger.add_score('absorption')

# Instantiate the Tally
tally = openmc.Tally(name='distribcell tally')
tally.add_filter(distribcell_filter)
tally.add_score('absorption')
tally.add_score('scatter')
tally.add_trigger(trigger)

# Add mesh and tally to TalliesFile
tallies_file.add_tally(tally)

In [19]:
# Export to "tallies.xml"
tallies_file.export_to_xml()

Now we a have a complete set of inputs, so we can go ahead and run our simulation.


In [20]:
# Remove old HDF5 (summary, statepoint) files
!rm statepoint.*

# Run OpenMC with MPI!
executor.run_simulation(mpi_procs=4)


       .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-2015 Massachusetts Institute of Technology
      License:        http://mit-crpg.github.io/openmc/license.html
      Version:        0.6.2
      Date/Time:      2015-08-11 13:40:43
      MPI Processes:  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: 92238.71c
 Loading ACE cross section table: 8016.71c
 Loading ACE cross section table: 92235.71c
 Loading ACE cross section table: 5010.71c
 Loading ACE cross section table: 1001.71c
 Loading ACE cross section table: 40090.71c
 Initializing source particles...

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

  Bat./Gen.      k            Average k         
  =========   ========   ====================   
        1/1    0.60069                       
        2/1    0.62857                       
        3/1    0.69431                       
        4/1    0.65935                       
        5/1    0.68092                       
        6/1    0.64791                       
        7/1    0.65859    0.65325 +/- 0.00534
        8/1    0.67381    0.66010 +/- 0.00752
        9/1    0.74149    0.68045 +/- 0.02103
       10/1    0.68244    0.68085 +/- 0.01629
       11/1    0.68068    0.68082 +/- 0.01330
       12/1    0.70394    0.68412 +/- 0.01172
       13/1    0.68624    0.68439 +/- 0.01015
       14/1    0.65667    0.68131 +/- 0.00947
       15/1    0.70080    0.68326 +/- 0.00869
       16/1    0.69639    0.68445 +/- 0.00795
       17/1    0.68786    0.68474 +/- 0.00726
       18/1    0.63698    0.68106 +/- 0.00762
       19/1    0.62785    0.67726 +/- 0.00802
       20/1    0.65759    0.67595 +/- 0.00758
 Triggers unsatisfied, max unc./thresh. is 1.20713 for absorption in tally 10002
 The estimated number of batches is 27
 Creating state point statepoint.020.h5...
       21/1    0.68391    0.67645 +/- 0.00711
       22/1    0.69243    0.67739 +/- 0.00674
       23/1    0.65491    0.67614 +/- 0.00648
       24/1    0.64021    0.67425 +/- 0.00641
       25/1    0.72281    0.67668 +/- 0.00655
       26/1    0.71261    0.67839 +/- 0.00646
       27/1    0.69503    0.67914 +/- 0.00621
 Triggers satisfied for batch 27
 Creating state point statepoint.027.h5...

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


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

 Total time for initialization     =  9.7300E-01 seconds
   Reading cross sections          =  3.0300E-01 seconds
 Total time in simulation          =  5.9130E+00 seconds
   Time in transport only          =  5.4000E+00 seconds
   Time in inactive batches        =  7.7300E-01 seconds
   Time in active batches          =  5.1400E+00 seconds
   Time synchronizing fission bank =  4.4600E-01 seconds
     Sampling source sites         =  0.0000E+00 seconds
     SEND/RECV source sites        =  0.0000E+00 seconds
   Time accumulating tallies       =  9.0000E-03 seconds
 Total time for finalization       =  0.0000E+00 seconds
 Total time elapsed                =  6.8870E+00 seconds
 Calculation Rate (inactive)       =  16170.8 neutrons/second
 Calculation Rate (active)         =  7295.72 neutrons/second

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

 k-effective (Collision)     =  0.68117 +/-  0.00597
 k-effective (Track-length)  =  0.67914 +/-  0.00621
 k-effective (Absorption)    =  0.67898 +/-  0.00471
 Combined k-effective        =  0.67922 +/-  0.00479
 Leakage Fraction            =  0.34264 +/-  0.00301

Out[20]:
0

Tally Data Processing


In [21]:
# We do not know how many batches were needed to satisfy the 
# tally trigger(s), so find the statepoint file(s)
statepoints = glob.glob('statepoint.*.h5')

# Load the last statepoint file
sp = StatePoint(statepoints[-1])
sp.read_results()
sp.compute_stdev()

In [22]:
# Load the summary file and link with statepoint
su = Summary('summary.h5')
sp.link_with_summary(su)

Analyze the mesh fission rate tally


In [23]:
# Find the mesh tally with the StatePoint API
tally = sp.get_tally(name='mesh tally')

# Print a little info about the mesh tally to the screen
print(tally)


Tally
	ID             =	10000
	Name           =	mesh tally
	Filters        =	
                		mesh	[1]
                		energy	[  0.00000000e+00   6.25000000e-07   2.00000000e+01]
	Nuclides       =	total 
	Scores         =	['fission', 'nu-fission']
	Estimator      =	tracklength

Use the new Tally data retrieval API with pure NumPy


In [24]:
# Get the relative error for the thermal fission reaction 
# rates in the four corner pins 
data = tally.get_values(scores=['fission'], filters=['mesh', 'energy'], \
                        filter_bins=[((1,1),(1,17), (17,1), (17,17)), \
                                    ((0., 0.625e-6),)], value='rel_err')
print(data)


[[[ 0.14583021]]

 [[ 0.07846909]]

 [[ 0.33705448]]

 [[ 0.15150059]]]

In [25]:
# Get a pandas dataframe for the mesh tally data
df = tally.get_pandas_dataframe(nuclides=False)

# Print the first twenty rows in the dataframe
df.head(20)


Out[25]:
mesh 1 energy [MeV] score mean std. dev.
x y z
bin
0 1 1 1 0.0e+00 - 6.3e-07 fission 0.000236 0.000034
1 1 1 1 0.0e+00 - 6.3e-07 nu-fission 0.000576 0.000084
2 1 1 1 6.3e-07 - 2.0e+01 fission 0.000069 0.000004
3 1 1 1 6.3e-07 - 2.0e+01 nu-fission 0.000183 0.000011
4 1 2 1 0.0e+00 - 6.3e-07 fission 0.000366 0.000052
5 1 2 1 0.0e+00 - 6.3e-07 nu-fission 0.000892 0.000127
6 1 2 1 6.3e-07 - 2.0e+01 fission 0.000109 0.000009
7 1 2 1 6.3e-07 - 2.0e+01 nu-fission 0.000284 0.000021
8 1 3 1 0.0e+00 - 6.3e-07 fission 0.000540 0.000058
9 1 3 1 0.0e+00 - 6.3e-07 nu-fission 0.001316 0.000141
10 1 3 1 6.3e-07 - 2.0e+01 fission 0.000144 0.000017
11 1 3 1 6.3e-07 - 2.0e+01 nu-fission 0.000376 0.000041
12 1 4 1 0.0e+00 - 6.3e-07 fission 0.000830 0.000085
13 1 4 1 0.0e+00 - 6.3e-07 nu-fission 0.002022 0.000207
14 1 4 1 6.3e-07 - 2.0e+01 fission 0.000168 0.000013
15 1 4 1 6.3e-07 - 2.0e+01 nu-fission 0.000434 0.000034
16 1 5 1 0.0e+00 - 6.3e-07 fission 0.000738 0.000043
17 1 5 1 0.0e+00 - 6.3e-07 nu-fission 0.001799 0.000105
18 1 5 1 6.3e-07 - 2.0e+01 fission 0.000186 0.000010
19 1 5 1 6.3e-07 - 2.0e+01 nu-fission 0.000486 0.000026

In [26]:
# Create a boxplot to view the distribution of
# fission and nu-fission rates in the pins
bp = df.boxplot(column='mean', by='score')



In [27]:
# Extract thermal nu-fission rates from pandas
fiss = df[df['score'] == 'nu-fission']
fiss = fiss[fiss['energy [MeV]'] == '0.0e+00 - 6.3e-07']

# Extract mean and reshape as 2D NumPy arrays
mean = fiss['mean'].reshape((17,17))

pylab.imshow(mean, interpolation='nearest')
pylab.title('fission rate')
pylab.xlabel('x')
pylab.ylabel('y')
pylab.colorbar()


Out[27]:
<matplotlib.colorbar.Colorbar instance at 0x7f0b654da998>

Analyze the cell+nuclides scatter-y2 rate tally


In [28]:
# Find the cell Tally with the StatePoint API
tally = sp.get_tally(name='cell tally')

# Print a little info about the cell tally to the screen
print(tally)


Tally
	ID             =	10001
	Name           =	cell tally
	Filters        =	
                		cell	[10000]
	Nuclides       =	U-235 U-238 
	Scores         =	['scatter-Y0,0', 'scatter-Y1,-1', 'scatter-Y1,0', 'scatter-Y1,1', 'scatter-Y2,-2', 'scatter-Y2,-1', 'scatter-Y2,0', 'scatter-Y2,1', 'scatter-Y2,2']
	Estimator      =	analog


In [29]:
# Get a pandas dataframe for the cell tally data
df = tally.get_pandas_dataframe()

# Print the first twenty rows in the dataframe
df.head(100)


Out[29]:
cell nuclide score mean std. dev.
bin
0 10000 U-235 scatter-Y0,0 0.036453 0.000941
1 10000 U-235 scatter-Y1,-1 -0.000725 0.000261
2 10000 U-235 scatter-Y1,0 -0.000088 0.000408
3 10000 U-235 scatter-Y1,1 0.000986 0.000412
4 10000 U-235 scatter-Y2,-2 0.000098 0.000204
5 10000 U-235 scatter-Y2,-1 -0.000358 0.000247
6 10000 U-235 scatter-Y2,0 0.000197 0.000140
7 10000 U-235 scatter-Y2,1 -0.000084 0.000196
8 10000 U-235 scatter-Y2,2 0.000052 0.000168
9 10000 U-238 scatter-Y0,0 2.325600 0.015545
10 10000 U-238 scatter-Y1,-1 -0.030089 0.002460
11 10000 U-238 scatter-Y1,0 -0.004451 0.003663
12 10000 U-238 scatter-Y1,1 0.020832 0.002831
13 10000 U-238 scatter-Y2,-2 -0.004149 0.001530
14 10000 U-238 scatter-Y2,-1 0.000735 0.001729
15 10000 U-238 scatter-Y2,0 0.003431 0.002098
16 10000 U-238 scatter-Y2,1 0.000385 0.001263
17 10000 U-238 scatter-Y2,2 0.000002 0.001718

Use the new Tally data retrieval API with pure NumPy


In [30]:
# Get the standard deviations for two of the spherical harmonic
# scattering reaction rates 
data = tally.get_values(scores=['scatter-Y2,2', 'scatter-Y0,0'], 
                        nuclides=['U-238', 'U-235'], value='std_dev')
print(data)


[[[ 0.00171834  0.01554515]
  [ 0.00016768  0.00094081]]]

Analyze the distribcell tally


In [31]:
# Find the distribcell Tally with the StatePoint API
tally = sp.get_tally(name='distribcell tally')

# Print a little info about the distribcell tally to the screen
print(tally)


Tally
	ID             =	10002
	Name           =	distribcell tally
	Filters        =	
                		distribcell	[10002]
	Nuclides       =	total 
	Scores         =	['absorption', 'scatter']
	Estimator      =	tracklength

Use the new Tally data retrieval API with pure NumPy


In [32]:
# Get the relative error for the scattering reaction rates in
# the first 30 distribcell instances 
data = tally.get_values(scores=['scatter'], filters=['distribcell'],
                        filter_bins=[range(10)], value='rel_err')
print(data)


[[[ 0.04682759]]

 [[ 0.03205271]]

 [[ 0.03592433]]

 [[ 0.02417979]]

 [[ 0.02524314]]

 [[ 0.02390359]]

 [[ 0.0274475 ]]

 [[ 0.02827721]]

 [[ 0.0231313 ]]

 [[ 0.01898386]]]

Print the distribcell tally dataframe without OpenCG info


In [33]:
# Get a pandas dataframe for the distribcell tally data
df = tally.get_pandas_dataframe(nuclides=False)

# Print the last twenty rows in the dataframe
df.tail(20)


Out[33]:
distribcell score mean std. dev.
bin
558 279 absorption 0.000095 0.000009
559 279 scatter 0.013611 0.000544
560 280 absorption 0.000096 0.000009
561 280 scatter 0.013999 0.000568
562 281 absorption 0.000117 0.000015
563 281 scatter 0.015951 0.000682
564 282 absorption 0.000104 0.000011
565 282 scatter 0.016057 0.000574
566 283 absorption 0.000117 0.000013
567 283 scatter 0.015997 0.000706
568 284 absorption 0.000108 0.000007
569 284 scatter 0.016720 0.000639
570 285 absorption 0.000116 0.000008
571 285 scatter 0.017764 0.000639
572 286 absorption 0.000111 0.000014
573 286 scatter 0.018101 0.000766
574 287 absorption 0.000115 0.000012
575 287 scatter 0.018411 0.000655
576 288 absorption 0.000138 0.000014
577 288 scatter 0.019154 0.000763

Print the distribcell tally dataframe with OpenCG info


In [34]:
# Get a pandas dataframe for the distribcell tally data
df = tally.get_pandas_dataframe(summary=su, nuclides=False)

# Print the last twenty rows in the dataframe
df.head(20)


Out[34]:
level 1 level 2 level 3 distribcell score mean std. dev.
cell univ lat cell univ
id id id x y z id id
bin
0 10003 0 10001 0 0 0 10002 10000 0 absorption 0.000122 0.000010
1 10003 0 10001 0 0 0 10002 10000 0 scatter 0.018596 0.000871
2 10003 0 10001 1 0 0 10002 10000 1 absorption 0.000206 0.000014
3 10003 0 10001 1 0 0 10002 10000 1 scatter 0.029733 0.000953
4 10003 0 10001 2 0 0 10002 10000 2 absorption 0.000280 0.000018
5 10003 0 10001 2 0 0 10002 10000 2 scatter 0.038494 0.001383
6 10003 0 10001 3 0 0 10002 10000 3 absorption 0.000384 0.000026
7 10003 0 10001 3 0 0 10002 10000 3 scatter 0.048839 0.001181
8 10003 0 10001 4 0 0 10002 10000 4 absorption 0.000457 0.000023
9 10003 0 10001 4 0 0 10002 10000 4 scatter 0.058061 0.001466
10 10003 0 10001 5 0 0 10002 10000 5 absorption 0.000494 0.000026
11 10003 0 10001 5 0 0 10002 10000 5 scatter 0.065874 0.001575
12 10003 0 10001 6 0 0 10002 10000 6 absorption 0.000490 0.000032
13 10003 0 10001 6 0 0 10002 10000 6 scatter 0.072420 0.001988
14 10003 0 10001 7 0 0 10002 10000 7 absorption 0.000605 0.000042
15 10003 0 10001 7 0 0 10002 10000 7 scatter 0.078802 0.002228
16 10003 0 10001 8 0 0 10002 10000 8 absorption 0.000627 0.000037
17 10003 0 10001 8 0 0 10002 10000 8 scatter 0.083684 0.001936
18 10003 0 10001 9 0 0 10002 10000 9 absorption 0.000711 0.000040
19 10003 0 10001 9 0 0 10002 10000 9 scatter 0.088989 0.001689

In [35]:
# Show summary statistics for absorption distribcell tally data
absorption = df[df['score'] == 'absorption']
absorption[['mean', 'std. dev.']].dropna().describe()

# Note that the maximum standard deviation does indeed
# meet the 5e-4 threshold set by the tally trigger


Out[35]:
mean std. dev.
count 289.000000 289.000000
mean 0.000414 0.000025
std 0.000241 0.000010
min 0.000013 0.000003
25% 0.000204 0.000017
50% 0.000387 0.000024
75% 0.000594 0.000031
max 0.000919 0.000060

Perform a statistical test comparing the tally sample distributions for two categories of fuel pins.


In [36]:
# Extract tally data from pins in the pins divided along y=x diagonal 
multi_index = ('level 2', 'lat',)
lower = df[df[multi_index + ('x',)] + df[multi_index + ('y',)] < 16]
upper = df[df[multi_index + ('x',)] + df[multi_index + ('y',)] > 16]
lower = lower[lower['score'] == 'absorption']
upper = upper[upper['score'] == 'absorption']

# Perform non-parametric Mann-Whitney U Test to see if the 
# absorption rates (may) come from same sampling distribution
u, p = scipy.stats.mannwhitneyu(lower['mean'], upper['mean'])
print('Mann-Whitney Test p-value: {0}'.format(p))


Mann-Whitney Test p-value: 0.378626583393

Note that the symmetry implied by the y=x diagonal ensures that the two sampling distributions are identical. Indeed, as illustrated by the test above, for any reasonable significance level (e.g., $\alpha$=0.05) one would not reject the null hypothesis that the two sampling distributions are identical.

Next, perform the same test but with two groupings of pins which are not symmetrically identical to one another.


In [37]:
# Extract tally data from pins in the pins divided along y=-x diagonal
multi_index = ('level 2', 'lat',)
lower = df[df[multi_index + ('x',)] > df[multi_index + ('y',)]]
upper = df[df[multi_index + ('x',)] < df[multi_index + ('y',)]]
lower = lower[lower['score'] == 'absorption']
upper = upper[upper['score'] == 'absorption']

# Perform non-parametric Mann-Whitney U Test to see if the 
# absorption rates (may) come from same sampling distribution
u, p = scipy.stats.mannwhitneyu(lower['mean'], upper['mean'])
print('Mann-Whitney Test p-value: {0}'.format(p))


Mann-Whitney Test p-value: 7.18782749267e-43

Note that the asymmetry implied by the y=-x diagonal ensures that the two sampling distributions are not identical. Indeed, as illustrated by the test above, for any reasonable significance level (e.g., $\alpha$=0.05) one would reject the null hypothesis that the two sampling distributions are identical.


In [38]:
# Extract the scatter tally data from pandas
scatter = df[df['score'] == 'scatter']

scatter['rel. err.'] = scatter['std. dev.'] / scatter['mean']

# Show a scatter plot of the mean vs. the std. dev.
scatter.plot(kind='scatter', x='mean', y='rel. err.', title='Scattering Rates')


/usr/local/lib/python2.7/dist-packages/IPython/kernel/__main__.py:4: SettingWithCopyWarning: 
A value is trying to be set on a copy of a slice from a DataFrame.
Try using .loc[row_indexer,col_indexer] = value instead

See the the caveats in the documentation: http://pandas.pydata.org/pandas-docs/stable/indexing.html#indexing-view-versus-copy
Out[38]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f0b654a8850>

In [39]:
# Plot a histogram and kernel density estimate for the scattering rates
scatter['mean'].plot(kind='hist', bins=25)
scatter['mean'].plot(kind='kde')
pylab.title('Scattering Rates')
pylab.xlabel('Mean')
pylab.legend(['KDE', 'Histogram'])


Out[39]:
<matplotlib.legend.Legend at 0x7f0b6527a390>