/* * File: SparseVector.java * Authors: Kevin R. Dixon * Company: Sandia National Laboratories * Project: Cognitive Foundry * * Copyright February 21, 2006, Sandia Corporation. Under the terms of Contract * DE-AC04-94AL85000, there is a non-exclusive license for use of this work by * or on behalf of the U.S. Government. Export of this program may require a * license from the United States Government. See CopyrightHistory.txt for * complete details. * */ package gov.sandia.cognition.math.matrix.mtj; import gov.sandia.cognition.annotation.CodeReview; import gov.sandia.cognition.math.matrix.SparseVectorFactory; import gov.sandia.cognition.math.UnivariateScalarFunction; import gov.sandia.cognition.math.matrix.Vector; import gov.sandia.cognition.math.matrix.VectorEntry; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; /** * A vector that only stores the nonzero elements, relies on MTJ's * SparseVector. It has fast traversal by skipping nonzero elements but has slow * indexing. * * @author Kevin R. Dixon * @since 1.0 */ @CodeReview( reviewer="Justin Basilico", date="2006-07-27", changesNeeded=false, comments="Looks good." ) public class SparseVector extends AbstractMTJVector { /** * Creates a new instance of SparseVector with no initial size. * * @param numDimensions Maximum number of entries in the SparseVector. */ protected SparseVector( final int numDimensions) { this(numDimensions, 0); } /** * Creates a new instance of SparseVector with a specified initial size. * * @param numDimensions Maximum number of entries in the SparseVector. * @param initialNonZeros Initial size of the SparseVector. */ protected SparseVector( final int numDimensions, final int initialNonZeros) { this(new no.uib.cipr.matrix.sparse.SparseVector( numDimensions, initialNonZeros)); } /** * Creates a new copy of SparseVector. * * @param internalVector Internal MTJ-based vector to set into this. */ protected SparseVector( final no.uib.cipr.matrix.sparse.SparseVector internalVector) { super(internalVector); } /** * Creates a new copy of SparseVector. * * @param vector Vector to copy into this, will not be modified. */ protected SparseVector( final Vector vector) { this(vector.getDimensionality()); for ( VectorEntry e : vector ) { double value = e.getValue(); if ( value != 0.0 ) { this.setElement(e.getIndex(), value); } } } @Override public int getEntryCount() { return this.getInternalVector().getUsed(); } /** * Gets the number of elements used inside the sparse vector, equals the * allocation size of the vector. * * @return The number of nonzero elements in the SparseVector. */ @Deprecated public int getNumElementsUsed() { return this.getInternalVector().getUsed(); } @Override public void setElement( final int index, final double value) { // Only set a value in a SparseVector if it's different than what's // in there already! // (This is to prevent us from adding zeros into the SparseVector // unnecessarily) double existing = this.getElement( index ); if( existing != value ) { super.setElement( index, value ); } } @Override protected no.uib.cipr.matrix.sparse.SparseVector getInternalVector() { return (no.uib.cipr.matrix.sparse.SparseVector) super.getInternalVector(); } @Override protected void setInternalVector( final no.uib.cipr.matrix.Vector internalVector) { // Force it to be sparse. this.setInternalVector((no.uib.cipr.matrix.sparse.SparseVector) internalVector); } /** * Setter for the internal MTJ vector. * * @param internalVector Internal MTJ-based vector. */ protected void setInternalVector( final no.uib.cipr.matrix.sparse.SparseVector internalVector) { super.setInternalVector(internalVector); } @Override public double euclideanDistanceSquared( final Vector other ) { return this.minus( other ).norm2Squared(); } @Override public SparseRowMatrix outerProduct( final AbstractMTJVector other) { int numRows = this.getDimensionality(); int numColumns = other.getDimensionality(); SparseRowMatrix result = new SparseRowMatrix(numRows, numColumns); for ( VectorEntry e : this ) { int i = e.getIndex(); for ( VectorEntry o : other ) { int j = o.getIndex(); result.setElement(i, j, e.getValue() * o.getValue()); } } return result; } /** * Prints the SparseVector in "Index: Value" format. * * @return String containing the representation of the SparseVector */ @Override public String toString() { final StringBuffer result = new StringBuffer(); for ( VectorEntry e : this ) { result.append("(" + e.getIndex() + "): " + e.getValue() + "\n"); } return result.toString(); } /** * Compacts the SparseVector, getting rid of any zero'ed elements. */ public void compact() { this.getInternalVector().compact(); } @Override public SparseVector stack( final Vector other) { int d1 = this.getDimensionality(); int d2 = other.getDimensionality(); SparseVector stacked = new SparseVector(d1 + d2); for ( VectorEntry e : this ) { stacked.setElement(e.getIndex(), e.getValue()); } for ( VectorEntry e : other ) { stacked.setElement(d1 + e.getIndex(), e.getValue()); } return stacked; } @Override public void transformNonZerosEquals( final UnivariateScalarFunction function) { final no.uib.cipr.matrix.sparse.SparseVector internal = this.getInternalVector(); final double[] values = internal.getData(); final int used = internal.getUsed(); for (int i = 0; i < used; i++) { final double value = values[i]; if (value != 0.0) { values[i] = function.evaluate(value); } } } @Override public void transformNonZerosEquals( final IndexValueTransform function) { final no.uib.cipr.matrix.sparse.SparseVector internal = this.getInternalVector(); final int[] indices = internal.getRawIndex(); final double[] values = internal.getData(); final int used = internal.getUsed(); for (int i = 0; i < used; i++) { final double value = values[i]; if (value != 0.0) { values[i] = function.transform(indices[i], value); } } } @Override public void forEachElement( final IndexValueConsumer consumer) { // This is an optimized version of the method so that it doesn't end // up doing a d * log(nnz) cost to iterate, even though it is a dense // method. final no.uib.cipr.matrix.sparse.SparseVector internal = this.getInternalVector(); final int[] indices = internal.getRawIndex(); final double[] values = internal.getData(); final int used = internal.getUsed(); final int dimensionality = internal.size(); int i = 0; int index = 0; while (i < used && index < dimensionality) { final double value; if (index < indices[i]) { // Index is for a missing (zero) value. value = 0.0; } else { // Get the value at the sparse index. value = values[i]; i++; } consumer.consume(index, value); index++; } // Passed the last used element fill in the rest in zeros. while (index < dimensionality) { consumer.consume(index, 0.0); index++; } } @Override public void forEachEntry( final IndexValueConsumer consumer) { final no.uib.cipr.matrix.sparse.SparseVector internal = this.getInternalVector(); final int[] indices = internal.getRawIndex(); final double[] values = internal.getData(); final int used = internal.getUsed(); for (int i = 0; i < used; i++) { consumer.consume(indices[i], values[i]); } } @Override public void forEachNonZero( final IndexValueConsumer consumer) { final no.uib.cipr.matrix.sparse.SparseVector internal = this.getInternalVector(); final int[] indices = internal.getRawIndex(); final double[] values = internal.getData(); final int used = internal.getUsed(); for (int i = 0; i < used; i++) { final double value = values[i]; if (value != 0.0) { consumer.consume(indices[i], value); } } } @Override public boolean isSparse() { return true; } @Override public double sum() { final double[] values = this.getInternalVector().getRawData(); double result = 0.0; for (final double value : values) { result += value; } return result; } @Override public double getMinValue() { double min = this.getEntryCount() < this.getDimensionality() ? 0.0 : Double.POSITIVE_INFINITY; for (final double value : this.getInternalVector().getRawData()) { if (value < min) { min = value; } } return min; } @Override public double getMaxValue() { double max = this.getEntryCount() < this.getDimensionality() ? 0.0 : Double.NEGATIVE_INFINITY; for (final double value : this.getInternalVector().getRawData()) { if (value > max) { max = value; } } return max; } @Override public int countNonZeros() { int result = 0; for (final double value : this.getInternalVector().getRawData()) { if (value != 0.0) { result++; } } return result; } /** * This method provides custom serialization for the class since the MTJ * class does not implement Serializable. * * @param out The ObjectOutputStream to write this object to. * @throws java.io.IOException If there is an error with the stream. */ private void writeObject( ObjectOutputStream out) throws IOException { // First compact the SparseVector this.compact(); // Do the default stuff. out.defaultWriteObject(); // Get all the data to write. int dimensionality = this.getDimensionality(); int[] index = this.getInternalVector().getIndex(); double[] data = this.getInternalVector().getData(); // Manually serialize the super class. out.writeObject( dimensionality ); out.writeObject( index ); out.writeObject( data ); } @Override public SparseVectorFactory<?> getVectorFactory() { return SparseVectorFactoryMTJ.INSTANCE; } /** * This method provides custom deserialization for the class since the MTJ * class does not implement Serializable. * * @param in The ObjectInputStream to read the object from. * @throws java.io.IOException If there is an error reading the object. * @throws java.lang.ClassNotFoundException If a class cannot be found. */ private void readObject( ObjectInputStream in) throws IOException, ClassNotFoundException { // First do the default stuff. in.defaultReadObject(); // Next read in the data. int dimensionality = (Integer) in.readObject(); int[] index = (int[]) in.readObject(); double[] data = (double[]) in.readObject(); // Set the internal vector. boolean deepCopy = false; this.setInternalVector( new no.uib.cipr.matrix.sparse.SparseVector( dimensionality, index, data, deepCopy)); this.compact(); } @Override public SparseVector subVector( final int minIndex, final int maxIndex) { int M = maxIndex - minIndex + 1; SparseVector subvector = new SparseVector( M ); for( int i = 0; i < M; i++ ) { double value = this.getElement(i + minIndex); if( value != 0.0 ) { subvector.setElement( i, value ); } } return subvector; } }