blob: 17da319d5a8feb793baa37b4412c551538c7c115 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.math4.legacy.linear;
import java.io.Serializable;
import org.apache.commons.math4.legacy.exception.DimensionMismatchException;
import org.apache.commons.math4.legacy.exception.MathArithmeticException;
import org.apache.commons.math4.legacy.exception.NotPositiveException;
import org.apache.commons.math4.legacy.exception.OutOfRangeException;
import org.apache.commons.math4.legacy.exception.util.LocalizedFormats;
import org.apache.commons.math4.core.jdkmath.JdkMath;
import org.apache.commons.math4.legacy.linear.OpenIntToDoubleHashMap.Iterator;
/**
* This class implements the {@link RealVector} interface with a
* {@link OpenIntToDoubleHashMap} backing store.
* <p>
* Caveat: This implementation assumes that, for any {@code x},
* the equality {@code x * 0d == 0d} holds. But it is is not true for
* {@code NaN}. Moreover, zero entries will lose their sign.
* Some operations (that involve {@code NaN} and/or infinities) may
* thus give incorrect results, like multiplications, divisions or
* functions mapping.
* </p>
* @since 2.0
*/
public class OpenMapRealVector extends SparseRealVector
implements 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.
* 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} method ({@link #append(double)},
* {@link #append(RealVector)}) to gather data into this vector.
*/
public OpenMapRealVector() {
this(0, DEFAULT_ZERO_TOLERANCE);
}
/**
* Construct a vector of zeroes.
*
* @param dimension Size of the vector.
*/
public OpenMapRealVector(int dimension) {
this(dimension, DEFAULT_ZERO_TOLERANCE);
}
/**
* Construct a vector of zeroes, specifying zero tolerance.
*
* @param dimension Size of the vector.
* @param epsilon Tolerance below which 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 Original vector.
* @param resize Amount to add.
*/
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 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 Size of the vector.
* @param expectedSize Expected number of non-zero entries.
* @param epsilon Tolerance below which a value is considered zero.
*/
public OpenMapRealVector(int dimension, int expectedSize, double epsilon) {
virtualSize = dimension;
entries = new OpenIntToDoubleHashMap(expectedSize, 0.0);
this.epsilon = epsilon;
}
/**
* Create from an array.
* Only non-zero entries will be stored.
*
* @param values Set of values to create from.
*/
public OpenMapRealVector(double[] values) {
this(values, DEFAULT_ZERO_TOLERANCE);
}
/**
* Create from an array, specifying zero tolerance.
* Only non-zero entries will be stored.
*
* @param values Set of values to create from.
* @param epsilon Tolerance below which a value is 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 an 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 an array.
* Only non-zero entries will be stored.
*
* @param values Set of values to create from.
* @param epsilon Tolerance below which a value is 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 Instance to copy from.
*/
public OpenMapRealVector(OpenMapRealVector v) {
virtualSize = v.getDimension();
entries = new OpenIntToDoubleHashMap(v.getEntries());
epsilon = v.epsilon;
}
/**
* Generic copy constructor.
*
* @param v 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 the entries of this instance.
*/
private OpenIntToDoubleHashMap getEntries() {
return entries;
}
/**
* Determine if this value is within epsilon of zero.
*
* @param value Value to test
* @return {@code true} if this value is within epsilon to zero,
* {@code false} otherwise.
* @since 2.1
*/
protected boolean isDefaultValue(double value) {
return JdkMath.abs(value) < epsilon;
}
/** {@inheritDoc} */
@Override
public RealVector add(RealVector v)
throws DimensionMismatchException {
checkVectorDimensions(v.getDimension());
if (v instanceof OpenMapRealVector) {
return add((OpenMapRealVector) v);
} else {
return super.add(v);
}
}
/**
* Optimized method to add two OpenMapRealVectors.
* It copies the larger vector, then iterates over the smaller.
*
* @param v Vector to add.
* @return the sum of {@code this} and {@code v}.
* @throws DimensionMismatchException if the dimensions do not match.
*/
public OpenMapRealVector add(OpenMapRealVector v)
throws DimensionMismatchException {
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} 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} */
@Override
public OpenMapRealVector append(RealVector v) {
if (v instanceof OpenMapRealVector) {
return append((OpenMapRealVector) v);
} else {
final OpenMapRealVector res = new OpenMapRealVector(this, v.getDimension());
for (int i = 0; i < v.getDimension(); i++) {
res.setEntry(i + virtualSize, v.getEntry(i));
}
return res;
}
}
/** {@inheritDoc} */
@Override
public OpenMapRealVector append(double d) {
OpenMapRealVector res = new OpenMapRealVector(this, 1);
res.setEntry(virtualSize, d);
return res;
}
/**
* {@inheritDoc}
* @since 2.1
*/
@Override
public OpenMapRealVector copy() {
return new OpenMapRealVector(this);
}
/** {@inheritDoc} */
@Override
public OpenMapRealVector ebeDivide(RealVector v)
throws DimensionMismatchException {
checkVectorDimensions(v.getDimension());
OpenMapRealVector res = new OpenMapRealVector(this);
/*
* MATH-803: it is not sufficient to loop through non zero entries of
* this only. Indeed, if this[i] = 0d and v[i] = 0d, then
* this[i] / v[i] = NaN, and not 0d.
*/
final int n = getDimension();
for (int i = 0; i < n; i++) {
res.setEntry(i, this.getEntry(i) / v.getEntry(i));
}
return res;
}
/** {@inheritDoc} */
@Override
public OpenMapRealVector ebeMultiply(RealVector v)
throws DimensionMismatchException {
checkVectorDimensions(v.getDimension());
OpenMapRealVector res = new OpenMapRealVector(this);
Iterator iter = entries.iterator();
while (iter.hasNext()) {
iter.advance();
res.setEntry(iter.key(), iter.value() * v.getEntry(iter.key()));
}
return res;
}
/** {@inheritDoc} */
@Override
public OpenMapRealVector getSubVector(int index, int n)
throws NotPositiveException, OutOfRangeException {
checkIndex(index);
if (n < 0) {
throw new NotPositiveException(LocalizedFormats.NUMBER_OF_ELEMENTS_SHOULD_BE_POSITIVE, n);
}
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 int getDimension() {
return virtualSize;
}
/**
* Optimized method to compute distance.
*
* @param v Vector to compute distance to.
* @return the distance from {@code this} and {@code v}.
* @throws DimensionMismatchException if the dimensions do not match.
*/
public double getDistance(OpenMapRealVector v)
throws DimensionMismatchException {
checkVectorDimensions(v.getDimension());
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 JdkMath.sqrt(res);
}
/** {@inheritDoc} */
@Override
public double getDistance(RealVector v) throws DimensionMismatchException {
checkVectorDimensions(v.getDimension());
if (v instanceof OpenMapRealVector) {
return getDistance((OpenMapRealVector) v);
} else {
return super.getDistance(v);
}
}
/** {@inheritDoc} */
@Override
public double getEntry(int index) throws OutOfRangeException {
checkIndex(index);
return entries.get(index);
}
/**
* Distance between two vectors.
* This method computes the distance consistent with
* L<sub>1</sub> norm, i.e. the sum of the absolute values of
* elements differences.
*
* @param v Vector to which distance is requested.
* @return distance between this vector and {@code v}.
* @throws DimensionMismatchException if the dimensions do not match.
*/
public double getL1Distance(OpenMapRealVector v)
throws DimensionMismatchException {
checkVectorDimensions(v.getDimension());
double max = 0;
Iterator iter = entries.iterator();
while (iter.hasNext()) {
iter.advance();
double delta = JdkMath.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 = JdkMath.abs(iter.value());
max += JdkMath.abs(delta);
}
}
return max;
}
/** {@inheritDoc} */
@Override
public double getL1Distance(RealVector v)
throws DimensionMismatchException {
checkVectorDimensions(v.getDimension());
if (v instanceof OpenMapRealVector) {
return getL1Distance((OpenMapRealVector) v);
} else {
return super.getL1Distance(v);
}
}
/**
* Optimized method to compute LInfDistance.
*
* @param v Vector to compute distance from.
* @return the LInfDistance.
* @throws DimensionMismatchException if the dimensions do not match.
*/
private double getLInfDistance(OpenMapRealVector v)
throws DimensionMismatchException {
checkVectorDimensions(v.getDimension());
double max = 0;
Iterator iter = entries.iterator();
while (iter.hasNext()) {
iter.advance();
double delta = JdkMath.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) && iter.value() > max) {
max = iter.value();
}
}
return max;
}
/** {@inheritDoc} */
@Override
public double getLInfDistance(RealVector v)
throws DimensionMismatchException {
checkVectorDimensions(v.getDimension());
if (v instanceof OpenMapRealVector) {
return getLInfDistance((OpenMapRealVector) v);
} else {
return super.getLInfDistance(v);
}
}
/** {@inheritDoc} */
@Override
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} */
@Override
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 void setEntry(int index, double value)
throws OutOfRangeException {
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 OutOfRangeException {
checkIndex(index);
checkIndex(index + v.getDimension() - 1);
for (int i = 0; i < v.getDimension(); i++) {
setEntry(i + index, v.getEntry(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 Vector to subtract from {@code this}.
* @return the difference of {@code this} and {@code v}.
* @throws DimensionMismatchException if the dimensions do not match.
*/
public OpenMapRealVector subtract(OpenMapRealVector v)
throws DimensionMismatchException {
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 RealVector subtract(RealVector v)
throws DimensionMismatchException {
checkVectorDimensions(v.getDimension());
if (v instanceof OpenMapRealVector) {
return subtract((OpenMapRealVector) v);
} else {
return super.subtract(v);
}
}
/** {@inheritDoc} */
@Override
public OpenMapRealVector unitVector() throws MathArithmeticException {
OpenMapRealVector res = copy();
res.unitize();
return res;
}
/** {@inheritDoc} */
@Override
public void unitize() throws MathArithmeticException {
double norm = getNorm();
if (isDefaultValue(norm)) {
throw new MathArithmeticException(LocalizedFormats.ZERO_NORM);
}
Iterator iter = entries.iterator();
while (iter.hasNext()) {
iter.advance();
entries.put(iter.key(), iter.value() / norm);
}
}
/** {@inheritDoc} */
@Override
public double[] toArray() {
double[] res = new double[virtualSize];
Iterator iter = entries.iterator();
while (iter.hasNext()) {
iter.advance();
res[iter.key()] = iter.value();
}
return res;
}
/**
* {@inheritDoc}
* 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()}.
*/
@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;
}
/**
* {@inheritDoc}
* 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}.
*/
@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.
* @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} optimized for OpenMap.
* This implementation does not allow arbitrary calls to {@code setIndex}
* since the order in which 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.
* 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} */
@Override
public boolean hasNext() {
return iter.hasNext();
}
/** {@inheritDoc} */
@Override
public Entry next() {
iter.advance();
return current;
}
/** {@inheritDoc} */
@Override
public void remove() {
throw new UnsupportedOperationException("Not supported");
}
}
}