diff --git a/mllib/src/main/scala/org/apache/spark/mllib/linalg/Matrices.scala b/mllib/src/main/scala/org/apache/spark/mllib/linalg/Matrices.scala index 2cc52e94282ba..d9298cacd7d99 100644 --- a/mllib/src/main/scala/org/apache/spark/mllib/linalg/Matrices.scala +++ b/mllib/src/main/scala/org/apache/spark/mllib/linalg/Matrices.scala @@ -20,9 +20,12 @@ package org.apache.spark.mllib.linalg import java.util.Arrays import breeze.linalg.{Matrix => BM, DenseMatrix => BDM, CSCMatrix => BSM} +import org.apache.spark.util.Utils import org.apache.spark.util.random.XORShiftRandom +import scala.collection.mutable.ArrayBuffer + /** * Trait for a local matrix. */ @@ -82,6 +85,12 @@ sealed trait Matrix extends Serializable { /** A human readable representation of the matrix */ override def toString: String = toBreeze.toString() + + /** Map the values of this matrix using a function. Generates a new matrix. */ + private[mllib] def map(f: Double => Double): Matrix + + /** Update all the values of this matrix using the function f. Performed in-place. */ + private[mllib] def update(f: Double => Double): Matrix } /** @@ -125,6 +134,119 @@ class DenseMatrix(val numRows: Int, val numCols: Int, val values: Array[Double]) } override def copy = new DenseMatrix(numRows, numCols, values.clone()) + + private[mllib] def map(f: Double => Double) = new DenseMatrix(numRows, numCols, values.map(f)) + + private[mllib] def update(f: Double => Double): DenseMatrix = { + val len = values.length + var i = 0 + while (i < len) { + values(i) = f(values(i)) + i += 1 + } + this + } +} + +/** + * Factory methods for [[org.apache.spark.mllib.linalg.DenseMatrix]]. + */ +object DenseMatrix { + + /** + * Generate a `DenseMatrix` consisting of zeros. + * @param numRows number of rows of the matrix + * @param numCols number of columns of the matrix + * @return `DenseMatrix` with size `numRows` x `numCols` and values of zeros + */ + def zeros(numRows: Int, numCols: Int): DenseMatrix = + new DenseMatrix(numRows, numCols, new Array[Double](numRows * numCols)) + + /** + * Generate a `DenseMatrix` consisting of ones. + * @param numRows number of rows of the matrix + * @param numCols number of columns of the matrix + * @return `DenseMatrix` with size `numRows` x `numCols` and values of ones + */ + def ones(numRows: Int, numCols: Int): DenseMatrix = + new DenseMatrix(numRows, numCols, Array.fill(numRows * numCols)(1.0)) + + /** + * Generate an Identity Matrix in `DenseMatrix` format. + * @param n number of rows and columns of the matrix + * @return `DenseMatrix` with size `n` x `n` and values of ones on the diagonal + */ + def eye(n: Int): DenseMatrix = { + val identity = DenseMatrix.zeros(n, n) + var i = 0 + while (i < n){ + identity.update(i, i, 1.0) + i += 1 + } + identity + } + + /** + * Generate a `DenseMatrix` consisting of i.i.d. uniform random numbers. + * @param numRows number of rows of the matrix + * @param numCols number of columns of the matrix + * @param seed the seed seed for the random number generator + * @return `DenseMatrix` with size `numRows` x `numCols` and values in U(0, 1) + */ + def rand(numRows: Int, numCols: Int, seed: Long): DenseMatrix = { + val rand = new XORShiftRandom(seed) + new DenseMatrix(numRows, numCols, Array.fill(numRows * numCols)(rand.nextDouble())) + } + + /** + * Generate a `DenseMatrix` consisting of i.i.d. uniform random numbers. + * @param numRows number of rows of the matrix + * @param numCols number of columns of the matrix + * @return `DenseMatrix` with size `numRows` x `numCols` and values in U(0, 1) + */ + def rand(numRows: Int, numCols: Int): DenseMatrix = { + rand(numRows, numCols, Utils.random.nextLong()) + } + + /** + * Generate a `DenseMatrix` consisting of i.i.d. gaussian random numbers. + * @param numRows number of rows of the matrix + * @param numCols number of columns of the matrix + * @param seed the seed seed for the random number generator + * @return `DenseMatrix` with size `numRows` x `numCols` and values in N(0, 1) + */ + def randn(numRows: Int, numCols: Int, seed: Long): DenseMatrix = { + val rand = new XORShiftRandom(seed) + new DenseMatrix(numRows, numCols, Array.fill(numRows * numCols)(rand.nextGaussian())) + } + + /** + * Generate a `DenseMatrix` consisting of i.i.d. gaussian random numbers. + * @param numRows number of rows of the matrix + * @param numCols number of columns of the matrix + * @return `DenseMatrix` with size `numRows` x `numCols` and values in N(0, 1) + */ + def randn(numRows: Int, numCols: Int): DenseMatrix = { + randn(numRows, numCols, Utils.random.nextLong()) + } + + /** + * Generate a diagonal matrix in `DenseMatrix` format from the supplied values. + * @param vector a `Vector` that will form the values on the diagonal of the matrix + * @return Square `DenseMatrix` with size `values.length` x `values.length` and `values` + * on the diagonal + */ + def diag(vector: Vector): DenseMatrix = { + val n = vector.size + val matrix = DenseMatrix.eye(n) + val values = vector.toArray + var i = 0 + while (i < n) { + matrix.update(i, i, values(i)) + i += 1 + } + matrix + } } /** @@ -199,6 +321,197 @@ class SparseMatrix( } override def copy = new SparseMatrix(numRows, numCols, colPtrs, rowIndices, values.clone()) + + private[mllib] def map(f: Double => Double) = + new SparseMatrix(numRows, numCols, colPtrs, rowIndices, values.map(f)) + + private[mllib] def update(f: Double => Double): SparseMatrix = { + val len = values.length + var i = 0 + while (i < len) { + values(i) = f(values(i)) + i += 1 + } + this + } +} + +/** + * Factory methods for [[org.apache.spark.mllib.linalg.SparseMatrix]]. + */ +object SparseMatrix { + + /** + * Generate an Identity Matrix in `SparseMatrix` format. + * @param n number of rows and columns of the matrix + * @return `SparseMatrix` with size `n` x `n` and values of ones on the diagonal + */ + def speye(n: Int): SparseMatrix = { + new SparseMatrix(n, n, (0 to n).toArray, (0 until n).toArray, Array.fill(n)(1.0)) + } + + /** Generates a SparseMatrix given an Array[Double] of size numRows * numCols. The number of + * non-zeros in `raw` is provided for efficiency. */ + private def genRand(numRows: Int, numCols: Int, raw: Array[Double], nonZero: Int): SparseMatrix = { + val sparseA: ArrayBuffer[Double] = new ArrayBuffer(nonZero) + + val sCols: ArrayBuffer[Int] = new ArrayBuffer(numCols + 1) + val sRows: ArrayBuffer[Int] = new ArrayBuffer(nonZero) + + var i = 0 + var nnz = 0 + var lastCol = -1 + + raw.foreach { v => + val r = i % numRows + val c = (i - r) / numRows + if ( v != 0.0) { + sRows.append(r) + sparseA.append(v) + while (c != lastCol){ + sCols.append(nnz) + lastCol += 1 + } + nnz += 1 + } + i += 1 + } + sCols.append(sparseA.length) + new SparseMatrix(numRows, numCols, sCols.toArray, sRows.toArray, sparseA.toArray) + } + + /** + * Generate a `SparseMatrix` consisting of i.i.d. uniform random numbers. + * @param numRows number of rows of the matrix + * @param numCols number of columns of the matrix + * @param density the desired density for the matrix + * @param seed the seed for the random generator + * @return `SparseMatrix` with size `numRows` x `numCols` and values in U(0, 1) + */ + def sprand( + numRows: Int, + numCols: Int, + density: Double, + seed: Long): SparseMatrix = { + + require(density > 0.0 && density < 1.0, "density must be a double in the range " + + s"0.0 < d < 1.0. Currently, density: $density") + val rand = new XORShiftRandom(seed) + val length = numRows * numCols + val rawA = Array.fill(length)(0.0) + var nnz = 0 + for (i <- 0 until length) { + val p = rand.nextDouble() + if (p < density) { + rawA.update(i, rand.nextDouble()) + nnz += 1 + } + } + genRand(numRows, numCols, rawA, nnz) + } + + /** + * Generate a `SparseMatrix` consisting of i.i.d. uniform random numbers. + * @param numRows number of rows of the matrix + * @param numCols number of columns of the matrix + * @param density the desired density for the matrix + * @return `SparseMatrix` with size `numRows` x `numCols` and values in U(0, 1) + */ + def sprand(numRows: Int, numCols: Int, density: Double): SparseMatrix = { + sprand(numRows, numCols, density, Utils.random.nextLong()) + } + + /** + * Generate a `SparseMatrix` consisting of i.i.d. gaussian random numbers. + * @param numRows number of rows of the matrix + * @param numCols number of columns of the matrix + * @param density the desired density for the matrix + * @param seed the seed for the random generator + * @return `SparseMatrix` with size `numRows` x `numCols` and values in N(0, 1) + */ + def sprandn( + numRows: Int, + numCols: Int, + density: Double, + seed: Long): SparseMatrix = { + + require(density > 0.0 && density < 1.0, "density must be a double in the range " + + s"0.0 < d < 1.0. Currently, density: $density") + val rand = new XORShiftRandom(seed) + val length = numRows * numCols + val rawA = Array.fill(length)(0.0) + var nnz = 0 + for (i <- 0 until length) { + val p = rand.nextDouble() + if (p < density) { + rawA.update(i, rand.nextGaussian()) + nnz += 1 + } + } + genRand(numRows, numCols, rawA, nnz) + } + + /** + * Generate a `SparseMatrix` consisting of i.i.d. gaussian random numbers. + * @param numRows number of rows of the matrix + * @param numCols number of columns of the matrix + * @param density the desired density for the matrix + * @return `SparseMatrix` with size `numRows` x `numCols` and values in N(0, 1) + */ + def sprandn(numRows: Int, numCols: Int, density: Double): SparseMatrix = { + sprandn(numRows, numCols, density, Utils.random.nextLong()) + } + + /** + * Generate a diagonal matrix in `DenseMatrix` format from the supplied values. + * @param vector a `Vector` that will form the values on the diagonal of the matrix + * @return Square `SparseMatrix` with size `values.length` x `values.length` and non-zero `values` + * on the diagonal + */ + def diag(vector: Vector): SparseMatrix = { + val n = vector.size + vector match { + case sVec: SparseVector => + val rows = sVec.indices + val values = sVec.values + var i = 0 + var lastCol = -1 + val colPtrs = new ArrayBuffer[Int](n) + rows.foreach { r => + while (r != lastCol) { + colPtrs.append(i) + lastCol += 1 + } + i += 1 + } + colPtrs.append(n) + new SparseMatrix(n, n, colPtrs.toArray, rows, values) + case dVec: DenseVector => + val values = dVec.values + var i = 0 + var nnz = 0 + val sVals = values.filter( v => v != 0.0) + var lastCol = -1 + val colPtrs = new ArrayBuffer[Int](n + 1) + val sRows = new ArrayBuffer[Int](sVals.length) + values.foreach { v => + if (v != 0.0) { + sRows.append(i) + while (lastCol != i) { + colPtrs.append(nnz) + lastCol += 1 + } + nnz += 1 + } + i += 1 + } + while (lastCol != i) { + colPtrs.append(nnz) + lastCol += 1 + } + new SparseMatrix(n, n, colPtrs.toArray, sRows.toArray, sVals) + } + } } /** @@ -260,8 +573,7 @@ object Matrices { * @param numCols number of columns of the matrix * @return `DenseMatrix` with size `numRows` x `numCols` and values of zeros */ - def zeros(numRows: Int, numCols: Int): Matrix = - new DenseMatrix(numRows, numCols, new Array[Double](numRows * numCols)) + def zeros(numRows: Int, numCols: Int): Matrix = DenseMatrix.zeros(numRows, numCols) /** * Generate a `DenseMatrix` consisting of ones. @@ -269,23 +581,31 @@ object Matrices { * @param numCols number of columns of the matrix * @return `DenseMatrix` with size `numRows` x `numCols` and values of ones */ - def ones(numRows: Int, numCols: Int): Matrix = - new DenseMatrix(numRows, numCols, Array.fill(numRows * numCols)(1.0)) + def ones(numRows: Int, numCols: Int): Matrix = DenseMatrix.ones(numRows, numCols) /** - * Generate an Identity Matrix in `DenseMatrix` format. + * Generate a dense Identity Matrix in `Matrix` format. * @param n number of rows and columns of the matrix * @return `DenseMatrix` with size `n` x `n` and values of ones on the diagonal */ - def eye(n: Int): Matrix = { - val identity = Matrices.zeros(n, n) - var i = 0 - while (i < n){ - identity.update(i, i, 1.0) - i += 1 - } - identity - } + def eye(n: Int): Matrix = DenseMatrix.eye(n) + + /** + * Generate a sparse Identity Matrix in `Matrix` format. + * @param n number of rows and columns of the matrix + * @return `SparseMatrix` with size `n` x `n` and values of ones on the diagonal + */ + def speye(n: Int): Matrix = SparseMatrix.speye(n) + + /** + * Generate a dense `Matrix` consisting of i.i.d. uniform random numbers. + * @param numRows number of rows of the matrix + * @param numCols number of columns of the matrix + * @param seed the seed for the random generator + * @return `DenseMatrix` with size `numRows` x `numCols` and values in U(0, 1) + */ + def rand(numRows: Int, numCols: Int, seed: Long): Matrix = + DenseMatrix.rand(numRows, numCols, seed) /** * Generate a `DenseMatrix` consisting of i.i.d. uniform random numbers. @@ -293,21 +613,67 @@ object Matrices { * @param numCols number of columns of the matrix * @return `DenseMatrix` with size `numRows` x `numCols` and values in U(0, 1) */ - def rand(numRows: Int, numCols: Int): Matrix = { - val rand = new XORShiftRandom - new DenseMatrix(numRows, numCols, Array.fill(numRows * numCols)(rand.nextDouble())) - } + def rand(numRows: Int, numCols: Int): Matrix = DenseMatrix.rand(numRows, numCols) + + /** + * Generate a `SparseMatrix` consisting of i.i.d. gaussian random numbers. + * @param numRows number of rows of the matrix + * @param numCols number of columns of the matrix + * @param density the desired density for the matrix + * @param seed the seed for the random generator + * @return `Matrix` with size `numRows` x `numCols` and values in U(0, 1) + */ + def sprand(numRows: Int, numCols: Int, density: Double, seed: Long): Matrix = + SparseMatrix.sprand(numRows, numCols, density, seed) + + /** + * Generate a `SparseMatrix` consisting of i.i.d. gaussian random numbers. + * @param numRows number of rows of the matrix + * @param numCols number of columns of the matrix + * @param density the desired density for the matrix + * @return `Matrix` with size `numRows` x `numCols` and values in U(0, 1) + */ + def sprand(numRows: Int, numCols: Int, density: Double): Matrix = + SparseMatrix.sprand(numRows, numCols, density) /** * Generate a `DenseMatrix` consisting of i.i.d. gaussian random numbers. * @param numRows number of rows of the matrix * @param numCols number of columns of the matrix + * @param seed the seed for the random generator * @return `DenseMatrix` with size `numRows` x `numCols` and values in N(0, 1) */ - def randn(numRows: Int, numCols: Int): Matrix = { - val rand = new XORShiftRandom - new DenseMatrix(numRows, numCols, Array.fill(numRows * numCols)(rand.nextGaussian())) - } + def randn(numRows: Int, numCols: Int, seed: Long): Matrix = + DenseMatrix.randn(numRows, numCols, seed) + + /** + * Generate a `DenseMatrix` consisting of i.i.d. gaussian random numbers. + * @param numRows number of rows of the matrix + * @param numCols number of columns of the matrix + * @return `DenseMatrix` with size `numRows` x `numCols` and values in N(0, 1) + */ + def randn(numRows: Int, numCols: Int): Matrix = DenseMatrix.randn(numRows, numCols) + + /** + * Generate a `SparseMatrix` consisting of i.i.d. gaussian random numbers. + * @param numRows number of rows of the matrix + * @param numCols number of columns of the matrix + * @param density the desired density for the matrix + * @param seed the seed for the random generator + * @return `Matrix` with size `numRows` x `numCols` and values in N(0, 1) + */ + def sprandn(numRows: Int, numCols: Int, density: Double, seed: Long): Matrix = + SparseMatrix.sprandn(numRows, numCols, density, seed) + + /** + * Generate a `SparseMatrix` consisting of i.i.d. gaussian random numbers. + * @param numRows number of rows of the matrix + * @param numCols number of columns of the matrix + * @param density the desired density for the matrix + * @return `Matrix` with size `numRows` x `numCols` and values in N(0, 1) + */ + def sprandn(numRows: Int, numCols: Int, density: Double): Matrix = + SparseMatrix.sprandn(numRows, numCols, density) /** * Generate a diagonal matrix in `DenseMatrix` format from the supplied values. @@ -315,15 +681,145 @@ object Matrices { * @return Square `DenseMatrix` with size `values.length` x `values.length` and `values` * on the diagonal */ - def diag(vector: Vector): Matrix = { - val n = vector.size - val matrix = Matrices.eye(n) - val values = vector.toArray - var i = 0 - while (i < n) { - matrix.update(i, i, values(i)) - i += 1 + def diag(vector: Vector): Matrix = DenseMatrix.diag(vector) + + /** + * Horizontally concatenate a sequence of matrices. The returned matrix will be in the format + * the matrices are supplied in. Supplying a mix of dense and sparse matrices will result in + * a dense matrix. + * @param matrices sequence of matrices + * @return a single `Matrix` composed of the matrices that were horizontally concatenated + */ + private[mllib] def horzCat(matrices: Seq[Matrix]): Matrix = { + if (matrices.size == 1) { + return matrices(0) + } + val numRows = matrices(0).numRows + var rowsMatch = true + var isDense = false + var isSparse = false + var numCols = 0 + matrices.foreach { mat => + if (numRows != mat.numRows) rowsMatch = false + mat match { + case sparse: SparseMatrix => isSparse = true + case dense: DenseMatrix => isDense = true + } + numCols += mat.numCols + } + require(rowsMatch, "The number of rows of the matrices in this sequence, don't match!") + + if (isSparse && !isDense) { + val allColPtrs: Array[(Int, Int)] = Array((0, 0)) ++ + matrices.zipWithIndex.flatMap { case (mat, ind) => + val ptr = mat.asInstanceOf[SparseMatrix].colPtrs + ptr.slice(1, ptr.length).map(p => (ind, p)) + } + var counter = 0 + var lastIndex = 0 + var lastPtr = 0 + val adjustedPtrs = allColPtrs.map { case (ind, p) => + if (ind != lastIndex) { + counter += lastPtr + lastIndex = ind + } + lastPtr = p + counter + p + } + new SparseMatrix(numRows, numCols, adjustedPtrs, + matrices.flatMap(_.asInstanceOf[SparseMatrix].rowIndices).toArray, + matrices.flatMap(_.asInstanceOf[SparseMatrix].values).toArray) + } else if (!isSparse && !isDense) { + throw new IllegalArgumentException("The supplied matrices are neither in SparseMatrix or" + + " DenseMatrix format!") + }else { + new DenseMatrix(numRows, numCols, matrices.flatMap(_.toArray).toArray) + } + } + + /** + * Vertically concatenate a sequence of matrices. The returned matrix will be in the format + * the matrices are supplied in. Supplying a mix of dense and sparse matrices will result in + * a dense matrix. + * @param matrices sequence of matrices + * @return a single `Matrix` composed of the matrices that were horizontally concatenated + */ + private[mllib] def vertCat(matrices: Seq[Matrix]): Matrix = { + if (matrices.size == 1) { + return matrices(0) + } + val numCols = matrices(0).numCols + var colsMatch = true + var isDense = false + var isSparse = false + var numRows = 0 + var valsLength = 0 + matrices.foreach { mat => + if (numCols != mat.numCols) colsMatch = false + mat match { + case sparse: SparseMatrix => + isSparse = true + valsLength += sparse.values.length + case dense: DenseMatrix => + isDense = true + valsLength += dense.values.length + } + numRows += mat.numRows + + } + require(colsMatch, "The number of rows of the matrices in this sequence, don't match!") + + if (isSparse && !isDense) { + val matMap = matrices.zipWithIndex.map(d => (d._2, d._1.asInstanceOf[SparseMatrix])).toMap + // (matrixInd, colInd, colStart, colEnd, numRows) + val allColPtrs: Seq[(Int, Int, Int, Int, Int)] = + matMap.flatMap { case (ind, mat) => + val ptr = mat.colPtrs + var colStart = 0 + var j = 0 + ptr.slice(1, ptr.length).map { p => + j += 1 + val oldColStart = colStart + colStart = p + (j - 1, ind, oldColStart, p, mat.numRows) + } + }.toSeq + val values = new ArrayBuffer[Double](valsLength) + val rowInd = new ArrayBuffer[Int](valsLength) + val newColPtrs = new Array[Int](numCols) + + // group metadata by column index and then sort in increasing order of column index + allColPtrs.groupBy(_._1).toArray.sortBy(_._1).foreach { case (colInd, data) => + // then sort by matrix index + val sortedPtrs = data.sortBy(_._1) + var startRow = 0 + sortedPtrs.foreach { case (colIdx, matrixInd, colStart, colEnd, nRows) => + val selectedMatrix = matMap(matrixInd) + val selectedValues = selectedMatrix.values.slice(colStart, colEnd) + val selectedRowIdx = selectedMatrix.rowIndices.slice(colStart, colEnd) + val len = selectedValues.length + newColPtrs(colIdx) += len + var i = 0 + while (i < len) { + values.append(selectedValues(i)) + rowInd.append(selectedRowIdx(i) + startRow) + i += 1 + } + startRow += nRows + } + } + val adjustedPtrs = newColPtrs.scanLeft(0)(_ + _) + new SparseMatrix(numRows, numCols, adjustedPtrs, rowInd.toArray, values.toArray) + } else if (!isSparse && !isDense) { + throw new IllegalArgumentException("The supplied matrices are neither in SparseMatrix or" + + " DenseMatrix format!") + }else { + val matData = matrices.zipWithIndex.flatMap { case (mat, ind) => + val values = mat.toArray + for (j <- 0 until numCols) yield (j, ind, + values.slice(j * mat.numRows, (j + 1) * mat.numRows)) + }.sortBy(x => (x._1, x._2)) + new DenseMatrix(numRows, numCols, matData.flatMap(_._3).toArray) } - matrix } } diff --git a/mllib/src/test/scala/org/apache/spark/mllib/linalg/MatricesSuite.scala b/mllib/src/test/scala/org/apache/spark/mllib/linalg/MatricesSuite.scala index 5f8b8c4b72697..2793e9aaef86d 100644 --- a/mllib/src/test/scala/org/apache/spark/mllib/linalg/MatricesSuite.scala +++ b/mllib/src/test/scala/org/apache/spark/mllib/linalg/MatricesSuite.scala @@ -112,4 +112,113 @@ class MatricesSuite extends FunSuite { assert(sparseMat(0, 1) === 10.0) assert(sparseMat.values(2) === 10.0) } + + test("map, update") { + val m = 3 + val n = 2 + val values = Array(1.0, 2.0, 4.0, 5.0) + val allValues = Array(1.0, 2.0, 0.0, 0.0, 4.0, 5.0) + val colPtrs = Array(0, 2, 4) + val rowIndices = Array(0, 1, 1, 2) + + val spMat1 = new SparseMatrix(m, n, colPtrs, rowIndices, values) + val deMat1 = new DenseMatrix(m, n, allValues) + val deMat2 = deMat1.map(_ * 2) + val spMat2 = spMat1.map(_ * 2) + deMat1.update(_ * 2) + spMat1.update(_ * 2) + + assert(spMat1.toArray === spMat2.toArray) + assert(deMat1.toArray === deMat2.toArray) + } + + test("horzCat, vertCat, eye, speye") { + val m = 3 + val n = 2 + val values = Array(1.0, 2.0, 4.0, 5.0) + val allValues = Array(1.0, 2.0, 0.0, 0.0, 4.0, 5.0) + val colPtrs = Array(0, 2, 4) + val rowIndices = Array(0, 1, 1, 2) + + val spMat1 = new SparseMatrix(m, n, colPtrs, rowIndices, values) + val deMat1 = new DenseMatrix(m, n, allValues) + val deMat2 = Matrices.eye(3) + val spMat2 = Matrices.speye(3) + val deMat3 = Matrices.eye(2) + val spMat3 = Matrices.speye(2) + + val spHorz = Matrices.horzCat(Seq(spMat1, spMat2)) + val deHorz1 = Matrices.horzCat(Seq(deMat1, deMat2)) + val deHorz2 = Matrices.horzCat(Seq(spMat1, deMat2)) + val deHorz3 = Matrices.horzCat(Seq(deMat1, spMat2)) + + assert(deHorz1.numRows === 3) + assert(deHorz2.numRows === 3) + assert(deHorz3.numRows === 3) + assert(spHorz.numRows === 3) + assert(deHorz1.numCols === 5) + assert(deHorz2.numCols === 5) + assert(deHorz3.numCols === 5) + assert(spHorz.numCols === 5) + + assert(deHorz1 === deHorz2) + assert(deHorz2 === deHorz3) + assert(spHorz(0, 0) === 1.0) + assert(spHorz(2, 1) === 5.0) + assert(spHorz(0, 2) === 1.0) + assert(spHorz(1, 2) === 0.0) + assert(spHorz(1, 3) === 1.0) + assert(spHorz(2, 4) === 1.0) + assert(spHorz(1, 4) === 0.0) + assert(deHorz1(0, 0) === 1.0) + assert(deHorz1(2, 1) === 5.0) + assert(deHorz1(0, 2) === 1.0) + assert(deHorz1(1, 2) === 0.0) + assert(deHorz1(1, 3) === 1.0) + assert(deHorz1(2, 4) === 1.0) + assert(deHorz1(1, 4) === 0.0) + + intercept[IllegalArgumentException] { + Matrices.horzCat(Seq(spMat1, spMat3)) + } + + intercept[IllegalArgumentException] { + Matrices.horzCat(Seq(deMat1, spMat3)) + } + + val spVert = Matrices.vertCat(Seq(spMat1, spMat3)) + val deVert1 = Matrices.vertCat(Seq(deMat1, deMat3)) + val deVert2 = Matrices.vertCat(Seq(spMat1, deMat3)) + val deVert3 = Matrices.vertCat(Seq(deMat1, spMat3)) + + assert(deVert1.numRows === 5) + assert(deVert2.numRows === 5) + assert(deVert3.numRows === 5) + assert(spVert.numRows === 5) + assert(deVert1.numCols === 2) + assert(deVert2.numCols === 2) + assert(deVert3.numCols === 2) + assert(spVert.numCols === 2) + + assert(deVert1 === deVert2) + assert(deVert2 === deVert3) + assert(spVert(0, 0) === 1.0) + assert(spVert(2, 1) === 5.0) + assert(spVert(3, 0) === 1.0) + assert(spVert(3, 1) === 0.0) + assert(spVert(4, 1) === 1.0) + assert(deVert1(0, 0) === 1.0) + assert(deVert1(2, 1) === 5.0) + assert(deVert1(3, 0) === 1.0) + assert(deVert1(3, 1) === 0.0) + assert(deVert1(4, 1) === 1.0) + + intercept[IllegalArgumentException] { + Matrices.vertCat(Seq(spMat1, spMat2)) + } + + intercept[IllegalArgumentException] { + Matrices.vertCat(Seq(deMat1, spMat2)) + } + } }