Modeling a Pin-Cell

In this example, we’ll create a simple pin-cell model. This will demonstrate the basics of building a geometry with mcnpy and setting up the supporting MCNP cards to run the simulation.

Initializing the Model

The start of any mcnpy script begins with imports as with any other Python package.

import mcnpy as mp

When mcnpy is imported, you’ll see the following output:

Metamodel Gateway Server Started

This indicates that the link between the Python and Java portions of the API has been established. After a slight delay, the module will finish importing and mcnpy will otherwise perform like a standard Python package.

deck = mp.Deck()

This creates our MCNP deck (or input file). Currently, the deck is empty as can be seen by calling print(deck).

MCNP Deck

    **CELL CARDS**
    Cells          =        0
    Universes      =        0

    **SURFACE CARDS**
    Surfaces       =        0

    **DATA CARDS**
    Geometry       =        0
    Transformation =        0
    Physics        =        0
    Source         =        0
    Var. Reduction =        0
    Tallies        =        0
    Tal. Settings  =        0
    Output         =        0
    Termination    =        0
    Misc           =        0
    Other Data     =        0

    **MATERIAL CARDS**
    Materials      =        0
    Mat. Settings  =        0

Note

As cards are added to the deck, this output will update accordingly.

Defining Materials

Now a new MCNP deck has been created. Cards can now be created and added to the deck. We will start with the material cards.

from mcnpy.elements import *
enr = 3
u_enr = U[238]%(100-enr) + U[235]%enr
fuel = mp.Material(u_enr + O[16]@2)

Materials can be defined symbolically using the singleton objects from mcnpy.elements such as U and O for uranium and oxygen, respectively. To assign the appropriate fractions to each isotope, the operators in the following table should be used.

Operation

Symbol (Python)

Symbol (MCNP)

Atomic Fraction

@

+

Weight Fraction

*

-

Weight Percent

%

N/A

Unlike is native MCNP syntax, operators can be combined within a single material. Here, the uranium enrichment is defined using Weight percentages while the final UO2 mixture uses atomic fractions. When the associated material card is generated, all conversions between the fraction types will be handled automatically. As a fissionable material, it is also important to add a thermal scattering card.

# Include S(a,B). Normally added as a separate card in MCNP.
fuel.s_alpha_beta = ['o_in_uo2', 'u238_in_uo2']
mod = mp.Nuclide('h1', 2.00) + mp.Nuclide('8016', 1.0)
# Alternatively...
# mod = mp.Material(H[1]@2 + O[16])
mod.s_alpha_beta = 'lwtr'

solutes = {SN: 1.5, FE: 0.2, CR: 0.1}
zirc4 = ZR%(100-sum(solutes.values()))
for sol in solutes:
    zirc4 += sol%solutes[sol]
clad = mp.Material(zirc4)

gap = mp.Material(H[1]@0.5 + HE[4]@0.5)

Note

Numeric ZAIDs are still perfectly valid.

The mcnpy.Material objects can now be added to the deck.

deck += [fuel, mod, clad, gap]

After creating any object corresponding to an MCNP card (Cell, Surface, Material, etc.), it must be added to the deck. This is easily done using the += operation.

Defining Surfaces

Next, the surface cards will be defined. The pin-cell will be defined using several Z-cylinders along with a rectangular bounding box.

fuel_outer_radius = mp.ZCylinder(name=1, x0=0, y0=0, r=0.39)
clad_inner_radius = mp.ZCylinder(name=2, x0=0, y0=0, r=0.40)
clad_outer_radius = mp.ZCylinder(name=3, x0=0, y0=0, r=0.46)

The bounding box for the pin-cell will be defined using an RPP macrobody. Macrobodies can be specified in the same way as simple surfaces.

pitch = 1.26
bounding_box = mp.RectangularPrism(x0=-pitch/2, x1=pitch/2, y0=-pitch/2,
                                   y1=pitch/2, z0=-pitch/2, z1=pitch/2,
                                   boundary_type='reflective', name=4)
deck += [fuel_outer_radius, clad_inner_radius, clad_outer_radius,
         bounding_box]

The X, Y, and Z limits for the macrobody are defined using a variable pitch for the pin-cell. We also use boundary_type to specify reflecting boundaries (or * in MCNP syntax).

Defining Regions

With all the surfaces defined, the regions that will define the cells can be created next. To define mcnpy.Region objects, a series of special operators are applied to surfaces.

fuel_region = -fuel_outer_radius & -bounding_box
gap_region = +fuel_outer_radius & -clad_inner_radius & -bounding_box
clad_region = +clad_inner_radius & -clad_outer_radius & -bounding_box
mod_region = +clad_outer_radius & -bounding_box
outside_region = +bounding_box

Note

Since a region is not an MCNP card itself, we do not need to directly add them to the deck. Instead, each mcnpy.Region will be added to an mcnpy.Cell.

Halfspaces are defined by prepending a mcnpy.Surface with + or - for positive and negative senses, respectively.

Halfspaces may then be combined with the following operations:

Operation

Symbol (Python)

Symbol (MCNP)

Semantics

Union

|

:

OR

Intersection

&

space

AND

Complement

~

#

NOT

Note

Complements may be applied to cells or regions.

Defining Cells

fuel_cell = mp.Cell(1, fuel_region, fuel*10.0)
# Alternatively...
# fuel_cell = mp.Cell(name=1, region=fuel_region, density=-10.0,
#                     material=fuel)

A mcnpy.Cell is defined for the fuel and requires a name, region, density, and material. Densities can be conveniently assigned using operators on the material argument. These operators are described in the following table.

Unit

Symbol (Python)

Symbol (MCNP)

atom/b-cm

@

+

g/cm3

*

-

clad_cell = mp.Cell(2, clad_region, clad*6.6)
mod_cell = mp.Cell(3, mod_region, mod*1.0)
gap_cell = mp.Cell(4, gap_region, gap*1.78e-4)
void_cell = mp.Cell(5, outside_region, None)
deck += [fuel_cell, clad_cell, mod_cell, gap_cell, void_cell]

The other cells are defined in the same way, except for void_cell. Since this cell corresponds to a region outside of the pin-cell’s reflecting boundaries, its material is set to void via material=None and no density is required.

Setting Cell Importances

MCNP requires that particle importances are set for each cell in the model. For this example, we will set the importance of neutrons to 1.0 in each cell except void_cell where the importance will be 0.0. Particle types (and many other enumerated inputs) can be set using a string or through an Enum like mcnpy.Particle.NEUTRON.

for cell in deck.cells.values():
    if cell.material is None:
        cell.importances = {mp.Particle.NEUTRON : 0.0}
    else:
        cell.importances = {'n' : 1.0}

Importances are provided as dictionaries where each importance is a key and lists of particles are the values. Here importances are set by looping over the cells.

Source Definition

For the pin-cell, we will be solving an eigenvalue problem. This can be set up with MCNP’s KCODE card.

# Define criticality source
deck += mp.CriticalitySource(histories=1e4, keff_guess=1.0, skip_cycles=200,
                            cycles=1200)
deck += mp.CriticalitySourcePoints([(0,0,0), (0,0,0.5), (0,0,-0.5)])

Using mcnpy.CriticalitySource and mcnpy.CriticalitySourcePoints, we are able to configure the source definition for the eigenvalue problem.

Output Control

To enable easy post-processing, it is useful to generate an MCTAL (tally summary) file. This can be done in a simple one line command.

deck += mp.PrintDump(print_mctal=1)

With the last card added to the deck, we can once again call print(deck) to see what the deck now includes:

MCNP Deck

    **CELL CARDS**
    Cells          =        5
    Universes      =        1

    **SURFACE CARDS**
    Surfaces       =        4

    **DATA CARDS**
    Geometry       =        0
    Transformation =        0
    Physics        =        0
    Source         =        2
    Var. Reduction =        0
    Tallies        =        0
    Tal. Settings  =        0
    Output         =        1
    Termination    =        0
    Misc           =        0
    Other Data     =        0

    **MATERIAL CARDS**
    Materials      =        4
    Mat. Settings  =        2

Writing the MCNP File

Now that the pin-cell model is complete, the final step is to write the model to a file which MCNP can understand. This is done by invoking write(filename).

deck.write('my_pincell.mcnp')

The following MCNP file should be generated:

$ This file was written with mcnpy
1 1 -10.0 -1 -4 IMP:N=1.0
2 3 -6.6 2 -3 -4 IMP:N=1.0
3 2 -1.0 3 -4 IMP:N=1.0
4 4 -1.78E-4 1 -2 -4 IMP:N=1.0
5 0 4 IMP:N=0.0

1 CZ 0.39
2 CZ 0.4
3 CZ 0.46
*4 RPP -0.63 0.63 -0.63 0.63 -0.63 0.63

M1
    92238  0.9696278737791809
    92235  0.030372126220819046
    8016  2.0
M2
    1001  2.0
    8016  1.0
M3
    40000  -0.982
    50000  -0.015
    26000  -0.002
    24000  -0.001
M4
    1001  0.5
    2004  0.5
MT1 O2-U U-O2
MT2 LWTR
KCODE 10000.0 1.0 200.0 1200.0
KSRC 0.0 0.0 0.0 0.0 0.0 0.5 0.0 0.0 -0.5
PRDMP 0.0 -60.0 1.0

With a complete input file, you are now ready to run MCNP. The generated file will always follow MCNP’s syntax and can even be read and modified using mcnpy. The full model can also be generated using

import mcnpy as mp

# Pin-cell example class
pincell = mp.example.Pincell()
# Deck object for the pin-cell
deck = pincell.deck