Index: CHANGES.txt =================================================================== --- CHANGES.txt (revision 1585779) +++ CHANGES.txt (working copy) @@ -3,6 +3,7 @@ Release 0.7.0 (unreleased changes) NEW FEATURES + HAMA-863: Implement SparseVector (Yexi Jiang) BUG FIXES Index: commons/src/main/java/org/apache/hama/commons/math/SparseDoubleVector.java =================================================================== --- commons/src/main/java/org/apache/hama/commons/math/SparseDoubleVector.java (revision 0) +++ commons/src/main/java/org/apache/hama/commons/math/SparseDoubleVector.java (working copy) @@ -0,0 +1,817 @@ +/** + * 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.hama.commons.math; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import com.google.common.base.Preconditions; +import com.google.common.collect.AbstractIterator; + +/** + * The implementation of SparseVector. + * + */ +public class SparseDoubleVector implements DoubleVector { + + private int dimension; + private double defaultValue; // 0 by default + private Map elements; // the non-default value + + /** + * Initialize a sparse vector with given dimension with default value 0. + * + * @param dimension + */ + public SparseDoubleVector(int dimension) { + this(dimension, 0.0); + } + + /** + * Initialize a sparse vector with given dimension and given default value 0. + * + * @param dimension + * @param defaultValue + */ + public SparseDoubleVector(int dimension, double defaultValue) { + this.elements = new HashMap(); + this.defaultValue = defaultValue; + this.dimension = dimension; + } + + /** + * Get the value of a given index. + */ + @Override + public double get(int index) { + Preconditions.checkArgument(index < this.dimension, + "Index out of max allowd dimension of sparse vector."); + Double val = this.elements.get(index); + if (val == null) { + val = this.defaultValue; + } + return val; + } + + /** + * Get the dimension of the vector. + */ + @Override + public int getLength() { + return this.getDimension(); + } + + /** + * Get the dimension of the vector. + */ + @Override + public int getDimension() { + return this.dimension; + } + + /** + * Set the value of a given index. + */ + @Override + public void set(int index, double value) { + Preconditions.checkArgument(index < this.dimension, String.format( + "Index %d out of max allowd dimension %d of sparse vector.", index, + this.dimension)); + if (value == this.defaultValue) { + this.elements.remove(index); + } else { + this.elements.put(index, value); + } + } + + /** + * Apply a function to the copy of current vector and return the result. + */ + @Override + public DoubleVector applyToElements(DoubleFunction func) { + SparseDoubleVector newVec = new SparseDoubleVector(this.dimension, + func.apply(this.defaultValue)); + // apply function to all non-empty entries + for (Map.Entry entry : this.elements.entrySet()) { + newVec.elements.put(entry.getKey(), func.apply(entry.getValue())); + } + + return newVec; + } + + /** + * Apply a binary function to the copy of current vector with another vector + * and then return the result. + */ + @Override + public DoubleVector applyToElements(DoubleVector other, + DoubleDoubleFunction func) { + + Preconditions.checkArgument(this.getDimension() == other.getDimension(), + "Dimension of two vectors should be equal."); + + double otherDefaultValue = 0.0; + if (other instanceof SparseDoubleVector) { + otherDefaultValue = ((SparseDoubleVector) other).defaultValue; + } + double newDefaultValue = this.defaultValue; + if (other instanceof SparseDoubleVector) { + newDefaultValue = func.apply(this.defaultValue, otherDefaultValue); + } + + SparseDoubleVector newVec = new SparseDoubleVector(this.dimension, + newDefaultValue); + + Iterator thisItr = this.iterateNonDefault(); + Iterator otherItr = other.iterateNonDefault(); + + DoubleVectorElement thisCur = null; + if (thisItr.hasNext()) { + thisCur = thisItr.next(); + } + DoubleVectorElement otherCur = null; + if (otherItr.hasNext()) { + otherCur = otherItr.next(); + } + + while (thisCur != null || otherCur != null) { + if (thisCur == null) { // the iterator of current vector reaches the end + newVec.set(otherCur.getIndex(), + func.apply(this.defaultValue, otherCur.getValue())); + if (newVec.get(otherCur.getIndex()) == newVec.defaultValue) { + // remove if the value equals the default value + newVec.elements.remove(otherCur.getIndex()); + } + if (otherItr.hasNext()) { + otherCur = otherItr.next(); + } else { + otherCur = null; + } + } else if (otherCur == null) { + newVec.set(thisCur.getIndex(), + func.apply(thisCur.getValue(), otherDefaultValue)); + if (newVec.get(thisCur.getIndex()) == newVec.defaultValue) { + // remove if the value equals the default value + newVec.elements.remove(thisCur.getIndex()); + } + if (thisItr.hasNext()) { + thisCur = thisItr.next(); + } else { + thisCur = null; + } + } else { + int curIdx = 0; + if (thisCur.getIndex() < otherCur.getIndex()) { + curIdx = thisCur.getIndex(); + newVec.set(curIdx, func.apply(thisCur.getValue(), otherDefaultValue)); + if (thisItr.hasNext()) { + thisCur = thisItr.next(); + } else { + thisCur = null; + } + } else if (thisCur.getIndex() > otherCur.getIndex()) { + curIdx = otherCur.getIndex(); + newVec + .set(curIdx, func.apply(this.defaultValue, otherCur.getValue())); + if (otherItr.hasNext()) { + otherCur = otherItr.next(); + } else { + otherCur = null; + } + } else { + curIdx = thisCur.getIndex(); + newVec.set(curIdx, + func.apply(thisCur.getValue(), otherCur.getValue())); + if (thisItr.hasNext()) { + thisCur = thisItr.next(); + } else { + thisCur = null; + } + if (otherItr.hasNext()) { + otherCur = otherItr.next(); + } else { + otherCur = null; + } + } + if (newVec.get(curIdx) == newVec.defaultValue) { + // remove if the value equals the default value + newVec.elements.remove(curIdx); + } + } + } + + return newVec; + } + + /** + * Add another vector. + */ + @Override + public DoubleVector addUnsafe(DoubleVector vector) { + return this.applyToElements(vector, new DoubleDoubleFunction() { + @Override + public double apply(double x1, double x2) { + return x1 + x2; + } + + @Override + public double applyDerivative(double x1, double x2) { + throw new UnsupportedOperationException(); + } + }); + } + + /** + * Add another vector to the copy of the current vector and return the result. + */ + @Override + public DoubleVector add(DoubleVector vector) { + Preconditions.checkArgument(this.dimension == vector.getDimension(), + "Dimensions of two vectors are not the same."); + return this.addUnsafe(vector); + } + + /** + * Add a scalar. + */ + @Override + public DoubleVector add(double scalar) { + final double val = scalar; + return this.applyToElements(new DoubleFunction() { + @Override + public double apply(double value) { + return value + val; + } + + @Override + public double applyDerivative(double value) { + throw new UnsupportedOperationException(); + } + }); + } + + /** + * Subtract a vector from current vector. + */ + @Override + public DoubleVector subtractUnsafe(DoubleVector vector) { + return this.applyToElements(vector, new DoubleDoubleFunction() { + @Override + public double apply(double x1, double x2) { + return x1 - x2; + } + + @Override + public double applyDerivative(double x1, double x2) { + return 0; + } + }); + } + + /** + * Subtract a vector from current vector. + */ + @Override + public DoubleVector subtract(DoubleVector vector) { + Preconditions.checkArgument(this.dimension == vector.getDimension(), + "Dimensions of two vector are not the same."); + return this.subtractUnsafe(vector); + } + + /** + * Subtract a scalar from a copy of the current vector. + */ + @Override + public DoubleVector subtract(double scalar) { + final double val = scalar; + return this.applyToElements(new DoubleFunction() { + @Override + public double apply(double value) { + return value - val; + } + + @Override + public double applyDerivative(double value) { + throw new UnsupportedOperationException(); + } + }); + } + + /** + * Subtract a scalar from a copy of the current vector and return the result. + */ + @Override + public DoubleVector subtractFrom(double scalar) { + final double val = scalar; + return this.applyToElements(new DoubleFunction() { + @Override + public double apply(double value) { + return val - value; + } + + @Override + public double applyDerivative(double value) { + throw new UnsupportedOperationException(); + } + }); + } + + /** + * Multiply a copy of the current vector by a scalar and return the result. + */ + @Override + public DoubleVector multiply(double scalar) { + final double val = scalar; + return this.applyToElements(new DoubleFunction() { + @Override + public double apply(double value) { + return val * value; + } + + @Override + public double applyDerivative(double value) { + throw new UnsupportedOperationException(); + } + }); + } + + /** + * Multiply a copy of the current vector with another vector and return the + * result. + */ + @Override + public DoubleVector multiplyUnsafe(DoubleVector vector) { + return this.applyToElements(vector, new DoubleDoubleFunction() { + @Override + public double apply(double x1, double x2) { + return x1 * x2; + } + + @Override + public double applyDerivative(double x1, double x2) { + throw new UnsupportedOperationException(); + } + }); + } + + /** + * Multiply a copy of the current vector with another vector and return the + * result. + */ + @Override + public DoubleVector multiply(DoubleVector vector) { + Preconditions.checkArgument(this.dimension == vector.getDimension(), + "Dimensions of two vectors are not the same."); + return this.multiplyUnsafe(vector); + } + + /** + * Multiply a copy of the current vector with a matrix and return the result, + * i.e. r = v * M. + */ + @Override + public DoubleVector multiply(DoubleMatrix matrix) { + Preconditions + .checkArgument(this.dimension == matrix.getColumnCount(), + "The dimension of vector does not equal to the dimension of the matrix column."); + return this.multiplyUnsafe(matrix); + } + + /** + * Multiply a copy of the current vector with a matrix and return the result, + * i.e. r = v * M. + */ + @Override + public DoubleVector multiplyUnsafe(DoubleMatrix matrix) { + // currently the result is a dense double vector + DoubleVector res = new DenseDoubleVector(matrix.getColumnCount()); + int columns = matrix.getColumnCount(); + for (int i = 0; i < columns; ++i) { + res.set(i, this.dotUnsafe(matrix.getColumnVector(i))); + } + return res; + } + + /** + * Divide a copy of the current vector by a scala. + */ + @Override + public DoubleVector divide(double scalar) { + Preconditions.checkArgument(scalar != 0, "Scalar cannot be 0."); + final double factor = scalar; + return this.applyToElements(new DoubleFunction() { + @Override + public double apply(double value) { + return value / factor; + } + + @Override + public double applyDerivative(double value) { + throw new UnsupportedOperationException(); + } + }); + } + + /* + * (non-Javadoc) + * @see org.apache.hama.commons.math.DoubleVector#divideFrom(double) + */ + @Override + public DoubleVector divideFrom(double scalar) { + Preconditions.checkArgument(scalar != 0, "Scalar cannot be 0."); + final double factor = scalar; + return this.applyToElements(new DoubleFunction() { + @Override + public double apply(double value) { + return factor / value; // value can be 0! + } + + @Override + public double applyDerivative(double value) { + throw new UnsupportedOperationException(); + } + }); + } + + /** + * Apply the power to each element of a copy of the current vector. + */ + @Override + public DoubleVector pow(int x) { + final int p = x; + return this.applyToElements(new DoubleFunction() { + @Override + public double apply(double value) { + return Math.pow(value, p); + } + + @Override + public double applyDerivative(double value) { + throw new UnsupportedOperationException(); + } + }); + } + + /** + * Apply the abs elementwise to a copy of current vector. + */ + @Override + public DoubleVector abs() { + return this.applyToElements(new DoubleFunction() { + @Override + public double apply(double value) { + return Math.abs(value); + } + + @Override + public double applyDerivative(double value) { + throw new UnsupportedOperationException(); + } + }); + } + + /** + * Apply the sqrt elementwise to a copy of current vector. + */ + @Override + public DoubleVector sqrt() { + return this.applyToElements(new DoubleFunction() { + @Override + public double apply(double value) { + return Math.sqrt(value); + } + + @Override + public double applyDerivative(double value) { + throw new UnsupportedOperationException(); + } + }); + } + + /** + * Get the sum of all the elements. + */ + @Override + public double sum() { + double sum = 0.0; + Iterator itr = this.iterate(); + while (itr.hasNext()) { + sum += itr.next().getValue(); + } + return sum; + } + + /* + * (non-Javadoc) + * @see + * org.apache.hama.commons.math.DoubleVector#dotUnsafe(org.apache.hama.commons + * .math.DoubleVector) + */ + @Override + public double dotUnsafe(DoubleVector vector) { + return this.multiplyUnsafe(vector).sum(); + } + + /** + * Apply dot onto a copy of current vector and another vector. + */ + @Override + public double dot(DoubleVector vector) { + Preconditions.checkArgument(this.dimension == vector.getDimension(), + "Dimensions of two vectors are not equal."); + return this.dotUnsafe(vector); + } + + /** + * Obtain a sub-vector from the current vector. + */ + @Override + public DoubleVector slice(int length) { + Preconditions.checkArgument(length >= 0 && length < this.dimension, + String.format("length must be in range [0, %d).", this.dimension)); + return this.sliceUnsafe(length); + } + + /** + * Obtain a sub-vector from the current vector. + */ + @Override + public DoubleVector sliceUnsafe(int length) { + DoubleVector newVector = new SparseDoubleVector(length, this.defaultValue); + for (Map.Entry entry : this.elements.entrySet()) { + if (entry.getKey() >= length) { + continue; + } + newVector.set(entry.getKey(), entry.getValue()); + } + return newVector; + } + + /** + * Obtain a sub-vector of the current vector + * + * @param start inclusive + * @param end inclusive + */ + @Override + public DoubleVector slice(int start, int end) { + Preconditions.checkArgument(start >= 0 && end < this.dimension, String + .format("Start and end range should be in [0, %d].", this.dimension)); + return this.sliceUnsafe(start, end); + } + + /** + * @param start inclusive + * @param end inclusive + */ + @Override + public DoubleVector sliceUnsafe(int start, int end) { + SparseDoubleVector slicedVec = new SparseDoubleVector(end - start + 1, + this.defaultValue); + for (Map.Entry entry : this.elements.entrySet()) { + if (entry.getKey() >= start && entry.getKey() <= end) { + slicedVec.elements.put(entry.getKey() - start, entry.getValue()); + } + if (entry.getKey() > end) { + continue; + } + } + return slicedVec; + } + + /* + * (non-Javadoc) + * @see org.apache.hama.commons.math.DoubleVector#max() + */ + @Override + public double max() { + double max = this.defaultValue; + for (Map.Entry entry : this.elements.entrySet()) { + max = Math.max(max, entry.getValue()); + } + return max; + } + + /* + * (non-Javadoc) + * @see org.apache.hama.commons.math.DoubleVector#min() + */ + @Override + public double min() { + double min = this.defaultValue; + for (Map.Entry entry : this.elements.entrySet()) { + min = Math.min(min, entry.getValue()); + } + return min; + } + + /* + * (non-Javadoc) + * @see org.apache.hama.commons.math.DoubleVector#toArray() + */ + @Override + public double[] toArray() { + throw new UnsupportedOperationException( + "SparseDoubleVector does not support toArray() method."); + } + + /* + * (non-Javadoc) + * @see org.apache.hama.commons.math.DoubleVector#deepCopy() + */ + @Override + public DoubleVector deepCopy() { + SparseDoubleVector copy = new SparseDoubleVector(this.dimension); + copy.elements = new HashMap(this.elements.size()); + copy.elements.putAll(this.elements); + return copy; + } + + /** + * Generate the iterator that iterates the non-default values. + */ + @Override + public Iterator iterateNonDefault() { + return new NonDefaultIterator(); + } + + /* + * (non-Javadoc) + * @see org.apache.hama.commons.math.DoubleVector#iterate() + */ + @Override + public Iterator iterate() { + return new DefaultIterator(); + } + + /* + * (non-Javadoc) + * @see org.apache.hama.commons.math.DoubleVector#isSparse() + */ + @Override + public boolean isSparse() { + return true; + } + + /* + * (non-Javadoc) + * @see org.apache.hama.commons.math.DoubleVector#isNamed() + */ + @Override + public boolean isNamed() { + return false; + } + + /* + * (non-Javadoc) + * @see org.apache.hama.commons.math.DoubleVector#getName() + */ + @Override + public String getName() { + return null; + } + + /** + * Non-zero iterator for vector elements. + */ + private final class NonDefaultIterator extends + AbstractIterator { + private final DoubleVectorElement element = new DoubleVectorElement(); + + private final int entryDimension; + private final Map entries; + private final double defaultV = defaultValue; + private int currentIndex = 0; + + public NonDefaultIterator() { + this.entryDimension = dimension; + this.entries = elements; + } + + @Override + protected DoubleVectorElement computeNext() { + DoubleVectorElement elem = getNext(); + // skip the entries with default values + while (elem != null && elem.getValue() == this.defaultV) { + elem = getNext(); + } + return elem; + } + + private DoubleVectorElement getNext() { + if (currentIndex < entryDimension) { + Double value = entries.get(currentIndex); + element.setIndex(currentIndex); + if (value == null) { + element.setValue(defaultV); + } else { + element.setValue(value); + } + ++currentIndex; + return element; + } else { + return endOfData(); + } + } + } + + private final class DefaultIterator extends + AbstractIterator { + + private final DoubleVectorElement element = new DoubleVectorElement(); + + private final int entryDimension; + private final Map entries; + private final double defaultV = defaultValue; + private int currentIndex = 0; + + public DefaultIterator() { + this.entryDimension = dimension; + this.entries = elements; + } + + @Override + protected DoubleVectorElement computeNext() { + if (currentIndex < entryDimension) { + Double value = entries.get(currentIndex); + element.setIndex(currentIndex); + if (value == null) { + element.setValue(defaultV); + } else { + element.setValue(value); + } + ++currentIndex; + return element; + } else { + return endOfData(); + } + } + + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + Iterator itr = this.iterate(); + sb.append('{'); + while (itr.hasNext()) { + DoubleVectorElement elem = itr.next(); + sb.append(String.format("%s: %f, ", elem.getIndex(), elem.getValue())); + } + sb.replace(sb.length() - 2, sb.length() - 1, "}"); + return sb.toString(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + long temp; + temp = Double.doubleToLongBits(defaultValue); + result = prime * result + (int) (temp ^ (temp >>> 32)); + result = prime * result + dimension; + result = prime * result + ((elements == null) ? 0 : elements.hashCode()); + return result; + } + + @Override + public boolean equals(Object other) { + if (other instanceof DoubleVector) { + DoubleVector otherVec = (DoubleVector) other; + if (this.dimension != otherVec.getDimension()) { + return false; + } + // check non-default entries first + for (Map.Entry entry : this.elements.entrySet()) { + if (Math.abs(entry.getValue() - otherVec.get(entry.getKey())) > 0.000001) { + return false; + } + } + // check default values + for (int i = 0; i < this.dimension; ++i) { + if (Math.abs(this.get(i) - otherVec.get(i)) > 0.000001) { + return false; + } + } + + return true; + } + return false; + } +} Index: commons/src/test/java/org/apache/hama/commons/math/TestSparseDoubleVector.java =================================================================== --- commons/src/test/java/org/apache/hama/commons/math/TestSparseDoubleVector.java (revision 0) +++ commons/src/test/java/org/apache/hama/commons/math/TestSparseDoubleVector.java (working copy) @@ -0,0 +1,393 @@ +/** + * 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.hama.commons.math; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; + +import java.util.Iterator; + +import org.apache.hama.commons.math.DoubleVector.DoubleVectorElement; +import org.junit.Test; + +/** + * The test cases of {@link SparseDoubleVector}. + * + */ +public class TestSparseDoubleVector { + + + + @Test + public void testBasic() { + DoubleVector v1 = new SparseDoubleVector(10); + for (int i = 0; i < 10; ++i) { + assertEquals(v1.get(i), 0.0, 0.000001); + } + + DoubleVector v2 = new SparseDoubleVector(10, 2.5); + for (int i = 0; i < 10; ++i) { + assertEquals(v2.get(i), 2.5, 0.000001); + } + + assertEquals(v1.getDimension(), 10); + assertEquals(v2.getLength(), 10); + + v1.set(5, 2); + assertEquals(v1.get(5), 2, 0.000001); + } + + @Test + public void testIterators() { + DoubleVector v1 = new SparseDoubleVector(10, 5.5); + Iterator itr1 = v1.iterate(); + int idx1 = 0; + while (itr1.hasNext()) { + DoubleVectorElement elem = itr1.next(); + assertEquals(idx1++, elem.getIndex()); + assertEquals(5.5, elem.getValue(), 0.000001); + } + + v1.set(2, 20); + v1.set(6, 30); + + Iterator itr2 = v1.iterateNonDefault(); + DoubleVectorElement elem = itr2.next(); + assertEquals(2, elem.getIndex()); + assertEquals(20, elem.getValue(), 0.000001); + elem = itr2.next(); + assertEquals(6, elem.getIndex()); + assertEquals(30, elem.getValue(), 0.000001); + + assertFalse(itr2.hasNext()); + } + + @Test + public void testApplyToElements() { + // v1 = {5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5, 5.5} + DoubleVector v1 = new SparseDoubleVector(10, 5.5); + + // v2 = {60.6, 60.5, 60.5, 60.5, 60.5, 60.5, 60.5, 60.5, 60.5, 60.5} + DoubleVector v2 = v1.applyToElements(new DoubleFunction() { + @Override + public double apply(double value) { + return value * 11; + } + + @Override + public double applyDerivative(double value) { + return 0; + } + }); + + // v3 = {4.5, 4.5, 4.5, 4.5, 4.5, 4.5, 4.5, 4.5, 4.5, 4.5} + DoubleVector v3 = v1.applyToElements(new DoubleFunction() { + @Override + public double apply(double value) { + return value / 2 + 1.75; + } + + @Override + public double applyDerivative(double value) { + return 0; + } + }); + + // v4 = {66, 66, 66, 66, 66, 66, 66, 66, 66, 66} + DoubleVector v4 = v1.applyToElements(v2, new DoubleDoubleFunction() { + @Override + public double apply(double x1, double x2) { + return x1 + x2; + } + + @Override + public double applyDerivative(double x1, double x2) { + return 0; + } + }); + + for (int i = 0; i < 10; ++i) { + assertEquals(v1.get(i), 5.5, 0.000001); + assertEquals(v2.get(i), 60.5, 0.000001); + assertEquals(v3.get(i), 4.5, 0.000001); + assertEquals(v4.get(i), 66, 0.000001); + } + + // v3 = {4.5, 4.5, 4.5, 10, 4.5, 4.5, 10, 4.5, 200, 4.5} + v3.set(3, 10); + v3.set(6, 10); + v3.set(8, 200); + + // v4 = {66, 66, 66, 66, 66, 100, 66, 66, 1, 66} + v4.set(5, 100); + v4.set(8, 1); + + // v5 = {615, 615, 615, 560, 615, 955, 560, 615, -1990, 615} + DoubleVector v5 = v4.applyToElements(v3, new DoubleDoubleFunction() { + @Override + public double apply(double x1, double x2) { + return (x1 - x2) * 10; + } + + @Override + public double applyDerivative(double x1, double x2) { + return 0; + } + }); + + // v6 = {615, 615, 615, 560, 615, 955, 560, 615, -1990, 615} + DoubleVector v6 = new SparseDoubleVector(10, 615); + v6.set(3, 560); + v6.set(5, 955); + v6.set(6, 560); + v6.set(8, -1990); + + for (int i = 0; i < v5.getDimension(); ++i) { + assertEquals(v5.get(i), v6.get(i), 0.000001); + } + + // v7 = {0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0} + DoubleVector v7 = new DenseDoubleVector(new double[] { 0.0, 1.0, 2.0, 3.0, + 4.0, 5.0, 6.0, 7.0, 8.0, 9.0 }); + + DoubleVector v8 = v5.applyToElements(v7, new DoubleDoubleFunction() { + @Override + public double apply(double x1, double x2) { + return (x1 + x2) * 3.3; + } + + @Override + public double applyDerivative(double x1, double x2) { + return 0; + } + }); + + DoubleVector v9 = v6.applyToElements(v7, new DoubleDoubleFunction() { + @Override + public double apply(double x1, double x2) { + return (x1 + x2) * 3.3; + } + + @Override + public double applyDerivative(double x1, double x2) { + return 0; + } + }); + + for (int i = 0; i < v7.getDimension(); ++i) { + assertEquals(v8.get(i), v9.get(i), 0.000001); + } + + } + + @Test + public void testAdd() { + // addition of two sparse vectors + DoubleVector spVec1 = new SparseDoubleVector(10, 1.5); + DoubleVector spVec2 = new SparseDoubleVector(10); + for (int i = 0; i < spVec2.getDimension(); ++i) { + spVec2.set(i, 1.5); + } + + DoubleVector expRes1 = new SparseDoubleVector(10, 3.0); + assertEquals(expRes1, spVec1.add(spVec2)); + + // addition of one sparse vector and one dense vector + DoubleVector dsVec1 = new DenseDoubleVector(10); + for (int i = 0; i < dsVec1.getDimension(); ++i) { + dsVec1.set(i, 1.5); + } + + DoubleVector expRes2 = new DenseDoubleVector(10); + for (int i = 0; i < expRes2.getDimension(); ++i) { + expRes2.set(i, 3.0); + } + assertEquals(expRes2, dsVec1.add(spVec2)); + } + + @Test + public void testSubtract() { + // subtract two sparse vectors + DoubleVector spVec1 = new SparseDoubleVector(10, 1.5); + DoubleVector spVec2 = new SparseDoubleVector(10); + DoubleVector spVec3 = new SparseDoubleVector(10, 2.2); + for (int i = 0; i < spVec2.getDimension(); ++i) { + spVec2.set(i, 1.2); + } + DoubleVector expRes1 = new SparseDoubleVector(10, 0.3); + assertEquals(expRes1, spVec1.subtract(spVec2)); + + DoubleVector expRes2 = new SparseDoubleVector(10, -0.7); + assertEquals(expRes2, spVec1.subtract(spVec3)); + + // subtract one sparse vector from a dense vector + DoubleVector dsVec1 = new DenseDoubleVector(10); + for (int i = 0; i < dsVec1.getDimension(); ++i) { + dsVec1.set(i, 1.7); + } + DoubleVector expRes3 = new SparseDoubleVector(10, 0.2); + assertEquals(expRes3, dsVec1.subtract(spVec1)); + + // subtract one dense vector from a sparse vector + DoubleVector expRes4 = new SparseDoubleVector(10, -0.2); + assertEquals(expRes4, spVec1.subtract(dsVec1)); + } + + @Test + public void testMultiplyScala() { + DoubleVector spVec1 = new SparseDoubleVector(10, 1.5); + DoubleVector spVec2 = new SparseDoubleVector(10); + DoubleVector spVec3 = new SparseDoubleVector(10, 2.2); + + DoubleVector spRes1 = spVec1.applyToElements(new DoubleFunction() { + @Override + public double apply(double value) { + return value * 3; + } + + @Override + public double applyDerivative(double value) { + throw new UnsupportedOperationException(); + } + }); + + assertEquals(spRes1, spVec1.multiply(3)); + assertEquals(spVec2, spVec2.multiply(1000)); + assertEquals(spVec2, spVec1.multiply(0)); + assertEquals(spVec1, spVec3.multiply(1.5 / 2.2)); + } + + @Test + public void testMultiply() { + DoubleVector spVec1 = new SparseDoubleVector(10, 1.5); + DoubleVector spVec2 = new SparseDoubleVector(10); + DoubleVector spVec3 = new SparseDoubleVector(10, 2.2); + + DoubleVector spRes1 = spVec1.applyToElements(spVec3, + new DoubleDoubleFunction() { + @Override + public double apply(double first, double second) { + return first * second; + } + + @Override + public double applyDerivative(double value, double second) { + throw new UnsupportedOperationException(); + } + }); + + assertEquals(spRes1, spVec1.multiply(spVec3)); + assertEquals(spVec2, spVec1.multiply(spVec2)); + } + + @Test + public void testDivide() { + DoubleVector spVec1 = new SparseDoubleVector(10, 1.5); + DoubleVector spVec2 = new SparseDoubleVector(10, 6.0); + DoubleVector spVec3 = new SparseDoubleVector(10, 2.2); + + assertEquals(spVec3, spVec1.divide(1.5 / 2.2)); + assertEquals(spVec2, spVec1.divideFrom(9)); + } + + @Test + public void testPow() { + DoubleVector spVec1 = new SparseDoubleVector(10, 1.5); + DoubleVector spVec2 = new SparseDoubleVector(10, 1); + DoubleVector spVec3 = new SparseDoubleVector(10, 2.25); + + assertEquals(spVec3, spVec1.pow(2)); + assertEquals(spVec2, spVec1.pow(0)); + } + + @Test + public void testAbs() { + DoubleVector spVec1 = new SparseDoubleVector(10, 1.5); + DoubleVector spVec2 = new SparseDoubleVector(10, 0); + DoubleVector spVec3 = new SparseDoubleVector(10, -1.5); + + assertEquals(spVec1, spVec3.abs()); + assertEquals(spVec2, spVec2.abs()); + } + + @Test + public void testSqrt() { + DoubleVector spVec1 = new SparseDoubleVector(10, 2.25); + DoubleVector spVec2 = new SparseDoubleVector(10, 0); + DoubleVector spVec3 = new SparseDoubleVector(10, 1.5); + DoubleVector spVec4 = new SparseDoubleVector(10, 1); + + assertEquals(spVec3, spVec1.sqrt()); + assertEquals(spVec2, spVec2.sqrt()); + assertEquals(spVec4, spVec4.sqrt()); + } + + @Test + public void testSum() { + DoubleVector spVec1 = new SparseDoubleVector(10, 2.25); + DoubleVector spVec2 = new SparseDoubleVector(10, 0); + DoubleVector spVec3 = new SparseDoubleVector(10, 1.5); + + assertEquals(22.5, spVec1.sum(), 0.00001); + assertEquals(0, spVec2.sum(), 0.000001); + assertEquals(15, spVec3.sum(), 0.000001); + } + + @Test + public void testDot() { + DoubleVector spVec1 = new SparseDoubleVector(10, 2.25); + DoubleVector spVec2 = new SparseDoubleVector(10, 0); + DoubleVector spVec3 = new SparseDoubleVector(10, 1.5); + DoubleVector spVec4 = new SparseDoubleVector(10, 1); + + assertEquals(spVec1.multiply(spVec3).sum(), spVec1.dot(spVec3), 0.000001); + assertEquals(spVec3.sum(), spVec3.dot(spVec4), 0.000001); + assertEquals(0, spVec1.dot(spVec2), 0.000001); + } + + @Test + public void testSlice() { + DoubleVector spVec1 = new SparseDoubleVector(10, 2.25); + DoubleVector spVec2 = new SparseDoubleVector(10, 0); + DoubleVector spVec3 = new SparseDoubleVector(5, 2.25); + DoubleVector spVec4 = new SparseDoubleVector(5, 0); + + spVec1.set(7, 100); + spVec2.set(2, 200); + + assertEquals(spVec3, spVec1.sliceUnsafe(5)); + assertFalse(spVec4.equals(spVec2.slice(5))); + + assertFalse(spVec3.equals(spVec1.slice(5, 9))); + assertEquals(spVec4, spVec2.slice(5, 9)); + } + + @Test + public void testMaxMin() { + DoubleVector spVec1 = new SparseDoubleVector(10, 2.25); + DoubleVector spVec2 = new SparseDoubleVector(10, 0); + + spVec1.set(7, 100); + spVec2.set(2, 200); + + assertEquals(100, spVec1.max(), 0.000001); + assertEquals(0, spVec2.min(), 0.000001); + } + +}