/* Copyright (C) 2005 The Scalable Software Infrastructure Project. All rights reserved.

   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are met:
   1. Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.
   2. Redistributions in binary form must reproduce the above copyright
      notice, this list of conditions and the following disclaimer in the
      documentation and/or other materials provided with the distribution.
   3. Neither the name of the project nor the names of its contributors 
      may be used to endorse or promote products derived from this software 
      without specific prior written permission.

   THIS SOFTWARE IS PROVIDED BY THE SCALABLE SOFTWARE INFRASTRUCTURE PROJECT
   ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
   TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
   PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE SCALABLE SOFTWARE INFRASTRUCTURE
   PROJECT BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
   OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
   SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
   INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
   CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
   ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
   POSSIBILITY OF SUCH DAMAGE.
*/

#ifdef HAVE_CONFIG_H
	#include "lis_config.h"
#else
#ifdef HAVE_CONFIG_WIN_H
	#include "lis_config_win.h"
#endif
#endif

#include <stdio.h>
#include <stdlib.h>
#ifdef HAVE_MALLOC_H
        #include <malloc.h>
#endif
#include <string.h>
#include <stdarg.h>
#include <math.h>
#include <ctype.h>
#ifdef _OPENMP
	#include <omp.h>
#endif
#ifdef USE_MPI
	#include <mpi.h>
#endif
#include "lislib.h"

/************************************************
 * lis_esolver_init
 * lis_esolver_create
 * lis_esolver_destroy
 * lis_iesolver_destroy
 * lis_esolver_work_destroy
 * lis_esolver_set_option
 * lis_esolver_get_option
 * lis_esolve
 ************************************************/

LIS_ESOLVER_EXECUTE lis_esolver_execute[] = {
  NULL,
  lis_epi, lis_eii, lis_eaii, lis_erqi, lis_ecg, lis_ecr, lis_ejd, lis_esi, lis_eli, lis_eai
};

#ifdef USE_QUAD_PRECISION
LIS_ESOLVER_EXECUTE lis_esolver_execute_quad[] = {
  NULL,
  lis_epi_quad, lis_eii_quad, NULL, lis_erqi_quad, NULL, NULL, NULL, lis_esi_quad, lis_eli_quad, lis_eai_quad
};
LIS_ESOLVER_EXECUTE lis_esolver_execute_switch[] = {
  NULL,
  NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL
};
#endif

LIS_ESOLVER_CHECK_PARAMS lis_esolver_check_params[] = {
  NULL,
  lis_epi_check_params,  lis_eii_check_params, lis_eaii_check_params, 
  lis_erqi_check_params, lis_ecg_check_params, lis_ecr_check_params,
  lis_ejd_check_params,  lis_esi_check_params,  lis_eli_check_params
};

LIS_ESOLVER_MALLOC_WORK lis_esolver_malloc_work[] = {
  NULL,
  lis_epi_malloc_work,  lis_eii_malloc_work, lis_eaii_malloc_work, 
  lis_erqi_malloc_work, lis_ecg_malloc_work, lis_ecr_malloc_work, 
  lis_ejd_malloc_work,  lis_esi_malloc_work,  lis_eli_malloc_work,
  lis_eai_malloc_work
};

#define LIS_ESOLVER_OPTION_LEN		12
#define LIS_EPRINT_LEN			 4
#define LIS_TRUEFALSE_LEN		 2
#define LIS_ESTORAGE_LEN		11
#define LIS_PRECISION_LEN		 3

char *LIS_ESOLVER_OPTNAME[] = {
  "-emaxiter", "-etol", "-e", "-ss", "-m", "-shift", "-eprint", "-initx_ones", "-ie", "-estorage", "-estorage_block", "-ef"
};

LIS_INT LIS_ESOLVER_OPTACT[] = {
LIS_EOPTIONS_MAXITER, LIS_EPARAMS_RESID, LIS_EOPTIONS_ESOLVER, 
LIS_EOPTIONS_SUBSPACE, LIS_EOPTIONS_MODE, LIS_EPARAMS_SHIFT, 
LIS_EOPTIONS_OUTPUT, LIS_EOPTIONS_INITGUESS_ONES, 
LIS_EOPTIONS_INNER_ESOLVER, LIS_EOPTIONS_STORAGE, 
LIS_EOPTIONS_STORAGE_BLOCK, LIS_EOPTIONS_PRECISION
};

char *lis_esolver_atoi[] = {"pi", "ii", "aii", "rqi", "cg", "cr", "jd", "si", "li", "ai"};
char *lis_eprint_atoi[] = {"none", "mem", "out", "all"};
char *lis_etruefalse_atoi[] = {"false", "true"};
char *lis_estorage_atoi[] = {"csr", "csc", "msr", "dia", "ell", "jad", "bsr", "bsc", "vbr", "coo", "dns"};
char *lis_eprecision_atoi[] = {"double", "quad", "switch"};

char *lis_esolvername[] = {"", "Power", "Inverse", "Approximate Inverse", "Rayleigh Quotient", "CG", "CR", "JD", "Subspace", "Lanczos", "Arnoldi"};

char *lis_estoragename[]   = {"CSR", "CSC", "MSR", "DIA", "ELL", "JAD", "BSR", "BSC", "VBR", "COO", "DNS"};

char *lis_ereturncode[] = {"LIS_SUCCESS", "LIS_ILL_OPTION", "LIS_BREAKDOWN", "LIS_OUT_OF_MEMORY", "LIS_MAXITER", "LIS_NOT_IMPLEMENTED", "LIS_ERR_FILE_IO"};

char *lis_eprecisionname[] = {"double", "quad", "switch"};

LIS_VECTOR lis_esolver_evalue = NULL;
LIS_INT lis_esolver_evalue_count = 0;

LIS_VECTOR lis_esolver_evector = NULL;
LIS_INT lis_esolver_evector_count = 0;

LIS_VECTOR lis_esolver_residual_history = NULL;
LIS_INT lis_esolver_residual_history_count = 0;

#undef __FUNC__
#define __FUNC__ "lis_esolver_init"
LIS_INT lis_esolver_init(LIS_ESOLVER esolver)
{

	LIS_DEBUG_FUNC_IN;

	esolver->A          = NULL;
	esolver->x          = NULL;
	esolver->evalue     = NULL;
	esolver->evector    = NULL;
	esolver->resid      = NULL;
	esolver->rhistory   = NULL;
	esolver->iter       = NULL;
	esolver->iter2      = NULL;

	esolver->worklen    = 0;
	esolver->time       = 0;
	esolver->itime      = 0;
	esolver->ptime      = 0;
	esolver->p_i_time   = 0;
	esolver->p_c_time   = 0;

	esolver->lshift     = 0;
	esolver->tol        = 0;
	esolver->eprecision = LIS_PRECISION_DOUBLE;

	esolver->options[LIS_EOPTIONS_ESOLVER]                = LIS_ESOLVER_CR;
	esolver->options[LIS_EOPTIONS_MAXITER]                = 1000;
	esolver->options[LIS_EOPTIONS_SUBSPACE]               = 1;
	esolver->options[LIS_EOPTIONS_MODE]                   = 0;
	esolver->options[LIS_EOPTIONS_OUTPUT]                 = LIS_FALSE;
	esolver->options[LIS_EOPTIONS_INITGUESS_ONES]         = LIS_TRUE;
	esolver->options[LIS_EOPTIONS_INNER_ESOLVER]          = 2;
	esolver->options[LIS_EOPTIONS_STORAGE]                = 0;
	esolver->options[LIS_EOPTIONS_STORAGE_BLOCK]          = 2;
	esolver->options[LIS_EOPTIONS_PRECISION]              = LIS_PRECISION_DOUBLE;
	esolver->params[LIS_EPARAMS_RESID - LIS_EOPTIONS_LEN] = 1.0e-12;
	esolver->params[LIS_EPARAMS_SHIFT - LIS_EOPTIONS_LEN] = 0.0;

	LIS_DEBUG_FUNC_OUT;
	return LIS_SUCCESS;
}

#undef __FUNC__
#define __FUNC__ "lis_esolver_create"
LIS_INT lis_esolver_create(LIS_ESOLVER *esolver)
{
	LIS_DEBUG_FUNC_IN;

	*esolver = NULL;

	*esolver = (LIS_ESOLVER)lis_malloc( sizeof(struct LIS_ESOLVER_STRUCT),"lis_esolver_create::esolver" );
	if( NULL==*esolver )
	{
		LIS_SETERR_MEM(sizeof(struct LIS_ESOLVER_STRUCT));
		return LIS_OUT_OF_MEMORY;
	}
	lis_esolver_init(*esolver);

	LIS_DEBUG_FUNC_OUT;
	return LIS_SUCCESS;
}

#undef __FUNC__
#define __FUNC__ "lis_esolver_work_destroy"
LIS_INT lis_esolver_work_destroy(LIS_ESOLVER esolver)
{
	LIS_INT i;

	LIS_DEBUG_FUNC_IN;

	if( esolver && esolver->work )
	{
		for(i=0;i<esolver->worklen;i++) lis_vector_destroy(esolver->work[i]);
		lis_free(esolver->work);
		esolver->work    = NULL;
		esolver->worklen = 0;
	}

	LIS_DEBUG_FUNC_OUT;
	return LIS_SUCCESS;
}

#undef __FUNC__
#define __FUNC__ "lis_esolver_destroy"
LIS_INT lis_esolver_destroy(LIS_ESOLVER esolver)
{
	LIS_INT i,ss;
  
	LIS_DEBUG_FUNC_IN;

	if( esolver )
	{
		lis_esolver_work_destroy(esolver);
   	        if( esolver->rhistory ) lis_free(esolver->rhistory);
	        if( esolver->evalue ) lis_free(esolver->evalue);
 	        if( esolver->resid ) lis_free(esolver->resid);
 	        if( esolver->iter) lis_free(esolver->iter);
 	        if( esolver->iter2) lis_free(esolver->iter2);
   	        if( esolver->evector ) 
		  {
		    if ( esolver->options[LIS_EOPTIONS_ESOLVER] == LIS_ESOLVER_LI || esolver->options[LIS_EOPTIONS_ESOLVER] == LIS_ESOLVER_AI || esolver->options[LIS_EOPTIONS_ESOLVER] == LIS_ESOLVER_SI )
		      {
			ss=esolver->options[LIS_EOPTIONS_SUBSPACE];
			for(i=0;i<ss+2;i++) lis_vector_destroy(esolver->evector[i]);
		      }
		    lis_free(esolver->evector);
		  }
		lis_free(esolver);
	}

	LIS_DEBUG_FUNC_OUT;
	return LIS_SUCCESS;
}

#undef __FUNC__
#define __FUNC__ "lis_esolve"
LIS_INT lis_esolve(LIS_MATRIX A, LIS_VECTOR x, LIS_SCALAR *evalue0, LIS_ESOLVER esolver)
{
        LIS_INT	nesolver,niesolver,emaxiter; 
	LIS_SCALAR *evalue;
	LIS_VECTOR *evector;
	LIS_SCALAR *resid;
	LIS_SCALAR *rhistory;
	LIS_INT	*iter,*iter2;
	LIS_INT	err;
	LIS_INT output;
	LIS_INT ss, mode;
	double time;
	double gshift;
	LIS_INT	estorage,eblock;
	LIS_MATRIX B;
	LIS_INT eprecision;
	LIS_VECTOR xx;

	LIS_DEBUG_FUNC_IN;

	/* begin parameter check */
	err = lis_matrix_check(A,LIS_MATRIX_CHECK_ALL);

	if( err ) return err;
	if( x==NULL )
	{
		LIS_SETERR(LIS_ERR_ILL_ARG,"vector x is undefined\n");
		return LIS_ERR_ILL_ARG;
	}
	if( A->n!=x->n )
	{
		return LIS_ERR_ILL_ARG;
	}
	if( A->gn<=0 )
	{
		LIS_SETERR1(LIS_ERR_ILL_ARG,"Size n(=%d) of matrix A is less than 0\n",A->gn);
		return LIS_ERR_ILL_ARG;
	}

	nesolver = esolver->options[LIS_EOPTIONS_ESOLVER];
	niesolver = esolver->options[LIS_EOPTIONS_INNER_ESOLVER];
	ss = esolver->options[LIS_EOPTIONS_SUBSPACE];
	mode = esolver->options[LIS_EOPTIONS_MODE];
	emaxiter = esolver->options[LIS_EOPTIONS_MAXITER];
	gshift = esolver->params[LIS_EPARAMS_SHIFT - LIS_EOPTIONS_LEN];
	output = esolver->options[LIS_EOPTIONS_OUTPUT];
	estorage = esolver->options[LIS_EOPTIONS_STORAGE];
	eblock = esolver->options[LIS_EOPTIONS_STORAGE_BLOCK];
	eprecision = esolver->options[LIS_EOPTIONS_PRECISION];
	esolver->eprecision = eprecision;

	if( nesolver < 1 || nesolver > LIS_ESOLVER_LEN )
	{
		LIS_SETERR2(LIS_ERR_ILL_ARG,"Parameter LIS_EOPTIONS_ESOLVER is %d (Set between 1 to %d)\n",nesolver, LIS_ESOLVER_LEN);
		return LIS_ERR_ILL_ARG;
	}

	if( niesolver < 1 || niesolver > 7 ) 
	{
		LIS_SETERR1(LIS_ERR_ILL_ARG,"Parameter LIS_EOPTIONS_INNER_ESOLVER is %d (Set between 1 to 7)\n", niesolver);
		return LIS_ERR_ILL_ARG;
	}

	if ( esolver->options[LIS_EOPTIONS_ESOLVER] == LIS_ESOLVER_SI && niesolver > 4 )
	{
		LIS_SETERR1(LIS_ERR_ILL_ARG,"Parameter LIS_EOPTIONS_INNER_ESOLVER is %d (Set between 1 to 4 for Subspace)\n", niesolver);
		return LIS_ERR_ILL_ARG;
	}

	if ( esolver->options[LIS_EOPTIONS_ESOLVER] == LIS_ESOLVER_LI && niesolver == LIS_ESOLVER_PI )
	{
		LIS_SETERR1(LIS_ERR_ILL_ARG,"Parameter LIS_EOPTIONS_INNER_ESOLVER is %d (Set between 2 to 7 for Lanczos)\n", niesolver);
		return LIS_ERR_ILL_ARG;
	}

	if ( esolver->options[LIS_EOPTIONS_ESOLVER] == LIS_ESOLVER_AI && (( niesolver == LIS_ESOLVER_PI ) || ( niesolver == LIS_ESOLVER_CG) || ( niesolver == LIS_ESOLVER_JD)) )
	{
		LIS_SETERR1(LIS_ERR_ILL_ARG,"Parameter LIS_EOPTIONS_INNER_ESOLVER is %d (Set between 2 to 4 or 6 for Arnoldi)\n", niesolver);
		return LIS_ERR_ILL_ARG;
	}

	if ( esolver->options[LIS_EOPTIONS_ESOLVER] == LIS_ESOLVER_SI && ss > A->gn )
	{
		LIS_SETERR2(LIS_ERR_ILL_ARG,"Parameter LIS_EOPTIONS_SUBSPACE is %d (Set less than or equal to matrix size %d for Subspace)\n", ss, A->gn);
		return LIS_ERR_ILL_ARG;
	}

	if (( esolver->options[LIS_EOPTIONS_ESOLVER] == LIS_ESOLVER_LI || esolver->options[LIS_EOPTIONS_ESOLVER] == LIS_ESOLVER_AI ) && ss > A->gn )
	{
		LIS_SETERR2(LIS_ERR_ILL_ARG,"Parameter LIS_EOPTIONS_SUBSPACE is %d (Set less than or equal to matrix size %d for Lanczos and Arnoldi)\n", ss, A->gn);
		return LIS_ERR_ILL_ARG;
	}

	if ( esolver->options[LIS_EOPTIONS_ESOLVER] == LIS_ESOLVER_SI && mode >= ss )
	{
		LIS_SETERR2(LIS_ERR_ILL_ARG,"Parameter LIS_EOPTIONS_MODE is %d (Set less than subspace size %d for Subspace)\n", mode, ss);
		return LIS_ERR_ILL_ARG;
	}

	if ( esolver->options[LIS_EOPTIONS_ESOLVER] == ( LIS_ESOLVER_LI || LIS_ESOLVER_AI ) && mode >= ss )
	{
		LIS_SETERR2(LIS_ERR_ILL_ARG,"Parameter LIS_EOPTIONS_MODE is %d (Set less than subspace size %d for Lanczos or Arnoldi)\n", mode, ss);
		return LIS_ERR_ILL_ARG;
	}

	#ifdef USE_QUAD_PRECISION
		if( eprecision==LIS_PRECISION_QUAD && lis_esolver_execute_quad[nesolver]==NULL )
		{
			LIS_SETERR1(LIS_ERR_NOT_IMPLEMENTED,"Quad precision eigensolver %s is not implemented\n",lis_esolvername[nesolver]);
			return LIS_ERR_NOT_IMPLEMENTED;
		}
		else if( eprecision==LIS_PRECISION_SWITCH && lis_esolver_execute_switch[nesolver]==NULL )
		{
			LIS_SETERR1(LIS_ERR_NOT_IMPLEMENTED,"Switch esolver %s is not implemented\n",lis_esolvername[nesolver]);
			return LIS_ERR_NOT_IMPLEMENTED;
		}
		if( esolver->options[LIS_EOPTIONS_SWITCH_MAXITER]==-1 )
		{
			esolver->options[LIS_EOPTIONS_SWITCH_MAXITER] = emaxiter;
		}
	#endif

	/* create eigenvalue array */
	if( esolver->evalue ) lis_free(esolver->evalue);
	evalue = (LIS_SCALAR *)lis_malloc((ss+2)*sizeof(LIS_SCALAR),"lis_esolve::evalue");
	if( evalue==NULL )
	{
		LIS_SETERR_MEM((ss+2)*sizeof(LIS_SCALAR));
		esolver->retcode = err;
		return err;
	}
	evalue[0] = 1.0;
	evalue[ss-1] = 1.0;

	/* create residual norm array */
	if( esolver->resid ) lis_free(esolver->resid);
	resid = (LIS_SCALAR *)lis_malloc((ss+2)*sizeof(LIS_SCALAR),"lis_esolve::resid");
	if( resid==NULL )
	{
		LIS_SETERR_MEM((ss+2)*sizeof(LIS_SCALAR));
		esolver->retcode = err;
		return err;
	}

	/* create number of iterations array */
	if( esolver->iter ) lis_free(esolver->iter);
	iter = (LIS_INT *)lis_malloc((ss+2)*sizeof(LIS_SCALAR),"lis_esolve::iter");
	if( iter==NULL )
	{
		LIS_SETERR_MEM((ss+2)*sizeof(LIS_SCALAR));
		esolver->retcode = err;
		return err;
	}

	/* create quad precision number of iterations array */
	if( esolver->iter2 ) lis_free(esolver->iter2);
	iter2 = (LIS_INT *)lis_malloc((ss+2)*sizeof(LIS_SCALAR),"lis_esolve::iter2");
	if( iter2==NULL )
	{
		LIS_SETERR_MEM((ss+2)*sizeof(LIS_SCALAR));
		esolver->retcode = err;
		return err;
	}

	/* create initial vector */
	#ifndef USE_QUAD_PRECISION
		err = lis_vector_duplicate(A,&xx);
	#else
		if( eprecision==LIS_PRECISION_DOUBLE )
		{
			err = lis_vector_duplicate(A,&xx);
		}
		else
		{
			err = lis_vector_duplicateex(LIS_PRECISION_QUAD,A,&xx);
		}
	#endif
	if( err )
	{
		esolver->retcode = err;
		return err;
	}
	if( esolver->options[LIS_EOPTIONS_INITGUESS_ONES] )
	{
	  if( output ) lis_printf(A->comm,"initial vector x      : 1\n");
		#ifndef USE_QUAD_PRECISION
			lis_vector_set_all(1.0,xx);
		#else
			if( eprecision==LIS_PRECISION_DOUBLE )
			{
				lis_vector_set_all(1.0,xx);
			}
			else
			{
				lis_vector_set_allex_nm(1.0,xx);
			}
		#endif
	}
	else
	{
	  if( output ) lis_printf(A->comm,"initial vector x      : user defined\n"); 
		#ifndef USE_QUAD_PRECISION
			lis_vector_copy(x,xx);
		#else
			if( eprecision==LIS_PRECISION_DOUBLE )
			{
				lis_vector_copy(x,xx);
			}
			else
			{
				lis_vector_copyex_nm(x,xx);
			}
		#endif
	}

	/* global shift */
	if ( output ) if( A->my_rank==0 ) printf("shift                 : %e\n", gshift);		

	/* create eigenvector array */
	if( esolver->evector ) lis_free(esolver->evector);
	evector = (LIS_VECTOR *)lis_malloc((ss+2)*sizeof(LIS_VECTOR),"lis_esolve::evector");
	if( evector==NULL )
	{
		LIS_SETERR_MEM((ss+2)*sizeof(LIS_VECTOR));
		esolver->retcode = err;
		return err;
	}

	/* create residual history array */
	if( esolver->rhistory ) lis_free(esolver->rhistory);
	rhistory = (LIS_SCALAR *)lis_malloc((emaxiter+2)*sizeof(LIS_SCALAR),"lis_esolve::rhistory");
	if( rhistory==NULL )
	{
		LIS_SETERR_MEM((emaxiter+2)*sizeof(LIS_SCALAR));
		lis_vector_destroy(xx);
		esolver->retcode = err;
		return err;
	}

	/* convert matrix */
	if( estorage>0 && A->matrix_type!=estorage )
	{
		err = lis_matrix_duplicate(A,&B);
		if( err ) return err;
		lis_matrix_set_blocksize(B,eblock,eblock,NULL,NULL);
		lis_matrix_set_type(B,estorage);
		err = lis_matrix_convert(A,B);
		if( err ) return err;
		lis_matrix_storage_destroy(A);
		lis_matrix_DLU_destroy(A);
		lis_matrix_diag_destroy(A->WD);
		if( A->l2g_map ) lis_free( A->l2g_map );
		if( A->commtable ) lis_commtable_destroy( A->commtable );
		if( A->ranges ) lis_free( A->ranges );
		err = lis_matrix_copy_struct(B,A);
		if( err ) return err;
		lis_free(B);
	}

	esolver->A        = A;
	esolver->evalue   = evalue;
	esolver->x        = x;
	esolver->evector  = evector;
	rhistory[0]       = 1.0;
	esolver->rhistory = rhistory;
	esolver->resid    = resid;
	esolver->iter     = iter;
	esolver->iter2    = iter2;

        if( A->my_rank==0 )
	  {
#ifdef _LONG__DOUBLE
  	    if ( output ) printf("precision             : long double\n");
#else
	    if ( output ) printf("precision             : %s\n", lis_eprecisionname[eprecision]);
#endif
#ifdef _LONG__LONG
	    if ( output ) printf("eigensolver           : %s\n", lis_esolvername[nesolver]);
#else
	    if ( output ) printf("eigensolver           : %s\n", lis_esolvername[nesolver]);
#endif
	  }

	if( A->my_rank==0 )
	  {
#ifdef _COMPLEX
#ifdef _LONG__DOUBLE
	    if ( output ) printf("convergence condition : ||lx-Ax||_2 <= %6.1Le * ||lx||_2\n", creall(esolver->params[LIS_EPARAMS_RESID - LIS_EOPTIONS_LEN]));
#else
	    if ( output ) printf("convergence condition : ||lx-Ax||_2 <= %6.1e * ||lx||_2\n", creal(esolver->params[LIS_EPARAMS_RESID - LIS_EOPTIONS_LEN])); 
#endif
#else	    
#ifdef _LONG__DOUBLE
	    if ( output ) printf("convergence condition : ||lx-Ax||_2 <= %6.1Le * ||lx||_2\n", esolver->params[LIS_EPARAMS_RESID - LIS_EOPTIONS_LEN]);
#else
	    if ( output ) printf("convergence condition : ||lx-Ax||_2 <= %6.1e * ||lx||_2\n", esolver->params[LIS_EPARAMS_RESID - LIS_EOPTIONS_LEN]); 
#endif
#endif	    
	  }

	if( A->my_rank==0 )
	  {
	    if( A->matrix_type==LIS_MATRIX_BSR || A->matrix_type==LIS_MATRIX_BSC )
	      {
#ifdef _LONG__LONG
		if ( output ) printf("matrix storage format : %s(%lld x %lld)\n", lis_estoragename[A->matrix_type-1],eblock,eblock);
#else
		if ( output ) printf("matrix storage format : %s(%d x %d)\n", lis_estoragename[A->matrix_type-1],eblock,eblock); 
#endif
	      }
	    else
	      {
		if ( output ) printf("matrix storage format : %s\n", lis_estoragename[A->matrix_type-1]); 
	      }
	  }
	
	time = lis_wtime();

	esolver->ptime = 0;
	esolver->itime = 0;
	esolver->p_c_time = 0;
	esolver->p_i_time = 0;


	if (gshift != 0.0) lis_matrix_shift_diagonal(A, gshift);

	/* create work vector */
	err = lis_esolver_malloc_work[nesolver](esolver);
	if( err )
	{
	  lis_vector_destroy(xx);
	  esolver->retcode = err;
	  return err;
	}

	esolver->x        = xx;
	esolver->xx       = x;

	/* execute esolver */
	#ifndef USE_QUAD_PRECISION
		err = lis_esolver_execute[nesolver](esolver);
	#else
		if( eprecision==LIS_PRECISION_DOUBLE )
		{
			err = lis_esolver_execute[nesolver](esolver);
		}
		else if( eprecision==LIS_PRECISION_QUAD )
		{
			err = lis_esolver_execute_quad[nesolver](esolver);
		}
		else if( eprecision==LIS_PRECISION_SWITCH )
		{
			err = lis_esolver_execute_switch[nesolver](esolver);
		}
	#endif
	esolver->retcode = err;

	*evalue0 = esolver->evalue[0];
	lis_vector_copy(esolver->x, x);

	esolver->time = lis_wtime() - time; 

	lis_matrix_shift_diagonal(A, -gshift);

        if( A->my_rank==0 )
        {
                if( err )
                {
#ifdef _LONG__LONG
                  if ( output ) printf("eigensolver status    : %s(code=%lld)\n\n",lis_ereturncode[err],err);
#else
                  if ( output ) printf("eigensolver status    : %s(code=%d)\n\n",lis_ereturncode[err],err);
#endif

                }
                else
                {
                  if ( output ) printf("eigensolver status    : normal end\n\n");
                }
        }

	if( eprecision==LIS_PRECISION_DOUBLE )
	{
		esolver->iter2[mode] = esolver->iter[mode];
	}
	else if( eprecision==LIS_PRECISION_QUAD )
	{
		esolver->iter2[mode] = 0;
	}

	lis_vector_destroy(xx);

	LIS_DEBUG_FUNC_OUT;
	return LIS_SUCCESS;
}

#undef __FUNC__
#define __FUNC__ "lis_esolver_set_optionC"
LIS_INT lis_esolver_set_optionC(LIS_ESOLVER esolver)
{
	LIS_ARGS p;

	LIS_DEBUG_FUNC_IN;

	p = cmd_args->next;
	while( p!=cmd_args )
	{
		lis_esolver_set_option2(p->arg1,p->arg2,esolver);
		p = p->next;
	}

	LIS_DEBUG_FUNC_OUT;
	return LIS_SUCCESS;
}

#undef __FUNC__
#define __FUNC__ "lis_esolver_set_option"
LIS_INT lis_esolver_set_option(char *text, LIS_ESOLVER esolver)
{
	LIS_ARGS args,p;

	LIS_DEBUG_FUNC_IN;

	lis_text2args(text,&args);
	p = args->next;
	while( p!=args )
	{
		lis_esolver_set_option2(p->arg1,p->arg2,esolver);
		p = p->next;
	}
	lis_args_free(args);

	LIS_DEBUG_FUNC_OUT;
	return LIS_SUCCESS;
}

#undef __FUNC__
#define __FUNC__ "lis_esolver_set_option2"
LIS_INT lis_esolver_set_option2(char* arg1, char *arg2, LIS_ESOLVER esolver)
{
	LIS_INT i;

	LIS_DEBUG_FUNC_IN;
	
	for(i=0;i<LIS_ESOLVER_OPTION_LEN;i++)
	{
		if( strcmp(arg1, LIS_ESOLVER_OPTNAME[i])==0 )
		{
			switch( LIS_ESOLVER_OPTACT[i] )
			{
			case LIS_EOPTIONS_ESOLVER:
			  lis_esolver_set_option_esolver(arg2,esolver);
			  break;
                        case LIS_EOPTIONS_OUTPUT:
			  lis_esolver_set_option_print(arg2,esolver);
			  break;
                        case LIS_EOPTIONS_INITGUESS_ONES:
			  lis_esolver_set_option_truefalse(arg2,LIS_EOPTIONS_INITGUESS_ONES,esolver);
			  break;
			case LIS_EOPTIONS_INNER_ESOLVER:
			  lis_esolver_set_option_iesolver(arg2,esolver);
			  break;
			case LIS_EOPTIONS_STORAGE:
				lis_esolver_set_option_storage(arg2,esolver);
				break;
                        case LIS_EOPTIONS_PRECISION:
			  lis_esolver_set_option_eprecision(arg2,LIS_EOPTIONS_PRECISION,esolver);
			  break;
			default:
			  if( LIS_ESOLVER_OPTACT[i] < LIS_EOPTIONS_LEN )
			    {
#ifdef _LONG__LONG
			      sscanf(arg2, "%lld", &esolver->options[LIS_ESOLVER_OPTACT[i]]);
#else
			      sscanf(arg2, "%d", &esolver->options[LIS_ESOLVER_OPTACT[i]]);
#endif
			    }
			  else
			    {
#ifdef _LONG__DOUBLE
			      sscanf(arg2, "%Lg", &esolver->params[LIS_ESOLVER_OPTACT[i]-LIS_EOPTIONS_LEN]);
#else
			      sscanf(arg2, "%lg", &esolver->params[LIS_ESOLVER_OPTACT[i]-LIS_EOPTIONS_LEN]);
#endif
			    }
			  break;
			}
		}
	}
	LIS_DEBUG_FUNC_OUT;
	return LIS_SUCCESS;
}

#undef __FUNC__
#define __FUNC__ "lis_esolver_set_option_esolver"
LIS_INT lis_esolver_set_option_esolver(char *argv, LIS_ESOLVER esolver)
{
	LIS_INT i;

	LIS_DEBUG_FUNC_IN;

	if( argv[0]>='0' && argv[0]<='9' )
	{
#ifdef _LONG__LONG
		sscanf(argv, "%lld", &esolver->options[LIS_EOPTIONS_ESOLVER]);
#else
		sscanf(argv, "%d", &esolver->options[LIS_EOPTIONS_ESOLVER]);
#endif
	}
	else
	{
		for(i=0;i<LIS_ESOLVER_LEN;i++)
		{
			if( strcmp(argv,lis_esolver_atoi[i])==0 )
			{
				esolver->options[LIS_EOPTIONS_ESOLVER] = i+1;
				break;
			}
		}
	}
	LIS_DEBUG_FUNC_OUT;
	return LIS_SUCCESS;
}

#undef __FUNC__
#define __FUNC__ "lis_esolver_set_option_iesolver"
LIS_INT lis_esolver_set_option_iesolver(char *argv, LIS_ESOLVER esolver)
{
	LIS_INT i;

	LIS_DEBUG_FUNC_IN;

	if( argv[0]>='0' && argv[0]<='9' )
	{
#ifdef _LONG__LONG
		sscanf(argv, "%lld", &esolver->options[LIS_EOPTIONS_INNER_ESOLVER]);
#else
		sscanf(argv, "%d", &esolver->options[LIS_EOPTIONS_INNER_ESOLVER]);
#endif
	}
	else
	{
		for(i=0;i<LIS_ESOLVER_LEN;i++)
		{
			if( strcmp(argv,lis_esolver_atoi[i])==0 )
			{
				esolver->options[LIS_EOPTIONS_INNER_ESOLVER] = i+1;
				break;
			}
		}
	}
	LIS_DEBUG_FUNC_OUT;
	return LIS_SUCCESS;
}

#undef __FUNC__
#define __FUNC__ "lis_esolver_set_option_print"
LIS_INT lis_esolver_set_option_print(char *argv, LIS_ESOLVER esolver)
{
	LIS_INT i;

	LIS_DEBUG_FUNC_IN;

	if( argv[0]>='0' && argv[0]<='3' )
	{
#ifdef _LONG__LONG
		sscanf(argv, "%lld", &esolver->options[LIS_EOPTIONS_OUTPUT]);
#else
		sscanf(argv, "%d", &esolver->options[LIS_EOPTIONS_OUTPUT]);
#endif
	}
	else
	{
		for(i=0;i<LIS_EPRINT_LEN;i++)
		{
			if( strcmp(argv,lis_eprint_atoi[i])==0 )
			{
				esolver->options[LIS_EOPTIONS_OUTPUT] = i;
				break;
			}
		}
	}
	LIS_DEBUG_FUNC_OUT;
	return LIS_SUCCESS;
}

#undef __FUNC__
#define __FUNC__ "lis_solver_set_option_truefalse"
LIS_INT lis_esolver_set_option_truefalse(char *argv, LIS_INT opt, LIS_ESOLVER esolver)
{
        LIS_INT i;

        LIS_DEBUG_FUNC_IN;

        if( argv[0]>='0' && argv[0]<='1' )
        {
#ifdef _LONG__LONG
                sscanf(argv, "%lld", &esolver->options[opt]);
#else
                sscanf(argv, "%d", &esolver->options[opt]);
#endif
        }
        else
        {
                for(i=0;i<LIS_TRUEFALSE_LEN;i++)
                {
                        if( strcmp(argv,lis_etruefalse_atoi[i])==0 )
                        {
                                esolver->options[opt] = i;
                                break;
                        }
                }
        }
        LIS_DEBUG_FUNC_OUT;
        return LIS_SUCCESS;
}

#undef __FUNC__
#define __FUNC__ "lis_esolver_set_option_eprecision"
LIS_INT lis_esolver_set_option_eprecision(char *argv, LIS_INT opt, LIS_ESOLVER esolver)
{
	LIS_INT i;

	LIS_DEBUG_FUNC_IN;

	if( argv[0]>='0' && argv[0]<='1' )
	{
#ifdef _LONG__LONG
		sscanf(argv, "%lld", &esolver->options[opt]);
#else
		sscanf(argv, "%d", &esolver->options[opt]);
#endif
	}
	else
	{
		for(i=0;i<LIS_PRECISION_LEN;i++)
		{
			if( strcmp(argv,lis_eprecision_atoi[i])==0 )
			{
				esolver->options[opt] = i;
				break;
			}
		}
	}
	LIS_DEBUG_FUNC_OUT;
	return LIS_SUCCESS;
}

#undef __FUNC__
#define __FUNC__ "lis_esolver_set_option_storage"
LIS_INT lis_esolver_set_option_storage(char *argv, LIS_ESOLVER esolver)
{
	LIS_INT i;

	LIS_DEBUG_FUNC_IN;

	if( argv[0]>='0' && argv[0]<='9' )
	{
#ifdef _LONG__LONG
		sscanf(argv, "%lld", &esolver->options[LIS_EOPTIONS_STORAGE]);
#else
		sscanf(argv, "%d", &esolver->options[LIS_EOPTIONS_STORAGE]);
#endif
	}
	else
	{
		for(i=0;i<LIS_ESTORAGE_LEN;i++)
		{
			if( strcmp(argv,lis_estorage_atoi[i])==0 )
			{
				esolver->options[LIS_EOPTIONS_STORAGE] = i+1;
				break;
			}
		}
	}
	LIS_DEBUG_FUNC_OUT;
	return LIS_SUCCESS;
}

#undef __FUNC__
#define __FUNC__ "lis_esolver_get_iter"
LIS_INT lis_esolver_get_iter(LIS_ESOLVER esolver, LIS_INT *iter)
{
	LIS_DEBUG_FUNC_IN;

	*iter = esolver->iter[0];

	LIS_DEBUG_FUNC_OUT;
	return LIS_SUCCESS;
}

#undef __FUNC__
#define __FUNC__ "lis_esolver_get_iterex"
LIS_INT lis_esolver_get_iterex(LIS_ESOLVER esolver, LIS_INT *iter, LIS_INT *iter_double, LIS_INT *iter_quad)
{
	LIS_DEBUG_FUNC_IN;

	*iter = esolver->iter[0];
	*iter_double = esolver->iter2[0];
	*iter_quad = esolver->iter[0] - esolver->iter2[0];

	LIS_DEBUG_FUNC_OUT;
	return LIS_SUCCESS;
}

#undef __FUNC__
#define __FUNC__ "lis_esolver_get_time"
LIS_INT lis_esolver_get_time(LIS_ESOLVER esolver, double *time)
{
	LIS_DEBUG_FUNC_IN;

	*time  = esolver->time;

	LIS_DEBUG_FUNC_OUT;
	return LIS_SUCCESS;
}

#undef __FUNC__
#define __FUNC__ "lis_esolver_get_timeex"
LIS_INT lis_esolver_get_timeex(LIS_ESOLVER esolver, double *time, double *itime, double *ptime, double *p_c_time, double *p_i_time)
{
	LIS_DEBUG_FUNC_IN;

	*time  = esolver->time;
	if( itime ) *itime = esolver->itime;
	if( ptime ) *ptime = esolver->ptime;
	if( p_c_time ) *p_c_time = esolver->p_c_time;
	if( p_i_time ) *p_i_time = esolver->p_i_time;

	LIS_DEBUG_FUNC_OUT;
	return LIS_SUCCESS;
}

#undef __FUNC__
#define __FUNC__ "lis_esolver_get_residualnorm"
LIS_INT lis_esolver_get_residualnorm(LIS_ESOLVER esolver, LIS_REAL *residual)
{
	LIS_DEBUG_FUNC_IN;

	*residual  = esolver->resid[0];

	LIS_DEBUG_FUNC_OUT;
	return LIS_SUCCESS;
}

#undef __FUNC__
#define __FUNC__ "lis_esolver_get_status"
LIS_INT lis_esolver_get_status(LIS_ESOLVER esolver, LIS_INT *status)
{
	LIS_DEBUG_FUNC_IN;

	*status = esolver->retcode;

	LIS_DEBUG_FUNC_OUT;
	return LIS_SUCCESS;
}

#undef __FUNC__
#define __FUNC__ "lis_esolver_get_rhistory"
LIS_INT lis_esolver_get_rhistory(LIS_ESOLVER esolver, LIS_VECTOR v)
{
	LIS_INT	i,n,maxiter;

	maxiter = esolver->iter[0]+1;
        if( esolver->retcode!=LIS_SUCCESS )
	  {
	    maxiter--;
	  }
	n = _min(v->n,maxiter);
	for(i=0;i<n;i++)
	{
		v->value[i] = esolver->rhistory[i];
	}
	return LIS_SUCCESS;
}

#undef __FUNC__
#define __FUNC__ "lis_esolver_get_evalues"
LIS_INT lis_esolver_get_evalues(LIS_ESOLVER esolver, LIS_VECTOR v)
{
	LIS_INT i,ii,ss,n,gn,is,ie;

	LIS_DEBUG_FUNC_IN;

	if ( esolver->options[LIS_EOPTIONS_ESOLVER] != LIS_ESOLVER_SI && esolver->options[LIS_EOPTIONS_ESOLVER] != LIS_ESOLVER_LI && esolver->options[LIS_EOPTIONS_ESOLVER] != LIS_ESOLVER_AI )
	{
		LIS_SETERR1(LIS_ERR_ILL_ARG,"Parameter LIS_EOPTIONS_ESOLVER is %d (Set Subspace, Lanczos, or Arnoldi)\n", esolver->options[LIS_EOPTIONS_ESOLVER]);
		return LIS_ERR_ILL_ARG;
	}

	ss = esolver->options[LIS_EOPTIONS_SUBSPACE];
	lis_vector_set_size(v,0,ss);
	lis_vector_get_size(v,&n,&gn);
	lis_vector_get_range(v,&is,&ie);
	
	for(i=0;i<n;i++)
	{
	  ii=i;
	  if( v->origin ) ii++;
	  lis_vector_set_value(LIS_INS_VALUE,ii+is,esolver->evalue[i+is],v);
	}

	LIS_DEBUG_FUNC_OUT;
	return LIS_SUCCESS;
}

#undef __FUNC__
#define __FUNC__ "lis_esolver_get_evectors"
LIS_INT lis_esolver_get_evectors(LIS_ESOLVER esolver, LIS_MATRIX M)
{
	LIS_INT i,ii,j,jj,n,gn,is,ie,js;
	LIS_INT ss,lis_esolver_evector_size;

	LIS_DEBUG_FUNC_IN;

	if ( esolver->options[LIS_EOPTIONS_ESOLVER] != LIS_ESOLVER_SI && esolver->options[LIS_EOPTIONS_ESOLVER] != LIS_ESOLVER_LI && esolver->options[LIS_EOPTIONS_ESOLVER] != LIS_ESOLVER_AI )
	{
		LIS_SETERR1(LIS_ERR_ILL_ARG,"Parameter LIS_EOPTIONS_ESOLVER is %d (Set Subspace, Lanczos, or Arnoldi)\n", esolver->options[LIS_EOPTIONS_ESOLVER]);
		return LIS_ERR_ILL_ARG;
	}

	ss = esolver->options[LIS_EOPTIONS_SUBSPACE];
	lis_esolver_evector_size = esolver->evector[0]->gn; 
	lis_matrix_set_size(M,0,lis_esolver_evector_size);
	lis_matrix_get_size(M,&n,&gn);
	lis_matrix_get_range(M,&is,&ie);
	js=0;
	if( esolver->evector[0]->origin ) 
	  {
	    is++;
	    js++;
	  }
	for(j=0;j<ss;j++)
	  {
	    for(i=0;i<n;i++)
	      {
		ii=i+is;
		jj=j+js;
		lis_matrix_set_value(LIS_INS_VALUE,ii,jj,esolver->evector[j]->value[i],M);
	      }
	  }
	lis_matrix_set_type(M,LIS_MATRIX_CSR);
	lis_matrix_assemble(M);

	LIS_DEBUG_FUNC_OUT;
	return LIS_SUCCESS;
}

#undef __FUNC__
#define __FUNC__ "lis_esolver_get_residualnorms"
LIS_INT lis_esolver_get_residualnorms(LIS_ESOLVER esolver, LIS_VECTOR v)
{
	LIS_INT i,ii,ss,n,gn,is,ie;

	LIS_DEBUG_FUNC_IN;

	if ( esolver->options[LIS_EOPTIONS_ESOLVER] != LIS_ESOLVER_SI && esolver->options[LIS_EOPTIONS_ESOLVER] != LIS_ESOLVER_LI && esolver->options[LIS_EOPTIONS_ESOLVER] != LIS_ESOLVER_AI )
	{
		LIS_SETERR1(LIS_ERR_ILL_ARG,"Parameter LIS_EOPTIONS_ESOLVER is %d (Set Subspace, Lanczos, or Arnoldi)\n", esolver->options[LIS_EOPTIONS_ESOLVER]);
		return LIS_ERR_ILL_ARG;
	}

	ss = esolver->options[LIS_EOPTIONS_SUBSPACE];
	lis_vector_set_size(v,0,ss);
	lis_vector_get_size(v,&n,&gn);
	lis_vector_get_range(v,&is,&ie);
	
	for(i=0;i<n;i++)
	{
	  ii=i;
	  if( v->origin ) ii++;
	  lis_vector_set_value(LIS_INS_VALUE,ii+is,esolver->resid[i+is],v);
	}

	LIS_DEBUG_FUNC_OUT;
	return LIS_SUCCESS;
}

#undef __FUNC__
#define __FUNC__ "lis_esolver_get_iters"
LIS_INT lis_esolver_get_iters(LIS_ESOLVER esolver, LIS_VECTOR v)
{
	LIS_INT i,ii,ss,n,gn,is,ie;

	LIS_DEBUG_FUNC_IN;

	if ( esolver->options[LIS_EOPTIONS_ESOLVER] != LIS_ESOLVER_SI && esolver->options[LIS_EOPTIONS_ESOLVER] != LIS_ESOLVER_LI && esolver->options[LIS_EOPTIONS_ESOLVER] != LIS_ESOLVER_AI )
	{
		LIS_SETERR1(LIS_ERR_ILL_ARG,"Parameter LIS_EOPTIONS_ESOLVER is %d (Set Subspace, Lanczos, or Arnoldi)\n", esolver->options[LIS_EOPTIONS_ESOLVER]);
		return LIS_ERR_ILL_ARG;
	}

	ss = esolver->options[LIS_EOPTIONS_SUBSPACE];
	lis_vector_set_size(v,0,ss);
	lis_vector_get_size(v,&n,&gn);
	lis_vector_get_range(v,&is,&ie);
	
	for(i=0;i<n;i++)
	{
	  ii=i;
	  if( v->origin ) ii++;
	  lis_vector_set_value(LIS_INS_VALUE,ii+is,esolver->iter[i+is],v);
	}

	v->intvalue = 1;

	LIS_DEBUG_FUNC_OUT;
	return LIS_SUCCESS;
}

#undef __FUNC__
#define __FUNC__ "lis_esolver_get_esolver"
LIS_INT lis_esolver_get_esolver(LIS_ESOLVER esolver, LIS_INT *nesol)
{
	LIS_DEBUG_FUNC_IN;

	*nesol = esolver->options[LIS_EOPTIONS_ESOLVER];

	LIS_DEBUG_FUNC_OUT;
	return LIS_SUCCESS;
}

#undef __FUNC__
#define __FUNC__ "lis_esolver_get_esolvername"
LIS_INT lis_esolver_get_esolvername(LIS_INT esolver, char *esolvername)
{
	LIS_DEBUG_FUNC_IN;

	if( esolver < 1 || esolver > LIS_ESOLVER_LEN )
	{
		esolvername = NULL;
		return LIS_FAILS;
	}
	strcpy(esolvername,lis_esolvername[esolver]);

	LIS_DEBUG_FUNC_OUT;
	return LIS_SUCCESS;
}

