/* * 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.commons.math.linear; import java.io.Serializable; import org.apache.commons.math.MathRuntimeException; import org.apache.commons.math.exception.util.LocalizedFormats; import org.apache.commons.math.util.OpenIntToDoubleHashMap; import org.apache.commons.math.util.OpenIntToDoubleHashMap.Iterator; import org.apache.commons.math.util.FastMath; /** * This class implements the {@link RealVector} interface with a {@link OpenIntToDoubleHashMap} backing store. * @version $Revision: 1073262 $ $Date: 2011-02-22 10:02:25 +0100 (mar. 22 févr. 2011) $ * @since 2.0 */ public class OpenMapRealVector extends AbstractRealVector implements SparseRealVector, Serializable { /** Default Tolerance for having a value considered zero. */ public static final double DEFAULT_ZERO_TOLERANCE = 1.0e-12; /** Serializable version identifier. */ private static final long serialVersionUID = 8772222695580707260L; /** Entries of the vector. */ private final OpenIntToDoubleHashMap entries; /** Dimension of the vector. */ private final int virtualSize; /** Tolerance for having a value considered zero. */ private final double epsilon; /** * Build a 0-length vector. * <p>Zero-length vectors may be used to initialized construction of vectors * by data gathering. We start with zero-length and use either the {@link * #OpenMapRealVector(OpenMapRealVector, int)} constructor * or one of the <code>append</code> method ({@link #append(double)}, {@link * #append(double[])}, {@link #append(RealVector)}) to gather data * into this vector.</p> */ public OpenMapRealVector() { this(0, DEFAULT_ZERO_TOLERANCE); } /** * Construct a (dimension)-length vector of zeros. * @param dimension size of the vector */ public OpenMapRealVector(int dimension) { this(dimension, DEFAULT_ZERO_TOLERANCE); } /** * Construct a (dimension)-length vector of zeros, specifying zero tolerance. * @param dimension Size of the vector * @param epsilon The tolerance for having a value considered zero */ public OpenMapRealVector(int dimension, double epsilon) { virtualSize = dimension; entries = new OpenIntToDoubleHashMap(0.0); this.epsilon = epsilon; } /** * Build a resized vector, for use with append. * @param v The original vector * @param resize The amount to resize it */ protected OpenMapRealVector(OpenMapRealVector v, int resize) { virtualSize = v.getDimension() + resize; entries = new OpenIntToDoubleHashMap(v.entries); epsilon = v.epsilon; } /** * Build a vector with known the sparseness (for advanced use only). * @param dimension The size of the vector * @param expectedSize The expected number of non-zero entries */ public OpenMapRealVector(int dimension, int expectedSize) { this(dimension, expectedSize, DEFAULT_ZERO_TOLERANCE); } /** * Build a vector with known the sparseness and zero tolerance setting (for advanced use only). * @param dimension The size of the vector * @param expectedSize The expected number of non-zero entries * @param epsilon The tolerance for having a value considered zero */ public OpenMapRealVector(int dimension, int expectedSize, double epsilon) { virtualSize = dimension; entries = new OpenIntToDoubleHashMap(expectedSize, 0.0); this.epsilon = epsilon; } /** * Create from a double array. * Only non-zero entries will be stored * @param values The set of values to create from */ public OpenMapRealVector(double[] values) { this(values, DEFAULT_ZERO_TOLERANCE); } /** * Create from a double array, specifying zero tolerance. * Only non-zero entries will be stored * @param values The set of values to create from * @param epsilon The tolerance for having a value considered zero */ public OpenMapRealVector(double[] values, double epsilon) { virtualSize = values.length; entries = new OpenIntToDoubleHashMap(0.0); this.epsilon = epsilon; for (int key = 0; key < values.length; key++) { double value = values[key]; if (!isDefaultValue(value)) { entries.put(key, value); } } } /** * Create from a Double array. * Only non-zero entries will be stored * @param values The set of values to create from */ public OpenMapRealVector(Double[] values) { this(values, DEFAULT_ZERO_TOLERANCE); } /** * Create from a Double array. * Only non-zero entries will be stored * @param values The set of values to create from * @param epsilon The tolerance for having a value considered zero */ public OpenMapRealVector(Double[] values, double epsilon) { virtualSize = values.length; entries = new OpenIntToDoubleHashMap(0.0); this.epsilon = epsilon; for (int key = 0; key < values.length; key++) { double value = values[key].doubleValue(); if (!isDefaultValue(value)) { entries.put(key, value); } } } /** * Copy constructor. * @param v The instance to copy from */ public OpenMapRealVector(OpenMapRealVector v) { virtualSize = v.getDimension(); entries = new OpenIntToDoubleHashMap(v.getEntries()); epsilon = v.epsilon; } /** * Generic copy constructor. * @param v The instance to copy from */ public OpenMapRealVector(RealVector v) { virtualSize = v.getDimension(); entries = new OpenIntToDoubleHashMap(0.0); epsilon = DEFAULT_ZERO_TOLERANCE; for (int key = 0; key < virtualSize; key++) { double value = v.getEntry(key); if (!isDefaultValue(value)) { entries.put(key, value); } } } /** * Get the entries of this instance. * @return entries of this instance */ private OpenIntToDoubleHashMap getEntries() { return entries; } /** * Determine if this value is within epsilon of zero. * @param value The value to test * @return <code>true</code> if this value is within epsilon to zero, <code>false</code> otherwise * @since 2.1 */ protected boolean isDefaultValue(double value) { return FastMath.abs(value) < epsilon; } /** {@inheritDoc} */ @Override public RealVector add(RealVector v) throws IllegalArgumentException { checkVectorDimensions(v.getDimension()); if (v instanceof OpenMapRealVector) { return add((OpenMapRealVector) v); } else { return super.add(v); } } /** * Optimized method to add two OpenMapRealVectors. Copies the larger vector, iterates over the smaller. * @param v Vector to add with * @return The sum of <code>this</code> with <code>v</code> * @throws IllegalArgumentException If the dimensions don't match */ public OpenMapRealVector add(OpenMapRealVector v) throws IllegalArgumentException{ checkVectorDimensions(v.getDimension()); boolean copyThis = entries.size() > v.entries.size(); OpenMapRealVector res = copyThis ? this.copy() : v.copy(); Iterator iter = copyThis ? v.entries.iterator() : entries.iterator(); OpenIntToDoubleHashMap randomAccess = copyThis ? entries : v.entries; while (iter.hasNext()) { iter.advance(); int key = iter.key(); if (randomAccess.containsKey(key)) { res.setEntry(key, randomAccess.get(key) + iter.value()); } else { res.setEntry(key, iter.value()); } } return res; } /** * Optimized method to append a OpenMapRealVector. * @param v vector to append * @return The result of appending <code>v</code> to self */ public OpenMapRealVector append(OpenMapRealVector v) { OpenMapRealVector res = new OpenMapRealVector(this, v.getDimension()); Iterator iter = v.entries.iterator(); while (iter.hasNext()) { iter.advance(); res.setEntry(iter.key() + virtualSize, iter.value()); } return res; } /** {@inheritDoc} */ public OpenMapRealVector append(RealVector v) { if (v instanceof OpenMapRealVector) { return append((OpenMapRealVector) v); } return append(v.getData()); } /** {@inheritDoc} */ public OpenMapRealVector append(double d) { OpenMapRealVector res = new OpenMapRealVector(this, 1); res.setEntry(virtualSize, d); return res; } /** {@inheritDoc} */ public OpenMapRealVector append(double[] a) { OpenMapRealVector res = new OpenMapRealVector(this, a.length); for (int i = 0; i < a.length; i++) { res.setEntry(i + virtualSize, a[i]); } return res; } /** * {@inheritDoc} * @since 2.1 */ @Override public OpenMapRealVector copy() { return new OpenMapRealVector(this); } /** * Optimized method to compute the dot product with an OpenMapRealVector. * Iterates over the smaller of the two. * @param v The vector to compute the dot product with * @return The dot product of <code>this</code> and <code>v</code> * @throws IllegalArgumentException If the dimensions don't match */ public double dotProduct(OpenMapRealVector v) throws IllegalArgumentException { checkVectorDimensions(v.getDimension()); boolean thisIsSmaller = entries.size() < v.entries.size(); Iterator iter = thisIsSmaller ? entries.iterator() : v.entries.iterator(); OpenIntToDoubleHashMap larger = thisIsSmaller ? v.entries : entries; double d = 0; while(iter.hasNext()) { iter.advance(); d += iter.value() * larger.get(iter.key()); } return d; } /** {@inheritDoc} */ @Override public double dotProduct(RealVector v) throws IllegalArgumentException { if(v instanceof OpenMapRealVector) { return dotProduct((OpenMapRealVector)v); } else { return super.dotProduct(v); } } /** {@inheritDoc} */ public OpenMapRealVector ebeDivide(RealVector v) throws IllegalArgumentException { checkVectorDimensions(v.getDimension()); OpenMapRealVector res = new OpenMapRealVector(this); Iterator iter = res.entries.iterator(); while (iter.hasNext()) { iter.advance(); res.setEntry(iter.key(), iter.value() / v.getEntry(iter.key())); } return res; } /** {@inheritDoc} */ @Override public OpenMapRealVector ebeDivide(double[] v) throws IllegalArgumentException { checkVectorDimensions(v.length); OpenMapRealVector res = new OpenMapRealVector(this); Iterator iter = res.entries.iterator(); while (iter.hasNext()) { iter.advance(); res.setEntry(iter.key(), iter.value() / v[iter.key()]); } return res; } /** {@inheritDoc} */ public OpenMapRealVector ebeMultiply(RealVector v) throws IllegalArgumentException { checkVectorDimensions(v.getDimension()); OpenMapRealVector res = new OpenMapRealVector(this); Iterator iter = res.entries.iterator(); while (iter.hasNext()) { iter.advance(); res.setEntry(iter.key(), iter.value() * v.getEntry(iter.key())); } return res; } /** {@inheritDoc} */ @Override public OpenMapRealVector ebeMultiply(double[] v) throws IllegalArgumentException { checkVectorDimensions(v.length); OpenMapRealVector res = new OpenMapRealVector(this); Iterator iter = res.entries.iterator(); while (iter.hasNext()) { iter.advance(); res.setEntry(iter.key(), iter.value() * v[iter.key()]); } return res; } /** {@inheritDoc} */ public OpenMapRealVector getSubVector(int index, int n) throws MatrixIndexException { checkIndex(index); checkIndex(index + n - 1); OpenMapRealVector res = new OpenMapRealVector(n); int end = index + n; Iterator iter = entries.iterator(); while (iter.hasNext()) { iter.advance(); int key = iter.key(); if (key >= index && key < end) { res.setEntry(key - index, iter.value()); } } return res; } /** {@inheritDoc} */ @Override public double[] getData() { double[] res = new double[virtualSize]; Iterator iter = entries.iterator(); while (iter.hasNext()) { iter.advance(); res[iter.key()] = iter.value(); } return res; } /** {@inheritDoc} */ public int getDimension() { return virtualSize; } /** * Optimized method to compute distance. * @param v The vector to compute distance to * @return The distance from <code>this</code> and <code>v</code> * @throws IllegalArgumentException If the dimensions don't match */ public double getDistance(OpenMapRealVector v) throws IllegalArgumentException { Iterator iter = entries.iterator(); double res = 0; while (iter.hasNext()) { iter.advance(); int key = iter.key(); double delta; delta = iter.value() - v.getEntry(key); res += delta * delta; } iter = v.getEntries().iterator(); while (iter.hasNext()) { iter.advance(); int key = iter.key(); if (!entries.containsKey(key)) { final double value = iter.value(); res += value * value; } } return FastMath.sqrt(res); } /** {@inheritDoc} */ @Override public double getDistance(RealVector v) throws IllegalArgumentException { checkVectorDimensions(v.getDimension()); if (v instanceof OpenMapRealVector) { return getDistance((OpenMapRealVector) v); } return getDistance(v.getData()); } /** {@inheritDoc} */ @Override public double getDistance(double[] v) throws IllegalArgumentException { checkVectorDimensions(v.length); double res = 0; for (int i = 0; i < v.length; i++) { double delta = entries.get(i) - v[i]; res += delta * delta; } return FastMath.sqrt(res); } /** {@inheritDoc} */ public double getEntry(int index) throws MatrixIndexException { checkIndex(index); return entries.get(index); } /** * Distance between two vectors. * <p>This method computes the distance consistent with * L<sub>1</sub> norm, i.e. the sum of the absolute values of * elements differences.</p> * @param v vector to which distance is requested * @return distance between two vectors. */ public double getL1Distance(OpenMapRealVector v) { double max = 0; Iterator iter = entries.iterator(); while (iter.hasNext()) { iter.advance(); double delta = FastMath.abs(iter.value() - v.getEntry(iter.key())); max += delta; } iter = v.getEntries().iterator(); while (iter.hasNext()) { iter.advance(); int key = iter.key(); if (!entries.containsKey(key)) { double delta = FastMath.abs(iter.value()); max += FastMath.abs(delta); } } return max; } /** {@inheritDoc} */ @Override public double getL1Distance(RealVector v) throws IllegalArgumentException { checkVectorDimensions(v.getDimension()); if (v instanceof OpenMapRealVector) { return getL1Distance((OpenMapRealVector) v); } return getL1Distance(v.getData()); } /** {@inheritDoc} */ @Override public double getL1Distance(double[] v) throws IllegalArgumentException { checkVectorDimensions(v.length); double max = 0; for (int i = 0; i < v.length; i++) { double delta = FastMath.abs(getEntry(i) - v[i]); max += delta; } return max; } /** * Optimized method to compute LInfDistance. * @param v The vector to compute from * @return the LInfDistance */ private double getLInfDistance(OpenMapRealVector v) { double max = 0; Iterator iter = entries.iterator(); while (iter.hasNext()) { iter.advance(); double delta = FastMath.abs(iter.value() - v.getEntry(iter.key())); if (delta > max) { max = delta; } } iter = v.getEntries().iterator(); while (iter.hasNext()) { iter.advance(); int key = iter.key(); if (!entries.containsKey(key)) { if (iter.value() > max) { max = iter.value(); } } } return max; } /** {@inheritDoc} */ @Override public double getLInfDistance(RealVector v) throws IllegalArgumentException { checkVectorDimensions(v.getDimension()); if (v instanceof OpenMapRealVector) { return getLInfDistance((OpenMapRealVector) v); } return getLInfDistance(v.getData()); } /** {@inheritDoc} */ @Override public double getLInfDistance(double[] v) throws IllegalArgumentException { checkVectorDimensions(v.length); double max = 0; for (int i = 0; i < v.length; i++) { double delta = FastMath.abs(getEntry(i) - v[i]); if (delta > max) { max = delta; } } return max; } /** {@inheritDoc} */ public boolean isInfinite() { boolean infiniteFound = false; Iterator iter = entries.iterator(); while (iter.hasNext()) { iter.advance(); final double value = iter.value(); if (Double.isNaN(value)) { return false; } if (Double.isInfinite(value)) { infiniteFound = true; } } return infiniteFound; } /** {@inheritDoc} */ public boolean isNaN() { Iterator iter = entries.iterator(); while (iter.hasNext()) { iter.advance(); if (Double.isNaN(iter.value())) { return true; } } return false; } /** {@inheritDoc} */ @Override public OpenMapRealVector mapAdd(double d) { return copy().mapAddToSelf(d); } /** {@inheritDoc} */ @Override public OpenMapRealVector mapAddToSelf(double d) { for (int i = 0; i < virtualSize; i++) { setEntry(i, getEntry(i) + d); } return this; } /** {@inheritDoc} */ @Override public RealMatrix outerProduct(double[] v) throws IllegalArgumentException { checkVectorDimensions(v.length); RealMatrix res = new OpenMapRealMatrix(virtualSize, virtualSize); Iterator iter = entries.iterator(); while (iter.hasNext()) { iter.advance(); int row = iter.key(); double value = iter.value(); for (int col = 0; col < virtualSize; col++) { res.setEntry(row, col, value * v[col]); } } return res; } /** {@inheritDoc} */ public RealVector projection(RealVector v) throws IllegalArgumentException { checkVectorDimensions(v.getDimension()); return v.mapMultiply(dotProduct(v) / v.dotProduct(v)); } /** {@inheritDoc} */ @Override public OpenMapRealVector projection(double[] v) throws IllegalArgumentException { checkVectorDimensions(v.length); return (OpenMapRealVector) projection(new OpenMapRealVector(v)); } /** {@inheritDoc} */ public void setEntry(int index, double value) throws MatrixIndexException { checkIndex(index); if (!isDefaultValue(value)) { entries.put(index, value); } else if (entries.containsKey(index)) { entries.remove(index); } } /** {@inheritDoc} */ @Override public void setSubVector(int index, RealVector v) throws MatrixIndexException { checkIndex(index); checkIndex(index + v.getDimension() - 1); setSubVector(index, v.getData()); } /** {@inheritDoc} */ @Override public void setSubVector(int index, double[] v) throws MatrixIndexException { checkIndex(index); checkIndex(index + v.length - 1); for (int i = 0; i < v.length; i++) { setEntry(i + index, v[i]); } } /** {@inheritDoc} */ @Override public void set(double value) { for (int i = 0; i < virtualSize; i++) { setEntry(i, value); } } /** * Optimized method to subtract OpenMapRealVectors. * @param v The vector to subtract from <code>this</code> * @return The difference of <code>this</code> and <code>v</code> * @throws IllegalArgumentException If the dimensions don't match */ public OpenMapRealVector subtract(OpenMapRealVector v) throws IllegalArgumentException{ checkVectorDimensions(v.getDimension()); OpenMapRealVector res = copy(); Iterator iter = v.getEntries().iterator(); while (iter.hasNext()) { iter.advance(); int key = iter.key(); if (entries.containsKey(key)) { res.setEntry(key, entries.get(key) - iter.value()); } else { res.setEntry(key, -iter.value()); } } return res; } /** {@inheritDoc} */ @Override public OpenMapRealVector subtract(RealVector v) throws IllegalArgumentException { checkVectorDimensions(v.getDimension()); if (v instanceof OpenMapRealVector) { return subtract((OpenMapRealVector) v); } return subtract(v.getData()); } /** {@inheritDoc} */ @Override public OpenMapRealVector subtract(double[] v) throws IllegalArgumentException { checkVectorDimensions(v.length); OpenMapRealVector res = new OpenMapRealVector(this); for (int i = 0; i < v.length; i++) { if (entries.containsKey(i)) { res.setEntry(i, entries.get(i) - v[i]); } else { res.setEntry(i, -v[i]); } } return res; } /** {@inheritDoc} */ @Override public OpenMapRealVector unitVector() { OpenMapRealVector res = copy(); res.unitize(); return res; } /** {@inheritDoc} */ @Override public void unitize() { double norm = getNorm(); if (isDefaultValue(norm)) { throw MathRuntimeException.createArithmeticException(LocalizedFormats.CANNOT_NORMALIZE_A_ZERO_NORM_VECTOR); } Iterator iter = entries.iterator(); while (iter.hasNext()) { iter.advance(); entries.put(iter.key(), iter.value() / norm); } } /** {@inheritDoc} */ @Override public double[] toArray() { return getData(); } /** {@inheritDoc} * <p> Implementation Note: This works on exact values, and as a result * it is possible for {@code a.subtract(b)} to be the zero vector, while * {@code a.hashCode() != b.hashCode()}.</p> */ @Override public int hashCode() { final int prime = 31; int result = 1; long temp; temp = Double.doubleToLongBits(epsilon); result = prime * result + (int) (temp ^ (temp >>> 32)); result = prime * result + virtualSize; Iterator iter = entries.iterator(); while (iter.hasNext()) { iter.advance(); temp = Double.doubleToLongBits(iter.value()); result = prime * result + (int) (temp ^ (temp >>32)); } return result; } /** * <p> Implementation Note: This performs an exact comparison, and as a result * it is possible for {@code a.subtract(b}} to be the zero vector, while * {@code a.equals(b) == false}.</p> * {@inheritDoc} */ @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (!(obj instanceof OpenMapRealVector)) { return false; } OpenMapRealVector other = (OpenMapRealVector) obj; if (virtualSize != other.virtualSize) { return false; } if (Double.doubleToLongBits(epsilon) != Double.doubleToLongBits(other.epsilon)) { return false; } Iterator iter = entries.iterator(); while (iter.hasNext()) { iter.advance(); double test = other.getEntry(iter.key()); if (Double.doubleToLongBits(test) != Double.doubleToLongBits(iter.value())) { return false; } } iter = other.getEntries().iterator(); while (iter.hasNext()) { iter.advance(); double test = iter.value(); if (Double.doubleToLongBits(test) != Double.doubleToLongBits(getEntry(iter.key()))) { return false; } } return true; } /** * * @return the percentage of none zero elements as a decimal percent. * @deprecated as of 2.2 replaced by the correctly spelled {@link #getSparsity()} */ @Deprecated public double getSparcity() { return getSparsity(); } /** * * @return the percentage of none zero elements as a decimal percent. * @since 2.2 */ public double getSparsity() { return (double)entries.size()/(double)getDimension(); } /** {@inheritDoc} */ @Override public java.util.Iterator<Entry> sparseIterator() { return new OpenMapSparseIterator(); } /** * Implementation of <code>Entry</code> optimized for OpenMap. * <p>This implementation does not allow arbitrary calls to <code>setIndex</code> * since the order that entries are returned is undefined. */ protected class OpenMapEntry extends Entry { /** Iterator pointing to the entry. */ private final Iterator iter; /** Build an entry from an iterator point to an element. * @param iter iterator pointing to the entry */ protected OpenMapEntry(Iterator iter) { this.iter = iter; } /** {@inheritDoc} */ @Override public double getValue() { return iter.value(); } /** {@inheritDoc} */ @Override public void setValue(double value) { entries.put(iter.key(), value); } /** {@inheritDoc} */ @Override public int getIndex() { return iter.key(); } } /** * Iterator class to do iteration over just the non-zero elements. * <p>This implementation is fail-fast, so cannot be used to modify any zero element. * */ protected class OpenMapSparseIterator implements java.util.Iterator<Entry> { /** Underlying iterator. */ private final Iterator iter; /** Current entry. */ private final Entry current; /** Simple constructor. */ protected OpenMapSparseIterator() { iter = entries.iterator(); current = new OpenMapEntry(iter); } /** {@inheritDoc} */ public boolean hasNext() { return iter.hasNext(); } /** {@inheritDoc} */ public Entry next() { iter.advance(); return current; } /** {@inheritDoc} */ public void remove() { throw new UnsupportedOperationException("Not supported"); } } }