Skip to content

Commit

Permalink
Refactor Grid to be ABC, intermediate StructuredGrid
Browse files Browse the repository at this point in the history
Heavier lift, but the classes defined in `armi.reactor.grids`
follow this inheritance pattern

- `Grid(abc.ABC)` :: abstract interface for _a_ grid, or things that reside in
  space
  - `StructuredGrid(Grid)` :: still abstract, but for things that have
    some structure, known to ARMI as unit steps and bounds
    - `HexGrid(StructuredGrid)`
    - `CartesianGrid(StructuredGrid)
    - `ThetaRZGrid(StructuredGrid)` - though how "structured" is this
      really?
    - `AxialGrid(StructuredGrid)`

In some cases, the shared interface between structured grids falls
apart. For example, `HexGrid.pitch` returns a single float, while
`CartesianGrid.pitch` is a 2-ple of float, one for x and one for y.
And the concept of ring and position appears to be stressed since the
inception of cartesian grids. But parts of ARMI require an interface
and so an attempt was made to define that interface.

The decision was made to not have `StructuredGrid` the base class, and
to introduce `Grid` as the base intentionally. There are cases for some
multiphysics codes where a grid may still be "structured" but not with
the level of structure the previous `Grid` implementations expect. One
example would be an irregular cartesian grid, where the unit cell
spacing in one or more dimensions may change. So the cell spacing is a
function of the cell position in that dimension.
  • Loading branch information
drewj-usnctech committed Aug 1, 2023
1 parent 1df8cf1 commit a312849
Show file tree
Hide file tree
Showing 8 changed files with 900 additions and 636 deletions.
3 changes: 2 additions & 1 deletion armi/reactor/grids/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@
addingIsValid,
)

from .grid import Grid, GridParameters, _tuplify
from .grid import Grid
from .structuredgrid import StructuredGrid, GridParameters, _tuplify
from .axial import AxialGrid, axialUnitGrid
from .cartesian import CartesianGrid
from .hexagonal import HexGrid, COS30, SIN30, TRIANGLES_IN_HEXAGON
Expand Down
50 changes: 47 additions & 3 deletions armi/reactor/grids/axial.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
from typing import Optional, TYPE_CHECKING
from typing import List, Optional, TYPE_CHECKING, NoReturn
import warnings

import numpy

from .grid import Grid
from .locations import IJType, LocationBase
from .structuredgrid import StructuredGrid

if TYPE_CHECKING:
from armi.reactor.composites import ArmiObject


class AxialGrid(Grid):
class AxialGrid(StructuredGrid):
"""1-D grid in the k-direction (z)
.. note:::
Expand All @@ -34,6 +36,43 @@ def fromNCells(
armiObject=armiObject,
)

@staticmethod
def getSymmetricEquivalents(indices: IJType) -> List[IJType]:
return []

@staticmethod
def locatorInDomain(
locator: LocationBase, symmetryOverlap: Optional[bool] = False
) -> NoReturn:
raise NotImplementedError

@staticmethod
def getIndicesFromRingAndPos(ring: int, pos: int) -> NoReturn:
raise NotImplementedError

@staticmethod
def getMinimumRings(n: int) -> NoReturn:
raise NotImplementedError

@staticmethod
def getPositionsInRing(ring: int) -> NoReturn:
raise NotImplementedError

@staticmethod
def overlapsWhichSymmetryLine(indices: IJType) -> None:
return None

@property
def pitch(self) -> float:
"""Grid spacing in the z-direction
Returns
-------
float
Pitch in cm
"""


def axialUnitGrid(
numCells: int, armiObject: Optional["ArmiObject"] = None
Expand All @@ -46,4 +85,9 @@ def axialUnitGrid(
Use :class:`AxialUnitGrid` class instead
"""
warnings.warn(
"Use grids.AxialGrid class rather than function",
PendingDeprecationWarning,
stacklevel=2,
)
return AxialGrid.fromNCells(numCells, armiObject)
68 changes: 47 additions & 21 deletions armi/reactor/grids/cartesian.py
Original file line number Diff line number Diff line change
@@ -1,29 +1,22 @@
# Copyright 2023 TerraPower, LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import itertools
from typing import Optional
from typing import Optional, NoReturn, Tuple

import numpy

from armi.reactor import geometry

from .grid import Grid
from .locations import IJType
from .structuredgrid import StructuredGrid


class CartesianGrid(Grid):
class CartesianGrid(StructuredGrid):
"""
Grid class representing a conformal Cartesian mesh.
Grid class representing a conformal Cartesian mesh
.. note::
It is recommended to call :meth:`fromRectangle` to construct,
rather than directly constructing with ``__init__``
Notes
-----
Expand Down Expand Up @@ -103,6 +96,17 @@ def fromRectangle(
symmetry=symmetry,
)

def overlapsWhichSymmetryLine(self, indices: IJType) -> None:
"""Return lines of symmetry position at a given index can be found
.. warning::
This is not really implemented, but parts of ARMI need it to
not fail, so it always returns None.
"""
return None

def getRingPos(self, indices):
"""
Return ring and position from indices.
Expand Down Expand Up @@ -159,14 +163,14 @@ def getRingPos(self, indices):
return (int(ring) + 1, int(pos) + 1)

@staticmethod
def getIndicesFromRingAndPos(ring, pos):
def getIndicesFromRingAndPos(ring: int, pos: int) -> NoReturn:
"""Not implemented for Cartesian-see getRingPos notes."""
raise NotImplementedError(
"Cartesian should not need need ring/pos, use i, j indices."
"See getRingPos doc string notes for more information/example."
)

def getMinimumRings(self, n):
def getMinimumRings(self, n: int) -> int:
"""Return the minimum number of rings needed to fit ``n`` objects."""
numPositions = 0
ring = 0
Expand All @@ -178,10 +182,15 @@ def getMinimumRings(self, n):

return ring

def getPositionsInRing(self, ring):
def getPositionsInRing(self, ring: int) -> int:
"""
Return the number of positions within a ring.
Parameters
----------
ring : int
Ring in question
Notes
-----
The number of positions within a ring will change
Expand All @@ -204,7 +213,7 @@ def locatorInDomain(self, locator, symmetryOverlap: Optional[bool] = False):
else:
return True

def changePitch(self, xw, yw):
def changePitch(self, xw: float, yw: float):
"""
Change the pitch of a Cartesian grid.
Expand Down Expand Up @@ -278,3 +287,20 @@ def getSymmetricEquivalents(self, indices):
def _isThroughCenter(self):
"""Return whether the central cells are split through the middle for symmetry."""
return all(self._offset == [0, 0, 0])

@property
def pitch(self) -> Tuple[float, float]:
"""Grid pitch in the x and y dimension
Returns
-------
float
x-pitch (cm)
float
y-pitch (cm)
"""
pitch = (self._unitSteps[0][0], self._unitSteps[1][1])
if pitch[0] == 0:
raise ValueError(f"Grid {self} does not have a defined pitch.")
return pitch
Loading

0 comments on commit a312849

Please sign in to comment.