/*
 * Copyright (c) 2020, Peter Abeles. All Rights Reserved.
 *
 * This file is part of Efficient Java Matrix Library (EJML).
 *
 * Licensed 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.ejml.simple;

import org.ejml.EjmlUnitTests;
import org.ejml.UtilEjml;
import org.ejml.data.*;
import org.ejml.dense.row.CommonOps_DDRM;
import org.ejml.dense.row.NormOps_DDRM;
import org.ejml.dense.row.RandomMatrices_DDRM;
import org.ejml.ops.ConvertMatrixType;
import org.ejml.simple.ops.SimpleOperations_DSCC;
import org.ejml.sparse.csc.RandomMatrices_DSCC;
import org.junit.jupiter.api.Test;

import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import static org.junit.jupiter.api.Assertions.*;

/**
 * @author Peter Abeles
 */
public class TestSimpleMatrix {

    Random rand = new Random(76343);

    @Test
    public void randomNormal() {
        SimpleMatrix Q = SimpleMatrix.diag(5, 3, 12);
        Q.set(0, 1, 0.5);
        Q.set(1, 0, 0.5);

        int N = 200;
        double[] sum = new double[3];
        for (int i = 0; i < N; i++) {
            SimpleMatrix x = SimpleMatrix.randomNormal(Q, rand);

            for (int j = 0; j < x.getNumElements(); j++) {
                sum[j] += x.get(j);
            }
        }
        for (int i = 0; i < sum.length; i++) {
            sum[i] /= N;
            assertTrue(sum[i] != 0);
            assertEquals(0, sum[i], 0.3);
        }
    }

    @Test
    public void constructor_1d_array() {
        double[] d = new double[]{2, 5, 3, 9, -2, 6, 7, 4};
        SimpleMatrix s = new SimpleMatrix(3, 2, true, d);
        DMatrixRMaj m = new DMatrixRMaj(3, 2, true, d);

        EjmlUnitTests.assertEquals((DMatrixRMaj)m, (DMatrixRMaj)s.getMatrix(), UtilEjml.TEST_F64);
    }

    @Test
    public void constructor_2d_array() {
        double[][] d = new double[][]{{1, 2}, {3, 4}, {5, 6}};

        SimpleMatrix s = new SimpleMatrix(d);
        DMatrixRMaj mat = new DMatrixRMaj(d);

        EjmlUnitTests.assertEquals((DMatrixRMaj)mat, (DMatrixRMaj)s.getMatrix(), UtilEjml.TEST_F64);
    }

    @Test
    public void constructor_dense() {
        DMatrixRMaj mat = RandomMatrices_DDRM.rectangle(3, 2, rand);
        SimpleMatrix s = new SimpleMatrix(mat);

        assertTrue(mat != s.getMatrix());
        EjmlUnitTests.assertEquals(mat, s.getMatrix());
    }

    @Test
    public void constructor_simple() {
        SimpleMatrix orig = SimpleMatrix.random_DDRM(3, 2, 0, 1, rand);
        SimpleMatrix copy = new SimpleMatrix(orig);

        assertTrue(orig.mat != copy.mat);
        EjmlUnitTests.assertEquals(orig.mat, copy.mat);
    }

    @Test
    public void wrap() {
        DMatrixRMaj mat = RandomMatrices_DDRM.rectangle(3, 2, rand);

        SimpleMatrix s = SimpleMatrix.wrap(mat);

        assertTrue(s.mat == mat);
    }

    @Test
    public void identity() {
        SimpleMatrix s = SimpleMatrix.identity(3);

        DMatrixRMaj d = CommonOps_DDRM.identity(3);

        EjmlUnitTests.assertEquals(d, (DMatrixRMaj)s.mat, UtilEjml.TEST_F64);
    }

    @Test
    public void getMatrix() {
        SimpleMatrix s = new SimpleMatrix(3, 2);

        // make sure a new instance isn't returned
        assertTrue(s.mat == s.getMatrix());
    }

    @Test
    public void convertToSparse() {
        var data = new double[]{0, 1, 0, 1};
        var simpleMatrix = new SimpleMatrix(2, 2, true, data);
        assertTrue(simpleMatrix.getType().isDense());
        var denseMatrix = simpleMatrix.mat.copy();

        simpleMatrix.convertToSparse();

        assertFalse(simpleMatrix.getType().isDense());
        EjmlUnitTests.assertEquals(denseMatrix, simpleMatrix.mat);
    }

    @Test
    public void transformMatrix() {
        var data = new float[]{0, 1, 0, 1};
        var s = new SimpleMatrix(2, 2, true, data);

        var sparseDMatrix = s.getDSCC();
        var sparseFMatrix = s.getFSCC();
        var denseDMatrix = s.getDDRM();
        var denseFMatrix = s.getFDRM();

        EjmlUnitTests.assertEquals(sparseDMatrix, denseDMatrix);
        EjmlUnitTests.assertEquals(sparseDMatrix, sparseFMatrix);
        EjmlUnitTests.assertEquals(sparseDMatrix, denseFMatrix);
    }

    @Test
    public void transpose() {
        SimpleMatrix orig = SimpleMatrix.random_DDRM(3, 2, 0, 1, rand);
        SimpleMatrix tran = orig.transpose();

        DMatrixRMaj dTran = new DMatrixRMaj(2, 3);
        CommonOps_DDRM.transpose((DMatrixRMaj)orig.mat, dTran);

        EjmlUnitTests.assertEquals(dTran, (DMatrixRMaj)tran.mat, UtilEjml.TEST_F64);
    }

    @Test
    public void mult() {
        SimpleMatrix a = SimpleMatrix.random_DDRM(3, 2, 0, 1, rand);
        SimpleMatrix b = SimpleMatrix.random_DDRM(2, 3, 0, 1, rand);
        SimpleMatrix c = a.mult(b);

        DMatrixRMaj c_dense = new DMatrixRMaj(3, 3);
        CommonOps_DDRM.mult((DMatrixRMaj)a.mat, (DMatrixRMaj)b.mat, c_dense);

        EjmlUnitTests.assertEquals(c_dense, (DMatrixRMaj)c.mat, UtilEjml.TEST_F64);
    }

    @Test
    public void kron() {
        SimpleMatrix a = SimpleMatrix.random_DDRM(3, 2, 0, 1, rand);
        SimpleMatrix b = SimpleMatrix.random_DDRM(2, 3, 0, 1, rand);
        SimpleMatrix c = a.kron(b);

        DMatrixRMaj c_dense = new DMatrixRMaj(6, 6);
        CommonOps_DDRM.kron((DMatrixRMaj)a.getMatrix(), (DMatrixRMaj)b.getMatrix(), c_dense);

        EjmlUnitTests.assertEquals(c_dense, c.mat);
    }

//    @Test
//    public void mult_trans() {
//        SimpleMatrix a = SimpleMatrix.random(3,2,rand);
//        SimpleMatrix b = SimpleMatrix.random(2,3,rand);
//        SimpleMatrix c;
//
//        DMatrixRMaj c_dense = new DMatrixRMaj(3,3);
//        CommonOps.mult(a.mat,b.mat,c_dense);
//
//        c = a.mult(false,false,b);
//        EjmlUnitTests.assertEquals(c_dense,c.mat);
//        c = a.transpose().mult(true,false,b);
//        EjmlUnitTests.assertEquals(c_dense,c.mat);
//        c = a.mult(false,true,b.transpose());
//        EjmlUnitTests.assertEquals(c_dense,c.mat);
//        c = a.transpose().mult(true,true,b.transpose());
//        EjmlUnitTests.assertEquals(c_dense,c.mat);
//    }

    @Test
    public void plus() {
        SimpleMatrix a = SimpleMatrix.random_DDRM(3, 2, 0, 1, rand);
        SimpleMatrix b = SimpleMatrix.random_DDRM(3, 2, 0, 1, rand);
        SimpleMatrix c = a.plus(b);

        DMatrixRMaj c_dense = new DMatrixRMaj(3, 2);
        CommonOps_DDRM.add((DMatrixRMaj)a.mat, (DMatrixRMaj)b.mat, c_dense);

        EjmlUnitTests.assertEquals(c_dense, c.mat);
    }

    @Test
    public void plus_scalar() {
        SimpleMatrix a = SimpleMatrix.random_DDRM(3, 2, 0, 1, rand);
        double b = 2.5;
        SimpleMatrix c = a.plus(b);

        DMatrixRMaj c_dense = new DMatrixRMaj(3, 2);
        CommonOps_DDRM.add((DMatrixRMaj)a.mat, b, c_dense);

        EjmlUnitTests.assertEquals(c_dense, c.mat);
    }

    @Test
    public void dot() {
        SimpleMatrix a = SimpleMatrix.random_DDRM(10, 1, -1, 1, rand);
        SimpleMatrix b = SimpleMatrix.random_DDRM(10, 1, -1, 1, rand);

        double expected = 0;
        for (int i = 0; i < 10; i++)
            expected += a.get(i)*b.get(i);

        double found = a.dot(b);

        assertEquals(expected, found, UtilEjml.TEST_F64);
    }

    @Test
    public void isVector() {
        assertTrue(new SimpleMatrix(1, 1).isVector());
        assertTrue(new SimpleMatrix(1, 10).isVector());
        assertTrue(new SimpleMatrix(10, 1).isVector());
        assertFalse(new SimpleMatrix(6, 5).isVector());
    }

    @Test
    public void minus_matrix_matrix() {
        SimpleMatrix a = SimpleMatrix.random_DDRM(3, 2, 0, 1, rand);
        SimpleMatrix b = SimpleMatrix.random_DDRM(3, 2, 0, 1, rand);
        SimpleMatrix c = a.minus(b);

        DMatrixRMaj c_dense = new DMatrixRMaj(3, 2);
        CommonOps_DDRM.subtract((DMatrixRMaj)a.mat, (DMatrixRMaj)b.mat, c_dense);

        EjmlUnitTests.assertEquals(c_dense, c.mat);
    }

    @Test
    public void minus_matrix_scalar() {
        SimpleMatrix a = SimpleMatrix.random_DDRM(3, 2, 0, 1, rand);
        double b = 0.14;
        SimpleMatrix c = a.minus(b);

        DMatrixRMaj c_dense = new DMatrixRMaj(3, 2);
        CommonOps_DDRM.subtract((DMatrixRMaj)a.mat, b, c_dense);

        EjmlUnitTests.assertEquals(c_dense, c.mat);
    }

    @Test
    public void plus_beta() {
        SimpleMatrix a = SimpleMatrix.random_DDRM(3, 2, 0, 1, rand);
        SimpleMatrix b = SimpleMatrix.random_DDRM(3, 2, 0, 1, rand);
        SimpleMatrix c = a.plus(2.5, b);

        DMatrixRMaj c_dense = new DMatrixRMaj(3, 2);
        CommonOps_DDRM.add((DMatrixRMaj)a.mat, 2.5, (DMatrixRMaj)b.mat, c_dense);

        EjmlUnitTests.assertEquals(c_dense, c.mat);
    }

    @Test
    public void invert() {
        SimpleMatrix a = SimpleMatrix.random_DDRM(3, 3, 0, 1, rand);
        SimpleMatrix inv = a.invert();

        DMatrixRMaj d_inv = new DMatrixRMaj(3, 3);
        CommonOps_DDRM.invert((DMatrixRMaj)a.mat, d_inv);

        EjmlUnitTests.assertEquals(d_inv, inv.mat);
    }

    @Test
    public void invert_NaN_INFINITY() {
        SimpleMatrix a = new SimpleMatrix(3, 3);
        try {
            a.fill(Double.NaN);
            a.invert();
            fail("Should have thrown an exception");
        } catch (RuntimeException ignore) {
        }

        try {
            a.fill(Double.POSITIVE_INFINITY);
            a.invert();
            fail("Should have thrown an exception");
        } catch (RuntimeException ignore) {
        }
    }

    @Test
    public void pseudoInverse() {
        // first test it against a non-square zero matrix
        SimpleMatrix inv = new SimpleMatrix(3, 4).pseudoInverse();
        assertEquals(0, inv.normF(), UtilEjml.TEST_F64);

        // now try it against a more standard matrix
        SimpleMatrix a = SimpleMatrix.random_DDRM(3, 3, 0, 1, rand);
        inv = a.pseudoInverse();

        DMatrixRMaj d_inv = new DMatrixRMaj(3, 3);
        CommonOps_DDRM.invert((DMatrixRMaj)a.mat, d_inv);

        EjmlUnitTests.assertEquals(d_inv, inv.mat);
    }

    @Test
    public void solve() {
        SimpleMatrix a = SimpleMatrix.random_DDRM(3, 3, 0, 1, rand);
        SimpleMatrix b = SimpleMatrix.random_DDRM(3, 2, 0, 1, rand);
        SimpleMatrix c = a.solve(b);

        DMatrixRMaj c_dense = new DMatrixRMaj(3, 2);
        CommonOps_DDRM.solve((DMatrixRMaj)a.mat, (DMatrixRMaj)b.mat, c_dense);

        EjmlUnitTests.assertEquals(c_dense, c.mat);
    }

    @Test
    public void solve_NaN_INFINITY() {
        SimpleMatrix a = new SimpleMatrix(3, 3);
        SimpleMatrix b = SimpleMatrix.random_DDRM(3, 2, 0, 1, rand);
        try {
            a.fill(Double.NaN);
            a.solve(b);
            fail("Should have thrown an exception");
        } catch (RuntimeException ignore) {
        }

        try {
            a.fill(Double.POSITIVE_INFINITY);
            a.solve(b);
            fail("Should have thrown an exception");
        } catch (RuntimeException ignore) {
        }
    }

    /**
     * See if it solves an over determined system correctly
     */
    @Test
    public void solve_notsquare() {
        SimpleMatrix a = SimpleMatrix.random_DDRM(6, 3, 0, 1, rand);
        SimpleMatrix b = SimpleMatrix.random_DDRM(6, 2, 0, 1, rand);
        SimpleMatrix c = a.solve(b);

        DMatrixRMaj c_dense = new DMatrixRMaj(3, 2);
        CommonOps_DDRM.solve((DMatrixRMaj)a.mat, (DMatrixRMaj)b.mat, c_dense);

        EjmlUnitTests.assertEquals(c_dense, c.mat);
    }

    @Test
    public void set_double() {
        SimpleMatrix a = new SimpleMatrix(3, 3);
        a.fill(16.0);

        DMatrixRMaj d = new DMatrixRMaj(3, 3);
        CommonOps_DDRM.fill(d, 16.0);

        EjmlUnitTests.assertEquals(d, a.mat);
    }

    @Test
    public void zero() {
        SimpleMatrix a = SimpleMatrix.random_DDRM(3, 3, 0, 1, rand);
        a.zero();

        DMatrixRMaj d = new DMatrixRMaj(3, 3);

        EjmlUnitTests.assertEquals(d, a.mat);
    }

    @Test
    public void normF() {
        SimpleMatrix a = SimpleMatrix.random_DDRM(3, 3, 0, 1, rand);

        double norm = a.normF();
        double dnorm = NormOps_DDRM.fastNormF((DMatrixRMaj)a.mat);

        assertEquals(dnorm, norm, 1e-10);
    }

    @Test
    public void conditionP2() {
        SimpleMatrix a = SimpleMatrix.random_DDRM(3, 3, 0, 1, rand);

        double cond = NormOps_DDRM.conditionP2((DMatrixRMaj)a.getMatrix());
        double found = a.conditionP2();

        assertTrue(cond == found);
    }

    @Test
    public void determinant() {
        SimpleMatrix a = SimpleMatrix.random_DDRM(3, 3, 0, 1, rand);

        double det = a.determinant();
        double ddet = CommonOps_DDRM.det((DMatrixRMaj)a.mat);

        assertEquals(ddet, det, 1e-10);
    }

    @Test
    public void trace() {
        SimpleMatrix a = SimpleMatrix.random_DDRM(3, 3, 0, 1, rand);

        double trace = a.trace();
        double dtrace = CommonOps_DDRM.trace((DMatrixRMaj)a.mat);

        assertEquals(dtrace, trace, 1e-10);
    }

    @Test
    public void reshape() {
        SimpleMatrix a = SimpleMatrix.random_DDRM(3, 3, 0, 1, rand);
        DMatrixRMaj b = a.mat.copy();

        a.reshape(2, 3);
        b.reshape(2, 3, false);

        EjmlUnitTests.assertEquals(b, a.mat);
    }

    @Test
    public void set_element() {
        SimpleMatrix a = SimpleMatrix.random_DDRM(3, 3, 0, 1, rand);
        a.set(0, 1, 10.3);

        assertEquals(10.3, a.get(0, 1), 1e-6);
    }

    @Test
    public void setRow() {
        SimpleMatrix a = SimpleMatrix.random_DDRM(3, 3, 0, 1, rand);
        a.setRow(2, 1, 2, 3);

        assertEquals(2, a.get(2, 1), 1e-6);
        assertEquals(3, a.get(2, 2), 1e-6);
    }

    @Test
    public void setColumn() {
        SimpleMatrix a = SimpleMatrix.random_DDRM(3, 3, 0, 1, rand);
        a.setColumn(2, 1, 2, 3);

        assertEquals(2, a.get(1, 2), 1e-6);
        assertEquals(3, a.get(2, 2), 1e-6);
    }

    @Test
    public void get_2d() {
        SimpleMatrix a = SimpleMatrix.random_DDRM(3, 3, 0, 1, rand);

        assertEquals(((DMatrixRMaj)a.mat).get(0, 1), a.get(0, 1), 1e-6);
    }

    @Test
    public void get_1d() {
        SimpleMatrix a = SimpleMatrix.random_DDRM(3, 3, 0, 1, rand);

        assertEquals(((DMatrixRMaj)a.mat).get(3), a.get(3), 1e-6);
    }

    @Test
    public void getIndex() {
        SimpleMatrix a = SimpleMatrix.random_DDRM(3, 3, 0, 1, rand);

        assertEquals(((DMatrixRMaj)a.mat).getIndex(0, 2), a.getIndex(0, 2), 1e-6);
    }

    @Test
    public void copy() {
        SimpleMatrix a = SimpleMatrix.random_DDRM(3, 3, 0, 1, rand);
        SimpleMatrix b = a.copy();

        assertTrue(a.mat != b.mat);
        EjmlUnitTests.assertEquals(b.mat, a.mat);
    }

    @Test
    public void svd() {
        SimpleMatrix a = SimpleMatrix.random_DDRM(3, 4, 0, 1, rand);

        SimpleSVD<SimpleMatrix> svd = a.svd();

        SimpleMatrix U = svd.getU();
        SimpleMatrix W = svd.getW();
        SimpleMatrix V = svd.getV();

        SimpleMatrix a_found = U.mult(W).mult(V.transpose());

        EjmlUnitTests.assertEquals(a.mat, a_found.mat);
    }

    @Test
    public void eig() {
        SimpleMatrix a = SimpleMatrix.random_DDRM(4, 4, 0, 1, rand);

        SimpleEVD evd = a.eig();

        assertEquals(4, evd.getNumberOfEigenvalues());

        for (int i = 0; i < 4; i++) {
            Complex_F64 c = evd.getEigenvalue(i);
            assertTrue(c != null);
            evd.getEigenVector(i);
        }
    }

    @Test
    public void insertIntoThis() {
        SimpleMatrix A = new SimpleMatrix(6, 4);
        SimpleMatrix B = SimpleMatrix.random_DDRM(3, 2, 0, 1, rand);

        DMatrixRMaj A_ = A.getMatrix().copy();

        A.insertIntoThis(1, 2, B);

        CommonOps_DDRM.insert((DMatrixRMaj)B.getMatrix(), A_, 1, 2);

        EjmlUnitTests.assertEquals(A_, A.getMatrix());
    }

    @Test
    public void combine() {
        SimpleMatrix A = new SimpleMatrix(6, 4);
        SimpleMatrix B = SimpleMatrix.random_DDRM(3, 4, 0, 1, rand);

        SimpleMatrix C = A.combine(2, 2, B);

        assertEquals(6, C.numRows());
        assertEquals(6, C.numCols());

        for (int i = 0; i < 6; i++) {
            for (int j = 0; j < 6; j++) {
                if (i >= 2 && i < 5 && j >= 2 && j < 6) {
                    // check to see if B was overlayed
                    assertTrue(B.get(i - 2, j - 2) == C.get(i, j));
                } else if (i >= 5 || j >= 4) {
                    // check zero padding
                    assertTrue(C.get(i, j) == 0);
                } else {
                    // see if the parts of A remain there
                    assertTrue(A.get(i, j) == C.get(i, j));
                }
            }
        }
    }

    @Test
    public void scale() {
        SimpleMatrix a = SimpleMatrix.random_DDRM(3, 2, 0, 1, rand);
        SimpleMatrix b = a.scale(1.5);

        for (int i = 0; i < a.numRows(); i++) {
            for (int j = 0; j < a.numCols(); j++) {
                assertEquals(a.get(i, j)*1.5, b.get(i, j), 1e-10);
            }
        }
    }

    @Test
    public void div_scalar() {
        SimpleMatrix a = SimpleMatrix.random_DDRM(3, 2, 0, 1, rand);
        SimpleMatrix b = a.divide(1.5);

        for (int i = 0; i < a.numRows(); i++) {
            for (int j = 0; j < a.numCols(); j++) {
                assertEquals(a.get(i, j)/1.5, b.get(i, j), 1e-10);
            }
        }
    }

    @Test
    public void elementSum() {
        SimpleMatrix a = new SimpleMatrix(7, 4);

        double expectedSum = 0;

        int index = 0;
        for (int i = 0; i < a.numRows(); i++) {
            for (int j = 0; j < a.numCols(); j++, index++) {
                expectedSum += index;
                a.set(i, j, index);
            }
        }

        assertEquals(expectedSum, a.elementSum(), UtilEjml.TEST_F64);
    }

    @Test
    public void elementMaxAbs() {
        SimpleMatrix a = SimpleMatrix.random_DDRM(7, 5, 0, 1, rand);

        a.set(3, 4, -5);
        a.set(4, 4, 4);

        assertEquals(5, a.elementMaxAbs(), 0.0);
    }

    @Test
    public void elementMinAbs() {
        SimpleMatrix a = SimpleMatrix.random_DDRM(7, 5, 4, 10, rand);

        a.set(3, 4, -2);
        a.set(4, 4, 0.5);

        assertEquals(0.5, a.elementMinAbs(), 0.0);
    }

    @Test
    public void elementMult() {
        SimpleMatrix A = SimpleMatrix.random_DDRM(4, 5, -1, 1, rand);
        SimpleMatrix B = SimpleMatrix.random_DDRM(4, 5, -1, 1, rand);

        SimpleMatrix C = A.elementMult(B);

        for (int i = 0; i < A.numRows(); i++) {
            for (int j = 0; j < A.numCols(); j++) {
                double expected = A.get(i, j)*B.get(i, j);

                assertTrue(expected == C.get(i, j));
            }
        }
    }

    @Test
    public void elementDiv() {
        SimpleMatrix A = SimpleMatrix.random_DDRM(4, 5, -1, 1, rand);
        SimpleMatrix B = SimpleMatrix.random_DDRM(4, 5, -1, 1, rand);

        SimpleMatrix C = A.elementDiv(B);

        for (int i = 0; i < A.numRows(); i++) {
            for (int j = 0; j < A.numCols(); j++) {
                double expected = A.get(i, j)/B.get(i, j);

                assertTrue(expected == C.get(i, j));
            }
        }
    }

    @Test
    public void elementPower_m() {
        SimpleMatrix A = SimpleMatrix.random_DDRM(4, 5, 0, 1, rand);
        SimpleMatrix B = SimpleMatrix.random_DDRM(4, 5, 0, 1, rand);

        SimpleMatrix C = A.elementPower(B);

        for (int i = 0; i < A.numRows(); i++) {
            for (int j = 0; j < A.numCols(); j++) {
                double expected = Math.pow(A.get(i, j), B.get(i, j));

                assertTrue(expected == C.get(i, j));
            }
        }
    }

    @Test
    public void elementPower_s() {
        SimpleMatrix A = SimpleMatrix.random_DDRM(4, 5, 0, 1, rand);
        double b = 1.1;

        SimpleMatrix C = A.elementPower(b);

        for (int i = 0; i < A.numRows(); i++) {
            for (int j = 0; j < A.numCols(); j++) {
                double expected = Math.pow(A.get(i, j), b);

                assertTrue(expected == C.get(i, j));
            }
        }
    }

    @Test
    public void elementLog() {
        SimpleMatrix A = SimpleMatrix.random_DDRM(4, 5, 0, 1, rand);

        SimpleMatrix C = A.elementLog();

        for (int i = 0; i < A.numRows(); i++) {
            for (int j = 0; j < A.numCols(); j++) {
                double expected = Math.log(A.get(i, j));

                assertTrue(expected == C.get(i, j));
            }
        }
    }

    @Test
    public void elementExp() {
        SimpleMatrix A = SimpleMatrix.random_DDRM(4, 5, 0, 1, rand);

        SimpleMatrix C = A.elementExp();

        for (int i = 0; i < A.numRows(); i++) {
            for (int j = 0; j < A.numCols(); j++) {
                double expected = Math.exp(A.get(i, j));

                assertTrue(expected == C.get(i, j));
            }
        }
    }

    @Test
    public void extractMatrix() {
        SimpleMatrix a = SimpleMatrix.random_DDRM(7, 5, 0, 1, rand);

        SimpleMatrix b = a.extractMatrix(2, 5, 3, 5);

        for (int i = 2; i <= 4; i++) {
            for (int j = 3; j <= 4; j++) {
                double expected = a.get(i, j);
                double found = b.get(i - 2, j - 3);

                assertTrue(expected == found);
            }
        }
    }

    @Test
    public void diag_extract() {
        SimpleMatrix a = SimpleMatrix.random_DDRM(3, 4, 0, 1, rand);

        DMatrixRMaj found = a.diag().getMatrix();
        DMatrixRMaj expected = new DMatrixRMaj(3, 1);

        CommonOps_DDRM.extractDiag((DMatrixRMaj)a.getMatrix(), expected);

        EjmlUnitTests.assertEquals(found, expected);
    }

    @Test
    public void diag_vector() {
        SimpleMatrix a = SimpleMatrix.random_DDRM(3, 1, 0, 1, rand);

        DMatrixRMaj found = a.diag().getMatrix();
        SimpleMatrix expected = SimpleMatrix.diag(((DMatrixRMaj)a.getMatrix()).data);

        EjmlUnitTests.assertEquals(found, expected.getMatrix());
    }

    @Test
    public void extractVector() {
        SimpleMatrix A = SimpleMatrix.random_DDRM(10, 7, 0, 1, rand);

        SimpleMatrix c = A.extractVector(false, 2);
        SimpleMatrix r = A.extractVector(true, 2);

        assertEquals(A.numCols(), r.numCols());
        assertEquals(1, r.numRows());
        assertEquals(A.numRows(), c.numRows());
        assertEquals(1, c.numCols());

        for (int i = 0; i < A.numCols(); i++) {
            assertEquals(A.get(2, i), r.get(i), 1e-10);
        }

        for (int i = 0; i < A.numRows(); i++) {
            assertEquals(A.get(i, 2), c.get(i), 1e-10);
        }
    }

    @Test
    public void negative() {
        SimpleMatrix A = SimpleMatrix.random_DDRM(5, 7, -1, 1, rand);

        SimpleMatrix A_neg = A.negative();

        double value = A.plus(A_neg).normF();

        assertEquals(0, value, UtilEjml.TEST_F64);
    }

    @Test
    public void isInBounds() {
        SimpleMatrix A = new SimpleMatrix(10, 15);

        assertTrue(A.isInBounds(0, 0));
        assertTrue(A.isInBounds(9, 0));
        assertTrue(A.isInBounds(0, 14));
        assertTrue(A.isInBounds(3, 3));

        assertFalse(A.isInBounds(-1, 0));
        assertFalse(A.isInBounds(0, -1));
        assertFalse(A.isInBounds(10, 0));
        assertFalse(A.isInBounds(0, 15));
        assertFalse(A.isInBounds(3, 1000));
    }

    @Test
    public void equation() {
        SimpleMatrix A = SimpleMatrix.random_DDRM(5, 7, -1, 1, rand);
        SimpleMatrix B = SimpleMatrix.random_DDRM(5, 7, -1, 1, rand);

        SimpleMatrix orig = A.copy();

        // test each variable type
        A.equation("A = A + B", 24.5, "B");
        assertEquals(orig.get(0, 0) + 24.5, A.get(0, 0), UtilEjml.TEST_F64);

        // test implicit A =
        A.setTo(orig);
        A.equation("A + B", 24.5, "B");
        assertEquals(orig.get(0, 0) + 24.5, A.get(0, 0), UtilEjml.TEST_F64);

        A.equation("A(B,5) = 4", 4, "B");
        assertEquals(4, A.get(4, 5), UtilEjml.TEST_F64);

        A.setTo(orig);
        A.equation("A = A+B", B, "B");
        assertEquals(orig.get(0, 0) + B.get(0, 0), A.get(0, 0), UtilEjml.TEST_F64);

        A.setTo(orig);
        A.equation("A = A+B", B.getDDRM(), "B");
        assertEquals(orig.get(0, 0) + B.get(0, 0), A.get(0, 0), UtilEjml.TEST_F64);

        A.setTo(orig);
        A.equation("B = B+B", "B");
        assertEquals(orig.get(0, 0)*2, A.get(0, 0), UtilEjml.TEST_F64);

        // test implicit B
        A.setTo(orig);
        A.equation("B+B", "B");
        assertEquals(orig.get(0, 0)*2, A.get(0, 0), UtilEjml.TEST_F64);
    }

    /**
     * More detailed test is done in the CommonOps functin
     */
    @Test
    public void concatColumns() {
        SimpleMatrix A = SimpleMatrix.random_DDRM(5, 7, -1, 1, rand);
        SimpleMatrix B0 = SimpleMatrix.random_DDRM(4, 3, -1, 1, rand);
        SimpleMatrix B1 = SimpleMatrix.random_DDRM(4, 3, -1, 1, rand);

        SimpleMatrix C = A.concatColumns(B0, B1);
        SimpleMatrix D = new SimpleMatrix(5, 13);
        D.insertIntoThis(0, 0, A);
        D.insertIntoThis(0, 7, B0);
        D.insertIntoThis(0, 10, B1);

        assertTrue(C.isIdentical(D, UtilEjml.TEST_F64));
    }

    @Test
    public void concatRows() {
        SimpleMatrix A = SimpleMatrix.random_DDRM(5, 7, -1, 1, rand);
        SimpleMatrix B0 = SimpleMatrix.random_DDRM(4, 3, -1, 1, rand);
        SimpleMatrix B1 = SimpleMatrix.random_DDRM(4, 3, -1, 1, rand);

        SimpleMatrix C = A.concatRows(B0, B1);
        SimpleMatrix D = new SimpleMatrix(13, 7);
        D.insertIntoThis(0, 0, A);
        D.insertIntoThis(5, 0, B0);
        D.insertIntoThis(9, 0, B1);

        assertTrue(C.isIdentical(D, UtilEjml.TEST_F64));
    }

    @Test
    public void rows() {
        SimpleMatrix A = SimpleMatrix.random_DDRM(5, 7, -1, 1, rand);
        SimpleMatrix B = A.rows(1, 3);

        for (int i = 1; i < 3; i++) {
            for (int j = 0; j < 7; j++) {
                assertEquals(A.get(i, j), B.get(i - 1, j), UtilEjml.TEST_F64);
            }
        }
    }

    @Test
    public void cols() {
        SimpleMatrix A = SimpleMatrix.random_DDRM(5, 7, -1, 1, rand);
        SimpleMatrix B = A.rows(1, 3);

        for (int i = 1; i < 3; i++) {
            for (int j = 0; j < 7; j++) {
                assertEquals(A.get(i, j), B.get(i - 1, j), UtilEjml.TEST_F64);
            }
        }
    }

    @Test
    public void serialization() {
        List<Matrix> matrixTypes = new ArrayList<>();
        matrixTypes.add(new DMatrixRMaj(2, 3));
        matrixTypes.add(new FMatrixRMaj(2, 3));
        matrixTypes.add(new ZMatrixRMaj(2, 3));
        matrixTypes.add(new CMatrixRMaj(2, 3));
        matrixTypes.add(new DMatrixSparseCSC(2, 3));
//        matrixTypes.add( new FMatrixSparseCSC(2,3));

        DMatrixRMaj template = RandomMatrices_DDRM.rectangle(2, 3, rand);

        for (Matrix m : matrixTypes) {
            m.setTo(ConvertMatrixType.convert(template, m.getType()));

            SimpleMatrix A = new SimpleMatrix(m);
            try {
                ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
                ObjectOutputStream stream = new ObjectOutputStream(byteStream);
                stream.writeObject(A);
                stream.flush();
                stream.close();

                ByteArrayInputStream inputByte = new ByteArrayInputStream(byteStream.toByteArray());
                ObjectInputStream inputStream = new ObjectInputStream(inputByte);
                SimpleMatrix B = (SimpleMatrix)inputStream.readObject();

                assertTrue(A.isIdentical(B, 1e-4));
                assertNotNull(B.convertType);

                // If properly constructed this shouldn't fail
                B.mult(A.transpose());

                // UnsupportedOperation is considered an acceptable response
                try {
                    assertTrue(B.plus(0.1).isIdentical(A.plus(0.1), 1e-4));
                } catch (UnsupportedOperation | ConvertToDenseException ignore) {
                }

                byteStream.close();
            } catch (IOException | ClassNotFoundException e) {
                e.printStackTrace();
                fail("Serialization failed");
            }
        }
    }

    @Test
    public void serialization2() {

    }

    /**
     * See if it correctly calls a specialized function that can handle different matrix types
     */
    @Test
    public void mult_specialized() {
        DMatrixSparse A = RandomMatrices_DSCC.rectangle(2, 2, 3, rand);
        DMatrixRMaj B = RandomMatrices_DDRM.rectangle(2, 3, rand);

        SimpleMatrix sA = SimpleMatrix.wrap(A);
        SimpleMatrix sB = SimpleMatrix.wrap(B);

        OpsCheckSpecial ops = new OpsCheckSpecial();
        sA.ops = ops;

        sA.mult(sB);
        assertTrue(ops.specalized);
    }

    /**
     * See if it correctly calls a specialized function that can handle different matrix types
     */
    @Test
    public void solve_specialized() {
        DMatrixSparse A = RandomMatrices_DSCC.rectangle(2, 2, 3, rand);
        DMatrixRMaj B = RandomMatrices_DDRM.rectangle(2, 2, rand);

        SimpleMatrix sA = SimpleMatrix.wrap(A);
        SimpleMatrix sB = SimpleMatrix.wrap(B);

        OpsCheckSpecial ops = new OpsCheckSpecial();
        sA.ops = ops;

        sA.solve(sB);
        assertTrue(ops.specalized);
    }

    /**
     * Helper used to test to see if a specialized function was called
     */
    public static class OpsCheckSpecial extends SimpleOperations_DSCC {
        public boolean specalized = false;

        @Override
        public void mult( DMatrixSparseCSC A, DMatrixRMaj B, DMatrixRMaj output ) {
            specalized = true;
        }

        @Override
        public void mult( DMatrixSparseCSC A, DMatrixSparseCSC B, DMatrixSparseCSC output ) {
            fail("Shouldn't have been called");
        }

        @Override
        public boolean solve( DMatrixSparseCSC A, DMatrixSparseCSC X, DMatrixSparseCSC B ) {
            fail("Shouldn't have been called");
            return true;
        }

        @Override
        public boolean solve( DMatrixSparseCSC A, DMatrixRMaj X, DMatrixRMaj B ) {
            specalized = true;
            return true;
        }
    }
}
