LCOV - code coverage report
Current view: top level - src/utils - RayTracingAngularQuadrature.C (source / functions) Hit Total Coverage
Test: idaholab/moose ray_tracing: #31405 (292dce) with base fef103 Lines: 140 140 100.0 %
Date: 2025-09-04 07:56:07 Functions: 13 13 100.0 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : //* This file is part of the MOOSE framework
       2             : //* https://mooseframework.inl.gov
       3             : //*
       4             : //* All rights reserved, see COPYRIGHT for full restrictions
       5             : //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
       6             : //*
       7             : //* Licensed under LGPL 2.1, please see LICENSE for details
       8             : //* https://www.gnu.org/licenses/lgpl-2.1.html
       9             : 
      10             : #include "RayTracingAngularQuadrature.h"
      11             : 
      12             : // MOOSE includes
      13             : #include "MooseUtils.h"
      14             : 
      15             : // libMesh includes
      16             : #include "libmesh/dense_vector.h"
      17             : 
      18         697 : RayTracingAngularQuadrature::RayTracingAngularQuadrature(const unsigned int dim,
      19             :                                                          const unsigned int polar_order,
      20             :                                                          const unsigned int azimuthal_order,
      21             :                                                          const Real mu_min,
      22         697 :                                                          const Real mu_max)
      23         697 :   : _dim(dim),
      24         697 :     _polar_order(polar_order),
      25         697 :     _azimuthal_order(azimuthal_order),
      26         697 :     _mu_min(mu_min),
      27         697 :     _mu_max(mu_max)
      28             : {
      29         697 :   if (_polar_order == 0)
      30           2 :     mooseError("polar_order must be positive in RayTracingAngularQuadrature");
      31         695 :   if (_azimuthal_order == 0)
      32           2 :     mooseError("azimuthal_order must be positive in RayTracingAngularQuadrature");
      33         693 :   if (_mu_min >= _mu_max)
      34           2 :     mooseError("mu_min must be < mu_max in RayTracingAngularQuadrature");
      35         691 :   if (_mu_min < -1)
      36           2 :     mooseError("mu_min must be >= -1 in RayTracingAngularQuadrature");
      37         689 :   if (_mu_max > 1)
      38           2 :     mooseError("mu_max must be <= 1 in RayTracingAngularQuadrature");
      39         687 :   if (dim != 2 && dim != 3)
      40           2 :     mooseError("RayTracingAngularQuadrature only supports dimensions 2 and 3");
      41             : 
      42             :   // Build the quadrature
      43         685 :   build();
      44             : 
      45             :   // Default rotation is up
      46         685 :   rotate(libMesh::Point(0, 0, 1));
      47         685 : }
      48             : 
      49             : void
      50         685 : RayTracingAngularQuadrature::build()
      51             : {
      52             :   // Chebyshev quadrature on [0, 2\pi]
      53         685 :   std::vector<Real> chebyshev_x(_azimuthal_order);
      54         685 :   std::vector<Real> chebyshev_w(_azimuthal_order);
      55         685 :   chebyshev(_azimuthal_order, chebyshev_x, chebyshev_w);
      56             : 
      57             :   // Gauss-Legendre quadrature on [0, 1]
      58             :   std::vector<Real> gauss_legendre_x;
      59             :   std::vector<Real> gauss_legendre_w;
      60         685 :   gaussLegendre(_polar_order, gauss_legendre_x, gauss_legendre_w);
      61             : 
      62         685 :   _phi.resize(0);
      63         685 :   _mu.resize(0);
      64         685 :   _w.resize(0);
      65             : 
      66             :   // Build the product quadrature
      67        2847 :   for (std::size_t i = 0; i < chebyshev_x.size(); ++i)
      68       10392 :     for (std::size_t j = 0; j < gauss_legendre_x.size(); ++j)
      69             :     {
      70             :       // If 2D, throw away the downward mu
      71        8230 :       if (_dim == 2 && gauss_legendre_x[j] < 0.5 - TOLERANCE * TOLERANCE)
      72        1449 :         continue;
      73             : 
      74        6781 :       _phi.push_back(chebyshev_x[i]);
      75        6781 :       _mu.push_back(gauss_legendre_x[j] * (_mu_max - _mu_min) + _mu_min);
      76        6781 :       _polar_sin.push_back(std::sin(std::acos(_mu.back())));
      77             : 
      78             :       const Real weight_factor =
      79        6781 :           (_dim == 2 && !MooseUtils::absoluteFuzzyEqual(gauss_legendre_x[j], 0.5)) ? 2.0 : 1.0;
      80        6781 :       _w.push_back(weight_factor * chebyshev_w[i] * gauss_legendre_w[j]);
      81             :     }
      82         685 : }
      83             : 
      84             : void
      85         688 : RayTracingAngularQuadrature::gaussLegendre(const unsigned int order,
      86             :                                            std::vector<Real> & x,
      87             :                                            std::vector<Real> & w)
      88             : {
      89         688 :   if (order == 0)
      90           2 :     mooseError("Order must be positive in gaussLegendre()");
      91             : 
      92         686 :   x.resize(order);
      93         686 :   w.resize(order);
      94         686 :   libMesh::DenseMatrix<Real> mat(order, order);
      95         686 :   libMesh::DenseVector<Real> lambda(order);
      96         686 :   libMesh::DenseVector<Real> lambdai(order);
      97         686 :   libMesh::DenseMatrix<Real> vec(order, order);
      98             : 
      99        3513 :   for (unsigned int i = 1; i < order; ++i)
     100             :   {
     101        2827 :     Real ri = i;
     102        2827 :     mat(i, i - 1) = ri / std::sqrt(((2. * ri - 1.) * (2. * ri + 1.)));
     103        2827 :     mat(i - 1, i) = mat(i, i - 1);
     104             :   }
     105         686 :   mat.evd_right(lambda, lambdai, vec);
     106             : 
     107        4199 :   for (unsigned int i = 0; i < order; ++i)
     108             :   {
     109        3513 :     x[i] = 0.5 * (lambda(i) + 1.0);
     110        3513 :     w[i] = vec(0, i) * vec(0, i);
     111             :   }
     112             : 
     113             :   // Sort based on the points
     114         686 :   std::vector<std::size_t> sorted_indices(x.size());
     115             :   std::iota(sorted_indices.begin(), sorted_indices.end(), 0);
     116         686 :   std::stable_sort(sorted_indices.begin(),
     117             :                    sorted_indices.end(),
     118        5801 :                    [&x](size_t i1, size_t i2) { return x[i1] < x[i2]; });
     119         686 :   const auto x_copy = x;
     120         686 :   const auto w_copy = w;
     121        4199 :   for (std::size_t i = 0; i < x.size(); ++i)
     122             :   {
     123        3513 :     x[i] = x_copy[sorted_indices[i]];
     124        3513 :     w[i] = w_copy[sorted_indices[i]];
     125             :   }
     126        1372 : }
     127             : 
     128             : void
     129       24414 : RayTracingAngularQuadrature::rotate(const libMesh::Point & rotation_direction)
     130             : {
     131       24414 :   _current_rotation_direction = rotation_direction;
     132             : 
     133       24414 :   libMesh::DenseMatrix<Real> rotation_matrix;
     134       24414 :   rotationMatrix(rotation_direction.unit(), rotation_matrix);
     135             : 
     136       24414 :   _current_directions.clear();
     137       24414 :   _current_directions.reserve(_phi.size());
     138             : 
     139       24414 :   _current_weights.clear();
     140       24414 :   _current_weights.reserve(_w.size());
     141             : 
     142       24414 :   _current_polar_sins.clear();
     143       24414 :   _current_polar_sins.reserve(_polar_sin.size());
     144             : 
     145       24414 :   libMesh::DenseVector<Real> omega(3);
     146       24414 :   libMesh::DenseVector<Real> omega_p(3);
     147             :   Point direction;
     148             : 
     149      262815 :   for (std::size_t q = 0; q < _phi.size(); ++q)
     150             :   {
     151             :     // Get direction as a DenseVector for rotation
     152      238401 :     omega(0) = sqrt(1 - _mu[q] * _mu[q]) * cos(_phi[q]);
     153      238401 :     omega(1) = sqrt(1 - _mu[q] * _mu[q]) * sin(_phi[q]);
     154      238401 :     omega(2) = _mu[q];
     155             : 
     156             :     // Rotate and create the direction
     157      238401 :     rotation_matrix.vector_mult(omega_p, omega);
     158      238401 :     direction(0) = omega_p(0);
     159      238401 :     direction(1) = omega_p(1);
     160      238401 :     direction(2) = omega_p(2);
     161             : 
     162             :     // The index for the new direction in the "current" data
     163             :     // structures. By default - we add a new one. However,
     164             :     // in 2D we may have a duplicate projected direction
     165             :     // so we need to keep track of this in the case
     166             :     // that we are adding information to an already
     167             :     // existant direction
     168             :     std::size_t l = _current_directions.size();
     169             : 
     170             :     // If 2D, project to the xy plane and see if any other
     171             :     // projected directions are a duplicate
     172      238401 :     if (_dim == 2)
     173             :     {
     174       12384 :       direction(2) = 0;
     175       12384 :       direction /= direction.norm();
     176             : 
     177             :       // If we loop through all current directions and find
     178             :       // no duplicates, this will set l back to _current_directions.size()
     179       47180 :       for (l = 0; l < _current_directions.size(); ++l)
     180       36034 :         if (_current_directions[l].absolute_fuzzy_equals(direction))
     181             :           break;
     182             :     }
     183             : 
     184             :     // If l is the current size of _current_directions, it means
     185             :     // that no duplicates were found and that we are inserting
     186             :     // a new direction
     187      238401 :     if (l == _current_directions.size())
     188             :     {
     189      237163 :       _current_directions.push_back(direction);
     190      237163 :       _current_weights.resize(l + 1);
     191      237163 :       _current_polar_sins.resize(l + 1);
     192             :     }
     193             :     else
     194             :       mooseAssert(_dim == 2, "Should only have duplicates in 2D");
     195             : 
     196             :     // Add to weights and sins for this direction
     197      238401 :     _current_weights[l].push_back(_w[q]);
     198      238401 :     _current_polar_sins[l].push_back(_polar_sin[q]);
     199             :   }
     200       24414 : }
     201             : 
     202             : const libMesh::Point &
     203      418611 : RayTracingAngularQuadrature::getDirection(const unsigned int l) const
     204             : {
     205      418611 :   checkDirection(l);
     206      418611 :   return _current_directions[l];
     207             : }
     208             : 
     209             : const std::vector<Real> &
     210         210 : RayTracingAngularQuadrature::getWeights(const unsigned int l) const
     211             : {
     212         210 :   checkDirection(l);
     213         210 :   return _current_weights[l];
     214             : }
     215             : 
     216             : Real
     217        1050 : RayTracingAngularQuadrature::getTotalWeight(const unsigned int l) const
     218             : {
     219        1050 :   checkDirection(l);
     220        1050 :   return std::accumulate(_current_weights[l].begin(), _current_weights[l].end(), (Real)0);
     221             : }
     222             : 
     223             : const std::vector<Real> &
     224          32 : RayTracingAngularQuadrature::getPolarSins(const unsigned int l) const
     225             : {
     226          32 :   checkDirection(l);
     227          32 :   return _current_polar_sins[l];
     228             : }
     229             : 
     230             : std::size_t
     231          20 : RayTracingAngularQuadrature::numPolar(const unsigned int l) const
     232             : {
     233          20 :   checkDirection(l);
     234             :   if (_dim == 3)
     235             :     mooseAssert(_current_weights[l].size() == 1, "Should be 1 polar in 3D");
     236          20 :   return _current_polar_sins[l].size();
     237             : }
     238             : 
     239             : void
     240      419925 : RayTracingAngularQuadrature::checkDirection(const unsigned int l) const
     241             : {
     242      419925 :   if (!hasDirection(l))
     243           2 :     mooseError("RayTracingAngularQuadrature does not have direction ", l);
     244      419923 : }
     245             : 
     246             : void
     247         686 : RayTracingAngularQuadrature::chebyshev(const unsigned int order,
     248             :                                        std::vector<Real> & x,
     249             :                                        std::vector<Real> & w)
     250             : {
     251         686 :   x.resize(order);
     252         686 :   w.resize(order);
     253             : 
     254        2948 :   for (std::size_t i = 0; i < order; ++i)
     255             :   {
     256        2262 :     x[i] = 2 * (Real)i * M_PI / (Real)order;
     257        2262 :     w[i] = 2 * M_PI / (Real)order;
     258             :   }
     259         686 : }
     260             : 
     261             : void
     262       24414 : RayTracingAngularQuadrature::rotationMatrix(const libMesh::Point & direction,
     263             :                                             libMesh::DenseMatrix<Real> & matrix)
     264             : {
     265       24414 :   matrix.resize(3, 3);
     266             :   matrix.zero();
     267             : 
     268             :   // Create a local coordinate system around direction
     269       24414 :   const libMesh::Point tx = orthonormalVector(direction);
     270       24414 :   const libMesh::Point ty = direction.cross(tx);
     271             : 
     272             :   // Create the rotation matrix
     273       97656 :   for (unsigned int j = 0; j < 3; ++j)
     274             :   {
     275       73242 :     matrix(j, 0) = tx(j);
     276       73242 :     matrix(j, 1) = ty(j);
     277       73242 :     matrix(j, 2) = direction(j);
     278             :   }
     279       24414 : }
     280             : 
     281             : libMesh::Point
     282       24416 : RayTracingAngularQuadrature::orthonormalVector(const libMesh::Point & v)
     283             : {
     284       24416 :   if (MooseUtils::absoluteFuzzyLessEqual(v.norm(), 0))
     285           2 :     ::mooseError("Vector v has norm close to 0 in orthonormalVector()");
     286             : 
     287       24414 :   if (MooseUtils::absoluteFuzzyEqual(v(0), 0))
     288             :     return libMesh::Point(1, 0, 0);
     289        7744 :   if (MooseUtils::absoluteFuzzyEqual(v(1), 0))
     290             :     return libMesh::Point(0, 1, 0);
     291          17 :   if (MooseUtils::absoluteFuzzyEqual(v(2), 0))
     292             :     return libMesh::Point(0, 0, 1);
     293             : 
     294           1 :   return libMesh::Point(-v(1), v(0), 0).unit();
     295             : }

Generated by: LCOV version 1.14