/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.apache.commons.math3.linear;

import org.apache.commons.math3.linear.BiDiagonalTransformer;
import org.apache.commons.math3.linear.MatrixUtils;
import org.apache.commons.math3.linear.RealMatrix;
import org.apache.commons.math3.util.FastMath;
import org.junit.Assert;
import org.junit.Test;

public class BiDiagonalTransformerTest {

    private double[][] testSquare = {
            { 24.0 / 25.0, 43.0 / 25.0 },
            { 57.0 / 25.0, 24.0 / 25.0 }
    };

    private double[][] testNonSquare = {
        {  -540.0 / 625.0,  963.0 / 625.0, -216.0 / 625.0 },
        { -1730.0 / 625.0, -744.0 / 625.0, 1008.0 / 625.0 },
        {  -720.0 / 625.0, 1284.0 / 625.0, -288.0 / 625.0 },
        {  -360.0 / 625.0,  192.0 / 625.0, 1756.0 / 625.0 },
    };

    @Test
    public void testDimensions() {
        checkdimensions(MatrixUtils.createRealMatrix(testSquare));
        checkdimensions(MatrixUtils.createRealMatrix(testNonSquare));
        checkdimensions(MatrixUtils.createRealMatrix(testNonSquare).transpose());
    }

    private void checkdimensions(RealMatrix matrix) {
        final int m = matrix.getRowDimension();
        final int n = matrix.getColumnDimension();
        BiDiagonalTransformer transformer = new BiDiagonalTransformer(matrix);
        Assert.assertEquals(m, transformer.getU().getRowDimension());
        Assert.assertEquals(m, transformer.getU().getColumnDimension());
        Assert.assertEquals(m, transformer.getB().getRowDimension());
        Assert.assertEquals(n, transformer.getB().getColumnDimension());
        Assert.assertEquals(n, transformer.getV().getRowDimension());
        Assert.assertEquals(n, transformer.getV().getColumnDimension());

    }

    @Test
    public void testAEqualUSVt() {
        checkAEqualUSVt(MatrixUtils.createRealMatrix(testSquare));
        checkAEqualUSVt(MatrixUtils.createRealMatrix(testNonSquare));
        checkAEqualUSVt(MatrixUtils.createRealMatrix(testNonSquare).transpose());
    }

    private void checkAEqualUSVt(RealMatrix matrix) {
        BiDiagonalTransformer transformer = new BiDiagonalTransformer(matrix);
        RealMatrix u = transformer.getU();
        RealMatrix b = transformer.getB();
        RealMatrix v = transformer.getV();
        double norm = u.multiply(b).multiply(v.transpose()).subtract(matrix).getNorm();
        Assert.assertEquals(0, norm, 1.0e-14);
    }

    @Test
    public void testUOrthogonal() {
        checkOrthogonal(new BiDiagonalTransformer(MatrixUtils.createRealMatrix(testSquare)).getU());
        checkOrthogonal(new BiDiagonalTransformer(MatrixUtils.createRealMatrix(testNonSquare)).getU());
        checkOrthogonal(new BiDiagonalTransformer(MatrixUtils.createRealMatrix(testNonSquare).transpose()).getU());
    }

    @Test
    public void testVOrthogonal() {
        checkOrthogonal(new BiDiagonalTransformer(MatrixUtils.createRealMatrix(testSquare)).getV());
        checkOrthogonal(new BiDiagonalTransformer(MatrixUtils.createRealMatrix(testNonSquare)).getV());
        checkOrthogonal(new BiDiagonalTransformer(MatrixUtils.createRealMatrix(testNonSquare).transpose()).getV());
    }

    private void checkOrthogonal(RealMatrix m) {
        RealMatrix mTm = m.transpose().multiply(m);
        RealMatrix id  = MatrixUtils.createRealIdentityMatrix(mTm.getRowDimension());
        Assert.assertEquals(0, mTm.subtract(id).getNorm(), 1.0e-14);
    }

    @Test
    public void testBBiDiagonal() {
        checkBiDiagonal(new BiDiagonalTransformer(MatrixUtils.createRealMatrix(testSquare)).getB());
        checkBiDiagonal(new BiDiagonalTransformer(MatrixUtils.createRealMatrix(testNonSquare)).getB());
        checkBiDiagonal(new BiDiagonalTransformer(MatrixUtils.createRealMatrix(testNonSquare).transpose()).getB());
    }

    private void checkBiDiagonal(RealMatrix m) {
        final int rows = m.getRowDimension();
        final int cols = m.getColumnDimension();
        for (int i = 0; i < rows; ++i) {
            for (int j = 0; j < cols; ++j) {
                if (rows < cols) {
                    if ((i < j) || (i > j + 1)) {
                        Assert.assertEquals(0, m.getEntry(i, j), 1.0e-16);
                    }
                } else {
                    if ((i < j - 1) || (i > j)) {
                        Assert.assertEquals(0, m.getEntry(i, j), 1.0e-16);
                    }
                }
            }
        }
    }

    @Test
    public void testSingularMatrix() {
       BiDiagonalTransformer transformer =
            new BiDiagonalTransformer(MatrixUtils.createRealMatrix(new double[][] {
                { 1.0, 2.0, 3.0 },
                { 2.0, 3.0, 4.0 },
                { 3.0, 5.0, 7.0 }
            }));
       final double s3  = FastMath.sqrt(3.0);
       final double s14 = FastMath.sqrt(14.0);
       final double s1553 = FastMath.sqrt(1553.0);
       RealMatrix uRef = MatrixUtils.createRealMatrix(new double[][] {
           {  -1.0 / s14,  5.0 / (s3 * s14),  1.0 / s3 },
           {  -2.0 / s14, -4.0 / (s3 * s14),  1.0 / s3 },
           {  -3.0 / s14,  1.0 / (s3 * s14), -1.0 / s3 }
       });
       RealMatrix bRef = MatrixUtils.createRealMatrix(new double[][] {
           { -s14, s1553 / s14,   0.0 },
           {  0.0, -87 * s3 / (s14 * s1553), -s3 * s14 / s1553 },
           {  0.0, 0.0, 0.0 }
       });
       RealMatrix vRef = MatrixUtils.createRealMatrix(new double[][] {
           { 1.0,   0.0,         0.0        },
           { 0.0,  -23 / s1553,  32 / s1553 },
           { 0.0,  -32 / s1553, -23 / s1553 }
       });

       // check values against known references
       RealMatrix u = transformer.getU();
       Assert.assertEquals(0, u.subtract(uRef).getNorm(), 1.0e-14);
       RealMatrix b = transformer.getB();
       Assert.assertEquals(0, b.subtract(bRef).getNorm(), 1.0e-14);
       RealMatrix v = transformer.getV();
       Assert.assertEquals(0, v.subtract(vRef).getNorm(), 1.0e-14);

       // check the same cached instance is returned the second time
       Assert.assertTrue(u == transformer.getU());
       Assert.assertTrue(b == transformer.getB());
       Assert.assertTrue(v == transformer.getV());

    }

    @Test
    public void testMatricesValues() {
       BiDiagonalTransformer transformer =
            new BiDiagonalTransformer(MatrixUtils.createRealMatrix(testSquare));
       final double s17 = FastMath.sqrt(17.0);
        RealMatrix uRef = MatrixUtils.createRealMatrix(new double[][] {
                {  -8 / (5 * s17), 19 / (5 * s17) },
                { -19 / (5 * s17), -8 / (5 * s17) }
        });
        RealMatrix bRef = MatrixUtils.createRealMatrix(new double[][] {
                { -3 * s17 / 5, 32 * s17 / 85 },
                {      0.0,     -5 * s17 / 17 }
        });
        RealMatrix vRef = MatrixUtils.createRealMatrix(new double[][] {
                { 1.0,  0.0 },
                { 0.0, -1.0 }
        });

        // check values against known references
        RealMatrix u = transformer.getU();
        Assert.assertEquals(0, u.subtract(uRef).getNorm(), 1.0e-14);
        RealMatrix b = transformer.getB();
        Assert.assertEquals(0, b.subtract(bRef).getNorm(), 1.0e-14);
        RealMatrix v = transformer.getV();
        Assert.assertEquals(0, v.subtract(vRef).getNorm(), 1.0e-14);

        // check the same cached instance is returned the second time
        Assert.assertTrue(u == transformer.getU());
        Assert.assertTrue(b == transformer.getB());
        Assert.assertTrue(v == transformer.getV());

    }

    @Test
    public void testUpperOrLower() {
        Assert.assertTrue(new BiDiagonalTransformer(MatrixUtils.createRealMatrix(testSquare)).isUpperBiDiagonal());
        Assert.assertTrue(new BiDiagonalTransformer(MatrixUtils.createRealMatrix(testNonSquare)).isUpperBiDiagonal());
        Assert.assertFalse(new BiDiagonalTransformer(MatrixUtils.createRealMatrix(testNonSquare).transpose()).isUpperBiDiagonal());
    }

}
