#include <../../nrnconf.h>

/**************************************************************************
**
** Copyright (C) 1993 David E. Steward & Zbigniew Leyk, all rights reserved.
**
**			     Meschach Library
** 
** This Meschach Library is provided "as is" without any express 
** or implied warranty of any kind with respect to this software. 
** In particular the authors shall not be liable for any direct, 
** indirect, special, incidental or consequential damages arising 
** in any way from use of the software.
** 
** Everyone is granted permission to copy, modify and redistribute this
** Meschach Library, provided:
**  1.  All copies contain this copyright notice.
**  2.  All modified copies shall carry a notice stating who
**      made the last modification and the date of such modification.
**  3.  No charge is made for this software or works derived from it.  
**      This clause shall not be construed as constraining other software
**      distributed on the same medium as this software, nor is a
**      distribution fee considered a charge.
**
***************************************************************************/

/*
	Arnoldi method for finding eigenvalues of large non-symmetric
		matrices
*/
#include	<stdio.h>
#include	<math.h>
#include	"matrix.h"
#include	"matrix2.h"
#include	"sparse.h"

static char rcsid[] = "arnoldi.c,v 1.1 1997/12/04 17:55:13 hines Exp";

/* arnoldi -- an implementation of the Arnoldi method */
MAT	*arnoldi(A,A_param,x0,m,h_rem,Q,H)
VEC	*(*A)();
void	*A_param;
VEC	*x0;
int	m;
Real	*h_rem;
MAT	*Q, *H;
{
	static VEC	*v=VNULL, *u=VNULL, *r=VNULL, *s=VNULL, *tmp=VNULL;
	int	i;
	Real	h_val;

	if ( ! A || ! Q || ! x0 )
	    error(E_NULL,"arnoldi");
	if ( m <= 0 )
	    error(E_BOUNDS,"arnoldi");
	if ( Q->n != x0->dim ||	Q->m != m )
	    error(E_SIZES,"arnoldi");

	m_zero(Q);
	H = m_resize(H,m,m);
	m_zero(H);
	u = v_resize(u,x0->dim);
	v = v_resize(v,x0->dim);
	r = v_resize(r,m);
	s = v_resize(s,m);
	tmp = v_resize(tmp,x0->dim);
	MEM_STAT_REG(u,TYPE_VEC);
	MEM_STAT_REG(v,TYPE_VEC);
	MEM_STAT_REG(r,TYPE_VEC);
	MEM_STAT_REG(s,TYPE_VEC);
	MEM_STAT_REG(tmp,TYPE_VEC);
	sv_mlt(1.0/v_norm2(x0),x0,v);
	for ( i = 0; i < m; i++ )
	{
	    set_row(Q,i,v);
	    u = (*A)(A_param,v,u);
	    r = mv_mlt(Q,u,r);
	    tmp = vm_mlt(Q,r,tmp);
	    v_sub(u,tmp,u);
	    h_val = v_norm2(u);
	    /* if u == 0 then we have an exact subspace */
	    if ( h_val == 0.0 )
	    {
		*h_rem = h_val;
		return H;
	    }
	    /* iterative refinement -- ensures near orthogonality */
	    do {
		s = mv_mlt(Q,u,s);
		tmp = vm_mlt(Q,s,tmp);
		v_sub(u,tmp,u);
		v_add(r,s,r);
	    } while ( v_norm2(s) > 0.1*(h_val = v_norm2(u)) );
	    /* now that u is nearly orthogonal to Q, update H */
	    set_col(H,i,r);
	    if ( i == m-1 )
	    {
		*h_rem = h_val;
		continue;
	    }
	    /* H->me[i+1][i] = h_val; */
	    m_set_val(H,i+1,i,h_val);
	    sv_mlt(1.0/h_val,u,v);
	}

	return H;
}

/* sp_arnoldi -- uses arnoldi() with an explicit representation of A */
MAT	*sp_arnoldi(A,x0,m,h_rem,Q,H)
SPMAT	*A;
VEC	*x0;
int	m;
Real	*h_rem;
MAT	*Q, *H;
{	return arnoldi(sp_mv_mlt,A,x0,m,h_rem,Q,H);	}

/* gmres -- generalised minimum residual algorithm of Saad & Schultz
		SIAM J. Sci. Stat. Comp. v.7, pp.856--869 (1986)
	-- y is overwritten with the solution */
VEC	*gmres(A,A_param,m,Q,R,b,tol,x)
VEC	*(*A)();
void	*A_param;
VEC	*b, *x;
int	m;
MAT	*Q, *R;
double	tol;
{
    static VEC	*v=VNULL, *u=VNULL, *r=VNULL, *tmp=VNULL, *rhs=VNULL;
    static VEC	*diag=VNULL, *beta=VNULL;
    int	i;
    Real	h_val, norm_b;
    
    if ( ! A || ! Q || ! b || ! R )
	error(E_NULL,"gmres");
    if ( m <= 0 )
	error(E_BOUNDS,"gmres");
    if ( Q->n != b->dim || Q->m != m )
	error(E_SIZES,"gmres");
    
    x = v_copy(b,x);
    m_zero(Q);
    R = m_resize(R,m+1,m);
    m_zero(R);
    u = v_resize(u,x->dim);
    v = v_resize(v,x->dim);
    tmp = v_resize(tmp,x->dim);
    rhs = v_resize(rhs,m+1);
    MEM_STAT_REG(u,TYPE_VEC);
    MEM_STAT_REG(v,TYPE_VEC);
    MEM_STAT_REG(r,TYPE_VEC);
    MEM_STAT_REG(tmp,TYPE_VEC);
    MEM_STAT_REG(rhs,TYPE_VEC);
    norm_b = v_norm2(x);
    if ( norm_b == 0.0 )
	error(E_RANGE,"gmres");
    sv_mlt(1.0/norm_b,x,v);
    
    for ( i = 0; i < m; i++ )
    {
	set_row(Q,i,v);
	tracecatch(u = (*A)(A_param,v,u),"gmres");
	r = mv_mlt(Q,u,r);
	tmp = vm_mlt(Q,r,tmp);
	v_sub(u,tmp,u);
	h_val = v_norm2(u);
	set_col(R,i,r);
	R->me[i+1][i] = h_val;
	sv_mlt(1.0/h_val,u,v);
    }
    
    /* use i x i submatrix of R */
    R = m_resize(R,i+1,i);
    rhs = v_resize(rhs,i+1);
    v_zero(rhs);
    rhs->ve[0] = norm_b;
    tmp = v_resize(tmp,i);
    diag = v_resize(diag,i+1);
    beta = v_resize(beta,i+1);
    MEM_STAT_REG(beta,TYPE_VEC);
    MEM_STAT_REG(diag,TYPE_VEC);
    QRfactor(R,diag /* ,beta */);
    tmp = QRsolve(R,diag, /* beta, */ rhs,tmp);
    v_resize(tmp,m);
    vm_mlt(Q,tmp,x);

    return x;
}
