You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@systemds.apache.org by mb...@apache.org on 2025/05/10 10:12:01 UTC

(systemds) branch main updated: [SYSTEMDS-3883] Added Vector API and FFM API prototypes to staging

This is an automated email from the ASF dual-hosted git repository.

mboehm7 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/systemds.git


The following commit(s) were added to refs/heads/main by this push:
     new 4ad40e0bdd [SYSTEMDS-3883] Added Vector API and FFM API prototypes to staging
4ad40e0bdd is described below

commit 4ad40e0bdd905695f1850fbcec8d459e3e99bcd5
Author: moatabay <mo...@outlook.com>
AuthorDate: Sat May 10 12:09:46 2025 +0200

    [SYSTEMDS-3883] Added Vector API and FFM API prototypes to staging
    
    Closes #2246.
---
 .../SIMD-FFM-prototypes/LibMatrixBincellFFM.java   | 2302 +++++++++
 .../SIMD-FFM-prototypes/LibMatrixBincellSIMD.java  | 2485 ++++++++++
 .../SIMD-FFM-prototypes/LibMatrixMultFFM.java      | 5144 ++++++++++++++++++++
 .../SIMD-FFM-prototypes/LibMatrixMultSIMD.java     | 5026 +++++++++++++++++++
 .../SIMD-FFM-prototypes/LibMatrixNativeFFM.java    |  408 ++
 5 files changed, 15365 insertions(+)

diff --git a/scripts/staging/SIMD-FFM-prototypes/LibMatrixBincellFFM.java b/scripts/staging/SIMD-FFM-prototypes/LibMatrixBincellFFM.java
new file mode 100644
index 0000000000..4c6b49c63f
--- /dev/null
+++ b/scripts/staging/SIMD-FFM-prototypes/LibMatrixBincellFFM.java
@@ -0,0 +1,2302 @@
+/*
+ * 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.sysds.runtime.matrix.data;
+
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.ValueLayout;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+
+import org.apache.commons.lang3.NotImplementedException;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.sysds.runtime.DMLRuntimeException;
+import org.apache.sysds.runtime.data.*;
+import org.apache.sysds.runtime.functionobjects.*;
+import org.apache.sysds.runtime.functionobjects.Builtin.BuiltinCode;
+import org.apache.sysds.runtime.matrix.operators.BinaryOperator;
+import org.apache.sysds.runtime.matrix.operators.ScalarOperator;
+import org.apache.sysds.runtime.matrix.operators.UnaryOperator;
+import org.apache.sysds.runtime.util.CommonThreadPool;
+import org.apache.sysds.runtime.util.DataConverter;
+import org.apache.sysds.runtime.util.SortUtils;
+import org.apache.sysds.runtime.util.UtilFunctions;
+
+/**
+ * Library for binary cellwise operations (incl arithmetic, relational, etc). Currently,
+ * we don't have dedicated support for the individual operations but for categories of
+ * operations and combinations of dense/sparse and MM/MV. Safe/unsafe refer to sparse-safe
+ * and sparse-unsafe operations.
+ * 
+ */
+public class LibMatrixBincellFFM {
+
+	private static final Log LOG = LogFactory.getLog(LibMatrixBincellFFM.class.getName());
+	private static final long PAR_NUMCELL_THRESHOLD2 = 16*1024;   //Min 16K elements
+	public static MemorySegment memorySegment1, memorySegment2, memorySegmentRet;
+	public static MemorySegment[] memorySegments1;
+	private static final ValueLayout.OfDouble layout = ValueLayout.JAVA_DOUBLE;
+
+	public enum BinaryAccessType {
+		MATRIX_MATRIX,
+		MATRIX_COL_VECTOR,
+		MATRIX_ROW_VECTOR,
+		COL_VECTOR_MATRIX,
+		ROW_VECTOR_MATRIX,
+		OUTER_VECTOR_VECTOR,
+		INVALID;
+		public boolean isMatrixVector() {
+			return this == MATRIX_COL_VECTOR
+				|| this == MATRIX_ROW_VECTOR
+				|| this == COL_VECTOR_MATRIX
+				|| this == ROW_VECTOR_MATRIX;
+		}
+	}
+
+	private LibMatrixBincellFFM() {
+		//prevent instantiation via private constructor
+	}
+	
+	///////////////////////////////////
+	// public matrix bincell interface
+	///////////////////////////////////
+	
+	public static MatrixBlock uncellOp(MatrixBlock m1, MatrixBlock ret, UnaryOperator op) {
+		if(!m1.sparse && !m1.isEmptyBlock(false) 
+			&& op.getNumThreads() > 1 && m1.getLength() > PAR_NUMCELL_THRESHOLD2  ) {
+			//note: we apply multi-threading in a best-effort manner here
+			//only for expensive operators such as exp, log, sigmoid, because
+			//otherwise allocation, read and write anyway dominates
+			if (!op.isInplace() || m1.isEmpty())
+				ret.allocateDenseBlock(false);
+			else
+				ret = m1;
+
+			int k = op.getNumThreads();
+			DenseBlock a = m1.getDenseBlock();
+			DenseBlock c = ret.getDenseBlock();
+			ExecutorService pool = CommonThreadPool.get(k);
+			try {
+				ArrayList<UncellTask> tasks = new ArrayList<>();
+				ArrayList<Integer> blklens = UtilFunctions.getBalancedBlockSizesDefault(ret.rlen, k, false);
+				for( int i=0, lb=0; i<blklens.size(); lb+=blklens.get(i), i++ )
+					tasks.add(new UncellTask(a, c, op, lb, lb+blklens.get(i)));
+				List<Future<Long>> taskret = pool.invokeAll(tasks);
+				
+				//aggregate non-zeros
+				ret.nonZeros = 0; //reset after execute
+				for( Future<Long> task : taskret )
+					ret.nonZeros += task.get();
+			}
+			catch(InterruptedException | ExecutionException ex) {
+				throw new DMLRuntimeException(ex);
+			}
+			finally{
+				pool.shutdown();
+			}
+		}
+		else {
+			if (op.isInplace() && !m1.isInSparseFormat() )
+				ret = m1;
+			
+			//default execute unary operations
+			if(op.sparseSafe)
+				sparseUnaryOperations(m1, ret, op);
+			else
+				denseUnaryOperations(m1, ret, op);
+		}
+		return ret;
+	}
+	
+	/**
+	 * matrix-scalar, scalar-matrix binary operations.
+	 * 
+	 * @param m1 input matrix
+	 * @param ret result matrix
+	 * @param op scalar operator
+	 */
+	public static void bincellOp(MatrixBlock m1, MatrixBlock ret, ScalarOperator op) {
+		//check internal assumptions 
+		if(   (op.sparseSafe && m1.isInSparseFormat()!=ret.isInSparseFormat())
+			||(!op.sparseSafe && ret.isInSparseFormat()) ) {
+			throw new DMLRuntimeException("Wrong output representation for safe="+op.sparseSafe+": "+m1.isInSparseFormat()+", "+ret.isInSparseFormat());
+		}
+
+		//execute binary cell operations
+		if(op.sparseSafe)
+			safeBinaryScalar(m1, ret, op, 0, m1.rlen);
+		else
+			unsafeBinaryScalar(m1, ret, op);
+		
+		//ensure empty results sparse representation 
+		//(no additional memory requirements)
+		if( ret.isEmptyBlock(false) )
+			ret.examSparsity();
+	}
+	
+	public static void bincellOp(MatrixBlock m1, MatrixBlock ret, ScalarOperator op, int k) {
+		//check internal assumptions 
+		if(   (op.sparseSafe && m1.isInSparseFormat()!=ret.isInSparseFormat())
+			||(!op.sparseSafe && ret.isInSparseFormat()) ) {
+			throw new DMLRuntimeException("Wrong output representation for safe="+op.sparseSafe+": "+m1.isInSparseFormat()+", "+ret.isInSparseFormat());
+		}
+		
+		//fallback to singlet-threaded for special cases
+		if( m1.isEmpty() || !op.sparseSafe 
+			|| ret.getLength() < PAR_NUMCELL_THRESHOLD2 ) {
+			bincellOp(m1, ret, op);
+			return;
+		}
+		
+		//preallocate dense/sparse block for multi-threaded operations
+		ret.allocateBlock();
+		
+		ExecutorService pool = CommonThreadPool.get(k);
+		try {
+			//execute binary cell operations
+			ArrayList<BincellScalarTask> tasks = new ArrayList<>();
+			ArrayList<Integer> blklens = UtilFunctions.getBalancedBlockSizesDefault(ret.rlen, k, false);
+			for( int i=0, lb=0; i<blklens.size(); lb+=blklens.get(i), i++ )
+				tasks.add(new BincellScalarTask(m1, ret, op, lb, lb+blklens.get(i)));
+			List<Future<Long>> taskret = pool.invokeAll(tasks);
+			
+			//aggregate non-zeros
+			ret.nonZeros = 0; //reset after execute
+			for( Future<Long> task : taskret )
+				ret.nonZeros += task.get();
+		}
+		catch(InterruptedException | ExecutionException ex) {
+			throw new DMLRuntimeException(ex);
+		}
+		finally{
+			pool.shutdown();
+		}
+		
+		//ensure empty results sparse representation 
+		//(no additional memory requirements)
+		if( ret.isEmptyBlock(false) )
+			ret.examSparsity();
+	}
+	
+	/**
+	 * matrix-matrix binary operations, MM, MV
+	 * 
+	 * @param m1 input matrix 1
+	 * @param m2 input matrix 2
+	 * @param ret result matrix
+	 * @param op binary operator
+	 */
+	public static void bincellOp(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, BinaryOperator op) {
+		BinaryAccessType atype = getBinaryAccessType(m1, m2);
+
+		// preallocate for consistency (but be careful 
+		// not to allocate if empty inputs might allow early abort)
+		if( atype == BinaryAccessType.MATRIX_MATRIX
+			&& !(m1.isEmpty() || m2.isEmpty()) )
+		{
+			ret.allocateBlock(); //chosen outside
+		}
+
+		//execute binary cell operations
+		long nnz = 0;
+		if(op.sparseSafe || isSparseSafeDivide(op, m2))
+			nnz = safeBinary(m1, m2, ret, op, atype, 0, m1.rlen);
+		else
+			nnz = unsafeBinary(m1, m2, ret, op, 0, m1.rlen);
+		ret.setNonZeros(nnz);
+		
+		//ensure empty results sparse representation 
+		//(no additional memory requirements)
+		if( ret.isEmptyBlock(false) )
+			ret.examSparsity();
+	}
+	
+	public static void bincellOp(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, BinaryOperator op, int k) {
+		BinaryAccessType atype = getBinaryAccessType(m1, m2);
+		
+		// fallback to sequential computation for specialized operations
+		if( m1.isEmpty() || m2.isEmpty()
+ 			|| ret.getLength() < PAR_NUMCELL_THRESHOLD2
+			|| ((op.sparseSafe || isSparseSafeDivide(op, m2))
+				&& !(atype == BinaryAccessType.MATRIX_MATRIX
+					|| atype.isMatrixVector() && isAllDense(m1, m2, ret))))
+		{
+			bincellOp(m1, m2, ret, op);
+			return;
+		}
+		
+		//preallocate dense/sparse block for multi-threaded operations
+		ret.allocateBlock(); //chosen outside
+		
+		ExecutorService pool = CommonThreadPool.get(k);
+		try {
+			//execute binary cell operations
+			ArrayList<BincellTask> tasks = new ArrayList<>();
+			ArrayList<Integer> blklens = UtilFunctions.getBalancedBlockSizesDefault(ret.rlen, k, false);
+			for( int i=0, lb=0; i<blklens.size(); lb+=blklens.get(i), i++ )
+				tasks.add(new BincellTask(m1, m2, ret, op, atype, lb, lb+blklens.get(i)));
+			List<Future<Long>> taskret = pool.invokeAll(tasks);
+			
+			//aggregate non-zeros
+			ret.nonZeros = 0; //reset after execute
+			for( Future<Long> task : taskret )
+				ret.nonZeros += task.get();
+		}
+		catch(InterruptedException | ExecutionException ex) {
+			throw new DMLRuntimeException(ex);
+		}
+		finally{
+			pool.shutdown();
+		}
+		
+		//ensure empty results sparse representation
+		//(no additional memory requirements)
+		if( ret.isEmptyBlock(false) )
+			ret.examSparsity();
+	}
+	
+	/**
+	 * NOTE: operations in place always require m1 and m2 to be of equal dimensions
+	 * 
+	 * defaults to right side operations, updating the m1 matrix with like:
+	 * 
+	 * m1ret op m2
+	 * 
+	 * @param m1ret result matrix updated in place
+	 * @param m2 matrix block the other matrix to take values from
+	 * @param op binary operator the operator that is placed in the middle of m1ret and m2
+	 * @return The same pointer to m1ret argument, and the updated result.
+	 */
+	public static MatrixBlock bincellOpInPlace(MatrixBlock m1ret, MatrixBlock m2, BinaryOperator op) {
+		return bincellOpInPlaceRight(m1ret, m2, op);
+	}
+
+	/**
+	 * Right side operations, updating the m1 matrix like:
+	 * 
+	 * m1ret op m2
+	 * 
+	 * @param m1ret result matrix updated in place
+	 * @param m2 matrix block the other matrix to take values from
+	 * @param op binary operator the operator that is placed in the middle of m1ret and m2
+	 * @return The result MatrixBlock (same object pointer to m1ret argument)
+	 */
+	public static MatrixBlock bincellOpInPlaceRight(MatrixBlock m1ret, MatrixBlock m2, BinaryOperator op) {
+		//execute binary cell operations
+		if(op.sparseSafe || isSparseSafeDivide(op, m2))
+			safeBinaryInPlace(m1ret, m2, op);
+		else
+			unsafeBinaryInPlace(m1ret, m2, op);
+		
+		//ensure empty results sparse representation 
+		//(no additional memory requirements)
+		if( m1ret.isEmptyBlock(false) )
+			m1ret.examSparsity();
+		return m1ret;
+	}
+
+	/**
+	 * Left side operations, updating the m1 matrix like:
+	 * 
+	 * m2 op m1ret
+	 * 
+	 * @param m1ret result matrix updated in place
+	 * @param m2 matrix block the other matrix to take values from
+	 * @param op binary operator the operator that is placed in the middle of m1ret and m2
+	 * @return The result MatrixBlock (same object pointer to m1ret argument)
+	 */
+	public static MatrixBlock bincellOpInPlaceLeft(MatrixBlock m1ret, MatrixBlock m2, BinaryOperator op) {
+		final int nRows = m1ret.getNumRows();
+		final int nCols = m1ret.getNumColumns();
+		if(m1ret.isInSparseFormat()){
+			// not doing in place, since the m1ret is in sparse format, and m2 might make it dense.
+			// this is not ideal either, but makes it work
+			LOG.warn("Inefficient bincell op in place left, because output is materialized in new matrix");
+			MatrixBlock right = new MatrixBlock(nRows, nCols, true);
+			right.copyShallow(m1ret);
+			m1ret.cleanupBlock(true, true);
+			bincellOp(m2, right, m1ret, op);
+			return m1ret;
+		}
+
+		// m1ret is dense:
+		final double[] retV = m1ret.getDenseBlockValues();
+		final ValueFunction f = op.fn;
+		
+		if(m2.isInSparseFormat() && op.sparseSafe) {
+			final SparseBlock sb = m2.getSparseBlock();
+			for(int row = 0; row < nRows; row++){
+				if(sb.isEmpty(row)){
+					continue;
+				}
+				final int apos = sb.pos(row);
+				final int alen = sb.size(row) + apos;
+				final int[] aix = sb.indexes(row);
+				final double[] aval = sb.values(row);
+				final int offsetV = row * nCols;
+				for(int j = apos; j < alen; j++){
+					final int idx = offsetV + aix[j];
+					retV[idx] =  f.execute(aval[j], retV[idx]);
+				}
+			}
+		}
+		else if(m2.isInSparseFormat()){
+			throw new NotImplementedException("Not implemented left bincell in place unsafe operations");
+		}
+		else{
+			final double[] m2V = m2.getDenseBlockValues();
+			final int size = nRows * nCols;
+			for(int i = 0; i < size; i++ ){
+				retV[i] = f.execute(m2V[i], retV[i]);
+			}
+			
+			if( m1ret.isEmptyBlock(false) )
+				m1ret.examSparsity();
+		}
+		return m1ret;
+	}
+
+	public static BinaryAccessType getBinaryAccessType(MatrixBlock m1, MatrixBlock m2)
+	{
+		int rlen1 = m1.rlen;
+		int rlen2 = m2.rlen;
+		int clen1 = m1.clen;
+		int clen2 = m2.clen;
+		
+		if( rlen1 == rlen2 && clen1 == clen2 )
+			return BinaryAccessType.MATRIX_MATRIX;
+		else if( clen1 > 1 && clen2 == 1 )
+			return BinaryAccessType.MATRIX_COL_VECTOR;
+		else if( rlen1 > 1 && clen1 > 1 && rlen2 == 1 )
+			return BinaryAccessType.MATRIX_ROW_VECTOR;
+		else if( clen1 == 1 && rlen2 == 1 )
+			return BinaryAccessType.OUTER_VECTOR_VECTOR;
+		else
+			return BinaryAccessType.INVALID;
+	}
+
+	public static BinaryAccessType getBinaryAccessTypeExtended(MatrixBlock m1, MatrixBlock m2) {
+		final int rlen1 = m1.rlen;
+		final int rlen2 = m2.rlen;
+		final int clen1 = m1.clen;
+		final int clen2 = m2.clen;
+
+		if(rlen1 == rlen2) {
+			if(clen1 == clen2)
+				return BinaryAccessType.MATRIX_MATRIX;
+			else if(clen1 < clen2)
+				return BinaryAccessType.COL_VECTOR_MATRIX;
+			else
+				return BinaryAccessType.MATRIX_COL_VECTOR;
+		}
+		else if(clen1 == clen2) {
+			if(rlen1 < rlen2)
+				return BinaryAccessType.ROW_VECTOR_MATRIX;
+			else
+				return BinaryAccessType.MATRIX_ROW_VECTOR;
+		}
+		else if(clen1 == 1 && rlen2 == 1)
+			return BinaryAccessType.OUTER_VECTOR_VECTOR;
+		else
+			return BinaryAccessType.INVALID;
+	}
+
+	public static void isValidDimensionsBinary(MatrixBlock m1, MatrixBlock m2)
+	{
+		final int rlen1 = m1.rlen;
+		final int clen1 = m1.clen;
+		final int rlen2 = m2.rlen;
+		final int clen2 = m2.clen;
+		
+		//currently we support three major binary cellwise operations:
+		//1) MM (where both dimensions need to match)
+		//2) MV operations w/ V either being a right-hand-side column or row vector 
+		//  (where one dimension needs to match and the other dimension is 1)
+		//3) VV outer vector operations w/ a common dimension of 1 
+		boolean isValid = (   (rlen1 == rlen2 && clen1==clen2)        //MM 
+							|| (rlen1 == rlen2 && clen1 > 1 && clen2 == 1) //MVc
+							|| (clen1 == clen2 && rlen1 > 1 && rlen2 == 1) //MVr
+							|| (clen1 == 1 && rlen2 == 1 ) );              //VV
+
+		if( !isValid ) {
+			throw new DMLRuntimeException("Block sizes are not matched for binary " +
+					"cell operations: " + rlen1 + "x" + clen1 + " vs " + rlen2 + "x" + clen2);
+		}
+	}
+
+	public static void isValidDimensionsBinaryExtended(MatrixBlock m1, MatrixBlock m2) {
+		final int rlen1 = m1.rlen;
+		final int clen1 = m1.clen;
+		final int rlen2 = m2.rlen;
+		final int clen2 = m2.clen;
+
+		// Added extra 2 options
+		// 2a) VM operations with V either being a left-hand-side column or row vector.
+		boolean isValid = ((rlen1 == rlen2 && clen1 == clen2) // MM
+			|| (rlen1 == rlen2 && clen1 > 1 && clen2 == 1) // MVc
+			|| (rlen1 == rlen2 && clen1 == 1 && clen2 > 1) // VMc
+			|| (clen1 == clen2 && rlen1 > 1 && rlen2 == 1) // MVr
+			|| (clen1 == clen2 && rlen1 == 1 && rlen2 > 1) // VMr
+			|| (clen1 == 1 && rlen2 == 1)); // VV
+
+		if(!isValid) {
+			throw new RuntimeException("Block sizes are not matched for binary " + "cell operations: " + rlen1 + "x"
+				+ clen1 + " vs " + rlen2 + "x" + clen2);
+		}
+	}
+
+	public static boolean isSparseSafeDivide(BinaryOperator op, MatrixBlock rhs)
+	{
+		//if rhs is fully dense, there cannot be a /0 and hence DIV becomes sparse safe
+		return (op.fn instanceof Divide && rhs.getNonZeros()==(long)rhs.getNumRows()*rhs.getNumColumns());
+	}
+	
+	public static boolean isAllDense(MatrixBlock... mb) {
+		return Arrays.stream(mb).allMatch(m -> !m.sparse);
+	}
+	
+	//////////////////////////////////////////////////////
+	// private sparse-safe/sparse-unsafe implementations
+	///////////////////////////////////
+
+	private static void denseUnaryOperations(MatrixBlock m1, MatrixBlock ret, UnaryOperator op) {
+		//prepare 0-value init (determine if unnecessarily sparse-unsafe)
+		double val0 = op.fn.execute(0d);
+		
+		final int m = m1.rlen;
+		final int n = m1.clen;
+		
+		//early abort possible if unnecessarily sparse unsafe
+		//(otherwise full init with val0, no need for computation)
+		if( m1.isEmptyBlock(false) ) {
+			if( val0 != 0 )
+				ret.reset(m, n, val0);
+			return;
+		}
+		
+		//redirection to sparse safe operation w/ init by val0
+		if( m1.sparse && val0 != 0 ) {
+			ret.reset(m, n, val0);
+			ret.nonZeros = (long)m * n;
+		}
+		sparseUnaryOperations(m1, ret, op);
+	}
+	
+	private static void sparseUnaryOperations(MatrixBlock m1, MatrixBlock ret, UnaryOperator op) {
+		//early abort possible since sparse-safe
+		if( m1.isEmptyBlock(false) )
+			return;
+		
+		final int m = m1.rlen;
+		final int n = m1.clen;
+		
+		if( m1.sparse && ret.sparse ) //SPARSE <- SPARSE
+		{
+			ret.allocateSparseRowsBlock();
+			SparseBlock a = m1.sparseBlock;
+			SparseBlock c = ret.sparseBlock;
+		
+			long nnz = 0;
+			for(int i=0; i<m; i++) {
+				if( a.isEmpty(i) ) continue;
+				
+				int apos = a.pos(i);
+				int alen = a.size(i);
+				int[] aix = a.indexes(i);
+				double[] avals = a.values(i);
+				
+				c.allocate(i, alen); //avoid repeated alloc
+				for( int j=apos; j<apos+alen; j++ ) {
+					double val = op.fn.execute(avals[j]);
+					c.append(i, aix[j], val);
+					nnz += (val != 0) ? 1 : 0;
+				}
+			}
+			ret.nonZeros = nnz;
+		}
+		else if( m1.sparse ) //DENSE <- SPARSE
+		{
+			ret.allocateDenseBlock(false);
+			SparseBlock a = m1.sparseBlock;
+			DenseBlock c = ret.denseBlock;
+			long nnz = (ret.nonZeros > 0) ?
+				(long) m*n-a.size() : 0;
+			for(int i=0; i<m; i++) {
+				if( a.isEmpty(i) ) continue;
+				int apos = a.pos(i);
+				int alen = a.size(i);
+				int[] aix = a.indexes(i);
+				double[] avals = a.values(i);
+				double[] cvals = c.values(i);
+				int cix = c.pos(i);
+				for( int j=apos; j<apos+alen; j++ ) {
+					double val = op.fn.execute(avals[j]);
+					cvals[cix + aix[j]] = val; 
+					nnz += (val != 0) ? 1 : 0;
+				}
+			}
+			ret.nonZeros = nnz;
+		}
+		else //DENSE <- DENSE
+		{
+			if( m1 != ret ) //!in-place
+				ret.allocateDenseBlock(false);
+			DenseBlock da = m1.getDenseBlock();
+			DenseBlock dc = ret.getDenseBlock();
+			
+			//unary op, incl nnz maintenance
+			long nnz = 0;
+			for( int bi=0; bi<da.numBlocks(); bi++ ) {
+				double[] a = da.valuesAt(bi);
+				double[] c = dc.valuesAt(bi);
+				int len = da.size(bi);
+				for( int i=0; i<len; i++ ) {
+					c[i] = op.fn.execute(a[i]);
+					nnz += (c[i] != 0) ? 1 : 0;
+				}
+			}
+			ret.nonZeros = nnz;
+		}
+	}
+	
+	private static long safeBinary(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, BinaryOperator op,
+		BinaryAccessType atype, int rl, int ru)
+	{
+		//NOTE: multi-threaded over rl-ru only applied for matrix-matrix, non-empty
+		
+		boolean skipEmpty = (op.fn instanceof Multiply 
+			|| isSparseSafeDivide(op, m2) );
+		boolean copyLeftRightEmpty = (op.fn instanceof Plus || op.fn instanceof Minus 
+			|| op.fn instanceof PlusMultiply || op.fn instanceof MinusMultiply);
+		boolean copyRightLeftEmpty = (op.fn instanceof Plus);
+		
+		//skip empty blocks (since sparse-safe)
+		if( m1.isEmptyBlock(false) && m2.isEmptyBlock(false) 
+			|| skipEmpty && (m1.isEmptyBlock(false) || m2.isEmptyBlock(false)) ) 
+		{
+			return 0;
+		}
+		
+		if( atype == BinaryAccessType.MATRIX_COL_VECTOR //MATRIX - VECTOR
+			|| atype == BinaryAccessType.MATRIX_ROW_VECTOR)
+		{
+			//note: m2 vector and hence always dense
+			if( !m1.sparse && !m2.sparse && !ret.sparse ) //DENSE all
+				return safeBinaryMVDense(m1, m2, ret, op, rl, ru);
+			else if( m1.sparse && !m2.sparse && !ret.sparse
+				&& atype == BinaryAccessType.MATRIX_ROW_VECTOR)
+				safeBinaryMVSparseDenseRow(m1, m2, ret, op);
+			else if( m1.sparse ) //SPARSE m1
+				safeBinaryMVSparse(m1, m2, ret, op);
+			else if( !m1.sparse && !m2.sparse && ret.sparse && op.fn instanceof Multiply
+				&& atype == BinaryAccessType.MATRIX_COL_VECTOR
+				&& (long)m1.rlen * m2.clen < Integer.MAX_VALUE )
+				safeBinaryMVDenseSparseMult(m1, m2, ret, op);
+			else //generic combinations
+				safeBinaryMVGeneric(m1, m2, ret, op);
+		}
+		else if( atype == BinaryAccessType.OUTER_VECTOR_VECTOR ) //VECTOR - VECTOR
+		{
+			safeBinaryVVGeneric(m1, m2, ret, op);
+		}
+		else //MATRIX - MATRIX
+		{
+			if( copyLeftRightEmpty && m2.isEmpty() ) {
+				//ret remains unchanged so a shallow copy is sufficient
+				ret.copyShallow(m1);
+			}
+			else if( copyRightLeftEmpty && m1.isEmpty() ) {
+				//ret remains unchanged so a shallow copy is sufficient
+				ret.copyShallow(m2);
+			}
+			else if(m1.sparse && m2.sparse) {
+				return safeBinaryMMSparseSparse(m1, m2, ret, op, rl, ru);
+			}
+			else if( !ret.sparse && (m1.sparse || m2.sparse) &&
+				(op.fn instanceof Plus || op.fn instanceof Minus ||
+				op.fn instanceof PlusMultiply || op.fn instanceof MinusMultiply ||
+				(op.fn instanceof Multiply && !m2.sparse ))) {
+				return safeBinaryMMSparseDenseDense(m1, m2, ret, op, rl, ru);
+			}
+			else if( !ret.sparse && !m1.sparse && !m2.sparse 
+				&& m1.denseBlock!=null && m2.denseBlock!=null ) {
+				return safeBinaryMMDenseDenseDense(m1, m2, ret, op, rl, ru);
+			}
+			else if( skipEmpty && (m1.sparse || m2.sparse) ) {
+				return safeBinaryMMSparseDenseSkip(m1, m2, ret, op, rl, ru);
+			}
+			else { //generic case
+				return safeBinaryMMGeneric(m1, m2, ret, op, rl, ru);
+			}
+		}
+		//default catch all
+		return ret.getNonZeros();
+	}
+
+	private static long safeBinaryMVDense(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, BinaryOperator op, int rl,
+		int ru) {
+		final boolean isMultiply = (op.fn instanceof Multiply);
+		final boolean skipEmpty = (isMultiply);
+
+		// early abort on skip and empy
+		if(skipEmpty && (m1.isEmptyBlock(false) || m2.isEmptyBlock(false)))
+			return 0; // skip entire empty block
+
+		// guard for postponed allocation in single-threaded exec
+		if(!ret.isAllocated())
+			ret.allocateDenseBlock();
+
+		final BinaryAccessType atype = getBinaryAccessType(m1, m2);
+
+		if(atype == BinaryAccessType.MATRIX_COL_VECTOR)
+			return safeBinaryMVDenseColVector(m1, m2, ret, op, rl, ru);
+		else // if( atype == BinaryAccessType.MATRIX_ROW_VECTOR )
+			return safeBinaryMVDenseRowVector(m1, m2, ret, op, rl, ru);
+	}
+
+	private static long safeBinaryMVDenseColVector(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, BinaryOperator op,
+		int rl, int ru) {
+		final boolean multiply = (op.fn instanceof Multiply);
+		final int clen = m1.clen;
+
+		final DenseBlock da = m1.getDenseBlock();
+		if(da.values(0) == null)
+			throw new RuntimeException("Invalid input with empty input");
+		final DenseBlock dc = ret.getDenseBlock();
+		long nnz = 0;
+		final double[] b = m2.getDenseBlockValues(); // always single block
+
+		if(b == null) {
+			if(multiply)
+				return 0;
+			else {
+				for(int i = rl; i < ru; i++) {
+					final double[] a = da.values(i);
+					final double[] c = dc.values(i);
+					final int ix = da.pos(i);
+					// GENERAL CASE
+					for(int j = 0; j < clen; j++) {
+						double val = op.fn.execute(a[ix + j], 0);
+						nnz += ((c[ix + j] = val) != 0) ? 1 : 0;
+					}
+				}
+			}
+		}
+		else if(multiply){
+			for(int i = rl; i < ru; i++) {
+				final double[] a = da.values(i);
+				final double[] c = dc.values(i);
+				final int ix = da.pos(i);
+
+				// replicate vector value
+				double v2 = b[i];
+				if(v2 == 0) // skip empty rows
+					continue;
+				else if(v2 == 1) { // ROW COPY
+					// a guaranteed to be non-null (see early abort)
+					System.arraycopy(a, ix, c, ix, clen);
+					nnz += m1.recomputeNonZeros(i, i, 0, clen - 1);
+				}
+				else {
+					// GENERAL CASE
+					for(int j = 0; j < clen; j++) {
+						double val = op.fn.execute(a[ix + j], v2);
+						nnz += ((c[ix + j] = val) != 0) ? 1 : 0;
+					}
+				}
+
+			}
+		}
+		else{
+			for(int i = rl; i < ru; i++) {
+				final double[] a = da.values(i);
+				final double[] c = dc.values(i);
+				final int ix = da.pos(i);
+
+				// replicate vector value
+				double v2 = b[i];
+
+				// GENERAL CASE
+				for(int j = 0; j < clen; j++) {
+					double val = op.fn.execute(a[ix + j], v2);
+					nnz += ((c[ix + j] = val) != 0) ? 1 : 0;
+				}
+
+			}
+		}
+		return nnz;
+	}
+
+	private static long safeBinaryMVDenseRowVector(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, BinaryOperator op,
+		int rl, int ru) {
+		final int clen = m1.clen;
+
+		final DenseBlock da = m1.getDenseBlock();
+		final DenseBlock dc = ret.getDenseBlock();
+		long nnz = 0;
+		final double[] b = m2.getDenseBlockValues(); // always single block
+
+		if(da == null && b == null) { // both empty
+			double val = op.fn.execute(0, 0);
+			dc.set(rl, ru, 0, clen, val);
+			nnz += (val != 0) ? (long) (ru - rl) * clen : 0;
+		}
+		else if(da == null) // left empty
+		{
+			// compute first row
+			double[] c = dc.values(rl);
+			for(int j = 0; j < clen; j++) {
+				double val = op.fn.execute(0, b[j]);
+				nnz += ((c[j] = val) != 0) ? (ru - rl) : 0;
+			}
+			// copy first to all other rows
+			for(int i = rl + 1; i < ru; i++)
+				dc.set(i, c);
+		}
+		else // default case (incl right empty)
+		{
+			for(int i = rl; i < ru; i++) {
+				double[] a = da.values(i);
+				double[] c = dc.values(i);
+				int ix = da.pos(i);
+				for(int j = 0; j < clen; j++) {
+					double val = op.fn.execute(a[ix + j], ((b != null) ? b[j] : 0));
+					nnz += ((c[ix + j] = val) != 0) ? 1 : 0;
+				}
+			}
+		}
+		return nnz;
+	}
+	
+
+	private static void safeBinaryMVSparseDenseRow(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, BinaryOperator op) {
+		boolean isMultiply = (op.fn instanceof Multiply);
+		boolean skipEmpty = (isMultiply);
+		int rlen = m1.rlen;
+		int clen = m1.clen;
+		SparseBlock a = m1.sparseBlock;
+		double[] b = m2.getDenseBlockValues();
+		DenseBlock c = ret.allocateDenseBlock().getDenseBlock();
+		
+		//early abort on skip and empty
+		if( skipEmpty && (m1.isEmptyBlock(false) || m2.isEmptyBlock(false) ) )
+			return; // skip entire empty block
+
+		//prepare op(0, m2) vector once for all rows
+		double[] tmp = new double[clen];
+		if( !skipEmpty ) {
+			for( int i=0; i<clen; i++ )
+				tmp[i] = op.fn.execute(0, b[i]);
+		}
+		
+		long nnz = 0;
+		for( int i=0; i<rlen; i++ ) {
+			if( skipEmpty && (a==null || a.isEmpty(i)) )
+				continue; //skip empty rows
+			
+			//set prepared empty row vector into output
+			double[] cvals = c.values(i);
+			int cpos = c.pos(i);
+			System.arraycopy(tmp, 0, cvals, cpos, clen);
+			
+			//overwrite row cells with existing sparse lhs values
+			if( a!=null && !a.isEmpty(i) ) {
+				int apos = a.pos(i);
+				int alen = a.size(i);
+				int[] aix = a.indexes(i);
+				double[] avals = a.values(i);
+				for( int j=apos; j<apos+alen; j++ )
+					cvals[cpos+aix[j]] = op.fn.execute(avals[j], b[aix[j]]);
+			}
+			
+			//compute row nnz with temporal locality
+			nnz += UtilFunctions.computeNnz(cvals, cpos, clen);
+		}
+		ret.nonZeros = nnz;
+	}
+	
+	private static void safeBinaryMVSparse(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, BinaryOperator op) {
+		boolean isMultiply = (op.fn instanceof Multiply);
+		boolean skipEmpty = (isMultiply || isSparseSafeDivide(op, m2));
+		BinaryAccessType atype = getBinaryAccessType(m1, m2);
+
+		// early abort on skip and empty
+		if(skipEmpty && (m1.isEmptyBlock(false) || m2.isEmptyBlock(false)))
+			return; // skip entire empty block
+
+		// allocate once in order to prevent repeated reallocation
+		if(ret.sparse)
+			ret.allocateSparseRowsBlock();
+
+		if(atype == BinaryAccessType.MATRIX_COL_VECTOR)
+			safeBinaryMVSparseColVector(m1, m2, ret, op);
+		else if(atype == BinaryAccessType.MATRIX_ROW_VECTOR)
+			safeBinaryMVSparseRowVector(m1, m2, ret, op);
+	}
+
+	private static void safeBinaryMVSparseColVector(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, BinaryOperator op) {
+		boolean isMultiply = (op.fn instanceof Multiply);
+		boolean skipEmpty = (isMultiply || isSparseSafeDivide(op, m2));
+
+		int rlen = m1.rlen;
+		int clen = m1.clen;
+		SparseBlock a = m1.sparseBlock;
+		for(int i = 0; i < rlen; i++) {
+			double v2 = m2.get(i, 0);
+
+			if((skipEmpty && (a == null || a.isEmpty(i) || v2 == 0)) || ((a == null || a.isEmpty(i)) && v2 == 0)) {
+				continue; // skip empty rows
+			}
+
+			if(isMultiply && v2 == 1) { // ROW COPY
+				if(a != null && !a.isEmpty(i))
+					ret.appendRow(i, a.get(i));
+			}
+			else { // GENERAL CASE
+				int lastIx = -1;
+				if(a != null && !a.isEmpty(i)) {
+					int apos = a.pos(i);
+					int alen = a.size(i);
+					int[] aix = a.indexes(i);
+					double[] avals = a.values(i);
+					for(int j = apos; j < apos + alen; j++) {
+						// empty left
+						fillZeroValues(op, v2, ret, skipEmpty, i, lastIx + 1, aix[j]);
+						// actual value
+						double v = op.fn.execute(avals[j], v2);
+						ret.appendValue(i, aix[j], v);
+						lastIx = aix[j];
+					}
+				}
+				// empty left
+				fillZeroValues(op, v2, ret, skipEmpty, i, lastIx + 1, clen);
+			}
+		}
+	}
+
+	private static void safeBinaryMVSparseRowVector(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, BinaryOperator op) {
+		boolean isMultiply = (op.fn instanceof Multiply);
+		boolean skipEmpty = (isMultiply || isSparseSafeDivide(op, m2));
+
+		int rlen = m1.rlen;
+		int clen = m1.clen;
+		SparseBlock a = m1.sparseBlock;
+		for(int i = 0; i < rlen; i++) {
+			if(skipEmpty && (a == null || a.isEmpty(i)))
+				continue; // skip empty rows
+			if(skipEmpty && ret.sparse)
+				ret.sparseBlock.allocate(i, a.size(i));
+			int lastIx = -1;
+			if(a != null && !a.isEmpty(i)) {
+				int apos = a.pos(i);
+				int alen = a.size(i);
+				int[] aix = a.indexes(i);
+				double[] avals = a.values(i);
+				for(int j = apos; j < apos + alen; j++) {
+					// empty left
+					fillZeroValues(op, m2, ret, skipEmpty, i, lastIx + 1, aix[j]);
+					// actual value
+					double v2 = m2.get(0, aix[j]);
+					double v = op.fn.execute(avals[j], v2);
+					ret.appendValue(i, aix[j], v);
+					lastIx = aix[j];
+				}
+			}
+			// empty left
+			fillZeroValues(op, m2, ret, skipEmpty, i, lastIx + 1, clen);
+		}
+	}
+	
+	private static final void fillZeroValues(BinaryOperator op, double v2, MatrixBlock ret, boolean skipEmpty, int rpos, int cpos, int len) {
+		if(skipEmpty)
+			return;
+		
+		final double v = op.fn.execute(0, v2);
+		if(v != 0){
+			for( int k=cpos; k<len; k++ )
+				// TODO change this to not do append but directly allocate the filled sparse row.
+				ret.appendValue(rpos, k, v);
+		}
+	}
+	
+	private static void fillZeroValues(BinaryOperator op, MatrixBlock m2, MatrixBlock ret, boolean skipEmpty, int rpos,
+		int cpos, int len) {
+		if(skipEmpty)
+			return;
+		else if(m2.isEmpty()) 
+			fillZeroValuesEmpty(op, m2, ret, skipEmpty, rpos, cpos, len);
+		else if(m2.isInSparseFormat()) 
+			fillZeroValuesSparse(op, m2, ret, skipEmpty, rpos, cpos, len);
+		else 
+			fillZeroValuesDense(op, m2, ret, skipEmpty, rpos, cpos, len);
+	}
+
+	private static void fillZeroValuesEmpty(BinaryOperator op, MatrixBlock m2, MatrixBlock ret, boolean skipEmpty,
+		int rpos, int cpos, int len) {
+		final double zero = op.fn.execute(0.0, 0.0);
+		final boolean zeroIsZero = zero == 0.0;
+		if(!zeroIsZero) {
+			while(cpos < len)
+				// TODO change this to a fill operation.
+				ret.appendValue(rpos, cpos++, zero);
+		}
+	}
+
+	private static void fillZeroValuesDense(BinaryOperator op, MatrixBlock m2, MatrixBlock ret, boolean skipEmpty,
+		int rpos, int cpos, int len) {
+		final DenseBlock db = m2.getDenseBlock();
+		final double[] vals = db.values(0);
+		final SparseBlock r = ret.getSparseBlock();
+		if(ret.isInSparseFormat() && r instanceof SparseBlockMCSR) {
+			SparseBlockMCSR mCSR = (SparseBlockMCSR) r;
+			mCSR.allocate(rpos, cpos, len);
+			SparseRow sr = mCSR.get(rpos);
+			for(int k = cpos; k < len; k++) {
+				sr.append(k, op.fn.execute(0, vals[k]));
+			}
+		}
+		else {
+			// def
+			for(int k = cpos; k < len; k++) {
+				ret.appendValue(rpos, k, op.fn.execute(0, vals[k]));
+			}
+		}
+	}
+
+	private static void fillZeroValuesSparse(BinaryOperator op, MatrixBlock m2, MatrixBlock ret, boolean skipEmpty,
+		int rpos, int cpos, int len) {
+
+		final double zero = op.fn.execute(0.0, 0.0);
+		final boolean zeroIsZero = zero == 0.0;
+		final SparseBlock sb = m2.getSparseBlock();
+		if(sb.isEmpty(0)) {
+			if(!zeroIsZero) {
+				while(cpos < len)
+					ret.appendValue(rpos, cpos++, zero);
+			}
+		}
+		else {
+			int apos = sb.pos(0);
+			final int alen = sb.size(0) + apos;
+			final int[] aix = sb.indexes(0);
+			final double[] vals = sb.values(0);
+			// skip aix pos until inside range of cpos and len
+			while(apos < alen && aix[apos] < len && cpos > aix[apos]) {
+				apos++;
+			}
+			// for each point in the sparse range
+			for(; apos < alen && aix[apos] < len; apos++) {
+				if(!zeroIsZero) {
+					while(cpos < len && cpos < aix[apos]) {
+						ret.appendValue(rpos, cpos++, zero);
+					}
+				}
+				cpos = aix[apos];
+				final double v = op.fn.execute(0, vals[apos]);
+				ret.appendValue(rpos, aix[apos], v);
+				// cpos++;
+			}
+			// process tail.
+			if(!zeroIsZero) {
+				while(cpos < len) {
+					ret.appendValue(rpos, cpos++, zero);
+				}
+			}
+		}
+	}
+
+	private static void safeBinaryMVDenseSparseMult(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, BinaryOperator op) {
+		if( m1.isEmptyBlock(false) || m2.isEmptyBlock(false) )
+			return;
+		int rlen = m1.rlen;
+		int clen = m1.clen;
+		BinaryAccessType atype = getBinaryAccessType(m1, m2);
+		double[] a = m1.getDenseBlockValues();
+		double[] b = m2.getDenseBlockValues();
+		
+		//note: invocation condition ensures max int nnz
+		if( atype == BinaryAccessType.MATRIX_COL_VECTOR ) {
+			//count output nnz (for CSR preallocation)
+			int nnz = 0;
+			for(int i=0, aix=0; i<rlen; i++, aix+=clen)
+				nnz += (b[i] != 0) ? UtilFunctions
+					.countNonZeros(a, aix, clen) : 0;
+			//allocate and compute output in CSR format
+			int[] rptr = new int[rlen+1];
+			int[] indexes = new int[nnz];
+			double[] vals = new double[nnz];
+			rptr[0] = 0;
+			for( int i=0, aix=0, pos=0; i<rlen; i++, aix+=clen ) {
+				double bval = b[i];
+				if( bval != 0 ) {
+					for( int j=0; j<clen; j++ ) {
+						double aval = a[aix+j];
+						if( aval == 0 ) continue;
+						indexes[pos] = j;
+						vals[pos] = aval * bval;
+						pos++;
+					}
+				}
+				rptr[i+1] = pos;
+			}
+			ret.sparseBlock = new SparseBlockCSR(
+				rptr, indexes, vals, nnz);
+			ret.setNonZeros(nnz);
+		}
+	}
+	
+	private static void safeBinaryMVGeneric(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, BinaryOperator op) {
+		boolean isMultiply = (op.fn instanceof Multiply);
+		boolean skipEmpty = (isMultiply);
+		int rlen = m1.rlen;
+		int clen = m1.clen;
+		BinaryAccessType atype = getBinaryAccessType(m1, m2);
+		
+		//early abort on skip and empty
+		if( skipEmpty && (m1.isEmptyBlock(false) || m2.isEmptyBlock(false) ) )
+			return; // skip entire empty block
+		
+		//allocate once in order to prevent repeated reallocation 
+		if( ret.sparse )
+			ret.allocateSparseRowsBlock();
+		
+		if( atype == BinaryAccessType.MATRIX_COL_VECTOR )
+		{
+			for( int i=0; i<rlen; i++ )
+			{
+				//replicate vector value
+				double v2 = m2.get(i, 0);
+				if( skipEmpty && v2 == 0 ) //skip zero rows
+					continue;
+				
+				if(isMultiply && v2 == 1) //ROW COPY
+				{
+					for( int j=0; j<clen; j++ ) {
+						double v1 = m1.get(i, j);
+						ret.appendValue(i, j, v1);
+					}
+				}
+				else //GENERAL CASE
+				{
+					for( int j=0; j<clen; j++ ) {
+						double v1 = m1.get(i, j);
+						double v = op.fn.execute( v1, v2 );
+						ret.appendValue(i, j, v);
+					}
+				}
+			}
+		}
+		else if( atype == BinaryAccessType.MATRIX_ROW_VECTOR )
+		{
+			//if the right hand side row vector is sparse we have to exploit that;
+			//otherwise, both sparse access (binary search) and asymtotic behavior
+			//in the number of cells become major bottlenecks
+			if( m2.sparse && ret.sparse && isMultiply ) //SPARSE *
+			{
+				//note: sparse block guaranteed to be allocated (otherwise early about)
+				SparseBlock b = m2.sparseBlock;
+				SparseBlock c = ret.sparseBlock;
+				if( b.isEmpty(0) ) return; 
+				int blen = b.size(0); //always pos 0
+				int[] bix = b.indexes(0);
+				double[] bvals = b.values(0);
+				for( int i=0; i<rlen; i++ ) {
+					c.allocate(i, blen);
+					for( int j=0; j<blen; j++ )
+						c.append(i, bix[j], m1.get(i, bix[j]) * bvals[j]);
+				}
+				ret.setNonZeros(c.size());
+			}
+			else //GENERAL CASE
+			{
+				for( int i=0; i<rlen; i++ )
+					for( int j=0; j<clen; j++ ) {
+						double v1 = m1.get(i, j);
+						double v2 = m2.get(0, j); //replicated vector value
+						double v = op.fn.execute( v1, v2 );
+						ret.appendValue(i, j, v);
+					}
+			}
+		}
+		
+		//no need to recomputeNonZeros since maintained in append value
+	}
+	
+	private static void safeBinaryVVGeneric(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, BinaryOperator op) {
+		int rlen = m1.rlen;
+		int clen = m2.clen;
+		
+		//allocate once in order to prevent repeated reallocation 
+		if( ret.sparse )
+			ret.allocateSparseRowsBlock();
+		
+		if(LibMatrixOuterAgg.isCompareOperator(op) 
+			&& m2.getNumColumns()>16 && SortUtils.isSorted(m2) ) {
+			performBinOuterOperation(m1, m2, ret, op);
+		}
+		else {
+			for(int r=0; r<rlen; r++) {
+				double v1 = m1.get(r, 0);
+				for(int c=0; c<clen; c++) {
+					double v2 = m2.get(0, c);
+					double v = op.fn.execute( v1, v2 );
+					ret.appendValue(r, c, v);
+				}
+			}
+		}
+		
+		//no need to recomputeNonZeros since maintained in append value
+	}
+	
+	private static long safeBinaryMMSparseSparse(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret,
+		BinaryOperator op, int rl, int ru)
+	{
+		//guard for postponed allocation in single-threaded exec
+		if( ret.sparse && !ret.isAllocated() )
+			ret.allocateSparseRowsBlock();
+		
+		//both sparse blocks existing
+		long lnnz = 0;
+		if(m1.sparseBlock!=null && m2.sparseBlock!=null)
+		{
+			SparseBlock lsblock = m1.sparseBlock;
+			SparseBlock rsblock = m2.sparseBlock;
+			
+			if( ret.sparse && lsblock.isAligned(rsblock) )
+			{
+				SparseBlock c = ret.sparseBlock;
+				for(int r=rl; r<ru; r++) 
+					if( !lsblock.isEmpty(r) ) {
+						int alen = lsblock.size(r);
+						int apos = lsblock.pos(r);
+						int[] aix = lsblock.indexes(r);
+						double[] avals = lsblock.values(r);
+						double[] bvals = rsblock.values(r);
+						c.allocate(r, alen);
+						for( int j=apos; j<apos+alen; j++ ) {
+							double tmp = op.fn.execute(avals[j], bvals[j]);
+							c.append(r, aix[j], tmp);
+						}
+						lnnz += c.size(r);
+					}
+			}
+			else //general case
+			{
+				for(int r=rl; r<ru; r++) {
+					if( !lsblock.isEmpty(r) && !rsblock.isEmpty(r) ) {
+						mergeForSparseBinary(op, lsblock.values(r), lsblock.indexes(r), lsblock.pos(r), lsblock.size(r),
+							rsblock.values(r), rsblock.indexes(r), rsblock.pos(r), rsblock.size(r), r, ret);
+					}
+					else if( !rsblock.isEmpty(r) ) {
+						appendRightForSparseBinary(op, rsblock.values(r), rsblock.indexes(r), 
+							rsblock.pos(r), rsblock.size(r), 0, r, ret);
+					}
+					else if( !lsblock.isEmpty(r) ){
+						appendLeftForSparseBinary(op, lsblock.values(r), lsblock.indexes(r), 
+							lsblock.pos(r), lsblock.size(r), 0, r, ret);
+					}
+					// do nothing if both not existing
+					lnnz += ret.recomputeNonZeros(r, r);
+				}
+			}
+		}
+		//right sparse block existing
+		else if( m2.sparseBlock!=null )
+		{
+			SparseBlock rsblock = m2.sparseBlock;
+			for(int r=rl; r<Math.min(ru, rsblock.numRows()); r++) {
+				if( rsblock.isEmpty(r) ) continue;
+				appendRightForSparseBinary(op, rsblock.values(r), rsblock.indexes(r), 
+					rsblock.pos(r), rsblock.size(r), 0, r, ret);
+				lnnz += ret.recomputeNonZeros(r, r);
+			}
+		}
+		//left sparse block existing
+		else
+		{
+			SparseBlock lsblock = m1.sparseBlock;
+			for(int r=rl; r<ru; r++) {
+				if( lsblock.isEmpty(r) ) continue;
+				appendLeftForSparseBinary(op, lsblock.values(r), lsblock.indexes(r), 
+					lsblock.pos(r), lsblock.size(r), 0, r, ret);
+				lnnz += ret.recomputeNonZeros(r, r);
+			}
+		}
+		return lnnz;
+	}
+	
+	private static long safeBinaryMMSparseDenseDense(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret,
+		BinaryOperator op, int rl, int ru)
+	{
+		//guard for postponed allocation in single-threaded exec
+		if( !ret.isAllocated() )
+			ret.allocateDenseBlock();
+		
+		//specific case in order to prevent binary search on sparse inputs (see quickget and quickset)
+		final int n = ret.clen;
+		DenseBlock dc = ret.getDenseBlock();
+		
+		//1) process left input: assignment
+		if( m1.sparse && m1.sparseBlock != null ) //SPARSE left
+		{
+			SparseBlock a = m1.sparseBlock;
+			for(int i=rl; i<ru; i++) {
+				double[] c = dc.values(i);
+				int cpos = dc.pos(i);
+				if( a.isEmpty(i) ) continue;
+				int apos = a.pos(i);
+				int alen = a.size(i);
+				int[] aix = a.indexes(i);
+				double[] avals = a.values(i);
+				for(int k = apos; k < apos+alen; k++) 
+					c[cpos+aix[k]] = avals[k];
+			}
+		}
+		else if( !m1.sparse ) //DENSE left
+		{
+			if( !m1.isEmptyBlock(false) ) {
+				int rlbix = dc.index(rl);
+				int rubix = dc.index(ru-1);
+				DenseBlock da = m1.getDenseBlock();
+				if( rlbix == rubix )
+					System.arraycopy(da.valuesAt(rlbix), da.pos(rl), dc.valuesAt(rlbix), dc.pos(rl), (ru-rl)*n);
+				else {
+					for(int i=rl; i<ru; i++)
+						System.arraycopy(da.values(i), da.pos(i), dc.values(i), dc.pos(i), n);
+				}
+			}
+			else
+				dc.set(0);
+		}
+		
+		//2) process right input: op.fn (+,-,*), * only if dense
+		long lnnz = 0;
+		if( m2.sparse && m2.sparseBlock!=null ) //SPARSE right
+		{
+			SparseBlock a = m2.sparseBlock;
+			for(int i=rl; i<ru; i++) {
+				double[] c = dc.values(i);
+				int cpos = dc.pos(i);
+				if( !a.isEmpty(i) ) {
+					int apos = a.pos(i);
+					int alen = a.size(i);
+					int[] aix = a.indexes(i);
+					double[] avals = a.values(i);
+					for(int k = apos; k < apos+alen; k++) 
+						c[cpos+aix[k]] = op.fn.execute(c[cpos+aix[k]], avals[k]);
+				}
+				//exploit temporal locality of rows
+				lnnz += ret.recomputeNonZeros(i, i);
+			}
+		}
+		else if( !m2.sparse ) //DENSE right
+		{
+			if( !m2.isEmptyBlock(false) ) {
+				DenseBlock da = m2.getDenseBlock();
+				for( int i=rl; i<ru; i++ ) {
+					double[] a = da.values(i);
+					double[] c = dc.values(i);
+					int apos = da.pos(i);
+					for( int j = apos; j<apos+n; j++ ) {
+						c[j] = op.fn.execute(c[j], a[j]);
+						lnnz += (c[j]!=0) ? 1 : 0;
+					}
+				}
+			}
+			else if(op.fn instanceof Multiply)
+				ret.denseBlock.set(0);
+			else
+				lnnz = m1.nonZeros;
+		}
+		
+		//3) recompute nnz
+		return lnnz;
+	}
+	
+	private static long safeBinaryMMDenseDenseDense(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret,
+		BinaryOperator op, int rl, int ru){
+		final int clen = m1.clen;
+		final boolean isPM = (op.fn instanceof PlusMultiply || op.fn instanceof MinusMultiply);
+		
+		//guard for postponed allocation in single-threaded exec
+		if(!ret.isAllocated())
+			ret.allocateDenseBlock();
+		
+		final DenseBlock da = m1.getDenseBlock();
+		final DenseBlock db = m2.getDenseBlock();
+		final DenseBlock dc = ret.getDenseBlock();
+		
+		if(isPM && clen >= 64)
+			return safeBinaryMMDenseDenseDensePM_Vec(da, db, dc, op, rl, ru, clen);
+		else if(da.isContiguous() && db.isContiguous() && dc.isContiguous()) {
+			if(op.fn instanceof PlusMultiply)
+				return safeBinaryMMDenseDenseDensePM(da, db, dc, op, rl, ru, clen);
+			else
+				return safeBinaryMMDenseDenseDenseContiguous(da, db, dc, op, rl, ru, clen);
+		}
+		else
+			return safeBinaryMMDenseDenseDenseGeneric(da, db, dc, op, rl, ru, clen);
+	}
+
+	private static final long safeBinaryMMDenseDenseDensePM_Vec(DenseBlock da, DenseBlock db, DenseBlock dc, BinaryOperator op,
+		int rl, int ru, int clen) {
+		final double cntPM = (op.fn instanceof PlusMultiply ? ((PlusMultiply) op.fn).getConstant() : -1d *
+			((MinusMultiply) op.fn).getConstant());
+		long lnnz = 0;
+		for(int i = rl; i < ru; i++) {
+			final double[] a = da.values(i);
+			final double[] b = db.values(i);
+			final double[] c = dc.values(i);
+			int pos = da.pos(i);
+			System.arraycopy(a, pos, c, pos, clen);
+			LibMatrixMult.vectMultiplyAdd(cntPM, b, c, pos, pos, clen);
+			lnnz += UtilFunctions.computeNnz(c, pos, clen);
+		}
+		return lnnz;
+	}
+
+	private static final long safeBinaryMMDenseDenseDensePM(DenseBlock da, DenseBlock db, DenseBlock dc, BinaryOperator op,
+		int rl, int ru, int clen) {
+		long lnnz = 0;
+		final double[] a = da.values(0);
+		final double[] b = db.values(0);
+		final double[] c = dc.values(0);
+		final double d = ((PlusMultiply)op.fn).getConstant();
+		for(int i = da.pos(rl); i < da.pos(ru); i++) {
+			c[i] = a[i] + d * b[i];
+			lnnz += (c[i] != 0) ? 1 : 0;
+		}
+		return lnnz;
+	}
+
+	private static final long safeBinaryMMDenseDenseDenseContiguous(DenseBlock da, DenseBlock db, DenseBlock dc, BinaryOperator op,
+		int rl, int ru, int clen) {
+		long lnnz = 0;
+		final double[] a = da.values(0);
+		final double[] b = db.values(0);
+		final double[] c = dc.values(0);
+		for(int i = da.pos(rl); i < da.pos(ru); i++) {
+			memorySegmentRet.setAtIndex(layout, i,
+					op.fn.execute(memorySegment1.getAtIndex(layout, i), memorySegment2.getAtIndex(layout, i)) +
+							memorySegmentRet.getAtIndex(layout, i));
+			lnnz += (memorySegmentRet.getAtIndex(layout, i) != 0) ? 1 : 0;
+		}
+		return lnnz;
+	}
+
+	private static final long safeBinaryMMDenseDenseDenseGeneric(DenseBlock da, DenseBlock db, DenseBlock dc,
+		BinaryOperator op, int rl, int ru, int clen) {
+		final ValueFunction fn = op.fn;
+		long lnnz = 0;
+		for(int i = rl; i < ru; i++) {
+			final double[] a = da.values(i);
+			final double[] b = db.values(i);
+			final double[] c = dc.values(i);
+			int pos = da.pos(i);
+			for(int j = pos; j < pos + clen; j++) {
+				c[j] = fn.execute(a[j], b[j]);
+				lnnz += (c[j] != 0) ? 1 : 0;
+			}
+		}
+		return lnnz;
+	}
+	
+	private static long safeBinaryMMSparseDenseSkip(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret,
+		BinaryOperator op, int rl, int ru)
+	{
+		SparseBlock a = m1.sparse ? m1.sparseBlock : m2.sparseBlock;
+		if( a == null )
+			return 0;
+		
+		//prepare second input and allocate output
+		MatrixBlock b = m1.sparse ? m2 : m1;
+		
+		//guard for postponed allocation in single-threaded exec
+		if( !ret.isAllocated() )
+			ret.allocateBlock();
+		
+		long lnnz = 0;
+		for( int i=rl; i<Math.min(ru, a.numRows()); i++ ) {
+			if( a.isEmpty(i) ) continue;
+			int apos = a.pos(i);
+			int alen = a.size(i);
+			int[] aix = a.indexes(i);
+			double[] avals = a.values(i);
+			if( ret.sparse && !b.sparse )
+				ret.sparseBlock.allocate(i, alen);
+			for(int k = apos; k < apos+alen; k++) {
+				double in2 = b.get(i, aix[k]);
+				if( in2==0 ) continue;
+				double val = op.fn.execute(avals[k], in2);
+				lnnz += (val != 0) ? 1 : 0;
+				ret.appendValuePlain(i, aix[k], val);
+			}
+		}
+		return lnnz;
+	}
+	
+	private static long safeBinaryMMGeneric(MatrixBlock m1, MatrixBlock m2,
+		MatrixBlock ret, BinaryOperator op, int rl, int ru)
+	{
+		int clen = m2.clen;
+		long lnnz = 0;
+		for(int r=rl; r<ru; r++)
+			for(int c=0; c<clen; c++) {
+				double in1 = m1.get(r, c);
+				double in2 = m2.get(r, c);
+				if( in1==0 && in2==0) continue;
+				double val = op.fn.execute(in1, in2);
+				lnnz += (val != 0) ? 1 : 0;
+				ret.appendValuePlain(r, c, val);
+			}
+		return lnnz;
+	}
+	
+	/**
+	 * 
+	 * This will do cell wise operation for &lt;, &lt;=, &gt;, &gt;=, == and != operators.
+	 * 
+	 * @param m1 left matrix
+	 * @param m2 right matrix
+	 * @param ret output matrix
+	 * @param bOp binary operator
+	 * 
+	 */
+	private static long performBinOuterOperation(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, BinaryOperator bOp) {
+		int rlen = m1.rlen;
+		int clen = ret.clen;
+		double b[] = DataConverter.convertToDoubleVector(m2);
+		if(!ret.isAllocated())
+			ret.allocateDenseBlock();
+		DenseBlock dc = ret.getDenseBlock();
+		
+		//pre-materialize various types used in inner loop
+		boolean scanType1 = (bOp.fn instanceof LessThan || bOp.fn instanceof Equals 
+			|| bOp.fn instanceof NotEquals || bOp.fn instanceof GreaterThanEquals);
+		boolean scanType2 = (bOp.fn instanceof LessThanEquals || bOp.fn instanceof Equals 
+			|| bOp.fn instanceof NotEquals || bOp.fn instanceof GreaterThan);
+		boolean lt = (bOp.fn instanceof LessThan), lte = (bOp.fn instanceof LessThanEquals);
+		boolean gt = (bOp.fn instanceof GreaterThan), gte = (bOp.fn instanceof GreaterThanEquals);
+		boolean eqNeq = (bOp.fn instanceof Equals || bOp.fn instanceof NotEquals);
+		
+		long lnnz = 0;
+		for( int bi=0; bi<dc.numBlocks(); bi++ ) {
+			double[] c = dc.valuesAt(bi);
+			for( int r=bi*dc.blockSize(), off=0; r<rlen; r++, off+=clen ) {
+				double value = m1.get(r, 0);
+				int ixPos1 = Arrays.binarySearch(b, value);
+				int ixPos2 = ixPos1;
+				if( ixPos1 >= 0 ) { //match, scan to next val
+					if(scanType1) while( ixPos1<b.length && value==b[ixPos1]  ) ixPos1++;
+					if(scanType2) while( ixPos2 > 0 && value==b[ixPos2-1]) --ixPos2;
+				} 
+				else
+					ixPos2 = ixPos1 = Math.abs(ixPos1) - 1;
+				int start = lt ? ixPos1 : (lte||eqNeq) ? ixPos2 : 0;
+				int end = gt ? ixPos2 : (gte||eqNeq) ? ixPos1 : clen;
+				
+				if (bOp.fn instanceof NotEquals) {
+					Arrays.fill(c, off, off+start, 1.0);
+					Arrays.fill(c, off+end, off+clen, 1.0);
+					lnnz += (start+(clen-end));
+				}
+				else if( start < end ) {
+					Arrays.fill(c, off+start, off+end, 1.0);
+					lnnz += (end-start);
+				}
+			}
+		}
+		ret.setNonZeros(lnnz);
+		ret.examSparsity();
+		return lnnz;
+	}
+
+	private static long unsafeBinary(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, BinaryOperator op, int rl, int ru) {
+		int clen = m1.clen;
+		BinaryAccessType atype = getBinaryAccessType(m1, m2);
+		
+		long lnnz = 0;
+		if( atype == BinaryAccessType.MATRIX_COL_VECTOR ) { //MATRIX - COL_VECTOR
+			for(int r=rl; r<ru; r++) {
+				double v2 = m2.get(r, 0);
+				for(int c=0; c<clen; c++) {
+					double v1 = m1.get(r, c);
+					double v = op.fn.execute( v1, v2 );
+					ret.appendValuePlain(r, c, v);
+					lnnz += (v!=0) ? 1 : 0;
+				}
+			}
+		}
+		else if( atype == BinaryAccessType.MATRIX_ROW_VECTOR ) { //MATRIX - ROW_VECTOR
+			for(int r=rl; r<ru; r++)
+				for(int c=0; c<clen; c++) {
+					double v1 = m1.get(r, c);
+					double v2 = m2.get(0, c);
+					double v = op.fn.execute( v1, v2 );
+					ret.appendValuePlain(r, c, v);
+					lnnz += (v!=0) ? 1 : 0;
+				}
+		}
+		else if( atype == BinaryAccessType.OUTER_VECTOR_VECTOR ) { //VECTOR - VECTOR
+			int clen2 = m2.clen; 
+			if(LibMatrixOuterAgg.isCompareOperator(op) 
+				&& m2.getNumColumns()>16 && SortUtils.isSorted(m2)) {
+				lnnz = performBinOuterOperation(m1, m2, ret, op);
+			} 
+			else {
+				for(int r=rl; r<ru; r++) {
+					double v1 = m1.get(r, 0);
+					for(int c=0; c<clen2; c++) {
+						double v2 = m2.get(0, c);
+						double v = op.fn.execute( v1, v2 );
+						lnnz += (v != 0) ? 1 : 0;
+						ret.appendValuePlain(r, c, v);
+					}
+				}
+			}
+		}
+		else // MATRIX - MATRIX
+		{
+			//dense non-empty vectors (always single block)
+			if( m1.clen==1 && !m1.sparse && !m1.isEmptyBlock(false)
+				&& !m2.sparse && !m2.isEmptyBlock(false)  )
+			{
+				ret.allocateDenseBlock();
+				double[] a = m1.getDenseBlockValues();
+				double[] b = m2.getDenseBlockValues();
+				double[] c = ret.getDenseBlockValues();
+				for( int i=rl; i<ru; i++ ) {
+					c[i] = op.fn.execute( a[i], b[i] );
+					lnnz += (c[i] != 0) ? 1 : 0;
+				}
+			}
+			//general case
+			else {
+				for(int r=rl; r<ru; r++)
+					for(int c=0; c<clen; c++) {
+						double v1 = m1.get(r, c);
+						double v2 = m2.get(r, c);
+						double v = op.fn.execute( v1, v2 );
+						ret.appendValuePlain(r, c, v);
+						lnnz += (v!=0) ? 1 : 0;
+					}
+			}
+		}
+		
+		return lnnz;
+	}
+
+	private static long safeBinaryScalar(MatrixBlock m1, MatrixBlock ret, ScalarOperator op, int rl, int ru) {
+		//early abort possible since sparsesafe
+		if( m1.isEmptyBlock(false) ) {
+			return 0;
+		}
+		
+		//sanity check input/output sparsity
+		if( m1.sparse != ret.sparse )
+			throw new DMLRuntimeException("Unsupported safe binary scalar operations over different input/output representation: "+m1.sparse+" "+ret.sparse);
+		
+		boolean copyOnes = (op.fn instanceof NotEquals && op.getConstant()==0);
+		boolean allocExact = (op.fn instanceof Multiply || op.fn instanceof Multiply2 
+			|| op.fn instanceof Power2 || Builtin.isBuiltinCode(op.fn, BuiltinCode.MAX)
+			|| Builtin.isBuiltinCode(op.fn, BuiltinCode.MIN));
+		long lnnz = 0;
+		
+		if( m1.sparse ) //SPARSE <- SPARSE
+		{
+			//allocate sparse row structure
+			ret.allocateSparseRowsBlock();
+			SparseBlock a = m1.sparseBlock;
+			SparseBlock c = ret.sparseBlock;
+			
+			long nnz = 0;
+			for(int r=rl; r<ru; r++) {
+				if( a.isEmpty(r) ) continue;
+				
+				int apos = a.pos(r);
+				int alen = a.size(r);
+				int[] aix = a.indexes(r);
+				//double[] avals = a.values(r);
+				MemorySegment avals = memorySegments1[r];
+				
+				if( copyOnes ) { //SPECIAL CASE: e.g., (X != 0) 
+					//create sparse row without repeated resizing
+					SparseRowVector crow = new SparseRowVector(alen);
+					crow.setSize(alen);
+					
+					//memcopy/memset of indexes/values (sparseblock guarantees absence of 0s) 
+					System.arraycopy(aix, apos, crow.indexes(), 0, alen);
+					Arrays.fill(crow.values(), 0, alen, 1);
+					c.set(r, crow, false);
+					nnz += alen;
+				}
+				else { //GENERAL CASE //TODO:FFM
+					//create sparse row without repeated resizing for specific ops
+					if( allocExact )
+						c.allocate(r, alen);
+
+					for(int j=apos; j<apos+alen; j++) {
+						double val = op.executeScalar(avals.getAtIndex(ValueLayout.JAVA_DOUBLE, j));
+						c.append(r, aix[j], val);
+						nnz += (val != 0) ? 1 : 0; 
+					}
+				}
+			}
+			lnnz = (ret.nonZeros = nnz);
+		}
+		else { //DENSE <- DENSE
+			lnnz = denseBinaryScalar(m1, ret, op, rl, ru);
+		}
+		
+		return lnnz;
+	}
+	
+	/**
+	 * Since this operation is sparse-unsafe, ret should always be passed in dense representation.
+	 * 
+	 * @param m1 input matrix
+	 * @param ret result matrix
+	 * @param op scalar operator
+	 */
+	private static long unsafeBinaryScalar(MatrixBlock m1, MatrixBlock ret, ScalarOperator op) {
+		//early abort possible since sparsesafe
+		if( m1.isEmptyBlock(false) ) {
+			//compute 0 op constant once and set into dense output
+			double val = op.executeScalar(0);
+			if( val != 0 )
+				ret.reset(ret.rlen, ret.clen, val);
+			return (val != 0) ? ret.getLength() : 0;
+		}
+		
+		//sanity check input/output sparsity
+		if( ret.sparse )
+			throw new DMLRuntimeException("Unsupported unsafe binary scalar operations over sparse output representation.");
+		
+		int m = m1.rlen;
+		int n = m1.clen;
+		long lnnz = 0;
+		
+		if( m1.sparse ) //SPARSE MATRIX
+		{
+			ret.allocateDenseBlock();
+			
+			SparseBlock a = m1.sparseBlock;
+			DenseBlock dc = ret.getDenseBlock();
+			
+			//init dense result with unsafe 0-value
+			double val0 = op.executeScalar(0);
+			boolean lsparseSafe = (val0 == 0);
+			if( !lsparseSafe )
+				dc.set(val0);
+			
+			//compute non-zero input values
+			long nnz = lsparseSafe ? 0 : m * n;
+			for(int bi=0; bi<dc.numBlocks(); bi++) {
+				int blen = dc.blockSize(bi);
+				double[] c = dc.valuesAt(bi);
+				for(int i=bi*dc.blockSize(), cix=i*n; i<blen && i<m; i++, cix+=n) {
+					if( a.isEmpty(i) ) continue;
+					int apos = a.pos(i);
+					int alen = a.size(i);
+					int[] aix = a.indexes(i);
+					double[] avals = a.values(i);
+					for(int j=apos; j<apos+alen; j++) {
+						double val = op.executeScalar(avals[j]);
+						c[ cix+aix[j] ] = val;
+						nnz += lsparseSafe ? (val!=0 ? 1 : 0) :
+							(val==0 ? -1 : 0);
+					}
+				}
+			}
+			lnnz = (ret.nonZeros = nnz);
+		}
+		else { //DENSE MATRIX
+			lnnz = denseBinaryScalar(m1, ret, op, 0, m);
+		}
+		
+		return lnnz;
+	}
+
+	private static long denseBinaryScalar(MatrixBlock m1, MatrixBlock ret, ScalarOperator op, int rl, int ru) {
+		//allocate dense block (if necessary), incl clear nnz
+		ret.allocateDenseBlock(true);
+		
+		DenseBlock da = m1.getDenseBlock();
+		DenseBlock dc = ret.getDenseBlock();
+		int clen = m1.clen;
+		
+		//compute scalar operation, incl nnz maintenance
+		long nnz = 0;
+		if( clen == 1 ) { //COL VECTOR
+			double[] a = da.valuesAt(0);
+			double[] c = dc.valuesAt(0);
+			for(int i=rl; i<ru; i++) { //VECTOR
+				c[i] = op.executeScalar( a[i] );
+				nnz += (c[i] != 0) ? 1 : 0;
+			}
+		}
+		else { //MULTI-COL MATRIX
+			for(int i=rl; i<ru; i++) {
+				double[] a = da.values(i);
+				double[] c = dc.values(i);
+				int apos = da.pos(i), cpos = dc.pos(i);
+				for(int j=0; j<clen; j++) {
+					c[cpos+j] = op.executeScalar( a[apos+j] );
+					nnz += (c[cpos+j] != 0) ? 1 : 0;
+				}
+			}
+		}
+		return ret.nonZeros = nnz;
+	}
+
+	private static void safeBinaryInPlace(MatrixBlock m1ret, MatrixBlock m2, BinaryOperator op) {
+		// early abort on skip and empty
+		final boolean PoM = op.fn instanceof Plus || op.fn instanceof Minus;
+		if((m1ret.isEmpty() && m2.isEmpty()) || (PoM && m2.isEmpty())) {
+			final boolean isEquals = op.fn instanceof Equals || op.fn instanceof LessThanEquals ||
+				op.fn instanceof GreaterThanEquals;
+
+			if(isEquals)
+				m1ret.reset(m1ret.rlen, m1ret.clen, 1);
+			return; // skip entire empty block
+		}
+		else if(m2.isEmpty() && // empty other side
+			(op.fn instanceof Multiply || (op.fn instanceof And))) {
+			m1ret.reset(m1ret.rlen, m1ret.clen, 0);
+			return;
+		}
+
+		if(m1ret.getNumRows() > 1 && m2.getNumRows() == 1)
+			safeBinaryInPlaceMatrixRowVector(m1ret, m2, op);
+		else
+			safeBinaryInPlaceMatrixMatrix(m1ret, m2, op);
+	}
+
+	private static void safeBinaryInPlaceMatrixRowVector(MatrixBlock m1ret, MatrixBlock m2, BinaryOperator op) {
+		if(m1ret.sparse) {
+			if(m2.isInSparseFormat() && !op.isRowSafeLeft(m2))
+				throw new DMLRuntimeException("Invalid row safety of inplace row operation: " + op);
+			else if(m2.isEmpty())
+				safeBinaryInPlaceSparseConst(m1ret, 0.0, op);
+			else if(m2.sparse)
+				throw new NotImplementedException("Not made sparse vector inplace to sparse " + op);
+			else
+				safeBinaryInPlaceSparseVector(m1ret, m2, op);
+		}
+		else {
+			if(!m1ret.isAllocated()) {
+				LOG.warn("Allocating inplace output block");
+				m1ret.allocateBlock();
+			}
+
+			if(m2.isEmpty())
+				safeBinaryInPlaceDenseConst(m1ret, 0.0, op);
+			else if(m2.sparse)
+				throw new NotImplementedException("Not made sparse vector inplace to dense " + op);
+			else
+				safeBinaryInPlaceDenseVector(m1ret, m2, op);
+		}
+	}
+
+	private static void safeBinaryInPlaceMatrixMatrix(MatrixBlock m1ret, MatrixBlock m2, BinaryOperator op) {
+		if(op.fn instanceof Plus && m1ret.isEmpty() && !m1ret.isAllocated())
+			m1ret.copy(m2);
+		else if(m1ret.sparse && m2.sparse)
+			safeBinaryInPlaceSparse(m1ret, m2, op);
+		else if(!m1ret.sparse && !m2.sparse)
+			safeBinaryInPlaceDense(m1ret, m2, op);
+		else if(m2.sparse && (op.fn instanceof Plus || op.fn instanceof Minus))
+			safeBinaryInPlaceDenseSparseAdd(m1ret, m2, op);
+		else
+			safeBinaryInPlaceGeneric(m1ret, m2, op);
+	}
+
+	private static void safeBinaryInPlaceSparse(MatrixBlock m1ret, MatrixBlock m2, BinaryOperator op) {
+		//allocation and preparation (note: for correctness and performance, this 
+		//implementation requires the lhs in MCSR and hence we explicitly convert)
+		if( m1ret.sparseBlock!=null )
+			m1ret.allocateSparseRowsBlock(false);
+		if( !(m1ret.sparseBlock instanceof SparseBlockMCSR) )
+			m1ret.sparseBlock = SparseBlockFactory.copySparseBlock(
+				SparseBlock.Type.MCSR, m1ret.sparseBlock, false);
+		if( m2.sparseBlock!=null )
+			m2.allocateSparseRowsBlock(false);
+		SparseBlock c = m1ret.sparseBlock;
+		SparseBlock b = m2.sparseBlock;
+		final int rlen = m1ret.rlen;
+		final int clen = m1ret.clen;
+		
+		final boolean compact = (op.fn instanceof Multiply || op.fn instanceof And );
+		final boolean mcsr = c instanceof SparseBlockMCSR;
+
+		if( c!=null && b!=null ) {
+			for(int r=0; r<rlen; r++) {
+				if(c.isEmpty(r) && b.isEmpty(r))
+					continue;
+				if( b.isEmpty(r) ) {
+					zeroRightForSparseBinary(op, r, m1ret);
+				}
+				else if( c.isEmpty(r) ) {
+					appendRightForSparseBinary(op, b.values(r), b.indexes(r), b.pos(r), b.size(r), r, m1ret);
+				}
+				else {
+					//this approach w/ single copy only works with the MCSR format
+					int estimateSize = Math.min(clen, (!c.isEmpty(r) ?
+						c.size(r) : 0) + (!b.isEmpty(r) ? b.size(r) : 0));
+					SparseRow old = c.get(r);
+					c.set(r, new SparseRowVector(estimateSize), false);
+					m1ret.nonZeros -= old.size();
+					mergeForSparseBinary(op, old.values(), old.indexes(), 0, 
+						old.size(), b.values(r), b.indexes(r), b.pos(r), b.size(r), r, m1ret);
+				}
+				if(compact && mcsr && !c.isEmpty(r))
+					c.get(r).compact();
+			}
+		}
+		else if( c == null ) { //lhs empty
+			m1ret.sparseBlock = SparseBlockFactory.createSparseBlock(rlen);
+			for( int r=0; r<rlen; r++) {
+				if( b.isEmpty(r) ) continue;
+				appendRightForSparseBinary(op, b.values(r),
+					b.indexes(r), b.pos(r), b.size(r), r, m1ret);
+			}
+		}
+		else { //rhs empty
+			for(int r=0; r<rlen; r++) {
+				if( c.isEmpty(r) ) continue;
+				zeroRightForSparseBinary(op, r, m1ret);
+			}
+
+		}
+		
+		m1ret.recomputeNonZeros();
+	}
+
+	private static void safeBinaryInPlaceSparseConst(MatrixBlock m1ret, double m2, BinaryOperator op) {
+		if(m1ret.isEmpty()) // early termination... it is empty and safe... just stop.
+			return;
+		final SparseBlock sb = m1ret.getSparseBlock();
+		final int rlen = m1ret.rlen;
+		for(int r = 0; r < rlen; r++) {
+			if(sb.isEmpty(r))
+				continue;
+			final int apos = sb.pos(r);
+			final int alen = sb.size(r) + apos;
+			final double[] avals = sb.values(r);
+			for(int k = apos; k < alen; k++)
+				avals[k] = op.fn.execute(avals[k], m2);
+		}
+	}
+
+	private static void safeBinaryInPlaceSparseVector(MatrixBlock m1ret, MatrixBlock m2, BinaryOperator op) {
+
+		if(m1ret.isEmpty()) // early termination... it is empty and safe... just stop.
+			return;
+		final SparseBlock sb = m1ret.getSparseBlock();
+		final double[] b = m2.getDenseBlockValues();
+		final int rlen = m1ret.rlen;
+
+		final boolean compact = (op.fn instanceof Multiply || op.fn instanceof And) //
+			&& op.isIntroducingZerosRight(m2);
+		final boolean mcsr = sb instanceof SparseBlockMCSR;
+		for(int r = 0; r < rlen; r++) {
+			if(sb.isEmpty(r))
+				continue;
+			final int apos = sb.pos(r);
+			final int alen = sb.size(r) + apos;
+			final double[] avals = sb.values(r);
+			final int[] aix = sb.indexes(r);
+			for(int k = apos; k < alen; k++)
+				avals[k] = op.fn.execute(avals[k], b[aix[k]]);
+
+			if(compact && mcsr) {
+				SparseRow sr = sb.get(r);
+				if(sr instanceof SparseRowVector)
+					((SparseRowVector) sr).setSize(avals.length);
+				sr.compact();
+			}
+		}
+		if(compact && !mcsr) {
+			((SparseBlockCSR) sb).compact();
+		}
+	}
+
+	private static void safeBinaryInPlaceDense(MatrixBlock m1ret, MatrixBlock m2, BinaryOperator op) {
+		// prepare outputs
+		if(!m1ret.isAllocated()) // allocate
+			m1ret.allocateDenseBlock();
+
+		if(m2.isEmptyBlock(false))
+			safeBinaryInPlaceDenseEmpty(m1ret, op);
+		else if(op.fn instanceof Plus)
+			safeBinaryInPlaceDensePlus(m1ret, m2, op);
+		else
+			safeBinaryInPlaceDenseGeneric(m1ret, m2, op);
+	}
+
+	private static void safeBinaryInPlaceDenseEmpty(MatrixBlock m1ret, BinaryOperator op) {
+		DenseBlock a = m1ret.getDenseBlock();
+		final int rlen = m1ret.rlen;
+		final int clen = m1ret.clen;
+		long lnnz = 0;
+		for(int r = 0; r < rlen; r++) {
+			double[] avals = a.values(r);
+			for(int c = 0, ix = a.pos(r); c < clen; c++, ix++) {
+				double tmp = op.fn.execute(avals[ix], 0);
+				lnnz += (avals[ix] = tmp) != 0 ? 1 : 0;
+			}
+		}
+		m1ret.setNonZeros(lnnz);
+	}
+
+	private static void safeBinaryInPlaceDensePlus(MatrixBlock m1ret, MatrixBlock m2, BinaryOperator op) {
+		DenseBlock a = m1ret.getDenseBlock();
+		DenseBlock b = m2.getDenseBlock();
+		final int rlen = m1ret.rlen;
+		final int clen = m1ret.clen;
+		long lnnz = 0;
+		if(a.isContiguous() && b.isContiguous()){
+			final double[] avals = a.values(0);
+			final double[] bvals = b.values(0);
+			for(int i = 0; i < avals.length; i++)
+				lnnz += (avals[i] += bvals[i]) == 0 ? 0 : 1;
+		}
+		else{
+			for(int r = 0; r < rlen; r++) {
+				final int aix = a.pos(r), bix = b.pos(r);
+				final double[] avals = a.values(r), bvals = b.values(r);
+				LibMatrixMult.vectAdd(bvals, avals, bix, aix, clen);
+				lnnz += UtilFunctions.computeNnz(avals, aix, clen);
+			}
+		}
+		m1ret.setNonZeros(lnnz);
+	}
+
+	private static void safeBinaryInPlaceDenseGeneric(MatrixBlock m1ret, MatrixBlock m2, BinaryOperator op) {
+		DenseBlock a = m1ret.getDenseBlock();
+		DenseBlock b = m2.getDenseBlock();
+		final int rlen = m1ret.rlen;
+		final int clen = m1ret.clen;
+		long lnnz = 0;
+		for(int r = 0; r < rlen; r++) {
+			double[] avals = a.values(r), bvals = b.values(r);
+			for(int c = 0, ix = a.pos(r); c < clen; c++, ix++) {
+				double tmp = op.fn.execute(avals[ix], bvals[ix]);
+				lnnz += (avals[ix] = tmp) != 0 ? 1 : 0;
+			}
+		}
+		m1ret.setNonZeros(lnnz);
+	}
+
+
+	private static void safeBinaryInPlaceDenseConst(MatrixBlock m1ret, double m2, BinaryOperator op) {
+		// prepare outputs
+		m1ret.allocateDenseBlock();
+		DenseBlock a = m1ret.getDenseBlock();
+		final int rlen = m1ret.rlen;
+		final int clen = m1ret.clen;
+
+		long lnnz = 0;
+		for(int r = 0; r < rlen; r++) {
+			double[] avals = a.values(r);
+			for(int c = 0, ix = a.pos(r); c < clen; c++, ix++) {
+				double tmp = op.fn.execute(avals[ix], m2);
+				lnnz += (avals[ix] = tmp) != 0 ? 1 : 0;
+			}
+		}
+
+		m1ret.setNonZeros(lnnz);
+	}
+
+	private static void safeBinaryInPlaceDenseVector(MatrixBlock m1ret, MatrixBlock m2, BinaryOperator op) {
+		// prepare outputs
+		m1ret.allocateDenseBlock();
+		DenseBlock a = m1ret.getDenseBlock();
+		double[] b = m2.getDenseBlockValues();
+		final int rlen = m1ret.rlen;
+		final int clen = m1ret.clen;
+
+		long lnnz = 0;
+		for(int r = 0; r < rlen; r++) {
+			double[] avals = a.values(r);
+			for(int c = 0, ix = a.pos(r); c < clen; c++, ix++) {
+				double tmp = op.fn.execute(avals[ix], b[ix % clen]);
+				lnnz += (avals[ix] = tmp) != 0 ? 1 : 0;
+			}
+		}
+		m1ret.setNonZeros(lnnz);
+	}
+	
+	private static void safeBinaryInPlaceDenseSparseAdd(MatrixBlock m1ret, MatrixBlock m2, BinaryOperator op) {
+		final int rlen = m1ret.rlen;
+		DenseBlock a = m1ret.denseBlock;
+		SparseBlock b = m2.sparseBlock;
+		long nnz = m1ret.getNonZeros();
+		for(int r=0; r<rlen; r++) {
+			if( b.isEmpty(r) ) continue;
+			int apos = a.pos(r), bpos = b.pos(r);
+			int blen = b.size(r);
+			int[] bix = b.indexes(r);
+			double[] avals = a.values(r), bvals = b.values(r);
+			for(int k = bpos; k<bpos+blen; k++) {
+				double vold = avals[apos+bix[k]];
+				double vnew = op.fn.execute(vold, bvals[k]);
+				nnz += (vold == 0 && vnew != 0) ? 1 :
+					(vold != 0 && vnew ==0) ? -1  : 0;
+				avals[apos+bix[k]] = vnew;
+			}
+		}
+		m1ret.setNonZeros(nnz);
+	}
+	
+	private static void safeBinaryInPlaceGeneric(MatrixBlock m1ret, MatrixBlock m2, BinaryOperator op) {
+		final int rlen = m1ret.rlen;
+		final int clen = m1ret.clen;
+		for(int r=0; r<rlen; r++)
+			for(int c=0; c<clen; c++) {
+				double thisvalue = m1ret.get(r, c);
+				double thatvalue = m2.get(r, c);
+				double resultvalue = op.fn.execute(thisvalue, thatvalue);
+				m1ret.set(r, c, resultvalue);
+			}
+	}
+	
+	private static void unsafeBinaryInPlace(MatrixBlock m1ret, MatrixBlock m2, BinaryOperator op){
+		int rlen = m1ret.rlen;
+		int clen = m1ret.clen;
+		BinaryAccessType atype = getBinaryAccessType(m1ret, m2);
+		
+		if( atype == BinaryAccessType.MATRIX_COL_VECTOR ) //MATRIX - COL_VECTOR
+		{
+			for(int r=0; r<rlen; r++) {
+				//replicated value
+				double v2 = m2.get(r, 0);
+				for(int c=0; c<clen; c++) {
+					double v1 = m1ret.get(r, c);
+					double v = op.fn.execute( v1, v2 );
+					m1ret.set(r, c, v);
+				}
+			}
+		}
+		else if( atype == BinaryAccessType.MATRIX_ROW_VECTOR ) //MATRIX - ROW_VECTOR
+		{
+			for(int r=0; r<rlen; r++)
+				for(int c=0; c<clen; c++) {
+					double v1 = m1ret.get(r, c);
+					double v2 = m2.get(0, c); //replicated value
+					double v = op.fn.execute( v1, v2 );
+					m1ret.set(r, c, v);
+				}
+		}
+		else // MATRIX - MATRIX
+		{
+			for(int r=0; r<rlen; r++)
+				for(int c=0; c<clen; c++) {
+					double v1 = m1ret.get(r, c);
+					double v2 = m2.get(r, c);
+					double v = op.fn.execute( v1, v2 );
+					m1ret.set(r, c, v);
+				}
+		}
+	}
+	
+	private static void mergeForSparseBinary(BinaryOperator op, double[] values1, int[] cols1, int pos1, int size1, 
+			double[] values2, int[] cols2, int pos2, int size2, int resultRow, MatrixBlock result) {
+		int p1 = 0, p2 = 0;
+		if( op.fn instanceof Multiply ) { //skip empty
+			//skip empty: merge-join (with inner join semantics)
+			//similar to sorted list intersection
+			if(result.getSparseBlock() == null)
+				result.allocateSparseRowsBlock();
+			SparseBlock sblock = result.getSparseBlock();
+			sblock.allocate(resultRow, Math.min(size1, size2), result.clen);
+			while( p1 < size1 && p2 < size2 ) {
+				int colPos1 = cols1[pos1+p1];
+				int colPos2 = cols2[pos2+p2];
+				if( colPos1 == colPos2 )
+					sblock.append(resultRow, colPos1,
+						op.fn.execute(values1[pos1+p1], values2[pos2+p2]));
+				p1 += (colPos1 <= colPos2) ? 1 : 0;
+				p2 += (colPos1 >= colPos2) ? 1 : 0;
+			}
+			result.nonZeros += sblock.size(resultRow);
+		}
+		else {
+			//general case: merge-join (with outer join semantics) 
+			while( p1 < size1 && p2 < size2 ) {
+				if(cols1[pos1+p1]<cols2[pos2+p2]) {
+					result.appendValue(resultRow, cols1[pos1+p1], 
+						op.fn.execute(values1[pos1+p1], 0));
+					p1++;
+				}
+				else if(cols1[pos1+p1]==cols2[pos2+p2]) {
+					result.appendValue(resultRow, cols1[pos1+p1], 
+						op.fn.execute(values1[pos1+p1], values2[pos2+p2]));
+					p1++;
+					p2++;
+				}
+				else {
+					result.appendValue(resultRow, cols2[pos2+p2], 
+						op.fn.execute(0, values2[pos2+p2]));
+					p2++;
+				}
+			}
+			//add left over
+			appendLeftForSparseBinary(op, values1, cols1, pos1, size1, p1, resultRow, result);
+			appendRightForSparseBinary(op, values2, cols2, pos2, size2, p2, resultRow, result);
+		}
+	}
+
+	private static void appendLeftForSparseBinary(BinaryOperator op, double[] values1, int[] cols1, int pos1, int size1, 
+				int pos, int resultRow, MatrixBlock result) {
+		for(int j=pos1+pos; j<pos1+size1; j++) {
+			double v = op.fn.execute(values1[j], 0);
+			result.appendValue(resultRow, cols1[j], v);
+		}
+	}
+
+	private static void appendRightForSparseBinary(BinaryOperator op, double[] vals, int[] ix, int pos, int size, int r, MatrixBlock ret) {
+		appendRightForSparseBinary(op, vals, ix, pos, size, 0, r, ret);
+	}
+	
+	private static void appendRightForSparseBinary(BinaryOperator op, double[] values2, int[] cols2, int pos2, int size2, 
+			int pos, int r, MatrixBlock result) {
+		for( int j=pos2+pos; j<pos2+size2; j++ ) {
+			double v = op.fn.execute(0, values2[j]);
+			result.appendValue(r, cols2[j], v);
+		}
+	}
+	
+	private static void zeroRightForSparseBinary(BinaryOperator op, int r, MatrixBlock ret) {
+		if( op.fn instanceof Plus || op.fn instanceof Minus )
+			return;
+		SparseBlock c = ret.sparseBlock;
+		int apos = c.pos(r);
+		int alen = c.size(r);
+		double[] values = c.values(r);
+		boolean zero = false;
+		for(int i=apos; i<apos+alen; i++)
+			zero |= ((values[i] = op.fn.execute(values[i], 0)) == 0);
+		if( zero )
+			c.compact(r);
+	}
+	
+	private static class BincellTask implements Callable<Long> {
+		private final MatrixBlock _m1;
+		private final MatrixBlock _m2;
+		private final MatrixBlock _ret;
+		private final BinaryOperator _bop;
+		BinaryAccessType _atype;
+		private final int _rl;
+		private final int _ru;
+
+		protected BincellTask( MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, BinaryOperator bop, BinaryAccessType atype, int rl, int ru ) {
+			_m1 = m1;
+			_m2 = m2;
+			_ret = ret;
+			_bop = bop;
+			_atype = atype;
+			_rl = rl;
+			_ru = ru;
+		}
+		
+		@Override
+		public Long call() {
+			// execute binary operation on row partition
+			// (including nnz maintenance)
+			if(_bop.sparseSafe || isSparseSafeDivide(_bop, _m2))
+				return safeBinary(_m1, _m2, _ret, _bop, _atype, _rl, _ru);
+			else
+				return unsafeBinary(_m1, _m2, _ret, _bop, _rl, _ru);
+		}
+	}
+	
+	private static class BincellScalarTask implements Callable<Long> {
+		private final MatrixBlock _m1;
+		private final MatrixBlock _ret;
+		private final ScalarOperator _sop;
+		private final int _rl;
+		private final int _ru;
+
+		protected BincellScalarTask( MatrixBlock m1, MatrixBlock ret, ScalarOperator sop, int rl, int ru ) {
+			_m1 = m1;
+			_ret = ret;
+			_sop = sop;
+			_rl = rl;
+			_ru = ru;
+		}
+		
+		@Override
+		public Long call() {
+			//execute binary operation on row partition
+			return safeBinaryScalar(_m1, _ret, _sop, _rl, _ru);
+		}
+	}
+	
+	private static class UncellTask implements Callable<Long> {
+		private final DenseBlock _a;
+		private final DenseBlock _c;
+		private final UnaryOperator _op;
+		private final int _rl;
+		private final int _ru;
+
+		protected UncellTask(DenseBlock a, DenseBlock c, UnaryOperator op, int rl, int ru ) {
+			_a = a;
+			_c = c;
+			_op = op;
+			_rl = rl;
+			_ru = ru;
+		}
+		
+		@Override
+		public Long call() {
+			long nnz = 0;
+			//fast dense-dense operations
+			if(_a.isContiguous(_rl, _ru)) {
+				double[] avals = _a.values(_rl);
+				double[] cvals = _c.values(_rl);
+				int start = _a.pos(_rl), end = _a.pos(_ru);
+				for( int i=start; i<end; i++ ) {
+					cvals[i] = _op.fn.execute(avals[i]);
+					nnz += (cvals[i] != 0) ? 1 : 0;
+				}
+			}
+			//generic dense-dense, including large blocks
+			else {
+				int clen = _a.getDim(1);
+				for(int i=_rl; i<_ru; i++) {
+					double[] avals = _a.values(i);
+					double[] cvals = _c.values(i);
+					int pos = _a.pos(i);
+					for( int j=0; j<clen; j++ ) {
+						cvals[pos+j] = _op.fn.execute(avals[pos+j]);
+						nnz += (cvals[pos+j] != 0) ? 1 : 0;
+					}
+				}
+			}
+			return nnz;
+		}
+	}
+}
diff --git a/scripts/staging/SIMD-FFM-prototypes/LibMatrixBincellSIMD.java b/scripts/staging/SIMD-FFM-prototypes/LibMatrixBincellSIMD.java
new file mode 100644
index 0000000000..3a1d22f1d0
--- /dev/null
+++ b/scripts/staging/SIMD-FFM-prototypes/LibMatrixBincellSIMD.java
@@ -0,0 +1,2485 @@
+/*
+ * 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.sysds.runtime.matrix.data;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+
+import jdk.incubator.vector.*;
+import org.apache.commons.lang3.NotImplementedException;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.sysds.runtime.DMLRuntimeException;
+import org.apache.sysds.runtime.data.*;
+import org.apache.sysds.runtime.functionobjects.*;
+import org.apache.sysds.runtime.functionobjects.Builtin.BuiltinCode;
+import org.apache.sysds.runtime.matrix.operators.BinaryOperator;
+import org.apache.sysds.runtime.matrix.operators.ScalarOperator;
+import org.apache.sysds.runtime.matrix.operators.UnaryOperator;
+import org.apache.sysds.runtime.util.CommonThreadPool;
+import org.apache.sysds.runtime.util.DataConverter;
+import org.apache.sysds.runtime.util.SortUtils;
+import org.apache.sysds.runtime.util.UtilFunctions;
+
+/**
+ * Library for binary cellwise operations (incl arithmetic, relational, etc). Currently, we don't have dedicated support
+ * for the individual operations but for categories of operations and combinations of dense/sparse and MM/MV.
+ * Safe/unsafe refer to sparse-safe and sparse-unsafe operations.
+ * 
+ */
+public class LibMatrixBincellSIMD {
+
+	private static final Log LOG = LogFactory.getLog(LibMatrixBincellSIMD.class.getName());
+	private static final long PAR_NUMCELL_THRESHOLD2 = 16 * 1024; // Min 16K elements
+	private static final VectorSpecies<Double> SPECIES = DoubleVector.SPECIES_PREFERRED;
+	private static final int speciesLen = SPECIES.length();
+
+	public enum BinaryAccessType {
+		MATRIX_MATRIX, MATRIX_COL_VECTOR, MATRIX_ROW_VECTOR, COL_VECTOR_MATRIX, ROW_VECTOR_MATRIX, OUTER_VECTOR_VECTOR,
+		INVALID;
+
+		public boolean isMatrixVector() {
+			return this == MATRIX_COL_VECTOR || this == MATRIX_ROW_VECTOR || this == COL_VECTOR_MATRIX ||
+				this == ROW_VECTOR_MATRIX;
+		}
+	}
+
+	private LibMatrixBincellSIMD() {
+		// prevent instantiation via private constructor
+	}
+
+	///////////////////////////////////
+	// public matrix bincell interface
+	///////////////////////////////////
+
+	public static MatrixBlock uncellOp(MatrixBlock m1, MatrixBlock ret, UnaryOperator op) {
+		if(!m1.sparse && !m1.isEmptyBlock(false) && op.getNumThreads() > 1 && m1.getLength() > PAR_NUMCELL_THRESHOLD2) {
+			// note: we apply multi-threading in a best-effort manner here
+			// only for expensive operators such as exp, log, sigmoid, because
+			// otherwise allocation, read and write anyway dominates
+			if(!op.isInplace() || m1.isEmpty())
+				ret.allocateDenseBlock(false);
+			else
+				ret = m1;
+
+			int k = op.getNumThreads();
+			DenseBlock a = m1.getDenseBlock();
+			DenseBlock c = ret.getDenseBlock();
+			ExecutorService pool = CommonThreadPool.get(k);
+			try {
+				ArrayList<UncellTask> tasks = new ArrayList<>();
+				ArrayList<Integer> blklens = UtilFunctions.getBalancedBlockSizesDefault(ret.rlen, k, false);
+				for(int i = 0, lb = 0; i < blklens.size(); lb += blklens.get(i), i++)
+					tasks.add(new UncellTask(a, c, op, lb, lb + blklens.get(i)));
+				List<Future<Long>> taskret = pool.invokeAll(tasks);
+
+				// aggregate non-zeros
+				ret.nonZeros = 0; // reset after execute
+				for(Future<Long> task : taskret)
+					ret.nonZeros += task.get();
+			}
+			catch(InterruptedException | ExecutionException ex) {
+				throw new DMLRuntimeException(ex);
+			}
+			finally {
+				pool.shutdown();
+			}
+		}
+		else {
+			if(op.isInplace() && !m1.isInSparseFormat())
+				ret = m1;
+
+			// default execute unary operations
+			if(op.sparseSafe)
+				sparseUnaryOperations(m1, ret, op);
+			else
+				denseUnaryOperations(m1, ret, op);
+		}
+		return ret;
+	}
+
+	/**
+	 * matrix-scalar, scalar-matrix binary operations.
+	 * 
+	 * @param m1  input matrix
+	 * @param ret result matrix
+	 * @param op  scalar operator
+	 */
+	public static void bincellOp(MatrixBlock m1, MatrixBlock ret, ScalarOperator op) {
+		// check internal assumptions
+		if((op.sparseSafe && m1.isInSparseFormat() != ret.isInSparseFormat()) ||
+			(!op.sparseSafe && ret.isInSparseFormat())) {
+			throw new DMLRuntimeException("Wrong output representation for safe=" + op.sparseSafe + ": "
+				+ m1.isInSparseFormat() + ", " + ret.isInSparseFormat());
+		}
+
+		// execute binary cell operations
+		if(op.sparseSafe)
+			safeBinaryScalar(m1, ret, op, 0, m1.rlen);
+		else
+			unsafeBinaryScalar(m1, ret, op);
+
+		// ensure empty results sparse representation
+		// (no additional memory requirements)
+		if(ret.isEmptyBlock(false))
+			ret.examSparsity();
+	}
+
+	public static void bincellOp(MatrixBlock m1, MatrixBlock ret, ScalarOperator op, int k) {
+		// check internal assumptions
+		if((op.sparseSafe && m1.isInSparseFormat() != ret.isInSparseFormat()) ||
+			(!op.sparseSafe && ret.isInSparseFormat())) {
+			throw new DMLRuntimeException("Wrong output representation for safe=" + op.sparseSafe + ": "
+				+ m1.isInSparseFormat() + ", " + ret.isInSparseFormat());
+		}
+
+		// fallback to singlet-threaded for special cases
+		if(m1.isEmpty() || !op.sparseSafe || ret.getLength() < PAR_NUMCELL_THRESHOLD2) {
+			bincellOp(m1, ret, op);
+			return;
+		}
+
+		// preallocate dense/sparse block for multi-threaded operations
+		ret.allocateBlock();
+
+		ExecutorService pool = CommonThreadPool.get(k);
+		try {
+			// execute binary cell operations
+			ArrayList<BincellScalarTask> tasks = new ArrayList<>();
+			ArrayList<Integer> blklens = UtilFunctions.getBalancedBlockSizesDefault(ret.rlen, k, false);
+			for(int i = 0, lb = 0; i < blklens.size(); lb += blklens.get(i), i++)
+				tasks.add(new BincellScalarTask(m1, ret, op, lb, lb + blklens.get(i)));
+			List<Future<Long>> taskret = pool.invokeAll(tasks);
+
+			// aggregate non-zeros
+			ret.nonZeros = 0; // reset after execute
+			for(Future<Long> task : taskret)
+				ret.nonZeros += task.get();
+		}
+		catch(InterruptedException | ExecutionException ex) {
+			throw new DMLRuntimeException(ex);
+		}
+		finally {
+			pool.shutdown();
+		}
+
+		// ensure empty results sparse representation
+		// (no additional memory requirements)
+		if(ret.isEmptyBlock(false))
+			ret.examSparsity();
+	}
+
+	/**
+	 * matrix-matrix binary operations, MM, MV
+	 * 
+	 * @param m1  input matrix 1
+	 * @param m2  input matrix 2
+	 * @param ret result matrix
+	 * @param op  binary operator
+	 */
+	public static void bincellOp(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, BinaryOperator op) {
+		BinaryAccessType atype = getBinaryAccessType(m1, m2);
+
+		// preallocate for consistency (but be careful
+		// not to allocate if empty inputs might allow early abort)
+		if(atype == BinaryAccessType.MATRIX_MATRIX && !(m1.isEmpty() || m2.isEmpty())) {
+			ret.allocateBlock(); // chosen outside
+		}
+		// execute binary cell operations
+		long nnz = 0;
+		if(op.sparseSafe || isSparseSafeDivide(op, m2))
+			nnz = safeBinary(m1, m2, ret, op, atype, 0, m1.rlen);
+		else
+			nnz = unsafeBinary(m1, m2, ret, op, 0, m1.rlen);
+		ret.setNonZeros(nnz);
+
+		// ensure empty results sparse representation
+		// (no additional memory requirements)
+		if(ret.isEmptyBlock(false))
+			ret.examSparsity();
+	}
+
+	public static void bincellOp(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, BinaryOperator op, int k) {
+		BinaryAccessType atype = getBinaryAccessType(m1, m2);
+
+		// fallback to sequential computation for specialized operations
+		if(m1.isEmpty() || m2.isEmpty() || ret.getLength() < PAR_NUMCELL_THRESHOLD2 ||
+			((op.sparseSafe || isSparseSafeDivide(op, m2)) &&
+				!(atype == BinaryAccessType.MATRIX_MATRIX || atype.isMatrixVector() && isAllDense(m1, m2, ret)))) {
+			bincellOp(m1, m2, ret, op);
+			return;
+		}
+
+		// preallocate dense/sparse block for multi-threaded operations
+		ret.allocateBlock(); // chosen outside
+
+		ExecutorService pool = CommonThreadPool.get(k);
+		try {
+			// execute binary cell operations
+			ArrayList<BincellTask> tasks = new ArrayList<>();
+			ArrayList<Integer> blklens = UtilFunctions.getBalancedBlockSizesDefault(ret.rlen, k, false);
+			for(int i = 0, lb = 0; i < blklens.size(); lb += blklens.get(i), i++)
+				tasks.add(new BincellTask(m1, m2, ret, op, atype, lb, lb + blklens.get(i)));
+			List<Future<Long>> taskret = pool.invokeAll(tasks);
+
+			// aggregate non-zeros
+			ret.nonZeros = 0; // reset after execute
+			for(Future<Long> task : taskret)
+				ret.nonZeros += task.get();
+		}
+		catch(InterruptedException | ExecutionException ex) {
+			throw new DMLRuntimeException(ex);
+		}
+		finally {
+			pool.shutdown();
+		}
+
+		// ensure empty results sparse representation
+		// (no additional memory requirements)
+		if(ret.isEmptyBlock(false))
+			ret.examSparsity();
+	}
+
+	/**
+	 * NOTE: operations in place always require m1 and m2 to be of equal dimensions
+	 * 
+	 * defaults to right side operations, updating the m1 matrix with like:
+	 * 
+	 * m1ret op m2
+	 * 
+	 * @param m1ret result matrix updated in place
+	 * @param m2    matrix block the other matrix to take values from
+	 * @param op    binary operator the operator that is placed in the middle of m1ret and m2
+	 * @return The same pointer to m1ret argument, and the updated result.
+	 */
+	public static MatrixBlock bincellOpInPlace(MatrixBlock m1ret, MatrixBlock m2, BinaryOperator op) {
+		return bincellOpInPlaceRight(m1ret, m2, op);
+	}
+
+	/**
+	 * Right side operations, updating the m1 matrix like:
+	 * 
+	 * m1ret op m2
+	 * 
+	 * @param m1ret result matrix updated in place
+	 * @param m2    matrix block the other matrix to take values from
+	 * @param op    binary operator the operator that is placed in the middle of m1ret and m2
+	 * @return The result MatrixBlock (same object pointer to m1ret argument)
+	 */
+	public static MatrixBlock bincellOpInPlaceRight(MatrixBlock m1ret, MatrixBlock m2, BinaryOperator op) {
+		// execute binary cell operations
+		if(op.sparseSafe || isSparseSafeDivide(op, m2))
+			safeBinaryInPlace(m1ret, m2, op);
+		else
+			unsafeBinaryInPlace(m1ret, m2, op);
+
+		// ensure empty results sparse representation
+		// (no additional memory requirements)
+		if(m1ret.isEmptyBlock(false))
+			m1ret.examSparsity();
+		return m1ret;
+	}
+
+	/**
+	 * Left side operations, updating the m1 matrix like:
+	 * 
+	 * m2 op m1ret
+	 * 
+	 * @param m1ret result matrix updated in place
+	 * @param m2    matrix block the other matrix to take values from
+	 * @param op    binary operator the operator that is placed in the middle of m1ret and m2
+	 * @return The result MatrixBlock (same object pointer to m1ret argument)
+	 */
+	public static MatrixBlock bincellOpInPlaceLeft(MatrixBlock m1ret, MatrixBlock m2, BinaryOperator op) {
+		final int nRows = m1ret.getNumRows();
+		final int nCols = m1ret.getNumColumns();
+		if(m1ret.isInSparseFormat()) {
+			// not doing in place, since the m1ret is in sparse format, and m2 might make it dense.
+			// this is not ideal either, but makes it work
+			LOG.warn("Inefficient bincell op in place left, because output is materialized in new matrix");
+			MatrixBlock right = new MatrixBlock(nRows, nCols, true);
+			right.copyShallow(m1ret);
+			m1ret.cleanupBlock(true, true);
+			bincellOp(m2, right, m1ret, op);
+			return m1ret;
+		}
+
+		// m1ret is dense:
+		final double[] retV = m1ret.getDenseBlockValues();
+		final ValueFunction f = op.fn;
+
+		if(m2.isInSparseFormat() && op.sparseSafe) {
+			final SparseBlock sb = m2.getSparseBlock();
+			for(int row = 0; row < nRows; row++) {
+				if(sb.isEmpty(row)) {
+					continue;
+				}
+				final int apos = sb.pos(row);
+				final int alen = sb.size(row) + apos;
+				final int[] aix = sb.indexes(row);
+				final double[] aval = sb.values(row);
+				final int offsetV = row * nCols;
+				for(int j = apos; j < alen; j++) {
+					final int idx = offsetV + aix[j];
+					retV[idx] = f.execute(aval[j], retV[idx]);
+				}
+			}
+		}
+		else if(m2.isInSparseFormat()) {
+			throw new NotImplementedException("Not implemented left bincell in place unsafe operations");
+		}
+		else {
+			final double[] m2V = m2.getDenseBlockValues();
+			final int size = nRows * nCols;
+			for(int i = 0; i < size; i++) {
+				retV[i] = f.execute(m2V[i], retV[i]);
+			}
+
+			if(m1ret.isEmptyBlock(false))
+				m1ret.examSparsity();
+		}
+		return m1ret;
+	}
+
+	public static BinaryAccessType getBinaryAccessType(MatrixBlock m1, MatrixBlock m2) {
+		int rlen1 = m1.rlen;
+		int rlen2 = m2.rlen;
+		int clen1 = m1.clen;
+		int clen2 = m2.clen;
+
+		if(rlen1 == rlen2 && clen1 == clen2)
+			return BinaryAccessType.MATRIX_MATRIX;
+		else if(clen1 > 1 && clen2 == 1)
+			return BinaryAccessType.MATRIX_COL_VECTOR;
+		else if(rlen1 > 1 && clen1 > 1 && rlen2 == 1)
+			return BinaryAccessType.MATRIX_ROW_VECTOR;
+		else if(clen1 == 1 && rlen2 == 1)
+			return BinaryAccessType.OUTER_VECTOR_VECTOR;
+		else
+			return BinaryAccessType.INVALID;
+	}
+
+	public static BinaryAccessType getBinaryAccessTypeExtended(MatrixBlock m1, MatrixBlock m2) {
+		final int rlen1 = m1.rlen;
+		final int rlen2 = m2.rlen;
+		final int clen1 = m1.clen;
+		final int clen2 = m2.clen;
+
+		if(rlen1 == rlen2) {
+			if(clen1 == clen2)
+				return BinaryAccessType.MATRIX_MATRIX;
+			else if(clen1 < clen2)
+				return BinaryAccessType.COL_VECTOR_MATRIX;
+			else
+				return BinaryAccessType.MATRIX_COL_VECTOR;
+		}
+		else if(clen1 == clen2) {
+			if(rlen1 < rlen2)
+				return BinaryAccessType.ROW_VECTOR_MATRIX;
+			else
+				return BinaryAccessType.MATRIX_ROW_VECTOR;
+		}
+		else if(clen1 == 1 && rlen2 == 1)
+			return BinaryAccessType.OUTER_VECTOR_VECTOR;
+		else
+			return BinaryAccessType.INVALID;
+	}
+
+	public static void isValidDimensionsBinary(MatrixBlock m1, MatrixBlock m2) {
+		final int rlen1 = m1.rlen;
+		final int clen1 = m1.clen;
+		final int rlen2 = m2.rlen;
+		final int clen2 = m2.clen;
+
+		// currently we support three major binary cellwise operations:
+		// 1) MM (where both dimensions need to match)
+		// 2) MV operations w/ V either being a right-hand-side column or row vector
+		// (where one dimension needs to match and the other dimension is 1)
+		// 3) VV outer vector operations w/ a common dimension of 1
+		boolean isValid = ((rlen1 == rlen2 && clen1 == clen2) // MM
+			|| (rlen1 == rlen2 && clen1 > 1 && clen2 == 1) // MVc
+			|| (clen1 == clen2 && rlen1 > 1 && rlen2 == 1) // MVr
+			|| (clen1 == 1 && rlen2 == 1)); // VV
+
+		if(!isValid) {
+			throw new DMLRuntimeException("Block sizes are not matched for binary " + "cell operations: " + rlen1 + "x"
+				+ clen1 + " vs " + rlen2 + "x" + clen2);
+		}
+	}
+
+	public static void isValidDimensionsBinaryExtended(MatrixBlock m1, MatrixBlock m2) {
+		final int rlen1 = m1.rlen;
+		final int clen1 = m1.clen;
+		final int rlen2 = m2.rlen;
+		final int clen2 = m2.clen;
+
+		// Added extra 2 options
+		// 2a) VM operations with V either being a left-hand-side column or row vector.
+		boolean isValid = ((rlen1 == rlen2 && clen1 == clen2) // MM
+			|| (rlen1 == rlen2 && clen1 > 1 && clen2 == 1) // MVc
+			|| (rlen1 == rlen2 && clen1 == 1 && clen2 > 1) // VMc
+			|| (clen1 == clen2 && rlen1 > 1 && rlen2 == 1) // MVr
+			|| (clen1 == clen2 && rlen1 == 1 && rlen2 > 1) // VMr
+			|| (clen1 == 1 && rlen2 == 1)); // VV
+
+		if(!isValid) {
+			throw new RuntimeException("Block sizes are not matched for binary " + "cell operations: " + rlen1 + "x"
+				+ clen1 + " vs " + rlen2 + "x" + clen2);
+		}
+	}
+
+	public static boolean isSparseSafeDivide(BinaryOperator op, MatrixBlock rhs) {
+		// if rhs is fully dense, there cannot be a /0 and hence DIV becomes sparse safe
+		// TODO: Changed for testing sparsedenseskip skip mechanic. change back
+		return(op.fn instanceof Divide && rhs.getNonZeros() >= (long) rhs.getNumRows() * rhs.getNumColumns() * 0.4);
+	}
+
+	public static boolean isAllDense(MatrixBlock... mb) {
+		return Arrays.stream(mb).allMatch(m -> !m.sparse);
+	}
+
+	//////////////////////////////////////////////////////
+	// private sparse-safe/sparse-unsafe implementations
+	///////////////////////////////////
+
+	private static void denseUnaryOperations(MatrixBlock m1, MatrixBlock ret, UnaryOperator op) {
+		// prepare 0-value init (determine if unnecessarily sparse-unsafe)
+		double val0 = op.fn.execute(0d);
+
+		final int m = m1.rlen;
+		final int n = m1.clen;
+
+		// early abort possible if unnecessarily sparse unsafe
+		// (otherwise full init with val0, no need for computation)
+		if(m1.isEmptyBlock(false)) {
+			if(val0 != 0)
+				ret.reset(m, n, val0);
+			return;
+		}
+
+		// redirection to sparse safe operation w/ init by val0
+		if(m1.sparse && val0 != 0) {
+			ret.reset(m, n, val0);
+			ret.nonZeros = (long) m * n;
+		}
+		sparseUnaryOperations(m1, ret, op);
+	}
+
+	private static void sparseUnaryOperations(MatrixBlock m1, MatrixBlock ret, UnaryOperator op) {
+		// early abort possible since sparse-safe
+		if(m1.isEmptyBlock(false))
+			return;
+
+		final int m = m1.rlen;
+		final int n = m1.clen;
+
+		if(m1.sparse && ret.sparse) // SPARSE <- SPARSE
+		{
+			ret.allocateSparseRowsBlock();
+			SparseBlock a = m1.sparseBlock;
+			SparseBlock c = ret.sparseBlock;
+
+			long nnz = 0;
+			for(int i = 0; i < m; i++) {
+				if(a.isEmpty(i))
+					continue;
+
+				int apos = a.pos(i);
+				int alen = a.size(i);
+				int[] aix = a.indexes(i);
+				double[] avals = a.values(i);
+
+				c.allocate(i, alen); // avoid repeated alloc
+				for(int j = apos; j < apos + alen; j++) {
+					double val = op.fn.execute(avals[j]);
+					c.append(i, aix[j], val);
+					nnz += (val != 0) ? 1 : 0;
+				}
+			}
+			ret.nonZeros = nnz;
+		}
+		else if(m1.sparse) // DENSE <- SPARSE
+		{
+			// TODO:SIMD sparse exp
+			ret.allocateDenseBlock(false);
+			SparseBlock a = m1.sparseBlock;
+			DenseBlock c = ret.denseBlock;
+			long nnz = (ret.nonZeros > 0) ? (long) m * n - a.size() : 0;
+			DoubleVector aVec, res;
+			int len, bn;
+
+			for(int i = 0; i < m; i++) {
+				if(a.isEmpty(i))
+					continue;
+				int apos = a.pos(i);
+				int alen = a.size(i);
+				int[] aix = a.indexes(i);
+				double[] avals = a.values(i);
+				double[] cvals = c.values(i);
+				int cix = c.pos(i);
+
+				len = apos + alen;
+
+				bn = alen % speciesLen;
+				int j = apos;
+				// Rest
+				for(; j < apos+bn; j++) {
+					double val = op.fn.execute(avals[j]);
+					cvals[cix + aix[j]] = val;
+					nnz += (val != 0) ? 1 : 0;
+				}
+
+				// Vectorized iteration
+				for(; j < len; j += speciesLen) {
+					aVec = DoubleVector.fromArray(SPECIES, avals, j);
+					res = aVec.lanewise(VectorOperators.EXP);
+					res.intoArray(cvals, cix, aix, j);
+					nnz += res.compare(VectorOperators.NE, 0).trueCount();
+				}
+			}
+			ret.nonZeros = nnz;
+		}
+		else // DENSE <- DENSE
+		{
+			// TODO:SIMD dense exp
+			if(m1 != ret) // !in-place
+				ret.allocateDenseBlock(false);
+			DenseBlock da = m1.getDenseBlock();
+			DenseBlock dc = ret.getDenseBlock();
+			DoubleVector aVec, res;
+
+			// unary op, incl nnz maintenance
+			long nnz = 0;
+
+			for(int bi = 0; bi < da.numBlocks(); bi++) {
+				double[] a = da.valuesAt(bi);
+				double[] c = dc.valuesAt(bi);
+				int len = da.size(bi);
+				int bn = len % speciesLen;
+
+				int i = 0;
+				// Rest
+				for(; i < bn; i++) {
+					c[i] = op.fn.execute(a[i]);
+					nnz += (c[i] != 0) ? 1 : 0;
+				}
+
+				// Vectorized iteration
+				for(; i < len; i += speciesLen) {
+					aVec = DoubleVector.fromArray(SPECIES, a, i);
+					res = aVec.lanewise(VectorOperators.EXP);
+					res.intoArray(c, i);
+					nnz += res.compare(VectorOperators.NE, 0).trueCount();
+				}
+			}
+			ret.nonZeros = nnz;
+		}
+	}
+
+	private static long safeBinary(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, BinaryOperator op,
+		BinaryAccessType atype, int rl, int ru) {
+		// NOTE: multi-threaded over rl-ru only applied for matrix-matrix, non-empty
+
+		boolean skipEmpty = (op.fn instanceof Multiply || isSparseSafeDivide(op, m2));
+		boolean copyLeftRightEmpty = (op.fn instanceof Plus || op.fn instanceof Minus ||
+			op.fn instanceof PlusMultiply || op.fn instanceof MinusMultiply);
+		boolean copyRightLeftEmpty = (op.fn instanceof Plus);
+
+		// skip empty blocks (since sparse-safe)
+		if(m1.isEmptyBlock(false) && m2.isEmptyBlock(false) ||
+			skipEmpty && (m1.isEmptyBlock(false) || m2.isEmptyBlock(false))) {
+			return 0;
+		}
+
+		if(atype == BinaryAccessType.MATRIX_COL_VECTOR // MATRIX - VECTOR
+			|| atype == BinaryAccessType.MATRIX_ROW_VECTOR) {
+			// note: m2 vector and hence always dense
+			if(!m1.sparse && !m2.sparse && !ret.sparse) // DENSE all
+				return safeBinaryMVDense(m1, m2, ret, op, rl, ru); // TODO:SIMD
+			else if(m1.sparse && !m2.sparse && !ret.sparse && atype == BinaryAccessType.MATRIX_ROW_VECTOR)
+				safeBinaryMVSparseDenseRow(m1, m2, ret, op);
+			else if(m1.sparse) // SPARSE m1
+				safeBinaryMVSparse(m1, m2, ret, op); // TODO:SIMD
+														// binarymvdense
+			else if(!m1.sparse && !m2.sparse && ret.sparse && op.fn instanceof Multiply &&
+				atype == BinaryAccessType.MATRIX_COL_VECTOR && (long) m1.rlen * m2.clen < Integer.MAX_VALUE)
+				safeBinaryMVDenseSparseMult(m1, m2, ret, op);
+			else // generic combinations
+				safeBinaryMVGeneric(m1, m2, ret, op);
+		}
+		else if(atype == BinaryAccessType.OUTER_VECTOR_VECTOR) // VECTOR - VECTOR
+		{
+			safeBinaryVVGeneric(m1, m2, ret, op);
+		}
+		else // MATRIX - MATRIX
+		{
+			if(copyLeftRightEmpty && m2.isEmpty()) {
+				// ret remains unchanged so a shallow copy is sufficient
+				ret.copyShallow(m1);
+			}
+			else if(copyRightLeftEmpty && m1.isEmpty()) {
+				// ret remains unchanged so a shallow copy is sufficient
+				ret.copyShallow(m2);
+			}
+			else if(m1.sparse && m2.sparse) {
+				return safeBinaryMMSparseSparse(m1, m2, ret, op, rl, ru);
+			}
+			else if(!ret.sparse && (m1.sparse || m2.sparse) &&
+				(op.fn instanceof Plus || op.fn instanceof Minus || op.fn instanceof PlusMultiply ||
+					op.fn instanceof MinusMultiply || (op.fn instanceof Multiply && !m2.sparse))) {
+				return safeBinaryMMSparseDenseDense(m1, m2, ret, op, rl, ru);
+			}
+			else if(!ret.sparse && !m1.sparse && !m2.sparse && m1.denseBlock != null && m2.denseBlock != null) {
+				return safeBinaryMMDenseDenseDense(m1, m2, ret, op, rl, ru); // TODO:SIMD
+			}
+			else if( skipEmpty && (m1.sparse || m2.sparse) ) {
+				return safeBinaryMMSparseDenseSkip(m1, m2, ret, op, rl, ru); // TODO:SIMD
+			}
+			else { //generic case
+				return safeBinaryMMGeneric(m1, m2, ret, op, rl, ru);
+			}
+		}
+		//default catch all
+		return ret.getNonZeros();
+	}
+
+	private static long safeBinaryMVDense(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, BinaryOperator op, int rl,
+		int ru) {
+		final boolean isMultiply = (op.fn instanceof Multiply);
+		final boolean skipEmpty = (isMultiply);
+
+		// early abort on skip and empy
+		if(skipEmpty && (m1.isEmptyBlock(false) || m2.isEmptyBlock(false)))
+			return 0; // skip entire empty block
+
+		// guard for postponed allocation in single-threaded exec
+		if(!ret.isAllocated())
+			ret.allocateDenseBlock();
+
+		final BinaryAccessType atype = getBinaryAccessType(m1, m2);
+
+		if(atype == BinaryAccessType.MATRIX_COL_VECTOR)
+			return safeBinaryMVDenseColVector(m1, m2, ret, op, rl, ru);
+		else // if( atype == BinaryAccessType.MATRIX_ROW_VECTOR )
+			return safeBinaryMVDenseRowVector(m1, m2, ret, op, rl, ru);
+	}
+
+	private static long safeBinaryMVDenseColVector(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, BinaryOperator op,
+		int rl, int ru) {
+		final boolean multiply = (op.fn instanceof Multiply);
+		final int clen = m1.clen;
+
+		final DenseBlock da = m1.getDenseBlock();
+		if(da.values(0) == null)
+			throw new RuntimeException("Invalid input with empty input");
+		final DenseBlock dc = ret.getDenseBlock();
+		long nnz = 0;
+		final double[] b = m2.getDenseBlockValues(); // always single block
+
+		if(b == null) {
+			if(multiply)
+				return 0;
+			else {
+				for(int i = rl; i < ru; i++) {
+					final double[] a = da.values(i);
+					final double[] c = dc.values(i);
+					final int ix = da.pos(i);
+					// GENERAL CASE
+					for(int j = 0; j < clen; j++) {
+						double val = op.fn.execute(a[ix + j], 0);
+						nnz += ((c[ix + j] = val) != 0) ? 1 : 0;
+					}
+				}
+			}
+		}
+		else if(multiply) {
+			for(int i = rl; i < ru; i++) {
+				final double[] a = da.values(i);
+				final double[] c = dc.values(i);
+				final int ix = da.pos(i);
+
+				// replicate vector value
+				double v2 = b[i];
+				if(v2 == 0) // skip empty rows
+					continue;
+				else if(v2 == 1) { // ROW COPY
+					// a guaranteed to be non-null (see early abort)
+					System.arraycopy(a, ix, c, ix, clen);
+					nnz += m1.recomputeNonZeros(i, i, 0, clen - 1);
+				}
+				else {
+					// GENERAL CASE
+					for(int j = 0; j < clen; j++) {
+						double val = op.fn.execute(a[ix + j], v2);
+						nnz += ((c[ix + j] = val) != 0) ? 1 : 0;
+					}
+				}
+
+			}
+		}
+		else { // TODO:SIMD div
+			int bn = clen % speciesLen;
+			DoubleVector aVec, bVec, res;
+
+			for(int i = rl; i < ru; i++) {
+				final double[] a = da.values(i);
+				final double[] c = dc.values(i);
+				final int ix = da.pos(i);
+
+				// replicate vector value and broadcast it to bVec
+				double v2 = b[i];
+				bVec = DoubleVector.broadcast(SPECIES, v2);
+
+				// GENERAL CASE
+				int j = 0;
+				for(; j < bn; j++) {
+					double val = op.fn.execute(a[ix + j], v2);
+					nnz += ((c[ix + j] = val) != 0) ? 1 : 0;
+				}
+
+				for(; j < clen; j += speciesLen) {
+					aVec = DoubleVector.fromArray(SPECIES, a, ix + j);
+					res = aVec.div(bVec);
+					res.intoArray(c, ix + j);
+					nnz += res.compare(VectorOperators.NE, 0).trueCount();
+				}
+			}
+		}
+		return nnz;
+	}
+
+	private static long safeBinaryMVDenseRowVector(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, BinaryOperator op,
+		int rl, int ru) {
+		final int clen = m1.clen;
+
+		final DenseBlock da = m1.getDenseBlock();
+		final DenseBlock dc = ret.getDenseBlock();
+		long nnz = 0;
+		final double[] b = m2.getDenseBlockValues(); // always single block
+
+		if(da == null && b == null) { // both empty
+			double val = op.fn.execute(0, 0);
+			dc.set(rl, ru, 0, clen, val);
+			nnz += (val != 0) ? (long) (ru - rl) * clen : 0;
+		}
+		else if(da == null) // left empty
+		{
+			// compute first row
+			double[] c = dc.values(rl);
+			for(int j = 0; j < clen; j++) {
+				double val = op.fn.execute(0, b[j]);
+				nnz += ((c[j] = val) != 0) ? (ru - rl) : 0;
+			}
+			// copy first to all other rows
+			for(int i = rl + 1; i < ru; i++)
+				dc.set(i, c);
+		}
+		else // default case (incl right empty) // TODO:SIMD div
+		{
+			DoubleVector aVec, bVec, res;
+
+			for(int i = rl; i < ru; i++) {
+				double[] a = da.values(i);
+				double[] c = dc.values(i);
+				int ix = da.pos(i);
+
+				int bn = clen % speciesLen;
+
+				int j = 0;
+				// Rest
+				for(; j < bn; j++) {
+					double val = op.fn.execute(a[ix + j], ((b != null) ? b[j] : 0));
+					nnz += ((c[ix + j] = val) != 0) ? 1 : 0;
+				}
+
+				// Vectorized iteration
+				for(; j < clen; j += speciesLen) {
+					aVec = DoubleVector.fromArray(SPECIES, a, ix + j);
+					bVec = DoubleVector.fromArray(SPECIES, b, j);
+					res = aVec.div(bVec);
+					res.intoArray(c, ix + j);
+					nnz += res.compare(VectorOperators.NE, 0).trueCount();
+				}
+			}
+		}
+		return nnz;
+	}
+
+	private static void safeBinaryMVSparseDenseRow(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, BinaryOperator op) {
+		boolean isMultiply = (op.fn instanceof Multiply);
+		boolean skipEmpty = (isMultiply);
+		int rlen = m1.rlen;
+		int clen = m1.clen;
+		SparseBlock a = m1.sparseBlock;
+		double[] b = m2.getDenseBlockValues();
+		DenseBlock c = ret.allocateDenseBlock().getDenseBlock();
+
+		// early abort on skip and empty
+		if(skipEmpty && (m1.isEmptyBlock(false) || m2.isEmptyBlock(false)))
+			return; // skip entire empty block
+
+		// prepare op(0, m2) vector once for all rows
+		double[] tmp = new double[clen];
+		if(!skipEmpty) {
+			for(int i = 0; i < clen; i++)
+				tmp[i] = op.fn.execute(0, b[i]);
+		}
+
+		long nnz = 0;
+		for(int i = 0; i < rlen; i++) {
+			if(skipEmpty && (a == null || a.isEmpty(i)))
+				continue; // skip empty rows
+
+			// set prepared empty row vector into output
+			double[] cvals = c.values(i);
+			int cpos = c.pos(i);
+			System.arraycopy(tmp, 0, cvals, cpos, clen);
+
+			// overwrite row cells with existing sparse lhs values
+			if(a != null && !a.isEmpty(i)) {
+				int apos = a.pos(i);
+				int alen = a.size(i);
+				int[] aix = a.indexes(i);
+				double[] avals = a.values(i);
+				for(int j = apos; j < apos + alen; j++)
+					cvals[cpos + aix[j]] = op.fn.execute(avals[j], b[aix[j]]);
+			}
+
+			// compute row nnz with temporal locality
+			nnz += UtilFunctions.computeNnz(cvals, cpos, clen);
+		}
+		ret.nonZeros = nnz;
+	}
+
+	private static void safeBinaryMVSparse(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, BinaryOperator op) {
+		boolean isMultiply = (op.fn instanceof Multiply);
+		boolean skipEmpty = (isMultiply || isSparseSafeDivide(op, m2));
+		BinaryAccessType atype = getBinaryAccessType(m1, m2);
+
+		// early abort on skip and empty
+		if(skipEmpty && (m1.isEmptyBlock(false) || m2.isEmptyBlock(false)))
+			return; // skip entire empty block
+
+		// allocate once in order to prevent repeated reallocation
+		if(ret.sparse)
+			ret.allocateSparseRowsBlock();
+
+		if(atype == BinaryAccessType.MATRIX_COL_VECTOR)
+			safeBinaryMVSparseColVector(m1, m2, ret, op);
+		else if(atype == BinaryAccessType.MATRIX_ROW_VECTOR)
+			safeBinaryMVSparseRowVector(m1, m2, ret, op);
+	}
+
+	private static void safeBinaryMVSparseColVector(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret,
+		BinaryOperator op) {
+		boolean isMultiply = (op.fn instanceof Multiply);
+		boolean skipEmpty = (isMultiply || isSparseSafeDivide(op, m2));
+
+		int rlen = m1.rlen;
+		int clen = m1.clen;
+		SparseBlock a = m1.sparseBlock;
+		DoubleVector aVec, bVec, res;
+		for(int i = 0; i < rlen; i++) {
+			double v2 = m2.get(i, 0);
+			bVec = DoubleVector.broadcast(SPECIES, v2);
+
+			if((skipEmpty && (a == null || a.isEmpty(i) || v2 == 0)) || ((a == null || a.isEmpty(i)) && v2 == 0)) {
+				continue; // skip empty rows
+			}
+
+			if(isMultiply && v2 == 1) { // ROW COPY
+				if(a != null && !a.isEmpty(i))
+					ret.appendRow(i, a.get(i));
+			}
+			else { // GENERAL CASE // TODO:SIMD div
+				int lastIx = -1;
+				if(a != null && !a.isEmpty(i)) {
+					int apos = a.pos(i);
+					int alen = a.size(i);
+					int[] aix = a.indexes(i);
+					double[] avals = a.values(i);
+
+					int len = apos + alen;
+
+					int j = apos;
+					int bn = len % speciesLen;
+					for(; j < apos + bn; j++) {
+						// empty left
+						fillZeroValues(op, v2, ret, skipEmpty, i, lastIx + 1, aix[j]);
+						// actual value
+						double v = op.fn.execute(avals[j], v2);
+						ret.appendValue(i, aix[j], v);
+						lastIx = aix[j];
+					}
+
+					for(; j < apos + len; j += speciesLen) {
+						fillZeroValues(op, v2, ret, skipEmpty, i, lastIx + 1, aix[j]);
+
+						aVec = DoubleVector.fromArray(SPECIES, avals, j);
+						res = aVec.div(bVec);
+
+						for(int k = 0; k < speciesLen; k++) {
+							ret.appendValue(i, aix[j + k], res.lane(k));
+						}
+						lastIx = aix[j];
+					}
+				}
+				// empty left
+				fillZeroValues(op, v2, ret, skipEmpty, i, lastIx + 1, clen);
+			}
+		}
+	}
+
+	// TODO:SIMD div
+	private static void safeBinaryMVSparseRowVector(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret,
+		BinaryOperator op) {
+		boolean isMultiply = (op.fn instanceof Multiply);
+		boolean skipEmpty = (isMultiply || isSparseSafeDivide(op, m2));
+
+		int rlen = m1.rlen;
+		int clen = m1.clen;
+		SparseBlock a = m1.sparseBlock;
+		DenseBlock b = m2.denseBlock;
+		double[] bvals = b.values(0);
+		DoubleVector aVec, bVec, res;
+
+		for(int i = 0; i < rlen; i++) {
+			if(skipEmpty && (a == null || a.isEmpty(i)))
+				continue; // skip empty rows
+			if(skipEmpty && ret.sparse)
+				ret.sparseBlock.allocate(i, a.size(i));
+			int lastIx = -1;
+			if(a != null && !a.isEmpty(i)) {
+				int apos = a.pos(i);
+				int alen = a.size(i);
+				int[] aix = a.indexes(i);
+				double[] avals = a.values(i);
+
+				int j = apos;
+				int bn = alen % speciesLen;
+				for(; j < apos + bn; j++) {
+					// empty left
+					fillZeroValues(op, m2, ret, skipEmpty, i, lastIx + 1, aix[j]);
+					// actual value
+					double v2 = m2.get(0, aix[j]);
+					double v = op.fn.execute(avals[j], v2);
+					ret.appendValue(i, aix[j], v);
+					lastIx = aix[j];
+				}
+
+				for(; j < apos + alen; j += speciesLen) {
+					// empty left
+					fillZeroValues(op, m2, ret, skipEmpty, i, lastIx + 1, aix[j]);
+
+					aVec = DoubleVector.fromArray(SPECIES, avals, j);
+					bVec = DoubleVector.fromArray(SPECIES, bvals, 0, aix, j);
+					res = aVec.div(bVec);
+
+					for(int k = 0; k < speciesLen; k++) {
+						ret.appendValue(i, aix[j + k], res.lane(k));
+					}
+
+					lastIx = aix[j];
+				}
+			}
+			// empty left
+			fillZeroValues(op, m2, ret, skipEmpty, i, lastIx + 1, clen);
+		}
+	}
+
+	private static final void fillZeroValues(BinaryOperator op, double v2, MatrixBlock ret, boolean skipEmpty, int rpos,
+		int cpos, int len) {
+		if(skipEmpty)
+			return;
+
+		final double v = op.fn.execute(0, v2);
+		if(v != 0) {
+			for(int k = cpos; k < len; k++)
+				// TODO change this to not do append but directly allocate the filled sparse row.
+				ret.appendValue(rpos, k, v);
+		}
+	}
+
+	private static void fillZeroValues(BinaryOperator op, MatrixBlock m2, MatrixBlock ret, boolean skipEmpty, int rpos,
+		int cpos, int len) {
+		if(skipEmpty)
+			return;
+		else if(m2.isEmpty())
+			fillZeroValuesEmpty(op, m2, ret, skipEmpty, rpos, cpos, len);
+		else if(m2.isInSparseFormat())
+			fillZeroValuesSparse(op, m2, ret, skipEmpty, rpos, cpos, len);
+		else
+			fillZeroValuesDense(op, m2, ret, skipEmpty, rpos, cpos, len);
+	}
+
+	private static void fillZeroValuesEmpty(BinaryOperator op, MatrixBlock m2, MatrixBlock ret, boolean skipEmpty,
+		int rpos, int cpos, int len) {
+		final double zero = op.fn.execute(0.0, 0.0);
+		final boolean zeroIsZero = zero == 0.0;
+		if(!zeroIsZero) {
+			while(cpos < len)
+				// TODO change this to a fill operation.
+				ret.appendValue(rpos, cpos++, zero);
+		}
+	}
+
+	private static void fillZeroValuesDense(BinaryOperator op, MatrixBlock m2, MatrixBlock ret, boolean skipEmpty,
+		int rpos, int cpos, int len) {
+		final DenseBlock db = m2.getDenseBlock();
+		final double[] vals = db.values(0);
+		final SparseBlock r = ret.getSparseBlock();
+		if(ret.isInSparseFormat() && r instanceof SparseBlockMCSR) {
+			SparseBlockMCSR mCSR = (SparseBlockMCSR) r;
+			mCSR.allocate(rpos, cpos, len);
+			SparseRow sr = mCSR.get(rpos);
+			for(int k = cpos; k < len; k++) {
+				sr.append(k, op.fn.execute(0, vals[k]));
+			}
+		}
+		else {
+			// def
+			for(int k = cpos; k < len; k++) {
+				ret.appendValue(rpos, k, op.fn.execute(0, vals[k]));
+			}
+		}
+	}
+
+	private static void fillZeroValuesSparse(BinaryOperator op, MatrixBlock m2, MatrixBlock ret, boolean skipEmpty,
+		int rpos, int cpos, int len) {
+
+		final double zero = op.fn.execute(0.0, 0.0);
+		final boolean zeroIsZero = zero == 0.0;
+		final SparseBlock sb = m2.getSparseBlock();
+		if(sb.isEmpty(0)) {
+			if(!zeroIsZero) {
+				while(cpos < len)
+					ret.appendValue(rpos, cpos++, zero);
+			}
+		}
+		else {
+			int apos = sb.pos(0);
+			final int alen = sb.size(0) + apos;
+			final int[] aix = sb.indexes(0);
+			final double[] vals = sb.values(0);
+			// skip aix pos until inside range of cpos and len
+			while(apos < alen && aix[apos] < len && cpos > aix[apos]) {
+				apos++;
+			}
+			// for each point in the sparse range
+			for(; apos < alen && aix[apos] < len; apos++) {
+				if(!zeroIsZero) {
+					while(cpos < len && cpos < aix[apos]) {
+						ret.appendValue(rpos, cpos++, zero);
+					}
+				}
+				cpos = aix[apos];
+				final double v = op.fn.execute(0, vals[apos]);
+				ret.appendValue(rpos, aix[apos], v);
+				// cpos++;
+			}
+			// process tail.
+			if(!zeroIsZero) {
+				while(cpos < len) {
+					ret.appendValue(rpos, cpos++, zero);
+				}
+			}
+		}
+	}
+
+	private static void safeBinaryMVDenseSparseMult(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret,
+		BinaryOperator op) {
+		if(m1.isEmptyBlock(false) || m2.isEmptyBlock(false))
+			return;
+		int rlen = m1.rlen;
+		int clen = m1.clen;
+		BinaryAccessType atype = getBinaryAccessType(m1, m2);
+		double[] a = m1.getDenseBlockValues();
+		double[] b = m2.getDenseBlockValues();
+
+		// note: invocation condition ensures max int nnz
+		if(atype == BinaryAccessType.MATRIX_COL_VECTOR) {
+			// count output nnz (for CSR preallocation)
+			int nnz = 0;
+			for(int i = 0, aix = 0; i < rlen; i++, aix += clen)
+				nnz += (b[i] != 0) ? UtilFunctions.countNonZeros(a, aix, clen) : 0;
+			// allocate and compute output in CSR format
+			int[] rptr = new int[rlen + 1];
+			int[] indexes = new int[nnz];
+			double[] vals = new double[nnz];
+			rptr[0] = 0;
+			for(int i = 0, aix = 0, pos = 0; i < rlen; i++, aix += clen) {
+				double bval = b[i];
+				if(bval != 0) {
+					for(int j = 0; j < clen; j++) {
+						double aval = a[aix + j];
+						if(aval == 0)
+							continue;
+						indexes[pos] = j;
+						vals[pos] = aval * bval;
+						pos++;
+					}
+				}
+				rptr[i + 1] = pos;
+			}
+			ret.sparseBlock = new SparseBlockCSR(rptr, indexes, vals, nnz);
+			ret.setNonZeros(nnz);
+		}
+	}
+
+	private static void safeBinaryMVGeneric(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, BinaryOperator op) {
+		boolean isMultiply = (op.fn instanceof Multiply);
+		boolean skipEmpty = (isMultiply);
+		int rlen = m1.rlen;
+		int clen = m1.clen;
+		BinaryAccessType atype = getBinaryAccessType(m1, m2);
+
+		// early abort on skip and empty
+		if(skipEmpty && (m1.isEmptyBlock(false) || m2.isEmptyBlock(false)))
+			return; // skip entire empty block
+
+		// allocate once in order to prevent repeated reallocation
+		if(ret.sparse)
+			ret.allocateSparseRowsBlock();
+
+		if(atype == BinaryAccessType.MATRIX_COL_VECTOR) {
+			for(int i = 0; i < rlen; i++) {
+				// replicate vector value
+				double v2 = m2.get(i, 0);
+				if(skipEmpty && v2 == 0) // skip zero rows
+					continue;
+
+				if(isMultiply && v2 == 1) // ROW COPY
+				{
+					for(int j = 0; j < clen; j++) {
+						double v1 = m1.get(i, j);
+						ret.appendValue(i, j, v1);
+					}
+				}
+				else // GENERAL CASE
+				{
+					for(int j = 0; j < clen; j++) {
+						double v1 = m1.get(i, j);
+						double v = op.fn.execute(v1, v2);
+						ret.appendValue(i, j, v);
+					}
+				}
+			}
+		}
+		else if(atype == BinaryAccessType.MATRIX_ROW_VECTOR) {
+			// if the right hand side row vector is sparse we have to exploit that;
+			// otherwise, both sparse access (binary search) and asymtotic behavior
+			// in the number of cells become major bottlenecks
+			if(m2.sparse && ret.sparse && isMultiply) // SPARSE *
+			{
+				// note: sparse block guaranteed to be allocated (otherwise early about)
+				SparseBlock b = m2.sparseBlock;
+				SparseBlock c = ret.sparseBlock;
+				if(b.isEmpty(0))
+					return;
+				int blen = b.size(0); // always pos 0
+				int[] bix = b.indexes(0);
+				double[] bvals = b.values(0);
+				for(int i = 0; i < rlen; i++) {
+					c.allocate(i, blen);
+					for(int j = 0; j < blen; j++)
+						c.append(i, bix[j], m1.get(i, bix[j]) * bvals[j]);
+				}
+				ret.setNonZeros(c.size());
+			}
+			else // GENERAL CASE
+			{
+				for(int i = 0; i < rlen; i++)
+					for(int j = 0; j < clen; j++) {
+						double v1 = m1.get(i, j);
+						double v2 = m2.get(0, j); // replicated vector value
+						double v = op.fn.execute(v1, v2);
+						ret.appendValue(i, j, v);
+					}
+			}
+		}
+
+		// no need to recomputeNonZeros since maintained in append value
+	}
+
+	private static void safeBinaryVVGeneric(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, BinaryOperator op) {
+		int rlen = m1.rlen;
+		int clen = m2.clen;
+
+		// allocate once in order to prevent repeated reallocation
+		if(ret.sparse)
+			ret.allocateSparseRowsBlock();
+
+		if(LibMatrixOuterAgg.isCompareOperator(op) && m2.getNumColumns() > 16 && SortUtils.isSorted(m2)) {
+			performBinOuterOperation(m1, m2, ret, op);
+		}
+		else {
+			for(int r = 0; r < rlen; r++) {
+				double v1 = m1.get(r, 0);
+				for(int c = 0; c < clen; c++) {
+					double v2 = m2.get(0, c);
+					double v = op.fn.execute(v1, v2);
+					ret.appendValue(r, c, v);
+				}
+			}
+		}
+
+		// no need to recomputeNonZeros since maintained in append value
+	}
+
+	private static long safeBinaryMMSparseSparse(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, BinaryOperator op,
+		int rl, int ru) {
+		// guard for postponed allocation in single-threaded exec
+		if(ret.sparse && !ret.isAllocated())
+			ret.allocateSparseRowsBlock();
+
+		// both sparse blocks existing
+		long lnnz = 0;
+		if(m1.sparseBlock != null && m2.sparseBlock != null) {
+			SparseBlock lsblock = m1.sparseBlock;
+			SparseBlock rsblock = m2.sparseBlock;
+
+			if(ret.sparse && lsblock.isAligned(rsblock)) {
+				SparseBlock c = ret.sparseBlock;
+				for(int r = rl; r < ru; r++)
+					if(!lsblock.isEmpty(r)) {
+						int alen = lsblock.size(r);
+						int apos = lsblock.pos(r);
+						int[] aix = lsblock.indexes(r);
+						double[] avals = lsblock.values(r);
+						double[] bvals = rsblock.values(r);
+						c.allocate(r, alen);
+						for(int j = apos; j < apos + alen; j++) {
+							double tmp = op.fn.execute(avals[j], bvals[j]);
+							c.append(r, aix[j], tmp);
+						}
+						lnnz += c.size(r);
+					}
+			}
+			else // general case
+			{
+				for(int r = rl; r < ru; r++) {
+					if(!lsblock.isEmpty(r) && !rsblock.isEmpty(r)) {
+						mergeForSparseBinary(op, lsblock.values(r), lsblock.indexes(r), lsblock.pos(r), lsblock.size(r),
+							rsblock.values(r), rsblock.indexes(r), rsblock.pos(r), rsblock.size(r), r, ret);
+					}
+					else if(!rsblock.isEmpty(r)) {
+						appendRightForSparseBinary(op, rsblock.values(r), rsblock.indexes(r), rsblock.pos(r),
+							rsblock.size(r), 0, r, ret);
+					}
+					else if(!lsblock.isEmpty(r)) {
+						appendLeftForSparseBinary(op, lsblock.values(r), lsblock.indexes(r), lsblock.pos(r),
+							lsblock.size(r), 0, r, ret);
+					}
+					// do nothing if both not existing
+					lnnz += ret.recomputeNonZeros(r, r);
+				}
+			}
+		}
+		// right sparse block existing
+		else if(m2.sparseBlock != null) {
+			SparseBlock rsblock = m2.sparseBlock;
+			for(int r = rl; r < Math.min(ru, rsblock.numRows()); r++) {
+				if(rsblock.isEmpty(r))
+					continue;
+				appendRightForSparseBinary(op, rsblock.values(r), rsblock.indexes(r), rsblock.pos(r), rsblock.size(r),
+					0, r, ret);
+				lnnz += ret.recomputeNonZeros(r, r);
+			}
+		}
+		// left sparse block existing
+		else {
+			SparseBlock lsblock = m1.sparseBlock;
+			for(int r = rl; r < ru; r++) {
+				if(lsblock.isEmpty(r))
+					continue;
+				appendLeftForSparseBinary(op, lsblock.values(r), lsblock.indexes(r), lsblock.pos(r), lsblock.size(r), 0,
+					r, ret);
+				lnnz += ret.recomputeNonZeros(r, r);
+			}
+		}
+		return lnnz;
+	}
+
+	private static long safeBinaryMMSparseDenseDense(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, BinaryOperator op,
+		int rl, int ru) {
+		// guard for postponed allocation in single-threaded exec
+		if(!ret.isAllocated())
+			ret.allocateDenseBlock();
+
+		// specific case in order to prevent binary search on sparse inputs (see quickget and quickset)
+		final int n = ret.clen;
+		DenseBlock dc = ret.getDenseBlock();
+
+		// 1) process left input: assignment
+		if(m1.sparse && m1.sparseBlock != null) // SPARSE left
+		{
+			SparseBlock a = m1.sparseBlock;
+			for(int i = rl; i < ru; i++) {
+				double[] c = dc.values(i);
+				int cpos = dc.pos(i);
+				if(a.isEmpty(i))
+					continue;
+				int apos = a.pos(i);
+				int alen = a.size(i);
+				int[] aix = a.indexes(i);
+				double[] avals = a.values(i);
+				for(int k = apos; k < apos + alen; k++)
+					c[cpos + aix[k]] = avals[k];
+			}
+		}
+		else if(!m1.sparse) // DENSE left
+		{
+			if(!m1.isEmptyBlock(false)) {
+				int rlbix = dc.index(rl);
+				int rubix = dc.index(ru - 1);
+				DenseBlock da = m1.getDenseBlock();
+				if(rlbix == rubix)
+					System.arraycopy(da.valuesAt(rlbix), da.pos(rl), dc.valuesAt(rlbix), dc.pos(rl), (ru - rl) * n);
+				else {
+					for(int i = rl; i < ru; i++)
+						System.arraycopy(da.values(i), da.pos(i), dc.values(i), dc.pos(i), n);
+				}
+			}
+			else
+				dc.set(0);
+		}
+
+		// 2) process right input: op.fn (+,-,*), * only if dense
+		long lnnz = 0;
+		if(m2.sparse && m2.sparseBlock != null) // SPARSE right
+		{
+			SparseBlock a = m2.sparseBlock;
+			for(int i = rl; i < ru; i++) {
+				double[] c = dc.values(i);
+				int cpos = dc.pos(i);
+				if(!a.isEmpty(i)) {
+					int apos = a.pos(i);
+					int alen = a.size(i);
+					int[] aix = a.indexes(i);
+					double[] avals = a.values(i);
+					for(int k = apos; k < apos + alen; k++)
+						c[cpos + aix[k]] = op.fn.execute(c[cpos + aix[k]], avals[k]);
+				}
+				// exploit temporal locality of rows
+				lnnz += ret.recomputeNonZeros(i, i);
+			}
+		}
+		else if(!m2.sparse) // DENSE right
+		{
+			if(!m2.isEmptyBlock(false)) {
+				DenseBlock da = m2.getDenseBlock();
+				for(int i = rl; i < ru; i++) {
+					double[] a = da.values(i);
+					double[] c = dc.values(i);
+					int apos = da.pos(i);
+					for(int j = apos; j < apos + n; j++) {
+						c[j] = op.fn.execute(c[j], a[j]);
+						lnnz += (c[j] != 0) ? 1 : 0;
+					}
+				}
+			}
+			else if(op.fn instanceof Multiply)
+				ret.denseBlock.set(0);
+			else
+				lnnz = m1.nonZeros;
+		}
+
+		// 3) recompute nnz
+		return lnnz;
+	}
+
+	private static long safeBinaryMMDenseDenseDense(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, BinaryOperator op,
+		int rl, int ru) {
+		final int clen = m1.clen;
+		final boolean isPM = (op.fn instanceof PlusMultiply || op.fn instanceof MinusMultiply);
+
+		// guard for postponed allocation in single-threaded exec
+		if(!ret.isAllocated())
+			ret.allocateDenseBlock();
+
+		final DenseBlock da = m1.getDenseBlock();
+		final DenseBlock db = m2.getDenseBlock();
+		final DenseBlock dc = ret.getDenseBlock();
+
+		if(isPM && clen >= 64)
+			return safeBinaryMMDenseDenseDensePM_Vec(da, db, dc, op, rl, ru, clen);
+		else if(da.isContiguous() && db.isContiguous() && dc.isContiguous()) {
+			if(op.fn instanceof PlusMultiply)
+				return safeBinaryMMDenseDenseDensePM(da, db, dc, op, rl, ru, clen);
+			else
+				return safeBinaryMMDenseDenseDenseContiguous(da, db, dc, op, rl, ru, clen);
+		}
+		else
+			return safeBinaryMMDenseDenseDenseGeneric(da, db, dc, op, rl, ru, clen);
+	}
+
+	private static final long safeBinaryMMDenseDenseDensePM_Vec(DenseBlock da, DenseBlock db, DenseBlock dc,
+		BinaryOperator op, int rl, int ru, int clen) {
+		final double cntPM = (op.fn instanceof PlusMultiply ? ((PlusMultiply) op.fn).getConstant() : -1d *
+			((MinusMultiply) op.fn).getConstant());
+		long lnnz = 0;
+		for(int i = rl; i < ru; i++) {
+			final double[] a = da.values(i);
+			final double[] b = db.values(i);
+			final double[] c = dc.values(i);
+			int pos = da.pos(i);
+			System.arraycopy(a, pos, c, pos, clen);
+			LibMatrixMultSIMD.vectMultiplyAdd(cntPM, b, c, pos, pos, clen);
+			lnnz += UtilFunctions.computeNnz(c, pos, clen);
+		}
+		return lnnz;
+	}
+
+	private static final long safeBinaryMMDenseDenseDensePM(DenseBlock da, DenseBlock db, DenseBlock dc,
+		BinaryOperator op, int rl, int ru, int clen) {
+		long lnnz = 0;
+		final double[] a = da.values(0);
+		final double[] b = db.values(0);
+		final double[] c = dc.values(0);
+		final double d = ((PlusMultiply) op.fn).getConstant();
+		for(int i = da.pos(rl); i < da.pos(ru); i++) {
+			c[i] = a[i] + d * b[i];
+			lnnz += (c[i] != 0) ? 1 : 0;
+		}
+		return lnnz;
+	}
+
+	// TODO:SIMD div
+	private static final long safeBinaryMMDenseDenseDenseContiguous(DenseBlock da, DenseBlock db, DenseBlock dc, BinaryOperator op,
+		int rl, int ru, int clen) {
+		long lnnz = 0;
+		final double[] a = da.values(0);
+		final double[] b = db.values(0);
+		final double[] c = dc.values(0);
+
+		DoubleVector aVec, bVec, cVec, res;
+
+		int bn = (da.pos(ru) - da.pos(rl)) % speciesLen;
+		int i = da.pos(rl);
+		// Rest
+		for(; i < da.pos(rl) + bn; i++) {
+			c[i] += op.fn.execute(a[i], b[i]);
+			lnnz += (c[i] != 0) ? 1 : 0;
+		}
+
+		// Vectorized iteration
+		for(; i < da.pos(ru); i += speciesLen) {
+			aVec = DoubleVector.fromArray(SPECIES, a, i);
+			bVec = DoubleVector.fromArray(SPECIES, b, i);
+			cVec = DoubleVector.fromArray(SPECIES, c, i);
+
+			res = aVec.div(bVec);
+			res = res.add(cVec); // TODO: is that even needed?
+			res.intoArray(c, i);
+
+			lnnz += res.compare(VectorOperators.NE, 0).trueCount(); // Count lnnz
+		}
+
+		return lnnz;
+	}
+
+	private static final long safeBinaryMMDenseDenseDenseGeneric(DenseBlock da, DenseBlock db, DenseBlock dc,
+		BinaryOperator op, int rl, int ru, int clen) {
+		final ValueFunction fn = op.fn;
+		long lnnz = 0;
+		for(int i = rl; i < ru; i++) {
+			final double[] a = da.values(i);
+			final double[] b = db.values(i);
+			final double[] c = dc.values(i);
+			int pos = da.pos(i);
+			for(int j = pos; j < pos + clen; j++) {
+				c[j] = fn.execute(a[j], b[j]);
+				lnnz += (c[j] != 0) ? 1 : 0;
+			}
+		}
+		return lnnz;
+	}
+
+	// TODO:SIMD div
+	private static long safeBinaryMMSparseDenseSkip(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, BinaryOperator op,
+		int rl, int ru) {
+		SparseBlock a = m1.sparse ? m1.sparseBlock : m2.sparseBlock;
+		if(a == null)
+			return 0;
+
+		// prepare second input and allocate output
+		MatrixBlock b = m1.sparse ? m2 : m1;
+		double[] bvals = b.getDenseBlockValues();
+
+		int bCols = m2.getNumColumns();
+		DoubleVector aVec, bVec, res;
+		VectorMask<Double> mask;
+
+		// guard for postponed allocation in single-threaded exec
+		if(!ret.isAllocated())
+			ret.allocateBlock();
+
+		double val;
+		long lnnz = 0;
+		for(int i = rl; i < Math.min(ru, a.numRows()); i++) {
+			if(a.isEmpty(i))
+				continue;
+			int apos = a.pos(i);
+			int alen = a.size(i);
+			int[] aix = a.indexes(i);
+			double[] avals = a.values(i);
+
+			if(ret.sparse && !b.sparse)
+				ret.sparseBlock.allocate(i, alen);
+
+			int k = apos;
+			int bn = alen % speciesLen;
+			for(; k < apos + bn; k++) {
+				double in2 = b.get(i, aix[k]);
+				if(in2 == 0)
+					continue;
+				val = op.fn.execute(avals[k], in2);
+				lnnz += (val != 0) ? 1 : 0;
+				ret.appendValuePlain(i, aix[k], val);
+			}
+
+			for (; k < apos + alen; k += speciesLen) {
+				aVec = DoubleVector.fromArray(SPECIES, avals, k);
+				bVec = DoubleVector.fromArray(SPECIES, bvals, i*bCols, aix, k);
+				mask = bVec.compare(VectorOperators.NE, 0.0);
+
+				res = aVec.div(bVec, mask);
+
+				// Store result when vector mask applies: value != 0
+				for(int l = 0; l < speciesLen; l++) {
+					if(mask.laneIsSet(l))
+						ret.appendValuePlain(i, aix[k + l], res.lane(l));
+				}
+				lnnz += mask.trueCount();
+			}
+		}
+		return lnnz;
+	}
+
+	private static long safeBinaryMMGeneric(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, BinaryOperator op, int rl,
+		int ru) {
+		int clen = m2.clen;
+		long lnnz = 0;
+		for(int r = rl; r < ru; r++)
+			for(int c = 0; c < clen; c++) {
+				double in1 = m1.get(r, c);
+				double in2 = m2.get(r, c);
+				if(in1 == 0 && in2 == 0)
+					continue;
+				double val = op.fn.execute(in1, in2);
+				lnnz += (val != 0) ? 1 : 0;
+				ret.appendValuePlain(r, c, val);
+			}
+		return lnnz;
+	}
+
+	/**
+	 * 
+	 * This will do cell wise operation for &lt;, &lt;=, &gt;, &gt;=, == and != operators.
+	 * 
+	 * @param m1  left matrix
+	 * @param m2  right matrix
+	 * @param ret output matrix
+	 * @param bOp binary operator
+	 * 
+	 */
+	private static long performBinOuterOperation(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, BinaryOperator bOp) {
+		int rlen = m1.rlen;
+		int clen = ret.clen;
+		double b[] = DataConverter.convertToDoubleVector(m2);
+		if(!ret.isAllocated())
+			ret.allocateDenseBlock();
+		DenseBlock dc = ret.getDenseBlock();
+
+		// pre-materialize various types used in inner loop
+		boolean scanType1 = (bOp.fn instanceof LessThan || bOp.fn instanceof Equals || bOp.fn instanceof NotEquals ||
+			bOp.fn instanceof GreaterThanEquals);
+		boolean scanType2 = (bOp.fn instanceof LessThanEquals || bOp.fn instanceof Equals ||
+			bOp.fn instanceof NotEquals || bOp.fn instanceof GreaterThan);
+		boolean lt = (bOp.fn instanceof LessThan), lte = (bOp.fn instanceof LessThanEquals);
+		boolean gt = (bOp.fn instanceof GreaterThan), gte = (bOp.fn instanceof GreaterThanEquals);
+		boolean eqNeq = (bOp.fn instanceof Equals || bOp.fn instanceof NotEquals);
+
+		long lnnz = 0;
+		for(int bi = 0; bi < dc.numBlocks(); bi++) {
+			double[] c = dc.valuesAt(bi);
+			for(int r = bi * dc.blockSize(), off = 0; r < rlen; r++, off += clen) {
+				double value = m1.get(r, 0);
+				int ixPos1 = Arrays.binarySearch(b, value);
+				int ixPos2 = ixPos1;
+				if(ixPos1 >= 0) { // match, scan to next val
+					if(scanType1)
+						while(ixPos1 < b.length && value == b[ixPos1])
+							ixPos1++;
+					if(scanType2)
+						while(ixPos2 > 0 && value == b[ixPos2 - 1])
+							--ixPos2;
+				}
+				else
+					ixPos2 = ixPos1 = Math.abs(ixPos1) - 1;
+				int start = lt ? ixPos1 : (lte || eqNeq) ? ixPos2 : 0;
+				int end = gt ? ixPos2 : (gte || eqNeq) ? ixPos1 : clen;
+
+				if(bOp.fn instanceof NotEquals) {
+					Arrays.fill(c, off, off + start, 1.0);
+					Arrays.fill(c, off + end, off + clen, 1.0);
+					lnnz += (start + (clen - end));
+				}
+				else if(start < end) {
+					Arrays.fill(c, off + start, off + end, 1.0);
+					lnnz += (end - start);
+				}
+			}
+		}
+		ret.setNonZeros(lnnz);
+		ret.examSparsity();
+		return lnnz;
+	}
+
+	private static long unsafeBinary(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, BinaryOperator op, int rl,
+		int ru) {
+		int clen = m1.clen;
+		BinaryAccessType atype = getBinaryAccessType(m1, m2);
+
+		long lnnz = 0;
+		if(atype == BinaryAccessType.MATRIX_COL_VECTOR) { // MATRIX - COL_VECTOR
+			for(int r = rl; r < ru; r++) {
+				double v2 = m2.get(r, 0);
+				for(int c = 0; c < clen; c++) {
+					double v1 = m1.get(r, c);
+					double v = op.fn.execute(v1, v2);
+					ret.appendValuePlain(r, c, v);
+					lnnz += (v != 0) ? 1 : 0;
+				}
+			}
+		}
+		else if(atype == BinaryAccessType.MATRIX_ROW_VECTOR) { // MATRIX - ROW_VECTOR
+			for(int r = rl; r < ru; r++)
+				for(int c = 0; c < clen; c++) {
+					double v1 = m1.get(r, c);
+					double v2 = m2.get(0, c);
+					double v = op.fn.execute(v1, v2);
+					ret.appendValuePlain(r, c, v);
+					lnnz += (v != 0) ? 1 : 0;
+				}
+		}
+		else if(atype == BinaryAccessType.OUTER_VECTOR_VECTOR) { // VECTOR - VECTOR
+			int clen2 = m2.clen;
+			if(LibMatrixOuterAgg.isCompareOperator(op) && m2.getNumColumns() > 16 && SortUtils.isSorted(m2)) {
+				lnnz = performBinOuterOperation(m1, m2, ret, op);
+			}
+			else {
+				for(int r = rl; r < ru; r++) {
+					double v1 = m1.get(r, 0);
+					for(int c = 0; c < clen2; c++) {
+						double v2 = m2.get(0, c);
+						double v = op.fn.execute(v1, v2);
+						lnnz += (v != 0) ? 1 : 0;
+						ret.appendValuePlain(r, c, v);
+					}
+				}
+			}
+		}
+		else // MATRIX - MATRIX
+		{
+			// dense non-empty vectors (always single block)
+			if(m1.clen == 1 && !m1.sparse && !m1.isEmptyBlock(false) && !m2.sparse && !m2.isEmptyBlock(false)) {
+				ret.allocateDenseBlock();
+				double[] a = m1.getDenseBlockValues();
+				double[] b = m2.getDenseBlockValues();
+				double[] c = ret.getDenseBlockValues();
+				for(int i = rl; i < ru; i++) {
+					c[i] = op.fn.execute(a[i], b[i]);
+					lnnz += (c[i] != 0) ? 1 : 0;
+				}
+			}
+			// general case
+			else {
+				for(int r = rl; r < ru; r++)
+					for(int c = 0; c < clen; c++) {
+						double v1 = m1.get(r, c);
+						double v2 = m2.get(r, c);
+						double v = op.fn.execute(v1, v2);
+						ret.appendValuePlain(r, c, v);
+						lnnz += (v != 0) ? 1 : 0;
+					}
+			}
+		}
+
+		return lnnz;
+	}
+
+	private static long safeBinaryScalar(MatrixBlock m1, MatrixBlock ret, ScalarOperator op, int rl, int ru) {
+		// early abort possible since sparsesafe
+		if(m1.isEmptyBlock(false)) {
+			return 0;
+		}
+
+		// sanity check input/output sparsity
+		if(m1.sparse != ret.sparse)
+			throw new DMLRuntimeException(
+				"Unsupported safe binary scalar operations over different input/output representation: " + m1.sparse
+					+ " " + ret.sparse);
+
+		boolean copyOnes = (op.fn instanceof NotEquals && op.getConstant() == 0);
+		boolean allocExact = (op.fn instanceof Multiply || op.fn instanceof Multiply2 || op.fn instanceof Power2 ||
+			Builtin.isBuiltinCode(op.fn, BuiltinCode.MAX) || Builtin.isBuiltinCode(op.fn, BuiltinCode.MIN));
+		long lnnz = 0;
+
+		if(m1.sparse) // SPARSE <- SPARSE
+		{
+			// allocate sparse row structure
+			ret.allocateSparseRowsBlock();
+			SparseBlock a = m1.sparseBlock;
+			SparseBlock c = ret.sparseBlock;
+
+			double exponent = op.getConstant();
+			double val;
+
+			long nnz = 0;
+			for(int r = rl; r < ru; r++) {
+				if(a.isEmpty(r))
+					continue;
+
+				int apos = a.pos(r);
+				int alen = a.size(r);
+				int[] aix = a.indexes(r);
+				double[] avals = a.values(r);
+
+				if(copyOnes) { // SPECIAL CASE: e.g., (X != 0)
+					// create sparse row without repeated resizing
+					SparseRowVector crow = new SparseRowVector(alen);
+					crow.setSize(alen);
+
+					// memcopy/memset of indexes/values (sparseblock guarantees absence of 0s)
+					System.arraycopy(aix, apos, crow.indexes(), 0, alen);
+					Arrays.fill(crow.values(), 0, alen, 1);
+					c.set(r, crow, false);
+					nnz += alen;
+				}
+				else { // GENERAL CASE // TODO:SIMD sparse power
+						// create sparse row without repeated resizing for specific ops
+					if(allocExact)
+						c.allocate(r, alen);
+
+					DoubleVector aVec, res;
+					int bn = alen % speciesLen;
+
+					// Rest
+					int j = apos;
+					for(; j < apos + bn; j++) {
+						val = op.executeScalar(avals[j]);
+						c.append(r, aix[j], val);
+						nnz += (val != 0) ? 1 : 0;
+					}
+
+					for(; j < apos + alen; j += speciesLen) {
+						aVec = DoubleVector.fromArray(SPECIES, avals, j);
+						res = aVec.lanewise(VectorOperators.POW, exponent);
+						for(int i = 0; i < speciesLen; i++) {
+							c.append(r, aix[j + i], res.lane(i));
+							//nnz += (val != 0) ? 1 : 0;
+						}
+						nnz += res.compare(VectorOperators.NE, 0).trueCount();
+					}
+				}
+			}
+			lnnz = (ret.nonZeros = nnz);
+		}
+		else { // DENSE <- DENSE
+			lnnz = denseBinaryScalar(m1, ret, op, rl, ru);
+		}
+
+		return lnnz;
+	}
+
+	/**
+	 * Since this operation is sparse-unsafe, ret should always be passed in dense representation.
+	 * 
+	 * @param m1  input matrix
+	 * @param ret result matrix
+	 * @param op  scalar operator
+	 */
+	private static long unsafeBinaryScalar(MatrixBlock m1, MatrixBlock ret, ScalarOperator op) {
+		// early abort possible since sparsesafe
+		if(m1.isEmptyBlock(false)) {
+			// compute 0 op constant once and set into dense output
+			double val = op.executeScalar(0);
+			if(val != 0)
+				ret.reset(ret.rlen, ret.clen, val);
+			return (val != 0) ? ret.getLength() : 0;
+		}
+
+		// sanity check input/output sparsity
+		if(ret.sparse)
+			throw new DMLRuntimeException(
+				"Unsupported unsafe binary scalar operations over sparse output representation.");
+
+		int m = m1.rlen;
+		int n = m1.clen;
+		long lnnz = 0;
+
+		if(m1.sparse) // SPARSE MATRIX
+		{
+			ret.allocateDenseBlock();
+
+			SparseBlock a = m1.sparseBlock;
+			DenseBlock dc = ret.getDenseBlock();
+
+			// init dense result with unsafe 0-value
+			double val0 = op.executeScalar(0);
+			boolean lsparseSafe = (val0 == 0);
+			if(!lsparseSafe)
+				dc.set(val0);
+
+			// compute non-zero input values
+			long nnz = lsparseSafe ? 0 : m * n;
+			for(int bi = 0; bi < dc.numBlocks(); bi++) {
+				int blen = dc.blockSize(bi);
+				double[] c = dc.valuesAt(bi);
+				for(int i = bi * dc.blockSize(), cix = i * n; i < blen && i < m; i++, cix += n) {
+					if(a.isEmpty(i))
+						continue;
+					int apos = a.pos(i);
+					int alen = a.size(i);
+					int[] aix = a.indexes(i);
+					double[] avals = a.values(i);
+					for(int j = apos; j < apos + alen; j++) {
+						double val = op.executeScalar(avals[j]);
+						c[cix + aix[j]] = val;
+						nnz += lsparseSafe ? (val != 0 ? 1 : 0) : (val == 0 ? -1 : 0);
+					}
+				}
+			}
+			lnnz = (ret.nonZeros = nnz);
+		}
+		else { // DENSE MATRIX
+			lnnz = denseBinaryScalar(m1, ret, op, 0, m);
+		}
+
+		return lnnz;
+	}
+
+	// TODO:SIMD dense power
+	private static long denseBinaryScalar(MatrixBlock m1, MatrixBlock ret, ScalarOperator op, int rl, int ru) {
+		// allocate dense block (if necessary), incl clear nnz
+		ret.allocateDenseBlock(true);
+
+		DenseBlock da = m1.getDenseBlock();
+		DenseBlock dc = ret.getDenseBlock();
+		int clen = m1.clen;
+
+		int i = rl;
+		int bn;
+		double exponent = op.getConstant();
+
+		DoubleVector aVec, res;
+
+		// compute scalar operation, incl nnz maintenance
+		long nnz = 0;
+		if(clen == 1) { // COL VECTOR
+			double[] a = da.valuesAt(0);
+			double[] c = dc.valuesAt(0);
+			bn = (ru - rl) % speciesLen;
+
+			// Rest
+			for(; i < rl + bn; i++) { // VECTOR
+				c[i] = op.executeScalar(a[i]);
+				nnz += (c[i] != 0) ? 1 : 0;
+			}
+
+			// Vectorized iteration
+			for(; i < ru; i += speciesLen) {
+				aVec = DoubleVector.fromArray(SPECIES, a, i);
+				res = aVec.lanewise(VectorOperators.POW, exponent);
+				res.intoArray(c, i);
+				nnz += res.compare(VectorOperators.NE, 0).trueCount();
+			}
+		}
+		else { // MULTI-COL MATRIX
+			bn = clen % speciesLen;
+
+			for(; i < ru; i++) {
+				double[] a = da.values(i);
+				double[] c = dc.values(i);
+				int apos = da.pos(i), cpos = dc.pos(i);
+
+				int j = 0;
+				for(; j < bn; j++) {
+					c[cpos + j] = op.executeScalar(a[apos + j]);
+					nnz += (c[cpos + j] != 0) ? 1 : 0;
+				}
+
+				for(; j < clen; j += speciesLen) {
+					aVec = DoubleVector.fromArray(SPECIES, a, apos + j);
+					res = aVec.lanewise(VectorOperators.POW, exponent);
+					res.intoArray(c, cpos + j);
+					nnz += res.compare(VectorOperators.NE, 0).trueCount();
+				}
+			}
+		}
+		return ret.nonZeros = nnz;
+	}
+
+	private static void safeBinaryInPlace(MatrixBlock m1ret, MatrixBlock m2, BinaryOperator op) {
+		// early abort on skip and empty
+		final boolean PoM = op.fn instanceof Plus || op.fn instanceof Minus;
+		if((m1ret.isEmpty() && m2.isEmpty()) || (PoM && m2.isEmpty())) {
+			final boolean isEquals = op.fn instanceof Equals || op.fn instanceof LessThanEquals ||
+				op.fn instanceof GreaterThanEquals;
+
+			if(isEquals)
+				m1ret.reset(m1ret.rlen, m1ret.clen, 1);
+			return; // skip entire empty block
+		}
+		else if(m2.isEmpty() && // empty other side
+			(op.fn instanceof Multiply || (op.fn instanceof And))) {
+			m1ret.reset(m1ret.rlen, m1ret.clen, 0);
+			return;
+		}
+
+		if(m1ret.getNumRows() > 1 && m2.getNumRows() == 1)
+			safeBinaryInPlaceMatrixRowVector(m1ret, m2, op);
+		else
+			safeBinaryInPlaceMatrixMatrix(m1ret, m2, op);
+	}
+
+	private static void safeBinaryInPlaceMatrixRowVector(MatrixBlock m1ret, MatrixBlock m2, BinaryOperator op) {
+		if(m1ret.sparse) {
+			if(m2.isInSparseFormat() && !op.isRowSafeLeft(m2))
+				throw new DMLRuntimeException("Invalid row safety of inplace row operation: " + op);
+			else if(m2.isEmpty())
+				safeBinaryInPlaceSparseConst(m1ret, 0.0, op);
+			else if(m2.sparse)
+				throw new NotImplementedException("Not made sparse vector inplace to sparse " + op);
+			else
+				safeBinaryInPlaceSparseVector(m1ret, m2, op);
+		}
+		else {
+			if(!m1ret.isAllocated()) {
+				LOG.warn("Allocating inplace output block");
+				m1ret.allocateBlock();
+			}
+
+			if(m2.isEmpty())
+				safeBinaryInPlaceDenseConst(m1ret, 0.0, op);
+			else if(m2.sparse)
+				throw new NotImplementedException("Not made sparse vector inplace to dense " + op);
+			else
+				safeBinaryInPlaceDenseVector(m1ret, m2, op);
+		}
+	}
+
+	private static void safeBinaryInPlaceMatrixMatrix(MatrixBlock m1ret, MatrixBlock m2, BinaryOperator op) {
+		if(op.fn instanceof Plus && m1ret.isEmpty() && !m1ret.isAllocated())
+			m1ret.copy(m2);
+		else if(m1ret.sparse && m2.sparse)
+			safeBinaryInPlaceSparse(m1ret, m2, op);
+		else if(!m1ret.sparse && !m2.sparse)
+			safeBinaryInPlaceDense(m1ret, m2, op);
+		else if(m2.sparse && (op.fn instanceof Plus || op.fn instanceof Minus))
+			safeBinaryInPlaceDenseSparseAdd(m1ret, m2, op);
+		else
+			safeBinaryInPlaceGeneric(m1ret, m2, op);
+	}
+
+	private static void safeBinaryInPlaceSparse(MatrixBlock m1ret, MatrixBlock m2, BinaryOperator op) {
+		// allocation and preparation (note: for correctness and performance, this
+		// implementation requires the lhs in MCSR and hence we explicitly convert)
+		if(m1ret.sparseBlock != null)
+			m1ret.allocateSparseRowsBlock(false);
+		if(!(m1ret.sparseBlock instanceof SparseBlockMCSR))
+			m1ret.sparseBlock = SparseBlockFactory.copySparseBlock(SparseBlock.Type.MCSR, m1ret.sparseBlock, false);
+		if(m2.sparseBlock != null)
+			m2.allocateSparseRowsBlock(false);
+		SparseBlock c = m1ret.sparseBlock;
+		SparseBlock b = m2.sparseBlock;
+		final int rlen = m1ret.rlen;
+		final int clen = m1ret.clen;
+
+		final boolean compact = (op.fn instanceof Multiply || op.fn instanceof And);
+		final boolean mcsr = c instanceof SparseBlockMCSR;
+
+		if(c != null && b != null) {
+			for(int r = 0; r < rlen; r++) {
+				if(c.isEmpty(r) && b.isEmpty(r))
+					continue;
+				if(b.isEmpty(r)) {
+					zeroRightForSparseBinary(op, r, m1ret);
+				}
+				else if(c.isEmpty(r)) {
+					appendRightForSparseBinary(op, b.values(r), b.indexes(r), b.pos(r), b.size(r), r, m1ret);
+				}
+				else {
+					// this approach w/ single copy only works with the MCSR format
+					int estimateSize = Math.min(clen,
+						(!c.isEmpty(r) ? c.size(r) : 0) + (!b.isEmpty(r) ? b.size(r) : 0));
+					SparseRow old = c.get(r);
+					c.set(r, new SparseRowVector(estimateSize), false);
+					m1ret.nonZeros -= old.size();
+					mergeForSparseBinary(op, old.values(), old.indexes(), 0, old.size(), b.values(r), b.indexes(r),
+						b.pos(r), b.size(r), r, m1ret);
+				}
+				if(compact && mcsr && !c.isEmpty(r))
+					c.get(r).compact();
+			}
+		}
+		else if(c == null) { // lhs empty
+			m1ret.sparseBlock = SparseBlockFactory.createSparseBlock(rlen);
+			for(int r = 0; r < rlen; r++) {
+				if(b.isEmpty(r))
+					continue;
+				appendRightForSparseBinary(op, b.values(r), b.indexes(r), b.pos(r), b.size(r), r, m1ret);
+			}
+		}
+		else { // rhs empty
+			for(int r = 0; r < rlen; r++) {
+				if(c.isEmpty(r))
+					continue;
+				zeroRightForSparseBinary(op, r, m1ret);
+			}
+
+		}
+
+		m1ret.recomputeNonZeros();
+	}
+
+	private static void safeBinaryInPlaceSparseConst(MatrixBlock m1ret, double m2, BinaryOperator op) {
+		if(m1ret.isEmpty()) // early termination... it is empty and safe... just stop.
+			return;
+		final SparseBlock sb = m1ret.getSparseBlock();
+		final int rlen = m1ret.rlen;
+		for(int r = 0; r < rlen; r++) {
+			if(sb.isEmpty(r))
+				continue;
+			final int apos = sb.pos(r);
+			final int alen = sb.size(r) + apos;
+			final double[] avals = sb.values(r);
+			for(int k = apos; k < alen; k++)
+				avals[k] = op.fn.execute(avals[k], m2);
+		}
+	}
+
+	private static void safeBinaryInPlaceSparseVector(MatrixBlock m1ret, MatrixBlock m2, BinaryOperator op) {
+
+		if(m1ret.isEmpty()) // early termination... it is empty and safe... just stop.
+			return;
+		final SparseBlock sb = m1ret.getSparseBlock();
+		final double[] b = m2.getDenseBlockValues();
+		final int rlen = m1ret.rlen;
+
+		final boolean compact = (op.fn instanceof Multiply || op.fn instanceof And) //
+			&& op.isIntroducingZerosRight(m2);
+		final boolean mcsr = sb instanceof SparseBlockMCSR;
+		for(int r = 0; r < rlen; r++) {
+			if(sb.isEmpty(r))
+				continue;
+			final int apos = sb.pos(r);
+			final int alen = sb.size(r) + apos;
+			final double[] avals = sb.values(r);
+			final int[] aix = sb.indexes(r);
+			for(int k = apos; k < alen; k++)
+				avals[k] = op.fn.execute(avals[k], b[aix[k]]);
+
+			if(compact && mcsr) {
+				SparseRow sr = sb.get(r);
+				if(sr instanceof SparseRowVector)
+					((SparseRowVector) sr).setSize(avals.length);
+				sr.compact();
+			}
+		}
+		if(compact && !mcsr) {
+			((SparseBlockCSR) sb).compact();
+		}
+	}
+
+	private static void safeBinaryInPlaceDense(MatrixBlock m1ret, MatrixBlock m2, BinaryOperator op) {
+		// prepare outputs
+		if(!m1ret.isAllocated()) // allocate
+			m1ret.allocateDenseBlock();
+
+		if(m2.isEmptyBlock(false))
+			safeBinaryInPlaceDenseEmpty(m1ret, op);
+		else if(op.fn instanceof Plus)
+			safeBinaryInPlaceDensePlus(m1ret, m2, op);
+		else
+			safeBinaryInPlaceDenseGeneric(m1ret, m2, op);
+	}
+
+	private static void safeBinaryInPlaceDenseEmpty(MatrixBlock m1ret, BinaryOperator op) {
+		DenseBlock a = m1ret.getDenseBlock();
+		final int rlen = m1ret.rlen;
+		final int clen = m1ret.clen;
+		long lnnz = 0;
+		for(int r = 0; r < rlen; r++) {
+			double[] avals = a.values(r);
+			for(int c = 0, ix = a.pos(r); c < clen; c++, ix++) {
+				double tmp = op.fn.execute(avals[ix], 0);
+				lnnz += (avals[ix] = tmp) != 0 ? 1 : 0;
+			}
+		}
+		m1ret.setNonZeros(lnnz);
+	}
+
+	private static void safeBinaryInPlaceDensePlus(MatrixBlock m1ret, MatrixBlock m2, BinaryOperator op) {
+		DenseBlock a = m1ret.getDenseBlock();
+		DenseBlock b = m2.getDenseBlock();
+		final int rlen = m1ret.rlen;
+		final int clen = m1ret.clen;
+		long lnnz = 0;
+		if(a.isContiguous() && b.isContiguous()) {
+			final double[] avals = a.values(0);
+			final double[] bvals = b.values(0);
+			for(int i = 0; i < avals.length; i++)
+				lnnz += (avals[i] += bvals[i]) == 0 ? 0 : 1;
+		}
+		else {
+			for(int r = 0; r < rlen; r++) {
+				final int aix = a.pos(r), bix = b.pos(r);
+				final double[] avals = a.values(r), bvals = b.values(r);
+				LibMatrixMultSIMD.vectAdd(bvals, avals, bix, aix, clen);
+				lnnz += UtilFunctions.computeNnz(avals, aix, clen);
+			}
+		}
+		m1ret.setNonZeros(lnnz);
+	}
+
+	private static void safeBinaryInPlaceDenseGeneric(MatrixBlock m1ret, MatrixBlock m2, BinaryOperator op) {
+		DenseBlock a = m1ret.getDenseBlock();
+		DenseBlock b = m2.getDenseBlock();
+		final int rlen = m1ret.rlen;
+		final int clen = m1ret.clen;
+		long lnnz = 0;
+		for(int r = 0; r < rlen; r++) {
+			double[] avals = a.values(r), bvals = b.values(r);
+			for(int c = 0, ix = a.pos(r); c < clen; c++, ix++) {
+				double tmp = op.fn.execute(avals[ix], bvals[ix]);
+				lnnz += (avals[ix] = tmp) != 0 ? 1 : 0;
+			}
+		}
+		m1ret.setNonZeros(lnnz);
+	}
+
+	private static void safeBinaryInPlaceDenseConst(MatrixBlock m1ret, double m2, BinaryOperator op) {
+		// prepare outputs
+		m1ret.allocateDenseBlock();
+		DenseBlock a = m1ret.getDenseBlock();
+		final int rlen = m1ret.rlen;
+		final int clen = m1ret.clen;
+
+		long lnnz = 0;
+		for(int r = 0; r < rlen; r++) {
+			double[] avals = a.values(r);
+			for(int c = 0, ix = a.pos(r); c < clen; c++, ix++) {
+				double tmp = op.fn.execute(avals[ix], m2);
+				lnnz += (avals[ix] = tmp) != 0 ? 1 : 0;
+			}
+		}
+
+		m1ret.setNonZeros(lnnz);
+	}
+
+	private static void safeBinaryInPlaceDenseVector(MatrixBlock m1ret, MatrixBlock m2, BinaryOperator op) {
+		// prepare outputs
+		m1ret.allocateDenseBlock();
+		DenseBlock a = m1ret.getDenseBlock();
+		double[] b = m2.getDenseBlockValues();
+		final int rlen = m1ret.rlen;
+		final int clen = m1ret.clen;
+
+		long lnnz = 0;
+		for(int r = 0; r < rlen; r++) {
+			double[] avals = a.values(r);
+			for(int c = 0, ix = a.pos(r); c < clen; c++, ix++) {
+				double tmp = op.fn.execute(avals[ix], b[ix % clen]);
+				lnnz += (avals[ix] = tmp) != 0 ? 1 : 0;
+			}
+		}
+		m1ret.setNonZeros(lnnz);
+	}
+
+	private static void safeBinaryInPlaceDenseSparseAdd(MatrixBlock m1ret, MatrixBlock m2, BinaryOperator op) {
+		final int rlen = m1ret.rlen;
+		DenseBlock a = m1ret.denseBlock;
+		SparseBlock b = m2.sparseBlock;
+		long nnz = m1ret.getNonZeros();
+		for(int r = 0; r < rlen; r++) {
+			if(b.isEmpty(r))
+				continue;
+			int apos = a.pos(r), bpos = b.pos(r);
+			int blen = b.size(r);
+			int[] bix = b.indexes(r);
+			double[] avals = a.values(r), bvals = b.values(r);
+			for(int k = bpos; k < bpos + blen; k++) {
+				double vold = avals[apos + bix[k]];
+				double vnew = op.fn.execute(vold, bvals[k]);
+				nnz += (vold == 0 && vnew != 0) ? 1 : (vold != 0 && vnew == 0) ? -1 : 0;
+				avals[apos + bix[k]] = vnew;
+			}
+		}
+		m1ret.setNonZeros(nnz);
+	}
+
+	private static void safeBinaryInPlaceGeneric(MatrixBlock m1ret, MatrixBlock m2, BinaryOperator op) {
+		final int rlen = m1ret.rlen;
+		final int clen = m1ret.clen;
+		for(int r = 0; r < rlen; r++)
+			for(int c = 0; c < clen; c++) {
+				double thisvalue = m1ret.get(r, c);
+				double thatvalue = m2.get(r, c);
+				double resultvalue = op.fn.execute(thisvalue, thatvalue);
+				m1ret.set(r, c, resultvalue);
+			}
+	}
+
+	private static void unsafeBinaryInPlace(MatrixBlock m1ret, MatrixBlock m2, BinaryOperator op) {
+		int rlen = m1ret.rlen;
+		int clen = m1ret.clen;
+		BinaryAccessType atype = getBinaryAccessType(m1ret, m2);
+
+		if(atype == BinaryAccessType.MATRIX_COL_VECTOR) // MATRIX - COL_VECTOR
+		{
+			for(int r = 0; r < rlen; r++) {
+				// replicated value
+				double v2 = m2.get(r, 0);
+				for(int c = 0; c < clen; c++) {
+					double v1 = m1ret.get(r, c);
+					double v = op.fn.execute(v1, v2);
+					m1ret.set(r, c, v);
+				}
+			}
+		}
+		else if(atype == BinaryAccessType.MATRIX_ROW_VECTOR) // MATRIX - ROW_VECTOR
+		{
+			for(int r = 0; r < rlen; r++)
+				for(int c = 0; c < clen; c++) {
+					double v1 = m1ret.get(r, c);
+					double v2 = m2.get(0, c); // replicated value
+					double v = op.fn.execute(v1, v2);
+					m1ret.set(r, c, v);
+				}
+		}
+		else // MATRIX - MATRIX
+		{
+			for(int r = 0; r < rlen; r++)
+				for(int c = 0; c < clen; c++) {
+					double v1 = m1ret.get(r, c);
+					double v2 = m2.get(r, c);
+					double v = op.fn.execute(v1, v2);
+					m1ret.set(r, c, v);
+				}
+		}
+	}
+
+	private static void mergeForSparseBinary(BinaryOperator op, double[] values1, int[] cols1, int pos1, int size1,
+		double[] values2, int[] cols2, int pos2, int size2, int resultRow, MatrixBlock result) {
+		int p1 = 0, p2 = 0;
+		if(op.fn instanceof Multiply) { // skip empty
+			// skip empty: merge-join (with inner join semantics)
+			// similar to sorted list intersection
+			if(result.getSparseBlock() == null)
+				result.allocateSparseRowsBlock();
+			SparseBlock sblock = result.getSparseBlock();
+			sblock.allocate(resultRow, Math.min(size1, size2), result.clen);
+			while(p1 < size1 && p2 < size2) {
+				int colPos1 = cols1[pos1 + p1];
+				int colPos2 = cols2[pos2 + p2];
+				if(colPos1 == colPos2)
+					sblock.append(resultRow, colPos1, op.fn.execute(values1[pos1 + p1], values2[pos2 + p2]));
+				p1 += (colPos1 <= colPos2) ? 1 : 0;
+				p2 += (colPos1 >= colPos2) ? 1 : 0;
+			}
+			result.nonZeros += sblock.size(resultRow);
+		}
+		else {
+			// general case: merge-join (with outer join semantics)
+			while(p1 < size1 && p2 < size2) {
+				if(cols1[pos1 + p1] < cols2[pos2 + p2]) {
+					result.appendValue(resultRow, cols1[pos1 + p1], op.fn.execute(values1[pos1 + p1], 0));
+					p1++;
+				}
+				else if(cols1[pos1 + p1] == cols2[pos2 + p2]) {
+					result.appendValue(resultRow, cols1[pos1 + p1],
+						op.fn.execute(values1[pos1 + p1], values2[pos2 + p2]));
+					p1++;
+					p2++;
+				}
+				else {
+					result.appendValue(resultRow, cols2[pos2 + p2], op.fn.execute(0, values2[pos2 + p2]));
+					p2++;
+				}
+			}
+			// add left over
+			appendLeftForSparseBinary(op, values1, cols1, pos1, size1, p1, resultRow, result);
+			appendRightForSparseBinary(op, values2, cols2, pos2, size2, p2, resultRow, result);
+		}
+	}
+
+	private static void appendLeftForSparseBinary(BinaryOperator op, double[] values1, int[] cols1, int pos1, int size1,
+		int pos, int resultRow, MatrixBlock result) {
+		for(int j = pos1 + pos; j < pos1 + size1; j++) {
+			double v = op.fn.execute(values1[j], 0);
+			result.appendValue(resultRow, cols1[j], v);
+		}
+	}
+
+	private static void appendRightForSparseBinary(BinaryOperator op, double[] vals, int[] ix, int pos, int size, int r,
+		MatrixBlock ret) {
+		appendRightForSparseBinary(op, vals, ix, pos, size, 0, r, ret);
+	}
+
+	private static void appendRightForSparseBinary(BinaryOperator op, double[] values2, int[] cols2, int pos2,
+		int size2, int pos, int r, MatrixBlock result) {
+		for(int j = pos2 + pos; j < pos2 + size2; j++) {
+			double v = op.fn.execute(0, values2[j]);
+			result.appendValue(r, cols2[j], v);
+		}
+	}
+
+	private static void zeroRightForSparseBinary(BinaryOperator op, int r, MatrixBlock ret) {
+		if(op.fn instanceof Plus || op.fn instanceof Minus)
+			return;
+		SparseBlock c = ret.sparseBlock;
+		int apos = c.pos(r);
+		int alen = c.size(r);
+		double[] values = c.values(r);
+		boolean zero = false;
+		for(int i = apos; i < apos + alen; i++)
+			zero |= ((values[i] = op.fn.execute(values[i], 0)) == 0);
+		if(zero)
+			c.compact(r);
+	}
+
+	private static class BincellTask implements Callable<Long> {
+		private final MatrixBlock _m1;
+		private final MatrixBlock _m2;
+		private final MatrixBlock _ret;
+		private final BinaryOperator _bop;
+		BinaryAccessType _atype;
+		private final int _rl;
+		private final int _ru;
+
+		protected BincellTask(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, BinaryOperator bop,
+			BinaryAccessType atype, int rl, int ru) {
+			_m1 = m1;
+			_m2 = m2;
+			_ret = ret;
+			_bop = bop;
+			_atype = atype;
+			_rl = rl;
+			_ru = ru;
+		}
+
+		@Override
+		public Long call() {
+			// execute binary operation on row partition
+			// (including nnz maintenance)
+			if(_bop.sparseSafe || isSparseSafeDivide(_bop, _m2))
+				return safeBinary(_m1, _m2, _ret, _bop, _atype, _rl, _ru);
+			else
+				return unsafeBinary(_m1, _m2, _ret, _bop, _rl, _ru);
+		}
+	}
+
+	private static class BincellScalarTask implements Callable<Long> {
+		private final MatrixBlock _m1;
+		private final MatrixBlock _ret;
+		private final ScalarOperator _sop;
+		private final int _rl;
+		private final int _ru;
+
+		protected BincellScalarTask(MatrixBlock m1, MatrixBlock ret, ScalarOperator sop, int rl, int ru) {
+			_m1 = m1;
+			_ret = ret;
+			_sop = sop;
+			_rl = rl;
+			_ru = ru;
+		}
+
+		@Override
+		public Long call() {
+			// execute binary operation on row partition
+			return safeBinaryScalar(_m1, _ret, _sop, _rl, _ru);
+		}
+	}
+
+	private static class UncellTask implements Callable<Long> {
+		private final DenseBlock _a;
+		private final DenseBlock _c;
+		private final UnaryOperator _op;
+		private final int _rl;
+		private final int _ru;
+
+		protected UncellTask(DenseBlock a, DenseBlock c, UnaryOperator op, int rl, int ru) {
+			_a = a;
+			_c = c;
+			_op = op;
+			_rl = rl;
+			_ru = ru;
+		}
+
+		@Override
+		public Long call() {
+			long nnz = 0;
+
+			DoubleVector aVec, res;
+			// fast dense-dense operations
+			if(_a.isContiguous(_rl, _ru)) { // TODO:SIMD dense exp multithreaded
+				double[] avals = _a.values(_rl);
+				double[] cvals = _c.values(_rl);
+				int start = _a.pos(_rl), end = _a.pos(_ru);
+
+				int bn = (end - start) % speciesLen;
+
+				int i = start;
+				for(; i < start + bn; i++) {
+					cvals[i] = _op.fn.execute(avals[i]);
+					nnz += (cvals[i] != 0) ? 1 : 0;
+				}
+
+				for(; i < end; i += speciesLen) {
+					aVec = DoubleVector.fromArray(SPECIES, avals, i);
+					res = aVec.lanewise(VectorOperators.EXP);
+					res.intoArray(cvals, i);
+					nnz += res.compare(VectorOperators.NE, 0).trueCount();
+				}
+			}
+			// generic dense-dense, including large blocks
+			else {
+				int clen = _a.getDim(1);
+				for(int i=_rl; i<_ru; i++) {
+					double[] avals = _a.values(i);
+					double[] cvals = _c.values(i);
+					int pos = _a.pos(i);
+					for( int j=0; j<clen; j++ ) {
+						cvals[pos+j] = _op.fn.execute(avals[pos+j]);
+						nnz += (cvals[pos+j] != 0) ? 1 : 0;
+					}
+				}
+			}
+			return nnz;
+		}
+	}
+}
diff --git a/scripts/staging/SIMD-FFM-prototypes/LibMatrixMultFFM.java b/scripts/staging/SIMD-FFM-prototypes/LibMatrixMultFFM.java
new file mode 100644
index 0000000000..bcaabe4a17
--- /dev/null
+++ b/scripts/staging/SIMD-FFM-prototypes/LibMatrixMultFFM.java
@@ -0,0 +1,5144 @@
+/*
+ * 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.sysds.runtime.matrix.data;
+
+import java.lang.foreign.MemorySegment;
+import java.lang.foreign.ValueLayout;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.*;
+import java.util.stream.IntStream;
+
+import org.apache.commons.lang3.NotImplementedException;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.commons.math3.util.FastMath;
+import org.apache.sysds.hops.OptimizerUtils;
+import org.apache.sysds.lops.MapMultChain.ChainType;
+import org.apache.sysds.lops.WeightedCrossEntropy.WCeMMType;
+import org.apache.sysds.lops.WeightedDivMM.WDivMMType;
+import org.apache.sysds.lops.WeightedSigmoid.WSigmoidType;
+import org.apache.sysds.lops.WeightedSquaredLoss.WeightsType;
+import org.apache.sysds.lops.WeightedUnaryMM.WUMMType;
+import org.apache.sysds.runtime.DMLRuntimeException;
+import org.apache.sysds.runtime.controlprogram.parfor.stat.InfrastructureAnalyzer;
+import org.apache.sysds.runtime.controlprogram.parfor.stat.Timing;
+import org.apache.sysds.runtime.data.*;
+import org.apache.sysds.runtime.data.SparseBlock.Type;
+import org.apache.sysds.runtime.functionobjects.SwapIndex;
+import org.apache.sysds.runtime.functionobjects.ValueFunction;
+import org.apache.sysds.runtime.matrix.operators.ReorgOperator;
+import org.apache.sysds.runtime.util.CommonThreadPool;
+import org.apache.sysds.runtime.util.UtilFunctions;
+import org.apache.sysds.utils.NativeHelper;
+
+/**
+ * MB: Library for matrix multiplications including MM, MV, VV for all
+ * combinations of dense, sparse, ultrasparse representations and special
+ * operations such as transpose-self matrix multiplication.
+ * <p>
+ * In general all implementations use internally dense outputs
+ * for direct access, but change the final result to sparse if necessary.
+ * The only exceptions are ultra-sparse matrix mult, wsloss and wsigmoid.
+ */
+public class LibMatrixMultFFM
+{
+	//internal configuration
+	private static final boolean LOW_LEVEL_OPTIMIZATION = true;
+	private static final long MEM_OVERHEAD_THRESHOLD = 2L*1024*1024; //MAX 2 MB
+	private static final long PAR_MINFLOP_THRESHOLD1 = 2L*1024*1024; //MIN 2 MFLOP
+	private static final long PAR_MINFLOP_THRESHOLD2 = 128L*1024; //MIN 2 MFLOP
+	public static final int L2_CACHESIZE = 256 * 1024; //256KB (common size)
+	public static final int L3_CACHESIZE = 16 * 1024 * 1024; //16MB (common size)
+	private static final Log LOG = LogFactory.getLog(LibMatrixMultFFM.class.getName());
+	public static MemorySegment segment1, segment2, segmentRet;
+	private static final ValueLayout.OfDouble layout = ValueLayout.JAVA_DOUBLE;
+
+	private LibMatrixMultFFM() {
+		//prevent instantiation via private constructor
+	}
+	
+	////////////////////////////////
+	// public matrix mult interface
+	////////////////////////////////
+	
+	/**
+	 * Performs a matrix multiplication
+	 * 
+	 * All variants use a IKJ access pattern, and internally use dense output. After the
+	 * actual computation, we recompute nnz and check for sparse/dense representation.
+	 * 
+	 * @param m1 first matrix
+	 * @param m2 second matrix
+	 * @return ret Matrix Block
+	 */
+	public static MatrixBlock matrixMult(MatrixBlock m1, MatrixBlock m2) {
+		return matrixMult(m1, m2, null, false, 1);
+	}
+
+	/**
+	 * Performs a matrix multiplication
+	 * 
+	 * All variants use a IKJ access pattern, and internally use dense output. After the
+	 * actual computation, we recompute nnz and check for sparse/dense representation.
+	 * 
+	 * @param m1 first matrix
+	 * @param m2 second matrix
+	 * @param k maximum parallelism
+	 * @return ret Matrix Block
+	 */
+	public static MatrixBlock matrixMult(MatrixBlock m1, MatrixBlock m2, int k) {
+		return matrixMult(m1, m2, null, false, k);
+	}
+
+	/**
+	 * Performs a matrix multiplication and stores the result in the output matrix.
+	 * 
+	 * All variants use a IKJ access pattern, and internally use dense output. After the
+	 * actual computation, we recompute nnz and check for sparse/dense representation.
+	 * 
+	 * @param m1 first matrix
+	 * @param m2 second matrix
+	 * @param ret result matrix
+	 * @return ret Matrix Block
+	 */
+	public static MatrixBlock matrixMult(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret) {
+		return matrixMult(m1, m2, ret, false, 1);
+	}
+	
+	/**
+	 * This method allows one to disabling exam sparsity. This feature is useful if matrixMult is used as an intermediate
+	 * operation (for example: LibMatrixDNN). It makes sense for LibMatrixDNN because the output is internally
+	 * consumed by another dense instruction, which makes repeated conversion to sparse wasteful.
+	 * This should be used in rare cases and if you are unsure,
+	 * use the method 'matrixMult(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret)' instead.
+	 * 
+	 * @param m1 first matrix
+	 * @param m2 second matrix
+	 * @param ret result matrix
+	 * @param fixedRet if true, output representation is fixed and nnzs not recomputed
+	 * @return ret Matrix Block
+	 */
+	public static MatrixBlock matrixMult(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, boolean fixedRet) {
+		return matrixMult(m1, m2, ret, fixedRet, 1);
+	}
+	
+	/**
+	 * Performs a multi-threaded matrix multiplication and stores the result in the output matrix.
+	 * The parameter k (k&gt;=1) determines the max parallelism k' with k'=min(k, vcores, m1.rlen).
+	 * 
+	 * @param m1 first matrix
+	 * @param m2 second matrix
+	 * @param ret result matrix
+	 * @param k maximum parallelism
+	 * @return ret Matrix Block
+	 */
+	public static MatrixBlock matrixMult(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, int k) {
+		return matrixMult(m1, m2, ret, false, k);
+	}
+	
+	/**
+	 * Performs a matrix multiplication and stores the result in the output matrix.
+	 * 
+	 * All variants use a IKJ access pattern, and internally use dense output. After the
+	 * actual computation, we recompute nnz and check for sparse/dense representation.
+	 * 
+	 * This method allows one to disabling exam sparsity. This feature is useful if matrixMult is used as an intermediate
+	 * operation (for example: LibMatrixDNN). It makes sense for LibMatrixDNN because the output is internally
+	 * consumed by another dense instruction, which makes repeated conversion to sparse wasteful.
+	 * This should be used in rare cases and if you are unsure,
+	 * use the method 'matrixMult(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret)' instead.
+	 * 
+	 * The parameter k (k&gt;=1) determines the max parallelism k' with k'=min(k, vcores, m1.rlen).
+	 * 
+	 * @param m1 first matrix
+	 * @param m2 second matrix
+	 * @param ret result matrix
+	 * @param fixedRet if true, output representation is fixed and nnzs not recomputed
+	 * @param k maximum parallelism
+	 * @return ret Matrix Block
+	 */
+	public static MatrixBlock matrixMult(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, boolean fixedRet, int k) {
+		if(m1.isEmptyBlock(false) || m2.isEmptyBlock(false)) 
+			return emptyMatrixMult(m1, m2, ret);
+		
+		Timing time = new Timing(true);
+		
+		// pre analysis
+		boolean m1Perm = m1.isSparsePermutationMatrix();
+		boolean ultraSparse = (fixedRet && ret.sparse) ||
+			(!fixedRet && isUltraSparseMatrixMult(m1, m2, m1Perm));
+		boolean sparse = !fixedRet && !ultraSparse && !m1Perm
+			&& isSparseOutputMatrixMult(m1, m2);
+
+		// allocate output
+		if(ret == null)
+			ret = new MatrixBlock(m1.rlen, m2.clen, ultraSparse | sparse);
+		else 
+			ret.reset(m1.rlen, m2.clen, ultraSparse | sparse);
+
+		if(ret.isInSparseFormat() && ret.getSparseBlock() instanceof SparseBlockMCSR) {
+			// we set the estimated number of non zeros per row to number of columns
+			// to make the allocation of cells more aggressive.
+			((SparseBlockMCSR) ret.getSparseBlock()).setNnzEstimatePerRow(m2.clen, m2.clen);
+		}
+		
+		if(m1.denseBlock instanceof DenseBlockFP64DEDUP){
+			DenseBlockFP64DEDUP tmp = (DenseBlockFP64DEDUP) m1.denseBlock;
+			if(tmp.getNrEmbsPerRow() != 1){
+				//TODO: currently impossible case, since Dedup reshape is not supported yet, once it is, this method needs
+				// to be implemented
+				throw new NotImplementedException("Check TODO");
+			}
+			ret.allocateDenseBlock(true, true);
+			tmp = (DenseBlockFP64DEDUP) ret.denseBlock;
+			tmp.setEmbeddingSize(ret.clen);
+		}
+		else
+			ret.allocateBlock();
+		
+		if(ret.isInSparseFormat() && !( ret.getSparseBlock() instanceof SparseBlockMCSR)){
+			throw new DMLRuntimeException("Matrix Multiplication Sparse output must be MCSR");
+		}
+
+		// Detect if we should transpose skinny right side.
+		boolean tm2 = !fixedRet && checkPrepMatrixMultRightInput(m1,m2);
+		m2 = prepMatrixMultRightInput(m1, m2, tm2);
+
+		// check for multi-threading
+		if (!ret.isThreadSafe() 
+				|| !satisfiesMultiThreadingConstraints(m1, m2, m1.rlen==1, true, 2, k)
+				|| fixedRet) // Fixed ret not supported in multithreaded execution yet
+			k = 1;
+
+		if(k <= 1)
+			singleThreadedMatrixMult(m1, m2, ret, ultraSparse, sparse, tm2, m1Perm, fixedRet);
+		else
+			parallelMatrixMult(m1, m2, ret, k, ultraSparse, sparse, tm2, m1Perm);
+
+		System.out.println("FFM MM "+k+" ("+m1.isInSparseFormat()+","+m1.getNumRows()+","+m1.getNumColumns()+","+m1.getNonZeros()+")x" +
+				"("+m2.isInSparseFormat()+","+m2.getNumRows()+","+m2.getNumColumns()+","+m2.getNonZeros()+") in "+time.stop());
+
+		return ret;
+	}
+
+	private static void singleThreadedMatrixMult(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret,  
+		boolean ultraSparse, boolean sparse, boolean tm2, boolean m1Perm, boolean fixedRet){
+		// prepare row-upper for special cases of vector-matrix
+		final boolean pm2 = !ultraSparse && checkParMatrixMultRightInputRows(m1, m2, Integer.MAX_VALUE);
+		final int ru2 = (pm2) ? m2.rlen : m1.rlen;
+
+		// core matrix mult computation
+		if(ultraSparse && !fixedRet)
+			matrixMultUltraSparse(m1, m2, ret, m1Perm, 0, ru2);
+		else if(!m1.sparse && !m2.sparse) {
+			matrixMultDenseDense(m1, m2, ret, tm2, pm2, 0, ru2, 0, m2.clen);
+		} else if(m1.sparse && m2.sparse)
+			matrixMultSparseSparse(m1, m2, ret, pm2, sparse, 0, ru2);
+		else if(m1.sparse)
+			matrixMultSparseDense(m1, m2, ret, pm2, 0, ru2);
+		else {
+			matrixMultDenseSparse(m1, m2, ret, pm2, 0, ru2);
+		}
+
+		// post-processing: nnz/representation
+		if(!fixedRet) {
+			if(!ret.sparse)
+				ret.recomputeNonZeros();
+			ret.examSparsity();
+		}
+	}
+
+	private static void parallelMatrixMult(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, int k, 
+		boolean ultraSparse, boolean sparse, boolean tm2, boolean m1Perm){
+		// prepare row-upper for special cases of vector-matrix / matrix-matrix
+		boolean pm2r = !ultraSparse && !sparse && checkParMatrixMultRightInputRows(m1, m2, k);
+		boolean pm2c = !ultraSparse && checkParMatrixMultRightInputCols(m1, m2, k, pm2r);
+		int num = pm2r ? m2.rlen : pm2c ? m2.clen : m1.rlen;
+
+		// core multi-threaded matrix mult computation
+		// (currently: always parallelization over number of rows)
+		final ExecutorService pool = CommonThreadPool.get(k);
+		try {
+			ArrayList<MatrixMultTask> tasks = new ArrayList<>();
+			ArrayList<Integer> blklens = UtilFunctions.getBalancedBlockSizesDefault(num, k,
+				(pm2r || pm2c || ret.denseBlock instanceof DenseBlockFP64DEDUP));
+			ConcurrentHashMap<double[], double[]> cache = m1.denseBlock instanceof DenseBlockFP64DEDUP ? new ConcurrentHashMap<>(): null;
+			for(int i = 0, lb = 0; i < blklens.size(); lb += blklens.get(i), i++)
+				tasks.add(new MatrixMultTask(m1, m2, ret, tm2, pm2r, pm2c, m1Perm, sparse, lb, lb + blklens.get(i), cache));
+			// execute tasks
+			
+			// aggregate partial results (nnz, ret for vector/matrix)
+			// reset nonZero before execution.
+			// nonZero count cannot be trusted since it is not atomic
+			// and some of the matrix multiplication kernels call quick set value modifying the count.
+			ret.nonZeros = 0; 
+			long nnzCount = 0;
+			for(Future<Object> task : pool.invokeAll(tasks)) {
+				if(pm2r) // guaranteed single block
+					vectAdd((double[]) task.get(), ret.getDenseBlockValues(), 0, 0, ret.rlen * ret.clen);
+				else // or count non zeros of the block
+					nnzCount += (Long) task.get();
+			}
+			if(pm2r)
+				ret.recomputeNonZeros(k);
+			else // set the non zeros to the counted values.
+				ret.nonZeros = nnzCount;
+
+			// post-processing (nnz maintained in parallel)
+			ret.examSparsity(k);
+		}
+		catch(Exception ex) {
+			throw new DMLRuntimeException(ex);
+		}
+		finally{
+			pool.shutdown();
+		}
+
+	}
+
+	public static MatrixBlock emptyMatrixMult(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret){
+		final int rl = m1.rlen;
+		final int cl = m2.clen;
+
+		if(ret == null)
+			return new MatrixBlock(rl, cl, true);
+		else {
+			ret.reset(rl, cl, true);
+			ret.setNonZeros(0);
+			ret.cleanupBlock(true, true);
+			return ret;
+		}
+	}
+
+	/**
+	 * Performs a matrix multiplication chain operation of type t(X)%*%(X%*%v) or t(X)%*%(w*(X%*%v)).
+	 * 
+	 * All variants use a IKJ access pattern, and internally use dense output. After the
+	 * actual computation, we recompute nnz and check for sparse/dense representation.
+	 * 
+	 * @param mX X matrix
+	 * @param mV v matrix
+	 * @param mW w matrix
+	 * @param ret result matrix
+	 * @param ct chain type
+	 */
+	public static void matrixMultChain(MatrixBlock mX, MatrixBlock mV, MatrixBlock mW, MatrixBlock ret, ChainType ct) {
+		//check inputs / outputs (after that mV and mW guaranteed to be dense)
+		if( mX.isEmptyBlock(false) || (mV.isEmptyBlock(false) && ct!=ChainType.XtXvy)
+			|| (mW !=null && mW.isEmptyBlock(false)) ) {
+			ret.examSparsity(); //turn empty dense into sparse
+			return;
+		}
+
+		//Timing time = new Timing(true);
+		
+		//pre-processing: output allocation
+		ret.sparse = false;
+		ret.allocateDenseBlock();
+		
+		//core matrix mult chain computation
+		if( mX.sparse )
+			matrixMultChainSparse(mX, mV, mW, ret, ct, 0, mX.rlen);
+		else
+			matrixMultChainDense(mX, mV, mW, ret, ct, 0, mX.rlen);
+		
+		//post-processing
+		ret.recomputeNonZeros();
+		ret.examSparsity();
+		
+		//System.out.println("MMChain "+ct.toString()+" ("+mX.isInSparseFormat()+","+mX.getNumRows()+","+mX.getNumColumns()+","+mX.getNonZeros()+")x" +
+		//		             "("+mV.isInSparseFormat()+","+mV.getNumRows()+","+mV.getNumColumns()+","+mV.getNonZeros()+") in "+time.stop());
+	}
+
+	/**
+	 * Performs a parallel matrix multiplication chain operation of type t(X)%*%(X%*%v) or t(X)%*%(w*(X%*%v)).
+	 * The parameter k (k&gt;=1) determines the max parallelism k' with k'=min(k, vcores, m1.rlen).
+	 * 
+	 * NOTE: This multi-threaded mmchain operation has additional memory requirements of k*ncol(X)*8bytes 
+	 * for partial aggregation. Current max memory: 256KB; otherwise redirect to sequential execution.
+	 * 
+	 * @param mX X matrix
+	 * @param mV v matrix
+	 * @param mW w matrix
+	 * @param ret result matrix
+	 * @param ct chain type
+	 * @param k maximum parallelism
+	 */
+	public static void matrixMultChain(MatrixBlock mX, MatrixBlock mV, MatrixBlock mW, MatrixBlock ret, ChainType ct, int k) {
+		//check inputs / outputs (after that mV and mW guaranteed to be dense)
+		if( mX.isEmptyBlock(false) || (mV.isEmptyBlock(false) && ct!=ChainType.XtXvy)
+			|| (mW !=null && mW.isEmptyBlock(false)) ) {
+			ret.examSparsity(); //turn empty dense into sparse
+			return;
+		}
+		
+		//check temporary memory and too small workload for multi-threading
+		if( !satisfiesMultiThreadingConstraints(mX, true, true, mX.sparse?2:4, k) ) { 
+			matrixMultChain(mX, mV, mW, ret, ct);
+			return;
+		}
+		
+		//Timing time = new Timing(true);
+		
+		//pre-processing (no need to check isThreadSafe)
+		ret.sparse = false;
+		ret.allocateDenseBlock();
+		
+		//core matrix mult chain computation
+		//(currently: always parallelization over number of rows)
+		ExecutorService pool = CommonThreadPool.get(k);
+		try {
+			ArrayList<Integer> blklens = UtilFunctions.getBalancedBlockSizesDefault(mX.rlen, k, true);
+			ArrayList<MatrixMultChainTask> tasks = new ArrayList<>();
+			for( int i=0, lb=0; i<blklens.size(); lb+=blklens.get(i), i++ )
+				tasks.add(new MatrixMultChainTask(mX, mV, mW, ct, lb, lb+blklens.get(i)));
+			List<Future<double[]>> taskret = pool.invokeAll(tasks);
+
+			//aggregate partial results and error handling
+			double[][] a = new double[taskret.size()][];
+			for(int i=0; i<taskret.size(); i++)
+				a[i] = taskret.get(i).get();
+			vectAddAll(a, ret.getDenseBlockValues(), 0, 0, mX.clen);
+		}
+		catch(Exception ex) {
+			throw new DMLRuntimeException(ex);
+		}
+		finally{
+			pool.shutdown();
+		}
+		
+		//post-processing
+		ret.recomputeNonZeros();
+		ret.examSparsity();
+		
+		//System.out.println("MMChain "+ct.toString()+" k="+k+" ("+mX.isInSparseFormat()+","+mX.getNumRows()+","+mX.getNumColumns()+","+mX.getNonZeros()+")x" +
+		//		              "("+mV.isInSparseFormat()+","+mV.getNumRows()+","+mV.getNumColumns()+","+mV.getNonZeros()+") in "+time.stop());
+	}
+
+	public static void matrixMultTransposeSelf( MatrixBlock m1, MatrixBlock ret, boolean leftTranspose ) {
+		matrixMultTransposeSelf(m1, ret, leftTranspose, true);
+	}
+
+	public static void matrixMultTransposeSelf(MatrixBlock m1, MatrixBlock ret, boolean leftTranspose, boolean copyToLowerTriangle){
+		//check inputs / outputs
+		if( m1.isEmptyBlock(false) ) {
+			ret.examSparsity(); //turn empty dense into sparse
+			return;
+		}
+		
+		//Timing time = new Timing(true);
+		
+		//pre-processing
+		ret.sparse = isSparseOutputTSMM(m1);
+		ret.allocateBlock();
+		MatrixBlock m1t = isSparseOutputTSMM(m1, true) ?
+			LibMatrixReorg.transpose(m1) : null;
+		
+		//core tsmm operation
+		matrixMultTransposeSelf(m1, m1t, ret, leftTranspose, 0, ret.rlen);
+
+		//post-processing
+		if(copyToLowerTriangle) {
+			long nnz = copyUpperToLowerTriangle(ret);
+			ret.setNonZeros(nnz);
+			ret.examSparsity();
+		}
+		else {
+			ret.recomputeNonZeros();
+		}
+		
+		//System.out.println("TSMM ("+m1.isInSparseFormat()+","+m1.getNumRows()+","+m1.getNumColumns()+","+m1.getNonZeros()+","+leftTranspose+") in "+time.stop());
+	}
+
+	/**
+	 * TSMM with optional transposed left side or not (Transposed self matrix multiplication)
+	 * 
+	 * @param m1            The matrix to do tsmm
+	 * @param ret           The output matrix to allocate the result to
+	 * @param leftTranspose If the left side should be considered transposed
+	 * @param k             the number of threads to use
+	 */
+	public static void matrixMultTransposeSelf(MatrixBlock m1, MatrixBlock ret, boolean leftTranspose, int k) {
+		//check inputs / outputs
+		if( m1.isEmptyBlock(false) ) {
+			ret.examSparsity(); //turn empty dense into sparse
+			return;
+		}
+		
+		//check too small workload and fallback to sequential if necessary
+		if( !satisfiesMultiThreadingConstraintsTSMM(m1, leftTranspose, 1, k) ) {
+			matrixMultTransposeSelf(m1, ret, leftTranspose);
+			return;
+		}
+		
+		//Timing time = new Timing(true);
+		
+		//pre-processing (no need to check isThreadSafe)
+		ret.sparse = isSparseOutputTSMM(m1);
+		ret.allocateBlock();
+		MatrixBlock m1t = isSparseOutputTSMM(m1, true) ?
+			LibMatrixReorg.transpose(m1, k) : null;
+		
+		//core multi-threaded matrix mult computation
+		ExecutorService pool = CommonThreadPool.get(k);
+		try {
+			ArrayList<MatrixMultTransposeTask> tasks = new ArrayList<>();
+			//load balance via #tasks=4k due to triangular shape 
+			int blklen = (int)(Math.ceil((double)ret.rlen / (4 * k)));
+			for(int i = 0; i < ret.rlen; i += blklen)
+				tasks.add(new MatrixMultTransposeTask(m1, m1t, ret, leftTranspose, i, Math.min(i+blklen, ret.rlen)));
+			for( Future<Object> rtask :  pool.invokeAll(tasks) )
+				rtask.get();
+		}
+		catch(Exception ex) {
+			throw new DMLRuntimeException(ex);
+		}
+		finally{
+			pool.shutdown();
+		}
+		
+		//post-processing
+		long nnz = copyUpperToLowerTriangle(ret);
+		ret.setNonZeros(nnz);
+		ret.examSparsity();
+		
+		//System.out.println("TSMM k="+k+" ("+m1.isInSparseFormat()+","+m1.getNumRows()+","+m1.getNumColumns()+","+m1.getNonZeros()+","+leftTranspose+") in "+time.stop());
+	}
+
+	public static void matrixMultPermute( MatrixBlock pm1, MatrixBlock m2, MatrixBlock ret1, MatrixBlock ret2 ) {
+		//check inputs / outputs
+		if( pm1.isEmptyBlock(false) || m2.isEmptyBlock(false) )
+			return;
+
+		//Timing time = new Timing(true);
+
+		//pre-processing
+		ret1.sparse = (m2.sparse || ret1.sparse);
+		if( ret1.sparse )
+			ret1.allocateSparseRowsBlock();
+		else
+			ret1.allocateDenseBlock();
+		
+		//core permutation mm computation
+		if( m2.sparse )
+			matrixMultPermuteSparse(pm1, m2, ret1, ret2, 0, pm1.rlen);
+		else if( ret1.sparse )
+			matrixMultPermuteDenseSparse(pm1, m2, ret1, ret2, 0, pm1.rlen);
+		else
+			matrixMultPermuteDense(pm1, m2, ret1, ret2, 0, pm1.rlen);
+
+		//post-processing
+		ret1.recomputeNonZeros();
+		ret1.examSparsity();
+		if( ret2 != null ) { //optional second output
+			ret2.recomputeNonZeros();
+			ret2.examSparsity();
+		}
+
+		//System.out.println("PMM Seq ("+pm1.isInSparseFormat()+","+pm1.getNumRows()+","+pm1.getNumColumns()+","+pm1.getNonZeros()+")x" +
+		//                  "("+m2.isInSparseFormat()+","+m2.getNumRows()+","+m2.getNumColumns()+","+m2.getNonZeros()+") in "+time.stop());
+	}	
+
+	public static void matrixMultPermute( MatrixBlock pm1, MatrixBlock m2, MatrixBlock ret1, MatrixBlock ret2, int k) {
+		//check inputs / outputs
+		if( pm1.isEmptyBlock(false) || m2.isEmptyBlock(false) )
+			return;
+
+		//check no parallelization benefit (fallback to sequential)
+		if (pm1.rlen == 1) {
+			matrixMultPermute(pm1, m2, ret1, ret2);
+			return;
+		}
+	
+		//Timing time = new Timing(true);
+		
+		//allocate first output block (second allocated if needed)
+		ret1.sparse = false;	  // no need to check isThreadSafe
+		ret1.allocateDenseBlock();
+		
+		ExecutorService pool = CommonThreadPool.get(k);
+		try {
+			ArrayList<MatrixMultPermuteTask> tasks = new ArrayList<>();
+			int blklen = (int)(Math.ceil((double)pm1.rlen/k));
+			for( int i=0; i<k & i*blklen<pm1.rlen; i++ )
+				tasks.add(new MatrixMultPermuteTask(pm1, m2, ret1, ret2, i*blklen, Math.min((i+1)*blklen, pm1.rlen)));
+			for(Future<Object> f : pool.invokeAll(tasks))
+				f.get();
+		} 
+		catch (Exception e) {
+			throw new DMLRuntimeException(e);
+		}
+		finally{
+			pool.shutdown();
+		}
+		
+		//post-processing
+		ret1.recomputeNonZeros(k);
+		ret1.examSparsity();
+		if( ret2 != null ) { //optional second output
+			ret2.recomputeNonZeros(k);
+			ret2.examSparsity();
+		}
+		
+		// System.out.println("PMM Par ("+pm1.isInSparseFormat()+","+pm1.getNumRows()+","+pm1.getNumColumns()+","+pm1.getNonZeros()+")x" +
+		//                   "("+m2.isInSparseFormat()+","+m2.getNumRows()+","+m2.getNumColumns()+","+m2.getNonZeros()+") in "+time.stop());
+	}	
+
+	public static void matrixMultWSLoss(MatrixBlock mX, MatrixBlock mU, MatrixBlock mV, MatrixBlock mW, MatrixBlock ret, WeightsType wt) {
+		//check for empty result
+		if( wt==WeightsType.POST && mW.isEmptyBlock(false) 
+			|| wt==WeightsType.POST_NZ && mX.isEmptyBlock(false) ) {
+			ret.examSparsity(); //turn empty dense into sparse
+			return; 
+		}
+
+		//Timing time = new Timing(true);
+
+		//core weighted square sum mm computation
+		if( !mX.sparse && !mU.sparse && !mV.sparse && (mW==null || !mW.sparse) 
+			&& !mX.isEmptyBlock() && !mU.isEmptyBlock() && !mV.isEmptyBlock() && (mW==null || !mW.isEmptyBlock()))
+			matrixMultWSLossDense(mX, mU, mV, mW, ret, wt, 0, mX.rlen);
+		else if( mX.sparse && !mU.sparse && !mV.sparse && (mW==null || mW.sparse)
+				&& !mX.isEmptyBlock() && !mU.isEmptyBlock() && !mV.isEmptyBlock() && (mW==null || !mW.isEmptyBlock()))
+			matrixMultWSLossSparseDense(mX, mU, mV, mW, ret, wt, 0, mX.rlen);
+		else
+			matrixMultWSLossGeneric(mX, mU, mV, mW, ret, wt, 0, mX.rlen);
+		
+		//add correction for sparse wsloss w/o weight
+		if( mX.sparse && wt==WeightsType.NONE )
+			addMatrixMultWSLossNoWeightCorrection(mU, mV, ret, 1);
+		
+		//System.out.println("MMWSLoss " +wt.toString()+ " ("+mX.isInSparseFormat()+","+mX.getNumRows()+","+mX.getNumColumns()+","+mX.getNonZeros()+")x" +
+		//                  "("+mV.isInSparseFormat()+","+mV.getNumRows()+","+mV.getNumColumns()+","+mV.getNonZeros()+") in "+time.stop());
+	}
+
+	public static void matrixMultWSLoss(MatrixBlock mX, MatrixBlock mU, MatrixBlock mV, MatrixBlock mW, MatrixBlock ret, WeightsType wt, int k) {
+		//check for empty result
+		if( wt==WeightsType.POST && mW.isEmptyBlock(false)
+			|| wt==WeightsType.POST_NZ && mX.isEmptyBlock(false) ) {
+			ret.examSparsity(); //turn empty dense into sparse
+			return;
+		}
+		
+		//check no parallelization benefit (fallback to sequential)
+		//no need to check isThreadSafe (scalar output)
+		if( mX.rlen == 1 ) {
+			matrixMultWSLoss(mX, mU, mV, mW, ret, wt);
+			return;
+		}
+		
+		//Timing time = new Timing(true);
+		
+		ExecutorService pool = CommonThreadPool.get(k);
+		try {
+			ArrayList<MatrixMultWSLossTask> tasks = new ArrayList<>();
+			int blklen = (int)(Math.ceil((double)mX.rlen/k));
+			for( int i=0; i<k & i*blklen<mX.rlen; i++ )
+				tasks.add(new MatrixMultWSLossTask(mX, mU, mV, mW, wt, i*blklen, Math.min((i+1)*blklen, mX.rlen)));
+	
+			sumScalarResults(pool.invokeAll(tasks), ret);
+		} 
+		catch( Exception e ) {
+			throw new DMLRuntimeException(e);
+		}
+		finally{
+			pool.shutdown();
+		}
+
+		//add correction for sparse wsloss w/o weight
+		if( mX.sparse && wt==WeightsType.NONE )
+			addMatrixMultWSLossNoWeightCorrection(mU, mV, ret, k);
+		
+		//System.out.println("MMWSLoss "+wt.toString()+" k="+k+" ("+mX.isInSparseFormat()+","+mX.getNumRows()+","+mX.getNumColumns()+","+mX.getNonZeros()+")x" +
+		//                   "("+mV.isInSparseFormat()+","+mV.getNumRows()+","+mV.getNumColumns()+","+mV.getNonZeros()+") in "+time.stop());
+	}
+
+	public static void matrixMultWSigmoid(MatrixBlock mW, MatrixBlock mU, MatrixBlock mV, MatrixBlock ret, WSigmoidType wt) {
+		//check for empty result
+		if( mW.isEmptyBlock(false) ) {
+			ret.examSparsity(); //turn empty dense into sparse
+			return; 
+		}
+
+		//Timing time = new Timing(true);
+
+		//pre-processing
+		ret.sparse = mW.sparse;
+		ret.allocateBlock();
+		
+		//core weighted square sum mm computation
+		boolean allDense = !mW.sparse && !mU.sparse && !mV.sparse
+			&& !mU.isEmptyBlock() && !mV.isEmptyBlock();
+		if( NativeHelper.isNativeLibraryLoaded() && allDense && (mW.rlen == 1 || mW.clen == 1) 
+			&& !LibMatrixNative.isMatMultMemoryBound(mU.rlen, mU.clen, mV.rlen)
+			&& mW.getDenseBlock().isContiguous() && mU.getDenseBlock().isContiguous() && mV.getDenseBlock().isContiguous() )
+			matrixMultWSigmoidDenseNative(mW, mU, mV, ret, wt);
+		else if( allDense )
+			matrixMultWSigmoidDense(mW, mU, mV, ret, wt, 0, mW.rlen);
+		else if( mW.sparse && !mU.sparse && !mV.sparse && !mU.isEmptyBlock() && !mV.isEmptyBlock())
+			matrixMultWSigmoidSparseDense(mW, mU, mV, ret, wt, 0, mW.rlen);
+		else
+			matrixMultWSigmoidGeneric(mW, mU, mV, ret, wt, 0, mW.rlen);
+		
+		//post-processing
+		ret.recomputeNonZeros();
+		ret.examSparsity();
+		
+		//System.out.println("MMWSig "+wt.toString()+" ("+mW.isInSparseFormat()+","+mW.getNumRows()+","+mW.getNumColumns()+","+mW.getNonZeros()+")x" +
+		//                 "("+mV.isInSparseFormat()+","+mV.getNumRows()+","+mV.getNumColumns()+","+mV.getNonZeros()+") in "+time.stop());
+	}
+
+	public static void matrixMultWSigmoid(MatrixBlock mW, MatrixBlock mU, MatrixBlock mV, MatrixBlock ret, WSigmoidType wt, int k) {
+		//check for empty result
+		if( mW.isEmptyBlock(false) ) {
+			ret.examSparsity(); //turn empty dense into sparse
+			return; 
+		}
+
+		//check no parallelization benefit (fallback to sequential)
+		if (mW.rlen == 1 || !MatrixBlock.isThreadSafe(mW.sparse)) {
+			matrixMultWSigmoid(mW, mU, mV, ret, wt);
+			return;
+		}
+		
+		//Timing time = new Timing(true);
+
+		//pre-processing
+		ret.sparse = mW.sparse;
+		ret.allocateBlock();
+		
+		ExecutorService pool = CommonThreadPool.get(k);
+		try {
+			ArrayList<MatrixMultWSigmoidTask> tasks = new ArrayList<>();
+			int blklen = (int)(Math.ceil((double)mW.rlen/k));
+			for( int i=0; i<k & i*blklen<mW.rlen; i++ )
+				tasks.add(new MatrixMultWSigmoidTask(mW, mU, mV, ret, wt, i*blklen, Math.min((i+1)*blklen, mW.rlen)));
+
+			//aggregate partial nnz and check for errors
+			ret.nonZeros = 0; //reset after execute
+			for( Future<Long> task : pool.invokeAll(tasks) )
+				ret.nonZeros += task.get();
+		} 
+		catch (Exception e) {
+			throw new DMLRuntimeException(e);
+		}
+		finally{
+			pool.shutdown();
+		}
+
+		//post-processing (nnz maintained in parallel)
+		ret.examSparsity();
+
+		//System.out.println("MMWSig "+wt.toString()+" k="+k+" ("+mW.isInSparseFormat()+","+mW.getNumRows()+","+mW.getNumColumns()+","+mW.getNonZeros()+")x" +
+		//                   "("+mV.isInSparseFormat()+","+mV.getNumRows()+","+mV.getNumColumns()+","+mV.getNonZeros()+") in "+time.stop() + ".");
+	}
+	
+	/**
+	 * NOTE: This operation has limited NaN support, which is acceptable because all our sparse-safe operations
+	 * have only limited NaN support. If this is not intended behavior, please disable the rewrite. In detail, 
+	 * this operator will produce for W/(U%*%t(V)) a zero intermediate for each zero in W (even if UVij is zero 
+	 * which would give 0/0=NaN) but INF/-INF for non-zero entries in V where the corresponding cell in (Y%*%X) 
+	 * is zero.
+	 * 
+	 * @param mW matrix W
+	 * @param mU matrix U
+	 * @param mV matrix V
+	 * @param mX matrix X
+	 * @param ret result type
+	 * @param wt weighted divide matrix multiplication type
+	 */
+	public static void matrixMultWDivMM(MatrixBlock mW, MatrixBlock mU, MatrixBlock mV, MatrixBlock mX, MatrixBlock ret, WDivMMType wt) {
+		//check for empty result 
+		if(   mW.isEmptyBlock(false) 
+		   || (wt.isLeft() && mU.isEmptyBlock(false))
+		   || (wt.isRight() && mV.isEmptyBlock(false))
+		   || (wt.isBasic() && mW.isEmptyBlock(false)))  {
+			ret.examSparsity(); //turn empty dense into sparse
+			return; 
+		}
+
+		//Timing time = new Timing(true);
+
+		//pre-processing
+		ret.sparse = wt.isBasic()?mW.sparse:false;
+		ret.allocateBlock();
+		
+		//core weighted div mm computation
+		boolean scalarX = wt.hasScalar();
+		if( !mW.sparse && !mU.sparse && !mV.sparse && (mX==null || !mX.sparse || scalarX) && !mU.isEmptyBlock() && !mV.isEmptyBlock() )
+			matrixMultWDivMMDense(mW, mU, mV, mX, ret, wt, 0, mW.rlen, 0, mW.clen);
+		else if( mW.sparse && !mU.sparse && !mV.sparse && (mX==null || mX.sparse || scalarX) && !mU.isEmptyBlock() && !mV.isEmptyBlock())
+			matrixMultWDivMMSparseDense(mW, mU, mV, mX, ret, wt, 0, mW.rlen, 0, mW.clen);
+		else
+			matrixMultWDivMMGeneric(mW, mU, mV, mX, ret, wt, 0, mW.rlen, 0, mW.clen);
+		
+		//post-processing
+		ret.recomputeNonZeros();
+		ret.examSparsity();
+		
+		//System.out.println("MMWDiv "+wt.toString()+" ("+mW.isInSparseFormat()+","+mW.getNumRows()+","+mW.getNumColumns()+","+mW.getNonZeros()+")x" +
+		//                 "("+mV.isInSparseFormat()+","+mV.getNumRows()+","+mV.getNumColumns()+","+mV.getNonZeros()+") in "+time.stop());
+	}
+	
+	/**
+	 * NOTE: This operation has limited NaN support, which is acceptable because all our sparse-safe operations
+	 * have only limited NaN support. If this is not intended behavior, please disable the rewrite. In detail, 
+	 * this operator will produce for W/(U%*%t(V)) a zero intermediate for each zero in W (even if UVij is zero 
+	 * which would give 0/0=NaN) but INF/-INF for non-zero entries in V where the corresponding cell in (Y%*%X) 
+	 * is zero.
+	 * 
+	 * @param mW matrix W
+	 * @param mU matrix U
+	 * @param mV matrix V
+	 * @param mX matrix X
+	 * @param ret result matrix
+	 * @param wt weighted divide matrix multiplication type
+	 * @param k maximum parallelism
+	 */
+	public static void matrixMultWDivMM(MatrixBlock mW, MatrixBlock mU, MatrixBlock mV, MatrixBlock mX, MatrixBlock ret, WDivMMType wt, int k) {
+		//check for empty result 
+		if(   mW.isEmptyBlock(false) 
+		   || (wt.isLeft() && mU.isEmptyBlock(false))
+		   || (wt.isRight() && mV.isEmptyBlock(false)) 
+		   || (wt.isBasic() && mW.isEmptyBlock(false)))  {
+			ret.examSparsity(); //turn empty dense into sparse
+			return; 
+		}
+		
+		//Timing time = new Timing(true);
+
+		//pre-processing
+		ret.sparse = wt.isBasic()?mW.sparse:false;
+		ret.allocateBlock();
+
+		if (!ret.isThreadSafe()){
+			matrixMultWDivMM(mW, mU, mV, mX, ret, wt);
+			return;
+		}
+		
+		ExecutorService pool = CommonThreadPool.get(k);
+		try {
+			ArrayList<MatrixMultWDivTask> tasks = new ArrayList<>();
+			//create tasks (for wdivmm-left, parallelization over columns;
+			//for wdivmm-right, parallelization over rows; both ensure disjoint results)
+			if( wt.isLeft() ) {
+				int blklen = (int)(Math.ceil((double)mW.clen/k));
+				for( int j=0; j<k & j*blklen<mW.clen; j++ )
+					tasks.add(new MatrixMultWDivTask(mW, mU, mV, mX, ret, wt, 0, mW.rlen, j*blklen, Math.min((j+1)*blklen, mW.clen)));
+			}
+			else { //basic/right
+				int blklen = (int)(Math.ceil((double)mW.rlen/k));
+				for( int i=0; i<k & i*blklen<mW.rlen; i++ )
+					tasks.add(new MatrixMultWDivTask(mW, mU, mV, mX, ret, wt, i*blklen, Math.min((i+1)*blklen, mW.rlen), 0, mW.clen));
+			}
+
+			//aggregate partial nnz and check for errors
+			ret.nonZeros = 0;  //reset after execute
+			for( Future<Long> task : pool.invokeAll(tasks) )
+				ret.nonZeros += task.get();
+		} 
+		catch (Exception e) {
+			throw new DMLRuntimeException(e);
+		} 
+		finally{
+			pool.shutdown();
+		}
+
+		//post-processing
+		ret.examSparsity();
+		
+		//System.out.println("MMWDiv "+wt.toString()+" k="+k+" ("+mW.isInSparseFormat()+","+mW.getNumRows()+","+mW.getNumColumns()+","+mW.getNonZeros()+")x" +
+		//                "("+mV.isInSparseFormat()+","+mV.getNumRows()+","+mV.getNumColumns()+","+mV.getNonZeros()+") in "+time.stop());
+	}	
+
+	public static void matrixMultWCeMM(MatrixBlock mW, MatrixBlock mU, MatrixBlock mV, double eps, MatrixBlock ret, WCeMMType wt) {
+		//check for empty result 
+		if( mW.isEmptyBlock(false) )  {
+			ret.examSparsity(); //turn empty dense into sparse
+			return; 
+		}
+
+		//Timing time = new Timing(true);
+
+		//pre-processing
+		ret.sparse = false;
+		ret.allocateDenseBlock();
+		
+		//core weighted cross entropy mm computation
+		if( !mW.sparse && !mU.sparse && !mV.sparse && !mU.isEmptyBlock() && !mV.isEmptyBlock() )
+			matrixMultWCeMMDense(mW, mU, mV, eps, ret, wt, 0, mW.rlen);
+		else if( mW.sparse && !mU.sparse && !mV.sparse && !mU.isEmptyBlock() && !mV.isEmptyBlock())
+			matrixMultWCeMMSparseDense(mW, mU, mV, eps, ret, wt, 0, mW.rlen);
+		else
+			matrixMultWCeMMGeneric(mW, mU, mV, eps, ret, wt, 0, mW.rlen);
+		
+		//System.out.println("MMWCe "+wt.toString()+" ("+mW.isInSparseFormat()+","+mW.getNumRows()+","+mW.getNumColumns()+","+mW.getNonZeros()+")x" +
+		//                 "("+mV.isInSparseFormat()+","+mV.getNumRows()+","+mV.getNumColumns()+","+mV.getNonZeros()+") in "+time.stop());
+	}
+
+	public static void matrixMultWCeMM(MatrixBlock mW, MatrixBlock mU, MatrixBlock mV, double eps, MatrixBlock ret, WCeMMType wt, int k) {
+		//check for empty result 
+		if( mW.isEmptyBlock(false) )  {
+			ret.examSparsity(); //turn empty dense into sparse
+			return; 
+		}
+
+		//Timing time = new Timing(true);
+
+		//pre-processing (no need to check isThreadSafe)
+		ret.sparse = false;
+		ret.allocateDenseBlock();
+		
+		ExecutorService pool = CommonThreadPool.get(k);
+		try {
+			ArrayList<MatrixMultWCeTask> tasks = new ArrayList<>();
+			int blklen = (int)(Math.ceil((double)mW.rlen/k));
+			for( int i=0; i<k & i*blklen<mW.rlen; i++ )
+				tasks.add(new MatrixMultWCeTask(mW, mU, mV, eps, wt, i*blklen, Math.min((i+1)*blklen, mW.rlen)));
+			List<Future<Double>> taskret = pool.invokeAll(tasks);
+			//aggregate partial results
+			sumScalarResults(taskret, ret);
+		} 
+		catch( Exception e ) {
+			throw new DMLRuntimeException(e);
+		}
+		finally{
+			pool.shutdown();
+		}
+		
+		//System.out.println("MMWCe "+wt.toString()+" k="+k+" ("+mW.isInSparseFormat()+","+mW.getNumRows()+","+mW.getNumColumns()+","+mW.getNonZeros()+")x" +
+		//                 "("+mV.isInSparseFormat()+","+mV.getNumRows()+","+mV.getNumColumns()+","+mV.getNonZeros()+") in "+time.stop());
+	}
+
+	public static void matrixMultWuMM(MatrixBlock mW, MatrixBlock mU, MatrixBlock mV, MatrixBlock ret, WUMMType wt, ValueFunction fn) {
+		//check for empty result
+		if( mW.isEmptyBlock(false) ) {
+			ret.examSparsity(); //turn empty dense into sparse
+			return; 
+		}
+
+		//Timing time = new Timing(true);
+
+		//pre-processing
+		ret.sparse = mW.sparse;
+		ret.allocateBlock();
+		
+		//core weighted square sum mm computation
+		if( !mW.sparse && !mU.sparse && !mV.sparse && !mU.isEmptyBlock() && !mV.isEmptyBlock() )
+			matrixMultWuMMDense(mW, mU, mV, ret, wt, fn, 0, mW.rlen);
+		else if( mW.sparse && !mU.sparse && !mV.sparse && !mU.isEmptyBlock() && !mV.isEmptyBlock())
+			matrixMultWuMMSparseDense(mW, mU, mV, ret, wt, fn, 0, mW.rlen);
+		else
+			matrixMultWuMMGeneric(mW, mU, mV, ret, wt, fn, 0, mW.rlen);
+		
+		//post-processing
+		ret.recomputeNonZeros();
+		ret.examSparsity();
+		
+		//System.out.println("MMWu "+wt.toString()+" ("+mW.isInSparseFormat()+","+mW.getNumRows()+","+mW.getNumColumns()+","+mW.getNonZeros()+")x" +
+		//                 "("+mV.isInSparseFormat()+","+mV.getNumRows()+","+mV.getNumColumns()+","+mV.getNonZeros()+") in "+time.stop());
+	}
+
+	public static void matrixMultWuMM(MatrixBlock mW, MatrixBlock mU, MatrixBlock mV, MatrixBlock ret, WUMMType wt, ValueFunction fn, int k) {
+		//check for empty result
+		if( mW.isEmptyBlock(false) ) {
+			ret.examSparsity(); //turn empty dense into sparse
+			return; 
+		}
+		
+		//check no parallelization benefit (fallback to sequential)
+		if (mW.rlen == 1 || !MatrixBlock.isThreadSafe(mW.sparse)) {
+			matrixMultWuMM(mW, mU, mV, ret, wt, fn);
+			return;
+		}
+		
+		//Timing time = new Timing(true);
+
+		//pre-processing
+		ret.sparse = mW.sparse;
+		ret.allocateBlock();
+		
+		ExecutorService pool = CommonThreadPool.get(k);
+		try {
+			ArrayList<MatrixMultWuTask> tasks = new ArrayList<>();
+			int blklen = (int)(Math.ceil((double)mW.rlen/k));
+			for( int i=0; i<k & i*blklen<mW.rlen; i++ )
+				tasks.add(new MatrixMultWuTask(mW, mU, mV, ret, wt, fn, i*blklen, Math.min((i+1)*blklen, mW.rlen)));
+
+			//aggregate partial nnz and check for errors
+			ret.nonZeros = 0; //reset after execute
+			for( Future<Long> task : pool.invokeAll(tasks) )
+				ret.nonZeros += task.get();
+		} 
+		catch (Exception e) {
+			throw new DMLRuntimeException(e);
+		}
+		finally{
+			pool.shutdown();
+		}
+
+		//post-processing (nnz maintained in parallel)
+		ret.examSparsity();
+
+		//System.out.println("MMWu "+wt.toString()+" k="+k+" ("+mW.isInSparseFormat()+","+mW.getNumRows()+","+mW.getNumColumns()+","+mW.getNonZeros()+")x" +
+		//                   "("+mV.isInSparseFormat()+","+mV.getNumRows()+","+mV.getNumColumns()+","+mV.getNonZeros()+") in "+time.stop() + ".");
+	}
+	
+	//////////////////////////////////////////
+	// optimized matrix mult implementation //
+	//////////////////////////////////////////
+
+	private static void matrixMultDenseDense(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, boolean tm2, boolean pm2, int rl, int ru, int cl, int cu) {
+		DenseBlock a = m1.getDenseBlock();
+		DenseBlock b = m2.getDenseBlock();
+		DenseBlock c = ret.getDenseBlock();
+		final int m = m1.rlen;
+		final int n = m2.clen;
+		final int cd = m1.clen;
+		
+		if( LOW_LEVEL_OPTIMIZATION ) {
+			if( m==1 && n==1 ) {            //DOT PRODUCT
+				double[] avals = a.valuesAt(0);
+				double[] bvals = b.valuesAt(0);
+				if( ru > m ) //pm2r - parallelize over common dim
+					c.set(0, 0, dotProduct(avals, bvals, rl, rl, ru-rl));
+				else
+					c.set(0, 0, dotProduct(avals, bvals, cd));
+			}
+			else if( n>1 && cd == 1 ) {     //OUTER PRODUCT
+				double[] avals = a.valuesAt(0);
+				double[] bvals = b.valuesAt(0);
+				for( int i=rl; i < ru; i++) {
+					double[] cvals = c.values(i);
+					int cix = c.pos(i);
+					if( avals[i] == 1 )
+						System.arraycopy(bvals, 0, cvals, cix, n);
+					else if( avals[i] != 0 )
+						vectMultiplyWrite(avals[i], bvals, cvals, 0, cix, n);
+					else
+						Arrays.fill(cvals, cix, cix+n, 0);
+				}
+			}
+			else if( n==1 && cd == 1 ) {    //VECTOR-SCALAR
+				double[] avals = a.valuesAt(0);
+				double[] cvals = c.valuesAt(0);
+				vectMultiplyWrite(b.get(0,0), avals, cvals, rl, rl, ru-rl);
+			}
+			else if( n==1 && cd<=2*1024 ) { //MATRIX-VECTOR (short rhs)
+				matrixMultDenseDenseMVShortRHS(a, b, c, cd, rl, ru);
+			}
+			else if( n==1 ) {               //MATRIX-VECTOR (tall rhs)
+				matrixMultDenseDenseMVTallRHS(a, b, c, cd, rl, ru);
+			}
+			else if( pm2 && m==1 ) {        //VECTOR-MATRIX
+				matrixMultDenseDenseVM(a, b, c, n, cd, rl, ru);
+			}
+			else if( pm2 && m<=16 ) {       //MATRIX-MATRIX (short lhs) 
+				matrixMultDenseDenseMMShortLHS(a, b, c, m, n, cd, rl, ru);
+			}
+			else if( tm2 ) {                //MATRIX-MATRIX (skinny rhs)
+				matrixMultDenseDenseMMSkinnyRHS(a, b, c, m2.rlen, cd, rl, ru);
+			}
+			else {                          //MATRIX-MATRIX
+				matrixMultDenseDenseMM(a, b, c, n, cd, rl, ru, cl, cu);
+			}
+		}
+		else {
+			for( int i = rl; i < ru; i++) {
+				double[] avals = a.values(i);
+				double[] cvals = c.values(i);
+				int aix = a.pos(i), cix = c.pos(i);
+				for( int k = 0; k < cd; k++) {
+					double val = avals[aix + k];
+					if( val != 0 ) {
+						double[] bvals = b.values(k);
+						int bix = b.pos(k);
+						for( int j = 0; j < n; j++) 
+							cvals[cix+j] += val * bvals[bix+j];
+					}
+				}
+			}
+		}
+	}
+	
+	private static void matrixMultDenseDenseMVShortRHS(DenseBlock a, DenseBlock b, DenseBlock c, int cd, int rl, int ru) {
+		double[] bvals = b.valuesAt(0);
+		double[] cvals = c.valuesAt(0);
+		for( int i=rl; i < ru; i++ )
+			cvals[i] = dotProduct(a.values(i), bvals, a.pos(i), 0, cd);
+	}
+	
+	private static void matrixMultDenseDenseMVTallRHS(DenseBlock a, DenseBlock b, DenseBlock c, int cd, int rl, int ru) {
+		final int blocksizeI = 32;
+		final int blocksizeK = 2*1024; //16KB vector blocks (L1)
+		double[] bvals = b.valuesAt(0);
+		double[] cvals = c.valuesAt(0);
+		for( int bi=rl; bi<ru; bi+=blocksizeI ) {
+			int bimin = Math.min(bi+blocksizeI, ru);
+			for( int bk=0; bk<cd; bk+=blocksizeK ) {
+				int bkmin = Math.min(bk+blocksizeK, cd);
+				for( int i=bi; i<bimin; i++) 
+					cvals[i] += dotProduct(a.values(i), bvals, a.pos(i,bk), bk, bkmin-bk);
+			}
+		}
+	}
+	
+	private static void matrixMultDenseDenseVM(DenseBlock a, DenseBlock b, DenseBlock c, int n, int cd, int rl, int ru) {
+		double[] avals = a.valuesAt(0); //vector
+		double[] cvals = c.valuesAt(0); //vector
+		
+		//parallelization over rows in rhs matrix
+		//rest not aligned to blocks of 2 rows
+		final int kn = b.isContiguous() ? rl+(ru-rl)%2 : ru;
+		for( int k = rl; k < kn; k++ )
+			if( avals[k] != 0 )
+				vectMultiplyAdd(avals[k], b.values(k), cvals, b.pos(k), 0, n);
+		
+		//compute blocks of 2 rows (2 instead of 4 for small n<64)
+		double[] bvals = b.valuesAt(0); //only for special case
+		for( int k=kn, bix=kn*n; k<ru; k+=2, bix+=2*n ){
+			if( avals[k] != 0 && avals[k+1] != 0  )
+				vectMultiplyAdd2(avals[k], avals[k+1], bvals, cvals, bix, bix+n, 0, n);
+			else if( avals[k] != 0 )
+				vectMultiplyAdd(avals[k], bvals, cvals, bix, 0, n);
+			else if( avals[k+1] != 0 )
+				vectMultiplyAdd(avals[k+1], bvals, cvals, bix+n, 0, n);
+		}
+	}
+	
+	private static void matrixMultDenseDenseMMShortLHS(DenseBlock a, DenseBlock b, DenseBlock c, int m, int n, int cd, int rl, int ru) {
+		//cache-conscious parallelization over rows in rhs matrix
+		final int kn = (ru-rl)%4;
+		
+		//rest not aligned to blocks of 2 rows
+		for( int i=0; i<m; i++ ) {
+			double[] avals = a.values(i), cvals = c.values(i);
+			int aix = a.pos(i), cix = c.pos(i);
+			for( int k=rl; k<rl+kn; k++ )
+				if( avals[aix+k] != 0 )
+					vectMultiplyAdd(avals[aix+k], b.values(k), cvals, b.pos(k), cix, n);
+		}
+		
+		final int blocksizeK = 48;
+		final int blocksizeJ = 1024;
+		
+		//blocked execution
+		for( int bk = rl+kn; bk < ru; bk+=blocksizeK ) {
+			int bkmin = Math.min(ru, bk+blocksizeK);
+			for( int bj = 0; bj < n; bj+=blocksizeJ ) {
+				//compute blocks of 4 rows in rhs w/ IKJ
+				int bjlen = Math.min(n, bj+blocksizeJ)-bj;
+				for( int i=0; i<m; i++ ) {
+					double[] avals = a.values(i), cvals = c.values(i);
+					int aix = a.pos(i), cix = c.pos(i, bj);
+					if( b.isContiguous(bk, bkmin-1) ) {
+						double[] bvals = b.values(bk);
+						for( int k=bk, bix=b.pos(bk, bj); k<bkmin; k+=4, bix+=4*n )
+							vectMultiplyAdd4(avals[aix+k], avals[aix+k+1], avals[aix+k+2], avals[aix+k+3],
+								bvals, cvals, bix, bix+n, bix+2*n, bix+3*n, cix, bjlen);
+					}
+					else {
+						for( int k=rl; k<rl+kn; k++ )
+							if( avals[aix+k] != 0 )
+								vectMultiplyAdd(avals[aix+k], b.values(k), cvals, b.pos(k), cix, n);
+					}
+				}
+			}
+		}
+	}
+	
+	private static void matrixMultDenseDenseMMSkinnyRHS(DenseBlock a, DenseBlock b, DenseBlock c, int n2, int cd, int rl, int ru) {
+		//note: prepared rhs input via transpose for: m > n && cd > 64 && n < 64
+		//however, explicit flag required since dimension change m2
+		for( int i=rl; i < ru; i++ ) {
+			double[] avals = a.values(i), cvals = c.values(i);
+			int aix = a.pos(i), cix = c.pos(i);
+			for( int j=0; j<n2; j++ )
+				cvals[cix+j] = dotProduct(avals, b.values(j), aix, b.pos(j), cd);
+		}
+	}
+
+	public static void matrixMultDenseDenseMMDedup(DenseBlockFP64DEDUP a, DenseBlock b, DenseBlockFP64DEDUP c, int n, int cd, int rl, int ru, ConcurrentHashMap<double[], double[]> cache) {
+		//n = m2.clen;
+		//cd = m1.clen;
+		if(a.getNrEmbsPerRow() != 1){
+			//TODO: currently impossible case, since Dedup reshape is not supported yet, once it is, this method needs
+			// to be implemented
+			throw new NotImplementedException("Check TODO");
+		}
+		for (int i = rl; i < ru; i++) {
+			double[] a_row = a.getDedupDirectly(i);
+			double[] c_row = cache.getOrDefault(a_row, null);
+			if (c_row == null) {
+				c_row = new double[n];
+				for (int j = 0; j < n; j++) {
+					c_row[j] = 0.0;
+					//the following requires b.isContiguous(0,cd)
+					double[] b_column = b.values(0);
+					for (int k = 0; k < cd; k++) {
+						c_row[j] += a_row[k] * b_column[b.pos(k, j)];
+					}
+				}
+				cache.put(a_row, c_row);
+			}
+			c.setDedupDirectly(i, c_row);
+		}
+	}
+
+	//note: public for use by codegen for consistency
+	public static void matrixMultDenseDenseMM(DenseBlock a, DenseBlock b, DenseBlock c, int n, int cd, int rl, int ru, int cl, int cu) {
+		//1) Unrolled inner loop (for better instruction-level parallelism)
+		//2) Blocked execution (for less cache trashing in parallel exec)
+		//3) Asymmetric block sizes (for less misses in inner loop, yet blocks in L1/L2)
+
+		final int blocksizeI = 32; //64//256KB c block (typical L2 size per core), 32KB a block
+		final int blocksizeK = 24; //64//256KB b block (typical L2 size per core), used while read 512B of a / read/write 4KB of c
+		final int blocksizeJ = 1024; //512//4KB (typical main-memory page size), for scan
+
+		//temporary arrays (nnz a, b index)
+		double[] ta = new double[ blocksizeK ];
+		int[]  tbi  = new int[ blocksizeK ];
+
+		//blocked execution
+		for( int bi = rl; bi < ru; bi+=blocksizeI )
+			for( int bk = 0, bimin = Math.min(ru, bi+blocksizeI); bk < cd; bk+=blocksizeK ) 
+				for( int bj = cl, bkmin = Math.min(cd, bk+blocksizeK); bj < cu; bj+=blocksizeJ ) 
+				{
+					int bklen = bkmin-bk;
+					int bjlen = Math.min(cu, bj+blocksizeJ)-bj;
+					
+					//core sub block matrix multiplication
+					for( int i = bi; i < bimin; i++) {
+						double[] avals = a.values(i), cvals = c.values(i);
+						int aixi = a.pos(i, bk), cixj = c.pos(i, bj);
+
+						if( b.isContiguous(bk, bkmin-1) ) {
+							double[] bvals = b.values(bk);
+							int bkpos = b.pos(bk, bj);
+							
+							//determine nnz of a (for sparsity-aware skipping of rows)
+							int knnz = copyNonZeroElements(avals, aixi, bkpos, n, ta, tbi, bklen);
+							
+							//rest not aligned to blocks of 4 rows
+							final int bn = knnz % 4;
+							switch( bn ){
+								case 1: vectMultiplyAdd(ta[0], bvals, cvals, tbi[0], cixj, bjlen); break;
+								case 2: vectMultiplyAdd2(ta[0],ta[1], bvals, cvals, tbi[0], tbi[1], cixj, bjlen); break;
+								case 3: vectMultiplyAdd3(ta[0],ta[1],ta[2], bvals, cvals, tbi[0], tbi[1],tbi[2], cixj, bjlen); break;
+							}
+							
+							//compute blocks of 4 rows (core inner loop)
+							for( int k = bn; k<knnz; k+=4 ){
+								vectMultiplyAdd4( ta[k], ta[k+1], ta[k+2], ta[k+3], bvals, cvals, 
+									tbi[k], tbi[k+1], tbi[k+2], tbi[k+3], cixj, bjlen );
+							}
+						}
+						else {
+							for( int k = bk; k<bkmin; k++ ) {
+								if( segment1.getAtIndex(layout, k) != 0 )
+									vectMultiplyAdd( segment1.getAtIndex(layout, k), b.values(k),
+										cvals, b.pos(k, bj), cixj, bjlen );
+							}
+						}
+					}
+				}
+	}
+
+	private static void matrixMultDenseSparse(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, boolean pm2, int rl, int ru) {
+		DenseBlock a = m1.getDenseBlock();
+		DenseBlock c = ret.getDenseBlock();
+		int m = m1.rlen;
+		int cd = m1.clen;
+		
+		// MATRIX-MATRIX (VV, MV not applicable here because V always dense)
+		if( LOW_LEVEL_OPTIMIZATION )
+		{
+			SparseBlock b = m2.sparseBlock;
+			
+			if( pm2 && m==1 ) {        //VECTOR-MATRIX
+				//parallelization over rows in rhs matrix
+				double[] avals = a.valuesAt(0); //vector
+				double[] cvals = c.valuesAt(0); //vector
+				for( int k=rl; k<ru; k++ )
+					if( avals[k] != 0 && !b.isEmpty(k) ) {
+						vectMultiplyAdd(avals[k], b.values(k), cvals,
+							b.indexes(k), b.pos(k), 0, b.size(k));
+					}
+			}
+			else {                     //MATRIX-MATRIX
+				//best effort blocking, without blocking over J because it is 
+				//counter-productive, even with front of current indexes
+				final int blocksizeK = 32;
+				final int blocksizeI = 32;
+				
+				int rl1 = pm2 ? 0 : rl;
+				int ru1 = pm2 ? m : ru;
+				int rl2 = pm2 ? rl : 0;
+				int ru2 = pm2 ? ru : cd;
+				
+				//blocked execution
+				for( int bi = rl1; bi < ru1; bi+=blocksizeI )
+					for( int bk = rl2, bimin = Math.min(ru1, bi+blocksizeI); bk < ru2; bk+=blocksizeK ) {
+						int bkmin = Math.min(ru2, bk+blocksizeK);
+						//core sub block matrix multiplication
+						for(int i = bi; i < bimin; i++) {
+							double[] avals = a.values(i), cvals = c.values(i);
+							int aix = a.pos(i), cix = c.pos(i);
+							for( int k = bk; k < bkmin; k++ ) {
+								double aval = avals[aix+k];
+								if( aval == 0 || b.isEmpty(k) )
+									continue;
+								vectMultiplyAdd(aval, b.values(k), cvals, 
+									b.indexes(k), b.pos(k), cix, b.size(k));
+							}
+						}
+					}
+			}
+		}
+		else {
+			SparseBlock b = m2.sparseBlock;
+			for( int i=rl; i < ru; i++ ) {
+				double[] avals = a.values(i), cvals = c.values(i);
+				int aix = a.pos(i), cix = c.pos(i);
+				for(int k = 0; k < cd; k++ ) {
+					double val = avals [aix];
+					if( val == 0 || b.isEmpty(k) ) continue;
+					int bpos = b.pos(k);
+					int blen = b.size(k);
+					int[] bix = b.indexes(k);
+					double[] bvals = b.values(k);
+					for(int j = bpos; j < bpos+blen; j++)
+						cvals[cix+bix[j]] += val * bvals[j];
+				}
+			}
+		}
+	}
+
+	private static void matrixMultSparseDense(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, boolean pm2, int rl, int ru) {
+		SparseBlock a = m1.sparseBlock;
+		DenseBlock b = m2.getDenseBlock();
+		DenseBlock c = ret.getDenseBlock();
+		final int m = m1.rlen;
+		final int n = m2.clen;
+		final int cd = m2.rlen;
+		final long xsp = (long)m*cd/m1.nonZeros;
+
+		if( LOW_LEVEL_OPTIMIZATION ) {
+			if( m==1 && n==1 ) {            //DOT PRODUCT
+				if( !a.isEmpty(0) )
+					c.set(0, 0, dotProduct(a.values(0), b.values(0), a.indexes(0), a.pos(0), 0, a.size(0)));
+			}
+			else if( n==1 && cd<=2*1024 ) { //MATRIX-VECTOR (short rhs)
+				matrixMultSparseDenseMVShortRHS(a, b, c, cd, rl, ru);
+			}
+			else if( n==1 ) {               //MATRIX-VECTOR (tall rhs)
+				matrixMultSparseDenseMVTallRHS(a, b, c, cd, xsp, rl, ru);
+			}
+			else if( pm2 && m==1 ) {        //VECTOR-MATRIX
+				matrixMultSparseDenseVM(a, b, c, n, rl, ru);
+			}
+			else if( pm2 && m<=16 ) {       //MATRIX-MATRIX (short lhs) 
+				matrixMultSparseDenseMMShortLHS(a, b, c, n, cd, rl, ru);
+			}
+			else if( n<=64 ) {              //MATRIX-MATRIX (skinny rhs)
+				matrixMultSparseDenseMMSkinnyRHS(a, b, c, n, rl, ru);
+			}
+			else {                          //MATRIX-MATRIX
+				matrixMultSparseDenseMM(a, b, c, n, cd, xsp, rl, ru);
+			}
+		}
+		else {
+			for( int i=rl; i<ru; i++ ) {
+				if( a.isEmpty(i) ) continue; 
+				int apos = a.pos(i);
+				int alen = a.size(i);
+				int[] aix = a.indexes(i);
+				double[] avals = a.values(i);
+				double[] cvals = c.values(i);
+				int cix = c.pos(i);
+				for(int k = apos; k < apos+alen; k++) {
+					double val = avals[k];
+					int colIndex = aix[k];
+					double[] bvals = b.values(colIndex);
+					int bix = b.pos(colIndex);
+					for(int j = 0; j < n; j++)
+						cvals[cix+j] += val * bvals[bix+j];
+				}
+			}
+		}
+	}
+	
+	private static void matrixMultSparseDenseMVShortRHS(SparseBlock a, DenseBlock b, DenseBlock c, int cd, int rl, int ru) {
+		double[] bvals = b.valuesAt(0);
+		double[] cvals = c.valuesAt(0);
+		for( int i=rl; i<ru; i++ ) {
+			if( a.isEmpty(i) ) continue;
+			int alen = a.size(i);
+			int apos = a.pos(i);
+			double[] avals = a.values(i);
+			cvals[i] = (alen==cd) ? dotProduct(avals, bvals, apos, 0, cd) :
+				dotProduct(avals, bvals, a.indexes(i), apos, 0, alen);
+		}
+	}
+	
+	private static void matrixMultSparseDenseMVTallRHS(SparseBlock a, DenseBlock b, DenseBlock c, int cd, long xsp, int rl, int ru) {
+		final int blocksizeI = 512; //8KB curk+cvals in L1
+		final int blocksizeK = (int)Math.max(2048,2048*xsp/32); //~256KB bvals in L2
+		
+		//short-cut to kernel w/o cache blocking if no benefit
+		if( blocksizeK >= cd ) {
+			matrixMultSparseDenseMVShortRHS(a, b, c, cd, rl, ru);
+			return;
+		}
+		
+		//sparse matrix-vector w/ cache blocking (keep front of positions)
+		double[] bvals = b.valuesAt(0);
+		double[] cvals = c.valuesAt(0);
+		int[] curk = new int[blocksizeI];
+		
+		for( int bi = rl; bi < ru; bi+=blocksizeI ) {
+			Arrays.fill(curk, 0); //reset positions
+			for( int bk=0, bimin = Math.min(ru, bi+blocksizeI); bk<cd; bk+=blocksizeK ) {
+				for( int i=bi, bkmin = bk+blocksizeK; i<bimin; i++) {
+					if( a.isEmpty(i) ) continue;
+					int apos = a.pos(i);
+					int alen = a.size(i);
+					int[] aix = a.indexes(i);
+					double[] avals = a.values(i);
+					int k = curk[i-bi] + apos;
+					for( ; k<apos+alen && aix[k]<bkmin; k++ )
+						cvals[i] += avals[k] * bvals[aix[k]];
+					curk[i-bi] = k - apos;
+				}
+			}
+		}
+	}
+	
+	private static void matrixMultSparseDenseVM(SparseBlock a, DenseBlock b, DenseBlock c, int n, int rl, int ru) {
+		if( a.isEmpty(0) )
+			return;
+		
+		//parallelization over rows in rhs matrix
+		int alen = a.size(0);
+		int[] aix = a.indexes(0);
+		double[] avals = a.values(0);
+		double[] cvals = c.valuesAt(0);
+		int rlix = (rl==0) ? 0 : a.posFIndexGTE(0,rl);
+		rlix = (rlix>=0) ? rlix : alen;
+		
+		if( b.isContiguous() ) {
+			double[] bvals = b.valuesAt(0);
+			for( int k=rlix; k<alen && aix[k]<ru; k++ )
+				if( k+1<alen && aix[k+1]<ru )
+					vectMultiplyAdd2(avals[k], avals[k+1], bvals, cvals, aix[k]*n, aix[++k]*n, 0, n);
+				else
+					vectMultiplyAdd(avals[k], bvals, cvals, aix[k]*n, 0, n);
+		}
+		else {
+			for( int k=rlix; k<alen && aix[k]<ru; k++ )
+				vectMultiplyAdd(avals[k], b.values(aix[k]), cvals, b.pos(aix[k]), 0, n);
+		}
+	}
+	
+	private static void matrixMultSparseDenseMMShortLHS(SparseBlock a, DenseBlock b, DenseBlock c, int n, int cd, int rl, int ru) {
+		int arlen = a.numRows();
+		for( int i=0; i<arlen; i++ ) {
+			if( a.isEmpty(i) ) continue;
+			int apos = a.pos(i);
+			int alen = a.size(i);
+			int[] aix = a.indexes(i);
+			double[] avals = a.values(i);
+			double[] cvals = c.values(i);
+			int cix = c.pos(i);
+			
+			int k1 = (rl==0) ? 0 : a.posFIndexGTE(i, rl);
+			k1 = (k1>=0) ? apos+k1 : apos+alen;
+			int k2 = (ru==cd) ? alen : a.posFIndexGTE(i, ru);
+			k2 = (k2>=0) ? apos+k2 : apos+alen;
+			
+			//note: guard k1 (and thus also k2) against overrun nnz, and guard
+			//contiguous check for k2-1 against underrun of start pos for k1==k2.
+			if( k1<apos+alen && (k1==k2 || b.isContiguous(aix[k1], aix[k2-1])) ) {
+				double[] bvals = b.values(aix[k1]);
+				int base = aix[k1]*n - b.pos(aix[k1]);
+				//rest not aligned to blocks of 4 rows
+				final int bn = (k2-k1) % 4;
+				switch( bn ){
+					case 1: vectMultiplyAdd(avals[k1], bvals, cvals, aix[k1]*n-base, cix, n); break;
+					case 2: vectMultiplyAdd2(avals[k1],avals[k1+1], bvals, cvals, aix[k1]*n-base, aix[k1+1]*n-base, cix, n); break;
+					case 3: vectMultiplyAdd3(avals[k1],avals[k1+1],avals[k1+2], bvals, cvals, aix[k1]*n-base, aix[k1+1]*n-base, aix[k1+2]*n-base, cix, n); break;
+				}
+				
+				//compute blocks of 4 rows (core inner loop)
+				for( int k = k1+bn; k<k2; k+=4 ) {
+					vectMultiplyAdd4( avals[k], avals[k+1], avals[k+2], avals[k+3], bvals, cvals, 
+						aix[k]*n-base, aix[k+1]*n-base, aix[k+2]*n-base, aix[k+3]*n-base, cix, n );
+				}
+			}
+			else {
+				for( int k = k1; k<k2; k++ )
+					vectMultiplyAdd( avals[k], b.values(aix[k]), cvals, b.pos(aix[k]), cix, n );
+			}
+		}
+	}
+	
+	private static void matrixMultSparseDenseMMSkinnyRHS(SparseBlock a, DenseBlock b, DenseBlock c, int n, int rl, int ru) {
+		//no blocking since b and c fit into cache anyway
+		for( int i=rl, cix=rl*n; i<ru; i++, cix+=n ) {
+			if( a.isEmpty(i) ) continue;
+			int apos = a.pos(i);
+			int alen = a.size(i);
+			int[] aix = a.indexes(i);
+			double[] avals = a.values(i);
+			double[] cvals = c.values(i);
+			//rest not aligned to blocks of 4 rows
+			int bn = b.isContiguous() ? alen%4 : alen;
+			for( int k=apos; k<apos+bn; k++ )
+				vectMultiplyAdd(avals[k], b.values(aix[k]), cvals, b.pos(aix[k]), cix, n);
+			//compute blocks of 4 rows (core inner loop)
+			double[] bvals = b.valuesAt(0); //only for contiguous
+			for( int k=apos+bn; k<apos+alen; k+=4 )
+				vectMultiplyAdd4( avals[k], avals[k+1], avals[k+2], avals[k+3], bvals, cvals,
+					aix[k]*n, aix[k+1]*n, aix[k+2]*n, aix[k+3]*n, cix, n );
+		}
+	}
+	
+	private static void matrixMultSparseDenseMM(SparseBlock a, DenseBlock b, DenseBlock c, int n, int cd, long xsp, int rl, int ru) {
+		//blocksizes to fit blocks of B (dense) and several rows of A/C in common L2 cache size, 
+		//while blocking A/C for L1/L2 yet allowing long scans (2 pages) in the inner loop over j
+		//in case of almost ultra-sparse matrices, we cannot ensure the blocking for the rhs and
+		//output - however, in this case it's unlikely that we consume every cache line in the rhs
+		final int blocksizeI = (int) (8L*xsp);
+		final int blocksizeK = (int) (8L*xsp);
+		final int blocksizeJ = 1024; 
+		
+		//temporary array of current sparse positions
+		int[] curk = new int[Math.min(blocksizeI, ru-rl)];
+		
+		//blocked execution over IKJ 
+		for( int bi = rl; bi < ru; bi+=blocksizeI ) {
+			Arrays.fill(curk, 0); //reset positions
+			for( int bk = 0, bimin = Math.min(ru, bi+blocksizeI); bk < cd; bk+=blocksizeK ) {
+				for( int bj = 0, bkmin = Math.min(cd, bk+blocksizeK); bj < n; bj+=blocksizeJ ) {
+					int bjlen = Math.min(n, bj+blocksizeJ)-bj;
+					
+					//core sub block matrix multiplication
+					for( int i=bi; i<bimin; i++ ) {
+						if( a.isEmpty(i) ) continue;
+						int apos = a.pos(i);
+						int alen = a.size(i);
+						int[] aix = a.indexes(i);
+						double[] avals = a.values(i);
+						double[] cvals = c.values(i);
+						int cix = c.pos(i, bj);
+						
+						int k = curk[i-bi] + apos;
+						//rest not aligned to blocks of 4 rows
+						int bn = b.isContiguous() ? alen%4 : alen;
+						for( ; k<apos+bn && aix[k]<bkmin; k++ )
+							vectMultiplyAdd(avals[k], b.values(aix[k]), cvals, b.pos(aix[k],bj), cix, bjlen); 
+						//compute blocks of 4 rows (core inner loop), allowed to exceed bkmin
+						double[] bvals = b.valuesAt(0); //only for contiguous
+						for( ; k<apos+alen && aix[k]<bkmin; k+=4 )
+							vectMultiplyAdd4( avals[k], avals[k+1], avals[k+2], avals[k+3], bvals, cvals, 
+								aix[k]*n+bj, aix[k+1]*n+bj, aix[k+2]*n+bj, aix[k+3]*n+bj, cix, bjlen );
+						//update positions on last bj block
+						if( bj+bjlen==n )
+							curk[i-bi] = k - apos;
+					}
+				}
+			}
+		}
+	}
+
+	private static void matrixMultSparseSparse(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, boolean pm2, boolean sparse, int rl, int ru) {
+		SparseBlock a = m1.sparseBlock;
+		SparseBlock b = m2.sparseBlock;
+		int m = m1.rlen;
+		int cd = m1.clen;
+		int n = m2.clen;
+		
+		// MATRIX-MATRIX (VV, MV not applicable here because V always dense)
+		if(LOW_LEVEL_OPTIMIZATION) {
+			if( pm2 && m==1 )               //VECTOR-MATRIX
+				matrixMultSparseSparseVM(a, b, ret.getDenseBlock(), rl, ru);
+			else if( sparse )               //SPARSE OUPUT
+				ret.setNonZeros(matrixMultSparseSparseSparseMM(a, b, ret.getSparseBlock(), n, rl, ru));
+			else if( m2.nonZeros < 2048 )   //MATRIX-SMALL MATRIX
+				matrixMultSparseSparseMMSmallRHS(a, b, ret.getDenseBlock(), rl, ru);
+			else                            //MATRIX-MATRIX
+				matrixMultSparseSparseMM(a, b, ret.getDenseBlock(), m, cd, m1.nonZeros, rl, ru);
+		}
+		else {
+			matrixMultSparseSparseMMGeneric(a, b, ret.getDenseBlock(), rl, ru);
+		}
+	}
+	
+	private static void matrixMultSparseSparseVM(SparseBlock a, SparseBlock b, DenseBlock c, int rl, int ru) {
+		//parallelization over rows in rhs matrix
+		if( a.isEmpty(0) )
+			return;
+		
+		int alen = a.size(0);
+		int[] aix = a.indexes(0);
+		double[] avals = a.values(0);
+		double[] cvals = c.valuesAt(0);
+		int rlix = (rl==0) ? 0 : a.posFIndexGTE(0,rl);
+		rlix = (rlix>=0) ? rlix : alen;
+		
+		for( int k=rlix; k<alen && aix[k]<ru; k++ )
+			if( !b.isEmpty(aix[k]) ) {
+				int bpos = b.pos(aix[k]);
+				int blen = b.size(aix[k]);
+				int[] bix = b.indexes(aix[k]);
+				double[] bvals = b.values(aix[k]);
+				vectMultiplyAdd(avals[k], bvals, cvals, bix, bpos, 0, blen);
+			}
+	}
+	
+	private static long matrixMultSparseSparseSparseMM(SparseBlock a, SparseBlock b, SparseBlock c, int n, int rl, int ru) {
+		double[] tmp = new double[n];
+		long nnz = 0;
+		for( int i=rl; i<Math.min(ru, a.numRows()); i++ ) {
+			if( a.isEmpty(i) ) continue;
+			final int apos = a.pos(i);
+			final int alen = a.size(i);
+			int[] aix = a.indexes(i);
+			double[] avals = a.values(i);
+			//compute row output in dense buffer
+			boolean hitNonEmpty = false;
+			for(int k = apos; k < apos+alen; k++) {
+				int aixk = aix[k];
+				if( b.isEmpty(aixk) ) continue;
+				vectMultiplyAdd(avals[k], b.values(aixk), tmp,
+					b.indexes(aixk), b.pos(aixk), 0, b.size(aixk));
+				hitNonEmpty = true;
+			}
+			//copy dense buffer into sparse output (CSR or MCSR)
+			if( hitNonEmpty ) {
+				int rnnz = UtilFunctions.computeNnz(tmp, 0, n);
+				nnz += rnnz;
+				c.allocate(i, rnnz);
+				for(int j=0; j<n; j++)
+					if( tmp[j] != 0 ) {
+						c.append(i, j, tmp[j]);
+						tmp[j] = 0;
+					}
+			}
+		}
+		return nnz;
+	}
+	
+	private static void matrixMultSparseSparseMMSmallRHS(SparseBlock a, SparseBlock b, DenseBlock c, int rl, int ru) {
+		for( int i=rl; i<Math.min(ru, a.numRows()); i++ ) {
+			if( a.isEmpty(i) ) continue;
+			final int apos = a.pos(i);
+			final int alen = a.size(i);
+			int[] aix = a.indexes(i);
+			double[] avals = a.values(i);
+			double[] cvals = c.values(i);
+			int cix = c.pos(i);
+			for(int k = apos; k < apos+alen; k++) {
+				int aixk = aix[k];
+				if( b.isEmpty(aixk) ) continue;
+				vectMultiplyAdd(avals[k], b.values(aixk), cvals,
+					b.indexes(aixk), b.pos(aixk), cix, b.size(aixk));
+			}
+		}
+	}
+	
+	private static void matrixMultSparseSparseMM(SparseBlock a, SparseBlock b, DenseBlock c, int m, int cd, long nnz1, int rl, int ru) {
+		//block sizes for best-effort blocking w/ sufficient row reuse in B yet small overhead
+		final int blocksizeI = 32;
+		final int blocksizeK = Math.max(32,
+			UtilFunctions.nextIntPow2((int)Math.pow((double)m*cd/nnz1, 2)));
+		
+		//temporary array of current sparse positions
+		int[] curk = new int[Math.min(blocksizeI, ru-rl)];
+		
+		//blocked execution over IK 
+		for( int bi = rl; bi < ru; bi+=blocksizeI ) {
+			Arrays.fill(curk, 0); //reset positions
+			for( int bk = 0, bimin = Math.min(ru, bi+blocksizeI); bk < cd; bk+=blocksizeK ) {
+				final int bkmin = Math.min(cd, bk+blocksizeK); 
+				//core sub block matrix multiplication
+				for( int i=bi; i<bimin; i++ ) {
+					if( a.isEmpty(i) ) continue;
+					final int apos = a.pos(i);
+					final int alen = a.size(i);
+					int[] aix = a.indexes(i);
+					double[] avals = a.values(i);
+					double[] cvals = c.values(i);
+					int cix = c.pos(i);
+					int k = curk[i-bi] + apos;
+					for(; k < apos+alen && aix[k]<bkmin; k++) {
+						if( b.isEmpty(aix[k]) ) continue;
+						vectMultiplyAdd(avals[k], b.values(aix[k]), cvals,
+							b.indexes(aix[k]), b.pos(aix[k]), cix, b.size(aix[k]));
+					}
+					curk[i-bi] = k - apos;
+				}
+			}
+		}
+	}
+	
+	private static void matrixMultSparseSparseMMGeneric(SparseBlock a, SparseBlock b, DenseBlock c, int rl, int ru) {
+		for( int i=rl; i<Math.min(ru, a.numRows()); i++ ) {
+			if( a.isEmpty(i) ) continue;
+			int apos = a.pos(i);
+			int alen = a.size(i);
+			int[] aix = a.indexes(i);
+			double[] avals = a.values(i);
+			double[] cvals = c.values(i);
+			int cix = c.pos(i);
+			for(int k = apos; k < apos+alen; k++) {
+				if( b.isEmpty(aix[k]) ) continue;
+				double val = avals[k];
+				int bpos = b.pos(aix[k]);
+				int blen = b.size(aix[k]);
+				int[] bix = b.indexes(aix[k]);
+				double[] bvals = b.values(aix[k]);
+				for(int j = bpos; j < bpos+blen; j++)
+					cvals[cix+bix[j]] += val * bvals[j];
+			}
+		}
+	}
+	
+	/**
+	 * This implementation applies to any combination of dense/sparse if at least one
+	 * input is ultrasparse (sparse and very few nnz). In that case, most importantly,
+	 * we want to create a sparse output and only iterate over the few nnz as the major
+	 * dimension. Low-level optimization have less importance in that case and having
+	 * this generic implementation helps to reduce the implementations from (2+1)^2
+	 * to 2^2+1.
+	 * 
+	 * @param m1 first matrix
+	 * @param m2 second matrix
+	 * @param ret result matrix
+	 * @param rl row lower bound
+	 * @param ru row upper bound
+	 */
+	private static void matrixMultUltraSparse(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, boolean m1Perm, int rl, int ru) {
+		final boolean leftUS = m1.isUltraSparse()
+			|| (m1.isUltraSparse(false) && !m2.isUltraSparse())
+			|| (m1.sparse && !m2.sparse);
+		if( m1 == m2 ) //self-product
+			matrixMultUltraSparseSelf(m1, ret, rl, ru);
+		else if( leftUS || m1Perm )
+			matrixMultUltraSparseLeft(m1, m2, ret, rl, ru);
+		else
+			matrixMultUltraSparseRight(m1, m2, ret, rl, ru);
+	}
+	
+	private static void matrixMultUltraSparseSelf(MatrixBlock m1, MatrixBlock ret, int rl, int ru) {
+		//common use case: self product G %*% G of graph resulting in dense but still sparse output
+		int n = m1.clen; //m2.clen
+		SparseBlock a = m1.sparseBlock;
+		SparseBlock c = ret.sparseBlock;
+		double[] tmp = null;
+		
+		//IKJ with dense working row for lhs nnz/row > threshold
+		for( int i=rl; i<ru; i++ ) {
+			if( a.isEmpty(i) ) continue;
+			int alen = a.size(i);
+			int apos = a.pos(i);
+			int[] aix = a.indexes(i);
+			double[] avals = a.values(i);
+			
+			//compute number of aggregated non-zeros for input row
+			int nnz1 = (int) Math.min(UtilFunctions.computeNnz(a, aix, apos, alen), n);
+			boolean ldense = nnz1 > n / 128;
+			
+			//perform vector-matrix multiply w/ dense or sparse output
+			if( ldense ) { //init dense tmp row
+				tmp = (tmp == null) ? new double[n] : tmp;
+				Arrays.fill(tmp, 0);
+			}
+			for( int k=apos; k<apos+alen; k++ ) {
+				if( a.isEmpty(aix[k]) ) continue;
+				int blen = a.size(aix[k]);
+				int bpos = a.pos(aix[k]);
+				int[] bix = a.indexes(aix[k]);
+				double aval = avals[k];
+				double[] bvals = a.values(aix[k]);
+				if( ldense ) { //dense aggregation
+					for( int j=bpos; j<bpos+blen; j++ )
+						tmp[bix[j]] += aval * bvals[j];
+				}
+				else { //sparse aggregation
+					c.allocate(i, nnz1);
+					for( int j=bpos; j<bpos+blen; j++ )
+						c.add(i, bix[j], aval * bvals[j]);
+					c.compact(i); //conditional compaction
+				}
+			}
+			if( ldense ) { //copy dense tmp row
+				int nnz2 = UtilFunctions.computeNnz(tmp, 0, n);
+				c.allocate(i, nnz2); //avoid reallocation
+				for( int j=0; j<n; j++ )
+					c.append(i, j, tmp[j]);
+			}
+		}
+		//recompute non-zero for single-threaded
+		if( rl == 0 && ru == m1.rlen ){
+			ret.recomputeNonZeros();
+		}
+	}
+	
+	/**
+	 * Ultra sparse kernel with guaranteed sparse output.
+	 * 
+	 * @param m1 Left side ultra sparse matrix
+	 * @param m2 Right side Matrix Sparse or Dense
+	 * @param ret Sparse output matrix
+	 * @param rl Row start
+	 * @param ru Row end
+	 */
+	private static void matrixMultUltraSparseLeft(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, int rl, int ru) {
+		final int m  = m1.rlen;
+		final int n  = m2.clen;
+
+		SparseBlock a = m1.sparseBlock;
+		SparseBlockMCSR c = (SparseBlockMCSR) ret.sparseBlock;
+		boolean rightSparse = m2.sparse;
+
+		if(rightSparse)
+			matrixMultUltraSparseSparseSparseLeft( a, m2.sparseBlock, c, m, n , rl, ru);
+		else
+			matrixMultUltraSparseDenseSparseLeftRow(a, m2.denseBlock, c, m, n, rl, ru);
+		
+		if( rl == 0 && ru == m ){
+			ret.recomputeNonZeros();
+		}
+	}
+
+	private static void matrixMultUltraSparseDenseSparseLeftRow(SparseBlock a, DenseBlock b, SparseBlockMCSR c, int m, int n,
+		int rl, int ru) {
+		for(int i = rl; i < ru; i++) {
+			if(a.isEmpty(i))
+				continue;
+			final int apos = a.pos(i);
+			final int alen = a.size(i);
+			final int[] aixs = a.indexes(i);
+			final double[] avals = a.values(i);
+			if(alen == 1) 
+				matrixMultUltraSparseDenseSparseLeftRowOneNonZero(i, aixs[apos], avals[apos], b, c, m, n);
+			else  
+				matrixMultUltraSparseDenseSparseLeftRowGeneric(i, apos, alen, aixs, avals, b, c, m, n);
+		}
+	}
+
+	private static void matrixMultUltraSparseDenseSparseLeftRowOneNonZero(int i, int aix, double aval, DenseBlock b,
+		SparseBlockMCSR c, int m, int n) {
+		final double[] bvals = b.values(aix);
+		final int  bix = b.pos(aix);
+		final int lnnz = UtilFunctions.computeNnz(bvals, bix, n);
+		if(lnnz == 0)
+			return;
+		else if(lnnz == 1) {
+			for(int j = 0; j < n; j++) {
+				final double bv = bvals[bix + j];
+				if(bv != 0) {
+					SparseRowScalar s = new SparseRowScalar(j, bv * aval);
+					c.set(i, s, false);
+					break;
+				}
+			}
+		}
+		else {
+			setSparseRowVector(lnnz, aval, bvals, bix, n, i, c);
+		}
+	}
+
+	private static void setSparseRowVector(int lnnz, double aval, double[] bvals, int bix, int n, int i, SparseBlockMCSR c) {
+		final double[] vals = new double[lnnz];
+		final int[] idx = new int[lnnz];
+		for(int j = 0, o = 0; j < n; j++) {
+			final double bv = bvals[bix + j];
+			if(bv != 0) {
+				vals[o] = bv * aval;
+				idx[o] = j;
+				o++;
+			}
+		}
+		SparseRowVector v = new SparseRowVector(vals, idx);
+		c.set(i, v, false);
+	}
+
+	private static void matrixMultUltraSparseDenseSparseLeftRowGeneric(int i, int apos, int alen, int[] aixs,
+		double[] avals, DenseBlock b, SparseBlock c, int m, int n){
+			for(int k = apos; k < apos + alen; k++) {
+					final double aval = avals[k];
+					final int aix = aixs[k];
+					for(int j = 0; j < n; j++) {
+						double cvald = aval * b.get(aix, j);
+						if(cvald != 0)
+							c.add(i, j,  cvald);
+					}
+				}
+		}
+
+	private static void matrixMultUltraSparseSparseSparseLeft(SparseBlock a, SparseBlock b, SparseBlockMCSR c, int m,
+		int n, int rl, int ru) {
+		for(int i = rl; i < ru; i++) {
+			if(a.isEmpty(i))
+				continue;
+			final int apos = a.pos(i);
+			final int alen = a.size(i);
+			final int[] aixs = a.indexes(i);
+			final double[] avals = a.values(i);
+			if(alen == 1)
+				matrixMultUltraSparseSparseSparseLeftRowOneNonZero(i, aixs[apos], avals[apos], b, c, m, n);
+			else // GENERAL CASE
+				matrixMultUltraSparseSparseSparseLeftRowGeneric(i, apos, alen, aixs, avals, b, c, m, n);
+		}
+	}
+
+	private static void matrixMultUltraSparseSparseSparseLeftRowOneNonZero(int i, int aix, double aval, SparseBlock b, SparseBlockMCSR c, int m, int n){
+		if(!b.isEmpty(aix)) {
+			c.set(i, b.get(aix), true);
+			// optional scaling if not pure selection
+			if(aval != 1) {
+				if(c.get(i) instanceof SparseRowScalar) {
+					SparseRowScalar sv = (SparseRowScalar) c.get(i);
+					c.set(i, new SparseRowScalar(sv.getIndex(), sv.getValue() * aval), false);
+				}
+				else
+					vectMultiplyInPlace(aval, c.values(i), c.pos(i), c.size(i));
+			}
+		}
+	}
+
+	private static void matrixMultUltraSparseSparseSparseLeftRowGeneric(int i, int apos, int alen, int[] aixs,
+		double[] avals, SparseBlock b, SparseBlockMCSR c, int m, int n) {
+		for(int k = apos; k < apos + alen; k++) {
+			final int aix = aixs[k];
+			if(b.isEmpty(aix))
+				continue;
+			final double aval = avals[k];
+			final int bpos = b.pos(aix);
+			final int blen = b.size(aix) + bpos;
+			final int[] bix = b.indexes(aix);
+			final double[] bvals = b.values(aix);
+			if(!c.isAllocated(i))
+				c.allocate(i, Math.max(blen - bpos, 2)); // guarantee a vector for efficiency
+			final SparseRowVector v = (SparseRowVector) c.get(i);
+			if(v.size() == n){ // If output row is dense already
+				final double[] vvals = v.values();
+				for(int bo = bpos; bo < blen; bo++) 
+					vvals[bix[bo]] += aval * bvals[bo];
+			}
+			else
+				for(int bo = bpos; bo < blen; bo++) 
+					v.add(bix[bo], aval * bvals[bo]);
+			
+		}
+	}
+
+	
+	private static void matrixMultUltraSparseRight(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, int rl, int ru) {
+		if(ret.isInSparseFormat()){
+			if(m1.isInSparseFormat())
+				matrixMultUltraSparseRightSparseMCSRLeftSparseOut(m1, m2, ret, rl, ru);
+			else
+				matrixMultUltraSparseRightDenseLeftSparseOut(m1, m2, ret, rl, ru);
+		}
+		else if(ret.getDenseBlock().isContiguous())
+			matrixMultUltraSparseRightDenseOut(m1, m2, ret, rl, ru);
+		else
+			matrixMultUltraSparseRightGeneric(m1, m2, ret, rl, ru);
+	}
+
+	private static void matrixMultUltraSparseRightDenseOut(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, int rl, int ru) {
+		final int cd = m1.clen;
+		final int kd = m2.clen;
+		double[] retV = ret.getDenseBlockValues();
+
+		// right is ultra-sparse (KJI)
+		final SparseBlock b = m2.sparseBlock;
+		for(int k = 0; k < cd; k++) {
+			if(b.isEmpty(k))
+				continue;
+			final int bpos = b.pos(k);
+			final int blen = b.size(k);
+			final int[] bixs = b.indexes(k);
+			final double[] bvals = b.values(k);
+			for(int j = bpos; j < bpos + blen; j++) {
+				double bval = bvals[j];
+				int bix = bixs[j];
+				for(int i = rl; i < ru; i++) 
+					retV[i *kd + bix] += bval * m1.get(i, k);
+			}
+		}
+	}
+
+	private static void matrixMultUltraSparseRightSparseMCSRLeftSparseOut(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, int rl, int ru) {
+		final int cd = m1.clen;
+
+		// right is ultra-sparse (KJI)
+		final SparseBlock a = m1.sparseBlock;
+		final SparseBlock b = m2.sparseBlock;
+		final SparseBlockMCSR r = (SparseBlockMCSR) ret.sparseBlock;
+		
+		for(int k = 0; k < cd; k++) {
+			if(b.isEmpty(k))
+				continue;
+			final int bpos = b.pos(k);
+			final int blen = b.size(k);
+			final int[] bixs = b.indexes(k);
+			final double[] bvals = b.values(k);
+			for(int i = rl; i < ru; i++) {
+				if(a.isEmpty(i))
+					continue;
+				final double cvald = a.get(i, k);
+				// since the left side is sparse as well, it is likely that this value is zero.
+				// therefore we reorder the loop to access the value here.
+				if(cvald != 0) {
+					for(int j = bpos; j < bpos + blen; j++) {
+						final int bix = bixs[j];
+						final double bval = bvals[j];
+						final double cval = r.get(i, bix);
+						r.set(i, bix, cval + bval * cvald);
+					}
+				}
+			}
+		}
+	}
+
+	private static void matrixMultUltraSparseRightDenseLeftSparseOut(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, int rl, int ru) {
+		final int cd = m1.clen;
+		final DenseBlock  a = m1.denseBlock;
+		final SparseBlock b = m2.sparseBlock;
+		final SparseBlockMCSR c = (SparseBlockMCSR) ret.sparseBlock;
+
+		for(int k = 0; k < cd; k++){
+			if(b.isEmpty(k))
+				continue; // skip emptry rows right side.
+			final int bpos = b.pos(k);
+			final int blen = b.size(k);
+			final int[] bixs = b.indexes(k);
+			final double[] bvals = b.values(k);
+			for(int i = rl; i < ru; i++) 
+				mmDenseMatrixSparseRow(bpos, blen, bixs, bvals, k, i, a, c);
+		}
+	}
+
+	private static void mmDenseMatrixSparseRow(int bpos, int blen, int[] bixs, double[] bvals, int k, int i,
+		DenseBlock a, SparseBlockMCSR c) {
+		final double[] aval = a.values(i);
+		final int apos = a.pos(i);
+		if(!c.isAllocated(i))
+			c.allocate(i, Math.max(blen, 2));
+		final SparseRowVector srv = (SparseRowVector) c.get(i); // guaranteed
+		for(int j = bpos; j < bpos + blen; j++) { // right side columns
+			final int bix = bixs[j];
+			final double bval = bvals[j];
+			srv.add(bix, bval * aval[apos + k]);
+		}
+
+	}
+
+	private static void matrixMultUltraSparseRightGeneric(MatrixBlock m1, MatrixBlock m2, MatrixBlock ret, int rl, int ru) {
+		final int cd = m1.clen;
+
+		// right is ultra-sparse (KJI)
+		final SparseBlock b = m2.sparseBlock;
+		for(int k = 0; k < cd; k++) {
+			if(b.isEmpty(k))
+				continue;
+			final int bpos = b.pos(k);
+			final int blen = b.size(k);
+			final int[] bixs = b.indexes(k);
+			final double[] bvals = b.values(k);
+			for(int j = bpos; j < bpos + blen; j++) {
+				double bval = bvals[j];
+				int bix = bixs[j];
+				for(int i = rl; i < ru; i++) {
+					double cvald = bval * m1.get(i, k);
+					if(cvald != 0) {
+						double cval = ret.get(i, bix);
+						ret.set(i, bix, cval + cvald);
+					}
+				}
+			}
+		}
+	}
+
+	private static void matrixMultChainDense(MatrixBlock mX, MatrixBlock mV, MatrixBlock mW, MatrixBlock ret, ChainType ct, int rl, int ru) 
+	{
+		DenseBlock a = mX.getDenseBlock();
+		double[] b = mV.getDenseBlockValues();
+		double[] w = (mW!=null) ? mW.getDenseBlockValues() : null;
+		double[] c = ret.getDenseBlockValues();
+		final int cd = mX.clen; //features in X
+		boolean weights = (ct == ChainType.XtwXv);
+		boolean weights2 = (ct == ChainType.XtXvy);
+		
+		//temporary array for cache blocking
+		//(blocksize chosen to fit b+v in L2 (256KB) for default 1k blocks)
+		final int blocksizeI = 24; // constraint: factor of 4
+		final int blocksizeJ = 1024;
+		double[] tmp = new double[blocksizeI];
+		final int bn = (ru-rl) % blocksizeI;
+		
+		//compute rest (not aligned to blocksize)
+		for( int i=rl; i < rl+bn; i++ ) {
+			double[] avals = a.values(i);
+			int aix = a.pos(i);
+			double val = (b == null) ? 0 :
+				dotProduct(avals, b, aix, 0, cd);
+			val *= (weights) ? w[i] : 1;
+			val -= (weights2) ? w[i] : 0;
+			vectMultiplyAdd(val, avals, c, aix, 0, cd);
+		}
+		
+		//blockwise mmchain computation
+		for( int bi=rl+bn; bi < ru; bi+=blocksizeI ) 
+		{
+			//compute 1st matrix-vector for row block
+			Arrays.fill(tmp, 0);
+			if( b != null ) {
+				for( int bj=0; bj<cd; bj+=blocksizeJ ) {
+					int bjmin = Math.min(cd-bj, blocksizeJ);
+					for( int i=0; i < blocksizeI; i++ )
+						tmp[i] += dotProduct(a.values(bi+i), b, a.pos(bi+i,bj), bj, bjmin);
+				}
+			}
+			
+			//multiply/subtract weights (in-place), if required
+			if( weights ) 
+				vectMultiply(w, tmp, bi, 0, blocksizeI);
+			else if( weights2 )
+				vectSubtract(w, tmp, bi, 0, blocksizeI);
+			
+			//compute 2nd matrix vector for row block and aggregate
+			for( int bj = 0; bj<cd; bj+=blocksizeJ ) {
+				int bjmin = Math.min(cd-bj, blocksizeJ);
+				if( a.isContiguous() ) {
+					double[] avals = a.values(0);
+					for( int i=0, aix=bi*cd+bj; i<blocksizeI; i+=4, aix+=4*cd )
+						vectMultiplyAdd4(tmp[i], tmp[i+1], tmp[i+2], tmp[i+3],
+							avals, c, aix, aix+cd, aix+2*cd, aix+3*cd, bj, bjmin);
+				}
+				else {
+					for( int i=0; i<blocksizeI; i++ )
+						vectMultiplyAdd(tmp[i], a.values(bi+i), c, a.pos(bi+i,bj), bj, bjmin);
+				}
+			}
+		}
+	}
+
+	private static void matrixMultChainSparse(MatrixBlock mX, MatrixBlock mV, MatrixBlock mW, MatrixBlock ret, ChainType ct, int rl, int ru) 
+	{
+		final SparseBlock a = mX.sparseBlock;
+		final double[] b = mV.getDenseBlockValues();
+		final double[] w = (mW != null) ? mW.getDenseBlockValues() : null;
+		final double[] c = ret.getDenseBlockValues();
+		
+		//row-wise mmchain computation
+		if(ct == ChainType.XtXvy)
+			matrixMultChainSparseXtXvy(a, b, w, c, rl, ru);
+		else if(b != null) {
+			if(ct == ChainType.XtwXv)
+				matrixMultChainSparseXtwXv(a, b, w, c, rl, ru);
+			else // XtXv
+				matrixMultChainSparseXtXv(a, b, c, rl, ru);
+		}
+	}
+
+	private static final void matrixMultChainSparseXtXv(SparseBlock a, double[] b,  double[] c, int rl,
+		int ru) {
+		for(int i = rl; i < ru; i++) {
+			if(a.isEmpty(i))
+				continue;
+			final int apos = a.pos(i);
+			final int alen = a.size(i);
+			final int[] aix = a.indexes(i);
+			final double[] avals = a.values(i);
+
+			// compute 1st matrix-vector dot product
+			final double val = dotProduct(avals, b, aix, apos, 0, alen);
+
+			// compute 2nd matrix vector and aggregate
+			if(val != 0)
+				vectMultiplyAdd(val, avals, c, aix, apos, 0, alen);
+		}
+	}
+
+	private static final void matrixMultChainSparseXtwXv(SparseBlock a, double[] b, double[] w, double[] c, int rl,
+		int ru) {
+		for(int i = rl; i < ru; i++) {
+			if(w[i] == 0 || a.isEmpty(i))
+				continue;
+			final int apos = a.pos(i);
+			final int alen = a.size(i);
+			final int[] aix = a.indexes(i);
+			final double[] avals = a.values(i);
+			// compute 1st matrix-vector dot product
+			double val = dotProduct(avals, b, aix, apos, 0, alen) * w[i];
+
+			// compute 2nd matrix vector and aggregate
+			if(val != 0)
+				vectMultiplyAdd(val, avals, c, aix, apos, 0, alen);
+		}
+	}
+
+
+	private static final void matrixMultChainSparseXtXvy(SparseBlock a, double[] b, double[] w, double[] c, int rl,
+		int ru) {
+		if(b == null && w == null) {// early abort
+			return;
+		}
+		else if(b == null && w != null) { // short case with empty B.
+			for(int i = rl; i < ru; i++) {
+				final double val = -w[i];
+				if(val != 0 && !a.isEmpty(i)) {
+					final int apos = a.pos(i);
+					final int alen = a.size(i);
+					final int[] aix = a.indexes(i);
+					final double[] avals = a.values(i);
+					vectMultiplyAdd(val, avals, c, aix, apos, 0, alen);
+				}
+			}
+		}
+		else { // case XtXvy
+			// row-wise mmchain computation
+			for(int i = rl; i < ru; i++) {
+				if(a.isEmpty(i))
+					continue;
+				final int apos = a.pos(i);
+				final int alen = a.size(i);
+				final int[] aix = a.indexes(i);
+				final double[] avals = a.values(i);
+
+				// compute 1st matrix-vector dot product
+				double val = dotProduct(avals, b, aix, apos, 0, alen);
+
+				// multiply/subtract weights, if required
+				if(w != null)
+					val -= w[i];
+
+				// compute 2nd matrix vector and aggregate
+				if(val != 0)
+					vectMultiplyAdd(val, avals, c, aix, apos, 0, alen);
+			}
+		}
+	}
+
+	private static void matrixMultTransposeSelfDense( MatrixBlock m1, MatrixBlock ret, boolean leftTranspose, int rl, int ru ) {
+		//2) transpose self matrix multiply dense
+		// (compute only upper-triangular matrix due to symmetry)
+		DenseBlock a = m1.getDenseBlock();
+		DenseBlock c = ret.getDenseBlock();
+		int m = m1.rlen;
+		int n = m1.clen;
+		
+		if( leftTranspose ) // t(X)%*%X
+		{
+			if( LOW_LEVEL_OPTIMIZATION )
+			{
+				if( n==1 ) //VECTOR (col)
+				{
+					double[] avals = a.valuesAt(0);
+					c.set(0, 0, dotProduct(avals, avals, m));
+				}
+				else //MATRIX
+				{
+					//1) Unrolled inner loop (for better instruction-level parallelism)
+					//2) Blocked execution (for less cache trashing in parallel exec)
+					//3) Asymmetric block sizes (for less misses in inner loop, yet blocks in L1/L2)
+					
+					final int blocksizeI = 32; //64//256KB c block (typical L2 size per core), 32KB a block
+					final int blocksizeK = 24; //64//256KB b block (typical L2 size per core), used while read 512B of a / read/write 4KB of c 
+					final int blocksizeJ = 1024; //512//4KB (typical main-memory page size), for scan
+
+					//temporary arrays (nnz a, b index)
+					double[] ta = new double[ blocksizeK ];
+					int[]  tbi  = new int[ blocksizeK ];
+					
+					final int mx = ru;
+					final int cdx = m;
+					final int nx = n;
+					
+					//blocked execution
+					for( int bi = rl; bi < mx; bi+=blocksizeI ) //from bi due to symmetry
+						for( int bk = 0, bimin = Math.min(mx, bi+blocksizeI); bk < cdx; bk+=blocksizeK )
+							for( int bj = bi, bkmin = Math.min(cdx, bk+blocksizeK); bj < nx; bj+=blocksizeJ )
+							{
+								int bklen = bkmin-bk;
+								int bjlen = Math.min(nx, bj+blocksizeJ)-bj;
+								
+								//core sub block matrix multiplication
+								for( int i = bi; i < bimin; i++) 
+								{
+									double[] cvals = c.values(i);
+									int cixj = c.pos(i, bj);
+									
+									if( a.isContiguous(bk, bkmin-1) ) {
+										double[] avals = a.values(bk);
+										int aixi = a.pos(bk, i);
+										int bkpos = a.pos(bk, bj);
+										
+										//determine nnz of a (for sparsity-aware skipping of rows)
+										int knnz = copyNonZeroElements(avals, aixi, bkpos, n, nx, ta, tbi, bklen);
+										
+										//rest not aligned to blocks of 4 rows
+										final int bn = knnz % 4;
+										switch( bn ){
+											case 1: vectMultiplyAdd(ta[0], avals, cvals, tbi[0], cixj, bjlen); break;
+											case 2: vectMultiplyAdd2(ta[0],ta[1], avals, cvals, tbi[0], tbi[1], cixj, bjlen); break;
+											case 3: vectMultiplyAdd3(ta[0],ta[1],ta[2], avals, cvals, tbi[0], tbi[1],tbi[2], cixj, bjlen); break;
+										}
+										
+										//compute blocks of 4 rows (core inner loop)
+										for( int k = bn; k<knnz; k+=4 ){
+											vectMultiplyAdd4( ta[k], ta[k+1], ta[k+2], ta[k+3], avals, cvals,
+												tbi[k], tbi[k+1], tbi[k+2], tbi[k+3], cixj, bjlen );
+										}
+									}
+									else {
+										for( int k = bk; k<bkmin; k++ ) {
+											double[] avals = a.values(bk);
+											int aix = a.pos(bk, i);
+											if( avals[aix] != 0 )
+												vectMultiplyAdd( avals[aix], a.values(k),
+													cvals, a.pos(k, bj), cixj, bjlen );
+										}
+									}
+								}
+							}
+				}
+			}
+			else {
+				for( int k = 0; k < m; k++ ) {
+					double[] avals = a.values(k);
+					int aix = a.pos(k);
+					for( int i = rl; i < ru; i++ ) {
+						double[] cvals = c.values(i);
+						int cix = c.pos(i);
+						double val = avals[ aix+i ];
+						if( val != 0 ) {
+							for(int j = i; j < n; j++) //from i due to symmetry
+								cvals[ cix+j ] += val * avals[ aix+j ];
+						}
+					}
+				}
+			}
+		}
+		else // X%*%t(X)
+		{
+			if(LOW_LEVEL_OPTIMIZATION)
+			{
+				if( m==1 ) //VECTOR
+				{
+					double[] avals = a.valuesAt(0);
+					c.set(0, 0, dotProduct(avals, avals, n));
+				}
+				else //MATRIX
+				{
+					//algorithm: scan c, foreach ci,j: scan row of a and t(a) (IJK)
+				
+					//1) Unrolled inner loop, for better ILP
+					//2) Blocked execution, for less cache trashing in parallel exec 
+					//   (we block such that lhs, rhs, and output roughly fit into L2, output in L1)
+					//3) Asymmetric block sizes and exploitation of result symmetry
+					int blocksizeK = 1024; //two memory pages for sufficiently long scans
+					int blocksizeIJ = L2_CACHESIZE / 8 / blocksizeK / 2 - 1; //15
+				
+					//blocked execution over IKJ (lhs/rhs in L2, output in L1)
+					for( int bi = rl; bi<ru; bi+=blocksizeIJ ) 
+						for( int bk = 0, bimin = Math.min(ru, bi+blocksizeIJ); bk<n; bk+=blocksizeK )
+							for( int bj = bi, bklen = Math.min(blocksizeK, n-bk); bj<m; bj+=blocksizeIJ ) {
+								//core tsmm block operation (15x15 vectors of length 1K elements)
+								int bjmin = Math.min(m, bj+blocksizeIJ);
+								for( int i=bi; i<bimin; i++ ) {
+									final int bjmax = Math.max(i,bj); //from i due to symmetry
+									double[] avals = a.values(i), cvals = c.values(i);
+									int aix = a.pos(i, bk), cix = c.pos(i);
+									for(int j=bjmax; j <bjmin; j++) 
+										cvals[ cix+j ] += dotProduct(avals, a.values(j), aix, a.pos(j, bk), bklen);
+								}
+							}
+				}
+			}
+			else
+			{
+				for( int i = rl; i < ru; i++ ) {
+					double[] avals1 = a.values(i), cvals = c.values(i);
+					int aix1 = a.pos(i), cix = c.pos(i);
+					for(int j = i; j < m; j++) { //from i due to symmetry
+						double[] avals2 = a.values(j);
+						int aix2 = a.pos(j);
+						double val = 0;
+						for(int k = 0; k < n; k++)
+							val += avals1[aix1+k] * avals2[aix2+k];
+						cvals[cix+j] = val;
+					}
+				}
+			}
+		}
+	}
+
+	private static void matrixMultTransposeSelf(MatrixBlock m1, MatrixBlock m1t, MatrixBlock ret, boolean leftTranspose, int rl, int ru) {
+		if(m1.sparse && ret.sparse) {
+			if( m1t == null )
+				matrixMultTransposeSelfUltraSparse(m1, ret, leftTranspose, rl, ru);
+			else
+				matrixMultTransposeSelfUltraSparse2(m1, m1t, ret, leftTranspose, rl, ru);
+		}
+		else if( m1.sparse )
+			matrixMultTransposeSelfSparse(m1, ret, leftTranspose, rl, ru);
+		else 
+			matrixMultTransposeSelfDense(m1, ret, leftTranspose, rl, ru );
+	}
+	
+	private static void matrixMultTransposeSelfSparse( MatrixBlock m1, MatrixBlock ret, boolean leftTranspose, int rl, int ru ) {
+		//2) transpose self matrix multiply sparse
+		// (compute only upper-triangular matrix due to symmetry)
+		SparseBlock a = m1.sparseBlock;
+		DenseBlock c = ret.getDenseBlock();
+		int m = m1.rlen;
+		
+		if( leftTranspose ) // t(X)%*%X 
+		{
+			//only general case (because vectors always dense)
+			//algorithm: scan rows, foreach row self join (KIJ)
+			if( LOW_LEVEL_OPTIMIZATION ) {
+				final int n = m1.clen;
+				final int arlen = a.numRows();
+				for( int r=0; r<arlen; r++ ) {
+					if( a.isEmpty(r) ) continue;
+					final int alen = a.size(r);
+					final double[] avals = a.values(r);
+					final int apos = a.pos(r);
+					if( alen == n ) { //dense row
+						for (int i = rl; i < ru; i++){
+							double[] cvals = c.values(i);
+							int cix = c.pos(i);
+							double val = avals[i + apos];
+							for(int j = i; j < m1.clen; j++)
+								cvals[cix + j] +=val * avals[j + apos];
+						}
+					}
+					else { //non-full sparse row
+						int[] aix = a.indexes(r);
+						int rlix = (rl==0) ? 0 : a.posFIndexGTE(r, rl);
+						rlix = (rlix>=0) ? apos+rlix : apos+alen;
+						int len = apos + alen;
+						for(int i = rlix; i < len && aix[i] < ru; i++)
+							vectMultiplyAdd(avals[i], avals, c.values(aix[i]), aix, i, c.pos(aix[i]), len - i);
+					}
+				}
+			}
+			else
+			{
+				for( int r=0; r<a.numRows(); r++ ) {
+					if( a.isEmpty(r) ) continue;
+					int apos = a.pos(r);
+					int alen = a.size(r);
+					int[] aix = a.indexes(r);
+					double[] avals = a.values(r);
+					int rlix = (rl==0) ? 0 : a.posFIndexGTE(r, rl);
+					rlix = (rlix>=0) ? apos+rlix : apos+alen;
+					for(int i = rlix; i < apos+alen && aix[i]<ru; i++) {
+						double val = avals[i];
+						if( val != 0 ) {
+							double[] cvals = c.values(aix[i]);
+							int cix = c.pos(aix[i]);
+							for(int j = i; j < apos+alen; j++)
+								cvals[cix+aix[j]] += val * avals[j];
+						}
+					}
+				}
+			}
+		}
+		else // X%*%t(X)
+		{
+			if( m==1 ) //VECTOR 
+			{
+				if( !m1.sparseBlock.isEmpty(0) ) {
+					int alen = m1.sparseBlock.size(0); //pos always 0
+					double[] avals = a.values(0);
+					c.set(0, 0, dotProduct(avals, avals, alen));
+				}
+			}
+			else //MATRIX
+			{
+				//note: reorg to similar layout as t(X)%*%X because faster than 
+				//direct computation with IJK (no dependencies/branches in inner loop)
+				//see preprocessMatrixMultTransposeSelf m1<-tmpBlock
+				m = m1.clen;
+				
+				//algorithm: scan rows, foreach row self join (KIJ)
+				if( LOW_LEVEL_OPTIMIZATION )
+				{
+					int arlen = a.numRows();
+					for( int r=0; r<arlen; r++ ) {
+						if( a.isEmpty(r) ) continue;
+						int apos = a.pos(r);
+						int alen = a.size(r);
+						int[] aix = a.indexes(r);
+						double[] avals = a.values(r);
+						int rlix = (rl==0) ? 0 : a.posFIndexGTE(r, rl);
+						rlix = (rlix>=0) ? apos+rlix : apos+alen;
+						for(int i = rlix; i < apos+alen && aix[i]<ru; i++) {
+							double val = avals[i];
+							if( val != 0 )
+								vectMultiplyAdd(val, avals, c.values(aix[i]),
+									aix, i, c.pos(aix[i]), alen-i);
+						}
+					}
+				}
+				else
+				{
+					for( int r=0; r<a.numRows(); r++ ) {
+						if( a.isEmpty(r) ) continue;
+						int apos = a.pos(r);
+						int alen = a.size(r);
+						int[] aix = a.indexes(r);
+						double[] avals = a.values(r);
+						int rlix = (rl==0) ? 0 : a.posFIndexGTE(r,rl);
+						rlix = (rlix>=0) ? apos+rlix : apos+alen;
+						for(int i = rlix; i < apos+alen && aix[i]<ru; i++) {
+							double val = avals[i];
+							if( val != 0 ) {
+								double[] cvals = c.values(aix[i]);
+								int cix = c.pos(aix[i]);
+								for( int j = i; j < alen; j++ )
+									cvals[cix+aix[j]] += val * avals[j];
+							}
+						}
+					}
+				}
+			}
+		}
+	}
+	
+	private static void matrixMultTransposeSelfUltraSparse(MatrixBlock m1, MatrixBlock ret, boolean leftTranspose,
+		int rl, int ru) {
+		SparseBlock a = m1.sparseBlock;
+		SparseBlock c = ret.sparseBlock;
+		int m = m1.rlen;
+
+		if(leftTranspose) {
+			// Operation t(X)%*%X, sparse input and output
+			for(int i=0; i<m; i++)
+				c.allocate(i, 8*SparseRowVector.initialCapacity);
+			SparseRow[] sr = ((SparseBlockMCSR) c).getRows();
+			for( int r=0; r<a.numRows(); r++ ) {
+				if( a.isEmpty(r) ) continue;
+				final int alen = a.size(r);
+				final double[] avals = a.values(r);
+				final int apos = a.pos(r);
+				int[] aix = a.indexes(r);
+				int rlix = (rl==0) ? 0 : a.posFIndexGTE(r, rl);
+				if(rlix>=0) {
+					int len = apos + alen;
+					for(int i = rlix; i < len && aix[i] < ru; i++) {
+						for (int k = a.posFIndexGTE(r, aix[i]); k < len; k++) {
+							sr[aix[i]].add(c.pos(k) + aix[k], avals[i] * avals[k]);
+						}
+					}
+				}
+			}
+		}
+		else {
+			// Operation X%*%t(X), sparse input and output
+			final int blocksize = 256;
+			for(int bi=rl; bi<ru; bi+=blocksize) { //blocking rows in X
+				int bimin = Math.min(bi+blocksize, ru);
+				for(int i=bi; i<bimin; i++) //preallocation
+					if( !a.isEmpty(i) )
+						c.allocate(i, 8*SparseRowVector.initialCapacity); //heuristic
+				for(int bj=bi; bj<m; bj+=blocksize ) { //blocking cols in t(X)
+					int bjmin = Math.min(bj+blocksize, m);
+					for(int i=bi; i<bimin; i++) { //rows in X
+						if( a.isEmpty(i) ) continue;
+						int apos = a.pos(i);
+						int alen = a.size(i);
+						int[] aix = a.indexes(i);
+						double[] avals = a.values(i);
+						for(int j=Math.max(bj,i); j<bjmin; j++) { //cols in t(X)
+							if( a.isEmpty(j) ) continue;
+							int bpos = a.pos(j);
+							int blen = a.size(j);
+							int[] bix = a.indexes(j);
+							double[] bvals = a.values(j);
+
+							//compute sparse dot product and append
+							double v = dotProduct(avals, aix, apos, alen, bvals, bix, bpos, blen);
+							if( v != 0 )
+								c.append(i, j, v);
+						}
+					}
+				}
+			}
+		}
+	}
+	
+	//alternative matrixMultTransposeSelfUltraSparse2 w/ IKJ iteration order and sparse updates
+	private static void matrixMultTransposeSelfUltraSparse2( MatrixBlock m1, MatrixBlock m1t, MatrixBlock ret, boolean leftTranspose, int rl, int ru ) {
+		SparseBlock a;
+		SparseBlock b;
+		if( leftTranspose ) {
+			a = m1t.sparseBlock;
+			b = m1.sparseBlock;
+		}
+		else {
+			a = m1.sparseBlock;
+			b = m1t.sparseBlock;
+		}
+
+		// Operation X%*%t(X), sparse input and output
+		SparseBlock c = ret.sparseBlock;
+		for(int i=rl; i<ru; i++) { //rows in X
+			if( a.isEmpty(i) ) continue;
+			int apos = a.pos(i);
+			int alen = a.size(i);
+			int[] aix = a.indexes(i);
+			double[] avals = a.values(i);
+			//aggregate arow %*% B into output
+			for(int k=apos; k<apos+alen; k++) {
+				int aixk = aix[k];
+				double aval = avals[k];
+				if( b.isEmpty(aixk) ) continue;
+				int bpos = b.pos(aixk);
+				int bpos2 = b.posFIndexGTE(aixk, i);
+				if( bpos2 < 0 ) continue;
+				int blen = b.size(aixk);
+				int[] bix = b.indexes(aixk);
+				double[] bvals = b.values(aixk);
+				//sparse updates for ultra-sparse output
+				for(int k2 = bpos+bpos2; k2<bpos+blen; k2++) {
+					c.add(i, bix[k2], aval*bvals[k2]);
+				}
+			}
+		}
+	}
+
+	private static void matrixMultPermuteDense(MatrixBlock pm1, MatrixBlock m2, MatrixBlock ret1, MatrixBlock ret2, int rl, int ru) {
+		double[] a = pm1.getDenseBlockValues();
+		DenseBlock b = m2.getDenseBlock();
+		DenseBlock c = ret1.getDenseBlock();
+
+		final int n = m2.clen;
+		final int blen = ret1.getNumRows();
+		int lastblk = -1;
+		
+		for( int i=rl; i<ru; i++ ) {
+			//compute block index and in-block indexes
+			int pos = UtilFunctions.toInt( a[ i ]); //safe cast
+			if( pos > 0 ) { //selected row
+				int bpos = (pos-1) % blen;
+				int blk = (pos-1) / blen;
+				//allocate and switch to second output block
+				//(never happens in cp, correct for multi-threaded usage)
+				if( lastblk!=-1 && lastblk<blk ) {
+					ret2.sparse = false;
+					ret2.allocateDenseBlock();
+					c = ret2.getDenseBlock();
+				}
+				//memcopy entire dense row into target position
+				System.arraycopy(b.values(i), b.pos(i), c.values(bpos), c.pos(bpos), n);
+				lastblk = blk;
+			}
+		}
+	}
+
+	private static void matrixMultPermuteDenseSparse( MatrixBlock pm1, MatrixBlock m2, MatrixBlock ret1, MatrixBlock ret2, int rl, int ru)
+	{
+		double[] a = pm1.getDenseBlockValues(); //vector
+		DenseBlock b = m2.getDenseBlock();
+		SparseBlock c = ret1.sparseBlock;
+
+		final int n = m2.clen;
+		final int blen = ret1.getNumRows();
+		
+		int lastblk = -1;
+		for( int i=rl; i<ru; i++ ) {
+			//compute block index and in-block indexes
+			int pos = UtilFunctions.toInt( a[ i ]); //safe cast
+			if( pos > 0 ) { //selected row
+				double[] bvals = b.values(i);
+				int bix = b.pos(i);
+				int bpos = (pos-1) % blen;
+				int blk = (pos-1) / blen;
+				//allocate and switch to second output block
+				//(never happens in cp, correct for multi-threaded usage)
+				if( lastblk!=-1 && lastblk<blk ){ 
+					ret2.sparse = true;
+					ret2.rlen=ret1.rlen;
+					ret2.allocateSparseRowsBlock();
+					c = ret2.sparseBlock;
+				}
+				//append entire dense row into sparse target position
+				for( int j=0; j<n; j++ )
+					c.append(bpos, j, bvals[bix+j]);
+				lastblk = blk;
+			}
+		}
+	}
+
+	private static void matrixMultPermuteSparse( MatrixBlock pm1, MatrixBlock m2, MatrixBlock ret1, MatrixBlock ret2, int rl, int ru)
+	{
+		double[] a = pm1.getDenseBlockValues(); //vector
+		SparseBlock b = m2.sparseBlock;
+		SparseBlock c = ret1.sparseBlock;
+
+		final int blen = ret1.getNumRows();
+		
+		int lastblk = -1;
+		for( int i=rl; i<ru; i++ )  {
+			//compute block index and in-block indexes
+			int pos = UtilFunctions.toInt( a[ i ]); //safe cast
+			if( pos > 0 ) { //selected row
+				int bpos = (pos-1) % blen;
+				int blk = (pos-1) / blen;
+				//allocate and switch to second output block
+				//(never happens in cp, correct for multi-threaded usage)
+				if( lastblk!=-1 && lastblk<blk ){ 
+					ret2.sparse = true;
+					ret2.allocateSparseRowsBlock();
+					c = ret2.sparseBlock;
+				}
+				//memcopy entire sparse row into target position
+				c.set(bpos, b.get(i), true);
+				lastblk = blk;
+			}
+		}
+
+	}
+
+	private static void matrixMultWSLossDense(MatrixBlock mX, MatrixBlock mU, MatrixBlock mV, MatrixBlock mW, MatrixBlock ret, WeightsType wt, int rl, int ru)
+	{
+		DenseBlock x = mX.getDenseBlock();
+		DenseBlock u = mU.getDenseBlock();
+		DenseBlock v = mV.getDenseBlock();
+		DenseBlock w = (mW!=null)? mW.getDenseBlock() : null;
+		final int n = mX.clen;
+		final int cd = mU.clen;
+		double wsloss = 0;
+		
+		// approach: iterate over all cells of X 
+		//cache-conscious blocking: due to blocksize constraint (default 1000),
+		//a blocksize of 16 allows to fit blocks of UV into L2 cache (256KB) 
+					
+		final int blocksizeIJ = 16; //u/v block (max at typical L2 size) 
+		
+
+		//blocked execution
+		for( int bi = rl; bi < ru; bi+=blocksizeIJ ) {
+			int bimin = Math.min(ru, bi+blocksizeIJ);
+			for( int bj = 0; bj < n; bj+=blocksizeIJ ){
+				int bjmin = Math.min(n, bj+blocksizeIJ);
+				
+				// Pattern 1) sum (W * (X - U %*% t(V)) ^ 2) (post weighting)
+				if( wt==WeightsType.POST ) {
+					for( int i=bi; i<bimin; i++ ) {
+						double[] wvals = w.values(i), xvals = x.values(i), uvals = u.values(i);
+						int xix = x.pos(i), uix = u.pos(i);
+						for( int j=bj; j<bjmin; j++ ) {
+							double wij = wvals[xix+j];
+							if( wij != 0 ) {
+								double uvij = dotProduct(uvals, v.values(j), uix, v.pos(j), cd);
+								wsloss += wij*(xvals[xix+j]-uvij)*(xvals[xix+j]-uvij); //^2
+							}
+						}
+					}
+				}
+				// Pattern 1b) sum ((X!=0) * (X - U %*% t(V)) ^ 2) (post_nz weighting)
+				else if( wt==WeightsType.POST_NZ ) {
+					for( int i=bi; i<bimin; i++ ) {
+						double[] xvals = x.values(i), uvals = u.values(i);
+						int xix = x.pos(i), uix = u.pos(i);
+						for( int j=bj; j<bjmin; j++ ) {
+							double xij = xvals[xix+j];
+							if( xij != 0 ) {
+								double uvij = dotProduct(uvals, v.values(j), uix, v.pos(j), cd);
+								wsloss += (xij-uvij)*(xij-uvij); //^2
+							}
+						}
+					}
+				}
+				// Pattern 2) sum ((X - W * (U %*% t(V))) ^ 2) (pre weighting)
+				else if( wt==WeightsType.PRE ) {
+					for( int i=bi; i<bimin; i++ ) {
+						double[] wvals = w.values(i), xvals = x.values(i), uvals = u.values(i);
+						int xix = x.pos(i), uix = u.pos(i);
+						for( int j=bj; j<bjmin; j++ ) {
+							double wij = wvals[xix+j];
+							double uvij = 0;
+							if( wij != 0 )
+								uvij = dotProduct(uvals, v.values(j), uix, v.pos(j), cd);
+							wsloss += (xvals[xix+j]-wij*uvij)*(xvals[xix+j]-wij*uvij); //^2
+						}
+					}
+				}
+				// Pattern 3) sum ((X - (U %*% t(V))) ^ 2) (no weighting)
+				else if( wt==WeightsType.NONE ) {
+					for( int i=bi; i<bimin; i++ ) {
+						double[] xvals = x.values(i), uvals = u.values(i);
+						int xix = x.pos(i), uix = u.pos(i);
+						for( int j=bj; j<bjmin; j++) {
+							double uvij = dotProduct(uvals, v.values(j), uix, v.pos(j), cd);
+							wsloss += (xvals[xix+j]-uvij)*(xvals[xix+j]-uvij); //^2
+						}
+					}
+				}
+			}
+		}
+		ret.set(0, 0, wsloss);
+	}
+
+	private static void matrixMultWSLossSparseDense(MatrixBlock mX, MatrixBlock mU, MatrixBlock mV, MatrixBlock mW, MatrixBlock ret, WeightsType wt, int rl, int ru)
+	{
+		SparseBlock x = mX.sparseBlock;
+		SparseBlock w = (mW!=null)? mW.sparseBlock : null;
+		DenseBlock u = mU.getDenseBlock();
+		DenseBlock v = mV.getDenseBlock();
+		final int n = mX.clen; 
+		final int cd = mU.clen;
+		double wsloss = 0; 
+		
+		// Pattern 1) sum (W * (X - U %*% t(V)) ^ 2) (post weighting)
+		if( wt==WeightsType.POST ) {
+			// approach: iterate over W, point-wise in order to exploit sparsity
+			for( int i=rl; i<ru; i++ ) {
+				if( w.isEmpty(i) ) continue;
+				int wpos = w.pos(i);
+				int wlen = w.size(i);
+				int[] wix = w.indexes(i);
+				double[] wval = w.values(i);
+				double[] uvals = u.values(i);
+				int uix = u.pos(i);
+				if( w.isAligned(i, x) ) {
+					//O(n) where n is nnz in w/x 
+					double[] xval = x.values(i);
+					for( int k=wpos; k<wpos+wlen; k++ ) {
+						double uvij = dotProduct(uvals, v.values(wix[k]), uix, v.pos(wix[k]), cd);
+						wsloss += wval[k]*(xval[k]-uvij)*(xval[k]-uvij);
+					}
+				}
+				else {
+					//O(n log m) where n/m is nnz in w/x 
+					for( int k=wpos; k<wpos+wlen; k++ ) {
+						double xi = mX.get(i, wix[k]);
+						double uvij = dotProduct(uvals, v.values(wix[k]), uix, v.pos(wix[k]), cd);
+						wsloss += wval[k]*(xi-uvij)*(xi-uvij);
+					}
+				}
+			}
+		}
+		// Pattern 1b) sum ((X!=0) * (X - U %*% t(V)) ^ 2) (post weighting)
+		else if( wt==WeightsType.POST_NZ ) {
+			// approach: iterate over W, point-wise in order to exploit sparsity
+			// blocked over ij, while maintaining front of column indexes, where the
+			// blocksize is chosen such that we reuse each vector on average 8 times.
+			final int blocksizeIJ = (int) (8L*mX.rlen*mX.clen/mX.nonZeros); 
+			int[] curk = new int[blocksizeIJ];
+			
+			for( int bi=rl; bi<ru; bi+=blocksizeIJ ) {
+				int bimin = Math.min(ru, bi+blocksizeIJ);
+				//prepare starting indexes for block row
+				Arrays.fill(curk, 0); 
+				//blocked execution over column blocks
+				for( int bj=0; bj<n; bj+=blocksizeIJ ) {
+					int bjmin = Math.min(n, bj+blocksizeIJ);
+					for( int i=bi; i<bimin; i++ ) {
+						if( x.isEmpty(i) ) continue;
+						int xpos = x.pos(i);
+						int xlen = x.size(i);
+						int[] xix = x.indexes(i);
+						double[] xval = x.values(i), uvals = u.values(i);
+						int uix = u.pos(i);
+						int k = xpos + curk[i-bi];
+						for( ; k<xpos+xlen && xix[k]<bjmin; k++ ) {
+							double uvij = dotProduct(uvals, v.values(xix[k]), uix, v.pos(xix[k]), cd);
+							wsloss += (xval[k]-uvij)*(xval[k]-uvij);
+						}
+						curk[i-bi] = k - xpos;
+					}
+				}
+			}
+		}
+		// Pattern 2) sum ((X - W * (U %*% t(V))) ^ 2) (pre weighting)
+		else if( wt==WeightsType.PRE ) {
+			// approach: iterate over all cells of X maybe sparse and dense
+			// (note: tuning similar to pattern 3 possible but more complex)
+			for( int i=rl; i<ru; i++ ) {
+				double[] uvals = u.values(i);
+				int uix = u.pos(i);
+				for( int j=0; j<n; j++ ) {
+					double xij = mX.get(i, j);
+					double wij = mW.get(i, j);
+					double uvij = 0;
+					if( wij != 0 )
+						uvij = dotProduct(uvals, v.values(j), uix, v.pos(j), cd);
+					wsloss += (xij-wij*uvij)*(xij-wij*uvij);
+				}
+			}
+		}
+		// Pattern 3) sum ((X - (U %*% t(V))) ^ 2) (no weighting)
+		else if( wt==WeightsType.NONE ) {
+			//approach: use sparsity-exploiting pattern rewrite sum((X-(U%*%t(V)))^2) 
+			//-> sum(X^2)-sum(2*X*(U%*%t(V))))+sum((t(U)%*%U)*(t(V)%*%V)), where each
+			//parallel task computes sum(X^2)-sum(2*X*(U%*%t(V)))) and the last term
+			//sum((t(U)%*%U)*(t(V)%*%V)) is computed once via two tsmm operations.
+			
+			final int blocksizeIJ = (int) (8L*mX.rlen*mX.clen/mX.nonZeros); 
+			int[] curk = new int[blocksizeIJ];
+			
+			for( int bi=rl; bi<ru; bi+=blocksizeIJ ) {
+				int bimin = Math.min(ru, bi+blocksizeIJ);
+				//prepare starting indexes for block row
+				Arrays.fill(curk, 0); 
+				//blocked execution over column blocks
+				for( int bj=0; bj<n; bj+=blocksizeIJ ) {
+					int bjmin = Math.min(n, bj+blocksizeIJ);
+					for( int i=bi; i<bimin; i++ ) {
+						if( x.isEmpty(i) ) continue; 
+						int xpos = x.pos(i);
+						int xlen = x.size(i);
+						int[] xix = x.indexes(i);
+						double[] xval = x.values(i);
+						double[] uvals = u.values(i);
+						int uix = u.pos(i);
+						int k = xpos + curk[i-bi];
+						for( ; k<xpos+xlen && xix[k]<bjmin; k++ ) {
+							double xij = xval[k];
+							double uvij = dotProduct(uvals, v.values(xix[k]), uix, v.pos(xix[k]), cd);
+							wsloss += xij * xij - 2 * xij * uvij;
+						}
+						curk[i-bi] = k - xpos;
+					}
+				}
+			}
+		}
+		
+		ret.set(0, 0, wsloss);
+	}
+
+	private static void matrixMultWSLossGeneric (MatrixBlock mX, MatrixBlock mU, MatrixBlock mV, MatrixBlock mW, MatrixBlock ret, WeightsType wt, int rl, int ru)
+	{
+		final int n = mX.clen; 
+		final int cd = mU.clen;
+		double wsloss = 0;
+		
+		// Pattern 1) sum (W * (X - U %*% t(V)) ^ 2) (post weighting)
+		if( wt==WeightsType.POST )
+		{
+			// approach: iterate over W, point-wise in order to exploit sparsity
+			if( mW.sparse ) //SPARSE
+			{
+				SparseBlock w = mW.sparseBlock;
+				for( int i=rl; i<ru; i++ ) {
+					if( w.isEmpty(i) ) continue;
+					int wpos = w.pos(i);
+					int wlen = w.size(i);
+					int[] wix = w.indexes(i);
+					double[] wval = w.values(i);
+					for( int k=wpos; k<wpos+wlen; k++ ) {
+						double uvij = dotProductGeneric(mU, mV, i, wix[k], cd);
+						double xi = mX.get(i, wix[k]);
+						wsloss += wval[k]*(xi-uvij)*(xi-uvij);
+					}
+				}
+			}
+			else //DENSE
+			{
+				DenseBlock w = mW.getDenseBlock();
+				for( int i=rl; i<ru; i++ ) {
+					double[] wvals = w.values(i);
+					int wix = w.pos(i);
+					for( int j=0; j<n; j++)
+						if( wvals[wix+j] != 0 ) {
+							double uvij = dotProductGeneric(mU, mV, i, j, cd);
+							double xij = mX.get(i, j);
+							wsloss += wvals[wix+j]*(xij-uvij)*(xij-uvij);
+						}
+				}
+			}
+		}
+		// Pattern 1b) sum ((X!=0) * (X - U %*% t(V)) ^ 2) (post weighting)
+		else if( wt==WeightsType.POST_NZ )
+		{
+			// approach: iterate over W, point-wise in order to exploit sparsity
+			if( mX.sparse ) //SPARSE
+			{
+				SparseBlock x = mX.sparseBlock;
+				for( int i=rl; i<ru; i++ ) {
+					if( x.isEmpty(i) ) continue;
+					int xpos = x.pos(i);
+					int xlen = x.size(i);
+					int[] xix = x.indexes(i);
+					double[] xval = x.values(i);
+					for( int k=xpos; k<xpos+xlen; k++ ) {
+						double uvij = dotProductGeneric(mU, mV, i, xix[k], cd);
+						wsloss += (xval[k]-uvij)*(xval[k]-uvij);
+					}
+				}
+			}
+			else //DENSE
+			{
+				DenseBlock x = mX.getDenseBlock();
+				for( int i=rl; i<ru; i++ ) {
+					double[] xvals = x.values(i);
+					int xix = x.pos(i);
+					for( int j=0; j<n; j++) {
+						double xij = xvals[xix+j];
+						if( xij != 0 ) {
+							double uvij = dotProductGeneric(mU, mV, i, j, cd);
+							wsloss += (xij-uvij)*(xij-uvij);
+						}
+					}
+				}
+			}
+		}
+		// Pattern 2) sum ((X - W * (U %*% t(V))) ^ 2) (pre weighting)
+		else if( wt==WeightsType.PRE )
+		{
+			// approach: iterate over all cells of X maybe sparse and dense
+			for( int i=rl; i<ru; i++ )
+				for( int j=0; j<n; j++) {
+					double xij = mX.get(i, j);
+					double wij = mW.get(i, j);
+					double uvij = 0;
+					if( wij != 0 )
+						uvij = dotProductGeneric(mU, mV, i, j, cd);
+					wsloss += (xij-wij*uvij)*(xij-wij*uvij);
+				}
+		}
+		// Pattern 3) sum ((X - (U %*% t(V))) ^ 2) (no weighting)
+		else if( wt==WeightsType.NONE )
+		{
+			//approach: use sparsity-exploiting pattern rewrite sum((X-(U%*%t(V)))^2) 
+			//-> sum(X^2)-sum(2*X*(U%*%t(V))))+sum((t(U)%*%U)*(t(V)%*%V)), where each
+			//parallel task computes sum(X^2)-sum(2*X*(U%*%t(V)))) and the last term
+			//sum((t(U)%*%U)*(t(V)%*%V)) is computed once via two tsmm operations.
+			
+			if( mX.sparse ) { //SPARSE
+				SparseBlock x = mX.sparseBlock;
+				for( int i=rl; i<ru; i++ ) {
+					if( x.isEmpty(i) ) continue;
+					int xpos = x.pos(i);
+					int xlen = x.size(i);
+					int[] xix = x.indexes(i);
+					double[] xval = x.values(i);
+					for( int k=xpos; k<xpos+xlen; k++ ) {
+						double xij = xval[k];
+						double uvij = dotProductGeneric(mU, mV, i, xix[k], cd);
+						wsloss += xij * xij - 2 * xij * uvij;
+					}
+				}
+			}
+			else { //DENSE
+				DenseBlock x = mX.getDenseBlock();
+				for( int i=rl; i<ru; i++ ) {
+					double[] xvals = x.values(i);
+					int xix = x.pos(i);
+					for( int j=0; j<n; j++)
+						if( xvals[xix+j] != 0 ) {
+							double xij = xvals[xix+j];
+							double uvij = dotProductGeneric(mU, mV, i, j, cd);
+							wsloss += xij * xij - 2 * xij * uvij;
+						}
+				}
+			}
+		}
+
+		ret.set(0, 0, wsloss);
+	}
+	
+	private static void addMatrixMultWSLossNoWeightCorrection(MatrixBlock mU, MatrixBlock mV, MatrixBlock ret, int k) {
+		MatrixBlock tmp1 = new MatrixBlock(mU.clen, mU.clen, false);
+		MatrixBlock tmp2 = new MatrixBlock(mU.clen, mU.clen, false);
+		matrixMultTransposeSelf(mU, tmp1, true, k);
+		matrixMultTransposeSelf(mV, tmp2, true, k);
+		ret.set(0, 0, ret.get(0, 0) + 
+			((tmp1.sparse || tmp2.sparse) ? dotProductGeneric(tmp1, tmp2) :
+			dotProduct(tmp1.getDenseBlockValues(), tmp2.getDenseBlockValues(), mU.clen*mU.clen)));
+	}
+
+	private static void matrixMultWSigmoidDenseNative(MatrixBlock mW, MatrixBlock mU, MatrixBlock mV, MatrixBlock ret, WSigmoidType wt) {
+		double[] w = mW.getDenseBlockValues();
+		double[] c = ret.getDenseBlockValues();
+		final int m = mW.rlen, n = mW.clen;
+		final int cd = mU.clen;
+		boolean flagminus = (wt==WSigmoidType.MINUS || wt==WSigmoidType.LOG_MINUS); 
+		boolean flaglog = (wt==WSigmoidType.LOG || wt==WSigmoidType.LOG_MINUS);
+		
+		//note: experiments with a fully native implementation of this method (even with #pragma omp simd)
+		//showed performance regressions compared to this version because we benefit from FastMath.exp 
+		
+		//call native matrix multiplication (only called for single-threaded and matrix-vector
+		//because this ensures that we can deal with the transpose mV without additional transpose)
+		long nnz =NativeHelper.dmmdd(((m==1)?mV:mU).getDenseBlockValues(),
+			((m==1)?mU:mV).getDenseBlockValues(), c, (m==1)?n:m, cd, 1, 1);
+		if(nnz < 0) {
+			//fallback to default java implementation
+			LOG.warn("matrixMultWSigmoidDenseNative: Native mat mult failed. Falling back to java version.");
+			matrixMult(((m==1)?mV:mU), ((m==1)?mU:mV), ret, false);
+		}
+		
+		//compute remaining wsigmoid for all relevant outputs
+		for( int i=0; i<m*n; i++ ) {
+			//compute core sigmoid function
+			double cval = flagminus ?
+				1 / (1 + FastMath.exp(c[i])) :
+				1 / (1 + FastMath.exp(-c[i]));
+			//compute weighted output
+			c[i] = w[i] * ((flaglog) ? Math.log(cval) : cval);
+		}
+	}
+	
+	private static void matrixMultWSigmoidDense(MatrixBlock mW, MatrixBlock mU, MatrixBlock mV, MatrixBlock ret, WSigmoidType wt, int rl, int ru) {
+		DenseBlock w = mW.getDenseBlock();
+		DenseBlock c = ret.getDenseBlock();
+		DenseBlock u = mU.getDenseBlock();
+		DenseBlock v = mV.getDenseBlock();
+		final int n = mW.clen;
+		final int cd = mU.clen;
+		
+		//note: cannot compute U %*% t(V) in-place of result w/ regular mm because
+		//t(V) comes in transformed form and hence would require additional memory
+	
+		boolean flagminus = (wt==WSigmoidType.MINUS || wt==WSigmoidType.LOG_MINUS); 
+		boolean flaglog = (wt==WSigmoidType.LOG || wt==WSigmoidType.LOG_MINUS);
+		
+		//approach: iterate over non-zeros of w, selective mm computation
+		//cache-conscious blocking: due to blocksize constraint (default 1000),
+		//a blocksize of 16 allows to fit blocks of UV into L2 cache (256KB) 
+		
+		final int blocksizeIJ = 16; //u/v block (max at typical L2 size) 
+		
+		//blocked execution
+		for( int bi = rl; bi < ru; bi+=blocksizeIJ ) {
+			int bimin = Math.min(ru, bi+blocksizeIJ);
+			for( int bj = 0; bj < n; bj+=blocksizeIJ ) {
+				int bjmin = Math.min(n, bj+blocksizeIJ);
+				//core wsigmoid computation
+				for( int i=bi; i<bimin; i++ ) {
+					double[] wvals = w.values(i), uvals = u.values(i), cvals = c.values(i);
+					int wix = w.pos(i), uix = u.pos(i);
+					for( int j=bj; j<bjmin; j++) {
+						double wij = wvals[wix+j];
+						if( wij != 0 )
+							cvals[wix+j] = wsigmoid(wij, uvals, v.values(j),
+								uix, v.pos(j), flagminus, flaglog, cd);
+					}
+				}
+			}
+		}
+	}
+
+	private static void matrixMultWSigmoidSparseDense(MatrixBlock mW, MatrixBlock mU, MatrixBlock mV, MatrixBlock ret, WSigmoidType wt, int rl, int ru) {
+		SparseBlock w = mW.sparseBlock;
+		SparseBlock c = ret.sparseBlock;
+		DenseBlock u = mU.getDenseBlock();
+		DenseBlock v = mV.getDenseBlock();
+		final int cd = mU.clen;
+		
+		boolean flagminus = (wt==WSigmoidType.MINUS || wt==WSigmoidType.LOG_MINUS);
+		boolean flaglog = (wt==WSigmoidType.LOG || wt==WSigmoidType.LOG_MINUS);
+	
+		//approach: iterate over non-zeros of w, selective mm computation
+		for( int i=rl; i<ru; i++ ) {
+			if( w.isEmpty(i) ) continue;
+			int wpos = w.pos(i);
+			int wlen = w.size(i);
+			int[] wix = w.indexes(i);
+			double[] wval = w.values(i);
+			double[] uvals = u.values(i);
+			int uix = u.pos(i);
+			c.allocate(i, wlen);
+			for( int k=wpos; k<wpos+wlen; k++ ) {
+				double cval = wsigmoid(wval[k], uvals, v.values(wix[k]),
+					uix, v.pos(wix[k]), flagminus, flaglog, cd);
+				c.append(i, wix[k], cval);
+			}
+		}
+	}
+
+	private static void matrixMultWSigmoidGeneric (MatrixBlock mW, MatrixBlock mU, MatrixBlock mV, MatrixBlock ret, WSigmoidType wt, int rl, int ru) {
+		final int n = mW.clen; 
+		final int cd = mU.clen;
+	
+		boolean flagminus = (wt==WSigmoidType.MINUS || wt==WSigmoidType.LOG_MINUS); 
+		boolean flaglog = (wt==WSigmoidType.LOG || wt==WSigmoidType.LOG_MINUS);
+	
+		//approach: iterate over non-zeros of w, selective mm computation
+		if( mW.sparse ) //SPARSE
+		{
+			//w and c always in same representation
+			SparseBlock w = mW.sparseBlock;
+			SparseBlock c = ret.sparseBlock;
+			for( int i=rl; i<ru; i++ ) {
+				if( w.isEmpty(i) ) continue;
+				int wpos = w.pos(i);
+				int wlen = w.size(i);
+				int[] wix = w.indexes(i);
+				double[] wval = w.values(i);
+				c.allocate(i, wlen);
+				for( int k=wpos; k<wpos+wlen; k++ ) {
+					double cval = wsigmoid(wval[k], mU, mV, i, wix[k], flagminus, flaglog, cd);
+					c.append(i, wix[k], cval);
+				}
+			}
+		}
+		else //DENSE
+		{
+			//w and c always in same representation
+			DenseBlock w = mW.getDenseBlock();
+			DenseBlock c = ret.getDenseBlock();
+			for( int i=rl; i<ru; i++ ) {
+				double[] wvals = w.values(i), cvals = c.values(i);
+				int ix = w.pos(i);
+				for( int j=0; j<n; j++ ) {
+					double wij = wvals[ix+j];
+					if( wij != 0 )
+						cvals[ix+j] = wsigmoid(wij, mU, mV, i, j, flagminus, flaglog, cd);
+				}
+			}
+		}
+	}
+
+	private static void matrixMultWDivMMDense(MatrixBlock mW, MatrixBlock mU, MatrixBlock mV, MatrixBlock mX, MatrixBlock ret, WDivMMType wt, int rl, int ru, int cl, int cu) {
+		final boolean basic = wt.isBasic();
+		final boolean left = wt.isLeft();
+		final boolean mult = wt.isMult();
+		final boolean minus = wt.isMinus();
+		final boolean four = wt.hasFourInputs();
+		final boolean scalar = wt.hasScalar();
+		final double eps = scalar ? mX.get(0, 0) : 0;
+		final int cd = mU.clen;
+		
+		DenseBlock w = mW.getDenseBlock();
+		DenseBlock u = mU.getDenseBlock();
+		DenseBlock v = mV.getDenseBlock();
+		DenseBlock x = (mX==null) ? null : mX.getDenseBlock();
+		DenseBlock c = ret.getDenseBlock();
+		
+		//approach: iterate over non-zeros of w, selective mm computation
+		//cache-conscious blocking: due to blocksize constraint (default 1000),
+		//a blocksize of 16 allows to fit blocks of UV into L2 cache (256KB) 
+		
+		final int blocksizeIJ = 16; //u/v block (max at typical L2 size) 
+		
+		//blocked execution
+		for( int bi = rl; bi < ru; bi+=blocksizeIJ ) {
+			int bimin = Math.min(ru, bi+blocksizeIJ);
+			for( int bj = cl; bj < cu; bj+=blocksizeIJ ) {
+				int bjmin = Math.min(cu, bj+blocksizeIJ);
+				//core wsigmoid computation
+				for( int i=bi; i<bimin; i++ ) {
+					double[] wvals = w.values(i), uvals = u.values(i);
+					double[] xvals = four ? x.values(i) : null; 
+					int wix = w.pos(i), uix = u.pos(i);
+					for( int j=bj; j<bjmin; j++ )
+						if( wvals[wix+j] != 0 ) {
+							double[] cvals = c.values((basic||!left) ? i : j);
+							if( basic ) 
+								cvals[wix+j] = wvals[wix+j] * dotProduct(uvals, v.values(j), uix, v.pos(j), cd);
+							else if( four ) { //left/right 
+								if (scalar)
+									wdivmm(wvals[wix+j], eps, uvals, v.values(j), cvals, uix, v.pos(j), left, scalar, cd);
+								else
+									wdivmm(wvals[wix+j], xvals[wix+j], uvals, v.values(j), cvals, uix, v.pos(j), left, scalar, cd);
+							}
+							else //left/right minus/default
+								wdivmm(wvals[wix+j], uvals, v.values(j), cvals, uix, v.pos(j), left, mult, minus, cd);
+						}
+				}
+			}
+		}
+	}
+
+	private static void matrixMultWDivMMSparseDense(MatrixBlock mW, MatrixBlock mU, MatrixBlock mV, MatrixBlock mX, MatrixBlock ret, WDivMMType wt, int rl, int ru, int cl, int cu) {
+		final boolean basic = wt.isBasic();
+		final boolean left = wt.isLeft();
+		final boolean mult = wt.isMult();
+		final boolean minus = wt.isMinus();
+		final boolean four = wt.hasFourInputs();
+		final boolean scalar = wt.hasScalar();
+		final double eps = scalar ? mX.get(0, 0) : 0;
+		final int cd = mU.clen;
+		
+		SparseBlock w = mW.sparseBlock;
+		DenseBlock u = mU.getDenseBlock();
+		DenseBlock v = mV.getDenseBlock();
+		DenseBlock c = ret.getDenseBlock();
+		SparseBlock x = (mX==null) ? null : mX.sparseBlock;
+		
+		//approach: iterate over non-zeros of w, selective mm computation
+		//blocked over ij, while maintaining front of column indexes, where the
+		//blocksize is chosen such that we reuse each  Ui/Vj vector on average 8 times,
+		//with custom blocksizeJ for wdivmm_left to avoid LLC misses on output.
+		final int blocksizeI = (int) (8L*mW.rlen*mW.clen/mW.nonZeros);
+		final int blocksizeJ = left ? Math.max(8,Math.min(L2_CACHESIZE/(mU.clen*8), blocksizeI)) : blocksizeI;
+		
+		int[] curk = new int[blocksizeI];
+		boolean[] aligned = (four&&!scalar) ? new boolean[blocksizeI] : null;
+		
+		//blocked execution over row blocks
+		for( int bi=rl; bi<ru; bi+=blocksizeI ) 
+		{
+			int bimin = Math.min(ru, bi+blocksizeI);
+			//prepare starting indexes for block row
+			for( int i=bi; i<bimin; i++ ) {
+				int k = (cl==0||w.isEmpty(i)) ? 0 : w.posFIndexGTE(i,cl);
+				curk[i-bi] = (k>=0) ? k : mW.clen;
+			}
+			//prepare alignment info if necessary
+			if( four && !scalar )
+				for( int i=bi; i<bimin; i++ )
+					aligned[i-bi] = w.isAligned(i-bi, x);
+			
+			//blocked execution over column blocks
+			for( int bj=cl; bj<cu; bj+=blocksizeJ )  
+			{
+				int bjmin = Math.min(cu, bj+blocksizeJ);
+				//core wdivmm block matrix mult
+				for( int i=bi; i<bimin; i++ ) {
+					if( w.isEmpty(i) ) continue;
+					
+					int wpos = w.pos(i);
+					int wlen = w.size(i);
+					int[] wix = w.indexes(i);
+					double[] wval = w.values(i);
+					double[] uvals = u.values(i);
+					int uix = u.pos(i);
+					
+					int k = wpos + curk[i-bi];
+					if( basic ) {
+						for( ; k<wpos+wlen && wix[k]<bjmin; k++ )
+							ret.appendValue( i, wix[k], wval[k] *dotProduct(
+								uvals, v.values(wix[k]), uix, v.pos(wix[k]), cd));
+					}
+					else if( four ) { //left/right
+						//checking alignment per row is ok because early abort if false, 
+						//row nnz likely fit in L1/L2 cache, and asymptotically better if aligned
+						if( !scalar && w.isAligned(i, x) ) {
+							//O(n) where n is nnz in w/x 
+							double[] xvals = x.values(i);
+							for( ; k<wpos+wlen && wix[k]<bjmin; k++ ) {
+								double[] cvals = c.values(left ? wix[k] : i);
+								wdivmm(wval[k], xvals[k], uvals, v.values(wix[k]),
+									cvals, uix, v.pos(wix[k]), left, scalar, cd);
+							}
+						}
+						else {
+							//scalar or O(n log m) where n/m are nnz in w/x
+							for( ; k<wpos+wlen && wix[k]<bjmin; k++ ) {
+								double[] cvals = c.values(left ? wix[k] : i);
+								if (scalar)
+									wdivmm(wval[k], eps, uvals, v.values(wix[k]),
+										cvals, uix, v.pos(wix[k]), left, scalar, cd);
+								else
+									wdivmm(wval[k], x.get(i, wix[k]), uvals,
+										v.values(wix[k]), cvals, uix, v.pos(wix[k]), left, scalar, cd);
+							}
+						}
+					}
+					else { //left/right minus default
+						for( ; k<wpos+wlen && wix[k]<bjmin; k++ ) {
+							double[] cvals = c.values(left ? wix[k] : i);
+							wdivmm(wval[k], uvals, v.values(wix[k]), cvals,
+								uix, v.pos(wix[k]), left, mult, minus, cd);
+						}
+					}
+					curk[i-bi] = k - wpos;
+				}
+			}
+		}
+	}
+
+	private static void matrixMultWDivMMGeneric(MatrixBlock mW, MatrixBlock mU, MatrixBlock mV, MatrixBlock mX, MatrixBlock ret, WDivMMType wt, int rl, int ru, int cl, int cu) {
+		final boolean basic = wt.isBasic();
+		final boolean left = wt.isLeft(); 
+		final boolean mult = wt.isMult();
+		final boolean minus = wt.isMinus();
+		final boolean four = wt.hasFourInputs();
+		final boolean scalar = wt.hasScalar();
+		final double eps = scalar ? mX.get(0, 0) : 0;
+		final int cd = mU.clen;
+
+		//output always in dense representation
+		DenseBlock c = ret.getDenseBlock();
+		
+		//approach: iterate over non-zeros of w, selective mm computation
+		if( mW.sparse ) //SPARSE
+		{
+			SparseBlock w = mW.sparseBlock;
+			
+			for( int i=rl; i<ru; i++ ) {
+				if( w.isEmpty(i) ) continue;
+				int wpos = w.pos(i);
+				int wlen = w.size(i);
+				int[] wix = w.indexes(i);
+				double[] wval = w.values(i);
+				int k = (cl==0) ? 0 : w.posFIndexGTE(i,cl);
+				k = (k>=0) ? wpos+k : wpos+wlen;
+				for( ; k<wpos+wlen && wix[k]<cu; k++ ) {
+					double[] cvals = c.values((basic||!left) ? i : wix[k]);
+					if( basic ) {
+						double uvij = dotProductGeneric(mU,mV, i, wix[k], cd);
+						ret.appendValue(i, wix[k], uvij);
+					}
+					else if( four ) { //left/right
+						double xij = scalar ? eps : mX.get(i, wix[k]);
+						wdivmm(wval[k], xij, mU, mV, cvals, i, wix[k], left, scalar, cd);
+					}
+					else { //left/right minus/default
+						wdivmm(wval[k], mU, mV, cvals, i, wix[k], left, mult, minus, cd);
+					}
+				}
+			}
+		}
+		else //DENSE
+		{
+			DenseBlock w = mW.getDenseBlock();
+			for( int i=rl; i<ru; i++ ) {
+				double[] wvals = w.values(i);
+				int ix = w.pos(i);
+				for( int j=cl; j<cu; j++)
+					if( wvals[ix+j] != 0 ) {
+						double[] cvals = c.values((basic||!left) ? i : j);
+						if( basic ) {
+							cvals[ix+j] = dotProductGeneric(mU,mV, i, j, cd);
+						}
+						else if( four ) { //left/right
+							double xij = scalar ? eps : mX.get(i, j);
+							wdivmm(wvals[ix+j], xij, mU, mV, cvals, i, j, left, scalar, cd);
+						}
+						else { //left/right minus/default
+							wdivmm(wvals[ix+j], mU, mV, cvals, i, j, left, mult, minus, cd);
+						}
+					}
+			}
+		}
+	}
+
+	private static void matrixMultWCeMMDense(MatrixBlock mW, MatrixBlock mU, MatrixBlock mV, double eps, MatrixBlock ret, WCeMMType wt, int rl, int ru)
+	{
+		DenseBlock w = mW.getDenseBlock();
+		DenseBlock u = mU.getDenseBlock();
+		DenseBlock v = mV.getDenseBlock();
+		final int n = mW.clen;
+		final int cd = mU.clen;
+		double wceval = 0;
+		
+		// approach: iterate over all cells of X 
+		//cache-conscious blocking: due to blocksize constraint (default 1000),
+		//a blocksize of 16 allows to fit blocks of UV into L2 cache (256KB) 
+		final int blocksizeIJ = 16; //u/v block (max at typical L2 size) 
+
+		//blocked execution
+		for( int bi = rl; bi < ru; bi+=blocksizeIJ ) {
+			int bimin = Math.min(ru, bi+blocksizeIJ);
+			for( int bj = 0; bj < n; bj+=blocksizeIJ ) {
+				int bjmin = Math.min(n, bj+blocksizeIJ);
+				for( int i=bi; i<bimin; i++ ) {
+					double[] wvals = w.values(i), uvals = u.values(i);
+					int wix = w.pos(i), uix = u.pos(i);
+					for( int j=bj; j<bjmin; j++ ) {
+						double wij = wvals[wix+j];
+						if( wij != 0 ) {
+							double uvij = dotProduct(uvals, v.values(j), uix, v.pos(j), cd);
+							wceval += wij * Math.log(uvij + eps);
+						}
+					}
+				}
+			}
+		}
+		ret.set(0, 0, wceval);
+	}
+
+	private static void matrixMultWCeMMSparseDense(MatrixBlock mW, MatrixBlock mU, MatrixBlock mV, double eps, MatrixBlock ret, WCeMMType wt, int rl, int ru)
+	{
+		SparseBlock w = mW.sparseBlock;
+		DenseBlock u = mU.getDenseBlock();
+		DenseBlock v = mV.getDenseBlock();
+		final int n = mW.clen;
+		final int cd = mU.clen;
+		double wceval = 0;
+		
+		// approach: iterate over W, point-wise in order to exploit sparsity
+		// blocked over ij, while maintaining front of column indexes, where the
+		// blocksize is chosen such that we reuse each vector on average 8 times.
+		final int blocksizeIJ = (int) (8L*mW.rlen*mW.clen/mW.nonZeros); 
+		int[] curk = new int[blocksizeIJ];
+		
+		for( int bi=rl; bi<ru; bi+=blocksizeIJ ) {
+			int bimin = Math.min(ru, bi+blocksizeIJ);
+			//prepare starting indexes for block row
+			Arrays.fill(curk, 0); 
+			//blocked execution over column blocks
+			for( int bj=0; bj<n; bj+=blocksizeIJ ) {
+				int bjmin = Math.min(n, bj+blocksizeIJ);
+				for( int i=bi; i<bimin; i++ ) {
+					if( w.isEmpty(i) ) continue;
+					int wpos = w.pos(i);
+					int wlen = w.size(i);
+					int[] wix = w.indexes(i);
+					double[] wvals = w.values(i);
+					double[] uvals = u.values(i);
+					int uix = u.pos(i);
+					int k = wpos + curk[i-bi];
+					for( ; k<wpos+wlen && wix[k]<bjmin; k++ ) {
+						double uvij = dotProduct(uvals, v.values(wix[k]), uix, v.pos(wix[k]), cd);
+						wceval += wvals[k] * Math.log(uvij + eps);
+					}
+					curk[i-bi] = k - wpos;
+				}
+			}
+		}
+		ret.set(0, 0, wceval);
+	}
+
+	private static void matrixMultWCeMMGeneric(MatrixBlock mW, MatrixBlock mU, MatrixBlock mV, double eps, MatrixBlock ret, WCeMMType wt, int rl, int ru)
+	{
+		final int n = mW.clen; 
+		final int cd = mU.clen;
+		double wceval = 0; 
+
+		//approach: iterate over non-zeros of w, selective mm computation
+		if( mW.sparse ) //SPARSE
+		{
+			SparseBlock w = mW.sparseBlock;
+			for( int i=rl; i<ru; i++ ) {
+				if( w.isEmpty(i) ) continue;
+				int wpos = w.pos(i);
+				int wlen = w.size(i);
+				int[] wix = w.indexes(i);
+				double[] wval = w.values(i);
+				for( int k=wpos; k<wpos+wlen; k++ ) {
+					double uvij = dotProductGeneric(mU, mV, i, wix[k], cd);
+					wceval += wval[k] * Math.log(uvij + eps);
+				}
+			}
+		}
+		else //DENSE
+		{
+			DenseBlock w = mW.getDenseBlock();
+			for( int i=rl; i<ru; i++ ) {
+				double[] wvals = w.values(i);
+				int wix = w.pos(i);
+				for( int j=0; j<n; j++ ) {
+					double wij = wvals[wix+j];
+					if( wij != 0 ) {
+						double uvij = dotProductGeneric(mU, mV, i, j, cd);
+						wceval += wij * Math.log(uvij + eps);
+					}
+				}
+			}
+		}
+
+		ret.set(0, 0, wceval);
+	}
+
+	private static void matrixMultWuMMDense(MatrixBlock mW, MatrixBlock mU, MatrixBlock mV, MatrixBlock ret, WUMMType wt, ValueFunction fn, int rl, int ru) {
+		DenseBlock w = mW.getDenseBlock();
+		DenseBlock c = ret.getDenseBlock();
+		DenseBlock u = mU.getDenseBlock();
+		DenseBlock v = mV.getDenseBlock();
+		final int n = mW.clen;
+		final int cd = mU.clen;
+		
+		//note: cannot compute U %*% t(V) in-place of result w/ regular mm because
+		//t(V) comes in transformed form and hence would require additional memory
+	
+		boolean flagmult = (wt==WUMMType.MULT);
+		
+		//approach: iterate over non-zeros of w, selective mm computation
+		//cache-conscious blocking: due to blocksize constraint (default 1000),
+		//a blocksize of 16 allows to fit blocks of UV into L2 cache (256KB)
+		
+		final int blocksizeIJ = 16; //u/v block (max at typical L2 size)
+		
+		//blocked execution
+		for( int bi = rl; bi < ru; bi+=blocksizeIJ ) {
+			int bimin = Math.min(ru, bi+blocksizeIJ);
+			for( int bj = 0; bj < n; bj+=blocksizeIJ ) {
+				int bjmin = Math.min(n, bj+blocksizeIJ);
+				//core wsigmoid computation
+				for( int i=bi; i<bimin; i++ ) {
+					double[] wvals = w.values(i), uvals = u.values(i), cvals = c.values(i);
+					int wix = w.pos(i), uix = u.pos(i);
+					for( int j=bj; j<bjmin; j++ ) {
+						double wij = wvals[wix+j];
+						if( wij != 0 )
+							cvals[wix+j] = wumm(wij, uvals, v.values(j),
+								uix, v.pos(j), flagmult, fn, cd);
+					}
+				}
+			}
+		}
+	}
+
+	private static void matrixMultWuMMSparseDense(MatrixBlock mW, MatrixBlock mU, MatrixBlock mV, MatrixBlock ret, WUMMType wt, ValueFunction fn, int rl, int ru) {
+		SparseBlock w = mW.sparseBlock;
+		SparseBlock c = ret.sparseBlock;
+		DenseBlock u = mU.getDenseBlock();
+		DenseBlock v = mV.getDenseBlock();
+		final int cd = mU.clen;
+		boolean flagmult = (wt==WUMMType.MULT);
+		
+		//approach: iterate over non-zeros of w, selective mm computation
+		for( int i=rl; i<ru; i++ ) {
+			if( w.isEmpty(i) ) continue;
+			int wpos = w.pos(i);
+			int wlen = w.size(i);
+			int[] wix = w.indexes(i);
+			double[] wvals = w.values(i);
+			double[] uvals = u.values(i);
+			int uix = u.pos(i);
+			c.allocate(i, wlen);
+			for( int k=wpos; k<wpos+wlen; k++ ) {
+				double cval = wumm(wvals[k], uvals, v.values(wix[k]),
+					uix, v.pos(wix[k]), flagmult, fn, cd);
+				c.append(i, wix[k], cval);
+			}
+		}
+	}
+
+	private static void matrixMultWuMMGeneric (MatrixBlock mW, MatrixBlock mU, MatrixBlock mV, MatrixBlock ret, WUMMType wt, ValueFunction fn, int rl, int ru) {
+		final int n = mW.clen;
+		final int cd = mU.clen;
+		boolean flagmult = (wt==WUMMType.MULT);
+		
+		//approach: iterate over non-zeros of w, selective mm computation
+		if( mW.sparse ) { //SPARSE
+			//w and c always in same representation
+			SparseBlock w = mW.sparseBlock;
+			SparseBlock c = ret.sparseBlock;
+			for( int i=rl; i<ru; i++ ) {
+				if( w.isEmpty(i) ) continue;
+				int wpos = w.pos(i);
+				int wlen = w.size(i);
+				int[] wix = w.indexes(i);
+				double[] wval = w.values(i);
+				c.allocate(i, wlen);
+				for( int k=wpos; k<wpos+wlen; k++ ) {
+					double cval = wumm(wval[k], mU, mV, i, wix[k], flagmult, fn, cd);
+					c.append(i, wix[k], cval);
+				}
+			}
+		}
+		else { //DENSE
+			//w and c always in same representation
+			DenseBlock w = mW.getDenseBlock();
+			DenseBlock c = ret.getDenseBlock();
+			for( int i=rl; i<ru; i++ ) {
+				double[] wvals = w.values(i), cvals = c.values(i);
+				int ix = w.pos(i);
+				for( int j=0; j<n; j++) {
+					double wij = wvals[ix+j];
+					if( wij != 0 )
+						cvals[ix+j] = wumm(wij, mU, mV, i, j, flagmult, fn, cd);
+				}
+			}
+		}
+	}
+	
+	////////////////////////////////////////////
+	// performance-relevant utility functions //
+	////////////////////////////////////////////
+	
+	/**
+	 * Computes the dot-product of two vectors. Experiments (on long vectors of
+	 * 10^7 values) showed that this generic function provides equivalent performance
+	 * even for the specific case of dotProduct(a,a,len) as used for TSMM.  
+	 * 
+	 * @param a first vector
+	 * @param b second vector
+	 * @param len length
+	 * @return dot product of the two input vectors
+	 */
+	private static double dotProduct( double[] a, double[] b, final int len )
+	{
+		double val = 0;
+		final int bn = len%8;
+				
+		//compute rest
+		for( int i = 0; i < bn; i++ )
+			val += a[ i ] * b[ i ];
+		
+		//unrolled 8-block  (for better instruction-level parallelism)
+		for( int i = bn; i < len; i+=8 )
+		{
+			//read 64B cachelines of a and b
+			//compute cval' = sum(a * b) + cval
+			val += a[ i+0 ] * b[ i+0 ]
+			     + a[ i+1 ] * b[ i+1 ]
+			     + a[ i+2 ] * b[ i+2 ]
+			     + a[ i+3 ] * b[ i+3 ]
+			     + a[ i+4 ] * b[ i+4 ]
+			     + a[ i+5 ] * b[ i+5 ]
+			     + a[ i+6 ] * b[ i+6 ]
+			     + a[ i+7 ] * b[ i+7 ];
+		}
+		
+		//scalar result
+		return val; 
+	}
+
+	//note: public for use by codegen for consistency
+	public static double dotProduct( double[] a, double[] b, int ai, int bi, final int len )
+	{
+		double val = 0;
+		final int bn = len%8;
+		
+		//compute rest
+		for( int i = 0; i < bn; i++, ai++, bi++ )
+			val += a[ ai ] * b[ bi ];
+		
+		//unrolled 8-block (for better instruction-level parallelism)
+		for( int i = bn; i < len; i+=8, ai+=8, bi+=8 )
+		{
+			//read 64B cachelines of a and b
+			//compute cval' = sum(a * b) + cval
+			val += a[ ai+0 ] * b[ bi+0 ]
+			     + a[ ai+1 ] * b[ bi+1 ]
+			     + a[ ai+2 ] * b[ bi+2 ]
+			     + a[ ai+3 ] * b[ bi+3 ]
+			     + a[ ai+4 ] * b[ bi+4 ]
+			     + a[ ai+5 ] * b[ bi+5 ]
+			     + a[ ai+6 ] * b[ bi+6 ]
+			     + a[ ai+7 ] * b[ bi+7 ];
+		}
+		
+		//scalar result
+		return val; 
+	}
+	
+	//note: public for use by codegen for consistency
+	public static double dotProduct( double[] a, double[] b, int[] aix, int ai, final int bi, final int len )
+	{
+		double val = 0;
+		final int bn = len%8;
+				
+		//compute rest
+		for( int i = ai; i < ai+bn; i++ )
+			val += a[ i ] * b[ bi+aix[i] ];
+		
+		//unrolled 8-block (for better instruction-level parallelism)
+		for( int i = ai+bn; i < ai+len; i+=8 )
+		{
+			//read 64B cacheline of a
+			//read 64B of b via 'gather'
+			//compute cval' = sum(a * b) + cval
+			val += a[ i+0 ] * b[ bi+aix[i+0] ]
+			     + a[ i+1 ] * b[ bi+aix[i+1] ]
+			     + a[ i+2 ] * b[ bi+aix[i+2] ]
+			     + a[ i+3 ] * b[ bi+aix[i+3] ]
+			     + a[ i+4 ] * b[ bi+aix[i+4] ]
+			     + a[ i+5 ] * b[ bi+aix[i+5] ]
+			     + a[ i+6 ] * b[ bi+aix[i+6] ]
+			     + a[ i+7 ] * b[ bi+aix[i+7] ];
+		}
+		
+		//scalar result
+		return val; 
+	}
+	
+	private static double dotProduct(double[] a, int[] aix, final int apos, final int alen, double[] b, int bix[], final int bpos, final int blen) {
+		final int asize = apos+alen;
+		final int bsize = bpos+blen;
+		int k = apos, k2 = bpos;
+		
+		//pruning filter
+		if(aix[apos]>bix[bsize-1] || aix[asize-1]<bix[bpos] )
+			return 0;
+		
+		//sorted set intersection
+		double v = 0;
+		while( k<asize & k2<bsize ) {
+			int aixk = aix[k];
+			int bixk = bix[k2];
+			if( aixk < bixk )
+				k++;
+			else if( aixk > bixk )
+				k2++;
+			else { // ===
+				v += a[k] * b[k2];
+				k++; k2++;
+			}
+			//note: branchless version slower
+			//v += (aixk==bixk) ? a[k] * b[k2] : 0;
+			//k += (aixk <= bixk) ? 1 : 0;
+			//k2 += (aixk >= bixk) ? 1 : 0;
+		}
+		return v;
+	}
+
+	//note: public for use by codegen for consistency
+	public static void vectMultiplyAdd( final double aval, double[] b, double[] c, int bi, int ci, final int len )
+	{
+		final int bn = len%8;
+		
+		//rest, not aligned to 8-blocks
+		for( int j = 0; j < bn; j++, bi++, ci++)
+			segmentRet.setAtIndex(layout, ci, aval * segment2.getAtIndex(layout, bi) +
+					segmentRet.getAtIndex(layout, ci));
+		
+		//unrolled 8-block  (for better instruction-level parallelism)
+		for( int j = bn; j < len; j+=8, bi+=8, ci+=8) 
+		{
+			//read 64B cachelines of b and c
+			//compute c' = aval * b + c
+			//write back 64B cacheline of c = c'
+			segmentRet.setAtIndex(layout, ci+0, aval * segment2.getAtIndex(layout, bi+0) +
+					segmentRet.getAtIndex(layout, ci+0));
+
+			segmentRet.setAtIndex(layout, ci+1, aval * segment2.getAtIndex(layout, bi+1) +
+					segmentRet.getAtIndex(layout, ci+1));
+
+			segmentRet.setAtIndex(layout, ci+2, aval * segment2.getAtIndex(layout, bi+2) +
+					segmentRet.getAtIndex(layout, ci+2));
+
+			segmentRet.setAtIndex(layout, ci+3, aval * segment2.getAtIndex(layout, bi+3) +
+					segmentRet.getAtIndex(layout, ci+3));
+
+			segmentRet.setAtIndex(layout, ci+4, aval * segment2.getAtIndex(layout, bi+4) +
+					segmentRet.getAtIndex(layout, ci+4));
+
+			segmentRet.setAtIndex(layout, ci+5, aval * segment2.getAtIndex(layout, bi+5) +
+					segmentRet.getAtIndex(layout, ci+5));
+
+			segmentRet.setAtIndex(layout, ci+6, aval * segment2.getAtIndex(layout, bi+6) +
+					segmentRet.getAtIndex(layout, ci+6));
+
+			segmentRet.setAtIndex(layout, ci+7, aval * segment2.getAtIndex(layout, bi+7) +
+					segmentRet.getAtIndex(layout, ci+7));
+		}
+	}
+
+	private static void vectMultiplyAdd2( final double aval1, final double aval2, double[] b, double[] c, int bi1, int bi2, int ci, final int len )
+	{
+		final int bn = len%8;
+		
+		//rest, not aligned to 8-blocks
+		for( int j = 0; j < bn; j++, bi1++, bi2++, ci++ )
+			segmentRet.setAtIndex(layout, ci, aval1 * segment2.getAtIndex(layout, bi1) +
+					aval2 * segment2.getAtIndex(layout, bi2) +
+					segmentRet.getAtIndex(layout, ci));
+		
+		//unrolled 8-block  (for better instruction-level parallelism)
+		for( int j = bn; j < len; j+=8, bi1+=8, bi2+=8, ci+=8 ) 
+		{
+			//read 64B cachelines of b (2x) and c
+			//compute c' = aval_1 * b_1 + aval_2 * b_2 + c
+			//write back 64B cacheline of c = c'
+			segmentRet.setAtIndex(layout, ci+0, aval1 * segment2.getAtIndex(layout, bi1+0) +
+					aval2 * segment2.getAtIndex(layout, bi2+0) +
+					segmentRet.getAtIndex(layout, ci+0));
+
+			segmentRet.setAtIndex(layout, ci+1, aval1 * segment2.getAtIndex(layout, bi1+1) +
+					aval2 * segment2.getAtIndex(layout, bi2+1) +
+					segmentRet.getAtIndex(layout, ci+1));
+
+			segmentRet.setAtIndex(layout, ci+2, aval1 * segment2.getAtIndex(layout, bi1+2) +
+					aval2 * segment2.getAtIndex(layout, bi2+2) +
+					segmentRet.getAtIndex(layout, ci+2));
+
+			segmentRet.setAtIndex(layout, ci+3, aval1 * segment2.getAtIndex(layout, bi1+3) +
+					aval2 * segment2.getAtIndex(layout, bi2+3) +
+					segmentRet.getAtIndex(layout, ci+3));
+
+			segmentRet.setAtIndex(layout, ci+4, aval1 * segment2.getAtIndex(layout, bi1+4) +
+					aval2 * segment2.getAtIndex(layout, bi2+4) +
+					segmentRet.getAtIndex(layout, ci+4));
+
+			segmentRet.setAtIndex(layout, ci+5, aval1 * segment2.getAtIndex(layout, bi1+5) +
+					aval2 * segment2.getAtIndex(layout, bi2+5) +
+					segmentRet.getAtIndex(layout, ci+5));
+
+			segmentRet.setAtIndex(layout, ci+6, aval1 * segment2.getAtIndex(layout, bi1+6) +
+					aval2 * segment2.getAtIndex(layout, bi2+6) +
+					segmentRet.getAtIndex(layout, ci+6));
+
+			segmentRet.setAtIndex(layout, ci+7, aval1 * segment2.getAtIndex(layout, bi1+7) +
+					aval2 * segment2.getAtIndex(layout, bi2+7) +
+					segmentRet.getAtIndex(layout, ci+7));
+		}
+	}
+
+	private static void vectMultiplyAdd3( final double aval1, final double aval2, final double aval3, double[] b, double[] c, int bi1, int bi2, int bi3, int ci, final int len )
+	{
+		final int bn = len%8;
+		
+		//rest, not aligned to 8-blocks
+		for( int j = 0; j < bn; j++, bi1++, bi2++, bi3++, ci++ )
+			segmentRet.setAtIndex(layout, ci, aval1 * segment2.getAtIndex(layout, bi1) +
+					aval2 * segment2.getAtIndex(layout, bi2) +
+					aval3 * segment2.getAtIndex(layout, bi3) +
+					segmentRet.getAtIndex(layout, ci));
+		
+		//unrolled 8-block (for better instruction-level parallelism)
+		for( int j = bn; j < len; j+=8, bi1+=8, bi2+=8, bi3+=8, ci+=8 ) 
+		{
+			//read 64B cachelines of b (3x) and c
+			//compute c' = aval_1 * b_1 + aval_2 * b_2 + c
+			//write back 64B cacheline of c = c'
+			segmentRet.setAtIndex(layout, ci+0, aval1 * segment2.getAtIndex(layout, bi1+0) +
+					aval2 * segment2.getAtIndex(layout, bi2+0) +
+					aval3 * segment2.getAtIndex(layout, bi3+0) +
+					segmentRet.getAtIndex(layout, ci+0));
+
+			segmentRet.setAtIndex(layout, ci+1, aval1 * segment2.getAtIndex(layout, bi1+1) +
+					aval2 * segment2.getAtIndex(layout, bi2+1) +
+					aval3 * segment2.getAtIndex(layout, bi3+1) +
+					segmentRet.getAtIndex(layout, ci+1));
+
+			segmentRet.setAtIndex(layout, ci+2, aval1 * segment2.getAtIndex(layout, bi1+2) +
+					aval2 * segment2.getAtIndex(layout, bi2+2) +
+					aval3 * segment2.getAtIndex(layout, bi3+2) +
+					segmentRet.getAtIndex(layout, ci+2));
+
+			segmentRet.setAtIndex(layout, ci+3, aval1 * segment2.getAtIndex(layout, bi1+3) +
+					aval2 * segment2.getAtIndex(layout, bi2+3) +
+					aval3 * segment2.getAtIndex(layout, bi3+3) +
+					segmentRet.getAtIndex(layout, ci+3));
+
+			segmentRet.setAtIndex(layout, ci+4, aval1 * segment2.getAtIndex(layout, bi1+4) +
+					aval2 * segment2.getAtIndex(layout, bi2+4) +
+					aval3 * segment2.getAtIndex(layout, bi3+4) +
+					segmentRet.getAtIndex(layout, ci+4));
+
+			segmentRet.setAtIndex(layout, ci+5, aval1 * segment2.getAtIndex(layout, bi1+5) +
+					aval2 * segment2.getAtIndex(layout, bi2+5) +
+					aval3 * segment2.getAtIndex(layout, bi3+5) +
+					segmentRet.getAtIndex(layout, ci+5));
+
+			segmentRet.setAtIndex(layout, ci+6, aval1 * segment2.getAtIndex(layout, bi1+6) +
+					aval2 * segment2.getAtIndex(layout, bi2+6) +
+					aval3 * segment2.getAtIndex(layout, bi3+6) +
+					segmentRet.getAtIndex(layout, ci+6));
+
+			segmentRet.setAtIndex(layout, ci+7, aval1 * segment2.getAtIndex(layout, bi1+7) +
+					aval2 * segment2.getAtIndex(layout, bi2+7) +
+					aval3 * segment2.getAtIndex(layout, bi3+7) +
+					segmentRet.getAtIndex(layout, ci+7));
+		}
+	}
+
+	private static void vectMultiplyAdd4( final double aval1, final double aval2, final double aval3, final double aval4, double[] b, double[] c, int bi1, int bi2, int bi3, int bi4, int ci, final int len )
+	{
+		final int bn = len%8;
+		
+		//rest, not aligned to 8-blocks
+		for( int j = 0; j < bn; j++, bi1++, bi2++, bi3++, bi4++, ci++ )
+			segmentRet.setAtIndex(layout, ci, aval1 * segment2.getAtIndex(layout, bi1) +
+					aval2 * segment2.getAtIndex(layout, bi2) +
+					aval3 * segment2.getAtIndex(layout, bi3) +
+					aval4 * segment2.getAtIndex(layout, bi4) +
+					segmentRet.getAtIndex(layout, ci));
+		
+		//unrolled 8-block  (for better instruction-level parallelism)
+		for( int j = bn; j < len; j+=8, bi1+=8, bi2+=8, bi3+=8, bi4+=8, ci+=8) 
+		{
+			//read 64B cachelines of b (4x) and c 
+			//compute c' = aval_1 * b_1 + aval_2 * b_2 + c
+			//write back 64B cacheline of c = c'
+			segmentRet.setAtIndex(layout, ci+0, aval1 * segment2.getAtIndex(layout, bi1+0) +
+					aval2 * segment2.getAtIndex(layout, bi2+0) +
+					aval3 * segment2.getAtIndex(layout, bi3+0) +
+					aval4 * segment2.getAtIndex(layout, bi4+0) +
+					segmentRet.getAtIndex(layout, ci+0));
+
+			segmentRet.setAtIndex(layout, ci+1, aval1 * segment2.getAtIndex(layout, bi1+1) +
+					aval2 * segment2.getAtIndex(layout, bi2+1) +
+					aval3 * segment2.getAtIndex(layout, bi3+1) +
+					aval4 * segment2.getAtIndex(layout, bi4+1) +
+					segmentRet.getAtIndex(layout, ci+1));
+
+			segmentRet.setAtIndex(layout, ci+2, aval1 * segment2.getAtIndex(layout, bi1+2) +
+					aval2 * segment2.getAtIndex(layout, bi2+2) +
+					aval3 * segment2.getAtIndex(layout, bi3+2) +
+					aval4 * segment2.getAtIndex(layout, bi4+2) +
+					segmentRet.getAtIndex(layout, ci+2));
+
+			segmentRet.setAtIndex(layout, ci+3, aval1 * segment2.getAtIndex(layout, bi1+3) +
+					aval2 * segment2.getAtIndex(layout, bi2+3) +
+					aval3 * segment2.getAtIndex(layout, bi3+3) +
+					aval4 * segment2.getAtIndex(layout, bi4+3) +
+					segmentRet.getAtIndex(layout, ci+3));
+
+			segmentRet.setAtIndex(layout, ci+4, aval1 * segment2.getAtIndex(layout, bi1+4) +
+					aval2 * segment2.getAtIndex(layout, bi2+4) +
+					aval3 * segment2.getAtIndex(layout, bi3+4) +
+					aval4 * segment2.getAtIndex(layout, bi4+4) +
+					segmentRet.getAtIndex(layout, ci+4));
+
+			segmentRet.setAtIndex(layout, ci+5, aval1 * segment2.getAtIndex(layout, bi1+5) +
+					aval2 * segment2.getAtIndex(layout, bi2+5) +
+					aval3 * segment2.getAtIndex(layout, bi3+5) +
+					aval4 * segment2.getAtIndex(layout, bi4+5) +
+					segmentRet.getAtIndex(layout, ci+5));
+
+			segmentRet.setAtIndex(layout, ci+6, aval1 * segment2.getAtIndex(layout, bi1+6) +
+					aval2 * segment2.getAtIndex(layout, bi2+6) +
+					aval3 * segment2.getAtIndex(layout, bi3+6) +
+					aval4 * segment2.getAtIndex(layout, bi4+6) +
+					segmentRet.getAtIndex(layout, ci+6));
+
+			segmentRet.setAtIndex(layout, ci+7, aval1 * segment2.getAtIndex(layout, bi1+7) +
+					aval2 * segment2.getAtIndex(layout, bi2+7) +
+					aval3 * segment2.getAtIndex(layout, bi3+7) +
+					aval4 * segment2.getAtIndex(layout, bi4+7) +
+					segmentRet.getAtIndex(layout, ci+7));
+		}
+	}
+	
+	@SuppressWarnings("unused")
+	private static void vectMultiplyAdd( final double aval, double[] b, double[] c, int[] bix, final int ci, final int len )
+	{
+		final int bn = len%8;
+		
+		//rest, not aligned to 8-blocks
+		for( int j = 0; j < bn; j++ )
+			c[ ci + bix[j] ] += aval * b[ j ];
+		
+		//unrolled 8-block (for better instruction-level parallelism)
+		for( int j = bn; j < len; j+=8 )
+		{
+			//read 64B cacheline of b
+			//read 64B of c via 'gather'
+			//compute c' = aval * b + c
+			//write back 64B of c = c' via 'scatter'
+			c[ ci+bix[j+0] ] += aval * b[ j+0 ];
+			c[ ci+bix[j+1] ] += aval * b[ j+1 ];
+			c[ ci+bix[j+2] ] += aval * b[ j+2 ];
+			c[ ci+bix[j+3] ] += aval * b[ j+3 ];
+			c[ ci+bix[j+4] ] += aval * b[ j+4 ];
+			c[ ci+bix[j+5] ] += aval * b[ j+5 ];
+			c[ ci+bix[j+6] ] += aval * b[ j+6 ];
+			c[ ci+bix[j+7] ] += aval * b[ j+7 ];
+		}
+	}
+
+	//note: public for use by codegen for consistency
+	public static void vectMultiplyAdd( final double aval, double[] b, double[] c, int[] bix, final int bi, final int ci, final int len )
+	{
+		final int bn = len%8;
+
+		//rest, not aligned to 8-blocks
+		for( int j = bi; j < bi+bn; j++ )
+			c[ ci + bix[j] ] += aval * b[ j ];
+		
+		//unrolled 8-block (for better instruction-level parallelism)
+		for( int j = bi+bn; j < bi+len; j+=8 )
+		{
+			//read 64B cacheline of b
+			//read 64B of c via 'gather'
+			//compute c' = aval * b + c
+			//write back 64B of c = c' via 'scatter'
+			c[ ci+bix[j+0] ] += aval * b[ j+0 ];
+			c[ ci+bix[j+1] ] += aval * b[ j+1 ];
+			c[ ci+bix[j+2] ] += aval * b[ j+2 ];
+			c[ ci+bix[j+3] ] += aval * b[ j+3 ];
+			c[ ci+bix[j+4] ] += aval * b[ j+4 ];
+			c[ ci+bix[j+5] ] += aval * b[ j+5 ];
+			c[ ci+bix[j+6] ] += aval * b[ j+6 ];
+			c[ ci+bix[j+7] ] += aval * b[ j+7 ];
+		}
+	}
+
+	//note: public for use by codegen for consistency
+	public static void vectMultiplyWrite( final double aval, double[] b, double[] c, int bi, int ci, final int len )
+	{
+		final int bn = len%8;
+		
+		//rest, not aligned to 8-blocks
+		for( int j = 0; j < bn; j++, bi++, ci++)
+			c[ ci ] = aval * b[ bi ];
+		
+		//unrolled 8-block  (for better instruction-level parallelism)
+		for( int j = bn; j < len; j+=8, bi+=8, ci+=8) 
+		{
+			//read 64B cachelines of b and c
+			//compute c' = aval * b + c
+			//write back 64B cacheline of c = c'
+			c[ ci+0 ] = aval * b[ bi+0 ];
+			c[ ci+1 ] = aval * b[ bi+1 ];
+			c[ ci+2 ] = aval * b[ bi+2 ];
+			c[ ci+3 ] = aval * b[ bi+3 ];
+			c[ ci+4 ] = aval * b[ bi+4 ];
+			c[ ci+5 ] = aval * b[ bi+5 ];
+			c[ ci+6 ] = aval * b[ bi+6 ];
+			c[ ci+7 ] = aval * b[ bi+7 ];
+		}
+	}
+	
+	public static void vectMultiplyInPlace( final double aval, double[] c, int ci, final int len ) {
+		final int bn = len%8;
+		//rest, not aligned to 8-blocks
+		for( int j = 0; j < bn; j++, ci++)
+			c[ ci ] *= aval;
+		//unrolled 8-block  (for better instruction-level parallelism)
+		for( int j = bn; j < len; j+=8, ci+=8) {
+			c[ ci+0 ] *= aval; c[ ci+1 ] *= aval;
+			c[ ci+2 ] *= aval; c[ ci+3 ] *= aval;
+			c[ ci+4 ] *= aval; c[ ci+5 ] *= aval;
+			c[ ci+6 ] *= aval; c[ ci+7 ] *= aval;
+		}
+	}
+
+	//note: public for use by codegen for consistency
+	public static void vectMultiplyWrite( double[] a, double[] b, double[] c, int ai, int bi, int ci, final int len )
+	{
+		final int bn = len%8;
+		
+		//rest, not aligned to 8-blocks
+		for( int j = 0; j < bn; j++, ai++, bi++, ci++)
+			c[ ci ] = a[ ai ] * b[ bi ];
+		
+		//unrolled 8-block  (for better instruction-level parallelism)
+		for( int j = bn; j < len; j+=8, ai+=8, bi+=8, ci+=8) 
+		{
+			//read 64B cachelines of a and b
+			//compute c' = a * b
+			//write back 64B cacheline of c = c'
+			c[ ci+0 ] = a[ ai+0 ] * b[ bi+0 ];
+			c[ ci+1 ] = a[ ai+1 ] * b[ bi+1 ];
+			c[ ci+2 ] = a[ ai+2 ] * b[ bi+2 ];
+			c[ ci+3 ] = a[ ai+3 ] * b[ bi+3 ];
+			c[ ci+4 ] = a[ ai+4 ] * b[ bi+4 ];
+			c[ ci+5 ] = a[ ai+5 ] * b[ bi+5 ];
+			c[ ci+6 ] = a[ ai+6 ] * b[ bi+6 ];
+			c[ ci+7 ] = a[ ai+7 ] * b[ bi+7 ];
+		}
+	}
+	
+	public static void vectMultiplyWrite( final double[] a, double[] b, double[] c, int[] bix, final int ai, final int bi, final int ci, final int len ) {
+		final int bn = len%8;
+		//rest, not aligned to 8-blocks
+		for( int j = bi; j < bi+bn; j++ )
+			c[ ci+bix[j] ] = a[ ai+bix[j] ] * b[ j ];
+		//unrolled 8-block (for better instruction-level parallelism)
+		for( int j = bi+bn; j < bi+len; j+=8 ) {
+			c[ ci+bix[j+0] ] = a[ ai+bix[j+0] ] * b[ j+0 ];
+			c[ ci+bix[j+1] ] = a[ ai+bix[j+1] ] * b[ j+1 ];
+			c[ ci+bix[j+2] ] = a[ ai+bix[j+2] ] * b[ j+2 ];
+			c[ ci+bix[j+3] ] = a[ ai+bix[j+3] ] * b[ j+3 ];
+			c[ ci+bix[j+4] ] = a[ ai+bix[j+4] ] * b[ j+4 ];
+			c[ ci+bix[j+5] ] = a[ ai+bix[j+5] ] * b[ j+5 ];
+			c[ ci+bix[j+6] ] = a[ ai+bix[j+6] ] * b[ j+6 ];
+			c[ ci+bix[j+7] ] = a[ ai+bix[j+7] ] * b[ j+7 ];
+		}
+	}
+
+	private static void vectMultiply( double[] a, double[] c, int ai, int ci, final int len )
+	{
+		final int bn = len%8;
+		
+		//rest, not aligned to 8-blocks
+		for( int j = 0; j < bn; j++, ai++, ci++)
+			c[ ci ] *= a[ ai ];
+		
+		//unrolled 8-block  (for better instruction-level parallelism)
+		for( int j = bn; j < len; j+=8, ai+=8, ci+=8) 
+		{
+			//read 64B cachelines of a and c
+			//compute c' = c * a
+			//write back 64B cacheline of c = c'
+			c[ ci+0 ] *= a[ ai+0 ];
+			c[ ci+1 ] *= a[ ai+1 ];
+			c[ ci+2 ] *= a[ ai+2 ];
+			c[ ci+3 ] *= a[ ai+3 ];
+			c[ ci+4 ] *= a[ ai+4 ];
+			c[ ci+5 ] *= a[ ai+5 ];
+			c[ ci+6 ] *= a[ ai+6 ];
+			c[ ci+7 ] *= a[ ai+7 ];
+		}
+	}
+
+	//note: public for use by codegen for consistency
+	public static void vectAdd( double[] a, double bval, double[] c, int ai, int ci, final int len ) {
+		final int bn = len%8;
+		//rest, not aligned to 8-blocks
+		for( int j = 0; j < bn; j++, ai++, ci++)
+			c[ ci ] += a[ ai ];
+		//unrolled 8-block  (for better ILP)
+		for( int j = bn; j < len; j+=8, ai+=8, ci+=8) {
+			c[ ci+0 ] += a[ ai+0 ] + bval;
+			c[ ci+1 ] += a[ ai+1 ] + bval;
+			c[ ci+2 ] += a[ ai+2 ] + bval;
+			c[ ci+3 ] += a[ ai+3 ] + bval;
+			c[ ci+4 ] += a[ ai+4 ] + bval;
+			c[ ci+5 ] += a[ ai+5 ] + bval;
+			c[ ci+6 ] += a[ ai+6 ] + bval;
+			c[ ci+7 ] += a[ ai+7 ] + bval;
+		}
+	}
+	
+	//note: public for use by codegen for consistency
+	public static void vectAdd( double[] a, double[] c, int ai, int ci, final int len )
+	{
+		final int bn = len%8;
+		
+		//rest, not aligned to 8-blocks
+		for( int j = 0; j < bn; j++, ai++, ci++)
+			c[ ci ] += a[ ai ];
+		
+		//unrolled 8-block  (for better instruction-level parallelism)
+		for( int j = bn; j < len; j+=8, ai+=8, ci+=8) 
+		{
+			//read 64B cachelines of a and c
+			//compute c' = c * a
+			//write back 64B cacheline of c = c'
+			c[ ci+0 ] += a[ ai+0 ];
+			c[ ci+1 ] += a[ ai+1 ];
+			c[ ci+2 ] += a[ ai+2 ];
+			c[ ci+3 ] += a[ ai+3 ];
+			c[ ci+4 ] += a[ ai+4 ];
+			c[ ci+5 ] += a[ ai+5 ];
+			c[ ci+6 ] += a[ ai+6 ];
+			c[ ci+7 ] += a[ ai+7 ];
+		}
+	}
+
+	public static void vectAdd( double[] a, double[] c, int[] aix, int ai, int ci, final int alen ) {
+		final int bn = alen%8;
+		//rest, not aligned to 8-blocks
+		for( int j = ai; j < ai+bn; j++ )
+			c[ ci+aix[j] ] += a[ j ];
+		//unrolled 8-block  (for better instruction-level parallelism)
+		for( int j = ai+bn; j < ai+alen; j+=8 ) {
+			c[ ci+aix[j+0] ] += a[ j+0 ];
+			c[ ci+aix[j+1] ] += a[ j+1 ];
+			c[ ci+aix[j+2] ] += a[ j+2 ];
+			c[ ci+aix[j+3] ] += a[ j+3 ];
+			c[ ci+aix[j+4] ] += a[ j+4 ];
+			c[ ci+aix[j+5] ] += a[ j+5 ];
+			c[ ci+aix[j+6] ] += a[ j+6 ];
+			c[ ci+aix[j+7] ] += a[ j+7 ];
+		}
+	}
+	
+	private static void vectAdd4( double[] a1, double[] a2, double[] a3, double[] a4, double[] c, int ai, int ci, final int len )
+	{
+		final int bn = len%8;
+		
+		//rest, not aligned to 8-blocks
+		for( int j = 0; j < bn; j++, ai++, ci++)
+			c[ ci ] += a1[ ai ] + a2[ ai ] + a3[ ai ] + a4[ ai ];
+		
+		//unrolled 8-block  (for better instruction-level parallelism)
+		for( int j = bn; j < len; j+=8, ai+=8, ci+=8) 
+		{
+			//read 64B cachelines of a (4x) and c
+			//compute c' = c + a1 + a2 + a3 + a4
+			//write back 64B cacheline of c = c'
+			c[ ci+0 ] += a1[ ai+0 ] + a2[ ai+0 ] + a3[ ai+0 ] + a4[ ai+0 ];
+			c[ ci+1 ] += a1[ ai+1 ] + a2[ ai+1 ] + a3[ ai+1 ] + a4[ ai+1 ];
+			c[ ci+2 ] += a1[ ai+2 ] + a2[ ai+2 ] + a3[ ai+2 ] + a4[ ai+2 ];
+			c[ ci+3 ] += a1[ ai+3 ] + a2[ ai+3 ] + a3[ ai+3 ] + a4[ ai+3 ];
+			c[ ci+4 ] += a1[ ai+4 ] + a2[ ai+4 ] + a3[ ai+4 ] + a4[ ai+4 ];
+			c[ ci+5 ] += a1[ ai+5 ] + a2[ ai+5 ] + a3[ ai+5 ] + a4[ ai+5 ];
+			c[ ci+6 ] += a1[ ai+6 ] + a2[ ai+6 ] + a3[ ai+6 ] + a4[ ai+6 ];
+			c[ ci+7 ] += a1[ ai+7 ] + a2[ ai+7 ] + a3[ ai+7 ] + a4[ ai+7 ];
+		}
+	}
+	
+	private static void vectAddAll(double[][] a, double[] c, int ai, int ci, final int len) {
+		int bi = a.length % 4;
+		//process stride for remaining blocks
+		for(int i=0; i<bi; i++)
+			vectAdd(a[i], c, ai, ci, len);
+		//process stride in 4 blocks at a time
+		for(int i=bi; i<a.length; i+=4)
+			vectAdd4(a[i], a[i+1], a[i+2], a[i+3], c, ai, ci, len);
+	}
+	
+	public static void vectAddInPlace(double aval, double[] c, final int ci, final int len) {
+		final int bn = len%8;
+		//rest, not aligned to 8-blocks
+		for( int j = ci; j < ci+bn; j++)
+			c[ j ] += aval;
+		//unrolled 8-block  (for better instruction-level parallelism)
+		for( int j = ci+bn; j < ci+len; j+=8) {
+			c[ j+0 ] += aval; c[ j+1 ] += aval; 
+			c[ j+2 ] += aval; c[ j+3 ] += aval;
+			c[ j+4 ] += aval; c[ j+5 ] += aval;
+			c[ j+6 ] += aval; c[ j+7 ] += aval;
+		}
+	}
+
+	private static void vectSubtract( double[] a, double[] c, int ai, int ci, final int len )
+	{
+		final int bn = len%8;
+		
+		//rest, not aligned to 8-blocks
+		for( int j = 0; j < bn; j++, ai++, ci++)
+			c[ ci ] -= a[ ai ];
+		
+		//unrolled 8-block  (for better instruction-level parallelism)
+		for( int j = bn; j < len; j+=8, ai+=8, ci+=8) 
+		{
+			//read 64B cachelines of a and c
+			//compute c' = c * a
+			//write back 64B cacheline of c = c'
+			c[ ci+0 ] -= a[ ai+0 ];
+			c[ ci+1 ] -= a[ ai+1 ];
+			c[ ci+2 ] -= a[ ai+2 ];
+			c[ ci+3 ] -= a[ ai+3 ];
+			c[ ci+4 ] -= a[ ai+4 ];
+			c[ ci+5 ] -= a[ ai+5 ];
+			c[ ci+6 ] -= a[ ai+6 ];
+			c[ ci+7 ] -= a[ ai+7 ];
+		}
+	}
+
+	private static double wsigmoid( final double wij, double[] u, double[] v, final int uix, final int vix, final boolean flagminus, final boolean flaglog, final int len )
+	{
+		//compute dot product over ui vj 
+		double uvij = dotProduct(u, v, uix, vix, len);
+		
+		//compute core sigmoid function  
+		double cval = flagminus ?
+				1 / (1 + FastMath.exp(uvij)) :
+				1 / (1 + FastMath.exp(-uvij));
+				
+		//compute weighted output
+		return wij * ((flaglog) ? Math.log(cval) : cval);
+	}
+
+	private static double wsigmoid( final double wij, MatrixBlock u, MatrixBlock v, final int uix, final int vix, final boolean flagminus, final boolean flaglog, final int len )
+	{
+		//compute dot product over ui vj 
+		double uvij = dotProductGeneric(u, v, uix, vix, len);
+		
+		//compute core sigmoid function  
+		double cval = flagminus ?
+				1 / (1 + FastMath.exp(uvij)) :
+				1 / (1 + FastMath.exp(-uvij));
+				
+		//compute weighted output
+		return wij * ((flaglog) ? Math.log(cval) : cval);
+	}
+	
+	private static void wdivmm( final double wij, double[] u, double[] v, double[] c, final int uix, final int vix, final boolean left, final boolean mult, final boolean minus, final int len )
+	{
+		//compute dot product over ui vj
+		double uvij = dotProduct(u, v, uix, vix, len);
+		
+		//compute core wdivmm  
+		double tmpval = minus ? uvij - wij :
+			mult ? wij * uvij : wij / uvij;
+		
+		//prepare inputs for final mm
+		int bix = left ? uix : vix;
+		int cix = left ? vix : uix;
+		double[] b = left ? u : v;
+		
+		//compute final mm output
+		vectMultiplyAdd(tmpval, b, c, bix, cix, len);
+	}
+
+	private static void wdivmm( final double wij, final double xij, double[] u, double[] v, double[] c, final int uix, final int vix, final boolean left, final boolean scalar, final int len )
+	{
+		//compute dot product over ui vj 
+		double uvij = dotProduct(u, v, uix, vix, len);
+		
+		//compute core wdivmm  
+		double tmpval = scalar ? wij / (uvij + xij) : wij * (uvij - xij);
+		
+		//prepare inputs for final mm
+		int bix = left ? uix : vix;
+		int cix = left ? vix : uix;
+		double[] b = left ? u : v;
+		
+		//compute final mm output
+		vectMultiplyAdd(tmpval, b, c, bix, cix, len);
+	}
+
+	private static void wdivmm( final double wij, MatrixBlock u, MatrixBlock v, double[] c, final int uix, final int vix, final boolean left, boolean mult, final boolean minus, final int len )
+	{
+		//compute dot product over ui vj 
+		double uvij = dotProductGeneric(u, v, uix, vix, len);
+		
+		//compute core wdivmm
+		double wtmp = minus ? uvij - wij :
+			mult ? wij * uvij : wij / uvij;
+		
+		//prepare inputs for final mm
+		int bix = left ? uix : vix;
+		int cix = left ? vix*len : uix*len;
+		MatrixBlock b = left ? u : v;
+		
+		//compute final mm
+		for( int k2=0; k2<len; k2++ )
+			c[cix+k2] += b.get(bix, k2) * wtmp;
+	}
+
+	private static void wdivmm( final double wij, final double xij, MatrixBlock u, MatrixBlock v, double[] c, final int uix, final int vix, final boolean left, final boolean scalar, final int len )
+	{
+		//compute dot product over ui vj 
+		double uvij = dotProductGeneric(u, v, uix, vix, len);
+		
+		//compute core wdivmm
+		double wtmp = scalar ? wij / (uvij + xij) : wij * (uvij - xij);
+		
+		//prepare inputs for final mm
+		int bix = left ? uix : vix;
+		int cix = left ? vix*len : uix*len;
+		MatrixBlock b = left ? u : v;
+		
+		//compute final mm
+		for( int k2=0; k2<len; k2++ )
+			c[cix+k2] += b.get(bix, k2) * wtmp;
+	}
+
+	private static double wumm( final double wij, double[] u, double[] v, final int uix, final int vix, final boolean flagmult, ValueFunction fn, final int len ) {
+		//compute dot product over ui vj 
+		double uvij = dotProduct(u, v, uix, vix, len);
+		
+		//compute unary operations
+		double cval = fn.execute(uvij);
+		
+		//compute weighted output
+		return flagmult ? wij * cval : wij / cval;
+	}
+
+	private static double wumm( final double wij, MatrixBlock u, MatrixBlock v, final int uix, final int vix, final boolean flagmult, ValueFunction fn, final int len ) {
+		//compute dot product over ui vj 
+		double uvij = dotProductGeneric(u, v, uix, vix, len);
+
+		//compute unary operations
+		double cval = fn.execute(uvij);
+		
+		//compute weighted output
+		return flagmult ? wij * cval : wij / cval;
+	}
+
+	private static double dotProductGeneric(MatrixBlock a, MatrixBlock b, final int ai, final int bi, int len)
+	{
+		double val = 0;
+		for( int k2=0; k2<len; k2++ )
+			val += a.get(ai, k2) * b.get(bi, k2);
+		
+		return val;
+	}
+	
+	private static double dotProductGeneric(MatrixBlock a, MatrixBlock b)
+	{
+		double val = 0;
+		for( int i=0; i<a.getNumRows(); i++ )
+			for( int j=0; j<a.getNumColumns(); j++ )
+				val += a.get(i, j) * b.get(i, j);
+		
+		return val;
+	}
+	
+	public static long copyUpperToLowerTriangle( MatrixBlock ret ) {
+		return ret.sparse ?
+			copyUpperToLowerTriangleSparse(ret) :
+			copyUpperToLowerTriangleDense(ret);
+	}
+	
+	
+	/**
+	 * Used for all version of TSMM where the result is known to be symmetric.
+	 * Hence, we compute only the upper triangular matrix and copy this partial
+	 * result down to lower triangular matrix once.
+	 * 
+	 * @param ret matrix
+	 * @return number of non zeros
+	 */
+	public static long copyUpperToLowerTriangleDense( MatrixBlock ret )
+	{
+		//ret is guaranteed to be a squared, symmetric matrix
+		if( ret.rlen != ret.clen )
+			throw new RuntimeException("Invalid non-squared input matrix.");
+		
+		final double[] c = ret.getDenseBlockValues();
+		final int n = ret.rlen;
+		long nnz = 0;
+		
+		//blocked execution (2x128KB for L2 blocking)
+		final int blocksizeIJ = 128; 
+		
+		//handle blocks on diagonal
+		for( int bi = 0; bi<n; bi+=blocksizeIJ ) {
+			int bimin = Math.min(bi+blocksizeIJ, n);
+			for( int i=bi, rix=bi*n; i<bimin; i++, rix+=n ) {
+				LibMatrixReorg.transposeRow(c, c, rix+bi, bi*n+i, n, bimin-bi);
+				nnz += (c[rix+i] != 0) ? 1 : 0; //for diagonal element
+				for( int j=rix+i+1; j<rix+bimin; j++ )
+					nnz += (c[j] != 0) ? 2 : 0;
+			}
+		}
+		
+		//handle non-diagonal blocks (full block copies)
+		for( int bi = 0; bi<n; bi+=blocksizeIJ ) {
+			int bimin = Math.min(bi+blocksizeIJ, n);
+			for( int bj = bi; bj<n; bj+=blocksizeIJ ) 
+				if( bi != bj ) { //not on diagonal
+					int bjmin = Math.min(bj+blocksizeIJ, n);
+					for( int i=bi, rix=bi*n; i<bimin; i++, rix+=n ) {
+						LibMatrixReorg.transposeRow(c, c, rix+bj, bj*n+i, n, bjmin-bj);
+						for( int j=rix+bj; j<rix+bjmin; j++ )
+							nnz += (c[j] != 0) ? 2 : 0;
+					}
+				}
+		}
+		
+		return nnz;
+	}
+
+	public static long copyUpperToLowerTriangleSparse( MatrixBlock ret )
+	{
+		//ret is guaranteed to be a squared, symmetric matrix
+		if( ret.rlen != ret.clen )
+			throw new RuntimeException("Invalid non-squared input matrix.");
+		
+		SparseBlock c = ret.getSparseBlock();
+		int n = ret.rlen;
+		long nnz = 0;
+		
+		//copy non-diagonal values from upper-triangular matrix
+		for(int i=0; i<n; i++) {
+			if(c.isEmpty(i)) continue;
+			int cpos = c.pos(i);
+			//int cpos2 = c.posFIndexGTE(i, i);
+			//if( cpos2 < 0 ) continue;
+			int clen = c.size(i);
+			int[] cix = c.indexes(i);
+			double[] cvals = c.values(i);
+			for(int k=cpos; k<cpos+clen; k++) {
+				if( cix[k] == i )
+					nnz ++;
+				else if( cix[k] > i ) {
+					c.append(cix[k], i, cvals[k]);
+					nnz += 2;
+				}
+			}
+		}
+		
+		//sort sparse rows (because append out of order)
+		c.sort();
+		
+		return nnz;
+	}
+	
+	public static MatrixBlock prepMatrixMultTransposeSelfInput( MatrixBlock m1, boolean leftTranspose, boolean par ) {
+		MatrixBlock ret = m1;
+		final int rlen = m1.rlen;
+		final int clen = m1.clen;
+		boolean retSparse = isSparseOutputTSMM(m1);
+		
+		if( !leftTranspose && !retSparse && m1.sparse && rlen > 1) { //X%*%t(X) SPARSE MATRIX
+			//directly via LibMatrixReorg in order to prevent sparsity change
+			MatrixBlock tmpBlock = new MatrixBlock(clen, rlen, m1.sparse);
+			LibMatrixReorg.reorg(m1, tmpBlock, new ReorgOperator(SwapIndex.getSwapIndexFnObject()));
+			ret = tmpBlock;
+		}
+		else if( leftTranspose && !retSparse && m1.sparse && m1.sparseBlock instanceof SparseBlockCSR ) {
+			//for a special case of CSR inputs where all non-empty rows are dense, we can
+			//create a shallow copy of the values arrays to a "dense" block and perform
+			//tsmm with the existing dense block operations w/o unnecessary gather/scatter
+			SparseBlockCSR sblock = (SparseBlockCSR)m1.sparseBlock;
+			boolean convertDense = (par ?
+				IntStream.range(0, rlen).parallel() : IntStream.range(0, rlen))
+				.allMatch(i -> sblock.isEmpty(i) || sblock.size(i)==clen );
+			if( convertDense ) {
+				int rows = (int) sblock.size() / clen;
+				MatrixBlock tmpBlock = new MatrixBlock(rows, clen, false);
+				tmpBlock.denseBlock = DenseBlockFactory
+					.createDenseBlock(sblock.values(), rows, clen);
+				tmpBlock.setNonZeros(m1.nonZeros);
+				ret = tmpBlock;
+			}
+		}
+		
+		return ret;
+	}
+
+	private static boolean checkPrepMatrixMultRightInput( MatrixBlock m1, MatrixBlock m2 ) {
+		//transpose if dense-dense, skinny rhs matrix (not vector), and memory guarded by output 
+		return (LOW_LEVEL_OPTIMIZATION && !m1.sparse && !m2.sparse 
+			&& isSkinnyRightHandSide(m1.rlen, m1.clen, m2.rlen, m2.clen, true));
+	}
+	
+	//note: public for use by codegen for consistency
+	public static boolean isSkinnyRightHandSide(long m1rlen, long m1clen, long m2rlen, long m2clen, boolean inclCacheSize) {
+		return m1rlen > m2clen && m2rlen > m2clen && m2clen > 1 
+			&& m2clen < 64 && (!inclCacheSize || 8*m2rlen*m2clen < L2_CACHESIZE);
+	}
+	
+	private static boolean checkParMatrixMultRightInputRows( MatrixBlock m1, MatrixBlock m2, int k ) {
+		//parallelize over rows in rhs matrix if number of rows in lhs/output is very small
+		double jvmMem = InfrastructureAnalyzer.getLocalMaxMemory();
+		return (m1.rlen==1 && LOW_LEVEL_OPTIMIZATION && !(m1.isUltraSparse()||m2.isUltraSparse()))
+			|| (m1.rlen<=16 && LOW_LEVEL_OPTIMIZATION && m2.rlen > m1.rlen 
+			   && ( !m1.isUltraSparse() && !(m1.sparse & m2.sparse) ) //dense-dense / sparse-dense / dense-sparse
+			   && (long)k * 8 * m1.rlen * m2.clen < Math.max(MEM_OVERHEAD_THRESHOLD,0.01*jvmMem) );
+	}
+
+	private static boolean checkParMatrixMultRightInputCols( MatrixBlock m1, MatrixBlock m2, int k, boolean pm2r ) {
+		//parallelize over cols in rhs matrix if dense, number of cols in rhs is large, and lhs fits in l2
+		return (LOW_LEVEL_OPTIMIZATION && !m1.sparse && !m2.sparse 
+				&& m2.clen > k * 1024 && m1.rlen < k * 32 && !pm2r
+				&& 8*m1.rlen*m1.clen < 256*1024 ); //lhs fits in L2 cache
+	}
+	
+	public static boolean satisfiesMultiThreadingConstraints(MatrixBlock m1, int k) {
+		return satisfiesMultiThreadingConstraints(m1, true, false, -1, k);
+	}
+	
+	public static boolean satisfiesMultiThreadingConstraints(MatrixBlock m1, boolean checkMem, boolean checkFLOPs, long FPfactor, int k) {
+		boolean sharedTP = (InfrastructureAnalyzer.getLocalParallelism() == k);
+		double jvmMem = InfrastructureAnalyzer.getLocalMaxMemory();
+		return k > 1 && LOW_LEVEL_OPTIMIZATION
+			&& (!checkMem || 8L * m1.clen * k < Math.max(MEM_OVERHEAD_THRESHOLD,0.01*jvmMem))
+			&& (!checkFLOPs || FPfactor * m1.rlen * m1.clen >
+			(sharedTP ? PAR_MINFLOP_THRESHOLD2 : PAR_MINFLOP_THRESHOLD1));
+	}
+	
+	public static boolean satisfiesMultiThreadingConstraints(MatrixBlock m1, MatrixBlock m2, boolean checkMem, boolean checkFLOPs, long FPfactor, int k) {
+		boolean sharedTP = (InfrastructureAnalyzer.getLocalParallelism() == k);
+		double jvmMem = InfrastructureAnalyzer.getLocalMaxMemory();
+		return k > 1 && LOW_LEVEL_OPTIMIZATION
+			&& (!checkMem || 8L * m2.clen * k < Math.max(MEM_OVERHEAD_THRESHOLD,0.01*jvmMem))
+			//note: cast to double to avoid long overflows on ultra-sparse matrices
+			//due to FLOP computation based on number of cells not non-zeros
+			&& (!checkFLOPs || (double)FPfactor * m1.rlen * m1.clen * m2.clen >
+			(sharedTP ? PAR_MINFLOP_THRESHOLD2 : PAR_MINFLOP_THRESHOLD1));
+	}
+	
+	private static boolean satisfiesMultiThreadingConstraintsTSMM(MatrixBlock m1, boolean leftTranspose, double FPfactor, int k) {
+		boolean sharedTP = (InfrastructureAnalyzer.getLocalParallelism() == k);
+		double threshold = sharedTP ? PAR_MINFLOP_THRESHOLD2 : PAR_MINFLOP_THRESHOLD1;
+		return k > 1 && LOW_LEVEL_OPTIMIZATION && (leftTranspose?m1.clen:m1.rlen)!=1
+			&& ((leftTranspose && FPfactor * m1.rlen * m1.clen * m1.clen > threshold)
+			||(!leftTranspose && FPfactor * m1.clen * m1.rlen * m1.rlen > threshold));
+	}
+	
+	public static boolean isUltraSparseMatrixMult(MatrixBlock m1, MatrixBlock m2, boolean m1Perm) {
+		if( m2.clen == 1 ) //mv always dense
+			return false;
+		//note: ultra-sparse matrix mult implies also sparse outputs, hence we need
+		//to be conservative an cannot use this for all ultra-sparse matrices.
+		double outSp = OptimizerUtils.getMatMultSparsity(
+			m1.getSparsity(), m2.getSparsity(), m1.rlen, m1.clen, m2.clen, true);
+		return (m1.isUltraSparse() || m2.isUltraSparse()) //base case
+			|| (m1.isUltraSparse(false) && m1 == m2) //ultra-sparse self product
+			|| (m1Perm && OptimizerUtils.getSparsity(m2.rlen, m2.clen, m2.nonZeros)<1.0)
+			|| ((m1.isUltraSparse(false) || m2.isUltraSparse(false)) 
+				&& outSp < MatrixBlock.ULTRA_SPARSITY_TURN_POINT2)
+			|| (m1.isInSparseFormat() // otherwise no matching branch
+				&& m1.getSparsity() < MatrixBlock.ULTRA_SPARSITY_TURN_POINT2
+				&& m1.getNonZeros() < MatrixBlock.ULTRA_SPARSE_BLOCK_NNZ
+				&& m1.getLength()+m2.getLength() < (long)m1.rlen*m2.clen
+				&& outSp < MatrixBlock.SPARSITY_TURN_POINT);
+	}
+	
+	public static boolean isSparseOutputMatrixMult(MatrixBlock m1, MatrixBlock m2) {
+		//output is a matrix (not vector), very likely sparse, and output rows fit into L1 cache
+		if( !(m1.sparse && m2.sparse && m1.rlen > 1 && m2.clen > 1) )
+			return false;
+		double estSp = OptimizerUtils.getMatMultSparsity(
+			m1.getSparsity(), m2.getSparsity(), m1.rlen, m1.clen, m2.clen, false);
+		long estNnz = (long)(estSp * m1.rlen * m2.clen);
+		boolean sparseOut = MatrixBlock.evalSparseFormatInMemory(m1.rlen, m2.clen, estNnz);
+		return m2.clen < 4*1024 && sparseOut;
+	}
+	
+	public static boolean isSparseOutputTSMM(MatrixBlock m1) {
+		return isSparseOutputTSMM(m1, false);
+	}
+	
+	public static boolean isSparseOutputTSMM(MatrixBlock m1, boolean ultraSparse) {
+		double sp = m1.getSparsity();
+		double osp = OptimizerUtils.getMatMultSparsity(sp, sp, m1.rlen, m1.clen, m1.rlen, false);
+		double sp_threshold = ultraSparse ?
+			MatrixBlock.ULTRA_SPARSITY_TURN_POINT : MatrixBlock.ULTRA_SPARSITY_TURN_POINT2;
+		return m1.sparse && osp < sp_threshold;
+	}
+
+	public static boolean isOuterProductTSMM(int rlen, int clen, boolean left) {
+		return left ? rlen == 1 & clen > 1 : rlen > 1 & clen == 1;
+	}
+
+	private static MatrixBlock prepMatrixMultRightInput( MatrixBlock m1, MatrixBlock m2, boolean tm2 ) {
+		MatrixBlock ret = m2;
+		
+		//transpose if dense-dense, skinny rhs matrix (not vector), and memory guarded by output 
+		if( tm2 ) {
+			MatrixBlock tmpBlock = new MatrixBlock(m2.clen, m2.rlen, m2.sparse);
+			ret = LibMatrixReorg.reorg(m2, tmpBlock, new ReorgOperator(SwapIndex.getSwapIndexFnObject()));
+		}
+		
+		return ret;
+	}
+
+	//cp non-zeros for dense-dense mm
+	private static int copyNonZeroElements( double[] a, final int aixi, final int bixk, final int n, double[] tmpa, int[] tmpbi, final int bklen ) {
+		int knnz = 0;
+		for( int k = 0; k < bklen; k++ )
+			if( segment1.getAtIndex(layout, aixi+k) != 0 ) {
+				tmpa[ knnz ] = segment1.getAtIndex(layout, aixi+k);
+				tmpbi[ knnz ] = bixk + k*n;
+				knnz ++;
+			}
+		return knnz;
+	}
+
+	//cp non-zeros for dense tsmm
+	private static int copyNonZeroElements( double[] a, int aixi, int bixk, final int n, final int nx, double[] tmpa, int[] tmpbi, final int bklen ) {
+		int knnz = 0;
+		for( int k = 0; k < bklen; k++, aixi+=n, bixk+=nx )
+			if( a[ aixi ] != 0 ) {
+				tmpa[ knnz ] = a[ aixi ];
+				tmpbi[ knnz ] = bixk;
+				knnz ++;
+			}
+		return knnz;
+	}
+	
+	@SuppressWarnings("unused")
+	private static void compactSparseOutput(MatrixBlock ret) {
+		if( !ret.sparse || ret.nonZeros > ret.rlen || ret.isEmpty() 
+			|| ret.getSparseBlock() instanceof SparseBlockCSR )
+			return; //early abort
+		ret.sparseBlock = SparseBlockFactory
+			.copySparseBlock(Type.CSR, ret.sparseBlock, false);
+	}
+	
+	@SuppressWarnings("unused")
+	private static void resetPosVect(int[] curk, SparseBlock sblock, int rl, int ru) {
+		if( sblock instanceof SparseBlockMCSR ) {
+			//all rows start at position 0 (individual arrays)
+			Arrays.fill(curk, 0, ru-rl, 0);
+		}
+		else if( sblock instanceof SparseBlockCSR ) {
+			//row start positions given in row ptr array
+			SparseBlockCSR csr = (SparseBlockCSR) sblock;
+			System.arraycopy(csr.rowPointers(), rl, curk, 0, ru-rl);
+		}
+		else { //general case
+			for(int i=rl; i<ru; i++)
+				curk[i-rl] = sblock.pos(i);
+		}
+	}
+
+	private static void sumScalarResults(List<Future<Double>> tasks, MatrixBlock ret) 
+		throws InterruptedException, ExecutionException
+	{
+		//aggregate partial results and check for errors
+		double val = 0;
+		for(Future<Double> task : tasks)
+			val += task.get();
+		ret.set(0, 0, val);
+	}
+
+	@SuppressWarnings("unused")
+	private static void sumDenseResults( double[][] partret, double[] ret )
+	{
+		final int len = ret.length;
+		final int k = partret.length;
+		final int bk = k % 4;
+		final int blocksize = 2 * 1024; //16KB (half of common L1 data)
+		
+		//cache-conscious aggregation to prevent repreated scans/writes of ret
+		for( int bi=0; bi<len; bi+=blocksize ) {
+			int llen = Math.min(len-bi, blocksize);
+			
+			//aggregate next block from all partial results
+			for( int j=0; j<bk; j++ ) //rest (not aligned to 4)
+				vectAdd(partret[j], ret, bi, bi, llen);
+			for( int j=bk; j<k; j+=4 ) //4 partial results at a time
+				vectAdd4(partret[j], partret[j+1], partret[j+2], partret[j+3], ret, bi, bi, llen);
+		}
+		
+	}
+	
+	/////////////////////////////////////////////////////////
+	// Task Implementations for Multi-Threaded Operations  //
+	/////////////////////////////////////////////////////////
+
+	private static class MatrixMultTask implements Callable<Object> 
+	{
+		private final MatrixBlock _m1;
+		private final MatrixBlock _m2;
+		private MatrixBlock _ret = null;
+		private final boolean _tm2; //transposed m2
+		private final boolean _pm2r; //par over m2 rows
+		private final boolean _pm2c; //par over m2 rows
+		private final boolean _m1Perm; //sparse permutation
+		private final boolean _sparse; //sparse output
+		private final int _rl;
+		private final int _ru;
+		private final ConcurrentHashMap<double[], double[]> _cache;
+
+		protected MatrixMultTask( MatrixBlock m1, MatrixBlock m2, MatrixBlock ret,
+			boolean tm2, boolean pm2r, boolean pm2c, boolean m1Perm, boolean sparse, int rl, int ru, ConcurrentHashMap<double[], double[]> cache )
+		{
+			_m1 = m1;
+			_m2 = m2;
+			_tm2 = tm2;
+			_pm2r = pm2r;
+			_pm2c = pm2c;
+			_m1Perm = m1Perm;
+			_sparse = sparse;
+			_rl = rl;
+			_ru = ru;
+			_cache = cache;
+
+			if( pm2r ) { //vector-matrix / matrix-matrix
+				//allocate local result for partial aggregation
+				_ret = new MatrixBlock(ret.rlen, ret.clen, false);
+			}
+			else { //default case
+				_ret = ret;
+			}
+		}
+		
+		@Override
+		public Object call() {
+			//setup target index ranges
+			int rl = _pm2c ? 0 : _rl;
+			int ru = _pm2c ? _m1.rlen : _ru;
+			int cl = _pm2c ? _rl : 0;
+			int cu = _pm2c ? _ru : _ret.clen;
+			
+			//thread-local allocation
+			if( _pm2r )
+				_ret.allocateDenseBlock();
+			
+			//compute block matrix multiplication
+			if( _ret.sparse ) //ultra-sparse
+				matrixMultUltraSparse(_m1, _m2, _ret, _m1Perm, rl, ru);
+			else if(!_m1.sparse && !_m2.sparse)
+				if(_m1.denseBlock instanceof DenseBlockFP64DEDUP && _m2.denseBlock.isContiguous(0,_m1.clen) && cl == 0 && cu == _m2.clen)
+					matrixMultDenseDenseMMDedup((DenseBlockFP64DEDUP) _m1.denseBlock, _m2.denseBlock, (DenseBlockFP64DEDUP) _ret.denseBlock, _m2.clen, _m1.clen, rl, ru, _cache);
+				else
+					matrixMultDenseDense(_m1, _m2, _ret, _tm2, _pm2r, rl, ru, cl, cu);
+
+			else if(_m1.sparse && _m2.sparse)
+				matrixMultSparseSparse(_m1, _m2, _ret, _pm2r, _sparse, rl, ru);
+			else if(_m1.sparse)
+				matrixMultSparseDense(_m1, _m2, _ret, _pm2r, rl, ru);
+			else
+				matrixMultDenseSparse(_m1, _m2, _ret, _pm2r, rl, ru);
+			
+			//maintain block nnz (upper bounds inclusive)
+			if( !_pm2r )
+				return _ret.recomputeNonZeros(rl, ru-1, cl, cu-1);
+			else
+				return _ret.getDenseBlockValues();
+		}
+	}
+
+	private static class MatrixMultChainTask implements Callable<double[]> 
+	{
+		private MatrixBlock _m1  = null;
+		private MatrixBlock _m2  = null;
+		private MatrixBlock _m3  = null;
+		private ChainType _ct = null;
+		private int _rl = -1;
+		private int _ru = -1;
+
+		protected MatrixMultChainTask( MatrixBlock mX, MatrixBlock mV, MatrixBlock mW, ChainType ct, int rl, int ru ) {
+			_m1 = mX;
+			_m2 = mV;
+			_m3 = mW;
+			_ct = ct;
+			_rl = rl;
+			_ru = ru;
+		}
+		
+		@Override
+		public double[] call() {
+			//thread-local allocation for partial aggregation
+			MatrixBlock ret = new MatrixBlock(1, _m1.clen, false);
+			ret.allocateDenseBlock();
+			
+			if( _m1.sparse )
+				matrixMultChainSparse(_m1, _m2, _m3, ret, _ct, _rl, _ru);
+			else
+				matrixMultChainDense(_m1, _m2, _m3, ret, _ct, _rl, _ru);
+			
+			//NOTE: we dont do global aggregation from concurrent tasks in order
+			//to prevent synchronization (sequential aggregation led to better 
+			//performance after JIT)
+			return ret.getDenseBlockValues();
+		}
+	}
+
+	private static class MatrixMultTransposeTask implements Callable<Object> 
+	{
+		private final MatrixBlock _m1;
+		private final MatrixBlock _m1t;
+		private final MatrixBlock _ret;
+		private final boolean _left;
+		private final int _rl;
+		private final int _ru;
+
+		protected MatrixMultTransposeTask( MatrixBlock m1, MatrixBlock m1t, MatrixBlock ret, boolean left, int rl, int ru )
+		{
+			_m1 = m1;
+			_m1t = m1t;
+			_ret = ret;
+			_left = left;
+			_rl = rl;
+			_ru = ru;
+		}
+		
+		@Override
+		public Object call() {
+			matrixMultTransposeSelf(_m1, _m1t, _ret, _left, _rl, _ru);
+			return null;
+		}
+	}
+
+	private static class MatrixMultPermuteTask implements Callable<Object> 
+	{
+		private MatrixBlock _pm1  = null;
+		private MatrixBlock _m2 = null;
+		private MatrixBlock _ret1 = null;
+		private MatrixBlock _ret2 = null;
+		private int _rl = -1;
+		private int _ru = -1;
+
+		protected MatrixMultPermuteTask( MatrixBlock pm1, MatrixBlock m2, MatrixBlock ret1, MatrixBlock ret2, int rl, int ru)
+		{
+			_pm1 = pm1;
+			_m2 = m2;
+			_ret1 = ret1;
+			_ret2 = ret2;
+			_rl = rl;
+			_ru = ru;
+		}
+		
+		@Override
+		public Object call() {
+			if( _m2.sparse )
+				matrixMultPermuteSparse(_pm1, _m2, _ret1, _ret2, _rl, _ru);
+			else if( _ret1.sparse )
+				matrixMultPermuteDenseSparse(_pm1, _m2, _ret1, _ret2, _rl, _ru);
+			else 
+				matrixMultPermuteDense(_pm1, _m2, _ret1, _ret2, _rl, _ru);
+
+			return null;
+		}
+	}
+
+	private static class MatrixMultWSLossTask implements Callable<Double>
+	{
+		private MatrixBlock _mX = null;
+		private MatrixBlock _mU = null;
+		private MatrixBlock _mV = null;
+		private MatrixBlock _mW = null;
+		private MatrixBlock _ret = null;
+		private WeightsType _wt = null;
+		private int _rl = -1;
+		private int _ru = -1;
+
+		protected MatrixMultWSLossTask(MatrixBlock mX, MatrixBlock mU, MatrixBlock mV, MatrixBlock mW, WeightsType wt, int rl, int ru) {
+			_mX = mX;
+			_mU = mU;
+			_mV = mV;
+			_mW = mW;
+			_wt = wt;
+			_rl = rl;
+			_ru = ru;
+			
+			//allocate local result for partial aggregation
+			_ret = new MatrixBlock(1, 1, false);
+			_ret.allocateDenseBlock();
+		}
+		
+		@Override
+		public Double call() {
+			if( !_mX.sparse && !_mU.sparse && !_mV.sparse && (_mW==null || !_mW.sparse) 
+				&& !_mX.isEmptyBlock() && !_mU.isEmptyBlock() && !_mV.isEmptyBlock() 
+				&& (_mW==null || !_mW.isEmptyBlock()))
+				matrixMultWSLossDense(_mX, _mU, _mV, _mW, _ret, _wt, _rl, _ru);
+			else if( _mX.sparse && !_mU.sparse && !_mV.sparse && (_mW==null || _mW.sparse)
+				    && !_mX.isEmptyBlock() && !_mU.isEmptyBlock() && !_mV.isEmptyBlock() 
+				    && (_mW==null || !_mW.isEmptyBlock()))
+				matrixMultWSLossSparseDense(_mX, _mU, _mV, _mW, _ret, _wt, _rl, _ru);
+			else
+				matrixMultWSLossGeneric(_mX, _mU, _mV, _mW, _ret, _wt, _rl, _ru);
+
+			return _ret.get(0, 0);
+		}
+	}
+
+	private static class MatrixMultWSigmoidTask implements Callable<Long> 
+	{
+		private MatrixBlock _mW = null;
+		private MatrixBlock _mU = null;
+		private MatrixBlock _mV = null;
+		private MatrixBlock _ret = null;
+		private WSigmoidType _wt = null;
+		private int _rl = -1;
+		private int _ru = -1;
+		
+		protected MatrixMultWSigmoidTask(MatrixBlock mW, MatrixBlock mU, MatrixBlock mV, MatrixBlock ret, WSigmoidType wt, int rl, int ru) {
+			_mW = mW;
+			_mU = mU;
+			_mV = mV;
+			_ret = ret;
+			_wt = wt;
+			_rl = rl;
+			_ru = ru;
+		}
+		
+		@Override
+		public Long call() {
+			//core weighted square sum mm computation
+			if( !_mW.sparse && !_mU.sparse && !_mV.sparse && !_mU.isEmptyBlock() && !_mV.isEmptyBlock() )
+				matrixMultWSigmoidDense(_mW, _mU, _mV, _ret, _wt, _rl, _ru);
+			else if( _mW.sparse && !_mU.sparse && !_mV.sparse && !_mU.isEmptyBlock() && !_mV.isEmptyBlock())
+				matrixMultWSigmoidSparseDense(_mW, _mU, _mV, _ret, _wt, _rl, _ru);
+			else
+				matrixMultWSigmoidGeneric(_mW, _mU, _mV, _ret, _wt, _rl, _ru);
+			
+			//maintain block nnz (upper bounds inclusive)
+			return _ret.recomputeNonZeros(_rl, _ru-1, 0, _ret.getNumColumns()-1);
+		}
+	}
+
+	private static class MatrixMultWDivTask implements Callable<Long> 
+	{
+		private MatrixBlock _mW = null;
+		private MatrixBlock _mU = null;
+		private MatrixBlock _mV = null;
+		private MatrixBlock _mX = null;
+		private MatrixBlock _ret = null;
+		private WDivMMType _wt = null;
+		private int _rl = -1;
+		private int _ru = -1;
+		private int _cl = -1;
+		private int _cu = -1;
+		
+		protected MatrixMultWDivTask(MatrixBlock mW, MatrixBlock mU, MatrixBlock mV, MatrixBlock mX, MatrixBlock ret, WDivMMType wt, int rl, int ru, int cl, int cu) {
+			_mW = mW;
+			_mU = mU;
+			_mV = mV;
+			_mX = mX;
+			_wt = wt;
+			_rl = rl;
+			_ru = ru;
+			_cl = cl;
+			_cu = cu;
+			_ret = ret;	
+		}
+		
+		@Override
+		public Long call() {
+			//core weighted div mm computation
+			boolean scalarX = _wt.hasScalar();
+			if( !_mW.sparse && !_mU.sparse && !_mV.sparse && (_mX==null || !_mX.sparse || scalarX) && !_mU.isEmptyBlock() && !_mV.isEmptyBlock() )
+				matrixMultWDivMMDense(_mW, _mU, _mV, _mX, _ret, _wt, _rl, _ru, _cl, _cu);
+			else if( _mW.sparse && !_mU.sparse && !_mV.sparse && (_mX==null || _mX.sparse || scalarX) && !_mU.isEmptyBlock() && !_mV.isEmptyBlock())
+				matrixMultWDivMMSparseDense(_mW, _mU, _mV, _mX, _ret, _wt, _rl, _ru, _cl, _cu);
+			else
+				matrixMultWDivMMGeneric(_mW, _mU, _mV, _mX, _ret, _wt, _rl, _ru, _cl, _cu);
+		
+			//maintain partial nnz for right (upper bounds inclusive)
+			int rl = _wt.isLeft() ? _cl : _rl;
+			int ru = _wt.isLeft() ? _cu : _ru;
+			return _ret.recomputeNonZeros(rl, ru-1, 0, _ret.getNumColumns()-1);
+		}
+	}
+	
+	private static class MatrixMultWCeTask implements Callable<Double>
+	{
+		private MatrixBlock _mW = null;
+		private MatrixBlock _mU = null;
+		private MatrixBlock _mV = null;
+		private double _eps = 0.0;
+		private MatrixBlock _ret = null;
+		private WCeMMType _wt = null;
+		private int _rl = -1;
+		private int _ru = -1;
+
+		protected MatrixMultWCeTask(MatrixBlock mW, MatrixBlock mU, MatrixBlock mV, double eps, WCeMMType wt, int rl, int ru) {
+			_mW = mW;
+			_mU = mU;
+			_mV = mV;
+			_eps = eps;
+			_wt = wt;
+			_rl = rl;
+			_ru = ru;
+			
+			//allocate local result for partial aggregation
+			_ret = new MatrixBlock(1, 1, false);
+			_ret.allocateDenseBlock();
+		}
+		
+		@Override
+		public Double call() {
+			//core weighted cross entropy mm computation
+			if( !_mW.sparse && !_mU.sparse && !_mV.sparse && !_mU.isEmptyBlock() && !_mV.isEmptyBlock() )
+				matrixMultWCeMMDense(_mW, _mU, _mV, _eps, _ret, _wt, _rl, _ru);
+			else if( _mW.sparse && !_mU.sparse && !_mV.sparse && !_mU.isEmptyBlock() && !_mV.isEmptyBlock())
+				matrixMultWCeMMSparseDense(_mW, _mU, _mV, _eps, _ret, _wt, _rl, _ru);
+			else
+				matrixMultWCeMMGeneric(_mW, _mU, _mV, _eps, _ret, _wt, _rl, _ru);
+			
+			
+			return _ret.get(0, 0);
+		}
+	}
+
+	private static class MatrixMultWuTask implements Callable<Long> 
+	{
+		private MatrixBlock _mW = null;
+		private MatrixBlock _mU = null;
+		private MatrixBlock _mV = null;
+		private MatrixBlock _ret = null;
+		private WUMMType _wt = null;
+		private ValueFunction _fn = null;
+		private int _rl = -1;
+		private int _ru = -1;
+		
+		protected MatrixMultWuTask(MatrixBlock mW, MatrixBlock mU, MatrixBlock mV, MatrixBlock ret, WUMMType wt, ValueFunction fn, int rl, int ru) {
+			_mW = mW;
+			_mU = mU;
+			_mV = mV;
+			_ret = ret;
+			_wt = wt;
+			_fn = fn;
+			_rl = rl;
+			_ru = ru;
+		}
+		
+		@Override
+		public Long call() {
+			//core weighted square sum mm computation
+			if( !_mW.sparse && !_mU.sparse && !_mV.sparse && !_mU.isEmptyBlock() && !_mV.isEmptyBlock() )
+				matrixMultWuMMDense(_mW, _mU, _mV, _ret, _wt, _fn, _rl, _ru);
+			else if( _mW.sparse && !_mU.sparse && !_mV.sparse && !_mU.isEmptyBlock() && !_mV.isEmptyBlock())
+				matrixMultWuMMSparseDense(_mW, _mU, _mV, _ret, _wt, _fn, _rl, _ru);
+			else
+				matrixMultWuMMGeneric(_mW, _mU, _mV, _ret, _wt, _fn, _rl, _ru);
+			
+			//maintain block nnz (upper bounds inclusive)
+			return _ret.recomputeNonZeros(_rl, _ru-1, 0, _ret.getNumColumns()-1);
+		}
+	}
+}
diff --git a/scripts/staging/SIMD-FFM-prototypes/LibMatrixMultSIMD.java b/scripts/staging/SIMD-FFM-prototypes/LibMatrixMultSIMD.java
new file mode 100644
index 0000000000..d6aab1a2c6
--- /dev/null
+++ b/scripts/staging/SIMD-FFM-prototypes/LibMatrixMultSIMD.java
@@ -0,0 +1,5026 @@
+/*
+ * 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.sysds.runtime.matrix.data;
+
+import jdk.incubator.vector.*;
+import org.apache.commons.lang3.NotImplementedException;
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.commons.math3.util.FastMath;
... 5414 lines suppressed ...