blob: 3710f72331809a0037c7efda4882141eb1ab5c0f [file] [log] [blame]
/*
* 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.data;
import org.apache.commons.lang.NotImplementedException;
import org.apache.sysds.common.Types.ValueType;
import org.apache.sysds.runtime.DMLRuntimeException;
import org.apache.sysds.runtime.functionobjects.KahanPlus;
import org.apache.sysds.runtime.functionobjects.Plus;
import org.apache.sysds.runtime.functionobjects.ReduceAll;
import org.apache.sysds.runtime.matrix.data.MatrixBlock;
import org.apache.sysds.runtime.matrix.operators.AggregateOperator;
import org.apache.sysds.runtime.matrix.operators.AggregateUnaryOperator;
import org.apache.sysds.runtime.util.UtilFunctions;
import java.io.Serializable;
import static org.apache.sysds.runtime.data.LibTensorAgg.aggregateBinaryTensor;
import static org.apache.sysds.runtime.data.TensorBlock.DEFAULT_DIMS;
import static org.apache.sysds.runtime.data.TensorBlock.DEFAULT_VTYPE;
public class BasicTensorBlock implements Serializable {
private static final long serialVersionUID = -7665685894181661833L;
public static final double SPARSITY_TURN_POINT = 0.4;
public static final SparseBlock.Type DEFAULT_SPARSEBLOCK = SparseBlock.Type.MCSR;
protected int[] _dims;
//constant value type of tensor block
protected ValueType _vt;
protected boolean _sparse = true;
protected long _nnz = 0;
//matrix data (sparse or dense)
protected DenseBlock _denseBlock = null;
protected SparseBlock _sparseBlock = null;
public BasicTensorBlock() {
this(DEFAULT_VTYPE, DEFAULT_DIMS.clone(), true, -1);
}
public BasicTensorBlock(ValueType vt, int[] dims) {
this(vt, dims, true, -1);
}
public BasicTensorBlock(ValueType vt, int[] dims, boolean sp) {
this(vt, dims, sp, -1);
}
public BasicTensorBlock(ValueType vt, int[] dims, boolean sp, long estnnz) {
_vt = vt;
reset(dims, sp, estnnz, 0);
}
public BasicTensorBlock(BasicTensorBlock that) {
_vt = that.getValueType();
copy(that);
}
public BasicTensorBlock(double val) {
_vt = DEFAULT_VTYPE;
reset(new int[] {1, 1}, false, 1, val);
}
public BasicTensorBlock(int[] dims, ValueType vt, double val) {
_vt = vt;
_dims = dims;
reset(dims, false, (val==0) ? 0 : getLength(), val);
}
public long getLength() {
return UtilFunctions.prod(_dims);
}
////////
// Initialization methods
// (reset, init, allocate, etc)
public void reset() {
reset(_dims, _sparse, -1, 0);
}
public void reset(int[] dims) {
reset(dims, _sparse, -1, 0);
}
public void reset(int[] dims, long estnnz) {
reset(dims, evalSparseFormatInMemory(dims, estnnz), estnnz, 0);
}
public void reset(int[] dims, boolean sp) {
reset(dims, sp, -1, 0);
}
public void reset(int[] dims, boolean sp, long estnnz) {
reset(dims, sp, estnnz, 0);
}
/**
* Internal canonical reset of dense and sparse tensor blocks.
*
* @param dims number and size of dimensions
* @param sp sparse representation
* @param estnnz estimated number of non-zeros
* @param val initialization value
*/
private void reset(int[] dims, boolean sp, long estnnz, double val) {
//check for valid dimensions
if( dims.length < 2 )
throw new DMLRuntimeException("Invalid number of tensor dimensions: " + dims.length);
for( int i=0; i<dims.length; i++ )
if( dims[i] < 0 )
throw new DMLRuntimeException("Invalid "+i+"th dimensions: "+dims[i]);
//reset basic meta data
_dims = dims;
_sparse = sp;
_nnz = (val == 0) ? 0 : getLength();
//reset sparse/dense blocks
if( _sparse )
resetSparse();
else
resetDense(val);
}
private void resetSparse() {
if(_sparseBlock == null)
return;
//TODO simplify estimated non-zeros
_sparseBlock.reset(-1, getDim(2));
}
private void resetDense(double val) {
//handle to dense block allocation and
//reset dense block to given value
if( _denseBlock != null )
_denseBlock.reset(_dims, val);
else {
if( val != 0 ) {
allocateDenseBlock(false);
_denseBlock.set(val);
}
else {
allocateDenseBlock(true);
}
}
}
/**
* Recomputes and materializes the number of non-zero values
* of the entire basic tensor block.
*
* @return number of non-zeros
*/
public long recomputeNonZeros() {
if( _sparse && _sparseBlock != null ) { //SPARSE
throw new DMLRuntimeException("Sparse tensor block not supported");
}
else if( !_sparse && _denseBlock != null ) { //DENSE
_nnz = _denseBlock.countNonZeros();
}
return _nnz;
}
public boolean isAllocated() {
return _sparse ? (_sparseBlock != null) : (_denseBlock != null);
}
public BasicTensorBlock allocateDenseBlock() {
allocateDenseBlock(true);
return this;
}
public BasicTensorBlock allocateBlock() {
if( _sparse )
allocateSparseBlock();
else
allocateDenseBlock();
return this;
}
public boolean allocateDenseBlock(boolean clearNNZ) {
//allocate block if non-existing or too small (guaranteed to be 0-initialized),
// TODO: use _denseBlock.reset instead, since LDRB need to check dimensions for actually available space
long limit = getLength();
boolean reset = (_denseBlock == null || _denseBlock.capacity() < limit);
if( _denseBlock == null )
_denseBlock = DenseBlockFactory.createDenseBlock(_vt, _dims);
else if( _denseBlock.capacity() < limit )
_denseBlock.reset(_dims);
//clear nnz if necessary
if( clearNNZ )
_nnz = 0;
_sparse = false;
return reset;
}
public boolean allocateSparseBlock() {
return allocateSparseBlock(true);
}
public boolean allocateSparseBlock(boolean clearNNZ) {
//allocate block if non-existing or too small (guaranteed to be 0-initialized)
//but do not replace existing block even if not in default type
boolean reset = _sparseBlock == null || _sparseBlock.numRows()<getDim(0);
if( reset ) {
_sparseBlock = SparseBlockFactory
.createSparseBlock(DEFAULT_SPARSEBLOCK, getDim(0));
}
//clear nnz if necessary
if( clearNNZ )
_nnz = 0;
return reset;
}
////////
// Basic meta data
public ValueType getValueType() {
return _vt;
}
public long getNonZeros() {
return _nnz;
}
public int getNumRows() {
return getDim(0);
}
public int getNumColumns() {
return getDim(1);
}
public int getNumDims() {
return _dims.length;
}
public int getDim(int i) {
return _dims[i];
}
public int[] getDims() {
return _dims;
}
public boolean isSparse() {
return _sparse;
}
public boolean isEmpty(boolean safe) {
boolean ret = false;
if( _sparse && _sparseBlock==null )
ret = true;
else if( !_sparse && _denseBlock==null )
ret = true;
if( _nnz==0 ) {
//prevent under-estimation
if(safe)
recomputeNonZeros();
ret = (_nnz == 0);
}
return ret;
}
public DenseBlock getDenseBlock() {
return _denseBlock;
}
public SparseBlock getSparseBlock() {
return _sparseBlock;
}
////////
// Basic modification
public Object get(int[] ix) {
if (_sparse) {
// TODO: Implement sparse
throw new NotImplementedException();
}
else {
switch (_vt) {
case FP64:
return _denseBlock.get(ix);
case FP32:
return (float) _denseBlock.get(ix);
case INT64:
return _denseBlock.getLong(ix);
case INT32:
return (int) _denseBlock.getLong(ix);
case BOOLEAN:
return _denseBlock.get(ix) != 0;
case STRING:
return _denseBlock.getString(ix);
default:
throw new DMLRuntimeException("Unsupported value type: " + _vt);
}
}
}
public double get(int r, int c) {
if (getNumDims() != 2)
throw new DMLRuntimeException("BasicTensor.get(int,int) dimension mismatch: expected=2 actual=" + getNumDims());
if (_sparse) {
// TODO: Implement sparse
throw new NotImplementedException();
//return _sparseBlock.get(ix);
}
else {
return _denseBlock.get(r, c);
}
}
public void set(int[] ix, Object v) {
if (_sparse) {
throw new NotImplementedException();
}
else if (v != null) {
if (v instanceof Double) {
double old = _denseBlock.get(ix);
_denseBlock.set(ix, (Double) v);
_nnz += (old == 0 ? 0 : -1) + ((Double) v == 0 ? 0 : 1);
}
else if (v instanceof Float) {
double old = _denseBlock.get(ix);
_denseBlock.set(ix, (Float) v);
_nnz += (old == 0 ? 0 : -1) + ((Float) v == 0 ? 0 : 1);
}
else if (v instanceof Long) {
long old = _denseBlock.getLong(ix);
_denseBlock.set(ix, (Long) v);
_nnz += (old == 0 ? 0 : -1) + ((Long) v == 0 ? 0 : 1);
}
else if (v instanceof Integer) {
long old = _denseBlock.getLong(ix);
_denseBlock.set(ix, (Integer) v);
_nnz += (old == 0 ? 0 : -1) + ((Integer) v == 0 ? 0 : 1);
}
else if (v instanceof Boolean) {
long old = _denseBlock.getLong(ix);
_denseBlock.set(ix, ((Boolean) v) ? 1.0 : 0.0);
_nnz += (old == 0 ? 0 : -1) + (!(Boolean) v ? 0 : 1);
}
else if (v instanceof String) {
String old = _denseBlock.getString(ix);
if (old != null && !old.isEmpty())
_nnz--;
_denseBlock.set(ix, (String) v);
if (!((String) v).isEmpty())
_nnz++;
}
else
throw new DMLRuntimeException("BasicTensor.set(int[],Object) is not implemented for the given Object");
}
}
public void set(int r, int c, double v) {
if (getNumDims() != 2)
throw new DMLRuntimeException("BasicTensor.set(int,int,double) dimension mismatch: expected=2 actual=" + getNumDims());
if (_sparse) {
throw new NotImplementedException();
}
else {
double old = _denseBlock.get(r, c);
_denseBlock.set(r, c, v);
_nnz += (old == 0 ? 0 : -1) + (v == 0 ? 0 : 1);
}
}
public void set(double v) {
if (_sparse) {
throw new NotImplementedException();
}
else {
_denseBlock.set(v);
if (v == 0)
_nnz = 0;
else
_nnz = getLength();
}
}
public void set(Object v) {
if (_sparse) {
throw new NotImplementedException();
}
else {
if (v instanceof Double) {
_denseBlock.set((Double) v);
_nnz += ((Double) v == 0 ? 0 : 1);
}
else if (v instanceof Float) {
_denseBlock.set((Float) v);
_nnz += ((Float) v == 0 ? 0 : 1);
}
else if (v instanceof Long) {
_denseBlock.set((Long) v);
_nnz += ((Long) v == 0 ? 0 : 1);
}
else if (v instanceof Integer) {
_denseBlock.set((Integer) v);
_nnz += ((Integer) v == 0 ? 0 : 1);
}
else if (v instanceof Boolean) {
_denseBlock.set(((Boolean) v) ? 1.0 : 0.0);
_nnz += (!(Boolean) v ? 0 : 1);
}
else if (v instanceof String) {
_denseBlock.set((String) v);
_nnz += (((String) v).isEmpty() ? 0 : 1);
}
else
throw new DMLRuntimeException("BasicTensor.set(Object) is not implemented for the given Object");
}
}
public void set(BasicTensorBlock other) {
if (_sparse)
throw new NotImplementedException();
else {
if (other.isSparse())
throw new NotImplementedException();
else {
_denseBlock.set(0, _dims[0], 0, _denseBlock.getCumODims(0), other.getDenseBlock());
_nnz = other._nnz;
}
}
}
public void set(MatrixBlock other) {
if (_sparse) {
throw new NotImplementedException();
}
else {
if (other.isInSparseFormat()) {
if (other.isEmpty()) {
_denseBlock.set(0);
}
else {
// TODO implement sparse set instead of converting to dense
other.sparseToDense();
_denseBlock.set(0, _dims[0], 0, _denseBlock.getCumODims(0), other.getDenseBlock());
_nnz = other.getNonZeros();
}
}
else {
_denseBlock.set(0, _dims[0], 0, _denseBlock.getCumODims(0), other.getDenseBlock());
_nnz = other.getNonZeros();
}
}
}
public void copy(BasicTensorBlock that) {
_dims = that._dims.clone();
_sparse = that._sparse;
_nnz = that._nnz;
if (that.isAllocated()) {
if (!_sparse)
copyDenseToDense(that);
else // TODO copy sparse to dense, dense to dense or sparse to dense
throw new NotImplementedException();
}
}
public BasicTensorBlock copyShallow(BasicTensorBlock that) {
_dims = that._dims.clone();
_sparse = that._sparse;
_nnz = that._nnz;
if (!_sparse)
_denseBlock = that._denseBlock;
else
_sparseBlock = that._sparseBlock;
return this;
}
private void copyDenseToDense(BasicTensorBlock that) {
_nnz = that._nnz;
//plain reset to 0 for empty input
if (that.isEmpty(false)) {
if (_denseBlock != null)
_denseBlock.reset(that._dims);
else
_denseBlock = DenseBlockFactory.createDenseBlock(that._vt, that._dims);
}
//allocate and copy dense block
allocateDenseBlock(false);
_denseBlock.set(that._denseBlock);
}
/**
* Copy a part of another <code>BasicTensorBlock</code>
* @param lower lower index of elements to copy (inclusive)
* @param upper upper index of elements to copy (exclusive)
* @param src source <code>BasicTensorBlock</code>
*/
public void copy(int[] lower, int[] upper, BasicTensorBlock src) {
// TODO consider sparse
if (src.isEmpty(false)) {
return;
}
DenseBlock db = src.getDenseBlock();
int rowLower = lower[0];
int rowUpper = upper[0] + 1;
int columnLower = lower[lower.length - 1];
int columnUpper = upper[upper.length - 1];
for (int i = 1; i < lower.length - 1; i++) {
columnLower += lower[i] * db.getCumODims(i);
columnUpper += upper[i] * db.getCumODims(i);
}
if (columnLower == columnUpper || columnUpper == 0) {
rowUpper--;
columnUpper = db.getCumODims(0);
}
_denseBlock.set(rowLower, rowUpper, columnLower, columnUpper, db);
}
////////
// Size estimation and format decisions
private static boolean evalSparseFormatInMemory(int[] dims, long estnnz) {
// TODO Auto-generated method stub
return false;
}
///////
// Aggregations
/**
* Aggregate a unary operation on this tensor.
* @param op the operation to apply
* @param result the result tensor
* @return the result tensor
*/
public BasicTensorBlock aggregateUnaryOperations(AggregateUnaryOperator op, BasicTensorBlock result) {
// TODO allow to aggregate along a dimension?
// TODO performance
if (op.aggOp.increOp.fn instanceof KahanPlus) {
op = new AggregateUnaryOperator(new AggregateOperator(0, Plus.getPlusFnObject()), op.indexFn, op.getNumThreads());
}
int dim0 = 1;
int dim1 = 1;
if (op.aggOp.existsCorrection()) {
dim1 = 2;
}
//prepare result matrix block
if (result == null || result._vt != _vt)
result = new BasicTensorBlock(_vt, new int[]{dim0, dim1}, false);
else
result.reset(new int[]{dim0, dim1}, false);
if (LibTensorAgg.isSupportedUnaryAggregateOperator(op))
if (op.indexFn instanceof ReduceAll)
LibTensorAgg.aggregateUnaryTensor(this, result, op);
else
throw new DMLRuntimeException("Only ReduceAll UnaryAggregationOperators are supported for tensor");
else
throw new DMLRuntimeException("Current UnaryAggregationOperator not supported for tensor");
return result;
}
public void incrementalAggregate(AggregateOperator aggOp, BasicTensorBlock partialResult) {
if( !aggOp.existsCorrection() && aggOp.increOp.fn instanceof Plus)
aggregateBinaryTensor(partialResult, this, aggOp);
else
throw new DMLRuntimeException("Correction not supported. correctionLocation: " + aggOp.correction);
}
}