From 35d1c8a6033303946a39927dfef9ff516171f0a3 Mon Sep 17 00:00:00 2001 From: "Ehsan M.Kermani" Date: Mon, 23 Nov 2015 12:36:11 -0800 Subject: [PATCH 1/6] Reimplement add() and subtract() using one common associative function on Breeze --- .../linalg/distributed/BlockMatrix.scala | 64 +++++++++++++------ .../linalg/distributed/BlockMatrixSuite.scala | 43 +++++++++++++ 2 files changed, 88 insertions(+), 19 deletions(-) diff --git a/mllib/src/main/scala/org/apache/spark/mllib/linalg/distributed/BlockMatrix.scala b/mllib/src/main/scala/org/apache/spark/mllib/linalg/distributed/BlockMatrix.scala index 09527dcf5d9e5..0060f35932654 100644 --- a/mllib/src/main/scala/org/apache/spark/mllib/linalg/distributed/BlockMatrix.scala +++ b/mllib/src/main/scala/org/apache/spark/mllib/linalg/distributed/BlockMatrix.scala @@ -317,14 +317,18 @@ class BlockMatrix @Since("1.3.0") ( } /** - * Adds two block matrices together. The matrices must have the same size and matching - * `rowsPerBlock` and `colsPerBlock` values. If one of the blocks that are being added are - * instances of [[SparseMatrix]], the resulting sub matrix will also be a [[SparseMatrix]], even - * if it is being added to a [[DenseMatrix]]. If two dense matrices are added, the output will - * also be a [[DenseMatrix]]. + * For given matrices `this` and `other` of compatible dimensions and compatible block dimensions, + * it applies an associative binary function on their corresponding blocks. + * + * @param other The BlockMatrix to operate on + * @param binMap An associative function taking two dense breeze matrices and returning one + * dense breeze matrix + * @return A [[BlockMatrix]] whose blocks are the results of a specified binary map on blocks + * of `this` and `other`. */ - @Since("1.3.0") - def add(other: BlockMatrix): BlockMatrix = { + private[mllib] def blockMap( + other: BlockMatrix, + binMap: (BDM[Double], BDM[Double]) => BDM[Double]): BlockMatrix = { require(numRows() == other.numRows(), "Both matrices must have the same number of rows. " + s"A.numRows: ${numRows()}, B.numRows: ${other.numRows()}") require(numCols() == other.numCols(), "Both matrices must have the same number of columns. " + @@ -332,18 +336,18 @@ class BlockMatrix @Since("1.3.0") ( if (rowsPerBlock == other.rowsPerBlock && colsPerBlock == other.colsPerBlock) { val addedBlocks = blocks.cogroup(other.blocks, createPartitioner()) .map { case ((blockRowIndex, blockColIndex), (a, b)) => - if (a.size > 1 || b.size > 1) { - throw new SparkException("There are multiple MatrixBlocks with indices: " + - s"($blockRowIndex, $blockColIndex). Please remove them.") - } - if (a.isEmpty) { - new MatrixBlock((blockRowIndex, blockColIndex), b.head) - } else if (b.isEmpty) { - new MatrixBlock((blockRowIndex, blockColIndex), a.head) - } else { - val result = a.head.toBreeze + b.head.toBreeze - new MatrixBlock((blockRowIndex, blockColIndex), Matrices.fromBreeze(result)) - } + if (a.size > 1 || b.size > 1) { + throw new SparkException("There are multiple MatrixBlocks with indices: " + + s"($blockRowIndex, $blockColIndex). Please remove them.") + } + if (a.isEmpty) { + new MatrixBlock((blockRowIndex, blockColIndex), b.head) + } else if (b.isEmpty) { + new MatrixBlock((blockRowIndex, blockColIndex), a.head) + } else { + val result = binMap(a.head.toBreeze(), b.head.toBreeze()) + new MatrixBlock((blockRowIndex, blockColIndex), Matrices.fromBreeze(result)) + } } new BlockMatrix(addedBlocks, rowsPerBlock, colsPerBlock, numRows(), numCols()) } else { @@ -351,6 +355,28 @@ class BlockMatrix @Since("1.3.0") ( } } + /** + * Adds two block matrices together. The matrices must have the same size and matching + * `rowsPerBlock` and `colsPerBlock` values. If one of the blocks that are being added are + * instances of [[SparseMatrix]], the resulting sub matrix will also be a [[SparseMatrix]], even + * if it is being added to a [[DenseMatrix]]. If two dense matrices are added, the output will + * also be a [[DenseMatrix]]. + */ + @Since("1.3.0") + def add(other: BlockMatrix): BlockMatrix = + blockMap(other, (x: BDM[Double], y: BDM[Double]) => x + y) + + /** + * Subtracts two block matrices together. The matrices must have the same size and matching + * `rowsPerBlock` and `colsPerBlock` values. If one of the blocks that are being added are + * instances of [[SparseMatrix]], the resulting sub matrix will also be a [[SparseMatrix]], even + * if it is being added to a [[DenseMatrix]]. If two dense matrices are added, the output will + * also be a [[DenseMatrix]]. + */ + @Since("1.6.0") + def subtract(other: BlockMatrix): BlockMatrix = + blockMap(other, (x: BDM[Double], y: BDM[Double]) => x - y) + /** Block (i,j) --> Set of destination partitions */ private type BlockDestinations = Map[(Int, Int), Set[Int]] diff --git a/mllib/src/test/scala/org/apache/spark/mllib/linalg/distributed/BlockMatrixSuite.scala b/mllib/src/test/scala/org/apache/spark/mllib/linalg/distributed/BlockMatrixSuite.scala index d91ba8a6fdb72..df011930c90ff 100644 --- a/mllib/src/test/scala/org/apache/spark/mllib/linalg/distributed/BlockMatrixSuite.scala +++ b/mllib/src/test/scala/org/apache/spark/mllib/linalg/distributed/BlockMatrixSuite.scala @@ -192,6 +192,49 @@ class BlockMatrixSuite extends SparkFunSuite with MLlibTestSparkContext { assert(sparseBM.add(sparseBM).toBreeze() === sparseBM.add(denseBM).toBreeze()) } + test("subtract") { + val blocks: Seq[((Int, Int), Matrix)] = Seq( + ((0, 0), new DenseMatrix(2, 2, Array(1.0, 0.0, 0.0, 2.0))), + ((0, 1), new DenseMatrix(2, 2, Array(0.0, 1.0, 0.0, 0.0))), + ((1, 0), new DenseMatrix(2, 2, Array(3.0, 0.0, 1.0, 1.0))), + ((1, 1), new DenseMatrix(2, 2, Array(1.0, 2.0, 0.0, 1.0))), + ((2, 0), new DenseMatrix(1, 2, Array(1.0, 0.0))), // Added block that doesn't exist in A + ((2, 1), new DenseMatrix(1, 2, Array(1.0, 5.0)))) + val rdd = sc.parallelize(blocks, numPartitions) + val B = new BlockMatrix(rdd, rowPerPart, colPerPart) + + val expected = BDM( + (0.0, 0.0, 0.0, 0.0), + (0.0, 0.0, 0.0, 0.0), + (0.0, 0.0, 0.0, 0.0), + (0.0, 0.0, 0.0, 0.0), + (1.0, 0.0, 0.0, 0.0)) + + val AsubtractB = gridBasedMat.subtract(B) + assert(AsubtractB.numRows() === m) + assert(AsubtractB.numCols() === B.numCols()) + assert(AsubtractB.toBreeze() === expected) + + val C = new BlockMatrix(rdd, rowPerPart, colPerPart, m, n + 1) // columns don't match + intercept[IllegalArgumentException] { + gridBasedMat.subtract(C) + } + val largerBlocks: Seq[((Int, Int), Matrix)] = Seq( + ((0, 0), new DenseMatrix(4, 4, new Array[Double](16))), + ((1, 0), new DenseMatrix(1, 4, Array(1.0, 0.0, 1.0, 5.0)))) + val C2 = new BlockMatrix(sc.parallelize(largerBlocks, numPartitions), 4, 4, m, n) + intercept[SparkException] { // partitioning doesn't match + gridBasedMat.subtract(C2) + } + // adding BlockMatrices composed of SparseMatrices + val sparseBlocks = for (i <- 0 until 4) yield ((i / 2, i % 2), SparseMatrix.speye(4)) + val denseBlocks = for (i <- 0 until 4) yield ((i / 2, i % 2), DenseMatrix.eye(4)) + val sparseBM = new BlockMatrix(sc.makeRDD(sparseBlocks, 4), 4, 4, 8, 8) + val denseBM = new BlockMatrix(sc.makeRDD(denseBlocks, 4), 4, 4, 8, 8) + + assert(sparseBM.subtract(sparseBM).toBreeze() === sparseBM.subtract(denseBM).toBreeze()) + } + test("multiply") { // identity matrix val blocks: Seq[((Int, Int), Matrix)] = Seq( From b28ddaebf86da992c938cff8031efcdd64f181b9 Mon Sep 17 00:00:00 2001 From: "Ehsan M.Kermani" Date: Tue, 24 Nov 2015 15:47:15 -0800 Subject: [PATCH 2/6] issue resolved - add Breeze Matrix --- .../spark/mllib/linalg/distributed/BlockMatrix.scala | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mllib/src/main/scala/org/apache/spark/mllib/linalg/distributed/BlockMatrix.scala b/mllib/src/main/scala/org/apache/spark/mllib/linalg/distributed/BlockMatrix.scala index 0060f35932654..e32eb76b12bad 100644 --- a/mllib/src/main/scala/org/apache/spark/mllib/linalg/distributed/BlockMatrix.scala +++ b/mllib/src/main/scala/org/apache/spark/mllib/linalg/distributed/BlockMatrix.scala @@ -19,7 +19,7 @@ package org.apache.spark.mllib.linalg.distributed import scala.collection.mutable.ArrayBuffer -import breeze.linalg.{DenseMatrix => BDM} +import breeze.linalg.{DenseMatrix => BDM, Matrix => BM} import org.apache.spark.{Logging, Partitioner, SparkException} import org.apache.spark.annotation.Since @@ -321,14 +321,14 @@ class BlockMatrix @Since("1.3.0") ( * it applies an associative binary function on their corresponding blocks. * * @param other The BlockMatrix to operate on - * @param binMap An associative function taking two dense breeze matrices and returning one + * @param binMap An associative function taking two dense breeze matrices and returning a * dense breeze matrix * @return A [[BlockMatrix]] whose blocks are the results of a specified binary map on blocks * of `this` and `other`. */ private[mllib] def blockMap( other: BlockMatrix, - binMap: (BDM[Double], BDM[Double]) => BDM[Double]): BlockMatrix = { + binMap: (BM[Double], BM[Double]) => BM[Double]): BlockMatrix = { require(numRows() == other.numRows(), "Both matrices must have the same number of rows. " + s"A.numRows: ${numRows()}, B.numRows: ${other.numRows()}") require(numCols() == other.numCols(), "Both matrices must have the same number of columns. " + @@ -345,7 +345,7 @@ class BlockMatrix @Since("1.3.0") ( } else if (b.isEmpty) { new MatrixBlock((blockRowIndex, blockColIndex), a.head) } else { - val result = binMap(a.head.toBreeze(), b.head.toBreeze()) + val result = binMap(a.head.toBreeze, b.head.toBreeze) new MatrixBlock((blockRowIndex, blockColIndex), Matrices.fromBreeze(result)) } } @@ -364,7 +364,7 @@ class BlockMatrix @Since("1.3.0") ( */ @Since("1.3.0") def add(other: BlockMatrix): BlockMatrix = - blockMap(other, (x: BDM[Double], y: BDM[Double]) => x + y) + blockMap(other, (x: BM[Double], y: BM[Double]) => x + y) /** * Subtracts two block matrices together. The matrices must have the same size and matching @@ -375,7 +375,7 @@ class BlockMatrix @Since("1.3.0") ( */ @Since("1.6.0") def subtract(other: BlockMatrix): BlockMatrix = - blockMap(other, (x: BDM[Double], y: BDM[Double]) => x - y) + blockMap(other, (x: BM[Double], y: BM[Double]) => x - y) /** Block (i,j) --> Set of destination partitions */ private type BlockDestinations = Map[(Int, Int), Set[Int]] From 7ca9036de16c3d1ff9c552e9855245a538f4f9cb Mon Sep 17 00:00:00 2001 From: "Ehsan M.Kermani" Date: Thu, 14 Jan 2016 18:28:03 -0800 Subject: [PATCH 3/6] blockMap and annotation are corrected and doc is modified --- .../linalg/distributed/BlockMatrix.scala | 61 ++++++++++--------- .../linalg/distributed/BlockMatrixSuite.scala | 4 +- 2 files changed, 33 insertions(+), 32 deletions(-) diff --git a/mllib/src/main/scala/org/apache/spark/mllib/linalg/distributed/BlockMatrix.scala b/mllib/src/main/scala/org/apache/spark/mllib/linalg/distributed/BlockMatrix.scala index e32eb76b12bad..18551ae193861 100644 --- a/mllib/src/main/scala/org/apache/spark/mllib/linalg/distributed/BlockMatrix.scala +++ b/mllib/src/main/scala/org/apache/spark/mllib/linalg/distributed/BlockMatrix.scala @@ -318,11 +318,10 @@ class BlockMatrix @Since("1.3.0") ( /** * For given matrices `this` and `other` of compatible dimensions and compatible block dimensions, - * it applies an associative binary function on their corresponding blocks. + * it applies a binary function on their corresponding blocks. * - * @param other The BlockMatrix to operate on - * @param binMap An associative function taking two dense breeze matrices and returning a - * dense breeze matrix + * @param other The second BlockMatrix argument for the operator specified by `binMap` + * @param binMap A function taking two breeze matrices and returning a breeze matrix * @return A [[BlockMatrix]] whose blocks are the results of a specified binary map on blocks * of `this` and `other`. */ @@ -334,46 +333,48 @@ class BlockMatrix @Since("1.3.0") ( require(numCols() == other.numCols(), "Both matrices must have the same number of columns. " + s"A.numCols: ${numCols()}, B.numCols: ${other.numCols()}") if (rowsPerBlock == other.rowsPerBlock && colsPerBlock == other.colsPerBlock) { - val addedBlocks = blocks.cogroup(other.blocks, createPartitioner()) + val newBlocks = blocks.cogroup(other.blocks, createPartitioner()) .map { case ((blockRowIndex, blockColIndex), (a, b)) => - if (a.size > 1 || b.size > 1) { - throw new SparkException("There are multiple MatrixBlocks with indices: " + - s"($blockRowIndex, $blockColIndex). Please remove them.") - } - if (a.isEmpty) { - new MatrixBlock((blockRowIndex, blockColIndex), b.head) - } else if (b.isEmpty) { - new MatrixBlock((blockRowIndex, blockColIndex), a.head) - } else { - val result = binMap(a.head.toBreeze, b.head.toBreeze) - new MatrixBlock((blockRowIndex, blockColIndex), Matrices.fromBreeze(result)) - } + if (a.size > 1 || b.size > 1) { + throw new SparkException("There are multiple MatrixBlocks with indices: " + + s"($blockRowIndex, $blockColIndex). Please remove them.") + } + if (a.isEmpty) { + val zeroBlock = BM.zeros[Double](b.head.numRows, b.head.numCols) + val result = binMap(zeroBlock, b.head.toBreeze) + new MatrixBlock((blockRowIndex, blockColIndex), Matrices.fromBreeze(result)) + } else if (b.isEmpty) { + new MatrixBlock((blockRowIndex, blockColIndex), a.head) + } else { + val result = binMap(a.head.toBreeze, b.head.toBreeze) + new MatrixBlock((blockRowIndex, blockColIndex), Matrices.fromBreeze(result)) + } } - new BlockMatrix(addedBlocks, rowsPerBlock, colsPerBlock, numRows(), numCols()) + new BlockMatrix(newBlocks, rowsPerBlock, colsPerBlock, numRows(), numCols()) } else { - throw new SparkException("Cannot add matrices with different block dimensions") + throw new SparkException("Cannot perform on matrices with different block dimensions") } } /** - * Adds two block matrices together. The matrices must have the same size and matching - * `rowsPerBlock` and `colsPerBlock` values. If one of the blocks that are being added are - * instances of [[SparseMatrix]], the resulting sub matrix will also be a [[SparseMatrix]], even - * if it is being added to a [[DenseMatrix]]. If two dense matrices are added, the output will - * also be a [[DenseMatrix]]. + * Adds the given block matrix `other` to `this` block matrix: `this + other`. + * The matrices must have the same size and matching `rowsPerBlock` and `colsPerBlock` values. + * If one of the blocks that are being added are instances of [[SparseMatrix]], the resulting + * sub matrix will also be a [[SparseMatrix]], even if it is being added to a [[DenseMatrix]]. + * If two dense matrices are added, the output will also be a [[DenseMatrix]]. */ @Since("1.3.0") def add(other: BlockMatrix): BlockMatrix = blockMap(other, (x: BM[Double], y: BM[Double]) => x + y) /** - * Subtracts two block matrices together. The matrices must have the same size and matching - * `rowsPerBlock` and `colsPerBlock` values. If one of the blocks that are being added are - * instances of [[SparseMatrix]], the resulting sub matrix will also be a [[SparseMatrix]], even - * if it is being added to a [[DenseMatrix]]. If two dense matrices are added, the output will - * also be a [[DenseMatrix]]. + * Subtracts the given block matrix `other` from `this` block matrix: `this - other`. + * The matrices must have the same size and matching `rowsPerBlock` and `colsPerBlock` values. + * If one of the blocks that are being subtracted are instances of [[SparseMatrix]], the resulting + * sub matrix will also be a [[SparseMatrix]], even if it is being subtracted from a [[DenseMatrix]]. + * If two dense matrices are subtracted, the output will also be a [[DenseMatrix]]. */ - @Since("1.6.0") + @Since("2.0.0") def subtract(other: BlockMatrix): BlockMatrix = blockMap(other, (x: BM[Double], y: BM[Double]) => x - y) diff --git a/mllib/src/test/scala/org/apache/spark/mllib/linalg/distributed/BlockMatrixSuite.scala b/mllib/src/test/scala/org/apache/spark/mllib/linalg/distributed/BlockMatrixSuite.scala index df011930c90ff..f737d2c51a262 100644 --- a/mllib/src/test/scala/org/apache/spark/mllib/linalg/distributed/BlockMatrixSuite.scala +++ b/mllib/src/test/scala/org/apache/spark/mllib/linalg/distributed/BlockMatrixSuite.scala @@ -208,7 +208,7 @@ class BlockMatrixSuite extends SparkFunSuite with MLlibTestSparkContext { (0.0, 0.0, 0.0, 0.0), (0.0, 0.0, 0.0, 0.0), (0.0, 0.0, 0.0, 0.0), - (1.0, 0.0, 0.0, 0.0)) + (-1.0, 0.0, 0.0, 0.0)) val AsubtractB = gridBasedMat.subtract(B) assert(AsubtractB.numRows() === m) @@ -226,7 +226,7 @@ class BlockMatrixSuite extends SparkFunSuite with MLlibTestSparkContext { intercept[SparkException] { // partitioning doesn't match gridBasedMat.subtract(C2) } - // adding BlockMatrices composed of SparseMatrices + // subtracting BlockMatrices composed of SparseMatrices val sparseBlocks = for (i <- 0 until 4) yield ((i / 2, i % 2), SparseMatrix.speye(4)) val denseBlocks = for (i <- 0 until 4) yield ((i / 2, i % 2), DenseMatrix.eye(4)) val sparseBM = new BlockMatrix(sc.makeRDD(sparseBlocks, 4), 4, 4, 8, 8) From e32cd4fa06e589cabb18964becf2f2fc1b826b28 Mon Sep 17 00:00:00 2001 From: "Ehsan M.Kermani" Date: Thu, 14 Jan 2016 19:19:50 -0800 Subject: [PATCH 4/6] style checked --- .../mllib/linalg/distributed/BlockMatrix.scala | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/mllib/src/main/scala/org/apache/spark/mllib/linalg/distributed/BlockMatrix.scala b/mllib/src/main/scala/org/apache/spark/mllib/linalg/distributed/BlockMatrix.scala index 18551ae193861..52e3bd78ed03d 100644 --- a/mllib/src/main/scala/org/apache/spark/mllib/linalg/distributed/BlockMatrix.scala +++ b/mllib/src/main/scala/org/apache/spark/mllib/linalg/distributed/BlockMatrix.scala @@ -358,10 +358,11 @@ class BlockMatrix @Since("1.3.0") ( /** * Adds the given block matrix `other` to `this` block matrix: `this + other`. - * The matrices must have the same size and matching `rowsPerBlock` and `colsPerBlock` values. - * If one of the blocks that are being added are instances of [[SparseMatrix]], the resulting - * sub matrix will also be a [[SparseMatrix]], even if it is being added to a [[DenseMatrix]]. - * If two dense matrices are added, the output will also be a [[DenseMatrix]]. + * The matrices must have the same size and matching `rowsPerBlock` and `colsPerBlock` + * values. If one of the blocks that are being added are instances of [[SparseMatrix]], + * the resulting sub matrix will also be a [[SparseMatrix]], even if it is being added + * to a [[DenseMatrix]]. If two dense matrices are added, the output will also be a + * [[DenseMatrix]]. */ @Since("1.3.0") def add(other: BlockMatrix): BlockMatrix = @@ -369,10 +370,11 @@ class BlockMatrix @Since("1.3.0") ( /** * Subtracts the given block matrix `other` from `this` block matrix: `this - other`. - * The matrices must have the same size and matching `rowsPerBlock` and `colsPerBlock` values. - * If one of the blocks that are being subtracted are instances of [[SparseMatrix]], the resulting - * sub matrix will also be a [[SparseMatrix]], even if it is being subtracted from a [[DenseMatrix]]. - * If two dense matrices are subtracted, the output will also be a [[DenseMatrix]]. + * The matrices must have the same size and matching `rowsPerBlock` and `colsPerBlock` + * values. If one of the blocks that are being subtracted are instances of [[SparseMatrix]], + * the resulting sub matrix will also be a [[SparseMatrix]], even if it is being subtracted + * from a [[DenseMatrix]]. If two dense matrices are subtracted, the output will also be a + * [[DenseMatrix]]. */ @Since("2.0.0") def subtract(other: BlockMatrix): BlockMatrix = From 4353e0252d51060ee381956a4b56fd4d827203e6 Mon Sep 17 00:00:00 2001 From: "Ehsan M.Kermani" Date: Thu, 21 Jan 2016 14:42:37 -0800 Subject: [PATCH 5/6] Note added to blockMap --- .../org/apache/spark/mllib/linalg/distributed/BlockMatrix.scala | 2 ++ 1 file changed, 2 insertions(+) diff --git a/mllib/src/main/scala/org/apache/spark/mllib/linalg/distributed/BlockMatrix.scala b/mllib/src/main/scala/org/apache/spark/mllib/linalg/distributed/BlockMatrix.scala index 52e3bd78ed03d..985eb55c8e50a 100644 --- a/mllib/src/main/scala/org/apache/spark/mllib/linalg/distributed/BlockMatrix.scala +++ b/mllib/src/main/scala/org/apache/spark/mllib/linalg/distributed/BlockMatrix.scala @@ -324,6 +324,8 @@ class BlockMatrix @Since("1.3.0") ( * @param binMap A function taking two breeze matrices and returning a breeze matrix * @return A [[BlockMatrix]] whose blocks are the results of a specified binary map on blocks * of `this` and `other`. + * Note: `blockMap` ONLY works for `add` and `subtract` methods and it does not support + * operators such as (a, b) => -a + b */ private[mllib] def blockMap( other: BlockMatrix, From ef176e634d6cd4df97666ca48162f048434884a4 Mon Sep 17 00:00:00 2001 From: "Ehsan M.Kermani" Date: Tue, 1 Mar 2016 15:45:42 -0800 Subject: [PATCH 6/6] add TODO --- .../org/apache/spark/mllib/linalg/distributed/BlockMatrix.scala | 1 + 1 file changed, 1 insertion(+) diff --git a/mllib/src/main/scala/org/apache/spark/mllib/linalg/distributed/BlockMatrix.scala b/mllib/src/main/scala/org/apache/spark/mllib/linalg/distributed/BlockMatrix.scala index 985eb55c8e50a..5b251a54e4f89 100644 --- a/mllib/src/main/scala/org/apache/spark/mllib/linalg/distributed/BlockMatrix.scala +++ b/mllib/src/main/scala/org/apache/spark/mllib/linalg/distributed/BlockMatrix.scala @@ -326,6 +326,7 @@ class BlockMatrix @Since("1.3.0") ( * of `this` and `other`. * Note: `blockMap` ONLY works for `add` and `subtract` methods and it does not support * operators such as (a, b) => -a + b + * TODO: Make the use of zero matrices more storage efficient. */ private[mllib] def blockMap( other: BlockMatrix,