// ---------------------------------------------------------------------------
// - Newton.cpp                                                              -
// - afnix:mth module - newton based zero solver class implementation        -
// ---------------------------------------------------------------------------
// - This program is free software;  you can redistribute it  and/or  modify -
// - it provided that this copyright notice is kept intact.                  -
// -                                                                         -
// - This program  is  distributed in  the hope  that it will be useful, but -
// - without  any  warranty;  without  even   the   implied    warranty   of -
// - merchantability or fitness for a particular purpose.  In no event shall -
// - the copyright holder be liable for any  direct, indirect, incidental or -
// - special damages arising in any way out of the use of this software.     -
// ---------------------------------------------------------------------------
// - copyright (c) 1999-2012 amaury darsch                                   -
// ---------------------------------------------------------------------------

#include "Real.hpp"
#include "Math.hpp"
#include "Vector.hpp"
#include "Newton.hpp"
#include "Linear.hpp"
#include "Runnable.hpp"
#include "QuarkZone.hpp"
#include "Exception.hpp"
 
namespace afnix {

  // the default verification mode
  static const bool NTW_VF_DEF = false;
  // the iteration factor for the newton solver
  static const long NTW_NI_DEF = 100L;

  // -------------------------------------------------------------------------
  // - class section                                                         -
  // -------------------------------------------------------------------------

  // create a default linear solver

  Newton::Newton (void) {
    d_avf = NTW_VF_DEF;
    d_mni = 0;
  }
  
  // create a solver by verification flag

  Newton::Newton (const bool avf) {
    d_avf = avf;
    d_mni = 0;
  }

  // create a solver by verification flag and mni value

  Newton::Newton (const bool avf, const long mni) {
    d_avf = avf;
    d_mni = mni;
  }

  // copy construct this object

  Newton::Newton (const Newton& that) {
    that.rdlock ();
    try {
      d_avf = that.d_avf;
      d_mni = that.d_mni;
      that.unlock ();
    } catch (...) {
      that.unlock ();
      throw;
    }
  }

  // return the class name
  
  String Newton::repr (void) const {
    return "Newton";
  }
  
  // return a clone of this object
  
  Object* Newton::clone (void) const {
    return new Newton (*this);
  }

  // assign an object to this one

  Newton& Newton::operator = (const Newton& that) {
    // check for self assignation
    if (this == &that) return *this;
    // lock and assign
    wrlock ();
    that.rdlock ();
    try {
      d_avf = that.d_avf;
      d_mni = that.d_mni;
      unlock ();
      that.unlock ();
      return *this;
    } catch (...) {
      unlock ();
      that.unlock ();
      throw;
    }
  }

  // find a function root with the newton method

  t_real Newton::solve (const Rfi& fo, const t_real xi) {
    rdlock ();
    try {
      // initialize current point
      t_real xn = xi;
      // get the number of iteration
      long ni = (d_mni <= 0) ? NTW_NI_DEF : d_mni;
      // newton loop
      for (long i = 0; i < ni; i++) {
	// check for zero
	t_real xz = fo.compute (xn);
	if (Math::rcmp (0, xz) == true) {
	  unlock ();
	  return xn;
	}
	// check for null derivative
	t_real xd = fo.derivate (xn);
	if (Math::rcmp (0, xd) == true) {
	  throw Exception ("newton-error", 
			   "null derivative in convergence loop");
	}
	// update delta
	xz /= xd;
	xn -= xz;
	// check for convergence
	if (Math::rcmp (xn, xz) == true) {
	  unlock ();
	  return xn;
	}
      }
      throw Exception ("newton-eror", "zero loop convergence failure");
    } catch (...) {
      unlock ();
      throw;
    }
  }

  // find a system root with the newton method

  Rvi* Newton::solve (const Rni& so, const Rvi& xi) {
    rdlock ();
    // get the number of iteration
    long ni = (d_mni <= 0) ? NTW_NI_DEF : d_mni;
    // initialize the current point
    Rvi* xn = dynamic_cast <Rvi*> (xi.clone ());
    Object::iref (xn);
    // initialize delta, lhs and rhs
    Rvi* xd  = nilp;
    Rvi* rhs = nilp;
    Rmi* lhs = nilp;
    try {
      // create the linear solver
      Linear lnr (d_avf);
      // newton loop
      for (long i = 0; i < ni; i++) {
	// check rhs for zero
	rhs = so.getrhs (xn);
	if (rhs == nilp) {
	  throw Exception ("newton-error", "null rhs in convergence loop");
	}
	if (Math::rcmp (0, rhs->norm ()) == true) {
	  Object::tref (xn);
	  unlock ();
	  return xn;
	}
	// get the lhs by operand
	Rmi* lhs = so.getlhs (xn);
	// try to solve the system
	xd = lnr.solve (*lhs, *rhs);
	// clean elements
	Object::dref (lhs); lhs = nilp;
	Object::dref (rhs); rhs = nilp;
	// check for null delta
	if (xd == nilp) {
	  throw Exception ("newton-error", "null delta in convergence loop");
	}
	// update delta
	*xn += *xd;
	// compute norm
	t_real nx = xn->norm ();
	t_real nd = xd->norm ();
	delete xd; xd = nilp;
	// check for null updated point
	if (Math::rcmp (0, nx) == true) {
	  rhs = so.getrhs (xn);
	  if (rhs == nilp) {
	    throw Exception ("newton-error", "null rhs in convergence loop");
	  }
	  // check for valid point
	  if (Math::rcmp (0, rhs->norm ()) == true) {
	    Object::tref (xn);
	    unlock ();
	    return xn;
	  }
	  // wrong point
	  throw Exception ("newton-error", "invalid point in convergence loop");
	}
	// check for convergence
	if (Math::rcmp (nd, nx) == true) {
	  Object::tref (xn);
	  unlock ();
	  return xn;
	}
      }
      throw Exception ("newton-error", "zero loop convergence failure");
    } catch (...) {
      delete xd;
      Object::dref (xn);
      Object::dref (lhs);
      Object::dref (rhs);
      unlock ();
      throw;
    }
  }
  
  // -------------------------------------------------------------------------
  // - object section                                                        -
  // -------------------------------------------------------------------------

  // the quark zone
  static const long QUARK_ZONE_LENGTH = 1;
  static QuarkZone  zone (QUARK_ZONE_LENGTH);

  // the rvi supported quarks
  static const long QUARK_SOLVE = zone.intern ("solve");

  // create a new object in a generic way
  
  Object* Newton::mknew (Vector* argv) {
    long argc = (argv == nilp) ? 0 : argv->length ();
    
    // check for 0 argument
    if (argc == 0) return new Newton;
    // check for 1 argument
    if (argc == 1) {
      bool avf = argv->getbool (0);
      return new Newton (avf);
    }
    // check for 2 arguments
    if (argc == 2) {
      bool avf = argv->getbool (0);
      long mni = argv->getlong (1);
      return new Newton (avf, mni);
    }
    // invalid arguments
    throw Exception ("argument-error", "invalid arguments with newton object");
  }

  // return true if the given quark is defined

  bool Newton::isquark (const long quark, const bool hflg) const {
    rdlock ();
    if (zone.exists (quark) == true){
      unlock ();
      return true;
    }
    bool result = hflg ? Object::isquark (quark, hflg) : false;
    unlock ();
    return result;
  }
  
  // apply this object with a set of arguments and a quark
  
  Object* Newton::apply (Runnable* robj, Nameset* nset, const long quark,
			 Vector* argv) {
    // get the number of arguments
    long argc = (argv == nilp) ? 0 : argv->length ();
    
    // dispatch 2 arguments
    if (argc == 2) {
      if (quark == QUARK_SOLVE) {
	Object* obj = argv->get (0);
	// check for rfi object
	Rfi* fo = dynamic_cast <Rfi*> (obj);
	if (fo != nilp) {
	  t_real xi = argv->getreal (1);
	  return new Real (solve (*fo, xi));
	}
	// check for rni object
	Rni* so = dynamic_cast <Rni*> (obj);
	if (so == nilp) {
	  throw Exception ("type-error", "invalid object with solve",
			   Object::repr (obj));
	}
	// check for rvi point
	obj = argv->get (1);
	Rvi* xi = dynamic_cast <Rvi*> (obj);
	if (xi == nilp) {
	  throw Exception ("type-error", "invalid object with solve",
			   Object::repr (obj));
	}
	// try to solve the system
	return solve (*so, *xi);
      }
    }
    // call the object
    return Object::apply (robj, nset, quark, argv);
  }
}
