blob: 7acf790b5b9f8f1b056bb72b33682b60c50b1b21 [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.test.component.compress;
import static org.junit.Assert.assertTrue;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import org.apache.sysds.lops.MMTSJ.MMTSJType;
import org.apache.sysds.lops.MapMultChain.ChainType;
import org.apache.sysds.runtime.compress.CompressedMatrixBlock;
import org.apache.sysds.runtime.compress.CompressionSettings;
import org.apache.sysds.runtime.compress.CompressionStatistics;
import org.apache.sysds.runtime.compress.colgroup.ColGroup;
import org.apache.sysds.runtime.functionobjects.Multiply;
import org.apache.sysds.runtime.functionobjects.Plus;
import org.apache.sysds.runtime.matrix.data.MatrixBlock;
import org.apache.sysds.runtime.matrix.operators.AggregateBinaryOperator;
import org.apache.sysds.runtime.matrix.operators.AggregateOperator;
import org.apache.sysds.runtime.matrix.operators.AggregateUnaryOperator;
import org.apache.sysds.runtime.matrix.operators.RightScalarOperator;
import org.apache.sysds.runtime.matrix.operators.ScalarOperator;
import org.apache.sysds.runtime.util.DataConverter;
import org.apache.sysds.test.TestUtils;
import org.apache.sysds.test.component.compress.TestConstants.MatrixTypology;
import org.apache.sysds.test.component.compress.TestConstants.SparsityType;
import org.apache.sysds.test.component.compress.TestConstants.ValueRange;
import org.apache.sysds.test.component.compress.TestConstants.ValueType;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.openjdk.jol.datamodel.X86_64_DataModel;
import org.openjdk.jol.info.ClassLayout;
import org.openjdk.jol.layouters.HotSpotLayouter;
import org.openjdk.jol.layouters.Layouter;
@RunWith(value = Parameterized.class)
public class CompressedMatrixTest extends AbstractCompressedUnaryTests {
public CompressedMatrixTest(SparsityType sparType, ValueType valType, ValueRange valRange,
CompressionSettings compSettings, MatrixTypology matrixTypology) {
super(sparType, valType, valRange, compSettings, matrixTypology);
}
@Test
public void testConstruction() {
try {
if(!(cmb instanceof CompressedMatrixBlock)) {
return; // Input was not compressed then just pass test
// Assert.assertTrue("Compression Failed \n" + this.toString(), false);
}
if(compressionSettings.lossy) {
TestUtils.compareMatrices(input, deCompressed, lossyTolerance);
}
else {
TestUtils.compareMatricesBitAvgDistance(input, deCompressed, 0, 0, compressionSettings.toString());
}
}
catch(Exception e) {
e.printStackTrace();
throw new RuntimeException(this.toString() + "\n" + e.getMessage(), e);
}
}
@Test
public void testGetValue() {
try {
if(!(cmb instanceof CompressedMatrixBlock))
return; // Input was not compressed then just pass test
for(int i = 0; i < rows; i++)
for(int j = 0; j < cols; j++) {
double ulaVal = input[i][j];
double claVal = cmb.getValue(i, j); // calls quickGetValue internally
if(compressionSettings.lossy) {
TestUtils.compareCellValue(ulaVal, claVal, lossyTolerance, false);
}
else {
TestUtils.compareScalarBitsJUnit(ulaVal, claVal, 0); // Should be exactly same value
}
}
}
catch(Exception e) {
e.printStackTrace();
throw new RuntimeException(this.toString() + "\n" + e.getMessage(), e);
}
}
@Test
public void testAppend() {
try {
if(!(cmb instanceof CompressedMatrixBlock))
return; // Input was not compressed then just pass test
MatrixBlock vector = DataConverter
.convertToMatrixBlock(TestUtils.generateTestMatrix(rows, 1, 1, 1, 1.0, 3));
// matrix-vector uncompressed
MatrixBlock ret1 = mb.append(vector, new MatrixBlock());
// matrix-vector compressed
MatrixBlock ret2 = cmb.append(vector, new MatrixBlock());
if(ret2 instanceof CompressedMatrixBlock)
ret2 = ((CompressedMatrixBlock) ret2).decompress();
// compare result with input
double[][] d1 = DataConverter.convertToDoubleMatrix(ret1);
double[][] d2 = DataConverter.convertToDoubleMatrix(ret2);
if(compressionSettings.lossy) {
TestUtils.compareMatrices(d1, d2, lossyTolerance);
}
else {
TestUtils.compareMatricesBitAvgDistance(d1, d2, 0, 1, "Test Append Matrix");
}
}
catch(Exception e) {
e.printStackTrace();
throw new RuntimeException(this.toString() + "\n" + e.getMessage(), e);
}
}
@Test
public void testMatrixMultChain() {
try {
if(!(cmb instanceof CompressedMatrixBlock))
return; // Input was not compressed then just pass test
MatrixBlock vector1 = DataConverter
.convertToMatrixBlock(TestUtils.generateTestMatrix(cols, 1, 0.5, 1.5, 1.0, 3));
// ChainType ctype = ChainType.XtwXv;
// Linear regression .
for(ChainType ctype : new ChainType[] {ChainType.XtwXv, ChainType.XtXv,
// ChainType.XtXvy
}) {
MatrixBlock vector2 = (ctype == ChainType.XtwXv) ? DataConverter
.convertToMatrixBlock(TestUtils.generateTestMatrix(rows, 1, 0.5, 1.5, 1.0, 3)) : null;
// matrix-vector uncompressed
MatrixBlock ret1 = mb.chainMatrixMultOperations(vector1, vector2, new MatrixBlock(), ctype);
// matrix-vector compressed
MatrixBlock ret2 = cmb.chainMatrixMultOperations(vector1, vector2, new MatrixBlock(), ctype);
// compare result with input
double[][] d1 = DataConverter.convertToDoubleMatrix(ret1);
double[][] d2 = DataConverter.convertToDoubleMatrix(ret2);
if(compressionSettings.lossy) {
// TODO Make actual calculation to know the tolerance
// double scaledTolerance = lossyTolerance * d1.length * d1.length * 1.5;
// if(ctype == ChainType.XtwXv){
// scaledTolerance *= d1.length * d1.length * 0.5;
// }
// TestUtils.compareMatrices(d1, d2, d1.length, d1[0].length, scaledTolerance );
TestUtils.compareMatricesPercentageDistance(d1, d2, 0.95, 0.95, compressionSettings.toString());
}
else {
TestUtils.compareMatricesBitAvgDistance(d1, d2, 512, 32, compressionSettings.toString());
}
}
}
catch(Exception e) {
e.printStackTrace();
throw new RuntimeException(this.toString() + "\n" + e.getMessage(), e);
}
}
@Test
public void testTransposeSelfMatrixMult() {
try {
if(!(cmb instanceof CompressedMatrixBlock))
return; // Input was not compressed then just pass test
// ChainType ctype = ChainType.XtwXv;
for(MMTSJType mType : new MMTSJType[] {MMTSJType.LEFT,
// MMTSJType.RIGHT
}) {
// matrix-vector uncompressed
MatrixBlock ret1 = mb.transposeSelfMatrixMultOperations(new MatrixBlock(), mType);
// matrix-vector compressed
MatrixBlock ret2 = cmb.transposeSelfMatrixMultOperations(new MatrixBlock(), mType);
// compare result with input
double[][] d1 = DataConverter.convertToDoubleMatrix(ret1);
double[][] d2 = DataConverter.convertToDoubleMatrix(ret2);
if(compressionSettings.lossy) {
/**
* Probably one of the worst thing you can do to increase the amount the values are estimated wrong
*/
TestUtils.compareMatricesPercentageDistance(d1, d2, 0.0, 0.8, compressionSettings.toString());
}
else {
TestUtils.compareMatricesBitAvgDistance(d1, d2, 2048, 20, compressionSettings.toString());
}
}
}
catch(Exception e) {
e.printStackTrace();
throw new RuntimeException(this.toString() + "\n" + e.getMessage(), e);
}
}
@Test
public void testMatrixVectorMult01() {
testMatrixVectorMult(1.0, 1.1);
}
@Test
public void testMatrixVectorMult02() {
testMatrixVectorMult(0.7, 1.0);
}
@Test
public void testMatrixVectorMult03() {
testMatrixVectorMult(-1.0, 1.0);
}
@Test
public void testMatrixVectorMult04() {
testMatrixVectorMult(1.0, 5.0);
}
public void testMatrixVectorMult(double min, double max) {
try {
if(!(cmb instanceof CompressedMatrixBlock))
return; // Input was not compressed then just pass test
MatrixBlock vector = DataConverter
.convertToMatrixBlock(TestUtils.generateTestMatrix(cols, 1, min, max, 1.0, 3));
// Make Operator
AggregateOperator aop = new AggregateOperator(0, Plus.getPlusFnObject());
AggregateBinaryOperator abop = new AggregateBinaryOperator(Multiply.getMultiplyFnObject(), aop);
// matrix-vector uncompressed
MatrixBlock ret1 = mb.aggregateBinaryOperations(mb, vector, new MatrixBlock(), abop);
// matrix-vector compressed
MatrixBlock ret2 = cmb.aggregateBinaryOperations(cmb, vector, new MatrixBlock(), abop);
// compare result with input
double[][] d1 = DataConverter.convertToDoubleMatrix(ret1);
double[][] d2 = DataConverter.convertToDoubleMatrix(ret2);
if(compressionSettings.lossy) {
// TODO Make actual calculation to know the actual tolerance
double scaledTolerance = lossyTolerance * 30 * max;
TestUtils.compareMatrices(d1, d2, scaledTolerance);
}
else {
TestUtils.compareMatricesBitAvgDistance(d1, d2, 2048, 5, compressionSettings.toString());
}
}
catch(Exception e) {
e.printStackTrace();
throw new RuntimeException(this.toString() + "\n" + e.getMessage(), e);
}
}
@Test
public void testVectorMatrixMult() {
try {
if(!(cmb instanceof CompressedMatrixBlock))
return; // Input was not compressed then just pass test
MatrixBlock vector = DataConverter
.convertToMatrixBlock(TestUtils.generateTestMatrix(1, rows, 0.5, 1.5, 1.0, 3));
// Make Operator
AggregateOperator aop = new AggregateOperator(0, Plus.getPlusFnObject());
AggregateBinaryOperator abop = new AggregateBinaryOperator(Multiply.getMultiplyFnObject(), aop);
// vector-matrix uncompressed
MatrixBlock ret1 = mb.aggregateBinaryOperations(vector, mb, new MatrixBlock(), abop);
// vector-matrix compressed
MatrixBlock ret2 = cmb.aggregateBinaryOperations(vector, cmb, new MatrixBlock(), abop);
// compare result with input
double[][] d1 = DataConverter.convertToDoubleMatrix(ret1);
double[][] d2 = DataConverter.convertToDoubleMatrix(ret2);
if(compressionSettings.lossy) {
TestUtils.compareMatricesPercentageDistance(d1, d2, 0.60, 0.97, compressionSettings.toString());
}
else {
TestUtils.compareMatricesBitAvgDistance(d1, d2, 10000, 500, compressionSettings.toString());
}
}
catch(Exception e) {
e.printStackTrace();
throw new RuntimeException(this.toString() + "\n" + e.getMessage(), e);
}
}
@Test
public void testScalarOperationsSparseUnsafe() {
try {
if(!(cmb instanceof CompressedMatrixBlock))
return; // Input was not compressed then just pass test
double addValue = 1000;
// matrix-scalar uncompressed
ScalarOperator sop = new RightScalarOperator(Plus.getPlusFnObject(), addValue);
MatrixBlock ret1 = mb.scalarOperations(sop, new MatrixBlock());
// matrix-scalar compressed
MatrixBlock ret2 = cmb.scalarOperations(sop, new MatrixBlock());
if(ret2 instanceof CompressedMatrixBlock)
ret2 = ((CompressedMatrixBlock) ret2).decompress();
// compare result with input
double[][] d1 = DataConverter.convertToDoubleMatrix(ret1);
double[][] d2 = DataConverter.convertToDoubleMatrix(ret2);
if(compressionSettings.lossy) {
double modifiedTolerance = Math.max(TestConstants.getMaxRangeValue(valRange) + addValue,
Math.abs(TestConstants.getMinRangeValue(valRange) + addValue)) * 2 / 127.0;
TestUtils.compareMatrices(d1, d2, modifiedTolerance);
}
else {
TestUtils.compareMatricesBitAvgDistance(d1, d2, 150, 1, compressionSettings.toString());
}
}
catch(Exception e) {
e.printStackTrace();
throw new RuntimeException(this.toString() + "\n" + e.getMessage(), e);
}
}
@Test
public void testScalarOperations() {
try {
if(!(cmb instanceof CompressedMatrixBlock))
return; // Input was not compressed then just pass test
// matrix-scalar uncompressed
ScalarOperator sop = new RightScalarOperator(Multiply.getMultiplyFnObject(), 7);
MatrixBlock ret1 = mb.scalarOperations(sop, new MatrixBlock());
// matrix-scalar compressed
MatrixBlock ret2 = cmb.scalarOperations(sop, new MatrixBlock());
if(ret2 instanceof CompressedMatrixBlock)
ret2 = ((CompressedMatrixBlock) ret2).decompress();
// compare result with input
double[][] d1 = DataConverter.convertToDoubleMatrix(ret1);
double[][] d2 = DataConverter.convertToDoubleMatrix(ret2);
if(compressionSettings.lossy) {
double modifiedTolerance = lossyTolerance * 7;
TestUtils.compareMatrices(d1, d2, modifiedTolerance);
}
else {
TestUtils.compareMatricesBitAvgDistance(d1, d2, 150, 1, compressionSettings.toString());
}
}
catch(Exception e) {
e.printStackTrace();
throw new RuntimeException(this.toString() + "\n" + e.getMessage(), e);
}
}
@Override
public void testUnaryOperators(AggType aggType) {
AggregateUnaryOperator auop = super.getUnaryOperator(aggType, 1);
testUnaryOperators(aggType, auop);
}
@Test
public void testSerialization() {
try {
if(!(cmb instanceof CompressedMatrixBlock))
return; // Input was not compressed then just pass test
// serialize compressed matrix block
ByteArrayOutputStream bos = new ByteArrayOutputStream();
DataOutputStream fos = new DataOutputStream(bos);
cmb.write(fos);
// deserialize compressed matrix block
ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
DataInputStream fis = new DataInputStream(bis);
CompressedMatrixBlock cmb2 = new CompressedMatrixBlock();
cmb2.readFields(fis);
// decompress the compressed matrix block
MatrixBlock tmp = cmb2.decompress();
// compare result with input
double[][] d1 = DataConverter.convertToDoubleMatrix(mb);
double[][] d2 = DataConverter.convertToDoubleMatrix(tmp);
if(compressionSettings.lossy) {
TestUtils.compareMatrices(d1, d2, lossyTolerance);
}
else {
TestUtils.compareMatricesBitAvgDistance(d1, d2, 0, 0, compressionSettings.toString());
}
}
catch(Exception e) {
e.printStackTrace();
throw new RuntimeException(this.toString() + "\n" + e.getMessage(), e);
}
}
@Test
public void testCompressionRatio() {
try {
if(!(cmb instanceof CompressedMatrixBlock))
return;
CompressionStatistics cStat = ((CompressedMatrixBlock) cmb).getCompressionStatistics();
assertTrue("Compression ration if compressed should be larger than 1", cStat.ratio > 1);
}
catch(Exception e) {
e.printStackTrace();
throw new RuntimeException(this.toString() + "\n" + e.getMessage(), e);
}
}
@Test
public void testCompressionEstimationVSCompression() {
try {
if(!(cmb instanceof CompressedMatrixBlock))
return;
CompressionStatistics cStat = ((CompressedMatrixBlock) cmb).getCompressionStatistics();
long colsEstimate = cStat.estimatedSizeCols;
long actualSize = cStat.size;
long originalSize = cStat.originalSize;
int allowedTolerance = 0;
if(compressionSettings.samplingRatio < 1.0) {
allowedTolerance = sampleTolerance;
}
StringBuilder builder = new StringBuilder();
builder.append("\n\t" + String.format("%-40s - %12d", "Actual compressed size: ", actualSize));
builder.append("\n\t" + String.format("%-40s - %12d with tolerance: %5d",
"<= estimated isolated ColGroups: ",
colsEstimate,
allowedTolerance));
builder.append("\n\t" + String.format("%-40s - %12d", "<= Original size: ", originalSize));
builder.append("\n\tcol groups types: " + cStat.getGroupsTypesString());
builder.append("\n\tcol groups sizes: " + cStat.getGroupsSizesString());
builder.append("\n\t" + this.toString());
boolean res = actualSize - colsEstimate <= allowedTolerance;
assertTrue(builder.toString(), res);
}
catch(Exception e) {
e.printStackTrace();
throw new RuntimeException(this.toString() + "\n" + e.getMessage(), e);
}
}
@Test
public void testCompressionEstimationVSJolEstimate() {
try {
if(!(cmb instanceof CompressedMatrixBlock))
return;
CompressionStatistics cStat = ((CompressedMatrixBlock) cmb).getCompressionStatistics();
long actualSize = cStat.size;
long originalSize = cStat.originalSize;
long JolEstimatedSize = getJolSize(((CompressedMatrixBlock) cmb));
StringBuilder builder = new StringBuilder();
builder.append("\n\t" + String.format("%-40s - %12d", "Actual compressed size: ", actualSize));
builder.append("\n\t" + String.format("%-40s - %12d", "<= Original size: ", originalSize));
builder.append("\n\t" + String.format("%-40s - %12d", "and equal to JOL Size: ", JolEstimatedSize));
// builder.append("\n\t " + getJolSizeString(cmb));
builder.append("\n\tcol groups types: " + cStat.getGroupsTypesString());
builder.append("\n\tcol groups sizes: " + cStat.getGroupsSizesString());
builder.append("\n\t" + this.toString());
// NOTE: The Jol estimate is wrong for shared dictionaries because
// it treats the object hierarchy as a tree and not a graph
assertTrue(builder.toString(),
actualSize <= originalSize &&
(compressionSettings.allowSharedDDCDictionary || actualSize == JolEstimatedSize));
}
catch(Exception e) {
e.printStackTrace();
throw new RuntimeException(this.toString() + "\n" + e.getMessage(), e);
}
}
@Test
public void testCompressionScale() {
// This test is here for a sanity check such that we verify that the compression
// ratio from our Matrix
// Compressed Block is not unreasonably good.
try {
if(!(cmb instanceof CompressedMatrixBlock))
return;
CompressionStatistics cStat = ((CompressedMatrixBlock) cmb).getCompressionStatistics();
double compressRatio = cStat.ratio;
long actualSize = cStat.size;
long originalSize = cStat.originalSize;
StringBuilder builder = new StringBuilder();
builder.append("Compression Ratio sounds suspiciously good at: " + compressRatio);
builder.append("\n\tActual compressed size: " + actualSize);
builder.append(" original size: " + originalSize);
builder.append("\n\tcol groups types: " + cStat.getGroupsTypesString());
builder.append("\n\tcol groups sizes: " + cStat.getGroupsSizesString());
builder.append("\n\t" + this.toString());
assertTrue(builder.toString(), compressRatio < 1000.0);
}
catch(Exception e) {
e.printStackTrace();
throw new RuntimeException(this.toString() + "\n" + e.getMessage(), e);
}
}
private static long getJolSize(CompressedMatrixBlock cmb) {
Layouter l = new HotSpotLayouter(new X86_64_DataModel());
long jolEstimate = 0;
CompressionStatistics cStat = cmb.getCompressionStatistics();
for(Object ob : new Object[] {cmb, cStat, cStat.getColGroups(), cStat.getTimeArrayList(), cmb.getColGroups()}) {
jolEstimate += ClassLayout.parseInstance(ob, l).instanceSize();
}
for(ColGroup cg : cmb.getColGroups()) {
jolEstimate += cg.estimateInMemorySize();
}
return jolEstimate;
}
@SuppressWarnings("unused")
private static String getJolSizeString(CompressedMatrixBlock cmb) {
StringBuilder builder = new StringBuilder();
Layouter l = new HotSpotLayouter(new X86_64_DataModel());
long diff;
long jolEstimate = 0;
CompressionStatistics cStat = cmb.getCompressionStatistics();
for(Object ob : new Object[] {cmb, cStat, cStat.getColGroups(), cStat.getTimeArrayList(), cmb.getColGroups()}) {
ClassLayout cl = ClassLayout.parseInstance(ob, l);
diff = cl.instanceSize();
jolEstimate += diff;
builder.append(cl.toPrintable());
builder.append("TOTAL MEM: " + jolEstimate + " diff " + diff + "\n");
}
for(ColGroup cg : cmb.getColGroups()) {
diff = cg.estimateInMemorySize();
jolEstimate += diff;
builder.append(cg.getCompType());
builder.append("TOTAL MEM: " + jolEstimate + " diff " + diff + "\n");
}
return builder.toString();
}
}