blob: e4a55ab10c76b0b92054dbe41d0b0e51d82f08f2 [file]
/*
* 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 java.io.Serializable;
import java.util.Arrays;
import org.apache.sysds.common.Types.ValueType;
import org.apache.sysds.runtime.DMLRuntimeException;
import org.apache.sysds.runtime.instructions.cp.KahanObject;
import org.apache.sysds.runtime.util.UtilFunctions;
import org.apache.sysds.utils.MemoryEstimates;
/**
* This DenseBlock is an abstraction for different dense, row-major
* matrix formats. For efficient dense operations, this API does not
* expose a row but a row-block abstraction, where a block can contain
* one or many contiguous rows.
*
*/
public abstract class DenseBlock implements Serializable, Block
{
private static final long serialVersionUID = 7517220490270237832L;
public enum Type {
DRB, //dense row block
LDRB, //large dense row block
}
//NOTE: for a MxNxPxQ tensor the dimensions are given as
//rlen=M, odims=[NxPxQ, PxQ, Q]
protected int _rlen; //number of rows
protected int[] _odims; //cumprod other dims
private double[] _reuse;
protected DenseBlock(int[] dims) {
setDims(dims);
}
/**
* Create a block in the internal blocks array. `allocateBlocks` has to be called
* before it for Large-Dense-Blocks.
*
* @param bix block index
* @param length space to allocate
*/
protected abstract void allocateBlock(int bix, int length);
/**
* Get the ith dimensions size of the dense block.
*
* @param i the number of dimension to get
* @return the size of the dimension
*/
public final int getDim(int i) {
return (i == 0) ?_rlen :
(i == _odims.length) ? _odims[i - 1] :
_odims[i - 1] / _odims[i];
}
/**
* Get the ith cumulative dimensions size of the dense block, without row.
*
* @param i the number of the cumulative dimension to get (0 equals the second dimension!)
* @return the size of the dimension cumulative with all following dimensions
*/
public final int getCumODims(int i) {
return _odims[i];
}
/**
* Resets the dense block by deleting non-zero values. After this
* call all countNonZeros() calls are guaranteed to return 0.
*/
public final void reset() {
reset(_rlen, _odims, 0);
}
/**
* Resets the dense block by deleting non-zero values. After this
* call all countNonZeros() calls are guaranteed to return 0. If
* the new dimensions exceed the current capacity, the underlying
* storage is extended accordingly.
*
* @param dims length and size of dimensions.
*/
public final void reset(int[] dims) {
reset(dims[0], createDimOffsets(dims), 0);
}
/**
* Resets the dense block by deleting non-zeros.
*
* @param dims lenth and size of dimensions
* @param v value
*/
public final void reset(int[] dims, double v) {
reset(dims[0], createDimOffsets(dims), v);
}
/**
* Resets the dense block by deleting non-zeros.
*
* @param rlen number of rows
* @param clen number of columns
*/
public final void reset(int rlen, int clen) {
reset(rlen, new int[]{clen}, 0);
}
/**
* Resets the dense block by deleting non-zeros.
*
* @param rlen number of rows
* @param odims offsets of other dimensions
*/
public final void reset(int rlen, int[] odims) {
reset(rlen, odims, 0);
}
/**
* Resets the dense block by setting the given value.
*
* @param rlen number of rows
* @param clen number of columns
* @param v value
*/
public final void reset(int rlen, int clen, double v) {
reset(rlen, new int[]{clen}, v);
}
/**
* Resets the dense block by setting the given value.
*
* @param rlen number of rows
* @param odims other dimensions
* @param v value
*/
public abstract void reset(int rlen, int[] odims, double v);
public final void resetNoFill(int rlen, int clen){
resetNoFill(rlen, new int[]{clen});
}
public abstract void resetNoFill(int rlen, int[] odims);
public static double estimateMemory(long nrows, long ncols){
long size = 16; // object
size += 4; // int
size += 4; // padding
size += MemoryEstimates.intArrayCost(1); // odims typically 1
size += 8; // pointer to reuse that is typically null;
return size;
}
/**
* Set the dimensions of the dense MatrixBlock.
* @param dims The dimensions to set, first dimension is rows, second cols.
*/
public void setDims(int[] dims){
long odims = UtilFunctions.prod(dims, 1);
if( odims > Integer.MAX_VALUE )
throw new DMLRuntimeException("Invalid dims: "+Arrays.toString(dims));
_rlen = dims[0];
//materialize dim offsets (reverse cumprod)
_odims = createDimOffsets(dims);
}
/**
* Get the number of rows.
*
* @return number of rows
*/
public final int numRows() {
return _rlen;
}
/**
* Get the number of columns / first dimension
*
* @return number of columns
*/
public final int numCols(){
return _odims[0];
}
/**
* Get the number of dimensions.
*
* @return number of dimensions, min 2
*/
public final int numDims() {
return 1 + _odims.length;
}
/**
* Get the number of allocated blocks.
*
* @return number of blocks
*/
public abstract int numBlocks();
/**
* Get the number of rows per block, except last one.
*
* @return number of rows in block
*/
public abstract int blockSize();
/**
* Get the number of rows of the given block.
*
* @param bix block index
* @return number of rows in block
*/
public abstract int blockSize(int bix);
/**
* Indicates if the dense block is numeric.
* @return true if numeric (FP, INT, BOOLEAN)
*/
public abstract boolean isNumeric();
/**
* Indicates if the dense block is a specific numeric value type.
* @param vt value type to check
* @return true if numeric and of value type vt
*/
public abstract boolean isNumeric(ValueType vt);
/**
* Indicates if the dense block has a single
* underlying block, i.e., if numBlocks==1.
*
* @return true if single block
*/
public abstract boolean isContiguous();
/**
* Indicates if the dense block has a single
* underlying block for the given row range.
*
* @param rl row lower index
* @param ru row upper index (inclusive)
* @return true if single block in row range
*/
public abstract boolean isContiguous(int rl, int ru);
/**
* Get the length of the dense block as the product
* of all dimensions.
*
* @return length
*/
public final long size() {
return (long)_rlen * _odims[0];
}
/**
* Get the length of the given block.
*
* @param bix block index
* @return length
*/
public abstract int size(int bix);
/**
* Get the total length of allocated blocks.
*
* @return capacity
*/
public abstract long capacity();
/**
* Computes the number of non zero elements of a certain range of elements in a block.
*
* @param bix index of block
* @param start start index in block
* @param length number of elements to check
* @return number of elements that are not zero
*/
protected abstract long computeNnz(int bix, int start, int length);
/**
* Compute the number of non-zero values, which potentially
* makes a full pass over the underlying blocks.
*
* @return number of non-zeros
*/
public abstract long countNonZeros();
/**
* Compute the number of non-zero values for the given row,
* which potentially makes a full pass over the underlying row.
*
* @param r row index
* @return number of non-zeros
*/
public abstract int countNonZeros(int r);
/**
* Compute the number of non-zero values, which potentially
* makes a full pass over the underlying blocks in the row range.
*
* @param rl row lower index
* @param ru row upper index (exclusive)
* @param cl column lower index
* @param cu column upper index (exclusive)
* @return number of non-zeros
*/
public abstract long countNonZeros(int rl, int ru, int cl, int cu);
/**
* Get the allocated block for the given row. This call
* is equivalent to valuesAt(indexes(r)).
*
* @param r row index
* @return block
*/
public abstract double[] values(int r);
/**
* Get an allocated block.
*
* @param bix block index
* @return block
*/
public abstract double[] valuesAt(int bix);
/**
* Get the block index for a given row.
*
* @param r row index
* @return block index
*/
public abstract int index(int r);
/**
* Get the position for a given row within
* its associated block.
*
* @param r row index
* @return block position
*/
public abstract int pos(int r);
/**
* Get the position for a given row and column
* within the associated block.
*
* @param r row index
* @param c column index
* @return block position
*/
public abstract int pos(int r, int c);
/**
* Get the position for a given cell
* within the associated block.
*
* @param ix cell indexes
* @return block position
*/
public abstract int pos(int[] ix);
/**
* Increments the given value for a given row and column.
*
* @param r row index
* @param c column index
*/
public abstract void incr(int r, int c);
/**
* Increments the given value for a given row and column
* by delta.
*
* @param r row index
* @param c column index
* @param delta increment value
*/
public abstract void incr(int r, int c, double delta);
/**
* Fill a certain range of elements of a block.
*
* @param bix index of block
* @param fromIndex starting index in block
* @param toIndex ending index in block (exclusive)
* @param v value
*/
protected abstract void fillBlock(int bix, int fromIndex, int toIndex, double v);
/**
* Set a value at a position given by block index and index in that block.
* @param bix block index
* @param ix block-array index
* @param v value
*/
protected abstract void setInternal(int bix, int ix, double v);
/**
* Set the given value for the entire dense block (fill).
*
* @param v value
* @return self
*/
public abstract DenseBlock set(double v);
/**
* Set the given string for the entire dense block (fill). Generally the string will be parsed, except for string
* DenseBlock.
*
* @param s string
* @return self
*/
public DenseBlock set(String s) {
set(Double.parseDouble(s));
return this;
}
/**
* Set the given value for an entire index range of the
* dense block (fill).
*
* @param rl row lower index
* @param ru row upper index (exclusive)
* @param cl column lower index
* @param cu column upper index (exclusive)
* @param v value
* @return self
*/
public abstract DenseBlock set(int rl, int ru, int cl, int cu, double v);
/**
* Set the given value for a given row and column.
*
* @param r row index
* @param c column index
* @param v value
* @return self
*/
public abstract DenseBlock set(int r, int c, double v);
/**
* Copy the given vector into the given row.
*
* @param r row index
* @param v value vector
* @return self
*/
public abstract DenseBlock set(int r, double[] v);
/**
* Copy the given dense block.
*
* @param db dense block
* @return self
*/
public abstract DenseBlock set(DenseBlock db);
/**
* Copy the given dense block into the specified
* index range.
*
* @param rl row lower index
* @param ru row upper index (exclusive)
* @param cl column lower index
* @param cu column upper index (exclusive)
* @param db dense block
* @return self
*/
public DenseBlock set(int rl, int ru, int cl, int cu, DenseBlock db) {
boolean allColumns = cl == 0 && cu == _odims[0];
boolean FP64 = isNumeric(ValueType.FP64) && db.isNumeric(ValueType.FP64);
if (db.isNumeric()) {
int rowOther = 0;
for (int bi = index(rl); bi <= index(ru-1); bi++) {
int brl = Math.max(rl-bi*blockSize(), 0);
int bru = Math.min(ru-bi*blockSize(), blockSize());
int offset = brl * _odims[0] + cl;
int clen = cu - cl;
for(int r = brl; r < bru; r++, offset+=_odims[0], rowOther++) {
if( !FP64 )
for(int c = 0; c < clen; c++)
setInternal(bi, offset + c, db.get(rowOther, c));
else
System.arraycopy(db.values(rowOther),
db.pos(rowOther), valuesAt(bi), offset, clen);
}
}
}
else {
int[] otherIx = new int[db.numDims()];
for (int bi = index(rl); bi <= index(ru - 1); bi++) {
String[] data;
if (this instanceof DenseBlockString) {
data = ((DenseBlockString) this)._data;
} else {
data = ((DenseBlockLString) this)._blocks[bi];
}
if (allColumns) {
int offset = rl * _odims[0] + cl;
for (int i = 0; i < (ru - rl) * _odims[0]; i++) {
data[offset + i] = db.getString(otherIx);
getNextIndexes(otherIx);
}
}
else {
int len = cu - cl;
for (int i = rl, ix1 = rl * _odims[0] + cl; i < ru; i++, ix1 += _odims[0]) {
for (int ix = 0; ix < len; ix++) {
data[ix1 + ix] = db.getString(otherIx);
getNextIndexes(otherIx);
}
otherIx[0] = i - rl + 1;
otherIx[1] = 0;
Arrays.fill(otherIx, 2, otherIx.length, 0);
}
}
rl = 0;
}
}
return this;
}
/**
* Calculates the next index array. Note that if the given index array was the last element, the next index will
* be outside of range.
*
* @param ix the index array which will be incremented to the next index array
*/
public void getNextIndexes(int[] ix) {
int i = ix.length - 1;
ix[i]++;
//calculating next index
if (ix[i] == getDim(i)) {
while (ix[i] == getDim(i)) {
if (i - 1 < 0) {
//we are finished
break;
}
ix[i] = 0;
i--;
ix[i]++;
}
}
}
/**
* Copy the given kahan object sum and correction.
*
* @param kbuff kahan object
* @return self
*/
public DenseBlock set(KahanObject kbuff) {
set(0, 0, kbuff._sum);
set(0, 1, kbuff._correction);
return this;
}
/**
* Set the specified cell to the given value.
*
* @param ix cell indexes
* @param v value
* @return self
*/
public abstract DenseBlock set(int[] ix, double v);
/**
* Set the specified cell to the given value.
*
* @param ix cell indexes
* @param v value
* @return self
*/
public abstract DenseBlock set(int[] ix, long v);
/**
* Set the specified cell to the given value.
*
* @param ix cell indexes
* @param v value as String
* @return self
*/
public abstract DenseBlock set(int[] ix, String v);
/**
* Copy the given kahan object sum and correction
* into the given row.
*
* @param r row index
* @param kbuff kahan object
* @return self
*/
public DenseBlock set(int r, KahanObject kbuff) {
set(r, 0, kbuff._sum);
set(r, 1, kbuff._correction);
return this;
}
/**
* Get the value for a given row and column.
*
* @param r row index
* @param c column index
* @return value
*/
public abstract double get(int r, int c);
/**
* Get the value of a given cell
*
* @param ix cell indexes
* @return value
*/
public abstract double get(int[] ix);
/**
* Get the value of a given cell as a String
*
* @param ix cell indexes
* @return value as String
*/
public abstract String getString(int[] ix);
/**
* Get the value of a given cell as long
*
* @param ix cell indexes
* @return value as long
*/
public abstract long getLong(int[] ix);
/**
* Checks if the block contains at least one value of the given
* pattern. Implementations need to handle NaN patterns as well
* (note that NaN==NaN yields false).
*
* @param pattern checked pattern
* @return true if pattern appears at least once, otherwise false
*/
public boolean contains(double pattern) {
boolean NaNpattern = Double.isNaN(pattern);
for(int i=0; i<numBlocks(); i++) {
double[] vals = valuesAt(i);
int len = size(i);
for(int j=0; j<len; j++)
if(vals[j]==pattern || (NaNpattern && Double.isNaN(vals[j])))
return true;
}
return false;
}
@Override
public boolean equals(Object o) {
if(o instanceof DenseBlock)
return equals((DenseBlock) o, Double.MIN_NORMAL * 1024);
return false;
}
/**
* Verify if the values in this dense block is equivalent to that dense block, not taking into account the dimensions
* of the contained values. Note in some cases one or the other block is allocated bigger than the other, so the
* values compared is only the values of the smaller block.
*
* @param o Other block
* @param eps Epsilon allowed
* @return If the blocs are equivalent.
*/
public boolean equals(DenseBlock o, double eps) {
if(isContiguous() && o.isContiguous())
return contiguousEquals(o, eps);
return genericEquals(o, eps);
}
private boolean contiguousEquals(DenseBlock o, double eps) {
final double[] va = values(0);
final double[] vb = o.values(0);
final int len = Math.min(va.length, vb.length);
for(int i = 0; i < len; i++)
if(Math.abs(va[i] - vb[i]) > eps)
return false;
return true;
}
private boolean genericEquals(DenseBlock o, double eps) {
final int nRows = getDim(0);
final int nCols = getDim(1);
for(int i = 0; i < nRows; i++)
for(int j = 0; j < nCols; j++)
if(Math.abs(get(i, j) - o.get(i, j)) > eps)
return false;
return true;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
if(_odims[0] == 1) {
sb.append("Printing column vector transposed:\n");
for(int b = 0; b < numBlocks(); b++) {
for(double v : valuesAt(b)) {
sb.append(getNiceFormat(v));
sb.append(" ");
}
}
sb.append("\n");
}
else{
for(int i=0; i<_rlen; i++) {
double[] data = values(i);
int ix = pos(i);
for(int j=0; j<_odims[0]; j++) {
double v = data[ix+j];
sb.append(getNiceFormat(v));
sb.append(" ");
}
sb.append("\n");
}
}
return sb.toString();
}
private String getNiceFormat(double v) {
if(v == (long) v)
return Long.toString((long) v);
else
return Double.toString(v);
}
protected double[] getReuseRow(boolean reset) {
if( _reuse != null && reset )
Arrays.fill(_reuse, 0);
if( _reuse == null )
_reuse = new double[_odims[0]];
return _reuse;
}
private static int[] createDimOffsets(int[] dims) {
int[] ret = new int[dims.length-1];
int prod = 1;
for(int i=dims.length-1; i>=1; i--) {
prod *= dims[i];
ret[i-1] = prod;
}
return ret;
}
}