package edu.stanford.nlp.loglinear.model; import com.pholser.junit.quickcheck.ForAll; import com.pholser.junit.quickcheck.From; import com.pholser.junit.quickcheck.generator.GenerationStatus; import com.pholser.junit.quickcheck.generator.Generator; import com.pholser.junit.quickcheck.generator.InRange; import com.pholser.junit.quickcheck.random.SourceOfRandomness; import org.junit.contrib.theories.Theories; import org.junit.contrib.theories.Theory; import org.junit.runner.RunWith; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.util.Random; import java.util.function.Function; import static org.junit.Assert.*; import static org.junit.Assume.assumeTrue; @RunWith(Theories.class) public class ConcatVectorTest { @Theory public void testNewEmptyClone(@ForAll(sampleSize = 50) @From(DenseTestVectorGenerator.class) DenseTestVector d1) { ConcatVector empty = new ConcatVector(d1.vector.getNumberOfComponents()); ConcatVector emptyClone = d1.vector.newEmptyClone(); assertTrue(empty.valueEquals(emptyClone, 1.0e-5)); } @Theory public void testResizeOnSetComponent(@ForAll(sampleSize = 50) @From(DenseTestVectorGenerator.class) DenseTestVector d1) { d1.vector.setSparseComponent(d1.values.length, 1, 0.0); d1.vector.setDenseComponent(d1.values.length + 1, new double[]{0.0}); } @Theory public void testCopyOnWrite(@ForAll(sampleSize = 50) @From(DenseTestVectorGenerator.class) DenseTestVector d1) { ConcatVector v2 = d1.vector.deepClone(); v2.addVectorInPlace(v2, 1.0); for (int i = 0; i < d1.values.length; i++) { for (int j = 0; j < d1.values[i].length; j++) { assertEquals(d1.values[i][j], d1.vector.getValueAt(i, j), 5.0e-4); assertEquals(d1.values[i][j] * 2, v2.getValueAt(i, j), 5.0e-4); } } } @Theory public void testAppendDenseComponent(@ForAll(sampleSize = 10) double[] vector1, @ForAll(sampleSize = 10) double[] vector2) throws Exception { ConcatVector v1 = new ConcatVector(1); ConcatVector v2 = new ConcatVector(1); v1.setDenseComponent(0, vector1); v2.setDenseComponent(0, vector2); double sum = 0.0f; for (int i = 0; i < Math.min(vector1.length, vector2.length); i++) { sum += vector1[i] * vector2[i]; } assertEquals(sum, v1.dotProduct(v2), 5.0e-4); } @Theory public void testAppendSparseComponent(@ForAll(sampleSize = 10) @InRange(minInt = 0, maxInt = 10000) int sparse1, @ForAll(sampleSize = 10) double sparse1Val, @ForAll(sampleSize = 10) @InRange(minInt = 0, maxInt = 10000) int sparse2, @ForAll(sampleSize = 10) double sparse2Val) throws Exception { ConcatVector v1 = new ConcatVector(1); ConcatVector v2 = new ConcatVector(1); v1.setSparseComponent(0, sparse1, sparse1Val); v2.setSparseComponent(0, sparse2, sparse2Val); if (sparse1 == sparse2) { assertEquals(sparse1Val * sparse2Val, v1.dotProduct(v2), 5.0e-4); } else { assertEquals(0.0, v1.dotProduct(v2), 5.0e-4); } } @Theory public void testGetSparseIndex(@ForAll(sampleSize = 10) @InRange(minInt = 0, maxInt = 10000) int sparse1, @ForAll(sampleSize = 10) double sparse1Val, @ForAll(sampleSize = 10) @InRange(minInt = 0, maxInt = 10000) int sparse2, @ForAll(sampleSize = 10) double sparse2Val) throws Exception { ConcatVector v1 = new ConcatVector(2); ConcatVector v2 = new ConcatVector(2); v1.setSparseComponent(0, sparse1, sparse1Val); v1.setSparseComponent(1, sparse2, sparse1Val); v2.setSparseComponent(0, sparse2, sparse2Val); v2.setSparseComponent(1, sparse1, sparse2Val); assertEquals(sparse1, v1.getSparseIndex(0)); assertEquals(sparse2, v1.getSparseIndex(1)); assertEquals(sparse2, v2.getSparseIndex(0)); assertEquals(sparse1, v2.getSparseIndex(1)); } @Theory public void testInnerProduct(@ForAll(sampleSize = 50) @From(DenseTestVectorGenerator.class) DenseTestVector d1, @ForAll(sampleSize = 50) @From(DenseTestVectorGenerator.class) DenseTestVector d2) throws Exception { assertEquals(d1.trueInnerProduct(d2) + d2.trueInnerProduct(d2), d1.vector.dotProduct(d2.vector) + d2.vector.dotProduct(d2.vector), 5.0e-4); } @Theory public void testDeepClone(@ForAll(sampleSize = 50) @From(DenseTestVectorGenerator.class) DenseTestVector d1, @ForAll(sampleSize = 50) @From(DenseTestVectorGenerator.class) DenseTestVector d2) throws Exception { assertEquals(d1.vector.dotProduct(d2.vector), d1.vector.deepClone().dotProduct(d2.vector), 5.0e-4); } @Theory public void testDeepCloneGetValueAt(@ForAll(sampleSize = 250) @From(DenseTestVectorGenerator.class) DenseTestVector d1) throws Exception { ConcatVector mv = d1.vector; ConcatVector clone = d1.vector.deepClone(); for (int i = 0; i < d1.values.length; i++) { for (int j = 0; j < d1.values[i].length; j++) { assertEquals(mv.getValueAt(i, j), clone.getValueAt(i, j), 1.0e-10); } } } @Theory public void testAddDenseToDense(@ForAll double[] dense1, @ForAll double[] dense2) { ConcatVector v1 = new ConcatVector(1); v1.setDenseComponent(0, dense1); ConcatVector v2 = new ConcatVector(1); v2.setDenseComponent(0, dense2); double expected = v1.dotProduct(v2) + 0.7f * (v2.dotProduct(v2)); v1.addVectorInPlace(v2, 0.7f); assertEquals(expected, v1.dotProduct(v2), 5.0e-3); } @Theory public void testAddSparseToDense(@ForAll(sampleSize = 50) double[] dense1, @ForAll(sampleSize = 20) @InRange(minInt = 0, maxInt = 100) int sparseIndex, @ForAll(sampleSize = 10) double v) { ConcatVector v1 = new ConcatVector(1); v1.setDenseComponent(0, dense1); ConcatVector v2 = new ConcatVector(1); v2.setSparseComponent(0, sparseIndex, v); double expected = v1.dotProduct(v2) + 0.7f * (v2.dotProduct(v2)); v1.addVectorInPlace(v2, 0.7f); assertEquals(expected, v1.dotProduct(v2), 5.0e-4); } @Theory public void testAddDenseToSparse(@ForAll(sampleSize = 50) double[] dense1, @ForAll(sampleSize = 20) @InRange(minInt = 0, maxInt = 100) int sparseIndex, @ForAll(sampleSize = 10) double v) { assumeTrue(sparseIndex >= 0); assumeTrue(sparseIndex <= 100); ConcatVector v1 = new ConcatVector(1); v1.setDenseComponent(0, dense1); ConcatVector v2 = new ConcatVector(1); v2.setSparseComponent(0, sparseIndex, v); double expected = v1.dotProduct(v2) + 0.7f * (v1.dotProduct(v1)); v2.addVectorInPlace(v1, 0.7f); assertEquals(expected, v2.dotProduct(v1), 5.0e-4); } @Theory public void testAddSparseToSparse(@ForAll(sampleSize = 20) @InRange(minInt = 0, maxInt = 10) int sparseIndex1, @ForAll(sampleSize = 10) double val1, @ForAll(sampleSize = 20) @InRange(minInt = 0, maxInt = 10) int sparseIndex2, @ForAll(sampleSize = 10) double val2) { ConcatVector v1 = new ConcatVector(1); v1.setSparseComponent(0, sparseIndex1, val1); ConcatVector v2 = new ConcatVector(1); v2.setSparseComponent(0, sparseIndex2, val2); double expected = v1.dotProduct(v2) + 0.7f * (v2.dotProduct(v2)); v1.addVectorInPlace(v2, 0.7f); assertEquals(expected, v1.dotProduct(v2), 5.0e-3); } @Theory public void testInnerProduct2(@ForAll(sampleSize = 10) @From(DenseTestVectorGenerator.class) DenseTestVector d1, @ForAll(sampleSize = 10) @From(DenseTestVectorGenerator.class) DenseTestVector d2, @ForAll(sampleSize = 10) @From(DenseTestVectorGenerator.class) DenseTestVector d3) throws Exception { // Test the invariant x^Tz + 0.7*y^Tz == (x+0.7*y)^Tz double d1d3 = d1.vector.dotProduct(d3.vector); assertEquals(d1.trueInnerProduct(d3), d1d3, 5.0e-4); double d2d3 = d2.vector.dotProduct(d3.vector); assertEquals(d2.trueInnerProduct(d3), d2d3, 5.0e-4); double expected = d1d3 + (0.7f * d2d3); assertEquals(d1.trueInnerProduct(d3) + (0.7 * d2.trueInnerProduct(d3)), expected, 5.0e-4); } @Theory public void testAddVector(@ForAll(sampleSize = 10) @From(DenseTestVectorGenerator.class) DenseTestVector d1, @ForAll(sampleSize = 10) @From(DenseTestVectorGenerator.class) DenseTestVector d2, @ForAll(sampleSize = 10) @From(DenseTestVectorGenerator.class) DenseTestVector d3) throws Exception { // Test the invariant x^Tz + 0.7*y^Tz == (x+0.7*y)^Tz double expected = d1.vector.dotProduct(d3.vector) + (0.7f * d2.vector.dotProduct(d3.vector)); ConcatVector clone = d1.vector.deepClone(); clone.addVectorInPlace(d2.vector, 0.7f); assertEquals(expected, clone.dotProduct(d3.vector), 5.0e-4); } @Theory public void testProtoVector(@ForAll(sampleSize = 50) @From(DenseTestVectorGenerator.class) DenseTestVector d1, @ForAll(sampleSize = 50) @From(DenseTestVectorGenerator.class) DenseTestVector d2) throws Exception { double expected = d1.vector.dotProduct(d2.vector); ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); assert (d1.vector.getClass() == ConcatVector.class); d1.vector.writeToStream(byteArrayOutputStream); byteArrayOutputStream.close(); byte[] bytes = byteArrayOutputStream.toByteArray(); ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); ConcatVector recovered = ConcatVector.readFromStream(byteArrayInputStream); assertEquals(expected, recovered.dotProduct(d2.vector), 5.0e-4); } @Theory public void testSizes(@ForAll(sampleSize = 50) @From(DenseTestVectorGenerator.class) DenseTestVector d1) { int size = d1.vector.getNumberOfComponents(); assertEquals(d1.values.length, size); } @Theory public void testIsSparse(@ForAll(sampleSize = 50) @From(DenseTestVectorGenerator.class) DenseTestVector d1) { int size = d1.vector.getNumberOfComponents(); assertEquals(d1.values.length, size); for (int i = 0; i < d1.values.length; i++) { if (d1.vector.isComponentSparse(i)) { for (int j = 0; j < d1.values[i].length; j++) { if (d1.vector.getSparseIndex(i) != j) assertEquals(0.0, d1.values[i][j], 1.0e-9); } } } } @Theory public void testRetrieveDense(@ForAll(sampleSize = 50) @From(DenseTestVectorGenerator.class) DenseTestVector d1) { int size = d1.vector.getNumberOfComponents(); assertEquals(d1.values.length, size); for (int i = 0; i < d1.values.length; i++) { if (!d1.vector.isComponentSparse(i)) { assertArrayEquals(d1.values[i], d1.vector.getDenseComponent(i), 1.0e-9); } } } @Theory public void testGetValueAt(@ForAll(sampleSize = 50) @From(DenseTestVectorGenerator.class) DenseTestVector d1) { for (int i = 0; i < d1.values.length; i++) { for (int j = 0; j < d1.values[i].length; j++) { assertEquals(d1.values[i][j], d1.vector.getValueAt(i, j), 5.0e-4); } } } @Theory public void testElementwiseProduct(@ForAll(sampleSize = 50) @From(DenseTestVectorGenerator.class) DenseTestVector d1, @ForAll(sampleSize = 50) @From(DenseTestVectorGenerator.class) DenseTestVector d2) { for (int i = 0; i < d1.values.length; i++) { for (int j = 0; j < d1.values[i].length; j++) { assumeTrue(d1.values[i][j] == d1.vector.getValueAt(i, j)); } } for (int i = 0; i < d2.values.length; i++) { for (int j = 0; j < d2.values[i].length; j++) { assumeTrue(d2.values[i][j] == d2.vector.getValueAt(i, j)); } } ConcatVector clone = d1.vector.deepClone(); clone.elementwiseProductInPlace(d2.vector); for (int i = 0; i < d1.values.length; i++) { for (int j = 0; j < d1.values[i].length; j++) { double val = 0.0f; if (i < d2.values.length) { if (j < d2.values[i].length) { val = d1.values[i][j] * d2.values[i][j]; } } assertEquals(val, clone.getValueAt(i, j), 5.0e-4); } } } @Theory public void testElementwiseDenseToDense(@ForAll double[] dense1, @ForAll double[] dense2) { ConcatVector v1 = new ConcatVector(1); v1.setDenseComponent(0, dense1); ConcatVector v2 = new ConcatVector(1); v2.setDenseComponent(0, dense2); v1.elementwiseProductInPlace(v2); for (int i = 0; i < dense1.length; i++) { double expected = 0.0f; if (i < dense2.length) expected = dense1[i] * dense2[i]; assertEquals(expected, v1.getValueAt(0, i), 5.0e-4); } } @Theory public void testElementwiseSparseToDense(@ForAll(sampleSize = 50) double[] dense1, @ForAll(sampleSize = 20) @InRange(minInt = 0, maxInt = 100) int sparseIndex, @ForAll(sampleSize = 10) double v) { ConcatVector v1 = new ConcatVector(1); v1.setDenseComponent(0, dense1); ConcatVector v2 = new ConcatVector(1); v2.setSparseComponent(0, sparseIndex, v); v1.elementwiseProductInPlace(v2); for (int i = 0; i < dense1.length; i++) { double expected = 0.0f; if (i == sparseIndex) expected = dense1[i] * v; assertEquals(expected, v1.getValueAt(0, i), 5.0e-4); } } @Theory public void testElementwiseDenseToSparse(@ForAll(sampleSize = 50) double[] dense1, @ForAll(sampleSize = 20) @InRange(minInt = 0, maxInt = 100) int sparseIndex, @ForAll(sampleSize = 10) double v) { assumeTrue(sparseIndex >= 0); assumeTrue(sparseIndex <= 100); ConcatVector v1 = new ConcatVector(1); v1.setDenseComponent(0, dense1); ConcatVector v2 = new ConcatVector(1); v2.setSparseComponent(0, sparseIndex, v); v2.elementwiseProductInPlace(v1); for (int i = 0; i < dense1.length; i++) { double expected = 0.0f; if (i == sparseIndex) expected = dense1[i] * v; assertEquals(expected, v2.getValueAt(0, i), 5.0e-4); } } @Theory public void testElementwiseSparseToSparse(@ForAll(sampleSize = 20) @InRange(minInt = 0, maxInt = 10) int sparseIndex1, @ForAll(sampleSize = 10) double val1, @ForAll(sampleSize = 20) @InRange(minInt = 0, maxInt = 10) int sparseIndex2, @ForAll(sampleSize = 10) double val2) { ConcatVector v1 = new ConcatVector(1); v1.setSparseComponent(0, sparseIndex1, val1); ConcatVector v2 = new ConcatVector(1); v2.setSparseComponent(0, sparseIndex2, val2); v1.elementwiseProductInPlace(v2); for (int i = 0; i < 10; i++) { double expected = 0.0f; if (i == sparseIndex1 && i == sparseIndex2) expected = val1 * val2; assertEquals(expected, v1.getValueAt(0, i), 5.0e-4); } } @Theory public void testMap(@ForAll(sampleSize = 50) @From(DenseTestVectorGenerator.class) DenseTestVector d1) { d1.vector.mapInPlace(x -> (x * Math.sqrt(x))); d1.map(x -> (x * Math.sqrt(x))); for (int i = 0; i < d1.values.length; i++) { for (int j = 0; j < d1.values[i].length; j++) { assertEquals(d1.values[i][j], d1.vector.getValueAt(i, j), 5.0e-4); } } } @Theory public void testValueEquals(@ForAll(sampleSize = 50) @From(DenseTestVectorGenerator.class) DenseTestVector d1) { ConcatVector clone = d1.vector.deepClone(); assertTrue(clone.valueEquals(d1.vector, 1.0e-5)); assertTrue(d1.vector.valueEquals(clone, 1.0e-5)); assertTrue(d1.vector.valueEquals(d1.vector, 1.0e-5)); assertTrue(clone.valueEquals(clone, 1.0e-5)); Random r = new Random(); int size = clone.getNumberOfComponents(); if (size > 0) { clone.addVectorInPlace(d1.vector, 1.0); // If the clone is a 0 vector boolean isZero = true; for (double[] arr : d1.values) { for (double d : arr) if (d != 0) isZero = false; } if (isZero) { assertTrue(clone.valueEquals(d1.vector, 1.0e-5)); assertTrue(d1.vector.valueEquals(clone, 1.0e-5)); } else { assertFalse(clone.valueEquals(d1.vector, 1.0e-5)); assertFalse(d1.vector.valueEquals(clone, 1.0e-5)); } assertTrue(d1.vector.valueEquals(d1.vector, 1.0e-5)); assertTrue(clone.valueEquals(clone, 1.0e-5)); // refresh the clone clone = d1.vector.deepClone(); int tinker = r.nextInt(size); d1.vector.setDenseComponent(tinker, new double[]{0, 0, 1}); clone.setSparseComponent(tinker, 2, 1); assertTrue(d1.vector.valueEquals(clone, 1.0e-5)); assertTrue(clone.valueEquals(d1.vector, 1.0e-5)); } } /** * Created by keenon on 12/6/14. * <p> * DenseVector with obviously correct semantics for checking the MultiVector against. */ public static class DenseTestVector { public double[][] values; public ConcatVector vector; public DenseTestVector(double[][] values, ConcatVector vector) { this.values = values; this.vector = vector; } public double trueInnerProduct(DenseTestVector testVector) { double sum = 0.0f; for (int i = 0; i < Math.min(values.length, testVector.values.length); i++) { for (int j = 0; j < Math.min(values[i].length, testVector.values[i].length); j++) { sum += values[i][j] * testVector.values[i][j]; } } return sum; } public void map(Function<Double, Double> fn) { for (int i = 0; i < values.length; i++) { for (int j = 0; j < values[i].length; j++) { values[i][j] = fn.apply(values[i][j]); } } } @Override public String toString() { return vector.toString(); } } /** * Created by keenon on 12/6/14. * <p> * Handles generating the inputs for Quickcheck against the MultiVector */ public static class DenseTestVectorGenerator extends Generator<DenseTestVector> { public DenseTestVectorGenerator(Class<DenseTestVector> type) { super(type); } static final int SPARSE_VECTOR_LENGTH = 5; public DenseTestVectorGenerator() { super(DenseTestVector.class); } @Override public DenseTestVector generate(SourceOfRandomness sourceOfRandomness, GenerationStatus generationStatus) { int length = sourceOfRandomness.nextInt(10); double[][] trueValues = new double[length][]; boolean[] sparse = new boolean[length]; int[] sizes = new int[length]; // Generate sizes in advance, so we can pass the clues on to the constructor for the multivector for (int i = 0; i < length; i++) { boolean isSparse = sourceOfRandomness.nextBoolean(); sparse[i] = isSparse; if (isSparse) { sizes[i] = -1; } else { int componentLength = sourceOfRandomness.nextInt(SPARSE_VECTOR_LENGTH); sizes[i] = componentLength; } } ConcatVector mv = new ConcatVector(length); for (int i = 0; i < length; i++) { if (sparse[i]) { trueValues[i] = new double[SPARSE_VECTOR_LENGTH]; int sparseIndex = sourceOfRandomness.nextInt(SPARSE_VECTOR_LENGTH); double sparseValue = sourceOfRandomness.nextDouble(); trueValues[i][sparseIndex] = sparseValue; mv.setSparseComponent(i, sparseIndex, sparseValue); } else { trueValues[i] = new double[sizes[i]]; // Ensure we have some null components in our generated vector if (sizes[i] > 0) { for (int j = 0; j < sizes[i]; j++) { trueValues[i][j] = sourceOfRandomness.nextDouble(); } mv.setDenseComponent(i, trueValues[i]); } } } return new DenseTestVector(trueValues, mv); } } }