3.3. Triangular Quadrature

This set uses a Gauss-Legendre quadrature along the polar angle, with decreasing orders of Gauss-Chebyshev quadrature along the azimuthal angle as the polar angle moves away from the equatorial plane.

To run the code, simply type: jupyter nbconvert --to python --execute <basename>.ipynb.

To convert it to a python file (named <basename>.py), simply type: jupyter nbconvert --to python <basename>.ipynb

[ ]:
import os
import sys
import numpy as np
from mpi4py import MPI

sys.path.append("../..")

from pyopensn.aquad import GLCTriangularQuadrature2DXY
from pyopensn.context import UseColor, Finalize

UseColor(False)

3.3.1. Quadrature parameters

Here, we use a Triangular Quadrature for two-dimensional geometries.

We pick a quadrature set with 4 polar angles. Unlike a product quadrature, the number of azimuthal angles varies per polar level: the maximum is \(2 \times N_{polar} = 8\) at the equator, decreasing by 4 (one per quadrant) for each polar level toward the pole. For 2D (upper hemisphere only with 2 polar levels), the azimuthal counts are [4, 8], giving a total of \(4 + 8 = 12\) directions.

[ ]:
# Create a 2D angular quadrature with 4 polar angles (triangular pattern).
tquad = GLCTriangularQuadrature2DXY(n_polar=4, scattering_order=0)

3.3.2. Retrieve directions

[ ]:
vec3_omegas = tquad.omegas

n_directions = len(vec3_omegas)
print('number of directions =',n_directions)
omegas = np.zeros((n_directions,3))

for d in range(n_directions):
    omegas[d,:] = [vec3_omegas[d].x, vec3_omegas[d].y, vec3_omegas[d].z]

dim = 2

3.3.3. Create a function to plot the quadrature

[ ]:
import matplotlib.pyplot as plt
from matplotlib.patches import FancyArrowPatch
from mpl_toolkits.mplot3d import proj3d
import itertools

colors = itertools.cycle(["r", "g", "b", "k", "m", "c", "y", "crimson"])

# Create figure with options
fig = plt.figure(dpi=150)
ax = fig.add_subplot(111, projection='3d')

# Transparent sphere data
u = np.linspace(0, 2 * np.pi, 100)
v = np.linspace(0, np.pi, 100)
x = np.outer(np.cos(u), np.sin(v))
y = np.outer(np.sin(u), np.sin(v))
z = np.outer(np.ones(np.size(u)), np.cos(v))
# Plot the surface
ax.plot_surface(x, y, z, color='b', alpha=0.1)

# Create a custom 3D arrow class
class Arrow3D(FancyArrowPatch):
    def __init__(self, xs, ys, zs, *args, **kwargs):
        # Initialize with dummy 2D points
        super().__init__((0, 0), (0, 0), *args, **kwargs)
        self._verts3d = xs, ys, zs

    def do_3d_projection(self, renderer=None):
        # Project 3D coordinates to 2D using the axes projection matrix
        xs3d, ys3d, zs3d = self._verts3d
        xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, self.axes.get_proj())
        self.set_positions((xs[0], ys[0]), (xs[1], ys[1]))
        # Return a numerical depth value (for example, the mean of the projected z-values)
        return np.mean(zs)

    def draw(self, renderer):
        # Perform projection using the axes' projection matrix
        xs3d, ys3d, zs3d = self._verts3d
        xs, ys, zs = proj3d.proj_transform(xs3d, ys3d, zs3d, self.axes.get_proj())
        self.set_positions((xs[0], ys[0]), (xs[1], ys[1]))
        return super().draw(renderer)


start = -1.0

# Create axes arrows
a = Arrow3D(
    [start, 1.1],
    [0, 0],
    [0, 0],
    mutation_scale=10,
    lw=0.5,
    arrowstyle="-|>",
    color="darkorange",
)
ax.add_artist(a)

a = Arrow3D(
    [0, 0],
    [start, 1.1],
    [0, 0],
    mutation_scale=10,
    lw=0.5,
    arrowstyle="-|>",
    color="darkorange",
)
ax.add_artist(a)

a = Arrow3D(
    [0, 0],
    [0, 0],
    [start, 1.1],
    mutation_scale=10,
    lw=0.5,
    arrowstyle="-|>",
    color="darkorange",
)
ax.add_artist(a)

# The following code plots quadrature directions, colored by polar level
# to highlight the triangular pattern (fewer directions near the pole).
mu = omegas[:, -1]
polar_levels = np.unique(np.round(mu, decimals=10))
print("number of polar levels =", len(polar_levels))
for plevel in polar_levels:
    clr = next(colors)
    mask = np.abs(mu - plevel) < 1e-9
    dirs = omegas[mask]
    print(f"  mu = {plevel:.4f}: {len(dirs)} directions")
    for om in dirs:
        ax.plot3D([0, om[0]], [0, om[1]], [0, om[2]], c=clr, linewidth=0.75)

for r in polar_levels:
    x = np.sqrt(1 - r ** 2) * np.cos(u)
    y = np.sqrt(1 - r ** 2) * np.sin(u)
    z = r * np.ones_like(u)
    ax.plot3D(x, y, z, 'grey', linestyle="dashed", linewidth=0.5)

ax.view_init(30, 70)

# uncomment this line for interactive plot
# plt.show()

3.3.3.1. You should be getting this plot (saved here in the Markdown for convenience).

tGLC.png

3.3.4. Finalize (for Jupyter Notebook only)

In Python script mode, PyOpenSn automatically handles environment termination. However, this automatic finalization does not occur when running in a Jupyter notebook, so explicit finalization of the environment at the end of the notebook is required. Do not call the finalization in Python script mode, or in console mode.

Note that PyOpenSn’s finalization must be called before MPI’s finalization.

[ ]:
from IPython import get_ipython

def finalize_env():
    Finalize()
    MPI.Finalize()

ipython_instance = get_ipython()
if ipython_instance is not None:
    ipython_instance.events.register("post_execute", finalize_env)