/** @(#) doglegstep.cpp */

// #include "ap.h"
/** @(#) .h */
/**
* @author Daniel Alba Cuellar
* @author Omar Posada Villarreal
* @version 1.0, /03/2002
*/
#ifndef DOGLEGSTEP_H
#define DOGLEGSTEP_H
//----------------------------------------------------------------------------
#include "../matrixdouble/squaredmatrix.h"
#include "../matrixdouble/vectordouble.h"
#include "../mathpos/mathpos.h"
#include <iomanip>
#include <iostream>
#include <string>
#include <stdexcept>	// runtime_exception
using namespace std;

/** Busca una solucion aproximada a
* min g^T s + 1/2 s^T L L^T s
* s E R^n
* sujeto a ||Dx s||2 <= delta
* mediante la seleccion de s en la curca del double dogleg
* descrito abajo de tal forma que ||Dx s||2 = delta,
* o s = SN si Newtlen <= delta. (Dx = diag ((Sx)1, ..., (sx)n).
* Logica uno.
* @author Omar Posada Villarreal, Daniel Alba Cuellar
* @param n Taman~o de vectores y matrices. No se checan dimensiones.
* @param g Gradiente
* @param L Triangular inferior. Se usa para construir el inverso de la Hessiana.
* @param sN = - (L L^T)^-1 g)
* @param Sx
* @param Newtlen = ||Dx sN||2
* @param maxstep Maximo taman~o de paso. Dado por QuasiNewton
* @param delta in/out
* @param firstdog in/out
*	false: Cauchylen, eta, sSD, v
*		tendran valores actuales en in/out.
*	true: Los parametros mencionados no se tomaran en cuenta.
*		Inicializa los parametros.
* @param Cauchylen in/out
* @param eta in/out gamma <= eta <=1
* @param sSD in/out
* @param v in/out
* @param s return Paso escogido
* @param Newttaken return
*	true: Se usa el paso de Newton
*	false: se usa el dogleg step
* @version 1.0, 20/04/2002, sig vers LowerTriangularMatrix &L
* @see Dennis. A6.4.4. DOGSTEP pagina 336
*/
// Ver codigo de preuba al final
void doubleDogLegStep(int n, VectorDouble &g, SquaredMatrix &L,
		VectorDouble &sN, VectorDouble &Sx, double Newtlen,
                double maxstep,
                double &delta, bool &firstdog, double &Cauchylen,
                double &eta, VectorDouble &sSD, VectorDouble &v,
        	VectorDouble &s, bool &Newttaken,
		int logType = SHORT_LOG, int waitType = NO_WAIT)
                throw (invalid_argument) {
	LLOG << "\n{ doubleDogLegStep";

        // Es una matriz cuadrada, pero se almacenara como un vector a Sx
	VectorDouble Dx(Sx);

	VectorDouble DxInv(n, 1);
	VectorDouble DxInv_g(n, 1);
	VectorDouble Dx_sN(n, 1);
	VectorDouble DxInv_sSD(n, 1);

	VectorDouble eta_Dx_sN(n, 1);
        VectorDouble lambda_v(n, 1);
        VectorDouble sSD_lambda_v(n, 1);

	double temp, tempv;
        double gT_sN;
        double alfa, beta, lambda, gamma;
        int i, j;

// A6.4.4
	if (Newtlen <= delta) {		// 1.
        				// 1T. s es el paso de Newton
        	Newttaken = true;	// 1T.1.
                s = sN;			// 1T.2. s = sN. antes copyFrom()
                delta = Newtlen;	// 1T.3.
                // A6.4.4. return desde aqui
        } else {			// 1E.
        	// el paso de Newton es muy largo, s en curva double dogleg
                Newttaken = false;      // 1E.1.
                if (firstdog) {		// 1E.2.
                	// Calcula la curva double dogleg
                        // Calcula los 4 parametros de in/out:
                        // Cauchylen, eta, sSD, v
                        firstdog = false;	// 1E.2.1.

                        // alfa = ||Dx^-1g||2 ^2
                        // Dx = diag((Sx)1, ... ,(Sx)n)
                        // se almacena como Sx E R^n..
                        // Para implementacion sugerida de expresiones
                        // que conciernen a Dx, ver seccion 1.5 del apendice
                        DxInv = Dx.inverse();	// 1E.2.2.
                        DxInv_g = DxInv.diagonalProduct(g);
                        //LLOG << "\nDx = " << Dx;
                        //LLOG << "\nDxInv = " << DxInv;
                        //LLOG << "\nDxInv_g = " << DxInv_g;
                        alfa = square( DxInv_g.norm2() );

                        // 1E.2.3-4. Implementan beta = ||L^T Dx^-2 g||2 ^2
                        beta = 0.0;		// 1E.2.3.
                        for (i = 1; i <= n; ++i) {	// 1E.2.4.
                        				// 1E.2.4.1.
                        	temp = 0.0;	// = sum
	                        for (j = 1; j <= n; ++j) {
        // Checar vs [][]
	                        	temp += (L(j,i) * g(j))
                                        	/ square( Sx(j) );
                                        //LLOG << "\nL(j,i) = " << L(j,i);
                                        //LLOG << "\ng(j) = " << g(j);
                                }
                                beta += square(temp);	// 1E.2.4.2.
                        }

                        // ^sSD es el paso de Cauchy en metrica escalada
                        // ssD en descripcion es Dx^-1 ^sSD
                        sSD = DxInv_g * (-alfa/beta);	// 1E.2.5.

                        // Cauchylen = ||^sSD||2
                        Cauchylen = pow(alfa, 1.5) / beta;	// 1E.2.6.
                        LLOG << "\ndelta = " << delta;	// didac
                        LLOG << "\n||s^CP||2. Cauchylen = " << Cauchylen;
                        	// didac
                        LLOG << "\nDentro de la region de confianza?"
                        	<< " CP < delta = "
                        	<< toStr(Cauchylen < delta);
                                // didac

                        // Son vectores g.transpose().multiply(sN);
                        gT_sN = g * sN; // innerProduct()

                        // eta <= 1. ver seccion 6.4.2.
                        					// 1E.2.7.
                        gamma = square(alfa) / (beta * fabs(gT_sN)); // didac
                        eta = 0.2 + (0.8 * gamma);
                        LLOG << "\ngamma <= eta <= 1. gamma = " << gamma;
                        	// didac
                        					// 1E.2.8.
                        // ^v es (eta sN - sSD) en la metrica escalada.
                        // ^v = eta Dx sN - sSD
                        Dx_sN = Dx.diagonalProduct(sN);	// No usar Dx*sN
                        eta_Dx_sN = Dx_sN * eta;
                        v = eta_Dx_sN - sSD;

                        if ( areAlmostEqual(delta, -1) ) {	// 1E.2.9.
	                        // 1a iteracion, y el usuario no dio una region
        	                // confiable inicial
                        	delta = min(Cauchylen, maxstep);
                        }
                } // 1E.2.
                if ((eta * Newtlen) <= delta) {	// 1.E.3a.
	                // Toma un paso intermedio en la direccion de Newton
                	s = sN * (delta / Newtlen);	// 1.E.3a.1.
                        // A6.4.4 regresa de aqui
                } else if (Cauchylen >= delta) {	// 1.E.3b
                	// Toma un paso en la direccion descendiente
                        // de mayor profundidad
                        				// 1.E.3b.1
                        // s = (delta / Cauchylen) * Dx^-1 ^sSD
                        DxInv = Dx.inverse();
                        DxInv_sSD = Dx.diagonalProduct(sSD);
                        s = DxInv_sSD * (delta / Cauchylen);

                        // A6.4.4 regresa de aqui
                } else {			// 1.E.3c
                	// Calcula la combinacion convexa de sSD y eta sN
                        // (= DX^-1 ^sSD + lambda Dx^-1 ^v)
                        // que tiene largo escalado = delta
                        temp = v * sSD;		// 1.E.3c.1 innerProduct()
                        tempv = v * v;		// 1.E.3c.2 innerProduct()
                        				// 1.E.3c.3
                        lambda = (sqrt(
                        		square(temp)
                        		- (tempv * (
                                        	square(Cauchylen)
                                                - square(delta))))
                        	- temp) / tempv;
                					// 1.E.3c.4.
                        // s = Dx^-1 (^sSD + lambda ^v)
                        DxInv = Dx.inverse();
                        lambda_v = v * lambda;
                        sSD_lambda_v = sSD + lambda_v;
                        s = DxInv.diagonalProduct(sSD_lambda_v);
                } // 1E.3c.
	} // 1.E.
	LLOG << "\n  } doubleDogLegStep";
}
//----------------------------------------------------------------------------
#endif
/* CODIGO DEL MAIN PARA PROBAR TESTDOGLEG
int main() {
  try {
        int n = 2; 	//dimension
	double fc;	// escalar
        VectorDouble 	xc(n, 1);
        VectorDouble 	Sx(n, 1);
        SquaredMatrix 	H(n, 1);
        Function1 	func;

	// Proceso
        VectorDouble g(n, 1);
        SquaredMatrix L(n, 1);
        VectorDouble sN(n, 1);
        //VectorDouble Sx(n, 1);
        double Newtlen;
	double maxstep;
        double delta;
        bool firstdog;
        double Cauchylen;
        double eta;
        VectorDouble sSD(n, 1);
        VectorDouble v(n, 1);
        VectorDouble s(n, 1);
        bool Newttaken;

        SquaredMatrix LT_L(n, 1);
        SquaredMatrix LT_LInv(n, 1);

        /*
        xc = [ 1	1 	]
        H(xc) =
        [ 14	0	]
	[ 0	2	]

        H^-1(xc) =
        [ 0.0714286	0	]
        [ 0		0.5	]
        */
/*	xc[1] = 1;
	xc[2] = 1;

        Sx[1] = 1;
        Sx[2] = 1;

        H[1][1] = 14;
        H[1][2] = 0;
        H[2][1] = 0;
        H[2][2] = 2;

	if (!factorizeCholesky(n, H, L) ) {
        	throw runtime_error("No se pudo factorizar.");
        }
        g[1] = 6.0;
        g[2] = 2.0;

        //LT_L = L.transpose() * L;
        LT_LInv[1][1] = 1.0/14.0;
        LT_LInv[2][2] = 1.0/2.0;
        //error sN = (LT_LInv * g) * (-1.0);
        sN[1] = -0.428571;
        sN[2] = -1.0;


        // Es una matriz cuadrada, pero se almacenara como un vector a Sx
        VectorDouble Dx(Sx);
        VectorDouble Dx_sN(n, 1);

        Dx_sN = Dx.diagonalProduct(sN);
        Newtlen = Dx_sN.norm2();

        // Dado por el usuario
        maxstep = 1.0;
        // Dado por el usuario
        delta = 0.75; //ejemplo

        firstdog = true; // init updTrusReg

        cout << "\nPunto inicial. xc: " << xc;
        cout << "\nHessiana. H:\n" << H;
	waitEnter();
	cout << "\n-----IN-----";
	cout << "\nDimension. n = " << n;
	cout << "\ngrad(f(xc)). g = " << g;
        cout << "\nTriangular inferior del Hessiano. L:\n" << L;
        cout << "\nsc^N = -Hc^-1 grad(f(xc)). sN:\n" << sN;
        cout << "\nEscala metrica. Sx: " << Sx;
	cout << "\nPaso de Newton. Newtlen = " << Newtlen;
	cout << "\nMaxima delta. maxstep = " << maxstep;
	cout << "\nRadio de la region de confianza. delta = " << delta;
	cout << "\ntrue: 1a. iteracion. firstdog = " << firstdog;
	cout << "\n-----doubleDogLegStep-----";

	doubleDogLegStep(n, g, L,
		sN, Sx, Newtlen,
                maxstep, delta, firstdog,
                Cauchylen, eta, sSD,
                v,
        	s, Newttaken);

	cout << "\n-----IN/OUT-----";
	cout << "\n||s^CP||2. Cauchylen = " << Cauchylen;
	cout << "\nFactor para pasar de CP a ^N. eta = " << eta;
	cout << "\ns^CP, vector de xc a CP. sSD = " << sSD;
	cout << "\neta s^(^N) - s^CP. Vector para pasar de CP a ^N. v = " << v;
	cout << "\n-----OUT-----";
	cout << "\ns^CP + lambda v. Paso. s = " << s;
	cout << "\ntrue: se tomo el paso de Newton. Newttaken = "
        	<< toString(Newttaken);

        /* Resultado de prueba. xc = (2, 2)T. H(xc):
	* [13	-4]
        * [-4	1 ]
        * Como esta especificado el algoritmo, solo se encuentra la H
        * para la diagonal y la matriz triangular superior.
        * El resto no se toca porque es simetrica.
        */
/*  } catch (exception e) {
  	cout << "\nmain: Excepcion " << e.what();
  }
	// Terminacion
	cout << "\n***Fin***";
	waitEnter();
	//si no hay pausa cin.get();
        //cin.get();
	return 0;
}  Fin TEST-----------------------------------------------------------------
*/

// Fin------------------------------------------------------------------------