http://git-wip-us.apache.org/repos/asf/ignite/blob/0abf6601/modules/ml/src/test/java/org/apache/ignite/ml/math/impls/vector/RandomVectorConstructorTest.java ---------------------------------------------------------------------- diff --git a/modules/ml/src/test/java/org/apache/ignite/ml/math/impls/vector/RandomVectorConstructorTest.java b/modules/ml/src/test/java/org/apache/ignite/ml/math/impls/vector/RandomVectorConstructorTest.java new file mode 100644 index 0000000..49e1a50 --- /dev/null +++ b/modules/ml/src/test/java/org/apache/ignite/ml/math/impls/vector/RandomVectorConstructorTest.java @@ -0,0 +1,145 @@ +/* + * 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.ignite.ml.math.impls.vector; + +import java.util.HashMap; +import java.util.Map; +import org.apache.ignite.ml.math.Vector; +import org.apache.ignite.ml.math.exceptions.UnsupportedOperationException; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** */ +public class RandomVectorConstructorTest { + /** */ + private static final int IMPOSSIBLE_SIZE = -1; + + /** */ + @Test(expected = UnsupportedOperationException.class) + public void mapInvalidArgsTest() { + assertEquals("Expect exception due to invalid args.", IMPOSSIBLE_SIZE, + new RandomVector(new HashMap<String, Object>() {{ + put("invalid", 99); + }}).size()); + } + + /** */ + @Test(expected = UnsupportedOperationException.class) + public void mapMissingArgsTest() { + final Map<String, Object> test = new HashMap<String, Object>() {{ + put("paramMissing", "whatever"); + }}; + + assertEquals("Expect exception due to missing args.", + -1, new RandomVector(test).size()); + } + + /** */ + @Test(expected = ClassCastException.class) + public void mapInvalidParamTypeTest() { + final Map<String, Object> test = new HashMap<String, Object>() {{ + put("size", "whatever"); + put("fastHash", true); + }}; + + assertEquals("Expect exception due to invalid param type.", IMPOSSIBLE_SIZE, + new RandomVector(test).size()); + } + + /** */ + @Test(expected = AssertionError.class) + public void mapNullTest() { + //noinspection ConstantConditions + assertEquals("Null map args.", IMPOSSIBLE_SIZE, + new RandomVector(null).size()); + } + + /** */ + @Test + public void mapTest() { + assertEquals("Size from args.", 99, + new RandomVector(new HashMap<String, Object>() {{ + put("size", 99); + }}).size()); + + final int test = 99; + + assertEquals("Size from args with fastHash false.", test, + new RandomVector(new HashMap<String, Object>() {{ + put("size", test); + put("fastHash", false); + }}).size()); + + assertEquals("Size from args with fastHash true.", test, + new RandomVector(new HashMap<String, Object>() {{ + put("size", test); + put("fastHash", true); + }}).size()); + } + + /** */ + @Test(expected = AssertionError.class) + public void negativeSizeTest() { + assertEquals("Negative size.", IMPOSSIBLE_SIZE, + new RandomVector(-1).size()); + } + + /** */ + @Test + public void basicTest() { + final int basicSize = 3; + + Vector v1 = new RandomVector(basicSize); + + //noinspection EqualsWithItself + assertTrue("Expect vector to be equal to self", v1.equals(v1)); + + //noinspection ObjectEqualsNull + assertFalse("Expect vector to be not equal to null", v1.equals(null)); + + assertEquals("Size differs from expected", basicSize, v1.size()); + + verifyValues(v1); + + Vector v2 = new RandomVector(basicSize, true); + + assertEquals("Size differs from expected", basicSize, v2.size()); + + verifyValues(v2); + + Vector v3 = new RandomVector(basicSize, false); + + assertEquals("Size differs from expected", basicSize, v3.size()); + + verifyValues(v3); + } + + /** */ + private void verifyValues(Vector v) { + for (Vector.Element e : v.all()) { + double val = e.get(); + + assertTrue("Value too small: " + val + " at index " + e.index(), -1d <= val); + + assertTrue("Value too large: " + val + " at index " + e.index(), val <= 1d); + } + } +}
http://git-wip-us.apache.org/repos/asf/ignite/blob/0abf6601/modules/ml/src/test/java/org/apache/ignite/ml/math/impls/vector/SingleElementVectorConstructorTest.java ---------------------------------------------------------------------- diff --git a/modules/ml/src/test/java/org/apache/ignite/ml/math/impls/vector/SingleElementVectorConstructorTest.java b/modules/ml/src/test/java/org/apache/ignite/ml/math/impls/vector/SingleElementVectorConstructorTest.java new file mode 100644 index 0000000..db4d5de --- /dev/null +++ b/modules/ml/src/test/java/org/apache/ignite/ml/math/impls/vector/SingleElementVectorConstructorTest.java @@ -0,0 +1,159 @@ +/* + * 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.ignite.ml.math.impls.vector; + +import java.util.HashMap; +import java.util.Map; +import org.apache.ignite.ml.math.Vector; +import org.apache.ignite.ml.math.exceptions.UnsupportedOperationException; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** */ +public class SingleElementVectorConstructorTest { + /** */ + private static final int IMPOSSIBLE_SIZE = -1; + + /** */ + @Test(expected = UnsupportedOperationException.class) + public void mapInvalidArgsTest() { + assertEquals("Expect exception due to invalid args.", IMPOSSIBLE_SIZE, + new SingleElementVector(new HashMap<String, Object>() {{ + put("invalid", 99); + }}).size()); + } + + /** */ + @Test(expected = UnsupportedOperationException.class) + public void mapMissingArgsTest() { + final Map<String, Object> test = new HashMap<String, Object>() {{ + put("size", 1); + + put("paramMissing", "whatever"); + }}; + + assertEquals("Expect exception due to missing args.", + -1, new SingleElementVector(test).size()); + } + + /** */ + @Test(expected = ClassCastException.class) + public void mapInvalidParamTypeTest() { + final Map<String, Object> test = new HashMap<String, Object>() {{ + put("size", "whatever"); + + put("index", 0); + put("value", 1.0); + }}; + + assertEquals("Expect exception due to invalid param type.", IMPOSSIBLE_SIZE, + new SingleElementVector(test).size()); + } + + /** */ + @Test(expected = AssertionError.class) + public void mapNullTest() { + //noinspection ConstantConditions + assertEquals("Null map args.", IMPOSSIBLE_SIZE, + new SingleElementVector(null).size()); + } + + /** */ + @Test + public void mapTest() { + assertEquals("Size from array in args.", 99, + new SingleElementVector(new HashMap<String, Object>() {{ + put("size", 99); + put("index", 0); + put("value", 1.0); + }}).size()); + } + + /** */ + @Test(expected = AssertionError.class) + public void negativeSizeTest() { + assertEquals("Negative size.", IMPOSSIBLE_SIZE, + new SingleElementVector(-1, 0, 1.0).size()); + } + + /** */ + @Test(expected = AssertionError.class) + public void zeroSizeTest() { + assertEquals("Zero size.", IMPOSSIBLE_SIZE, + new SingleElementVector(0, 0, 1.0).size()); + } + + /** */ + @Test(expected = AssertionError.class) + public void wrongIndexTest() { + //noinspection ConstantConditions + assertEquals("Wrong index.", IMPOSSIBLE_SIZE, + new SingleElementVector(1, 2, 1.0).size()); + } + + /** */ + @Test + public void basicTest() { + final int[] sizes = new int[] {1, 4, 8}; + + for (int size : sizes) + for (int idx = 0; idx < size; idx++) + basicTest(size, idx); + } + + /** */ + private void basicTest(int size, int idx) { + final Double expVal = (double)(size - idx); + + Vector v = new SingleElementVector(size, idx, expVal); + + assertTrue("Expect value " + expVal + " at index " + idx + " for size " + size, + expVal.equals(v.get(idx))); + + final double delta = 1.0; + + v.set(idx, expVal - delta); + + assertTrue("Expect value " + expVal + " at index " + idx + " for size " + size, + expVal.equals(v.get(idx) + delta)); + + final Double zero = 0.0; + + for (int i = 0; i < size; i++) { + if (i == idx) + continue; + + assertTrue("Expect zero at index " + i + " for size " + size, + zero.equals(v.get(i))); + + boolean eCaught = false; + + try { + v.set(i, 1.0); + } + catch (UnsupportedOperationException uoe) { + eCaught = true; + } + + assertTrue("Expect " + java.lang.UnsupportedOperationException.class.getSimpleName() + + " at index " + i + " for size " + size, eCaught); + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/0abf6601/modules/ml/src/test/java/org/apache/ignite/ml/math/impls/vector/SingleElementVectorViewConstructorTest.java ---------------------------------------------------------------------- diff --git a/modules/ml/src/test/java/org/apache/ignite/ml/math/impls/vector/SingleElementVectorViewConstructorTest.java b/modules/ml/src/test/java/org/apache/ignite/ml/math/impls/vector/SingleElementVectorViewConstructorTest.java new file mode 100644 index 0000000..a693319 --- /dev/null +++ b/modules/ml/src/test/java/org/apache/ignite/ml/math/impls/vector/SingleElementVectorViewConstructorTest.java @@ -0,0 +1,137 @@ +/* + * 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.ignite.ml.math.impls.vector; + +import org.apache.ignite.ml.math.Vector; +import org.apache.ignite.ml.math.exceptions.UnsupportedOperationException; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +/** */ +public class SingleElementVectorViewConstructorTest { + /** */ + private static final int IMPOSSIBLE_SIZE = -1; + + /** */ + private static final SampleHelper helper = new SampleHelper(); + + /** */ + @Test(expected = AssertionError.class) + public void nullVecParamTest() { + assertEquals("Expect exception due to null vector param.", IMPOSSIBLE_SIZE, + new SingleElementVectorView(null, helper.idx).size()); + } + + /** */ + @Test(expected = AssertionError.class) + public void negativeIdxParamTest() { + assertEquals("Expect exception due to negative index param.", IMPOSSIBLE_SIZE, + new SingleElementVectorView(helper.vec, -1).size()); + } + + /** */ + @Test(expected = AssertionError.class) + public void tooLargeIdxParamTest() { + assertEquals("Expect exception due to too large index param.", IMPOSSIBLE_SIZE, + new SingleElementVectorView(helper.vec, helper.vec.size()).size()); + } + + /** */ + @Test(expected = AssertionError.class) + public void emptyVecParamTest() { + assertEquals("Expect exception due to empty vector param.", IMPOSSIBLE_SIZE, + new SingleElementVectorView(helper.vecEmpty, 0).size()); + } + + /** */ + @Test + public void basicTest() { + final int[] sizes = new int[] {1, 4, 8}; + + for (int size : sizes) + for (int idx = 0; idx < size; idx++) + basicTest(size, idx); + } + + /** */ + private void basicTest(int size, int idx) { + final Double expVal = (double)(size - idx); + + Vector orig = helper.newSample(size, idx, expVal); + + SingleElementVectorView svv = new SingleElementVectorView(orig, idx); + + assertEquals("Size differs from expected", size, svv.size()); + + assertTrue("Expect value " + expVal + " at index " + idx + " for size " + size, + expVal.equals(svv.get(idx))); + + final double delta = 1.0; + + svv.set(idx, expVal - delta); + + assertTrue("Expect value " + expVal + " at index " + idx + " for size " + size, + expVal.equals(orig.get(idx) + delta)); + + final Double zero = 0.0; + + for (int i = 0; i < size; i++) { + if (i == idx) + continue; + + assertTrue("Expect zero at index " + i + " for size " + size, + zero.equals(svv.get(i))); + + boolean eCaught = false; + + try { + svv.set(i, 1.0); + } + catch (UnsupportedOperationException uoe) { + eCaught = true; + } + + assertTrue("Expect " + UnsupportedOperationException.class.getSimpleName() + + " at index " + i + " for size " + size, eCaught); + } + } + + /** */ + private static class SampleHelper { + /** */ + final double[] data = new double[] {0, 1}; + /** */ + final Vector vec = new DenseLocalOnHeapVector(data); + /** */ + final Vector vecEmpty = new DenseLocalOnHeapVector(new double[] {}); + /** */ + final int idx = 0; + + /** */ + Vector newSample(int size, int idx, double expVal) { + final Vector v = new DenseLocalOnHeapVector(size); + + for (int i = 0; i < size; i++) + v.set(i, i == idx ? expVal : i); + + return v; + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/0abf6601/modules/ml/src/test/java/org/apache/ignite/ml/math/impls/vector/SparseLocalVectorConstructorTest.java ---------------------------------------------------------------------- diff --git a/modules/ml/src/test/java/org/apache/ignite/ml/math/impls/vector/SparseLocalVectorConstructorTest.java b/modules/ml/src/test/java/org/apache/ignite/ml/math/impls/vector/SparseLocalVectorConstructorTest.java new file mode 100644 index 0000000..912168e --- /dev/null +++ b/modules/ml/src/test/java/org/apache/ignite/ml/math/impls/vector/SparseLocalVectorConstructorTest.java @@ -0,0 +1,54 @@ +/* + * 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.ignite.ml.math.impls.vector; + +import org.apache.ignite.ml.math.StorageConstants; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** */ +public class SparseLocalVectorConstructorTest { + /** */ + private static final int IMPOSSIBLE_SIZE = -1; + + /** */ + @Test(expected = AssertionError.class) + public void negativeSizeTest() { + assertEquals("Negative size.", IMPOSSIBLE_SIZE, + new SparseLocalVector(-1, 1).size()); + } + + /** */ + @Test(expected = AssertionError.class) + public void zeroSizeTest() { + assertEquals("0 size.", IMPOSSIBLE_SIZE, + new SparseLocalVector(0, 1).size()); + } + + /** */ + @Test + public void primitiveTest() { + assertEquals("1 size, random access.", 1, + new SparseLocalVector(1, StorageConstants.RANDOM_ACCESS_MODE).size()); + + assertEquals("1 size, sequential access.", 1, + new SparseLocalVector(1, StorageConstants.SEQUENTIAL_ACCESS_MODE).size()); + + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/0abf6601/modules/ml/src/test/java/org/apache/ignite/ml/math/impls/vector/VectorAttributesTest.java ---------------------------------------------------------------------- diff --git a/modules/ml/src/test/java/org/apache/ignite/ml/math/impls/vector/VectorAttributesTest.java b/modules/ml/src/test/java/org/apache/ignite/ml/math/impls/vector/VectorAttributesTest.java new file mode 100644 index 0000000..e2f6a40 --- /dev/null +++ b/modules/ml/src/test/java/org/apache/ignite/ml/math/impls/vector/VectorAttributesTest.java @@ -0,0 +1,217 @@ +/* + * 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.ignite.ml.math.impls.vector; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; +import org.apache.ignite.ml.math.Vector; +import org.apache.ignite.ml.math.impls.matrix.DenseLocalOffHeapMatrix; +import org.apache.ignite.ml.math.impls.matrix.DenseLocalOnHeapMatrix; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** */ +public class VectorAttributesTest { + /** */ + private final List<AttrCfg> attrCfgs = Arrays.asList( + new AttrCfg("isDense", Vector::isDense, + DenseLocalOnHeapVector.class, DenseLocalOffHeapVector.class, RandomVector.class, ConstantVector.class, + SingleElementVector.class), + new AttrCfg("isArrayBased", Vector::isArrayBased, + DenseLocalOnHeapVector.class), + new AttrCfg("isSequentialAccess", Vector::isSequentialAccess, + DenseLocalOnHeapVector.class, DenseLocalOffHeapVector.class, SparseLocalVectorSequentialAccess.class, + RandomVector.class, ConstantVector.class, SingleElementVector.class), + new AttrCfg("guidNotNull", v -> v.guid() == null), // IMPL NOTE this is somewhat artificial + new AttrCfg("isRandomAccess", Vector::isRandomAccess, + DenseLocalOnHeapVector.class, DenseLocalOffHeapVector.class, RandomVector.class, ConstantVector.class, + SingleElementVector.class, SparseLocalVectorSequentialAccess.class, SparseLocalVectorRandomAccess.class), + new AttrCfg("isDistributed", Vector::isDistributed)); + + /** */ + private final List<Specification> specFixture = Arrays.asList( + new Specification(new DenseLocalOnHeapVector(1)), + new Specification(new DenseLocalOffHeapVector(1)), + new Specification(new DelegatingVector(new DenseLocalOnHeapVector(1)), + DenseLocalOnHeapVector.class, "isDense", "isArrayBased", "isSequentialAccess", + "isRandomAccess", "isDistributed"), + new Specification(new DelegatingVector(new DenseLocalOffHeapVector(1)), + DenseLocalOffHeapVector.class, "isDense", "isArrayBased", "isSequentialAccess", + "isRandomAccess", "isDistributed"), + new Specification(new SparseLocalVectorSequentialAccess(1)), + new Specification(new SparseLocalVectorRandomAccess(1)), + new Specification(new RandomVector(1)), + new Specification(new ConstantVector(1, 1.0)), + new Specification(new FunctionVector(1, idx -> (double)idx)), + new Specification(new SingleElementVector(1, 0, 1.0)), + new Specification(new PivotedVectorView(new DenseLocalOnHeapVector(1), new int[] {0}), + DenseLocalOnHeapVector.class, "isDense", "isArrayBased", "isSequentialAccess", + "isRandomAccess", "isDistributed"), + new Specification(new PivotedVectorView(new DenseLocalOffHeapVector(1), new int[] {0}), + DenseLocalOffHeapVector.class, "isDense", "isArrayBased", "isSequentialAccess", + "isRandomAccess", "isDistributed"), + new Specification(new SingleElementVectorView(new DenseLocalOnHeapVector(1), 0), + DenseLocalOnHeapVector.class, "isDense", "isSequentialAccess", + "isRandomAccess", "isDistributed"), + new Specification(new SingleElementVectorView(new DenseLocalOffHeapVector(1), 0), + DenseLocalOffHeapVector.class, "isDense", "isSequentialAccess", + "isRandomAccess", "isDistributed"), + new Specification(new MatrixVectorView(new DenseLocalOnHeapMatrix(1, 1), 0, 0, 1, 1), + DenseLocalOnHeapVector.class, "isDense", + "isRandomAccess", "isDistributed"), // todo find out why "isSequentialAccess" fails here + new Specification(new MatrixVectorView(new DenseLocalOffHeapMatrix(1, 1), 0, 0, 1, 1), + DenseLocalOffHeapVector.class, "isDense", + "isRandomAccess", "isDistributed")); + + /** */ + @Test + public void isDenseTest() { + assertAttribute("isDense"); + } + + /** */ + @Test + public void isArrayBasedTest() { + assertAttribute("isArrayBased"); + } + + /** */ + @Test + public void isSequentialAccessTest() { + assertAttribute("isSequentialAccess"); + } + + /** */ + @Test + public void guidTest() { + assertAttribute("guidNotNull"); + } + + /** */ + @Test + public void isRandomAccessTest() { + assertAttribute("isRandomAccess"); + } + + /** */ + @Test + public void isDistributedTest() { + assertAttribute("isDistributed"); + } + + /** */ + private void assertAttribute(String name) { + final AttrCfg attr = attrCfg(name); + + for (Specification spec : specFixture) + spec.verify(attr); + } + + /** */ + private AttrCfg attrCfg(String name) { + for (AttrCfg attr : attrCfgs) + if (attr.name.equals(name)) + return attr; + + throw new IllegalArgumentException("Undefined attribute " + name); + } + + /** See http://en.wikipedia.org/wiki/Specification_pattern */ + private static class Specification { + /** */ + private final Vector v; + /** */ + private final Class<? extends Vector> underlyingType; + /** */ + private final List<String> attrsFromUnderlying; + /** */ + final String desc; + + /** */ + Specification(Vector v, Class<? extends Vector> underlyingType, String... attrsFromUnderlying) { + this.v = v; + this.underlyingType = underlyingType; + this.attrsFromUnderlying = Arrays.asList(attrsFromUnderlying); + final Class<? extends Vector> clazz = v.getClass(); + desc = clazz.getSimpleName() + (clazz.equals(underlyingType) + ? "" : " (underlying type " + underlyingType.getSimpleName() + ")"); + } + + /** */ + Specification(Vector v) { + this(v, v.getClass()); + } + + /** */ + void verify(AttrCfg attr) { + final boolean obtained = attr.obtain.apply(v); + + final Class<? extends Vector> typeToCheck + = attrsFromUnderlying.contains(attr.name) ? underlyingType : v.getClass(); + + final boolean exp = attr.trueInTypes.contains(typeToCheck); + + assertEquals("Unexpected " + attr.name + " value for " + desc, exp, obtained); + } + } + + /** */ + private static class AttrCfg { + /** */ + final String name; + /** */ + final Function<Vector, Boolean> obtain; + /** */ + final List<Class> trueInTypes; + + /** */ + AttrCfg(String name, Function<Vector, Boolean> obtain, Class... trueInTypes) { + this.name = name; + this.obtain = obtain; + this.trueInTypes = Arrays.asList(trueInTypes); + } + } + + /** */ + private static class SparseLocalVectorSequentialAccess extends SparseLocalVector { + /** */ + public SparseLocalVectorSequentialAccess() { + // No-op. + } + + /** */ + SparseLocalVectorSequentialAccess(int size) { + super(size, SEQUENTIAL_ACCESS_MODE); + } + } + + /** */ + private static class SparseLocalVectorRandomAccess extends SparseLocalVector { + /** */ + public SparseLocalVectorRandomAccess() { + // No-op. + } + + /** */ + SparseLocalVectorRandomAccess(int size) { + super(size, RANDOM_ACCESS_MODE); + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/0abf6601/modules/ml/src/test/java/org/apache/ignite/ml/math/impls/vector/VectorFoldMapTest.java ---------------------------------------------------------------------- diff --git a/modules/ml/src/test/java/org/apache/ignite/ml/math/impls/vector/VectorFoldMapTest.java b/modules/ml/src/test/java/org/apache/ignite/ml/math/impls/vector/VectorFoldMapTest.java new file mode 100644 index 0000000..676bb17 --- /dev/null +++ b/modules/ml/src/test/java/org/apache/ignite/ml/math/impls/vector/VectorFoldMapTest.java @@ -0,0 +1,122 @@ +/* + * 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.ignite.ml.math.impls.vector; + +import java.util.Arrays; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Function; +import org.apache.ignite.ml.math.Vector; +import org.apache.ignite.ml.math.functions.Functions; +import org.junit.Test; + +import static java.util.function.DoubleUnaryOperator.identity; +import static org.junit.Assert.assertTrue; + +/** See also: {@link AbstractVectorTest} and {@link VectorToMatrixTest}. */ +public class VectorFoldMapTest { + /** */ + @Test + public void mapVectorTest() { + operationVectorTest((operand1, operand2) -> operand1 + operand2, (Vector v1, Vector v2) -> v1.map(v2, Functions.PLUS)); + } + + /** */ + @Test + public void mapDoubleFunctionTest() { + consumeSampleVectors((v, desc) -> operatorTest(v, desc, + (vec) -> vec.map(Functions.INV), (val) -> 1.0 / val)); + } + + /** */ + @Test + public void mapBiFunctionTest() { + consumeSampleVectors((v, desc) -> operatorTest(v, desc, + (vec) -> vec.map(Functions.PLUS, 1.0), (val) -> 1.0 + val)); + } + + /** */ + @Test + public void foldMapTest() { + toDoubleTest( + ref -> Arrays.stream(ref).map(identity()).sum(), + (v) -> v.foldMap(Functions.PLUS, Functions.IDENTITY, 0.0)); + } + + /** */ + @Test + public void foldMapVectorTest() { + toDoubleTest( + ref -> 2.0 * Arrays.stream(ref).sum(), + (v) -> v.foldMap(v, Functions.PLUS, Functions.PLUS, 0.0)); + + } + + /** */ + private void operatorTest(Vector v, String desc, Function<Vector, Vector> op, Function<Double, Double> refOp) { + final int size = v.size(); + final double[] ref = new double[size]; + + VectorImplementationsTest.ElementsChecker checker = new VectorImplementationsTest.ElementsChecker(v, ref, desc); + + Vector actual = op.apply(v); + + for (int idx = 0; idx < size; idx++) + ref[idx] = refOp.apply(ref[idx]); + + checker.assertCloseEnough(actual, ref); + } + + /** */ + private void toDoubleTest(Function<double[], Double> calcRef, Function<Vector, Double> calcVec) { + consumeSampleVectors((v, desc) -> { + final int size = v.size(); + final double[] ref = new double[size]; + + new VectorImplementationsTest.ElementsChecker(v, ref, desc); // IMPL NOTE this initialises vector and reference array + + final VectorImplementationsTest.Metric metric = new VectorImplementationsTest.Metric(calcRef.apply(ref), calcVec.apply(v)); + + assertTrue("Not close enough at " + desc + + ", " + metric, metric.closeEnough()); + }); + } + + /** */ + private void operationVectorTest(BiFunction<Double, Double, Double> operation, + BiFunction<Vector, Vector, Vector> vecOperation) { + consumeSampleVectors((v, desc) -> { + // TODO find out if more elaborate testing scenario is needed or it's okay as is. + final int size = v.size(); + final double[] ref = new double[size]; + + final VectorImplementationsTest.ElementsChecker checker = new VectorImplementationsTest.ElementsChecker(v, ref, desc); + final Vector operand = v.copy(); + + for (int idx = 0; idx < size; idx++) + ref[idx] = operation.apply(ref[idx], ref[idx]); + + checker.assertCloseEnough(vecOperation.apply(v, operand), ref); + }); + } + + /** */ + private void consumeSampleVectors(BiConsumer<Vector, String> consumer) { + new VectorImplementationsFixtures().consumeSampleVectors(null, consumer); + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/0abf6601/modules/ml/src/test/java/org/apache/ignite/ml/math/impls/vector/VectorImplementationsFixtures.java ---------------------------------------------------------------------- diff --git a/modules/ml/src/test/java/org/apache/ignite/ml/math/impls/vector/VectorImplementationsFixtures.java b/modules/ml/src/test/java/org/apache/ignite/ml/math/impls/vector/VectorImplementationsFixtures.java new file mode 100644 index 0000000..be3bb22 --- /dev/null +++ b/modules/ml/src/test/java/org/apache/ignite/ml/math/impls/vector/VectorImplementationsFixtures.java @@ -0,0 +1,655 @@ +/* + * 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.ignite.ml.math.impls.vector; + +import java.io.IOException; +import java.io.ObjectInput; +import java.io.ObjectOutput; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Supplier; +import org.apache.ignite.ml.math.Matrix; +import org.apache.ignite.ml.math.StorageConstants; +import org.apache.ignite.ml.math.Vector; +import org.apache.ignite.ml.math.impls.matrix.DenseLocalOnHeapMatrix; +import org.apache.ignite.ml.math.impls.storage.vector.FunctionVectorStorage; +import org.jetbrains.annotations.NotNull; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** */ +class VectorImplementationsFixtures { + /** */ + private static final List<Supplier<Iterable<Vector>>> suppliers = Arrays.asList( + (Supplier<Iterable<Vector>>)DenseLocalOnHeapVectorFixture::new, + (Supplier<Iterable<Vector>>)DenseLocalOffHeapVectorFixture::new, + (Supplier<Iterable<Vector>>)SparseLocalVectorFixture::new, + (Supplier<Iterable<Vector>>)RandomVectorFixture::new, + (Supplier<Iterable<Vector>>)ConstantVectorFixture::new, + (Supplier<Iterable<Vector>>)DelegatingVectorFixture::new, + (Supplier<Iterable<Vector>>)FunctionVectorFixture::new, + (Supplier<Iterable<Vector>>)SingleElementVectorFixture::new, + (Supplier<Iterable<Vector>>)PivotedVectorViewFixture::new, + (Supplier<Iterable<Vector>>)SingleElementVectorViewFixture::new, + (Supplier<Iterable<Vector>>)MatrixVectorViewFixture::new, + (Supplier<Iterable<Vector>>)SparseLocalOffHeapVectorFixture::new + ); + + /** */ + void consumeSampleVectors(Consumer<Integer> paramsConsumer, BiConsumer<Vector, String> consumer) { + for (Supplier<Iterable<Vector>> fixtureSupplier : VectorImplementationsFixtures.suppliers) { + final Iterable<Vector> fixture = fixtureSupplier.get(); + + for (Vector v : fixture) { + if (paramsConsumer != null) + paramsConsumer.accept(v.size()); + + consumer.accept(v, fixture.toString()); + } + } + } + + /** */ + void selfTest() { + new VectorSizesExtraIterator<>("VectorSizesExtraIterator test", + (size, shallowCp) -> new DenseLocalOnHeapVector(new double[size], shallowCp), + null, "shallow copy", new Boolean[] {false, true, null}).selfTest(); + + new VectorSizesIterator("VectorSizesIterator test", DenseLocalOffHeapVector::new, null).selfTest(); + } + + /** */ + private static class DenseLocalOnHeapVectorFixture extends VectorSizesExtraFixture<Boolean> { + /** */ + DenseLocalOnHeapVectorFixture() { + super("DenseLocalOnHeapVector", + (size, shallowCp) -> new DenseLocalOnHeapVector(new double[size], shallowCp), + "shallow copy", new Boolean[] {false, true, null}); + } + } + + /** */ + private static class DenseLocalOffHeapVectorFixture extends VectorSizesFixture { + /** */ + DenseLocalOffHeapVectorFixture() { + super("DenseLocalOffHeapVector", DenseLocalOffHeapVector::new); + } + } + + /** */ + private static class SparseLocalVectorFixture extends VectorSizesExtraFixture<Integer> { + /** */ + SparseLocalVectorFixture() { + super("SparseLocalVector", SparseLocalVector::new, "access mode", + new Integer[] {StorageConstants.SEQUENTIAL_ACCESS_MODE, StorageConstants.RANDOM_ACCESS_MODE, null}); + } + } + + /** */ + private static class RandomVectorFixture extends VectorSizesFixture { + /** */ + RandomVectorFixture() { + super("RandomVector", RandomVector::new); + } + } + + /** */ + private static class ConstantVectorFixture extends VectorSizesExtraFixture<Double> { + /** */ + ConstantVectorFixture() { + super("ConstantVector", ConstantVector::new, + "value", new Double[] {-1.0, 0.0, 0.5, 1.0, 2.0, null}); + } + } + + /** */ + private static class FunctionVectorFixture extends VectorSizesExtraFixture<Double> { + /** */ + FunctionVectorFixture() { + super("FunctionVector", + (size, scale) -> new FunctionVectorForTest(new double[size], scale), + "scale", new Double[] {0.5, 1.0, 2.0, null}); + } + } + + /** */ + private static class SingleElementVectorFixture implements Iterable<Vector> { + /** */ + private final Supplier<TwoParamsIterator<Integer, Double>> iter; + + /** */ + private final AtomicReference<String> ctxDescrHolder = new AtomicReference<>("Iterator not started."); + + /** */ + SingleElementVectorFixture() { + iter = () -> new TwoParamsIterator<Integer, Double>("SingleElementVector", + null, ctxDescrHolder::set, + "size", new Integer[] {1, null}, + "value", new Double[] {-1.0, 0.0, 0.5, 1.0, 2.0, null}) { + + /** {@inheritDoc} */ + @Override BiFunction<Integer, Double, Vector> ctor() { + return (size, value) -> new SingleElementVector(size, 0, value); + } + }; + } + + /** {@inheritDoc} */ + @NotNull + @Override public Iterator<Vector> iterator() { + return iter.get();//( + } + + /** {@inheritDoc} */ + @Override public String toString() { + // IMPL NOTE index within bounds is expected to be guaranteed by proper code in this class + return ctxDescrHolder.get(); + } + } + + /** */ + private static class PivotedVectorViewFixture extends VectorSizesFixture { + /** */ + PivotedVectorViewFixture() { + super("PivotedVectorView", PivotedVectorViewFixture::pivotedVectorView); + } + + /** */ + private static PivotedVectorView pivotedVectorView(int size) { + final DenseLocalOnHeapVector vec = new DenseLocalOnHeapVector(size); + + final int[] pivot = new int[size]; + + for (int idx = 0; idx < size; idx++) + pivot[idx] = size - 1 - idx; + + PivotedVectorView tmp = new PivotedVectorView(vec, pivot); + + final int[] unpivot = new int[size]; + + for (int idx = 0; idx < size; idx++) + unpivot[idx] = tmp.unpivot(idx); + + final int[] idxRecovery = new int[size]; + + for (int idx = 0; idx < size; idx++) + idxRecovery[idx] = idx; + + return new PivotedVectorView(new PivotedVectorView(tmp, unpivot), idxRecovery); + } + } + + /** */ + private static class SingleElementVectorViewFixture implements Iterable<Vector> { + /** */ + private final Supplier<TwoParamsIterator<Integer, Double>> iter; + + /** */ + private final AtomicReference<String> ctxDescrHolder = new AtomicReference<>("Iterator not started."); + + /** */ + SingleElementVectorViewFixture() { + iter = () -> new TwoParamsIterator<Integer, Double>("SingleElementVectorView", + null, ctxDescrHolder::set, + "size", new Integer[] {1, null}, + "value", new Double[] {-1.0, 0.0, 0.5, 1.0, 2.0, null}) { + + /** {@inheritDoc} */ + @Override BiFunction<Integer, Double, Vector> ctor() { + return (size, value) -> new SingleElementVectorView(new SingleElementVector(size, 0, value), 0); + } + }; + } + + /** {@inheritDoc} */ + @NotNull + @Override public Iterator<Vector> iterator() { + return iter.get(); + } + + /** {@inheritDoc} */ + @Override public String toString() { + // IMPL NOTE index within bounds is expected to be guaranteed by proper code in this class + return ctxDescrHolder.get(); + } + } + + /** */ + private static class MatrixVectorViewFixture extends VectorSizesExtraFixture<Integer> { + /** */ + MatrixVectorViewFixture() { + super("MatrixVectorView", + MatrixVectorViewFixture::newView, + "stride kind", new Integer[] {0, 1, 2, null}); + } + + /** */ + private static Vector newView(int size, int strideKind) { + final Matrix parent = new DenseLocalOnHeapMatrix(size, size); + + return new MatrixVectorView(parent, 0, 0, strideKind != 1 ? 1 : 0, strideKind != 0 ? 1 : 0); + } + } + + /** */ + private static class VectorSizesExtraFixture<T> implements Iterable<Vector> { + /** */ + private final Supplier<VectorSizesExtraIterator<T>> iter; + + /** */ + private final AtomicReference<String> ctxDescrHolder = new AtomicReference<>("Iterator not started."); + + /** */ + VectorSizesExtraFixture(String vectorKind, BiFunction<Integer, T, Vector> ctor, String extraParamName, + T[] extras) { + iter = () -> new VectorSizesExtraIterator<>(vectorKind, ctor, ctxDescrHolder::set, extraParamName, extras); + } + + /** {@inheritDoc} */ + @NotNull + @Override public Iterator<Vector> iterator() { + return iter.get(); + } + + /** {@inheritDoc} */ + @Override public String toString() { + // IMPL NOTE index within bounds is expected to be guaranteed by proper code in this class + return ctxDescrHolder.get(); + } + } + + /** */ + private static abstract class VectorSizesFixture implements Iterable<Vector> { + /** */ + private final Supplier<VectorSizesIterator> iter; + + /** */ + private final AtomicReference<String> ctxDescrHolder = new AtomicReference<>("Iterator not started."); + + /** */ + VectorSizesFixture(String vectorKind, Function<Integer, Vector> ctor) { + iter = () -> new VectorSizesIterator(vectorKind, ctor, ctxDescrHolder::set); + } + + /** {@inheritDoc} */ + @NotNull + @Override public Iterator<Vector> iterator() { + return iter.get(); + } + + /** {@inheritDoc} */ + @Override public String toString() { + // IMPL NOTE index within bounds is expected to be guaranteed by proper code in this class + return ctxDescrHolder.get(); + } + } + + /** */ + private static class VectorSizesExtraIterator<T> extends VectorSizesIterator { + /** */ + private final T[] extras; + /** */ + private int extraIdx = 0; + /** */ + private final BiFunction<Integer, T, Vector> ctor; + /** */ + private final String extraParamName; + + /** + * @param vectorKind Descriptive name to use for context logging. + * @param ctor Constructor for objects to iterate over. + * @param ctxDescrConsumer Context logging consumer. + * @param extraParamName Name of extra parameter to iterate over. + * @param extras Array of extra parameter values to iterate over. + */ + VectorSizesExtraIterator(String vectorKind, BiFunction<Integer, T, Vector> ctor, + Consumer<String> ctxDescrConsumer, String extraParamName, T[] extras) { + super(vectorKind, null, ctxDescrConsumer); + + this.ctor = ctor; + this.extraParamName = extraParamName; + this.extras = extras; + } + + /** {@inheritDoc} */ + @Override public boolean hasNext() { + return super.hasNext() && hasNextExtra(extraIdx); + } + + /** {@inheritDoc} */ + @Override void nextIdx() { + assert extras[extraIdx] != null + : "Index(es) out of bound at " + VectorSizesExtraIterator.this; + + if (hasNextExtra(extraIdx + 1)) { + extraIdx++; + + return; + } + + extraIdx = 0; + + super.nextIdx(); + } + + /** {@inheritDoc} */ + @Override public String toString() { + // IMPL NOTE index within bounds is expected to be guaranteed by proper code in this class + return "{" + super.toString() + + ", " + extraParamName + "=" + extras[extraIdx] + + '}'; + } + + /** {@inheritDoc} */ + @Override BiFunction<Integer, Integer, Vector> ctor() { + return (size, delta) -> ctor.apply(size + delta, extras[extraIdx]); + } + + /** */ + void selfTest() { + final Set<Integer> extraIdxs = new HashSet<>(); + + int cnt = 0; + + while (hasNext()) { + assertNotNull("Expect not null vector at " + this, next()); + + if (extras[extraIdx] != null) + extraIdxs.add(extraIdx); + + cnt++; + } + + assertEquals("Extra param tested", extraIdxs.size(), extras.length - 1); + + assertEquals("Combinations tested mismatch.", + 7 * 3 * (extras.length - 1), cnt); + } + + /** */ + private boolean hasNextExtra(int idx) { + return extras[idx] != null; + } + } + + /** */ + private static class VectorSizesIterator extends TwoParamsIterator<Integer, Integer> { + /** */ + private final Function<Integer, Vector> ctor; + + /** */ + VectorSizesIterator(String vectorKind, Function<Integer, Vector> ctor, Consumer<String> ctxDescrConsumer) { + super(vectorKind, null, ctxDescrConsumer, + "size", new Integer[] {2, 4, 8, 16, 32, 64, 128, null}, + "size delta", new Integer[] {-1, 0, 1, null}); + + this.ctor = ctor; + } + + /** {@inheritDoc} */ + @Override BiFunction<Integer, Integer, Vector> ctor() { + return (size, delta) -> ctor.apply(size + delta); + } + } + + /** */ + private static class TwoParamsIterator<T, U> implements Iterator<Vector> { + /** */ + private final T params1[]; + + /** */ + private final U params2[]; + + /** */ + private final String vectorKind; + + /** */ + private final String param1Name; + + /** */ + private final String param2Name; + + /** */ + private final BiFunction<T, U, Vector> ctor; + + /** */ + private final Consumer<String> ctxDescrConsumer; + + /** */ + private int param1Idx = 0; + + /** */ + private int param2Idx = 0; + + /** */ + TwoParamsIterator(String vectorKind, BiFunction<T, U, Vector> ctor, + Consumer<String> ctxDescrConsumer, String param1Name, T[] params1, String param2Name, U[] params2) { + this.param1Name = param1Name; + this.params1 = params1; + + this.param2Name = param2Name; + this.params2 = params2; + + this.vectorKind = vectorKind; + + this.ctor = ctor; + + this.ctxDescrConsumer = ctxDescrConsumer; + } + + /** {@inheritDoc} */ + @Override public boolean hasNext() { + return hasNextParam1(param1Idx) && hasNextParam2(param2Idx); + } + + /** {@inheritDoc} */ + @Override public Vector next() { + if (!hasNext()) + throw new NoSuchElementException(TwoParamsIterator.this.toString()); + + if (ctxDescrConsumer != null) + ctxDescrConsumer.accept(toString()); + + Vector res = ctor().apply(params1[param1Idx], params2[param2Idx]); + + nextIdx(); + + return res; + } + + /** */ + void selfTest() { + final Set<Integer> sizeIdxs = new HashSet<>(), deltaIdxs = new HashSet<>(); + + int cnt = 0; + + while (hasNext()) { + assertNotNull("Expect not null vector at " + this, next()); + + if (params1[param1Idx] != null) + sizeIdxs.add(param1Idx); + + if (params2[param2Idx] != null) + deltaIdxs.add(param2Idx); + + cnt++; + } + + assertEquals("Sizes tested mismatch.", sizeIdxs.size(), params1.length - 1); + + assertEquals("Deltas tested", deltaIdxs.size(), params2.length - 1); + + assertEquals("Combinations tested mismatch.", + (params1.length - 1) * (params2.length - 1), cnt); + } + + /** IMPL NOTE override in subclasses if needed */ + void nextIdx() { + assert params1[param1Idx] != null && params2[param2Idx] != null + : "Index(es) out of bound at " + TwoParamsIterator.this; + + if (hasNextParam2(param2Idx + 1)) { + param2Idx++; + + return; + } + + param2Idx = 0; + + param1Idx++; + } + + /** {@inheritDoc} */ + @Override public String toString() { + // IMPL NOTE index within bounds is expected to be guaranteed by proper code in this class + return vectorKind + "{" + param1Name + "=" + params1[param1Idx] + + ", " + param2Name + "=" + params2[param2Idx] + + '}'; + } + + /** IMPL NOTE override in subclasses if needed */ + BiFunction<T, U, Vector> ctor() { + return ctor; + } + + /** */ + private boolean hasNextParam1(int idx) { + return params1[idx] != null; + } + + /** */ + private boolean hasNextParam2(int idx) { + return params2[idx] != null; + } + } + + /** Delegating vector with dense local onheap vector */ + private static class DelegatingVectorFixture implements Iterable<Vector> { + + /** */ + private final Supplier<VectorSizesExtraIterator<Boolean>> iter; + + /** */ + private final AtomicReference<String> ctxDescrHolder = new AtomicReference<>("Iterator not started."); + + /** */ + DelegatingVectorFixture() { + iter = () -> new VectorSizesExtraIterator<>("DelegatingVector with DenseLocalOnHeapVector", + (size, shallowCp) -> new DelegatingVector(new DenseLocalOnHeapVector(new double[size], shallowCp)), + ctxDescrHolder::set, "shallow copy", new Boolean[] {false, true, null}); + } + + /** {@inheritDoc} */ + @NotNull + @Override public Iterator<Vector> iterator() { + return iter.get(); + } + + /** {@inheritDoc} */ + @Override public String toString() { + // IMPL NOTE index within bounds is expected to be guaranteed by proper code in this class + return ctxDescrHolder.get(); + } + } + + /** Subclass tweaked for serialization */ + private static class FunctionVectorForTest extends FunctionVector { + /** */ + double[] arr; + + /** */ + double scale; + + /** */ + public FunctionVectorForTest() { + // No-op. + } + + /** */ + FunctionVectorForTest(double[] arr, double scale) { + super(arr.length, idx -> arr[idx] * scale, (idx, value) -> arr[idx] = value / scale); + + this.arr = arr; + + this.scale = scale; + } + + /** {@inheritDoc} */ + @Override public void writeExternal(ObjectOutput out) throws IOException { + super.writeExternal(out); + + out.writeObject(arr); + + out.writeDouble(scale); + } + + /** {@inheritDoc} */ + @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { + super.readExternal(in); + + arr = (double[])in.readObject(); + + scale = in.readDouble(); + + setStorage(new FunctionVectorStorage(arr.length, idx -> arr[idx] * scale, (idx, value) -> arr[idx] = value / scale)); + } + + /** {@inheritDoc} */ + @Override public int hashCode() { + int res = 1; + + res = res * 37 + Double.hashCode(scale); + res = res * 37 + Integer.hashCode(getStorage().size()); + + return res; + } + + /** {@inheritDoc} */ + @Override public boolean equals(Object o) { + if (this == o) + return true; + + if (o == null || getClass() != o.getClass()) + return false; + + FunctionVectorForTest that = (FunctionVectorForTest)o; + + return new Double(scale).equals(that.scale) + && (arr != null ? Arrays.equals(arr, that.arr) : that.arr == null); + } + } + + /** */ + private static class SparseLocalOffHeapVectorFixture extends VectorSizesFixture { + + /** */ + SparseLocalOffHeapVectorFixture() { + super("SparseLocalOffHeapVector", SparseLocalOffHeapVector::new); + } + } +} http://git-wip-us.apache.org/repos/asf/ignite/blob/0abf6601/modules/ml/src/test/java/org/apache/ignite/ml/math/impls/vector/VectorImplementationsTest.java ---------------------------------------------------------------------- diff --git a/modules/ml/src/test/java/org/apache/ignite/ml/math/impls/vector/VectorImplementationsTest.java b/modules/ml/src/test/java/org/apache/ignite/ml/math/impls/vector/VectorImplementationsTest.java new file mode 100644 index 0000000..48dcc36 --- /dev/null +++ b/modules/ml/src/test/java/org/apache/ignite/ml/math/impls/vector/VectorImplementationsTest.java @@ -0,0 +1,861 @@ +/* + * 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.ignite.ml.math.impls.vector; + +import java.util.Arrays; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; +import org.apache.ignite.IgniteException; +import org.apache.ignite.ml.math.ExternalizeTest; +import org.apache.ignite.ml.math.Vector; +import org.apache.ignite.ml.math.exceptions.CardinalityException; +import org.apache.ignite.ml.math.exceptions.UnsupportedOperationException; +import org.junit.Assert; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +/** See also: {@link AbstractVectorTest} and {@link VectorToMatrixTest}. */ +public class VectorImplementationsTest { // todo split this to smaller cohesive test classes + /** */ + @Test + public void vectorImplementationsFixturesTest() { + new VectorImplementationsFixtures().selfTest(); + } + + /** */ + @Test + public void setGetTest() { + consumeSampleVectors((v, desc) -> mutateAtIdxTest(v, desc, (vec, idx, val) -> { + vec.set(idx, val); + + return val; + })); + } + + /** */ + @Test + public void setXTest() { + consumeSampleVectors((v, desc) -> mutateAtIdxTest(v, desc, (vec, idx, val) -> { + vec.setX(idx, val); + + return val; + })); + } + + /** */ + @Test + public void incrementTest() { + consumeSampleVectors((v, desc) -> mutateAtIdxTest(v, desc, (vec, idx, val) -> { + double old = vec.get(idx); + + vec.increment(idx, val); + + return old + val; + })); + } + + /** */ + @Test + public void incrementXTest() { + consumeSampleVectors((v, desc) -> mutateAtIdxTest(v, desc, (vec, idx, val) -> { + double old = vec.getX(idx); + + vec.incrementX(idx, val); + + return old + val; + })); + } + + /** */ + @Test + public void operateXOutOfBoundsTest() { + consumeSampleVectors((v, desc) -> { + if (v instanceof DenseLocalOffHeapVector || v instanceof SparseLocalVector || v instanceof SparseLocalOffHeapVector) + return; // todo find out if it's OK to skip by instances here + + boolean expECaught = false; + + try { + v.getX(-1); + } + catch (ArrayIndexOutOfBoundsException | IgniteException e) { + expECaught = true; + } + + if (!getXOutOfBoundsOK(v)) + assertTrue("Expect exception at negative index getX in " + desc, expECaught); + + expECaught = false; + + try { + v.setX(-1, 0); + } + catch (ArrayIndexOutOfBoundsException | IgniteException e) { + expECaught = true; + } + + assertTrue("Expect exception at negative index setX in " + desc, expECaught); + + expECaught = false; + + try { + v.incrementX(-1, 1); + } + catch (ArrayIndexOutOfBoundsException | IgniteException e) { + expECaught = true; + } + + assertTrue("Expect exception at negative index incrementX in " + desc, expECaught); + + expECaught = false; + + try { + v.getX(v.size()); + } + catch (ArrayIndexOutOfBoundsException | IgniteException e) { + expECaught = true; + } + + if (!getXOutOfBoundsOK(v)) + assertTrue("Expect exception at too large index getX in " + desc, expECaught); + + expECaught = false; + + try { + v.setX(v.size(), 1); + } + catch (ArrayIndexOutOfBoundsException | IgniteException e) { + expECaught = true; + } + + assertTrue("Expect exception at too large index setX in " + desc, expECaught); + + expECaught = false; + + try { + v.incrementX(v.size(), 1); + } + catch (ArrayIndexOutOfBoundsException | IgniteException e) { + expECaught = true; + } + + assertTrue("Expect exception at too large index incrementX in " + desc, expECaught); + }); + } + + /** */ + @Test + public void sizeTest() { + final AtomicReference<Integer> expSize = new AtomicReference<>(0); + + consumeSampleVectors( + expSize::set, + (v, desc) -> Assert.assertEquals("Expected size for " + desc, + (int)expSize.get(), v.size()) + ); + } + + /** */ + @Test + public void getElementTest() { + consumeSampleVectors((v, desc) -> new ElementsChecker(v, desc).assertCloseEnough(v)); + } + + /** */ + @Test + public void copyTest() { + consumeSampleVectors((v, desc) -> new ElementsChecker(v, desc).assertCloseEnough(v.copy())); + } + + /** */ + @Test + public void divideTest() { + operationTest((val, operand) -> val / operand, Vector::divide); + } + + /** */ + @Test + public void likeTest() { + for (int card : new int[] {1, 2, 4, 8, 16, 32, 64, 128}) + consumeSampleVectors((v, desc) -> { + Class<? extends Vector> expType = expLikeType(v); + + if (expType == null) { + try { + v.like(card); + } + catch (UnsupportedOperationException uoe) { + return; + } + + fail("Expected exception wasn't caught for " + desc); + + return; + } + + Vector vLike = v.like(card); + + assertNotNull("Expect non-null like vector for " + expType.getSimpleName() + " in " + desc, vLike); + assertEquals("Expect size equal to cardinality at " + desc, card, vLike.size()); + + Class<? extends Vector> actualType = vLike.getClass(); + + assertTrue("Actual vector type " + actualType.getSimpleName() + + " should be assignable from expected type " + expType.getSimpleName() + " in " + desc, + actualType.isAssignableFrom(expType)); + }); + } + + /** */ + @Test + public void minusTest() { + operationVectorTest((operand1, operand2) -> operand1 - operand2, Vector::minus); + } + + /** */ + @Test + public void plusVectorTest() { + operationVectorTest((operand1, operand2) -> operand1 + operand2, Vector::plus); + } + + /** */ + @Test + public void plusDoubleTest() { + operationTest((val, operand) -> val + operand, Vector::plus); + } + + /** */ + @Test + public void timesVectorTest() { + operationVectorTest((operand1, operand2) -> operand1 * operand2, Vector::times); + } + + /** */ + @Test + public void timesDoubleTest() { + operationTest((val, operand) -> val * operand, Vector::times); + } + + /** */ + @Test + public void viewPartTest() { + consumeSampleVectors((v, desc) -> { + final int size = v.size(); + final double[] ref = new double[size]; + final int delta = size > 32 ? 3 : 1; // IMPL NOTE this is for faster test execution + + final ElementsChecker checker = new ElementsChecker(v, ref, desc); + + for (int off = 0; off < size; off += delta) + for (int len = 1; len < size - off; len += delta) + checker.assertCloseEnough(v.viewPart(off, len), Arrays.copyOfRange(ref, off, off + len)); + }); + } + + /** */ + @Test + public void sumTest() { + toDoubleTest( + ref -> Arrays.stream(ref).sum(), + Vector::sum); + } + + /** */ + @Test + public void minValueTest() { + toDoubleTest( + ref -> Arrays.stream(ref).min().getAsDouble(), + Vector::minValue); + } + + /** */ + @Test + public void maxValueTest() { + toDoubleTest( + ref -> Arrays.stream(ref).max().getAsDouble(), + Vector::maxValue); + } + + /** */ + @Test + public void sortTest() { + consumeSampleVectors((v, desc) -> { + if (readOnly(v) || !v.isArrayBased()) { + boolean expECaught = false; + + try { + v.sort(); + } + catch (UnsupportedOperationException uoe) { + expECaught = true; + } + + assertTrue("Expected exception was not caught for sort in " + desc, expECaught); + + return; + } + + final int size = v.size(); + final double[] ref = new double[size]; + + new ElementsChecker(v, ref, desc).assertCloseEnough(v.sort(), Arrays.stream(ref).sorted().toArray()); + }); + } + + /** */ + @Test + public void metaAttributesTest() { + consumeSampleVectors((v, desc) -> { + assertNotNull("Null meta storage in " + desc, v.getMetaStorage()); + + final String key = "test key"; + final String val = "test value"; + final String details = "key [" + key + "] for " + desc; + + v.setAttribute(key, val); + assertTrue("Expect to have meta attribute for " + details, v.hasAttribute(key)); + assertEquals("Unexpected meta attribute value for " + details, val, v.getAttribute(key)); + + v.removeAttribute(key); + assertFalse("Expect not to have meta attribute for " + details, v.hasAttribute(key)); + assertNull("Unexpected meta attribute value for " + details, v.getAttribute(key)); + }); + } + + /** */ + @Test + public void assignDoubleTest() { + consumeSampleVectors((v, desc) -> { + if (readOnly(v)) + return; + + for (double val : new double[] {0, -1, 0, 1}) { + v.assign(val); + + for (int idx = 0; idx < v.size(); idx++) { + final Metric metric = new Metric(val, v.get(idx)); + + assertTrue("Not close enough at index " + idx + ", val " + val + ", " + metric + + ", " + desc, metric.closeEnough()); + } + } + }); + } + + /** */ + @Test + public void assignDoubleArrTest() { + consumeSampleVectors((v, desc) -> { + if (readOnly(v)) + return; + + final int size = v.size(); + final double[] ref = new double[size]; + + final ElementsChecker checker = new ElementsChecker(v, ref, desc); + + for (int idx = 0; idx < size; idx++) + ref[idx] = -ref[idx]; + + v.assign(ref); + + checker.assertCloseEnough(v, ref); + + assignDoubleArrWrongCardinality(v, desc); + }); + } + + /** */ + @Test + public void assignVectorTest() { + consumeSampleVectors((v, desc) -> { + if (readOnly(v)) + return; + + final int size = v.size(); + final double[] ref = new double[size]; + + final ElementsChecker checker = new ElementsChecker(v, ref, desc); + + for (int idx = 0; idx < size; idx++) + ref[idx] = -ref[idx]; + + v.assign(new DenseLocalOnHeapVector(ref)); + + checker.assertCloseEnough(v, ref); + + assignVectorWrongCardinality(v, desc); + }); + } + + /** */ + @Test + public void assignFunctionTest() { + consumeSampleVectors((v, desc) -> { + if (readOnly(v)) + return; + + final int size = v.size(); + final double[] ref = new double[size]; + + final ElementsChecker checker = new ElementsChecker(v, ref, desc); + + for (int idx = 0; idx < size; idx++) + ref[idx] = -ref[idx]; + + v.assign((idx) -> ref[idx]); + + checker.assertCloseEnough(v, ref); + }); + } + + /** */ + @Test + public void minElementTest() { + consumeSampleVectors((v, desc) -> { + final ElementsChecker checker = new ElementsChecker(v, desc); + + final Vector.Element minE = v.minElement(); + + final int minEIdx = minE.index(); + + assertTrue("Unexpected index from minElement " + minEIdx + ", " + desc, + minEIdx >= 0 && minEIdx < v.size()); + + final Metric metric = new Metric(minE.get(), v.minValue()); + + assertTrue("Not close enough minElement at index " + minEIdx + ", " + metric + + ", " + desc, metric.closeEnough()); + + checker.assertNewMinElement(v); + }); + } + + /** */ + @Test + public void maxElementTest() { + consumeSampleVectors((v, desc) -> { + final ElementsChecker checker = new ElementsChecker(v, desc); + + final Vector.Element maxE = v.maxElement(); + + final int minEIdx = maxE.index(); + + assertTrue("Unexpected index from minElement " + minEIdx + ", " + desc, + minEIdx >= 0 && minEIdx < v.size()); + + final Metric metric = new Metric(maxE.get(), v.maxValue()); + + assertTrue("Not close enough maxElement at index " + minEIdx + ", " + metric + + ", " + desc, metric.closeEnough()); + + checker.assertNewMaxElement(v); + }); + } + + /** */ + @Test + public void externalizeTest() { + (new ExternalizeTest<Vector>() { + /** {@inheritDoc} */ + @Override public void externalizeTest() { + consumeSampleVectors((v, desc) -> { + if (v instanceof SparseLocalOffHeapVector) + return; //TODO: wait till SparseLocalOffHeapVector externalization support. + + externalizeTest(v); + }); + } + }).externalizeTest(); + } + + /** */ + @Test + public void hashCodeTest() { + consumeSampleVectors((v, desc) -> assertTrue("Zero hash code for " + desc, v.hashCode() != 0)); + } + + /** */ + private boolean getXOutOfBoundsOK(Vector v) { + // todo find out if this is indeed OK + return v instanceof RandomVector || v instanceof ConstantVector + || v instanceof SingleElementVector || v instanceof SingleElementVectorView; + } + + /** */ + private void mutateAtIdxTest(Vector v, String desc, MutateAtIdx operation) { + if (readOnly(v)) { + if (v.size() < 1) + return; + + boolean expECaught = false; + + try { + operation.apply(v, 0, 1); + } + catch (UnsupportedOperationException uoe) { + expECaught = true; + } + + assertTrue("Expect exception at attempt to mutate element in " + desc, expECaught); + + return; + } + + for (double val : new double[] {0, -1, 0, 1}) + for (int idx = 0; idx < v.size(); idx++) { + double exp = operation.apply(v, idx, val); + + final Metric metric = new Metric(exp, v.get(idx)); + + assertTrue("Not close enough at index " + idx + ", val " + val + ", " + metric + + ", " + desc, metric.closeEnough()); + } + } + + /** */ + private Class<? extends Vector> expLikeType(Vector v) { + Class<? extends Vector> clazz = v.getClass(); + + if (clazz.isAssignableFrom(PivotedVectorView.class) || clazz.isAssignableFrom(SingleElementVectorView.class)) + return null; + + if (clazz.isAssignableFrom(MatrixVectorView.class) || clazz.isAssignableFrom(DelegatingVector.class)) + return DenseLocalOnHeapVector.class; // IMPL NOTE per fixture + + return clazz; + } + + /** */ + private void toDoubleTest(Function<double[], Double> calcRef, Function<Vector, Double> calcVec) { + consumeSampleVectors((v, desc) -> { + final int size = v.size(); + final double[] ref = new double[size]; + + new ElementsChecker(v, ref, desc); // IMPL NOTE this initialises vector and reference array + + final Metric metric = new Metric(calcRef.apply(ref), calcVec.apply(v)); + + assertTrue("Not close enough at " + desc + + ", " + metric, metric.closeEnough()); + }); + } + + /** */ + private void operationVectorTest(BiFunction<Double, Double, Double> operation, + BiFunction<Vector, Vector, Vector> vecOperation) { + consumeSampleVectors((v, desc) -> { + // TODO find out if more elaborate testing scenario is needed or it's okay as is. + final int size = v.size(); + final double[] ref = new double[size]; + + final ElementsChecker checker = new ElementsChecker(v, ref, desc); + final Vector operand = v.copy(); + + for (int idx = 0; idx < size; idx++) + ref[idx] = operation.apply(ref[idx], ref[idx]); + + checker.assertCloseEnough(vecOperation.apply(v, operand), ref); + + assertWrongCardinality(v, desc, vecOperation); + }); + } + + /** */ + private void assignDoubleArrWrongCardinality(Vector v, String desc) { + boolean expECaught = false; + + try { + v.assign(new double[v.size() + 1]); + } + catch (CardinalityException ce) { + expECaught = true; + } + + assertTrue("Expect exception at too large size in " + desc, expECaught); + + if (v.size() < 2) + return; + + expECaught = false; + + try { + v.assign(new double[v.size() - 1]); + } + catch (CardinalityException ce) { + expECaught = true; + } + + assertTrue("Expect exception at too small size in " + desc, expECaught); + } + + /** */ + private void assignVectorWrongCardinality(Vector v, String desc) { + boolean expECaught = false; + + try { + v.assign(new DenseLocalOnHeapVector(v.size() + 1)); + } + catch (CardinalityException ce) { + expECaught = true; + } + + assertTrue("Expect exception at too large size in " + desc, expECaught); + + if (v.size() < 2) + return; + + expECaught = false; + + try { + v.assign(new DenseLocalOnHeapVector(v.size() - 1)); + } + catch (CardinalityException ce) { + expECaught = true; + } + + assertTrue("Expect exception at too small size in " + desc, expECaught); + } + + /** */ + private void assertWrongCardinality( + Vector v, String desc, BiFunction<Vector, Vector, Vector> vecOperation) { + boolean expECaught = false; + + try { + vecOperation.apply(v, new DenseLocalOnHeapVector(v.size() + 1)); + } + catch (CardinalityException ce) { + expECaught = true; + } + + assertTrue("Expect exception at too large size in " + desc, expECaught); + + if (v.size() < 2) + return; + + expECaught = false; + + try { + vecOperation.apply(v, new DenseLocalOnHeapVector(v.size() - 1)); + } + catch (CardinalityException ce) { + expECaught = true; + } + + assertTrue("Expect exception at too small size in " + desc, expECaught); + } + + /** */ + private void operationTest(BiFunction<Double, Double, Double> operation, + BiFunction<Vector, Double, Vector> vecOperation) { + for (double val : new double[] {0, 0.1, 1, 2, 10}) + consumeSampleVectors((v, desc) -> { + final int size = v.size(); + final double[] ref = new double[size]; + + final ElementsChecker checker = new ElementsChecker(v, ref, "val " + val + ", " + desc); + + for (int idx = 0; idx < size; idx++) + ref[idx] = operation.apply(ref[idx], val); + + checker.assertCloseEnough(vecOperation.apply(v, val), ref); + }); + } + + /** */ + private void consumeSampleVectors(BiConsumer<Vector, String> consumer) { + consumeSampleVectors(null, consumer); + } + + /** */ + private void consumeSampleVectors(Consumer<Integer> paramsConsumer, BiConsumer<Vector, String> consumer) { + new VectorImplementationsFixtures().consumeSampleVectors(paramsConsumer, consumer); + } + + /** */ + private static boolean readOnly(Vector v) { + return v instanceof RandomVector || v instanceof ConstantVector; + } + + /** */ + private interface MutateAtIdx { + /** */ + double apply(Vector v, int idx, double val); + } + + /** */ + static class ElementsChecker { + /** */ + private final String fixtureDesc; + + /** */ + private final double[] refReadOnly; + + /** */ + private final boolean nonNegative; + + /** */ + ElementsChecker(Vector v, double[] ref, String fixtureDesc, boolean nonNegative) { + this.fixtureDesc = fixtureDesc; + + this.nonNegative = nonNegative; + + refReadOnly = readOnly(v) && ref == null ? new double[v.size()] : null; + + init(v, ref); + } + + /** */ + ElementsChecker(Vector v, double[] ref, String fixtureDesc) { + this(v, ref, fixtureDesc, false); + } + + /** */ + ElementsChecker(Vector v, String fixtureDesc) { + this(v, null, fixtureDesc); + } + + /** */ + void assertCloseEnough(Vector obtained, double[] exp) { + final int size = obtained.size(); + + for (int i = 0; i < size; i++) { + final Vector.Element e = obtained.getElement(i); + + if (refReadOnly != null && exp == null) + exp = refReadOnly; + + final Metric metric = new Metric(exp == null ? generated(i) : exp[i], e.get()); + + assertEquals("Unexpected vector index at " + fixtureDesc, i, e.index()); + assertTrue("Not close enough at index " + i + ", size " + size + ", " + metric + + ", " + fixtureDesc, metric.closeEnough()); + } + } + + /** */ + void assertCloseEnough(Vector obtained) { + assertCloseEnough(obtained, null); + } + + /** */ + void assertNewMinElement(Vector v) { + if (readOnly(v)) + return; + + int exp = v.size() / 2; + + v.set(exp, -(v.size() * 2 + 1)); + + assertEquals("Unexpected minElement index at " + fixtureDesc, exp, v.minElement().index()); + } + + /** */ + void assertNewMaxElement(Vector v) { + if (readOnly(v)) + return; + + int exp = v.size() / 2; + + v.set(exp, v.size() * 2 + 1); + + assertEquals("Unexpected minElement index at " + fixtureDesc, exp, v.maxElement().index()); + } + + /** */ + private void init(Vector v, double[] ref) { + if (readOnly(v)) { + initReadonly(v, ref); + + return; + } + + for (Vector.Element e : v.all()) { + int idx = e.index(); + + // IMPL NOTE introduce negative values because their absence + // blocked catching an ugly bug in AbstractVector#kNorm + int val = generated(idx); + + e.set(val); + + if (ref != null) + ref[idx] = val; + } + } + + /** */ + private void initReadonly(Vector v, double[] ref) { + if (refReadOnly != null) + for (Vector.Element e : v.all()) + refReadOnly[e.index()] = e.get(); + + if (ref != null) + for (Vector.Element e : v.all()) + ref[e.index()] = e.get(); + } + + /** */ + private int generated(int idx) { + return nonNegative || (idx & 1) == 0 ? idx : -idx; + } + } + + /** */ + static class Metric { // todo consider if softer tolerance (like say 0.1 or 0.01) would make sense here + /** */ + private final double exp; + + /** */ + private final double obtained; + + /** **/ + Metric(double exp, double obtained) { + this.exp = exp; + this.obtained = obtained; + } + + /** */ + boolean closeEnough() { + return new Double(exp).equals(obtained) || closeEnoughToZero(); + } + + /** {@inheritDoc} */ + @Override public String toString() { + return "Metric{" + "expected=" + exp + + ", obtained=" + obtained + + '}'; + } + + /** */ + private boolean closeEnoughToZero() { + return (new Double(exp).equals(0.0) && new Double(obtained).equals(-0.0)) + || (new Double(exp).equals(-0.0) && new Double(obtained).equals(0.0)); + } + } +}