You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mahout.apache.org by td...@apache.org on 2011/09/01 13:28:14 UTC

svn commit: r1164010 - in /mahout/trunk/math/src: main/java/org/apache/mahout/math/ test/java/org/apache/mahout/math/

Author: tdunning
Date: Thu Sep  1 11:28:14 2011
New Revision: 1164010

URL: http://svn.apache.org/viewvc?rev=1164010&view=rev
Log:
MAHOUT-790 - Add some vector and matrix types to simplify certain manipulations.

Added:
    mahout/trunk/math/src/main/java/org/apache/mahout/math/ConstantVector.java
    mahout/trunk/math/src/main/java/org/apache/mahout/math/DiagonalMatrix.java
    mahout/trunk/math/src/main/java/org/apache/mahout/math/PermutedVectorView.java
    mahout/trunk/math/src/main/java/org/apache/mahout/math/PivotedMatrix.java
    mahout/trunk/math/src/test/java/org/apache/mahout/math/PermutedVectorViewTest.java
    mahout/trunk/math/src/test/java/org/apache/mahout/math/PivotedMatrixTest.java

Added: mahout/trunk/math/src/main/java/org/apache/mahout/math/ConstantVector.java
URL: http://svn.apache.org/viewvc/mahout/trunk/math/src/main/java/org/apache/mahout/math/ConstantVector.java?rev=1164010&view=auto
==============================================================================
--- mahout/trunk/math/src/main/java/org/apache/mahout/math/ConstantVector.java (added)
+++ mahout/trunk/math/src/main/java/org/apache/mahout/math/ConstantVector.java Thu Sep  1 11:28:14 2011
@@ -0,0 +1,125 @@
+package org.apache.mahout.math;
+
+import com.google.common.collect.AbstractIterator;
+
+import java.util.Iterator;
+
+/**
+ * Implements a vector with all the same values.
+ */
+public class ConstantVector extends AbstractVector {
+  private double value;
+
+  public ConstantVector(double value, int size) {
+    super(size);
+    this.value = value;
+  }
+
+  /**
+   * Subclasses must override to return an appropriately sparse or dense result
+   *
+   * @param rows    the row cardinality
+   * @param columns the column cardinality
+   * @return a Matrix
+   */
+  @Override
+  protected Matrix matrixLike(int rows, int columns) {
+    return new DenseMatrix(rows, columns);
+  }
+
+  /**
+   * @return true iff this implementation should be considered dense -- that it explicitly represents
+   *         every value
+   */
+  @Override
+  public boolean isDense() {
+    return true;
+  }
+
+  /**
+   * @return true iff this implementation should be considered to be iterable in index order in an
+   *         efficient way. In particular this implies that {@link #iterator()} and {@link
+   *         #iterateNonZero()} return elements in ascending order by index.
+   */
+  @Override
+  public boolean isSequentialAccess() {
+    return true;
+  }
+
+  /**
+   * Iterates over all elements <p/> * NOTE: Implementations may choose to reuse the Element returned
+   * for performance reasons, so if you need a copy of it, you should call {@link #getElement(int)}
+   * for the given index
+   *
+   * @return An {@link java.util.Iterator} over all elements
+   */
+  @Override
+  public Iterator<Element> iterator() {
+    return new AbstractIterator<Element>() {
+      int i = 0;
+      int n = size();
+      @Override
+      protected Element computeNext() {
+        if (i < n) {
+          return new LocalElement(i++);
+        } else {
+          return endOfData();
+        }
+      }
+    };
+  }
+
+  /**
+   * Iterates over all non-zero elements. <p/> NOTE: Implementations may choose to reuse the Element
+   * returned for performance reasons, so if you need a copy of it, you should call {@link
+   * #getElement(int)} for the given index
+   *
+   * @return An {@link java.util.Iterator} over all non-zero elements
+   */
+  @Override
+  public Iterator<Element> iterateNonZero() {
+    return iterator();
+  }
+
+  /**
+   * Return the value at the given index, without checking bounds
+   *
+   * @param index an int index
+   * @return the double at the index
+   */
+  @Override
+  public double getQuick(int index) {
+    return value;
+  }
+
+  /**
+   * Return an empty vector of the same underlying class as the receiver
+   *
+   * @return a Vector
+   */
+  @Override
+  public Vector like() {
+    return new DenseVector(size());
+  }
+
+  /**
+   * Set the value at the given index, without checking bounds
+   *
+   * @param index an int index into the receiver
+   * @param value a double value to set
+   */
+  @Override
+  public void setQuick(int index, double value) {
+    throw new UnsupportedOperationException("Can't set a value in a constant matrix");
+  }
+
+  /**
+   * Return the number of values in the recipient
+   *
+   * @return an int
+   */
+  @Override
+  public int getNumNondefaultElements() {
+    return size();
+  }
+}

Added: mahout/trunk/math/src/main/java/org/apache/mahout/math/DiagonalMatrix.java
URL: http://svn.apache.org/viewvc/mahout/trunk/math/src/main/java/org/apache/mahout/math/DiagonalMatrix.java?rev=1164010&view=auto
==============================================================================
--- mahout/trunk/math/src/main/java/org/apache/mahout/math/DiagonalMatrix.java (added)
+++ mahout/trunk/math/src/main/java/org/apache/mahout/math/DiagonalMatrix.java Thu Sep  1 11:28:14 2011
@@ -0,0 +1,153 @@
+package org.apache.mahout.math;
+
+/**
+ * Created by IntelliJ IDEA. User: tdunning Date: 8/9/11 Time: 10:36 PM To change this template use
+ * File | Settings | File Templates.
+ */
+public class DiagonalMatrix extends AbstractMatrix {
+  private Vector diagonal;
+
+  public DiagonalMatrix(Vector values) {
+    this.diagonal = values;
+    super.cardinality[0] = values.size();
+    super.cardinality[1] = values.size();
+  }
+
+  public DiagonalMatrix(Matrix values) {
+    this(values.viewDiagonal());
+  }
+
+  public DiagonalMatrix(double value, int size) {
+    this(new ConstantVector(value, size));
+  }
+
+  public DiagonalMatrix(double[] values) {
+    this.diagonal = new DenseVector(values);
+  }
+
+  public static DiagonalMatrix identity(int size) {
+    return new DiagonalMatrix(1, size);
+  }
+
+  @Override
+  public Matrix assignColumn(int column, Vector other) {
+    throw new UnsupportedOperationException("Can't assign a column to a diagonal matrix");
+  }
+
+  /**
+   * Assign the other vector values to the row of the receiver
+   *
+   * @param row   the int row to assign
+   * @param other a Vector
+   * @return the modified receiver
+   * @throws CardinalityException if the cardinalities differ
+   */
+  @Override
+  public Matrix assignRow(int row, Vector other) {
+    throw new UnsupportedOperationException("Can't assign a row to a diagonal matrix");
+  }
+
+  /**
+   * Return the column at the given index
+   *
+   * @param column an int column index
+   * @return a Vector at the index
+   * @throws IndexException if the index is out of bounds
+   */
+  @Override
+  public Vector getColumn(int column) {
+    return new MatrixVectorView(this, 0, column, 1, 0);
+  }
+
+  /**
+   * Return the row at the given index
+   *
+   * @param row an int row index
+   * @return a Vector at the index
+   * @throws IndexException if the index is out of bounds
+   */
+  @Override
+  public Vector getRow(int row) {
+    return new MatrixVectorView(this, row, 0, 0, 1);
+  }
+
+  /**
+   * Provides a view of the diagonal of a matrix.
+   */
+  @Override
+  public Vector viewDiagonal() {
+    return this.diagonal;
+  }
+
+  /**
+   * Return the value at the given location, without checking bounds
+   *
+   * @param row    an int row index
+   * @param column an int column index
+   * @return the double at the index
+   */
+  @Override
+  public double getQuick(int row, int column) {
+    if (row == column) {
+      return diagonal.get(row);
+    } else {
+      return 0;
+    }
+  }
+
+  /**
+   * Return an empty matrix of the same underlying class as the receiver
+   *
+   * @return a Matrix
+   */
+  @Override
+  public Matrix like() {
+    return new SparseRowMatrix(size());
+  }
+
+  /**
+   * Returns an empty matrix of the same underlying class as the receiver and of the specified
+   * size.
+   *
+   * @param rows    the int number of rows
+   * @param columns the int number of columns
+   */
+  @Override
+  public Matrix like(int rows, int columns) {
+    return new SparseRowMatrix(new int[]{rows, columns});
+  }
+
+  @Override
+  public void setQuick(int row, int column, double value) {
+    if (row == column) {
+      diagonal.set(row, value);
+    } else {
+      throw new UnsupportedOperationException("Can't set off-diagonal element");
+    }
+  }
+
+  /**
+   * Return the number of values in the recipient
+   *
+   * @return an int[2] containing [row, column] count
+   */
+  @Override
+  public int[] getNumNondefaultElements() {
+    throw new UnsupportedOperationException("Don't understand how to implement this");
+  }
+
+  /**
+   * Return a new matrix containing the subset of the recipient
+   *
+   * @param offset an int[2] offset into the receiver
+   * @param size   the int[2] size of the desired result
+   * @return a new Matrix that is a view of the original
+   * @throws CardinalityException if the length is greater than the cardinality of the receiver
+   * @throws IndexException       if the offset is negative or the offset+length is outside of the
+   *                              receiver
+   */
+  @Override
+  public Matrix viewPart(int[] offset, int[] size) {
+    return new MatrixView(this, offset, size);
+  }
+}

Added: mahout/trunk/math/src/main/java/org/apache/mahout/math/PermutedVectorView.java
URL: http://svn.apache.org/viewvc/mahout/trunk/math/src/main/java/org/apache/mahout/math/PermutedVectorView.java?rev=1164010&view=auto
==============================================================================
--- mahout/trunk/math/src/main/java/org/apache/mahout/math/PermutedVectorView.java (added)
+++ mahout/trunk/math/src/main/java/org/apache/mahout/math/PermutedVectorView.java Thu Sep  1 11:28:14 2011
@@ -0,0 +1,192 @@
+package org.apache.mahout.math;
+
+import com.google.common.collect.AbstractIterator;
+
+import java.util.Iterator;
+
+/**
+ * Provides a permuted view of a vector.
+ */
+public class PermutedVectorView extends AbstractVector {
+  private Vector vector;            // the vector containing the data
+  private int[] pivot;              // convert from external index to internal
+  private int[] unpivot;            // convert from internal index to external
+
+  public PermutedVectorView(Vector vector, int[] pivot, int[] unpivot) {
+    super(vector.size());
+    this.vector = vector;
+    this.pivot = pivot;
+    this.unpivot = unpivot;
+  }
+
+  public PermutedVectorView(Vector vector, int[] pivot) {
+    this(vector, pivot, reversePivotPermutation(pivot));
+  }
+
+  private static int[] reversePivotPermutation(int[] pivot) {
+    int[] unpivot1 = new int[pivot.length];
+    for (int i = 0; i < pivot.length; i++) {
+      unpivot1[pivot[i]] = i;
+    }
+    return unpivot1;
+  }
+
+  /**
+   * Subclasses must override to return an appropriately sparse or dense result
+   *
+   * @param rows    the row cardinality
+   * @param columns the column cardinality
+   * @return a Matrix
+   */
+  @Override
+  protected Matrix matrixLike(int rows, int columns) {
+    if (vector.isDense()) {
+      return new DenseMatrix(rows, columns);
+    } else {
+      return new SparseRowMatrix(new int[] {rows, columns});
+    }
+  }
+
+  /**
+   * @return true iff this implementation should be considered dense -- that it explicitly
+   *         represents every value
+   */
+  @Override
+  public boolean isDense() {
+    return vector.isDense();
+  }
+
+  /**
+   * @return true iff this implementation should be considered to be iterable in index order in an
+   *         efficient way. In particular this implies that {@link #iterator()} and {@link
+   *         #iterateNonZero()} return elements in ascending order by index.
+   */
+  @Override
+  public boolean isSequentialAccess() {
+    return vector.isSequentialAccess();
+  }
+
+  /**
+   * Iterates over all elements <p/> * NOTE: Implementations may choose to reuse the Element
+   * returned for performance reasons, so if you need a copy of it, you should call {@link
+   * #getElement(int)} for the given index
+   *
+   * @return An {@link java.util.Iterator} over all elements
+   */
+  @Override
+  public Iterator<Element> iterator() {
+    return new AbstractIterator<Element>() {
+      Iterator<Element> i = vector.iterator();
+
+      @Override
+      protected Vector.Element computeNext() {
+        if (i.hasNext()) {
+          final Element x = i.next();
+          return new Element() {
+            int index = unpivot[x.index()];
+
+            @Override
+            public double get() {
+              return x.get();
+            }
+
+            @Override
+            public int index() {
+              return index;
+            }
+
+            @Override
+            public void set(double value) {
+              x.set(value);
+            }
+          };
+        } else {
+          return endOfData();
+        }
+      }
+    };
+  }
+
+  /**
+   * Iterates over all non-zero elements. <p/> NOTE: Implementations may choose to reuse the Element
+   * returned for performance reasons, so if you need a copy of it, you should call {@link
+   * #getElement(int)} for the given index
+   *
+   * @return An {@link java.util.Iterator} over all non-zero elements
+   */
+  @Override
+  public Iterator<Element> iterateNonZero() {
+    return new AbstractIterator<Element>() {
+      Iterator<Element> i = vector.iterateNonZero();
+
+      @Override
+      protected Vector.Element computeNext() {
+        if (i.hasNext()) {
+          final Element x = i.next();
+          return new Element() {
+            int index = unpivot[x.index()];
+
+            @Override
+            public double get() {
+              return x.get();
+            }
+
+            @Override
+            public int index() {
+              return index;
+            }
+
+            @Override
+            public void set(double value) {
+              x.set(value);
+            }
+          };
+        } else {
+          return endOfData();
+        }
+      }
+    };
+  }
+
+  /**
+   * Return the value at the given index, without checking bounds
+   *
+   * @param index an int index
+   * @return the double at the index
+   */
+  @Override
+  public double getQuick(int index) {
+    return vector.getQuick(pivot[index]);
+  }
+
+  /**
+   * Return an empty vector of the same underlying class as the receiver
+   *
+   * @return a Vector
+   */
+  @Override
+  public Vector like() {
+    return vector.like();
+  }
+
+  /**
+   * Set the value at the given index, without checking bounds
+   *
+   * @param index an int index into the receiver
+   * @param value a double value to set
+   */
+  @Override
+  public void setQuick(int index, double value) {
+    vector.setQuick(pivot[index], value);
+  }
+
+  /**
+   * Return the number of values in the recipient
+   *
+   * @return an int
+   */
+  @Override
+  public int getNumNondefaultElements() {
+    return vector.getNumNondefaultElements();
+  }
+}

Added: mahout/trunk/math/src/main/java/org/apache/mahout/math/PivotedMatrix.java
URL: http://svn.apache.org/viewvc/mahout/trunk/math/src/main/java/org/apache/mahout/math/PivotedMatrix.java?rev=1164010&view=auto
==============================================================================
--- mahout/trunk/math/src/main/java/org/apache/mahout/math/PivotedMatrix.java (added)
+++ mahout/trunk/math/src/main/java/org/apache/mahout/math/PivotedMatrix.java Thu Sep  1 11:28:14 2011
@@ -0,0 +1,257 @@
+package org.apache.mahout.math;
+
+import com.google.common.base.Preconditions;
+
+/**
+ * Matrix that allows transparent row and column permutation.
+ */
+public class PivotedMatrix extends AbstractMatrix {
+  private Matrix base;
+  private int[] rowPivot;
+  private int[] rowUnpivot;
+
+  private int[] columnPivot;
+  private int[] columnUnpivot;
+
+  public PivotedMatrix(Matrix base, int[] pivot) {
+    this(base, pivot, java.util.Arrays.copyOf(pivot, pivot.length));
+  }
+  public PivotedMatrix(Matrix base, int[] rowPivot, int[] columnPivot) {
+    cardinality[ROW] = base.rowSize();
+    cardinality[COL] = base.columnSize();
+
+    this.base = base;
+    this.rowPivot = rowPivot;
+    rowUnpivot = invert(rowPivot);
+
+    this.columnPivot = columnPivot;
+    columnUnpivot = invert(columnPivot);
+  }
+
+  public PivotedMatrix(Matrix base) {
+    this(base, identityPivot(base.rowSize()),identityPivot(base.columnSize()));
+  }
+
+  /**
+   * Swaps indexes i and j.  This does both row and column permutation.
+   *
+   * @param i First index to swap.
+   * @param j Second index to swap.
+   */
+  public void swap(int i, int j) {
+    swapRows(i, j);
+    swapColumns(i, j);
+  }
+
+  /**
+   * Swaps indexes i and j.  This does just row permutation.
+   *
+   * @param i First index to swap.
+   * @param j Second index to swap.
+   */
+  public void swapRows(int i, int j) {
+    swap(rowPivot, rowUnpivot, i, j);
+  }
+
+
+  /**
+   * Swaps indexes i and j.  This does just row permutation.
+   *
+   * @param i First index to swap.
+   * @param j Second index to swap.
+   */
+  public void swapColumns(int i, int j) {
+    swap(columnPivot, columnUnpivot, i, j);
+  }
+
+  private void swap(int[] pivot, int[] unpivot, int i, int j) {
+    Preconditions.checkPositionIndex(i, pivot.length);
+    Preconditions.checkPositionIndex(j, pivot.length);
+    if (i != j) {
+      int tmp = pivot[i];
+      pivot[i] = pivot[j];
+      pivot[j] = tmp;
+
+      unpivot[pivot[i]] = i;
+      unpivot[pivot[j]] = j;
+    }
+  }
+
+  /**
+   * Assign the other vector values to the column of the receiver
+   *
+   * @param column the int row to assign
+   * @param other  a Vector
+   * @return the modified receiver
+   * @throws org.apache.mahout.math.CardinalityException
+   *          if the cardinalities differ
+   */
+  @Override
+  public Matrix assignColumn(int column, Vector other) {
+    // note the reversed pivoting for other
+    return base.assignColumn(columnPivot[column], new PermutedVectorView(other, rowUnpivot, rowPivot));
+  }
+
+  /**
+   * Assign the other vector values to the row of the receiver
+   *
+   * @param row   the int row to assign
+   * @param other a Vector
+   * @return the modified receiver
+   * @throws org.apache.mahout.math.CardinalityException
+   *          if the cardinalities differ
+   */
+  @Override
+  public Matrix assignRow(int row, Vector other) {
+    // note the reversed pivoting for other
+    return base.assignRow(rowPivot[row], new PermutedVectorView(other, columnUnpivot, columnPivot));
+  }
+
+  /**
+   * Return the column at the given index
+   *
+   * @param column an int column index
+   * @return a Vector at the index
+   * @throws org.apache.mahout.math.IndexException
+   *          if the index is out of bounds
+   */
+  @Override
+  public Vector getColumn(int column) {
+    if (column < 0 || column >= columnSize()) {
+      throw new IndexException(column, columnSize());
+    }
+    return new PermutedVectorView(base.getColumn(columnPivot[column]), rowPivot, rowUnpivot);
+  }
+
+  /**
+   * Return the row at the given index
+   *
+   * @param row an int row index
+   * @return a Vector at the index
+   * @throws org.apache.mahout.math.IndexException
+   *          if the index is out of bounds
+   */
+  @Override
+  public Vector getRow(int row) {
+    if (row < 0 || row >= rowSize()) {
+      throw new IndexException(row, rowSize());
+    }
+    return new PermutedVectorView(base.getRow(rowPivot[row]), columnPivot, columnUnpivot);
+  }
+
+  /**
+   * Return the value at the given indexes, without checking bounds
+   *
+   * @param row    an int row index
+   * @param column an int column index
+   * @return the double at the index
+   */
+  @Override
+  public double getQuick(int row, int column) {
+    return base.getQuick(rowPivot[row], columnPivot[column]);
+  }
+
+  /**
+   * Return an empty matrix of the same underlying class as the receiver
+   *
+   * @return a Matrix
+   */
+  @Override
+  public Matrix like() {
+    return new PivotedMatrix(base.like());
+  }
+
+  /**
+   * Returns an empty matrix of the same underlying class as the receiver and of the specified
+   * size.
+   *
+   * @param rows    the int number of rows
+   * @param columns the int number of columns
+   */
+  @Override
+  public Matrix like(int rows, int columns) {
+    return new PivotedMatrix(base.like(rows, columns));
+  }
+
+  /**
+   * Set the value at the given index, without checking bounds
+   *
+   * @param row    an int row index into the receiver
+   * @param column an int column index into the receiver
+   * @param value  a double value to set
+   */
+  @Override
+  public void setQuick(int row, int column, double value) {
+    base.setQuick(rowPivot[row], columnPivot[column], value);
+  }
+
+  /**
+   * Return the number of values in the recipient
+   *
+   * @return an int[2] containing [row, column] count
+   */
+  @Override
+  public int[] getNumNondefaultElements() {
+    return base.getNumNondefaultElements();
+  }
+
+  /**
+   * Return a new matrix containing the subset of the recipient
+   *
+   * @param offset an int[2] offset into the receiver
+   * @param size   the int[2] size of the desired result
+   * @return a new Matrix that is a view of the original
+   * @throws org.apache.mahout.math.CardinalityException
+   *          if the length is greater than the cardinality of the receiver
+   * @throws org.apache.mahout.math.IndexException
+   *          if the offset is negative or the offset+length is outside of the receiver
+   */
+  @Override
+  public Matrix viewPart(int[] offset, int[] size) {
+    return new MatrixView(this, offset, size);
+  }
+
+  public int rowUnpivot(int k) {
+    return rowUnpivot[k];
+  }
+
+  public int columnUnpivot(int k) {
+    return columnUnpivot[k];
+  }
+
+  public int[] getRowPivot() {
+    return rowPivot;
+  }
+
+  public int[] getInverseRowPivot() {
+    return rowUnpivot;
+  }
+
+  public int[] getColumnPivot() {
+    return columnPivot;
+  }
+
+  public int[] getInverseColumnPivot() {
+    return columnUnpivot;
+  }
+
+  public Matrix getBase() {
+    return base;
+  }
+
+  private static int[] identityPivot(int n) {
+    int[] pivot = new int[n];
+    for (int i = 0; i < n; i++) {
+      pivot[i] = i;
+    }
+    return pivot;
+  }
+
+  private int[] invert(int[] pivot) {
+    int[] x = new int[pivot.length];
+    for (int i = 0; i < pivot.length; i++) {
+      x[pivot[i]] = i;
+    }
+    return x;
+  }
+}

Added: mahout/trunk/math/src/test/java/org/apache/mahout/math/PermutedVectorViewTest.java
URL: http://svn.apache.org/viewvc/mahout/trunk/math/src/test/java/org/apache/mahout/math/PermutedVectorViewTest.java?rev=1164010&view=auto
==============================================================================
--- mahout/trunk/math/src/test/java/org/apache/mahout/math/PermutedVectorViewTest.java (added)
+++ mahout/trunk/math/src/test/java/org/apache/mahout/math/PermutedVectorViewTest.java Thu Sep  1 11:28:14 2011
@@ -0,0 +1,88 @@
+package org.apache.mahout.math;
+
+import org.apache.mahout.common.RandomUtils;
+import org.apache.mahout.math.function.DoubleFunction;
+import org.junit.Test;
+
+import java.util.Iterator;
+import java.util.Random;
+
+public class PermutedVectorViewTest extends MahoutTestCase {
+  @Test
+  public void testViewBasics() {
+    Vector v = randomVector();
+
+    int[] pivot = pivot();
+
+    PermutedVectorView pvv = new PermutedVectorView(v, pivot);
+
+    // verify the view has the same contents
+    for (int i = 0; i < 20; i++) {
+      assertEquals("Element " + i, v.get(pivot[i]), pvv.get(i), 0);
+    }
+
+    // change a view element or two on each side
+    pvv.set(6, 321);
+    v.set(9, 512);
+
+    // verify again
+    for (int i = 0; i < 20; i++) {
+      assertEquals("Element " + i, v.get(pivot[i]), pvv.get(i), 0);
+    }
+  }
+
+  @Test
+  public void testIterators() {
+    int[] pivot = pivot();
+    int[] unpivot = unpivot();
+
+    Vector v = randomVector();
+    PermutedVectorView pvv = new PermutedVectorView(v, pivot);
+
+    // check a simple operation and thus an iterator
+    assertEquals(v.zSum(), pvv.zSum(), 0);
+
+    assertEquals(v.getNumNondefaultElements(), pvv.getNumNondefaultElements());
+    v.set(11, 0);
+    assertEquals(v.getNumNondefaultElements(), pvv.getNumNondefaultElements());
+
+    Iterator<Vector.Element> vi = pvv.iterator();
+    int i = 0;
+    while (vi.hasNext()) {
+      Vector.Element e = vi.next();
+      assertEquals("Index " + i, i, pivot[e.index()]);
+      assertEquals("Reverse Index " + i, unpivot[i], e.index());
+      assertEquals("Self-value " + i, e.get(), pvv.get(e.index()), 0);
+      // note that we iterate in the original vector order
+      assertEquals("Value " + i, v.get(i), e.get(), 0);
+      i++;
+    }
+  }
+
+  private int[] pivot() {
+    return new int[]{11, 7, 10, 9, 8, 3, 17, 0, 19, 13, 12, 1, 5, 6, 16, 2, 4, 14, 18, 15};
+  }
+
+  private int[] unpivot() {
+    int[] pivot = pivot();
+    int[] unpivot = new int[20];
+
+    for (int i = 0; i < 20; i++) {
+      unpivot[pivot[i]] = i;
+    }
+    return unpivot;
+  }
+
+  private Vector randomVector() {
+    Vector v = new DenseVector(20);
+    v.assign(new DoubleFunction() {
+      Random gen = RandomUtils.getRandom();
+
+      @Override
+      public double apply(double arg1) {
+        return gen.nextDouble();
+      }
+    });
+    return v;
+  }
+}

Added: mahout/trunk/math/src/test/java/org/apache/mahout/math/PivotedMatrixTest.java
URL: http://svn.apache.org/viewvc/mahout/trunk/math/src/test/java/org/apache/mahout/math/PivotedMatrixTest.java?rev=1164010&view=auto
==============================================================================
--- mahout/trunk/math/src/test/java/org/apache/mahout/math/PivotedMatrixTest.java (added)
+++ mahout/trunk/math/src/test/java/org/apache/mahout/math/PivotedMatrixTest.java Thu Sep  1 11:28:14 2011
@@ -0,0 +1,48 @@
+package org.apache.mahout.math;
+
+import org.apache.mahout.common.RandomUtils;
+import org.junit.Test;
+
+import java.util.Random;
+
+public class PivotedMatrixTest extends MatrixTest {
+  @Override
+  public Matrix matrixFactory(double[][] values) {
+    Random gen = RandomUtils.getRandom();
+
+    Matrix base = new DenseMatrix(values);
+
+    // for general tests, we just make a scrambled matrix and fill it
+    // with the standard data.  Then we can test the details of the
+    // row and/or column swapping separately.
+    PivotedMatrix pm = new PivotedMatrix(base.like());
+
+    pm.swap(0, 1);
+    pm.swapRows(1, 2);
+    pm.assign(base);
+    return pm;
+  }
+
+  @Test
+  public void testSwap() {
+    Matrix m = new DenseMatrix(10, 10);
+    for (int i = 0; i < 10; i++) {
+      for (int j = 0; j < 10; j++) {
+        m.set(i, j, 10 * i + j);
+      }
+    }
+
+    PivotedMatrix pm = new PivotedMatrix(m);
+
+    pm.swap(3, 5);
+
+    assertEquals(0, pm.viewDiagonal().minus(new DenseVector(new double[]{0, 11, 22, 55, 44, 33, 66, 77, 88, 99})).norm(1), 1e-10);
+
+    pm.swap(2, 7);
+    assertEquals(0, pm.viewDiagonal().minus(new DenseVector(new double[]{0, 11, 77, 55, 44, 33, 66, 22, 88, 99})).norm(1), 1e-10);
+
+    pm.swap(5, 8);
+    assertEquals(0, pm.viewColumn(4).minus(new DenseVector(new double[]{4.0,14.0,74.0,54.0,44.0,84.0,64.0,24.0,34.0,94.0})).norm(1), 1e-10);
+    assertEquals(0, pm.viewDiagonal().minus(new DenseVector(new double[]{0, 11, 77, 55, 44, 88, 66, 22, 33, 99})).norm(1), 1e-10);
+  }
+}