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 ``.mcnp`` file 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. .. code-block:: python 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)``. .. code-block:: 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. .. code-block:: python 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. .. code-block:: python 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. .. code-block:: python 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. .. code-block:: python 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. .. code-block:: python # 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)``. .. code-block:: python deck.write('my_lattice.mcnp') The following MCNP file should be generated: .. code-block:: mcnp $ 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 .. |gcm3| replace:: g/cm\ :sup:`3` .. |atm-b-cm| replace:: atom/b-cm