/*
 * 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.util.OpenIntToDoubleHashMap;
import org.apache.commons.math.util.OpenIntToDoubleHashMap.Iterator;

/**
 * This class implements the {@link RealVector} interface with a {@link OpenIntToDoubleHashMap} backing store.
 * @version $Revision: 728186 $ $Date$
 * @since 2.0
*/
public class OpenMapRealVector implements SparseRealVector, Serializable {

    /** Serializable version identifier. */
    private static final long serialVersionUID = 8772222695580707260L;

    /** Default Tolerance for having a value considered zero. */
    public static final double DEFAULT_ZERO_TOLERANCE = 1.0e-12;

    /** Entries of the vector. */
    private final OpenIntToDoubleHashMap entries;

    /** Dimension of the vector. */
    private final int virtualSize;

    /** Tolerance for having a value considered zero. */
    private 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.getEpsilon();
    }

    /**
     * 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 (!isZero(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 (!isZero(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.getEpsilon();
    }

    /**
     * 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 (!isZero(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 zero.
     * @param value The value to test
     * @return <code>true</code> if this value is zero, <code>false</code> otherwise
     */
    protected boolean isZero(double value) {
        return value > -epsilon && value < epsilon;
    }

    /**
     * Get the tolerance for having a value considered zero.
     * @return The test range for testing if a value is zero
     */
    public double getEpsilon() {
        return epsilon;
    }

    /**
     * Set the tolerance for having a value considered zero.
     * @param epsilon The test range for testing if a value is zero
     */
    public void setEpsilon(double epsilon) {
        this.epsilon = epsilon;
    }

    /** {@inheritDoc} */
    public OpenMapRealVector add(RealVector v) throws IllegalArgumentException {
        checkVectorDimensions(v.getDimension());
        if (v instanceof OpenMapRealVector) {
            return add((OpenMapRealVector) v);
        }
        return add(v.getData());
    }

    /**
     * Optimized method to add two OpenMapRealVectors.
     * @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());
        OpenMapRealVector res = (OpenMapRealVector)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} */
    public OpenMapRealVector add(double[] v) throws IllegalArgumentException {
        checkVectorDimensions(v.length);
        OpenMapRealVector res = new OpenMapRealVector(getDimension());
        for (int i = 0; i < v.length; i++) {
            res.setEntry(i, v[i] + getEntry(i));
        }
        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} */
    public OpenMapRealVector copy() {
        return new OpenMapRealVector(this);
    }

    /** {@inheritDoc} */
    public double dotProduct(RealVector v) throws IllegalArgumentException {
        checkVectorDimensions(v.getDimension());
        double res = 0;
        Iterator iter = entries.iterator();
        while (iter.hasNext()) {
            iter.advance();
            res += v.getEntry(iter.key()) * iter.value();
        }
        return res;
    }

    /** {@inheritDoc} */
    public double dotProduct(double[] v) throws IllegalArgumentException {
        checkVectorDimensions(v.length);
        double res = 0;
        Iterator iter = entries.iterator();
        while (iter.hasNext()) {
            int idx = iter.key();
            double value = 0;
            if (idx < v.length) {
                value = v[idx];
            }
            res += value * iter.value();
        }
        return res;
    }

    /** {@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} */
    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} */
    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} */
    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 Math.sqrt(res);
    }

    /** {@inheritDoc} */
    public double getDistance(RealVector v) throws IllegalArgumentException {
        checkVectorDimensions(v.getDimension());
        if (v instanceof OpenMapRealVector) {
            return getDistance((OpenMapRealVector) v);
        }
        return getDistance(v.getData());
    }

    /** {@inheritDoc} */
    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 Math.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 = Math.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 = Math.abs(iter.value());
                max +=  Math.abs(delta);
            }
        }
        return max;
    }

    /** {@inheritDoc} */
    public double getL1Distance(RealVector v) throws IllegalArgumentException {
        checkVectorDimensions(v.getDimension());
        if (v instanceof OpenMapRealVector) {
            return getL1Distance((OpenMapRealVector) v);
        }
        return getL1Distance(v.getData());
    }

    /** {@inheritDoc} */
    public double getL1Distance(double[] v) throws IllegalArgumentException {
        checkVectorDimensions(v.length);
        double max = 0;
        for (int i = 0; i < v.length; i++) {
            double delta = Math.abs(getEntry(i) - v[i]);
            max += delta;
        }
        return max;
    }

    /** {@inheritDoc} */
    public double getL1Norm() {
        double res = 0;
        Iterator iter = entries.iterator();
        while (iter.hasNext()) {
            iter.advance();
            res += Math.abs(iter.value());
        }
        return res;
    }

    /**
     * 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 = Math.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} */
    public double getLInfDistance(RealVector v) throws IllegalArgumentException {
        checkVectorDimensions(v.getDimension());
        if (v instanceof OpenMapRealVector) {
            return getLInfDistance((OpenMapRealVector) v);
        }
        return getLInfDistance(v.getData());
    }

    /** {@inheritDoc} */
    public double getLInfDistance(double[] v) throws IllegalArgumentException {
        checkVectorDimensions(v.length);
        double max = 0;
        for (int i = 0; i < v.length; i++) {
            double delta = Math.abs(getEntry(i) - v[i]);
            if (delta > max) {
                max = delta;
            }
        }
        return max;
    }

    /** {@inheritDoc} */
    public double getLInfNorm() {
        double max = 0;
        Iterator iter = entries.iterator();
        while (iter.hasNext()) {
            iter.advance();
            max += iter.value();
        }
        return max;
    }

    /** {@inheritDoc} */
    public double getNorm() {
        double res = 0;
        Iterator iter = entries.iterator();
        while (iter.hasNext()) {
            iter.advance();
            res += iter.value() * iter.value();
        }
        return Math.sqrt(res);
    }

    /** {@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} */
    public OpenMapRealVector mapAbs() {
        return copy().mapAbsToSelf();
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapAbsToSelf() {
        Iterator iter = entries.iterator();
        while (iter.hasNext()) {
            iter.advance();
            entries.put(iter.key(), Math.abs(iter.value()));
        }
        return this;
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapAcos() {
        return copy().mapAcosToSelf();
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapAcosToSelf() {
        for (int i = 0; i < virtualSize; i++) {
            setEntry(i, Math.acos(getEntry(i)));
        }
        return this;
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapAdd(double d) {
        return copy().mapAddToSelf(d);
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapAddToSelf(double d) {
        for (int i = 0; i < virtualSize; i++) {
            setEntry(i, getEntry(i) + d);
        }
        return this;
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapAsin() {
        return copy().mapAsinToSelf();
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapAsinToSelf() {
        Iterator iter = entries.iterator();
        while (iter.hasNext()) {
            iter.advance();
            entries.put(iter.key(), Math.asin(iter.value()));
        }
        return this;
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapAtan() {
        return copy().mapAtanToSelf();
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapAtanToSelf() {
        Iterator iter = entries.iterator();
        while (iter.hasNext()) {
            iter.advance();
            entries.put(iter.key(), Math.atan(iter.value()));
        }
        return this;
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapCbrt() {
        return copy().mapCbrtToSelf();
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapCbrtToSelf() {
        Iterator iter = entries.iterator();
        while (iter.hasNext()) {
            iter.advance();
            entries.put(iter.key(), Math.cbrt(iter.value()));
        }
        return this;
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapCeil() {
        return copy().mapCeilToSelf();
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapCeilToSelf() {
        Iterator iter = entries.iterator();
        while (iter.hasNext()) {
            iter.advance();
            entries.put(iter.key(), Math.ceil(iter.value()));
        }
        return this;
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapCos() {
        return copy().mapCosToSelf();
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapCosToSelf() {
        for (int i = 0; i < virtualSize; i++) {
            setEntry(i, Math.cos(getEntry(i)));
        }
        return this;
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapCosh() {
        return copy().mapCoshToSelf();
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapCoshToSelf() {
        for (int i = 0; i < virtualSize; i++) {
            setEntry(i, Math.cosh(getEntry(i)));
        }
        return this;
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapDivide(double d) {
        return copy().mapDivideToSelf(d);
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapDivideToSelf(double d) {
        Iterator iter = entries.iterator();
        while (iter.hasNext()) {
            iter.advance();
            entries.put(iter.key(), iter.value() / d);
        }
        return this;
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapExp() {
        return copy().mapExpToSelf();
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapExpToSelf() {
        for (int i = 0; i < virtualSize; i++) {
            entries.put(i, Math.exp(entries.get(i)));
        }
        return this;
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapExpm1() {
        return copy().mapExpm1ToSelf();
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapExpm1ToSelf() {
        Iterator iter = entries.iterator();
        while (iter.hasNext()) {
            iter.advance();
            entries.put(iter.key(), Math.expm1(iter.value()));
        }
        return this;
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapFloor() {
        return copy().mapFloorToSelf();
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapFloorToSelf() {
        Iterator iter = entries.iterator();
        while (iter.hasNext()) {
            iter.advance();
            entries.put(iter.key(), Math.floor(iter.value()));
        }
        return this;
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapInv() {
        return copy().mapInvToSelf();
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapInvToSelf() {
        for (int i = 0; i < virtualSize; i++) {
            setEntry(i, 1.0/getEntry(i));
        }
        return this;
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapLog() {
        return copy().mapLogToSelf();
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapLog10() {
        return copy().mapLog10ToSelf();
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapLog10ToSelf() {
        for (int i = 0; i < virtualSize; i++) {
            setEntry(i, Math.log10(getEntry(i)));
        }
        return this;
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapLog1p() {
        return copy().mapLog1pToSelf();
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapLog1pToSelf() {
        Iterator iter = entries.iterator();
        while (iter.hasNext()) {
            iter.advance();
            entries.put(iter.key(), Math.log1p(iter.value()));
        }
        return this;
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapLogToSelf() {
        for (int i = 0; i < virtualSize; i++) {
            setEntry(i, Math.log(getEntry(i)));
        }
       return this;
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapMultiply(double d) {
        return copy().mapMultiplyToSelf(d);
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapMultiplyToSelf(double d) {
        Iterator iter = entries.iterator();
        while (iter.hasNext()) {
            iter.advance();
            entries.put(iter.key(), iter.value() * d);
        }
        return this;
    }
    /** {@inheritDoc} */
    public OpenMapRealVector mapPow(double d) {
        return copy().mapPowToSelf(d);
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapPowToSelf(double d) {
        Iterator iter = entries.iterator();
        while (iter.hasNext()) {
            iter.advance();
            entries.put(iter.key(), Math.pow(iter.value(), d));
        }
        return this;
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapRint() {
        return copy().mapRintToSelf();
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapRintToSelf() {
        Iterator iter = entries.iterator();
        while (iter.hasNext()) {
            iter.advance();
            entries.put(iter.key(), Math.rint(iter.value()));
        }
        return this;
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapSignum() {
        return copy().mapSignumToSelf();
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapSignumToSelf() {
        Iterator iter = entries.iterator();
        while (iter.hasNext()) {
            iter.advance();
            entries.put(iter.key(), Math.signum(iter.value()));
        }
        return this;
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapSin() {
        return copy().mapSinToSelf();
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapSinToSelf() {
        Iterator iter = entries.iterator();
        while (iter.hasNext()) {
            iter.advance();
            entries.put(iter.key(), Math.sin(iter.value()));
        }
        return this;
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapSinh() {
        return copy().mapSinhToSelf();
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapSinhToSelf() {

        Iterator iter = entries.iterator();
        while (iter.hasNext()) {
            iter.advance();
            entries.put(iter.key(), Math.sinh(iter.value()));
        }
        return this;
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapSqrt() {
        return copy().mapSqrtToSelf();
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapSqrtToSelf() {
        Iterator iter = entries.iterator();
        while (iter.hasNext()) {
            iter.advance();
            entries.put(iter.key(), Math.sqrt(iter.value()));
        }
        return this;
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapSubtract(double d) {
        return copy().mapSubtractToSelf(d);
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapSubtractToSelf(double d) {
        return mapAddToSelf(-d);
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapTan() {
        return copy().mapTanToSelf();
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapTanToSelf() {
        Iterator iter = entries.iterator();
        while (iter.hasNext()) {
            iter.advance();
            entries.put(iter.key(), Math.tan(iter.value()));
        }
        return this;
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapTanh() {
        return copy().mapTanhToSelf();
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapTanhToSelf() {
        Iterator iter = entries.iterator();
        while (iter.hasNext()) {
            iter.advance();
            entries.put(iter.key(), Math.tanh(iter.value()));
        }
        return this;
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapUlp() {
        return copy().mapUlpToSelf();
    }

    /** {@inheritDoc} */
    public OpenMapRealVector mapUlpToSelf() {
        Iterator iter = entries.iterator();
        while (iter.hasNext()) {
            iter.advance();
            entries.put(iter.key(), Math.ulp(iter.value()));
        }
        return this;
    }

    /**
     * Optimized method to compute the outer product.
     * @param v The vector to comput the outer product on
     * @return The outer product of <code>this</code> and <code>v</code>
     * @throws IllegalArgumentException If the dimensions don't match
     */
    public OpenMapRealMatrix outerproduct(OpenMapRealVector v) throws IllegalArgumentException{
        checkVectorDimensions(v.getDimension());
        OpenMapRealMatrix res = new OpenMapRealMatrix(virtualSize, virtualSize);
        Iterator iter = entries.iterator();
        while (iter.hasNext()) {
            iter.advance();
            Iterator iter2 = v.getEntries().iterator();
            while (iter2.hasNext()) {
                iter2.advance();
                res.setEntry(iter.key(), iter2.key(), iter.value()*iter2.value());
            }
        }
        return res;
    }

    /** {@inheritDoc} */
    public RealMatrix outerProduct(RealVector v)
            throws IllegalArgumentException {
        checkVectorDimensions(v.getDimension());
        if (v instanceof OpenMapRealVector) {
            return outerproduct((OpenMapRealVector)v);
        }
        RealMatrix res = new OpenMapRealMatrix(virtualSize, virtualSize);
        Iterator iter = entries.iterator();
        while (iter.hasNext()) {
            iter.advance();
            int row = iter.key();
            for (int col = 0; col < virtualSize; col++) {
                res.setEntry(row, col, iter.value()*v.getEntry(col));
            }
        }
        return res;
    }

    /** {@inheritDoc} */
    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} */
    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 (!isZero(value)) {
            entries.put(index, value);
        } else if (entries.containsKey(index)) {
            entries.remove(index);
        }
    }

    /** {@inheritDoc} */
    public void setSubVector(int index, RealVector v) throws MatrixIndexException {
        checkIndex(index);
        checkIndex(index + v.getDimension() - 1);
        setSubVector(index, v.getData());
    }

    /** {@inheritDoc} */
    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} */
    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 = (OpenMapRealVector)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} */
    public OpenMapRealVector subtract(RealVector v) throws IllegalArgumentException {
        checkVectorDimensions(v.getDimension());
        if (v instanceof OpenMapRealVector) {
            return subtract((OpenMapRealVector) v);
        }
        return subtract(v.getData());
    }

    /** {@inheritDoc} */
    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} */
    public OpenMapRealVector unitVector() {
        OpenMapRealVector res = copy();
        res.unitize();
        return res;
    }

    /** {@inheritDoc} */
    public void unitize() {
        double norm = getNorm();
        if (isZero(norm)) {
            throw  MathRuntimeException.createArithmeticException("cannot normalize a zero norm vector");
        }
        Iterator iter = entries.iterator();
        while (iter.hasNext()) {
            iter.advance();
            entries.put(iter.key(), iter.value() / norm);
        }

    }

    /**
     * Check if an index is valid.
     *
     * @param index
     *            index to check
     * @exception MatrixIndexException
     *                if index is not valid
     */
    private void checkIndex(final int index) throws MatrixIndexException {
        if (index < 0 || index >= getDimension()) {
            throw new MatrixIndexException(
                    "index {0} out of allowed range [{1}, {2}]",
                    index, 0, getDimension() - 1);
        }
    }

    /**
     * Check if instance dimension is equal to some expected value.
     *
     * @param n
     *            expected dimension.
     * @exception IllegalArgumentException
     *                if the dimension is inconsistent with vector size
     */
    protected void checkVectorDimensions(int n) throws IllegalArgumentException {
        if (getDimension() != n) {
            throw MathRuntimeException.createIllegalArgumentException(
                    "vector length mismatch: got {0} but expected {1}",
                    getDimension(), n);
        }
    }

    /** {@inheritDoc} */
    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 == null) {
            return false;
        }
        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.
     */
    public double getSparcity() {
        return (double)entries.size()/(double)getDimension();
    }

}
