2.6.6. Adjoint Source over a Logical Volume
This tutorial defines an adjoint source over a response region and explicitly enables adjoint mode in the transport problem.
Audience: Users preparing adjoint calculations for response evaluation.
Prerequisites: Logical-volume sources and the introductory adjoint problem.
2.6.6.1. Build the material regions
The background has \(\sigma_t=1.0\) and scattering ratio \(c=0.8\). A separate response region spanning \(1.0 \le z \le 1.5\) has \(\sigma_t=1.5\) and \(c=0.1\). The logical volume assigns this region its own mesh block and also identifies where the response source is applied.
[ ]:
from mpi4py import MPI
from pyopensn.aquad import GLProductQuadrature1DSlab
from pyopensn.context import Finalize
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
nodes = [i / 20.0 for i in range(41)]
mesh = OrthogonalMeshGenerator(node_sets=[nodes]).Execute()
mesh.SetUniformBlockID(0)
response_region = RPPLogicalVolume(
infx=True, infy=True, zmin=1.0, zmax=1.5
)
mesh.SetBlockIDFromLogicalVolume(response_region, 1, True)
background_xs = MultiGroupXS()
background_xs.CreateSimpleOneGroup(sigma_t=1.0, c=0.8)
response_xs = MultiGroupXS()
response_xs.CreateSimpleOneGroup(sigma_t=1.5, c=0.1)
quadrature = GLProductQuadrature1DSlab(n_polar=16, scattering_order=0)
2.6.6.2. Define the adjoint source
At the input level, adjoint sources are defined with the same VolumetricSource and PointSource interfaces used for forward sources. There is no separate adjoint-source object. The distinction comes from enabling adjoint mode on the transport problem.
The user must supply the appropriate response function as the source strength and spatial support. OpenSn does not infer that response from the material assigned to the region. In this example, group_strength=1.5 supplies the chosen one-group response weighting throughout response_region; OpenSn distributes that volumetric strength over the cells selected by the logical volume.
[ ]:
adjoint_source = VolumetricSource(
logical_volume=response_region, group_strength=[1.5]
)
2.6.6.3. Enable adjoint mode
The source definition itself does not contain an adjoint flag. Setting adjoint=True in the problem options causes OpenSn to interpret the supplied response function as an adjoint source and solve the adjoint transport equation.
[ ]:
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": 100,
}
],
xs_map=[
{"block_ids": [0], "xs": background_xs},
{"block_ids": [1], "xs": response_xs},
],
volumetric_sources=[adjoint_source],
boundary_conditions=[
{"name": "zmin", "type": "reflecting"},
{"name": "zmax", "type": "reflecting"},
],
options={"adjoint": True},
)
solver = SteadyStateSourceSolver(problem=problem, compute_balance=True)
solver.Initialize()
solver.Execute()
2.6.6.4. Verify the adjoint source
The adjoint source strength is the response weighting applied throughout the selected volume. OpenSn distributes group_strength=1.5 over the response region when assembling the volumetric source. With reflecting boundaries, the integrated absorption must balance the integrated source, including scattering in both materials.
[ ]:
balance = solver.ComputeBalanceTable()
production = balance["production_rate"]
absorption = balance["absorption_rate"]
balance_error = abs(production - absorption)
if rank == 0:
print(f"Adjoint-source production rate={production:.8e}")
print(f"Adjoint-source absorption rate={absorption:.8e}")
print(f"Adjoint-source balance error={balance_error:.8e}")
assert abs(production - 0.75) < 1.0e-8
assert balance_error < 1.0e-8
if "opensn_console" not in globals():
from IPython import get_ipython
if get_ipython() is not None:
Finalize()
MPI.Finalize()