armi.reactor.grids module¶
This contains structured meshes in multiple geometries and spatial locators (i.e. locations).
Grids are objects that map indices (i, j, k) to spatial locations (x,y,z) or (t,r,z).
They are useful for arranging things in reactors, such as:
Fuel assemblies in a reactor
Plates in a heat exchanger
Pins in a fuel assembly
Blocks in a fuel assembly (1-D)
Fast reactors often use a hexagonal grid, while other reactors may be better suited for Cartesian or RZT grids. This module contains representations of all these.
``Grid``s can be defined by any arbitrary combination of absolute grid boundaries and unit step directions.
Associated with grids are IndexLocations. Each of these maps to a single cell in a grid,
or to an arbitrary point in the continuous space represented by a grid. When a Grid` is
built, it builds a collection of ``IndexLocation``s, one for each cell.
In the ARMI armi.reactor module, each object is assigned a locator either from a grid
or in arbitrary, continuous space (using a CoordinateLocation) on the
spatialLocator attribute.
Below is a basic example of how to use a 2-D grid:
>>> grid = cartesianGridFromRectangle(1.0, 1.0) # 1 cm square-pitch Cartesian grid
>>> location = grid[1,2,0]
>>> location.getGlobalCoordinates()
array([ 1., 2., 0.])
Grids can be chained together in a parent-child relationship. This is often used in
ARMI where a 1-D axial grid (e.g. in an assembly) is being positioned in a core
or spent-fuel pool. See example in
armi.reactor.tests.test_grids.TestSpatialLocator.test_recursion().
The “radial” (ring, position) indexing used in DIF3D can be converted to
and from the more quasi-Cartesian indexing in a hex mesh easily with the utility methods
HexGrid.getRingPos() and indicesToRingPos().
Note
IndexLocation is intended to replace armi.reactor.locations.Location.
This module is designed to satisfy the spatial arrangement requirements of
the Reactor package.
Throughout the module, the term global refers to the top-level coordinate system while the word local refers to within the current coordinate system defined by the current grid.
-
class
armi.reactor.grids.LocationBase(i, j, k, grid)[source]¶ Bases:
objectA namedtuple-like object for storing location information.
It’s immutable (you can’t set things after construction) and has names.
Notes
This was originally a namedtuple but there was a somewhat unbelievable bug in Python 2.7.8 where unpickling a reactor full of these ended up inexplicably replacing one of them with an AssemblyParameterCollection. The bug did not show up in Python 3.
Converting to this class solved that problem.
-
__setstate__(state)[source]¶ Unpickle a locator, the grid will attach itself if it was also pickled, otherwis this will be detached.
-
property
i¶
-
property
j¶
-
property
k¶
-
property
grid¶
-
__hash__()[source]¶ Define a hash so we can use these as dict keys w/o having exact object.
Notes
Including the
gridattribute may be more robust; however, using only (i, j, k) allows dictionaries to use IndexLocations and (i,j,k) tuples interchangeably.
-
__lt__(that)[source]¶ A Locationbase is less than another if the pseudo-radius is less, or if equal, in order any index is less.
Examples
>>> grid = grids.hexGridFromPitch(1.0) >>> grid[0, 0, 0] < grid[2, 3, 4] # the "radius" is less True >>> grid[2, 3, 4] < grid[2, 3, 4] # they are equal False >>> grid[2, 3, 4] < grid[-2, 3, 4] # 2 is greater than -2 False >>> grid[-2, 3, 4] < grid[2, 3, 4] # -2 is less than 2 True >>> grid[1, 3, 4] < grid[-2, 3, 4] # the "radius" is less True
-
-
class
armi.reactor.grids.IndexLocation(i, j, k, grid)[source]¶ Bases:
armi.reactor.grids.LocationBaseAn immutable location representing one cell in a grid.
The locator is intimately tied to a grid and together, they represent a grid cell somewhere in the coordinate system of the grid.
gridis not in the constructor (must be added after construction ) because the extra argument (grid) gives an inconsistency between __init__ and __new__. Unfortunately this decision makes whipping up IndexLocations on the fly awkward. But perhaps that’s ok because they should only be created by their grids.-
__add__(that)[source]¶ Enable adding with other objects like this and/or 3-tuples.
Tuples are needed so we can terminate the recursive additions with a (0,0,0) basis.
-
detachedCopy()[source]¶ Make a copy of this locator that is not associated with a grid.
See also
armi.reactor.reactors.detach()uses this
-
property
parentLocation¶ Get the spatialLocator of the ArmiObject that this locator’s grid is anchored to.
For example, if this is one of many spatialLocators in a 2-D grid representing a reactor, then the
parentLocationis the spatialLocator of the reactor, which will often be aCoordinateLocation.
-
property
indices¶ Get the non-grid indices (i,j,k) of this locator.
This strips off the annoying
gridtagalong which is there to ensure proper equality (i.e. (0,0,0) in a storage rack is not equal to (0,0,0) in a core).It is a numpy array for two reasons: 1. It can be added and subtracted for the recursive computations
through different coordinate systems
It can be written/read from the database.
-
getCompleteIndices() → Tuple[int, int, int][source]¶ Transform the indices of this object up to the top mesh.
The top mesh is either the one where there’s no more parent (true top) or when an axis gets added twice. Unlike with coordinates, you can only add each index axis one time. Thus a complete set of indices is one where an index for each axis has been defined by a set of 1, 2, or 3 nested grids.
This is useful for getting the reactor-level (i,j,k) indices of an object in a multi-layered 2-D(assemblies in core)/1-D(blocks in assembly) mesh like the one mapping blocks up to reactor in Hex reactors.
The benefit of that particular mesh over a 3-D one is that different assemblies can have different axial meshes, a common situation.
It will just return local indices for pin-meshes inside of blocks.
A tuple is returned so that it is easy to compare pairs of indices.
-
getLocalCoordinates(nativeCoords=False)[source]¶ Return the coordinates of the center of the mesh cell here in cm.
-
getGlobalCoordinates(nativeCoords=False)[source]¶ Get coordinates in global 3D space of the centroid of this object.
-
-
class
armi.reactor.grids.CoordinateLocation(i, j, k, grid)[source]¶ Bases:
armi.reactor.grids.IndexLocationA triple representing a point in space.
This is still associated with a grid. The grid defines the continuous coordinate space and axes that the location is within. This also links to the composite tree.
-
getLocalCoordinates(nativeCoords=False)[source]¶ Return x,y,z coordinates in cm within the grid’s coordinate system.
-
-
class
armi.reactor.grids.Grid(unitSteps=(0, 0, 0), bounds=(None, None, None), unitStepLimits=((0, 1), (0, 1), (0, 1)), offset=None, armiObject=None)[source]¶ Bases:
objectA connected set of cells characterized by indices mapping to space and vice versa.
The cells may be characterized by any mixture of regular repeating steps and user-defined steps in any direction.
For example, a 2-D hex lattice has constant, regular steps whereas a 3-D hex mesh may have user-defined axial meshes. Similar for Cartesian, RZT, etc.
- Parameters
unitSteps (tuple of tuples, optional) –
Describes the grid spatially as a function on indices. Each tuple describes how each
(x,y,or z)dimension is influenced by(i,j,k). In other words, it is:(dxi, dxj, jxk), (dyi, dyj, dyk), (dzi, dzj, dzk)
- where
dmnis the distance (in cm) that dimensionmwill change as a function of index
n.
Unit steps are used as a generic method for defining repetitive grids in a variety of geometries, including hexagonal and Cartesian. The tuples are not vectors in the direction of the translation, but rather grouped by direction. If the bounds argument is described for a direction, the bounds will be used rather than the unit step information. The default of (0, 0, 0) makes all dimensions insensitive to indices since the coordinates are calculated by the dot product of this and the indices. With this default, any dimension that is desired to change with indices should be defined with bounds. RZtheta grids are created exclusively with bounds.
- where
bounds (3-tuple) – Absolute increasing bounds in cm including endpoints of a non-uniform grid. Each item represents the boundaries in the associated direction. Use Nones when unitSteps should be applied instead. Most useful for thetaRZ grids or other non-uniform grids.
unitStepLimits (3-tuple) – The limit of the steps in all three directions. This constrains step-defined grids to be finite so we can populate them with SpatialLocator objects.
offset (3-tuple, optional) – Offset in cm for each axis. By default the center of the (0,0,0)-th object is in the center of the grid. Offsets can move it so that the (0,0,0)-th object can be fully within a quadrant (i.e. in a Cartesian grid).
armiObject (ArmiObject, optional) – The ArmiObject that this grid describes. For example if it’s a 1-D assembly grid, the armiObject is the assembly. Note that
self.armiObject.spatialGridisself.
Examples
A 2D a rectangular grid with width (x) 2 and height (y) 3 would be:
>>> grid = Grid(unitSteps=((2, 0, 0), (0, 3, 0),(0, 0, 0)))
A regular hex grid with pitch 1 is:
>>> grid = Grid(unitSteps= ((sqrt(3)/2, 0.0, 0.0), (0.5, 1.0, 0.0), (0, 0, 0))
Note
For this unit hex the magnitude of the vector constructed using the 0th index of each tuple is 1.0.
Notes
Each dimension must either be defined through unitSteps or bounds. The combination of unitSteps with bounds was settled upon after some struggle to have one unified definition of a grid (i.e. just bounds). A hexagonal grid is somewhat challenging to represent with bounds because the axes are not orthogonal, so a unit-direction vector plus bounds would be required. And then the bounds would be wasted space because they can be derived simply by unit steps. Memory efficiency is important in this object so the compact representation of unitSteps-when-possible, bounds-otherwise was settled upon.
Design considerations include:
unitSteps are more intuitive as operations starting from the center of a cell, particularly with hexagons and rectangles. Otherwise the 0,0 position of a hexagon in the center of 1/3-symmetric hexagon is at the phantom bottom left of the hexagon.
Users generally prefer to input mesh bounds rather than centers (e.g. starting at 0.5 instead of 0.0 in a unit mesh is weird).
If we store bounds, computing bounds is simple and computing centers takes ~2x the effort. If we store centers, it’s the opposite.
Regardless of how we store things, we’ll need a Grid that has the lower-left assembly fully inside the problem (i.e. for full core Cartesian) as well as another one that has the lower-left assembly half-way or quarter-way sliced off (for 1/2, 1/4, and 1/8 symmetries). The
offsetparameter handles this.Looking up mesh boundaries (to define a mesh in another code) is generally more common than looking up centers (for plotting or measuring distance).
A grid can be anchored to the object that it is in with a backreference. This gives it the ability to traverse the composite tree and map local to global locations without having to duplicate the composite pattern on grids. This remains optional so grids can be used for non-reactor-package reasons. It may seem slightly cleaner to set the armiObject to the parent’s spatialLocator itself but the major disadvantage of this is that when an object moves, the armiObject would have to be updated. By anchoring directly to Composite objects, the parent is always up to date no matter where or how things get moved.
Unit step calculations use dot products and must not be polluted by the bound indices. Thus we reduce the size of the unitSteps tuple accordingly.
-
reduce()[source]¶ Return the set of arguments used to create this Grid.
This is very much like the argument tuple from
__reduce__, but we do not implement__reduce__for real, because we are generally happy with__getstate__and__setstate__for pickling purposes. However, getting these arguments to__init__is useful for storing Grids to the database, as they are more stable (less likely to change) than the actual internal state of the objects.The return value should be hashable, such that a set of these can be created.
-
__getstate__()[source]¶ Pickling removes reference to
armiObjectRemoving the
armiObjectallows us to pickle an assembly without pickling the entire reactor. AnAssembly.spatialLocator.grid.armiObjectis the reactor, by removing the link here, we still have spatial orientation, but are not required to pickle the entire reactor to pickle an assembly.This relies on the
armiObject.__setstate__to assign itself.
-
__setstate__(state)[source]¶ Pickling removes reference to
armiObjectThis relies on the
ArmiObject.__setstate__to assign itself.
-
__getitem__(ijk)[source]¶ Get a location by (i, j, k) indices. If it does not exist, create a new one and return it.
Notes
The method is defaultdict-like, in that it will create a new location on the fly. However, the class itself is not really a dictionary, it is just index-able. For example, there is no desire to have a
__setitem__method, because the only way to create a location is by retrieving it or throughbuildLocations.
-
getCoordinates(indices, nativeCoords=False)[source]¶ Return the coordinates of the center of the mesh cell at the given given indices in cm.
-
getNeighboringCellIndices(i, j=0, k=0)[source]¶ Return the indices of the immediate neighbors of a mesh point in the plane.
-
cellIndicesContainingPoint(x, y=0.0, z=0.0)[source]¶ Return the indices of a mesh cell that contains a point.
-
getLocationFromRingAndPos(i, j, k=0)[source]¶ Return the location based on ring and position.
- Parameters
i (int) – Ring index
j (int) – Position index
k (int, optional) – Axial index
See also
HexGrid.getLocationFromRingAndPos()This implements a special method to transform the i, j location based on ring and position.
-
getIndexBounds()[source]¶ Get min index and number of indices in this grid.
Step-defined grids would be infinite but for the step limits defined in the constructor.
Notes
This produces output that is intended to be passed to a
rangestatement.
-
class
armi.reactor.grids.HexGrid(unitSteps=(0, 0, 0), bounds=(None, None, None), unitStepLimits=((0, 1), (0, 1), (0, 1)), offset=None, armiObject=None)[source]¶ Bases:
armi.reactor.grids.GridHas 6 neighbors in plane.
-
property
pitch¶ Get the hex-pitch of a regular hexagonal array.
See also
-
getNeighboringCellIndices(i, j=0, k=0)[source]¶ Return the indices of the immediate neighbors of a mesh point in the plane.
Note that these neighbors are ordered counter-clockwise beginning from 2 o’clock. This is very important!
-
getLocationFromRingAndPos(i, j, k=0)[source]¶ Return the location based on ring and position.
- Parameters
i (int) – Ring index
j (int) – Position index
k (int, optional) – Axial index
See also
-
getRingPos(indices)[source]¶ Get 1-based ring and position from normal indices.
See also
getIndicesFromRingAndPos()does the reverse
-
overlapsWhichSymmetryLine(indices)[source]¶ Return a list of which lines of symmetry this is on.
If none, returns [] If on a line of symmetry in 1/6 geometry, returns a list containing a 6. If on a line of symmetry in 1/3 geometry, returns a list containing a 3. Only the 1/3 core view geometry is actually coded in here right now.
Being “on” a symmety line means the line goes through the middle of you.
-
getSymmetricIdenticalsThird(indices)[source]¶ This works by rotating the indices by 120 degrees twice, counterclockwise.
-
triangleCoords(indices)[source]¶ Return 6 coordinate pairs representing the centers of the 6 triangles in a hexagon centered here.
Ignores z-coordinate and only operates in 2D for now.
-
isInFirstThird(locator, includeTopEdge=False)[source]¶ True if locator is in first third of hex grid.
-
generateSortedHexLocationList(nLocs)[source]¶ Generate a list IndexLocations, sorted based on their distance from the center.
IndexLocation are taken from a full core.
Ties between locations with the same distance (e.g. A3001 and A3003) are broken by ring number then position number.
-
allPositionsInThird(ring, includeEdgeAssems=False)[source]¶ Returns a list of all the positions in a ring (in the first third)
- Parameters
ring (int) – The ring to check
includeEdgeAssems (bool, optional) – If True, include repeated positions in odd ring numbers. Default: False
Notes
Rings start at 1, positions start at 1
- Returns
positions
- Return type
int
-
property
-
class
armi.reactor.grids.ThetaRZGrid(unitSteps=(0, 0, 0), bounds=(None, None, None), unitStepLimits=((0, 1), (0, 1), (0, 1)), offset=None, armiObject=None)[source]¶ Bases:
armi.reactor.grids.GridA grid characterized by azimuthal, radial, and zeta indices.
The angular meshes are limited to 0 to 2pi radians. R and Zeta are as in other meshes.
See Figure 2.2 in Derstine 1984, ANL. [DIF3D].
-
getCoordinates(indices, nativeCoords=False)[source]¶ Return the coordinates of the center of the mesh cell at the given given indices in cm.
-
indicesOfBounds(rad0, rad1, theta0, theta1, sigma=0.0001)[source]¶ Return indices corresponding to upper and lower radial and theta bounds.
- Parameters
rad0 (float) – inner radius of control volume
rad1 (float) – outer radius of control volume
theta0 (float) – inner azimuthal location of control volume in radians
theta1 (float) – inner azimuthal of control volume in radians
sigma (float) – acceptable relative error (i.e. if one of the positions in the mesh are within this error it’ll act the same if it matches a position in the mesh)
- Returns
tuple
- Return type
i, j, k of given bounds
-
-
armi.reactor.grids.hexGridFromPitch(pitch, numRings=25, armiObject=None, pointedEndUp=False)[source]¶ Build a finite step-based 2-D hex grid from a hex pitch in cm.
- Parameters
pitch (float) – Hex pitch (flat-to-flat) in cm
numRings (int, optional) – The number of rings in the grid to pre-populate with locatator objects. Even if positions are not pre-populated, locators will be generated there on the fly.
armiObject (ArmiObject, optional) – The object that this grid is anchored to (i.e. the reactor for a grid of assemblies)
pointedEndUp (bool, optional) – Rotate the hexagons 30 degrees so that the pointed end faces up instead of the flat.
- Returns
A functional hexagonal grid object.
- Return type
-
armi.reactor.grids.cartesianGridFromRectangle(width, height, numRings=25, isOffset=False, armiObject=None)[source]¶ Build a finite step-based 2-D Cartesian grid based on a width and height in cm.
- isOffsetbool
If true will put first mesh cell fully within the grid instead of centering it on the crosshairs.
-
armi.reactor.grids.axialUnitGrid(numCells, armiObject=None)[source]¶ Build a 1-D unit grid in the k-direction based on a number of times. Each mesh is 1cm wide.
numCells + 1 mesh boundaries are added, since one block would require a bottom and a top.
-
armi.reactor.grids.thetaRZGridFromGeom(geom, armiObject=None)[source]¶ Build 2-D R-theta grid based on a Geometry object.
- Parameters
geomInfo (list) – list of ((indices), assemName) tuples for all positions in core with input in radians
See also
armi.reactor.geometry.SystemLayoutInput.readGeomXML()produces the geomInfo structure
Examples
>>> grid = grids.thetaRZGridFromGeom(geomInfo)
-
armi.reactor.grids.ringPosFromRingLabel(ringLabel)[source]¶ Convert a ring-based label like A2003B into 1-based ring, location indices.
-
armi.reactor.grids.indicesToRingPos(i, j)[source]¶ Convert spatialLocator indices to ring/position.
One benefit it has is that it never has negative numbers.
Notes
Ring, pos index system goes in counterclockwise hex rings.
-
armi.reactor.grids.addingIsValid(myGrid, parentGrid)[source]¶ True if adding a indices from one grid to another is considered valid.
In ARMI we allow the addition of a 1-D axial grid with a 2-D grid. We do not allow any other kind of adding. This enables the 2D/1D grid layout in Assemblies/Blocks but does not allow 2D indexing in pins to become inconsistent.