.. _methods_geometry:

========
Geometry
========

---------------------------
Constructive Solid Geometry
---------------------------

OpenMC uses a technique known as `constructive solid geometry`_ (CSG) to build
arbitrarily complex three-dimensional models in Euclidean space. In a CSG model,
every unique object is described as the union and/or intersection of
*half-spaces* created by bounding `surfaces`_. Every surface divides all of
space into exactly two half-spaces. We can mathematically define a surface as a
collection of points that satisfy an equation of the form :math:`f(x,y,z) = 0`
where :math:`f(x,y,z)` is a given function. All coordinates for which
:math:`f(x,y,z) < 0` are referred to as the negative half-space (or simply the
*negative side*) and coordinates for which :math:`f(x,y,z) > 0` are referred to
as the positive half-space.

Let us take the example of a sphere centered at the point :math:`(x_0,y_0,z_0)`
with radius :math:`R`. One would normally write the equation of the sphere as

.. math::
    :label: sphere-equation

    (x - x_0)^2 + (y - y_0)^2 + (z - z_0)^2 = R^2

By subtracting the right-hand term from both sides of equation
:eq:`sphere-equation`, we can then write the surface equation for the sphere:

.. math::
    :label: surface-equation-sphere

    f(x,y,z) = (x - x_0)^2 + (y - y_0)^2 + (z - z_0)^2 - R^2 = 0

One can confirm that any point inside this sphere will correspond to
:math:`f(x,y,z) < 0` and any point outside the sphere will correspond to
:math:`f(x,y,z) > 0`.

In OpenMC, every surface defined by the user is assigned an integer to uniquely
identify it. We can then refer to either of the two half-spaces created by a
surface by a combination of the unique ID of the surface and a positive/negative
sign.  Figure :num:`fig-halfspace` shows an example of an ellipse with unique ID 1
dividing space into two half-spaces.

.. _fig-halfspace:

.. figure:: ../_images/halfspace.svg
   :align: center
   :figclass: align-center

   Example of an ellipse and its associated half-spaces.

References to half-spaces created by surfaces are used to define regions of
space of uniform composition, which are then assigned to *cells*. OpenMC allows
regions to be defined using union, intersection, and complement operators. As in
MCNP_, the intersection operator is implicit as doesn't need to be written in a
region specification. A defined region is then associated with a material
composition in a cell. Figure :num:`fig-union` shows an example of a cell region
defined as the intersection of an ellipse and two planes.

.. _fig-union:

.. figure:: ../_images/union.svg
   :align: center
   :figclass: align-center

   The shaded region represents a cell bounded by three surfaces.

The ability to form regions based on bounding quadratic surfaces enables OpenMC
to model arbitrarily complex three-dimensional objects. In practice, one is
limited only by the different surface types available in OpenMC. The following
table lists the available surface types, the identifier used to specify them in
input files, the corresponding surface equation, and the input parameters needed
to fully define the surface.

.. table:: Surface types available in OpenMC.

    +----------------------+------------+------------------------------+-------------------------+
    | Surface              | Identifier | Equation                     | Parameters              |
    +======================+============+==============================+=========================+
    | Plane perpendicular  | x-plane    | :math:`x - x_0 = 0`          | :math:`x_0`             |
    | to :math:`x`-axis    |            |                              |                         |
    +----------------------+------------+------------------------------+-------------------------+
    | Plane perpendicular  | y-plane    | :math:`y - y_0 = 0`          | :math:`y_0`             |
    | to :math:`y`-axis    |            |                              |                         |
    +----------------------+------------+------------------------------+-------------------------+
    | Plane perpendicular  | z-plane    | :math:`z - z_0 = 0`          | :math:`z_0`             |
    | to :math:`z`-axis    |            |                              |                         |
    +----------------------+------------+------------------------------+-------------------------+
    | Arbitrary plane      | plane      | :math:`Ax + By + Cz = D`     | :math:`A\;B\;C\;D`      |
    +----------------------+------------+------------------------------+-------------------------+
    | Infinite cylinder    | x-cylinder | :math:`(y-y_0)^2 + (z-z_0)^2 | :math:`y_0\;z_0\;R`     |
    | parallel to          |            | = R^2`                       |                         |
    | :math:`x`-axis       |            |                              |                         |
    +----------------------+------------+------------------------------+-------------------------+
    | Infinite cylinder    | y-cylinder | :math:`(x-x_0)^2 + (z-z_0)^2 | :math:`x_0\;z_0\;R`     |
    | parallel to          |            | = R^2`                       |                         |
    | :math:`y`-axis       |            |                              |                         |
    +----------------------+------------+------------------------------+-------------------------+
    | Infinite cylinder    | z-cylinder | :math:`(x-x_0)^2 + (y-y_0)^2 | :math:`x_0\;y_0\;R`     |
    | parallel to          |            | = R^2`                       |                         |
    | :math:`z`-axis       |            |                              |                         |
    +----------------------+------------+------------------------------+-------------------------+
    | Sphere               | sphere     | :math:`(x-x_0)^2 + (y-y_0)^2 | :math:`x_0 \; y_0 \;    |
    |                      |            | + (z-z_0)^2 = R^2`           | z_0 \; R`               |
    +----------------------+------------+------------------------------+-------------------------+
    | Cone parallel to the | x-cone     | :math:`(y-y_0)^2 + (z-z_0)^2 | :math:`x_0 \; y_0 \;    |
    | :math:`x`-axis       |            | = R^2(x-x_0)^2`              | z_0 \; R^2`             |
    +----------------------+------------+------------------------------+-------------------------+
    | Cone parallel to the | y-cone     | :math:`(x-x_0)^2 + (z-z_0)^2 | :math:`x_0 \; y_0 \;    |
    | :math:`y`-axis       |            | = R^2(y-y_0)^2`              | z_0 \; R^2`             |
    +----------------------+------------+------------------------------+-------------------------+
    | Cone parallel to the | z-cone     | :math:`(x-x_0)^2 + (y-y_0)^2 | :math:`x_0 \; y_0 \;    |
    | :math:`z`-axis       |            | = R^2(z-z_0)^2`              | z_0 \; R^2`             |
    +----------------------+------------+------------------------------+-------------------------+
    | General quadric      | quadric    | :math:`Ax^2 + By^2 + Cz^2 +  | :math:`A \; B \; C \; D |
    | surface              |            | Dxy + Eyz + Fxz + Gx + Hy +  | \; E \; F \; G \; H \;  |
    |                      |            | Jz + K = 0`                  | J \; K`                 |
    +----------------------+------------+------------------------------+-------------------------+
    | Torus parallel to the| x-torus    | :math:`(x-x_0)^2/B^2+\frac{( | :math:`x_0 \; y_0 \;    |
    | :math:`x`-axis       |            | \sqrt{(y-y_0)^2+(z-z_0)^2} - | z_0 \; A \; B \; C`     |
    |                      |            | A)^2}{C^2} - 1 = 0`          |                         |
    +----------------------+------------+------------------------------+-------------------------+
    | Torus parallel to the| y-torus    | :math:`(y-y_0)^2/B^2+\frac{( | :math:`x_0 \; y_0 \;    |
    | :math:`y`-axis       |            | \sqrt{(x-x_0)^2+(z-z_0)^2} - | z_0 \; A \; B \; C`     |
    |                      |            | A)^2}{C^2} - 1 = 0`          |                         |
    +----------------------+------------+------------------------------+-------------------------+
    | Torus parallel to the| z-torus    | :math:`(z-z_0)^2/B^2+\frac{( | :math:`x_0 \; y_0 \;    |
    | :math:`z`-axis       |            | \sqrt{(x-x_0)^2+(y-y_0)^2} - | z_0 \; A \; B \; C`     |
    |                      |            | A)^2}{C^2} - 1 = 0`          |                         |
    +----------------------+------------+------------------------------+-------------------------+

.. _universes:

Universes
---------

OpenMC supports universe-based geometry similar to the likes of MCNP_ and
Serpent_. This capability enables user to model any identical repeated
structures once and then fill them in various spots in the geometry. A
prototypical example of a repeated structure would be a fuel pin within a fuel
assembly or a fuel assembly within a core.

Each cell in OpenMC can either be filled with a normal material or with a
universe. If the cell is filled with a universe, only the region of the universe
that is within the defined boundaries of the parent cell will be present in the
geometry. That is to say, even though a collection of cells in a universe may
extend to infinity, not all of the universe will be "visible" in the geometry
since it will be truncated by the boundaries of the cell that contains it.

When a cell is filled with a universe, it is possible to specify that the
universe filling the cell should be rotated and translated. This is done through
the ``rotation`` and ``translation`` attributes on a cell (note though that
these can only be specified on a cell that is filled with another universe, not
a material).

It is not necessary to use or assign universes in a geometry if there are no
repeated structures. Any cell in the geometry that is not assigned to a
specified universe is automatically part of the *base universe* whose
coordinates are just the normal coordinates in Euclidean space.

Lattices
--------

Often times, repeated structures in a geometry occur in a regular pattern such
as a rectangular or hexagonal lattice. In such a case, it would be cumbersome
for a user to have to define the boundaries of each of the cells to be filled
with a universe. Thus, OpenMC provides a lattice capability similar to that used
in MCNP_ and Serpent_.

The implementation of lattices is similar in principle to universes --- instead
of a cell being filled with a universe, the user can specify that it is filled
with a finite lattice. The lattice is then defined by a two-dimensional array of
universes that are to fill each position in the lattice. A good example of the
use of lattices and universes can be seen in the OpenMC model for the `Monte
Carlo Performance benchmark`_.

------------------------------------------
Computing the Distance to Nearest Boundary
------------------------------------------

One of the most basic algorithms in any Monte Carlo code is determining the
distance to the nearest surface within a cell. Since each cell is defined by
the surfaces that bound it, if we compute the distance to all surfaces bounding
a cell, we can determine the nearest one.

With the possibility of a particle having coordinates on multiple levels
(universes) in a geometry, we must exercise care when calculating the distance
to the nearest surface. Each different level of geometry has a set of boundaries
with which the particle's direction of travel may intersect. Thus, it is
necessary to check the distance to the surfaces bounding the cell in each
level. This should be done starting the highest (most global) level going down
to the lowest (most local) level. That ensures that if two surfaces on different
levels are coincident, by default the one on the higher level will be selected
as the nearest surface. Although they are not explicitly defined, it is also
necessary to check the distance to surfaces representing lattice boundaries if a
lattice exists on a given level.

The following procedure is used to calculate the distance to each bounding
surface. Suppose we have a particle at :math:`(x_0,y_0,z_0)` traveling in the
direction :math:`u_0,v_0,w_0`. To find the distance :math:`d` to a surface
:math:`f(x,y,z) = 0`, we need to solve the equation:

.. math::
    :label: dist-to-boundary-1

    f(x_0 + du_0, y_0 + dv_0, z_0 + dw_0) = 0

If no solutions to equation :eq:`dist-to-boundary-1` exist or the only solutions
are complex, then the particle's direction of travel will not intersect the
surface. If the solution to equation :eq:`dist-to-boundary-1` is negative, this
means that the surface is "behind" the particle, i.e. if the particle continues
traveling in its current direction, it will not hit the surface. The complete
derivation for different types of surfaces used in OpenMC will be presented in
the following sections.

Since :math:`f(x,y,z)` in general is quadratic in :math:`x`, :math:`y`, and
:math:`z`, this implies that :math:`f(x_0 + du_0, y + dv_0, z + dw_0)` is
quadratic in :math:`d`. Thus we expect at most two real solutions to
:eq:`dist-to-boundary-1`. If no solutions to :eq:`dist-to-boundary-1` exist or
the only solutions are complex, then the particle's direction of travel will not
intersect the surface. If the solution to :eq:`dist-to-boundary-1` is negative,
this means that the surface is "behind" the particle, i.e. if the particle
continues traveling in its current direction, it will not hit the surface.

Once a distance has been computed to a surface, we need to check if it is closer
than previously-computed distances to surfaces. Unfortunately, we cannot just
use the minimum function because some of the calculated distances, which should
be the same in theory (e.g. coincident surfaces), may be slightly different due
to the use of floating-point arithmetic. Consequently, we should first check for
floating-point equality of the current distance calculated and the minimum found
thus far. This is done by checking if

.. math::
    :label: fp-distance

    \frac{| d - d_{min} |}{d_{min}} < \epsilon

where :math:`d` is the distance to a surface just calculated, :math:`d_{min}` is
the minimum distance found thus far, and :math:`\epsilon` is a small number. In
OpenMC, this parameter is set to :math:`\epsilon = 10^{-14}` since all floating
calculations are done on 8-byte floating point numbers.

Plane Perpendicular to an Axis
------------------------------

The equation for a plane perpendicular to, for example, the x-axis is simply
:math:`x - x_0 = 0`. As such, we need to solve :math:`x + du - x_0 = 0`. The
solution for the distance is

.. math::
    :label: dist-xplane

    d = \frac{x_0 - x}{u}

Note that if the particle's direction of flight is parallel to the x-axis,
i.e. :math:`u = 0`, the distance to the surface will be infinity. While the
example here was for a plane perpendicular to the x-axis, the same formula can
be applied for the surfaces :math:`y = y_0` and :math:`z = z_0`.

Generic Plane
-------------

The equation for a generic plane is :math:`Ax + By + Cz = D`. Thus, we need to
solve the equation :math:`A(x + du) + B(y + dv) + C(z + dw) = D`. The solution
to this equation for the distance is

.. math::
    :label: dist-plane

    d = \frac{D - Ax - By - Cz}{Au + Bv + Cw}

Again, we need to check whether the denominator is zero. If so, this means that
the particle's direction of flight is parallel to the plane and it will
therefore never hit the plane.

.. _cylinder_distance:

Cylinder Parallel to an Axis
----------------------------

The equation for a cylinder parallel to, for example, the x-axis is :math:`(y -
y_0)^2 + (z - z_0)^2 = R^2`. Thus, we need to solve :math:`(y + dv - y_0)^2 +
(z + dw - z_0)^2 = R^2`. Let us define :math:`\bar{y} = y - y_0` and
:math:`\bar{z} = z - z_0`. We then have

.. math::
    :label: dist-xcylinder-1

    (\bar{y} + dv)^2 + (\bar{z} + dw)^2 = R^2

Expanding equation :eq:`dist-xcylinder-1` and rearranging terms, we obtain

.. math::
    :label: dist-xcylinder-2

    (v^2 + w^2) d^2 + 2 (\bar{y}v + \bar{z}w) d + (\bar{y}^2 + \bar{z}^2 - R^2)
    = 0

This is a quadratic equation for :math:`d`. To simplify notation, let us define
:math:`a = v^2 + w^2`, :math:`k = \bar{y}v + \bar{z}w`, and :math:`c =
\bar{y}^2 + \bar{z}^2 - R^2`. Thus, the distance is just the solution to
:math:`ad^2 + 2kd + c = 0`:

.. math::
    :label: dist-xcylinder-3

    d = \frac{-k \pm \sqrt{k^2 - ac}}{a}

A few conditions must be checked for. If :math:`a = 0`, this means the particle
is parallel to the cylinder and will thus never intersect it. Also, if
:math:`k^2 - ac < 0`, this means that both solutions to the quadratic are
complex. In physical terms, this means that the ray along which the particle is
traveling does not make any intersections with the cylinder.

If we do have intersections and :math:`c < 0`, this means that the particle is
inside the cylinder. Thus, one solution should be positive and one should be
negative. Clearly, the positive distance will occur when the sign on the
square root of the discriminant is positive since :math:`a > 0`.

If we have intersections and :math:`c > 0` this means that the particle is
outside the cylinder. Thus, the solutions to the quadratic are either both
positive or both negative. If they are both positive, the smaller (closer) one
will be the solution with a negative sign on the square root of the
discriminant.

The same equations and logic here can be used for cylinders that are parallel to
the y- or z-axis with appropriate substitution of constants.

Sphere
------

The equation for a sphere is :math:`(x - x_0)^2 + (y - y_0)^2 + (z - z_0)^2 =
R^2`. Thus, we need to solve the equation

.. math::
    :label: dist-sphere-1

    (x + du - x_0)^2 + (y + dv - y_0)^2 + (z + dw - z_0)^2 = R^2

Let us define :math:`\bar{x} = x - x_0`, :math:`\bar{y} = y - y_0`, and
:math:`\bar{z} = z - z_0`. We then have

.. math::
    :label: dist-sphere-2

    (\bar{x} + du)^2 + (\bar{y} + dv)^2 + (\bar{z} - dw)^2 = R^2

Expanding equation :eq:`dist-sphere-2` and rearranging terms, we obtain

.. math::
    :label: dist-sphere-3

    d^2 + 2 (\bar{x}u + \bar{y}v + \bar{z}w) d + (\bar{x}^2 + \bar{y}^2 +
    \bar{z}^2 - R^2) = 0

This is a quadratic equation for :math:`d`. To simplify notation, let us define
:math:`k = \bar{x}u + \bar{y}v + \bar{z}w` and :math:`c = \bar{x}^2 +
\bar{y}^2 + \bar{z}^2 - R^2`. Thus, the distance is just the solution to
:math:`d^2 + 2kd + c = 0`:

.. math::
    :label: dist-sphere-4

    d = -k \pm \sqrt{k^2 - c}

If the discriminant :math:`k^2 - c < 0`, this means that both solutions to the
quadratic are complex. In physical terms, this means that the ray along which
the particle is traveling does not make any intersections with the sphere.

If we do have intersections and :math:`c < 0`, this means that the particle is
inside the sphere. Thus, one solution should be positive and one should be
negative. The positive distance will occur when the sign on the square root of
the discriminant is positive. If we have intersections but :math:`c > 0` this
means that the particle is outside the sphere. The solutions to the quadratic
will then be either both positive or both negative. If they are both positive,
the smaller (closer) one will be the solution with a negative sign on the square
root of the discriminant.

Cone Parallel to an Axis
------------------------

The equation for a cone parallel to, for example, the x-axis is :math:`(y -
y_0)^2 + (z - z_0)^2 = R^2(x - x_0)^2`. Thus, we need to solve :math:`(y + dv -
y_0)^2 + (z + dw - z_0)^2 = R^2(x + du - x_0)^2`. Let us define :math:`\bar{x} =
x - x_0`, :math:`\bar{y} = y - y_0`, and :math:`\bar{z} = z - z_0`. We then have

.. math::
    :label: dist-xcone-1

    (\bar{y} + dv)^2 + (\bar{z} + dw)^2 = R^2(\bar{x} + du)^2

Expanding equation :eq:`dist-xcone-1` and rearranging terms, we obtain

.. math::
    :label: dist-xcone-2

    (v^2 + w^2 - R^2u^2) d^2 + 2 (\bar{y}v + \bar{z}w - R^2\bar{x}u) d +
    (\bar{y}^2 + \bar{z}^2 - R^2\bar{x}^2) = 0

Defining the terms

.. math::
    :label: dist-xcone-terms

    a = v^2 + w^2 - R^2u^2

    k = \bar{y}v + \bar{z}w - R^2\bar{x}u

    c = \bar{y}^2 + \bar{z}^2 - R^2\bar{x}^2

we then have the simple quadratic equation :math:`ad^2 + 2kd + c = 0` which can
be solved as described in :ref:`cylinder_distance`.

General Quadric
---------------

The equation for a general quadric surface is :math:`Ax^2 + By^2 + Cz^2 + Dxy +
Eyz + Fxz + Gx + Hy + Jz + K = 0`. Thus, we need to solve the equation

.. math::
    :label: dist-quadric-1

    A(x+du)^2 + B(y+dv)^2 + C(z+dw)^2 + D(x+du)(y+dv) + E(y+dv)(z+dw) + \\
    F(x+du)(z+dw) + G(x+du) + H(y+dv) + J(z+dw) + K = 0

Expanding equation :eq:`dist-quadric-1` and rearranging terms, we obtain

.. math::
    :label: dist-quadric-2

    d^2(uv + vw + uw) + 2d(Aux + Bvy + Cwx + (D(uv + vx) + E(vz + wy) + \\
    F(wx + uz))/2) + (x(Ax + Dy) + y(By + Ez) + z(Cz + Fx)) = 0

Defining the terms

.. math::
    :label: dist-quadric-terms

    a = uv + vw + uw

    k = Aux + Bvy + Cwx + (D(uv + vx) + E(vz + wy) + F(wx + uz))/2

    c = x(Ax + Dy) + y(By + Ez) + z(Cz + Fx)

we then have the simple quadratic equation :math:`ad^2 + 2kd + c = 0` which can
be solved as described in :ref:`cylinder_distance`.

Torus Parallel to an Axis
-------------------------

The equation for a torus parallel to, for example, the x-axis is

.. math::
    :label: dist-xtorus-sqrt

    \frac{(x-x_0)^2}{B^2} + \frac{(\sqrt{(y-y_0)^2 + (z-z_0)^2} - A)^2}{C^2} -
    1 = 0.

First, it needs to be cast into a polynomial form. Rearranging terms,

.. math::
    :label: dist-xtorus-1

     (D\bar{x}^2 + \bar{y}^2 + \bar{z}^2 + A^2 - C^2)^2 = 4A^2(\bar{y}^2 +
     \bar{z}^2)

where :math:`D = (C/B)^2`, :math:`\bar{x} = x - x_0`, :math:`\bar{y} = y - y_0`,
and :math:`\bar{z} = z - z_0`. To find the distance to the surface, we thus need
to solve

.. math::
    :label: dist-xtorus-2

    (D(\bar{x} + du)^2 + (\bar{y} + dv)^2 + (\bar{z} + dw)^2 + A^2 - C^2)^2 =
    4A^2((\bar{y} + dv)^2 + (\bar{z} + dw)^2).

Expanding and collecting like powers of :math:`d` yields

.. math::
    :label: dist-xtorus-3

    (c_2d^2 + c_1d + c_0)^2 = c_2'd^2 + c_1'd + c_0'

where

.. math::
    :label: dist-xtorus-4

    \begin{aligned}
    c_2 &= Du^2 + v^2 + w^2 \\
    c_1 &= 2(Du\bar{x} + v\bar{y} + w\bar{z}) \\
    c_0 &= D\bar{x}^2 + \bar{y}^2 + \bar{z}^2 + A^2 - C^2 \\
    c_2' &= 4A^2 (v^2 + w^2) \\
    c_1' &= 8A^2 (v\bar{y} + w\bar{z}) \\
    c_0' &= 4A^2(\bar{y}^2 + \bar{z}^2).
    \end{aligned}

Expanding the left-hand side and collecting like powers of :math:`d` on one
side, we obtain

.. math::
    :label: dist-xtorus-5

    (c_2^2)d^4 + (2c_1c_2)d^3 + (c_1^2 + 2c_0c_2 - c_2')d^2 + (2c_0c_1 - c_1')d
    + (c_0^2 - c_0') = 0.

The above equation is a fourth-order (quartic) polynomial equation. Although
there is an analytical solution to the general quartic equation, it can be
subject to roundoff errors when evaluated numerically. OpenMC uses an external
`quartic equation solver <https://doi.org/10.1145/3386241>`_ developed by
Orellana and De Michele that is based on the decomposition of the quartic
polynomial into two quadratics.

.. _find-cell:

----------------------------
Finding a Cell Given a Point
----------------------------

Another basic algorithm is to determine which cell contains a given point in the
global coordinate system, i.e. if the particle's position is :math:`(x,y,z)`,
what cell is it currently in. This is done in the following manner in
OpenMC. With the possibility of multiple levels of coordinates, we must perform
a recursive search for the cell. First, we start in the highest (most global)
universe, which we call the base universe, and loop over each cell within
that universe. For each cell, we check whether the specified point is inside the
cell using the algorithm described in :ref:`cell-contains`. If the cell is
filled with a normal material, the search is done and we have identified the
cell containing the point. If the cell is filled with another universe, we then
search all cells within that universe to see if any of them contain the
specified point. If the cell is filled with a lattice, the position within the
lattice is determined, and then whatever universe fills that lattice position is
recursively searched. The search ends once a cell containing a normal material
is found that contains the specified point.

.. _cell-contains:

----------------------
Finding a Lattice Tile
----------------------

If a particle is inside a lattice, its position inside the lattice must be
determined before assigning it to a cell.  Throughout this section, the
volumetric units of the lattice will be referred to as "tiles".  Tiles are
identified by thier indices, and the process of discovering which tile contains
the particle is referred to as "indexing".

Rectilinear Lattice Indexing
----------------------------

Indices are assigned to tiles in a rectilinear lattice based on the tile's
position along the :math:`x`, :math:`y`, and :math:`z` axes.  Figure
:num:`fig-rect-lat` maps the indices for a 2D lattice.  The indices, (1, 1),
map to the lower-left tile.  (5, 1) and (5, 5) map to the lower-right and
upper-right tiles, respectively.

.. _fig-rect-lat:

.. figure:: ../_images/rect_lat.svg
   :align: center
   :figclass: align-center
   :width: 400px

   Rectilinear lattice tile indices.

In general, a lattice tile is specified by the three indices,
:math:`(i_x, i_y, i_z)`.  If a particle's current coordinates are
:math:`(x, y, z)` then the indices can be determined from these formulas:

.. math::
    :label: rect_indexing

    i_x = \left \lceil \frac{x - x_0}{p_0} \right \rceil

    i_y = \left \lceil \frac{y - y_0}{p_1} \right \rceil

    i_z = \left \lceil \frac{z - z_0}{p_2} \right \rceil

where :math:`(x_0, y_0, z_0)` are the coordinates to the lower-left-bottom
corner of the lattice, and :math:`p_0, p_1, p_2` are the pitches along the
:math:`x`, :math:`y`, and :math:`z` axes, respectively.

.. _hexagonal_indexing:

Hexagonal Lattice Indexing
--------------------------

A skewed coordinate system is used for indexing hexagonal lattice tiles.
Rather than a :math:`y`-axis, another axis is used that is rotated 30 degrees
counter-clockwise from the :math:`y`-axis.  This axis is referred to as the
:math:`\alpha`-axis.  Figure :num:`fig-hex-lat` shows how 2D hexagonal tiles
are mapped with the :math:`(x, \alpha)` basis.  In this system, (0, 0) maps to
the center tile, (0, 2) to the top tile, and (2, -1) to the middle tile on the
right side.

.. _fig-hex-lat:

.. figure:: ../_images/hex_lat.svg
   :align: center
   :figclass: align-center
   :width: 400px

   Hexagonal lattice tile indices.

Unfortunately, the indices cannot be determined with one simple formula as
before.  Indexing requires a two-step process, a coarse step which determines a
set of four tiles that contains the particle and a fine step that determines
which of those four tiles actually contains the particle.

In the first step, indices are found using these formulas:

.. math::
    :label: hex_indexing

    \alpha = -\frac{x}{\sqrt{3}} + y

    i_x^* = \left \lfloor \frac{x}{p_0 \sqrt{3} / 2} \right \rfloor

    i_\alpha^* = \left \lfloor \frac{\alpha}{p_0} \right \rfloor

where :math:`p_0` is the lattice pitch (in the :math:`x`-:math:`y` plane).  The
true index of the particle could be :math:`(i_x^*, i_\alpha^*)`,
:math:`(i_x^* + 1, i_\alpha^*)`, :math:`(i_x^*, i_\alpha^* + 1)`, or
:math:`(i_x^* + 1, i_\alpha^* + 1)`.

The second step selects the correct tile from that neighborhood of 4.  OpenMC
does this by calculating the distance between the particle and the centers of
each of the 4 tiles, and then picking the closest tile.  This works because
regular hexagonal tiles form a Voronoi tessellation which means that all of the
points within a tile are closest to the center of that same tile.

Indexing along the :math:`z`-axis uses the same method from rectilinear
lattices, i.e.

.. math::
    :label: hex_indexing_z

    i_z = \left \lceil \frac{z - z_0}{p_2} \right \rceil

----------------------------------------
Determining if a Coordinate is in a Cell
----------------------------------------

To determine which cell a particle is in given its coordinates, we need to be
able to check whether a given cell contains a point. The algorithm for
determining if a cell contains a point is as follows. For each surface that
bounds a cell, we determine the particle's sense with respect to the surface. As
explained earlier, if we have a point :math:`(x_0,y_0,z_0)` and a surface
:math:`f(x,y,z) = 0`, the point is said to have negative sense if
:math:`f(x_0,y_0,z_0) < 0` and positive sense if :math:`f(x_0,y_0,z_0) > 0`. If
for all surfaces, the sense of the particle with respect to the surface matches
the specified sense that defines the half-space within the cell, then the point
is inside the cell. Note that this algorithm works only for *simple cells*
defined as intersections of half-spaces.

It may help to illustrate this algorithm using a simple example. Let's say we
have a cell defined as

.. code-block:: xml

    <surface id="1" type="sphere"  coeffs="0 0 0 10" />
    <surface id="2" type="x-plane" coeffs="-3" />
    <surface id="3" type="y-plane" coeffs="2" />
    <cell id="1" surfaces="-1 2 -3" />

This means that the cell is defined as the intersection of the negative half
space of a sphere, the positive half-space of an x-plane, and the negative
half-space of a y-plane. Said another way, any point inside this cell must
satisfy the following equations

.. math::
    :label: cell-contains-example

    x^2 + y^2 + z^2 - 10^2 < 0 \\
    x - (-3) > 0 \\
    y - 2 < 0

In order to determine if a point is inside the cell, we would substitute its
coordinates into equation :eq:`cell-contains-example`. If the inequalities are
satisfied, than the point is indeed inside the cell.

--------------------------
Handling Surface Crossings
--------------------------

A particle will cross a surface if the distance to the nearest surface is closer
than the distance sampled to the next collision. A number of things happen when
a particle hits a surface. First, we need to check if a non-transmissive
boundary condition has been applied to the surface. If a vacuum boundary
condition has been applied, the particle is killed and any surface current
tallies are scored to as needed. If a reflective boundary condition has been
applied to the surface, surface current tallies are scored to and then the
particle's direction is changed according to the procedure in :ref:`reflection`.
Note that the white boundary condition can be considered as the special case of
reflective boundary condition, where the same processing method will be applied to
deal with the surface current tallies scoring, except for determining the
changes of particle's direction according to the procedures in :ref:`white`.

Next, we need to determine what cell is beyond the surface in the direction of
travel of the particle so that we can evaluate cross sections based on its
material properties. At initialization, a list of neighboring cells is created
for each surface in the problem as described in :ref:`neighbor-lists`. The
algorithm outlined in :ref:`find-cell` is used to find a cell containing the
particle with one minor modification; rather than searching all cells in the
base universe, only the list of neighboring cells is searched. If this search is
unsuccessful, then a search is done over every cell in the base universe.

.. _neighbor-lists:

-----------------------
Building Neighbor Lists
-----------------------

Neighbor lists are data structures that are used to accelerate geometry searches
when a particle crosses a boundary. Namely, they are used to constrain the
number of cells that must be searched in order to determine which cell a
particle is crossing into. Earlier versions of OpenMC relied on "surface-based"
neighbor lists, where the cells that are adjacent to each surface are stored in
lists, one for each side of a surface. As of version 0.11, OpenMC switched to
using "cell-based" neighbor lists. For each cell, a list of the adjacent cells
is stored and then used to limit future searches. Unlike surface-based neighbor
lists, cell-based neighbor lists cannot be computed prior to transport. Thus,
cell-based neighbor lists in OpenMC grow dynamically as particles are
transported through the geometry and cross surfaces. Special care must be taken
to ensure that these dynamic neighbor lists are populated in a threadsafe
manner. Full details of the implementation in OpenMC can be found in a paper by
`Harper et al <https://doi.org/10.1080/00295639.2020.1719765>`_.

.. _reflection:

------------------------------
Reflective Boundary Conditions
------------------------------

If the velocity of a particle is :math:`\mathbf{v}` and it crosses a surface of
the form :math:`f(x,y,z) = 0` with a reflective boundary condition, it can be
shown based on geometric arguments that the velocity vector will then become

.. math::
    :label: reflection-v

    \mathbf{v'} = \mathbf{v} - 2 (\mathbf{v} \cdot \hat{\mathbf{n}})
    \hat{\mathbf{n}}

where :math:`\hat{\mathbf{n}}` is a unit vector normal to the surface at the
point of the surface crossing. The rationale for this can be understood by
noting that :math:`(\mathbf{v} \cdot \hat{\mathbf{n}}) \hat{\mathbf{n}}` is the
projection of the velocity vector onto the normal vector. By subtracting two
times this projection, the velocity is reflected with respect to the surface
normal. Since the magnitude of the velocity of the particle will not change as
it undergoes reflection, we can work with the direction of the particle instead,
simplifying equation :eq:`reflection-v` to

.. math::
    :label: reflection-omega

    \mathbf{\Omega'} = \mathbf{\Omega} - 2 (\mathbf{\Omega} \cdot
    \hat{\mathbf{n}}) \hat{\mathbf{n}}

where :math:`\mathbf{v} = || \mathbf{v} || \mathbf{\Omega}`. The direction of
the surface normal will be the gradient of the surface at the point of crossing,
i.e. :math:`\mathbf{n} = \nabla f(x,y,z)`. Substituting this into equation
:eq:`reflection-omega`, we get

.. math::
    :label: reflection-omega-2

    \mathbf{\Omega'} = \mathbf{\Omega} - \frac{2 ( \mathbf{\Omega} \cdot \nabla
    f )}{|| \nabla f ||^2} \nabla f


If we write the initial and final directions in terms of their vector
components, :math:`\mathbf{\Omega} = (u,v,w)` and :math:`\mathbf{\Omega'} = (u',
v', w')`, this allows us to represent equation :eq:`reflection-omega` as a
series of equations:

.. math::
    :label: reflection-system

    u' = u - \frac{2 ( \mathbf{\Omega} \cdot \nabla f )}{|| \nabla f ||^2}
    \frac{\partial f}{\partial x} \\

    v' = v - \frac{2 ( \mathbf{\Omega} \cdot \nabla f )}{|| \nabla f ||^2}
    \frac{\partial f}{\partial y} \\

    w' = w - \frac{2 ( \mathbf{\Omega} \cdot \nabla f )}{|| \nabla f ||^2}
    \frac{\partial f}{\partial z}

One can then use equation :eq:`reflection-system` to develop equations for
transforming a particle's direction given the equation of the surface.

Plane Perpendicular to an Axis
------------------------------

For a plane that is perpendicular to an axis, the rule for reflection is almost
so simple that no derivation is needed at all. Nevertheless, we will proceed
with the derivation to confirm that the rules of geometry agree with our
intuition. The gradient of the surface :math:`f(x,y,z) = x - x_0 = 0` is simply
:math:`\nabla f = (1, 0, 0)`. Note that this vector is already normalized,
i.e. :math:`|| \nabla f || = 1`. The second two equations in
:eq:`reflection-system` tell us that :math:`v` and :math:`w` do not change and
the first tell us that

.. math::
    :label: reflection-xplane

    u' = u - 2u = -u

We see that reflection for a plane perpendicular to an axis only entails
negating the directional cosine for that axis.

Generic Plane
-------------

A generic plane has the form :math:`f(x,y,z) = Ax + By + Cz - D = 0`. Thus, the
gradient to the surface is simply :math:`\nabla f = (A,B,C)` whose norm squared
is :math:`A^2 + B^2 + C^2`. This implies that

.. math::
    :label: reflection-plane-constant

    \frac{2 (\mathbf{\Omega} \cdot \nabla f)}{|| \nabla f ||^2} = \frac{2(Au +
    Bv + Cw)}{A^2 + B^2 + C^2}

Substituting equation :eq:`reflection-plane-constant` into equation
:eq:`reflection-system` gives us the form of the solution. For example, the
x-component of the reflected direction will be

.. math::
    :label: reflection-plane

    u' = u - \frac{2A(Au + Bv + Cw)}{A^2 + B^2 + C^2}


Cylinder Parallel to an Axis
----------------------------

A cylinder parallel to, for example, the x-axis has the form :math:`f(x,y,z) =
(y - y_0)^2 + (z - z_0)^2 - R^2 = 0`. Thus, the gradient to the surface is

.. math::
    :label: reflection-cylinder-grad

    \nabla f = 2 \left ( \begin{array}{c} 0 \\ y - y_0 \\ z - z_0 \end{array}
    \right ) = 2 \left ( \begin{array}{c} 0 \\ \bar{y} \\ \bar{z} \end{array}
    \right )

where we have introduced the constants :math:`\bar{y}` and
:math:`\bar{z}`. Taking the square of the norm of the gradient, we find that

.. math::
    :label: reflection-cylinder-norm

    || \nabla f ||^2 = 4 \bar{y}^2 + 4 \bar{z}^2 = 4 R^2

This implies that

.. math::
    :label: reflection-cylinder-constant

    \frac{2 (\mathbf{\Omega} \cdot \nabla f)}{|| \nabla f ||^2} =
    \frac{\bar{y}v + \bar{z}w}{R^2}

Substituting equations :eq:`reflection-cylinder-constant` and
:eq:`reflection-cylinder-grad` into equation :eq:`reflection-system` gives us
the form of the solution. In this case, the x-component will not change. The y-
and z-components of the reflected direction will be

.. math::
    :label: reflection-cylinder

    v' = v - \frac{2 ( \bar{y}v + \bar{z}w ) \bar{y}}{R^2} \\

    w' = w - \frac{2 ( \bar{y}v + \bar{z}w ) \bar{z}}{R^2}


Sphere
------

The surface equation for a sphere has the form :math:`f(x,y,z) = (x - x_0)^2 +
(y - y_0)^2 + (z - z_0)^2 - R^2 = 0`. Thus, the gradient to the surface is

.. math::
    :label: reflection-sphere-grad

    \nabla f = 2 \left ( \begin{array}{c} x - x_0 \\ y - y_0 \\ z - z_0
    \end{array} \right ) = 2 \left ( \begin{array}{c} \bar{x} \\ \bar{y} \\
    \bar{z} \end{array} \right )

where we have introduced the constants :math:`\bar{x}, \bar{y}, \bar{z}`. Taking
the square of the norm of the gradient, we find that

.. math::
    :label: reflection-sphere-norm

    || \nabla f ||^2 = 4 \bar{x}^2 + 4 \bar{y}^2 + 4 \bar{z}^2 = 4 R^2

This implies that

.. math::
    :label: reflection-sphere-constant

    \frac{2 (\mathbf{\Omega} \cdot \nabla f)}{|| \nabla f ||^2} =
    \frac{\bar{x}u + \bar{y}v + \bar{z}w}{R^2}

Substituting equations :eq:`reflection-sphere-constant` and
:eq:`reflection-sphere-grad` into equation :eq:`reflection-system` gives us the
form of the solution:

.. math::
    :label: reflection-sphere

    u' = u - \frac{2 ( \bar{x}u + \bar{y}v + \bar{z}w ) \bar{x} }{R^2} \\

    v' = v - \frac{2 ( \bar{x}u + \bar{y}v + \bar{z}w ) \bar{y} }{R^2} \\

    w' = w - \frac{2 ( \bar{x}u + \bar{y}v + \bar{z}w ) \bar{z} }{R^2}

Cone Parallel to an Axis
------------------------

A cone parallel to, for example, the z-axis has the form :math:`f(x,y,z) = (x -
x_0)^2 + (y - y_0)^2 - R^2(z - z_0)^2 = 0`. Thus, the gradient to the surface is

.. math::
    :label: reflection-cone-grad

    \nabla f = 2 \left ( \begin{array}{c} x - x_0 \\ y - y_0 \\ -R^2(z - z_0)
    \end{array} \right ) = 2 \left ( \begin{array}{c} \bar{x} \\ \bar{y} \\
    -R^2\bar{z} \end{array} \right )

where we have introduced the constants :math:`\bar{x}`, :math:`\bar{y}`, and
:math:`\bar{z}`. Taking the square of the norm of the gradient, we find that

.. math::
    :label: reflection-cone-norm

    || \nabla f ||^2 = 4 \bar{x}^2 + \bar{y}^2 + 4 R^4 \bar{z}^2 \\ = 4 R^2
       \bar{z}^2 + 4 R^4 \bar{z}^2 \\ = 4 R^2 (1 + R^2) \bar{z}^2

This implies that

.. math::
    :label: reflection-cone-constant

    \frac{2 (\mathbf{\Omega} \cdot \nabla f)}{|| \nabla f ||^2} =
    \frac{\bar{x}u + \bar{y}v - R^2\bar{z}w}{R^2 (1 + R^2) \bar{z}^2}

Substituting equations :eq:`reflection-cone-constant` and
:eq:`reflection-cone-grad` into equation :eq:`reflection-system` gives us the
form of the solution:

.. math::
    :label: reflection-cone

    u' = u - \frac{2 (\bar{x}u + \bar{y}v - R^2\bar{z}w) \bar{x}}{R^2 (1 + R^2)
    \bar{z}^2}

    v' = v - \frac{2 (\bar{x}u + \bar{y}v - R^2\bar{z}w) \bar{y}}{R^2 (1 + R^2)
    \bar{z}^2}

    w' = w + \frac{2 (\bar{x}u + \bar{y}v - R^2\bar{z}w)}{R^2 (1 + R^2) \bar{z}}

General Quadric
---------------

A general quadric surface has the form :math:`f(x,y,z) = Ax^2 + By^2 + Cz^2 +
Dxy + Eyz + Fxz + Gx + Hy + Jz + K = 0`. Thus, the gradient to the surface is

.. math::
    :label: reflection-quadric-grad

    \nabla f = \left ( \begin{array}{c} 2Ax + Dy + Fz + G \\ 2By + Dx + Ez + H
    \\ 2Cz + Ey + Fx + J \end{array} \right ).

Torus Parallel to an Axis
-------------------------

A torus parallel to, for example, the x-axis has the form

.. math::
    :label: reflection-torus-1

    f(x,y,z) = \frac{(x-x_0)^2}{B^2} + \frac{(\sqrt{(y-y_0)^2 + (z-z_0)^2} -
    A)^2}{C^2} - 1.

The gradient to the surface is therefore

.. math::
    :label: reflection-torus-grad

    \nabla f = \left ( \begin{array}{c} 2\bar{x}/B^2 \\ 2\bar{y}(g - A)/(C^2g)
    \\ 2\bar{z}(g - A)/(C^2g) \end{array} \right )

where :math:`g = \sqrt{\bar{y}^2 + \bar{z}^2}` and, as always, :math:`\bar{x} =
x - x_0`, :math:`\bar{y} = y - y_0`, and :math:`\bar{z} = z - z_0`.

.. _white:

-------------------------
White Boundary Conditions
-------------------------

The `white boundary condition <https://doi.org/10.1016/j.anucene.2019.05.006>`_
is usually applied in deterministic codes, where the particle will hit the
surface and travel back with isotropic angular distribution. The change in
particle's direction is sampled from a cosine distribution instead of uniform.
Figure :num:`fig-cosine-dist` shows an example of cosine-distribution reflection
on the arbitrary surface relative to the surface normal.

.. _fig-cosine-dist:

.. figure:: ../_images/cosine-dist.png
   :align: center
   :figclass: align-center

   Cosine-distribution reflection on an arbitrary surface.

The probability density function (pdf) for the reflected direction can be
expressed as follows,

.. math::
    :label: white-reflection-pdf

    f(\mu, \phi) d\mu d\phi = \frac{\mu}{\pi} d\mu d\phi = 2\mu d\mu \frac{d\phi}{2\pi}

where :math:`\mu = \cos \theta` is the cosine of the polar angle between
reflected direction and the normal to the surface; and :math:`\theta` is the
azimuthal angle in :math:`[0,2\pi]`. We can separate the multivariate
probability density into two separate univariate density functions, one for
the cosine of the polar angle,

.. math::
    :label: white-reflection-cosine

    f(\mu) = 2\mu

and one for the azimuthal angle,

.. math::
    :label: white-reflection-uniform

    f(\phi) = \frac{1}{2\pi}.

Each of these density functions can be sampled by analytical inversion of the
cumulative distribution distribution, resulting in the following sampling
scheme:

.. math::
    :label: white-reflection-sqrt-prn

    \mu = \sqrt{\xi_1} \\
    \phi = 2\pi\xi_2

where :math:`\xi_1` and :math:`\xi_2` are uniform random numbers on
:math:`[0,1)`. With the sampled values of :math:`\mu` and :math:`\phi`, the
final reflected direction vector can be computed via rotation of the surface
normal using the equations from :ref:`transform-coordinates`. The white boundary
condition can be applied to any kind of surface, as long as the normal to the
surface is known as in :ref:`reflection`.

.. _constructive solid geometry: https://en.wikipedia.org/wiki/Constructive_solid_geometry
.. _surfaces: https://en.wikipedia.org/wiki/Surface
.. _MCNP: https://mcnp.lanl.gov
.. _Serpent: https://serpent.vtt.fi
.. _Monte Carlo Performance benchmark: https://github.com/mit-crpg/benchmarks/tree/master/mc-performance/openmc
