| // This file is part of Eigen, a lightweight C++ template library |
| // for linear algebra. |
| // |
| // Copyright (C) 2009 Gael Guennebaud <gael.guennebaud@inria.fr> |
| // |
| // This Source Code Form is subject to the terms of the Mozilla |
| // Public License v. 2.0. If a copy of the MPL was not distributed |
| // with this file, You can obtain one at http://mozilla.org/MPL/2.0/. |
| // SPDX-License-Identifier: MPL-2.0 |
| |
| #ifndef EIGEN_STABLENORM_H |
| #define EIGEN_STABLENORM_H |
| |
| // IWYU pragma: private |
| #include "./InternalHeaderCheck.h" |
| |
| namespace Eigen { |
| |
| namespace internal { |
| |
| template <typename Accumulator, bool = std::is_floating_point<Accumulator>::value> |
| struct stable_norm_unscaled_predicate { |
| static inline bool run(const Accumulator&, const Accumulator&) { return false; } |
| }; |
| |
| template <typename Accumulator> |
| struct stable_norm_unscaled_predicate<Accumulator, true> { |
| static inline bool run(const Accumulator& maxCoeff, const Accumulator& invScale) { |
| using std::sqrt; |
| // A block has at most 8192 real components. These bounds keep the error |
| // from flushed component squares below one epsilon. |
| static const Accumulator kSqrtMin = |
| sqrt((numext::numeric_limits<Accumulator>::min)() * (Accumulator(16384) / NumTraits<Accumulator>::epsilon())); |
| static const Accumulator kSqrtMax = sqrt(NumTraits<Accumulator>::highest() / Accumulator(16384)); |
| // A normal invScale avoids fast-math flushing invScale^2. |
| static const Accumulator kSqrtNormalMin = sqrt((numext::numeric_limits<Accumulator>::min)()); |
| return maxCoeff >= kSqrtMin && maxCoeff <= kSqrtMax && invScale >= kSqrtNormalMin; |
| } |
| }; |
| |
| template <typename ExpressionType, typename Accumulator> |
| inline Accumulator stable_norm_squared_norm(const ExpressionType& block, const Accumulator& invScale, |
| const Accumulator& maxCoeff) { |
| if (stable_norm_unscaled_predicate<Accumulator>::run(maxCoeff, invScale)) { |
| return block.realView().template cast<Accumulator>().squaredNorm() * numext::abs2(invScale); |
| } |
| return (block.realView().template cast<Accumulator>() * invScale).squaredNorm(); |
| } |
| |
| template <typename ExpressionType, typename Accumulator> |
| inline void stable_norm_kernel(const ExpressionType& block, Accumulator& ssq, Accumulator& scale, |
| Accumulator& invScale) { |
| // Component-wise maxima give the required scale without complex hypot calls. |
| Accumulator maxCoeff = block.realView().template cast<Accumulator>().cwiseAbs().template maxCoeff<PropagateNaN>(); |
| |
| if (maxCoeff > scale) { |
| if (maxCoeff > NumTraits<Accumulator>::highest()) // we got an INF |
| { |
| invScale = Accumulator(1); |
| scale = maxCoeff; |
| } else { |
| const auto factors = safe_scaling<Accumulator>::compute_ceiling_factors_with_normal_reciprocal(maxCoeff); |
| if (scale > Accumulator(0)) ssq = ssq * numext::abs2(scale * factors.invScale); |
| scale = factors.scale; |
| invScale = factors.invScale; |
| } |
| } else if (maxCoeff != maxCoeff) // we got a NaN |
| { |
| scale = maxCoeff; |
| } |
| |
| // TODO: skip sub-vector when maxCoeff << current scale. |
| if (scale > Accumulator(0)) // if scale==0, then block is 0 |
| ssq += stable_norm_squared_norm(block, invScale, maxCoeff); |
| } |
| |
| template <typename VectorType, typename Accumulator> |
| void stable_norm_impl_inner_step(const VectorType& vec, Accumulator& ssq, Accumulator& scale, Accumulator& invScale) { |
| const Index blockSize = 4096; |
| |
| Index n = vec.size(); |
| Index blockEnd = numext::round_down(n, blockSize); |
| for (Index i = 0; i < blockEnd; i += blockSize) { |
| internal::stable_norm_kernel(vec.template segment<blockSize>(i), ssq, scale, invScale); |
| } |
| if (n > blockEnd) { |
| internal::stable_norm_kernel(vec.tail(n - blockEnd), ssq, scale, invScale); |
| } |
| } |
| |
| template <typename VectorType, typename Accumulator, |
| bool = bool(traits<VectorType>::Flags & DirectAccessBit) && |
| (int(inner_stride_at_compile_time<VectorType>::value) != 1)> |
| struct stable_norm_vector_dispatch { |
| static inline void run(const VectorType& vec, Accumulator& ssq, Accumulator& scale, Accumulator& invScale) { |
| stable_norm_impl_inner_step(vec, ssq, scale, invScale); |
| } |
| }; |
| |
| template <typename VectorType, typename Accumulator> |
| struct stable_norm_vector_dispatch<VectorType, Accumulator, true> { |
| static inline void run(const VectorType& vec, Accumulator& ssq, Accumulator& scale, Accumulator& invScale) { |
| if (vec.innerStride() == 1) { |
| using Scalar = typename traits<VectorType>::Scalar; |
| using PlainVector = Matrix<Scalar, VectorType::SizeAtCompileTime, 1, 0, VectorType::MaxSizeAtCompileTime, 1>; |
| using ContiguousMap = Map<const PlainVector, evaluator<VectorType>::Alignment>; |
| const ContiguousMap contiguous(vec.data(), vec.size()); |
| stable_norm_impl_inner_step(contiguous, ssq, scale, invScale); |
| return; |
| } |
| stable_norm_impl_inner_step(vec, ssq, scale, invScale); |
| } |
| }; |
| |
| template <typename VectorType, typename Accumulator> |
| inline void stable_norm_impl_inner_dispatch(const VectorType& vec, Accumulator& ssq, Accumulator& scale, |
| Accumulator& invScale) { |
| stable_norm_vector_dispatch<VectorType, Accumulator>::run(vec, ssq, scale, invScale); |
| } |
| |
| template <typename MatrixType, typename Accumulator> |
| inline void stable_norm_impl_outer_steps(const MatrixType& mat, Accumulator& ssq, Accumulator& scale, |
| Accumulator& invScale) { |
| for (Index j = 0; j < mat.outerSize(); ++j) { |
| stable_norm_impl_inner_dispatch(mat.innerVector(j), ssq, scale, invScale); |
| } |
| } |
| |
| template <typename MatrixType, typename Accumulator, bool = bool(traits<MatrixType>::Flags & DirectAccessBit)> |
| struct stable_norm_matrix_dispatch { |
| static inline void run(const MatrixType& mat, Accumulator& ssq, Accumulator& scale, Accumulator& invScale) { |
| stable_norm_impl_outer_steps(mat, ssq, scale, invScale); |
| } |
| }; |
| |
| template <typename MatrixType, typename Accumulator> |
| struct stable_norm_matrix_dispatch<MatrixType, Accumulator, true> { |
| static inline void run(const MatrixType& mat, Accumulator& ssq, Accumulator& scale, Accumulator& invScale) { |
| if (mat.innerStride() == 1 && (mat.outerSize() == 1 || mat.outerStride() == mat.innerSize())) { |
| using Scalar = typename traits<MatrixType>::Scalar; |
| using PlainVector = Matrix<Scalar, MatrixType::SizeAtCompileTime, 1, 0, MatrixType::MaxSizeAtCompileTime, 1>; |
| using ContiguousMap = Map<const PlainVector, evaluator<MatrixType>::Alignment>; |
| const ContiguousMap contiguous(mat.data(), mat.size()); |
| stable_norm_impl_inner_step(contiguous, ssq, scale, invScale); |
| return; |
| } |
| stable_norm_impl_outer_steps(mat, ssq, scale, invScale); |
| } |
| }; |
| |
| template <typename VectorType, std::enable_if_t<VectorType::IsVectorAtCompileTime, int> = 0> |
| typename VectorType::RealScalar stable_norm_impl(const VectorType& vec) { |
| using std::sqrt; |
| |
| Index n = vec.size(); |
| if (EIGEN_PREDICT_FALSE(n == 1)) return numext::abs(vec.coeff(0)); |
| |
| using RealScalar = typename VectorType::RealScalar; |
| using Accumulator = typename stable_norm_accumulator<RealScalar>::type; |
| Accumulator scale(0); |
| Accumulator invScale(1); |
| Accumulator ssq(0); // sum of squares |
| |
| stable_norm_vector_dispatch<VectorType, Accumulator>::run(vec, ssq, scale, invScale); |
| |
| return RealScalar(scale * sqrt(ssq)); |
| } |
| |
| template <typename MatrixType, std::enable_if_t<!MatrixType::IsVectorAtCompileTime, int> = 0> |
| typename MatrixType::RealScalar stable_norm_impl(const MatrixType& mat) { |
| using std::sqrt; |
| |
| using RealScalar = typename MatrixType::RealScalar; |
| using Accumulator = typename stable_norm_accumulator<RealScalar>::type; |
| Accumulator scale(0); |
| Accumulator invScale(1); |
| Accumulator ssq(0); // sum of squares |
| |
| stable_norm_matrix_dispatch<MatrixType, Accumulator>::run(mat, ssq, scale, invScale); |
| return RealScalar(scale * sqrt(ssq)); |
| } |
| |
| inline int stable_norm_floor_div2(int value) { return value / 2 - ((value < 0 && value % 2 != 0) ? 1 : 0); } |
| |
| inline int stable_norm_ceil_div2(int value) { return value / 2 + ((value > 0 && value % 2 != 0) ? 1 : 0); } |
| |
| template <typename Accumulator> |
| inline void blue_norm_accumulate_component(const Accumulator& ax, const Accumulator& tsml, const Accumulator& tbig, |
| const Accumulator& ssml, const Accumulator& sbig, bool& notBig, |
| Accumulator& asml, Accumulator& amed, Accumulator& abig) { |
| if (ax > tbig) { |
| abig += numext::abs2(ax * sbig); |
| notBig = false; |
| } else if (ax < tsml) { |
| if (notBig) asml += numext::abs2(ax * ssml); |
| } else { |
| amed += numext::abs2(ax); |
| } |
| } |
| |
| template <typename Scalar, typename Accumulator, bool = NumTraits<Scalar>::IsComplex> |
| struct blue_norm_accumulate_scalar { |
| static inline void run(const Scalar& value, const Accumulator& tsml, const Accumulator& tbig, const Accumulator& ssml, |
| const Accumulator& sbig, bool& notBig, Accumulator& asml, Accumulator& amed, |
| Accumulator& abig) { |
| const Accumulator ax = numext::abs(Accumulator(value)); |
| blue_norm_accumulate_component(ax, tsml, tbig, ssml, sbig, notBig, asml, amed, abig); |
| } |
| }; |
| |
| template <typename Scalar, typename Accumulator> |
| struct blue_norm_accumulate_scalar<Scalar, Accumulator, true> { |
| static inline void run(const Scalar& value, const Accumulator& tsml, const Accumulator& tbig, const Accumulator& ssml, |
| const Accumulator& sbig, bool& notBig, Accumulator& asml, Accumulator& amed, |
| Accumulator& abig) { |
| const Accumulator real = numext::abs(Accumulator(numext::real(value))); |
| const Accumulator imag = numext::abs(Accumulator(numext::imag(value))); |
| blue_norm_accumulate_component(real, tsml, tbig, ssml, sbig, notBig, asml, amed, abig); |
| blue_norm_accumulate_component(imag, tsml, tbig, ssml, sbig, notBig, asml, amed, abig); |
| } |
| }; |
| |
| template <typename Derived> |
| inline typename NumTraits<typename traits<Derived>::Scalar>::Real blueNorm_impl(const EigenBase<Derived>& _vec) { |
| using RealScalar = typename Derived::RealScalar; |
| using Accumulator = typename stable_norm_accumulator<RealScalar>::type; |
| using Scalar = typename traits<Derived>::Scalar; |
| using std::pow; |
| using std::sqrt; |
| |
| const Derived& vec(_vec.derived()); |
| if (vec.size() == 0) return RealScalar(0); |
| |
| // Blue, ACM TOMS 4(1), 1978, https://doi.org/10.1145/355769.355771. |
| // The small-value multiplier includes Anderson's denormal correction from |
| // Algorithm 978, ACM TOMS 44(1), 2017, https://doi.org/10.1145/3061665. |
| // These thresholds and the three-accumulator merge follow Reference BLAS |
| // xNRM2 (LAPACK 3.12.1), expressed independently for Eigen scalar types. |
| static const int ibeta = std::numeric_limits<Accumulator>::radix; |
| static const int it = NumTraits<Accumulator>::digits(); |
| static const int iemin = NumTraits<Accumulator>::min_exponent(); |
| static const int iemax = NumTraits<Accumulator>::max_exponent(); |
| static const Accumulator tsml = Accumulator(pow(Accumulator(ibeta), Accumulator(stable_norm_ceil_div2(iemin - 1)))); |
| static const Accumulator tbig = |
| Accumulator(pow(Accumulator(ibeta), Accumulator(stable_norm_floor_div2(iemax - it + 1)))); |
| static const Accumulator ssml = |
| Accumulator(pow(Accumulator(ibeta), Accumulator(-stable_norm_floor_div2(iemin - it)))); |
| static const Accumulator sbig = |
| Accumulator(pow(Accumulator(ibeta), Accumulator(-stable_norm_ceil_div2(iemax + it - 1)))); |
| |
| bool notBig = true; |
| Accumulator asml(0); |
| Accumulator amed(0); |
| Accumulator abig(0); |
| |
| for (Index j = 0; j < vec.outerSize(); ++j) { |
| for (typename Derived::InnerIterator iter(vec, j); iter; ++iter) { |
| blue_norm_accumulate_scalar<Scalar, Accumulator>::run(iter.value(), tsml, tbig, ssml, sbig, notBig, asml, amed, |
| abig); |
| } |
| } |
| |
| Accumulator scale(1); |
| Accumulator sumsq(0); |
| if (abig > Accumulator(0)) { |
| if (amed > Accumulator(0) || amed > NumTraits<Accumulator>::highest() || amed != amed) abig += (amed * sbig) * sbig; |
| scale = Accumulator(1) / sbig; |
| sumsq = abig; |
| } else if (asml > Accumulator(0)) { |
| if (amed > Accumulator(0) || amed > NumTraits<Accumulator>::highest() || amed != amed) { |
| amed = sqrt(amed); |
| asml = sqrt(asml) / ssml; |
| // Spell this as in xNRM2 rather than with min/max: when amed is NaN, |
| // it must become ymax so that the final result remains NaN. |
| const bool smallIsLarger = asml > amed; |
| const Accumulator ymin = smallIsLarger ? amed : asml; |
| const Accumulator ymax = smallIsLarger ? asml : amed; |
| sumsq = numext::abs2(ymax) * (Accumulator(1) + numext::abs2(ymin / ymax)); |
| } else { |
| scale = Accumulator(1) / ssml; |
| sumsq = asml; |
| } |
| } else { |
| sumsq = amed; |
| } |
| return RealScalar(scale * sqrt(sumsq)); |
| } |
| |
| } // end namespace internal |
| |
| /** \returns the \em l2 norm of \c *this avoiding underflow and overflow. |
| * This version uses a blockwise two-pass algorithm: |
| * 1 - find the absolute largest coefficient and choose a scale \c s (a nearby normal power of two for supported |
| * binary floating-point accumulators) |
| * 2 - compute \f$ s \Vert \frac{*this}{s} \Vert \f$ in a standard way |
| * |
| * For architecture/scalar types supporting vectorization, this version |
| * is faster than blueNorm(). Otherwise the blueNorm() is much faster. |
| * |
| * \sa norm(), blueNorm(), hypotNorm() |
| */ |
| template <typename Derived> |
| inline typename NumTraits<typename internal::traits<Derived>::Scalar>::Real MatrixBase<Derived>::stableNorm() const { |
| using Nested = typename internal::nested_eval<Derived, 2>::type; |
| Nested nested(derived()); |
| return internal::stable_norm_impl(nested); |
| } |
| |
| /** \returns the \em l2 norm of \c *this using Blue's algorithm, with |
| * Anderson's Algorithm 978 correction for denormalized values. |
| * Blue, A Portable Fortran Program to Find the Euclidean Norm of a Vector, |
| * ACM TOMS, Vol 4, Issue 1, 1978; Anderson, ACM TOMS, Vol 44, Issue 1, 2017. |
| * |
| * For architecture/scalar types without vectorization, this version |
| * is much faster than stableNorm(). Otherwise the stableNorm() is faster. |
| * |
| * \sa norm(), stableNorm(), hypotNorm() |
| */ |
| template <typename Derived> |
| inline typename NumTraits<typename internal::traits<Derived>::Scalar>::Real MatrixBase<Derived>::blueNorm() const { |
| return internal::blueNorm_impl(*this); |
| } |
| |
| /** \returns the \em l2 norm of \c *this avoiding underflow and overflow. |
| * This version use a concatenation of hypot() calls, and it is very slow. |
| * |
| * \sa norm(), stableNorm() |
| */ |
| template <typename Derived> |
| inline typename NumTraits<typename internal::traits<Derived>::Scalar>::Real MatrixBase<Derived>::hypotNorm() const { |
| using Accumulator = typename internal::stable_norm_accumulator<RealScalar>::type; |
| if (size() == 0) return RealScalar(0); |
| // Component reduction avoids rounded complex magnitudes and permits promoted accumulation. |
| return RealScalar( |
| derived().realView().template cast<Accumulator>().cwiseAbs().redux(internal::scalar_hypot_op<Accumulator>())); |
| } |
| |
| } // end namespace Eigen |
| |
| #endif // EIGEN_STABLENORM_H |