/*
 * MLNLS.C - non-linear Newton solver for PML
 *         - courtesy of Jeff Painter
 *
 * Source Version: 2.0
 * Software Release #92-0043
 *
 */

#include "cpyright.h"

#include "pml.h"

static void
 SC_DECLARE(_PM_settol,
         (int neqns, REAL *tol, REAL *y, double atol, double rtol));

static double
 SC_DECLARE(_PM_wvnorm, (int neqns, REAL *x, REAL *tol));

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* PM_NEWTONDL - a simple nonlinear solver designed to be conveniently called
 *             - by Alpal-generated codes.  This uses Newton's method to
 *             - solve f(Y)=0, with Y an array of length NEQNS.  On input,
 *             - NEQNS and the maximum number of iterations MAXITER need to
 *             - be set along with ATOL and RTOL which are the absolute and
 *             - relative error tolerance parameters (good values are 1.0e-4
 *             - and 1.0e-3 respectively).  Initial y values come in through Y
 *             - and the solution values are returned in Y.
 *             - One subroutine must be passed in through LINSOLV.
 *             - LINSOLV(x, y, iter) computes the Jacobian and the right-hand
 *             - side in the equation:
 *             -
 *             -     J.dy = -f(y1)     where J = (df/dy) at y1
 *             -     y1 = y[iter-1]
 *             -     dy = y[iter] - y1
 *             -
 *             - and returns the solution of the equation.  Iter is the Newton
 *             - iteration index. 
 *             -
 *             - Returns 0 iff we converged.
 *             - The convergence criterion is simple minded:
 *             -
 *             -     Norm(dy)/(atol + rtol*abs(y2[i])) < 1
 *             -
 *             - where, dy is the correction to the solution found in
 *             - the last iteration.
 */

double PM_newtondl(neqns, y, dy, tol, maxiter, atol, rtol, linsolv, arg)
   int neqns;
   REAL *y, *dy, *tol;
   int maxiter;
   double atol, rtol;
   PFVoid linsolv;
   byte *arg;
   {double err;
    int i, n, iter;

    iter = 0;
    err  = 2.0;
    n    = neqns*sizeof(double);

    while ((err > 1.0) && (iter++ < maxiter))

/* initial iterate */
       {memset(dy, 0, n);

/* solve J.dy = -f(y) */
        linsolv(neqns, dy, y, iter, arg);

/* update y */
        for (i = 0; i < neqns; i++)
            y[i] += dy[i];

        _PM_settol(neqns, tol, y, atol, rtol);

        err = _PM_wvnorm(neqns, dy, tol);};

    return((err <= 1.0) ? 0.0 : err);}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* PM_NEWTONUL - a simple nonlinear solver designed to be conveniently called
 *             - by Alpal-generated codes.  This uses Newton's method to
 *             - solve f(Y)=0, with Y an array of length NEQNS.  On input,
 *             - NEQNS and the maximum number of iterations MAXITER need to
 *             - be set along with ATOL and RTOL which are the absolute and
 *             - relative error tolerance parameters (good values are 1.0e-4
 *             - and 1.0e-3 respectively).  Initial y values come in through Y
 *             - and the solution values are returned in Y.
 *             - One subroutine must be passed in through LINSOLV.
 *             - LINSOLV(x, y, iter) computes the Jacobian and the right-hand
 *             - side in the equation:
 *             -
 *             -     J.y2 = J.y1 - f(y1)   where J = (df/dy) at y1
 *             -     y1 = y[iter-1]
 *             -     y2 = y[iter]
 *             -
 *             - and returns the solution of the equation.  Iter is the Newton
 *             - iteration index. 
 *             -
 *             - Returns 0 iff we converged.
 *             - The convergence criterion is simple minded:
 *             -
 *             -     Norm(delta - y1[i])/(atol + rtol*abs(y2[i])) < 1
 *             -
 *             - where, delta - y1 is the correction to the solution found in
 *             - the last iteration.
 */

double PM_newtonul(neqns, y2, y1, tol, maxiter, atol, rtol, linsolv, arg)
   int neqns;
   REAL *y2, *y1, *tol;
   int maxiter;
   double atol, rtol;
   PFVoid linsolv;
   byte *arg;
   {double err;
    int i, iter, n;

    iter = 0;
    err  = 2.0;
    n    = neqns*sizeof(double);

    while ((err > 1.0) && (iter++ < maxiter))

/* initial iterate */
       {memset(y1, 0, n);

/* solve J.y1 = -f(y2) */
        linsolv(neqns, y1, y2, iter, arg);

/* update y2 and set y1 to the correction made in this iteration
 * use tol for temporary storage to allow vectorization
 */
        for (i = 0; i < neqns; i++)
            tol[i] = y1[i] - y2[i];

        for (i = 0; i < neqns; i++)
            y2[i] = y1[i];

        for (i = 0; i < neqns; i++)
            y1[i] = tol[i];

        _PM_settol(neqns, tol, y2, atol, rtol);

        err = _PM_wvnorm(neqns, y1, tol);};

    return((err <= 1.0) ? 0.0 : err);}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* _PM_SETTOL - set the tolerances */

static void _PM_settol(neqns, tol, x, atol, rtol)
   int neqns;
   REAL *tol, *x;
   double atol, rtol;
   {int i;

    for (i = 0; i < neqns; i++)
        tol[i] = 1.0 / (atol + rtol*ABS(x[i]));

    return;}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/

/* _PM_WVNORM - weighted vector norm */

static double _PM_wvnorm(neqns, x, tol)
   int neqns;
   REAL *x, *tol;
   {int i;
    double val, t;

    val = 0.0;
    for (i = 0; i < neqns; i++)
        {t    = tol[i]*x[i];
         val += t*t;};

    return(sqrt(val));}

/*--------------------------------------------------------------------------*/
/*--------------------------------------------------------------------------*/



