Back to home page

EIC code displayed by LXR

 
 

    


File indexing completed on 2026-11-13 10:31:45

0001 //------------------------------- -*- C++ -*- -------------------------------//
0002 // Copyright Celeritas contributors: see top-level COPYRIGHT file for details
0003 // SPDX-License-Identifier: (Apache-2.0 OR MIT)
0004 //---------------------------------------------------------------------------//
0005 //! \file corecel/math/ArrayUtils.hh
0006 //! \brief Math functions using celeritas::Array
0007 //---------------------------------------------------------------------------//
0008 #pragma once
0009 
0010 #include <cmath>
0011 
0012 #include "corecel/Assert.hh"
0013 #include "corecel/Types.hh"
0014 #include "corecel/cont/Array.hh"
0015 
0016 #include "Algorithms.hh"
0017 #include "ArraySoftUnit.hh"
0018 #include "SoftEqual.hh"
0019 
0020 #include "detail/ArrayUtilsImpl.hh"
0021 
0022 namespace celeritas
0023 {
0024 //---------------------------------------------------------------------------//
0025 // Perform y <- ax + y
0026 template<class T, std::size_t N>
0027 inline CELER_FUNCTION void axpy(T a, Array<T, N> const& x, Array<T, N>* y);
0028 
0029 //---------------------------------------------------------------------------//
0030 // Calculate product of two vectors
0031 template<class T, std::size_t N>
0032 [[nodiscard]] inline CELER_FUNCTION T dot_product(Array<T, N> const& x,
0033                                                   Array<T, N> const& y);
0034 
0035 //---------------------------------------------------------------------------//
0036 // Calculate product of two vectors
0037 template<class T>
0038 [[nodiscard]] inline CELER_FUNCTION Array<T, 3> cross_product(
0039     Array<T, 3> const& x, Array<T, 3> const& y);
0040 
0041 //---------------------------------------------------------------------------//
0042 // Calculate the Euclidean (2) norm of a vector
0043 template<class T, std::size_t N>
0044 [[nodiscard]] inline CELER_FUNCTION T norm(Array<T, N> const& vec);
0045 
0046 //---------------------------------------------------------------------------//
0047 // Construct a vector with unit magnitude
0048 template<class T, std::size_t N>
0049 [[nodiscard]] inline CELER_FUNCTION Array<T, N> make_unit_vector(
0050     Array<T, N> const& v);
0051 
0052 //---------------------------------------------------------------------------//
0053 // Return x - (x . y) * y for a unit vector y
0054 template<class T, std::size_t N>
0055 [[nodiscard]] inline CELER_FUNCTION Array<T, N> make_orthogonal(
0056     Array<T, N> const& x, Array<T, N> const& y);
0057 
0058 //---------------------------------------------------------------------------//
0059 // Check whether two vectors are approximately orthogonal
0060 template<class T, std::size_t N>
0061 inline CELER_FUNCTION bool is_soft_orthogonal(Array<T, N> const& x,
0062                                               Array<T, N> const& y);
0063 
0064 //---------------------------------------------------------------------------//
0065 // Check whether two vectors are approximately collinear
0066 template<class T, std::size_t N>
0067 inline CELER_FUNCTION bool is_soft_collinear(Array<T, N> const& x,
0068                                              Array<T, N> const& y);
0069 
0070 //---------------------------------------------------------------------------//
0071 // Calculate the Euclidean (2) distance between two points
0072 template<class T, std::size_t N>
0073 [[nodiscard]] inline CELER_FUNCTION T distance(Array<T, N> const& x,
0074                                                Array<T, N> const& y);
0075 
0076 //---------------------------------------------------------------------------//
0077 // Calculate a cartesian unit vector from spherical coordinates
0078 template<class T>
0079 [[nodiscard]] inline CELER_FUNCTION Array<T, 3> from_spherical(T costheta,
0080                                                                T phi);
0081 
0082 //---------------------------------------------------------------------------//
0083 // Rotate the direction 'dir' according to the reference rotation axis 'rot'
0084 template<class T>
0085 [[nodiscard]] inline CELER_FUNCTION Array<T, 3> rotate(Array<T, 3> const& dir,
0086                                                        Array<T, 3> const& rot);
0087 
0088 //---------------------------------------------------------------------------//
0089 // INLINE DEFINITIONS
0090 //---------------------------------------------------------------------------//
0091 /*!
0092  * Increment a vector by another vector multiplied by a scalar.
0093  *
0094  * \f[
0095  * y \gets \alpha x + y
0096  * \f]
0097  *
0098  * Note that this uses \c celeritas::fma which supports types other than
0099  * floating point.
0100  */
0101 template<class T, std::size_t N>
0102 CELER_FUNCTION void axpy(T a, Array<T, N> const& x, Array<T, N>* y)
0103 {
0104     CELER_EXPECT(y);
0105     for (size_type i = 0; i != N; ++i)
0106     {
0107         (*y)[i] = fma(a, x[i], (*y)[i]);
0108     }
0109 }
0110 
0111 //---------------------------------------------------------------------------//
0112 /*!
0113  * Dot product of two vectors.
0114  *
0115  * Note that this uses \c celeritas::fma which supports types other than
0116  * floating point.
0117  */
0118 template<class T, std::size_t N>
0119 CELER_FUNCTION T dot_product(Array<T, N> const& x, Array<T, N> const& y)
0120 {
0121     T result{};
0122     for (size_type i = 0; i != N; ++i)
0123     {
0124         result = fma(x[i], y[i], result);
0125     }
0126     return result;
0127 }
0128 
0129 //---------------------------------------------------------------------------//
0130 /*!
0131  * Cross product of two space vectors.
0132  */
0133 template<class T>
0134 CELER_FUNCTION Array<T, 3> cross_product(Array<T, 3> const& x,
0135                                          Array<T, 3> const& y)
0136 {
0137     return {x[1] * y[2] - x[2] * y[1],
0138             x[2] * y[0] - x[0] * y[2],
0139             x[0] * y[1] - x[1] * y[0]};
0140 }
0141 
0142 //---------------------------------------------------------------------------//
0143 /*!
0144  * Calculate the Euclidean (2) norm of a vector.
0145  */
0146 template<class T, std::size_t N>
0147 CELER_FUNCTION T norm(Array<T, N> const& v)
0148 {
0149     return std::sqrt(dot_product(v, v));
0150 }
0151 
0152 //---------------------------------------------------------------------------//
0153 /*!
0154  * Construct a unit vector.
0155  *
0156  * Unit vectors have an Euclidean norm magnitude of 1.
0157  */
0158 template<class T, std::size_t N>
0159 CELER_FUNCTION Array<T, N> make_unit_vector(Array<T, N> const& v)
0160 {
0161     Array<T, N> result{v};
0162     T const scale_factor = 1 / norm(result);
0163     for (auto& el : result)
0164     {
0165         el *= scale_factor;
0166     }
0167     return result;
0168 }
0169 
0170 //---------------------------------------------------------------------------//
0171 /*!
0172  * Return the component of \em x that is orthogonal to the unit vector \em y.
0173  *
0174  * In this implementation, \em y must be normalized, and the result is not
0175  * normalized.
0176  *
0177  * \f[
0178 \mathbf{x}' \gets \mathbf{x} - (\mathbf{x} \cdot \mathbf{y}) \mathbf{y}
0179 \, , \quad \|\mathbf{y}\| = 1
0180 \f]
0181  */
0182 template<class T, std::size_t N>
0183 [[nodiscard]] inline CELER_FUNCTION Array<T, N> make_orthogonal(
0184     Array<T, N> const& x, Array<T, N> const& y)
0185 {
0186     CELER_EXPECT(is_soft_unit_vector(y));
0187     Array<T, N> result{x};
0188     axpy(-dot_product(x, y), y, &result);
0189     return result;
0190 }
0191 
0192 //---------------------------------------------------------------------------//
0193 /*!
0194  * Check whether two unit vectors are approximately orthogonal.
0195  *
0196  * Note that the test for orthogonality should use relative tolerance, not
0197  * absolute.
0198  */
0199 template<class T, std::size_t N>
0200 inline CELER_FUNCTION bool is_soft_orthogonal(Array<T, N> const& x,
0201                                               Array<T, N> const& y)
0202 {
0203     SoftZero const soft_zero{SoftEqual<T>{}.rel()};
0204     return soft_zero(dot_product(x, y));
0205 }
0206 
0207 //---------------------------------------------------------------------------//
0208 /*!
0209  * Check whether two vectors are approximately collinear.
0210  *
0211  * \pre Vectors must be normalized, i.e., have unit magnitude.
0212  */
0213 template<class T, std::size_t N>
0214 inline CELER_FUNCTION bool is_soft_collinear(Array<T, N> const& x,
0215                                              Array<T, N> const& y)
0216 {
0217     CELER_EXPECT(is_soft_unit_vector(x) && is_soft_unit_vector(y));
0218     SoftEqual<T> const soft_eq;
0219     return soft_eq(dot_product(x, y), 1);
0220 }
0221 
0222 //---------------------------------------------------------------------------//
0223 /*!
0224  * Calculate the Euclidean (2) distance between two points.
0225  */
0226 template<class T, std::size_t N>
0227 CELER_FUNCTION T distance(Array<T, N> const& x, Array<T, N> const& y)
0228 {
0229     T dist_sq = 0;
0230     for (size_type i = 0; i != N; ++i)
0231     {
0232         dist_sq += ipow<2>(y[i] - x[i]);
0233     }
0234     return std::sqrt(dist_sq);
0235 }
0236 
0237 //---------------------------------------------------------------------------//
0238 /*!
0239  * Calculate a Cartesian vector from spherical coordinates.
0240  *
0241  * Theta is the angle between the \em z axis and the outgoing vector, and \c
0242  * phi is the angle between the \em x axis and the projection of the vector
0243  * onto the \em x-y plane.
0244  */
0245 template<class T>
0246 inline CELER_FUNCTION Array<T, 3> from_spherical(T costheta, T phi)
0247 {
0248     CELER_EXPECT(costheta >= -1 && costheta <= 1);
0249 
0250     T const sintheta = std::sqrt(1 - costheta * costheta);
0251     return {sintheta * std::cos(phi), sintheta * std::sin(phi), costheta};
0252 }
0253 
0254 //---------------------------------------------------------------------------//
0255 /*!
0256  * Rotate a direction about the given scattering direction.
0257  *
0258  * The equivalent to calling the Shift transport code's \code
0259     void cartesian_vector_transform(
0260         double      costheta,
0261         double      phi,
0262         Vector_View vector);
0263  * \endcode
0264  * is the call
0265  * \code
0266     vector = rotate(from_spherical(costheta, phi), vector);
0267  * \endcode
0268  *
0269  * This code effectively decomposes the given rotation vector \c rot into two
0270  * sequential transform matrices, one with an angle \em theta about the \em y
0271  * axis and one about \em phi rotating around the \em z axis. These two angles
0272  * are the spherical coordinate transform of the given \c rot cartesian
0273  * direction vector.
0274  *
0275  * This function is often used for calculating exiting scattering angles. In
0276  * that case, \c dir is the exiting angle from the scattering calculation, and
0277  * \c rot is the original direction of the particle. The direction vectors are
0278  * defined as
0279  * \f[
0280    \vec{\Omega}
0281    =   \sin\theta\cos\phi\vec{i}
0282      + \sin\theta\sin\phi\vec{j}
0283      + \cos\theta\vec{k} \,.
0284  * \f]
0285  *
0286  * There is some extra code in here to deal with loss of precision when the
0287  * incident direction is along the \em z axis. As \c rot approaches \em z, the
0288  * azimuthal angle \f$ \phi \f$ must be calculated carefully from both the
0289  * \em x and \em y components of the vector, not independently.
0290  * If \c rot actually equals \em z
0291  * then the azimuthal angle is completely indeterminate so we arbitrarily
0292  * choose \f$ \phi = 0 \f$.
0293  */
0294 template<class T>
0295 inline CELER_FUNCTION Array<T, 3> rotate(Array<T, 3> const& dir,
0296                                          Array<T, 3> const& rot)
0297 {
0298     CELER_EXPECT(is_soft_unit_vector(dir));
0299     CELER_EXPECT(is_soft_unit_vector(rot));
0300 
0301     // Direction enumeration
0302     using TraitsT = detail::RealVecTraits<T>;
0303 
0304     constexpr int X = 0, Y = 1, Z = 2;
0305 
0306     // Transform direction vector into theta, phi so we can use it as a
0307     // rotation matrix
0308     T sintheta = std::sqrt(1 - ipow<2>(rot[Z]));
0309     T cosphi;
0310     T sinphi;
0311 
0312     if (sintheta >= TraitsT::min_accurate_sintheta)
0313     {
0314         // Typical case: far enough from z axis to assume the X and Y
0315         // components have a hypotenuse of 1 within epsilon tolerance
0316         T const inv_sintheta = 1 / sintheta;
0317         cosphi = rot[X] * inv_sintheta;
0318         sinphi = rot[Y] * inv_sintheta;
0319     }
0320     else if (rot[X] != 0 || rot[Y] != 0)
0321     {
0322         // Avoid catastrophic roundoff error by normalizing x/y components
0323         cosphi = rot[X] / hypot(rot[X], rot[Y]);
0324         sinphi = std::sqrt(1 - ipow<2>(cosphi));
0325     }
0326     else
0327     {
0328         // NaNs or coincident Z: force sin theta to zero and choose an
0329         // arbitrary azimuthal angle for the incident dir
0330         sintheta = 0;
0331         cosphi = 1;
0332         sinphi = 0;
0333     }
0334 
0335     Array<T, 3> result
0336         = {(rot[Z] * dir[X] + sintheta * dir[Z]) * cosphi - sinphi * dir[Y],
0337            (rot[Z] * dir[X] + sintheta * dir[Z]) * sinphi + cosphi * dir[Y],
0338            -sintheta * dir[X] + rot[Z] * dir[Z]};
0339 
0340     // Always normalize to prevent roundoff error from propagating
0341     return make_unit_vector(result);
0342 }
0343 
0344 //---------------------------------------------------------------------------//
0345 }  // namespace celeritas