You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@lucene.apache.org by cp...@apache.org on 2016/10/19 02:18:19 UTC

[2/3] lucene-solr:jira/solr-8542-v2: Generic models for ranking (#174)

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test-files/modelExamples/svm-model-efi.json
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test-files/modelExamples/svm-model-efi.json b/solr/contrib/ltr/src/test-files/modelExamples/svm-model-efi.json
deleted file mode 100644
index f7bf902..0000000
--- a/solr/contrib/ltr/src/test-files/modelExamples/svm-model-efi.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
-  "class":"org.apache.solr.ltr.model.RankSVMModel",
-  "name":"svm-efi",
-  "features":[
-    {"name":"sampleConstant"},
-    {"name":"search_number_of_nights"}
-  ],
-  "params":{
-    "weights":{
-      "sampleConstant":1.0,
-      "search_number_of_nights":2.0
-    }
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test-files/modelExamples/svm-model.json
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test-files/modelExamples/svm-model.json b/solr/contrib/ltr/src/test-files/modelExamples/svm-model.json
deleted file mode 100644
index d3cbfc4..0000000
--- a/solr/contrib/ltr/src/test-files/modelExamples/svm-model.json
+++ /dev/null
@@ -1,20 +0,0 @@
-{
-	"class":"org.apache.solr.ltr.model.RankSVMModel",
-	"name":"svm",
-	"features":[
-		{"name":"constant1"},
-	    {"name":"constant2"},
-	    {"name":"constant3"},
-        {"name":"constant4"},
-	    {"name":"constant5"}
-	],
-	 "params":{
-     "weights":{
-       "constant1":1,
-       "constant2":2,
-       "constant3":3,
-       "constant4":4,
-       "constant5":5
-     }
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestLTROnSolrCloud.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestLTROnSolrCloud.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestLTROnSolrCloud.java
index f929e4b..c9f00dd 100644
--- a/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestLTROnSolrCloud.java
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestLTROnSolrCloud.java
@@ -31,7 +31,7 @@ import org.apache.solr.common.cloud.ZkStateReader;
 import org.apache.solr.ltr.TestRerankBase;
 import org.apache.solr.ltr.feature.SolrFeature;
 import org.apache.solr.ltr.feature.ValueFeature;
-import org.apache.solr.ltr.model.RankSVMModel;
+import org.apache.solr.ltr.model.LinearModel;
 import org.eclipse.jetty.servlet.ServletHolder;
 import org.junit.AfterClass;
 import org.junit.Test;
@@ -192,7 +192,7 @@ public class TestLTROnSolrCloud extends TestRerankBase {
 
     loadModel(
              "powpularityS-model",
-             RankSVMModel.class.getCanonicalName(),
+             LinearModel.class.getCanonicalName(),
              featureNames,
              featureStore,
              jsonModelParams

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestLTRQParserExplain.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestLTRQParserExplain.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestLTRQParserExplain.java
index 72d7e4e..8a46336 100644
--- a/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestLTRQParserExplain.java
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestLTRQParserExplain.java
@@ -19,7 +19,7 @@ package org.apache.solr.ltr;
 import org.apache.solr.SolrTestCaseJ4.SuppressSSL;
 import org.apache.solr.client.solrj.SolrQuery;
 import org.apache.solr.ltr.TestRerankBase;
-import org.apache.solr.ltr.model.RankSVMModel;
+import org.apache.solr.ltr.model.LinearModel;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -41,7 +41,7 @@ public class TestLTRQParserExplain extends TestRerankBase {
 
   @Test
   public void testRerankedExplain() throws Exception {
-    loadModel("svm2", RankSVMModel.class.getCanonicalName(), new String[] {
+    loadModel("linear2", LinearModel.class.getCanonicalName(), new String[] {
         "constant1", "constant2", "pop"},
         "{\"weights\":{\"pop\":1.0,\"constant1\":1.5,\"constant2\":3.5}}");
 
@@ -49,18 +49,18 @@ public class TestLTRQParserExplain extends TestRerankBase {
     query.setQuery("title:bloomberg");
     query.setParam("debugQuery", "on");
     query.add("rows", "2");
-    query.add("rq", "{!ltr reRankDocs=2 model=svm2}");
+    query.add("rq", "{!ltr reRankDocs=2 model=linear2}");
     query.add("fl", "*,score");
     
     assertJQ(
         "/query" + query.toQueryString(),
-        "/debug/explain/9=='\n13.5 = RankSVMModel(name=svm2,featureWeights=[constant1=1.5,constant2=3.5,pop=1.0]) model applied to features, sum of:\n  1.5 = prod of:\n    1.5 = weight on feature\n    1.0 = ValueFeature [name=constant1, params={value=1}]\n  7.0 = prod of:\n    3.5 = weight on feature\n    2.0 = ValueFeature [name=constant2, params={value=2}]\n  5.0 = prod of:\n    1.0 = weight on feature\n    5.0 = FieldValueFeature [name=pop, params={field=popularity}]\n'");
+        "/debug/explain/9=='\n13.5 = LinearModel(name=linear2,featureWeights=[constant1=1.5,constant2=3.5,pop=1.0]) model applied to features, sum of:\n  1.5 = prod of:\n    1.5 = weight on feature\n    1.0 = ValueFeature [name=constant1, params={value=1}]\n  7.0 = prod of:\n    3.5 = weight on feature\n    2.0 = ValueFeature [name=constant2, params={value=2}]\n  5.0 = prod of:\n    1.0 = weight on feature\n    5.0 = FieldValueFeature [name=pop, params={field=popularity}]\n'");
   }
 
   @Test
   public void testRerankedExplainSameBetweenDifferentDocsWithSameFeatures() throws Exception {
-    loadFeatures("features-ranksvm.json");
-    loadModels("ranksvm-model.json");
+    loadFeatures("features-linear.json");
+    loadModels("linear-model.json");
 
     final SolrQuery query = new SolrQuery();
     query.setQuery("title:bloomberg");
@@ -70,7 +70,7 @@ public class TestLTRQParserExplain extends TestRerankBase {
     query.add("fl", "*,score");
     query.add("wt", "json");
     final String expectedExplainNormalizer = "normalized using MinMaxNormalizer(min=0.0,max=10.0)";
-    final String expectedExplain = "\n3.5116758 = RankSVMModel(name=6029760550880411648,featureWeights=["
+    final String expectedExplain = "\n3.5116758 = LinearModel(name=6029760550880411648,featureWeights=["
         + "title=0.0,"
         + "description=0.1,"
         + "keywords=0.2,"
@@ -89,19 +89,19 @@ public class TestLTRQParserExplain extends TestRerankBase {
   }
 
   @Test
-  public void SVMScoreExplainMissingEfiFeatureShouldReturnDefaultScore() throws Exception {
-    loadFeatures("features-ranksvm-efi.json");
-    loadModels("svm-model-efi.json");
+  public void LinearScoreExplainMissingEfiFeatureShouldReturnDefaultScore() throws Exception {
+    loadFeatures("features-linear-efi.json");
+    loadModels("linear-model-efi.json");
 
     SolrQuery query = new SolrQuery();
     query.setQuery("title:bloomberg");
     query.setParam("debugQuery", "on");
     query.add("rows", "4");
-    query.add("rq", "{!ltr reRankDocs=4 model=svm-efi}");
+    query.add("rq", "{!ltr reRankDocs=4 model=linear-efi}");
     query.add("fl", "*,score");
     query.add("wt", "xml");
 
-    final String svmModelEfiString = "RankSVMModel(name=svm-efi,featureWeights=["
+    final String linearModelEfiString = "LinearModel(name=linear-efi,featureWeights=["
       + "sampleConstant=1.0,"
       + "search_number_of_nights=2.0])";
 
@@ -109,22 +109,22 @@ public class TestLTRQParserExplain extends TestRerankBase {
     query.add("wt", "json");
     assertJQ(
         "/query" + query.toQueryString(),
-        "/debug/explain/7=='\n5.0 = "+svmModelEfiString+" model applied to features, sum of:\n  5.0 = prod of:\n    1.0 = weight on feature\n    5.0 = ValueFeature [name=sampleConstant, params={value=5}]\n" +
+        "/debug/explain/7=='\n5.0 = "+linearModelEfiString+" model applied to features, sum of:\n  5.0 = prod of:\n    1.0 = weight on feature\n    5.0 = ValueFeature [name=sampleConstant, params={value=5}]\n" +
             "  0.0 = prod of:\n" +
             "    2.0 = weight on feature\n" +
             "    0.0 = The feature has no value\n'}");
     assertJQ(
         "/query" + query.toQueryString(),
-        "/debug/explain/9=='\n5.0 = "+svmModelEfiString+" model applied to features, sum of:\n  5.0 = prod of:\n    1.0 = weight on feature\n    5.0 = ValueFeature [name=sampleConstant, params={value=5}]\n" +
+        "/debug/explain/9=='\n5.0 = "+linearModelEfiString+" model applied to features, sum of:\n  5.0 = prod of:\n    1.0 = weight on feature\n    5.0 = ValueFeature [name=sampleConstant, params={value=5}]\n" +
             "  0.0 = prod of:\n" +
             "    2.0 = weight on feature\n" +
             "    0.0 = The feature has no value\n'}");
   }
 
   @Test
-  public void lambdaMARTScoreExplainMissingEfiFeatureShouldReturnDefaultScore() throws Exception {
+  public void multipleAdditiveTreesScoreExplainMissingEfiFeatureShouldReturnDefaultScore() throws Exception {
     loadFeatures("external_features_for_sparse_processing.json");
-    loadModels("lambdamartmodel_external_binary_features.json");
+    loadModels("multipleadditivetreesmodel_external_binary_features.json");
 
     SolrQuery query = new SolrQuery();
     query.setQuery("title:bloomberg");
@@ -141,13 +141,13 @@ public class TestLTRQParserExplain extends TestRerankBase {
     assertJQ(
         "/query" + query.toQueryString(),
         "/debug/explain/7=='\n" +
-            "65.0 = LambdaMARTModel(name=external_model_binary_feature,trees="+trees+") model applied to features, sum of:\n" +
+            "65.0 = MultipleAdditiveTreesModel(name=external_model_binary_feature,trees="+trees+") model applied to features, sum of:\n" +
             "  0.0 = tree 0 | \\'user_device_smartphone\\':0.0 <= 0.500001, Go Left | val: 0.0\n" +
             "  65.0 = tree 1 | \\'user_device_tablet\\':1.0 > 0.500001, Go Right | val: 65.0\n'}");
     assertJQ(
         "/query" + query.toQueryString(),
         "/debug/explain/9=='\n" +
-            "65.0 = LambdaMARTModel(name=external_model_binary_feature,trees="+trees+") model applied to features, sum of:\n" +
+            "65.0 = MultipleAdditiveTreesModel(name=external_model_binary_feature,trees="+trees+") model applied to features, sum of:\n" +
             "  0.0 = tree 0 | \\'user_device_smartphone\\':0.0 <= 0.500001, Go Left | val: 0.0\n" +
             "  65.0 = tree 1 | \\'user_device_tablet\\':1.0 > 0.500001, Go Right | val: 65.0\n'}");
   }

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestLTRQParserPlugin.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestLTRQParserPlugin.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestLTRQParserPlugin.java
index c18c155..256aa92 100644
--- a/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestLTRQParserPlugin.java
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestLTRQParserPlugin.java
@@ -33,8 +33,8 @@ public class TestLTRQParserPlugin extends TestRerankBase {
     // store = getModelStore();
     bulkIndex();
 
-    loadFeatures("features-ranksvm.json");
-    loadModels("ranksvm-model.json");
+    loadFeatures("features-linear.json");
+    loadModels("linear-model.json");
   }
 
   @AfterClass

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestLTRReRankingPipeline.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestLTRReRankingPipeline.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestLTRReRankingPipeline.java
index ba9de13..0d8b4fa 100644
--- a/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestLTRReRankingPipeline.java
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestLTRReRankingPipeline.java
@@ -50,7 +50,7 @@ import org.apache.solr.ltr.LTRScoringQuery.ModelWeight.ModelScorer;
 import org.apache.solr.ltr.feature.Feature;
 import org.apache.solr.ltr.feature.FieldValueFeature;
 import org.apache.solr.ltr.model.LTRScoringModel;
-import org.apache.solr.ltr.model.TestRankSVMModel;
+import org.apache.solr.ltr.model.TestLinearModel;
 import org.apache.solr.ltr.norm.IdentityNormalizer;
 import org.apache.solr.ltr.norm.Normalizer;
 import org.junit.Ignore;
@@ -150,7 +150,7 @@ public class TestLTRReRankingPipeline extends LuceneTestCase {
             Collections.nCopies(features.size(),IdentityNormalizer.INSTANCE));
     final List<Feature> allFeatures = makeFieldValueFeatures(new int[] {0, 1,
         2, 3, 4, 5, 6, 7, 8, 9}, "final-score");
-    final LTRScoringModel ltrScoringModel = TestRankSVMModel.createRankSVMModel("test",
+    final LTRScoringModel ltrScoringModel = TestLinearModel.createLinearModel("test",
         features, norms, "test", allFeatures, null);
 
     final LTRRescorer rescorer = new LTRRescorer(new LTRScoringQuery(ltrScoringModel));
@@ -226,7 +226,7 @@ public class TestLTRReRankingPipeline extends LuceneTestCase {
             Collections.nCopies(features.size(),IdentityNormalizer.INSTANCE));
     final List<Feature> allFeatures = makeFieldValueFeatures(new int[] {0, 1,
         2, 3, 4, 5, 6, 7, 8, 9}, "final-score");
-    final LTRScoringModel ltrScoringModel = TestRankSVMModel.createRankSVMModel("test",
+    final LTRScoringModel ltrScoringModel = TestLinearModel.createLinearModel("test",
         features, norms, "test", allFeatures, null);
 
     final LTRRescorer rescorer = new LTRRescorer(new LTRScoringQuery(ltrScoringModel));

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestLTRScoringQuery.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestLTRScoringQuery.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestLTRScoringQuery.java
index 799fef3..1dbed26 100644
--- a/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestLTRScoringQuery.java
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestLTRScoringQuery.java
@@ -48,7 +48,7 @@ import org.apache.solr.ltr.feature.Feature;
 import org.apache.solr.ltr.feature.ValueFeature;
 import org.apache.solr.ltr.model.LTRScoringModel;
 import org.apache.solr.ltr.model.ModelException;
-import org.apache.solr.ltr.model.TestRankSVMModel;
+import org.apache.solr.ltr.model.TestLinearModel;
 import org.apache.solr.ltr.norm.IdentityNormalizer;
 import org.apache.solr.ltr.norm.Normalizer;
 import org.junit.Test;
@@ -136,7 +136,7 @@ public class TestLTRScoringQuery extends LuceneTestCase {
         new int[] {0, 1, 2, 3, 4, 5, 6, 7, 8, 9});
     final Map<String,Object> modelParams = makeFeatureWeights(features);
 
-    final LTRScoringModel algorithm1 = TestRankSVMModel.createRankSVMModel(
+    final LTRScoringModel algorithm1 = TestLinearModel.createLinearModel(
         "testModelName",
         features, norms, "testStoreName", allFeatures, modelParams);
 
@@ -164,7 +164,7 @@ public class TestLTRScoringQuery extends LuceneTestCase {
     assertFalse(m1.hashCode() == m0.hashCode());
     
     
-    final LTRScoringModel algorithm2 = TestRankSVMModel.createRankSVMModel(
+    final LTRScoringModel algorithm2 = TestLinearModel.createLinearModel(
         "testModelName2",
         features, norms, "testStoreName", allFeatures, modelParams);
     final LTRScoringQuery m3 = new LTRScoringQuery(algorithm2);
@@ -172,7 +172,7 @@ public class TestLTRScoringQuery extends LuceneTestCase {
     assertFalse(m1.equals(m3));
     assertFalse(m1.hashCode() == m3.hashCode());
 
-    final LTRScoringModel algorithm3 = TestRankSVMModel.createRankSVMModel(
+    final LTRScoringModel algorithm3 = TestLinearModel.createLinearModel(
         "testModelName",
         features, norms, "testStoreName3", allFeatures, modelParams);
     final LTRScoringQuery m4 = new LTRScoringQuery(algorithm3);
@@ -222,7 +222,7 @@ public class TestLTRScoringQuery extends LuceneTestCase {
     List<Normalizer> norms = 
         new ArrayList<Normalizer>(
             Collections.nCopies(features.size(),IdentityNormalizer.INSTANCE));
-    LTRScoringModel ltrScoringModel = TestRankSVMModel.createRankSVMModel("test",
+    LTRScoringModel ltrScoringModel = TestLinearModel.createLinearModel("test",
         features, norms, "test", allFeatures,
         makeFeatureWeights(features));
 
@@ -249,7 +249,7 @@ public class TestLTRScoringQuery extends LuceneTestCase {
     norms = 
         new ArrayList<Normalizer>(
             Collections.nCopies(features.size(),IdentityNormalizer.INSTANCE));
-    ltrScoringModel = TestRankSVMModel.createRankSVMModel("test",
+    ltrScoringModel = TestLinearModel.createLinearModel("test",
         features, norms, "test", allFeatures, makeFeatureWeights(features));
 
     modelWeight = performQuery(hits, searcher, hits.scoreDocs[0].doc,
@@ -269,7 +269,7 @@ public class TestLTRScoringQuery extends LuceneTestCase {
         new ArrayList<Normalizer>(
             Collections.nCopies(features.size(),IdentityNormalizer.INSTANCE));
     try {
-      ltrScoringModel = TestRankSVMModel.createRankSVMModel("test",
+      ltrScoringModel = TestLinearModel.createLinearModel("test",
           features, norms, "test", allFeatures, makeFeatureWeights(features));
       fail("unexpectedly got here instead of catching "+expectedModelException);
       modelWeight = performQuery(hits, searcher, hits.scoreDocs[0].doc,
@@ -296,7 +296,7 @@ public class TestLTRScoringQuery extends LuceneTestCase {
     norms = 
         new ArrayList<Normalizer>(
             Collections.nCopies(features.size(),norm));
-    final LTRScoringModel normMeta = TestRankSVMModel.createRankSVMModel("test",
+    final LTRScoringModel normMeta = TestLinearModel.createLinearModel("test",
         features, norms, "test", allFeatures,
         makeFeatureWeights(features));
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestLTRWithFacet.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestLTRWithFacet.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestLTRWithFacet.java
index ed6f476..ebab516 100644
--- a/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestLTRWithFacet.java
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestLTRWithFacet.java
@@ -21,7 +21,7 @@ import org.apache.lucene.util.LuceneTestCase.SuppressCodecs;
 import org.apache.solr.client.solrj.SolrQuery;
 import org.apache.solr.ltr.TestRerankBase;
 import org.apache.solr.ltr.feature.SolrFeature;
-import org.apache.solr.ltr.model.RankSVMModel;
+import org.apache.solr.ltr.model.LinearModel;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -58,7 +58,7 @@ public class TestLTRWithFacet extends TestRerankBase {
     loadFeature("powpularityS", SolrFeature.class.getCanonicalName(),
         "{\"q\":\"{!func}pow(popularity,2)\"}");
 
-    loadModel("powpularityS-model", RankSVMModel.class.getCanonicalName(),
+    loadModel("powpularityS-model", LinearModel.class.getCanonicalName(),
         new String[] {"powpularityS"}, "{\"weights\":{\"powpularityS\":1.0}}");
 
     final SolrQuery query = new SolrQuery();

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestLTRWithSort.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestLTRWithSort.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestLTRWithSort.java
index 8b86ed2..f9740dc 100644
--- a/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestLTRWithSort.java
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestLTRWithSort.java
@@ -21,7 +21,7 @@ import org.apache.lucene.util.LuceneTestCase.SuppressCodecs;
 import org.apache.solr.client.solrj.SolrQuery;
 import org.apache.solr.ltr.TestRerankBase;
 import org.apache.solr.ltr.feature.SolrFeature;
-import org.apache.solr.ltr.model.RankSVMModel;
+import org.apache.solr.ltr.model.LinearModel;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -57,7 +57,7 @@ public class TestLTRWithSort extends TestRerankBase {
     loadFeature("powpularityS", SolrFeature.class.getCanonicalName(),
         "{\"q\":\"{!func}pow(popularity,2)\"}");
 
-    loadModel("powpularityS-model", RankSVMModel.class.getCanonicalName(),
+    loadModel("powpularityS-model", LinearModel.class.getCanonicalName(),
         new String[] {"powpularityS"}, "{\"weights\":{\"powpularityS\":1.0}}");
 
     final SolrQuery query = new SolrQuery();

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestRerankBase.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestRerankBase.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestRerankBase.java
index 512a568..f224c87 100644
--- a/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestRerankBase.java
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestRerankBase.java
@@ -44,7 +44,7 @@ import org.apache.solr.ltr.feature.ValueFeature;
 import org.apache.solr.ltr.feature.ValueFeature.ValueFeatureWeight;
 import org.apache.solr.ltr.model.LTRScoringModel;
 import org.apache.solr.ltr.model.ModelException;
-import org.apache.solr.ltr.model.RankSVMModel;
+import org.apache.solr.ltr.model.LinearModel;
 import org.apache.solr.ltr.store.rest.ManagedFeatureStore;
 import org.apache.solr.ltr.store.rest.ManagedModelStore;
 import org.apache.solr.request.SolrQueryRequestBase;
@@ -364,7 +364,7 @@ public class TestRerankBase extends RestTestBase {
           "{\"value\":" + i + "}");
     }
 
-    loadModel(name, RankSVMModel.class.getCanonicalName(), features,
+    loadModel(name, LinearModel.class.getCanonicalName(), features,
         "{\"weights\":{" + StringUtils.join(weights, ",") + "}}");
   }
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestSelectiveWeightCreation.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestSelectiveWeightCreation.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestSelectiveWeightCreation.java
index d351f92..0554dc1 100644
--- a/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestSelectiveWeightCreation.java
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/TestSelectiveWeightCreation.java
@@ -45,7 +45,7 @@ import org.apache.solr.ltr.feature.Feature;
 import org.apache.solr.ltr.feature.ValueFeature;
 import org.apache.solr.ltr.model.LTRScoringModel;
 import org.apache.solr.ltr.model.ModelException;
-import org.apache.solr.ltr.model.TestRankSVMModel;
+import org.apache.solr.ltr.model.TestLinearModel;
 import org.apache.solr.ltr.norm.IdentityNormalizer;
 import org.apache.solr.ltr.norm.Normalizer;
 import org.junit.AfterClass;
@@ -174,7 +174,7 @@ public class TestSelectiveWeightCreation extends TestRerankBase {
     }
 
     // when features are NOT requested in the response, only the modelFeature weights should be created
-    final LTRScoringModel ltrScoringModel1 = TestRankSVMModel.createRankSVMModel("test",
+    final LTRScoringModel ltrScoringModel1 = TestLinearModel.createLinearModel("test",
         features, norms, "test", allFeatures,
         makeFeatureWeights(features));
     LTRScoringQuery.ModelWeight modelWeight = performQuery(hits, searcher,
@@ -190,7 +190,7 @@ public class TestSelectiveWeightCreation extends TestRerankBase {
     assertEquals(validFeatures, features.size());
     
     // when features are requested in the response, weights should be created for all features
-    final LTRScoringModel ltrScoringModel2 = TestRankSVMModel.createRankSVMModel("test",
+    final LTRScoringModel ltrScoringModel2 = TestLinearModel.createLinearModel("test",
         features, norms, "test", allFeatures,
         makeFeatureWeights(features));
     modelWeight = performQuery(hits, searcher,

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestEdisMaxSolrFeature.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestEdisMaxSolrFeature.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestEdisMaxSolrFeature.java
index cba4130..9fce51a 100644
--- a/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestEdisMaxSolrFeature.java
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestEdisMaxSolrFeature.java
@@ -18,7 +18,7 @@ package org.apache.solr.ltr.feature;
 
 import org.apache.solr.client.solrj.SolrQuery;
 import org.apache.solr.ltr.TestRerankBase;
-import org.apache.solr.ltr.model.RankSVMModel;
+import org.apache.solr.ltr.model.LinearModel;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -60,7 +60,7 @@ public class TestEdisMaxSolrFeature extends TestRerankBase {
         SolrFeature.class.getCanonicalName(),
         "{\"q\":\"{!edismax qf='title description' pf='description' mm=100% boost='pow(popularity, 0.1)' v='w1' tie=0.1}\"}");
 
-    loadModel("EdisMax-model", RankSVMModel.class.getCanonicalName(),
+    loadModel("EdisMax-model", LinearModel.class.getCanonicalName(),
         new String[] {"SomeEdisMax"}, "{\"weights\":{\"SomeEdisMax\":1.0}}");
 
     final SolrQuery query = new SolrQuery();

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestExternalValueFeatures.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestExternalValueFeatures.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestExternalValueFeatures.java
index a8a2e7a..707da24 100644
--- a/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestExternalValueFeatures.java
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestExternalValueFeatures.java
@@ -43,7 +43,7 @@ public class TestExternalValueFeatures extends TestRerankBase {
     assertU(commit());
 
     loadFeatures("external_features_for_sparse_processing.json");
-    loadModels("lambdamartmodel_external_binary_features.json");
+    loadModels("multipleadditivetreesmodel_external_binary_features.json");
   }
 
   @AfterClass

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestFeatureLogging.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestFeatureLogging.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestFeatureLogging.java
index ba77ba4..1086c83 100644
--- a/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestFeatureLogging.java
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestFeatureLogging.java
@@ -19,7 +19,7 @@ package org.apache.solr.ltr.feature;
 import org.apache.solr.SolrTestCaseJ4.SuppressSSL;
 import org.apache.solr.client.solrj.SolrQuery;
 import org.apache.solr.ltr.TestRerankBase;
-import org.apache.solr.ltr.model.RankSVMModel;
+import org.apache.solr.ltr.model.LinearModel;
 import org.apache.solr.ltr.store.FeatureStore;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
@@ -53,7 +53,7 @@ public class TestFeatureLogging extends TestRerankBase {
     loadFeature("yesmatch", SolrFeature.class.getCanonicalName(), "test1",
         "{\"q\":\"{!terms f=popularity}2\"}");
 
-    loadModel("sum1", RankSVMModel.class.getCanonicalName(), new String[] {
+    loadModel("sum1", LinearModel.class.getCanonicalName(), new String[] {
         "c1", "c2", "c3"}, "test1",
         "{\"weights\":{\"c1\":1.0,\"c2\":1.0,\"c3\":1.0}}");
 
@@ -98,7 +98,7 @@ public class TestFeatureLogging extends TestRerankBase {
     loadFeature("store9f1", ValueFeature.class.getCanonicalName(),
         "store9",
         "{\"value\":3.0}");
-    loadModel("store9m1", RankSVMModel.class.getCanonicalName(),
+    loadModel("store9m1", LinearModel.class.getCanonicalName(),
       new String[] {"store9f1"},
       "store9",
       "{\"weights\":{\"store9f1\":1.0}}");
@@ -142,7 +142,7 @@ public class TestFeatureLogging extends TestRerankBase {
     loadFeature("pop", FieldValueFeature.class.getCanonicalName(), "testgroup",
         "{\"field\":\"popularity\"}");
 
-    loadModel("sumgroup", RankSVMModel.class.getCanonicalName(), new String[] {
+    loadModel("sumgroup", LinearModel.class.getCanonicalName(), new String[] {
         "c1", "c2", "c3"}, "testgroup",
         "{\"weights\":{\"c1\":1.0,\"c2\":1.0,\"c3\":1.0}}");
 
@@ -185,7 +185,7 @@ public class TestFeatureLogging extends TestRerankBase {
     loadFeature("c4", ValueFeature.class.getCanonicalName(), "test4",
         "{\"value\":1.0}");
 
-    loadModel("sum4", RankSVMModel.class.getCanonicalName(), new String[] {
+    loadModel("sum4", LinearModel.class.getCanonicalName(), new String[] {
         "match"}, "test4",
         "{\"weights\":{\"match\":1.0}}");
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestFieldLengthFeature.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestFieldLengthFeature.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestFieldLengthFeature.java
index b54367e..77d9585 100644
--- a/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestFieldLengthFeature.java
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestFieldLengthFeature.java
@@ -19,7 +19,7 @@ package org.apache.solr.ltr.feature;
 import org.apache.lucene.util.LuceneTestCase.SuppressCodecs;
 import org.apache.solr.client.solrj.SolrQuery;
 import org.apache.solr.ltr.TestRerankBase;
-import org.apache.solr.ltr.model.RankSVMModel;
+import org.apache.solr.ltr.model.LinearModel;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -59,7 +59,7 @@ public class TestFieldLengthFeature extends TestRerankBase {
     loadFeature("description-length2", FieldLengthFeature.class.getCanonicalName(),
             "{\"field\":\"description\"}");
 
-    loadModel("description-model2", RankSVMModel.class.getCanonicalName(),
+    loadModel("description-model2", LinearModel.class.getCanonicalName(),
             new String[] {"description-length2"}, "{\"weights\":{\"description-length2\":1.0}}");
 
     final SolrQuery query = new SolrQuery();
@@ -80,7 +80,7 @@ public class TestFieldLengthFeature extends TestRerankBase {
     loadFeature("description-length3", FieldLengthFeature.class.getCanonicalName(),
             "{\"field\":\"description\"}");
 
-    loadModel("description-model3", RankSVMModel.class.getCanonicalName(),
+    loadModel("description-model3", LinearModel.class.getCanonicalName(),
             new String[] {"description-length3"}, "{\"weights\":{\"description-length3\":1.0}}");
 
     final SolrQuery query = new SolrQuery();
@@ -97,7 +97,7 @@ public class TestFieldLengthFeature extends TestRerankBase {
     loadFeature("title-length", FieldLengthFeature.class.getCanonicalName(),
         "{\"field\":\"title\"}");
 
-    loadModel("title-model", RankSVMModel.class.getCanonicalName(),
+    loadModel("title-model", LinearModel.class.getCanonicalName(),
         new String[] {"title-length"}, "{\"weights\":{\"title-length\":1.0}}");
 
     final SolrQuery query = new SolrQuery();
@@ -135,7 +135,7 @@ public class TestFieldLengthFeature extends TestRerankBase {
     loadFeature("description-length",
         FieldLengthFeature.class.getCanonicalName(),
         "{\"field\":\"description\"}");
-    loadModel("description-model", RankSVMModel.class.getCanonicalName(),
+    loadModel("description-model", LinearModel.class.getCanonicalName(),
         new String[] {"description-length"},
         "{\"weights\":{\"description-length\":1.0}}");
     query.setQuery("title:w1");

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestFieldValueFeature.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestFieldValueFeature.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestFieldValueFeature.java
index cb16775..f830d5a 100644
--- a/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestFieldValueFeature.java
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestFieldValueFeature.java
@@ -19,7 +19,7 @@ package org.apache.solr.ltr.feature;
 import org.apache.lucene.util.LuceneTestCase.SuppressCodecs;
 import org.apache.solr.client.solrj.SolrQuery;
 import org.apache.solr.ltr.TestRerankBase;
-import org.apache.solr.ltr.model.RankSVMModel;
+import org.apache.solr.ltr.model.LinearModel;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -58,7 +58,7 @@ public class TestFieldValueFeature extends TestRerankBase {
     loadFeature("popularity", FieldValueFeature.class.getCanonicalName(),
             "{\"field\":\"popularity\"}");
 
-    loadModel("popularity-model", RankSVMModel.class.getCanonicalName(),
+    loadModel("popularity-model", LinearModel.class.getCanonicalName(),
             new String[] {"popularity"}, "{\"weights\":{\"popularity\":1.0}}");
   }
 
@@ -136,7 +136,7 @@ public class TestFieldValueFeature extends TestRerankBase {
     query.add("fl", "*, score");
     query.add("rows", "4");
 
-    loadModel("popularity-model42", RankSVMModel.class.getCanonicalName(),
+    loadModel("popularity-model42", LinearModel.class.getCanonicalName(),
             new String[] {"popularity42"}, fstore, "{\"weights\":{\"popularity42\":1.0}}");
 
     assertJQ("/query" + query.toQueryString(), "/response/numFound/==1");
@@ -158,7 +158,7 @@ public class TestFieldValueFeature extends TestRerankBase {
     loadFeature("not-existing-field", FieldValueFeature.class.getCanonicalName(), fstore,
             "{\"field\":\"cowabunga\"}");
 
-    loadModel("not-existing-field-model", RankSVMModel.class.getCanonicalName(),
+    loadModel("not-existing-field-model", LinearModel.class.getCanonicalName(),
             new String[] {"not-existing-field"}, fstore, "{\"weights\":{\"not-existing-field\":1.0}}");
 
     final SolrQuery query = new SolrQuery();

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestFilterSolrFeature.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestFilterSolrFeature.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestFilterSolrFeature.java
index dd24114..0f636f4 100644
--- a/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestFilterSolrFeature.java
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestFilterSolrFeature.java
@@ -19,7 +19,7 @@ package org.apache.solr.ltr.feature;
 import org.apache.lucene.util.LuceneTestCase.SuppressCodecs;
 import org.apache.solr.client.solrj.SolrQuery;
 import org.apache.solr.ltr.TestRerankBase;
-import org.apache.solr.ltr.model.RankSVMModel;
+import org.apache.solr.ltr.model.LinearModel;
 import org.apache.solr.ltr.store.rest.ManagedFeatureStore;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
@@ -61,7 +61,7 @@ public class TestFilterSolrFeature extends TestRerankBase {
         "{\"fq\":[\"{!terms f=popularity}88888\"]}");
     loadFeature("SomeEfiFQ", SolrFeature.class.getCanonicalName(),
         "{\"fq\":[\"{!terms f=title}${user_query}\"]}");
-    loadModel("Term-modelFQ", RankSVMModel.class.getCanonicalName(),
+    loadModel("Term-modelFQ", LinearModel.class.getCanonicalName(),
         new String[] {"SomeTermFQ", "SomeEfiFQ"},
         "{\"weights\":{\"SomeTermFQ\":1.6, \"SomeEfiFQ\":2.0}}");
     final SolrQuery query = new SolrQuery();

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestNoMatchSolrFeature.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestNoMatchSolrFeature.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestNoMatchSolrFeature.java
index ba2cbe7..24c67a6 100644
--- a/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestNoMatchSolrFeature.java
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestNoMatchSolrFeature.java
@@ -21,8 +21,8 @@ import java.util.Map;
 
 import org.apache.solr.client.solrj.SolrQuery;
 import org.apache.solr.ltr.TestRerankBase;
-import org.apache.solr.ltr.model.LambdaMARTModel;
-import org.apache.solr.ltr.model.RankSVMModel;
+import org.apache.solr.ltr.model.MultipleAdditiveTreesModel;
+import org.apache.solr.ltr.model.LinearModel;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -60,19 +60,19 @@ public class TestNoMatchSolrFeature extends TestRerankBase {
         "{\"q\":\"foobarbat12345\",\"df\":\"title\"}");
     loadModel(
         "nomatchmodel",
-        RankSVMModel.class.getCanonicalName(),
+        LinearModel.class.getCanonicalName(),
         new String[] {"nomatchfeature", "yesmatchfeature", "nomatchfeature2"},
         "{\"weights\":{\"nomatchfeature\":1.0,\"yesmatchfeature\":1.1,\"nomatchfeature2\":1.1}}");
 
     loadFeature("nomatchfeature3", SolrFeature.class.getCanonicalName(),
         "{\"q\":\"foobarbat12345\",\"df\":\"title\"}");
-    loadModel("nomatchmodel2", RankSVMModel.class.getCanonicalName(),
+    loadModel("nomatchmodel2", LinearModel.class.getCanonicalName(),
         new String[] {"nomatchfeature3"},
         "{\"weights\":{\"nomatchfeature3\":1.0}}");
 
     loadFeature("nomatchfeature4", SolrFeature.class.getCanonicalName(),
         "noMatchFeaturesStore", "{\"q\":\"foobarbat12345\",\"df\":\"title\"}");
-    loadModel("nomatchmodel3", RankSVMModel.class.getCanonicalName(),
+    loadModel("nomatchmodel3", LinearModel.class.getCanonicalName(),
         new String[] {"nomatchfeature4"}, "noMatchFeaturesStore",
         "{\"weights\":{\"nomatchfeature4\":1.0}}");
   }
@@ -170,10 +170,10 @@ public class TestNoMatchSolrFeature extends TestRerankBase {
   @Test
   public void tesOnlyNoMatchFeaturesInStoreAndModelReturnsNonzeroScore() throws Exception {
     // Tests model with all no matching features but expects a non 0 score
-    //  LambdaMART will return scores even for docs without any feature matches
+    //  MultipleAdditiveTrees will return scores even for docs without any feature matches
     loadModel(
         "nomatchmodel4",
-        LambdaMARTModel.class.getCanonicalName(),
+        MultipleAdditiveTreesModel.class.getCanonicalName(),
         new String[] {"nomatchfeature4"},
         "noMatchFeaturesStore",
         "{\"trees\":[{\"weight\":\"1f\", \"root\":{\"feature\": \"matchedTitle\",\"threshold\": \"0.5f\",\"left\":{\"value\" : \"-10\"},\"right\":{\"value\" : \"9\"}}}]}");

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestOriginalScoreFeature.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestOriginalScoreFeature.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestOriginalScoreFeature.java
index 4cf2412..9d3a1c5 100644
--- a/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestOriginalScoreFeature.java
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestOriginalScoreFeature.java
@@ -22,7 +22,7 @@ import java.util.Map;
 import org.apache.lucene.util.LuceneTestCase.SuppressCodecs;
 import org.apache.solr.client.solrj.SolrQuery;
 import org.apache.solr.ltr.TestRerankBase;
-import org.apache.solr.ltr.model.RankSVMModel;
+import org.apache.solr.ltr.model.LinearModel;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -55,7 +55,7 @@ public class TestOriginalScoreFeature extends TestRerankBase {
   public void testOriginalScore() throws Exception {
     loadFeature("score", OriginalScoreFeature.class.getCanonicalName(), "{}");
 
-    loadModel("originalScore", RankSVMModel.class.getCanonicalName(),
+    loadModel("originalScore", LinearModel.class.getCanonicalName(),
         new String[] {"score"}, "{\"weights\":{\"score\":1.0}}");
 
     final SolrQuery query = new SolrQuery();
@@ -108,7 +108,7 @@ public class TestOriginalScoreFeature extends TestRerankBase {
     loadFeature("c2", ValueFeature.class.getCanonicalName(), "store2",
         "{\"value\":2.0}");
 
-    loadModel("origScore", RankSVMModel.class.getCanonicalName(),
+    loadModel("origScore", LinearModel.class.getCanonicalName(),
         new String[] {"origScore"}, "store2",
         "{\"weights\":{\"origScore\":1.0}}");
 

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestRankingFeature.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestRankingFeature.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestRankingFeature.java
index bad1da1..787fa4c 100644
--- a/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestRankingFeature.java
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestRankingFeature.java
@@ -18,7 +18,7 @@ package org.apache.solr.ltr.feature;
 
 import org.apache.solr.client.solrj.SolrQuery;
 import org.apache.solr.ltr.TestRerankBase;
-import org.apache.solr.ltr.model.RankSVMModel;
+import org.apache.solr.ltr.model.LinearModel;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -62,9 +62,9 @@ public class TestRankingFeature extends TestRerankBase {
     loadFeature("unpopularityS", SolrFeature.class.getCanonicalName(),
         "{\"q\":\"{!func}div(1,popularity)\"}");
 
-    loadModel("powpularityS-model", RankSVMModel.class.getCanonicalName(),
+    loadModel("powpularityS-model", LinearModel.class.getCanonicalName(),
         new String[] {"powpularityS"}, "{\"weights\":{\"powpularityS\":1.0}}");
-    loadModel("unpopularityS-model", RankSVMModel.class.getCanonicalName(),
+    loadModel("unpopularityS-model", LinearModel.class.getCanonicalName(),
         new String[] {"unpopularityS"}, "{\"weights\":{\"unpopularityS\":1.0}}");
 
     final SolrQuery query = new SolrQuery();
@@ -106,7 +106,7 @@ public class TestRankingFeature extends TestRerankBase {
     //bad solr ranking feature
     loadFeature("powdesS", SolrFeature.class.getCanonicalName(),
         "{\"q\":\"{!func}pow(description,2)\"}");
-    loadModel("powdesS-model", RankSVMModel.class.getCanonicalName(),
+    loadModel("powdesS-model", LinearModel.class.getCanonicalName(),
         new String[] {"powdesS"}, "{\"weights\":{\"powdesS\":1.0}}");
 
     query.remove("rq");

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestUserTermScoreWithQ.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestUserTermScoreWithQ.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestUserTermScoreWithQ.java
index 47167dc..01eb484 100644
--- a/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestUserTermScoreWithQ.java
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestUserTermScoreWithQ.java
@@ -18,7 +18,7 @@ package org.apache.solr.ltr.feature;
 
 import org.apache.solr.client.solrj.SolrQuery;
 import org.apache.solr.ltr.TestRerankBase;
-import org.apache.solr.ltr.model.RankSVMModel;
+import org.apache.solr.ltr.model.LinearModel;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -58,7 +58,7 @@ public class TestUserTermScoreWithQ extends TestRerankBase {
     // before();
     loadFeature("SomeTermQ", SolrFeature.class.getCanonicalName(),
         "{\"q\":\"{!terms f=popularity}88888\"}");
-    loadModel("Term-modelQ", RankSVMModel.class.getCanonicalName(),
+    loadModel("Term-modelQ", LinearModel.class.getCanonicalName(),
         new String[] {"SomeTermQ"}, "{\"weights\":{\"SomeTermQ\":1.0}}");
     final SolrQuery query = new SolrQuery();
     query.setQuery("title:w1");

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestUserTermScorerQuery.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestUserTermScorerQuery.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestUserTermScorerQuery.java
index 01083aa..1a6d505 100644
--- a/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestUserTermScorerQuery.java
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestUserTermScorerQuery.java
@@ -18,7 +18,7 @@ package org.apache.solr.ltr.feature;
 
 import org.apache.solr.client.solrj.SolrQuery;
 import org.apache.solr.ltr.TestRerankBase;
-import org.apache.solr.ltr.model.RankSVMModel;
+import org.apache.solr.ltr.model.LinearModel;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -58,7 +58,7 @@ public class TestUserTermScorerQuery extends TestRerankBase  {
     // before();
     loadFeature("matchedTitleDFExt", SolrFeature.class.getCanonicalName(),
         "{\"q\":\"${user_query}\",\"df\":\"title\"}");
-    loadModel("Term-matchedTitleDFExt", RankSVMModel.class.getCanonicalName(),
+    loadModel("Term-matchedTitleDFExt", LinearModel.class.getCanonicalName(),
         new String[] {"matchedTitleDFExt"},
         "{\"weights\":{\"matchedTitleDFExt\":1.1}}");
     final SolrQuery query = new SolrQuery();

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestUserTermScorereQDF.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestUserTermScorereQDF.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestUserTermScorereQDF.java
index d95bbf2..fdbeed8 100644
--- a/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestUserTermScorereQDF.java
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestUserTermScorereQDF.java
@@ -18,7 +18,7 @@ package org.apache.solr.ltr.feature;
 
 import org.apache.solr.client.solrj.SolrQuery;
 import org.apache.solr.ltr.TestRerankBase;
-import org.apache.solr.ltr.model.RankSVMModel;
+import org.apache.solr.ltr.model.LinearModel;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -58,7 +58,7 @@ public class TestUserTermScorereQDF extends TestRerankBase {
     // before();
     loadFeature("matchedTitleDF", SolrFeature.class.getCanonicalName(),
         "{\"q\":\"w5\",\"df\":\"title\"}");
-    loadModel("Term-matchedTitleDF", RankSVMModel.class.getCanonicalName(),
+    loadModel("Term-matchedTitleDF", LinearModel.class.getCanonicalName(),
         new String[] {"matchedTitleDF"},
         "{\"weights\":{\"matchedTitleDF\":1.0}}");
     final SolrQuery query = new SolrQuery();

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestValueFeature.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestValueFeature.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestValueFeature.java
index cda962f..6c9cf0e 100644
--- a/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestValueFeature.java
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/feature/TestValueFeature.java
@@ -19,7 +19,7 @@ package org.apache.solr.ltr.feature;
 import org.apache.lucene.util.LuceneTestCase.SuppressCodecs;
 import org.apache.solr.client.solrj.SolrQuery;
 import org.apache.solr.ltr.TestRerankBase;
-import org.apache.solr.ltr.model.RankSVMModel;
+import org.apache.solr.ltr.model.LinearModel;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
@@ -76,7 +76,7 @@ public class TestValueFeature extends TestRerankBase {
   public void testRerankingWithConstantValueFeatureReplacesDocScore() throws Exception {
     loadFeature("c3", ValueFeature.class.getCanonicalName(), "c3",
         "{\"value\":2}");
-    loadModel("m3", RankSVMModel.class.getCanonicalName(), new String[] {"c3"},
+    loadModel("m3", LinearModel.class.getCanonicalName(), new String[] {"c3"},
         "c3", "{\"weights\":{\"c3\":1.0}}");
 
     final SolrQuery query = new SolrQuery();
@@ -96,7 +96,7 @@ public class TestValueFeature extends TestRerankBase {
   public void testRerankingWithEfiValueFeatureReplacesDocScore() throws Exception {
     loadFeature("c6", ValueFeature.class.getCanonicalName(), "c6",
         "{\"value\":\"${val6}\"}");
-    loadModel("m6", RankSVMModel.class.getCanonicalName(), new String[] {"c6"},
+    loadModel("m6", LinearModel.class.getCanonicalName(), new String[] {"c6"},
         "c6", "{\"weights\":{\"c6\":1.0}}");
 
     final SolrQuery query = new SolrQuery();
@@ -117,7 +117,7 @@ public class TestValueFeature extends TestRerankBase {
   public void testValueFeatureImplicitlyNotRequiredShouldReturnOkStatusCode() throws Exception {
     loadFeature("c5", ValueFeature.class.getCanonicalName(), "c5",
         "{\"value\":\"${val6}\"}");
-    loadModel("m5", RankSVMModel.class.getCanonicalName(), new String[] {"c5"},
+    loadModel("m5", LinearModel.class.getCanonicalName(), new String[] {"c5"},
         "c5", "{\"weights\":{\"c5\":1.0}}");
 
     final SolrQuery query = new SolrQuery();
@@ -134,7 +134,7 @@ public class TestValueFeature extends TestRerankBase {
   public void testValueFeatureExplictlyNotRequiredShouldReturnOkStatusCode() throws Exception {
     loadFeature("c7", ValueFeature.class.getCanonicalName(), "c7",
         "{\"value\":\"${val7}\",\"required\":false}");
-    loadModel("m7", RankSVMModel.class.getCanonicalName(), new String[] {"c7"},
+    loadModel("m7", LinearModel.class.getCanonicalName(), new String[] {"c7"},
         "c7", "{\"weights\":{\"c7\":1.0}}");
 
     final SolrQuery query = new SolrQuery();
@@ -151,7 +151,7 @@ public class TestValueFeature extends TestRerankBase {
   public void testValueFeatureRequiredShouldReturn400StatusCode() throws Exception {
     loadFeature("c8", ValueFeature.class.getCanonicalName(), "c8",
         "{\"value\":\"${val8}\",\"required\":true}");
-    loadModel("m8", RankSVMModel.class.getCanonicalName(), new String[] {"c8"},
+    loadModel("m8", LinearModel.class.getCanonicalName(), new String[] {"c8"},
         "c8", "{\"weights\":{\"c8\":1.0}}");
 
     final SolrQuery query = new SolrQuery();

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test/org/apache/solr/ltr/model/TestLambdaMARTModel.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/model/TestLambdaMARTModel.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/model/TestLambdaMARTModel.java
deleted file mode 100644
index 67e6109..0000000
--- a/solr/contrib/ltr/src/test/org/apache/solr/ltr/model/TestLambdaMARTModel.java
+++ /dev/null
@@ -1,249 +0,0 @@
-/*
- * 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.solr.ltr.model;
-
-//import static org.junit.internal.matchers.StringContains.containsString;
-
-import org.apache.lucene.util.LuceneTestCase.SuppressCodecs;
-import org.apache.solr.client.solrj.SolrQuery;
-import org.apache.solr.ltr.TestRerankBase;
-import org.apache.solr.ltr.model.ModelException;
-import org.junit.AfterClass;
-import org.junit.BeforeClass;
-import org.junit.Ignore;
-import org.junit.Test;
-
-@SuppressCodecs({"Lucene3x", "Lucene41", "Lucene40", "Appending"})
-public class TestLambdaMARTModel extends TestRerankBase {
-
-
-  @BeforeClass
-  public static void before() throws Exception {
-    setuptest("solrconfig-ltr.xml", "schema-ltr.xml");
-
-    assertU(adoc("id", "1", "title", "w1", "description", "w1", "popularity","1"));
-    assertU(adoc("id", "2", "title", "w2", "description", "w2", "popularity","2"));
-    assertU(adoc("id", "3", "title", "w3", "description", "w3", "popularity","3"));
-    assertU(adoc("id", "4", "title", "w4", "description", "w4", "popularity","4"));
-    assertU(adoc("id", "5", "title", "w5", "description", "w5", "popularity","5"));
-    assertU(commit());
-
-    loadFeatures("lambdamart_features.json"); // currently needed to force
-    // scoring on all docs
-    loadModels("lambdamartmodel.json");
-  }
-
-  @AfterClass
-  public static void after() throws Exception {
-    aftertest();
-  }
-
-  
-  @Test
-  public void testLambdaMartScoringWithAndWithoutEfiFeatureMatches() throws Exception {
-    final SolrQuery query = new SolrQuery();
-    query.setQuery("*:*");
-    query.add("rows", "3");
-    query.add("fl", "*,score");
-
-    // Regular scores
-    assertJQ("/query" + query.toQueryString(), "/response/docs/[0]/score==1.0");
-    assertJQ("/query" + query.toQueryString(), "/response/docs/[1]/score==1.0");
-    assertJQ("/query" + query.toQueryString(), "/response/docs/[2]/score==1.0");
-
-    // No match scores since user_query not passed in to external feature info
-    // and feature depended on it.
-    query.add("rq", "{!ltr reRankDocs=3 model=lambdamartmodel efi.user_query=dsjkafljjk}");
-
-    assertJQ("/query" + query.toQueryString(), "/response/docs/[0]/score==-120.0");
-    assertJQ("/query" + query.toQueryString(), "/response/docs/[1]/score==-120.0");
-    assertJQ("/query" + query.toQueryString(), "/response/docs/[2]/score==-120.0");
-
-    // Matched user query since it was passed in
-    query.remove("rq");
-    query.add("rq", "{!ltr reRankDocs=3 model=lambdamartmodel efi.user_query=w3}");
-
-    assertJQ("/query" + query.toQueryString(), "/response/docs/[0]/id=='3'");
-    assertJQ("/query" + query.toQueryString(), "/response/docs/[0]/score==-20.0");
-    assertJQ("/query" + query.toQueryString(), "/response/docs/[1]/score==-120.0");
-    assertJQ("/query" + query.toQueryString(), "/response/docs/[2]/score==-120.0");
-  }
-
-  @Ignore
-  @Test
-  public void lambdaMartTestExplain() throws Exception {
-    final SolrQuery query = new SolrQuery();
-    query.setQuery("*:*");
-    query.add("fl", "*,score,[fv]");
-    query.add("rows", "3");
-
-    query.add("rq",
-        "{!ltr reRankDocs=3 model=lambdamartmodel efi.user_query=w3}");
-
-    // test out the explain feature, make sure it returns something
-    query.setParam("debugQuery", "on");
-    String qryResult = JQ("/query" + query.toQueryString());
-
-    qryResult = qryResult.replaceAll("\n", " ");
-    // FIXME containsString doesn't exist.
-    // assertThat(qryResult, containsString("\"debug\":{"));
-    // qryResult = qryResult.substring(qryResult.indexOf("debug"));
-    //
-    // assertThat(qryResult, containsString("\"explain\":{"));
-    // qryResult = qryResult.substring(qryResult.indexOf("explain"));
-    //
-    // assertThat(qryResult, containsString("lambdamartmodel"));
-    // assertThat(qryResult,
-    // containsString(LambdaMARTModel.class.getCanonicalName()));
-    //
-    // assertThat(qryResult, containsString("-100.0 = tree 0"));
-    // assertThat(qryResult, containsString("50.0 = tree 0"));
-    // assertThat(qryResult, containsString("-20.0 = tree 1"));
-    // assertThat(qryResult, containsString("'matchedTitle':1.0 > 0.5"));
-    // assertThat(qryResult, containsString("'matchedTitle':0.0 <= 0.5"));
-    //
-    // assertThat(qryResult, containsString(" Go Right "));
-    // assertThat(qryResult, containsString(" Go Left "));
-    // assertThat(qryResult,
-    // containsString("'this_feature_doesnt_exist' does not exist in FV"));
-  }
-
-  @Test
-  public void lambdaMartTestNoParams() throws Exception {
-    final ModelException expectedException = 
-        new ModelException("no trees declared for model lambdamartmodel_no_params");
-    try {
-        createModelFromFiles("lambdamartmodel_no_params.json",
-              "lambdamart_features.json");
-        fail("lambdaMartTestNoParams failed to throw exception: "+expectedException);
-    } catch (Exception actualException) {
-      Throwable rootError = getRootCause(actualException);
-      assertEquals(expectedException.toString(), rootError.toString());
-    }
-
-  }
-
-  @Test
-  public void lambdaMartTestEmptyParams() throws Exception {
-    final ModelException expectedException = 
-        new ModelException("no trees declared for model lambdamartmodel_no_trees");
-    try {
-        createModelFromFiles("lambdamartmodel_no_trees.json",
-            "lambdamart_features.json");
-        fail("lambdaMartTestEmptyParams failed to throw exception: "+expectedException);
-    } catch (Exception actualException) {
-      Throwable rootError = getRootCause(actualException);
-      assertEquals(expectedException.toString(), rootError.toString());
-    }
-  }
-
-  @Test
-  public void lambdaMartTestNoWeight() throws Exception {
-    final ModelException expectedException = 
-        new ModelException("LambdaMARTModel tree doesn't contain a weight");
-    try {
-        createModelFromFiles("lambdamartmodel_no_weight.json",
-            "lambdamart_features.json");
-        fail("lambdaMartTestNoWeight failed to throw exception: "+expectedException);
-    } catch (Exception actualException) {
-      Throwable rootError = getRootCause(actualException);
-      assertEquals(expectedException.toString(), rootError.toString());
-    }
-  }
-
-  @Test
-  public void lambdaMartTestTreesParamDoesNotContatinTree() throws Exception {
-    final ModelException expectedException = 
-        new ModelException("LambdaMARTModel tree doesn't contain a tree");
-    try {
-        createModelFromFiles("lambdamartmodel_no_tree.json",
-            "lambdamart_features.json");
-        fail("lambdaMartTestTreesParamDoesNotContatinTree failed to throw exception: "+expectedException);
-    } catch (Exception actualException) {
-      Throwable rootError = getRootCause(actualException);
-      assertEquals(expectedException.toString(), rootError.toString());
-    }
-  }
-
-  @Test
-  public void lambdaMartTestNoFeaturesSpecified() throws Exception {
-    final ModelException expectedException = 
-        new ModelException("no features declared for model lambdamartmodel_no_features");
-    try {
-        createModelFromFiles("lambdamartmodel_no_features.json",
-            "lambdamart_features.json");
-        fail("lambdaMartTestNoFeaturesSpecified failed to throw exception: "+expectedException);
-    } catch (ModelException actualException) {
-      assertEquals(expectedException.toString(), actualException.toString());
-    }
-  }
-
-  @Test
-  public void lambdaMartTestNoRight() throws Exception {
-    final ModelException expectedException = 
-        new ModelException("LambdaMARTModel tree node is missing right");
-    try {
-        createModelFromFiles("lambdamartmodel_no_right.json",
-            "lambdamart_features.json");
-        fail("lambdaMartTestNoRight failed to throw exception: "+expectedException);
-    } catch (Exception actualException) {
-      Throwable rootError = getRootCause(actualException);
-      assertEquals(expectedException.toString(), rootError.toString());
-    }
-  }
-
-  @Test
-  public void lambdaMartTestNoLeft() throws Exception {
-    final ModelException expectedException = 
-        new ModelException("LambdaMARTModel tree node is missing left");
-    try {
-        createModelFromFiles("lambdamartmodel_no_left.json",
-            "lambdamart_features.json");
-        fail("lambdaMartTestNoLeft failed to throw exception: "+expectedException);
-    } catch (Exception actualException) {
-      Throwable rootError = getRootCause(actualException);
-      assertEquals(expectedException.toString(), rootError.toString());
-    }
-  }
-
-  @Test
-  public void lambdaMartTestNoThreshold() throws Exception {
-    final ModelException expectedException = 
-        new ModelException("LambdaMARTModel tree node is missing threshold");
-    try {
-        createModelFromFiles("lambdamartmodel_no_threshold.json",
-            "lambdamart_features.json");
-        fail("lambdaMartTestNoThreshold failed to throw exception: "+expectedException);
-    } catch (Exception actualException) {
-      Throwable rootError = getRootCause(actualException);
-      assertEquals(expectedException.toString(), rootError.toString());
-    }
-  }
-
-  @Test
-  public void lambdaMartTestMissingTreeFeature() throws Exception {
-    final ModelException expectedException = 
-        new ModelException("LambdaMARTModel tree node is leaf with left=-100.0 and right=75.0");
-    try {
-        createModelFromFiles("lambdamartmodel_no_feature.json",
-              "lambdamart_features.json");
-        fail("lambdaMartTestMissingTreeFeature failed to throw exception: "+expectedException);
-    } catch (ModelException actualException) {
-      assertEquals(expectedException.toString(), actualException.toString());
-    }
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test/org/apache/solr/ltr/model/TestLinearModel.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/model/TestLinearModel.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/model/TestLinearModel.java
new file mode 100644
index 0000000..e066ea3
--- /dev/null
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/model/TestLinearModel.java
@@ -0,0 +1,211 @@
+/*
+ * 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.solr.ltr.model;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.solr.common.SolrException;
+import org.apache.solr.common.SolrException.ErrorCode;
+import org.apache.solr.ltr.TestRerankBase;
+import org.apache.solr.ltr.feature.Feature;
+import org.apache.solr.ltr.model.LTRScoringModel;
+import org.apache.solr.ltr.model.ModelException;
+import org.apache.solr.ltr.model.LinearModel;
+import org.apache.solr.ltr.norm.IdentityNormalizer;
+import org.apache.solr.ltr.norm.Normalizer;
+import org.apache.solr.ltr.store.FeatureStore;
+import org.apache.solr.ltr.store.rest.ManagedModelStore;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+public class TestLinearModel extends TestRerankBase {
+
+  public static LTRScoringModel createLinearModel(String name, List<Feature> features,
+      List<Normalizer> norms,
+      String featureStoreName, List<Feature> allFeatures,
+      Map<String,Object> params) throws ModelException {
+    final LTRScoringModel model = LTRScoringModel.getInstance(solrResourceLoader,
+        LinearModel.class.getCanonicalName(),
+        name,
+        features, norms, featureStoreName, allFeatures, params);
+    return model;
+  }
+
+  static ManagedModelStore store = null;
+  static FeatureStore fstore = null;
+
+  @BeforeClass
+  public static void setup() throws Exception {
+    setuptest();
+    // loadFeatures("features-store-test-model.json");
+    store = getManagedModelStore();
+    fstore = getManagedFeatureStore().getFeatureStore("test");
+
+  }
+
+  @Test
+  public void getInstanceTest() {
+    final Map<String,Object> weights = new HashMap<>();
+    weights.put("constant1", 1d);
+    weights.put("constant5", 1d);
+
+    Map<String,Object> params = new HashMap<String,Object>();
+    final List<Feature> features = getFeatures(new String[] {
+        "constant1", "constant5"});
+    final List<Normalizer> norms = 
+        new ArrayList<Normalizer>(
+            Collections.nCopies(features.size(),IdentityNormalizer.INSTANCE));
+    params.put("weights", weights);
+    final LTRScoringModel ltrScoringModel = createLinearModel("test1",
+        features, norms, "test", fstore.getFeatures(),
+        params);
+
+    store.addModel(ltrScoringModel);
+    final LTRScoringModel m = store.getModel("test1");
+    assertEquals(ltrScoringModel, m);
+  }
+
+  @Test
+  public void nullFeatureWeightsTest() {
+    final ModelException expectedException = 
+        new ModelException("Model test2 doesn't contain any weights");
+    try {
+      final List<Feature> features = getFeatures(new String[] 
+          {"constant1", "constant5"});
+      final List<Normalizer> norms = 
+        new ArrayList<Normalizer>(
+            Collections.nCopies(features.size(),IdentityNormalizer.INSTANCE));
+      final LTRScoringModel ltrScoringModel = createLinearModel("test2",
+          features, norms, "test", fstore.getFeatures(), null);
+      fail("unexpectedly got here instead of catching "+expectedException);
+    } catch (ModelException actualException) {
+      assertEquals(expectedException.toString(), actualException.toString());
+    }
+  }
+
+  @Test
+  public void existingNameTest() {
+    final SolrException expectedException = 
+        new SolrException(ErrorCode.BAD_REQUEST,
+            ModelException.class.getCanonicalName()+": model 'test3' already exists. Please use a different name");
+    try {
+      final List<Feature> features = getFeatures(new String[] 
+          {"constant1", "constant5"});
+      final List<Normalizer> norms = 
+        new ArrayList<Normalizer>(
+            Collections.nCopies(features.size(),IdentityNormalizer.INSTANCE));
+      final Map<String,Object> weights = new HashMap<>();
+      weights.put("constant1", 1d);
+      weights.put("constant5", 1d);
+
+      Map<String,Object> params = new HashMap<String,Object>();
+      params.put("weights", weights);
+      final LTRScoringModel ltrScoringModel = createLinearModel("test3",
+          features, norms, "test", fstore.getFeatures(),
+              params);
+      store.addModel(ltrScoringModel);
+      final LTRScoringModel m = store.getModel("test3");
+      assertEquals(ltrScoringModel, m);
+      store.addModel(ltrScoringModel);
+      fail("unexpectedly got here instead of catching "+expectedException);
+    } catch (SolrException actualException) {
+      assertEquals(expectedException.toString(), actualException.toString());
+    }
+  }
+
+  @Test
+  public void duplicateFeatureTest() {
+    final ModelException expectedException = 
+        new ModelException("duplicated feature constant1 in model test4");
+    try {
+      final List<Feature> features = getFeatures(new String[] 
+          {"constant1", "constant1"});
+      final List<Normalizer> norms = 
+        new ArrayList<Normalizer>(
+            Collections.nCopies(features.size(),IdentityNormalizer.INSTANCE));
+      final Map<String,Object> weights = new HashMap<>();
+      weights.put("constant1", 1d);
+      weights.put("constant5", 1d);
+
+      Map<String,Object> params = new HashMap<String,Object>();
+      params.put("weights", weights);
+      final LTRScoringModel ltrScoringModel = createLinearModel("test4",
+          features, norms, "test", fstore.getFeatures(),
+              params);
+      store.addModel(ltrScoringModel);
+      fail("unexpectedly got here instead of catching "+expectedException);
+    } catch (ModelException actualException) {
+      assertEquals(expectedException.toString(), actualException.toString());
+    }
+
+  }
+
+  @Test
+  public void missingFeatureWeightTest() {
+    final ModelException expectedException = 
+        new ModelException("Model test5 lacks weight(s) for [constant5]");
+    try {
+      final List<Feature> features = getFeatures(new String[] 
+          {"constant1", "constant5"});
+      final List<Normalizer> norms = 
+        new ArrayList<Normalizer>(
+            Collections.nCopies(features.size(),IdentityNormalizer.INSTANCE));
+      final Map<String,Object> weights = new HashMap<>();
+      weights.put("constant1", 1d);
+      weights.put("constant5missing", 1d);
+
+      Map<String,Object> params = new HashMap<String,Object>();
+      params.put("weights", weights);
+      final LTRScoringModel ltrScoringModel = createLinearModel("test5",
+          features, norms, "test", fstore.getFeatures(),
+              params);
+      fail("unexpectedly got here instead of catching "+expectedException);
+    } catch (ModelException actualException) {
+      assertEquals(expectedException.toString(), actualException.toString());
+    }
+  }
+
+  @Test
+  public void emptyFeaturesTest() {
+    final ModelException expectedException =
+        new ModelException("no features declared for model test6");
+    try {
+      final List<Feature> features = getFeatures(new String[] {});
+      final List<Normalizer> norms = 
+        new ArrayList<Normalizer>(
+            Collections.nCopies(features.size(),IdentityNormalizer.INSTANCE));
+      final Map<String,Object> weights = new HashMap<>();
+      weights.put("constant1", 1d);
+      weights.put("constant5missing", 1d);
+
+      Map<String,Object> params = new HashMap<String,Object>();
+      params.put("weights", weights);
+      final LTRScoringModel ltrScoringModel = createLinearModel("test6",
+          features, norms, "test", fstore.getFeatures(),
+          params);
+      store.addModel(ltrScoringModel);
+      fail("unexpectedly got here instead of catching "+expectedException);
+    } catch (ModelException actualException) {
+      assertEquals(expectedException.toString(), actualException.toString());
+    }
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test/org/apache/solr/ltr/model/TestMultipleAdditiveTreesModel.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/model/TestMultipleAdditiveTreesModel.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/model/TestMultipleAdditiveTreesModel.java
new file mode 100644
index 0000000..3414dc8
--- /dev/null
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/model/TestMultipleAdditiveTreesModel.java
@@ -0,0 +1,249 @@
+/*
+ * 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.solr.ltr.model;
+
+//import static org.junit.internal.matchers.StringContains.containsString;
+
+import org.apache.lucene.util.LuceneTestCase.SuppressCodecs;
+import org.apache.solr.client.solrj.SolrQuery;
+import org.apache.solr.ltr.TestRerankBase;
+import org.apache.solr.ltr.model.ModelException;
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Ignore;
+import org.junit.Test;
+
+@SuppressCodecs({"Lucene3x", "Lucene41", "Lucene40", "Appending"})
+public class TestMultipleAdditiveTreesModel extends TestRerankBase {
+
+
+  @BeforeClass
+  public static void before() throws Exception {
+    setuptest("solrconfig-ltr.xml", "schema-ltr.xml");
+
+    assertU(adoc("id", "1", "title", "w1", "description", "w1", "popularity","1"));
+    assertU(adoc("id", "2", "title", "w2", "description", "w2", "popularity","2"));
+    assertU(adoc("id", "3", "title", "w3", "description", "w3", "popularity","3"));
+    assertU(adoc("id", "4", "title", "w4", "description", "w4", "popularity","4"));
+    assertU(adoc("id", "5", "title", "w5", "description", "w5", "popularity","5"));
+    assertU(commit());
+
+    loadFeatures("multipleadditivetreesmodel_features.json"); // currently needed to force
+    // scoring on all docs
+    loadModels("multipleadditivetreesmodel.json");
+  }
+
+  @AfterClass
+  public static void after() throws Exception {
+    aftertest();
+  }
+
+  
+  @Test
+  public void testMultipleAdditiveTreesScoringWithAndWithoutEfiFeatureMatches() throws Exception {
+    final SolrQuery query = new SolrQuery();
+    query.setQuery("*:*");
+    query.add("rows", "3");
+    query.add("fl", "*,score");
+
+    // Regular scores
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[0]/score==1.0");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[1]/score==1.0");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[2]/score==1.0");
+
+    // No match scores since user_query not passed in to external feature info
+    // and feature depended on it.
+    query.add("rq", "{!ltr reRankDocs=3 model=multipleadditivetreesmodel efi.user_query=dsjkafljjk}");
+
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[0]/score==-120.0");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[1]/score==-120.0");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[2]/score==-120.0");
+
+    // Matched user query since it was passed in
+    query.remove("rq");
+    query.add("rq", "{!ltr reRankDocs=3 model=multipleadditivetreesmodel efi.user_query=w3}");
+
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[0]/id=='3'");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[0]/score==-20.0");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[1]/score==-120.0");
+    assertJQ("/query" + query.toQueryString(), "/response/docs/[2]/score==-120.0");
+  }
+
+  @Ignore
+  @Test
+  public void multipleAdditiveTreesTestExplain() throws Exception {
+    final SolrQuery query = new SolrQuery();
+    query.setQuery("*:*");
+    query.add("fl", "*,score,[fv]");
+    query.add("rows", "3");
+
+    query.add("rq",
+        "{!ltr reRankDocs=3 model=multipleadditivetreesmodel efi.user_query=w3}");
+
+    // test out the explain feature, make sure it returns something
+    query.setParam("debugQuery", "on");
+    String qryResult = JQ("/query" + query.toQueryString());
+
+    qryResult = qryResult.replaceAll("\n", " ");
+    // FIXME containsString doesn't exist.
+    // assertThat(qryResult, containsString("\"debug\":{"));
+    // qryResult = qryResult.substring(qryResult.indexOf("debug"));
+    //
+    // assertThat(qryResult, containsString("\"explain\":{"));
+    // qryResult = qryResult.substring(qryResult.indexOf("explain"));
+    //
+    // assertThat(qryResult, containsString("multipleadditivetreesmodel"));
+    // assertThat(qryResult,
+    // containsString(MultipleAdditiveTreesModel.class.getCanonicalName()));
+    //
+    // assertThat(qryResult, containsString("-100.0 = tree 0"));
+    // assertThat(qryResult, containsString("50.0 = tree 0"));
+    // assertThat(qryResult, containsString("-20.0 = tree 1"));
+    // assertThat(qryResult, containsString("'matchedTitle':1.0 > 0.5"));
+    // assertThat(qryResult, containsString("'matchedTitle':0.0 <= 0.5"));
+    //
+    // assertThat(qryResult, containsString(" Go Right "));
+    // assertThat(qryResult, containsString(" Go Left "));
+    // assertThat(qryResult,
+    // containsString("'this_feature_doesnt_exist' does not exist in FV"));
+  }
+
+  @Test
+  public void multipleAdditiveTreesTestNoParams() throws Exception {
+    final ModelException expectedException = 
+        new ModelException("no trees declared for model multipleadditivetreesmodel_no_params");
+    try {
+        createModelFromFiles("multipleadditivetreesmodel_no_params.json",
+              "multipleadditivetreesmodel_features.json");
+        fail("multipleAdditiveTreesTestNoParams failed to throw exception: "+expectedException);
+    } catch (Exception actualException) {
+      Throwable rootError = getRootCause(actualException);
+      assertEquals(expectedException.toString(), rootError.toString());
+    }
+
+  }
+
+  @Test
+  public void multipleAdditiveTreesTestEmptyParams() throws Exception {
+    final ModelException expectedException = 
+        new ModelException("no trees declared for model multipleadditivetreesmodel_no_trees");
+    try {
+        createModelFromFiles("multipleadditivetreesmodel_no_trees.json",
+            "multipleadditivetreesmodel_features.json");
+        fail("multipleAdditiveTreesTestEmptyParams failed to throw exception: "+expectedException);
+    } catch (Exception actualException) {
+      Throwable rootError = getRootCause(actualException);
+      assertEquals(expectedException.toString(), rootError.toString());
+    }
+  }
+
+  @Test
+  public void multipleAdditiveTreesTestNoWeight() throws Exception {
+    final ModelException expectedException = 
+        new ModelException("MultipleAdditiveTreesModel tree doesn't contain a weight");
+    try {
+        createModelFromFiles("multipleadditivetreesmodel_no_weight.json",
+            "multipleadditivetreesmodel_features.json");
+        fail("multipleAdditiveTreesTestNoWeight failed to throw exception: "+expectedException);
+    } catch (Exception actualException) {
+      Throwable rootError = getRootCause(actualException);
+      assertEquals(expectedException.toString(), rootError.toString());
+    }
+  }
+
+  @Test
+  public void multipleAdditiveTreesTestTreesParamDoesNotContatinTree() throws Exception {
+    final ModelException expectedException = 
+        new ModelException("MultipleAdditiveTreesModel tree doesn't contain a tree");
+    try {
+        createModelFromFiles("multipleadditivetreesmodel_no_tree.json",
+            "multipleadditivetreesmodel_features.json");
+        fail("multipleAdditiveTreesTestTreesParamDoesNotContatinTree failed to throw exception: "+expectedException);
+    } catch (Exception actualException) {
+      Throwable rootError = getRootCause(actualException);
+      assertEquals(expectedException.toString(), rootError.toString());
+    }
+  }
+
+  @Test
+  public void multipleAdditiveTreesTestNoFeaturesSpecified() throws Exception {
+    final ModelException expectedException = 
+        new ModelException("no features declared for model multipleadditivetreesmodel_no_features");
+    try {
+        createModelFromFiles("multipleadditivetreesmodel_no_features.json",
+            "multipleadditivetreesmodel_features.json");
+        fail("multipleAdditiveTreesTestNoFeaturesSpecified failed to throw exception: "+expectedException);
+    } catch (ModelException actualException) {
+      assertEquals(expectedException.toString(), actualException.toString());
+    }
+  }
+
+  @Test
+  public void multipleAdditiveTreesTestNoRight() throws Exception {
+    final ModelException expectedException = 
+        new ModelException("MultipleAdditiveTreesModel tree node is missing right");
+    try {
+        createModelFromFiles("multipleadditivetreesmodel_no_right.json",
+            "multipleadditivetreesmodel_features.json");
+        fail("multipleAdditiveTreesTestNoRight failed to throw exception: "+expectedException);
+    } catch (Exception actualException) {
+      Throwable rootError = getRootCause(actualException);
+      assertEquals(expectedException.toString(), rootError.toString());
+    }
+  }
+
+  @Test
+  public void multipleAdditiveTreesTestNoLeft() throws Exception {
+    final ModelException expectedException = 
+        new ModelException("MultipleAdditiveTreesModel tree node is missing left");
+    try {
+        createModelFromFiles("multipleadditivetreesmodel_no_left.json",
+            "multipleadditivetreesmodel_features.json");
+        fail("multipleAdditiveTreesTestNoLeft failed to throw exception: "+expectedException);
+    } catch (Exception actualException) {
+      Throwable rootError = getRootCause(actualException);
+      assertEquals(expectedException.toString(), rootError.toString());
+    }
+  }
+
+  @Test
+  public void multipleAdditiveTreesTestNoThreshold() throws Exception {
+    final ModelException expectedException = 
+        new ModelException("MultipleAdditiveTreesModel tree node is missing threshold");
+    try {
+        createModelFromFiles("multipleadditivetreesmodel_no_threshold.json",
+            "multipleadditivetreesmodel_features.json");
+        fail("multipleAdditiveTreesTestNoThreshold failed to throw exception: "+expectedException);
+    } catch (Exception actualException) {
+      Throwable rootError = getRootCause(actualException);
+      assertEquals(expectedException.toString(), rootError.toString());
+    }
+  }
+
+  @Test
+  public void multipleAdditiveTreesTestMissingTreeFeature() throws Exception {
+    final ModelException expectedException = 
+        new ModelException("MultipleAdditiveTreesModel tree node is leaf with left=-100.0 and right=75.0");
+    try {
+        createModelFromFiles("multipleadditivetreesmodel_no_feature.json",
+              "multipleadditivetreesmodel_features.json");
+        fail("multipleAdditiveTreesTestMissingTreeFeature failed to throw exception: "+expectedException);
+    } catch (ModelException actualException) {
+      assertEquals(expectedException.toString(), actualException.toString());
+    }
+  }
+}