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:18 UTC

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

Repository: lucene-solr
Updated Branches:
  refs/heads/jira/solr-8542-v2 [created] bfa05b830


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test/org/apache/solr/ltr/model/TestRankSVMModel.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/model/TestRankSVMModel.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/model/TestRankSVMModel.java
deleted file mode 100644
index ff2746d..0000000
--- a/solr/contrib/ltr/src/test/org/apache/solr/ltr/model/TestRankSVMModel.java
+++ /dev/null
@@ -1,211 +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 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.RankSVMModel;
-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 TestRankSVMModel extends TestRerankBase {
-
-  public static LTRScoringModel createRankSVMModel(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,
-        RankSVMModel.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 = createRankSVMModel("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 = createRankSVMModel("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 = createRankSVMModel("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 = createRankSVMModel("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 = createRankSVMModel("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 = createRankSVMModel("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/store/rest/TestModelManager.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/store/rest/TestModelManager.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/store/rest/TestModelManager.java
index f54f58a..2e5a628 100644
--- a/solr/contrib/ltr/src/test/org/apache/solr/ltr/store/rest/TestModelManager.java
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/store/rest/TestModelManager.java
@@ -22,7 +22,7 @@ import org.apache.solr.core.SolrResourceLoader;
 import org.apache.solr.ltr.TestRerankBase;
 import org.apache.solr.ltr.feature.FieldValueFeature;
 import org.apache.solr.ltr.feature.ValueFeature;
-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.rest.ManagedResource;
@@ -115,24 +115,24 @@ public class TestModelManager extends TestRerankBase {
     assertJPut(ManagedFeatureStore.REST_END_POINT, badfeature,
         "/error/msg/=='No setter corrresponding to \\'value\\' in "+fieldValueFeatureClassName+"'");
 
-    final String rankSVMModelClassName = RankSVMModel.class.getCanonicalName();
+    final String linearModelClassName = LinearModel.class.getCanonicalName();
 
     // Add models
-    String model = "{ \"name\":\"testmodel1\", \"class\":\""+rankSVMModelClassName+"\", \"features\":[] }";
+    String model = "{ \"name\":\"testmodel1\", \"class\":\""+linearModelClassName+"\", \"features\":[] }";
     // fails since it does not have features
     assertJPut(ManagedModelStore.REST_END_POINT, model,
         "/responseHeader/status==400");
     // fails since it does not have weights
-    model = "{ \"name\":\"testmodel2\", \"class\":\""+rankSVMModelClassName+"\", \"features\":[{\"name\":\"test1\"}, {\"name\":\"test2\"}] }";
+    model = "{ \"name\":\"testmodel2\", \"class\":\""+linearModelClassName+"\", \"features\":[{\"name\":\"test1\"}, {\"name\":\"test2\"}] }";
     assertJPut(ManagedModelStore.REST_END_POINT, model,
         "/responseHeader/status==400");
     // success
-    model = "{ \"name\":\"testmodel3\", \"class\":\""+rankSVMModelClassName+"\", \"features\":[{\"name\":\"test1\"}, {\"name\":\"test2\"}],\"params\":{\"weights\":{\"test1\":1.5,\"test2\":2.0}}}";
+    model = "{ \"name\":\"testmodel3\", \"class\":\""+linearModelClassName+"\", \"features\":[{\"name\":\"test1\"}, {\"name\":\"test2\"}],\"params\":{\"weights\":{\"test1\":1.5,\"test2\":2.0}}}";
     assertJPut(ManagedModelStore.REST_END_POINT, model,
         "/responseHeader/status==0");
     // success
-    final String multipleModels = "[{ \"name\":\"testmodel4\", \"class\":\""+rankSVMModelClassName+"\", \"features\":[{\"name\":\"test1\"}, {\"name\":\"test2\"}],\"params\":{\"weights\":{\"test1\":1.5,\"test2\":2.0}} }\n"
-        + ",{ \"name\":\"testmodel5\", \"class\":\""+rankSVMModelClassName+"\", \"features\":[{\"name\":\"test1\"}, {\"name\":\"test2\"}],\"params\":{\"weights\":{\"test1\":1.5,\"test2\":2.0}} } ]";
+    final String multipleModels = "[{ \"name\":\"testmodel4\", \"class\":\""+linearModelClassName+"\", \"features\":[{\"name\":\"test1\"}, {\"name\":\"test2\"}],\"params\":{\"weights\":{\"test1\":1.5,\"test2\":2.0}} }\n"
+        + ",{ \"name\":\"testmodel5\", \"class\":\""+linearModelClassName+"\", \"features\":[{\"name\":\"test1\"}, {\"name\":\"test2\"}],\"params\":{\"weights\":{\"test1\":1.5,\"test2\":2.0}} } ]";
     assertJPut(ManagedModelStore.REST_END_POINT, multipleModels,
         "/responseHeader/status==0");
     final String qryResult = JQ(ManagedModelStore.REST_END_POINT);
@@ -155,8 +155,8 @@ public class TestModelManager extends TestRerankBase {
 
   @Test
   public void testEndpointsFromFile() throws Exception {
-    loadFeatures("features-ranksvm.json");
-    loadModels("ranksvm-model.json");
+    loadFeatures("features-linear.json");
+    loadModels("linear-model.json");
 
     assertJQ(ManagedModelStore.REST_END_POINT,
         "/models/[0]/name=='6029760550880411648'");

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test/org/apache/solr/ltr/store/rest/TestModelManagerPersistence.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test/org/apache/solr/ltr/store/rest/TestModelManagerPersistence.java b/solr/contrib/ltr/src/test/org/apache/solr/ltr/store/rest/TestModelManagerPersistence.java
index 2b81be7..18baeac 100644
--- a/solr/contrib/ltr/src/test/org/apache/solr/ltr/store/rest/TestModelManagerPersistence.java
+++ b/solr/contrib/ltr/src/test/org/apache/solr/ltr/store/rest/TestModelManagerPersistence.java
@@ -23,7 +23,7 @@ import org.apache.commons.io.FileUtils;
 import org.apache.solr.SolrTestCaseJ4.SuppressSSL;
 import org.apache.solr.ltr.TestRerankBase;
 import org.apache.solr.ltr.feature.ValueFeature;
-import org.apache.solr.ltr.model.RankSVMModel;
+import org.apache.solr.ltr.model.LinearModel;
 import org.junit.Before;
 import org.junit.Test;
 import org.noggit.ObjectBuilder;
@@ -70,9 +70,9 @@ public class TestModelManagerPersistence extends TestRerankBase {
         "/features/[0]/name=='feature1'");
     assertJQ(ManagedFeatureStore.REST_END_POINT + "/test2",
         "/features/[0]/name=='feature3'");
-    loadModel("test-model", RankSVMModel.class.getCanonicalName(),
+    loadModel("test-model", LinearModel.class.getCanonicalName(),
         new String[] {"feature"}, "test", "{\"weights\":{\"feature\":1.0}}");
-    loadModel("test-model2", RankSVMModel.class.getCanonicalName(),
+    loadModel("test-model2", LinearModel.class.getCanonicalName(),
         new String[] {"feature1"}, "test1", "{\"weights\":{\"feature1\":1.0}}");
     final String fstorecontent = FileUtils
         .readFileToString(fstorefile, "UTF-8");


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

Posted by cp...@apache.org.
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());
+    }
+  }
+}


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

Posted by cp...@apache.org.
Generic models for ranking (#174)

This is to make the models more generic to handle all forms of two types of models. One type of model is the linear model. Previously, we focused on only the RankSVM, but this version is generic with any linear model including Pranking, a form of ordinal regression. The other type of model is a generic multiple additive trees model. Previosuly, we focused only on LambdaMART, but this version is generic with any form of additive trees, which also includes gradient boosted regression trees (GBRT) models.


Project: http://git-wip-us.apache.org/repos/asf/lucene-solr/repo
Commit: http://git-wip-us.apache.org/repos/asf/lucene-solr/commit/bfa05b83
Tree: http://git-wip-us.apache.org/repos/asf/lucene-solr/tree/bfa05b83
Diff: http://git-wip-us.apache.org/repos/asf/lucene-solr/diff/bfa05b83

Branch: refs/heads/jira/solr-8542-v2
Commit: bfa05b830dce07837f675745bfbd07814fe4794b
Parents: f2a8e8a
Author: jdorando <jo...@gmail.com>
Authored: Tue Oct 18 10:08:24 2016 -0400
Committer: mnilsson23 <mn...@bloomberg.net>
Committed: Tue Oct 18 10:17:12 2016 -0400

----------------------------------------------------------------------
 solr/contrib/ltr/README.md                      |  58 ++-
 solr/contrib/ltr/example/libsvm_formatter.py    |   2 +-
 .../contrib/ltr/example/techproducts-model.json |   4 +-
 .../apache/solr/ltr/model/LambdaMARTModel.java  | 369 ------------------
 .../org/apache/solr/ltr/model/LinearModel.java  | 145 +++++++
 .../ltr/model/MultipleAdditiveTreesModel.java   | 375 +++++++++++++++++++
 .../org/apache/solr/ltr/model/RankSVMModel.java | 139 -------
 .../java/org/apache/solr/ltr/package-info.java  |   2 +-
 solr/contrib/ltr/src/java/overview.html         |   2 +-
 .../featureExamples/features-linear-efi.json    |  17 +
 .../featureExamples/features-linear.json        |  51 +++
 .../featureExamples/features-ranksvm-efi.json   |  17 -
 .../featureExamples/features-ranksvm.json       |  51 ---
 .../featureExamples/lambdamart_features.json    |  16 -
 .../multipleadditivetreesmodel_features.json    |  16 +
 .../modelExamples/external_model.json           |   2 +-
 .../modelExamples/external_model_store.json     |   2 +-
 .../src/test-files/modelExamples/fq-model.json  |   2 +-
 .../modelExamples/lambdamartmodel.json          |  38 --
 ...ambdamartmodel_external_binary_features.json |  38 --
 .../lambdamartmodel_no_feature.json             |  24 --
 .../lambdamartmodel_no_features.json            |  14 -
 .../modelExamples/lambdamartmodel_no_left.json  |  22 --
 .../lambdamartmodel_no_params.json              |   8 -
 .../modelExamples/lambdamartmodel_no_right.json |  22 --
 .../lambdamartmodel_no_threshold.json           |  24 --
 .../modelExamples/lambdamartmodel_no_tree.json  |  15 -
 .../modelExamples/lambdamartmodel_no_trees.json |  10 -
 .../lambdamartmodel_no_weight.json              |  24 --
 .../modelExamples/linear-model-efi.json         |  14 +
 .../test-files/modelExamples/linear-model.json  |  30 ++
 .../multipleadditivetreesmodel.json             |  38 ++
 ...tivetreesmodel_external_binary_features.json |  38 ++
 .../multipleadditivetreesmodel_no_feature.json  |  24 ++
 .../multipleadditivetreesmodel_no_features.json |  14 +
 .../multipleadditivetreesmodel_no_left.json     |  22 ++
 .../multipleadditivetreesmodel_no_params.json   |   8 +
 .../multipleadditivetreesmodel_no_right.json    |  22 ++
 ...multipleadditivetreesmodel_no_threshold.json |  24 ++
 .../multipleadditivetreesmodel_no_tree.json     |  15 +
 .../multipleadditivetreesmodel_no_trees.json    |  10 +
 .../multipleadditivetreesmodel_no_weight.json   |  24 ++
 .../test-files/modelExamples/ranksvm-model.json |  30 --
 .../test-files/modelExamples/svm-model-efi.json |  14 -
 .../src/test-files/modelExamples/svm-model.json |  20 -
 .../org/apache/solr/ltr/TestLTROnSolrCloud.java |   4 +-
 .../apache/solr/ltr/TestLTRQParserExplain.java  |  36 +-
 .../apache/solr/ltr/TestLTRQParserPlugin.java   |   4 +-
 .../solr/ltr/TestLTRReRankingPipeline.java      |   6 +-
 .../apache/solr/ltr/TestLTRScoringQuery.java    |  16 +-
 .../org/apache/solr/ltr/TestLTRWithFacet.java   |   4 +-
 .../org/apache/solr/ltr/TestLTRWithSort.java    |   4 +-
 .../org/apache/solr/ltr/TestRerankBase.java     |   4 +-
 .../solr/ltr/TestSelectiveWeightCreation.java   |   6 +-
 .../ltr/feature/TestEdisMaxSolrFeature.java     |   4 +-
 .../ltr/feature/TestExternalValueFeatures.java  |   2 +-
 .../solr/ltr/feature/TestFeatureLogging.java    |  10 +-
 .../ltr/feature/TestFieldLengthFeature.java     |  10 +-
 .../solr/ltr/feature/TestFieldValueFeature.java |   8 +-
 .../solr/ltr/feature/TestFilterSolrFeature.java |   4 +-
 .../ltr/feature/TestNoMatchSolrFeature.java     |  14 +-
 .../ltr/feature/TestOriginalScoreFeature.java   |   6 +-
 .../solr/ltr/feature/TestRankingFeature.java    |   8 +-
 .../ltr/feature/TestUserTermScoreWithQ.java     |   4 +-
 .../ltr/feature/TestUserTermScorerQuery.java    |   4 +-
 .../ltr/feature/TestUserTermScorereQDF.java     |   4 +-
 .../solr/ltr/feature/TestValueFeature.java      |  12 +-
 .../solr/ltr/model/TestLambdaMARTModel.java     | 249 ------------
 .../apache/solr/ltr/model/TestLinearModel.java  | 211 +++++++++++
 .../model/TestMultipleAdditiveTreesModel.java   | 249 ++++++++++++
 .../apache/solr/ltr/model/TestRankSVMModel.java | 211 -----------
 .../solr/ltr/store/rest/TestModelManager.java   |  18 +-
 .../store/rest/TestModelManagerPersistence.java |   6 +-
 73 files changed, 1482 insertions(+), 1492 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/README.md
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/README.md b/solr/contrib/ltr/README.md
index ada1d5b..5fe0087 100644
--- a/solr/contrib/ltr/README.md
+++ b/solr/contrib/ltr/README.md
@@ -33,7 +33,7 @@ the techproducts example please follow these steps.
         `mkdir example/techproducts/solr/techproducts/lib`
      3. Install the plugin in the lib folder
 
-        `cp build/contrib/ltr/lucene-ltr-7.0.0-SNAPSHOT.jar example/techproducts/solr/techproducts/lib/`
+        `cp build/contrib/ltr/solr-ltr-7.0.0-SNAPSHOT.jar example/techproducts/solr/techproducts/lib/`
      4. Replace the original solrconfig with one importing all the ltr components
 
         `cp contrib/ltr/example/solrconfig.xml example/techproducts/solr/techproducts/conf/`
@@ -61,7 +61,7 @@ the techproducts example please follow these steps.
        http://localhost:8983/solr/techproducts/schema/model-store
      * Perform a reranking query using the model, and retrieve the features
 
-       http://localhost:8983/solr/techproducts/query?indent=on&q=test&wt=json&rq={!ltr%20model=svm%20reRankDocs=25%20efi.user_query=%27test%27}&fl=[features],price,score,name
+       http://localhost:8983/solr/techproducts/query?indent=on&q=test&wt=json&rq={!ltr%20model=linear%20reRankDocs=25%20efi.user_query=%27test%27}&fl=[features],price,score,name
 
 
 BONUS: Train an actual machine learning model
@@ -78,7 +78,7 @@ BONUS: Train an actual machine learning model
 
    This script deploys your features from `config.json` "featuresFile" to Solr.  Then it takes the relevance judged query
    document pairs of "userQueriesFile" and merges it with the features extracted from Solr into a training
-   file.  That file is used to train a rankSVM model, which is then deployed to Solr for you to rerank results.
+   file.  That file is used to train a linear model, which is then deployed to Solr for you to rerank results.
 
 4. Search and rerank the results using the trained model
 
@@ -153,7 +153,7 @@ using standard Solr queries. As an example:
 ]
 ```
 
-Defines four features. Anything that is a valid Solr query can be used to define
+Defines five features. Anything that is a valid Solr query can be used to define
 a feature.
 
 ### Filter Query Features
@@ -198,19 +198,18 @@ The majority of features should be possible to create using the methods describe
 above.
 
 # Defining Models
-Currently the Learning to Rank plugin supports 2 main types of
-ranking models: [Ranking SVM](http://www.cs.cornell.edu/people/tj/publications/joachims_02c.pdf)
-and [LambdaMART](http://research.microsoft.com/pubs/132652/MSR-TR-2010-82.pdf)
+Currently the Learning to Rank plugin supports 2 generalized forms of
+models: 1. Linear Model i.e. [RankSVM](http://www.cs.cornell.edu/people/tj/publications/joachims_02c.pdf), [Pranking](https://papers.nips.cc/paper/2023-pranking-with-ranking.pdf)
+and 2. Multiple Additive Trees i.e. [LambdaMART](http://research.microsoft.com/pubs/132652/MSR-TR-2010-82.pdf), [Gradient Boosted Regression Trees (GBRT)](https://papers.nips.cc/paper/3305-a-general-boosting-method-and-its-application-to-learning-ranking-functions-for-web-search.pdf)
 
-### Ranking SVM
-Currently only a linear ranking svm is supported. Use LambdaMART for
-a non-linear model. If you'd like to introduce a bias set a constant feature
+### Linear
+If you'd like to introduce a bias set a constant feature
 to the bias value you'd like and make a weight of 1.0 for that feature.
 
 ###### model.json
 ```json
 {
-    "class":"org.apache.solr.ltr.model.RankSVMModel",
+    "class":"org.apache.solr.ltr.model.LinearModel",
     "name":"myModelName",
     "features":[
         { "name": "userTextTitleMatch"},
@@ -228,27 +227,26 @@ to the bias value you'd like and make a weight of 1.0 for that feature.
 }
 ```
 
-This is an example of a toy Ranking SVM model. Type specifies the class to be
-using to interpret the model (RankSVMModel in the case of Ranking SVM).
-Name is the model identifier you will use when making request to the ltr
-framework. Features specifies the feature space that you want extracted
-when using this model. All features that appear in the model params will
-be used for scoring and must appear in the features list.  You can add
-extra features to the features list that will be computed but not used in the
-model for scoring, which can be useful for logging.
-Params are the Ranking SVM parameters.
+This is an example of a toy Linear model. Class specifies the class to be
+using to interpret the model. Name is the model identifier you will use 
+when making request to the ltr framework. Features specifies the feature 
+space that you want extracted when using this model. All features that 
+appear in the model params will be used for scoring and must appear in 
+the features list.  You can add extra features to the features list that 
+will be computed but not used in the model for scoring, which can be useful 
+for logging. Params are the Linear parameters.
 
-Good library for training SVM's (https://www.csie.ntu.edu.tw/~cjlin/liblinear/ ,
-https://www.csie.ntu.edu.tw/~cjlin/libsvm/) . You will need to convert the
-libSVM model format to the format specified above.
+Good library for training SVM, an example of a Linear model, is 
+(https://www.csie.ntu.edu.tw/~cjlin/liblinear/ , https://www.csie.ntu.edu.tw/~cjlin/libsvm/) . 
+You will need to convert the libSVM model format to the format specified above.
 
-### LambdaMART
+### Multiple Additive Trees
 
 ###### model2.json
 ```json
 {
-    "class":"org.apache.solr.ltr.model.LambdaMARTModel",
-    "name":"lambdamartmodel",
+    "class":"org.apache.solr.ltr.model.MultipleAdditiveTreesModel",
+    "name":"multipleadditivetreesmodel",
     "features":[
         { "name": "userTextTitleMatch"},
         { "name": "originalScore"}
@@ -285,17 +283,17 @@ libSVM model format to the format specified above.
     }
 }
 ```
-This is an example of a toy LambdaMART. Type specifies the class to be using to
-interpret the model (LambdaMARTModel in the case of LambdaMART). Name is the
+This is an example of a toy Multiple Additive Trees. Class specifies the class to be using to
+interpret the model. Name is the
 model identifier you will use when making request to the ltr framework.
 Features specifies the feature space that you want extracted when using this
 model. All features that appear in the model params will be used for scoring and
 must appear in the features list.  You can add extra features to the features
 list that will be computed but not used in the model for scoring, which can
-be useful for logging. Params are the LambdaMART specific parameters. In this
+be useful for logging. Params are the Multiple Additive Trees specific parameters. In this
 case we have 2 trees, one with 3 leaf nodes and one with 1 leaf node.
 
-A good library for training LambdaMART ( http://sourceforge.net/p/lemur/wiki/RankLib/ ).
+A good library for training LambdaMART, an example of Multiple Additive Trees, is ( http://sourceforge.net/p/lemur/wiki/RankLib/ ).
 You will need to convert the RankLib model format to the format specified above.
 
 # Deploy Models and Features

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/example/libsvm_formatter.py
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/example/libsvm_formatter.py b/solr/contrib/ltr/example/libsvm_formatter.py
index 89ba12f..25cf10b 100644
--- a/solr/contrib/ltr/example/libsvm_formatter.py
+++ b/solr/contrib/ltr/example/libsvm_formatter.py
@@ -45,7 +45,7 @@ class LibSvmFormatter:
     def convertLibSvmModelToLtrModel(self,libSvmModelLocation, outputFile, modelName):
         with open(libSvmModelLocation, 'r') as inFile:
             with open(outputFile,'w') as convertedOutFile:
-                convertedOutFile.write('{\n\t"class":"org.apache.solr.ltr.model.RankSVMModel",\n')
+                convertedOutFile.write('{\n\t"class":"org.apache.solr.ltr.model.LinearModel",\n')
                 convertedOutFile.write('\t"name": "' + str(modelName) + '",\n')
                 convertedOutFile.write('\t"features": [\n')
                 isFirst = True;

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/example/techproducts-model.json
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/example/techproducts-model.json b/solr/contrib/ltr/example/techproducts-model.json
index a1b58f1..0efded7 100644
--- a/solr/contrib/ltr/example/techproducts-model.json
+++ b/solr/contrib/ltr/example/techproducts-model.json
@@ -1,6 +1,6 @@
 {
-    "class":"org.apache.solr.ltr.model.RankSVMModel",
-    "name":"svm",
+    "class":"org.apache.solr.ltr.model.LinearModel",
+    "name":"linear",
     "features":[
     {"name":"isInStock"},
     {"name":"price"},

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/java/org/apache/solr/ltr/model/LambdaMARTModel.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/java/org/apache/solr/ltr/model/LambdaMARTModel.java b/solr/contrib/ltr/src/java/org/apache/solr/ltr/model/LambdaMARTModel.java
deleted file mode 100644
index 23704d0..0000000
--- a/solr/contrib/ltr/src/java/org/apache/solr/ltr/model/LambdaMARTModel.java
+++ /dev/null
@@ -1,369 +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 java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.lucene.index.LeafReaderContext;
-import org.apache.lucene.search.Explanation;
-import org.apache.solr.ltr.feature.Feature;
-import org.apache.solr.ltr.norm.Normalizer;
-import org.apache.solr.util.SolrPluginUtils;
-
-/**
- * A scoring model that computes scores based on the LambdaMART algorithm.
- * <p>
- * Example configuration:
-<pre>{
-   "class" : "org.apache.solr.ltr.model.LambdaMARTModel",
-   "name" : "lambdamartmodel",
-   "features":[
-       { "name" : "userTextTitleMatch"},
-       { "name" : "originalScore"}
-   ],
-   "params" : {
-       "trees" : [
-           {
-               "weight" : 1,
-               "root": {
-                   "feature" : "userTextTitleMatch",
-                   "threshold" : 0.5,
-                   "left" : {
-                       "value" : -100
-                   },
-                   "right" : {
-                       "feature" : "originalScore",
-                       "threshold" : 10.0,
-                       "left" : {
-                           "value" : 50
-                       },
-                       "right" : {
-                           "value" : 75
-                       }
-                   }
-               }
-           },
-           {
-               "weight" : 2,
-               "root" : {
-                   "value" : -10
-               }
-           }
-       ]
-   }
-}</pre>
- * <p>
- * Background reading:
- * <ul>
- * <li> <a href="http://research.microsoft.com/pubs/132652/MSR-TR-2010-82.pdf">
- * Christopher J.C. Burges. From RankNet to LambdaRank to LambdaMART: An Overview.
- * Microsoft Research Technical Report MSR-TR-2010-82.</a>
- * </ul>
- */
-public class LambdaMARTModel extends LTRScoringModel {
-
-  private final HashMap<String,Integer> fname2index;
-  private List<RegressionTree> trees;
-
-  private RegressionTree createRegressionTree(Map<String,Object> map) {
-    final RegressionTree rt = new RegressionTree();
-    if (map != null) {
-      SolrPluginUtils.invokeSetters(rt, map.entrySet());
-    }
-    return rt;
-  }
-
-  private RegressionTreeNode createRegressionTreeNode(Map<String,Object> map) {
-    final RegressionTreeNode rtn = new RegressionTreeNode();
-    if (map != null) {
-      SolrPluginUtils.invokeSetters(rtn, map.entrySet());
-    }
-    return rtn;
-  }
-
-  public class RegressionTreeNode {
-    private static final float NODE_SPLIT_SLACK = 1E-6f;
-
-    private float value = 0f;
-    private String feature;
-    private int featureIndex = -1;
-    private Float threshold;
-    private RegressionTreeNode left;
-    private RegressionTreeNode right;
-
-    public void setValue(float value) {
-      this.value = value;
-    }
-
-    public void setValue(String value) {
-      this.value = Float.parseFloat(value);
-    }
-
-    public void setFeature(String feature) {
-      this.feature = feature;
-      final Integer idx = fname2index.get(this.feature);
-      // this happens if the tree specifies a feature that does not exist
-      // this could be due to lambdaSmart building off of pre-existing trees
-      // that use a feature that is no longer output during feature extraction
-      featureIndex = (idx == null) ? -1 : idx;
-    }
-
-    public void setThreshold(float threshold) {
-      this.threshold = threshold + NODE_SPLIT_SLACK;
-    }
-
-    public void setThreshold(String threshold) {
-      this.threshold = Float.parseFloat(threshold) + NODE_SPLIT_SLACK;
-    }
-
-    public void setLeft(Object left) {
-      this.left = createRegressionTreeNode((Map<String,Object>) left);
-    }
-
-    public void setRight(Object right) {
-      this.right = createRegressionTreeNode((Map<String,Object>) right);
-    }
-
-    public boolean isLeaf() {
-      return feature == null;
-    }
-
-    public float score(float[] featureVector) {
-      if (isLeaf()) {
-        return value;
-      }
-
-      // unsupported feature (tree is looking for a feature that does not exist)
-      if  ((featureIndex < 0) || (featureIndex >= featureVector.length)) {
-        return 0f;
-      }
-
-      if (featureVector[featureIndex] <= threshold) {
-        return left.score(featureVector);
-      } else {
-        return right.score(featureVector);
-      }
-    }
-
-    public String explain(float[] featureVector) {
-      if (isLeaf()) {
-        return "val: " + value;
-      }
-
-      // unsupported feature (tree is looking for a feature that does not exist)
-      if  ((featureIndex < 0) || (featureIndex >= featureVector.length)) {
-        return  "'" + feature + "' does not exist in FV, Return Zero";
-      }
-
-      // could store extra information about how much training data supported
-      // each branch and report
-      // that here
-
-      if (featureVector[featureIndex] <= threshold) {
-        String rval = "'" + feature + "':" + featureVector[featureIndex] + " <= "
-            + threshold + ", Go Left | ";
-        return rval + left.explain(featureVector);
-      } else {
-        String rval = "'" + feature + "':" + featureVector[featureIndex] + " > "
-            + threshold + ", Go Right | ";
-        return rval + right.explain(featureVector);
-      }
-    }
-
-    @Override
-    public String toString() {
-      final StringBuilder sb = new StringBuilder();
-      if (isLeaf()) {
-        sb.append(value);
-      } else {
-        sb.append("(feature=").append(feature);
-        sb.append(",threshold=").append(threshold.floatValue()-NODE_SPLIT_SLACK);
-        sb.append(",left=").append(left);
-        sb.append(",right=").append(right);
-        sb.append(')');
-      }
-      return sb.toString();
-    }
-
-    public RegressionTreeNode() {
-    }
-
-    public void validate() throws ModelException {
-      if (isLeaf()) {
-        if (left != null || right != null) {
-          throw new ModelException("LambdaMARTModel tree node is leaf with left="+left+" and right="+right);
-        }
-        return;
-      }
-      if (null == threshold) {
-        throw new ModelException("LambdaMARTModel tree node is missing threshold");
-      }
-      if (null == left) {
-        throw new ModelException("LambdaMARTModel tree node is missing left");
-      } else {
-        left.validate();
-      }
-      if (null == right) {
-        throw new ModelException("LambdaMARTModel tree node is missing right");
-      } else {
-        right.validate();
-      }
-    }
-
-  }
-
-  public class RegressionTree {
-
-    private Float weight;
-    private RegressionTreeNode root;
-
-    public void setWeight(float weight) {
-      this.weight = new Float(weight);
-    }
-
-    public void setWeight(String weight) {
-      this.weight = new Float(weight);
-    }
-
-    public void setRoot(Object root) {
-      this.root = createRegressionTreeNode((Map<String,Object>)root);
-    }
-
-    public float score(float[] featureVector) {
-      return weight.floatValue() * root.score(featureVector);
-    }
-
-    public String explain(float[] featureVector) {
-      return root.explain(featureVector);
-    }
-
-    @Override
-    public String toString() {
-      final StringBuilder sb = new StringBuilder();
-      sb.append("(weight=").append(weight);
-      sb.append(",root=").append(root);
-      sb.append(")");
-      return sb.toString();
-    }
-
-    public RegressionTree() {
-    }
-
-    public void validate() throws ModelException {
-      if (weight == null) {
-        throw new ModelException("LambdaMARTModel tree doesn't contain a weight");
-      }
-      if (root == null) {
-        throw new ModelException("LambdaMARTModel tree doesn't contain a tree");
-      } else {
-        root.validate();
-      }
-    }
-  }
-
-  public void setTrees(Object trees) {
-    this.trees = new ArrayList<RegressionTree>();
-    for (final Object o : (List<Object>) trees) {
-      final RegressionTree rt = createRegressionTree((Map<String,Object>) o);
-      this.trees.add(rt);
-    }
-  }
-
-  public LambdaMARTModel(String name, List<Feature> features,
-      List<Normalizer> norms,
-      String featureStoreName, List<Feature> allFeatures,
-      Map<String,Object> params) {
-    super(name, features, norms, featureStoreName, allFeatures, params);
-
-    fname2index = new HashMap<String,Integer>();
-    for (int i = 0; i < features.size(); ++i) {
-      final String key = features.get(i).getName();
-      fname2index.put(key, i);
-    }
-  }
-
-  @Override
-  public void validate() throws ModelException {
-    super.validate();
-    if (trees == null) {
-      throw new ModelException("no trees declared for model "+name);
-    }
-    for (RegressionTree tree : trees) {
-      tree.validate();
-    }
-  }
-
- @Override
-  public float score(float[] modelFeatureValuesNormalized) {
-    float score = 0;
-    for (final RegressionTree t : trees) {
-      score += t.score(modelFeatureValuesNormalized);
-    }
-    return score;
-  }
-
-  // /////////////////////////////////////////
-  // produces a string that looks like:
-  // 40.0 = lambdamartmodel [ org.apache.solr.ltr.model.LambdaMARTModel ]
-  // model applied to
-  // features, sum of:
-  // 50.0 = tree 0 | 'matchedTitle':1.0 > 0.500001, Go Right |
-  // 'this_feature_doesnt_exist' does not
-  // exist in FV, Go Left | val: 50.0
-  // -10.0 = tree 1 | val: -10.0
-  @Override
-  public Explanation explain(LeafReaderContext context, int doc,
-      float finalScore, List<Explanation> featureExplanations) {
-    final float[] fv = new float[featureExplanations.size()];
-    int index = 0;
-    for (final Explanation featureExplain : featureExplanations) {
-      fv[index] = featureExplain.getValue();
-      index++;
-    }
-
-    final List<Explanation> details = new ArrayList<>();
-    index = 0;
-
-    for (final RegressionTree t : trees) {
-      final float score = t.score(fv);
-      final Explanation p = Explanation.match(score, "tree " + index + " | "
-          + t.explain(fv));
-      details.add(p);
-      index++;
-    }
-
-    return Explanation.match(finalScore, toString()
-        + " model applied to features, sum of:", details);
-  }
-
-  @Override
-  public String toString() {
-    final StringBuilder sb = new StringBuilder(getClass().getSimpleName());
-    sb.append("(name=").append(getName());
-    sb.append(",trees=[");
-    for (int ii = 0; ii < trees.size(); ++ii) {
-      if (ii>0) sb.append(',');
-      sb.append(trees.get(ii));
-    }
-    sb.append("])");
-    return sb.toString();
-  }
-
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/java/org/apache/solr/ltr/model/LinearModel.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/java/org/apache/solr/ltr/model/LinearModel.java b/solr/contrib/ltr/src/java/org/apache/solr/ltr/model/LinearModel.java
new file mode 100644
index 0000000..0e8d190
--- /dev/null
+++ b/solr/contrib/ltr/src/java/org/apache/solr/ltr/model/LinearModel.java
@@ -0,0 +1,145 @@
+/*
+ * 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.List;
+import java.util.Map;
+
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.search.Explanation;
+import org.apache.solr.ltr.feature.Feature;
+import org.apache.solr.ltr.norm.Normalizer;
+
+/**
+ * A scoring model that computes scores using a dot product. 
+ * Example models are RankSVM and Pranking.
+ * <p>
+ * Example configuration:
+ * <pre>{
+   "class" : "org.apache.solr.ltr.model.LinearModel",
+   "name" : "myModelName",
+   "features" : [
+       { "name" : "userTextTitleMatch" },
+       { "name" : "originalScore" },
+       { "name" : "isBook" }
+   ],
+   "params" : {
+       "weights" : {
+           "userTextTitleMatch" : 1.0,
+           "originalScore" : 0.5,
+           "isBook" : 0.1
+       }
+   }
+}</pre>
+ * <p>
+ * Background reading:
+ * <ul>
+ * <li> <a href="http://www.cs.cornell.edu/people/tj/publications/joachims_02c.pdf">
+ * Thorsten Joachims. Optimizing Search Engines Using Clickthrough Data.
+ * Proceedings of the ACM Conference on Knowledge Discovery and Data Mining (KDD), ACM, 2002.</a>
+ * </ul>
+ * <ul>
+ * <li> <a href="https://papers.nips.cc/paper/2023-pranking-with-ranking.pdf">
+ * Koby Crammer and Yoram Singer. Pranking with Ranking.
+ * Advances in Neural Information Processing Systems (NIPS), 2001.</a>
+ * </ul>
+ */
+public class LinearModel extends LTRScoringModel {
+
+  protected Float[] featureToWeight;
+
+  public void setWeights(Object weights) {
+    final Map<String,Double> modelWeights = (Map<String,Double>) weights;
+    for (int ii = 0; ii < features.size(); ++ii) {
+      final String key = features.get(ii).getName();
+      final Double val = modelWeights.get(key);
+      featureToWeight[ii] = (val == null ? null : new Float(val.floatValue()));
+    }
+  }
+
+  public LinearModel(String name, List<Feature> features,
+      List<Normalizer> norms,
+      String featureStoreName, List<Feature> allFeatures,
+      Map<String,Object> params) {
+    super(name, features, norms, featureStoreName, allFeatures, params);
+    featureToWeight = new Float[features.size()];
+  }
+
+  @Override
+  public void validate() throws ModelException {
+    super.validate();
+
+    final ArrayList<String> missingWeightFeatureNames = new ArrayList<String>();
+    for (int i = 0; i < features.size(); ++i) {
+      if (featureToWeight[i] == null) {
+        missingWeightFeatureNames.add(features.get(i).getName());
+      }
+    }
+    if (missingWeightFeatureNames.size() == features.size()) {
+      throw new ModelException("Model " + name + " doesn't contain any weights");
+    }
+    if (!missingWeightFeatureNames.isEmpty()) {
+      throw new ModelException("Model " + name + " lacks weight(s) for "+missingWeightFeatureNames);
+    }
+  }
+
+  @Override
+  public float score(float[] modelFeatureValuesNormalized) {
+    float score = 0;
+    for (int i = 0; i < modelFeatureValuesNormalized.length; ++i) {
+      score += modelFeatureValuesNormalized[i] * featureToWeight[i];
+    }
+    return score;
+  }
+
+  @Override
+  public Explanation explain(LeafReaderContext context, int doc,
+      float finalScore, List<Explanation> featureExplanations) {
+    final List<Explanation> details = new ArrayList<>();
+    int index = 0;
+
+    for (final Explanation featureExplain : featureExplanations) {
+      final List<Explanation> featureDetails = new ArrayList<>();
+      featureDetails.add(Explanation.match(featureToWeight[index],
+          "weight on feature"));
+      featureDetails.add(featureExplain);
+
+      details.add(Explanation.match(featureExplain.getValue()
+          * featureToWeight[index], "prod of:", featureDetails));
+      index++;
+    }
+
+    return Explanation.match(finalScore, toString()
+        + " model applied to features, sum of:", details);
+  }
+
+  @Override
+  public String toString() {
+    final StringBuilder sb = new StringBuilder(getClass().getSimpleName());
+    sb.append("(name=").append(getName());
+    sb.append(",featureWeights=[");
+    for (int ii = 0; ii < features.size(); ++ii) {
+      if (ii>0) sb.append(',');
+      final String key = features.get(ii).getName();
+      sb.append(key).append('=').append(featureToWeight[ii]);
+    }
+    sb.append("])");
+    return sb.toString();
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/java/org/apache/solr/ltr/model/MultipleAdditiveTreesModel.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/java/org/apache/solr/ltr/model/MultipleAdditiveTreesModel.java b/solr/contrib/ltr/src/java/org/apache/solr/ltr/model/MultipleAdditiveTreesModel.java
new file mode 100644
index 0000000..0046b46
--- /dev/null
+++ b/solr/contrib/ltr/src/java/org/apache/solr/ltr/model/MultipleAdditiveTreesModel.java
@@ -0,0 +1,375 @@
+/*
+ * 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.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.lucene.index.LeafReaderContext;
+import org.apache.lucene.search.Explanation;
+import org.apache.solr.ltr.feature.Feature;
+import org.apache.solr.ltr.norm.Normalizer;
+import org.apache.solr.util.SolrPluginUtils;
+
+/**
+ * A scoring model that computes scores based on the summation of multiple weighted trees.
+ * Example models are LambdaMART and Gradient Boosted Regression Trees (GBRT) .
+ * <p>
+ * Example configuration:
+<pre>{
+   "class" : "org.apache.solr.ltr.model.MultipleAdditiveTreesModel",
+   "name" : "multipleadditivetreesmodel",
+   "features":[
+       { "name" : "userTextTitleMatch"},
+       { "name" : "originalScore"}
+   ],
+   "params" : {
+       "trees" : [
+           {
+               "weight" : 1,
+               "root": {
+                   "feature" : "userTextTitleMatch",
+                   "threshold" : 0.5,
+                   "left" : {
+                       "value" : -100
+                   },
+                   "right" : {
+                       "feature" : "originalScore",
+                       "threshold" : 10.0,
+                       "left" : {
+                           "value" : 50
+                       },
+                       "right" : {
+                           "value" : 75
+                       }
+                   }
+               }
+           },
+           {
+               "weight" : 2,
+               "root" : {
+                   "value" : -10
+               }
+           }
+       ]
+   }
+}</pre>
+ * <p>
+ * Background reading:
+ * <ul>
+ * <li> <a href="http://research.microsoft.com/pubs/132652/MSR-TR-2010-82.pdf">
+ * Christopher J.C. Burges. From RankNet to LambdaRank to LambdaMART: An Overview.
+ * Microsoft Research Technical Report MSR-TR-2010-82.</a>
+ * </ul>
+ * <ul>
+ * <li> <a href="https://papers.nips.cc/paper/3305-a-general-boosting-method-and-its-application-to-learning-ranking-functions-for-web-search.pdf">
+ * Z. Zheng, H. Zha, T. Zhang, O. Chapelle, K. Chen, and G. Sun. A General Boosting Method and its Application to Learning Ranking Functions for Web Search.
+ * Advances in Neural Information Processing Systems (NIPS), 2007.</a>
+ * </ul>
+ */
+public class MultipleAdditiveTreesModel extends LTRScoringModel {
+
+  private final HashMap<String,Integer> fname2index;
+  private List<RegressionTree> trees;
+
+  private RegressionTree createRegressionTree(Map<String,Object> map) {
+    final RegressionTree rt = new RegressionTree();
+    if (map != null) {
+      SolrPluginUtils.invokeSetters(rt, map.entrySet());
+    }
+    return rt;
+  }
+
+  private RegressionTreeNode createRegressionTreeNode(Map<String,Object> map) {
+    final RegressionTreeNode rtn = new RegressionTreeNode();
+    if (map != null) {
+      SolrPluginUtils.invokeSetters(rtn, map.entrySet());
+    }
+    return rtn;
+  }
+
+  public class RegressionTreeNode {
+    private static final float NODE_SPLIT_SLACK = 1E-6f;
+
+    private float value = 0f;
+    private String feature;
+    private int featureIndex = -1;
+    private Float threshold;
+    private RegressionTreeNode left;
+    private RegressionTreeNode right;
+
+    public void setValue(float value) {
+      this.value = value;
+    }
+
+    public void setValue(String value) {
+      this.value = Float.parseFloat(value);
+    }
+
+    public void setFeature(String feature) {
+      this.feature = feature;
+      final Integer idx = fname2index.get(this.feature);
+      // this happens if the tree specifies a feature that does not exist
+      // this could be due to lambdaSmart building off of pre-existing trees
+      // that use a feature that is no longer output during feature extraction
+      featureIndex = (idx == null) ? -1 : idx;
+    }
+
+    public void setThreshold(float threshold) {
+      this.threshold = threshold + NODE_SPLIT_SLACK;
+    }
+
+    public void setThreshold(String threshold) {
+      this.threshold = Float.parseFloat(threshold) + NODE_SPLIT_SLACK;
+    }
+
+    public void setLeft(Object left) {
+      this.left = createRegressionTreeNode((Map<String,Object>) left);
+    }
+
+    public void setRight(Object right) {
+      this.right = createRegressionTreeNode((Map<String,Object>) right);
+    }
+
+    public boolean isLeaf() {
+      return feature == null;
+    }
+
+    public float score(float[] featureVector) {
+      if (isLeaf()) {
+        return value;
+      }
+
+      // unsupported feature (tree is looking for a feature that does not exist)
+      if  ((featureIndex < 0) || (featureIndex >= featureVector.length)) {
+        return 0f;
+      }
+
+      if (featureVector[featureIndex] <= threshold) {
+        return left.score(featureVector);
+      } else {
+        return right.score(featureVector);
+      }
+    }
+
+    public String explain(float[] featureVector) {
+      if (isLeaf()) {
+        return "val: " + value;
+      }
+
+      // unsupported feature (tree is looking for a feature that does not exist)
+      if  ((featureIndex < 0) || (featureIndex >= featureVector.length)) {
+        return  "'" + feature + "' does not exist in FV, Return Zero";
+      }
+
+      // could store extra information about how much training data supported
+      // each branch and report
+      // that here
+
+      if (featureVector[featureIndex] <= threshold) {
+        String rval = "'" + feature + "':" + featureVector[featureIndex] + " <= "
+            + threshold + ", Go Left | ";
+        return rval + left.explain(featureVector);
+      } else {
+        String rval = "'" + feature + "':" + featureVector[featureIndex] + " > "
+            + threshold + ", Go Right | ";
+        return rval + right.explain(featureVector);
+      }
+    }
+
+    @Override
+    public String toString() {
+      final StringBuilder sb = new StringBuilder();
+      if (isLeaf()) {
+        sb.append(value);
+      } else {
+        sb.append("(feature=").append(feature);
+        sb.append(",threshold=").append(threshold.floatValue()-NODE_SPLIT_SLACK);
+        sb.append(",left=").append(left);
+        sb.append(",right=").append(right);
+        sb.append(')');
+      }
+      return sb.toString();
+    }
+
+    public RegressionTreeNode() {
+    }
+
+    public void validate() throws ModelException {
+      if (isLeaf()) {
+        if (left != null || right != null) {
+          throw new ModelException("MultipleAdditiveTreesModel tree node is leaf with left="+left+" and right="+right);
+        }
+        return;
+      }
+      if (null == threshold) {
+        throw new ModelException("MultipleAdditiveTreesModel tree node is missing threshold");
+      }
+      if (null == left) {
+        throw new ModelException("MultipleAdditiveTreesModel tree node is missing left");
+      } else {
+        left.validate();
+      }
+      if (null == right) {
+        throw new ModelException("MultipleAdditiveTreesModel tree node is missing right");
+      } else {
+        right.validate();
+      }
+    }
+
+  }
+
+  public class RegressionTree {
+
+    private Float weight;
+    private RegressionTreeNode root;
+
+    public void setWeight(float weight) {
+      this.weight = new Float(weight);
+    }
+
+    public void setWeight(String weight) {
+      this.weight = new Float(weight);
+    }
+
+    public void setRoot(Object root) {
+      this.root = createRegressionTreeNode((Map<String,Object>)root);
+    }
+
+    public float score(float[] featureVector) {
+      return weight.floatValue() * root.score(featureVector);
+    }
+
+    public String explain(float[] featureVector) {
+      return root.explain(featureVector);
+    }
+
+    @Override
+    public String toString() {
+      final StringBuilder sb = new StringBuilder();
+      sb.append("(weight=").append(weight);
+      sb.append(",root=").append(root);
+      sb.append(")");
+      return sb.toString();
+    }
+
+    public RegressionTree() {
+    }
+
+    public void validate() throws ModelException {
+      if (weight == null) {
+        throw new ModelException("MultipleAdditiveTreesModel tree doesn't contain a weight");
+      }
+      if (root == null) {
+        throw new ModelException("MultipleAdditiveTreesModel tree doesn't contain a tree");
+      } else {
+        root.validate();
+      }
+    }
+  }
+
+  public void setTrees(Object trees) {
+    this.trees = new ArrayList<RegressionTree>();
+    for (final Object o : (List<Object>) trees) {
+      final RegressionTree rt = createRegressionTree((Map<String,Object>) o);
+      this.trees.add(rt);
+    }
+  }
+
+  public MultipleAdditiveTreesModel(String name, List<Feature> features,
+      List<Normalizer> norms,
+      String featureStoreName, List<Feature> allFeatures,
+      Map<String,Object> params) {
+    super(name, features, norms, featureStoreName, allFeatures, params);
+
+    fname2index = new HashMap<String,Integer>();
+    for (int i = 0; i < features.size(); ++i) {
+      final String key = features.get(i).getName();
+      fname2index.put(key, i);
+    }
+  }
+
+  @Override
+  public void validate() throws ModelException {
+    super.validate();
+    if (trees == null) {
+      throw new ModelException("no trees declared for model "+name);
+    }
+    for (RegressionTree tree : trees) {
+      tree.validate();
+    }
+  }
+
+ @Override
+  public float score(float[] modelFeatureValuesNormalized) {
+    float score = 0;
+    for (final RegressionTree t : trees) {
+      score += t.score(modelFeatureValuesNormalized);
+    }
+    return score;
+  }
+
+  // /////////////////////////////////////////
+  // produces a string that looks like:
+  // 40.0 = multipleadditivetreesmodel [ org.apache.solr.ltr.model.MultipleAdditiveTreesModel ]
+  // model applied to
+  // features, sum of:
+  // 50.0 = tree 0 | 'matchedTitle':1.0 > 0.500001, Go Right |
+  // 'this_feature_doesnt_exist' does not
+  // exist in FV, Go Left | val: 50.0
+  // -10.0 = tree 1 | val: -10.0
+  @Override
+  public Explanation explain(LeafReaderContext context, int doc,
+      float finalScore, List<Explanation> featureExplanations) {
+    final float[] fv = new float[featureExplanations.size()];
+    int index = 0;
+    for (final Explanation featureExplain : featureExplanations) {
+      fv[index] = featureExplain.getValue();
+      index++;
+    }
+
+    final List<Explanation> details = new ArrayList<>();
+    index = 0;
+
+    for (final RegressionTree t : trees) {
+      final float score = t.score(fv);
+      final Explanation p = Explanation.match(score, "tree " + index + " | "
+          + t.explain(fv));
+      details.add(p);
+      index++;
+    }
+
+    return Explanation.match(finalScore, toString()
+        + " model applied to features, sum of:", details);
+  }
+
+  @Override
+  public String toString() {
+    final StringBuilder sb = new StringBuilder(getClass().getSimpleName());
+    sb.append("(name=").append(getName());
+    sb.append(",trees=[");
+    for (int ii = 0; ii < trees.size(); ++ii) {
+      if (ii>0) sb.append(',');
+      sb.append(trees.get(ii));
+    }
+    sb.append("])");
+    return sb.toString();
+  }
+
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/java/org/apache/solr/ltr/model/RankSVMModel.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/java/org/apache/solr/ltr/model/RankSVMModel.java b/solr/contrib/ltr/src/java/org/apache/solr/ltr/model/RankSVMModel.java
deleted file mode 100644
index 64e1216..0000000
--- a/solr/contrib/ltr/src/java/org/apache/solr/ltr/model/RankSVMModel.java
+++ /dev/null
@@ -1,139 +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 java.util.ArrayList;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.lucene.index.LeafReaderContext;
-import org.apache.lucene.search.Explanation;
-import org.apache.solr.ltr.feature.Feature;
-import org.apache.solr.ltr.norm.Normalizer;
-
-/**
- * A scoring model that computes scores using a linear Support Vector Machine (SVM) algorithm.
- * <p>
- * Example configuration:
- * <pre>{
-   "class" : "org.apache.solr.ltr.model.RankSVMModel",
-   "name" : "myModelName",
-   "features" : [
-       { "name" : "userTextTitleMatch" },
-       { "name" : "originalScore" },
-       { "name" : "isBook" }
-   ],
-   "params" : {
-       "weights" : {
-           "userTextTitleMatch" : 1.0,
-           "originalScore" : 0.5,
-           "isBook" : 0.1
-       }
-   }
-}</pre>
- * <p>
- * Background reading:
- * <ul>
- * <li> <a href="http://www.cs.cornell.edu/people/tj/publications/joachims_02c.pdf">
- * Thorsten Joachims. Optimizing Search Engines Using Clickthrough Data.
- * Proceedings of the ACM Conference on Knowledge Discovery and Data Mining (KDD), ACM, 2002.</a>
- * </ul>
- */
-public class RankSVMModel extends LTRScoringModel {
-
-  protected Float[] featureToWeight;
-
-  public void setWeights(Object weights) {
-    final Map<String,Double> modelWeights = (Map<String,Double>) weights;
-    for (int ii = 0; ii < features.size(); ++ii) {
-      final String key = features.get(ii).getName();
-      final Double val = modelWeights.get(key);
-      featureToWeight[ii] = (val == null ? null : new Float(val.floatValue()));
-    }
-  }
-
-  public RankSVMModel(String name, List<Feature> features,
-      List<Normalizer> norms,
-      String featureStoreName, List<Feature> allFeatures,
-      Map<String,Object> params) {
-    super(name, features, norms, featureStoreName, allFeatures, params);
-    featureToWeight = new Float[features.size()];
-  }
-
-  @Override
-  public void validate() throws ModelException {
-    super.validate();
-
-    final ArrayList<String> missingWeightFeatureNames = new ArrayList<String>();
-    for (int i = 0; i < features.size(); ++i) {
-      if (featureToWeight[i] == null) {
-        missingWeightFeatureNames.add(features.get(i).getName());
-      }
-    }
-    if (missingWeightFeatureNames.size() == features.size()) {
-      throw new ModelException("Model " + name + " doesn't contain any weights");
-    }
-    if (!missingWeightFeatureNames.isEmpty()) {
-      throw new ModelException("Model " + name + " lacks weight(s) for "+missingWeightFeatureNames);
-    }
-  }
-
-  @Override
-  public float score(float[] modelFeatureValuesNormalized) {
-    float score = 0;
-    for (int i = 0; i < modelFeatureValuesNormalized.length; ++i) {
-      score += modelFeatureValuesNormalized[i] * featureToWeight[i];
-    }
-    return score;
-  }
-
-  @Override
-  public Explanation explain(LeafReaderContext context, int doc,
-      float finalScore, List<Explanation> featureExplanations) {
-    final List<Explanation> details = new ArrayList<>();
-    int index = 0;
-
-    for (final Explanation featureExplain : featureExplanations) {
-      final List<Explanation> featureDetails = new ArrayList<>();
-      featureDetails.add(Explanation.match(featureToWeight[index],
-          "weight on feature"));
-      featureDetails.add(featureExplain);
-
-      details.add(Explanation.match(featureExplain.getValue()
-          * featureToWeight[index], "prod of:", featureDetails));
-      index++;
-    }
-
-    return Explanation.match(finalScore, toString()
-        + " model applied to features, sum of:", details);
-  }
-
-  @Override
-  public String toString() {
-    final StringBuilder sb = new StringBuilder(getClass().getSimpleName());
-    sb.append("(name=").append(getName());
-    sb.append(",featureWeights=[");
-    for (int ii = 0; ii < features.size(); ++ii) {
-      if (ii>0) sb.append(',');
-      final String key = features.get(ii).getName();
-      sb.append(key).append('=').append(featureToWeight[ii]);
-    }
-    sb.append("])");
-    return sb.toString();
-  }
-
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/java/org/apache/solr/ltr/package-info.java
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/java/org/apache/solr/ltr/package-info.java b/solr/contrib/ltr/src/java/org/apache/solr/ltr/package-info.java
index 678ffd4..59aebe8 100644
--- a/solr/contrib/ltr/src/java/org/apache/solr/ltr/package-info.java
+++ b/solr/contrib/ltr/src/java/org/apache/solr/ltr/package-info.java
@@ -33,7 +33,7 @@
  * defines how to combine the features in order to create a new
  * score for a document. A new Learning to Rank model is plugged
  * into the framework  by extending {@link org.apache.solr.ltr.model.LTRScoringModel},
- * (see for example {@link org.apache.solr.ltr.model.LambdaMARTModel} and {@link org.apache.solr.ltr.model.RankSVMModel}).
+ * (see for example {@link org.apache.solr.ltr.model.MultipleAdditiveTreesModel} and {@link org.apache.solr.ltr.model.LinearModel}).
  * </p>
  * <p>
  * The {@link org.apache.solr.ltr.LTRScoringQuery} will take care of computing the values of

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/java/overview.html
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/java/overview.html b/solr/contrib/ltr/src/java/overview.html
index d27aa9e..7b8198c 100644
--- a/solr/contrib/ltr/src/java/overview.html
+++ b/solr/contrib/ltr/src/java/overview.html
@@ -57,7 +57,7 @@ A {@link org.apache.solr.ltr.LTRScoringQuery} is created by providing an instanc
 defines how to combine the features in order to create a new
 score for a document. A new learning to rank model is plugged
 into the framework  by extending {@link org.apache.solr.ltr.model.LTRScoringModel},
-(see for example {@link org.apache.solr.ltr.model.LambdaMARTModel} and {@link org.apache.solr.ltr.model.RankSVMModel}).
+(see for example {@link org.apache.solr.ltr.model.MultipleAdditiveTreesModel} and {@link org.apache.solr.ltr.model.LinearModel}).
 </p>
 <p>
 The {@link org.apache.solr.ltr.LTRScoringQuery} will take care of computing the values of

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test-files/featureExamples/features-linear-efi.json
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test-files/featureExamples/features-linear-efi.json b/solr/contrib/ltr/src/test-files/featureExamples/features-linear-efi.json
new file mode 100644
index 0000000..e05542a
--- /dev/null
+++ b/solr/contrib/ltr/src/test-files/featureExamples/features-linear-efi.json
@@ -0,0 +1,17 @@
+[
+  {
+    "name": "sampleConstant",
+    "class": "org.apache.solr.ltr.feature.ValueFeature",
+    "params": {
+      "value": 5
+    }
+  },
+  {
+    "name" : "search_number_of_nights",
+    "class":"org.apache.solr.ltr.feature.ValueFeature",
+    "params" : {
+      "value": "${search_number_of_nights}"
+    }
+  }
+
+]

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test-files/featureExamples/features-linear.json
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test-files/featureExamples/features-linear.json b/solr/contrib/ltr/src/test-files/featureExamples/features-linear.json
new file mode 100644
index 0000000..8cc2996
--- /dev/null
+++ b/solr/contrib/ltr/src/test-files/featureExamples/features-linear.json
@@ -0,0 +1,51 @@
+[
+  {
+     "name": "title",
+     "class": "org.apache.solr.ltr.feature.ValueFeature",
+     "params": {
+         "value": 1
+     }
+  },
+  {
+    "name": "description",
+    "class": "org.apache.solr.ltr.feature.ValueFeature",
+    "params": {
+       "value": 2
+    }
+  },
+  {
+    "name": "keywords",
+    "class": "org.apache.solr.ltr.feature.ValueFeature",
+    "params": {
+        "value": 2
+    }
+  },
+ {
+     "name": "popularity",
+     "class": "org.apache.solr.ltr.feature.ValueFeature",
+     "params": {
+         "value": 3
+     }
+ },
+ {
+     "name": "text",
+     "class": "org.apache.solr.ltr.feature.ValueFeature",
+     "params": {
+         "value": 4
+     }
+ },
+ {
+   "name": "queryIntentPerson",
+   "class": "org.apache.solr.ltr.feature.ValueFeature",
+   "params": {
+       "value": 5
+   }
+ },
+ {
+   "name": "queryIntentCompany",
+   "class": "org.apache.solr.ltr.feature.ValueFeature",
+   "params": {
+       "value": 5
+   }
+ }
+]

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test-files/featureExamples/features-ranksvm-efi.json
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test-files/featureExamples/features-ranksvm-efi.json b/solr/contrib/ltr/src/test-files/featureExamples/features-ranksvm-efi.json
deleted file mode 100644
index e05542a..0000000
--- a/solr/contrib/ltr/src/test-files/featureExamples/features-ranksvm-efi.json
+++ /dev/null
@@ -1,17 +0,0 @@
-[
-  {
-    "name": "sampleConstant",
-    "class": "org.apache.solr.ltr.feature.ValueFeature",
-    "params": {
-      "value": 5
-    }
-  },
-  {
-    "name" : "search_number_of_nights",
-    "class":"org.apache.solr.ltr.feature.ValueFeature",
-    "params" : {
-      "value": "${search_number_of_nights}"
-    }
-  }
-
-]

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test-files/featureExamples/features-ranksvm.json
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test-files/featureExamples/features-ranksvm.json b/solr/contrib/ltr/src/test-files/featureExamples/features-ranksvm.json
deleted file mode 100644
index 8cc2996..0000000
--- a/solr/contrib/ltr/src/test-files/featureExamples/features-ranksvm.json
+++ /dev/null
@@ -1,51 +0,0 @@
-[
-  {
-     "name": "title",
-     "class": "org.apache.solr.ltr.feature.ValueFeature",
-     "params": {
-         "value": 1
-     }
-  },
-  {
-    "name": "description",
-    "class": "org.apache.solr.ltr.feature.ValueFeature",
-    "params": {
-       "value": 2
-    }
-  },
-  {
-    "name": "keywords",
-    "class": "org.apache.solr.ltr.feature.ValueFeature",
-    "params": {
-        "value": 2
-    }
-  },
- {
-     "name": "popularity",
-     "class": "org.apache.solr.ltr.feature.ValueFeature",
-     "params": {
-         "value": 3
-     }
- },
- {
-     "name": "text",
-     "class": "org.apache.solr.ltr.feature.ValueFeature",
-     "params": {
-         "value": 4
-     }
- },
- {
-   "name": "queryIntentPerson",
-   "class": "org.apache.solr.ltr.feature.ValueFeature",
-   "params": {
-       "value": 5
-   }
- },
- {
-   "name": "queryIntentCompany",
-   "class": "org.apache.solr.ltr.feature.ValueFeature",
-   "params": {
-       "value": 5
-   }
- }
-]

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test-files/featureExamples/lambdamart_features.json
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test-files/featureExamples/lambdamart_features.json b/solr/contrib/ltr/src/test-files/featureExamples/lambdamart_features.json
deleted file mode 100644
index 3bc2c77..0000000
--- a/solr/contrib/ltr/src/test-files/featureExamples/lambdamart_features.json
+++ /dev/null
@@ -1,16 +0,0 @@
-[
-    {
-        "name": "matchedTitle",
-        "class": "org.apache.solr.ltr.feature.SolrFeature",
-        "params": {
-            "q": "{!terms f=title}${user_query}"
-        }
-    },
-    {
-        "name": "constantScoreToForceLambdaMARTScoreAllDocs",
-        "class": "org.apache.solr.ltr.feature.ValueFeature",
-        "params": {
-            "value": 1
-        }
-    }
-]

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test-files/featureExamples/multipleadditivetreesmodel_features.json
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test-files/featureExamples/multipleadditivetreesmodel_features.json b/solr/contrib/ltr/src/test-files/featureExamples/multipleadditivetreesmodel_features.json
new file mode 100644
index 0000000..92f3861
--- /dev/null
+++ b/solr/contrib/ltr/src/test-files/featureExamples/multipleadditivetreesmodel_features.json
@@ -0,0 +1,16 @@
+[
+    {
+        "name": "matchedTitle",
+        "class": "org.apache.solr.ltr.feature.SolrFeature",
+        "params": {
+            "q": "{!terms f=title}${user_query}"
+        }
+    },
+    {
+        "name": "constantScoreToForceMultipleAdditiveTreesScoreAllDocs",
+        "class": "org.apache.solr.ltr.feature.ValueFeature",
+        "params": {
+            "value": 1
+        }
+    }
+]

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test-files/modelExamples/external_model.json
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test-files/modelExamples/external_model.json b/solr/contrib/ltr/src/test-files/modelExamples/external_model.json
index c7f35c0..04ab229 100644
--- a/solr/contrib/ltr/src/test-files/modelExamples/external_model.json
+++ b/solr/contrib/ltr/src/test-files/modelExamples/external_model.json
@@ -1,5 +1,5 @@
 {
-    "class":"org.apache.solr.ltr.model.RankSVMModel",
+    "class":"org.apache.solr.ltr.model.LinearModel",
     "name":"externalmodel",
     "features":[
         { "name": "matchedTitle"}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test-files/modelExamples/external_model_store.json
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test-files/modelExamples/external_model_store.json b/solr/contrib/ltr/src/test-files/modelExamples/external_model_store.json
index 227a943..f8e6648 100644
--- a/solr/contrib/ltr/src/test-files/modelExamples/external_model_store.json
+++ b/solr/contrib/ltr/src/test-files/modelExamples/external_model_store.json
@@ -1,5 +1,5 @@
 {
-    "class":"org.apache.solr.ltr.model.RankSVMModel",
+    "class":"org.apache.solr.ltr.model.LinearModel",
     "name":"externalmodelstore",
     "store": "fstore2",
     "features":[

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test-files/modelExamples/fq-model.json
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test-files/modelExamples/fq-model.json b/solr/contrib/ltr/src/test-files/modelExamples/fq-model.json
index c0fa77a..b5d631f 100644
--- a/solr/contrib/ltr/src/test-files/modelExamples/fq-model.json
+++ b/solr/contrib/ltr/src/test-files/modelExamples/fq-model.json
@@ -1,5 +1,5 @@
 {
-	"class":"org.apache.solr.ltr.model.RankSVMModel",
+	"class":"org.apache.solr.ltr.model.LinearModel",
 	"name":"fqmodel",
 	"features":[
         {

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel.json
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel.json b/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel.json
deleted file mode 100644
index 782e641..0000000
--- a/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel.json
+++ /dev/null
@@ -1,38 +0,0 @@
-{
-    "class":"org.apache.solr.ltr.model.LambdaMARTModel",
-    "name":"lambdamartmodel",
-    "features":[
-        { "name": "matchedTitle"},
-        { "name": "constantScoreToForceLambdaMARTScoreAllDocs"}
-    ],
-    "params":{
-        "trees": [
-            {
-                "weight" : "1f",
-                "root": {
-                    "feature": "matchedTitle",
-                    "threshold": "0.5f",
-                    "left" : {
-                        "value" : "-100"
-                    },
-                    "right": {
-                        "feature" : "this_feature_doesnt_exist",
-                        "threshold": "10.0f",
-                        "left" : {
-                            "value" : "50"
-                        },
-                        "right" : {
-                            "value" : "75"
-                        }
-                    }
-                }
-            },
-            {
-                "weight" : "2f",
-                "root": {
-                    "value" : "-10"
-                }
-            }
-        ]
-    }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_external_binary_features.json
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_external_binary_features.json b/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_external_binary_features.json
deleted file mode 100644
index ce884de..0000000
--- a/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_external_binary_features.json
+++ /dev/null
@@ -1,38 +0,0 @@
-{
-  "class":"org.apache.solr.ltr.model.LambdaMARTModel",
-  "name":"external_model_binary_feature",
-  "features":[
-    { "name": "user_device_smartphone"},
-    { "name": "user_device_tablet"}
-  ],
-  "params":{
-    "trees": [
-      {
-        "weight" : "1f",
-        "root": {
-          "feature": "user_device_smartphone",
-          "threshold": "0.5f",
-          "left" : {
-            "value" : "0"
-          },
-          "right" : {
-            "value" : "50"
-          }
-
-        }},
-      {
-        "weight" : "1f",
-        "root": {
-          "feature": "user_device_tablet",
-          "threshold": "0.5f",
-          "left" : {
-            "value" : "0"
-          },
-          "right" : {
-            "value" : "65"
-          }
-
-        }}
-    ]
-  }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_no_feature.json
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_no_feature.json b/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_no_feature.json
deleted file mode 100644
index 96e304f..0000000
--- a/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_no_feature.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
-    "class":"org.apache.solr.ltr.model.LambdaMARTModel",
-    "name":"lambdamartmodel_no_feature",
-    "features":[
-        { "name": "matchedTitle"},
-        { "name": "constantScoreToForceLambdaMARTScoreAllDocs"}
-    ],
-    "params":{
-        "trees": [
-            {
-                "weight" : "1f",
-                "root": {
-                    "threshold": "0.5f",
-                    "left" : {
-                        "value" : "-100"
-                    },
-                    "right": {
-                        "value" : "75"
-                    }
-                }
-            }
-        ]
-    }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_no_features.json
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_no_features.json b/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_no_features.json
deleted file mode 100644
index e534696..0000000
--- a/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_no_features.json
+++ /dev/null
@@ -1,14 +0,0 @@
-{
-    "class":"org.apache.solr.ltr.model.LambdaMARTModel",
-    "name":"lambdamartmodel_no_features",
-    "params":{
-        "trees": [
-            {
-                "weight" : "2f",
-                "root": {
-                    "value" : "-10"
-                }
-            }
-        ]
-    }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_no_left.json
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_no_left.json b/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_no_left.json
deleted file mode 100644
index 5564bc3..0000000
--- a/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_no_left.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
-    "class":"org.apache.solr.ltr.model.LambdaMARTModel",
-    "name":"lambdamartmodel_no_left",
-    "features":[
-        { "name": "matchedTitle"},
-        { "name": "constantScoreToForceLambdaMARTScoreAllDocs"}
-    ],
-    "params":{
-        "trees": [
-            {
-                "weight" : "1f",
-                "root": {
-                    "feature": "matchedTitle",
-                    "threshold": "0.5f",
-                    "right": {
-                        "value" : "75"
-                    }
-                }
-            }
-        ]
-    }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_no_params.json
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_no_params.json b/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_no_params.json
deleted file mode 100644
index e48489d..0000000
--- a/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_no_params.json
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-    "class":"org.apache.solr.ltr.model.LambdaMARTModel",
-    "name":"lambdamartmodel_no_params",
-    "features":[
-        { "name": "matchedTitle"},
-        { "name": "constantScoreToForceLambdaMARTScoreAllDocs"}
-    ]
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_no_right.json
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_no_right.json b/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_no_right.json
deleted file mode 100644
index 672716d..0000000
--- a/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_no_right.json
+++ /dev/null
@@ -1,22 +0,0 @@
-{
-    "class":"org.apache.solr.ltr.model.LambdaMARTModel",
-    "name":"lambdamartmodel_no_right",
-    "features":[
-        { "name": "matchedTitle"},
-        { "name": "constantScoreToForceLambdaMARTScoreAllDocs"}
-    ],
-    "params":{
-        "trees": [
-            {
-                "weight" : "1f",
-                "root": {
-                    "feature": "matchedTitle",
-                    "threshold": "0.5f",
-                    "left" : {
-                        "value" : "-100"
-                    }
-                }
-            }
-        ]
-    }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_no_threshold.json
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_no_threshold.json b/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_no_threshold.json
deleted file mode 100644
index 2c6922f..0000000
--- a/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_no_threshold.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
-    "class":"org.apache.solr.ltr.model.LambdaMARTModel",
-    "name":"lambdamartmodel_no_threshold",
-    "features":[
-        { "name": "matchedTitle"},
-        { "name": "constantScoreToForceLambdaMARTScoreAllDocs"}
-    ],
-    "params":{
-        "trees": [
-            {
-                "weight" : "1f",
-                "root": {
-                    "feature": "matchedTitle",
-                    "left" : {
-                        "value" : "-100"
-                    },
-                    "right": {
-                        "value" : "75"
-                    }
-                }
-            }
-        ]
-    }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_no_tree.json
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_no_tree.json b/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_no_tree.json
deleted file mode 100644
index 6d1ae71..0000000
--- a/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_no_tree.json
+++ /dev/null
@@ -1,15 +0,0 @@
-{
-    "class":"org.apache.solr.ltr.model.LambdaMARTModel",
-    "name":"lambdamartmodel_no_tree",
-    "features":[
-        { "name": "matchedTitle"},
-        { "name": "constantScoreToForceLambdaMARTScoreAllDocs"}
-    ],
-    "params":{
-        "trees": [
-            {
-                "weight" : "2f"
-            }
-        ]
-    }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_no_trees.json
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_no_trees.json b/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_no_trees.json
deleted file mode 100644
index 8576782..0000000
--- a/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_no_trees.json
+++ /dev/null
@@ -1,10 +0,0 @@
-{
-    "class":"org.apache.solr.ltr.model.LambdaMARTModel",
-    "name":"lambdamartmodel_no_trees",
-    "features":[
-        { "name": "matchedTitle"},
-        { "name": "constantScoreToForceLambdaMARTScoreAllDocs"}
-    ],
-    "params":{
-    }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_no_weight.json
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_no_weight.json b/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_no_weight.json
deleted file mode 100644
index 9dbda56..0000000
--- a/solr/contrib/ltr/src/test-files/modelExamples/lambdamartmodel_no_weight.json
+++ /dev/null
@@ -1,24 +0,0 @@
-{
-    "class":"org.apache.solr.ltr.model.LambdaMARTModel",
-    "name":"lambdamartmodel_no_weight",
-    "features":[
-        { "name": "matchedTitle"},
-        { "name": "constantScoreToForceLambdaMARTScoreAllDocs"}
-    ],
-    "params":{
-        "trees": [
-            {
-                "root": {
-                    "feature": "matchedTitle",
-                    "threshold": "0.5f",
-                    "left" : {
-                        "value" : "-100"
-                    },
-                    "right": {
-                        "value" : "75"
-                    }
-                }
-            }
-        ]
-    }
-}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test-files/modelExamples/linear-model-efi.json
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test-files/modelExamples/linear-model-efi.json b/solr/contrib/ltr/src/test-files/modelExamples/linear-model-efi.json
new file mode 100644
index 0000000..018466e
--- /dev/null
+++ b/solr/contrib/ltr/src/test-files/modelExamples/linear-model-efi.json
@@ -0,0 +1,14 @@
+{
+  "class":"org.apache.solr.ltr.model.LinearModel",
+  "name":"linear-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/linear-model.json
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test-files/modelExamples/linear-model.json b/solr/contrib/ltr/src/test-files/modelExamples/linear-model.json
new file mode 100644
index 0000000..6b46dca
--- /dev/null
+++ b/solr/contrib/ltr/src/test-files/modelExamples/linear-model.json
@@ -0,0 +1,30 @@
+{
+	"class":"org.apache.solr.ltr.model.LinearModel",
+	"name":"6029760550880411648",
+	"features":[
+        {"name":"title"},
+        {"name":"description"},
+        {"name":"keywords"},
+        {
+            "name":"popularity",
+            "norm": {
+                "class":"org.apache.solr.ltr.norm.MinMaxNormalizer",
+                "params":{ "min":"0.0f", "max":"10.0f" }
+            }
+        },
+        {"name":"text"},
+        {"name":"queryIntentPerson"},
+        {"name":"queryIntentCompany"}
+	],
+	"params":{
+	  "weights": {
+	    "title": 0.0000000000,
+	    "description": 0.1000000000,
+	    "keywords": 0.2000000000,
+	    "popularity": 0.3000000000,
+	    "text": 0.4000000000,
+	    "queryIntentPerson":0.1231231,
+	    "queryIntentCompany":0.12121211
+	  }
+	}
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel.json
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel.json b/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel.json
new file mode 100644
index 0000000..37551a0
--- /dev/null
+++ b/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel.json
@@ -0,0 +1,38 @@
+{
+    "class":"org.apache.solr.ltr.model.MultipleAdditiveTreesModel",
+    "name":"multipleadditivetreesmodel",
+    "features":[
+        { "name": "matchedTitle"},
+        { "name": "constantScoreToForceMultipleAdditiveTreesScoreAllDocs"}
+    ],
+    "params":{
+        "trees": [
+            {
+                "weight" : "1f",
+                "root": {
+                    "feature": "matchedTitle",
+                    "threshold": "0.5f",
+                    "left" : {
+                        "value" : "-100"
+                    },
+                    "right": {
+                        "feature" : "this_feature_doesnt_exist",
+                        "threshold": "10.0f",
+                        "left" : {
+                            "value" : "50"
+                        },
+                        "right" : {
+                            "value" : "75"
+                        }
+                    }
+                }
+            },
+            {
+                "weight" : "2f",
+                "root": {
+                    "value" : "-10"
+                }
+            }
+        ]
+    }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_external_binary_features.json
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_external_binary_features.json b/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_external_binary_features.json
new file mode 100644
index 0000000..cb8996e
--- /dev/null
+++ b/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_external_binary_features.json
@@ -0,0 +1,38 @@
+{
+  "class":"org.apache.solr.ltr.model.MultipleAdditiveTreesModel",
+  "name":"external_model_binary_feature",
+  "features":[
+    { "name": "user_device_smartphone"},
+    { "name": "user_device_tablet"}
+  ],
+  "params":{
+    "trees": [
+      {
+        "weight" : "1f",
+        "root": {
+          "feature": "user_device_smartphone",
+          "threshold": "0.5f",
+          "left" : {
+            "value" : "0"
+          },
+          "right" : {
+            "value" : "50"
+          }
+
+        }},
+      {
+        "weight" : "1f",
+        "root": {
+          "feature": "user_device_tablet",
+          "threshold": "0.5f",
+          "left" : {
+            "value" : "0"
+          },
+          "right" : {
+            "value" : "65"
+          }
+
+        }}
+    ]
+  }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_no_feature.json
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_no_feature.json b/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_no_feature.json
new file mode 100644
index 0000000..2919f07
--- /dev/null
+++ b/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_no_feature.json
@@ -0,0 +1,24 @@
+{
+    "class":"org.apache.solr.ltr.model.MultipleAdditiveTreesModel",
+    "name":"multipleadditivetreesmodel_no_feature",
+    "features":[
+        { "name": "matchedTitle"},
+        { "name": "constantScoreToForceMultipleAdditiveTreesScoreAllDocs"}
+    ],
+    "params":{
+        "trees": [
+            {
+                "weight" : "1f",
+                "root": {
+                    "threshold": "0.5f",
+                    "left" : {
+                        "value" : "-100"
+                    },
+                    "right": {
+                        "value" : "75"
+                    }
+                }
+            }
+        ]
+    }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_no_features.json
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_no_features.json b/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_no_features.json
new file mode 100644
index 0000000..ec4c37f
--- /dev/null
+++ b/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_no_features.json
@@ -0,0 +1,14 @@
+{
+    "class":"org.apache.solr.ltr.model.MultipleAdditiveTreesModel",
+    "name":"multipleadditivetreesmodel_no_features",
+    "params":{
+        "trees": [
+            {
+                "weight" : "2f",
+                "root": {
+                    "value" : "-10"
+                }
+            }
+        ]
+    }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_no_left.json
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_no_left.json b/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_no_left.json
new file mode 100644
index 0000000..653d2ff
--- /dev/null
+++ b/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_no_left.json
@@ -0,0 +1,22 @@
+{
+    "class":"org.apache.solr.ltr.model.MultipleAdditiveTreesModel",
+    "name":"multipleadditivetreesmodel_no_left",
+    "features":[
+        { "name": "matchedTitle"},
+        { "name": "constantScoreToForceMultipleAdditiveTreesScoreAllDocs"}
+    ],
+    "params":{
+        "trees": [
+            {
+                "weight" : "1f",
+                "root": {
+                    "feature": "matchedTitle",
+                    "threshold": "0.5f",
+                    "right": {
+                        "value" : "75"
+                    }
+                }
+            }
+        ]
+    }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_no_params.json
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_no_params.json b/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_no_params.json
new file mode 100644
index 0000000..4d50c4e
--- /dev/null
+++ b/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_no_params.json
@@ -0,0 +1,8 @@
+{
+    "class":"org.apache.solr.ltr.model.MultipleAdditiveTreesModel",
+    "name":"multipleadditivetreesmodel_no_params",
+    "features":[
+        { "name": "matchedTitle"},
+        { "name": "constantScoreToForceMultipleAdditiveTreesScoreAllDocs"}
+    ]
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_no_right.json
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_no_right.json b/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_no_right.json
new file mode 100644
index 0000000..acd2d83
--- /dev/null
+++ b/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_no_right.json
@@ -0,0 +1,22 @@
+{
+    "class":"org.apache.solr.ltr.model.MultipleAdditiveTreesModel",
+    "name":"multipleadditivetreesmodel_no_right",
+    "features":[
+        { "name": "matchedTitle"},
+        { "name": "constantScoreToForceMultipleAdditiveTreesScoreAllDocs"}
+    ],
+    "params":{
+        "trees": [
+            {
+                "weight" : "1f",
+                "root": {
+                    "feature": "matchedTitle",
+                    "threshold": "0.5f",
+                    "left" : {
+                        "value" : "-100"
+                    }
+                }
+            }
+        ]
+    }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_no_threshold.json
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_no_threshold.json b/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_no_threshold.json
new file mode 100644
index 0000000..d0fc381
--- /dev/null
+++ b/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_no_threshold.json
@@ -0,0 +1,24 @@
+{
+    "class":"org.apache.solr.ltr.model.MultipleAdditiveTreesModel",
+    "name":"multipleadditivetreesmodel_no_threshold",
+    "features":[
+        { "name": "matchedTitle"},
+        { "name": "constantScoreToForceMultipleAdditiveTreesScoreAllDocs"}
+    ],
+    "params":{
+        "trees": [
+            {
+                "weight" : "1f",
+                "root": {
+                    "feature": "matchedTitle",
+                    "left" : {
+                        "value" : "-100"
+                    },
+                    "right": {
+                        "value" : "75"
+                    }
+                }
+            }
+        ]
+    }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_no_tree.json
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_no_tree.json b/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_no_tree.json
new file mode 100644
index 0000000..507def3
--- /dev/null
+++ b/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_no_tree.json
@@ -0,0 +1,15 @@
+{
+    "class":"org.apache.solr.ltr.model.MultipleAdditiveTreesModel",
+    "name":"multipleadditivetreesmodel_no_tree",
+    "features":[
+        { "name": "matchedTitle"},
+        { "name": "constantScoreToForceMultipleAdditiveTreesScoreAllDocs"}
+    ],
+    "params":{
+        "trees": [
+            {
+                "weight" : "2f"
+            }
+        ]
+    }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_no_trees.json
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_no_trees.json b/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_no_trees.json
new file mode 100644
index 0000000..bb360dd
--- /dev/null
+++ b/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_no_trees.json
@@ -0,0 +1,10 @@
+{
+    "class":"org.apache.solr.ltr.model.MultipleAdditiveTreesModel",
+    "name":"multipleadditivetreesmodel_no_trees",
+    "features":[
+        { "name": "matchedTitle"},
+        { "name": "constantScoreToForceMultipleAdditiveTreesScoreAllDocs"}
+    ],
+    "params":{
+    }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_no_weight.json
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_no_weight.json b/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_no_weight.json
new file mode 100644
index 0000000..9048e6c
--- /dev/null
+++ b/solr/contrib/ltr/src/test-files/modelExamples/multipleadditivetreesmodel_no_weight.json
@@ -0,0 +1,24 @@
+{
+    "class":"org.apache.solr.ltr.model.MultipleAdditiveTreesModel",
+    "name":"multipleadditivetreesmodel_no_weight",
+    "features":[
+        { "name": "matchedTitle"},
+        { "name": "constantScoreToForceMultipleAdditiveTreesScoreAllDocs"}
+    ],
+    "params":{
+        "trees": [
+            {
+                "root": {
+                    "feature": "matchedTitle",
+                    "threshold": "0.5f",
+                    "left" : {
+                        "value" : "-100"
+                    },
+                    "right": {
+                        "value" : "75"
+                    }
+                }
+            }
+        ]
+    }
+}

http://git-wip-us.apache.org/repos/asf/lucene-solr/blob/bfa05b83/solr/contrib/ltr/src/test-files/modelExamples/ranksvm-model.json
----------------------------------------------------------------------
diff --git a/solr/contrib/ltr/src/test-files/modelExamples/ranksvm-model.json b/solr/contrib/ltr/src/test-files/modelExamples/ranksvm-model.json
deleted file mode 100644
index 774958a..0000000
--- a/solr/contrib/ltr/src/test-files/modelExamples/ranksvm-model.json
+++ /dev/null
@@ -1,30 +0,0 @@
-{
-	"class":"org.apache.solr.ltr.model.RankSVMModel",
-	"name":"6029760550880411648",
-	"features":[
-        {"name":"title"},
-        {"name":"description"},
-        {"name":"keywords"},
-        {
-            "name":"popularity",
-            "norm": {
-                "class":"org.apache.solr.ltr.norm.MinMaxNormalizer",
-                "params":{ "min":"0.0f", "max":"10.0f" }
-            }
-        },
-        {"name":"text"},
-        {"name":"queryIntentPerson"},
-        {"name":"queryIntentCompany"}
-	],
-	"params":{
-	  "weights": {
-	    "title": 0.0000000000,
-	    "description": 0.1000000000,
-	    "keywords": 0.2000000000,
-	    "popularity": 0.3000000000,
-	    "text": 0.4000000000,
-	    "queryIntentPerson":0.1231231,
-	    "queryIntentCompany":0.12121211
-	  }
-	}
-}