You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mahout.apache.org by pa...@apache.org on 2015/04/01 20:07:41 UTC

[10/51] [partial] mahout git commit: MAHOUT-1655 Refactors mr-legacy into mahout-hdfs and mahout-mr, closes apache/mahout#86

http://git-wip-us.apache.org/repos/asf/mahout/blob/b988c493/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/GenericItemBasedRecommenderTest.java
----------------------------------------------------------------------
diff --git a/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/GenericItemBasedRecommenderTest.java b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/GenericItemBasedRecommenderTest.java
new file mode 100644
index 0000000..16cbdca
--- /dev/null
+++ b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/GenericItemBasedRecommenderTest.java
@@ -0,0 +1,324 @@
+/**
+ * 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.mahout.cf.taste.impl.recommender;
+
+import com.google.common.collect.Lists;
+import org.apache.mahout.cf.taste.impl.TasteTestCase;
+import org.apache.mahout.cf.taste.impl.common.FastIDSet;
+import org.apache.mahout.cf.taste.impl.model.GenericPreference;
+import org.apache.mahout.cf.taste.impl.model.GenericUserPreferenceArray;
+import org.apache.mahout.cf.taste.impl.similarity.GenericItemSimilarity;
+import org.apache.mahout.cf.taste.model.DataModel;
+import org.apache.mahout.cf.taste.model.PreferenceArray;
+import org.apache.mahout.cf.taste.recommender.CandidateItemsStrategy;
+import org.apache.mahout.cf.taste.recommender.ItemBasedRecommender;
+import org.apache.mahout.cf.taste.recommender.MostSimilarItemsCandidateItemsStrategy;
+import org.apache.mahout.cf.taste.recommender.RecommendedItem;
+import org.apache.mahout.cf.taste.recommender.Recommender;
+import org.apache.mahout.cf.taste.similarity.ItemSimilarity;
+import org.easymock.EasyMock;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.List;
+
+/** <p>Tests {@link GenericItemBasedRecommender}.</p> */
+public final class GenericItemBasedRecommenderTest extends TasteTestCase {
+
+  @Test
+  public void testRecommender() throws Exception {
+    Recommender recommender = buildRecommender();
+    List<RecommendedItem> recommended = recommender.recommend(1, 1);
+    assertNotNull(recommended);
+    assertEquals(1, recommended.size());
+    RecommendedItem firstRecommended = recommended.get(0);
+    assertEquals(2, firstRecommended.getItemID());
+    assertEquals(0.1f, firstRecommended.getValue(), EPSILON);
+    recommender.refresh(null);
+    recommended = recommender.recommend(1, 1);
+    firstRecommended = recommended.get(0);    
+    assertEquals(2, firstRecommended.getItemID());
+    assertEquals(0.1f, firstRecommended.getValue(), EPSILON);
+  }
+
+  @Test
+  public void testHowMany() throws Exception {
+
+    DataModel dataModel = getDataModel(
+            new long[] {1, 2, 3, 4, 5},
+            new Double[][] {
+                    {0.1, 0.2},
+                    {0.2, 0.3, 0.3, 0.6},
+                    {0.4, 0.4, 0.5, 0.9},
+                    {0.1, 0.4, 0.5, 0.8, 0.9, 1.0},
+                    {0.2, 0.3, 0.6, 0.7, 0.1, 0.2},
+            });
+
+    Collection<GenericItemSimilarity.ItemItemSimilarity> similarities = Lists.newArrayList();
+    for (int i = 0; i < 6; i++) {
+      for (int j = i + 1; j < 6; j++) {
+        similarities.add(
+            new GenericItemSimilarity.ItemItemSimilarity(i, j, 1.0 / (1.0 + i + j)));
+      }
+    }
+    ItemSimilarity similarity = new GenericItemSimilarity(similarities);
+    Recommender recommender = new GenericItemBasedRecommender(dataModel, similarity);
+    List<RecommendedItem> fewRecommended = recommender.recommend(1, 2);
+    List<RecommendedItem> moreRecommended = recommender.recommend(1, 4);
+    for (int i = 0; i < fewRecommended.size(); i++) {
+      assertEquals(fewRecommended.get(i).getItemID(), moreRecommended.get(i).getItemID());
+    }
+    recommender.refresh(null);
+    for (int i = 0; i < fewRecommended.size(); i++) {
+      assertEquals(fewRecommended.get(i).getItemID(), moreRecommended.get(i).getItemID());
+    }
+  }
+
+  @Test
+  public void testRescorer() throws Exception {
+
+    DataModel dataModel = getDataModel(
+            new long[] {1, 2, 3},
+            new Double[][] {
+                    {0.1, 0.2},
+                    {0.2, 0.3, 0.3, 0.6},
+                    {0.4, 0.4, 0.5, 0.9},
+            });
+
+    Collection<GenericItemSimilarity.ItemItemSimilarity> similarities = Lists.newArrayList();
+    similarities.add(new GenericItemSimilarity.ItemItemSimilarity(0, 1, 1.0));
+    similarities.add(new GenericItemSimilarity.ItemItemSimilarity(0, 2, 0.5));
+    similarities.add(new GenericItemSimilarity.ItemItemSimilarity(0, 3, 0.2));
+    similarities.add(new GenericItemSimilarity.ItemItemSimilarity(1, 2, 0.7));
+    similarities.add(new GenericItemSimilarity.ItemItemSimilarity(1, 3, 0.5));
+    similarities.add(new GenericItemSimilarity.ItemItemSimilarity(2, 3, 0.9));
+    ItemSimilarity similarity = new GenericItemSimilarity(similarities);
+    Recommender recommender = new GenericItemBasedRecommender(dataModel, similarity);
+    List<RecommendedItem> originalRecommended = recommender.recommend(1, 2);
+    List<RecommendedItem> rescoredRecommended =
+        recommender.recommend(1, 2, new ReversingRescorer<Long>());
+    assertNotNull(originalRecommended);
+    assertNotNull(rescoredRecommended);
+    assertEquals(2, originalRecommended.size());
+    assertEquals(2, rescoredRecommended.size());
+    assertEquals(originalRecommended.get(0).getItemID(), rescoredRecommended.get(1).getItemID());
+    assertEquals(originalRecommended.get(1).getItemID(), rescoredRecommended.get(0).getItemID());
+  }
+
+  @Test
+  public void testIncludeKnownItems() throws Exception {
+
+    DataModel dataModel = getDataModel(
+            new long[] {1, 2, 3},
+            new Double[][] {
+                    {0.1, 0.2},
+                    {0.2, 0.3, 0.3, 0.6},
+                    {0.4, 0.4, 0.5, 0.9},
+            });
+
+    Collection<GenericItemSimilarity.ItemItemSimilarity> similarities = Lists.newArrayList();
+    similarities.add(new GenericItemSimilarity.ItemItemSimilarity(0, 1, 0.8));
+    similarities.add(new GenericItemSimilarity.ItemItemSimilarity(0, 2, 0.5));
+    similarities.add(new GenericItemSimilarity.ItemItemSimilarity(0, 3, 0.2));
+    similarities.add(new GenericItemSimilarity.ItemItemSimilarity(1, 2, 0.7));
+    similarities.add(new GenericItemSimilarity.ItemItemSimilarity(1, 3, 0.5));
+    similarities.add(new GenericItemSimilarity.ItemItemSimilarity(2, 3, 0.9));
+    ItemSimilarity similarity = new GenericItemSimilarity(similarities);
+    Recommender recommender = new GenericItemBasedRecommender(dataModel, similarity);
+    List<RecommendedItem> originalRecommended = recommender.recommend(1, 4, null, true);
+    List<RecommendedItem> rescoredRecommended = recommender.recommend(1, 4, new ReversingRescorer<Long>(), true);
+    assertNotNull(originalRecommended);
+    assertNotNull(rescoredRecommended);
+    assertEquals(4, originalRecommended.size());
+    assertEquals(4, rescoredRecommended.size());
+    assertEquals(originalRecommended.get(0).getItemID(), rescoredRecommended.get(3).getItemID());
+    assertEquals(originalRecommended.get(3).getItemID(), rescoredRecommended.get(0).getItemID());
+  }
+  
+  @Test
+  public void testEstimatePref() throws Exception {
+    Recommender recommender = buildRecommender();
+    assertEquals(0.1f, recommender.estimatePreference(1, 2), EPSILON);
+  }
+
+  /**
+   * Contributed test case that verifies fix for bug
+   * <a href="http://sourceforge.net/tracker/index.php?func=detail&amp;aid=1396128&amp;group_id=138771&amp;atid=741665">
+   * 1396128</a>.
+   */
+  @Test
+  public void testBestRating() throws Exception {
+    Recommender recommender = buildRecommender();
+    List<RecommendedItem> recommended = recommender.recommend(1, 1);
+    assertNotNull(recommended);
+    assertEquals(1, recommended.size());
+    RecommendedItem firstRecommended = recommended.get(0);
+    // item one should be recommended because it has a greater rating/score
+    assertEquals(2, firstRecommended.getItemID());
+    assertEquals(0.1f, firstRecommended.getValue(), EPSILON);
+  }
+
+  @Test
+  public void testMostSimilar() throws Exception {
+    ItemBasedRecommender recommender = buildRecommender();
+    List<RecommendedItem> similar = recommender.mostSimilarItems(0, 2);
+    assertNotNull(similar);
+    assertEquals(2, similar.size());
+    RecommendedItem first = similar.get(0);
+    RecommendedItem second = similar.get(1);
+    assertEquals(1, first.getItemID());
+    assertEquals(1.0f, first.getValue(), EPSILON);
+    assertEquals(2, second.getItemID());
+    assertEquals(0.5f, second.getValue(), EPSILON);
+  }
+
+  @Test
+  public void testMostSimilarToMultiple() throws Exception {
+    ItemBasedRecommender recommender = buildRecommender2();
+    List<RecommendedItem> similar = recommender.mostSimilarItems(new long[] {0, 1}, 2);
+    assertNotNull(similar);
+    assertEquals(2, similar.size());
+    RecommendedItem first = similar.get(0);
+    RecommendedItem second = similar.get(1);
+    assertEquals(2, first.getItemID());
+    assertEquals(0.85f, first.getValue(), EPSILON);
+    assertEquals(3, second.getItemID());
+    assertEquals(-0.3f, second.getValue(), EPSILON);
+  }
+
+  @Test
+  public void testMostSimilarToMultipleExcludeIfNotSimilarToAll() throws Exception {
+    ItemBasedRecommender recommender = buildRecommender2();
+    List<RecommendedItem> similar = recommender.mostSimilarItems(new long[] {3, 4}, 2);
+    assertNotNull(similar);
+    assertEquals(1, similar.size());
+    RecommendedItem first = similar.get(0);
+    assertEquals(0, first.getItemID());
+    assertEquals(0.2f, first.getValue(), EPSILON);
+  }
+
+  @Test
+  public void testMostSimilarToMultipleDontExcludeIfNotSimilarToAll() throws Exception {
+    ItemBasedRecommender recommender = buildRecommender2();
+    List<RecommendedItem> similar = recommender.mostSimilarItems(new long[] {1, 2, 4}, 10, false);
+    assertNotNull(similar);
+    assertEquals(2, similar.size());
+    RecommendedItem first = similar.get(0);
+    RecommendedItem second = similar.get(1);
+    assertEquals(0, first.getItemID());
+    assertEquals(0.933333333f, first.getValue(), EPSILON);
+    assertEquals(3, second.getItemID());
+    assertEquals(-0.2f, second.getValue(), EPSILON);
+  }
+
+
+  @Test
+  public void testRecommendedBecause() throws Exception {
+    ItemBasedRecommender recommender = buildRecommender2();
+    List<RecommendedItem> recommendedBecause = recommender.recommendedBecause(1, 4, 3);
+    assertNotNull(recommendedBecause);
+    assertEquals(3, recommendedBecause.size());
+    RecommendedItem first = recommendedBecause.get(0);
+    RecommendedItem second = recommendedBecause.get(1);
+    RecommendedItem third = recommendedBecause.get(2);
+    assertEquals(2, first.getItemID());
+    assertEquals(0.99f, first.getValue(), EPSILON);
+    assertEquals(3, second.getItemID());
+    assertEquals(0.4f, second.getValue(), EPSILON);
+    assertEquals(0, third.getItemID());
+    assertEquals(0.2f, third.getValue(), EPSILON);
+  }
+
+  private static ItemBasedRecommender buildRecommender() {
+    DataModel dataModel = getDataModel();
+    Collection<GenericItemSimilarity.ItemItemSimilarity> similarities = Lists.newArrayList();
+    similarities.add(new GenericItemSimilarity.ItemItemSimilarity(0, 1, 1.0));
+    similarities.add(new GenericItemSimilarity.ItemItemSimilarity(0, 2, 0.5));
+    similarities.add(new GenericItemSimilarity.ItemItemSimilarity(1, 2, 0.0));
+    ItemSimilarity similarity = new GenericItemSimilarity(similarities);
+    return new GenericItemBasedRecommender(dataModel, similarity);
+  }
+
+  private static ItemBasedRecommender buildRecommender2() {
+
+    DataModel dataModel = getDataModel(
+        new long[] {1, 2, 3, 4},
+        new Double[][] {
+                {0.1, 0.3, 0.9, 0.8},
+                {0.2, 0.3, 0.3, 0.4},
+                {0.4, 0.3, 0.5, 0.1, 0.1},
+                {0.7, 0.3, 0.8, 0.5, 0.6},
+        });
+
+    Collection<GenericItemSimilarity.ItemItemSimilarity> similarities = Lists.newArrayList();
+    similarities.add(new GenericItemSimilarity.ItemItemSimilarity(0, 1, 1.0));
+    similarities.add(new GenericItemSimilarity.ItemItemSimilarity(0, 2, 0.8));
+    similarities.add(new GenericItemSimilarity.ItemItemSimilarity(0, 3, -0.6));
+    similarities.add(new GenericItemSimilarity.ItemItemSimilarity(0, 4, 1.0));
+    similarities.add(new GenericItemSimilarity.ItemItemSimilarity(1, 2, 0.9));
+    similarities.add(new GenericItemSimilarity.ItemItemSimilarity(1, 3, 0.0));
+    similarities.add(new GenericItemSimilarity.ItemItemSimilarity(1, 1, 1.0));
+    similarities.add(new GenericItemSimilarity.ItemItemSimilarity(2, 3, -0.1));
+    similarities.add(new GenericItemSimilarity.ItemItemSimilarity(2, 4, 0.1));
+    similarities.add(new GenericItemSimilarity.ItemItemSimilarity(3, 4, -0.5));
+    ItemSimilarity similarity = new GenericItemSimilarity(similarities);
+    return new GenericItemBasedRecommender(dataModel, similarity);
+  }
+
+
+  /**
+   * we're making sure that a user's preferences are fetched only once from the {@link DataModel} for one call to
+   * {@link GenericItemBasedRecommender#recommend(long, int)}
+   *
+   * @throws Exception
+   */
+  @Test
+  public void preferencesFetchedOnlyOnce() throws Exception {
+
+    DataModel dataModel = EasyMock.createMock(DataModel.class);
+    ItemSimilarity itemSimilarity = EasyMock.createMock(ItemSimilarity.class);
+    CandidateItemsStrategy candidateItemsStrategy = EasyMock.createMock(CandidateItemsStrategy.class);
+    MostSimilarItemsCandidateItemsStrategy mostSimilarItemsCandidateItemsStrategy =
+        EasyMock.createMock(MostSimilarItemsCandidateItemsStrategy.class);
+
+    PreferenceArray preferencesFromUser = new GenericUserPreferenceArray(
+        Arrays.asList(new GenericPreference(1L, 1L, 5.0f), new GenericPreference(1L, 2L, 4.0f)));
+
+    EasyMock.expect(dataModel.getMinPreference()).andReturn(Float.NaN);
+    EasyMock.expect(dataModel.getMaxPreference()).andReturn(Float.NaN);
+
+    EasyMock.expect(dataModel.getPreferencesFromUser(1L)).andReturn(preferencesFromUser);
+    EasyMock.expect(candidateItemsStrategy.getCandidateItems(1L, preferencesFromUser, dataModel, false))
+        .andReturn(new FastIDSet(new long[] { 3L, 4L }));
+
+    EasyMock.expect(itemSimilarity.itemSimilarities(3L, preferencesFromUser.getIDs()))
+        .andReturn(new double[] { 0.5, 0.3 });
+    EasyMock.expect(itemSimilarity.itemSimilarities(4L, preferencesFromUser.getIDs()))
+        .andReturn(new double[] { 0.4, 0.1 });
+
+    EasyMock.replay(dataModel, itemSimilarity, candidateItemsStrategy, mostSimilarItemsCandidateItemsStrategy);
+
+    Recommender recommender = new GenericItemBasedRecommender(dataModel, itemSimilarity,
+        candidateItemsStrategy, mostSimilarItemsCandidateItemsStrategy);
+
+    recommender.recommend(1L, 3);
+
+    EasyMock.verify(dataModel, itemSimilarity, candidateItemsStrategy, mostSimilarItemsCandidateItemsStrategy);
+  }
+}

http://git-wip-us.apache.org/repos/asf/mahout/blob/b988c493/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/GenericUserBasedRecommenderTest.java
----------------------------------------------------------------------
diff --git a/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/GenericUserBasedRecommenderTest.java b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/GenericUserBasedRecommenderTest.java
new file mode 100644
index 0000000..121cd1a
--- /dev/null
+++ b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/GenericUserBasedRecommenderTest.java
@@ -0,0 +1,174 @@
+/**
+ * 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.mahout.cf.taste.impl.recommender;
+
+import org.apache.mahout.cf.taste.common.TasteException;
+import org.apache.mahout.cf.taste.impl.TasteTestCase;
+import org.apache.mahout.cf.taste.impl.neighborhood.NearestNUserNeighborhood;
+import org.apache.mahout.cf.taste.impl.similarity.PearsonCorrelationSimilarity;
+import org.apache.mahout.cf.taste.model.DataModel;
+import org.apache.mahout.cf.taste.neighborhood.UserNeighborhood;
+import org.apache.mahout.cf.taste.recommender.RecommendedItem;
+import org.apache.mahout.cf.taste.recommender.Recommender;
+import org.apache.mahout.cf.taste.recommender.UserBasedRecommender;
+import org.apache.mahout.cf.taste.similarity.UserSimilarity;
+import org.junit.Test;
+
+import java.util.List;
+
+/** <p>Tests {@link GenericUserBasedRecommender}.</p> */
+public final class GenericUserBasedRecommenderTest extends TasteTestCase {
+
+  @Test
+  public void testRecommender() throws Exception {
+    Recommender recommender = buildRecommender();
+    List<RecommendedItem> recommended = recommender.recommend(1, 1);
+    assertNotNull(recommended);
+    assertEquals(1, recommended.size());
+    RecommendedItem firstRecommended = recommended.get(0);
+    assertEquals(2, firstRecommended.getItemID());
+    assertEquals(0.1f, firstRecommended.getValue(), EPSILON);
+    recommender.refresh(null);
+    assertEquals(2, firstRecommended.getItemID());
+    assertEquals(0.1f, firstRecommended.getValue(), EPSILON);
+  }
+
+  @Test
+  public void testHowMany() throws Exception {
+    DataModel dataModel = getDataModel(
+            new long[] {1, 2, 3, 4, 5},
+            new Double[][] {
+                    {0.1, 0.2},
+                    {0.2, 0.3, 0.3, 0.6},
+                    {0.4, 0.4, 0.5, 0.9},
+                    {0.1, 0.4, 0.5, 0.8, 0.9, 1.0},
+                    {0.2, 0.3, 0.6, 0.7, 0.1, 0.2},
+            });
+    UserSimilarity similarity = new PearsonCorrelationSimilarity(dataModel);
+    UserNeighborhood neighborhood = new NearestNUserNeighborhood(2, similarity, dataModel);
+    Recommender recommender = new GenericUserBasedRecommender(dataModel, neighborhood, similarity);
+    List<RecommendedItem> fewRecommended = recommender.recommend(1, 2);
+    List<RecommendedItem> moreRecommended = recommender.recommend(1, 4);
+    for (int i = 0; i < fewRecommended.size(); i++) {
+      assertEquals(fewRecommended.get(i).getItemID(), moreRecommended.get(i).getItemID());
+    }
+    recommender.refresh(null);
+    for (int i = 0; i < fewRecommended.size(); i++) {
+      assertEquals(fewRecommended.get(i).getItemID(), moreRecommended.get(i).getItemID());
+    }
+  }
+
+  @Test
+  public void testRescorer() throws Exception {
+    DataModel dataModel = getDataModel(
+            new long[] {1, 2, 3},
+            new Double[][] {
+                    {0.1, 0.2},
+                    {0.2, 0.3, 0.3, 0.6},
+                    {0.4, 0.5, 0.5, 0.9},
+            });
+    UserSimilarity similarity = new PearsonCorrelationSimilarity(dataModel);
+    UserNeighborhood neighborhood = new NearestNUserNeighborhood(2, similarity, dataModel);
+    Recommender recommender = new GenericUserBasedRecommender(dataModel, neighborhood, similarity);
+    List<RecommendedItem> originalRecommended = recommender.recommend(1, 2);
+    List<RecommendedItem> rescoredRecommended =
+        recommender.recommend(1, 2, new ReversingRescorer<Long>());
+    assertNotNull(originalRecommended);
+    assertNotNull(rescoredRecommended);
+    assertEquals(2, originalRecommended.size());
+    assertEquals(2, rescoredRecommended.size());
+    assertEquals(originalRecommended.get(0).getItemID(), rescoredRecommended.get(1).getItemID());
+    assertEquals(originalRecommended.get(1).getItemID(), rescoredRecommended.get(0).getItemID());
+  }
+
+  @Test
+  public void testIncludeKnownItems() throws Exception {
+    DataModel dataModel = getDataModel(
+            new long[] {1, 2, 3},
+            new Double[][] {
+                    {0.1, 0.2},
+                    {0.2, 0.3, 0.3, 0.6},
+                    {0.4, 0.5, 0.5, 0.9},
+            });
+    UserSimilarity similarity = new PearsonCorrelationSimilarity(dataModel);
+    UserNeighborhood neighborhood = new NearestNUserNeighborhood(2, similarity, dataModel);
+    Recommender recommender = new GenericUserBasedRecommender(dataModel, neighborhood, similarity);
+    List<RecommendedItem> originalRecommended = recommender.recommend(1, 4, null, true);
+    List<RecommendedItem> rescoredRecommended = recommender.recommend(1, 4, new ReversingRescorer<Long>(), true);
+    assertNotNull(originalRecommended);
+    assertNotNull(rescoredRecommended);
+    assertEquals(4, originalRecommended.size());
+    assertEquals(4, rescoredRecommended.size());
+    assertEquals(originalRecommended.get(0).getItemID(), rescoredRecommended.get(3).getItemID());
+    assertEquals(originalRecommended.get(3).getItemID(), rescoredRecommended.get(0).getItemID());
+  }
+
+  @Test
+  public void testEstimatePref() throws Exception {
+    Recommender recommender = buildRecommender();
+    assertEquals(0.1f, recommender.estimatePreference(1, 2), EPSILON);
+  }
+
+  @Test
+  public void testBestRating() throws Exception {
+    Recommender recommender = buildRecommender();
+    List<RecommendedItem> recommended = recommender.recommend(1, 1);
+    assertNotNull(recommended);
+    assertEquals(1, recommended.size());
+    RecommendedItem firstRecommended = recommended.get(0);
+    // item one should be recommended because it has a greater rating/score
+    assertEquals(2, firstRecommended.getItemID());
+    assertEquals(0.1f, firstRecommended.getValue(), EPSILON);
+  }
+
+  @Test
+  public void testMostSimilar() throws Exception {
+    UserBasedRecommender recommender = buildRecommender();
+    long[] similar = recommender.mostSimilarUserIDs(1, 2);
+    assertNotNull(similar);
+    assertEquals(2, similar.length);
+    assertEquals(2, similar[0]);
+    assertEquals(3, similar[1]);
+  }
+
+  @Test
+  public void testIsolatedUser() throws Exception {
+    DataModel dataModel = getDataModel(
+            new long[] {1, 2, 3, 4},
+            new Double[][] {
+                    {0.1, 0.2},
+                    {0.2, 0.3, 0.3, 0.6},
+                    {0.4, 0.4, 0.5, 0.9},
+                    {null, null, null, null, 1.0},
+            });
+    UserSimilarity similarity = new PearsonCorrelationSimilarity(dataModel);
+    UserNeighborhood neighborhood = new NearestNUserNeighborhood(3, similarity, dataModel);
+    UserBasedRecommender recommender = new GenericUserBasedRecommender(dataModel, neighborhood, similarity);
+    long[] mostSimilar = recommender.mostSimilarUserIDs(4, 3);
+    assertNotNull(mostSimilar);
+    assertEquals(0, mostSimilar.length);
+  }
+
+  private static UserBasedRecommender buildRecommender() throws TasteException {
+    DataModel dataModel = getDataModel();
+    UserSimilarity similarity = new PearsonCorrelationSimilarity(dataModel);
+    UserNeighborhood neighborhood = new NearestNUserNeighborhood(2, similarity, dataModel);
+    return new GenericUserBasedRecommender(dataModel, neighborhood, similarity);
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/mahout/blob/b988c493/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/ItemAverageRecommenderTest.java
----------------------------------------------------------------------
diff --git a/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/ItemAverageRecommenderTest.java b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/ItemAverageRecommenderTest.java
new file mode 100644
index 0000000..243eaa9
--- /dev/null
+++ b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/ItemAverageRecommenderTest.java
@@ -0,0 +1,43 @@
+/**
+ * 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.mahout.cf.taste.impl.recommender;
+
+import org.apache.mahout.cf.taste.impl.TasteTestCase;
+import org.apache.mahout.cf.taste.recommender.RecommendedItem;
+import org.apache.mahout.cf.taste.recommender.Recommender;
+import org.junit.Test;
+
+import java.util.List;
+
+public final class ItemAverageRecommenderTest extends TasteTestCase {
+
+  @Test
+  public void testRecommender() throws Exception {
+    Recommender recommender = new ItemAverageRecommender(getDataModel());
+    List<RecommendedItem> recommended = recommender.recommend(1, 1);
+    assertNotNull(recommended);
+    assertEquals(1, recommended.size());
+    RecommendedItem firstRecommended = recommended.get(0);
+    assertEquals(2, firstRecommended.getItemID());
+    assertEquals(0.53333336f, firstRecommended.getValue(), EPSILON);
+    recommender.refresh(null);
+    assertEquals(2, firstRecommended.getItemID());
+    assertEquals(0.53333336f, firstRecommended.getValue(), EPSILON);
+  }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mahout/blob/b988c493/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/ItemUserAverageRecommenderTest.java
----------------------------------------------------------------------
diff --git a/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/ItemUserAverageRecommenderTest.java b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/ItemUserAverageRecommenderTest.java
new file mode 100644
index 0000000..f8bf1a1
--- /dev/null
+++ b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/ItemUserAverageRecommenderTest.java
@@ -0,0 +1,43 @@
+/**
+ * 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.mahout.cf.taste.impl.recommender;
+
+import org.apache.mahout.cf.taste.impl.TasteTestCase;
+import org.apache.mahout.cf.taste.recommender.RecommendedItem;
+import org.apache.mahout.cf.taste.recommender.Recommender;
+import org.junit.Test;
+
+import java.util.List;
+
+public final class ItemUserAverageRecommenderTest extends TasteTestCase {
+
+  @Test
+  public void testRecommender() throws Exception {
+    Recommender recommender = new ItemUserAverageRecommender(getDataModel());
+    List<RecommendedItem> recommended = recommender.recommend(1, 1);
+    assertNotNull(recommended);
+    assertEquals(1, recommended.size());
+    RecommendedItem firstRecommended = recommended.get(0);
+    assertEquals(2, firstRecommended.getItemID());
+    assertEquals(0.35151517f, firstRecommended.getValue(), EPSILON);
+    recommender.refresh(null);
+    assertEquals(2, firstRecommended.getItemID());
+    assertEquals(0.35151517f, firstRecommended.getValue(), EPSILON);
+  }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mahout/blob/b988c493/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/MockRecommender.java
----------------------------------------------------------------------
diff --git a/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/MockRecommender.java b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/MockRecommender.java
new file mode 100644
index 0000000..50a16cb
--- /dev/null
+++ b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/MockRecommender.java
@@ -0,0 +1,89 @@
+/**
+ * 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.mahout.cf.taste.impl.recommender;
+
+import org.apache.commons.lang3.mutable.MutableInt;
+import org.apache.mahout.cf.taste.common.Refreshable;
+import org.apache.mahout.cf.taste.impl.TasteTestCase;
+import org.apache.mahout.cf.taste.model.DataModel;
+import org.apache.mahout.cf.taste.recommender.IDRescorer;
+import org.apache.mahout.cf.taste.recommender.RecommendedItem;
+import org.apache.mahout.cf.taste.recommender.Recommender;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+
+final class MockRecommender implements Recommender {
+
+  private final MutableInt recommendCount;
+
+  MockRecommender(MutableInt recommendCount) {
+    this.recommendCount = recommendCount;
+  }
+
+  @Override
+  public List<RecommendedItem> recommend(long userID, int howMany) {
+    recommendCount.increment();
+    return Collections.<RecommendedItem>singletonList(
+        new GenericRecommendedItem(1, 1.0f));
+  }
+
+  @Override
+  public List<RecommendedItem> recommend(long userID, int howMany, boolean includeKnownItems) {
+    return recommend(userID, howMany);
+  }
+
+  @Override
+  public List<RecommendedItem> recommend(long userID, int howMany, IDRescorer rescorer) {
+    return recommend(userID, howMany);
+  }
+  
+  @Override
+  public List<RecommendedItem> recommend(long userID, int howMany, IDRescorer rescorer, boolean includeKnownItems) {
+    return recommend(userID, howMany);
+  }
+
+  @Override
+  public float estimatePreference(long userID, long itemID) {
+    recommendCount.increment();
+    return 0.0f;
+  }
+
+  @Override
+  public void setPreference(long userID, long itemID, float value) {
+    // do nothing
+  }
+
+  @Override
+  public void removePreference(long userID, long itemID) {
+    // do nothing
+  }
+
+  @Override
+  public DataModel getDataModel() {
+    return TasteTestCase.getDataModel(
+            new long[] {1, 2, 3},
+            new Double[][]{{1.0},{2.0},{3.0}});
+  }
+
+  @Override
+  public void refresh(Collection<Refreshable> alreadyRefreshed) {}
+
+}

http://git-wip-us.apache.org/repos/asf/mahout/blob/b988c493/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/NullRescorerTest.java
----------------------------------------------------------------------
diff --git a/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/NullRescorerTest.java b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/NullRescorerTest.java
new file mode 100644
index 0000000..97e539e
--- /dev/null
+++ b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/NullRescorerTest.java
@@ -0,0 +1,47 @@
+/**
+ * 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.mahout.cf.taste.impl.recommender;
+
+import org.apache.mahout.cf.taste.impl.TasteTestCase;
+import org.apache.mahout.cf.taste.recommender.IDRescorer;
+import org.junit.Test;
+
+/** <p>Tests {@link NullRescorer}.</p> */
+public final class NullRescorerTest extends TasteTestCase {
+
+  @Test
+  public void testItemRescorer() throws Exception {
+    IDRescorer rescorer = NullRescorer.getItemInstance();
+    assertNotNull(rescorer);
+    assertEquals(1.0, rescorer.rescore(1L, 1.0), EPSILON);
+    assertEquals(1.0, rescorer.rescore(0L, 1.0), EPSILON);
+    assertEquals(0.0, rescorer.rescore(1L, 0.0), EPSILON);
+    assertTrue(Double.isNaN(rescorer.rescore(1L, Double.NaN)));
+  }
+
+  @Test
+  public void testUserRescorer() throws Exception {
+    IDRescorer rescorer = NullRescorer.getUserInstance();
+    assertNotNull(rescorer);
+    assertEquals(1.0, rescorer.rescore(1L, 1.0), EPSILON);
+    assertEquals(1.0, rescorer.rescore(0L, 1.0), EPSILON);
+    assertEquals(0.0, rescorer.rescore(1L, 0.0), EPSILON);
+    assertTrue(Double.isNaN(rescorer.rescore(1L, Double.NaN)));
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/mahout/blob/b988c493/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/PreferredItemsNeighborhoodCandidateItemsStrategyTest.java
----------------------------------------------------------------------
diff --git a/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/PreferredItemsNeighborhoodCandidateItemsStrategyTest.java b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/PreferredItemsNeighborhoodCandidateItemsStrategyTest.java
new file mode 100644
index 0000000..cbf20cf
--- /dev/null
+++ b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/PreferredItemsNeighborhoodCandidateItemsStrategyTest.java
@@ -0,0 +1,75 @@
+/**
+ * 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.mahout.cf.taste.impl.recommender;
+
+import java.util.Collections;
+import java.util.List;
+
+import com.google.common.collect.Lists;
+import org.apache.mahout.cf.taste.common.TasteException;
+import org.apache.mahout.cf.taste.impl.TasteTestCase;
+import org.apache.mahout.cf.taste.impl.common.FastIDSet;
+import org.apache.mahout.cf.taste.impl.model.GenericItemPreferenceArray;
+import org.apache.mahout.cf.taste.impl.model.GenericPreference;
+import org.apache.mahout.cf.taste.impl.model.GenericUserPreferenceArray;
+import org.apache.mahout.cf.taste.model.DataModel;
+import org.apache.mahout.cf.taste.model.Preference;
+import org.apache.mahout.cf.taste.model.PreferenceArray;
+import org.apache.mahout.cf.taste.recommender.CandidateItemsStrategy;
+import org.easymock.EasyMock;
+import org.junit.Test;
+
+/**
+ * Tests {@link PreferredItemsNeighborhoodCandidateItemsStrategy}
+ */
+public final class PreferredItemsNeighborhoodCandidateItemsStrategyTest extends TasteTestCase {
+
+  @Test
+  public void testStrategy() throws TasteException {
+    FastIDSet itemIDsFromUser123 = new FastIDSet();
+    itemIDsFromUser123.add(1L);
+
+    FastIDSet itemIDsFromUser456 = new FastIDSet();
+    itemIDsFromUser456.add(1L);
+    itemIDsFromUser456.add(2L);
+
+    List<Preference> prefs = Lists.newArrayList();
+    prefs.add(new GenericPreference(123L, 1L, 1.0f));
+    prefs.add(new GenericPreference(456L, 1L, 1.0f));
+    PreferenceArray preferencesForItem1 = new GenericItemPreferenceArray(prefs);
+
+    DataModel dataModel = EasyMock.createMock(DataModel.class);
+    EasyMock.expect(dataModel.getPreferencesForItem(1L)).andReturn(preferencesForItem1);
+    EasyMock.expect(dataModel.getItemIDsFromUser(123L)).andReturn(itemIDsFromUser123);
+    EasyMock.expect(dataModel.getItemIDsFromUser(456L)).andReturn(itemIDsFromUser456);
+
+    PreferenceArray prefArrayOfUser123 =
+        new GenericUserPreferenceArray(Collections.singletonList(new GenericPreference(123L, 1L, 1.0f)));
+
+    CandidateItemsStrategy strategy = new PreferredItemsNeighborhoodCandidateItemsStrategy();
+
+    EasyMock.replay(dataModel);
+
+    FastIDSet candidateItems = strategy.getCandidateItems(123L, prefArrayOfUser123, dataModel, false);
+    assertEquals(1, candidateItems.size());
+    assertTrue(candidateItems.contains(2L));
+
+    EasyMock.verify(dataModel);
+  }
+  
+}

http://git-wip-us.apache.org/repos/asf/mahout/blob/b988c493/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/RandomRecommenderTest.java
----------------------------------------------------------------------
diff --git a/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/RandomRecommenderTest.java b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/RandomRecommenderTest.java
new file mode 100644
index 0000000..f57d389
--- /dev/null
+++ b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/RandomRecommenderTest.java
@@ -0,0 +1,41 @@
+/**
+ * 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.mahout.cf.taste.impl.recommender;
+
+import org.apache.mahout.cf.taste.impl.TasteTestCase;
+import org.apache.mahout.cf.taste.recommender.RecommendedItem;
+import org.apache.mahout.cf.taste.recommender.Recommender;
+import org.junit.Test;
+
+import java.util.List;
+
+public final class RandomRecommenderTest extends TasteTestCase {
+
+  @Test
+  public void testRecommender() throws Exception {
+    Recommender recommender = new RandomRecommender(getDataModel());
+    List<RecommendedItem> recommended = recommender.recommend(1, 1);
+    assertNotNull(recommended);
+    assertEquals(1, recommended.size());
+    RecommendedItem firstRecommended = recommended.get(0);
+    assertEquals(2, firstRecommended.getItemID());
+    recommender.refresh(null);
+    assertEquals(2, firstRecommended.getItemID());
+  }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mahout/blob/b988c493/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/ReversingRescorer.java
----------------------------------------------------------------------
diff --git a/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/ReversingRescorer.java b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/ReversingRescorer.java
new file mode 100644
index 0000000..3c4f7fc
--- /dev/null
+++ b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/ReversingRescorer.java
@@ -0,0 +1,46 @@
+/**
+ * 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.mahout.cf.taste.impl.recommender;
+
+import org.apache.mahout.cf.taste.recommender.IDRescorer;
+import org.apache.mahout.cf.taste.recommender.Rescorer;
+
+/** <p>Simple {@link Rescorer} which negates the given score, thus reversing order of rankings.</p> */
+public final class ReversingRescorer<T> implements Rescorer<T>, IDRescorer {
+
+  @Override
+  public double rescore(T thing, double originalScore) {
+    return -originalScore;
+  }
+
+  @Override
+  public boolean isFiltered(T thing) {
+    return false;
+  }
+
+  @Override
+  public double rescore(long ID, double originalScore) {
+    return -originalScore;
+  }
+
+  @Override
+  public boolean isFiltered(long ID) {
+    return false;
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/mahout/blob/b988c493/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/SamplingCandidateItemsStrategyTest.java
----------------------------------------------------------------------
diff --git a/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/SamplingCandidateItemsStrategyTest.java b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/SamplingCandidateItemsStrategyTest.java
new file mode 100644
index 0000000..687b2b1
--- /dev/null
+++ b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/SamplingCandidateItemsStrategyTest.java
@@ -0,0 +1,71 @@
+/**
+ * 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.mahout.cf.taste.impl.recommender;
+
+import com.google.common.collect.Lists;
+import org.apache.mahout.cf.taste.common.TasteException;
+import org.apache.mahout.cf.taste.impl.TasteTestCase;
+import org.apache.mahout.cf.taste.impl.common.FastByIDMap;
+import org.apache.mahout.cf.taste.impl.common.FastIDSet;
+import org.apache.mahout.cf.taste.impl.model.GenericDataModel;
+import org.apache.mahout.cf.taste.impl.model.GenericPreference;
+import org.apache.mahout.cf.taste.impl.model.GenericUserPreferenceArray;
+import org.apache.mahout.cf.taste.model.DataModel;
+import org.apache.mahout.cf.taste.model.Preference;
+import org.apache.mahout.cf.taste.model.PreferenceArray;
+import org.apache.mahout.cf.taste.recommender.CandidateItemsStrategy;
+import org.junit.Test;
+
+import java.util.List;
+
+/**
+ * Tests {@link SamplingCandidateItemsStrategy}
+ */
+public final class SamplingCandidateItemsStrategyTest extends TasteTestCase {
+
+  @Test
+  public void testStrategy() throws TasteException {
+    List<Preference> prefsOfUser123 = Lists.newArrayList();
+    prefsOfUser123.add(new GenericPreference(123L, 1L, 1.0f));
+
+    List<Preference> prefsOfUser456 = Lists.newArrayList();
+    prefsOfUser456.add(new GenericPreference(456L, 1L, 1.0f));
+    prefsOfUser456.add(new GenericPreference(456L, 2L, 1.0f));
+
+    List<Preference> prefsOfUser789 = Lists.newArrayList();
+    prefsOfUser789.add(new GenericPreference(789L, 1L, 0.5f));
+    prefsOfUser789.add(new GenericPreference(789L, 3L, 1.0f));
+
+    PreferenceArray prefArrayOfUser123 = new GenericUserPreferenceArray(prefsOfUser123);
+
+    FastByIDMap<PreferenceArray> userData = new FastByIDMap<PreferenceArray>();
+    userData.put(123L, prefArrayOfUser123);
+    userData.put(456L, new GenericUserPreferenceArray(prefsOfUser456));
+    userData.put(789L, new GenericUserPreferenceArray(prefsOfUser789));
+
+    DataModel dataModel = new GenericDataModel(userData);
+
+    CandidateItemsStrategy strategy =
+        new SamplingCandidateItemsStrategy(1, 1, 1, dataModel.getNumUsers(), dataModel.getNumItems());
+
+    FastIDSet candidateItems = strategy.getCandidateItems(123L, prefArrayOfUser123, dataModel, false);
+    /* result can be either item2 or item3 or empty */
+    assertTrue(candidateItems.size() <= 1);
+    assertFalse(candidateItems.contains(1L));
+  }
+}

http://git-wip-us.apache.org/repos/asf/mahout/blob/b988c493/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/TopItemsTest.java
----------------------------------------------------------------------
diff --git a/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/TopItemsTest.java b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/TopItemsTest.java
new file mode 100644
index 0000000..1d8b862
--- /dev/null
+++ b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/TopItemsTest.java
@@ -0,0 +1,158 @@
+/**
+ * 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.mahout.cf.taste.impl.recommender;
+
+import com.google.common.collect.Lists;
+import org.apache.mahout.cf.taste.impl.TasteTestCase;
+import org.apache.mahout.cf.taste.impl.common.LongPrimitiveArrayIterator;
+import org.apache.mahout.cf.taste.impl.common.LongPrimitiveIterator;
+import org.apache.mahout.cf.taste.impl.similarity.GenericItemSimilarity;
+import org.apache.mahout.cf.taste.impl.similarity.GenericUserSimilarity;
+import org.apache.mahout.cf.taste.recommender.RecommendedItem;
+import org.apache.mahout.common.RandomUtils;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.Random;
+
+/**
+ * Tests for {@link TopItems}.
+ */
+public final class TopItemsTest extends TasteTestCase {
+
+  @Test
+  public void testTopItems() throws Exception {
+    long[] ids = new long[100];
+    for (int i = 0; i < 100; i++) {
+      ids[i] = i;
+    }
+    LongPrimitiveIterator possibleItemIds = new LongPrimitiveArrayIterator(ids);
+    TopItems.Estimator<Long> estimator = new TopItems.Estimator<Long>() {
+      @Override
+      public double estimate(Long thing) {
+        return thing;
+      }
+    };
+    List<RecommendedItem> topItems = TopItems.getTopItems(10, possibleItemIds, null, estimator);
+    int gold = 99;
+    for (RecommendedItem topItem : topItems) {
+      assertEquals(gold, topItem.getItemID());
+      assertEquals(gold--, topItem.getValue(), 0.01);
+    }
+  }
+
+  @Test
+  public void testTopItemsRandom() throws Exception {
+    long[] ids = new long[100];
+    for (int i = 0; i < 100; i++) {
+      ids[i] = i;
+    }
+    LongPrimitiveIterator possibleItemIds = new LongPrimitiveArrayIterator(ids);
+    final Random random = RandomUtils.getRandom();
+    TopItems.Estimator<Long> estimator = new TopItems.Estimator<Long>() {
+      @Override
+      public double estimate(Long thing) {
+        return random.nextDouble();
+      }
+    };
+    List<RecommendedItem> topItems = TopItems.getTopItems(10, possibleItemIds, null, estimator);
+    assertEquals(10, topItems.size());
+    double last = 2.0;
+    for (RecommendedItem topItem : topItems) {
+      assertTrue(topItem.getValue() <= last);
+      last = topItem.getItemID();
+    }
+  }
+
+  @Test
+  public void testTopUsers() throws Exception {
+    long[] ids = new long[100];
+    for (int i = 0; i < 100; i++) {
+      ids[i] = i;
+    }
+    LongPrimitiveIterator possibleItemIds = new LongPrimitiveArrayIterator(ids);
+    TopItems.Estimator<Long> estimator = new TopItems.Estimator<Long>() {
+      @Override
+      public double estimate(Long thing) {
+        return thing;
+      }
+    };
+    long[] topItems = TopItems.getTopUsers(10, possibleItemIds, null, estimator);
+    int gold = 99;
+    for (long topItem : topItems) {
+      assertEquals(gold--, topItem);
+    }
+  }
+
+  @Test
+  public void testTopItemItem() throws Exception {
+    List<GenericItemSimilarity.ItemItemSimilarity> sims = Lists.newArrayList();
+    for (int i = 0; i < 99; i++) {
+      sims.add(new GenericItemSimilarity.ItemItemSimilarity(i, i + 1, i / 99.0));
+    }
+
+    List<GenericItemSimilarity.ItemItemSimilarity> res = TopItems.getTopItemItemSimilarities(10, sims.iterator());
+    int gold = 99;
+    for (GenericItemSimilarity.ItemItemSimilarity re : res) {
+      assertEquals(gold--, re.getItemID2()); //the second id should be equal to 99 to start
+    }
+  }
+
+  @Test
+  public void testTopItemItemAlt() throws Exception {
+    List<GenericItemSimilarity.ItemItemSimilarity> sims = Lists.newArrayList();
+    for (int i = 0; i < 99; i++) {
+      sims.add(new GenericItemSimilarity.ItemItemSimilarity(i, i + 1, 1 - (i / 99.0)));
+    }
+
+    List<GenericItemSimilarity.ItemItemSimilarity> res = TopItems.getTopItemItemSimilarities(10, sims.iterator());
+    int gold = 0;
+    for (GenericItemSimilarity.ItemItemSimilarity re : res) {
+      assertEquals(gold++, re.getItemID1()); //the second id should be equal to 99 to start
+    }
+  }
+
+  @Test
+  public void testTopUserUser() throws Exception {
+    List<GenericUserSimilarity.UserUserSimilarity> sims = Lists.newArrayList();
+    for (int i = 0; i < 99; i++) {
+      sims.add(new GenericUserSimilarity.UserUserSimilarity(i, i + 1, i / 99.0));
+    }
+
+    List<GenericUserSimilarity.UserUserSimilarity> res = TopItems.getTopUserUserSimilarities(10, sims.iterator());
+    int gold = 99;
+    for (GenericUserSimilarity.UserUserSimilarity re : res) {
+      assertEquals(gold--, re.getUserID2()); //the second id should be equal to 99 to start
+    }
+  }
+
+  @Test
+  public void testTopUserUserAlt() throws Exception {
+    List<GenericUserSimilarity.UserUserSimilarity> sims = Lists.newArrayList();
+    for (int i = 0; i < 99; i++) {
+      sims.add(new GenericUserSimilarity.UserUserSimilarity(i, i + 1, 1 - (i / 99.0)));
+    }
+
+    List<GenericUserSimilarity.UserUserSimilarity> res = TopItems.getTopUserUserSimilarities(10, sims.iterator());
+    int gold = 0;
+    for (GenericUserSimilarity.UserUserSimilarity re : res) {
+      assertEquals(gold++, re.getUserID1()); //the first id should be equal to 0 to start
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/mahout/blob/b988c493/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/ALSWRFactorizerTest.java
----------------------------------------------------------------------
diff --git a/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/ALSWRFactorizerTest.java b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/ALSWRFactorizerTest.java
new file mode 100644
index 0000000..23fa38f
--- /dev/null
+++ b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/ALSWRFactorizerTest.java
@@ -0,0 +1,208 @@
+/**
+ * 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.mahout.cf.taste.impl.recommender.svd;
+
+import org.apache.mahout.cf.taste.impl.TasteTestCase;
+import org.apache.mahout.cf.taste.impl.common.FastByIDMap;
+import org.apache.mahout.cf.taste.impl.common.FullRunningAverage;
+import org.apache.mahout.cf.taste.impl.common.LongPrimitiveIterator;
+import org.apache.mahout.cf.taste.impl.common.RunningAverage;
+import org.apache.mahout.cf.taste.impl.model.GenericDataModel;
+import org.apache.mahout.cf.taste.impl.model.GenericPreference;
+import org.apache.mahout.cf.taste.impl.model.GenericUserPreferenceArray;
+import org.apache.mahout.cf.taste.model.DataModel;
+import org.apache.mahout.cf.taste.model.Preference;
+import org.apache.mahout.cf.taste.model.PreferenceArray;
+import org.apache.mahout.math.DenseVector;
+import org.apache.mahout.math.Matrix;
+import org.apache.mahout.math.MatrixSlice;
+import org.apache.mahout.math.SparseRowMatrix;
+import org.apache.mahout.math.Vector;
+import org.junit.Before;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.carrotsearch.randomizedtesting.annotations.ThreadLeakLingering;
+
+import java.util.Arrays;
+import java.util.Iterator;
+
+public class ALSWRFactorizerTest extends TasteTestCase {
+
+  private ALSWRFactorizer factorizer;
+  private DataModel dataModel;
+
+  private static final Logger log = LoggerFactory.getLogger(ALSWRFactorizerTest.class);
+
+      /**
+       *  rating-matrix
+       *
+       *          burger  hotdog  berries  icecream
+       *  dog       5       5        2        -
+       *  rabbit    2       -        3        5
+       *  cow       -       5        -        3
+       *  donkey    3       -        -        5
+       */
+
+  @Override
+  @Before
+  public void setUp() throws Exception {
+    super.setUp();
+    FastByIDMap<PreferenceArray> userData = new FastByIDMap<PreferenceArray>();
+
+    userData.put(1L, new GenericUserPreferenceArray(Arrays.asList(new GenericPreference(1L, 1L, 5.0f),
+                                                                  new GenericPreference(1L, 2L, 5.0f),
+                                                                  new GenericPreference(1L, 3L, 2.0f))));
+
+    userData.put(2L, new GenericUserPreferenceArray(Arrays.asList(new GenericPreference(2L, 1L, 2.0f),
+                                                                  new GenericPreference(2L, 3L, 3.0f),
+                                                                  new GenericPreference(2L, 4L, 5.0f))));
+
+    userData.put(3L, new GenericUserPreferenceArray(Arrays.asList(new GenericPreference(3L, 2L, 5.0f),
+                                                                  new GenericPreference(3L, 4L, 3.0f))));
+
+    userData.put(4L, new GenericUserPreferenceArray(Arrays.asList(new GenericPreference(4L, 1L, 3.0f),
+                                                                  new GenericPreference(4L, 4L, 5.0f))));
+
+    dataModel = new GenericDataModel(userData);
+    factorizer = new ALSWRFactorizer(dataModel, 3, 0.065, 10);
+  }
+
+  @Test
+  public void setFeatureColumn() throws Exception {
+    ALSWRFactorizer.Features features = new ALSWRFactorizer.Features(factorizer);
+    Vector vector = new DenseVector(new double[] { 0.5, 2.0, 1.5 });
+    int index = 1;
+
+    features.setFeatureColumnInM(index, vector);
+    double[][] matrix = features.getM();
+
+    assertEquals(vector.get(0), matrix[index][0], EPSILON);
+    assertEquals(vector.get(1), matrix[index][1], EPSILON);
+    assertEquals(vector.get(2), matrix[index][2], EPSILON);
+  }
+
+  @Test
+  public void ratingVector() throws Exception {
+    PreferenceArray prefs = dataModel.getPreferencesFromUser(1);
+
+    Vector ratingVector = ALSWRFactorizer.ratingVector(prefs);
+
+    assertEquals(prefs.length(), ratingVector.getNumNondefaultElements());
+    assertEquals(prefs.get(0).getValue(), ratingVector.get(0), EPSILON);
+    assertEquals(prefs.get(1).getValue(), ratingVector.get(1), EPSILON);
+    assertEquals(prefs.get(2).getValue(), ratingVector.get(2), EPSILON);
+  }
+
+  @Test
+  public void averageRating() throws Exception {
+    ALSWRFactorizer.Features features = new ALSWRFactorizer.Features(factorizer);
+    assertEquals(2.5, features.averateRating(3L), EPSILON);
+  }
+
+  @Test
+  public void initializeM() throws Exception {
+    ALSWRFactorizer.Features features = new ALSWRFactorizer.Features(factorizer);
+    double[][] M = features.getM();
+
+    assertEquals(3.333333333, M[0][0], EPSILON);
+    assertEquals(5, M[1][0], EPSILON);
+    assertEquals(2.5, M[2][0], EPSILON);
+    assertEquals(4.333333333, M[3][0], EPSILON);
+
+    for (int itemIndex = 0; itemIndex < dataModel.getNumItems(); itemIndex++) {
+      for (int feature = 1; feature < 3; feature++ ) {
+        assertTrue(M[itemIndex][feature] >= 0);
+        assertTrue(M[itemIndex][feature] <= 0.1);
+      }
+    }
+  }
+
+  @ThreadLeakLingering(linger = 10)
+  @Test
+  public void toyExample() throws Exception {
+
+    SVDRecommender svdRecommender = new SVDRecommender(dataModel, factorizer);
+
+   /* a hold out test would be better, but this is just a toy example so we only check that the
+    * factorization is close to the original matrix */
+    RunningAverage avg = new FullRunningAverage();
+    LongPrimitiveIterator userIDs = dataModel.getUserIDs();
+    while (userIDs.hasNext()) {
+      long userID = userIDs.nextLong();
+      for (Preference pref : dataModel.getPreferencesFromUser(userID)) {
+        double rating = pref.getValue();
+        double estimate = svdRecommender.estimatePreference(userID, pref.getItemID());
+        double err = rating - estimate;
+        avg.addDatum(err * err);
+      }
+    }
+
+    double rmse = Math.sqrt(avg.getAverage());
+    assertTrue(rmse < 0.2);
+  }
+
+  @Test
+  public void toyExampleImplicit() throws Exception {
+
+    Matrix observations = new SparseRowMatrix(4, 4, new Vector[] {
+        new DenseVector(new double[] { 5.0, 5.0, 2.0, 0 }),
+        new DenseVector(new double[] { 2.0, 0,   3.0, 5.0 }),
+        new DenseVector(new double[] { 0,   5.0, 0,   3.0 }),
+        new DenseVector(new double[] { 3.0, 0,   0,   5.0 }) });
+
+    Matrix preferences = new SparseRowMatrix(4, 4, new Vector[] {
+        new DenseVector(new double[] { 1.0, 1.0, 1.0, 0 }),
+        new DenseVector(new double[] { 1.0, 0,   1.0, 1.0 }),
+        new DenseVector(new double[] { 0,   1.0, 0,   1.0 }),
+        new DenseVector(new double[] { 1.0, 0,   0,   1.0 }) });
+
+    double alpha = 20;
+
+    ALSWRFactorizer factorizer = new ALSWRFactorizer(dataModel, 3, 0.065, 5, true, alpha);
+
+    SVDRecommender svdRecommender = new SVDRecommender(dataModel, factorizer);
+
+    RunningAverage avg = new FullRunningAverage();
+    Iterator<MatrixSlice> sliceIterator = preferences.iterateAll();
+    while (sliceIterator.hasNext()) {
+      MatrixSlice slice = sliceIterator.next();
+      for (Vector.Element e : slice.vector().all()) {
+
+        long userID = slice.index() + 1;
+        long itemID = e.index() + 1;
+
+        if (!Double.isNaN(e.get())) {
+          double pref = e.get();
+          double estimate = svdRecommender.estimatePreference(userID, itemID);
+
+          double confidence = 1 + alpha * observations.getQuick(slice.index(), e.index());
+          double err = confidence * (pref - estimate) * (pref - estimate);
+          avg.addDatum(err);
+          log.info("Comparing preference of user [{}] towards item [{}], was [{}] with confidence [{}] "
+              + "estimate is [{}]", slice.index(), e.index(), pref, confidence, estimate);
+        }
+      }
+    }
+    double rmse = Math.sqrt(avg.getAverage());
+    log.info("RMSE: {}", rmse);
+
+    assertTrue(rmse < 0.4);
+  }
+}

http://git-wip-us.apache.org/repos/asf/mahout/blob/b988c493/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/FilePersistenceStrategyTest.java
----------------------------------------------------------------------
diff --git a/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/FilePersistenceStrategyTest.java b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/FilePersistenceStrategyTest.java
new file mode 100644
index 0000000..eb8a356
--- /dev/null
+++ b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/FilePersistenceStrategyTest.java
@@ -0,0 +1,53 @@
+/**
+ * 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.mahout.cf.taste.impl.recommender.svd;
+
+import org.apache.mahout.cf.taste.impl.TasteTestCase;
+import org.apache.mahout.cf.taste.impl.common.FastByIDMap;
+import org.junit.Test;
+
+import java.io.File;
+
+public class FilePersistenceStrategyTest extends TasteTestCase {
+
+  @Test
+  public void persistAndLoad() throws Exception {
+    FastByIDMap<Integer> userIDMapping = new FastByIDMap<Integer>();
+    FastByIDMap<Integer> itemIDMapping = new FastByIDMap<Integer>();
+
+    userIDMapping.put(123, 0);
+    userIDMapping.put(456, 1);
+
+    itemIDMapping.put(12, 0);
+    itemIDMapping.put(34, 1);
+
+    double[][] userFeatures = { { 0.1, 0.2, 0.3 }, { 0.4, 0.5, 0.6 } };
+    double[][] itemFeatures = { { 0.7, 0.8, 0.9 }, { 1.0, 1.1, 1.2 } };
+
+    Factorization original = new Factorization(userIDMapping, itemIDMapping, userFeatures, itemFeatures);
+    File storage = getTestTempFile("storage.bin");
+    PersistenceStrategy persistenceStrategy = new FilePersistenceStrategy(storage);
+
+    assertNull(persistenceStrategy.load());
+
+    persistenceStrategy.maybePersist(original);
+    Factorization clone = persistenceStrategy.load();
+
+    assertEquals(original, clone);
+  }
+}

http://git-wip-us.apache.org/repos/asf/mahout/blob/b988c493/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/ParallelSGDFactorizerTest.java
----------------------------------------------------------------------
diff --git a/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/ParallelSGDFactorizerTest.java b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/ParallelSGDFactorizerTest.java
new file mode 100644
index 0000000..8a91e7a
--- /dev/null
+++ b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/ParallelSGDFactorizerTest.java
@@ -0,0 +1,355 @@
+/**
+ * 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.mahout.cf.taste.impl.recommender.svd;
+
+import java.util.Arrays;
+import java.util.List;
+
+import com.carrotsearch.randomizedtesting.annotations.ThreadLeakLingering;
+import com.google.common.collect.Lists;
+import org.apache.mahout.cf.taste.impl.TasteTestCase;
+import org.apache.mahout.cf.taste.impl.common.FastByIDMap;
+import org.apache.mahout.cf.taste.impl.common.FullRunningAverage;
+import org.apache.mahout.cf.taste.impl.common.LongPrimitiveIterator;
+import org.apache.mahout.cf.taste.impl.common.RunningAverage;
+import org.apache.mahout.cf.taste.impl.model.GenericDataModel;
+import org.apache.mahout.cf.taste.impl.model.GenericPreference;
+import org.apache.mahout.cf.taste.impl.model.GenericUserPreferenceArray;
+import org.apache.mahout.cf.taste.impl.recommender.svd.ParallelSGDFactorizer.PreferenceShuffler;
+import org.apache.mahout.cf.taste.model.DataModel;
+import org.apache.mahout.cf.taste.model.Preference;
+import org.apache.mahout.cf.taste.model.PreferenceArray;
+import org.apache.mahout.common.RandomUtils;
+import org.apache.mahout.common.RandomWrapper;
+import org.apache.mahout.math.DenseMatrix;
+import org.apache.mahout.math.DenseVector;
+import org.apache.mahout.math.Matrix;
+import org.apache.mahout.math.Vector;
+import org.apache.mahout.math.function.DoubleFunction;
+import org.apache.mahout.math.function.VectorFunction;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class ParallelSGDFactorizerTest extends TasteTestCase {
+
+  protected DataModel dataModel;
+
+  protected int rank;
+  protected double lambda;
+  protected int numIterations;
+
+  private RandomWrapper random = (RandomWrapper) RandomUtils.getRandom();
+
+  protected Factorizer factorizer;
+  protected SVDRecommender svdRecommender;
+
+  private static final Logger logger = LoggerFactory.getLogger(ParallelSGDFactorizerTest.class);
+
+  private Matrix randomMatrix(int numRows, int numColumns, double range) {
+    double[][] data = new double[numRows][numColumns];
+    for (int i = 0; i < numRows; i++) {
+      for (int j = 0; j < numColumns; j++) {
+        double sqrtUniform = random.nextDouble();
+        data[i][j] = sqrtUniform * range;
+      }
+    }
+    return new DenseMatrix(data);
+  }
+
+  private void normalize(Matrix source, final double range) {
+    final double max = source.aggregateColumns(new VectorFunction() {
+      @Override
+      public double apply(Vector column) {
+        return column.maxValue();
+      }
+    }).maxValue();
+
+    final double min = source.aggregateColumns(new VectorFunction() {
+      @Override
+      public double apply(Vector column) {
+        return column.minValue();
+      }
+    }).minValue();
+
+    source.assign(new DoubleFunction() {
+      @Override
+      public double apply(double value) {
+        return (value - min) * range / (max - min);
+      }
+    });
+  }
+
+  public void setUpSyntheticData() throws Exception {
+
+    int numUsers = 2000;
+    int numItems = 1000;
+    double sparsity = 0.5;
+
+    this.rank = 20;
+    this.lambda = 0.000000001;
+    this.numIterations = 100;
+
+    Matrix users = randomMatrix(numUsers, rank, 1);
+    Matrix items = randomMatrix(rank, numItems, 1);
+    Matrix ratings = users.times(items);
+    normalize(ratings, 5);
+
+    FastByIDMap<PreferenceArray> userData = new FastByIDMap<PreferenceArray>();
+    for (int userIndex = 0; userIndex < numUsers; userIndex++) {
+      List<Preference> row= Lists.newArrayList();
+      for (int itemIndex = 0; itemIndex < numItems; itemIndex++) {
+        if (random.nextDouble() <= sparsity) {
+          row.add(new GenericPreference(userIndex, itemIndex, (float) ratings.get(userIndex, itemIndex)));
+        }
+      }
+
+      userData.put(userIndex, new GenericUserPreferenceArray(row));
+    }
+
+    dataModel = new GenericDataModel(userData);
+  }
+
+  public void setUpToyData() throws Exception {
+    this.rank = 3;
+    this.lambda = 0.01;
+    this.numIterations = 1000;
+
+    FastByIDMap<PreferenceArray> userData = new FastByIDMap<PreferenceArray>();
+
+    userData.put(1L, new GenericUserPreferenceArray(Arrays.asList(new GenericPreference(1L, 1L, 5.0f),
+        new GenericPreference(1L, 2L, 5.0f),
+        new GenericPreference(1L, 3L, 2.0f))));
+
+    userData.put(2L, new GenericUserPreferenceArray(Arrays.asList(new GenericPreference(2L, 1L, 2.0f),
+        new GenericPreference(2L, 3L, 3.0f),
+        new GenericPreference(2L, 4L, 5.0f))));
+
+    userData.put(3L, new GenericUserPreferenceArray(Arrays.asList(new GenericPreference(3L, 2L, 5.0f),
+        new GenericPreference(3L, 4L, 3.0f))));
+
+    userData.put(4L, new GenericUserPreferenceArray(Arrays.asList(new GenericPreference(4L, 1L, 3.0f),
+        new GenericPreference(4L, 4L, 5.0f))));
+    dataModel = new GenericDataModel(userData);
+  }
+
+  @Test
+  public void testPreferenceShufflerWithSyntheticData() throws Exception {
+    setUpSyntheticData();
+
+    ParallelSGDFactorizer.PreferenceShuffler shuffler = new PreferenceShuffler(dataModel);
+    shuffler.shuffle();
+    shuffler.stage();
+
+    FastByIDMap<FastByIDMap<Boolean>> checked = new FastByIDMap<FastByIDMap<Boolean>>();
+
+    for (int i = 0; i < shuffler.size(); i++) {
+      Preference pref=shuffler.get(i);
+
+      float value = dataModel.getPreferenceValue(pref.getUserID(), pref.getItemID());
+      assertEquals(pref.getValue(), value, 0.0);
+      if (!checked.containsKey(pref.getUserID())) {
+        checked.put(pref.getUserID(), new FastByIDMap<Boolean>());
+      }
+
+      assertNull(checked.get(pref.getUserID()).get(pref.getItemID()));
+
+      checked.get(pref.getUserID()).put(pref.getItemID(), true);
+    }
+
+    LongPrimitiveIterator userIDs = dataModel.getUserIDs();
+    int index=0;
+    while (userIDs.hasNext()) {
+      long userID = userIDs.nextLong();
+      PreferenceArray preferencesFromUser = dataModel.getPreferencesFromUser(userID);
+      for (Preference preference : preferencesFromUser) {
+        assertTrue(checked.get(preference.getUserID()).get(preference.getItemID()));
+        index++;
+      }
+    }
+    assertEquals(index, shuffler.size());
+  }
+
+  @ThreadLeakLingering(linger = 1000)
+  @Test
+  public void testFactorizerWithToyData() throws Exception {
+
+    setUpToyData();
+
+    long start = System.currentTimeMillis();
+
+    factorizer = new ParallelSGDFactorizer(dataModel, rank, lambda, numIterations, 0.01, 1, 0, 0);
+
+    Factorization factorization = factorizer.factorize();
+
+    long duration = System.currentTimeMillis() - start;
+
+    /* a hold out test would be better, but this is just a toy example so we only check that the
+     * factorization is close to the original matrix */
+    RunningAverage avg = new FullRunningAverage();
+    LongPrimitiveIterator userIDs = dataModel.getUserIDs();
+    LongPrimitiveIterator itemIDs;
+
+    while (userIDs.hasNext()) {
+      long userID = userIDs.nextLong();
+      for (Preference pref : dataModel.getPreferencesFromUser(userID)) {
+        double rating = pref.getValue();
+        Vector userVector = new DenseVector(factorization.getUserFeatures(userID));
+        Vector itemVector = new DenseVector(factorization.getItemFeatures(pref.getItemID()));
+        double estimate = userVector.dot(itemVector);
+        double err = rating - estimate;
+
+        avg.addDatum(err * err);
+      }
+    }
+
+    double sum = 0.0;
+
+    userIDs = dataModel.getUserIDs();
+    while (userIDs.hasNext()) {
+      long userID = userIDs.nextLong();
+      Vector userVector = new DenseVector(factorization.getUserFeatures(userID));
+      double regularization = userVector.dot(userVector);
+      sum += regularization;
+    }
+
+    itemIDs = dataModel.getItemIDs();
+    while (itemIDs.hasNext()) {
+      long itemID = itemIDs.nextLong();
+      Vector itemVector = new DenseVector(factorization.getUserFeatures(itemID));
+      double regularization = itemVector.dot(itemVector);
+      sum += regularization;
+    }
+
+    double rmse = Math.sqrt(avg.getAverage());
+    double loss = avg.getAverage() / 2 + lambda / 2 * sum;
+    logger.info("RMSE: " + rmse + ";\tLoss: " + loss + ";\tTime Used: " + duration);
+    assertTrue(rmse < 0.2);
+  }
+
+  @ThreadLeakLingering(linger = 1000)
+  @Test
+  public void testRecommenderWithToyData() throws Exception {
+
+    setUpToyData();
+
+    factorizer = new ParallelSGDFactorizer(dataModel, rank, lambda, numIterations, 0.01, 1, 0,0);
+    svdRecommender = new SVDRecommender(dataModel, factorizer);
+
+    /* a hold out test would be better, but this is just a toy example so we only check that the
+     * factorization is close to the original matrix */
+    RunningAverage avg = new FullRunningAverage();
+    LongPrimitiveIterator userIDs = dataModel.getUserIDs();
+    while (userIDs.hasNext()) {
+      long userID = userIDs.nextLong();
+      for (Preference pref : dataModel.getPreferencesFromUser(userID)) {
+        double rating = pref.getValue();
+        double estimate = svdRecommender.estimatePreference(userID, pref.getItemID());
+        double err = rating - estimate;
+        avg.addDatum(err * err);
+      }
+    }
+
+    double rmse = Math.sqrt(avg.getAverage());
+    logger.info("rmse: " + rmse);
+    assertTrue(rmse < 0.2);
+  }
+
+  @Test
+  public void testFactorizerWithWithSyntheticData() throws Exception {
+
+    setUpSyntheticData();
+
+    long start = System.currentTimeMillis();
+
+    factorizer = new ParallelSGDFactorizer(dataModel, rank, lambda, numIterations, 0.01, 1, 0, 0);
+
+    Factorization factorization = factorizer.factorize();
+
+    long duration = System.currentTimeMillis() - start;
+
+    /* a hold out test would be better, but this is just a toy example so we only check that the
+     * factorization is close to the original matrix */
+    RunningAverage avg = new FullRunningAverage();
+    LongPrimitiveIterator userIDs = dataModel.getUserIDs();
+    LongPrimitiveIterator itemIDs;
+
+    while (userIDs.hasNext()) {
+      long userID = userIDs.nextLong();
+      for (Preference pref : dataModel.getPreferencesFromUser(userID)) {
+        double rating = pref.getValue();
+        Vector userVector = new DenseVector(factorization.getUserFeatures(userID));
+        Vector itemVector = new DenseVector(factorization.getItemFeatures(pref.getItemID()));
+        double estimate = userVector.dot(itemVector);
+        double err = rating - estimate;
+
+        avg.addDatum(err * err);
+      }
+    }
+
+    double sum = 0.0;
+
+    userIDs = dataModel.getUserIDs();
+    while (userIDs.hasNext()) {
+      long userID = userIDs.nextLong();
+      Vector userVector = new DenseVector(factorization.getUserFeatures(userID));
+      double regularization=userVector.dot(userVector);
+      sum += regularization;
+    }
+
+    itemIDs = dataModel.getItemIDs();
+    while (itemIDs.hasNext()) {
+      long itemID = itemIDs.nextLong();
+      Vector itemVector = new DenseVector(factorization.getUserFeatures(itemID));
+      double regularization = itemVector.dot(itemVector);
+      sum += regularization;
+    }
+
+    double rmse = Math.sqrt(avg.getAverage());
+    double loss = avg.getAverage() / 2 + lambda / 2 * sum;
+    logger.info("RMSE: " + rmse + ";\tLoss: " + loss + ";\tTime Used: " + duration + "ms");
+    assertTrue(rmse < 0.2);
+  }
+
+  @Test
+  public void testRecommenderWithSyntheticData() throws Exception {
+
+    setUpSyntheticData();
+
+    factorizer= new ParallelSGDFactorizer(dataModel, rank, lambda, numIterations, 0.01, 1, 0, 0);
+    svdRecommender = new SVDRecommender(dataModel, factorizer);
+
+    /* a hold out test would be better, but this is just a toy example so we only check that the
+     * factorization is close to the original matrix */
+    RunningAverage avg = new FullRunningAverage();
+    LongPrimitiveIterator userIDs = dataModel.getUserIDs();
+    while (userIDs.hasNext()) {
+      long userID = userIDs.nextLong();
+      for (Preference pref : dataModel.getPreferencesFromUser(userID)) {
+        double rating = pref.getValue();
+        double estimate = svdRecommender.estimatePreference(userID, pref.getItemID());
+        double err = rating - estimate;
+        avg.addDatum(err * err);
+      }
+    }
+
+    double rmse = Math.sqrt(avg.getAverage());
+    logger.info("rmse: " + rmse);
+    assertTrue(rmse < 0.2);
+  }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/mahout/blob/b988c493/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/SVDRecommenderTest.java
----------------------------------------------------------------------
diff --git a/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/SVDRecommenderTest.java b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/SVDRecommenderTest.java
new file mode 100644
index 0000000..aebd324
--- /dev/null
+++ b/mr/src/test/java/org/apache/mahout/cf/taste/impl/recommender/svd/SVDRecommenderTest.java
@@ -0,0 +1,86 @@
+/**
+ * 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.mahout.cf.taste.impl.recommender.svd;
+
+import org.apache.mahout.cf.taste.impl.TasteTestCase;
+import org.apache.mahout.cf.taste.impl.common.FastIDSet;
+import org.apache.mahout.cf.taste.model.DataModel;
+import org.apache.mahout.cf.taste.model.PreferenceArray;
+import org.apache.mahout.cf.taste.recommender.CandidateItemsStrategy;
+import org.apache.mahout.cf.taste.recommender.RecommendedItem;
+import org.easymock.EasyMock;
+import org.junit.Test;
+
+import java.util.List;
+
+public class SVDRecommenderTest extends TasteTestCase {
+
+  @Test
+  public void estimatePreference() throws Exception {
+    DataModel dataModel = EasyMock.createMock(DataModel.class);
+    Factorizer factorizer = EasyMock.createMock(Factorizer.class);
+    Factorization factorization = EasyMock.createMock(Factorization.class);
+
+    EasyMock.expect(factorizer.factorize()).andReturn(factorization);
+    EasyMock.expect(factorization.getUserFeatures(1L)).andReturn(new double[] { 0.4, 2 });
+    EasyMock.expect(factorization.getItemFeatures(5L)).andReturn(new double[] { 1, 0.3 });
+    EasyMock.replay(dataModel, factorizer, factorization);
+
+    SVDRecommender svdRecommender = new SVDRecommender(dataModel, factorizer);
+
+    float estimate = svdRecommender.estimatePreference(1L, 5L);
+    assertEquals(1, estimate, EPSILON);
+
+    EasyMock.verify(dataModel, factorizer, factorization);
+  }
+
+  @Test
+  public void recommend() throws Exception {
+    DataModel dataModel = EasyMock.createMock(DataModel.class);
+    PreferenceArray preferencesFromUser = EasyMock.createMock(PreferenceArray.class);
+    CandidateItemsStrategy candidateItemsStrategy = EasyMock.createMock(CandidateItemsStrategy.class);
+    Factorizer factorizer = EasyMock.createMock(Factorizer.class);
+    Factorization factorization = EasyMock.createMock(Factorization.class);
+
+    FastIDSet candidateItems = new FastIDSet();
+    candidateItems.add(5L);
+    candidateItems.add(3L);
+
+    EasyMock.expect(factorizer.factorize()).andReturn(factorization);
+    EasyMock.expect(dataModel.getPreferencesFromUser(1L)).andReturn(preferencesFromUser);
+    EasyMock.expect(candidateItemsStrategy.getCandidateItems(1L, preferencesFromUser, dataModel, false))
+        .andReturn(candidateItems);
+    EasyMock.expect(factorization.getUserFeatures(1L)).andReturn(new double[] { 0.4, 2 });
+    EasyMock.expect(factorization.getItemFeatures(5L)).andReturn(new double[] { 1, 0.3 });
+    EasyMock.expect(factorization.getUserFeatures(1L)).andReturn(new double[] { 0.4, 2 });
+    EasyMock.expect(factorization.getItemFeatures(3L)).andReturn(new double[] { 2, 0.6 });
+
+    EasyMock.replay(dataModel, candidateItemsStrategy, factorizer, factorization);
+
+    SVDRecommender svdRecommender = new SVDRecommender(dataModel, factorizer, candidateItemsStrategy);
+
+    List<RecommendedItem> recommendedItems = svdRecommender.recommend(1L, 5);
+    assertEquals(2, recommendedItems.size());
+    assertEquals(3L, recommendedItems.get(0).getItemID());
+    assertEquals(2.0f, recommendedItems.get(0).getValue(), EPSILON);
+    assertEquals(5L, recommendedItems.get(1).getItemID());
+    assertEquals(1.0f, recommendedItems.get(1).getValue(), EPSILON);
+
+    EasyMock.verify(dataModel, candidateItemsStrategy, factorizer, factorization);
+  }
+}

http://git-wip-us.apache.org/repos/asf/mahout/blob/b988c493/mr/src/test/java/org/apache/mahout/cf/taste/impl/similarity/AveragingPreferenceInferrerTest.java
----------------------------------------------------------------------
diff --git a/mr/src/test/java/org/apache/mahout/cf/taste/impl/similarity/AveragingPreferenceInferrerTest.java b/mr/src/test/java/org/apache/mahout/cf/taste/impl/similarity/AveragingPreferenceInferrerTest.java
new file mode 100644
index 0000000..d8242e3
--- /dev/null
+++ b/mr/src/test/java/org/apache/mahout/cf/taste/impl/similarity/AveragingPreferenceInferrerTest.java
@@ -0,0 +1,37 @@
+/**
+ * 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.mahout.cf.taste.impl.similarity;
+
+import org.apache.mahout.cf.taste.common.TasteException;
+import org.apache.mahout.cf.taste.impl.TasteTestCase;
+import org.apache.mahout.cf.taste.model.DataModel;
+import org.apache.mahout.cf.taste.similarity.PreferenceInferrer;
+import org.junit.Test;
+
+/** <p>Tests {@link AveragingPreferenceInferrer}.</p> */
+public final class AveragingPreferenceInferrerTest extends TasteTestCase {
+
+  @Test
+  public void testInferrer() throws TasteException {
+    DataModel model = getDataModel(new long[] {1}, new Double[][] {{3.0,-2.0,5.0}});
+    PreferenceInferrer inferrer = new AveragingPreferenceInferrer(model);
+    double inferred = inferrer.inferPreference(1, 3);
+    assertEquals(2.0, inferred, EPSILON);
+  }
+
+}