
#ifndef lint
static char vcid[] = "$Id: snesmfj.c,v 1.28 1996/03/26 04:47:51 bsmith Exp bsmith $";
#endif

#include "draw.h"       /*I  "draw.h"   I*/
#include "snesimpl.h"   /*I  "snes.h"   I*/

typedef struct {  /* default context for matrix-free SNES */
  SNES        snes;
  Vec         w;
  PCNullSpace sp;
} MFCtx_Private;

int SNESMatrixFreeDestroy_Private(PetscObject obj)
{
  int           ierr;
  Mat           mat = (Mat) obj;
  MFCtx_Private *ctx;

  ierr = MatShellGetContext(mat,(void **)&ctx);
  ierr = VecDestroy(ctx->w); CHKERRQ(ierr);
  if (ctx->sp) {ierr = PCNullSpaceDestroy(ctx->sp); CHKERRQ(ierr);}
  PetscFree(ctx);
  return 0;
}

/*
  SNESMatrixFreeMult_Private - Default matrix-free form of A*u.
*/
int SNESMatrixFreeMult_Private(Mat mat,Vec dx,Vec y)
{
  MFCtx_Private *ctx;
  SNES          snes;
  double        norm, sum, epsilon = 1.e-8; /* assumes double precision */
  Scalar        h, dot, mone = -1.0;
  Vec           w,U,F;
  int           ierr, (*eval_fct)(SNES,Vec,Vec);

  MatShellGetContext(mat,(void **)&ctx);
  snes = ctx->snes;
  w = ctx->w;

  /* We log matrix-free matrix-vector products separately, so that we can
     separate the performance monitoring from the cases that use conventional
     storage.  We may eventually modify event logging to associate events
     with particular objects, hence alleviating the more general problem. */
  PLogEventBegin(MAT_MatrixFreeMult,dx,y,0,0);

  ierr = SNESGetSolution(snes,&U); CHKERRQ(ierr);
  if (snes->method_class == SNES_NONLINEAR_EQUATIONS) {
    eval_fct = SNESComputeFunction;
    ierr = SNESGetFunction(snes,&F); CHKERRQ(ierr);
  }
  else if (snes->method_class == SNES_UNCONSTRAINED_MINIMIZATION) {
    eval_fct = SNESComputeGradient;
    ierr = SNESGetGradient(snes,&F); CHKERRQ(ierr);
  }
  else SETERRQ(1,"SNESMatrixFreeMult_Private: Invalid method class");

  /* Determine a "good" step size */
  ierr = VecDot(U,dx,&dot); CHKERRQ(ierr);
  ierr = VecNorm(dx,NORM_1,&sum); CHKERRQ(ierr);
  ierr = VecNorm(dx,NORM_2,&norm); CHKERRQ(ierr);
  if (sum == 0.0) {dot = 1.0; norm = 1.0;}
#if defined(PETSC_COMPLEX)
  else if (abs(dot) < 1.e-16*sum && real(dot) >= 0.0) dot = 1.e-16*sum;
  else if (abs(dot) < 0.0 && real(dot) > 1.e-16*sum) dot = -1.e-16*sum;
#else
  else if (dot < 1.e-16*sum && dot >= 0.0) dot = 1.e-16*sum;
  else if (dot < 0.0 && dot > 1.e-16*sum) dot = -1.e-16*sum;
#endif
  h = epsilon*dot/(norm*norm);
  
  /* Evaluate function at F(x + dx) */
  ierr = VecWAXPY(&h,dx,U,w); CHKERRQ(ierr);
  ierr = eval_fct(snes,w,y); CHKERRQ(ierr);
  ierr = VecAXPY(&mone,F,y); CHKERRQ(ierr);
  h = 1.0/h;
  ierr = VecScale(&h,y); CHKERRQ(ierr);
  if (ctx->sp) {ierr = PCNullSpaceRemove(ctx->sp,y); CHKERRQ(ierr);}

  PLogEventEnd(MAT_MatrixFreeMult,dx,y,0,0);
  return 0;
}

/*@C
   SNESDefaultMatrixFreeMatCreate - Creates a matrix-free matrix
   context for use with a SNES solver.  This matrix can be used as
   the Jacobian argument for the routine SNESSetJacobian().

   Input Parameters:
.  x - vector where SNES solution is to be stored.

   Output Parameters:
.  J - the matrix-free matrix

   Notes:
   The matrix-free matrix context merely contains the function pointers
   and work space for performing finite difference approximations of
   matrix operations such as matrix-vector products.

   The user should call MatDestroy() when finished with the matrix-free
   matrix context.

.keywords: SNES, default, matrix-free, create, matrix

.seealso: MatDestroy()
@*/
int SNESDefaultMatrixFreeMatCreate(SNES snes,Vec x, Mat *J)
{
  MPI_Comm      comm;
  MFCtx_Private *mfctx;
  int           n,ierr;

  mfctx = (MFCtx_Private *) PetscMalloc(sizeof(MFCtx_Private)); CHKPTRQ(mfctx);
  PLogObjectMemory(snes,sizeof(MFCtx_Private));
  mfctx->sp   = 0;
  mfctx->snes = snes;
  ierr = VecDuplicate(x,&mfctx->w); CHKERRQ(ierr);
  ierr = PetscObjectGetComm((PetscObject)x,&comm); CHKERRQ(ierr);
  ierr = VecGetSize(x,&n); CHKERRQ(ierr);
  ierr = MatCreateShell(comm,n,n,(void*)mfctx,J); CHKERRQ(ierr);
  ierr = MatShellSetOperation(*J,MAT_MULT,(void*)SNESMatrixFreeMult_Private);
         CHKERRQ(ierr);
  ierr = MatShellSetOperation(*J,MAT_DESTROY,(void *)SNESMatrixFreeDestroy_Private);
         CHKERRQ(ierr);
  PLogObjectParent(*J,mfctx->w);
  PLogObjectParent(snes,*J);
  return 0;
}

/*@
    SNESDefaultMatrixFreeMatAddNullSpace - Provide a null space that 
        an operator is suppose to have. Since round off will create a 
        small component in the null space, if you know the null space 
        you may have it automatically removed.

  Input Parameters:
.  J - the matrix free matrix
.  has_cnst - PETSC_TRUE or PETSC_FALSE indicating if null space has constants
.  n - number of vectors (excluding constant vector) in null space
.  vecs - the vectors that span the null space (excluding the constant vector)
.         these vectors must be orthonormal

.keywords: SNES, matrix-free, null space
@*/
int SNESDefaultMatrixFreeMatAddNullSpace(Mat J,int has_cnst,int n,Vec *vecs)
{
  int           ierr;
  MFCtx_Private *ctx;
  MPI_Comm      comm;

  PetscObjectGetComm((PetscObject)J,&comm);

  ierr = MatShellGetContext(J,(void **)&ctx); CHKERRQ(ierr);
  /* no context indicates that it is not the "matrix free" matrix type */
  if (!ctx) return 0;
  ierr = PCNullSpaceCreate(comm,has_cnst,n,vecs,&ctx->sp); CHKERRQ(ierr);
  return 0;
}




