3.1.8. A Three-Dimensional Volumetric-Source Problem

This example solves a steady, one-group transport problem on a three-dimensional Cartesian mesh containing separate background, source, and detector materials. An isotropic volumetric source is confined to a scattering material, particles travel through a predominantly scattering background, and the detector response is evaluated in an absorbing region. All material, source, mesh, and solver parameters are defined in the input, so the example requires no command-line parameters or interactive input.

3.1.8.1. Import the OpenSn objects

[ ]:
if "opensn_console" not in globals():
    from mpi4py import MPI
    from pyopensn.aquad import GLCProductQuadrature3DXYZ
    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.8.2. Create the three-dimensional mesh

Supplying three node sets creates a Cartesian mesh in \(x\), \(y\), and \(z\). The nodes align the mesh with every source and detector interface in the 10-by-10-by-10 domain, with two cells across each coordinate interval.

[ ]:
nodes = [
    0.0, 1.0, 2.0, 2.5, 3.0, 3.5, 4.0, 5.0,
    6.0, 7.0, 8.0, 8.25, 8.5, 9.25, 10.0,
]

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

BACKGROUND_MATERIAL = 0
SOURCE_MATERIAL = 1
DETECTOR_MATERIAL = 2
mesh.SetUniformBlockID(BACKGROUND_MATERIAL)

3.1.8.3. Define the material and source regions

The source is confined to the cube from 2 to 4 along each axis. The much smaller detector occupies the cube from 8 to 8.5. These logical volumes assign distinct material IDs to the two regions while the rest of the domain remains the background material.

[ ]:
source_region = RPPLogicalVolume(
    xmin=2.0, xmax=4.0,
    ymin=2.0, ymax=4.0,
    zmin=2.0, zmax=4.0,
)
detector_region = RPPLogicalVolume(
    xmin=8.0, xmax=8.5,
    ymin=8.0, ymax=8.5,
    zmin=8.0, zmax=8.5,
)

mesh.SetBlockIDFromLogicalVolume(source_region, SOURCE_MATERIAL, True)
mesh.SetBlockIDFromLogicalVolume(detector_region, DETECTOR_MATERIAL, True)

background_xs = MultiGroupXS()
background_xs.CreateSimpleOneGroup(sigma_t=1.0, c=0.9)

source_xs = MultiGroupXS()
source_xs.CreateSimpleOneGroup(sigma_t=2.0, c=1.0)

detector_sigma_t = 0.8
detector_xs = MultiGroupXS()
detector_xs.CreateSimpleOneGroup(sigma_t=detector_sigma_t, c=0.0)

source_volume = (4.0 - 2.0) ** 3
source = VolumetricSource(
    block_ids=[SOURCE_MATERIAL],
    group_strength=[1.0 / source_volume],
)

3.1.8.4. Assemble and solve the transport problem

A three-dimensional product quadrature represents particle directions over the sphere. Vacuum conditions on all six faces allow particles to leave the cube. The source strength is normalized by its volume so that the integrated source rate is one particle per unit time.

[ ]:
quadrature = GLCProductQuadrature3DXYZ(
    n_polar=8,
    n_azimuthal=8,
    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-10,
            "l_max_its": 200,
            "gmres_restart_interval": 30,
        }
    ],
    xs_map=[
        {"block_ids": [BACKGROUND_MATERIAL], "xs": background_xs},
        {"block_ids": [SOURCE_MATERIAL], "xs": source_xs},
        {"block_ids": [DETECTOR_MATERIAL], "xs": detector_xs},
    ],
    volumetric_sources=[source],
    boundary_conditions=[
        {"name": "xmin", "type": "vacuum"},
        {"name": "xmax", "type": "vacuum"},
        {"name": "ymin", "type": "vacuum"},
        {"name": "ymax", "type": "vacuum"},
        {"name": "zmin", "type": "vacuum"},
        {"name": "zmax", "type": "vacuum"},
    ],
)

solver = SteadyStateSourceSolver(problem=problem)
solver.Initialize()
solver.Execute()

3.1.8.5. Evaluate the detector response

The volume interpolation integrates the scalar flux over the detector. Multiplication by the detector’s total interaction cross section produces its response. This single deterministic value also makes the example suitable for regression testing.

[ ]:
scalar_flux = problem.GetScalarFluxFieldFunction()[0]
response = FieldFunctionInterpolationVolume()
response.SetOperationType("sum")
response.SetLogicalVolume(detector_region)
response.AddFieldFunction(scalar_flux)
response.Execute()
response_value = detector_sigma_t * response.GetValue()

if rank == 0:
    print(f"FIXED_SOURCE_3D_RESPONSE={response_value:.8e}")

3.1.8.6. Visualize the scalar flux

from pyopensn.fieldfunc import FieldFunctionGridBased

fflist = problem.GetScalarFluxFieldFunction()
FieldFunctionGridBased.ExportMultipleToPVTU(
    [fflist[0]], "Flux/Phi_p"
)

The resulting scalar-flux distribution is shown below.

Three-dimensional scalar-flux distribution

Diagonal slice showing the source and detector regions Scalar flux on the diagonal slice

3.1.8.7. Next steps

Try refining each coordinate interval, changing the background material, or moving the source and detector regions. Because every parameter is set above, the notebook and its generated Python input can be run without supplying environment variables or other terminal-level inputs.

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

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