/** @(#) numericalap/finitenewtondogleg.cpp */
//#include "finitenewtondogleg.h"

/** @(#) finitenewtondogleg.h */
#ifndef FINITENEWTONDOGLEG_H
#define FINITENEWTONDOGLEG_H
//----------------------------------------------------------------------------
#include "../adjustedrecipes/qrdcmp.cpp"
#include "../adjustedrecipes/qrsolv.cpp"
#include "../functionnd/functionnd.h"
#include "../functionnd/functionnd_1d.h"
#include "../matrixdouble/squaredmatrix.h"
#include "../mathpos/mathpos.h"	// sign, MACHEPS
#include "../utilpos/interfacepos.h"
#include "factorizecholesky.cpp"
#include "doglegdriver.cpp"
//#include "finitejacobi.cpp";
#include <iostream>	// salida
#include <fstream>
#include <algorithm>	// max
#include <cmath>
#include <iostream>
#include <stdexcept>	// runtime_exception
using namespace std;

void checkFiniteNewtonDogLeg(int n, VectorDouble &xc, double &fc,
		FunctionND_1D &theFunction, VectorDouble &Sx,
		int iterations,
                double maxstep, double steptol,
                double &delta,
        	double eta = MACHEPS,
                int logType = SHORT_LOG, int waitType = NO_LOG,
                string pathFile = "finiteNewtonDogleg.txt")
                throw (invalid_argument, runtime_error);
void finiteNewtonDogLeg(int n, VectorDouble &xc, double &fc,
		FunctionND_1D &theFunction, VectorDouble &Sx,
		int iterations,
                double maxstep, double steptol,
                double &delta,
        	double eta = MACHEPS,
                int logType = SHORT_LOG, int waitType = NO_WAIT,
                string pathFile = "finiteNewtonDogleg.txt")
                throw (runtime_error);

class FunctionG : public FunctionND {
public:
	// No se checa las dimensiones de xc y Fc.
	void evaluate(VectorDouble &xc, VectorDouble &Fc) {
	        xc.setLogic(1);
	        Fc.setLogic(1);

                // Funcion del usuario
		Fc[1] = 4.0*pow(xc[1], 3.0) + 2.0*xc[1];
		Fc[2] = 2.0*xc[2];
        }
}; // No olvidar ";"

void hessianEvaluate(VectorDouble &xc, SquaredMatrix &H) {
	xc.setLogic(1);
        H.setLogic(1);

	// Funcion del usuario
        H[1][1] = 12.0 * pow(xc[1], 2.0) + 2.0;
        H[1][2] = 0.0;
        H[2][1] = 0.0;
        H[2][2] = 2.0;
}

/** Aproximacion por diferencias finitas por el metodo de quasi Newton
* Para la decision del paso elige entre elpaso de Newton o el DogLeg.
* No se checan dimensiones. La dimension n se toma de la dimension de xc.
* Logica uno.
* @author Daniel Alba Cuellar, Omar Posada Villarreal
* @version 1.0, 22/04/2002
* @param n Entero. Natural. Dimension de las matrices y vectores.
* @param xc IN/OUT
*	IN: Vector con punto de prueba inicial.
*	OUT: Solucion
* @param fc IN/OUT
*	IN: Vector con evaluacion punto de prueba inicial.
*	OUT: Evaluacion de la solucion
* @param theFunction Nombre de la funcion. F:R^n->R^n. FVEC.
* @param Sx Escala. Valores usuales.
* @param maxstep Maximo taman~o de paso. Dado por QuasiNewton
* @param steptol Precision del paso
* @param delta in/out Radio de la region de confianza.
* @param xp return x+
* @param fp return f+
* @param eta Precision de la maquina. Omision: mathpos::MACHEPS.
* @param deb debug. Mostrar resultados.
*	0: No mostrar.
*	1: Mostrar resumen, version corta.
*	2: Mostrar pasos intermedios, version larga.
* @param logType
* @see pages 314-315
*/
/*
* @param H return. Matriz cuadrada Hessiana.
* @param fc return Vector sobreescrito Entrada/Salida.
*/
void finiteNewtonDogLeg(int n, VectorDouble &xc, double &fc,
		FunctionND_1D &theFunction, VectorDouble &Sx,
		int iterations,
                double maxstep, double steptol,
                double &delta,
        	double eta,
                int logType, int waitType,
                string pathFile)
                throw (runtime_error) {
// QUITAR MAX_LOG Y OTROS, CAMBIAR LA FIRMA DE LAS FUNCIONES

// En depuracion usar checkMethod, al final del archivo
	LLOG << "\n{ finiteNewton";

        // log file
        ofstream outFile(pathFile.data(), ios::out);
	if ( !outFile ) {	// !sobrecargado
		throw runtime_error(
                "finiteNewtonDogLeg: Error de apertura de archivo.");
        }

	// { posada
	xc.setLogic(1);
	Sx.setLogic(1);
	//H.setLogic(1);
	// } posada

        FunctionG	funcGrad;
        SquaredMatrix H(n, 1);		// Hessiana
        SquaredMatrix R(n, 1);		// Factoriz QR
        SquaredMatrix L(n, 1);		// Factoriz Cholesky
	VectorDouble c(n, 1);          // used by qrdcmp
	VectorDouble d(n, 1);          // used by qrdcmp
        VectorDouble	g(n, 1);	// grad(f(xk))
        VectorDouble	xp(n, 1);	// x+

        VectorDouble	sN(n, 1);	// en el libro lo llama sk^N
        	// param de IN/OUT, en Ax=b en IN es b, y en OUT x
		//sN Solucion aproximada a H(f(xk)) sk^N = - grad(f(xk))
	bool maxtaken;	// x+ esta en el radio de la region de confinza
        int	retcode;
	int i, k;
        int sing;
        double	cond;
        double	mx, num, den, aux;	// condicion de paro
        double	fp;	// f+
        bool	Newttaken;

	// Encabezado
        SLOG << "Iter\tRadio1\tRadio2\tPaso\tfc\tdim(xc)\txc";
        FLOG << "Iter\tRadio1\tRadio2\tPaso\tfc\tdim(xc)\txc";

        // iteracion 0
        SLOG << "\n0\t" << delta << "\t" << delta << "\tInicial"
        	<< "\t" << fc << "\t" << n << "\t" << xc;
        FLOG << "\n0\t" << delta << "\t" << delta << "\tInicial"
        	<< "\t" << fc << "\t" << n << "\t" << xc;

	fc = theFunction.evaluate(xc);	// antes param

        // Valor tipico de f typ f
	double typF = theFunction.evaluate(Sx);


	// newton iteration
	for (k=1; k<=iterations; k++) {
		funcGrad.evaluate(xc, g);
		hessianEvaluate(xc, H);
////      finiteJacobi(n, xc, Fc, theFunction, Sx, J, eta); //aprox por dif fin
////	Fc.multiply(-1.0, negFc);
		sing = qrdcmp(H, n, c, d); // aproximacion

		// Si es singular, no se puede buscar la condicion.
		if (sing) {
        		throw runtime_error(
                        "\nfiniteNewtonDogLeg: La matriz es singular.");
                } else {
			//sN Solucion aproximada a H(f(xk)) sk^N = -grad(f(xk))
			R.createRMatrix(H, d);
/*        qrdcmp(fQR, n, c, fd, &sing);
	        createRMatrix(QR, d, R, n);
	        double cond = condest(R, n);
*/
			cond = R.condition();
                        if (cond > (1.0 / eta)) {
                          throw runtime_error(
                          "\nfiniteNewtonDogLeg: Hessiano mal condicionado.");
                        }
                        sN = g * -1.0;
			qrsolv(H, n, c, d, sN); //sN in/out

                        // Obtener triang inf de hessiana
			if ( !factorizeCholesky(n, H, L)) {
                          throw runtime_error(
                          "\nfiniteNewtonDogLeg: No se pudo factorizar H.");
                        }

                        // delta es in/out
			SLOG << "\n" << k << "\t" << delta; // IN
			FLOG << "\n" << k << "\t" << delta; // IN

	        	// en dogdriver se actualiza x
			doubleDogLegDriver(n, xc, fc,
				theFunction, g, L,
		                sN, Sx,
		                maxstep, steptol,
		                delta,
		                retcode, xp, fp, maxtaken, Newttaken);
			SLOG << "\t" << delta; // OUT
			FLOG << "\t" << delta; // OUT
                        if (maxtaken) {
                        	LLOG << "\nx+ esta cerca del"
                                	<< " radio de la region de confinza.";
                        }
                        // duda con las evaluaciones de fp y fc
                        if (Newttaken) {
                        	// Usa el paso de Newton
                        	xc = xc + sN;
				fc = theFunction.evaluate(xc);
                                SLOG << "\tNewton";
                                FLOG << "\tNewton";
                        } else {
                        	// Usa el doglegstep
                        	xc = xp;
                                fc = fp;
                                SLOG << "\tDogleg";
                                FLOG << "\tDogleg";
                        }

/*	0: x+ satisfactorio encontrado
*	1: la rutina fallo para encontrar una x+ satisfactoria,
*	suficientemente distinta de xc.
*/
                        //if (retcode == 0) { // No hacer nada
                        if (retcode == 1) {
				LLOG << "\n  } finiteNewtonDogLeg";
				return;
                        }

                        SLOG << "\t" << fc << "\t" << n << "\t" << xc;
                        FLOG << "\t" << fc << "\t" << n << "\t" << xc;

                        // Criterio de paro
                        // Dennis. pag 160. Ecuacion (7.2.5)
                        // max (i=[1, n]) | num/den | <= steptol
                        // num = grad(f(x)i max{|(x+)i|, typ xi}
                        // den = max{|f(x+)|, typ f}
                        // typf = f(xc)
                        den = max( fabs(fc), fabs(typF));
                        if (isAlmostZero(den)) {
                          throw runtime_error(
                          "\nfiniteNewtonDogLeg: Division entre cero.");
                        }

                        mx = -1;	// mx debe ser positivo
                        for (i = 1; i <= n; ++i) {
                        	num = g[i] * max( fabs(xp[i]), 1.0/Sx[i] );
                                aux = fabs(num / den);
                                if (aux > mx) {
                                	mx = aux;
                                }
                        }

			LLOG << "\nCondicion de paro. mx < steptol ("
                        	<< mx << " < " << steptol << "): "
                                << toStr(mx < steptol);
                        if (mx < steptol) {
				LLOG << "\n  } finiteNewtonDogLeg";
				return;
                        }
                        SWAIT();

		} // if                    1

	}
	LLOG << "\n  } finiteNewtonDogLeg";
}

/** Checa parametros: dimensiones, rangos. */
void checkFiniteNewtonDogLeg(int n, VectorDouble &xc, double &fc,
		FunctionND_1D &theFunction, VectorDouble &Sx,
		int iterations,
                double maxstep, double steptol,
                double &delta,
        	double eta,
                int logType, int waitType,
                string pathFile)
                throw (invalid_argument, runtime_error) {
	// Condiciones invalidas
        if ( (n < 1)
        	|| (xc.getSize() != n)
        	|| (Sx.getSize() != n)
                || (iterations < 1)
                || (delta < 0.0)
                || (eta < 0.0)
        	) {
        	throw invalid_argument("\nfiniteNewtonDogLeg: arg invalidos.");
        }
        // Prevee division entre cero
        if (Sx.hasAlmostZeros()) {
        	throw invalid_argument(
                	"finiteNewtonDogLeg: Division entre cero.");
        }
	finiteNewtonDogLeg(n, xc, fc, theFunction, Sx, iterations,
        	maxstep, steptol, delta, eta,
                logType, waitType, pathFile);
}

//----------------------------------------------------------------------------
#endif
// Fin------------------------------------------------------------------------