blob: 2a43961de5e0018f804f0328e2d90581e475b1c7 [file]
// 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