Discrete Ordinates Solvers
Solver
Unified SN (Discrete Ordinates) eigenvalue solver.
Supports 1D (ny=1, GL quadrature) and 2D (Lebedev quadrature) with selectable inner solver strategy and scattering anisotropy order.
The transport sweep uses diamond-difference spatial discretization: - 1D: cumulative-product recurrence (~ms) - 2D: wavefront parallelism along anti-diagonals
Boundary conditions are reflective on all sides (infinite lattice).
See also
Discrete Ordinates Method (SN) — Key Facts, equations, gotchas.
- class orpheus.sn.solver.SNResult(keff, keff_history, angular_flux, scalar_flux, geometry, quadrature, eg, elapsed_seconds)[source]
Bases:
objectResults of an SN transport calculation.
The primary output is the angular flux (the direct solution of the SN equations). Scalar flux is derived by quadrature integration.
- Parameters:
- keff: float
- angular_flux: ndarray
- scalar_flux: ndarray
- geometry: Mesh1D | Mesh2D
- quadrature: AngularQuadrature
- eg: ndarray
- elapsed_seconds: float
- class orpheus.sn.solver.SNSolver(materials, sn_mesh, inner_solver='source_iteration', scattering_order=0, keff_tol=1e-07, flux_tol=1e-06, max_inner=200, inner_tol=1e-08)[source]
Bases:
objectUnified SN eigenvalue solver satisfying the EigenvalueSolver protocol.
- Parameters:
materials (dict mapping material ID to Mixture.)
sn_mesh (SNMesh — augmented geometry (wraps Mesh1D or Mesh2D with) – precomputed streaming stencil).
inner_solver ("source_iteration" or "bicgstab".)
scattering_order (int — Legendre order for scattering (0 = P0).)
keff_tol (outer iteration convergence.)
flux_tol (outer iteration convergence.)
max_inner (inner iteration parameters.)
inner_tol (inner iteration parameters.)
- initial_flux_distribution()[source]
Initial scalar flux guess: ones(nx, ny, ng).
- Return type:
- compute_fission_source(flux_distribution, keff)[source]
Fission source: χ · (νΣ_f · φ) / k.
- solve_fixed_source(fission_source, flux_distribution)[source]
Solve the within-group transport equation for given fission source.
Returns updated scalar flux (nx, ny, ng).
- compute_keff(flux_distribution)[source]
k = production / absorption (volume-weighted).
- orpheus.sn.solver.solve_sn(materials, mesh, quadrature, inner_solver='source_iteration', scattering_order=0, max_outer=500, keff_tol=1e-07, flux_tol=1e-06, max_inner=200, inner_tol=1e-08)[source]
Solve the multi-group SN eigenvalue problem.
- Parameters:
materials (dict mapping material ID to Mixture.)
mesh (Mesh1D or Mesh2D (base geometry).)
quadrature (AngularQuadrature (GaussLegendre1D or LebedevSphere).)
inner_solver ("source_iteration" (default) or "bicgstab".)
scattering_order (Legendre order for scattering (0 = P0).)
max_outer (maximum outer (power) iterations.)
keff_tol (outer convergence.)
flux_tol (outer convergence.)
max_inner (inner solver parameters.)
inner_tol (inner solver parameters.)
- Return type:
SNResult
Geometry
Augmented geometry for SN discrete ordinates transport.
SNMesh wraps a Mesh1D or
Mesh2D and precomputes the coordinate-specific
streaming stencil used by the transport sweep.
Three coordinate systems are supported: Cartesian (1D/2D), spherical (1D), and cylindrical (1D). Curvilinear geometries precompute angular redistribution coefficients (\(\alpha\)), the geometry factor \(\Delta A/w\), and Morel–Montry angular closure weights.
- class orpheus.sn.geometry.SNMesh(mesh, quadrature)[source]
Bases:
objectAugmented geometry for the discrete ordinates method.
Wraps a
Mesh1DorMesh2Dand precomputes the streaming stencil (diamond-difference coefficients that depend only on geometry and angular quadrature, not on cross sections).For Cartesian geometry the stencil stores:
streaming_x[n, i]= \(2|\mu_{x,n}| / \Delta x_i\)streaming_y[n, j]= \(2|\mu_{y,n}| / \Delta y_j\)
For future curvilinear geometries, additional curvature terms (\(\alpha_n / r_i\)) will be stored in
self.curvature.- Parameters:
mesh (Mesh1D or Mesh2D) – Base geometry.
quadrature (AngularQuadrature) – Angular quadrature (Gauss–Legendre, Lebedev, etc.).
- property volumes: ndarray
Cell volumes, shape (nx, ny).
- property is_1d: bool
True if this is a 1-D mesh (ny == 1).
Angular Quadrature
Angular quadrature for SN transport.
Provides a protocol and two implementations: - GaussLegendre1D: for 1D slab problems (mu on [-1,1], weights sum to 2) - LebedevSphere: for 2D/3D problems (directions on unit sphere, weights sum to 4π)
The solver uses 1/sum(weights) as the isotropic normalization factor,
making it quadrature-agnostic.
- class orpheus.sn.quadrature.AngularQuadrature(*args, **kwargs)[source]
Bases:
ProtocolContract for any angular quadrature usable by the SN solver.
- mu_x: ndarray
- mu_y: ndarray
- weights: ndarray
- N: int
- reflection_index(axis)[source]
Index array: ref[n] = partner of ordinate n reflected in
axis.
- class orpheus.sn.quadrature.GaussLegendre1D(mu_x, mu_y, weights, N)[source]
Bases:
objectGauss-Legendre quadrature on [-1, 1] for 1D slab transport.
mu_x = GL points, mu_y = 0. Weights sum to 2.
- mu_x: ndarray
- mu_y: ndarray
- weights: ndarray
- N: int
- property mu: ndarray
Alias for mu_x (the 1D direction cosines).
- classmethod create(n_ordinates=16)[source]
Build N-point GL quadrature (must be even for SN).
- Parameters:
n_ordinates (int)
- Return type:
GaussLegendre1D
- reflection_index(axis)[source]
GL is symmetric: partner of i is N-1-i for x-reflection.
- class orpheus.sn.quadrature.LebedevSphere(mu_x, mu_y, mu_z, weights, N, _ref_x, _ref_y, _ref_z)[source]
Bases:
objectLebedev quadrature on the unit sphere for 2D/3D transport.
Weights sum to 4π. Directions cover the full sphere.
- Parameters:
- mu_x: ndarray
- mu_y: ndarray
- mu_z: ndarray
- weights: ndarray
- N: int
- class orpheus.sn.quadrature.LevelSymmetricSN(mu_x, mu_y, mu_z, weights, N, _ref_x, _ref_y, _ref_z, n_levels, level_indices, level_mu)[source]
Bases:
objectLevel-symmetric S_N quadrature on the unit sphere.
Standard triangular quadrature with N/2 μ-levels per hemisphere. Provides the
level_indicesstructure required by the cylindrical SN sweep for azimuthal redistribution.Weights sum to 4π.
- Parameters:
- mu_x: ndarray
- mu_y: ndarray
- mu_z: ndarray
- weights: ndarray
- N: int
- n_levels: int
- level_mu: ndarray
- class orpheus.sn.quadrature.ProductQuadrature(mu_x, mu_y, mu_z, weights, N, _ref_x, _ref_y, _ref_z, n_levels, level_indices, level_mu)[source]
Bases:
objectProduct quadrature: Gauss-Legendre(μ) × equispaced(φ).
The polar angle θ is discretised via Gauss-Legendre on μ = cos θ ∈ [-1, 1]. The azimuthal angle φ is discretised uniformly on [0, 2π).
Direction cosines: - μ_z = μ (axial, = cos θ) - μ_x = η = sin(θ) cos(φ) (radial for cylindrical) - μ_y = ξ = sin(θ) sin(φ) (azimuthal for cylindrical)
Weights:
w = w_GL(μ) · (2π / n_phi)— sum to 4π.Provides
level_indicesfor the cylindrical sweep.- Parameters:
- mu_x: ndarray
- mu_y: ndarray
- mu_z: ndarray
- weights: ndarray
- N: int
- n_levels: int
- level_mu: ndarray
- classmethod create(n_mu=8, n_phi=8)[source]
Build product quadrature with n_mu GL points and n_phi azimuthal points.
Transport Sweep
Diamond-difference transport sweep for 1D and 2D Cartesian meshes.
Two paths, automatically dispatched: - 1D cumprod: for ny=1, mu_y=0 (Gauss-Legendre). Solves the
recurrence via cumulative products — O(nc) numpy ops, ~ms.
2D wavefront: for general 2D. Sweeps cells along anti-diagonals (i+j=const), vectorized within each diagonal.
Both paths use the precomputed streaming stencil from SNMesh
to avoid redundant per-ordinate per-cell divisions.
- orpheus.sn.sweep.transport_sweep(Q, sig_t, sn_mesh, psi_bc, Q_aniso=None)[source]
Perform one full diamond-difference transport sweep.
- Parameters:
Q ((nx, ny, ng) isotropic source density.)
sig_t ((nx, ny, ng) total macroscopic cross section.)
sn_mesh (SNMesh — augmented geometry with precomputed stencil.)
psi_bc (mutable dict storing persistent boundary fluxes) – for reflective BCs between outer iterations.
Q_aniso ((N, nx, ny, ng) per-ordinate anisotropic source (P1+) – scattering). None for isotropic-only (P0).
- Returns:
angular_flux ((N, nx, ny, ng) angular flux per ordinate.)
scalar_flux ((nx, ny, ng) = Σ_n w_n ψ_n.)
- Return type:
tuple[np.ndarray, np.ndarray]
Direct Transport Operator
Direct transport operator for Krylov inner solves.
Provides the explicit operator T: ψ → T·ψ via finite differences, used
by the bicgstab inner solver path in SNSolver.
Two geometries are supported:
Cartesian 2D —
T = μ_x ∂/∂x + μ_y ∂/∂y + Σ_tSpherical 1D —
T = μ (A ∂/∂r)/V + (α ∂/∂μ)/V + Σ_t
The sweep-based solver (source iteration) inverts T implicitly via diamond-difference sweeps. This module forms T explicitly so that scipy’s Krylov solvers (BiCGSTAB, GMRES) can solve T·ψ = b directly.
- class orpheus.sn.operator.EquationMap(n_eq, n_unknowns, ordinate, ix, iy)[source]
Bases:
objectMapping between 1D solution vector and 4D angular flux.
- n_eq: int
- n_unknowns: int
- ordinate: ndarray
- ix: ndarray
- iy: ndarray
- orpheus.sn.operator.build_equation_map(nx, ny, quad, ng)[source]
Identify which (ordinate, cell) combos are unknowns.
Filter: mu_z >= 0 (upper hemisphere), and NOT incoming at reflective boundaries (those are determined by reflection).
- orpheus.sn.operator.solution_to_angular_flux(solution, eq_map, quad, nx, ny, ng)[source]
Convert 1D solution vector to 4D angular flux (ng, N, nx, ny).
Applies z-reflection and reflective BCs to fill the full array from the reduced set of unknowns.
- orpheus.sn.operator.angular_flux_to_scalar(fi, quad, nx, ny, ng)[source]
Integrate angular flux to scalar flux: φ = Σ w_n ψ_n.
- orpheus.sn.operator.transport_operator_matvec(solution, eq_map, quad, sig_t, nx, ny, ng, dx, dy)[source]
Apply the streaming + collision operator T·ψ.
- orpheus.sn.operator.build_transport_linear_operator(eq_map, quad, sig_t, nx, ny, ng, dx, dy)[source]
Build scipy LinearOperator for T = μ·∇ + Σ_t.
- orpheus.sn.operator.build_rhs(fission_source, scalar_flux, eq_map, quad, sig_s, sig2, mat_map, nx, ny, ng, scattering_order=0, angular_flux=None)[source]
Build the RHS source vector for T·ψ = b.
All isotropic sources are divided by sum(weights) — the angular normalization for the discrete angular flux equation.
For Pn scattering (scattering_order > 0), the scattering source is per-ordinate using Legendre moments of the angular flux:
qS(n) = Σ_l (2l+1) · Σ_s^l^T @ [Σ_m fiL_lm · Y_lm(n)] / sum_w
- Parameters:
fission_source ((nx, ny, ng) — already divided by sum(w) by the caller.)
scalar_flux ((nx, ny, ng) — current scalar flux for scattering.)
sig_s (dict[mat_id → list of (ng, ng)] Legendre scattering matrices.)
scattering_order (Legendre order L (0 = P0 isotropic).)
angular_flux ((ng, N, nx, ny) angular flux for computing Legendre) – moments. Required if scattering_order > 0.
eq_map (EquationMap)
quad (AngularQuadrature)
mat_map (ndarray)
nx (int)
ny (int)
ng (int)
- Return type:
(n_unknowns,) RHS vector.
- orpheus.sn.operator.build_equation_map_spherical(nx, quad, ng)[source]
Equation map for spherical 1D: all (ordinate, cell) pairs except incoming directions at the outer reflective boundary.
- orpheus.sn.operator.solution_to_angular_flux_spherical(solution, eq_map, quad, nx, ng)[source]
Convert 1D solution vector to angular flux array (ng, N, nx, 1).
Applies reflective BC at the outer boundary.
- orpheus.sn.operator.transport_operator_matvec_spherical(solution, eq_map, quad, sig_t, nx, ng, face_areas, volumes, alpha_half, redist_dAw, tau_mm)[source]
Apply the spherical transport operator T·ψ.
\[(T\psi)_{n,i} = \frac{\mu_n}{V_i} \bigl[A_{i+\frac12}\psi_{i+\frac12} - A_{i-\frac12}\psi_{i-\frac12}\bigr] + \frac{\Delta A_i}{w_n V_i} \bigl[\alpha_{n+\frac12}\psi_{n+\frac12} - \alpha_{n-\frac12}\psi_{n-\frac12}\bigr] + \Sigma_t \psi_{n,i}\]The \(\Delta A / w\) geometry factor (
redist_dAw, precomputed inSNMesh) ensures per-ordinate flat-flux consistency (Bailey et al. 2009).Face fluxes are approximated by arithmetic averages of cell-centre values.
- orpheus.sn.operator.build_transport_linear_operator_spherical(eq_map, quad, sig_t, nx, ng, face_areas, volumes, alpha_half, redist_dAw, tau_mm)[source]
Build scipy LinearOperator for spherical T.
- orpheus.sn.operator.build_rhs_spherical(fission_source, scalar_flux, eq_map, quad, sig_s, sig2, mat_map, nx, ng, scattering_order=0, angular_flux=None)[source]
Build the RHS source vector for spherical T·ψ = b.
Same structure as Cartesian
build_rhsbut with spherical equation map (no y-direction, no z-reflection filtering).
- orpheus.sn.operator.build_equation_map_cylindrical(nx, quad, ng)
Equation map for spherical 1D: all (ordinate, cell) pairs except incoming directions at the outer reflective boundary.
- orpheus.sn.operator.solution_to_angular_flux_cylindrical(solution, eq_map, quad, nx, ng)
Convert 1D solution vector to angular flux array (ng, N, nx, 1).
Applies reflective BC at the outer boundary.
- orpheus.sn.operator.transport_operator_matvec_cylindrical(solution, eq_map, quad, sig_t, nx, ng, face_areas, volumes, alpha_per_level, redist_dAw_per_level, tau_mm_per_level)[source]
Apply the cylindrical transport operator T·ψ.
Per-level azimuthal redistribution with geometry-weighted \(\Delta A / w\) factor and Morel–Montry angular closure.
- orpheus.sn.operator.build_transport_linear_operator_cylindrical(eq_map, quad, sig_t, nx, ng, face_areas, volumes, alpha_per_level, redist_dAw_per_level, tau_mm_per_level)[source]
Build scipy LinearOperator for cylindrical T.
- orpheus.sn.operator.build_rhs_cylindrical(fission_source, scalar_flux, eq_map, quad, sig_s, sig2, mat_map, nx, ng, scattering_order=0, angular_flux=None)
Build the RHS source vector for spherical T·ψ = b.
Same structure as Cartesian
build_rhsbut with spherical equation map (no y-direction, no z-reflection filtering).