/*
 * Copyright (C) 2003-2006 Bjørn-Ove Heimsund
 * 
 * This file is part of MTJ.
 * 
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by the
 * Free Software Foundation; either version 2.1 of the License, or (at your
 * option) any later version.
 * 
 * This library 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. See the GNU Lesser General Public License
 * for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library; if not, write to the Free Software Foundation,
 * Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 */

package no.uib.cipr.matrix;

import java.util.Arrays;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;

import no.uib.cipr.matrix.EVD;
import no.uib.cipr.matrix.Matrix;
import no.uib.cipr.matrix.NotConvergedException;
import no.uib.cipr.matrix.SVD;
import no.uib.cipr.matrix.Vector;

/**
 * Utilities for the testers
 */
public final class Utilities {

    private Utilities() {
        // No need to instantiate
    }

    /**
     * Populates the sparse matrix in a symmetric fashion
     * 
     * @param A
     *            Matrix to populate
     * @param num
     *            Number of entries on each symmetry band
     * @return The matrix data in dense format
     */
    public static double[][] symmetryPopulate(Matrix A, int num) {
        int n = A.numRows(), m = A.numColumns();
        if (m != n)
            throw new IllegalArgumentException("m != n");
        double[][] values = new double[n][m];
        if (n == 0)
            return values;

        for (int j = 0; j < m; ++j)
            for (int i = 0; i < num; ++i) {
                double value = Math.random();
                int k = (int) (Math.random() * n);
                values[k][j] = value;
                values[j][k] = value;
                A.set(k, j, value);
                A.set(j, k, value);
            }
        return values;
    }

    /**
     * Populates the matrix, putting a given number of entries on each column
     * 
     * @param A
     *            Matrix to populate
     * @param num
     *            Number of entries on each column
     * @return The matrix data in dense format
     */
    public static double[][] columnPopulate(Matrix A, int num) {
        int n = A.numRows(), m = A.numColumns();
        double[][] values = new double[n][m];
        if (n == 0)
            return values;

        for (int j = 0; j < m; ++j)
            for (int i = 0; i < num; ++i) {
                double value = Math.random();
                int k = (int) (Math.random() * n);
                values[k][j] = value;
                A.set(k, j, value);
            }
        return values;
    }

    /**
     * Populates the matrix, putting a given number of entries on each row
     * 
     * @param A
     *            Matrix to populate
     * @param num
     *            Number of entries on each row
     * @return The matrix data in dense format
     */
    public static double[][] rowPopulate(Matrix A, int num) {
        int n = A.numRows(), m = A.numColumns();
        double[][] values = new double[n][m];
        if (m == 0)
            return values;

        for (int i = 0; i < n; ++i)
            for (int j = 0; j < num; ++j) {
                double value = Math.random();
                int k = (int) (Math.random() * m);
                values[i][k] = value;
                A.set(i, k, value);
            }
        return values;
    }

    /**
     * Gets a row-wise non-zero pattern suitable for creating compressed row
     * matrices
     * 
     * @param n
     *            Number of rows
     * @param m
     *            Number of columns
     * @param b
     *            Number of entries on each row
     */
    public static int[][] getRowPattern(int n, int m, int b) {
        int[][] nz = new int[n][];

        for (int i = 0; i < n; ++i) {
            Set<Integer> row = new HashSet<Integer>();
            for (int j = 0; j < b; ++j)
                row.add(getInt(m));

            nz[i] = new int[row.size()];
            int j = 0;
            for (Integer colind : row)
                nz[i][j++] = colind;
        }

        return nz;
    }

    /**
     * Gets a column-wise non-zero pattern suitable for creating compressed
     * column matrices
     * 
     * @param n
     *            Number of rows
     * @param m
     *            Number of columns
     * @param b
     *            Number of entries on each column
     */
    public static int[][] getColumnPattern(int n, int m, int b) {
        int[][] nz = new int[m][];

        for (int i = 0; i < m; ++i) {
            Set<Integer> column = new HashSet<Integer>();
            for (int j = 0; j < b; ++j)
                column.add(getInt(n));

            nz[i] = new int[column.size()];
            int j = 0;
            for (Integer rowind : column)
                nz[i][j++] = rowind;
        }

        return nz;
    }

    /**
     * Populates the matrix, using the given non-zero pattern
     * 
     * @param A
     *            Matrix to populate
     * @param nz
     *            Column indices on each row
     * @return The matrix data in dense format
     */
    public static double[][] rowPopulate(Matrix A, int[][] nz) {
        int n = A.numRows(), m = A.numColumns();
        double[][] values = new double[n][m];
        if (m == 0)
            return values;

        for (int i = 0; i < n; ++i)
            for (int j = 0; j < nz[i].length; ++j) {
                double value = Math.random();
                int k = nz[i][j];
                values[i][k] = value;
                A.set(i, k, value);
            }

        return values;
    }

    /**
     * Populates the matrix, using the given non-zero pattern
     * 
     * @param A
     *            Matrix to populate
     * @param nz
     *            Row indices on each column
     * @return The matrix data in dense format
     */
    public static double[][] columnPopulate(Matrix A, int[][] nz) {
        int n = A.numRows(), m = A.numColumns();
        double[][] values = new double[n][m];
        if (n == 0)
            return values;

        for (int j = 0; j < m; ++j)
            for (int i = 0; i < nz[j].length; ++i) {
                double value = Math.random();
                int k = nz[j][i];
                values[k][j] = value;
                A.set(k, j, value);
            }

        return values;
    }

    /**
     * Populates the matrix fully
     * 
     * @param A
     *            Matrix to populate
     * @return The matrix data in dense format
     */
    public static double[][] populate(Matrix A) {
        int n = A.numRows(), m = A.numColumns();
        double[][] values = new double[n][m];
        for (int i = 0; i < n; ++i)
            for (int j = 0; j < m; ++j) {
                double value = Math.random();
                values[i][j] = value;
                A.set(i, j, value);
            }
        return values;
    }

    /**
     * Populates the banded matrix
     * 
     * @param A
     *            Matrix to populate
     * @param kl
     *            Number of subdiagonls
     * @param ku
     *            Number of superdiagonals
     * @return The matrix data in dense format
     */
    public static double[][] bandPopulate(Matrix A, int kl, int ku) {
        int n = A.numRows(), m = A.numColumns();
        double[][] values = new double[n][m];
        for (int i = 0; i < n; ++i)
            for (int j = 0; j < m; ++j)
                if (j - ku <= i && i <= j + kl) {
                    double value = Math.random();
                    values[i][j] = value;
                    A.set(i, j, value);
                }
        return values;
    }

    /**
     * Populates the banded matrix, but not its main diagonal
     * 
     * @param A
     *            Matrix to populate
     * @param kl
     *            Number of subdiagonls
     * @param ku
     *            Number of superdiagonals
     * @return The matrix data in dense format
     */
    public static double[][] unitBandPopulate(Matrix A, int kl, int ku) {
        int n = A.numRows(), m = A.numColumns();
        double[][] values = new double[n][m];
        for (int i = 0; i < n; ++i)
            for (int j = 0; j < m; ++j)
                if (i != j && j - ku <= i && i <= j + kl) {
                    double value = Math.random();
                    values[i][j] = value;
                    A.set(i, j, value);
                }
        return values;
    }

    /**
     * Populates the lower triangular part of the matrix
     * 
     * @param A
     *            Matrix to populate
     * @return The matrix data in dense format
     */
    public static double[][] lowerPopulate(Matrix A) {
        int n = A.numRows(), m = A.numColumns();
        double[][] values = new double[n][m];
        for (int i = 0; i < n; ++i)
            for (int j = 0; j < m; ++j)
                if (j <= i) {
                    double value = Math.random();
                    values[i][j] = value;
                    A.set(i, j, value);
                }
        return values;
    }

    /**
     * Populates the upper triangular part of the matrix
     * 
     * @param A
     *            Matrix to populate
     * @return The matrix data in dense format
     */
    public static double[][] upperPopulate(Matrix A) {
        int n = A.numRows(), m = A.numColumns();
        double[][] values = new double[n][m];
        for (int i = 0; i < n; ++i)
            for (int j = 0; j < m; ++j)
                if (i <= j) {
                    double value = Math.random();
                    values[i][j] = value;
                    A.set(i, j, value);
                }
        return values;
    }

    /**
     * Populates the strictly lower triangular part of the matrix
     * 
     * @param A
     *            Matrix to populate
     * @return The matrix data in dense format
     */
    public static double[][] unitLowerPopulate(Matrix A) {
        int n = A.numRows(), m = A.numColumns();
        double[][] values = new double[n][m];
        for (int i = 0; i < n; ++i)
            for (int j = 0; j < m; ++j)
                if (j < i) {
                    double value = Math.random();
                    values[i][j] = value;
                    A.set(i, j, value);
                }
        return values;
    }

    /**
     * Populates the strictly upper triangular part of the matrix
     * 
     * @param A
     *            Matrix to populate
     * @return The matrix data in dense format
     */
    public static double[][] unitUpperPopulate(Matrix A) {
        int n = A.numRows(), m = A.numColumns();
        double[][] values = new double[n][m];
        for (int i = 0; i < n; ++i)
            for (int j = 0; j < m; ++j)
                if (i < j) {
                    double value = Math.random();
                    values[i][j] = value;
                    A.set(i, j, value);
                }
        return values;
    }

    /**
     * Puts the upper triangular part into the lower triangle
     */
    public static void lowerSymmetrice(double[][] Ad) {
        int n = Ad.length;
        for (int i = 0; i < n; ++i)
            for (int j = 0; j < i; ++j)
                Ad[j][i] = Ad[i][j];
    }

    /**
     * Puts the lower triangular part into the upper triangle
     */
    public static void upperSymmetrice(double[][] Ad) {
        int n = Ad.length;
        for (int i = 0; i < n; ++i) {
            int m = Ad[0].length;
            for (int j = i + 1; j < m; ++j)
                Ad[j][i] = Ad[i][j];
        }
    }

    /**
     * Sets one on the main diagonal
     */
    public static double[][] unitSet(double[][] Ad) {
        for (int i = 0; i < Ad.length; ++i)
            Ad[i][i] = 1;
        return Ad;
    }

    private final static Random r = new Random();

    /**
     * Returns an integer between zero (inclusive) and max (exclusive)
     */
    public static int getInt(int max) {
        return r.nextInt(max);
    }

    /**
     * Returns an integer between min (inclusive) and max (exclusive)
     */
    public static int getInt(int min, int max) {
        return Math.max(min, getInt(max));
    }

    /**
     * Returns true if the matrix is singular
     */
    public static boolean singular(Matrix A) throws NotConvergedException {
        SVD svd = SVD.factorize(A);
        double[] S = svd.getS();
        for (int i = 0; i < S.length; ++i)
            if (S[i] == 0)
                return true;
        return false;
    }

    /**
     * Returns true if the matrix is positive definite
     */
    public static boolean spd(Matrix A) throws NotConvergedException {
        EVD evd = EVD.factorize(A);
        {
            double[] S = evd.getRealEigenvalues();
            for (int i = 0; i < S.length; ++i)
                if (S[i] <= 0.)
                    return false;
        }
        {
            double[] S = evd.getImaginaryEigenvalues();
            for (int i = 0; i < S.length; ++i)
                if (Math.abs(S[i]) > 1e-10)
                    return false;
        }
        return true;
    }

    /**
     * Populates the given vector, and returns an array containing its values
     */
    public static double[] populate(Vector x) {
        double[] xd = new double[x.size()];
        for (int i = 0; i < x.size(); ++i) {
            double alpha = Math.random();
            xd[i] = alpha;
            x.set(i, alpha);
        }
        return xd;
    }

    /**
     * Populates the given vector, and returns an array containing its values.
     * Only m entries are inserted
     */
    public static double[] populate(Vector x, int m) {
        double[] xd = new double[x.size()];
        for (int i = 0; i < m; ++i) {
            double alpha = Math.random();
            int k = (int) (Math.random() * x.size());
            xd[k] = alpha;
            x.set(k, alpha);
        }
        return xd;
    }

    /**
     * Zeros the given array
     */
    public static void zero(double[][] A) {
        for (int i = 0; i < A.length; ++i)
            Arrays.fill(A[i], 0);
    }

    /**
     * Adds to the diagonal of both the matrix and the array
     */
    public static void addDiagonal(Matrix A, double[][] Ad, double shift) {
        int n = A.numRows(), m = A.numColumns();
        for (int i = 0; i < Math.min(m, n); ++i) {
            A.add(i, i, shift);
            Ad[i][i] += shift;
        }
    }

    /**
     * Adds to the diagonal of the matrix
     */
    public static void addDiagonal(Matrix A, double shift) {
        int n = A.numRows(), m = A.numColumns();
        for (int i = 0; i < Math.min(m, n); ++i)
            A.add(i, i, shift);
    }

}
