Source code for ants.benchmarks.kobayashi

########################################################################
#                        ___    _   _____________
#                       /   |  / | / /_  __/ ___/
#                      / /| | /  |/ / / /  \__ \
#                     / ___ |/ /|  / / /  ___/ /
#                    /_/  |_/_/ |_/ /_/  /____/
#
# Kobayashi 3D benchmarks for simple geometries with void regions
#
########################################################################

"""Kobayashi three-dimensional benchmarks with void regions.

The problems and reference fluxes are from K. Kobayashi, N. Sugimura, and
Y. Nagaya, "3-D Radiation Transport Benchmark Problems and Results for
Simple Geometries with Void Regions", NEA/NSC/DOC(2000)4, OECD Nuclear
Energy Agency (2000), also published in Progress in Nuclear Energy 39,
119-144 (2001).

Each problem is a one-group source in the corner of a shield, with a void
region next to the source: a cube (Problem 1), a straight duct (Problem 2),
and a dog leg duct (Problem 3). The x = 0, y = 0, and z = 0 faces are
reflecting and the outer faces are vacuum. Case i is a pure absorber and
case ii has a scattering cross section that is half of the total. The
reference fluxes are the exact values for case i and Monte Carlo (GMVP)
values for case ii.
"""

import numpy as np

import ants
from ants.datatypes import (
    Geometry,
    GeometryData,
    MaterialData,
    SolverData,
    SourceData,
    SpatialDiscretization,
)

# Outer (x, y, z) lengths of each problem (cm)
LENGTHS = {1: (100.0, 100.0, 100.0), 2: (60.0, 100.0, 60.0), 3: (60.0, 100.0, 60.0)}

# Source region in the corner of every problem, [(x1, y1, z1), dx, dy, dz]
SOURCE = [[(0.0, 0.0, 0.0), 10.0, 10.0, 10.0]]

# Void regions, which are placed on the shield before the source region
VOID = {
    1: [[(0.0, 0.0, 0.0), 50.0, 50.0, 50.0]],
    2: [[(0.0, 10.0, 0.0), 10.0, 90.0, 10.0]],
    3: [
        [(0.0, 10.0, 0.0), 10.0, 50.0, 10.0],
        [(10.0, 50.0, 0.0), 30.0, 10.0, 10.0],
        [(30.0, 50.0, 10.0), 10.0, 10.0, 30.0],
        [(30.0, 60.0, 30.0), 10.0, 40.0, 10.0],
    ],
}

# Total cross sections of the source (0), void (1), and shield (2) regions,
# the scattering ratio of case ii, and the source strength (n / cm^3 s)
XS_TOTAL = np.array([0.1, 1e-4, 0.1])
SCATTER_RATIO = 0.5
SOURCE_STRENGTH = 1.0

# Reference total flux at each point (cm^-2 s^-1). The columns are the
# (x, y, z) location (cm), the exact flux for case i, and the GMVP flux and
# its 1 sigma fractional standard deviation (%) for case ii, from Tables 2-4
# of NEA/NSC/DOC(2000)4.
REFERENCE = {
    "1A": """
           5    5    5   5.95659e+00   8.29260e+00   0.021
           5   15    5   1.37185e+00   1.87028e+00   0.005
           5   25    5   5.00871e-01   7.13986e-01   0.003
           5   35    5   2.52429e-01   3.84685e-01   0.004
           5   45    5   1.50260e-01   2.53984e-01   0.006
           5   55    5   5.95286e-02   1.37220e-01   0.073
           5   65    5   1.53283e-02   4.65913e-02   0.117
           5   75    5   4.17689e-03   1.58766e-02   0.197
           5   85    5   1.18533e-03   5.47036e-03   0.343
           5   95    5   3.46846e-04   1.85082e-03   0.619
    """,
    "1B": """
           5    5    5   5.95659e+00   8.29260e+00   0.021
          15   15   15   4.70754e-01   6.63233e-01   0.004
          25   25   25   1.69968e-01   2.68828e-01   0.003
          35   35   35   8.68334e-02   1.56683e-01   0.005
          45   45   45   5.25132e-02   1.04405e-01   0.011
          55   55   55   1.33378e-02   3.02145e-02   0.061
          65   65   65   1.45867e-03   4.06555e-03   0.074
          75   75   75   1.75364e-04   5.86124e-04   0.116
          85   85   85   2.24607e-05   8.66059e-05   0.198
          95   95   95   3.01032e-06   1.12892e-05   0.383
    """,
    "1C": """
           5   55    5   5.95286e-02   1.37220e-01   0.073
          15   55    5   5.50247e-02   1.27890e-01   0.076
          25   55    5   4.80754e-02   1.13582e-01   0.080
          35   55    5   3.96765e-02   9.59578e-02   0.088
          45   55    5   3.16366e-02   7.82701e-02   0.094
          55   55    5   2.35303e-02   5.67030e-02   0.111
          65   55    5   5.83721e-03   1.88631e-02   0.189
          75   55    5   1.56731e-03   6.46624e-03   0.314
          85   55    5   4.53113e-04   2.28099e-03   0.529
          95   55    5   1.37079e-04   7.93924e-04   0.890
    """,
    "2A": """
           5    5    5   5.95659e+00   8.61696e+00   0.063
           5   15    5   1.37185e+00   2.16123e+00   0.015
           5   25    5   5.00871e-01   8.93437e-01   0.011
           5   35    5   2.52429e-01   4.77452e-01   0.012
           5   45    5   1.50260e-01   2.88719e-01   0.013
           5   55    5   9.91726e-02   1.88959e-01   0.014
           5   65    5   7.01791e-02   1.31026e-01   0.016
           5   75    5   5.22062e-02   9.49890e-02   0.017
           5   85    5   4.03188e-02   7.12403e-02   0.019
           5   95    5   3.20574e-02   5.44807e-02   0.019
    """,
    "2B": """
           5   95    5   3.20574e-02   5.44807e-02   0.019
          15   95    5   1.70541e-03   6.58233e-03   0.244
          25   95    5   1.40557e-04   1.28002e-03   0.336
          35   95    5   3.27058e-05   4.13414e-04   0.363
          45   95    5   1.08505e-05   1.55548e-04   0.454
          55   95    5   4.14132e-06   6.02771e-05   0.599
    """,
    "3A": """
           5    5    5   5.95659e+00   8.61578e+00   0.044
           5   15    5   1.37185e+00   2.16130e+00   0.010
           5   25    5   5.00871e-01   8.93784e-01   0.008
           5   35    5   2.52429e-01   4.78052e-01   0.008
           5   45    5   1.50260e-01   2.89424e-01   0.009
           5   55    5   9.91726e-02   1.92698e-01   0.010
           5   65    5   4.22623e-02   1.04982e-01   0.077
           5   75    5   1.14703e-02   3.37544e-02   0.107
           5   85    5   3.24662e-03   1.08158e-02   0.163
           5   95    5   9.48324e-04   3.39632e-03   0.275
    """,
    "3B": """
           5   55    5   9.91726e-02   1.92698e-01   0.010
          15   55    5   2.45041e-02   6.72147e-02   0.019
          25   55    5   4.54477e-03   2.21799e-02   0.028
          35   55    5   1.42960e-03   9.90646e-03   0.033
          45   55    5   2.64846e-04   3.39066e-03   0.195
          55   55    5   9.14210e-05   1.05629e-03   0.327
    """,
    "3C": """
           5   95   35   3.27058e-05   3.44804e-04   0.793
          15   95   35   2.68415e-05   2.91825e-04   0.659
          25   95   35   1.70019e-05   2.05793e-04   0.529
          35   95   35   3.37981e-05   2.62086e-04   0.075
          45   95   35   6.04893e-06   1.05367e-04   0.402
          55   95   35   3.36460e-06   4.44962e-05   0.440
    """,
}


[docs] def build_problem( problem, scattering=False, cell_width=2.0, angles=8, space_disc=SpatialDiscretization.STEP_CHARACTERISTIC, solver=None, ): """Create the ANTS inputs for a Kobayashi benchmark problem. Parameters ---------- problem : int Problem number (1, 2, or 3). scattering : bool, optional If True, case ii (50% scattering), otherwise case i (pure absorber). cell_width : float, optional Width of the cubic spatial cells (cm). Every 10 cm must hold an odd number of cells so the reference points are at cell centers. angles : int, optional Number of angles in each direction for ``ants.angular_xyz``. space_disc : SpatialDiscretization, optional Spatial discretization. The default is step characteristic, since diamond difference gives negative fluxes in the shield. solver : SolverData, optional Solver options. The default is ``SolverData()``. Returns ------- materials : MaterialData Source (0), void (1), and shield (2) cross sections. sources : SourceData Unit source in the source region and vacuum boundaries. geometry : GeometryData Medium map and cell widths. quadrature : QuadratureData Angles ordered for the reflecting faces. solver : SolverData Solver options. """ cells_per_10 = 10.0 / cell_width if (abs(cells_per_10 - round(cells_per_10)) > 1e-9) or ( round(cells_per_10) % 2 == 0 ): raise ValueError( f"cell_width={cell_width} must split 10 cm into an odd number of " "cells, such as 10 / 3, 2, or 10 / 9" ) edges = [ np.linspace(0, length, int(round(length / cell_width)) + 1) for length in LENGTHS[problem] ] cells = tuple(edge.size - 1 for edge in edges) # Shield everywhere, with the void and the source placed on top medium_map = np.full(cells, 2, dtype=np.int32) medium_map = ants.spatial3d(medium_map, 1, VOID[problem], *edges) medium_map = ants.spatial3d(medium_map, 0, SOURCE, *edges) ratio = SCATTER_RATIO if scattering else 0.0 materials = MaterialData( total=XS_TOTAL[:, None].copy(), scatter=ratio * XS_TOTAL[:, None, None], fission=np.zeros((3, 1, 1)), ) external = np.zeros(cells + (1, 1)) external[medium_map == 0] = SOURCE_STRENGTH sources = SourceData( external=external, boundary_x=np.zeros((2, 1, 1, 1, 1)), boundary_y=np.zeros((2, 1, 1, 1, 1)), boundary_z=np.zeros((2, 1, 1, 1, 1)), ) # Reflecting at x = 0, y = 0, and z = 0 bc = [1, 0] geometry = GeometryData( medium_map=medium_map, delta_x=np.diff(edges[0]), delta_y=np.diff(edges[1]), delta_z=np.diff(edges[2]), bc_x=bc, bc_y=bc, bc_z=bc, geometry=Geometry.SLAB3D, space_disc=space_disc, ) quadrature = ants.angular_xyz(angles, bc_x=bc, bc_y=bc, bc_z=bc) solver = SolverData() if solver is None else solver return materials, sources, geometry, quadrature, solver
[docs] def reference(case, scattering=False): """Reference total flux of a Kobayashi benchmark case. Parameters ---------- case : str Set of points, ``"1A"``, ``"1B"``, ``"1C"``, ``"2A"``, ``"2B"``, ``"3A"``, ``"3B"``, or ``"3C"``. The first character is the problem. scattering : bool, optional If True, case ii (50% scattering), otherwise case i (pure absorber). Returns ------- points : ndarray, shape (points, 3) (x, y, z) location of each point (cm). flux : ndarray, shape (points,) Reference total flux (cm^-2 s^-1). uncertainty : ndarray, shape (points,) Fractional standard deviation of the Monte Carlo flux for case ii, and zeros for the exact case i values. """ table = np.array(REFERENCE[case].split(), dtype=float).reshape(-1, 6) points = table[:, :3] if scattering: return points, table[:, 4], 0.01 * table[:, 5] return points, table[:, 3], np.zeros(points.shape[0])
[docs] def flux_at(flux, points, geometry): """Scalar flux of the cells holding each point. Parameters ---------- flux : ndarray, shape (cells_x, cells_y, cells_z) or (..., groups) Scalar flux from ``ants.fixed3d.fixed_source``. points : ndarray, shape (points, 3) (x, y, z) location of each point (cm). geometry : GeometryData Geometry used for the flux. Returns ------- ndarray, shape (points,) or (points, groups) Flux of the cell holding each point. A point on an edge between two cells takes the cell above it, except on the upper boundary. """ points = np.asarray(points, dtype=float) index = [] for axis, delta in enumerate( (geometry.delta_x, geometry.delta_y, geometry.delta_z) ): edges = np.concatenate(([0.0], np.cumsum(delta))) if np.any((points[:, axis] < 0.0) | (points[:, axis] > edges[-1])): raise ValueError(f"points must lie inside the domain along axis {axis}") cell = np.searchsorted(edges, points[:, axis], side="right") - 1 index.append(np.minimum(cell, delta.size - 1)) return flux[tuple(index)]