// Programa de diagonalizacion por Jacobi
//con busqueda del mayor elemento fuera de la diagonal
#include <iostream>
#include <cmath>
#include <iomanip>
#include <fstream>
using namespace std;

//Numero de digitos y precision
const int nwide=10;
const int nprec=6;

void
jacobi(int n, double **m, double **v)
{

//Numero maximo de iteraciones
    int maxiter = 100;

//Tolerancia
    double tol = 1e-12;

//definir v(matriz vectores propios) como matriz identidad
    for (int i = 0; i < n; i++)
    {
        for (int j = 0; j < n; j++)
            v[i][j] = 0;
        v[i][i] = 1;
    }

//Calcular Delta
    double delta = 0.;

    for (int i = 0; i < n; i++)
        for (int j = i + 1; j < n; j++)
            delta = delta + m[i][j] * m[i][j];

    cout << "Delta inicial = " << delta << endl;

//ciclo de iteraciones
    for (int i = 0; i < maxiter; i++)
    {
//Busqueda del mayor elemento fuera de la diagonal
//Esto no es eficiente para grandes matrices. Hacer un barrido en este caso.
        double apq = 0;
        int ip = 0;
        int iq = 1;

        for (int j = 0; j < n; j++)
        {
            for (int k = j + 1; k < n; k++)
            {
                if (abs(m[j][k]) > apq)
                {
                    ip = j;
                    iq = k;
                    apq = abs(m[ip][iq]);

                }
            }
        }
        cout << "p= " << ip << " q= " << iq << " apq= " << apq << endl;

//Determinar c, s, c, tau
        double t = 1;

        if (m[ip][ip] != m[iq][iq])
        {
            double alpha = (m[iq][iq] - m[ip][ip]) / (2*m[ip][iq]);

            t = -alpha + alpha / abs(alpha) * sqrt(alpha * alpha + 1);
        }
        double c = 1 / sqrt(t * t + 1);
        double s = t * c;
        double tau = s / (1 + c);
        double d = m[ip][iq];

        cout << "  s= " << s << "  c= " << c << " t=" << t << " tau=" << tau << endl << endl;

//Actualizar matriz m en y por encima de la diagonal
        m[ip][ip] = m[ip][ip] - t * m[ip][iq];
        m[iq][iq] = m[iq][iq] + t * m[ip][iq];

        for (int j = 0; j < ip; j++)
        {
            double temp1 = m[j][ip];
            double temp2 = m[j][iq];

            m[j][ip] = temp1 - s * (temp2 + tau * temp1);
            m[j][iq] = temp2 + s * (temp1 - tau * temp2);
        }
        for (int j = ip + 1; j < iq; j++)
        {
            double temp1 = m[ip][j];
            double temp2 = m[j][iq];

            m[j][iq] = temp2 + s * (temp1 - tau * temp2);
            m[ip][j] = temp1 - s * (temp2 + tau * temp1);
        }
        for (int j = iq + 1; j < n; j++)
        {
            double temp1 = m[ip][j];
            double temp2 = m[iq][j];

            m[iq][j] = temp2 + s * (temp1 - tau * temp2);
            m[ip][j] = temp1 - s * (temp2 + tau * temp1);
        }
        m[ip][iq] = 0;

//Imprimir m en cada iteracion
        cout << "Matriz iteracion " << i + 1 << endl;
        for (int j = 0; j < n; j++)
        {
            cout << endl;
            for (int k = 0; k <= j; k++)
                cout << setw(nwide) <<  m[k][j] << "   ";
        }
        cout << endl << endl << endl;

//Actualizar v
        for (int j = 0; j < n; j++)
        {
            double temp1 = v[j][ip];
            double temp2 = v[j][iq];

            v[j][ip] = c * temp1 - s * temp2;
            v[j][iq] = s * temp1 + c * temp2;
        }

//Imprimir v

        cout << "V iteracion " << i + 1 << endl;
        for (int j = 0; j < n; j++)
        {
            cout << endl;
            for (int k = 0; k < n; k++)
                cout << setw(nwide) << v[j][k] << "   ";
        }
        cout << endl << endl;

//Actualizar delta
        delta = delta - d * d;

        cout << " Delta= " << delta << endl << endl;

//Si ha convergido actualizar diagonal inferior de m y volver a main
        if (abs(delta) <= tol)
        {
            for (int j = 0; j < n; j++)
                for (int k = j + 1; k < n; k++)
                    m[k][j] = m[j][k];
            cout << " Jacobi ha convergido  con " << i + 1 << " iteraciones" << endl << endl;
            cout << "Delta= " << delta << endl << endl;
            return;
        }
    }
//Ha hecho maxiter iteraciones
    cout << "Numero de  maximo de iteraciones en jacobi sin convergencia" << endl << endl;
    cout << "  Delta= " << delta << endl << endl;
}


int
main()
{
//Definir data file
    ifstream fin("jacobi.dat");
    ofstream fout("res.dat");
    int n;

//Leer dimension del sistema(jacobi.dat)
    fin >> n;

//Definir matriz coeficientes

    double **mat;
    mat = new double *[n];

    for (int i = 0; i < n; i++)
        mat[i] = new double[n];

//Definir matriz vectores propios
    double **vecp;
    vecp = new double *[n];

    for (int i = 0; i < n; i++)
        vecp[i] = new double[n];


//Leer matriz en jacobi.dat;
    for (int i = 0; i < n; i++)
        for (int j = 0; j < n; j++)
            fin >> mat[i][j];

//Verificar lectura datos
    for (int i = 0; i < n; i++)
    {
        cout << endl;
        for (int j = 0; j < n; j++)
            cout << setw(nwide) << mat[i][j] << "   ";
    }
    cout << endl;

    jacobi(n, mat, vecp);

//Imprimir matriz diagonalizada

    cout << "Matriz diagonalizada" << endl;
    for (int i = 0; i < n; i++)
    {
        cout << endl;
        for (int j = 0; j < n; j++)
            cout << setw(nwide) << mat[i][j] << "   ";
    }
    cout << endl << endl;
//Imprimir valores propios

    fout << "Los valores propios son "<< endl;
    for (int i = 0; i < n; i++)
    {
        fout << endl;
        fout << mat[i][i] << "   ";
    }
    fout << endl << endl;
//Imprimir matriz vectores propios

    cout << "Vectores propios (columnas) " << endl;
    for (int i = 0; i < n; i++)
    {
        cout << endl;
        for (int j = 0; j < n; j++)
            cout << setw(nwide) << vecp[i][j] << "   ";
    }
    cout << endl << endl;
   
    fout << "Vectores propios (columnas) " << endl;
    for (int i = 0; i < n; i++)
    {
        fout << endl;
        for (int j = 0; j < n; j++)
            fout << setw(nwide) << vecp[i][j] << "   ";
    }
    fout << endl << endl;

//Comprobacion ortogonalidad vectores propios

    cout << "Productos escalares de vectores propios" << endl << endl;

    for (int i = 0; i < n; i++)
    {
        for (int j = i; j < n; j++)
        {
            double temp = 0;

            for (int k = 0; k < n; k++)
                temp = temp + vecp[k][i] * vecp[k][j];
            cout << setw(nwide) <<temp << "  ";
        }
        cout << endl;
    }
//Liberar memoria
    for (int j = 0; j < n; j++) delete[] mat[j];
    delete[] mat;
    delete[] vecp;
    mat = 0;
    vecp = 0;
    return 0;
}
