3.1.3. The Two-Dimensional Reed Problem

This problem extends the one-dimensional Reed material sequence into a square. The innermost region is a source-bearing absorber, and each subsequent material forms an L-shaped shell around it. The cross sections and source strengths are unchanged from the one-dimensional Reed problem.

3.1.3.1. Import the OpenSn objects

[ ]:
if "opensn_console" not in globals():
    from mpi4py import MPI
    from pyopensn.aquad import GLCProductQuadrature2DXY
    from pyopensn.context import Finalize, UseColor
    from pyopensn.fieldfunc import FieldFunctionInterpolationVolume
    from pyopensn.logvol import RPPLogicalVolume
    from pyopensn.mesh import OrthogonalMeshGenerator
    from pyopensn.solver import DiscreteOrdinatesProblem, SteadyStateSourceSolver
    from pyopensn.source import VolumetricSource
    from pyopensn.xs import MultiGroupXS

    rank = MPI.COMM_WORLD.rank
    UseColor(False)

3.1.3.2. Create the two-dimensional mesh

The domain is the square \([0,8]\times[0,8]\) cm. A uniform spacing of 0.1 cm gives 80 cells along each axis, with nodes at every material interface: 2, 3, 5, and 6 cm.

[ ]:
domain_length = 8.0
cells_per_cm = 10
num_cells = int(domain_length * cells_per_cm)
nodes = [i / cells_per_cm for i in range(num_cells + 1)]

mesh = OrthogonalMeshGenerator(node_sets=[nodes, nodes]).Execute()
mesh.SetOrthogonalBoundaries()

3.1.3.3. Construct the nested material zones

The material at \((x,y)\) is determined by \(\max(x,y)\). Consequently, the first material occupies a square and each later material occupies the difference between two nested squares:

Reed problem 2D

Block ID

Coordinate range

Region

\(\sigma_t\) (cm\(^{-1}\))

\(\sigma_s\) (cm\(^{-1}\))

\(Q\)

0

\(0\leq\max(x,y)\leq2\)

absorber and source

50

0

50

1

\(2<\max(x,y)\leq3\)

absorber

5

0

0

2

\(3<\max(x,y)\leq5\)

void

0

0

0

3

\(5<\max(x,y)\leq6\)

scatterer and source

1

0.9

1

4

\(6<\max(x,y)\leq8\)

scatterer

1

0.9

0

The block IDs are assigned from the outside inward. Each smaller square overwrites the block ID of the cells it contains, leaving the preceding material as an L-shaped shell.

[ ]:
zone_outer_edges = [2.0, 3.0, 5.0, 6.0, 8.0]
mesh.SetUniformBlockID(4)

for block_id, upper_edge in reversed(
    list(enumerate(zone_outer_edges[:-1]))
):
    nested_square = RPPLogicalVolume(
        xmin=0.0, xmax=upper_edge,
        ymin=0.0, ymax=upper_edge,
        infz=True,
    )
    mesh.SetBlockIDFromLogicalVolume(nested_square, block_id, True)

3.1.3.4. Define the materials and sources

As in the one-dimensional problem, CreateSimpleOneGroup receives the total cross section and scattering ratio \(c=\sigma_s/\sigma_t\). The two volumetric sources select the source-bearing regions by block ID.

[ ]:
sigma_t = [50.0, 5.0, 0.0, 1.0, 1.0]
scattering_ratio = [0.0, 0.0, 0.0, 0.9, 0.9]
xs_map = []

for block_id, (total, ratio) in enumerate(
    zip(sigma_t, scattering_ratio)
):
    xs = MultiGroupXS()
    xs.CreateSimpleOneGroup(sigma_t=total, c=ratio)
    xs_map.append({"block_ids": [block_id], "xs": xs})

absorber_source = VolumetricSource(
    block_ids=[0], group_strength=[50.0]
)
scattering_source = VolumetricSource(
    block_ids=[3], group_strength=[1.0]
)

3.1.3.5. Assemble and solve the transport problem

A Gauss–Legendre–Chebyshev quadrature represents directions in the \(xy\) plane. All four exterior faces are vacuum boundaries, preserving the leakage behavior of the one-dimensional problem while allowing particles to stream in two dimensions.

[ ]:
quadrature = GLCProductQuadrature2DXY(
    n_polar=2, n_azimuthal=32, scattering_order=0
)

problem = DiscreteOrdinatesProblem(
    mesh=mesh,
    num_groups=1,
    groupsets=[
        {
            "groups_from_to": (0, 0),
            "angular_quadrature": quadrature,
            "inner_linear_method": "petsc_gmres",
            "l_abs_tol": 1.0e-9,
            "l_max_its": 300,
            "gmres_restart_interval": 30,
        }
    ],
    xs_map=xs_map,
    volumetric_sources=[absorber_source, scattering_source],
    boundary_conditions=[
        {"name": "xmin", "type": "vacuum"},
        {"name": "xmax", "type": "vacuum"},
        {"name": "ymin", "type": "vacuum"},
        {"name": "ymax", "type": "vacuum"},
    ],
)

solver = SteadyStateSourceSolver(problem=problem, compute_balance=True)
solver.Initialize()
solver.Execute()

3.1.3.6. Verify the solution and source geometry

The source-bearing absorber has area \(2^2=4\) cm\(^2\), while the source-bearing scattering shell has area \(6^2-5^2=11\) cm\(^2\). Their strengths therefore give an integrated source rate of \(50(4)+1(11)=211\). The transport balance and maximum scalar flux provide checks on the geometry and computed solution.

[ ]:
balance = solver.ComputeBalanceTable()
source_rate = balance["production_rate"] + balance["inflow_rate"]
loss_rate = balance["absorption_rate"] + balance["outflow_rate"]
relative_balance_error = abs(source_rate - loss_rate) / source_rate

scalar_flux = problem.GetScalarFluxFieldFunction()[0]
whole_domain = RPPLogicalVolume(
    xmin=0.0, xmax=domain_length,
    ymin=0.0, ymax=domain_length,
    infz=True,
)
maximum = FieldFunctionInterpolationVolume()
maximum.SetOperationType("max")
maximum.SetLogicalVolume(whole_domain)
maximum.AddFieldFunction(scalar_flux)
maximum.Execute()
maximum_flux = maximum.GetValue()

if rank == 0:
    print(f"REED_2D_SOURCE_RATE={source_rate:.8e}")
    print(f"REED_2D_MAX_FLUX={maximum_flux:.8e}")
    print(f"REED_2D_BALANCE_ERROR={relative_balance_error:.8e}")

assert abs(source_rate - 211.0) < 1.0e-8
assert relative_balance_error < 1.0e-8

3.1.3.7. Export and visualize the scalar flux

After running the generated Python input, use the following code to export the scalar flux for visualization. It is kept in Markdown so the regression test does not create output files.

from pyopensn.fieldfunc import FieldFunctionGridBased

FieldFunctionGridBased.ExportMultipleToPVTU(
    [scalar_flux], "Flux/Reed2D_Phi"
)

Scalar flux for the two-dimensional Reed problem

3.1.3.8. Next steps

Compare diagonal and axis-aligned slices of the two-dimensional solution with the one-dimensional Reed flux. You can also refine the mesh near the absorber and void interfaces or increase the azimuthal quadrature to study ray effects.

[ ]:
if "opensn_console" not in globals():
    from IPython import get_ipython

    if get_ipython() is not None:
        Finalize()
        MPI.Finalize()