/**
 *
 * @file frobeniusupdate.h
 *
 * Formula to update frobenius norm computation in a safe manner.
 *
 * @copyright 2004-2024 Bordeaux INP, CNRS (LaBRI UMR 5800), Inria,
 *                      Univ. Bordeaux. All rights reserved.
 *
 * @version 1.2.4
 * @author Mathieu Faverge
 * @author Tony Delarue
 * @date 2024-05-29
 *
 */
#ifndef _frobeniusupdate_h_
#define _frobeniusupdate_h_

#include <math.h>

/**
 *******************************************************************************
 *
 * @ingroup spm_internal
 *
 * @brief Update the couple (scale, sumsq) with one element when computing the
 * Froebnius norm.
 *
 * The frobenius norm is equal to scale * sqrt( sumsq ), this method allows to
 * avoid overflow in the sum square computation.
 *
 *******************************************************************************
 *
 * @param[in] nb
 *          The number of time the value must be integrated into the couple
 *          (scale, sumsq)
 *
 * @param[inout] scale
 *           On entry, the former scale
 *           On exit, the update scale to take into account the value
 *
 * @param[inout] sumsq
 *           On entry, the former sumsq
 *           On exit, the update sumsq to take into account the value
 *
 * @param[in] value
 *          The value to integrate into the couple (scale, sumsq)
 *
 *******************************************************************************/
static inline void
#if defined(PRECISION_d) || defined(PRECISION_z)
frobenius_update( int nb, double *scale, double *sumsq, const double *value )
{
    double absval = fabs(*value);
    double ratio;
    if ( absval != 0. ){
        if ( (*scale) < absval ) {
            ratio = (*scale) / absval;
            *sumsq = (double)nb + (*sumsq) * ratio * ratio;
            *scale = absval;
        } else {
            ratio = absval / (*scale);
            *sumsq = (*sumsq) + (double)nb * ratio * ratio;
        }
    }
}
#elif defined(PRECISION_s) || defined(PRECISION_c)
frobenius_update( int nb, float *scale, float *sumsq, const float *value )
{
    float absval = fabs(*value);
    float ratio;
    if ( absval != 0. ){
        if ( (*scale) < absval ) {
            ratio = (*scale) / absval;
            *sumsq = (float)nb + (*sumsq) * ratio * ratio;
            *scale = absval;
        } else {
            ratio = absval / (*scale);
            *sumsq = (*sumsq) + (float)nb * ratio * ratio;
        }
    }
}
#endif

/**
 *******************************************************************************
 *
 * @ingroup spm_internal
 *
 * @brief Merge together two sum square stored as a couple (scale, sumsq).
 *
 * The frobenius norm is equal to scale * sqrt( sumsq ), this method allows to
 * avoid overflow in the sum square computation.
 *
 *******************************************************************************
 *
 * @param[in] scl_in
 *           The scale factor of the first couple to merge
 *
 * @param[in] ssq_in
 *           The sumsquare factor of the first couple to merge
 *
 * @param[inout] scl_out
 *           On entry, the scale factor of the second couple to merge
 *           On exit, the updated scale factor.
 *
 * @param[inout] ssq_out
 *           The sumsquare factor of the second couple to merge
 *           On exit, the updated sumsquare factor.
 *
 *******************************************************************************/
static inline void
#if defined(PRECISION_d) || defined(PRECISION_z)
frobenius_merge( double  scl_in,  double  ssq_in,
                 double *scl_out, double *ssq_out )
{
    double ratio;
    if ( (*scl_out) < scl_in ) {
        ratio  = (*scl_out) / scl_in;
        *ssq_out = (*ssq_out) * ratio * ratio + ssq_in;
        *scl_out = scl_in;
    }
    else {
        if ( *scl_out != 0. ) {
            ratio  = scl_in / (*scl_out);
            *ssq_out = (*ssq_out) + ssq_in * ratio * ratio;
        }
    }
}
#else
frobenius_merge( float  scl_in,  float ssq_in,
                 float *scl_out, float *ssq_out )
{
    float ratio;
    if ( (*scl_out) < scl_in ) {
        ratio  = (*scl_out) / scl_in;
        *ssq_out = (*ssq_out) * ratio * ratio + ssq_in;
        *scl_out = scl_in;
    }
    else {
        if ( *scl_out != 0. ) {
            ratio  = scl_in / (*scl_out);
            *ssq_out = (*ssq_out) + ssq_in * ratio * ratio;
        }
    }
}
#endif

#endif /* _frobeniusupdate_h_ */
