/*
    -- micMAGMA (version 0.3) --
       Univ. of Tennessee, Knoxville
       Univ. of California, Berkeley
       Univ. of Colorado, Denver
       April 2012

       @generated ds Fri Nov 22 14:21:13 2013

*/
#include <stdio.h>
#include "common_magma.h"

#include "../testing/flops.h"

#define PRECISION_d
#if defined(PRECISION_z) || defined(PRECISION_c)
#define FLOPS(m, n) ( 6. * FMULS_GETRF(m, n) + 2. * FADDS_GETRF(m, n) )
#else
#define FLOPS(m, n) (      FMULS_GETRF(m, n) +      FADDS_GETRF(m, n) )
#endif


#define BWDMAX 1.0
#define ITERMAX 30

magma_err_t
magma_dsgesv2(char trans, magma_int_t N, magma_int_t NRHS,
	      double *A, magma_int_t lda,
	      magmaFloat_ptr dA, size_t dA_offset, magma_int_t ldda, 
	      magma_int_t *IPIV,  magma_int_t *dIPIV,
	      double *B, magma_int_t ldb, 
	      double *X, magma_int_t ldx, 
	      double *workd, float *works,
	      magma_int_t *iter, magma_int_t *info,
	      magma_queue_t queue )
{
/*  -- MAGMA (version 1.3.0) --
       Univ. of Tennessee, Knoxville
       Univ. of California, Berkeley
       Univ. of Colorado, Denver
       November 2012

    Purpose
    =======
    DSGESV computes the solution to a real system of linear equations
       A * X = B or A' * X = B
    where A is an N-by-N matrix and X and B are N-by-NRHS matrices.

    DSGESV first attempts to factorize the matrix in SINGLE PRECISION
    and use this factorization within an iterative refinement procedure
    to produce a solution with DOUBLE PRECISION norm-wise backward error
    quality (see below). If the approach fails the method switches to a
    DOUBLE PRECISION factorization and solve.

    The iterative refinement is not going to be a winning strategy if
    the ratio SINGLE PRECISION performance over DOUBLE PRECISION
    performance is too small. A reasonable strategy should take the
    number of right-hand sides and the size of the matrix into account.
    This might be done with a call to ILAENV in the future. Up to now, we
    always try iterative refinement.
    The iterative refinement process is stopped if
        ITER > ITERMAX
    or for all the RHS we have:
        RNRM < SQRT(N)*XNRM*ANRM*EPS*BWDMAX
    where
        o ITER is the number of the current iteration in the iterative
          refinement process
        o RNRM is the infinity-norm of the residual
        o XNRM is the infinity-norm of the solution
        o ANRM is the infinity-operator-norm of the matrix A
        o EPS is the machine epsilon returned by DLAMCH('Epsilon')
    The value ITERMAX and BWDMAX are fixed to 30 and 1.0D+00 respectively.

    Arguments
    =========
    TRANS   (input) CHARACTER*1
            Specifies the form of the system of equations:
            = 'N':  A * X = B  (No transpose)
            = 'T':  A'* X = B  (Transpose)
            = 'C':  A'* X = B  (ugate transpose = Transpose)

    N       (input) INTEGER
            The number of linear equations, i.e., the order of the
            matrix A.  N >= 0.

    NRHS    (input) INTEGER
            The number of right hand sides, i.e., the number of columns
            of the matrix B.  NRHS >= 0.

    dA      (input or input/output) DOUBLE PRECISION array, dimension (ldda,N)
            On entry, the N-by-N coefficient matrix A.
            On exit, if iterative refinement has been successfully used
            (info.EQ.0 and ITER.GE.0, see description below), A is
            unchanged. If double precision factorization has been used
            (info.EQ.0 and ITER.LT.0, see description below), then the
            array A contains the factors L and U from the factorization
            A = P*L*U; the unit diagonal elements of L are not stored.

    ldda    (input) INTEGER
            The leading dimension of the array A.  ldda >= max(1,N).

    IPIV    (output) INTEGER array, dimension (N)
            The pivot indices that define the permutation matrix P;
            row i of the matrix was interchanged with row IPIV(i).
            Corresponds either to the single precision factorization
            (if info.EQ.0 and ITER.GE.0) or the double precision
            factorization (if info.EQ.0 and ITER.LT.0).

    dIPIV   (output) INTEGER array on the GPU, dimension (min(M,N))
            The pivot indices; for 1 <= i <= min(M,N), row i of the
            matrix was moved to row IPIV(i).

    B       (input) DOUBLE PRECISION array, dimension (ldb,NRHS)
            The N-by-NRHS right hand side matrix B.

    ldb     (input) INTEGER
            The leading dimension of the array B.  ldb >= max(1,N).

    dX      (output) DOUBLE PRECISION array, dimension (ldx,NRHS)
            If info = 0, the N-by-NRHS solution matrix X.

    ldx     (input) INTEGER
            The leading dimension of the array X.  ldx >= max(1,N).

    dworkd  (workspace) DOUBLE PRECISION array, dimension (N*NRHS)
            This array is used to hold the residual vectors.

    dworks  (workspace) SINGLE PRECISION array, dimension (N*(N+NRHS))
            This array is used to store the single precision matrix and the
            right-hand sides or solutions in single precision.

    iter    (output) INTEGER
            < 0: iterative refinement has failed, double precision
                 factorization has been performed
                 -1 : the routine fell back to full precision for
                      implementation- or machine-specific reasons
                 -2 : narrowing the precision induced an overflow,
                      the routine fell back to full precision
                 -3 : failure of SGETRF
                 -31: stop the iterative refinement after the 30th
                      iterations
            > 0: iterative refinement has been successfully used.
                 Returns the number of iterations
 
    info   (output) INTEGER
            = 0:  successful exit
            < 0:  if info = -i, the i-th argument had an illegal value
            > 0:  if info = i, U(i,i) computed in DOUBLE PRECISION is
                  exactly zero.  The factorization has been completed,
                  but the factor U is exactly singular, so the solution
                  could not be computed.
    =====================================================================    */

    double c_neg_one = MAGMA_D_NEG_ONE;
    double c_one     = MAGMA_D_ONE;
    magma_int_t     ione  = 1;
    double          cte, eps;
    double Xnrmv, Rnrmv;
    float *SA, *SX;
    double          Anrm, Xnrm, Rnrm;
    magma_int_t     i, j, iiter;
    
    real_Double_t time;

    /*
      Check The Parameters. 
    */
    *info = *iter = 0 ;
    if ( N <0)
        *info = -1;
    else if(NRHS<0)
        *info =-2;
    else if(ldda < max(1,N))
        *info =-4;
    else if( ldb < max(1,N))
        *info =-8;
    else if( ldx < max(1,N))
        *info =-10;
    
    if (*info != 0) {
        magma_xerbla( __func__, -(*info) );
        return *info;
    }
    
    if( N == 0 || NRHS == 0 )
        return *info;

    if(*info !=0){
      *iter = -2 ;
      printf("dlag2s\n");
      goto L40;
    }
    
    // Cast the matrix to single precision
    SX = works;
    SA = works+N*NRHS;
    lapackf77_dlag2s(&N, &N, A, &lda, SA, &N, info );
    
    if(*info !=0){
      *iter = -2 ;
      printf("dlag2s\n");
      goto L40;
    }

    eps  = lapackf77_dlamch("Epsilon");
    Anrm = lapackf77_slange("I", &N, &N, SA, &N, (float*)works );
    cte  = Anrm * eps * pow((double)N,0.5) * BWDMAX;
    
    magma_kernel_sync();
    time = magma_wtime();
    magma_sgetrf2(N, N, SA, N, IPIV, dA + dA_offset, ldda, dIPIV, info, queue);
    time = magma_wtime() - time;                                                                                           
                                                                                                                            
    double gflops, perf;                                                                                   
    gflops = FLOPS( (double)N, (double)N ) / 1e9;                                                                    
    perf = gflops / time;                                                                                        
    printf("\nPerformance = %f GFlop/s\n", perf);

    if(info[0] !=0){
        *iter = -3 ;
        goto L40;
    }
  
    // Convert B from double precision to single precision and store it in SX.
    lapackf77_dlag2s( &N, &NRHS, B, &ldb, SX, &N, info );

    // Solve the system SA*SX = SB.
    lapackf77_sgetrs("No Transpose", &N, &NRHS, SA, &N, IPIV, SX, &N, info);
    
    // Convert SX back to double precision
    lapackf77_slag2d( &N, &NRHS, SX, &N, X, &ldx, info );

    // Compute R = B - AX (R is WORK)
    lapackf77_dlacpy( "All", &N, &NRHS, B, &ldb, workd, &N );
    blasf77_dgemm( "No Transpose", "No Transpose", &N, &NRHS, &N, &c_neg_one, 
		   A, &lda, X, &ldx, &c_one, workd, &N );
    
    for(i=0;i<NRHS;i++)
    {
        j = blasf77_idamax( &N, X+i*ldx, &ione) ;
        Xnrm = lapackf77_dlange( "F", &ione, &ione, X+i*ldx+j-1, &ione, NULL );
        
        j = blasf77_idamax(&N, workd+i*N, &ione );
        Rnrm = lapackf77_dlange( "F", &ione, &ione, workd+i*N+j-1, &ione, NULL );
        
        if( Rnrm >  (Xnrm*cte) ){
            goto L10;
        }
    }
    
    *iter = 0;
    return *info;
  L10:
    ;
    
    for(iiter=1;iiter<ITERMAX;)
    {
        *info = 0 ;
        
        //  Convert R (in dworkd) from double precision to single precision
        //  and store the result in SX.
	lapackf77_dlag2s( &N, &NRHS, workd, &N, SX, &N, info );

        if(info[0] != 0){
            *iter = -3 ;
            goto L40;
        }
	// Solve the system SA*SX = SR.
	lapackf77_sgetrs("No Transpose", &N, &NRHS, SA, &N, IPIV, SX, &N, info);

        // Add the correction (workd) to dX and make workd = dB.
        //   This saves going through workd a second time (if done with one more kernel). 
	lapackf77_slag2d( &N, &NRHS, SX, &N, workd, &N, info );
        for(i=0;i<NRHS;i++){
	  blasf77_daxpy(&N, &c_one, workd+i*N, &ione, X+i*ldx, &ione);
        }
        
        // Compute R = B - AX (R is WORK)
        lapackf77_dlacpy( "All", &N, &NRHS, B, &ldb, workd, &N );
	blasf77_dgemm( "No Transpose", "No Transpose", &N, &NRHS, &N, &c_neg_one,
		       A, &lda, X, &ldx, &c_one, workd, &N );        
        
        //  Check whether the NRHS normwise backward errors satisfy the
        //  stopping criterion. If yes, set ITER=IITER>0 and return.
	for(i=0;i<NRHS;i++)
        {
            j = blasf77_idamax( &N, X+i*ldx, &ione) ;
            Xnrm = lapackf77_dlange( "F", &ione, &ione, X+i*ldx+j-1, &ione, NULL );
            
            j = blasf77_idamax( &N, workd+i*N, &ione );
            Rnrm = lapackf77_dlange( "F", &ione, &ione, workd+i*N+j-1, &ione, NULL );
            
            if( Rnrm >  Xnrm *cte ){
                goto L20;
            }
        }
        
        //  If we are here, the NRHS normwise backward errors satisfy the
	//   stopping criterion, we are good to exit.
        
        *iter = iiter ;
        return *info;
      L20:
        iiter++ ;
    }
    
    //  If we are at this place of the code, this is because we have
    //  performed ITER=ITERMAX iterations and never satisified the
    //  stopping criterion, set up the ITER flag accordingly and follow up
    //  on double precision routine.
    *iter = -ITERMAX - 1 ;
    
  L40:
    
    //  Single-precision iterative refinement failed to converge to a
    //  satisfactory solution, so we resort to double precision.  
    if( *info != 0 ){
        return *info;
    }
    
    lapackf77_dgetrf( &N, &N, A, &lda, IPIV, info );
    if( *info == 0 )
      {
	lapackf77_dlacpy( "All", &N, &NRHS, B, &ldb, X, &ldx );
	lapackf77_dgetrs( "No transpose", &N, &NRHS, A, &lda, IPIV, X, &ldx, info );
      }

    return *info;
}

