Reading a Deck from a File
MCNPy is able to parse existing MCNP decks and work with the resulting in-memory model. Once a deck has been parsed, it can be operated on just as a deck created entirely with MCNPy can be. This example will illustrate how to parse an existing deck, extract some information, and finally make a few alterations.
Formatting Requirements
To support its parsing process, MCNPy imposes a few (minor) formatting requirements for MCNP files:
Use the
.mcnpfile extension.Begin with a
$comment.End with exactly one blank line.
Not have any extra blank lines at the start or end of a file.
Beyond these requirements, there are some select pieces of MCNP syntax that do not parse correctly. Please refer here for the most up-to-date list.
Reading the Deck
Reading a deck is performed using mcnpy.Deck.read(filename) where filename is the name of the on-disk MCNP file.
import mcnpy as mp
deck = mp.Deck.read('my_pincell.mcnp')
Note
Use the full file path for filename if Python was started in a different directory.
The deck has now been parsed. An overview of its contents can be seen with print(deck).
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
Surface Boundaries
With the deck fully parsed, it can now be modified. Since the model is for a pin-cell, let’s convert it into a simple lattice. First, we need to determine which surface(s) compose(s) the pin-cell’s bounding box. While we could refer to the original input file, we can also use MCNPy to extract the same information. Since a meaningful pin-cell model should use reflecting boundaries, any surface with a boundary type matching mcnpy.BoundaryType.REFLECTIVE will be considered as part of the bounding box. Naturally, such assumptions will need to be adjusted for different models. Any boundary surface will be changed to vacuum boundary conditions so that transport between lattice elements is possible.
boundaries = []
for surf in deck.surfaces.values():
if surf.boundary_type == mp.BoundaryType.REFLECTIVE:
surf.boundary_type = mp.BoundaryType.VACUUM
boundaries.append(surf)
New Surfaces
Next, two new surfaces must be added to the deck to support the lattice definition. These are the background fill for the lattice element, elem, and the lattice container, lat_box. Together, these surfaces will ensure that there is no empty space within a lattice element and that all lattice elements fit neatly into the boundary provided by the lattice’s outer container.
elem = mp.ZCylinder(99, 0, 0, 100) # Background surface.
lat_dims = pitch * 3 # Maximun lattice dimension.
# Define lattice container to be 3 × 3 × 1 pin-cells.
lat_box = mp.RectangularPrism(98, -lat_dims/2, lat_dims/2, -lat_dims/2,
lat_dims/2, -pitch/2, pitch/2)
deck += [elem, lat_box]
Modifying Existing Cells
To use the existing cell definitions from the pin-cell model, the CSG region for each cell must be updated to reflect the new surfaces and the cells must also be added to a universe. Regions can easily be updated by iterating over the cells of the deck and subsequently making the necessary substitutions to the region definition of individual cells. Since a Region is a multi-tiered data structure, it is convenient to make the substitutions by leveraging MCNPy’s mcnpy.Region.from_expression method which converts a region specified as a string (as one would in MCNP) into an mcnpy.Region object. The substitutions are simply performed on the string representation of the original region and then the newly defined string is supplied to mcnpy.Region.from_expression where dictionaries of the deck’s surfaces and cells are used to construct the proper region definition.
if len(boundaries) == 1:
bounding_box = boundaries[0]
for cell in deck.cells.values():
if bounding_box in cell.region.get_surfaces().values():
# Create new region definitions.
name = str(bounding_box.name)
if cell == void_cell:
new_region = str(cell.region).replace(name, str(lat_box.name))
else:
new_region = str(cell.region).replace(name, str(elem.name))
# Create pin-cell universe.
cell.universe = mp.Universe(1)
# Redefine cell region.
cell.region = mp.Region.from_expression(new_region, deck.surfaces,
deck.cells)
Note
These substitutions are simplistic since a single surface was used for the pin-cell’s boundaries.
Adding Lattice Cells
With the existing cells updated, two new cells are still required to properly implement the lattice structure.
lat_element = mp.Cell(100, -bounding_box, None)
lat_element.universe = mp.Universe(10)
lat_container = mp.Cell(200, -lat_box, None)
lat_container.fill = lat_element.universe
for cell in [lat_element, lat_container]:
cell.importances = {mp.Particle.NEUTRON : 1.0}
deck += cell
To make lattices more intuitive and easier to manipulate, MCNPy treats them as standalone objects despite an MCNP lattice actually being a permutation of the FILL keyword located on a cell card. This enables users to work exclusively with the lattice itself and then to finally supply it to a cell as the fill parameter. Since Lattice objects are able to accept a numerical array and use it in tandem with a dictionary that maps array values to universe objects, users are able to define their lattice in a variety of ways which would be limited with an array of mcnpy.Universe objects. For instance, lat could be defined as np.ones([1,3,3]) since all universe IDs happen to be “1”. More generally, lattices can be easily defined and reshaped using all methods that are applicable to arrays from Python’s NumPy package. Importantly, MCNPy will also return mcnpy.Lattice objects when extracting data from parsed MCNP decks enabling the same kind of modification flexibility for existing inputs.
# Use NumPy package for defining arrays.
import numpy as np
# Array of size 1, 3, 3.
lat = np.array([[[1,1,1], # i = (-1 to 1), j = -1
[1,1,1], # i = (-1 to 1), j = -0
[1,1,1]]]) # i = (-1 to 1), j = 1
# Define lattice with array of universe IDs and dictionary
# mapping universe IDs to universe objects.
lattice = mp.Lattice(i=[-1,1], j=[-1,1], k=[0,0], lattice=lat,
type='REC', universes=deck.universes)
lat_element.fill = lattice
Writing the MCNP File
Now that the lattice 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_lattice.mcnp')
The following MCNP file should be generated:
$ This file was written with mcnpy
1 1 -10.0 -1 -99 U 1 IMP:N=1.0
2 3 -6.6 2 -3 -99 U 1 IMP:N=1.0
3 2 -1.0 3 -99 U 1 IMP:N=1.0
4 4 -1.78E-4 1 -2 -99 U 1 IMP:N=1.0
5 0 98 IMP:N=0.0
100 0 -4 U 10 LAT 1 FILL -1:1 -1:1 0:0
1 1 1 $ i = (-1 to 1), j = -1
1 1 1 $ i = (-1 to 1), j = 0
1 1 1 $ i = (-1 to 1), j = 1
IMP:N=1.0
200 0 -98 FILL 10 IMP:N=1.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
99 CZ 100.0
98 RPP -1.89 1.89 -1.89 1.89 -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
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