summaryrefslogtreecommitdiffstats
path: root/searchlib/src/test/java/com/yahoo
diff options
context:
space:
mode:
authorJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
committerJon Bratseth <bratseth@yahoo-inc.com>2016-06-15 23:09:44 +0200
commit72231250ed81e10d66bfe70701e64fa5fe50f712 (patch)
tree2728bba1131a6f6e5bdf95afec7d7ff9358dac50 /searchlib/src/test/java/com/yahoo
Publish
Diffstat (limited to 'searchlib/src/test/java/com/yahoo')
-rwxr-xr-xsearchlib/src/test/java/com/yahoo/searchlib/aggregation/AggregationTestCase.java346
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/aggregation/ExpressionCountAggregationResultTest.java82
-rwxr-xr-xsearchlib/src/test/java/com/yahoo/searchlib/aggregation/ForceLoadTestCase.java19
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/aggregation/GroupTestCase.java229
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/aggregation/GroupingSerializationTest.java387
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/aggregation/GroupingTestCase.java227
-rwxr-xr-xsearchlib/src/test/java/com/yahoo/searchlib/aggregation/MergeTestCase.java735
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/aggregation/hll/BiasEstimatorTest.java70
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/aggregation/hll/HyperLogLogEstimatorTest.java89
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/aggregation/hll/HyperLogLogPrecisionBenchmark.java70
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/aggregation/hll/NormalSketchTest.java121
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/aggregation/hll/SketchMergerTest.java69
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/aggregation/hll/SketchUtils.java46
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/aggregation/hll/SparseSketchTest.java62
-rwxr-xr-xsearchlib/src/test/java/com/yahoo/searchlib/expression/ExpressionTestCase.java932
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/expression/FixedWidthBucketFunctionTestCase.java21
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/expression/FloatBucketResultNodeTestCase.java44
-rwxr-xr-xsearchlib/src/test/java/com/yahoo/searchlib/expression/ForceLoadTestCase.java19
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/expression/IntegerBucketResultNodeTestCase.java35
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/expression/IntegerResultNodeTestCase.java118
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/expression/NullResultNodeTestCase.java36
-rwxr-xr-xsearchlib/src/test/java/com/yahoo/searchlib/expression/ObjectVisitorTestCase.java61
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/expression/RangeBucketPreDefFunctionTestCase.java21
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/expression/RawBucketResultNodeTestCase.java46
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/expression/ResultNodeTest.java43
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/expression/ResultNodeVectorTestCase.java167
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/expression/StringBucketResultNodeTestCase.java57
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/expression/TimeStampFunctionTestCase.java29
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/expression/ZCurveFunctionTestCase.java25
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/gbdt/GbdtConverterTestCase.java169
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/gbdt/GbdtModelTestCase.java65
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/gbdt/ReferenceNodeTestCase.java101
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/gbdt/ResponseNodeTestCase.java40
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/gbdt/TreeNodeTestCase.java57
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/gbdt/XmlHelperTestCase.java153
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/mlr/ga/test/CsvFileCaseListTestCase.java81
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/mlr/ga/test/ExampleLearningSessions.java110
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/mlr/ga/test/MainTestCase.java57
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/mlr/ga/test/MockTrainingSetTestCase.java46
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/mlr/ga/test/TripAdvisorFileCaseList.java99
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/mlr/gbdt/ExpressionAnalysisRunner.java19
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/ranking/features/ElementCompletenessTestCase.java80
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/ranking/features/FieldTermMatchTestCase.java30
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/ranking/features/fieldmatch/SemanticDistanceTestCase.java140
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/ranking/features/fieldmatch/reference/OptimalStringAlignmentDistance.java201
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/ranking/features/fieldmatch/reference/TextbookLevenshteinDistance.java38
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/ranking/features/fieldmatch/reference/test/OptimalStringAlignmentTestCase.java58
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/ranking/features/fieldmatch/test/FieldMatchMetricsTestCase.java757
-rwxr-xr-xsearchlib/src/test/java/com/yahoo/searchlib/rankingexpression/FeatureListTestCase.java77
-rwxr-xr-xsearchlib/src/test/java/com/yahoo/searchlib/rankingexpression/RankingExpressionTestCase.java281
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/Benchmark.java144
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/EvaluationBenchmark.java474
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/EvaluationTestCase.java399
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/NeuralNetEvaluationTestCase.java49
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/StreamEvaluationBenchmark.java160
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/ContextReuseTestCase.java61
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/GBDTForestOptimizerTestCase.java109
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/GBDTOptimizerTestCase.java105
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/rule/ArgumentsTestCase.java42
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/rule/ReferenceNodeTestCase.java35
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/transform/ConstantDereferencerTestCase.java30
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/transform/SimplifierTestCase.java80
-rwxr-xr-xsearchlib/src/test/java/com/yahoo/searchlib/treenet/TreeNetParserTestCase.java79
63 files changed, 8632 insertions, 0 deletions
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/aggregation/AggregationTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/aggregation/AggregationTestCase.java
new file mode 100755
index 00000000000..2f271ec84db
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/aggregation/AggregationTestCase.java
@@ -0,0 +1,346 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.aggregation;
+
+import com.yahoo.document.DocumentId;
+import com.yahoo.document.GlobalId;
+import com.yahoo.io.GrowableByteBuffer;
+import com.yahoo.searchlib.expression.*;
+import com.yahoo.vespa.objects.BufferSerializer;
+import com.yahoo.vespa.objects.Identifiable;
+import com.yahoo.vespa.objects.ObjectOperation;
+import com.yahoo.vespa.objects.ObjectPredicate;
+import junit.framework.TestCase;
+
+/**
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ */
+public class AggregationTestCase extends TestCase {
+
+ public void testSumAggregationResult() {
+ SumAggregationResult a = new SumAggregationResult();
+ a.setExpression(new AttributeNode("attributeA"));
+ a.setSum(new IntegerResultNode(7));
+ assertEquals(a.getSum().getInteger(), 7);
+ SumAggregationResult b = (SumAggregationResult)serializeDeserialize(a);
+ assertEquals(b.getSum().getInteger(), 7);
+ b.merge(a);
+ assertEquals(b.getSum().getInteger(), 14);
+ }
+
+ public void testXorAggregationResult() {
+ XorAggregationResult a = new XorAggregationResult(6);
+ a.setExpression(new AttributeNode("attributeA"));
+ assertEquals(a.getXor(), 6);
+ a.setXor(7);
+ assertEquals(a.getXor(), 7);
+ XorAggregationResult b = (XorAggregationResult)serializeDeserialize(a);
+ assertEquals(b.getXor(), 7);
+ b.merge(a);
+ assertEquals(b.getXor(), 0);
+ }
+
+ public void testCountAggregationResult() {
+ CountAggregationResult a = new CountAggregationResult(6);
+ a.setExpression(new AttributeNode("attributeA"));
+ assertEquals(a.getCount(), 6);
+ a.setCount(7);
+ assertEquals(a.getCount(), 7);
+ CountAggregationResult b = (CountAggregationResult)serializeDeserialize(a);
+ assertEquals(b.getCount(), 7);
+ b.merge(a);
+ assertEquals(b.getCount(), 14);
+ }
+
+ public void testMinAggregationResult() {
+ MinAggregationResult a = new MinAggregationResult(new IntegerResultNode(6));
+ a.setExpression(new AttributeNode("attributeA"));
+ assertEquals(a.getMin().getInteger(), 6);
+ a.setMin(new IntegerResultNode(7));
+ assertEquals(a.getMin().getInteger(), 7);
+ MinAggregationResult b = (MinAggregationResult)serializeDeserialize(a);
+ a.setMin(new IntegerResultNode(6));
+ assertEquals(b.getMin().getInteger(), 7);
+ b.merge(a);
+ assertEquals(b.getMin().getInteger(), 6);
+ }
+
+ public void testMaxAggregationResult() {
+ MaxAggregationResult a = new MaxAggregationResult(new IntegerResultNode(6));
+ a.setExpression(new AttributeNode("attributeA"));
+ assertEquals(a.getMax().getInteger(), 6);
+ a.setMax(new IntegerResultNode(7));
+ assertEquals(a.getMax().getInteger(), 7);
+ MaxAggregationResult b = (MaxAggregationResult)serializeDeserialize(a);
+ a.setMax(new IntegerResultNode(6));
+ assertEquals(b.getMax().getInteger(), 7);
+ b.merge(a);
+ assertEquals(b.getMax().getInteger(), 7);
+ }
+
+ public void testAverageAggregationResult() {
+ AverageAggregationResult a = new AverageAggregationResult(new FloatResultNode(72), 6);
+ a.setExpression(new AttributeNode("attributeA"));
+ assertEquals(a.getCount(), 6);
+ a.setCount(8);
+ assertEquals(a.getCount(), 8);
+ AverageAggregationResult b = (AverageAggregationResult)serializeDeserialize(a);
+ assertEquals(b.getCount(), 8);
+ a.setCount(6);
+ b.merge(a);
+ assertEquals(b.getCount(), 14);
+ assertEquals(b.getSum().getInteger(), 144);
+ }
+
+ private static boolean equals(Object a, Object b) {
+ return a.equals(b);
+ }
+
+ private GlobalId createGlobalId(int docId) {
+ return new GlobalId((new DocumentId("doc:test:" + docId)).getGlobalId());
+ }
+
+ public void testFs4HitsAggregationResult() {
+ double rank1 = 1;
+ double rank2 = 2;
+ assertEquals(new FS4Hit(1, createGlobalId(1), rank1), new FS4Hit(1, createGlobalId(1), rank1));
+ assertFalse(equals(new FS4Hit(1, createGlobalId(1), rank1), new FS4Hit(2, createGlobalId(1), rank1)));
+ assertFalse(equals(new FS4Hit(1, createGlobalId(1), rank1), new FS4Hit(1, createGlobalId(2), rank1)));
+ assertFalse(equals(new FS4Hit(1, createGlobalId(1), rank1), new FS4Hit(1, createGlobalId(1), rank2)));
+
+ HitsAggregationResult a = new HitsAggregationResult(5);
+ assertEquals(5, a.getMaxHits());
+ assertEquals(0, a.getHits().size());
+ a.setExpression(new AttributeNode("attributeA"));
+ a.addHit(new FS4Hit(1, createGlobalId(2), rank1));
+ a.addHit(new FS4Hit(5, createGlobalId(7), rank2));
+ assertEquals(2, a.getHits().size());
+ HitsAggregationResult b = (HitsAggregationResult)serializeDeserialize(a);
+ assertEquals(a, b);
+ a.postMerge();
+ assertEquals(2, a.getHits().size());
+ assertEquals(2.0, a.getHits().get(0).getRank());
+ a.setMaxHits(1).postMerge();
+ assertEquals(1, a.getHits().size());
+ assertEquals(2.0, a.getHits().get(0).getRank());
+
+ HitsAggregationResult hits = new HitsAggregationResult(3)
+ .addHit(new FS4Hit(1, createGlobalId(3), 1))
+ .addHit(new FS4Hit(2, createGlobalId(2), 2))
+ .addHit(new FS4Hit(3, createGlobalId(1), 3));
+ Grouping request = new Grouping()
+ .setRoot(new Group()
+ .addAggregationResult(hits.clone())
+ .addChild(new Group()
+ .addAggregationResult(hits.clone())
+ .addChild(new Group()
+ .addAggregationResult(hits.clone())))
+ .addChild(new Group()
+ .addAggregationResult(hits.clone())
+ .addChild(new Group()
+ .addAggregationResult(hits.clone())
+ .addChild(new Group()
+ .addAggregationResult(hits.clone())))));
+ assertFS4Hits(request, 0, 0, 3);
+ assertFS4Hits(request, 1, 1, 6);
+ assertFS4Hits(request, 2, 2, 6);
+ assertFS4Hits(request, 3, 3, 3);
+ assertFS4Hits(request, 4, 4, 0);
+
+ assertFS4Hits(request, 0, 1, 9);
+ assertFS4Hits(request, 0, 2, 15);
+ assertFS4Hits(request, 0, 3, 18);
+ assertFS4Hits(request, 0, 4, 18);
+ assertFS4Hits(request, 1, 4, 15);
+ assertFS4Hits(request, 2, 4, 9);
+ assertFS4Hits(request, 3, 4, 3);
+
+ assertFS4Hits(request, 1, 2, 12);
+ assertFS4Hits(request, 2, 3, 9);
+ assertFS4Hits(request, 3, 4, 3);
+ assertFS4Hits(request, 4, 5, 0);
+ }
+
+ public void testVdsHitsAggregationResult() {
+ double rank1 = 1;
+ double rank2 = 2;
+ byte [] s1 = {'a','b','c'};
+ byte [] s2 = {'n','o','e'};
+ byte [] s3 = {'n','o','3'};
+ assertEquals(new VdsHit("1", s1, rank1), new VdsHit("1", s1, rank1));
+ assertFalse(equals(new VdsHit("1", s1, rank1), new VdsHit("2", s1, rank1)));
+ assertFalse(equals(new VdsHit("1", s1, rank1), new VdsHit("1", s2, rank1)));
+ assertFalse(equals(new VdsHit("1", s1, rank1), new VdsHit("1", s1, rank2)));
+
+ HitsAggregationResult a = new HitsAggregationResult(5);
+ assertEquals(5, a.getMaxHits());
+ assertEquals(0, a.getHits().size());
+ a.setExpression(new AttributeNode("attributeA"));
+ a.addHit(new VdsHit("1", s2, rank1));
+// a.addHit(new VdsHit("5", s7, rank2));
+// assertEquals(2, a.getHits().size());
+ HitsAggregationResult b = (HitsAggregationResult)serializeDeserialize(a);
+ assertEquals(a, b);
+
+ HitsAggregationResult hits = new HitsAggregationResult(3)
+ .addHit(new VdsHit("1", s3, 1))
+ .addHit(new VdsHit("2", s2, 2))
+ .addHit(new VdsHit("3", s1, 3));
+ Grouping request = new Grouping()
+ .setRoot(new Group()
+ .addAggregationResult(hits.clone())
+ .addChild(new Group()
+ .addAggregationResult(hits.clone())
+ .addChild(new Group()
+ .addAggregationResult(hits.clone())))
+ .addChild(new Group()
+ .addAggregationResult(hits.clone())
+ .addChild(new Group()
+ .addAggregationResult(hits.clone())
+ .addChild(new Group()
+ .addAggregationResult(hits.clone())))));
+ assertVdsHits(request, 0, 0, 3);
+ assertVdsHits(request, 1, 1, 6);
+ assertVdsHits(request, 2, 2, 6);
+ assertVdsHits(request, 3, 3, 3);
+ assertVdsHits(request, 4, 4, 0);
+
+ assertVdsHits(request, 0, 1, 9);
+ assertVdsHits(request, 0, 2, 15);
+ assertVdsHits(request, 0, 3, 18);
+ assertVdsHits(request, 0, 4, 18);
+ assertVdsHits(request, 1, 4, 15);
+ assertVdsHits(request, 2, 4, 9);
+ assertVdsHits(request, 3, 4, 3);
+
+ assertVdsHits(request, 1, 2, 12);
+ assertVdsHits(request, 2, 3, 9);
+ assertVdsHits(request, 3, 4, 3);
+ assertVdsHits(request, 4, 5, 0);
+ }
+
+
+ private void assertFS4Hits(Grouping request, int firstLevel, int lastLevel, int expected) {
+ CountFS4Hits obj = new CountFS4Hits();
+ request.setFirstLevel(firstLevel);
+ request.setLastLevel(lastLevel);
+ request.select(obj, obj);
+ assertEquals(expected, obj.count);
+ }
+
+ private void assertVdsHits(Grouping request, int firstLevel, int lastLevel, int expected) {
+ CountVdsHits obj = new CountVdsHits();
+ request.setFirstLevel(firstLevel);
+ request.setLastLevel(lastLevel);
+ request.select(obj, obj);
+ assertEquals(expected, obj.count);
+ }
+
+ private class CountFS4Hits implements ObjectPredicate, ObjectOperation {
+ int count;
+ public boolean check(Object obj) {
+ return obj instanceof FS4Hit;
+ }
+ public void execute(Object obj) {
+ ++count;
+ }
+ }
+
+ private class CountVdsHits implements ObjectPredicate, ObjectOperation {
+ int count;
+ public boolean check(Object obj) {
+ return obj instanceof VdsHit;
+ }
+ public void execute(Object obj) {
+ ++count;
+ }
+ }
+
+ public void testGroup() {
+ Group a = new Group();
+ a.setId(new IntegerResultNode(17));
+ a.addAggregationResult(new XorAggregationResult());
+ serializeDeserialize1(a);
+ }
+
+ public void testGrouping() {
+ Grouping a = new Grouping();
+ GroupingLevel level = new GroupingLevel();
+ level.setExpression(new AttributeNode("folder"));
+
+ XorAggregationResult xor = new XorAggregationResult();
+ xor.setExpression(new MD5BitFunctionNode(new AttributeNode("docid"), 64));
+ level.getGroupPrototype().addAggregationResult(xor);
+
+ SumAggregationResult sum = new SumAggregationResult();
+ MinFunctionNode min = new MinFunctionNode();
+ min.addArg(new AttributeNode("attribute1"));
+ min.addArg(new AttributeNode("attribute2"));
+ sum.setExpression(min);
+ level.getGroupPrototype().addAggregationResult(sum);
+
+ CatFunctionNode cat = new CatFunctionNode();
+ cat.addArg(new GetDocIdNamespaceSpecificFunctionNode());
+ cat.addArg(new DocumentFieldNode("folder"));
+ cat.addArg(new DocumentFieldNode("flags"));
+ XorAggregationResult xor2 = new XorAggregationResult();
+ xor2.setExpression(new XorBitFunctionNode(cat, 64));
+ level.getGroupPrototype().addAggregationResult(xor2);
+ a.addLevel(level);
+
+ Group g = new Group();
+ g.setId(new IntegerResultNode(17));
+ g.addAggregationResult(xor); // XXX: this is BAD
+ a.getRoot().addChild(g);
+ serializeDeserialize1(a);
+
+
+ Grouping foo = new Grouping();
+ foo.addLevel(level);
+ int hashBefore = foo.hashCode();
+ foo.setFirstLevel(66);
+ assertEquals(hashBefore, foo.hashCode());
+ foo.setFirstLevel(99);
+ assertEquals(hashBefore, foo.hashCode());
+ foo.setLastLevel(66);
+ assertEquals(hashBefore, foo.hashCode());
+ foo.setLastLevel(99);
+ assertEquals(hashBefore, foo.hashCode());
+ foo.getRoot().addChild(g);
+ assertEquals(hashBefore, foo.hashCode());
+ }
+
+ // --------------------------------------------------------------------------------
+ //
+ // Everything below this point is helper functions.
+ //
+ // --------------------------------------------------------------------------------
+ private static Identifiable serializeDeserialize1(Identifiable a) {
+ BufferSerializer buf = new BufferSerializer(new GrowableByteBuffer());
+ a.serializeWithId(buf);
+ buf.flip();
+ Identifiable b = Identifiable.create(buf);
+ assertEquals(a.getClass(), b.getClass());
+ assertEquals(buf.getBuf().hasRemaining(), false);
+ Identifiable c = b.clone();
+ assertEquals(b.getClass(), c.getClass());
+ BufferSerializer bb = new BufferSerializer(new GrowableByteBuffer());
+ BufferSerializer cb = new BufferSerializer(new GrowableByteBuffer());
+ b.serializeWithId(bb);
+ c.serializeWithId(cb);
+ assertEquals(bb.getBuf().limit(), cb.getBuf().limit());
+ assertEquals(bb.position(), cb.position());
+ bb.getBuf().flip();
+ cb.getBuf().flip();
+ for (int i = 0; i < bb.getBuf().limit(); i++) {
+ assertEquals(bb.getBuf().get(), cb.getBuf().get());
+ }
+
+ return b;
+ }
+
+ private static AggregationResult serializeDeserialize(AggregationResult a) {
+ AggregationResult b = (AggregationResult)serializeDeserialize1(a);
+ assertEquals(a.getExpression().getClass(), b.getExpression().getClass());
+ return b;
+ }
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/aggregation/ExpressionCountAggregationResultTest.java b/searchlib/src/test/java/com/yahoo/searchlib/aggregation/ExpressionCountAggregationResultTest.java
new file mode 100644
index 00000000000..0d7c4c8bca1
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/aggregation/ExpressionCountAggregationResultTest.java
@@ -0,0 +1,82 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.aggregation;
+
+import com.yahoo.searchlib.aggregation.hll.*;
+import com.yahoo.vespa.objects.BufferSerializer;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author bjorncs
+ */
+public class ExpressionCountAggregationResultTest {
+
+ @Test
+ public void requireThatSketchesAreMerged() {
+ ExpressionCountAggregationResult aggr1 = createAggregationWithSparseSketch(42);
+ ExpressionCountAggregationResult aggr2 = createAggregationWithSparseSketch(1337);
+
+ // Merge performs union of the underlying data of the sparse sketch.
+ aggr1.onMerge(aggr2);
+
+ SparseSketch sketch = (SparseSketch) aggr1.getSketch();
+ SketchUtils.assertSparseSketchContains(sketch, 42, 1337);
+ }
+
+ @Test
+ public void requireThatEstimateIsCorrect() {
+ ExpressionCountAggregationResult aggr = createAggregationWithSparseSketch(42);
+ assertTrue(aggr.getEstimatedUniqueCount() == 1);
+ }
+
+ @Test
+ public void requireThatPostMergeUpdatesEstimate() {
+ ExpressionCountAggregationResult aggr = createAggregationWithSparseSketch(1337);
+ assertEquals(1, aggr.getEstimatedUniqueCount());
+ // Merge performs union of the underlying data of the sparse sketch.
+ aggr.onMerge(createAggregationWithSparseSketch(9001));
+ assertEquals(2, aggr.getEstimatedUniqueCount());
+ }
+
+ @Test
+ public void requireThatSerializationDeserializationMatchSparseSketch() {
+ ExpressionCountAggregationResult from = createAggregationWithSparseSketch(42);
+ ExpressionCountAggregationResult to = createAggregationWithSparseSketch(1337);
+ testSerialization(from, to);
+ }
+
+ @Test
+ public void requireThatSerializationDeserializationMatchNormalSketch() {
+ ExpressionCountAggregationResult from = createAggregationWithNormalSketch(42);
+ ExpressionCountAggregationResult to = createAggregationWithNormalSketch(1337);
+ testSerialization(from, to);
+ }
+
+ private void testSerialization(ExpressionCountAggregationResult from, ExpressionCountAggregationResult to) {
+ BufferSerializer buffer = new BufferSerializer();
+ from.serialize(buffer);
+ buffer.flip();
+ to.deserialize(buffer);
+
+ assertEquals(from.getSketch(), to.getSketch());
+ }
+
+ private static ExpressionCountAggregationResult createAggregationWithSparseSketch(int sketchValue) {
+ SparseSketch initialSketch = SketchUtils.createSparseSketch(sketchValue);
+ return new ExpressionCountAggregationResult(
+ initialSketch,
+ sketch -> ((SparseSketch) sketch).size()
+ );
+ }
+
+ private static ExpressionCountAggregationResult createAggregationWithNormalSketch(int sketchValue) {
+ NormalSketch initialSketch = SketchUtils.createNormalSketch(sketchValue);
+ return new ExpressionCountAggregationResult(
+ initialSketch,
+ sketch -> 42
+ );
+ }
+
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/aggregation/ForceLoadTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/aggregation/ForceLoadTestCase.java
new file mode 100755
index 00000000000..ee7d50f33cb
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/aggregation/ForceLoadTestCase.java
@@ -0,0 +1,19 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.aggregation;
+
+public class ForceLoadTestCase extends junit.framework.TestCase {
+
+ public ForceLoadTestCase(String name) {
+ super(name);
+ }
+
+ public void testLoadClasses() {
+ try {
+ new com.yahoo.searchlib.aggregation.ForceLoad();
+ assertTrue(com.yahoo.searchlib.aggregation.ForceLoad.forceLoad());
+ } catch (com.yahoo.system.ForceLoadError e) {
+ e.printStackTrace();
+ assertTrue(false);
+ }
+ }
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/aggregation/GroupTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/aggregation/GroupTestCase.java
new file mode 100644
index 00000000000..1852f292a48
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/aggregation/GroupTestCase.java
@@ -0,0 +1,229 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.aggregation;
+
+import com.yahoo.searchlib.expression.*;
+import com.yahoo.vespa.objects.BufferSerializer;
+import com.yahoo.vespa.objects.Identifiable;
+import org.junit.Test;
+
+import java.util.Arrays;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class GroupTestCase {
+
+ @Test
+ public void requireThatAggregationResultsCanBeAdded() {
+ Group group = new Group();
+ AggregationResult res = new AverageAggregationResult();
+ group.addAggregationResult(res);
+ assertEquals(1, group.getAggregationResults().size());
+ assertSame(res, group.getAggregationResults().get(0));
+ }
+
+ @Test
+ public void requireThatAggregationResultListIsNotImmutable() {
+ Group group = new Group();
+ group.getAggregationResults().add(new AverageAggregationResult());
+ }
+
+ @Test
+ public void requireThatOrderByExpressionsCanBeAdded() {
+ Group group = new Group();
+ ExpressionNode foo = new ConstantNode(new IntegerResultNode(6));
+ group.addOrderBy(foo, true);
+ assertEquals(1, group.getOrderByExpressions().size());
+ assertSame(foo, group.getOrderByExpressions().get(0));
+ assertEquals(Arrays.asList(1), group.getOrderByIndexes());
+
+ ExpressionNode bar = new ConstantNode(new IntegerResultNode(9));
+ group.addOrderBy(bar, false);
+ assertEquals(2, group.getOrderByExpressions().size());
+ assertSame(bar, group.getOrderByExpressions().get(1));
+ assertEquals(Arrays.asList(1, -2), group.getOrderByIndexes());
+ }
+
+ @Test
+ public void requireThatOrderByListsAreImmutable() {
+ Group group = new Group();
+ try {
+ group.getOrderByExpressions().add(new ConstantNode(new IntegerResultNode(69)));
+ fail();
+ } catch (UnsupportedOperationException e) {
+
+ }
+ try {
+ group.getOrderByIndexes().add(69);
+ fail();
+ } catch (UnsupportedOperationException e) {
+
+ }
+ }
+
+ @Test
+ public void requireThatAddOrderByAddsAggregationResult() {
+ Group group = new Group();
+ AggregationResult res = new MinAggregationResult();
+ group.addOrderBy(res, true);
+ assertEquals(1, group.getAggregationResults().size());
+ assertSame(res, group.getAggregationResults().get(0));
+ }
+
+ @Test
+ public void requireThatAddOrderByDoesNotAddDuplicateAggregationResult() {
+ Group group = new Group();
+ AggregationResult res = new MinAggregationResult();
+ group.addAggregationResult(res);
+ group.addOrderBy(res, true);
+ assertEquals(1, group.getAggregationResults().size());
+ assertSame(res, group.getAggregationResults().get(0));
+ }
+
+ @Test
+ public void requireThatAddOrderByIgnoresAggregationResultTagWhenMatching() {
+ Group group = new Group();
+ AggregationResult foo = new MinAggregationResult();
+ foo.setTag(6);
+ group.addAggregationResult(foo);
+ AggregationResult bar = new MinAggregationResult();
+ bar.setTag(9);
+ group.addOrderBy(bar, true);
+ assertEquals(1, group.getAggregationResults().size());
+ assertSame(foo, group.getAggregationResults().get(0));
+ assertEquals(6, foo.getTag());
+ }
+
+ @Test
+ public void requireThatAddOrderByDoesNotModifyTagOfNewAggregationResult() {
+ Group group = new Group();
+ AggregationResult foo = new MinAggregationResult();
+ foo.setTag(6);
+ group.addAggregationResult(foo);
+ AggregationResult bar = new MaxAggregationResult();
+ bar.setTag(9);
+ group.addOrderBy(bar, true);
+ assertEquals(2, group.getAggregationResults().size());
+ assertSame(foo, group.getAggregationResults().get(0));
+ assertEquals(6, foo.getTag());
+ assertSame(bar, group.getAggregationResults().get(1));
+ assertEquals(9, bar.getTag());
+ }
+
+ @Test
+ public void requireThatAddOrderByAddsReferencedAggregationResult() {
+ Group group = new Group();
+ AggregationResult res = new MinAggregationResult();
+ group.addOrderBy(new AggregationRefNode(res), true);
+ assertEquals(1, group.getAggregationResults().size());
+ assertSame(res, group.getAggregationResults().get(0));
+ }
+
+ @Test
+ public void requireThatAddOrderByDoesNotAddDuplicateReferencedAggregationResult() {
+ Group group = new Group();
+ AggregationResult res = new MinAggregationResult();
+ group.addAggregationResult(res);
+ group.addOrderBy(new AggregationRefNode(res), true);
+ assertEquals(1, group.getAggregationResults().size());
+ assertSame(res, group.getAggregationResults().get(0));
+ }
+
+ @Test
+ public void requireThatAddOrderByAddsDeepReferencedAggregationResult() {
+ Group group = new Group();
+ AggregationResult res = new MinAggregationResult();
+ group.addOrderBy(new NegateFunctionNode(new AggregationRefNode(res)), true);
+ assertEquals(1, group.getAggregationResults().size());
+ assertSame(res, group.getAggregationResults().get(0));
+ }
+
+ @Test
+ public void requireThatAddOrderByDoesNotAddDuplicateDeepReferencedAggregationResult() {
+ Group group = new Group();
+ AggregationResult res = new MinAggregationResult();
+ group.addAggregationResult(res);
+ group.addOrderBy(new NegateFunctionNode(new AggregationRefNode(res)), true);
+ assertEquals(1, group.getAggregationResults().size());
+ assertSame(res, group.getAggregationResults().get(0));
+ }
+
+ @Test
+ public void requireThatAddOrderByResolvesReferenceIndex() {
+ Group group = new Group();
+ AggregationResult res = new MinAggregationResult();
+ group.addAggregationResult(res);
+ group.addOrderBy(new AggregationRefNode(res), true);
+ assertEquals(1, group.getOrderByExpressions().size());
+ AggregationRefNode ref = (AggregationRefNode)group.getOrderByExpressions().get(0);
+ assertEquals(0, ref.getIndex());
+ assertSame(res, ref.getExpression());
+ }
+
+ @Test
+ public void requireThatAddOrderByResolvesDeepReferenceIndex() {
+ Group group = new Group();
+ AggregationResult res = new MinAggregationResult();
+ group.addAggregationResult(res);
+ group.addOrderBy(new NegateFunctionNode(new AggregationRefNode(res)), true);
+ assertEquals(1, group.getOrderByExpressions().size());
+ AggregationRefNode ref = (AggregationRefNode)((NegateFunctionNode)group.getOrderByExpressions().get(0)).getArg();
+ assertEquals(0, ref.getIndex());
+ assertSame(res, ref.getExpression());
+ }
+
+ @Test
+ public void requireThatAddOrderByResolvesReferenceResult() {
+ Group group = new Group();
+ AggregationResult res = new MinAggregationResult();
+ group.addOrderBy(new AggregationRefNode(res), true);
+ assertEquals(1, group.getOrderByExpressions().size());
+ AggregationRefNode ref = (AggregationRefNode)group.getOrderByExpressions().get(0);
+ assertEquals(0, ref.getIndex());
+ assertSame(res, ref.getExpression());
+ }
+
+ @Test
+ public void requireThatAddOrderByResolvesDeepReferenceResult() {
+ Group group = new Group();
+ AggregationResult res = new MinAggregationResult();
+ group.addOrderBy(new NegateFunctionNode(new AggregationRefNode(res)), true);
+ assertEquals(1, group.getOrderByExpressions().size());
+ AggregationRefNode ref = (AggregationRefNode)((NegateFunctionNode)group.getOrderByExpressions().get(0)).getArg();
+ assertEquals(0, ref.getIndex());
+ assertSame(res, ref.getExpression());
+ }
+
+ @Test
+ public void requireThatCloneResolvesAggregationRef() {
+ Group group = new Group();
+ AggregationResult res = new MinAggregationResult();
+ group.addOrderBy(new AggregationRefNode(res), true);
+ group = group.clone();
+
+ assertEquals(1, group.getOrderByExpressions().size());
+ AggregationRefNode ref = (AggregationRefNode)group.getOrderByExpressions().get(0);
+ assertEquals(0, ref.getIndex());
+ assertEquals(res, ref.getExpression());
+ assertNotSame(res, ref.getExpression());
+ }
+
+ @Test
+ public void requireThatDeserializeResolvesAggregationRef() {
+ Group group = new Group();
+ AggregationResult res = new MinAggregationResult();
+ group.addOrderBy(new AggregationRefNode(res), true);
+ BufferSerializer buf = new BufferSerializer();
+ group.serializeWithId(buf);
+ buf.flip();
+ group = (Group)Identifiable.create(buf);
+
+ assertEquals(1, group.getOrderByExpressions().size());
+ AggregationRefNode ref = (AggregationRefNode)group.getOrderByExpressions().get(0);
+ assertEquals(0, ref.getIndex());
+ assertEquals(res, ref.getExpression());
+ assertNotSame(res, ref.getExpression());
+ }
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/aggregation/GroupingSerializationTest.java b/searchlib/src/test/java/com/yahoo/searchlib/aggregation/GroupingSerializationTest.java
new file mode 100644
index 00000000000..a9926f7c0e2
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/aggregation/GroupingSerializationTest.java
@@ -0,0 +1,387 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.aggregation;
+
+import com.yahoo.document.DocumentId;
+import com.yahoo.document.GlobalId;
+import com.yahoo.io.GrowableByteBuffer;
+import com.yahoo.searchlib.aggregation.hll.SparseSketch;
+import com.yahoo.searchlib.expression.*;
+import com.yahoo.vespa.objects.BufferSerializer;
+import com.yahoo.vespa.objects.Identifiable;
+import com.yahoo.vespa.objects.ObjectDumper;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import java.io.*;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.util.Arrays;
+
+import static org.junit.Assert.fail;
+
+/**
+ * Tests serialization compatibility across Java and C++. The comparison is performed by comparing serialized Java
+ * object graphs with the content of specific binary files. C++ unit tests serializes
+ * identical data structures into these files.
+ * Note: This test relies heavily on proper implementation of {@link Object#equals(Object)}!
+ */
+public class GroupingSerializationTest {
+
+ @BeforeClass
+ public static void forceLoadingOfSerializableClasses() {
+ com.yahoo.searchlib.aggregation.ForceLoad.forceLoad();
+ com.yahoo.searchlib.expression.ForceLoad.forceLoad();
+ }
+
+ @Test
+ public void testResultTypes() throws IOException {
+ try (SerializationTester t = new SerializationTester("testResultTypes")) {
+ t.assertMatch(new IntegerResultNode(7));
+ t.assertMatch(new FloatResultNode(7.3));
+ t.assertMatch(new StringResultNode("7.3"));
+ t.assertMatch(new StringResultNode(
+ new String(new byte[]{(byte)0xe5, (byte)0xa6, (byte)0x82, (byte)0xe6, (byte)0x9e, (byte)0x9c})));
+ t.assertMatch(new RawResultNode(new byte[]{'7', '.', '4'}));
+ t.assertMatch(new IntegerBucketResultNode());
+ t.assertMatch(new FloatBucketResultNode());
+ t.assertMatch(new IntegerBucketResultNode(10, 20));
+ t.assertMatch(new FloatBucketResultNode(10, 20));
+ t.assertMatch(new StringBucketResultNode("10.0", "20.0"));
+ t.assertMatch(new RawBucketResultNode(
+ new RawResultNode(new byte[]{1, 0, 0}),
+ new RawResultNode(new byte[]{1, 1, 0})));
+ t.assertMatch(new IntegerBucketResultNodeVector()
+ .add(new IntegerBucketResultNode(878, 3246823)));
+ t.assertMatch(new FloatBucketResultNodeVector()
+ .add(new FloatBucketResultNode(878, 3246823)));
+ t.assertMatch(new StringBucketResultNodeVector()
+ .add(new StringBucketResultNode("878", "3246823")));
+ t.assertMatch(new RawBucketResultNodeVector()
+ .add(new RawBucketResultNode(
+ new RawResultNode(new byte[]{1, 0, 0}),
+ new RawResultNode(new byte[]{1, 1, 0}))));
+ }
+
+ }
+
+ @Test
+ public void testSpecialNodes() throws IOException {
+ try (SerializationTester t = new SerializationTester("testSpecialNodes")) {
+ t.assertMatch(new AttributeNode("testattribute"));
+ t.assertMatch(new DocumentFieldNode("testdocumentfield"));
+ t.assertMatch(new GetDocIdNamespaceSpecificFunctionNode(new IntegerResultNode(7)));
+ t.assertMatch(new GetYMUMChecksumFunctionNode());
+ }
+ }
+
+ @Test
+ public void testFunctionNodes() throws IOException {
+ try (SerializationTester t = new SerializationTester("testFunctionNodes")) {
+ t.assertMatch(new AddFunctionNode()
+ .addArg(new ConstantNode(new IntegerResultNode(7)))
+ .addArg(new ConstantNode(new IntegerResultNode(8)))
+ .addArg(new ConstantNode(new IntegerResultNode(9))));
+ t.assertMatch(new XorFunctionNode()
+ .addArg(new ConstantNode(new IntegerResultNode(7)))
+ .addArg(new ConstantNode(new IntegerResultNode(8)))
+ .addArg(new ConstantNode(new IntegerResultNode(9))));
+ t.assertMatch(new MultiplyFunctionNode()
+ .addArg(new ConstantNode(new IntegerResultNode(7)))
+ .addArg(new ConstantNode(new IntegerResultNode(8)))
+ .addArg(new ConstantNode(new IntegerResultNode(9))));
+ t.assertMatch(new DivideFunctionNode()
+ .addArg(new ConstantNode(new IntegerResultNode(7)))
+ .addArg(new ConstantNode(new IntegerResultNode(8)))
+ .addArg(new ConstantNode(new IntegerResultNode(9))));
+ t.assertMatch(new ModuloFunctionNode()
+ .addArg(new ConstantNode(new IntegerResultNode(7)))
+ .addArg(new ConstantNode(new IntegerResultNode(8)))
+ .addArg(new ConstantNode(new IntegerResultNode(9))));
+ t.assertMatch(new MinFunctionNode()
+ .addArg(new ConstantNode(new IntegerResultNode(7)))
+ .addArg(new ConstantNode(new IntegerResultNode(8)))
+ .addArg(new ConstantNode(new IntegerResultNode(9))));
+ t.assertMatch(new MaxFunctionNode()
+ .addArg(new ConstantNode(new IntegerResultNode(7)))
+ .addArg(new ConstantNode(new IntegerResultNode(8)))
+ .addArg(new ConstantNode(new IntegerResultNode(9))));
+ t.assertMatch(new TimeStampFunctionNode(new ConstantNode(new IntegerResultNode(7)),
+ TimeStampFunctionNode.TimePart.Hour, true));
+ t.assertMatch(new ZCurveFunctionNode(new ConstantNode(new IntegerResultNode(7)),
+ ZCurveFunctionNode.Dimension.X));
+ t.assertMatch(new ZCurveFunctionNode(new ConstantNode(new IntegerResultNode(7)),
+ ZCurveFunctionNode.Dimension.Y));
+ t.assertMatch(new NegateFunctionNode(new ConstantNode(new IntegerResultNode(7))));
+ t.assertMatch(new SortFunctionNode(new ConstantNode(new IntegerResultNode(7))));
+ t.assertMatch(new NormalizeSubjectFunctionNode(new ConstantNode(
+ new StringResultNode("foo"))));
+ t.assertMatch(new ReverseFunctionNode(new ConstantNode(new IntegerResultNode(7))));
+ t.assertMatch(new MD5BitFunctionNode(new ConstantNode(new IntegerResultNode(7)), 64));
+ t.assertMatch(new XorBitFunctionNode(new ConstantNode(new IntegerResultNode(7)), 64));
+ t.assertMatch(new CatFunctionNode()
+ .addArg(new ConstantNode(new IntegerResultNode(7)))
+ .addArg(new ConstantNode(new IntegerResultNode(8)))
+ .addArg(new ConstantNode(new IntegerResultNode(9))));
+ t.assertMatch(new FixedWidthBucketFunctionNode());
+ t.assertMatch(new FixedWidthBucketFunctionNode().addArg(new AttributeNode("foo")));
+ t.assertMatch(new FixedWidthBucketFunctionNode(new IntegerResultNode(10), new AttributeNode("foo")));
+ t.assertMatch(new FixedWidthBucketFunctionNode(new FloatResultNode(10.0), new AttributeNode("foo")));
+ t.assertMatch(new RangeBucketPreDefFunctionNode());
+ t.assertMatch(new RangeBucketPreDefFunctionNode().addArg(new AttributeNode("foo")));
+ t.assertMatch(new DebugWaitFunctionNode(new ConstantNode(new IntegerResultNode(5)),
+ 3.3, false));
+ }
+
+ }
+
+ @Test
+ public void testAggregatorResults() throws IOException {
+ try (SerializationTester t = new SerializationTester("testAggregatorResults")) {
+ t.assertMatch(new SumAggregationResult(new IntegerResultNode(7))
+ .setExpression(new AttributeNode("attributeA")));
+ t.assertMatch(new XorAggregationResult()
+ .setXor(7)
+ .setExpression(new AttributeNode("attributeA")));
+ t.assertMatch(new CountAggregationResult()
+ .setCount(7)
+ .setExpression(new AttributeNode("attributeA")));
+ t.assertMatch(new MinAggregationResult(new IntegerResultNode(7))
+ .setExpression(new AttributeNode("attributeA")));
+ t.assertMatch(new MaxAggregationResult(new IntegerResultNode(7))
+ .setExpression(new AttributeNode("attributeA")));
+ t.assertMatch(new AverageAggregationResult(new IntegerResultNode(7), 0)
+ .setExpression(new AttributeNode("attributeA")));
+ SparseSketch sketch = new SparseSketch();
+ sketch.aggregate(1955583074);
+ t.assertMatch(new ExpressionCountAggregationResult(sketch, s -> 42)
+ .setExpression(new ConstantNode(new IntegerResultNode(67))));
+ }
+ }
+
+ @Test
+ public void testHitCollection() throws IOException {
+ try (SerializationTester t = new SerializationTester("testHitCollection")) {
+ t.assertMatch(new FS4Hit(0, new GlobalId(new byte[GlobalId.LENGTH]), 0, -1));
+ t.assertMatch(new FS4Hit(0, createGlobalId(100), 50.0, -1));
+ t.assertMatch(new VdsHit());
+ //TODO Verify the two structures below
+ t.assertMatch(new VdsHit("100", new byte[0], 50.0));
+ t.assertMatch(new VdsHit("100", "rawsummary".getBytes(), 50.0));
+ t.assertMatch(new HitsAggregationResult());
+ t.assertMatch(new HitsAggregationResult()
+ .setMaxHits(5)
+ .addHit(new FS4Hit(0, createGlobalId(10), 1.0, -1))
+ .addHit(new FS4Hit(0, createGlobalId(20), 2.0, -1))
+ .addHit(new FS4Hit(0, createGlobalId(30), 3.0, -1))
+ .addHit(new FS4Hit(0, createGlobalId(40), 4.0, -1))
+ .addHit(new FS4Hit(0, createGlobalId(50), 5.0, -1))
+ .setExpression(new ConstantNode(new IntegerResultNode(5))));
+ t.assertMatch(new HitsAggregationResult()
+ .setMaxHits(3)
+ .addHit(new FS4Hit(0, createGlobalId(10), 1.0, 100))
+ .addHit(new FS4Hit(0, createGlobalId(20), 2.0, 200))
+ .addHit(new FS4Hit(0, createGlobalId(30), 3.0, 300))
+ .setExpression(new ConstantNode(new IntegerResultNode(5))));
+ //TODO Verify content
+ t.assertMatch(new HitsAggregationResult()
+ .setMaxHits(3)
+ .addHit(new VdsHit("10", "100".getBytes(), 1.0))
+ .addHit(new VdsHit("20", "200".getBytes(), 2.0))
+ .addHit(new VdsHit("30", "300".getBytes(), 3.0))
+ .setExpression(new ConstantNode(new IntegerResultNode(5))));
+ }
+ }
+
+ @Test
+ public void testGroupingLevel() throws IOException {
+ try (SerializationTester t = new SerializationTester("testGroupingLevel")) {
+ GroupingLevel groupingLevel = new GroupingLevel();
+ groupingLevel.setMaxGroups(100)
+ .setExpression(createDummyExpression())
+ .getGroupPrototype()
+ .addAggregationResult(
+ new SumAggregationResult()
+ .setExpression(createDummyExpression()));
+ t.assertMatch(groupingLevel);
+ }
+ }
+
+ @Test
+ public void testGroup() throws IOException {
+ try (SerializationTester t = new SerializationTester("testGroup")) {
+ t.assertMatch(new Group());
+ t.assertMatch(new Group().setId(new IntegerResultNode(50))
+ .setRank(10));
+ t.assertMatch(new Group().setId(new IntegerResultNode(100))
+ .addChild(new Group().setId(new IntegerResultNode(110)))
+ .addChild(new Group().setId(new IntegerResultNode(120))
+ .setRank(20.5)
+ .addAggregationResult(new SumAggregationResult()
+ .setExpression(createDummyExpression()))
+ .addAggregationResult(new SumAggregationResult()
+ .setExpression(createDummyExpression())))
+ .addChild(new Group().setId(new IntegerResultNode(130))
+ .addChild(new Group().setId(new IntegerResultNode(131)))));
+ }
+ }
+
+ @Test
+ public void testGrouping() throws IOException {
+ try (SerializationTester t = new SerializationTester("testGrouping")) {
+ t.assertMatch(new Grouping());
+
+ GroupingLevel level1 = new GroupingLevel();
+ level1.setMaxGroups(100)
+ .setExpression(createDummyExpression())
+ .getGroupPrototype()
+ .addAggregationResult(
+ new SumAggregationResult()
+ .setExpression(createDummyExpression()));
+ GroupingLevel level2 = new GroupingLevel();
+ level2.setMaxGroups(10)
+ .setExpression(createDummyExpression())
+ .getGroupPrototype()
+ .addAggregationResult(
+ new SumAggregationResult()
+ .setExpression(createDummyExpression()))
+ .addAggregationResult(
+ new SumAggregationResult()
+ .setExpression(createDummyExpression()));
+ t.assertMatch(new Grouping()
+ .addLevel(level1)
+ .addLevel(level2));
+
+ GroupingLevel level3 = new GroupingLevel();
+ level3.setExpression(new AttributeNode("folder"))
+ .getGroupPrototype()
+ .addAggregationResult(
+ new XorAggregationResult()
+ .setExpression(new MD5BitFunctionNode(new AttributeNode("docid"), 64)))
+ .addAggregationResult(
+ new SumAggregationResult()
+ .setExpression(new MinFunctionNode()
+ .addArg(new AttributeNode("attribute1"))
+ .addArg(new AttributeNode("attribute2"))))
+ .addAggregationResult(
+ new XorAggregationResult()
+ .setExpression(
+ new XorBitFunctionNode(new CatFunctionNode()
+ .addArg(new GetDocIdNamespaceSpecificFunctionNode(new StringResultNode("")))
+ .addArg(new DocumentFieldNode("folder"))
+ .addArg(new DocumentFieldNode("flags")), 64)));
+ t.assertMatch(new Grouping()
+ .addLevel(level3));
+ }
+ }
+
+
+ private static GlobalId createGlobalId(int docId) {
+ return new GlobalId(
+ new DocumentId(String.format("doc:test:%d", docId)).getGlobalId());
+ }
+
+ private static ExpressionNode createDummyExpression() {
+ return new AddFunctionNode()
+ .addArg(new ConstantNode(new IntegerResultNode(2)))
+ .addArg(new ConstantNode(new IntegerResultNode(2)));
+ }
+
+ private static class SerializationTester implements AutoCloseable {
+
+ private static final String FILE_PATH = "src/test/files";
+
+ private final DataInputStream in;
+ private final String fileName;
+
+ public SerializationTester(String fileName) throws IOException {
+ this.fileName = fileName;
+ this.in = new DataInputStream(
+ new BufferedInputStream(
+ new FileInputStream(
+ new File(FILE_PATH, fileName))));
+ }
+
+ public SerializationTester assertMatch(Identifiable expectedObject) throws IOException {
+ int length = readLittleEndianInt(in);
+ byte[] originalData = new byte[length];
+ in.readFully(originalData);
+ Identifiable deserializedObject = Identifiable.create(new BufferSerializer(originalData));
+
+ if (!deserializedObject.equals(expectedObject)) {
+ fail(String.format("Serialized object in file '%s' does not equal expected values.\n" +
+ "==================================================\n" +
+ "Expected:\n" +
+ "==================================================\n" +
+ "%s\n" +
+ "==================================================\n" +
+ "Actual:\n" +
+ "==================================================\n" +
+ "%s\n" +
+ "==================================================\n",
+ fileName, dumpObject(expectedObject), dumpObject(deserializedObject)));
+ }
+ GrowableByteBuffer buffer = new GrowableByteBuffer(1024 * 8);
+ BufferSerializer serializer = new BufferSerializer(buffer);
+ deserializedObject.serializeWithId(serializer);
+ buffer.flip();
+
+ byte[] newData = new byte[buffer.limit()];
+ buffer.get(newData);
+ if (!Arrays.equals(newData, originalData)) {
+ fail(String.format("Serialized object data does not match the original serialized data from file.\n" +
+ "==================================================\n" +
+ "Original:\n" +
+ "==================================================\n" +
+ "%s\n" +
+ "==================================================\n" +
+ "Serialized:\n" +
+ "==================================================\n" +
+ "%s\n" +
+ "==================================================\n",
+ toHexString(originalData), toHexString(newData)));
+ }
+ return this;
+ }
+
+ private static int readLittleEndianInt(DataInputStream in) throws IOException {
+ byte[] data = new byte[4];
+ in.readFully(data);
+ ByteBuffer buffer = ByteBuffer.wrap(data);
+ buffer.order(ByteOrder.LITTLE_ENDIAN);
+ return buffer.getInt();
+ }
+
+ private static String dumpObject(Identifiable obj) {
+ ObjectDumper dumper = new ObjectDumper();
+ obj.visitMembers(dumper);
+ return dumper.toString();
+ }
+
+ @Override
+ public void close() throws IOException {
+ boolean moreDataAvailable = in.read() != -1;
+ in.close();
+ if (moreDataAvailable) {
+ fail("The file was not fully consumed. Did you forget to deserialize an object on Java side?");
+ }
+ }
+
+ private static String toHexString(byte[] data) {
+ char[] table = "0123456789ABCDEF".toCharArray();
+ StringBuilder builder = new StringBuilder();
+ builder.append("(" + data.length + " bytes)");
+ for (int i = 0; i < data.length; i++) {
+ if (i % 16 == 0) {
+ builder.append("\n");
+ }
+ builder.append(table[(data[i] >> 4) & 0xf]);
+ builder.append(table[data[i] & 0xf]);
+ builder.append(" ");
+ }
+ return builder.toString();
+ }
+
+
+ }
+
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/aggregation/GroupingTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/aggregation/GroupingTestCase.java
new file mode 100644
index 00000000000..f4ae62265d7
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/aggregation/GroupingTestCase.java
@@ -0,0 +1,227 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.aggregation;
+
+import com.yahoo.searchlib.expression.NullResultNode;
+import com.yahoo.searchlib.expression.StringBucketResultNode;
+import com.yahoo.vespa.objects.BufferSerializer;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class GroupingTestCase {
+
+ private static final int VALID_BYTE_INDEX = 8;
+
+ @Test
+ public void requireThatIdAccessorsWork() {
+ Grouping grouping = new Grouping();
+ assertEquals(0, grouping.getId());
+
+ grouping = new Grouping(6);
+ assertEquals(6, grouping.getId());
+ grouping.setId(9);
+ assertEquals(9, grouping.getId());
+
+ Grouping other = new Grouping(6);
+ assertFalse(grouping.equals(other));
+ other.setId(9);
+ assertEquals(grouping, other);
+
+ assertEquals(grouping, grouping.clone());
+ assertSerialize(grouping);
+ }
+
+ @Test
+ public void requireThatAllAccessorsWork() {
+ Grouping grouping = new Grouping();
+ assertFalse(grouping.getAll());
+ grouping.setAll(true);
+ assertTrue(grouping.getAll());
+
+ Grouping other = new Grouping();
+ assertFalse(grouping.equals(other));
+ other.setAll(true);
+ assertEquals(grouping, other);
+
+ assertEquals(grouping, grouping.clone());
+ assertSerialize(grouping);
+ }
+
+ @Test
+ public void requireThatTopNAccessorsWork() {
+ Grouping grouping = new Grouping();
+ assertEquals(-1, grouping.getTopN());
+ grouping.setTopN(69);
+ assertEquals(69, grouping.getTopN());
+
+ Grouping other = new Grouping();
+ assertFalse(grouping.equals(other));
+ other.setTopN(69);
+ assertEquals(grouping, other);
+
+ assertEquals(grouping, grouping.clone());
+ assertSerialize(grouping);
+ }
+
+ @Test
+ public void requireThatFirstLevelAccessorsWork() {
+ Grouping grouping = new Grouping();
+ assertEquals(0, grouping.getFirstLevel());
+ grouping.setFirstLevel(69);
+ assertEquals(69, grouping.getFirstLevel());
+
+ Grouping other = new Grouping();
+ assertFalse(grouping.equals(other));
+ other.setFirstLevel(69);
+ assertEquals(grouping, other);
+
+ assertEquals(grouping, grouping.clone());
+ assertSerialize(grouping);
+ }
+
+ @Test
+ public void requireThatLastLevelAccessorsWork() {
+ Grouping grouping = new Grouping();
+ assertEquals(0, grouping.getLastLevel());
+ grouping.setLastLevel(69);
+ assertEquals(69, grouping.getLastLevel());
+
+ Grouping other = new Grouping();
+ assertFalse(grouping.equals(other));
+ other.setLastLevel(69);
+ assertEquals(grouping, other);
+
+ assertEquals(grouping, grouping.clone());
+ assertSerialize(grouping);
+ }
+
+ @Test
+ public void requireThatRootAccessorsWork() {
+ Grouping grouping = new Grouping();
+ assertEquals(new Group(), grouping.getRoot());
+ try {
+ grouping.setRoot(null);
+ fail();
+ } catch (NullPointerException e) {
+
+ }
+ Group root = new Group().setRank(6.9);
+ grouping.setRoot(root);
+ assertEquals(root, grouping.getRoot());
+
+ Grouping other = new Grouping();
+ assertFalse(grouping.equals(other));
+ other.setRoot(root);
+ assertEquals(grouping, other);
+
+ assertEquals(grouping, grouping.clone());
+ assertSerialize(grouping);
+ }
+
+ @Test
+ public void requireThatLevelAccessorsWork() {
+ Grouping grouping = new Grouping();
+ assertEquals(Collections.emptyList(), grouping.getLevels());
+ try {
+ grouping.addLevel(null);
+ fail();
+ } catch (NullPointerException e) {
+
+ }
+ GroupingLevel level = new GroupingLevel();
+ grouping.addLevel(level);
+ assertEquals(Arrays.asList(level), grouping.getLevels());
+
+ Grouping other = new Grouping();
+ assertFalse(grouping.equals(other));
+ other.addLevel(level);
+ assertEquals(grouping, other);
+
+ assertEquals(grouping, grouping.clone());
+ assertSerialize(grouping);
+ }
+
+ @Test
+ public void requireThatHashCodeIsImplemented() {
+ assertEquals(new Grouping().hashCode(), new Grouping().hashCode());
+ }
+
+ @Test
+ public void requireThatEqualsIsImplemented() {
+ assertFalse(new Grouping().equals(new Object()));
+ assertTrue(new Grouping().equals(new Grouping()));
+ }
+
+ @Test
+ public void requireThatValidAccessorsWork() {
+ byte[] arr = new byte[1024];
+ BufferSerializer buf = new BufferSerializer(arr);
+ Grouping grouping = new Grouping();
+ grouping.serializeWithId(buf);
+ buf.flip();
+ assertEquals(1, arr[VALID_BYTE_INDEX]);
+ arr[VALID_BYTE_INDEX] = 0;
+
+ Grouping other = (Grouping)Grouping.create(buf);
+ assertFalse(other.valid());
+
+ assertEquals(grouping, grouping.clone());
+ assertSerialize(grouping);
+ }
+
+ @Test
+ public void requireThatSetForceSinglePassWorks() {
+ assertFalse(new Grouping().getForceSinglePass());
+ assertFalse(new Grouping().setForceSinglePass(false).getForceSinglePass());
+ assertTrue(new Grouping().setForceSinglePass(true).getForceSinglePass());
+ }
+
+ @Test
+ public void requireThatNeedDeepResultCollectionWorks() {
+ assertFalse(new Grouping().addLevel(new GroupingLevel().setGroupPrototype(new Group())).needDeepResultCollection());
+ assertTrue(new Grouping().addLevel(new GroupingLevel().setGroupPrototype(new Group().addOrderBy(new CountAggregationResult(9), true))).needDeepResultCollection());
+ }
+
+ @Test
+ public void requireThatUseSinglePassWorks() {
+ assertFalse(new Grouping().useSinglePass());
+ assertFalse(new Grouping().setForceSinglePass(false).useSinglePass());
+ assertTrue(new Grouping().setForceSinglePass(true).useSinglePass());
+ assertFalse(new Grouping().addLevel(new GroupingLevel().setGroupPrototype(new Group())).useSinglePass());
+ assertTrue(new Grouping().addLevel(new GroupingLevel().setGroupPrototype(new Group().addOrderBy(new CountAggregationResult(9), true))).useSinglePass());
+ }
+
+ @Test
+ public void requireThatUnifyNullReplacesEmptyBucketIds() {
+ Grouping grouping = new Grouping();
+ grouping.getRoot().addChild(new Group().setId(new StringBucketResultNode()));
+ grouping.setLastLevel(1); // otherwise unifyNull will not operate on it
+ grouping.unifyNull();
+ assertEquals(NullResultNode.class, grouping.getRoot().getChildren().get(0).getId().getClass());
+ }
+
+ @Test
+ public void requireThatUnifyNullDoesNotReplaceNonEmptyBucketIds() {
+ Grouping grouping = new Grouping();
+ grouping.getRoot().addChild(new Group().setId(new StringBucketResultNode("6", "9")));
+ grouping.setLastLevel(1); // otherwise unifyNull will not operate on it
+ grouping.unifyNull();
+ assertEquals(StringBucketResultNode.class, grouping.getRoot().getChildren().get(0).getId().getClass());
+ }
+
+ private static void assertSerialize(Grouping grouping) {
+ BufferSerializer buf = new BufferSerializer();
+ grouping.serializeWithId(buf);
+
+ buf.flip();
+ Grouping other = (Grouping)Grouping.create(buf);
+ assertEquals(grouping, other);
+ }
+}
+
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/aggregation/MergeTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/aggregation/MergeTestCase.java
new file mode 100755
index 00000000000..67361048773
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/aggregation/MergeTestCase.java
@@ -0,0 +1,735 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.aggregation;
+
+import com.yahoo.document.DocumentId;
+import com.yahoo.document.GlobalId;
+import com.yahoo.searchlib.expression.*;
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class MergeTestCase extends TestCase {
+
+ private GlobalId createGlobalId(int docId) {
+ return new GlobalId((new DocumentId("doc:test:" + docId)).getGlobalId());
+ }
+
+ // Test merging of hits.
+ public void testMergeHits() {
+ Grouping request = new Grouping()
+ .setFirstLevel(0)
+ .setLastLevel(1)
+ .addLevel(new GroupingLevel().setMaxGroups(69));
+
+ Group expect = new Group()
+ .addAggregationResult(new HitsAggregationResult()
+ .setMaxHits(5)
+ .addHit(new FS4Hit(30, createGlobalId(30), 30))
+ .addHit(new FS4Hit(20, createGlobalId(20), 20))
+ .addHit(new FS4Hit(10, createGlobalId(10), 10))
+ .addHit(new FS4Hit(5, createGlobalId(9), 9))
+ .addHit(new FS4Hit(6, createGlobalId(8), 8))
+ .setExpression(new ConstantNode( new IntegerResultNode(0))));
+
+ Group a = new Group()
+ .addAggregationResult(new HitsAggregationResult()
+ .setMaxHits(5)
+ .addHit(new FS4Hit(10, createGlobalId(10), 10))
+ .addHit(new FS4Hit(1, createGlobalId(5), 5))
+ .addHit(new FS4Hit(2, createGlobalId(4), 4))
+ .setExpression(new ConstantNode( new IntegerResultNode(0))));
+
+ Group b = new Group()
+ .addAggregationResult(new HitsAggregationResult()
+ .setMaxHits(5)
+ .addHit(new FS4Hit(20, createGlobalId(20), 20))
+ .addHit(new FS4Hit(3, createGlobalId(7), 7))
+ .addHit(new FS4Hit(4, createGlobalId(6), 6))
+ .setExpression(new ConstantNode( new IntegerResultNode(0))));
+
+ Group c = new Group()
+ .addAggregationResult(new HitsAggregationResult()
+ .setMaxHits(5)
+ .addHit(new FS4Hit(30, createGlobalId(30), 30))
+ .addHit(new FS4Hit(5, createGlobalId(9), 9))
+ .addHit(new FS4Hit(6, createGlobalId(8), 8))
+ .setExpression(new ConstantNode( new IntegerResultNode(0))));
+
+ assertMerge(request, a, b, c, expect);
+ assertMerge(request, a, c, b, expect);
+ assertMerge(request, b, a, c, expect);
+ assertMerge(request, c, a, b, expect);
+ assertMerge(request, b, c, a, expect);
+ assertMerge(request, c, b, a, expect);
+ }
+
+ // Test merging the sum of the values from a single attribute vector that was collected directly into the root node.
+ public void testMergeSimpleSum() {
+ Grouping lhs = new Grouping()
+ .setRoot(new Group()
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(20))
+ .setExpression(new AttributeNode("foo"))));
+
+ Grouping rhs = new Grouping()
+ .setRoot(new Group()
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(30))
+ .setExpression(new AttributeNode("foo"))));
+
+ Group expect = new Group()
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(50))
+ .setExpression(new AttributeNode("foo")));
+
+ assertMerge(lhs, rhs, expect);
+ }
+
+ // Test merging of the value from a single attribute vector in level 1.
+ public void testMergeSingleChild() {
+ Grouping lhs = new Grouping()
+ .setFirstLevel(0)
+ .setLastLevel(1)
+ .setRoot(new Group().addChild(new Group()
+ .setId(new StringResultNode("foo"))
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(20))
+ .setExpression(new AttributeNode("foo")))));
+
+ Grouping rhs = new Grouping()
+ .setFirstLevel(0)
+ .setLastLevel(1)
+ .setRoot(new Group().addChild(new Group()
+ .setId(new StringResultNode("foo"))
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(30))
+ .setExpression(new AttributeNode("foo")))));
+
+ Group expect = new Group().addChild(new Group()
+ .setId(new StringResultNode("foo"))
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(50))
+ .setExpression(new AttributeNode("foo"))));
+
+ assertMerge(lhs, rhs, expect);
+ }
+
+ // Test merging of the value from a multiple attribute vectors in level 1.
+ public void testMergeMultiChild() {
+ Grouping lhs = new Grouping()
+ .setFirstLevel(0)
+ .setLastLevel(1)
+ .setRoot(new Group()
+ .addChild(new Group()
+ .setId(new StringResultNode("foo"))
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(20))
+ .setExpression(new AttributeNode("foo"))))
+ .addChild(new Group()
+ .setId(new StringResultNode("bar"))
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(40))
+ .setExpression(new AttributeNode("foo")))));
+
+ Grouping rhs = new Grouping()
+ .setFirstLevel(0)
+ .setLastLevel(1)
+ .setRoot(new Group()
+ .addChild(new Group()
+ .setId(new StringResultNode("foo"))
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(30))
+ .setExpression(new AttributeNode("foo"))))
+ .addChild(new Group()
+ .setId(new StringResultNode("baz"))
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(30))
+ .setExpression(new AttributeNode("foo")))));
+
+ Group expect = new Group().addChild(
+ new Group()
+ .setId(new StringResultNode("foo"))
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(50))
+ .setExpression(new AttributeNode("foo"))))
+ .addChild(new Group()
+ .setId(new StringResultNode("bar"))
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(40))
+ .setExpression(new AttributeNode("foo"))))
+ .addChild(new Group()
+ .setId(new StringResultNode("baz"))
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(30))
+ .setExpression(new AttributeNode("foo"))));
+
+ assertMerge(lhs, rhs, expect);
+ }
+
+ // Verify that frozen levels are not touched during merge.
+ public void testMergeLevels() {
+ Grouping request = new Grouping()
+ .addLevel(new GroupingLevel()
+ .setExpression(new AttributeNode("c1"))
+ .setGroupPrototype(new Group().addAggregationResult(
+ new SumAggregationResult().setExpression(new AttributeNode("s1")))))
+ .addLevel(new GroupingLevel()
+ .setExpression(new AttributeNode("c2"))
+ .setGroupPrototype(new Group().addAggregationResult(
+ new SumAggregationResult().setExpression(new AttributeNode("s2")))))
+ .addLevel(new GroupingLevel()
+ .setExpression(new AttributeNode("c3"))
+ .setGroupPrototype(new Group().addAggregationResult(
+ new SumAggregationResult().setExpression(new AttributeNode("s3")))));
+
+ Group lhs = new Group()
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(5))
+ .setExpression(new AttributeNode("s0")))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(10))
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(10))
+ .setExpression(new AttributeNode("s1")))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(20))
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(15))
+ .setExpression(new AttributeNode("s2")))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(30))
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(20))
+ .setExpression(new AttributeNode("s3"))))));
+
+ Group rhs = new Group()
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(5))
+ .setExpression(new AttributeNode("s0")))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(10))
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(10))
+ .setExpression(new AttributeNode("s1")))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(20))
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(15))
+ .setExpression(new AttributeNode("s2")))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(30))
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(20))
+ .setExpression(new AttributeNode("s3"))))));
+
+ Group expectAll = new Group()
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(10))
+ .setExpression(new AttributeNode("s0")))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(10))
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(20))
+ .setExpression(new AttributeNode("s1")))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(20))
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(30))
+ .setExpression(new AttributeNode("s2")))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(30))
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(40))
+ .setExpression(new AttributeNode("s3"))))));
+
+ Group expect0 = new Group()
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(5))
+ .setExpression(new AttributeNode("s0")))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(10))
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(20))
+ .setExpression(new AttributeNode("s1")))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(20))
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(30))
+ .setExpression(new AttributeNode("s2")))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(30))
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(40))
+ .setExpression(new AttributeNode("s3"))))));
+
+ Group expect1 = new Group()
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(5))
+ .setExpression(new AttributeNode("s0")))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(10))
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(10))
+ .setExpression(new AttributeNode("s1")))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(20))
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(30))
+ .setExpression(new AttributeNode("s2")))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(30))
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(40))
+ .setExpression(new AttributeNode("s3"))))));
+
+ Group expect2 = new Group()
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(5))
+ .setExpression(new AttributeNode("s0")))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(10))
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(10))
+ .setExpression(new AttributeNode("s1")))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(20))
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(15))
+ .setExpression(new AttributeNode("s2")))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(30))
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(40))
+ .setExpression(new AttributeNode("s3"))))));
+
+ Group expect3 = new Group()
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(5))
+ .setExpression(new AttributeNode("s0")))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(10))
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(10))
+ .setExpression(new AttributeNode("s1")))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(20))
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(15))
+ .setExpression(new AttributeNode("s2")))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(30))
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(20))
+ .setExpression(new AttributeNode("s3"))))));
+
+ request.setFirstLevel(0).setLastLevel(3);
+ assertMerge(request, lhs, rhs, expectAll);
+ request.setFirstLevel(1).setLastLevel(3);
+ assertMerge(request, lhs, rhs, expect0);
+ request.setFirstLevel(2).setLastLevel(5);
+ assertMerge(request, lhs, rhs, expect1);
+ request.setFirstLevel(3).setLastLevel(5);
+ assertMerge(request, lhs, rhs, expect2);
+ request.setFirstLevel(4).setLastLevel(4);
+ assertMerge(request, lhs, rhs, expect3);
+ }
+
+ // Verify that the number of groups for a level is pruned down to maxGroups, that the remaining groups are the
+ // highest ranked ones, and that they are sorted by group id.
+ public void testMergeGroups() {
+ Grouping request = new Grouping()
+ .addLevel(new GroupingLevel()
+ .setExpression(new AttributeNode("attr")));
+ Group lhs = new Group()
+ .addChild(new Group().setId(new IntegerResultNode(5)).setRank(5))
+ .addChild(new Group().setId(new IntegerResultNode(10)).setRank(5))
+ .addChild(new Group().setId(new IntegerResultNode(15)).setRank(15))
+ .addChild(new Group().setId(new IntegerResultNode(40)).setRank(100))
+ .addChild(new Group().setId(new IntegerResultNode(50)).setRank(30));
+
+ Group rhs = new Group()
+ .addChild(new Group().setId(new IntegerResultNode(0)).setRank(10))
+ .addChild(new Group().setId(new IntegerResultNode(10)).setRank(50))
+ .addChild(new Group().setId(new IntegerResultNode(20)).setRank(25))
+ .addChild(new Group().setId(new IntegerResultNode(40)).setRank(10))
+ .addChild(new Group().setId(new IntegerResultNode(45)).setRank(20));
+
+ Group expect3 = new Group()
+ .addChild(new Group().setId(new IntegerResultNode(10)).setRank(50))
+ .addChild(new Group().setId(new IntegerResultNode(40)).setRank(100))
+ .addChild(new Group().setId(new IntegerResultNode(50)).setRank(30));
+
+ Group expect5 = new Group()
+ .addChild(new Group().setId(new IntegerResultNode(10)).setRank(50))
+ .addChild(new Group().setId(new IntegerResultNode(20)).setRank(25))
+ .addChild(new Group().setId(new IntegerResultNode(40)).setRank(100))
+ .addChild(new Group().setId(new IntegerResultNode(45)).setRank(20))
+ .addChild(new Group().setId(new IntegerResultNode(50)).setRank(30));
+
+ Group expectAll = new Group()
+ .addChild(new Group().setId(new IntegerResultNode(0)).setRank(10))
+ .addChild(new Group().setId(new IntegerResultNode(5)).setRank(5))
+ .addChild(new Group().setId(new IntegerResultNode(10)).setRank(50))
+ .addChild(new Group().setId(new IntegerResultNode(15)).setRank(15))
+ .addChild(new Group().setId(new IntegerResultNode(20)).setRank(25))
+ .addChild(new Group().setId(new IntegerResultNode(40)).setRank(100))
+ .addChild(new Group().setId(new IntegerResultNode(45)).setRank(20))
+ .addChild(new Group().setId(new IntegerResultNode(50)).setRank(30));
+
+ request.getLevels().get(0).setMaxGroups(3);
+ assertMerge(request, lhs, rhs, expect3);
+ assertMerge(request, rhs, lhs, expect3);
+
+ request.getLevels().get(0).setMaxGroups(5);
+ assertMerge(request, lhs, rhs, expect5);
+ assertMerge(request, rhs, lhs, expect5);
+
+ request.getLevels().get(0).setMaxGroups(-1);
+ assertMerge(request, lhs, rhs, expectAll);
+ assertMerge(request, rhs, lhs, expectAll);
+ }
+
+ public void testMergeBuckets() {
+ Grouping lhs = new Grouping()
+ .setRoot(new Group().setTag(0)
+ .addChild(new Group().setId(new FloatBucketResultNode(FloatResultNode.getNegativeInfinity().getFloat(), 0.4))
+ .addAggregationResult(new CountAggregationResult().setCount(1))
+ .setTag(1))
+ .addChild(new Group().setId(new FloatBucketResultNode(0, 0))
+ .addAggregationResult(new CountAggregationResult().setCount(12))
+ .setTag(1)));
+
+ Grouping rhs = new Grouping()
+ .setRoot(new Group().setTag(0)
+ .addChild(new Group().setId(new FloatBucketResultNode(FloatResultNode.getNegativeInfinity().getFloat(), 0.4))
+ .addAggregationResult(new CountAggregationResult().setCount(0))
+ .setTag(1))
+ .addChild(new Group().setId(new FloatBucketResultNode(0, 0))
+ .addAggregationResult(new CountAggregationResult().setCount(15))
+ .setTag(1)));
+
+ Group expected = new Group().setTag(0)
+ .addChild(new Group().setId(new FloatBucketResultNode(FloatResultNode.getNegativeInfinity().getFloat(), 0.4))
+ .addAggregationResult(new CountAggregationResult().setCount(1))
+ .setTag(1))
+ .addChild(new Group().setId(new FloatBucketResultNode(0, 0))
+ .addAggregationResult(new CountAggregationResult().setCount(27))
+ .setTag(1));
+ assertMerge(lhs, rhs, expected);
+ }
+
+ // Merge two trees that are ordered by an expression, and verify that the resulting order after merge is correct.
+ public void testMergeExpressions() {
+ Grouping a = new Grouping()
+ .setFirstLevel(0)
+ .setLastLevel(1)
+ .addLevel(new GroupingLevel().setMaxGroups(1))
+ .setRoot(new Group()
+ .addChild(new Group().setId(new StringResultNode("aa"))
+ .addAggregationResult(new MaxAggregationResult().setMax(new IntegerResultNode(9)))
+ .addAggregationResult(new CountAggregationResult().setCount(2))
+ .addOrderBy(new MultiplyFunctionNode().addArg(new AggregationRefNode(0))
+ .addArg(new AggregationRefNode(1)), true)));
+ Grouping b = new Grouping()
+ .setFirstLevel(0)
+ .setLastLevel(1)
+ .addLevel(new GroupingLevel().setMaxGroups(1))
+ .setRoot(new Group()
+ .addChild(new Group().setId(new StringResultNode("ab"))
+ .addAggregationResult(new MaxAggregationResult().setMax(
+ new IntegerResultNode(12)))
+ .addAggregationResult(new CountAggregationResult().setCount(1))
+ .addOrderBy(new MultiplyFunctionNode().addArg(new AggregationRefNode(0))
+ .addArg(new AggregationRefNode(1)), true)));
+
+ Grouping expected = new Grouping()
+ .setFirstLevel(0)
+ .setLastLevel(1)
+ .addLevel(new GroupingLevel().setMaxGroups(1))
+ .setRoot(new Group()
+ .addChild(new Group().setId(new StringResultNode("ab"))
+ .addAggregationResult(new MaxAggregationResult().setMax(
+ new IntegerResultNode(12)))
+ .addAggregationResult(new CountAggregationResult().setCount(1))
+ .addOrderBy(new MultiplyFunctionNode().addArg(new AggregationRefNode(0))
+ .addArg(new AggregationRefNode(1)), true)));
+ expected.postMerge();
+
+ a.merge(b);
+ a.postMerge();
+ assertEquals(expected.toString(), a.toString());
+ }
+
+ // Merge two relatively complex tree structures and verify that the end result is as expected.
+ public void testMergeTrees() {
+ Grouping request = new Grouping()
+ .addLevel(new GroupingLevel()
+ .setMaxGroups(3)
+ .setExpression(new AttributeNode("c1"))
+ .setGroupPrototype(new Group().addAggregationResult(
+ new SumAggregationResult().setExpression(new AttributeNode("s1")))))
+ .addLevel(new GroupingLevel()
+ .setMaxGroups(2)
+ .setExpression(new AttributeNode("c2"))
+ .setGroupPrototype(new Group().addAggregationResult(
+ new SumAggregationResult().setExpression(new AttributeNode("s2")))))
+ .addLevel(new GroupingLevel()
+ .setMaxGroups(1)
+ .setExpression(new AttributeNode("c3"))
+ .setGroupPrototype(new Group().addAggregationResult(
+ new SumAggregationResult().setExpression(new AttributeNode("s3")))));
+
+ Group lhs = new Group()
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(100))
+ .setExpression(new AttributeNode("s0")))
+ .addChild(new Group().setId(new IntegerResultNode(4)).setRank(10))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(5))
+ .setRank(5) // merged with 200 rank node
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(100))
+ .setExpression(new AttributeNode("s1")))
+ .addChild(new Group().setId(new IntegerResultNode(4)).setRank(10))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(5))
+ .setRank(500)
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(100))
+ .setExpression(new AttributeNode("s2")))
+ .addChild(new Group().setId(new IntegerResultNode(4)).setRank(10))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(5))
+ .setRank(200)
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(100))
+ .setExpression(new AttributeNode("s3"))))))
+ .addChild(new Group().setId(new IntegerResultNode(9)).setRank(10))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(10))
+ .setRank(100)
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(100))
+ .setExpression(new AttributeNode("s1")))
+ // dummy child would be picked up here
+ .addChild(new Group()
+ .setId(new IntegerResultNode(15))
+ .setRank(200)
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(100))
+ .setExpression(new AttributeNode("s2")))
+ .addChild(new Group().setId(new IntegerResultNode(14)).setRank(10))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(15))
+ .setRank(300)
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(100))
+ .setExpression(new AttributeNode("s3"))))))
+ .addChild(new Group().setId(new IntegerResultNode(14)).setRank(10))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(15))
+ .setRank(300)
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(100))
+ .setExpression(new AttributeNode("s1")))
+ .addChild(new Group().setId(new IntegerResultNode(19)).setRank(10))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(20))
+ .setRank(100)
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(100))
+ .setExpression(new AttributeNode("s2")))));
+
+ Group rhs = new Group()
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(100))
+ .setExpression(new AttributeNode("s0")))
+ .addChild(new Group().setId(new IntegerResultNode(4)).setRank(10))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(5))
+ .setRank(200)
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(100))
+ .setExpression(new AttributeNode("s1")))
+ .addChild(new Group().setId(new IntegerResultNode(9)).setRank(10))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(10))
+ .setRank(400)
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(100))
+ .setExpression(new AttributeNode("s2")))
+ .addChild(new Group().setId(new IntegerResultNode(9)).setRank(10))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(10))
+ .setRank(100)
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(100))
+ .setExpression(new AttributeNode("s3"))))))
+ .addChild(new Group().setId(new IntegerResultNode(9)).setRank(10))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(10))
+ .setRank(100)
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(100))
+ .setExpression(new AttributeNode("s1")))
+ // dummy child would be picket up here
+ .addChild(new Group()
+ .setId(new IntegerResultNode(15))
+ .setRank(200)
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(100))
+ .setExpression(new AttributeNode("s2")))))
+ .addChild(new Group().setId(new IntegerResultNode(14)).setRank(10))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(15))
+ .setRank(5) // merged with 300 rank node
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(100))
+ .setExpression(new AttributeNode("s1")))
+ .addChild(new Group().setId(new IntegerResultNode(19)).setRank(10))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(20))
+ .setRank(5) // merged with 100 rank node
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(100))
+ .setExpression(new AttributeNode("s2")))
+ .addChild(new Group().setId(new IntegerResultNode(19)).setRank(10))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(20))
+ .setRank(500)
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(100))
+ .setExpression(new AttributeNode("s3")))))
+ .addChild(new Group().setId(new IntegerResultNode(24)).setRank(10))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(25))
+ .setRank(300)
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(100))
+ .setExpression(new AttributeNode("s2")))
+ .addChild(new Group().setId(new IntegerResultNode(24)).setRank(10))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(25))
+ .setRank(400)
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(100))
+ .setExpression(new AttributeNode("s3"))))));
+
+ Group expect = new Group()
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(200))
+ .setExpression(new AttributeNode("s0")))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(5))
+ .setRank(200)
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(200))
+ .setExpression(new AttributeNode("s1")))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(5))
+ .setRank(500)
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(100))
+ .setExpression(new AttributeNode("s2")))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(5))
+ .setRank(200)
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(100))
+ .setExpression(new AttributeNode("s3")))))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(10))
+ .setRank(400)
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(100))
+ .setExpression(new AttributeNode("s2")))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(10))
+ .setRank(100)
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(100))
+ .setExpression(new AttributeNode("s3"))))))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(10))
+ .setRank(100)
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(200))
+ .setExpression(new AttributeNode("s1")))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(15))
+ .setRank(200)
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(200))
+ .setExpression(new AttributeNode("s2")))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(15))
+ .setRank(300)
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(100))
+ .setExpression(new AttributeNode("s3"))))))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(15))
+ .setRank(300)
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(200))
+ .setExpression(new AttributeNode("s1")))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(20))
+ .setRank(100)
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(200))
+ .setExpression(new AttributeNode("s2")))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(20))
+ .setRank(500)
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(100))
+ .setExpression(new AttributeNode("s3")))))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(25))
+ .setRank(300)
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(100))
+ .setExpression(new AttributeNode("s2")))
+ .addChild(new Group()
+ .setId(new IntegerResultNode(25))
+ .setRank(400)
+ .addAggregationResult(new SumAggregationResult()
+ .setSum(new IntegerResultNode(100))
+ .setExpression(new AttributeNode("s3"))))));
+
+ assertMerge(request, lhs, rhs, expect);
+ assertMerge(request, rhs, lhs, expect);
+ }
+
+ private static void assertMerge(Grouping request, Group lhs, Group rhs, Group expect) {
+ assertMerge(Arrays.asList(request.clone().setRoot(lhs.clone()),
+ request.clone().setRoot(rhs.clone())),
+ expect);
+ }
+
+ private static void assertMerge(Grouping request, Group a, Group b, Group c, Group expect) {
+ assertMerge(Arrays.asList(request.clone().setRoot(a.clone()),
+ request.clone().setRoot(b.clone()),
+ request.clone().setRoot(c.clone())),
+ expect);
+ }
+
+ private static void assertMerge(Grouping lhs, Grouping rhs, Group expect) {
+ assertMerge(Arrays.asList(lhs, rhs), expect);
+ }
+
+ private static void assertMerge(List<Grouping> groupingList, Group expect) {
+ Grouping tmp = groupingList.get(0).clone();
+ for (int i = 1; i < groupingList.size(); ++i) {
+ tmp.merge(groupingList.get(i));
+ }
+ tmp.postMerge();
+ assertEquals(expect.toString(), tmp.getRoot().toString());
+ assertEquals(expect, tmp.getRoot());
+ }
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/aggregation/hll/BiasEstimatorTest.java b/searchlib/src/test/java/com/yahoo/searchlib/aggregation/hll/BiasEstimatorTest.java
new file mode 100644
index 00000000000..307214d8c1c
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/aggregation/hll/BiasEstimatorTest.java
@@ -0,0 +1,70 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.aggregation.hll;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+public class BiasEstimatorTest {
+
+ @Test
+ public void requireThatExactValueIsReturnedIfAvailable() {
+ BiasEstimator biasEstimator = new BiasEstimator(10);
+ // Index 0 in biasData/rawEstimateData
+ assertEstimateEquals(737.1256, 738.1256, biasEstimator);
+ // Index 10 in biasData/rawEstimateData
+ assertEstimateEquals(612.1992, 868.1992, biasEstimator);
+ // Index 199 (last) in biasData/rawEstimateData
+ assertEstimateEquals(-9.81720000000041, 5084.1828, biasEstimator);
+ }
+
+ @Test
+ public void requireThatBiasEstimatorHandlesAllValidPrecisions() {
+ // Index 0 values for biasData/rawEstimateData for each precision
+ double[][] testValuesForPrecision = new double[][] {
+ {11, 10},
+ {23, 22},
+ {46, 45},
+ {92, 91},
+ {184.2152, 183.2152},
+ {369, 368},
+ {738.1256, 737.1256},
+ {1477, 1476},
+ {2954, 2953},
+ {5908.5052, 5907.5052},
+ {11817.475, 11816.475},
+ {23635.0036, 23634.0036},
+ {47271, 47270},
+ {94542, 94541},
+ {189084, 189083}
+ };
+ for (int p = 4; p <= 18; p++) {
+ assertEstimateEquals(testValuesForPrecision[p - 4][1], testValuesForPrecision[p - 4][0], new BiasEstimator(p));
+ }
+ }
+
+ @Test
+ public void requireThatEdgeCasesAreCorrect() {
+ BiasEstimator estimator = new BiasEstimator(10);
+ // Test with a raw estimate less than first element of rawEstimateData
+ assertEstimateEquals(737.1256, 7, estimator);
+ // Test with a raw estimate larger than last element of rawEstimateData
+ assertEstimateEquals(-9.81720000000041, 9001, estimator);
+ }
+
+ @Test
+ public void requireThatLinearInterpolationIsCorrect() {
+ BiasEstimator estimator = new BiasEstimator(10);
+ double rawEstimate = (738.1256 + 750.4234) / 2; // average of two first elements
+ double expectedBias = (737.1256 + 724.4234) / 2;
+ assertEstimateEquals(expectedBias, rawEstimate, estimator);
+
+ rawEstimate = 3 * 854.7864 / 4 + 868.1992 / 4; // weighted average of element 10 and 11
+ expectedBias = 3 * 623.7864 / 4 + 612.1992 / 4;
+ assertEstimateEquals(expectedBias, rawEstimate, estimator);
+ }
+
+ private static void assertEstimateEquals(double expected, double rawEstimate, BiasEstimator biasEstimator) {
+ assertEquals(expected, biasEstimator.estimateBias(rawEstimate), 0.00000001);
+ }
+} \ No newline at end of file
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/aggregation/hll/HyperLogLogEstimatorTest.java b/searchlib/src/test/java/com/yahoo/searchlib/aggregation/hll/HyperLogLogEstimatorTest.java
new file mode 100644
index 00000000000..1ba4a71d102
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/aggregation/hll/HyperLogLogEstimatorTest.java
@@ -0,0 +1,89 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.aggregation.hll;
+
+import net.jpountz.xxhash.XXHash32;
+import net.jpountz.xxhash.XXHashFactory;
+import org.junit.Test;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+import java.util.Random;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class HyperLogLogEstimatorTest {
+
+ private XXHash32 hashGenerator = XXHashFactory.safeInstance().hash32();
+
+ @Test
+ public void requireThatEstimateInRangeForSmallValueSetUsingNormalSketch() {
+ testEstimateUsingNormalSketch(15, 1337);
+ }
+
+ @Test
+ public void requireThatEstimateInRangeForLargeValueSetUsingNormalSketch() {
+ testEstimateUsingNormalSketch(1_000_000, 1337);
+ }
+
+ @Test
+ public void requireThatEstimateIsReasonableForFullNormalSketch() {
+ HyperLogLogEstimator estimator = new HyperLogLogEstimator(10);
+ NormalSketch sketch = new NormalSketch(10);
+ // Fill sketch with 23 - highest possible zero prefix for precision 10.
+ Arrays.fill(sketch.data(), (byte) 23);
+ long estimate = estimator.estimateCount(sketch);
+ assertTrue(estimate > 6_000_000_000l);
+ }
+
+ @Test
+ public void requireThatEstimateIsCorrectForSparseSketch() {
+ SparseSketch sketch = new SparseSketch();
+ HyperLogLogEstimator estimator = new HyperLogLogEstimator(10);
+ long estimate = estimator.estimateCount(sketch);
+ assertEquals(0, estimate);
+
+ // Check that estimate is correct for every possible sketch size up to threshold
+ for (int i = 1; i <= HyperLogLog.SPARSE_SKETCH_CONVERSION_THRESHOLD; i++) {
+ sketch.aggregate(i);
+ estimate = estimator.estimateCount(sketch);
+ assertEquals(i, estimate);
+ }
+ }
+
+ private void testEstimateUsingNormalSketch(int nValues, int seed) {
+ for (int precision = 4; precision <= 16; precision++) {
+ HyperLogLogEstimator estimator = new HyperLogLogEstimator(precision);
+
+ long uniqueCount = new Random(seed)
+ .ints(nValues)
+ .map(this::makeHash)
+ .distinct()
+ .count();
+
+ Iterable<Integer> hashValues = () ->
+ new Random(seed)
+ .ints(nValues)
+ .map(this::makeHash)
+ .iterator();
+
+ NormalSketch sketch = new NormalSketch(precision);
+ sketch.aggregate(hashValues);
+ long estimate = estimator.estimateCount(sketch);
+ double standardError = standardErrorForPrecision(precision);
+ assertTrue(estimate > uniqueCount * (1 - standardError) * 0.9);
+ assertTrue(estimate < uniqueCount * (1 + standardError) * 1.1);
+ }
+ }
+
+ private static double standardErrorForPrecision(int precision) {
+ return 1.04 / Math.sqrt(1 << precision); // HLL standard error
+ }
+
+
+ private int makeHash(int value) {
+ final int seed = 42424242;
+ byte[] bytes = ByteBuffer.allocate(4).putInt(value).array();
+ return hashGenerator.hash(bytes, 0, 4, seed);
+ }
+} \ No newline at end of file
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/aggregation/hll/HyperLogLogPrecisionBenchmark.java b/searchlib/src/test/java/com/yahoo/searchlib/aggregation/hll/HyperLogLogPrecisionBenchmark.java
new file mode 100644
index 00000000000..5dba5e48578
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/aggregation/hll/HyperLogLogPrecisionBenchmark.java
@@ -0,0 +1,70 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.aggregation.hll;
+
+import net.jpountz.xxhash.XXHash32;
+import net.jpountz.xxhash.XXHashFactory;
+
+import java.nio.ByteBuffer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Random;
+
+/**
+ * This benchmarks performs a series of unique counting tests to analyse the HyperLogLog accuracy.
+ */
+public class HyperLogLogPrecisionBenchmark {
+
+ private static final int MAX_VAL = 256_000;
+ private static final int MAX_ITERATION = 1000;
+
+ private static final XXHash32 hashGenerator = XXHashFactory.safeInstance().hash32();
+ private static final HyperLogLogEstimator estimator = new HyperLogLogEstimator();
+ private static final Random random = new Random(424242);
+
+
+ public static void main(String[] args) {
+ System.out.println("Unique count; Average estimated unique count; Normalized standard error; Standard error; Min; Max");
+ for (int val = 1; val <= MAX_VAL; val *= 2) {
+ List<Long> samples = new ArrayList<>();
+ long sumEstimates = 0;
+ for (int iteration = 0; iteration < MAX_ITERATION; iteration++) {
+ long sample = estimateUniqueCount(val);
+ samples.add(sample);
+ sumEstimates += sample;
+ }
+ double average = sumEstimates / (double) MAX_ITERATION;
+ long min = samples.stream().min(Long::compare).get();
+ long max = samples.stream().max(Long::compare).get();
+ double standardDeviation = getStandardDeviation(samples, average);
+ System.out.printf("%d; %.2f; %.4f; %.4f; %d; %d\n", val, average, standardDeviation / average, standardDeviation, min, max);
+ }
+ }
+
+ private static double getStandardDeviation(List<Long> samples, double average) {
+ double sumSquared = 0;
+ for (long sample : samples) {
+ sumSquared += Math.pow(sample - average, 2);
+ }
+ return Math.sqrt(sumSquared / samples.size());
+ }
+
+ private static long estimateUniqueCount(int nValues) {
+ SparseSketch sparse = new SparseSketch();
+ while (sparse.size() < nValues) {
+ sparse.aggregate(makeHash(random.nextInt()));
+ }
+ if (sparse.size() > HyperLogLog.SPARSE_SKETCH_CONVERSION_THRESHOLD) {
+ NormalSketch normal = new NormalSketch();
+ normal.aggregate(sparse.data());
+ return estimator.estimateCount(normal);
+ } else {
+ return estimator.estimateCount(sparse);
+ }
+ }
+
+ private static int makeHash(int value) {
+ final int seed = 1333337;
+ byte[] bytes = ByteBuffer.allocate(4).putInt(value).array();
+ return hashGenerator.hash(bytes, 0, 4, seed);
+ }
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/aggregation/hll/NormalSketchTest.java b/searchlib/src/test/java/com/yahoo/searchlib/aggregation/hll/NormalSketchTest.java
new file mode 100644
index 00000000000..3b0a584f37b
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/aggregation/hll/NormalSketchTest.java
@@ -0,0 +1,121 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.aggregation.hll;
+
+import com.yahoo.vespa.objects.BufferSerializer;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+
+public class NormalSketchTest {
+
+ @Test
+ public void requireThatSerializationIsCorrectForCompressibleData() {
+ testSerializationForPrecision(16);
+ }
+
+ @Test
+ public void requireThatSerializationIsCorrectForIncompressibleData() {
+ // A sketch of precision 1 contains only two elements and will therefore not be compressible.
+ testSerializationForPrecision(1);
+ }
+
+ private static void testSerializationForPrecision(int precision) {
+ NormalSketch from = new NormalSketch(precision); // precision p => 2^p bytes
+ for (int i = 0; i < from.size(); i++) {
+ from.data()[i] = (byte) i;
+ }
+ NormalSketch to = new NormalSketch(precision);
+
+ BufferSerializer buffer = new BufferSerializer();
+ from.serialize(buffer);
+ buffer.flip();
+ to.deserialize(buffer);
+
+ assertEquals(from, to);
+ }
+
+ @Test
+ public void requireThatMergeDoesElementWiseMax() {
+ NormalSketch s1 = new NormalSketch(2);
+ setSketchValues(s1, 0, 1, 1, 3);
+ NormalSketch s2 = new NormalSketch(2);
+ setSketchValues(s2, 2, 1, 1, 0);
+ s1.merge(s2);
+
+ assertBucketEquals(s1, 0, 2);
+ assertBucketEquals(s1, 1, 1);
+ assertBucketEquals(s1, 2, 1);
+ assertBucketEquals(s1, 3, 3);
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void requireThatMergingFailsForSketchesOfDifferentSize() {
+ NormalSketch s1 = new NormalSketch(2);
+ NormalSketch s2 = new NormalSketch(3);
+ s1.merge(s2);
+ }
+
+ @Test
+ public void requireThatEqualsIsCorrect() {
+ NormalSketch s1 = new NormalSketch(1);
+ setSketchValues(s1, 42, 127);
+ NormalSketch s2 = new NormalSketch(1);
+ setSketchValues(s2, 42, 127);
+ assertEquals(s1, s2);
+ }
+
+ @Test
+ public void requireThatSketchBucketsAreCorrectForSingleValues() {
+
+ testSingleValueAggregation(0, 0, 23);
+ testSingleValueAggregation(1, 1, 23);
+ testSingleValueAggregation(-1, 1023, 1);
+ testSingleValueAggregation(Integer.MAX_VALUE, 1023, 2);
+ testSingleValueAggregation(Integer.MIN_VALUE, 0, 1);
+ testSingleValueAggregation(42, 42, 23);
+ testSingleValueAggregation(0b00000011_00000000_00000000_11000011, 0b11000011, 7);
+ }
+
+ private static void testSingleValueAggregation(int hashValue, int bucketIndex, int expectedValue) {
+ NormalSketch sketch = new NormalSketch(10);
+ sketch.aggregate(hashValue);
+ assertBucketEquals(sketch, bucketIndex, expectedValue);
+ for (int i = 0; i < sketch.size(); i++) {
+ if (i == bucketIndex) {
+ continue;
+ }
+ assertBucketEquals(sketch, i, 0);
+ }
+ }
+
+ @Test
+ public void requireThatSketchBucketsAreCorrectForMultipleValues() {
+ NormalSketch sketch = new NormalSketch(10);
+
+ // Aggregate multiple values
+ sketch.aggregate(Arrays.asList(0, 1, 2, 3, 4, 5, 6, 7, 8, 9));
+ for (int i = 0; i < 10; i++) {
+ assertBucketEquals(sketch, i, 23);
+ }
+ // Check that the other values are zero.
+ for (int i = 10; i < 1024; i++) {
+ assertBucketEquals(sketch, i, 0);
+ }
+ }
+
+ private static void assertBucketEquals(NormalSketch sketch, int index, int expectedValue) {
+ assertEquals(expectedValue, sketch.data()[index]);
+ }
+
+ private static void setSketchValues(NormalSketch sketch, Integer... values) {
+ for (int i = 0; i < values.length; i++) {
+ sketch.data()[i] = values[i].byteValue();
+ }
+ }
+
+} \ No newline at end of file
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/aggregation/hll/SketchMergerTest.java b/searchlib/src/test/java/com/yahoo/searchlib/aggregation/hll/SketchMergerTest.java
new file mode 100644
index 00000000000..07488d21fd3
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/aggregation/hll/SketchMergerTest.java
@@ -0,0 +1,69 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.aggregation.hll;
+
+import org.junit.Test;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class SketchMergerTest {
+
+ private final SketchMerger merger = new SketchMerger();
+
+ @Test
+ public void requireThatMergingTwoSmallSparseSketchesReturnsSparseSketch() {
+ SparseSketch s1 = SketchUtils.createSparseSketch(1);
+ SparseSketch s2 = SketchUtils.createSparseSketch(2);
+
+ Sketch<?> result = merger.merge(s1, s2);
+ assertEquals(result.getClass(), SparseSketch.class);
+ assertTrue("Should return the instance given by first argument.", result == s1);
+ SketchUtils.assertSketchContains(result, 1, 2);
+ }
+
+ @Test
+ public void requireThatMergingTwoThresholdSizeSparseSketchesReturnsNormalSketch() {
+ SparseSketch s1 = SketchUtils.createSparseSketch();
+ SparseSketch s2 = SketchUtils.createSparseSketch();
+
+ // Fill sketches with disjoint data.
+ for (int i = 0; i < HyperLogLog.SPARSE_SKETCH_CONVERSION_THRESHOLD; i++) {
+ s1.aggregate(i);
+ s2.aggregate(i + HyperLogLog.SPARSE_SKETCH_CONVERSION_THRESHOLD);
+ }
+
+ Sketch<?> result = merger.merge(s1, s2);
+ assertEquals(result.getClass(), NormalSketch.class);
+
+ List<Integer> unionOfSketchData = new ArrayList<>();
+ unionOfSketchData.addAll(s1.data());
+ unionOfSketchData.addAll(s2.data());
+ Integer[] expectedValues = unionOfSketchData.toArray(new Integer[unionOfSketchData.size()]);
+ SketchUtils.assertSketchContains(result, expectedValues);
+ }
+
+ @Test
+ public void requireThatMergingTwoNormalSketchesReturnsNormalSketch() {
+ NormalSketch s1 = SketchUtils.createNormalSketch(1);
+ NormalSketch s2 = SketchUtils.createNormalSketch(2);
+
+ Sketch<?> result = merger.merge(s1, s2);
+ assertEquals(result.getClass(), NormalSketch.class);
+ assertTrue("Should return the instance given by first argument.", result == s1);
+ SketchUtils.assertSketchContains(result, 1, 2);
+ }
+
+ @Test
+ public void requireThatMergingNormalAndSparseSketchReturnsNormalSketch() {
+ SparseSketch s1 = SketchUtils.createSparseSketch(1);
+ NormalSketch s2 = SketchUtils.createNormalSketch(2);
+
+ Sketch<?> result = merger.merge(s1, s2);
+ assertEquals(result.getClass(), NormalSketch.class);
+ assertTrue("Should return the NormalSketch instance given by the arguments.", result == s2);
+ SketchUtils.assertSketchContains(result, 1, 2);
+ }
+} \ No newline at end of file
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/aggregation/hll/SketchUtils.java b/searchlib/src/test/java/com/yahoo/searchlib/aggregation/hll/SketchUtils.java
new file mode 100644
index 00000000000..90098f8c950
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/aggregation/hll/SketchUtils.java
@@ -0,0 +1,46 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.aggregation.hll;
+
+import java.util.Arrays;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Utility class for creating sketches and comparing their content.
+ *
+ * @author bjorncs
+ */
+public class SketchUtils {
+
+ private SketchUtils() {}
+
+ public static SparseSketch createSparseSketch(Integer... values) {
+ SparseSketch sketch = new SparseSketch();
+ sketch.aggregate(Arrays.asList(values));
+ return sketch;
+ }
+
+ public static NormalSketch createNormalSketch(Integer... values) {
+ NormalSketch sketch = new NormalSketch();
+ sketch.aggregate(Arrays.asList(values));
+ return sketch;
+ }
+
+ public static void assertSketchContains(Sketch<?> sketch, Integer... values) {
+ if (sketch instanceof SparseSketch) {
+ assertSparseSketchContains((SparseSketch) sketch, values);
+ } else {
+ assertNormalSketchContains((NormalSketch) sketch, values);
+ }
+ }
+
+ public static void assertNormalSketchContains(NormalSketch sketch, Integer... values) {
+ NormalSketch expectedSketch = createNormalSketch(values);
+ assertEquals(expectedSketch, sketch);
+ }
+
+ public static void assertSparseSketchContains(SparseSketch sketch, Integer... values) {
+ SparseSketch expectedSketch = createSparseSketch(values);
+ assertEquals(expectedSketch, sketch);
+ }
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/aggregation/hll/SparseSketchTest.java b/searchlib/src/test/java/com/yahoo/searchlib/aggregation/hll/SparseSketchTest.java
new file mode 100644
index 00000000000..4be0f89514d
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/aggregation/hll/SparseSketchTest.java
@@ -0,0 +1,62 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.aggregation.hll;
+
+import com.yahoo.vespa.objects.BufferSerializer;
+import org.junit.Test;
+
+import java.util.HashSet;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class SparseSketchTest {
+
+ @Test
+ public void requireThatMergeDoesSetUnion() {
+ SparseSketch s1 = new SparseSketch();
+ s1.aggregate(42);
+ s1.aggregate(9001);
+
+ SparseSketch s2 = new SparseSketch();
+ s2.aggregate(1337);
+ s2.aggregate(9001);
+
+ s1.merge(s2);
+
+ HashSet<Integer> data = s1.data();
+ assertEquals(3, s1.size());
+ assertTrue(data.contains(42));
+ assertTrue(data.contains(1337));
+ assertTrue(data.contains(9001));
+ }
+
+
+ @Test
+ public void requireThatSerializationRetainAllData() {
+ SparseSketch from = new SparseSketch();
+ from.aggregate(42);
+ from.aggregate(1337);
+
+ SparseSketch to = new SparseSketch();
+
+ BufferSerializer buffer = new BufferSerializer();
+ from.serialize(buffer);
+ buffer.flip();
+ to.deserialize(buffer);
+
+ assertEquals(from, to);
+ }
+
+ @Test
+ public void requireThatEqualsComparesDataContent() {
+ SparseSketch s1 = new SparseSketch();
+ s1.aggregate(1337);
+ s1.aggregate(42);
+
+ SparseSketch s2 = new SparseSketch();
+ s2.aggregate(42);
+ s2.aggregate(1337);
+
+ assertEquals(s1.data(), s2.data());
+ }
+} \ No newline at end of file
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/expression/ExpressionTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/expression/ExpressionTestCase.java
new file mode 100755
index 00000000000..2c5e65c03e4
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/expression/ExpressionTestCase.java
@@ -0,0 +1,932 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.expression;
+
+import com.yahoo.io.GrowableByteBuffer;
+import com.yahoo.text.Utf8;
+import com.yahoo.vespa.objects.BufferSerializer;
+import com.yahoo.vespa.objects.Identifiable;
+import junit.framework.TestCase;
+
+import java.nio.ByteBuffer;
+import java.util.Arrays;
+
+/**
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ */
+public class ExpressionTestCase extends TestCase {
+
+ public void testRangeBucketPreDefFunctionNode() {
+ assertMultiArgFunctionNode(new RangeBucketPreDefFunctionNode(new StringBucketResultNodeVector().add(new StringBucketResultNode("10", "20")), new AttributeNode("foo")));
+ assertEquals(new RangeBucketPreDefFunctionNode(), new RangeBucketPreDefFunctionNode());
+ assertEquals(new RangeBucketPreDefFunctionNode(new StringBucketResultNodeVector().add(new StringBucketResultNode("10", "20")), new AttributeNode("foo")),
+ new RangeBucketPreDefFunctionNode(new StringBucketResultNodeVector().add(new StringBucketResultNode("10", "20")), new AttributeNode("foo")));
+ assertNotEquals(new RangeBucketPreDefFunctionNode(new StringBucketResultNodeVector().add(new StringBucketResultNode("10", "20")), new AttributeNode("foo")),
+ new RangeBucketPreDefFunctionNode(new StringBucketResultNodeVector().add(new StringBucketResultNode("10", "21")), new AttributeNode("foo")));
+ assertNotEquals(new RangeBucketPreDefFunctionNode(new StringBucketResultNodeVector().add(new StringBucketResultNode("10", "20")), new AttributeNode("foo")),
+ new RangeBucketPreDefFunctionNode(new StringBucketResultNodeVector().add(new StringBucketResultNode("10", "20")), new AttributeNode("bar")));
+ }
+
+ public void testFixedWidthBucketFunctionNode() {
+ assertMultiArgFunctionNode(new FixedWidthBucketFunctionNode());
+ assertEquals(new FixedWidthBucketFunctionNode(), new FixedWidthBucketFunctionNode());
+ assertEquals(new FixedWidthBucketFunctionNode(new IntegerResultNode(5), new AttributeNode("foo")),
+ new FixedWidthBucketFunctionNode(new IntegerResultNode(5), new AttributeNode("foo")));
+ assertNotEquals(new FixedWidthBucketFunctionNode(new IntegerResultNode(5), new AttributeNode("foo")),
+ new FixedWidthBucketFunctionNode(new IntegerResultNode(6), new AttributeNode("foo")));
+ assertNotEquals(new FixedWidthBucketFunctionNode(new IntegerResultNode(5), new AttributeNode("foo")),
+ new FixedWidthBucketFunctionNode(new IntegerResultNode(5), new AttributeNode("bar")));
+ }
+
+ public void testIntegerBucketResultNodeVector() {
+ assertResultNode(new IntegerBucketResultNodeVector().add(new IntegerBucketResultNode(10, 20)));
+ assertEquals(new IntegerBucketResultNodeVector().add(new IntegerBucketResultNode(10, 20)),
+ new IntegerBucketResultNodeVector().add(new IntegerBucketResultNode(10, 20)));
+ assertNotEquals(new IntegerBucketResultNodeVector().add(new IntegerBucketResultNode(10, 20)),
+ new IntegerBucketResultNodeVector());
+ assertNotEquals(new IntegerBucketResultNodeVector().add(new IntegerBucketResultNode(10, 20)),
+ new IntegerBucketResultNodeVector().add(new IntegerBucketResultNode(11, 20)));
+ }
+
+ public void testFloatBucketResultNodeVector() {
+ assertResultNode(new FloatBucketResultNodeVector().add(new FloatBucketResultNode(10, 20)));
+ assertEquals(new FloatBucketResultNodeVector().add(new FloatBucketResultNode(10, 20)),
+ new FloatBucketResultNodeVector().add(new FloatBucketResultNode(10, 20)));
+ assertNotEquals(new FloatBucketResultNodeVector().add(new FloatBucketResultNode(10, 20)),
+ new FloatBucketResultNodeVector());
+ assertNotEquals(new FloatBucketResultNodeVector().add(new FloatBucketResultNode(10, 20)),
+ new FloatBucketResultNodeVector().add(new FloatBucketResultNode(11, 20)));
+ }
+
+ public void testStringBucketResultNodeVector() {
+ assertResultNode(new StringBucketResultNodeVector().add(new StringBucketResultNode("10", "20")));
+ assertEquals(new StringBucketResultNodeVector().add(new StringBucketResultNode("10", "20")),
+ new StringBucketResultNodeVector().add(new StringBucketResultNode("10", "20")));
+ assertNotEquals(new StringBucketResultNodeVector().add(new StringBucketResultNode("10", "20")),
+ new StringBucketResultNodeVector());
+ assertNotEquals(new StringBucketResultNodeVector().add(new StringBucketResultNode("10", "20")),
+ new StringBucketResultNodeVector().add(new StringBucketResultNode("11", "20")));
+ }
+
+ public void testIntegerBucketResultNode() {
+ assertResultNode(new IntegerBucketResultNode(10, 20));
+ assertEquals(new IntegerBucketResultNode(10, 20), new IntegerBucketResultNode(10, 20));
+ assertNotEquals(new IntegerBucketResultNode(10, 20), new IntegerBucketResultNode(11, 20));
+ assertNotEquals(new IntegerBucketResultNode(10, 20), new IntegerBucketResultNode(10, 21));
+ }
+
+ public void testFloatBucketResultNode() {
+ assertResultNode(new FloatBucketResultNode(10.0, 20.0));
+ assertEquals(new FloatBucketResultNode(10.0, 20.0), new FloatBucketResultNode(10.0, 20.0));
+ assertNotEquals(new FloatBucketResultNode(10.0, 20.0), new FloatBucketResultNode(11.0, 20.0));
+ assertNotEquals(new FloatBucketResultNode(10.0, 20.0), new FloatBucketResultNode(10.0, 21.0));
+ }
+
+ public void testStringBucketResultNode() {
+ assertResultNode(new StringBucketResultNode("10.0", "20.0"));
+ assertEquals(new StringBucketResultNode("10.0", "20.0"), new StringBucketResultNode("10.0", "20.0"));
+ assertNotEquals(new StringBucketResultNode("10.0", "20.0"), new StringBucketResultNode("11.0", "20.0"));
+ assertNotEquals(new StringBucketResultNode("10.0", "20.0"), new StringBucketResultNode("10.0", "21.0"));
+ compare(new StringBucketResultNode("10.0", "20.0"), new StringBucketResultNode("10.0", "21.0"), new StringBucketResultNode("10.0", "22.0"));
+ compare(new StringBucketResultNode("10.0", "20.0"), new StringBucketResultNode("11.0", "19.0"), new StringBucketResultNode("11.0", "20.0"));
+ compare(new StringBucketResultNode(StringResultNode.getNegativeInfinity(), new StringResultNode("20.0")),
+ new StringBucketResultNode("11.0", "19.0"), new StringBucketResultNode("11.0", "20.0"));
+ compare(new StringBucketResultNode(StringResultNode.getNegativeInfinity(), new StringResultNode("20.0")),
+ new StringBucketResultNode(StringResultNode.getNegativeInfinity(), new StringResultNode("21.0")),
+ new StringBucketResultNode("11.0", "20.0"));
+ compare(new StringBucketResultNode("10.0", "20.0"), new StringBucketResultNode("10.0", "21.0"),
+ new StringBucketResultNode(new StringResultNode("10.0"), StringResultNode.getPositiveInfinity()));
+ compare(new StringBucketResultNode(new StringResultNode("10.0"), StringResultNode.getPositiveInfinity()),
+ new StringBucketResultNode("11.0", "19.0"), new StringBucketResultNode("11.0", "20.0"));
+ }
+
+ public void testPositiveInfinity() {
+ PositiveInfinityResultNode inf = new PositiveInfinityResultNode();
+ PositiveInfinityResultNode inf2 = new PositiveInfinityResultNode();
+ assertResultNode(inf);
+ assertEquals(inf, inf2);
+ }
+
+ public void testAddFunctionNode() {
+ assertMultiArgFunctionNode(new AddFunctionNode());
+ assertFunctionNode(new AddFunctionNode().addArg(new ConstantNode(new IntegerResultNode(2)))
+ .addArg(new ConstantNode(new IntegerResultNode(3))),
+ 5, 5.0, "5", longAsRaw(5));
+ assertFunctionNode(new AddFunctionNode().addArg(new ConstantNode(new FloatResultNode(3.0)))
+ .addArg(new ConstantNode(new IntegerResultNode(2))),
+ 5, 5.0, "5.0", doubleAsRaw(5.0));
+ assertFunctionNode(new AddFunctionNode().addArg(new ConstantNode(new IntegerResultNode(3)))
+ .addArg(new ConstantNode(new FloatResultNode(2.0))),
+ 5, 5.0, "5.0", doubleAsRaw(5.0));
+ }
+
+ public void testAndFunctionNode() {
+ assertMultiArgFunctionNode(new AndFunctionNode());
+ assertFunctionNode(new AndFunctionNode().addArg(new ConstantNode(new IntegerResultNode(3)))
+ .addArg(new ConstantNode(new IntegerResultNode(7))),
+ 3, 3.0, "3", longAsRaw(3));
+ }
+
+ public void testZCurveFunctionNode() {
+ assertMultiArgFunctionNode(
+ new ZCurveFunctionNode(new ConstantNode(new IntegerResultNode(7)), ZCurveFunctionNode.Dimension.Y));
+ }
+
+ public void testTimeStampFunctionNode() {
+ assertMultiArgFunctionNode(new TimeStampFunctionNode(new AttributeNode("testattribute"), TimeStampFunctionNode.TimePart.Hour, true));
+ assertEquals(new TimeStampFunctionNode(new AttributeNode("testattribute"), TimeStampFunctionNode.TimePart.Hour, true),
+ new TimeStampFunctionNode(new AttributeNode("testattribute"), TimeStampFunctionNode.TimePart.Hour, true));
+ assertNotEquals(
+ new TimeStampFunctionNode(new AttributeNode("testattribute"), TimeStampFunctionNode.TimePart.Hour,
+ true),
+ new TimeStampFunctionNode(new AttributeNode("testattributt"), TimeStampFunctionNode.TimePart.Hour,
+ true));
+ assertNotEquals(
+ new TimeStampFunctionNode(new AttributeNode("testattribute"), TimeStampFunctionNode.TimePart.Hour,
+ true),
+ new TimeStampFunctionNode(new AttributeNode("testattribute"), TimeStampFunctionNode.TimePart.Year,
+ true));
+ assertNotEquals(
+ new TimeStampFunctionNode(new AttributeNode("testattribute"), TimeStampFunctionNode.TimePart.Hour,
+ true),
+ new TimeStampFunctionNode(new AttributeNode("testattribute"), TimeStampFunctionNode.TimePart.Hour,
+ false));
+ }
+
+ public void testExpressionRefNode() {
+ AggregationRefNode ref = new AggregationRefNode(3);
+ assertEquals(3, ref.getIndex());
+ }
+
+ public void testAttributeNode() {
+ try {
+ new AttributeNode(null);
+ fail("Should not be able to set null attribute name.");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ try {
+ new AttributeNode().setAttributeName(null);
+ fail("Should not be able to set null attribute name.");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ try {
+ new AttributeNode().prepare();
+ fail("Should not be possible to prepare or execute attribute node");
+ } catch (RuntimeException e) {
+ // expected
+ }
+ try {
+ new AttributeNode().execute();
+ fail("Should not be possible to prepare or execute attribute node");
+ } catch (RuntimeException e) {
+ // expected
+ }
+ AttributeNode a = new AttributeNode("testattribute");
+ assertEquals("testattribute", a.getAttributeName());
+ AttributeNode b = (AttributeNode)assertSerialize(a);
+ assertEquals("testattribute", b.getAttributeName());
+ AttributeNode c = new AttributeNode("testattribute");
+ assertEquals(b, c);
+ c.setAttributeName("fail");
+ assertFalse(b.equals(c));
+ }
+
+ public void testInterpolatedLookupNode() {
+ ExpressionNode argA = new ConstantNode(new FloatResultNode(2.71828182846));
+ ExpressionNode argB = new ConstantNode(new FloatResultNode(3.14159265359));
+ try {
+ new InterpolatedLookupNode(null, argA);
+ fail("Should not be able to set null attribute name.");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ try {
+ new InterpolatedLookupNode().setAttributeName(null);
+ fail("Should not be able to set null attribute name.");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ try {
+ new InterpolatedLookupNode().prepare();
+ fail("Should not be possible to prepare or execute interpolatedlookup node");
+ } catch (RuntimeException e) {
+ // expected
+ }
+ try {
+ new InterpolatedLookupNode().execute();
+ fail("Should not be possible to prepare or execute interpolatedlookup node");
+ } catch (RuntimeException e) {
+ // expected
+ }
+ ExpressionNode a1 = new InterpolatedLookupNode().setAttributeName("foo").addArg(argA);
+ InterpolatedLookupNode a2 = new InterpolatedLookupNode("foo", argA);
+ assertEquals("foo", ((InterpolatedLookupNode)a1).getAttributeName());
+ assertEquals("foo", a2.getAttributeName());
+ assertEquals(argA, ((InterpolatedLookupNode)a1).getArg());
+ assertEquals(argA, a2.getArg());
+ assertEquals(a1, a2);
+ InterpolatedLookupNode b1 = new InterpolatedLookupNode("foo", argB);
+ InterpolatedLookupNode b2 = new InterpolatedLookupNode("bar", argA);
+ assertFalse(a1.equals(b1));
+ assertFalse(a1.equals(b2));
+ assertFalse(a2.equals(b1));
+ assertFalse(a2.equals(b2));
+ a2.setAttributeName("fail");
+ assertFalse(a1.equals(a2));
+ }
+
+ public void testCatFunctionNode() {
+ assertMultiArgFunctionNode(new CatFunctionNode());
+ assertFunctionNode(new CatFunctionNode().addArg(new ConstantNode(new RawResultNode(asRaw('1', '2'))))
+ .addArg(new ConstantNode(new RawResultNode(asRaw('3', '4')))),
+ 0, 0.0, "1234", asRaw('1', '2', '3', '4'));
+ }
+
+ public void testStrCatFunctionNode() {
+ assertMultiArgFunctionNode(new StrCatFunctionNode());
+ assertFunctionNode(new StrCatFunctionNode().addArg(new ConstantNode(new StringResultNode("foo")))
+ .addArg(new ConstantNode(new StringResultNode("bar"))),
+ 0, 0.0, "foobar", stringAsRaw("foobar"));
+ }
+
+ public void testDivideFunctionNode() {
+ assertMultiArgFunctionNode(new DivideFunctionNode());
+ assertFunctionNode(new DivideFunctionNode().addArg(new ConstantNode(new IntegerResultNode(10)))
+ .addArg(new ConstantNode(new IntegerResultNode(2))),
+ 5, 5.0, "5", longAsRaw(5));
+ assertFunctionNode(new DivideFunctionNode().addArg(new ConstantNode(new IntegerResultNode(6)))
+ .addArg(new ConstantNode(new FloatResultNode(2.0))),
+ 3, 3.0, "3.0", doubleAsRaw(3.0));
+ assertFunctionNode(new DivideFunctionNode().addArg(new ConstantNode(new IntegerResultNode(6)))
+ .addArg(new ConstantNode(new FloatResultNode(12.0))),
+ 1, 0.5, "0.5", doubleAsRaw(0.5));
+ }
+
+ public void testDocumentFieldNode() {
+ try {
+ new DocumentFieldNode(null);
+ fail("Should not be able to set null field name.");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ try {
+ new DocumentFieldNode().setDocumentFieldName(null);
+ fail("Should not be able to set null field name.");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ try {
+ new DocumentFieldNode("foo").prepare();
+ fail("Should not be able to prepare documentfieldnode");
+ } catch (RuntimeException e) {
+ // expected
+ }
+ try {
+ new DocumentFieldNode("foo").execute();
+ fail("Should not be able to execute documentfieldnode");
+ } catch (RuntimeException e) {
+ // expected
+ }
+ DocumentFieldNode a = new DocumentFieldNode("testdocumentfield");
+ assertEquals("testdocumentfield", a.getDocumentFieldName());
+ DocumentFieldNode b = (DocumentFieldNode)assertSerialize(a);
+ assertEquals("testdocumentfield", b.getDocumentFieldName());
+ DocumentFieldNode c = new DocumentFieldNode("testdocumentfield");
+ assertEquals(b, c);
+ c.setDocumentFieldName("fail");
+ assertFalse(b.equals(c));
+ }
+
+ public void testFloatResultNode() {
+ FloatResultNode a = new FloatResultNode(7.3);
+ assertEquals(a.getInteger(), 7);
+ assertEquals(a.getFloat(), 7.3);
+ assertEquals(a.getString(), "7.3");
+ assertEquals(a.getNumber(), new Double(7.3));
+ byte[] raw = a.getRaw();
+ assertEquals(raw.length, 8);
+ assertResultNode(a);
+ compare(new FloatResultNode(-1), new FloatResultNode(0), new FloatResultNode(1));
+ a.set(new FloatResultNode(4));
+ assertResultNode(a);
+
+ FloatResultNode b = new FloatResultNode(7.5);
+ assertEquals(b.getInteger(), 8);
+ assertEquals(b.getFloat(), 7.5);
+ assertEquals(b.getString(), "7.5");
+ assertEquals(b.getNumber(), new Double(7.5));
+ }
+
+ public void testGetDocIdNamespaceSpecificFunctionNode() {
+ GetDocIdNamespaceSpecificFunctionNode a = new GetDocIdNamespaceSpecificFunctionNode(new IntegerResultNode(7));
+ assertTrue(a.getResult() instanceof IntegerResultNode);
+ GetDocIdNamespaceSpecificFunctionNode b = (GetDocIdNamespaceSpecificFunctionNode)assertSerialize(a);
+ assertTrue(b.getResult() instanceof IntegerResultNode);
+ assertEquals(7, b.getResult().getInteger());
+ GetDocIdNamespaceSpecificFunctionNode c = new GetDocIdNamespaceSpecificFunctionNode(new IntegerResultNode(7));
+ assertEquals(b, c);
+ try {
+ new GetDocIdNamespaceSpecificFunctionNode(new IntegerResultNode(7)).prepare();
+ fail("Should not be able to prepare documentfieldnode");
+ } catch (RuntimeException e) {
+ // expected
+ }
+ try {
+ new GetDocIdNamespaceSpecificFunctionNode(new IntegerResultNode(7)).execute();
+ fail("Should not be able to execute documentfieldnode");
+ } catch (RuntimeException e) {
+ // expected
+ }
+ }
+
+ public void testGetYMUMChecksumFunctionNode() {
+ GetYMUMChecksumFunctionNode a = new GetYMUMChecksumFunctionNode();
+ assertTrue(a.getResult() instanceof IntegerResultNode);
+ assertSerialize(a);
+ try {
+ new GetYMUMChecksumFunctionNode().prepare();
+ fail("Should not be able to prepare documentfieldnode");
+ } catch (RuntimeException e) {
+ // expected
+ }
+ try {
+ new GetYMUMChecksumFunctionNode().execute();
+ fail("Should not be able to execute documentfieldnode");
+ } catch (RuntimeException e) {
+ // expected
+ }
+ }
+
+ public void testIntegerResultNode() {
+ IntegerResultNode a = new IntegerResultNode(7);
+ assertEquals(a.getInteger(), 7);
+ assertEquals(a.getFloat(), 7.0);
+ assertEquals(a.getString(), "7");
+ assertEquals(a.getNumber(), new Long(7));
+ byte[] raw = a.getRaw();
+ assertEquals(raw.length, 8);
+ assertResultNode(a);
+ compare(new IntegerResultNode(-1), new IntegerResultNode(0), new IntegerResultNode(1));
+ compare(new IntegerResultNode(-1), new IntegerResultNode(0), new IntegerResultNode(0x80000000L));
+ }
+
+ public void testMaxFunctionNode() {
+ assertMultiArgFunctionNode(new MaxFunctionNode());
+ assertFunctionNode(new MaxFunctionNode().addArg(new ConstantNode(new IntegerResultNode(3)))
+ .addArg(new ConstantNode(new IntegerResultNode(5))),
+ 5, 5.0, "5", longAsRaw(5));
+ assertFunctionNode(new MaxFunctionNode().addArg(new ConstantNode(new FloatResultNode(4.9999999)))
+ .addArg(new ConstantNode(new IntegerResultNode(5))),
+ 5, 5.0, "5.0", doubleAsRaw(5.0));
+ }
+
+ public void testMD5BitFunctionNode() {
+ try {
+ new MD5BitFunctionNode(null, 64);
+ fail("Should not be able to set null argument.");
+ } catch (NullPointerException e) {
+ // expected
+ }
+ try {
+ new MD5BitFunctionNode().prepare();
+ fail("Should not be able to run prepare.");
+ } catch (RuntimeException e) {
+ // expected
+ }
+ try {
+ new MD5BitFunctionNode().execute();
+ fail("Should not be able to run execute.");
+ } catch (RuntimeException e) {
+ // expected
+ }
+ assertUnaryBitFunctionNode(new MD5BitFunctionNode());
+ }
+
+ public void testMinFunctionNode() {
+ assertMultiArgFunctionNode(new MinFunctionNode());
+ assertFunctionNode(new MinFunctionNode().addArg(new ConstantNode(new IntegerResultNode(3)))
+ .addArg(new ConstantNode(new IntegerResultNode(5))),
+ 3, 3.0, "3", longAsRaw(3));
+ assertFunctionNode(new MinFunctionNode().addArg(new ConstantNode(new FloatResultNode(4.9999999)))
+ .addArg(new ConstantNode(new IntegerResultNode(5))),
+ 5, 4.9999999, "4.9999999", doubleAsRaw(4.9999999));
+ }
+
+ public void testModuloFunctionNode() {
+ assertMultiArgFunctionNode(new ModuloFunctionNode());
+ assertFunctionNode(new ModuloFunctionNode().addArg(new ConstantNode(new IntegerResultNode(13)))
+ .addArg(new ConstantNode(new IntegerResultNode(5))),
+ 3, 3.0, "3", longAsRaw(3));
+ assertFunctionNode(new ModuloFunctionNode().addArg(new ConstantNode(new FloatResultNode(4.9999999)))
+ .addArg(new ConstantNode(new IntegerResultNode(5))),
+ 5, 4.9999999, "4.9999999", doubleAsRaw(4.9999999));
+ }
+
+ public void testMultiplyFunctionNode() {
+ assertMultiArgFunctionNode(new MultiplyFunctionNode());
+ assertFunctionNode(new MultiplyFunctionNode().addArg(new ConstantNode(new IntegerResultNode(3)))
+ .addArg(new ConstantNode(new IntegerResultNode(5))),
+ 15, 15.0, "15", longAsRaw(15));
+ assertFunctionNode(new MultiplyFunctionNode().addArg(new ConstantNode(new FloatResultNode(4.5)))
+ .addArg(new ConstantNode(new IntegerResultNode(5))),
+ 23, 22.5, "22.5", doubleAsRaw(22.5));
+ }
+
+ public void testNegateFunctionNode() {
+ assertMultiArgFunctionNode(new NegateFunctionNode());
+ assertFunctionNode(new NegateFunctionNode().addArg(new ConstantNode(new IntegerResultNode(3))),
+ -3, -3.0, "-3", longAsRaw(-3));
+ assertFunctionNode(new NegateFunctionNode().addArg(new ConstantNode(new FloatResultNode(3.0))),
+ -3, -3.0, "-3.0", doubleAsRaw(-3.0));
+ }
+
+ public void testSortFunctionNode() {
+ assertMultiArgFunctionNode(new SortFunctionNode());
+
+ }
+
+ public void testReverseFunctionNode() {
+ assertMultiArgFunctionNode(new ReverseFunctionNode());
+ }
+
+ public void testToIntFunctionNode() {
+ assertMultiArgFunctionNode(new ToIntFunctionNode());
+ assertFunctionNode(new ToIntFunctionNode().addArg(new ConstantNode(new StringResultNode("1337"))),
+ 1337, 1337.0, "1337", longAsRaw(1337));
+ }
+
+ public void testToFloatFunctionNode() {
+ assertMultiArgFunctionNode(new ToFloatFunctionNode());
+ assertFunctionNode(new ToFloatFunctionNode().addArg(new ConstantNode(new FloatResultNode(3.14))),
+ 3, 3.14, "3.14", doubleAsRaw(3.14));
+ }
+
+ public void testMathFunctionNode() {
+ assertMultiArgFunctionNode(new MathFunctionNode(MathFunctionNode.Function.LOG10));
+ assertFunctionNode(new MathFunctionNode(MathFunctionNode.Function.LOG10).addArg(new ConstantNode(new IntegerResultNode(100000))),
+ 5, 5.0, "5.0", doubleAsRaw(5.0));
+ }
+
+ public void testStrLenFunctionNode() {
+ assertMultiArgFunctionNode(new StrLenFunctionNode());
+ assertFunctionNode(new StrLenFunctionNode().addArg(new ConstantNode(new StringResultNode("foo"))),
+ 3, 3.0, "3", longAsRaw(3));
+ }
+
+ public void testNormalizeSubjectFunctionNode() {
+ assertMultiArgFunctionNode(new NormalizeSubjectFunctionNode());
+ assertFunctionNode(new NormalizeSubjectFunctionNode().addArg(new ConstantNode(new StringResultNode("Re: Your mail"))),
+ 0, 0, "Your mail", stringAsRaw("Your mail"));
+ }
+
+ public void testNormalizeSubjectFunctionNode2() {
+ assertMultiArgFunctionNode(new NormalizeSubjectFunctionNode());
+ assertFunctionNode(new NormalizeSubjectFunctionNode().addArg(new ConstantNode(new StringResultNode("Your mail"))),
+ 0, 0, "Your mail", stringAsRaw("Your mail"));
+ }
+
+ public void testNumElemFunctionNode() {
+ assertMultiArgFunctionNode(new NumElemFunctionNode());
+ assertFunctionNode(new NumElemFunctionNode().addArg(new ConstantNode(new IntegerResultNode(1337))),
+ 1, 1.0, "1", longAsRaw(1));
+ }
+
+ public void testToStringFunctionNode() {
+ assertMultiArgFunctionNode(new ToStringFunctionNode());
+ assertFunctionNode(new ToStringFunctionNode().addArg(new ConstantNode(new IntegerResultNode(1337))),
+ 1337, 1337.0, "1337", stringAsRaw("1337"));
+ }
+
+ public void testToRawFunctionNode() {
+ assertMultiArgFunctionNode(new ToRawFunctionNode());
+ assertFunctionNode(new ToRawFunctionNode().addArg(new ConstantNode(new IntegerResultNode(1337))),
+ 1337, 1337.0, "1337", longAsRaw(1337));
+ }
+
+ public void testNullResultNode() {
+ // TODO: Implement.
+ }
+
+ public void testOrFunctionNode() {
+ assertMultiArgFunctionNode(new OrFunctionNode());
+ assertFunctionNode(new OrFunctionNode().addArg(new ConstantNode(new IntegerResultNode(2)))
+ .addArg(new ConstantNode(new IntegerResultNode(4))),
+ 6, 6.0, "6", longAsRaw(6));
+ }
+
+ public void testDebugWaitFunctionNode() {
+ assertFunctionNode(
+ new DebugWaitFunctionNode(new OrFunctionNode().addArg(new ConstantNode(new IntegerResultNode(2)))
+ .addArg(new ConstantNode(new IntegerResultNode(4))),
+ 0.01,
+ true),
+ 6, 6.0, "6", longAsRaw(6));
+ DebugWaitFunctionNode n = new DebugWaitFunctionNode(new OrFunctionNode().addArg(new ConstantNode(new IntegerResultNode(2)))
+ .addArg(new ConstantNode(new IntegerResultNode(4))),
+ 0.3,
+ false);
+ n.prepare();
+ long start = System.currentTimeMillis();
+ n.execute();
+ long end = System.currentTimeMillis();
+ assertTrue(end - start > 250);
+
+ DebugWaitFunctionNode n2 = new DebugWaitFunctionNode(new OrFunctionNode().addArg(new ConstantNode(new IntegerResultNode(2)))
+ .addArg(new ConstantNode(new IntegerResultNode(4))),
+ 0.5,
+ true);
+ n2.prepare();
+ start = System.currentTimeMillis();
+ n2.execute();
+ end = System.currentTimeMillis();
+ assertTrue(end - start > 450);
+ }
+
+ public void testRawResultNode() {
+ try {
+ new RawResultNode(null);
+ fail("Should not be able to set null value.");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ try {
+ new RawResultNode().setValue(null);
+ fail("Should not be able to set null value.");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ byte[] b = { '7', '.', '4' };
+ RawResultNode a = new RawResultNode(b);
+ byte[] raw = a.getRaw();
+ assertEquals(raw.length, 3);
+ assertEquals(raw[0], '7');
+ assertEquals(raw[1], '.');
+ assertEquals(raw[2], '4');
+ assertEquals(a.getInteger(), 0);
+ assertEquals(a.getFloat(), 0.0);
+ assertEquals(a.getString(), "7.4");
+ assertResultNode(a);
+ compare(new RawResultNode(), new RawResultNode(new byte [] {'z'}), new RawResultNode(new byte [] {'z', 'z'}));
+ compare(new RawResultNode(new byte [] {'z'}), new RawResultNode(new byte [] {'z', 'z'}), new RawResultNode(new byte [] {'z','z','z'}));
+ compare(new RawResultNode(new byte [] {'z'}), new RawResultNode(new byte [] {'z','z'}), new PositiveInfinityResultNode());
+ byte [] b1 = {0x00};
+ byte [] b2 = {0x07};
+ byte [] b3 = {0x7f};
+ byte [] b4 = {(byte)0x80};
+ byte [] b5 = {(byte)0xb1};
+ byte [] b6 = {(byte)0xff};
+
+ assertEquals(0x00, b1[0]);
+ assertEquals(0x07, b2[0]);
+ assertEquals(0x7f, b3[0]);
+ assertEquals(0x80, ((int)b4[0]) & 0xff);
+ assertEquals(0xb1, ((int)b5[0]) & 0xff);
+ assertEquals(0xff, ((int)b6[0]) & 0xff);
+
+ RawResultNode r1 = new RawResultNode(b1);
+ RawResultNode r2 = new RawResultNode(b2);
+ RawResultNode r3 = new RawResultNode(b3);
+ RawResultNode r4 = new RawResultNode(b4);
+ RawResultNode r5 = new RawResultNode(b5);
+ RawResultNode r6 = new RawResultNode(b6);
+
+ assertTrue(r1.compareTo(r1) == 0);
+ assertTrue(r1.compareTo(r2) < 0);
+ assertTrue(r1.compareTo(r3) < 0);
+ assertTrue(r1.compareTo(r4) < 0);
+ assertTrue(r1.compareTo(r5) < 0);
+ assertTrue(r1.compareTo(r6) < 0);
+
+ assertTrue(r2.compareTo(r1) > 0);
+ assertTrue(r2.compareTo(r2) == 0);
+ assertTrue(r2.compareTo(r3) < 0);
+ assertTrue(r2.compareTo(r4) < 0);
+ assertTrue(r2.compareTo(r5) < 0);
+ assertTrue(r2.compareTo(r6) < 0);
+
+ assertTrue(r3.compareTo(r1) > 0);
+ assertTrue(r3.compareTo(r2) > 0);
+ assertTrue(r3.compareTo(r3) == 0);
+ assertTrue(r3.compareTo(r4) < 0);
+ assertTrue(r3.compareTo(r5) < 0);
+ assertTrue(r3.compareTo(r6) < 0);
+
+ assertTrue(r4.compareTo(r1) > 0);
+ assertTrue(r4.compareTo(r2) > 0);
+ assertTrue(r4.compareTo(r3) > 0);
+ assertTrue(r4.compareTo(r4) == 0);
+ assertTrue(r4.compareTo(r5) < 0);
+ assertTrue(r4.compareTo(r6) < 0);
+
+ assertTrue(r5.compareTo(r1) > 0);
+ assertTrue(r5.compareTo(r2) > 0);
+ assertTrue(r5.compareTo(r3) > 0);
+ assertTrue(r5.compareTo(r4) > 0);
+ assertTrue(r5.compareTo(r5) == 0);
+ assertTrue(r5.compareTo(r6) < 0);
+
+ assertTrue(r6.compareTo(r1) > 0);
+ assertTrue(r6.compareTo(r2) > 0);
+ assertTrue(r6.compareTo(r3) > 0);
+ assertTrue(r6.compareTo(r4) > 0);
+ assertTrue(r6.compareTo(r5) > 0);
+ assertTrue(r6.compareTo(r6) == 0);
+
+ }
+
+ private void compare(ResultNode small, ResultNode medium, ResultNode large) {
+ assertTrue(small.compareTo(medium) < 0);
+ assertTrue(small.compareTo(large) < 0);
+ assertTrue(medium.compareTo(large) < 0);
+ assertTrue(medium.compareTo(small) > 0);
+ assertTrue(large.compareTo(small) > 0);
+ assertTrue(large.compareTo(medium) > 0);
+ assertEquals(0, small.compareTo(small));
+ assertEquals(0, medium.compareTo(medium));
+ assertEquals(0, large.compareTo(large));
+ }
+
+ public void testStringResultNode() {
+ try {
+ new StringResultNode(null);
+ fail("Should not be able to set null value.");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ try {
+ new StringResultNode().setValue(null);
+ fail("Should not be able to set null value.");
+ } catch (IllegalArgumentException e) {
+ // expected
+ }
+ StringResultNode a = new StringResultNode("7.3");
+ assertEquals(a.getInteger(), 0);
+ assertEquals(a.getFloat(), 7.3);
+ assertEquals(a.getString(), "7.3");
+ byte[] raw = a.getRaw();
+ assertEquals(raw.length, 3);
+ assertResultNode(a);
+ compare(new StringResultNode(), new StringResultNode("z"), new StringResultNode("zz"));
+ compare(new StringResultNode("z"), new StringResultNode("zz"), new StringResultNode("zzz"));
+ compare(new StringResultNode("a"), new StringResultNode("zz"), new PositiveInfinityResultNode());
+ }
+
+ public void testXorBitFunctionNode() {
+ try {
+ new XorBitFunctionNode(null, 64);
+ fail("Should not be able to set null argument.");
+ } catch (NullPointerException e) {
+ // expected
+ }
+ try {
+ new XorBitFunctionNode().prepare();
+ fail("Should not be able to run prepare.");
+ } catch (RuntimeException e) {
+ // expected
+ }
+ try {
+ new XorBitFunctionNode().execute();
+ fail("Should not be able to run execute.");
+ } catch (RuntimeException e) {
+ // expected
+ }
+ assertUnaryBitFunctionNode(new XorBitFunctionNode());
+ }
+
+ public void testUcaFunctionNode() {
+ try {
+ new UcaFunctionNode(null, "foo");
+ fail("Should not be able to set null argument.");
+ } catch (NullPointerException e) {
+ // expected
+ }
+ try {
+ new UcaFunctionNode().prepare();
+ fail("Should not be able to run prepare.");
+ } catch (RuntimeException e) {
+ // expected
+ }
+ try {
+ new UcaFunctionNode().execute();
+ fail("Should not be able to run execute.");
+ } catch (RuntimeException e) {
+ // expected
+ }
+ assertUcaFunctionNode(new UcaFunctionNode(new ConstantNode(new IntegerResultNode(1337)), "foo", "bar"));
+ }
+
+ public void testNestedFunctions() {
+ assertFunctionNode(new AddFunctionNode()
+ .addArg(new MultiplyFunctionNode().addArg(new ConstantNode(new IntegerResultNode(3)))
+ .addArg(new ConstantNode(
+ new StringResultNode("4"))))
+ .addArg(new ConstantNode(new FloatResultNode(2.0))),
+ 14, 14.0, "14.0", doubleAsRaw(14.0));
+ }
+
+ public void testArithmeticNodes() {
+ ExpressionNode i1 = new ConstantNode(new IntegerResultNode(1));
+ ExpressionNode i2 = new ConstantNode(new IntegerResultNode(2));
+ ExpressionNode f2 = new ConstantNode(new FloatResultNode(9.9));
+ ExpressionNode s2 = new ConstantNode(new StringResultNode("2"));
+ ExpressionNode r2 = new ConstantNode(new RawResultNode(asRaw(2)));
+
+ AddFunctionNode add1 = new AddFunctionNode();
+ add1.addArg(i1).addArg(i2);
+ ExpressionNode exp1 = add1;
+ exp1.prepare();
+ assertTrue(exp1.getResult() instanceof IntegerResultNode);
+ assertTrue(exp1.execute());
+ assertEquals(exp1.getResult().getInteger(), 3);
+ assertTrue(exp1.execute());
+ assertEquals(exp1.getResult().getInteger(), 3);
+
+ AddFunctionNode add2 = new AddFunctionNode();
+ add2.addArg(i1);
+ add2.addArg(f2);
+ add2.prepare();
+ assertTrue(add2.getResult() instanceof FloatResultNode);
+
+ AddFunctionNode add3 = new AddFunctionNode();
+ add3.addArg(i1);
+ add3.addArg(s2);
+ add3.prepare();
+ assertTrue(add3.getResult() instanceof IntegerResultNode);
+
+ AddFunctionNode add4 = new AddFunctionNode();
+ add4.addArg(i1);
+ add4.addArg(r2);
+ add4.prepare();
+ assertTrue(add4.getResult() instanceof IntegerResultNode);
+ }
+
+ public void testArithmeticOperations() {
+ ExpressionNode i1 = new ConstantNode(new IntegerResultNode(1793253241));
+ ExpressionNode i2 = new ConstantNode(new IntegerResultNode(1676521321));
+ ExpressionNode f1 = new ConstantNode(new FloatResultNode(1.1109876));
+ ExpressionNode f2 = new ConstantNode(new FloatResultNode(9.767681239));
+
+ assertAdd(i1, i2, 3469774562l, 3469774562l);
+ assertAdd(i1, f2, 1793253251l, 1793253250.767681239);
+ assertAdd(f1, f2, 11, 10.878668839);
+ assertMultiply(i1, i2, 3006427292488851361l, 3006427292488851361l);
+ assertMultiply(i1, f2, 17515926039l, 1793253241.0 * 9.767681239);
+ assertMultiply(f1, f2, 11, 10.8517727372816364);
+ }
+
+ // --------------------------------------------------------------------------------
+ //
+ // Everything below this point is helper functions.
+ //
+ // --------------------------------------------------------------------------------
+ private static void assertNotEquals(Object lhs, Object rhs) {
+ assertFalse(lhs.equals(rhs));
+ }
+
+ private static void assertUcaFunctionNode(UcaFunctionNode node) {
+ UcaFunctionNode obj = node.clone();
+ assertEquals(obj, node);
+ assertMultiArgFunctionNode((UcaFunctionNode)Identifiable.createFromId(node.getClassId()));
+ }
+
+ public byte[] asRaw(int ... extra) {
+ byte[] mybytes = new byte[extra.length];
+ for (int i = 0; i < mybytes.length; i++) {
+ mybytes[i] = (byte)extra[i];
+ }
+ return mybytes;
+ }
+
+ public byte[] longAsRaw(long value) {
+ return ByteBuffer.allocate(8).putLong(value).array();
+ }
+
+ public byte[] doubleAsRaw(double value) {
+ return ByteBuffer.allocate(8).putDouble(value).array();
+ }
+
+ public byte[] stringAsRaw(String value) {
+ return Utf8.toBytes(value);
+ }
+
+ private static void assertUnaryBitFunctionNode(UnaryBitFunctionNode node) {
+ UnaryBitFunctionNode obj = (UnaryBitFunctionNode)node.clone();
+ assertEquals(obj, node);
+
+ obj.setNumBits(obj.getNumBits() + 1);
+ assertFalse(obj.equals(node));
+
+ assertMultiArgFunctionNode((UnaryBitFunctionNode)Identifiable.createFromId(node.getClassId()));
+ }
+
+ private static void assertMultiArgFunctionNode(MultiArgFunctionNode node) {
+ try {
+ node.addArg(null);
+ fail("Should not be able to add a null argument.");
+ } catch (NullPointerException e) {
+ // expected
+ }
+ int initialSz = node.getNumArgs();
+ node.addArg(new ConstantNode(new IntegerResultNode(69)));
+ assertEquals(1+initialSz, node.getNumArgs());
+ node.addArg(new ConstantNode(new IntegerResultNode(6699)));
+ assertEquals(2+initialSz, node.getNumArgs());
+ node.addArg(new ConstantNode(new IntegerResultNode(666999)));
+ assertEquals(3+initialSz, node.getNumArgs());
+
+ MultiArgFunctionNode obj = (MultiArgFunctionNode)assertSerialize(node);
+ assertEquals(node, obj);
+ assertEquals(node.getNumArgs(), obj.getNumArgs());
+ for (int i = 0, len = node.getNumArgs(); i < len; i++) {
+ assertEquals(node.getArg(i), obj.getArg(i));
+ }
+
+ obj.addArg(new ConstantNode(new IntegerResultNode(69)));
+ assertFalse(node.equals(obj));
+ }
+
+ public void assertAdd(ExpressionNode arg1, ExpressionNode arg2, long lexpected, double dexpected) {
+ assertArith(new AddFunctionNode(), arg1, arg2, lexpected, dexpected);
+ }
+
+ public void assertMultiply(ExpressionNode arg1, ExpressionNode arg2, long lexpected, double dexpected) {
+ assertArith(new MultiplyFunctionNode(), arg1, arg2, lexpected, dexpected);
+ }
+
+ public void assertArith(MultiArgFunctionNode node, ExpressionNode arg1, ExpressionNode arg2, long lexpected, double dexpected) {
+ node.addArg(arg1);
+ node.addArg(arg2);
+ node.prepare();
+ node.execute();
+ assertEquals(lexpected, node.getResult().getInteger());
+ assertEquals(dexpected, node.getResult().getFloat());
+ }
+
+ public void assertFunctionNode(FunctionNode node, long lexpected, double dexpected, String sexpected, byte[] rexpected) {
+ node.prepare();
+ node.execute();
+ assertEquals(lexpected, node.getResult().getInteger());
+ assertEquals(dexpected, node.getResult().getFloat());
+ assertEquals(sexpected, node.getResult().getString());
+ assertTrue(Arrays.equals(rexpected, node.getResult().getRaw()));
+ }
+
+ private static void assertResultNode(ResultNode node) {
+ BufferSerializer buf = new BufferSerializer(new GrowableByteBuffer());
+ long oldInteger = node.getInteger();
+ double oldFloat = node.getFloat();
+ String oldString = node.getString();
+ byte[] oldRaw = node.getRaw();
+ node.serialize(buf);
+ buf.flip();
+ node.deserialize(buf);
+ assertEquals(oldInteger, node.getInteger());
+ assertEquals(oldFloat, node.getFloat());
+ assertEquals(oldString, node.getString());
+ assertEquals(oldRaw.length, node.getRaw().length);
+
+ buf = new BufferSerializer(new GrowableByteBuffer());
+ node.serializeWithId(buf);
+ buf.flip();
+ node.deserializeWithId(buf);
+ assertEquals(oldInteger, node.getInteger());
+ assertEquals(oldFloat, node.getFloat());
+ assertEquals(oldString, node.getString());
+ assertEquals(oldRaw.length, node.getRaw().length);
+
+ buf = new BufferSerializer(new GrowableByteBuffer());
+ node.serializeWithId(buf);
+ buf.flip();
+ ResultNode obj = (ResultNode)Identifiable.create(buf);
+ assertEquals(oldInteger, obj.getInteger());
+ assertEquals(oldFloat, obj.getFloat());
+ assertEquals(oldString, obj.getString());
+ assertEquals(oldRaw.length, obj.getRaw().length);
+
+ assertSerialize(node);
+ }
+
+ private static Identifiable assertSerialize(Identifiable node) {
+ BufferSerializer buf = new BufferSerializer(new GrowableByteBuffer());
+ node.serializeWithId(buf);
+ buf.flip();
+ Identifiable created = Identifiable.create(buf);
+ assertEquals(node, created);
+ assertEquals(buf.getBuf().hasRemaining(), false);
+ Identifiable cloned = created.clone();
+ assertEquals(node, cloned);
+ BufferSerializer createdBuffer = new BufferSerializer(new GrowableByteBuffer());
+ BufferSerializer clonedBuffer = new BufferSerializer(new GrowableByteBuffer());
+ created.serializeWithId(createdBuffer);
+ cloned.serializeWithId(clonedBuffer);
+ assertEquals(createdBuffer.getBuf().limit(), clonedBuffer.getBuf().limit());
+ assertEquals(createdBuffer.position(), clonedBuffer.position());
+ createdBuffer.getBuf().flip();
+ clonedBuffer.getBuf().flip();
+ for (int i = 0; i < createdBuffer.getBuf().limit(); i++) {
+ assertEquals(createdBuffer.getBuf().get(), clonedBuffer.getBuf().get());
+ }
+ return created;
+ }
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/expression/FixedWidthBucketFunctionTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/expression/FixedWidthBucketFunctionTestCase.java
new file mode 100644
index 00000000000..4836c9c05d2
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/expression/FixedWidthBucketFunctionTestCase.java
@@ -0,0 +1,21 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.expression;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertSame;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class FixedWidthBucketFunctionTestCase {
+
+ @Test
+ public void requireThatAccessorsWork() {
+ ExpressionNode arg = new AttributeNode("foo");
+ NumericResultNode width = new IntegerResultNode(69L);
+ FixedWidthBucketFunctionNode node = new FixedWidthBucketFunctionNode(width, arg);
+ assertSame(arg, node.getArg());
+ assertSame(width, node.getWidth());
+ }
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/expression/FloatBucketResultNodeTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/expression/FloatBucketResultNodeTestCase.java
new file mode 100644
index 00000000000..a1255db4536
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/expression/FloatBucketResultNodeTestCase.java
@@ -0,0 +1,44 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.expression;
+
+import org.junit.Test;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.*;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class FloatBucketResultNodeTestCase extends ResultNodeTest {
+ @Test
+ public void testEmpty() {
+ final double val = 3.14;
+ final FloatBucketResultNode node = createNode(val, val);
+ assertTrue(node.empty());
+ assertCorrectSerialization(node, new FloatBucketResultNode());
+ }
+
+ @Test
+ public void testRange() {
+ FloatBucketResultNode bucket = createNode(3.14, 6.9);
+ assertFalse(bucket.empty());
+ assertEquals(bucket.getFrom(), 3.14, 0.01);
+ assertEquals(bucket.getTo(), 6.9, 0.01);
+ assertCorrectSerialization(bucket, new FloatBucketResultNode());
+ assertTrue(dumpNode(bucket).contains("from: 3.14"));
+ assertTrue(dumpNode(bucket).contains("to: 6.9"));
+ }
+
+ private FloatBucketResultNode createNode(double from, double to) {
+ return new FloatBucketResultNode(from, to);
+ }
+
+ @Test
+ public void testCmp() {
+ assertOrder(createNode(6, 9), createNode(7, 9), createNode(8, 9));
+ assertOrder(createNode(6, 7), createNode(6, 8), createNode(6, 9));
+ assertOrder(createNode(6, 3), createNode(7, 2), createNode(8, 1));
+ assertTrue(createNode(6, 8).onCmp(new NullResultNode()) != 0);
+ }
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/expression/ForceLoadTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/expression/ForceLoadTestCase.java
new file mode 100755
index 00000000000..e1bfe321619
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/expression/ForceLoadTestCase.java
@@ -0,0 +1,19 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.expression;
+
+public class ForceLoadTestCase extends junit.framework.TestCase {
+
+ public ForceLoadTestCase(String name) {
+ super(name);
+ }
+
+ public void testLoadClasses() {
+ try {
+ new com.yahoo.searchlib.expression.ForceLoad();
+ assertTrue(com.yahoo.searchlib.expression.ForceLoad.forceLoad());
+ } catch (com.yahoo.system.ForceLoadError e) {
+ e.printStackTrace();
+ assertTrue(false);
+ }
+ }
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/expression/IntegerBucketResultNodeTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/expression/IntegerBucketResultNodeTestCase.java
new file mode 100644
index 00000000000..a7517952703
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/expression/IntegerBucketResultNodeTestCase.java
@@ -0,0 +1,35 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.expression;
+
+import com.yahoo.vespa.objects.BufferSerializer;
+import org.junit.Test;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class IntegerBucketResultNodeTestCase extends ResultNodeTest {
+
+ @Test
+ public void testEmptyRange() {
+ IntegerBucketResultNode bucket = new IntegerBucketResultNode(4, 4);
+ assertTrue(bucket.empty());
+ assertCorrectSerialization(bucket, new IntegerBucketResultNode());
+ }
+
+ @Test
+ public void testRange() {
+ IntegerBucketResultNode bucket = new IntegerBucketResultNode(4, 10);
+ assertThat(bucket.getFrom(), is(4l));
+ assertThat(bucket.getTo(), is(10l));
+ assertFalse(bucket.empty());
+ assertTrue(dumpNode(bucket).contains("from: 4"));
+ assertTrue(dumpNode(bucket).contains("to: 10"));
+ assertCorrectSerialization(bucket, new IntegerBucketResultNode());
+ }
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/expression/IntegerResultNodeTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/expression/IntegerResultNodeTestCase.java
new file mode 100644
index 00000000000..07c88464958
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/expression/IntegerResultNodeTestCase.java
@@ -0,0 +1,118 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.expression;
+
+import com.yahoo.vespa.objects.BufferSerializer;
+import com.yahoo.vespa.objects.ObjectDumper;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class IntegerResultNodeTestCase extends ResultNodeTest {
+
+ List<NumericResultNode> getResultNodes(long startvalue) {
+ return Arrays.asList(new Int8ResultNode((byte)startvalue),
+ new Int16ResultNode((short)startvalue),
+ new Int32ResultNode((int)startvalue),
+ new IntegerResultNode(startvalue));
+ }
+
+ @Test
+ public void testClassId() {
+ assertThat(new Int8ResultNode().getClassId(), is(Int8ResultNode.classId));
+ assertThat(new Int16ResultNode().getClassId(), is(Int16ResultNode.classId));
+ assertThat(new Int32ResultNode().getClassId(), is(Int32ResultNode.classId));
+ assertThat(new IntegerResultNode().getClassId(), is(IntegerResultNode.classId));
+
+ }
+
+ @Test
+ public void testTypeConversion() {
+ for (NumericResultNode node : getResultNodes(3)) {
+ assertThat(node.getInteger(), is(3l));
+ assertEquals(node.getFloat(), 3.0, 0.01);
+ assertThat(node.getRaw(), is(new byte[]{0, 0, 0, 0, 0, 0, 0, (byte) 3}));
+ assertThat(node.getString(), is("3"));
+ assertThat(node.getNumber().toString(), is("3"));
+ }
+ }
+
+ @Test
+ public void testMath() {
+ for (NumericResultNode node : getResultNodes(5)) {
+ assertThat(node.getInteger(), is(5l));
+ node.negate();
+ assertThat(node.getInteger(), is(-5l));
+ node.multiply(new Int32ResultNode(3));
+ assertThat(node.getInteger(), is(-15l));
+ node.add(new Int32ResultNode(1));
+ assertThat(node.getInteger(), is(-14l));
+ node.divide(new Int32ResultNode(2));
+ assertThat(node.getInteger(), is(-7l));
+ node.modulo(new Int32ResultNode(3));
+ assertThat(node.getInteger(), is(-1l));
+ node.min(new Int32ResultNode(2));
+ assertThat(node.getInteger(), is(-1l));
+ node.min(new Int32ResultNode(-2));
+ assertThat(node.getInteger(), is(-2l));
+ node.max(new Int32ResultNode(-4));
+ assertThat(node.getInteger(), is(-2l));
+ node.max(new Int32ResultNode(4));
+ assertThat(node.getInteger(), is(4l));
+ assertThat(node.onCmp(new Int32ResultNode(3)), is(1));
+ assertThat(node.onCmp(new Int32ResultNode(4)), is(0));
+ assertThat(node.onCmp(new Int32ResultNode(5)), is(-1));
+ node.set(new Int32ResultNode(8));
+ assertThat(node.getInteger(), is(8l));
+ assertThat(node.hashCode(), is((int)(8 + node.getClassId())));
+ assertTrue(dumpNode(node).contains("value: 8"));
+ }
+ }
+
+ @Test
+ public void testInt8() {
+ Int8ResultNode node = new Int8ResultNode();
+ node.setValue((byte) 5);
+ assertThat(node.getInteger(), is(5l));
+ }
+
+ @Test
+ public void testInt16() {
+ Int16ResultNode node = new Int16ResultNode();
+ node.setValue((short)5);
+ assertThat(node.getInteger(), is(5l));
+ }
+
+ @Test
+ public void testInt32() {
+ Int32ResultNode node = new Int32ResultNode();
+ node.setValue(5);
+ assertThat(node.getInteger(), is(5l));
+ }
+
+ @Test
+ public void testLong() {
+ IntegerResultNode node = new IntegerResultNode();
+ node.setValue(5);
+ assertThat(node.getInteger(), is(5l));
+ }
+
+ @Test
+ public void testSerialization() throws IllegalAccessException, InstantiationException {
+ for (NumericResultNode node : getResultNodes(8)) {
+ assertThat(node.getInteger(), is(8l));
+ NumericResultNode out = node.getClass().newInstance();
+ assertCorrectSerialization(node, out);
+ assertThat(out.getInteger(), is(node.getInteger()));
+ }
+ }
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/expression/NullResultNodeTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/expression/NullResultNodeTestCase.java
new file mode 100644
index 00000000000..9eb4ee4fea7
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/expression/NullResultNodeTestCase.java
@@ -0,0 +1,36 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.expression;
+
+import com.yahoo.vespa.objects.ObjectDumper;
+import org.junit.Test;
+
+import java.util.regex.Pattern;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class NullResultNodeTestCase {
+ @Test
+ public void testNullResultNode() {
+ NullResultNode nullRes = new NullResultNode();
+ assertThat(nullRes.onGetClassId(), is(NullResultNode.classId));
+ assertThat(nullRes.getInteger(), is(0l));
+ assertThat(nullRes.getString(), is(""));
+ assertThat(nullRes.getRaw(), is(new byte[0]));
+ assertEquals(nullRes.getFloat(), 0.0, 0.01);
+ assertThat(nullRes.onCmp(new NullResultNode()), is(0));
+ assertThat(nullRes.onCmp(new IntegerResultNode(0)), is(not(0)));
+ ObjectDumper dumper = new ObjectDumper();
+ nullRes.visitMembers(dumper);
+ assertTrue(dumper.toString().contains("result: <NULL>"));
+ nullRes.set(new IntegerResultNode(3));
+ assertThat(nullRes.onCmp(new IntegerResultNode(3)), is(not(0)));
+ }
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/expression/ObjectVisitorTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/expression/ObjectVisitorTestCase.java
new file mode 100755
index 00000000000..2924ee945e5
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/expression/ObjectVisitorTestCase.java
@@ -0,0 +1,61 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.expression;
+
+import com.yahoo.vespa.objects.ObjectDumper;
+import com.yahoo.searchlib.expression.FixedWidthBucketFunctionNode;
+import com.yahoo.searchlib.expression.IntegerResultNode;
+import com.yahoo.searchlib.expression.AttributeNode;
+import junit.framework.TestCase;
+
+import java.util.Arrays;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ObjectVisitorTestCase extends TestCase {
+
+ public void testObjectDumper() {
+ assertDump("test: <NULL>\n", null);
+ assertDump("test: 1\n", 1);
+ assertDump("test: 'foo'\n", "foo");
+ assertDump("test: List {\n" +
+ " [0]: 'foo'\n" +
+ " [1]: 69\n" +
+ " [2]: <NULL>\n" +
+ "}\n",
+ Arrays.asList("foo", 69, null));
+ assertDump("test: String[] {\n" +
+ " [0]: 'foo'\n" +
+ " [1]: 'bar'\n" +
+ " [2]: 'baz'\n" +
+ "}\n",
+ new String[] { "foo", "bar", "baz" });
+ assertDump("test: IntegerResultNode {\n" +
+ " classId: 16491\n" +
+ " value: 5\n" +
+ "}\n",
+ new IntegerResultNode(5));
+ assertDump("test: FixedWidthBucketFunctionNode {\n" +
+ " classId: 16461\n" +
+ " result: <NULL>\n" +
+ " args: List {\n" +
+ " [0]: AttributeNode {\n" +
+ " classId: 16439\n" +
+ " result: <NULL>\n" +
+ " attribute: 'foo'\n" +
+ " }\n" +
+ " }\n" +
+ " width: IntegerResultNode {\n" +
+ " classId: 16491\n" +
+ " value: 5\n" +
+ " }\n" +
+ "}\n",
+ new FixedWidthBucketFunctionNode(new IntegerResultNode(5), new AttributeNode("foo")));
+ }
+
+ private void assertDump(String expected, Object obj) {
+ ObjectDumper dump = new ObjectDumper();
+ dump.visit("test", obj);
+ assertEquals(expected, dump.toString());
+ }
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/expression/RangeBucketPreDefFunctionTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/expression/RangeBucketPreDefFunctionTestCase.java
new file mode 100644
index 00000000000..d2db697c743
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/expression/RangeBucketPreDefFunctionTestCase.java
@@ -0,0 +1,21 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.expression;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertSame;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class RangeBucketPreDefFunctionTestCase {
+
+ @Test
+ public void requireThatAccessorsWork() {
+ ResultNodeVector bucketList = new IntegerResultNodeVector();
+ ExpressionNode arg = new AttributeNode("foo");
+ RangeBucketPreDefFunctionNode node = new RangeBucketPreDefFunctionNode(bucketList, arg);
+ assertSame(bucketList, node.getBucketList());
+ assertSame(arg, node.getArg());
+ }
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/expression/RawBucketResultNodeTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/expression/RawBucketResultNodeTestCase.java
new file mode 100644
index 00000000000..83a36445294
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/expression/RawBucketResultNodeTestCase.java
@@ -0,0 +1,46 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.expression;
+
+import org.junit.Test;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class RawBucketResultNodeTestCase extends ResultNodeTest {
+ @Test
+ public void testEmpty() {
+ RawBucketResultNode bucket = new RawBucketResultNode(new RawResultNode(new byte[]{6, 9}), new RawResultNode(new byte[]{6, 9}));
+ assertTrue(bucket.empty());
+ assertCorrectSerialization(bucket, new RawBucketResultNode());
+ }
+
+ @Test
+ public void testRange() {
+ RawBucketResultNode bucket = new RawBucketResultNode(new RawResultNode(new byte[]{6, 9}), new RawResultNode(new byte[]{9, 6}));
+ assertFalse(bucket.empty());
+ assertThat(bucket.getFrom(), is(new byte[]{6, 9}));
+ assertThat(bucket.getTo(), is(new byte[]{9, 6}));
+ assertCorrectSerialization(bucket, new RawBucketResultNode());
+ assertTrue(dumpNode(bucket).contains("value: RawData(data = [6, 9])"));
+ assertTrue(dumpNode(bucket).contains("value: RawData(data = [9, 6])"));
+ }
+
+ private RawBucketResultNode createNode(int from, int to) {
+ return new RawBucketResultNode(new RawResultNode(new byte[]{(byte)from}),
+ new RawResultNode(new byte[]{(byte)to}));
+ }
+
+ @Test
+ public void testCmp() {
+ assertOrder(createNode(6, 9), createNode(7, 9), createNode(8, 9));
+ assertOrder(createNode(6, 7), createNode(6, 8), createNode(6, 9));
+ assertOrder(createNode(6, 3), createNode(7, 2), createNode(8, 1));
+ assertTrue(createNode(6, 8).onCmp(new NullResultNode()) != 0);
+ }
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/expression/ResultNodeTest.java b/searchlib/src/test/java/com/yahoo/searchlib/expression/ResultNodeTest.java
new file mode 100644
index 00000000000..17744db7edb
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/expression/ResultNodeTest.java
@@ -0,0 +1,43 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.expression;
+
+import com.yahoo.vespa.objects.BufferSerializer;
+import com.yahoo.vespa.objects.ObjectDumper;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class ResultNodeTest {
+ public String dumpNode(ResultNode node) {
+ ObjectDumper dump = new ObjectDumper();
+ node.visitMembers(dump);
+ return dump.toString();
+ }
+
+ public void assertCorrectSerialization(ResultNode from, ResultNode to) {
+ BufferSerializer buffer = new BufferSerializer();
+ from.serialize(buffer);
+ buffer.flip();
+ to.deserialize(buffer);
+ assertThat(from.onCmp(to), is(0));
+ }
+
+ public void assertOrder(ResultNode a, ResultNode b, ResultNode c) {
+ assertTrue(a.onCmp(a) == 0);
+ assertTrue(a.onCmp(b) < 0);
+ assertTrue(a.onCmp(c) < 0);
+
+ assertTrue(b.onCmp(a) > 0);
+ assertTrue(b.onCmp(b) == 0);
+ assertTrue(b.onCmp(c) < 0);
+
+ assertTrue(c.onCmp(a) > 0);
+ assertTrue(c.onCmp(b) > 0);
+ assertTrue(c.onCmp(c) == 0);
+ }
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/expression/ResultNodeVectorTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/expression/ResultNodeVectorTestCase.java
new file mode 100644
index 00000000000..ba306099a80
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/expression/ResultNodeVectorTestCase.java
@@ -0,0 +1,167 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.expression;
+
+import com.yahoo.vespa.objects.BufferSerializer;
+import org.junit.Test;
+
+import java.util.List;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class ResultNodeVectorTestCase extends ResultNodeTest {
+ @Test
+ public void testClassId() {
+ assertThat(new IntegerResultNodeVector().getClassId(), is(IntegerResultNodeVector.classId));
+ assertThat(new Int32ResultNodeVector().getClassId(), is(Int32ResultNodeVector.classId));
+ assertThat(new Int16ResultNodeVector().getClassId(), is(Int16ResultNodeVector.classId));
+ assertThat(new Int8ResultNodeVector().getClassId(), is(Int8ResultNodeVector.classId));
+ assertThat(new FloatResultNodeVector().getClassId(), is(FloatResultNodeVector.classId));
+ }
+
+ @Test
+ public void testVectorAdd() {
+ Int8ResultNodeVector i8 = new Int8ResultNodeVector();
+ i8.add(new Int8ResultNode((byte)9));
+ i8.add(new Int8ResultNode((byte)2));
+ i8.add((ResultNode)new Int8ResultNode((byte)5));
+ assertThat(i8.getVector().size(), is(3));
+
+ Int16ResultNodeVector i16 = new Int16ResultNodeVector();
+ i16.add(new Int16ResultNode((short)9));
+ i16.add(new Int16ResultNode((short)2));
+ i16.add((ResultNode)new Int16ResultNode((short)5));
+ assertThat(i16.getVector().size(), is(3));
+
+ Int32ResultNodeVector i32 = new Int32ResultNodeVector();
+ i32.add(new Int32ResultNode(9));
+ i32.add(new Int32ResultNode(2));
+ i32.add((ResultNode)new Int32ResultNode(5));
+ assertThat(i32.getVector().size(), is(3));
+
+ IntegerResultNodeVector ieger = new IntegerResultNodeVector();
+ ieger.add(new IntegerResultNode(9));
+ ieger.add(new IntegerResultNode(2));
+ ieger.add((ResultNode)new IntegerResultNode(5));
+ assertThat(ieger.getVector().size(), is(3));
+
+ FloatResultNodeVector floatvec = new FloatResultNodeVector();
+ floatvec.add(new FloatResultNode(3.3));
+ floatvec.add(new FloatResultNode(3.4));
+ floatvec.add((ResultNode)new FloatResultNode(3.5));
+ assertThat(floatvec.getVector().size(), is(3));
+ }
+
+ @Test
+ public void testCmp() {
+ ResultNodeVector int8vec = new Int8ResultNodeVector().add(new Int8ResultNode((byte) 2));
+ ResultNodeVector int8veclarge = new Int8ResultNodeVector().add(new Int8ResultNode((byte) 2)).add(new Int8ResultNode((byte) 5));
+ ResultNodeVector int8vecsmall = new Int8ResultNodeVector().add(new Int8ResultNode((byte) 1));
+
+ ResultNodeVector int16vec = new Int16ResultNodeVector().add(new Int16ResultNode((short) 2));
+ ResultNodeVector int16veclarge = new Int16ResultNodeVector().add(new Int16ResultNode((short) 2)).add(new Int16ResultNode((short) 5));
+ ResultNodeVector int16vecsmall = new Int16ResultNodeVector().add(new Int16ResultNode((short) 1));
+
+ ResultNodeVector int32vec = new Int32ResultNodeVector().add(new Int32ResultNode(2));
+ ResultNodeVector int32veclarge = new Int32ResultNodeVector().add(new Int32ResultNode(2)).add(new Int32ResultNode(5));
+ ResultNodeVector int32vecsmall = new Int32ResultNodeVector().add(new Int32ResultNode(1));
+
+ ResultNodeVector intvec = new IntegerResultNodeVector().add(new IntegerResultNode(2));
+ ResultNodeVector intveclarge = new IntegerResultNodeVector().add(new IntegerResultNode(2)).add(new IntegerResultNode(5));
+ ResultNodeVector intvecsmall = new IntegerResultNodeVector().add(new IntegerResultNode(1));
+
+ FloatResultNodeVector floatvec = new FloatResultNodeVector().add(new FloatResultNode(2.2));
+ FloatResultNodeVector floatveclarge = new FloatResultNodeVector().add(new FloatResultNode(2.2)).add(new FloatResultNode(5.5));
+ FloatResultNodeVector floatvecsmall = new FloatResultNodeVector().add(new FloatResultNode(1.2));
+
+ StringResultNodeVector strvec = new StringResultNodeVector().add(new StringResultNode("foo"));
+ StringResultNodeVector strveclarge = new StringResultNodeVector().add(new StringResultNode("foolio"));
+ StringResultNodeVector strvecsmall = new StringResultNodeVector().add(new StringResultNode("bario"));
+
+ RawResultNodeVector rawvec = new RawResultNodeVector().add(new RawResultNode(new byte[]{6, 9}));
+ RawResultNodeVector rawveclarge = new RawResultNodeVector().add(new RawResultNode(new byte[]{9, 6}));
+ RawResultNodeVector rawvecsmall = new RawResultNodeVector().add(new RawResultNode(new byte[]{6, 6}));
+
+ assertClassCmp(int8vec);
+ assertClassCmp(int16vec);
+ assertClassCmp(int32vec);
+ assertClassCmp(intvec);
+ assertClassCmp(floatvec);
+ assertClassCmp(strvec);
+ assertClassCmp(rawvec);
+
+ assertVecEqual(int8vec, int8vec);
+ assertVecLt(int8vec, int8veclarge);
+ assertVecGt(int8veclarge, int8vec);
+ assertVecGt(int8vec, int8vecsmall);
+ assertVecLt(int8vecsmall, int8vec);
+
+ assertVecEqual(int16vec, int16vec);
+ assertVecLt(int16vec, int16veclarge);
+ assertVecGt(int16veclarge, int16vec);
+ assertVecGt(int16vec, int16vecsmall);
+ assertVecLt(int16vecsmall, int16vec);
+
+ assertVecEqual(int32vec, int32vec);
+ assertVecLt(int32vec, int32veclarge);
+ assertVecGt(int32veclarge, int32vec);
+ assertVecGt(int32vec, int32vecsmall);
+ assertVecLt(int32vecsmall, int32vec);
+
+ assertVecEqual(intvec, intvec);
+ assertVecLt(intvec, intveclarge);
+ assertVecGt(intveclarge, intvec);
+ assertVecGt(intvec, intvecsmall);
+ assertVecLt(intvecsmall, intvec);
+
+ assertVecEqual(floatvec, floatvec);
+ assertVecLt(floatvec, floatveclarge);
+ assertVecGt(floatveclarge, floatvec);
+ assertVecGt(floatvec, floatvecsmall);
+ assertVecLt(floatvecsmall, floatvec);
+
+ assertVecEqual(strvec, strvec);
+ assertVecLt(strvec, strveclarge);
+ assertVecGt(strveclarge, strvec);
+ assertVecGt(strvec, strvecsmall);
+ assertVecLt(strvecsmall, strvec);
+
+ assertVecEqual(rawvec, rawvec);
+ assertVecLt(rawvec, rawveclarge);
+ assertVecGt(rawveclarge, rawvec);
+ assertVecGt(rawvec, rawvecsmall);
+ assertVecLt(rawvecsmall, rawvec);
+ }
+
+ private void assertVecLt(ResultNodeVector vec1, ResultNodeVector vec2) {
+ assertTrue(vec1.onCmp(vec2) < 0);
+ }
+
+ private void assertVecGt(ResultNodeVector vec1, ResultNodeVector vec2) {
+ assertTrue(vec1.onCmp(vec2) > 0);
+ }
+
+ private void assertVecEqual(ResultNodeVector vec1, ResultNodeVector vec2) {
+ assertThat(vec1.onCmp(vec2), is(0));
+ }
+
+ private void assertClassCmp(ResultNodeVector add) {
+ assertThat(add.onCmp(new NullResultNode()), is(not(0)));
+ }
+
+ @Test
+ public void testSerialize() throws InstantiationException, IllegalAccessException {
+ assertCorrectSerialization(new FloatResultNodeVector().add(new FloatResultNode(1.1)).add(new FloatResultNode(3.3)), new FloatResultNodeVector());
+ assertCorrectSerialization(new IntegerResultNodeVector().add(new IntegerResultNode(1)).add(new IntegerResultNode(3)), new IntegerResultNodeVector());
+ assertCorrectSerialization(new Int16ResultNodeVector().add(new Int16ResultNode((short) 1)).add(new Int16ResultNode((short) 3)), new Int16ResultNodeVector());
+ assertCorrectSerialization(new Int8ResultNodeVector().add(new Int8ResultNode((byte) 1)).add(new Int8ResultNode((byte) 3)), new Int8ResultNodeVector());
+ assertCorrectSerialization(new StringResultNodeVector().add(new StringResultNode("foo")).add(new StringResultNode("bar")), new StringResultNodeVector());
+ assertCorrectSerialization(new RawResultNodeVector().add(new RawResultNode(new byte[]{6, 9})).add(new RawResultNode(new byte[]{9, 6})), new RawResultNodeVector());
+ }
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/expression/StringBucketResultNodeTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/expression/StringBucketResultNodeTestCase.java
new file mode 100644
index 00000000000..b82c7a34048
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/expression/StringBucketResultNodeTestCase.java
@@ -0,0 +1,57 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.expression;
+
+import org.junit.Test;
+
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author lulf
+ * @since 5.1
+ */
+public class StringBucketResultNodeTestCase extends ResultNodeTest {
+ @Test
+ public void testEmpty() {
+ StringBucketResultNode bucket = new StringBucketResultNode("aaa", "aaa");
+ assertTrue(bucket.empty());
+ assertCorrectSerialization(bucket, new StringBucketResultNode());
+ }
+
+ @Test
+ public void testRange() {
+ StringBucketResultNode bucket = new StringBucketResultNode("a", "d");
+ assertThat(bucket.getFrom(), is("a"));
+ assertThat(bucket.getTo(), is("d"));
+ assertTrue(dumpNode(bucket).contains("value: 'a'"));
+ assertTrue(dumpNode(bucket).contains("value: 'd'"));
+ assertCorrectSerialization(bucket, new StringBucketResultNode());
+ }
+
+ @Test
+ public void testCmp() {
+ StringBucketResultNode b1 = new StringBucketResultNode("a", "d");
+ StringBucketResultNode b2 = new StringBucketResultNode("d", "h");
+ StringBucketResultNode b3 = new StringBucketResultNode("h", "u");
+ assertTrue(b1.onCmp(b1) == 0);
+ assertTrue(b1.onCmp(b2) < 0);
+ assertTrue(b1.onCmp(b3) < 0);
+
+ assertTrue(b2.onCmp(b1) > 0);
+ assertTrue(b2.onCmp(b2) == 0);
+ assertTrue(b2.onCmp(b3) < 0);
+
+ assertTrue(b3.onCmp(b1) > 0);
+ assertTrue(b3.onCmp(b2) > 0);
+ assertTrue(b3.onCmp(b3) == 0);
+
+ b2 = new StringBucketResultNode("a", "b");
+ assertTrue(b1.onCmp(b2) > 0);
+ b2 = new StringBucketResultNode("a", "f");
+ assertTrue(b1.onCmp(b2) < 0);
+ b2 = new StringBucketResultNode("k", "a");
+ assertTrue(b1.onCmp(b2) < 0);
+ assertTrue(b1.onCmp(new NullResultNode()) != 0);
+ }
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/expression/TimeStampFunctionTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/expression/TimeStampFunctionTestCase.java
new file mode 100644
index 00000000000..4d591843321
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/expression/TimeStampFunctionTestCase.java
@@ -0,0 +1,29 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.expression;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class TimeStampFunctionTestCase {
+
+ @Test
+ public void requireThatAccessorsWork() {
+ ExpressionNode arg = new AttributeNode("foo");
+ for (TimeStampFunctionNode.TimePart part : TimeStampFunctionNode.TimePart.values()) {
+ for (Boolean gmt : Arrays.asList(true, false)) {
+ TimeStampFunctionNode node = new TimeStampFunctionNode(arg, part, gmt);
+ assertSame(arg, node.getArg());
+ assertEquals(part, node.getTimePart());
+ assertEquals(gmt, node.isGmt());
+ assertEquals(!gmt, node.isLocal());
+ }
+ }
+ }
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/expression/ZCurveFunctionTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/expression/ZCurveFunctionTestCase.java
new file mode 100644
index 00000000000..899e4e28a20
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/expression/ZCurveFunctionTestCase.java
@@ -0,0 +1,25 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.expression;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ZCurveFunctionTestCase {
+
+ @Test
+ public void requireThatAccessorsWork() {
+ ExpressionNode arg = new AttributeNode("foo");
+ ZCurveFunctionNode node = new ZCurveFunctionNode(arg, ZCurveFunctionNode.Dimension.X);
+ assertSame(arg, node.getArg());
+ assertEquals(ZCurveFunctionNode.Dimension.X, node.getDimension());
+
+ node = new ZCurveFunctionNode(arg, ZCurveFunctionNode.Dimension.Y);
+ assertSame(arg, node.getArg());
+ assertEquals(ZCurveFunctionNode.Dimension.Y, node.getDimension());
+ }
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/gbdt/GbdtConverterTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/gbdt/GbdtConverterTestCase.java
new file mode 100644
index 00000000000..fc21b3496f9
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/gbdt/GbdtConverterTestCase.java
@@ -0,0 +1,169 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.gbdt;
+
+import com.yahoo.searchlib.rankingexpression.RankingExpression;
+import com.yahoo.searchlib.rankingexpression.parser.ParseException;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.ByteArrayOutputStream;
+import java.io.PrintStream;
+import java.io.UnsupportedEncodingException;
+import java.security.Permission;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class GbdtConverterTestCase {
+
+ @Before
+ public void enableSecurityManager() {
+ System.setSecurityManager(new NoExitSecurityManager());
+ }
+
+ @After
+ public void disableSecurityManager() {
+ System.setSecurityManager(null);
+ }
+
+ @Test
+ public void testOnlyOneArgumentIsAccepted() throws UnsupportedEncodingException {
+ assertError("Usage: GbdtConverter <filename>\n", new String[0]);
+ assertError("Usage: GbdtConverter <filename>\n", new String[] { "foo", "bar" });
+ }
+
+ @Test
+ public void testFileIsFound() throws UnsupportedEncodingException {
+ assertError("Could not find file 'not.found'.\n", new String[] { "not.found" });
+ }
+
+ @Test
+ public void testFileParsingExceptionIsCaught() throws UnsupportedEncodingException {
+ assertError("An error occurred while parsing the content of file 'src/test/files/gbdt_err.xml': " +
+ "Node 'Unknown' has no 'DecisionTree' children.\n",
+ new String[] { "src/test/files/gbdt_err.xml" });
+ }
+
+ @Test
+ public void testEmptyTreesAreIgnored() throws Exception {
+ assertConvert("src/test/files/gbdt_empty_tree.xml",
+ "if (INFD_SCORE < 3.2105989, if (GMP_SCORE < 0.013873, if (INFD_SCORE < 1.8138845, 0.0018257, if (GMP_SCORE < 0.006184, 0.0034753, 0.0062119)), if (INFD_SCORE < 1.5684295, if (GMP_SCORE < 0.0217475, 0.0043064, 0.0082065), 0.0110743)), if (GMP_SCORE < 0.010012, if (INFD_SCORE < 5.5982456, if (GMP_SCORE < 0.0052305, 0.0060169, 0.0094888), 0.0119292), 0.017415))\n" +
+ "\n");
+ }
+
+ @Test
+ public void testTreesMayContainAResponse() throws Exception {
+ assertConvert("src/test/files/gbdt_tree_response.xml",
+ "if (INFD_SCORE < 2.128036, -1.12E-5, 8.71E-5) +\n" +
+ "if (value(0) < 1.0, 2.8E-6, 0.0) +\n" +
+ "if (GMP_SCORE < 0.016798, if (INFD_SCORE < 3.9760852, if (INFD_SCORE < 0.1266405, -5.98E-5, 2.25E-5), -1.383E-4), 1.529E-4)\n" +
+ "\n");
+ }
+
+ @Test
+ public void testConvertedModelIsPrintedToSystemOut() throws Exception {
+ assertConvert("src/test/files/gbdt.xml",
+ "if (F55 < 2.0932798, if (F42 < 1.7252731, if (F33 < 0.5, if (F38 < 1.5367546, 1.7333333, 1.3255814), if (F37 < 0.675922, 1.9014085, 1.0)), if (F109 < 0.5, if (F116 < 5.25, if (F111 < 0.0521445, 1.0, 1.9090909), if (F38 < 4.0740733, 0.8, if (F38 < 6.6152048, 1.7142857, 0.625))), 1.5945946)), if (F109 < 0.5, if (F113 < 0.7835808, if (F110 < 491.0, if (F56 < 2.5423126, if (F108 < 243.5, 1.375, 0.78), 0.5), 2.0), if (F103 < 0.9918365, 1.6, 0.3333333)), if (F59 < 0.9207, if (F30 < 0.86, 1.5890411, 0.625), if (F100 < 5.9548216, 1.0, 0.0)))) +\n" +
+ "if (F55 < 59.5480576, if (F42 < 1.8308522, if (F100 < 5.9549484, if (F107 < 0.5, -0.3406279, if (F56 < 1.7057916, if (F36 < 3.778285, if (F103 < 0.5600199, 0.047108, if (F36 < 1.2203553, if (F102 < 1.5, 0.0460316, -0.473794), -0.9825869)), -0.8848045), if (F47 < 15.5, 0.348047, -1.0890411))), 1.75), if (F113 < 0.8389627, if (F110 < 7.5, -0.5778378, if (F111 < 0.8596972, if (F114 < 831.5, if (F113 < 0.3807178, 0.0497646, if (F110 < 63.0, 0.6549377, 0.2486999)), if (F39 < 8.9685574, 0.3222195, -0.1690968)), 1.0381818)), if (F58 < 0.889763, -0.0702703, -1.6))), if (F102 < 3.5, -0.3059684, -1.5890411)) +\n" +
+ "if (F55 < 119.6311035, if (F55 < 90.895813, if (F39 < 12.162282, if (F35 < 1.1213787, if (F55 < 34.9389648, if (F45 < 3.5, if (F51 < 0.0502058, if (F103 < 0.8550526, if (F55 < 4.96804, 0.048519, 0.6596588), if (F38 < 1.3808891, -0.7416763, 0.0176633)), 0.4502234), -0.6811898), 0.5572351), if (F100 < 3.3971992, if (F39 < 7.0869236, if (F43 < 5.5100875, if (F46 < 4.5, -0.1702421, -0.9797453), -1.5426025), 0.0774408), if (F52 < 22.3562355, if (F35 < 4.4263992, 0.4011598, -0.3898472), -1.75))), if (F39 < 14.5762558, if (F109 < 0.5, 1.6616928, 0.4001626), if (F100 < 3.0519419, 0.616491, -0.1808479))), -1.2135522), 0.5535716) +\n" +
+ "if (F43 < 9.272151, if (F36 < 9.0613861, if (F115 < 36.5, if (F34 < 1.4407213, if (F41 < 10.4713802, if (F34 < 1.2610778, if (F105 < 8.2159586, if (F46 < 88.5, 0.0075843, -0.6358738), if (F105 < 9.5308332, 1.4464284, -0.0895592)), 0.3532708), -1.8289603), if (F45 < 24.5, if (F111 < 0.9095335, if (F113 < 0.0529755, -0.6272416, if (F50 < 34.2163391, if (F113 < 0.0813664, 0.3683843, if (F34 < 1.6283135, -0.6334628, -0.1610307)), 1.5559684)), -1.7492068), 1.5060212)), if (F49 < 23.5787125, if (F100 < 6.5115452, if (F37 < 0.8601408, if (F57 < 6.5, 0.0547747, 1.193346), 0.6402962), 1.7395205), 2.5559684)), -3.1016318), 1.8657542) +\n" +
+ "if (F55 < 764.9404297, if (F34 < 23.2379246, if (F36 < 9.2296076, if (F114 < 116.0, if (F108 < 13.5, if (F108 < 12.5, -0.2736142, -1.7384173), if (F110 < 10.5, 0.0794336, -0.2171646)), if (F114 < 129.0, if (F109 < 0.5, 1.4407836, -0.1458547), if (F111 < 0.9703438, if (F47 < 18.5, if (F32 < 3.5, 0.0708936, if (F118 < 0.6794872, if (F119 < 3.8533711, if (F34 < 0.1213822, -2.0046196, -8.566E-4), -0.9490828), 0.0790339)), if (F113 < 0.3637481, 0.1161088, -0.9997786)), 1.3003114))), if (F111 < 0.2438112, -2.0582902, 0.6918949)), if (F115 < 95.0, -2.8602383, -0.0063699)), if (F101 < 0.9411763, -2.0253283, -0.6417007)) +\n" +
+ "if (F114 < 516.0, if (F49 < 8.9197922, if (F48 < 3.5, if (F36 < 1.3889931, if (F43 < 0.9699799, if (F34 < 9.6113167, if (F106 < 8.5, if (F108 < 153.5, if (F110 < 130.5, 0.180242, 2.545163), if (F108 < 161.5, -2.2253985, if (F55 < 31.4965668, -0.0122572, 0.7364454))), -0.2596613), 0.7247348), if (F111 < 0.2817393, -0.6409092, 0.2100071)), if (F116 < 18.75, 0.511352, -0.1093323)), 0.9379161), 0.3603908), if (F46 < 32.5, if (F46 < 5.5, if (F39 < 11.7440758, if (F115 < 774.0, -0.0433343, -1.7439904), -0.3662575), 0.5413771), if (F110 < 67.0, if (F46 < 34.5, -2.6581287, -0.9399502), 0.075664))) +\n" +
+ "if (F42 < 24.3080139, if (F118 < 0.8452381, if (F119 < 6.2847767, if (F100 < 3.2778931, if (F46 < 30.0, if (F43 < 1.2712233, if (F104 < 3.5, 0.1365837, 0.5592712), if (F39 < 0.6294491, -0.8729556, -0.0123421)), 3.7677864), if (F111 < 0.6580936, if (F103 < 0.9319581, -0.2822538, if (F107 < 1.5, -0.3983539, if (F104 < 5.5, 0.0792465, 0.7273864))), if (F104 < 3.5, -1.1550477, 0.0490706))), 1.4735778), if (F111 < 0.3724709, if (F51 < 16.0989189, if (F114 < 154.0, if (F108 < 57.5, -0.0675733, -0.3994327), -0.0250285), -1.4871782), if (F34 < 2.1943491, 0.0229469, if (F108 < 1527.0, 1.4706301, 0.0285333)))), 3.489949) +\n" +
+ "if (F34 < 30.3465347, if (F103 < 0.9996098, if (F38 < 0.558669, if (F105 < 3.6287756, if (F104 < 3.5, if (F31 < 0.86, 0.1121421, 1.8153648), -0.8281607), if (F55 < 37.6819153, 0.9656266, 0.1585065)), if (F113 < 0.840385, if (F38 < 9.6623116, if (F46 < 136.0, if (F53 < 0.5548913, if (F38 < 8.4469957, if (F34 < 3.1969421, if (F114 < 20.0, -0.2944335, 0.03499), if (F34 < 3.4671984, -1.3154796, -0.1742507)), 0.4071658), if (F105 < 2.315434, if (F110 < 59.5, -0.1713032, -1.420465), -0.1456236)), 0.5520287), if (F108 < 12156.5, if (F111 < 0.3892631, -0.16285, -0.9015614), -2.6391831)), 0.2011691)), -3.073049), -3.2461861) +\n" +
+ "if (F55 < 28.4668102, if (F34 < 0.4929269, if (F30 < 0.86, if (F37 < 0.8360082, -0.0815482, -0.7898247), -0.5144471), if (F108 < 20498.0, if (F44 < 1.1856511, if (F56 < 1.0706565, if (F39 < 8.377079, if (F59 < 0.5604, 0.0429508, if (F34 < 0.7287493, -1.0264078, 0.6052195)), -0.4814408), if (F119 < 3.7530813, if (F115 < 8.5, 0.4916013, 0.0457533), if (F114 < 1093.5, 1.1673864, 0.3411176))), -0.6176305), if (F100 < 3.151973, 2.6908011, 0.3835885))), if (F116 < 62.0, if (F114 < 562.0, -0.415543, if (F103 < 0.9826763, -0.1169933, if (F104 < 0.5, -0.0665763, 1.0238317))), if (F100 < 5.8046961, -3.2954836, 0.2781039))) +\n" +
+ "if (F34 < 26.9548168, if (F35 < 18.4714928, if (F115 < 698.0, if (F116 < 41.5, if (F38 < 1.1138718, if (F46 < 9.0, if (F31 < 0.86, 0.1059075, -0.2995292), if (F46 < 25.5, if (F46 < 13.0, 0.6297316, 1.8451736), 0.2079161)), if (F38 < 19.3839836, if (F49 < 29.9797497, if (F46 < 235.5, if (F38 < 1.2626771, -0.5165347, if (F35 < 10.3027954, if (F50 < 0.2823648, -0.0424489, if (F113 < 0.0776736, 0.7495954, -0.2948665)), 0.3229146)), -1.0711968), 0.3153474), if (F116 < 5.2182379, 2.8017734, 0.3444192))), if (F113 < 0.5691726, 1.7530511, 0.3534861)), -2.4915219), if (F103 < 0.9680555, -2.1724317, 0.2143739)), 3.1712332)\n" +
+ "\n");
+ }
+
+ @Test
+ public void testSetTestsWork() throws Exception {
+ assertConvert("src/test/files/gbdt_set_inclusion_test.xml",
+ "if (AGE_GROUP$ in [2.0], if (EDUCATION_LEVEL$ in [0.0], -0.25, 0.125), if (AGE_GROUP$ in [1.0], 0.125, 0.25)) +\n" +
+ "if (AGE_GROUP$ in [2.0], if (EDUCATION_LEVEL$ in [0.0], -0.2189117, -0.0), if (EDUCATION_LEVEL$ in [0.0], 0.1094559, 0.2343953)) +\n" +
+ "if (AGE_GROUP$ in [2.0], -0.0962185, if (EDUCATION_LEVEL$ in [0.0], if (AGE_GROUP$ in [1.0], 0.0, 0.2055456), 0.205553)) +\n" +
+ "if (EDUCATION_LEVEL$ in [0.0], 0.0905977, 0.1812016) +\n" +
+ "if (EDUCATION_LEVEL$ in [0.0, 1.0], if (AGE_GROUP$ in [2.0], if (EDUCATION_LEVEL$ in [0.0], -0.191772, -0.0), if (AGE_GROUP$ in [1.0], if (EDUCATION_LEVEL$ in [0.0], 0.0, 0.1608304), 0.1708644)), 0.1923393) +\n" +
+ "if (EDUCATION_LEVEL$ in [\"foo\", \"bar\"], if (AGE_GROUP$ in [2.0], if (EDUCATION_LEVEL$ in [\"baz\"], -0.1696624, -0.0), if (AGE_GROUP$ in [1.0], if (EDUCATION_LEVEL$ in [0.0], 0.0, 0.1438091), 0.1521967)), 0.2003772) +\n" +
+ "if (value(0) < 1.0, -0.0108278, 0.0) +\n" +
+ "if (EDUCATION_LEVEL$ in [0.0], -0.1500528, if (GENDER$ in [1.0], 0.0652894, 0.1543407)) +\n" +
+ "if (AGE_GROUP$ in [1.0], 0.0, 0.1569706) +\n" +
+ "if (AGE_GROUP$ in [1.0], 0.0, if (EDUCATION_LEVEL$ in [1.0], 0.0, 0.1405829))\n" +
+ "\n");
+ }
+
+ @Test
+ public void testExtModelCausesBranchProbabilitiesToBeUsed() throws Exception {
+ assertConvert("src/test/files/gbdt.ext.xml",
+ "if (F4 < 0.6972222, if (F1 < 0.7928572, if (F54 < 0.9166666, 0.1145211, if (F111 < 1105.0, 0.3115265, 1.6772487, 0.77256316), 0.89193755), 1.493617, 0.970347), if (F111 < 85.5, 1.1202186, 2.5111421, 0.33763838), 0.93598676) +\n" +
+ "if (F1 < 0.8875, if (F1 < 0.0634921, 0.4755052, if (F111 < 8765.0, -0.0572274, 0.542222, 0.983461), 0.04500549), if (F114 < 55.0, -0.2409815, if (F54 < 0.55, 0.2211539, 1.3125561, 0.29620853), 0.21268657), 0.9683477) +\n" +
+ "if (F4 < 0.6972222, if (F3 < 0.9285715, if (F8 < 0.0540936, -0.007629, 0.322873, 0.95869595), if (F1 < 0.8166667, 0.843579, 0.1053924, 0.57522124), 0.97148263), if (F4 < 0.7619048, -0.5500016, 0.0274784, 0.5784133), 0.93598676) +\n" +
+ "if (F74 < 0.875, if (F54 < 0.8452381, -0.0031926, if (F111 < 141.5, -0.1402742, if (F4 < 0.5871212, 1.2691849, 0.2681826, 0.35703), 0.47206005), 0.92346483), if (F111 < 1105.0, -0.0588169, -0.7294473, 0.7697161), 0.92512107) +\n" +
+ "if (F1 < 0.7619048, 0.0089472, if (F3 < 0.9285715, if (F114 < 36.5, -1.1389426, if (F97 < 0.0468557, if (F6 < 0.5357143, 0.5614127, -0.2162048, 0.32456142), -0.8289478, 0.742671), 0.21483377), 0.0168442, 0.3867458), 0.9402976) +\n" +
+ "if (F1 < 0.6583333, -0.0187975, if (F74 < 0.2104235, 0.1951745, if (F68 < 0.8158333, if (F68 < 0.7616667, -0.0701389, -1.908711, 0.8685714), if (F91 < 0.9516667, 0.2880719, 0.0202404, 0.08918849), 0.043402776), 0.12821622), 0.72688085) +\n" +
+ "if (F97 < 0.0104738, if (F4 < 0.6833333, -0.1119661, -0.7331711, 0.795539), if (F111 < 1.5, -0.0487729, if (F54 < 0.0294118, if (F6 < 0.225, 0.3140816, 0.0241852, 0.44444445), 0.0063921, 0.077068806), 0.20816082), 0.015885202) +\n" +
+ "if (F8 < 0.0488095, if (F97 < 0.0196587, -0.037317, if (F4 < 0.5527778, 0.0085123, if (F111 < 4064.5, if (F111 < 109.5, 0.2020749, -0.1841633, 0.5994437), 0.4359319, 0.8789731), 0.86483806), 0.24595065), -0.1090751, 0.94791543) +\n" +
+ "if (F111 < 7801.5, 0.005243, if (F4 < 0.5444444, -0.4434354, if (F4 < 0.725, if (F111 < 86382.5, if (F77 < 0.0250039, 0.9485625, 0.1099304, 0.2840909), -1.5740248, 0.9361702), -0.2924902, 0.48205128), 0.47580644), 0.97803235) +\n" +
+ "if (F4 < 0.9270834, if (F1 < 0.8166667, 0.0033574, if (F4 < 0.7071428, -0.2470163, 0.0482702, 0.5796915), 0.9535162), if (F54 < 0.5833334, 0.8142192, if (F1 < 0.95, 1.2211719, -0.0357525, 0.07643312), 0.20304568), 0.9883666) +\n" +
+ "if (F113 < 37.5050011, if (F111 < 252.5, -0.0110506, if (F4 < 0.69375, if (F5 < 0.9, 0.0488562, 0.3987899, 0.9362022), if (F74 < 0.75, -0.2113237, 0.3806402, 0.8606272), 0.8527072), 0.7694356), -0.5899943, 0.9981103) +\n" +
+ "if (F3 < 0.4365079, -0.0192181, if (F77 < 0.1715686, if (F111 < 1187.5, 0.016142, if (F112 < 467.5, if (F68 < 0.855, 0.9831077, 0.227789, 0.12048193), 0.0345274, 0.36617646), 0.89238805), 0.7605657, 0.9962163), 0.62542814) +\n" +
+ "if (F5 < 0.6125, if (F4 < 0.7928572, 0.0063205, 1.68561, 0.99925923), if (F113 < 1.6900001, if (F113 < 1.635, -0.0275853, 1.1438084, 0.99412453), if (F97 < 0.0363399, -0.0843354, -0.346791, 0.552356), 0.8166987), 0.876934) +\n" +
+ "if (F8 < 0.1396104, -0.001079, if (F54 < 0.55, if (F111 < 513.5, if (F77 < 0.0380987, -0.1117221, 0.9370234, 0.6551724), 1.654114, 0.7631579), if (F113 < 1.0700001, 0.1069487, -1.0835573, 0.8292683), 0.48101267), 0.9953348) +\n" +
+ "if (F6 < 0.7321429, 0.0033418, if (F111 < 74.5, if (F4 < 0.6708333, if (F1 < 0.5435606, 0.5229282, -0.451666, 0.11594203), 0.253665, 0.3270142), if (F113 < 2.47, -0.2267124, 0.2586769, 0.8803419), 0.4741573), 0.947443)\n" +
+ "\n");
+ }
+
+ private static void assertConvert(String gbdtModelFile, String expectedExpression)
+ throws ParseException, UnsupportedEncodingException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ System.setOut(new PrintStream(out));
+ GbdtConverter.main(new String[] { gbdtModelFile });
+ String actualExpression = out.toString("UTF-8");
+ assertEquals(expectedExpression, actualExpression);
+ assertNotNull(new RankingExpression(actualExpression));
+ }
+
+ private static void assertError(String expected, String[] args) throws UnsupportedEncodingException {
+ ByteArrayOutputStream err = new ByteArrayOutputStream();
+ System.setErr(new PrintStream(err));
+ try {
+ GbdtConverter.main(args);
+ fail();
+ } catch (ExitException e) {
+ assertEquals(1, e.status);
+ assertEquals(expected, err.toString("UTF-8"));
+ }
+ }
+
+ private static class NoExitSecurityManager extends SecurityManager {
+
+ @Override
+ public void checkPermission(Permission perm) {
+ // allow anything
+ }
+
+ @Override
+ public void checkPermission(Permission perm, Object context) {
+ // allow anything
+ }
+
+ @Override
+ public void checkExit(int status) {
+ throw new ExitException(status);
+ }
+ }
+
+ private static class ExitException extends SecurityException {
+
+ final int status;
+
+ ExitException(int status) {
+ this.status = status;
+ }
+ }
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/gbdt/GbdtModelTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/gbdt/GbdtModelTestCase.java
new file mode 100644
index 00000000000..0561fb8ac7f
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/gbdt/GbdtModelTestCase.java
@@ -0,0 +1,65 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.gbdt;
+
+import com.yahoo.searchlib.rankingexpression.RankingExpression;
+import org.junit.Test;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class GbdtModelTestCase {
+
+ @Test
+ public void requireThatFactoryMethodWorks() throws Exception {
+ GbdtModel model = GbdtModel.fromXmlFile("src/test/files/gbdt.xml");
+ assertEquals(10, model.trees().size());
+ String exp = model.toRankingExpression();
+ assertEquals(readFile("src/test/files/gbdt.expression").trim(), exp.trim());
+ assertNotNull(new RankingExpression(exp));
+ }
+
+ @Test
+ public void requireThatIllegalXmlThrowsException() throws Exception {
+ assertIllegalXml("<Unknown />");
+ assertIllegalXml("<DecisionTree />");
+ assertIllegalXml("<DecisionTree>" +
+ " <Unknown />" +
+ "</DecisionTree>");
+ assertIllegalXml("<DecisionTree>" +
+ " <Forest />" +
+ "</DecisionTree>");
+ assertIllegalXml("<DecisionTree>" +
+ " <Forest>" +
+ " <Unknown />" +
+ " </Forest>" +
+ "</DecisionTree>");
+ }
+
+ private static void assertIllegalXml(String xml) throws Exception {
+ try {
+ GbdtModel.fromXml(xml);
+ fail();
+ } catch (IllegalArgumentException e) {
+
+ }
+ }
+
+ private static String readFile(String file) throws IOException {
+ StringBuilder ret = new StringBuilder();
+ BufferedReader in = new BufferedReader(new FileReader(file));
+ while (true) {
+ String str = in.readLine();
+ if (str == null) {
+ break;
+ }
+ ret.append(str).append("\n");
+ }
+ return ret.toString();
+ }
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/gbdt/ReferenceNodeTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/gbdt/ReferenceNodeTestCase.java
new file mode 100644
index 00000000000..6b4e075b769
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/gbdt/ReferenceNodeTestCase.java
@@ -0,0 +1,101 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.gbdt;
+
+import com.yahoo.searchlib.rankingexpression.evaluation.DoubleValue;
+import org.junit.Test;
+
+import java.util.Optional;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class ReferenceNodeTestCase {
+
+ @Test
+ public void requireThatAccessorsWork() {
+ TreeNode lhs = new ResponseNode(6.0, null);
+ TreeNode rhs = new ResponseNode(9.0, null);
+ NumericFeatureNode node = new NumericFeatureNode("foo", new DoubleValue(6.9), null, lhs, rhs);
+ assertEquals("foo", node.feature());
+ assertEquals(6.9, node.value().asDouble(), 1E-6);
+ assertSame(lhs, node.left());
+ assertSame(rhs, node.right());
+ }
+
+ @Test
+ public void requireThatRankingExpressionCanBeGenerated() {
+ assertExpression("if (a < 0.0, b, c)", new NumericFeatureNode("a", new DoubleValue(0), null, new MyNode("b"), new MyNode("c")));
+ assertExpression("if (d < 1.0, e, f)", new NumericFeatureNode("d", new DoubleValue(1), null, new MyNode("e"), new MyNode("f")));
+ assertExpression("if (d < 1.0, e, f, 0.5)", new NumericFeatureNode("d", new DoubleValue(1), null, new MyNode("e", 1), new MyNode("f", 1)));
+ assertExpression("if (d < 1.0, e, f, 0.75)", new NumericFeatureNode("d", new DoubleValue(1), null, new MyNode("e", 3), new MyNode("f", 1)));
+ }
+
+ @Test
+ public void requireThatNodeCanBeGeneratedFromDomNode() throws Exception {
+ String xml = "<Node feature='a' value='1'>\n" +
+ " <Response value='2' />\n" +
+ " <Response value='4' />\n" +
+ "</Node>\n";
+ NumericFeatureNode node = (NumericFeatureNode)FeatureNode.fromDom(XmlHelper.parseXml(xml));
+ assertEquals("a", node.feature());
+ assertEquals(1, node.value().asDouble(), 1E-6);
+ assertTrue(node.left() instanceof ResponseNode);
+ assertEquals(2, ((ResponseNode)node.left()).value(), 1E-6);
+ assertTrue(node.right() instanceof ResponseNode);
+ assertEquals(4, ((ResponseNode)node.right()).value(), 1E-6);
+ }
+
+ @Test
+ public void requireThatUnknownNodeThrowsException() throws Exception {
+ String xml = "<Node feature='a' value='1'>\n" +
+ " <Response value='2' />\n" +
+ "</Node>\n";
+ try {
+ TreeNode.fromDom(XmlHelper.parseXml(xml));
+ fail();
+ } catch (IllegalArgumentException e) {
+
+ }
+ xml = "<Node feature='a' value='1'>\n" +
+ " <Response value='2' />\n" +
+ " <Response value='4' />\n" +
+ " <Response value='8' />\n" +
+ "</Node>\n";
+ try {
+ TreeNode.fromDom(XmlHelper.parseXml(xml));
+ fail();
+ } catch (IllegalArgumentException e) {
+
+ }
+ }
+
+ private static void assertExpression(String expected, TreeNode node) {
+ assertEquals(expected, node.toRankingExpression());
+ }
+
+ private static class MyNode extends TreeNode {
+
+ final String str;
+
+ MyNode(String str) {
+ this(str, Optional.empty());
+ }
+
+ MyNode(String str, int samples) {
+ super(Optional.of(samples));
+ this.str = str;
+ }
+
+ MyNode(String str, Optional<Integer> samples) {
+ super(samples);
+ this.str = str;
+ }
+
+ @Override
+ public String toRankingExpression() {
+ return str;
+ }
+ }
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/gbdt/ResponseNodeTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/gbdt/ResponseNodeTestCase.java
new file mode 100644
index 00000000000..7d6022fa304
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/gbdt/ResponseNodeTestCase.java
@@ -0,0 +1,40 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.gbdt;
+
+import org.junit.Test;
+import org.xml.sax.SAXException;
+
+import javax.xml.parsers.ParserConfigurationException;
+import java.io.IOException;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class ResponseNodeTestCase {
+
+ @Test
+ public void requireThatAccessorsWork() {
+ ResponseNode node = new ResponseNode(6.9, null);
+ assertEquals(6.9, node.value(), 1E-6);
+ }
+
+ @Test
+ public void requireThatRankingExpressionCanBeGenerated() {
+ assertExpression("0.0", new ResponseNode(0, null));
+ assertExpression("1.0", new ResponseNode(1, null));
+ }
+
+ @Test
+ public void requireThatNodeCanBeGeneratedFromDomNode() throws ParserConfigurationException, IOException, SAXException {
+ String xml = "<Response value='1' />\n";
+ ResponseNode node = ResponseNode.fromDom(XmlHelper.parseXml(xml));
+ assertEquals(1, node.value(), 1E-6);
+ }
+
+ private static void assertExpression(String expected, TreeNode node) {
+ assertEquals(expected, node.toRankingExpression());
+ }
+
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/gbdt/TreeNodeTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/gbdt/TreeNodeTestCase.java
new file mode 100644
index 00000000000..572bd2d8c11
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/gbdt/TreeNodeTestCase.java
@@ -0,0 +1,57 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.gbdt;
+
+import org.junit.Test;
+import org.xml.sax.SAXException;
+
+import javax.xml.parsers.ParserConfigurationException;
+import java.io.IOException;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class TreeNodeTestCase {
+
+ @Test
+ public void requireThatFeatureNodeCanBeGeneratedFromDomNode()
+ throws ParserConfigurationException, IOException, SAXException
+ {
+ String xml = "<Node feature='a' value='1'>\n" +
+ " <Response value='2' />\n" +
+ " <Response value='4' />\n" +
+ "</Node>\n";
+ TreeNode obj = TreeNode.fromDom(XmlHelper.parseXml(xml));
+ assertTrue(obj instanceof FeatureNode);
+ NumericFeatureNode node = (NumericFeatureNode)obj;
+ assertEquals("a", node.feature());
+ assertEquals(1, node.value().asDouble(), 1E-6);
+ assertTrue(node.left() instanceof ResponseNode);
+ assertEquals(2, ((ResponseNode)node.left()).value(), 1E-6);
+ assertTrue(node.right() instanceof ResponseNode);
+ assertEquals(4, ((ResponseNode)node.right()).value(), 1E-6);
+ }
+
+ @Test
+ public void requireThatResponseNodeCanBeGeneratedFromDomNode()
+ throws ParserConfigurationException, IOException, SAXException
+ {
+ String xml = "<Response value='1' />\n";
+ TreeNode obj = TreeNode.fromDom(XmlHelper.parseXml(xml));
+ assertTrue(obj instanceof ResponseNode);
+ assertEquals(1, ((ResponseNode)obj).value(), 1E-6);
+ }
+
+ @Test
+ public void requireThatUnknownNodeThrowsException()
+ throws ParserConfigurationException, IOException, SAXException
+ {
+ try {
+ TreeNode.fromDom(XmlHelper.parseXml("<Unknown />"));
+ fail();
+ } catch (UnsupportedOperationException e) {
+ assertEquals("Unknown", e.getMessage());
+ }
+ }
+} \ No newline at end of file
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/gbdt/XmlHelperTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/gbdt/XmlHelperTestCase.java
new file mode 100644
index 00000000000..7dc7c42f590
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/gbdt/XmlHelperTestCase.java
@@ -0,0 +1,153 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.gbdt;
+
+import org.junit.Test;
+import org.w3c.dom.Element;
+
+import java.util.List;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen Hult</a>
+ */
+public class XmlHelperTestCase {
+
+ @Test
+ public void requireThatAttributeTextCanBeRetrieved() throws Exception {
+ Element node = XmlHelper.parseXml("<element a1='v1' a2='v2' />");
+ assertEquals("v1", XmlHelper.getAttributeText(node, "a1"));
+ assertEquals("v2", XmlHelper.getAttributeText(node, "a2"));
+ }
+
+ @Test
+ public void requireThatMissingAttributeTextThrowsIllegalArgument() throws Exception {
+ try {
+ XmlHelper.getAttributeText(XmlHelper.parseXml("<element />"), "a1");
+ fail();
+ } catch (IllegalArgumentException e) {
+
+ }
+ try {
+ XmlHelper.getAttributeText(XmlHelper.parseXml("<element a1='' />"), "a1");
+ fail();
+ } catch (IllegalArgumentException e) {
+
+ }
+ }
+
+ @Test
+ public void requireThatSingleElementCanBeRetrieved() throws Exception {
+ String xml = "<parent>" +
+ " <child id='a' />" +
+ "</parent>";
+ Element element = XmlHelper.getSingleElement(XmlHelper.parseXml(xml), null);
+ assertNotNull(element);
+ assertEquals("a", XmlHelper.getAttributeText(element, "id"));
+ }
+
+ @Test
+ public void requireThatNamedSingleElementCanBeRetrieved() throws Exception {
+ String xml = "<parent>" +
+ " <bastard id='a' />" +
+ " <child id='b' />" +
+ " <bastard id='c' />" +
+ "</parent>";
+ Element element = XmlHelper.getSingleElement(XmlHelper.parseXml(xml), "child");
+ assertNotNull(element);
+ assertEquals("b", XmlHelper.getAttributeText(element, "id"));
+ }
+
+ @Test
+ public void requireThatMissingSingleElementThrowsIllegalArgument() throws Exception {
+ try {
+ XmlHelper.getSingleElement(XmlHelper.parseXml("<parent />"), null);
+ fail();
+ } catch (IllegalArgumentException e) {
+
+ }
+ }
+
+ @Test
+ public void requireThatMissingNamedSingleElementThrowsIllegalArgument() throws Exception {
+ String xml = "<parent>" +
+ " <bastard id='a' />" +
+ "</parent>";
+ try {
+ XmlHelper.getSingleElement(XmlHelper.parseXml(xml), "child");
+ fail();
+ } catch (IllegalArgumentException e) {
+
+ }
+ }
+
+ @Test
+ public void requireThatAmbigousSingleElementThrowsIllegalArgument() throws Exception {
+ String xml = "<parent>" +
+ " <child id='a' />" +
+ " <child id='b' />" +
+ "</parent>";
+ try {
+ XmlHelper.getSingleElement(XmlHelper.parseXml(xml), null);
+ fail();
+ } catch (IllegalArgumentException e) {
+
+ }
+ }
+
+ @Test
+ public void requireThatAmbigousNamedSingleElementThrowsIllegalArgument() throws Exception {
+ String xml = "<parent>" +
+ " <child id='a' />" +
+ " <bastard id='b' />" +
+ " <child id='c' />" +
+ "</parent>";
+ try {
+ XmlHelper.getSingleElement(XmlHelper.parseXml(xml), "child");
+ fail();
+ } catch (IllegalArgumentException e) {
+
+ }
+ }
+
+ @Test
+ public void requireThatChildElementsCanBeRetrieved() throws Exception {
+ String xml = "<parent>" +
+ " <child id='a' />" +
+ " <child id='b' />" +
+ "</parent>";
+ List<Element> lst = XmlHelper.getChildElements(XmlHelper.parseXml(xml), null);
+ assertNotNull(lst);
+ assertEquals(2, lst.size());
+ assertEquals("a", XmlHelper.getAttributeText(lst.get(0), "id"));
+ assertEquals("b", XmlHelper.getAttributeText(lst.get(1), "id"));
+ }
+
+ @Test
+ public void requireThatNamedChildElementsCanBeRetrieved() throws Exception {
+ String xml = "<parent>" +
+ " <child id='a' />" +
+ " <bastard id='b' />" +
+ " <child id='c' />" +
+ "</parent>";
+ List<Element> lst = XmlHelper.getChildElements(XmlHelper.parseXml(xml), "child");
+ assertNotNull(lst);
+ assertEquals(2, lst.size());
+ assertEquals("a", XmlHelper.getAttributeText(lst.get(0), "id"));
+ assertEquals("c", XmlHelper.getAttributeText(lst.get(1), "id"));
+ }
+
+ @Test
+ public void requireThatChildElementsAreNeverNull() throws Exception {
+ List<Element> lst = XmlHelper.getChildElements(XmlHelper.parseXml("<parent />"), null);
+ assertNotNull(lst);
+ assertTrue(lst.isEmpty());
+ }
+
+ @Test
+ public void requireThatNamedChildElementsAreNeverNull() throws Exception {
+ List<Element> lst = XmlHelper.getChildElements(XmlHelper.parseXml("<parent />"), "child");
+ assertNotNull(lst);
+ assertTrue(lst.isEmpty());
+ }
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/mlr/ga/test/CsvFileCaseListTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/mlr/ga/test/CsvFileCaseListTestCase.java
new file mode 100644
index 00000000000..e95af6ad61d
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/mlr/ga/test/CsvFileCaseListTestCase.java
@@ -0,0 +1,81 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.mlr.ga.test;
+
+import com.yahoo.searchlib.mlr.ga.TrainingParameters;
+import com.yahoo.searchlib.mlr.ga.caselist.CsvFileCaseList;
+import com.yahoo.yolean.Exceptions;
+import com.yahoo.searchlib.mlr.ga.TrainingSet;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class CsvFileCaseListTestCase {
+
+ private static final double delta = 0.000001;
+
+ @Test
+ public void testLegalFile() {
+ CsvFileCaseList list = new CsvFileCaseList("src/test/files/mlr/cases.csv");
+
+ assertEquals(3,list.cases().size());
+ {
+ TrainingSet.Case case1 = list.cases().get(0);
+ assertEquals(1.0, case1.targetValue(), delta);
+ assertEquals(2, case1.arguments().names().size());
+ assertEquals(2.0, case1.arguments().get("arg1").asDouble(),delta);
+ assertEquals(-1.3, case1.arguments().get("arg2").asDouble(),delta);
+ }
+
+ {
+ TrainingSet.Case case2 = list.cases().get(1);
+ assertEquals(-1.003, case2.targetValue(), delta);
+ assertEquals(1, case2.arguments().names().size());
+ assertEquals(500007, case2.arguments().get("arg1").asDouble(),delta);
+ }
+
+ {
+ TrainingSet.Case case3 = list.cases().get(2);
+ assertEquals(0, case3.targetValue(), delta);
+ assertEquals(1, case3.arguments().names().size());
+ assertEquals(1.0, case3.arguments().get("arg2").asDouble(),delta);
+ }
+
+ TrainingSet trainingSet = new TrainingSet(list, new TrainingParameters());
+ assertEquals(2, trainingSet.argumentNames().size());
+ assertTrue(trainingSet.argumentNames().contains("arg1"));
+ assertTrue(trainingSet.argumentNames().contains("arg2"));
+ }
+
+ @Test
+ public void testNonExistingFile() {
+ try {
+ new CsvFileCaseList("nosuchfile");
+ }
+ catch (IllegalArgumentException e) {
+ assertEquals("Could not create a case list from file 'nosuchfile': nosuchfile (No such file or directory)", Exceptions.toMessageString(e));
+ }
+ }
+
+ @Test
+ public void testInvalidFile1() {
+ try {
+ new CsvFileCaseList("src/test/files/mlr/cases-illegal1.csv");
+ }
+ catch (IllegalArgumentException e) {
+ assertEquals("Could not create a case list from file 'src/test/files/mlr/cases-illegal1.csv': At line 5, element 3: Expected argument on the form 'identifier:double', got ' arg2:'", Exceptions.toMessageString(e));
+ }
+ }
+
+ @Test
+ public void testInvalidFile2() {
+ try {
+ new CsvFileCaseList("src/test/files/mlr/cases-illegal2.csv");
+ }
+ catch (IllegalArgumentException e) {
+ assertEquals("Could not create a case list from file 'src/test/files/mlr/cases-illegal2.csv': At line 2: Expected a target value double at the start of the line, got '5db'", Exceptions.toMessageString(e));
+ }
+ }
+
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/mlr/ga/test/ExampleLearningSessions.java b/searchlib/src/test/java/com/yahoo/searchlib/mlr/ga/test/ExampleLearningSessions.java
new file mode 100644
index 00000000000..fc834181f53
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/mlr/ga/test/ExampleLearningSessions.java
@@ -0,0 +1,110 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.mlr.ga.test;
+
+import com.yahoo.searchlib.mlr.ga.PrintingTracker;
+import com.yahoo.searchlib.mlr.ga.RankingExpressionCaseList;
+import com.yahoo.searchlib.mlr.ga.Trainer;
+import com.yahoo.searchlib.mlr.ga.TrainingParameters;
+import com.yahoo.searchlib.mlr.ga.TrainingSet;
+import com.yahoo.searchlib.rankingexpression.RankingExpression;
+import com.yahoo.searchlib.rankingexpression.evaluation.Context;
+import com.yahoo.searchlib.rankingexpression.evaluation.MapContext;
+import com.yahoo.searchlib.rankingexpression.parser.ParseException;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Main class - drives a learning session from the command line.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class ExampleLearningSessions {
+
+ public static void main(String[] args) throws ParseException {
+ test3();
+ }
+
+ // Always learnt precisely in less than a second
+ private static void test1() throws ParseException {
+ TrainingParameters parameters = new TrainingParameters();
+
+ RankingExpression target = new RankingExpression("2*x");
+ List<Context> arguments = new ArrayList<>();
+ arguments.add(MapContext.fromString("x:0").freeze());
+ arguments.add(MapContext.fromString("x:1").freeze());
+ arguments.add(MapContext.fromString("x:2").freeze());
+ TrainingSet trainingSet = new TrainingSet(new RankingExpressionCaseList(arguments, target), parameters);
+
+ Trainer trainer = new Trainer(trainingSet);
+
+ System.out.println("Learning ...");
+ RankingExpression learntExpression = trainer.train(parameters, new PrintingTracker());
+ }
+
+ // Solved well in a few seconds at most. Slow going thereafter.
+ private static void test2() throws ParseException {
+ TrainingParameters parameters = new TrainingParameters();
+ parameters.setSpeciesLifespan(100); // Shorter lifespan is faster?
+
+ RankingExpression target = new RankingExpression("5*x*x + 2*x + 13");
+ List<Context> arguments = new ArrayList<>();
+ arguments.add(MapContext.fromString("x:0").freeze());
+ arguments.add(MapContext.fromString("x:1").freeze());
+ arguments.add(MapContext.fromString("x:2").freeze());
+ arguments.add(MapContext.fromString("x:3").freeze());
+ arguments.add(MapContext.fromString("x:4").freeze());
+ arguments.add(MapContext.fromString("x:5").freeze());
+ arguments.add(MapContext.fromString("x:6").freeze());
+ arguments.add(MapContext.fromString("x:7").freeze());
+ arguments.add(MapContext.fromString("x:8").freeze());
+ arguments.add(MapContext.fromString("x:9").freeze());
+ arguments.add(MapContext.fromString("x:10").freeze());
+ arguments.add(MapContext.fromString("x:50").freeze());
+ arguments.add(MapContext.fromString("x:500").freeze());
+ arguments.add(MapContext.fromString("x:5000").freeze());
+ arguments.add(MapContext.fromString("x:50000").freeze());
+ TrainingSet trainingSet = new TrainingSet(new RankingExpressionCaseList(arguments, target), parameters);
+
+ Trainer trainer = new Trainer(trainingSet);
+
+ System.out.println("Learning ...");
+ RankingExpression learntExpression = trainer.train(parameters, new PrintingTracker());
+ }
+
+ // Solved well in at most a few minutes
+ private static void test3() throws ParseException {
+ TrainingParameters parameters = new TrainingParameters();
+ parameters.setAllowConditions(false); // disallow non-smooth functions: Speeds up learning of smooth ones greatly
+
+ RankingExpression target = new RankingExpression("-2.7*x*x*x + 5*x*x + 2*x + 13");
+ List<Context> arguments = new ArrayList<>();
+ arguments.add(MapContext.fromString("x:-50000").freeze());
+ arguments.add(MapContext.fromString("x:-5000").freeze());
+ arguments.add(MapContext.fromString("x:-500").freeze());
+ arguments.add(MapContext.fromString("x:-50").freeze());
+ arguments.add(MapContext.fromString("x:-10").freeze());
+ arguments.add(MapContext.fromString("x:0").freeze());
+ arguments.add(MapContext.fromString("x:1").freeze());
+ arguments.add(MapContext.fromString("x:2").freeze());
+ arguments.add(MapContext.fromString("x:3").freeze());
+ arguments.add(MapContext.fromString("x:4").freeze());
+ arguments.add(MapContext.fromString("x:5").freeze());
+ arguments.add(MapContext.fromString("x:6").freeze());
+ arguments.add(MapContext.fromString("x:7").freeze());
+ arguments.add(MapContext.fromString("x:8").freeze());
+ arguments.add(MapContext.fromString("x:9").freeze());
+ arguments.add(MapContext.fromString("x:10").freeze());
+ arguments.add(MapContext.fromString("x:50").freeze());
+ arguments.add(MapContext.fromString("x:500").freeze());
+ arguments.add(MapContext.fromString("x:5000").freeze());
+ arguments.add(MapContext.fromString("x:50000").freeze());
+ TrainingSet trainingSet = new TrainingSet(new RankingExpressionCaseList(arguments, target), parameters);
+
+ Trainer trainer = new Trainer(trainingSet);
+
+ System.out.println("Learning ...");
+ RankingExpression learntExpression = trainer.train(parameters, new PrintingTracker());
+ }
+
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/mlr/ga/test/MainTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/mlr/ga/test/MainTestCase.java
new file mode 100644
index 00000000000..51460855983
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/mlr/ga/test/MainTestCase.java
@@ -0,0 +1,57 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.mlr.ga.test;
+
+import com.yahoo.searchlib.mlr.ga.Evolvable;
+import com.yahoo.searchlib.mlr.ga.Main;
+import com.yahoo.searchlib.mlr.ga.PrintingTracker;
+import com.yahoo.searchlib.mlr.ga.Species;
+import com.yahoo.searchlib.mlr.ga.Tracker;
+import com.yahoo.searchlib.mlr.ga.TrainingParameters;
+import com.yahoo.searchlib.rankingexpression.RankingExpression;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+import java.util.List;
+
+/**
+ * Tests the main class used from the command line
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class MainTestCase {
+
+ /** Tests that an extremely simple function expressed as cases in a file is learnt perfectly. */
+ @Test
+ public void testMain() {
+ SilentTestTracker tracker = new SilentTestTracker();
+ new Main(new String[] { "src/test/files/mlr/cases-linear.csv"}, tracker);
+ assertTrue(Double.isInfinite(tracker.winner.getFitness()));
+ }
+
+ private static class SilentTestTracker implements Tracker {
+
+ public Evolvable winner;
+
+ @Override
+ public void newSpecies(Species predecessor, int initialSize, List<RankingExpression> genePool) {
+ }
+
+ @Override
+ public void newSpeciesCreated(Species predecessor) {
+ }
+
+ @Override
+ public void speciesCompleted(Species predecessor) {
+ }
+
+ @Override
+ public void iteration(Species species, int generation) {
+ }
+
+ @Override
+ public void result(Evolvable winner) {
+ this.winner = winner;
+ }
+ }
+
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/mlr/ga/test/MockTrainingSetTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/mlr/ga/test/MockTrainingSetTestCase.java
new file mode 100644
index 00000000000..ab1d5c362b8
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/mlr/ga/test/MockTrainingSetTestCase.java
@@ -0,0 +1,46 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.mlr.ga.test;
+
+import com.yahoo.searchlib.mlr.ga.RankingExpressionCaseList;
+import com.yahoo.searchlib.mlr.ga.TrainingParameters;
+import com.yahoo.searchlib.mlr.ga.TrainingSet;
+import com.yahoo.searchlib.rankingexpression.RankingExpression;
+import com.yahoo.searchlib.rankingexpression.evaluation.Context;
+import com.yahoo.searchlib.rankingexpression.evaluation.MapContext;
+import com.yahoo.searchlib.rankingexpression.parser.ParseException;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class MockTrainingSetTestCase {
+
+ @Test
+ public void testMockTrainingSet() throws ParseException {
+ RankingExpression target = new RankingExpression("2*x");
+ List<Context> arguments = new ArrayList<>();
+ arguments.add(MapContext.fromString("x:0"));
+ arguments.add(MapContext.fromString("x:1"));
+ arguments.add(MapContext.fromString("x:2"));
+ TrainingSet trainingSet = new TrainingSet(new RankingExpressionCaseList(arguments, target), new TrainingParameters());
+ assertTrue(Double.isInfinite(trainingSet.evaluate(new RankingExpression("2*x"))));
+ assertEquals(4.0, trainingSet.evaluate(new RankingExpression("x")), 0.001);
+ assertEquals(0.0, trainingSet.evaluate(new RankingExpression("x/x")), 0.001);
+ }
+
+ @Test
+ public void testEvaluation() throws ParseException {
+ // with freezing
+ assertEquals(16.0,new RankingExpression("2*x*x*x").evaluate(MapContext.fromString("x:2").freeze()).asDouble(),0.0001);
+ assertEquals(8.0,new RankingExpression("x*x+x*x").evaluate(MapContext.fromString("x:2").freeze()).asDouble(),0.0001);
+
+ // without freezing
+ assertEquals(16.0,new RankingExpression("2*x*x*x").evaluate(MapContext.fromString("x:2")).asDouble(),0.0001);
+ assertEquals(8.0,new RankingExpression("x*x+x*x").evaluate(MapContext.fromString("x:2")).asDouble(),0.0001);
+ }
+
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/mlr/ga/test/TripAdvisorFileCaseList.java b/searchlib/src/test/java/com/yahoo/searchlib/mlr/ga/test/TripAdvisorFileCaseList.java
new file mode 100644
index 00000000000..9c3e514ddad
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/mlr/ga/test/TripAdvisorFileCaseList.java
@@ -0,0 +1,99 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.mlr.ga.test;
+
+import com.yahoo.searchlib.mlr.ga.CaseList;
+import com.yahoo.searchlib.mlr.ga.TrainingSet;
+import com.yahoo.searchlib.rankingexpression.evaluation.Context;
+import com.yahoo.searchlib.rankingexpression.evaluation.MapContext;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * Reads a tripadvisor Kaggle challenge training set
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class TripAdvisorFileCaseList implements CaseList {
+
+ private List<TrainingSet.Case> cases = new ArrayList<>();
+ private Map<Integer,String> columnNames = new HashMap<>();
+
+ /**
+ * Reads a case list from file.
+ *
+ * @throws IllegalArgumentException if the file could not be found or opened
+ */
+ public TripAdvisorFileCaseList(String fileName) throws IllegalArgumentException {
+ System.out.print("Reading training data ");
+ try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
+ String line;
+ readColumnNames(reader.readLine());
+ int lineNumber=1;
+ while (null != (line=reader.readLine())) {
+ lineNumber++;
+ line = line.trim();
+ if (line.startsWith("#")) continue;
+ if (line.isEmpty()) continue;
+ cases.add(lineToCase(line, lineNumber));
+ }
+ }
+ catch (IOException | IllegalArgumentException e) {
+ throw new IllegalArgumentException("Could not create a case list from file '" + fileName + "'", e);
+ }
+ System.out.println("done");
+ }
+
+ private void readColumnNames(String line) {
+ int columnNumber = 0;
+ for (String columnName : line.split(","))
+ columnNames.put(columnNumber++, columnName);
+ }
+
+ protected TrainingSet.Case lineToCase(String line, int lineNumber) {
+ if ((lineNumber % 10000) ==0)
+ System.out.print(".");
+
+ Map<String,Double> columnValues = readColumns(line);
+
+ double targetValue = columnValues.get("click_bool") + columnValues.get("booking_bool")*5;
+
+ Context context = new MapContext();
+ for (Map.Entry<String,Double> value : columnValues.entrySet()) {
+ if (value.getKey().equals("click_bool")) continue;
+ if (value.getKey().equals("gross_bookings_usd")) continue;
+ if (value.getKey().equals("booking_bool")) continue;
+ context.put(value.getKey(),value.getValue());
+ }
+ return new TrainingSet.Case(context, targetValue);
+ }
+
+ private Map<String, Double> readColumns(String line) {
+ Map<String,Double> columnValues = new LinkedHashMap<>();
+ int columnNumber = 0;
+ for (String valueString : line.split(",")) {
+ String columnName = columnNames.get(columnNumber++);
+ if (columnName.equals("date_time")) continue;
+ Double columnValue;
+ if (valueString.equals("NULL")) {
+ columnValue = 0.0;
+ }
+ else {
+ try {
+ columnValue = Double.parseDouble(valueString);
+ }
+ catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Could not parse column '" + columnName + "'",e);
+ }
+ }
+ columnValues.put(columnName, columnValue);
+ }
+ return columnValues;
+ }
+
+ @Override
+ public List<TrainingSet.Case> cases() { return Collections.unmodifiableList(cases); }
+
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/mlr/gbdt/ExpressionAnalysisRunner.java b/searchlib/src/test/java/com/yahoo/searchlib/mlr/gbdt/ExpressionAnalysisRunner.java
new file mode 100644
index 00000000000..301fdfcd4f2
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/mlr/gbdt/ExpressionAnalysisRunner.java
@@ -0,0 +1,19 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.mlr.gbdt;
+
+import org.junit.Ignore;
+import org.junit.Test;
+
+/**
+ * Run an expression analyser without having to muck with classpath.
+ *
+ * @author bratseth
+ */
+public class ExpressionAnalysisRunner {
+
+ @Test @Ignore
+ public void runAnalysis() {
+ ExpressionAnalysis.main(new String[] { "/Users/bratseth/Downloads/getty_mlr_001.expression"});
+ }
+
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/ranking/features/ElementCompletenessTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/ranking/features/ElementCompletenessTestCase.java
new file mode 100644
index 00000000000..804f34ccce8
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/ranking/features/ElementCompletenessTestCase.java
@@ -0,0 +1,80 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.ranking.features;
+
+import static org.junit.Assert.*;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author bratseth
+ */
+public class ElementCompletenessTestCase {
+
+ private static final double delta = 0.0001;
+
+ @Test
+ public void testElementCompleteness1() {
+ Map<String, Integer> query = createQuery();
+ ElementCompleteness.Item[] items = createField(1);
+
+ Features f = ElementCompleteness.compute(query, items);
+ assertEquals(0.26111111111111107, f.get("completeness").asDouble(), delta);
+ assertEquals(1.0, f.get("fieldCompleteness").asDouble(), delta);
+ assertEquals(0.2222222222222222, f.get("queryCompleteness").asDouble(), delta);
+ assertEquals(3.0, f.get("elementWeight").asDouble(), delta);
+ }
+
+ @Test
+ public void testElementCompleteness2() {
+ Map<String, Integer> query = createQuery();
+ ElementCompleteness.Item[] items = createField(2);
+
+ Features f = ElementCompleteness.compute(query, items);
+ assertEquals(0.975, f.get("completeness").asDouble(), delta);
+ assertEquals(0.5, f.get("fieldCompleteness").asDouble(), delta);
+ assertEquals(1.0, f.get("queryCompleteness").asDouble(), delta);
+ assertEquals(4.0, f.get("elementWeight").asDouble(), delta);
+ }
+
+ @Test
+ public void testElementCompleteness3() {
+ Map<String, Integer> query = createQuery();
+ ElementCompleteness.Item[] items = createField(3);
+
+ Features f = ElementCompleteness.compute(query, items);
+ assertEquals(1.0, f.get("completeness").asDouble(), delta);
+ assertEquals(1.0, f.get("fieldCompleteness").asDouble(), delta);
+ assertEquals(1.0, f.get("queryCompleteness").asDouble(), delta);
+ assertEquals(5.0, f.get("elementWeight").asDouble(), delta);
+ }
+
+ @Test
+ public void testElementCompletenessNoMatches() {
+ ElementCompleteness.Item[] items = createField(3);
+
+ Features f = ElementCompleteness.compute(new HashMap<String, Integer>(), items);
+ assertEquals(0.0, f.get("completeness").asDouble(), delta);
+ assertEquals(0.0, f.get("fieldCompleteness").asDouble(), delta);
+ assertEquals(0.0, f.get("queryCompleteness").asDouble(), delta);
+ assertEquals(0.0, f.get("elementWeight").asDouble(), delta);
+ }
+
+ private Map<String, Integer> createQuery() {
+ Map<String, Integer> query = new HashMap<>();
+ query.put("a", 100);
+ query.put("b", 150);
+ query.put("c", 200);
+ return query;
+ }
+
+ private ElementCompleteness.Item[] createField(int size) {
+ ElementCompleteness.Item[] items = new ElementCompleteness.Item[size];
+ if (size > 0) items[0] = new ElementCompleteness.Item("a", 3); // qc: 100/450=0.22, fc: 1.0, c: 0.611
+ if (size > 1) items[1] = new ElementCompleteness.Item("a b c d e f", 4); // qc: 1.0, fc: 0.5, c: 0.75
+ if (size > 2) items[2] = new ElementCompleteness.Item("a b c", 5);
+ return items;
+ }
+
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/ranking/features/FieldTermMatchTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/ranking/features/FieldTermMatchTestCase.java
new file mode 100644
index 00000000000..61c313956c5
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/ranking/features/FieldTermMatchTestCase.java
@@ -0,0 +1,30 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.ranking.features;
+
+import static org.junit.Assert.*;
+
+import org.junit.Test;
+
+/**
+ * @author bratseth
+ */
+public class FieldTermMatchTestCase {
+
+ private static final double delta = 0.0001;
+
+ @Test
+ public void testFieldTermMatch() {
+ assertEquals(1.0, FieldTermMatch.compute("a", "a b c").get("occurrences").asDouble(), delta);
+ assertEquals(0.0, FieldTermMatch.compute("a", "a b c").get("firstPosition").asDouble(), delta);
+
+ assertEquals(3.0, FieldTermMatch.compute("a", "a a a").get("occurrences").asDouble(), delta);
+ assertEquals(0.0, FieldTermMatch.compute("a", "a a a").get("firstPosition").asDouble(), delta);
+
+ assertEquals(0.0, FieldTermMatch.compute("d", "a b c").get("occurrences").asDouble(), delta);
+ assertEquals(1000000.0, FieldTermMatch.compute("d", "a b c").get("firstPosition").asDouble(), delta);
+
+ assertEquals(0.0, FieldTermMatch.compute("d", "").get("occurrences").asDouble(), delta);
+ assertEquals(1000000, FieldTermMatch.compute("d", "").get("firstPosition").asDouble(), delta);
+ }
+
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/ranking/features/fieldmatch/SemanticDistanceTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/ranking/features/fieldmatch/SemanticDistanceTestCase.java
new file mode 100644
index 00000000000..14ea58961ba
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/ranking/features/fieldmatch/SemanticDistanceTestCase.java
@@ -0,0 +1,140 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.ranking.features.fieldmatch;
+
+import com.yahoo.searchlib.ranking.features.fieldmatch.FieldMatchMetricsComputer;
+
+import java.util.Set;
+import java.util.HashSet;
+
+/**
+ * The "semantic distance" refers to the non-continuous distance from a token
+ * to the next token used by the string match metrics algorithm. This class
+ * tests invariants which must hold for any such distance metric as well as specifics
+ * for the currently used distance metric.
+ *
+ * @author bratseth
+ */
+public class SemanticDistanceTestCase extends junit.framework.TestCase {
+
+ FieldMatchMetricsComputer c;
+
+ public SemanticDistanceTestCase(String name) {
+ super(name);
+ }
+
+ public @Override void setUp() {
+ c=new FieldMatchMetricsComputer();
+ StringBuilder field=new StringBuilder();
+ for (int i=0; i<150; i++)
+ field.append("t" + i + " ");
+ c.compute("query",field.toString()); // Just to set the field value
+ }
+
+ /** Must be true using any semantic distance function */
+ public void testBothWayConversionProducesOriginalValue() {
+ assertBothWayConversionProducesOriginalValue(50);
+ assertBothWayConversionProducesOriginalValue(10);
+ assertBothWayConversionProducesOriginalValue(5);
+ assertBothWayConversionProducesOriginalValue(0);
+ assertBothWayConversionProducesOriginalValue(140);
+ assertBothWayConversionProducesOriginalValue(145);
+ assertBothWayConversionProducesOriginalValue(149);
+ }
+
+ /** Must be true using any semantic distance function */
+ public void testFunctionsAreOneToOne() {
+ assertFunctionsAreOneToOne(50);
+ assertFunctionsAreOneToOne(10);
+ assertFunctionsAreOneToOne(5);
+ assertFunctionsAreOneToOne(0);
+ assertFunctionsAreOneToOne(140);
+ assertFunctionsAreOneToOne(145);
+ assertFunctionsAreOneToOne(149);
+ }
+
+ /** Specific to this particular distance function */
+ public void testFunction() {
+ int zeroJ=50;
+ assertFunction(50,0,zeroJ);
+ assertFunction(59,9,zeroJ);
+ assertFunction(49,10,zeroJ);
+ assertFunction(40,19,zeroJ);
+ assertFunction(60,20,zeroJ);
+ assertFunction(149,109,zeroJ);
+ assertFunction(39,110,zeroJ);
+ assertFunction(0,149,zeroJ);
+
+ zeroJ=0;
+ assertFunction(0,0,zeroJ);
+ assertFunction(10,10,zeroJ);
+ assertFunction(20,20,zeroJ);
+ assertFunction(149,149,zeroJ);
+
+ zeroJ=5;
+ assertFunction(5,0,zeroJ);
+ assertFunction(10,5,zeroJ);
+ assertFunction(14,9,zeroJ);
+ assertFunction(4,10,zeroJ);
+ assertFunction(0,14,zeroJ);
+ assertFunction(15,15,zeroJ);
+ assertFunction(25,25,zeroJ);
+ assertFunction(149,149,zeroJ);
+
+ zeroJ=149;
+ assertFunction(149,0,zeroJ);
+ assertFunction(140,9,zeroJ);
+ assertFunction(130,19,zeroJ);
+ assertFunction(0,149,zeroJ);
+
+ zeroJ=145;
+ assertFunction(145,0,zeroJ);
+ assertFunction(149,4,zeroJ);
+ assertFunction(144,5,zeroJ);
+ assertFunction(140,9,zeroJ);
+ assertFunction(135,14,zeroJ);
+ assertFunction(125,24,zeroJ);
+ assertFunction(0,149,zeroJ);
+ }
+
+ /** Hits both limits at once */
+ public void testSmallField() {
+ c=new FieldMatchMetricsComputer();
+ c.compute("query","my field value four"); // Just to set the field value
+ assertBothWayConversionProducesOriginalValue(2);
+ assertBothWayConversionProducesOriginalValue(0);
+ assertBothWayConversionProducesOriginalValue(3);
+ assertFunctionsAreOneToOne(2);
+ assertFunctionsAreOneToOne(0);
+ assertFunctionsAreOneToOne(3);
+
+ int zeroJ=2;
+ assertFunction(2,0,zeroJ);
+ assertFunction(3,1,zeroJ);
+ assertFunction(1,2,zeroJ);
+ assertFunction(0,3,zeroJ);
+ }
+
+ private void assertBothWayConversionProducesOriginalValue(int zeroJ) {
+ // Starting point in the middle
+ for (int j=0; j<c.getField().terms().size(); j++) {
+ int semanticDistance=c.fieldIndexToSemanticDistance(j,zeroJ);
+ assertTrue("Using zeroJ=" + zeroJ + ": " + semanticDistance +">=0", semanticDistance >= 0);
+ int backConvertedJ=c.semanticDistanceToFieldIndex(semanticDistance,zeroJ);
+ assertEquals("Using zeroJ=" + zeroJ + ": " + j + "->" + semanticDistance + "->" + backConvertedJ,j, backConvertedJ);
+ }
+ }
+
+ private void assertFunctionsAreOneToOne(int zeroJ) {
+ Set<Integer> distances=new HashSet<Integer>();
+ for (int j=0; j<c.getField().terms().size(); j++) {
+ int semanticDistance=c.fieldIndexToSemanticDistance(j,zeroJ);
+ assertTrue("Using zeroJ=" + zeroJ + ": " + j + "->" + semanticDistance + " is unique", ! distances.contains(semanticDistance));
+ distances.add(semanticDistance);
+ }
+ }
+
+ private void assertFunction(int j,int semanticDistance,int zeroJ) {
+ assertEquals(j + "->" + semanticDistance,semanticDistance,c.fieldIndexToSemanticDistance(j,zeroJ));
+ }
+
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/ranking/features/fieldmatch/reference/OptimalStringAlignmentDistance.java b/searchlib/src/test/java/com/yahoo/searchlib/ranking/features/fieldmatch/reference/OptimalStringAlignmentDistance.java
new file mode 100644
index 00000000000..272ca98d7c4
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/ranking/features/fieldmatch/reference/OptimalStringAlignmentDistance.java
@@ -0,0 +1,201 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.ranking.features.fieldmatch.reference;
+
+import java.util.Arrays;
+
+/**
+ * Implementation of optimal string alignment distance which also retains the four subdistances
+ * and which uses 2*query length memory rather than field length*query length.
+ * This class is not thread safe.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class OptimalStringAlignmentDistance {
+
+ /** The cell containg the last calculated edit distance */
+ private Cell value=new Cell(0,0,0,0);
+
+ // Temporary variables
+ private Cell[] thisRow, previousRow, previousPreviousRow;
+
+ private String[] query, field;
+
+ private boolean printTable=false;
+
+ public void calculate(String queryString,String fieldString) {
+ this.query=queryString.split(" ");
+ this.field=fieldString.split(" ");
+
+ thisRow=new Cell[query.length+1];
+ previousRow=new Cell[query.length+1];
+ previousPreviousRow=new Cell[query.length+1];
+
+ for(int i=0; i<=query.length; i++) {
+ thisRow[i]=new Cell(i+1,0,0,0);
+ previousRow[i]=new Cell(i,0,0,0);
+ previousPreviousRow[i]=new Cell(i-1,0,0,0);
+ }
+
+ print(previousRow);
+
+ for(int j=1;j<=field.length; j++) {
+ thisRow[0].setTo(0,j,0,0);
+ for(int i=1; i<=query.length; i++) {
+ setCell(i,j);
+ }
+
+ print(thisRow);
+
+ // Shift round thisRow -> previousRow -> previousPreviousRow -> thisRow
+ Cell[] temporaryRow=thisRow;
+ thisRow=previousPreviousRow;
+ previousPreviousRow=previousRow;
+ previousRow=temporaryRow;
+ }
+ value=previousRow[query.length];
+ }
+
+ private void setCell(int i,int j) {
+ Cell thisCell=thisRow[i];
+ Cell left=thisRow[i-1];
+ Cell above=previousRow[i];
+ Cell leftAbove=previousRow[i-1];
+
+ boolean substitution=!query[i-1].equals(field[j-1]);
+
+ int leftCost=left.getTotal()+1;
+ int aboveCost=above.getTotal()+1;
+ int leftAboveCost=leftAbove.getTotal() + ( substitution ? 1 : 0 );
+
+ if (leftCost<=aboveCost && leftCost<=leftAboveCost) {
+ thisCell.setTo(left);
+ thisCell.addDeletion();
+ }
+ else if (aboveCost<=leftCost && aboveCost<=leftAboveCost) {
+ thisCell.setTo(above);
+ thisCell.addInsertion();
+ }
+ else {
+ thisCell.setTo(leftAbove);
+ if (substitution)
+ thisCell.addSubstitution();
+ }
+
+ if (i>1 && j>1 && query[i-1].equals(field[j-2]) && query[i-2].equals(field[j-1]) ) {
+ Cell twoAboveAndLeft=previousPreviousRow[i-2];
+ int transpositionCost= + ( substitution ? 1 : 0);
+ if (transpositionCost<thisCell.getTotal()) {
+ thisCell.setTo(twoAboveAndLeft);
+ thisCell.addTransposition();
+ }
+ }
+ }
+
+ private void setCell(Cell thisCell,Cell left, Cell above, Cell leftAbove, boolean substitution) {
+ int a=left.getTotal()+1;
+ int b=above.getTotal()+1;
+ int c=leftAbove.getTotal();
+
+ c+=substitution ? 1 : 0;
+
+ if (a<=b && a<=c) {
+ thisCell.setTo(left);
+ thisCell.addDeletion();
+ }
+ else if (b<=a && b<=c) {
+ thisCell.setTo(above);
+ thisCell.addInsertion();
+ }
+ else {
+ thisCell.setTo(leftAbove);
+ if (substitution)
+ thisCell.addSubstitution();
+ }
+ /*
+ if(i > 1 and j > 1 and str1[i] = str2[j-1] and str1[i-1] = str2[j]) then
+ d[i, j] := minimum(
+ d[i, j],
+ d[i-2, j-2] + cost // transposition
+ )
+ */
+ }
+
+ public float getTotal() { return value.getTotal(); }
+ public float getSubstitutions() { return value.getSubstitutions(); }
+ public float getDeletions() { return value.getDeletions(); }
+ public float getInsertions() { return value.getInsertions(); }
+ public float getTranspositions() { return value.getTranspositions(); }
+
+ /** Print the calculated edit distance table as we go */
+ public void setPrintTable(boolean printTable) {
+ this.printTable=printTable;
+ }
+
+ private void print(Cell[] row) {
+ if (!printTable) return;
+ for (Cell cell : row) {
+ System.out.print(cell.toShortString());
+ System.out.print(" ");
+ }
+ System.out.println();
+ }
+
+ /** Returns the current state as a string */
+ public String toString() {
+ StringBuffer b=new StringBuffer();
+ b.append("Query: " + Arrays.toString(query) + "\n");
+ b.append("Field: " + Arrays.toString(field) + "\n");
+ b.append(value);
+ return b.toString();
+ }
+
+ /** An edit distance table cell */
+ public static final class Cell {
+
+ private int deletions, insertions, substitutions, transpositions;
+
+ public Cell(int deletions,int insertions,int substitutions,int transpositions) {
+ setTo(deletions,insertions,substitutions,transpositions);
+ }
+
+ public void setTo(Cell cell) {
+ this.deletions=cell.deletions;
+ this.insertions=cell.insertions;
+ this.substitutions=cell.substitutions;
+ this.transpositions=cell.transpositions;
+ }
+
+ public void setTo(int deletions,int insertions,int substitutions,int transpositions) {
+ this.deletions=deletions;
+ this.insertions=insertions;
+ this.substitutions=substitutions;
+ this.transpositions=transpositions;
+ }
+
+ public int getTotal() {
+ return deletions+insertions+substitutions+transpositions;
+ }
+
+ public void addDeletion() { deletions++; }
+ public void addInsertion() { insertions++; }
+ public void addSubstitution() { substitutions++; }
+ public void addTransposition() { transpositions++; }
+
+ public int getDeletions() { return deletions; }
+ public int getInsertions() { return insertions; }
+ public int getSubstitutions() { return substitutions; }
+ public int getTranspositions() { return transpositions; }
+
+ public String toString() {
+ return "Total: " + getTotal() + ", substitutions: " + substitutions + ", deletions: " +
+ deletions + ", insertions: " + insertions + ", transpositions: " + transpositions + "\n";
+ }
+
+ public String toShortString() {
+ return "(" + substitutions + "," + deletions + "," + insertions + "," + transpositions + ")";
+ }
+
+
+ }
+
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/ranking/features/fieldmatch/reference/TextbookLevenshteinDistance.java b/searchlib/src/test/java/com/yahoo/searchlib/ranking/features/fieldmatch/reference/TextbookLevenshteinDistance.java
new file mode 100644
index 00000000000..5ad3449a9d3
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/ranking/features/fieldmatch/reference/TextbookLevenshteinDistance.java
@@ -0,0 +1,38 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.ranking.features.fieldmatch.reference;
+
+/**
+ * Textbook implementation from
+ * <a href="http://en.wikibooks.org/wiki/Algorithm_implementation/Strings/Levenshtein_distance#Java">
+ * Wikipedia algorithms</a>
+ * Licensed under the Creative Commons Attribution-ShareAlike License
+ */
+public class TextbookLevenshteinDistance {
+
+ private static int minimum(int a, int b, int c){
+ if (a<=b && a<=c)
+ return a;
+ if (b<=a && b<=c)
+ return b;
+ return c;
+ }
+
+ public static int computeLevenshteinDistance(char[] str1, char[] str2) {
+ int[][] distance = new int[str1.length+1][];
+
+ for(int i=0; i<=str1.length; i++){
+ distance[i] = new int[str2.length+1];
+ distance[i][0] = i;
+ }
+ for(int j=0; j<=str2.length; j++)
+ distance[0][j]=j;
+
+ for(int i=1; i<=str1.length; i++)
+ for(int j=1;j<=str2.length; j++)
+ distance[i][j]= minimum(distance[i-1][j]+1, distance[i][j-1]+1,
+ distance[i-1][j-1]+((str1[i-1]==str2[j-1])?0:1));
+
+ return distance[str1.length][str2.length];
+ }
+
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/ranking/features/fieldmatch/reference/test/OptimalStringAlignmentTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/ranking/features/fieldmatch/reference/test/OptimalStringAlignmentTestCase.java
new file mode 100644
index 00000000000..398c4e70fb7
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/ranking/features/fieldmatch/reference/test/OptimalStringAlignmentTestCase.java
@@ -0,0 +1,58 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.ranking.features.fieldmatch.reference.test;
+
+import com.yahoo.searchlib.ranking.features.fieldmatch.reference.OptimalStringAlignmentDistance;
+
+/**
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class OptimalStringAlignmentTestCase extends junit.framework.TestCase {
+
+ public OptimalStringAlignmentTestCase(String name) {
+ super(name);
+ }
+
+ public void testEditDistance() {
+ // Edit distance, substitution, deletion, insertion, transposition, query, field, print?
+
+ boolean print=false;
+ assertEditDistance(0,0,0,0,0,"niels bohr","niels bohr",print);
+ assertEditDistance(1,1,0,0,0,"niels","bohr",print);
+ assertEditDistance(1,0,0,1,0,"niels","niels bohr",print);
+ assertEditDistance(1,0,1,0,0,"niels bohr","bohr",print);
+ assertEditDistance(1,0,0,0,1,"niels bohr","bohr niels",print);
+ assertEditDistance(1,0,0,1,0,"niels bohr","niels henrik bohr",print);
+ assertEditDistance(2,0,0,1,1,"niels bohr","bohr niels henrik",print);
+ assertEditDistance(4,1,0,3,0,"niels bohr","niels henrik bor i kopenhagen",print);
+ assertEditDistance(3,2,0,1,0,"niels bohr i kopenhagen","niels henrik bor i stockholm",print);
+ }
+
+ public void testEditDistanceAsRelevance() {
+ boolean print=false;
+ assertEditDistance(2,0,0,2,0,"niels bohr","niels blah blah bohr",print);
+ assertEditDistance(4,0,1,3,0,"niels bohr","bohr blah blah niels",print); // Not desired
+ assertEditDistance(4,2,0,2,0,"niels bohr","koko blah blah bahia",print);
+ }
+
+ private void assertEditDistance(int total,int substitution,int deletion,int insertion,int transposition,String query,String field,boolean printResult) {
+ assertEditDistance(total,substitution,deletion,insertion,transposition,query,field,printResult,false);
+ }
+
+ private void assertEditDistance(int total,int substitution,int deletion,int insertion,int transposition,String query,String field,boolean printResult,boolean printTable) {
+ OptimalStringAlignmentDistance e=new OptimalStringAlignmentDistance();
+ e.setPrintTable(printTable);
+ e.calculate(query,field);
+
+ if (printResult) {
+ System.out.print(e.toString());
+ System.out.println();
+ }
+
+ assertEquals("Substitutions",(float)substitution,e.getSubstitutions());
+ assertEquals("Deletions",(float)deletion,e.getDeletions());
+ assertEquals("Insertions",(float)insertion,e.getInsertions());
+ assertEquals("Transpositions",(float)transposition,e.getTranspositions());
+ assertEquals("Total",(float)total,e.getTotal());
+ }
+
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/ranking/features/fieldmatch/test/FieldMatchMetricsTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/ranking/features/fieldmatch/test/FieldMatchMetricsTestCase.java
new file mode 100644
index 00000000000..ef8daec2b73
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/ranking/features/fieldmatch/test/FieldMatchMetricsTestCase.java
@@ -0,0 +1,757 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.ranking.features.fieldmatch.test;
+
+import com.google.common.collect.ImmutableList;
+import com.yahoo.searchlib.ranking.features.fieldmatch.Field;
+import com.yahoo.searchlib.ranking.features.fieldmatch.FieldMatchMetrics;
+import com.yahoo.searchlib.ranking.features.fieldmatch.FieldMatchMetricsComputer;
+import com.yahoo.searchlib.ranking.features.fieldmatch.FieldMatchMetricsParameters;
+import com.yahoo.searchlib.ranking.features.fieldmatch.QueryTerm;
+import com.yahoo.searchlib.ranking.features.fieldmatch.Query;
+
+import java.util.List;
+
+/**
+ * Tests of calculation of all the string match metrics.
+ * Add true as the fourth parameter to assertMetrics to see a trace of what the test is doing.
+ *
+ * @author bratseth
+ */
+public class FieldMatchMetricsTestCase extends junit.framework.TestCase {
+
+ public FieldMatchMetricsTestCase(String name) {
+ super(name);
+ }
+
+ public void testOutOfOrder() {
+ assertMetrics("outOfOrder:0","a","a");
+ assertMetrics("outOfOrder:0","a b c","a b c");
+ assertMetrics("outOfOrder:1","a b c","a c b");
+ assertMetrics("outOfOrder:2","a b c","c b a");
+ assertMetrics("outOfOrder:2","a b c d e","c x a b x x x x x e x x d");
+ assertMetrics("outOfOrder:2","a b c d e","c x a b x x x x x e x x d");
+ assertMetrics("outOfOrder:2", "a b c d e", "c x a b x x x x x e x x d");
+ }
+
+ public void testSegments() {
+ assertMetrics("segments:1","a","a");
+ assertMetrics("segments:1","a b c","a b c");
+ assertMetrics("segments:1","a b c","a x x b c");
+ assertMetrics("segments:2","a b c","a x x x x x x x x x x x x x x x x x x x b c");
+ assertMetrics("segments:2","a b c","b c x x x x x x x x x x x x x x x x x x x a");
+ assertMetrics("segments:2 gaps:1","a b c","x x x a x x x x x x x x x x x x x x x x x x x b x x c x x");
+ assertMetrics("segments:2 gaps:0 outOfOrder:0","a b c","b c x x x x x x x x x x x x x x x x x x x a");
+ assertMetrics("segments:2 gaps:1","a b c","x x x b x x c x x x x x x x x x x x x x x x x x x x a x x");
+ assertMetrics("segments:2 gaps:1","a y y b c","x x x b x x c x x x x x x x x x x x x x x x x x x x a x x");
+ }
+
+ public void testGaps() {
+ assertMetrics("gaps:0","a","a");
+ assertMetrics("gaps:0","x�a","a");
+ assertMetrics("gaps:0 gapLength:0","a b c","a b c");
+ assertMetrics("gaps:1 gapLength:1","a b","b a");
+ assertMetrics("gaps:1 gapLength:1","a b c","a x b c");
+ assertMetrics("gaps:1 gapLength:3","a b c","a x X Xb c");
+ assertMetrics("gaps:2 gapLength:2 outOfOrder:1","a b c","a c b");
+ assertMetrics("gaps:2 gapLength:2 outOfOrder:0","a b c","a x b x c");
+ assertMetrics("gaps:2 gapLength:5 outOfOrder:1","a b c","a x c x b");
+ assertMetrics("gaps:3 outOfOrder:2 segments:1","a b c d e","x d x x b c x x a e");
+ assertMetrics("gaps:0","y a b c","a b c x");
+ }
+
+ public void testHead() {
+ assertMetrics("head:0","a","a");
+ assertMetrics("head:0","y","a");
+ assertMetrics("head:1","a","x a");
+ assertMetrics("head:2","a b c","x x a b c");
+ assertMetrics("head:2","a b c","x x c x x a b");
+ assertMetrics("head:2", "a b c", "x x c x x x x x x x x x x x x x x x a b");
+ }
+
+ public void testTail() {
+ assertMetrics("tail:0","a","a");
+ assertMetrics("tail:0","y","a");
+ assertMetrics("tail:1","a","a x");
+ assertMetrics("tail:2","a b c","a b c x x");
+ assertMetrics("tail:2","a b c","x x x c x x x x a b x x");
+ assertMetrics("tail:0","a b c","x x c x x x x x x x x x x x x x x x a b");
+ }
+
+ public void testLongestSequence() {
+ assertMetrics("longestSequence:1","a","a");
+ assertMetrics("longestSequence:1","a","a b c");
+ assertMetrics("longestSequence:1","b","a b c");
+ assertMetrics("longestSequence:3","a b c","x x a b c x x a b x");
+ assertMetrics("longestSequence:3 segments:1","a b c","x x a b x x a b c x");
+ assertMetrics("longestSequence:2","a b c d","x x c d x x a b x");
+ assertMetrics("longestSequence:2","a b c d","x x a b x c d x x");
+ assertMetrics("longestSequence:2","a b c d","x x a b x x x x x x x x x x x x x x x x x c d x x");
+ assertMetrics("longestSequence:4 segments:1","a b c d","x x a b x x x x x x x x x x x x x x x x x c d x x a b c d");
+ }
+
+ public void testMatches() {
+ assertMetrics("matches:1 queryCompleteness:1 fieldCompleteness:1", "a","a");
+ assertMetrics("matches:3 queryCompleteness:1 fieldCompleteness:1", "a b c","a b c");
+ assertMetrics("matches:3 queryCompleteness:1 fieldCompleteness:0.5", "a b c","a b c a b d");
+ assertMetrics("matches:3 queryCompleteness:0.5 fieldCompleteness:0.25","a y y b c y","a x x b c x a x a b x x");
+ }
+
+ public void testCompleteness() {
+ assertMetrics("completeness:1 queryCompleteness:1 fieldCompleteness:1", "a","a");
+ assertMetrics("completeness:0 queryCompleteness:0 fieldCompleteness:0", "a","x");
+ assertMetrics("completeness:0 queryCompleteness:0 fieldCompleteness:0", "y","a");
+ assertMetrics("completeness:0.975 queryCompleteness:1 fieldCompleteness:0.5","a","a a");
+ assertMetrics("completeness:0.525 queryCompleteness:0.5 fieldCompleteness:1", "a a","a");
+ assertMetrics("completeness:1 queryCompleteness:1 fieldCompleteness:1", "a b c","a b c");
+ assertMetrics("completeness:0.525 queryCompleteness:0.5 fieldCompleteness:1", "a b c d","a b");
+ assertMetrics("completeness:0.975 queryCompleteness:1 fieldCompleteness:0.5","a b","a b c d");
+ assertMetrics("completeness:0.97 queryCompleteness:1 fieldCompleteness:0.4","a b","a b c d e");
+ assertMetrics("completeness:0.97 queryCompleteness:1 fieldCompleteness:0.4","a b","a b b b b");
+ }
+
+ public void testOrderness() {
+ assertMetrics("orderness:1", "a","a");
+ assertMetrics("orderness:1", "a","x");
+ assertMetrics("orderness:0", "a a a","a"); // Oh well...
+ assertMetrics("orderness:1", "a","a a a");
+ assertMetrics("orderness:0", "a b","b a");
+ assertMetrics("orderness:0.5","a b c","b a c");
+ assertMetrics("orderness:0.5","a b c d","c b d x x x x x x x x x x x x x x x x x x x x x a");
+ assertMetrics("orderness:1", "a b","b x x x x x x x x x x x x x x x x x x x x x a");
+ }
+
+ public void testRelatedness() {
+ assertMetrics("relatedness:1", "a","a");
+ assertMetrics("relatedness:0", "a","x");
+ assertMetrics("relatedness:1", "a b","a b");
+ assertMetrics("relatedness:1", "a b c","a b c");
+ assertMetrics("relatedness:0.5","a b c","a b x x x x x x x x x x x x x x x x x x x x x x x c");
+ assertMetrics("relatedness:0.5","a y b y y y c","a b x x x x x x x x x x x x x x x x x x x x x x x c");
+ }
+
+ public void testLongestSequenceRatio() {
+ assertMetrics("longestSequenceRatio:1", "a","a");
+ assertMetrics("longestSequenceRatio:0", "a","x");
+ assertMetrics("longestSequenceRatio:1", "a a","a");
+ assertMetrics("longestSequenceRatio:1", "a","a a");
+ assertMetrics("longestSequenceRatio:1", "a b","a b");
+ assertMetrics("longestSequenceRatio:1", "a y"," a x");
+ assertMetrics("longestSequenceRatio:0.5","a b","a x b");
+ assertMetrics("longestSequenceRatio:0.75","a b c d","x x a b x a x c d a b c x d x");
+ }
+
+ public void testEarliness() {
+ assertMetrics("earliness:1", "a","a");
+ assertMetrics("earliness:0", "a","x");
+ assertMetrics("earliness:1", "a","a a a");
+ assertMetrics("earliness:1", "a a a","a");
+ assertMetrics("earliness:0.8", "b","a b c");
+ assertMetrics("earliness:0.8", "b","a b");
+ assertMetrics("earliness:0.9091","a b c","x b c x x x x x a x x x");
+ assertMetrics("earliness:0.2", "a b c","x b c a x x x x a x x x x x x x a b c x x");
+ }
+
+ public void testWeight() {
+ assertMetrics("weight:1", "a","a");
+ assertMetrics("weight:0", "y","a");
+ assertMetrics("weight:0.3333","a a a","a");
+ assertMetrics("weight:1", "a","a a a");
+ assertMetrics("weight:1", "a b c","a b c");
+ assertMetrics("weight:1", "a b c","x x a b x a x c x x a b x c c x");
+
+ assertMetrics("weight:0.3333","a b c","a");
+ assertMetrics("weight:0.6667","a b c","a b");
+
+ assertMetrics("weight:1", "a b c!200","a b c"); // Best
+ assertMetrics("weight:0.75","a b c!200","b c"); // Middle
+ assertMetrics("weight:0.5", "a b c!200","a b"); // Worst
+
+ assertMetrics("weight:1","a!300 b c!200","a b c"); // Best too
+
+ assertMetrics("weight:1", "a b c!50","a b c"); // Best
+ assertMetrics("weight:0.6","a b c!50","b c"); // Worse
+ assertMetrics("weight:0.4","a b c!50","b"); // Worse
+ assertMetrics("weight:0.2","a b c!50","c"); // Worst
+ assertMetrics("weight:0.8","a b c!50","a b"); // Middle
+
+ assertMetrics("weight:1", "a b c!0","a b c"); // Best
+ assertMetrics("weight:0.5","a b c!0","b c"); // Worst
+ assertMetrics("weight:1", "a b c!0","a b"); // As good as best
+ assertMetrics("weight:0", "a b c!0","c"); // No contribution
+
+ assertMetrics("weight:0","a!0 b!0","a b");
+ assertMetrics("weight:0","a!0 b!0","");
+
+ // The query also has other terms having a total weight of 300
+ // so we add a weight parameter which is the sum of the weights of this query terms + 300
+ assertMetrics("weight:0.25", "a","a",400);
+ assertMetrics("weight:0", "y","a",400);
+ assertMetrics("weight:0.1667","a a a","a",600);
+ assertMetrics("weight:0.25", "a","a a a",400);
+ assertMetrics("weight:0.5", "a b c","a b c",600);
+ assertMetrics("weight:0.5", "a b c","x x a b x a x c x x a b x c c x",600);
+
+ assertMetrics("weight:0.1667","a b c","a",600);
+ assertMetrics("weight:0.3333","a b c","a b",600);
+
+ assertMetrics("weight:0.5714","a b c!200","a b c",700); // Best
+ assertMetrics("weight:0.4286","a b c!200","b c",700); // Middle
+ assertMetrics("weight:0.2857","a b c!200","a b",700); // Worst
+
+ assertMetrics("weight:0.6667","a!300 b c!200","a b c",900); // Better than best
+
+ assertMetrics("weight:0.4545","a b c!50","a b c",550); // Best
+ assertMetrics("weight:0.2727","a b c!50","b c",550); // Worse
+ assertMetrics("weight:0.1818","a b c!50","b",550); // Worse
+ assertMetrics("weight:0.0909","a b c!50","c",550); // Worst
+ assertMetrics("weight:0.3636","a b c!50","a b",550); // Middle
+
+ assertMetrics("weight:0.4","a b c!0","a b c",500); // Best
+ assertMetrics("weight:0.2","a b c!0","b c",500); // Worst
+ assertMetrics("weight:0.4","a b c!0","a b",500); // As good as best
+ assertMetrics("weight:0", "a b c!0","c",500); // No contribution
+
+ assertMetrics("weight:0","a!0 b!0","a b",300);
+ assertMetrics("weight:0","a!0 b!0","",300);
+ }
+
+ /** Calculated the same way as weight */
+ public void testSignificance() {
+ assertMetrics("significance:1", "a","a");
+ assertMetrics("significance:0", "a","x");
+ assertMetrics("significance:0.3333","a a a","a");
+ assertMetrics("significance:1", "a","a a a");
+ assertMetrics("significance:1", "a b c","a b c");
+ assertMetrics("significance:1", "a b c","x x a b x a x c x x a b x c c x");
+
+ assertMetrics("significance:0.3333","a b c","a");
+ assertMetrics("significance:0.6667","a b c","a b");
+
+ assertMetrics("significance:1", "a b c%0.2","a b c"); // Best
+ assertMetrics("significance:0.75","a b c%0.2","b c"); // Middle
+ assertMetrics("significance:0.5", "a b c%0.2","a b"); // Worst
+
+ assertMetrics("significance:1","a%0.3 b c%0.2","a b c"); // Best too
+
+ assertMetrics("significance:1", "a b c%0.05","a b c"); // Best
+ assertMetrics("significance:0.6","a b c%0.05","b c"); // Worse
+ assertMetrics("significance:0.4","a b c%0.05","b"); // Worse
+ assertMetrics("significance:0.2","a b c%0.05","c"); // Worst
+ assertMetrics("significance:0.8","a b c%0.05","a b"); // Middle
+
+ assertMetrics("significance:1", "a b c%0","a b c"); // Best
+ assertMetrics("significance:0.5","a b c%0","b c"); // Worst
+ assertMetrics("significance:1", "a b c%0","a b"); // As good as best
+ assertMetrics("significance:0", "a b c%0","c"); // No contribution
+
+ assertMetrics("significance:0","a%0 b%0","a b");
+ assertMetrics("significance:0","a%0 b%0","");
+
+ // The query also has other terms having a total significance of 0.3
+ // so we add a significance parameter which is the sum of the significances of this query terms + 0.3
+ assertMetrics("significance:0.25", "a","a",0.4f);
+ assertMetrics("significance:0", "y","a",0.4f);
+ assertMetrics("significance:0.1667","a a a","a",0.6f);
+ assertMetrics("significance:0.25", "a","a a a",0.4f);
+ assertMetrics("significance:0.5", "a b c","a b c",0.6f);
+ assertMetrics("significance:0.5", "a b c","x x a b x a x c x x a b x c c x",0.6f);
+
+ assertMetrics("significance:0.1667","a b c","a",0.6f);
+ assertMetrics("significance:0.3333","a b c","a b",0.6f);
+
+ assertMetrics("significance:0.5714","a b c%0.2","a b c",0.7f); // Best
+ assertMetrics("significance:0.4286","a b c%0.2","b c",0.7f); // Middle
+ assertMetrics("significance:0.2857","a b c%0.2","a b",0.7f); // Worst
+
+ assertMetrics("significance:0.6667","a%0.3 b c%0.2","a b c",0.9f); // Better than best
+
+ assertMetrics("significance:0.4545","a b c%0.05","a b c",0.55f); // Best
+ assertMetrics("significance:0.2727","a b c%0.05","b c",0.55f); // Worse
+ assertMetrics("significance:0.1818","a b c%0.05","b",0.55f); // Worse
+ assertMetrics("significance:0.0909","a b c%0.05","c",0.55f); // Worst
+ assertMetrics("significance:0.3636","a b c%0.05","a b",0.55f); // Middle
+
+ assertMetrics("significance:0.4","a b c%0","a b c",0.5f); // Best
+ assertMetrics("significance:0.2","a b c%0","b c",0.5f); // Worst
+ assertMetrics("significance:0.4","a b c%0","a b",0.5f); // As good as best
+ assertMetrics("significance:0", "a b c%0","c",0.5f); // No contribution
+
+ assertMetrics("significance:0","a%0 b%0","a b",0.3f);
+ assertMetrics("significance:0","a%0 b%0","",0.3f);
+ }
+
+ public void testImportance() {
+ assertMetrics("importance:0.75","a b c", "a x x b x c c c",600);
+ assertMetrics("importance:0.85","a b!500 c","a x x b x c c c",1000);
+
+ // Twice as common - twice as weighty, but total weight has the extra 300 - less than the previous
+ assertMetrics("importance:0.7857","a b!200%0.05 c","a x x b x c c c",700);
+ // Here higher importancy exactly offsets the lowered uniqueness
+ assertMetrics("importance:0.85","a b!500%0.5 c","a x x b x c c c",1000);
+ }
+
+ public void testOccurrence() {
+ assertMetrics("occurrence:0","a","x");
+ assertMetrics("occurrence:1","a","a");
+ assertMetrics("occurrence:0","a a a","x");
+ assertMetrics("occurrence:1","a a a","a");
+ assertMetrics("occurrence:1","a a a","a a a");
+ assertMetrics("occurrence:1","a a a","a a a a");
+ assertMetrics("occurrence:0.3571","a","x x x a x x a x a x x x a a");
+ assertMetrics("occurrence:1","a","a a a a a a a a a a a a a a");
+ assertMetrics("occurrence:1","a b","a b b a a a a a b a a b a a");
+
+ // tests going beyond the occurrence limit
+ FieldMatchMetricsParameters parameters=new FieldMatchMetricsParameters();
+ parameters.setMaxOccurrences(10);
+ parameters.freeze();
+ FieldMatchMetricsComputer c=new FieldMatchMetricsComputer(parameters);
+ assertMetrics("occurrence:1", "a b","a a a a a a a a a a b b",false,c);
+ assertMetrics("occurrence:0.9231","a b","a a a a a a a a a a a b b",false,c); // Starting to cut off
+ assertMetrics("occurrence:0.6", "a b","a a a a a a a a a a a a a a a a a a a a a b b",false,c); // Way beyond cutoff for a
+ assertMetrics("occurrence:1", "a b","a a a a a a a a a a b b b b b b b b b b",false,c); // Exactly no cutoff
+ assertMetrics("occurrence:1", "a b","a a a a a a a a a a a b b b b b b b b b b b",false,c); // Field is too large to consider field length
+ }
+
+ public void testAbsoluteOccurrence() {
+ assertMetrics("absoluteOccurrence:0", "a","x");
+ assertMetrics("absoluteOccurrence:0.01","a","a");
+ assertMetrics("absoluteOccurrence:0","a a a","x");
+ assertMetrics("absoluteOccurrence:0.01", "a a a","a");
+ assertMetrics("absoluteOccurrence:0.03", "a a a","a a a");
+ assertMetrics("absoluteOccurrence:0.04", "a a a","a a a a");
+ assertMetrics("absoluteOccurrence:0.05","a","x x x a x x a x a x x x a a");
+ assertMetrics("absoluteOccurrence:0.14","a","a a a a a a a a a a a a a a");
+ assertMetrics("absoluteOccurrence:0.07","a b","a b b a a a a a b a a b a a");
+
+ // tests going beyond the occurrence limit
+ FieldMatchMetricsParameters parameters=new FieldMatchMetricsParameters();
+ parameters.setMaxOccurrences(10);
+ parameters.freeze();
+ FieldMatchMetricsComputer c=new FieldMatchMetricsComputer(parameters);
+ assertMetrics("absoluteOccurrence:0.6","a b","a a a a a a a a a a b b",false,c);
+ assertMetrics("absoluteOccurrence:0.6","a b","a a a a a a a a a a a b b",false,c); // Starting to cut off
+ assertMetrics("absoluteOccurrence:0.6","a b","a a a a a a a a a a a a a a a a a a a a a b b",false,c); // Way beyond cutoff for a
+ assertMetrics("absoluteOccurrence:1", "a b","a a a a a a a a a a b b b b b b b b b b",false,c); // Exactly no cutoff
+ assertMetrics("absoluteOccurrence:1", "a b","a a a a a a a a a a a b b b b b b b b b b b",false,c); // Field is too large to consider field length
+ }
+
+ public void testWeightedOccurrence() {
+ assertMetrics("weightedOccurrence:0","a!200","x");
+ assertMetrics("weightedOccurrence:1","a!200","a");
+ assertMetrics("weightedOccurrence:0","a!200 a a","x");
+ assertMetrics("weightedOccurrence:1","a!200 a a","a");
+ assertMetrics("weightedOccurrence:1","a a a","a a a");
+ assertMetrics("weightedOccurrence:1","a!200 a a","a a a a");
+ assertMetrics("weightedOccurrence:0.3571","a!200","x x x a x x a x a x x x a a");
+ assertMetrics("weightedOccurrence:1","a!200","a a a a a a a a a a a a a a");
+ assertMetrics("weightedOccurrence:0.5","a b","a b b a a a a a b a a b a a");
+
+ assertMetrics("weightedOccurrence:0.5714","a!200 b","a b b a a a a a b a a b a a");
+ assertMetrics("weightedOccurrence:0.6753","a!1000 b","a b b a a a a a b a a b a a"); // Should be higher
+ assertMetrics("weightedOccurrence:0.4286","a b!200","a b b a a a a a b a a b a a"); // Should be lower
+ assertMetrics("weightedOccurrence:0.3061","a b!2000","a b b a a a a a b a a b a a"); // Should be even lower
+
+ assertMetrics("weightedOccurrence:0.30","a b", "a a b b b b x x x x");
+ assertMetrics("weightedOccurrence:0.3333","a b!200","a a b b b b x x x x"); // More frequent is more important - higher
+ assertMetrics("weightedOccurrence:0.2667","a!200 b","a a b b b b x x x x"); // Less frequent is more important - lower
+ assertMetrics("weightedOccurrence:0.2667","a b!50", "a a b b b b x x x x"); // Same relative
+
+ assertMetrics("weightedOccurrence:0","a!0 b!0", "a a b b b b x x x x");
+
+ // tests going beyond the occurrence limit
+ FieldMatchMetricsParameters parameters=new FieldMatchMetricsParameters();
+ parameters.setMaxOccurrences(10);
+ parameters.freeze();
+ FieldMatchMetricsComputer c=new FieldMatchMetricsComputer(parameters);
+ assertMetrics("weightedOccurrence:0.6","a b","a a a a a a a a a a b b",false,c);
+ assertMetrics("weightedOccurrence:0.6","a b","a a a a a a a a a a a b b",false,c); // Starting to cut off
+ assertMetrics("weightedOccurrence:0.6","a b","a a a a a a a a a a a a a a a a a a a a a b b",false,c); // Way beyond cutoff for a
+ assertMetrics("weightedOccurrence:1", "a b","a a a a a a a a a a b b b b b b b b b b",false,c); // Exactly no cutoff
+ assertMetrics("weightedOccurrence:1", "a b","a a a a a a a a a a a b b b b b b b b b b b",false,c); // Field is too large to consider field length
+
+ assertMetrics("weightedOccurrence:0.7333","a!200 b","a a a a a a a a a a b b",false,c);
+ assertMetrics("weightedOccurrence:0.4667","a b!200","a a a a a a a a a a b b",false,c);
+ assertMetrics("weightedOccurrence:0.7333","a!200 b","a a a a a a a a a a a b b",false,c); // Starting to cut off
+ assertMetrics("weightedOccurrence:0.7333","a!200 b","a a a a a a a a a a a a a a a a a a a a a b b",false,c); // Way beyond cutoff for a
+ assertMetrics("weightedOccurrence:1", "a!200 b","a a a a a a a a a a b b b b b b b b b b",false,c); // Exactly no cutoff
+ assertMetrics("weightedOccurrence:1", "a!200 b","a a a a a a a a a a a b b b b b b b b b b b",false,c); // Field is too large to consider field length
+ }
+
+ public void testWeightedAbsoluteOccurrence() {
+ assertMetrics("weightedAbsoluteOccurrence:0", "a!200","x");
+ assertMetrics("weightedAbsoluteOccurrence:0.01", "a!200","a");
+ assertMetrics("weightedAbsoluteOccurrence:0", "a!200 a a","x");
+ assertMetrics("weightedAbsoluteOccurrence:0.01", "a!200 a a","a");
+ assertMetrics("weightedAbsoluteOccurrence:0.03", "a a a","a a a");
+ assertMetrics("weightedAbsoluteOccurrence:0.04", "a!200 a a","a a a a");
+ assertMetrics("weightedAbsoluteOccurrence:0.05", "a!200","x x x a x x a x a x x x a a");
+ assertMetrics("weightedAbsoluteOccurrence:0.14", "a!200","a a a a a a a a a a a a a a");
+ assertMetrics("weightedAbsoluteOccurrence:0.07","a b","a b b a a a a a b a a b a a");
+
+ assertMetrics("weightedAbsoluteOccurrence:0.08", "a!200 b","a b b a a a a a b a a b a a");
+ assertMetrics("weightedAbsoluteOccurrence:0.0945","a!1000 b","a b b a a a a a b a a b a a"); // Should be higher
+ assertMetrics("weightedAbsoluteOccurrence:0.06", "a b!200","a b b a a a a a b a a b a a"); // Should be lower
+ assertMetrics("weightedAbsoluteOccurrence:0.0429","a b!2000","a b b a a a a a b a a b a a"); // Should be even lower
+
+ assertMetrics("weightedAbsoluteOccurrence:0.03", "a b", "a a b b b b x x x x");
+ assertMetrics("weightedAbsoluteOccurrence:0.0333","a b!200","a a b b b b x x x x"); // More frequent is more important - higher
+ assertMetrics("weightedAbsoluteOccurrence:0.0267","a!200 b","a a b b b b x x x x"); // Less frequent is more important - lower
+ assertMetrics("weightedAbsoluteOccurrence:0.0267","a b!50", "a a b b b b x x x x"); // Same relative
+
+ assertMetrics("weightedAbsoluteOccurrence:0","a!0 b!0", "a a b b b b x x x x");
+
+ // tests going beyond the occurrence limit
+ FieldMatchMetricsParameters parameters=new FieldMatchMetricsParameters();
+ parameters.setMaxOccurrences(10);
+ parameters.freeze();
+ FieldMatchMetricsComputer c=new FieldMatchMetricsComputer(parameters);
+ assertMetrics("weightedAbsoluteOccurrence:0.6","a b","a a a a a a a a a a b b",false,c);
+ assertMetrics("weightedAbsoluteOccurrence:0.6","a b","a a a a a a a a a a a b b",false,c); // Starting to cut off
+ assertMetrics("weightedAbsoluteOccurrence:0.6","a b","a a a a a a a a a a a a a a a a a a a a a b b",false,c); // Way beyond cutoff for a
+ assertMetrics("weightedAbsoluteOccurrence:1", "a b","a a a a a a a a a a b b b b b b b b b b",false,c); // Exactly no cutoff
+ assertMetrics("weightedAbsoluteOccurrence:1", "a b","a a a a a a a a a a a b b b b b b b b b b b",false,c); // Field is too large to consider field length
+
+ assertMetrics("weightedAbsoluteOccurrence:0.7333","a!200 b","a a a a a a a a a a b b",false,c);
+ assertMetrics("weightedAbsoluteOccurrence:0.4667","a b!200","a a a a a a a a a a b b",false,c);
+ assertMetrics("weightedAbsoluteOccurrence:0.7333","a!200 b","a a a a a a a a a a a b b",false,c); // Starting to cut off
+ assertMetrics("weightedAbsoluteOccurrence:0.7333","a!200 b","a a a a a a a a a a a a a a a a a a a a a b b",false,c); // Way beyond cutoff for a
+ assertMetrics("weightedAbsoluteOccurrence:1", "a!200 b","a a a a a a a a a a b b b b b b b b b b",false,c); // Exactly no cutoff
+ assertMetrics("weightedAbsoluteOccurrence:1", "a!200 b","a a a a a a a a a a a b b b b b b b b b b b",false,c); // Field is too large to consider field length
+ }
+
+ public void testSignificantOccurrence() {
+ assertMetrics("significantOccurrence:0","a%0.2","x");
+ assertMetrics("significantOccurrence:1","a%0.2","a");
+ assertMetrics("significantOccurrence:0","a%0.2 a a","x");
+ assertMetrics("significantOccurrence:1","a%0.2 a a","a");
+ assertMetrics("significantOccurrence:1","a a a","a a a");
+ assertMetrics("significantOccurrence:1","a%0.2 a a","a a a a");
+ assertMetrics("significantOccurrence:0.3571","a%0.2","x x x a x x a x a x x x a a");
+ assertMetrics("significantOccurrence:1","a%0.2","a a a a a a a a a a a a a a");
+ assertMetrics("significantOccurrence:0.5","a b","a b b a a a a a b a a b a a");
+
+ assertMetrics("significantOccurrence:0.5714","a%0.2 b","a b b a a a a a b a a b a a");
+ assertMetrics("significantOccurrence:0.6753","a%1 b","a b b a a a a a b a a b a a"); // Should be higher
+ assertMetrics("significantOccurrence:0.4286","a b%0.2","a b b a a a a a b a a b a a"); // Should be lower
+ assertMetrics("significantOccurrence:0.3247","a b%1","a b b a a a a a b a a b a a"); // Should be even lower
+
+ assertMetrics("significantOccurrence:0.30","a b", "a a b b b b x x x x");
+ assertMetrics("significantOccurrence:0.3333","a b%0.2","a a b b b b x x x x"); // More frequent is more important - higher
+ assertMetrics("significantOccurrence:0.2667","a%0.2 b","a a b b b b x x x x"); // Less frequent is more important - lower
+ assertMetrics("significantOccurrence:0.2667","a b%0.05", "a a b b b b x x x x"); // Same relative
+
+ assertMetrics("significantOccurrence:0","a%0 b%0", "a a b b b b x x x x");
+
+ // tests going beyond the occurrence limit
+ FieldMatchMetricsParameters parameters=new FieldMatchMetricsParameters();
+ parameters.setMaxOccurrences(10);
+ parameters.freeze();
+ FieldMatchMetricsComputer c=new FieldMatchMetricsComputer(parameters);
+ assertMetrics("significantOccurrence:0.6","a b","a a a a a a a a a a b b",false,c);
+ assertMetrics("significantOccurrence:0.6","a b","a a a a a a a a a a a b b",false,c); // Starting to cut off
+ assertMetrics("significantOccurrence:0.6","a b","a a a a a a a a a a a a a a a a a a a a a b b",false,c); // Way beyond cutoff for a
+ assertMetrics("significantOccurrence:1", "a b","a a a a a a a a a a b b b b b b b b b b",false,c); // Exactly no cutoff
+ assertMetrics("significantOccurrence:1", "a b","a a a a a a a a a a a b b b b b b b b b b b",false,c); // Field is too large to consider field length
+
+ assertMetrics("significantOccurrence:0.7333","a%0.2 b","a a a a a a a a a a b b",false,c);
+ assertMetrics("significantOccurrence:0.4667","a b%0.2","a a a a a a a a a a b b",false,c);
+ assertMetrics("significantOccurrence:0.7333","a%0.2 b","a a a a a a a a a a a b b",false,c); // Starting to cut off
+ assertMetrics("significantOccurrence:0.7333","a%0.2 b","a a a a a a a a a a a a a a a a a a a a a b b",false,c); // Way beyond cutoff for a
+ assertMetrics("significantOccurrence:1", "a%0.2 b","a a a a a a a a a a b b b b b b b b b b",false,c); // Exactly no cutoff
+ assertMetrics("significantOccurrence:1", "a%0.2 b","a a a a a a a a a a a b b b b b b b b b b b",false,c); // Field is too large to consider field length
+ }
+
+ public void testUnweightedProximity() {
+ assertMetrics("unweightedProximity:1", "a","a");
+ assertMetrics("unweightedProximity:1", "a b c","a b c");
+ assertMetrics("unweightedProximity:1", "a b c","a b c x");
+ assertMetrics("unweightedProximity:1", "y a b c","a b c x");
+ assertMetrics("unweightedProximity:1", "y a b c", "a b c x");
+ assertMetrics("unweightedProximity:0.855", "y a b c", "a b x c x");
+ assertMetrics("unweightedProximity:0.750","y a b c","a b x x c x");
+ assertMetrics("unweightedProximity:0.71", "y a b c","a x b x c x"); // Should be slightly worse than the previous one
+ assertMetrics("unweightedProximity:0.605","y a b c","a x b x x c x");
+ assertMetrics("unweightedProximity:0.53", "y a b c","a x b x x x c x");
+ assertMetrics("unweightedProximity:0.5", "y a b c","a x x b x x c x");
+ }
+
+ public void testReverseProximity() {
+ assertMetrics("unweightedProximity:0.33", "a b","b a");
+ assertMetrics("unweightedProximity:0.62", "a b c","c a b");
+ assertMetrics("unweightedProximity:0.585", "y a b c","c x a b");
+ assertMetrics("unweightedProximity:0.33", "a b c","c b a");
+ assertMetrics("unweightedProximity:0.6875","a b c d e","a b d c e");
+ assertMetrics("unweightedProximity:0.9275","a b c d e","a b x c d e");
+ }
+
+ public void testProximity() {
+ assertMetrics("absoluteProximity:0.1 proximity:1", "a b","a b");
+ assertMetrics("absoluteProximity:0.3 proximity:1", "a 0.3:b","a b");
+ assertMetrics("absoluteProximity:0.1 proximity:1", "a 0.0:b","a b");
+ assertMetrics("absoluteProximity:1 proximity:1", "a 1.0:b","a b");
+ assertMetrics("absoluteProximity:0.033 proximity:0.33", "a b","b a");
+ assertMetrics("absoluteProximity:0.0108 proximity:0.0359","a 0.3:b","b a"); // Should be worse than the previous one
+ assertMetrics("absoluteProximity:0.1 proximity:1", "a 0.0:b","b a");
+ assertMetrics("absoluteProximity:0 proximity:0", "a 1.0:b","b a");
+
+ // proximity with connextedness
+ assertMetrics("absoluteProximity:0.0605 proximity:0.605", "a b c","a x b x x c");
+ assertMetrics("absoluteProximity:0.0701 proximity:0.2003","a 0.5:b 0.2:c","a x b x x c"); // Most important is close, less important is far: Better
+ assertMetrics("absoluteProximity:0.0605 proximity:0.605", "a b c","a x x b x c");
+ assertMetrics("absoluteProximity:0.0582 proximity:0.1663","a 0.5:b 0.2:c","a x x b x c"); // Most important is far, less important is close: Worse
+
+ assertMetrics("absoluteProximity:0.0727 proximity:0.7267","a b c d","a b x x x x x c d");
+ assertMetrics("absoluteProximity:0.1 proximity:1", "a b 0:c d","a b x x x x x c d"); // Should be better because the gap is unimportant
+ }
+
+ /**
+ * Tests exactness (using field exactness only - nothing additional of interest to test with query exactness
+ * as that is just another number multiplied with the term exactness)
+ */
+ public void testExactness() {
+ assertMetrics("exactness:1", "a b c","a x b x x c");
+ assertMetrics("exactness:0.9", "a b c","a x b:0.7 x x c");
+ assertMetrics("exactness:0.7", "a b c","a x b:0.6 x x c:0.5");
+ assertMetrics("exactness:0.775", "a!200 b c","a x b:0.6 x x c:0.5");
+ assertMetrics("exactness:0.65", "a b c!200","a x b:0.6 x x c:0.5");
+ }
+
+ public void testMultiSegmentProximity() {
+ assertMetrics("absoluteProximity:0.1 proximity:1", "a b c", "a b x x x x x x x x x x x x x x x x x x x x x x c");
+ assertMetrics("absoluteProximity:0.05 proximity:0.5","a b c", "a x x b x x x x x x x x x x x x x x x x x x x x x x c");
+ assertMetrics("absoluteProximity:0.075 proximity:0.75","a b c d","a x x b x x x x x x x x x x x x x x x x x x x x x x c d");
+ }
+
+ public void testSegmentDistance() {
+ assertMetrics("segmentDistance:13 absoluteProximity:0.1", "a b c","a b x x x x x x x x x x c");
+ assertMetrics("segmentDistance:13 absoluteProximity:0.5", "a 0.5:b c","a b x x x x x x x x x x c");
+ assertMetrics("segmentDistance:13 absoluteProximity:0.1", "a b c","b c x x x x x x x x x x a");
+ assertMetrics("segmentDistance:25 absoluteProximity:0.1", "a b c","b x x x x x x x x x x x a x x x x x x x x x x c");
+ assertMetrics("segmentDistance:13 absoluteProximity:0.006","a b c","a x x x x x x x x x x x b x x x x x x x x c");
+ assertMetrics("segmentDistance:24 absoluteProximity:0.1", "a b c","a x x x x x x x x x x x b x x x x x x x x x c");
+ assertMetrics("segmentDistance:25 absoluteProximity:0.1", "a b c","a x x x x x x x x x x x b x x x x x x x x x x c");
+ assertMetrics("segmentDistance:25 absoluteProximity:0.1", "a b c","c x x x x x x x x x x x b x x x x x x x x x x a");
+ }
+
+ public void testSegmentProximity() {
+ assertMetrics("segmentProximity:1", "a","a");
+ assertMetrics("segmentProximity:0", "a","x");
+ assertMetrics("segmentProximity:1", "a","a x");
+ assertMetrics("segmentProximity:0", "a b","a x x x x x x x x x x x x x x x x x x x x x x x b");
+ assertMetrics("segmentProximity:0.4","a b","a x x x x x x x x x x x x x x x x x x x x x x b x x x x x x x x x x x x x x x x");
+ assertMetrics("segmentProximity:0", "a b c","a b x x x x x x x x x x x x x x x x x x x x x c");
+ assertMetrics("segmentProximity:0.4","a b c","a b x x x x x x x x x x x x x x x x x x x x x c x x x x x x x x x x x x x x x x");
+ assertMetrics("segmentProximity:0.4","a b c","b c x x x x x x x x x x x x x x x x x x x x x a x x x x x x x x x x x x x x x x");
+ }
+
+ /** Test cases where we choose between multiple different segmentations */
+ public void testSegmentSelection() {
+ assertMetrics("segments:2 absoluteProximity:0.1 proximity:1 segmentStarts:19,41",
+ "a b c d e","x a b x c x x x x x x x x x x x x x x a b c x x x x x x x x x e x d x c d x x x c d e");
+ // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2
+ // 0 1 2 3 4
+ // Should choose - - - - -
+
+ // Same as above but best matching segment have too low exactness
+ assertMetrics("segments:2 absoluteProximity:0.0903 proximity:0.9033 segmentStarts:1,41",
+ "a b c d e","x a b x c x x x x x x x x x x x x x x a:0.2 b:0.3 c:0.4 x x x x x x x x x e x d x c d x x x c d e");
+ // 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2
+ // 0 1 2 3 4
+ // Should choose - - - - -
+
+ assertMetrics("segments:1 absoluteProximity:0.0778 proximity:0.778","a b c d e f","x x a b b b c f e d a b c d x e x x x x x f d e f a b c a a b b c c d d e e f f");
+
+ // Prefer one segment with ok proximity over two segments with great proximity
+ assertMetrics("segments:1 segmentStarts:0","a b c d","a b x c d x x x x x x x x x x x a b x x x x x x x x x x x c d");
+ assertMetrics("segments:1 segmentStarts:0","a b c d","a b x x x x x x x x c d x x x x x x x x x x x a b x x x x x x x x x x x c d");
+ }
+
+ public void testMoreThanASegmentLengthOfUnmatchedQuery() {
+ assertMetrics("absoluteProximity:0.1 proximity:1","a b y y y y y y y y y y y y y y y","a b");
+ assertMetrics("segments:2 absoluteProximity:0.1 proximity:1","a b c d y y y y y y y y y y y y y y y","a b x x x x x x x x x x x x x x x x x x c d");
+ assertMetrics("segments:2 absoluteProximity:0.1 proximity:1","a b y y y y y y y y y y y y y y y c d","a b x x x x x x x x x x x x x x x x x x c d");
+ }
+
+ public void testQueryRepeats() {
+ // Not really handled perfectly, but good enough
+ assertMetrics("absoluteProximity:0.1 proximity:1 head:0 tail:0", "a a a","a");
+ assertMetrics("absoluteProximity:0.1 proximity:1 head:0 tail:0 gapLength:0","a a b c c","a a b c c");
+ assertMetrics("absoluteProximity:0.1 proximity:1 head:0 tail:0 gapLength:0","a a b c c","a b c");
+ assertMetrics("absoluteProximity:0.1 proximity:1 head:0 tail:0 gapLength:0","a b a b","a b a b");
+ assertMetrics("absoluteProximity:0.0903 proximity:0.9033 head:0 tail:0 gapLength:1","a b a b","a b x a b");
+ // Both terms take the same segment:
+ assertMetrics("absoluteProximity:0.1 proximity:1 segments:2 gapLength:0 head:3 tail:18","a a","x x x a x x x x x x x x x x x x x x a x x x");
+ // But not when the second is preferable
+ assertMetrics("absoluteProximity:0.1 proximity:1 segments:2 gapLength:0 head:3 tail:3","a b b a","x x x a b x x x x x x x x x x x x x x b a x x x");
+
+ assertMetrics("matches:2 fieldCompleteness:1","a b b b","a b");
+ }
+
+ public void testZeroCases() {
+ assertMetrics("absoluteProximity:0.1 proximity:1 matches:0 exactness:0","y","a");
+ assertMetrics("absoluteProximity:0.1 proximity:1 matches:0 exactness:0","a","x");
+ assertMetrics("absoluteProximity:0.1 proximity:1 matches:0 exactness:0","","x");
+ assertMetrics("absoluteProximity:0.1 proximity:1 matches:0 exactness:0","y","");
+ assertMetrics("absoluteProximity:0.1 proximity:1 matches:0 exactness:0","","");
+ }
+
+ public void testExceedingIterationLimit() {
+
+ { // Segments found: a x x b and c d
+ FieldMatchMetricsParameters p=new FieldMatchMetricsParameters();
+ p.setMaxAlternativeSegmentations(0);
+ FieldMatchMetricsComputer m=new FieldMatchMetricsComputer(p);
+ assertMetrics("matches:4 tail:0 proximity:0.75 absoluteProximity:0.075","a b c d","a x x b x x x a x b x x x x x a b x x x x x x x x x x x x x x x x x c d",false,m);
+ }
+
+ { // Segments found: a x b and c d
+ FieldMatchMetricsParameters p=new FieldMatchMetricsParameters();
+ p.setMaxAlternativeSegmentations(1);
+ FieldMatchMetricsComputer m=new FieldMatchMetricsComputer(p);
+ assertMetrics("matches:4 tail:0 proximity:0.855 absoluteProximity:0.0855","a b c d","a x x b x x x a x b x x x x x a b x x x x x x x x x x x x x x x x x c d",false,m);
+ }
+
+ { // Segments found: a b and c d
+ FieldMatchMetricsParameters p=new FieldMatchMetricsParameters();
+ p.setMaxAlternativeSegmentations(2);
+ FieldMatchMetricsComputer m=new FieldMatchMetricsComputer(p);
+ assertMetrics("matches:4 tail:0 proximity:1 absoluteProximity:0.1","a b c d","a x x b x x x a x b x x x x x a b x x x x x x x x x x x x x x x x x c d",false,m);
+ }
+ }
+
+ public void testMatch() {
+ // Ordered by decreasing match score per query
+ assertMetrics("match:1", "a","a");
+ assertMetrics("match:0.9339","a","a x");
+ assertMetrics("match:0", "a","x");
+ assertMetrics("match:0.9243","a","x a");
+ assertMetrics("match:0.9025","a","x a x");
+
+ assertMetrics("match:1", "a b","a b");
+ assertMetrics("match:0.9558","a b","a b x");
+ assertMetrics("match:0.9463","a b","x a b");
+ assertMetrics("match:0.1296","a b","a x x x x x x x x x x x x x x x x x x x x x x b");
+ assertMetrics("match:0.1288","a b","a x x x x x x x x x x x x x x x x x x x x x x x x x x x b");
+
+ assertMetrics("match:0.8647","a b c","x x a x b x x x x x x x x a b c x x x x x x x x c x x");
+ assertMetrics("match:0.861", "a b c","x x a x b x x x x x x x x x x a b c x x x x x x c x x");
+ assertMetrics("match:0.4869","a b c","a b x x x x x x x x x x x x x x x x x x x x x x c x x");
+ assertMetrics("match:0.4853","a b c","x x a x b x x x x x x x x x x b a c x x x x x x c x x");
+ assertMetrics("match:0.3621","a b c","a x b x x x x x x x x x x x x x x x x x x x x x c x x");
+ assertMetrics("match:0.3619","a b c","x x a x b x x x x x x x x x x x x x x x x x x x c x x");
+ assertMetrics("match:0.3584","a b c","x x a x b x x x x x x x x x x x x x x x x x x x x x c");
+ assertMetrics("match:0.3474","a b c","x x a x b x x x x x x x x x x x x x x b x x x b x b x");
+ assertMetrics("match:0.3421","a b c","x x a x b x x x x x x x x x x x x x x x x x x x x x x");
+ assertMetrics("match:0.305" ,"a b c","x x a x b:0.7 x x x x x x x x x x x x x x x x x x x x x x");
+ assertMetrics("match:0.2927","a b!200 c","x x a x b:0.7 x x x x x x x x x x x x x x x x x x x x x x");
+ }
+
+ public void testRepeatedMatch() {
+ // gap==1 caused by finding two possible segments due to repeated matching
+ assertMetrics("fieldCompleteness:1 queryCompleteness:0.6667 segments:1 earliness:1 gaps:1",
+ "pizza hut pizza","pizza hut");
+ }
+
+ /** Three segments - improving the score on the first should impact the last */
+ public void testNestedAlternatives() {
+ assertMetrics("segmentStarts:6,19,32 proximity:1",
+ "a b c d e f",
+ "a x b x x x a b x x x x x x x x x x x c d x x x x x x x x x x x e f");
+ assertMetrics("segmentStarts:6,19,47 proximity:1",
+ "a b c d e f",
+ "a x b x x x a b x x x x x x x x x x x c d x x x x x x x x x x x e x f x x x x x x x x x x x x e f");
+ }
+
+ /** Nice demonstration of the limitations of this algorithm: Segment end points are determined greedily */
+ public void testSegmentationGreedyness() {
+ assertMetrics("match:0.3717","a b c","a x b x x x x x x x x b c");
+ assertMetrics("match:0.4981","a b c","a x z x x x x x x x x b c");
+ }
+
+ protected void assertMetrics(String correctSpec, String query, String field) {
+ assertMetrics(correctSpec, query, field, false);
+ }
+
+ protected void assertMetrics(String correctSpec, String queryString, String field, int totalTermWeight) {
+ Query query=toQuery(queryString);
+ query.setTotalTermWeight(totalTermWeight);
+ assertMetrics(correctSpec, query, toField(field), false, new FieldMatchMetricsComputer());
+ }
+
+ protected void assertMetrics(String correctSpec, String queryString, String field, float totalSignificance) {
+ Query query=toQuery(queryString);
+ query.setTotalSignificance(totalSignificance);
+ assertMetrics(correctSpec, query, toField(field), false, new FieldMatchMetricsComputer());
+ }
+
+ protected void assertMetrics(String correctSpec,String query,String field,boolean printTrace) {
+ assertMetrics(correctSpec,query,field,printTrace,new FieldMatchMetricsComputer());
+ }
+
+ protected void assertMetrics(String correctSpec,String query,String field,boolean printTrace,FieldMatchMetricsComputer m) {
+ assertMetrics(correctSpec, toQuery(query), toField(field), printTrace, m);
+ }
+
+ protected void assertMetrics(String correctSpec, Query query, Field field, boolean printTrace, FieldMatchMetricsComputer m) {
+ FieldMatchMetrics metrics = m.compute(query, field, printTrace);
+ if (printTrace)
+ System.out.println(metrics.trace());
+
+ if (printTrace)
+ System.out.println(metrics.toStringDump());
+
+ for (String correctValueSpec: correctSpec.split(" ")) {
+ if (correctValueSpec.trim().equals("")) continue;
+ String metricName=correctValueSpec.split(":")[0];
+ String correctValueString=correctValueSpec.split(":")[1];
+ if (metricName.equals("segmentStarts")) {
+ String[] correctSegmentStarts=correctValueString.split(",");
+ List<Integer> segmentStarts=metrics.getSegmentStarts();
+ assertEquals("Segment start count",correctSegmentStarts.length,segmentStarts.size());
+ for (int i=0; i<segmentStarts.size(); i++)
+ assertEquals("Expected segment starts " + correctValueString + " was " + segmentStarts,
+ Integer.valueOf(correctSegmentStarts[i]),segmentStarts.get(i));
+ }
+ else {
+ float correctValue=Float.parseFloat(correctValueString);
+ assertEquals(metricName, correctValue, (float)Math.round(metrics.get(metricName)*10000)/10000 );
+ }
+ }
+ }
+
+ private Query toQuery(String queryString) {
+ if (queryString.length()==0) return new Query(new QueryTerm[0]);
+ String[] queryTerms=queryString.split(" ");
+ QueryTerm[] query=new QueryTerm[queryTerms.length];
+ for (int i=0; i<query.length; i++) {
+ String[] percentSplit=queryTerms[i].split("%");
+ String[] bangSplit=percentSplit[0].split("!");
+ String[] colonSplit=bangSplit[0].split(":");
+ if (colonSplit.length>1)
+ query[i]=new QueryTerm(colonSplit[1],Float.parseFloat(colonSplit[0]));
+ else
+ query[i]=new QueryTerm(colonSplit[0]);
+
+ if (bangSplit.length>1)
+ query[i].setWeight(Integer.parseInt(bangSplit[1]));
+ if (percentSplit.length>1)
+ query[i].setSignificance(Float.parseFloat(percentSplit[1]));
+ }
+ return new Query(query);
+ }
+
+ private Field toField(String fieldString) {
+ if (fieldString.length() == 0) return new Field(ImmutableList.of());
+
+ ImmutableList.Builder<Field.Term> terms = new ImmutableList.Builder<>();
+ for (String termString : fieldString.split(" ")) {
+ String[] colonSplit = termString.split(":");
+ if (colonSplit.length > 1)
+ terms.add(new Field.Term(colonSplit[0], Float.parseFloat(colonSplit[1])));
+ else
+ terms.add(new Field.Term(colonSplit[0]));
+ }
+ return new Field(terms.build());
+ }
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/FeatureListTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/FeatureListTestCase.java
new file mode 100755
index 00000000000..7399088ac1c
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/FeatureListTestCase.java
@@ -0,0 +1,77 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.rankingexpression;
+
+import com.yahoo.searchlib.rankingexpression.parser.ParseException;
+import org.junit.Test;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.Reader;
+import java.io.StringReader;
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class FeatureListTestCase {
+
+ @Test
+ public void requireThatFeatureListFromStringWorks() throws ParseException {
+ assertFromString("attribute(foo).out",
+ Arrays.asList("attribute(foo).out"));
+ assertFromString("attribute(foo).out attribute ( bar ) . out",
+ Arrays.asList("attribute(foo).out", "attribute(bar).out"));
+ assertFromString("foo\n bar\n \t \t \n baz \n",
+ Arrays.asList("foo", "bar", "baz"));
+ assertFromString("attribute attribute(foo) attribute(foo).out attribute(bar).out.out",
+ Arrays.asList("attribute", "attribute(foo)", "attribute(foo).out", "attribute(bar).out.out"));
+ }
+
+ @Test
+ public void requireThatFeatureListFromReaderWorks() throws ParseException {
+ assertFromReader(new StringReader("attribute(foo).out"),
+ Arrays.asList("attribute(foo).out"));
+ assertFromReader(new StringReader("attribute(foo).out attribute ( bar ) . out"),
+ Arrays.asList("attribute(foo).out", "attribute(bar).out"));
+ assertFromReader(new StringReader("foo\n bar\n \t \t \n baz \n"),
+ Arrays.asList("foo", "bar", "baz"));
+ assertFromReader(new StringReader("attribute attribute(foo) attribute(foo).out attribute(bar).out.out"),
+ Arrays.asList("attribute", "attribute(foo)", "attribute(foo).out", "attribute(bar).out.out"));
+ }
+
+ @Test
+ public void requireThatFeatureListFromFileWorks() throws ParseException, FileNotFoundException {
+ assertFromFile(new File("src/test/files/features01.expression"),
+ Arrays.asList("attribute(foo).out"));
+ assertFromFile(new File("src/test/files/features02.expression"),
+ Arrays.asList("attribute(foo).out", "attribute(bar).out"));
+ assertFromFile(new File("src/test/files/features03.expression"),
+ Arrays.asList("foo", "bar", "baz"));
+ assertFromFile(new File("src/test/files/features04.expression"),
+ Arrays.asList("attribute", "attribute(foo)", "attribute(foo).out", "attribute(bar).out.out"));
+ }
+
+ public void assertFromString(String input, List<String> expected) throws ParseException {
+ assertFeatureList(new FeatureList(input), expected);
+ }
+
+ public void assertFromReader(Reader input, List<String> expected) throws ParseException {
+ assertFeatureList(new FeatureList(input), expected);
+ }
+
+ public void assertFromFile(File input, List<String> expected) throws ParseException, FileNotFoundException {
+ assertFeatureList(new FeatureList(input), expected);
+ }
+
+ public void assertFeatureList(FeatureList features, List<String> expected) throws ParseException {
+ assertEquals(expected.size(), features.size());
+ for (int i = 0; i < features.size(); ++i) {
+ assertTrue(features.get(i) != null);
+ assertEquals(expected.get(i), features.get(i).toString());
+ }
+ }
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/RankingExpressionTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/RankingExpressionTestCase.java
new file mode 100755
index 00000000000..24d7c82235c
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/RankingExpressionTestCase.java
@@ -0,0 +1,281 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.rankingexpression;
+
+import com.yahoo.searchlib.rankingexpression.parser.ParseException;
+import com.yahoo.searchlib.rankingexpression.rule.CompositeNode;
+import com.yahoo.searchlib.rankingexpression.rule.IfNode;
+import com.yahoo.searchlib.rankingexpression.rule.ExpressionNode;
+import com.yahoo.searchlib.rankingexpression.rule.FunctionNode;
+import junit.framework.TestCase;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.*;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class RankingExpressionTestCase extends TestCase {
+
+ public void testParamInFeature() throws ParseException {
+ assertParse("if (1 > 2, dotProduct(allparentid,query(cate1_parentid)), 2)",
+ "if ( 1 > 2,\n" +
+ "dotProduct(allparentid, query(cate1_parentid)),\n" +
+ "2\n" +
+ ")");
+ }
+
+ public void testDollarShorthand() throws ParseException {
+ assertParse("query(var1)", " $var1");
+ assertParse("query(var1)", " $var1 ");
+ assertParse("query(var1) + query(var2)", " $var1 + $var2 ");
+ assertParse("query(var1) + query(var2) - query(var3)", " $var1 + $var2 - $var3 ");
+ assertParse("query(var1) + query(var2) - query(var3) * query(var4) / query(var5)", " $var1 + $var2 - $var3 * $var4 / $var5 ");
+ assertParse("(query(var1) + query(var2)) - query(var3) * query(var4) / query(var5)", "($var1 + $var2)- $var3 * $var4 / $var5 ");
+ assertParse("query(var1) + (query(var2) - query(var3)) * query(var4) / query(var5)", " $var1 +($var2 - $var3)* $var4 / $var5 ");
+ assertParse("query(var1) + query(var2) - (query(var3) * query(var4)) / query(var5)", " $var1 + $var2 -($var3 * $var4)/ $var5 ");
+ assertParse("query(var1) + query(var2) - query(var3) * (query(var4) / query(var5))", " $var1 + $var2 - $var3 *($var4 / $var5)");
+ assertParse("if (if (f1.out < query(p1), 0, 1) < if (f2.out < query(p2), 0, 1), f3.out, query(p3))", "if(if(f1.out<$p1,0,1)<if(f2.out<$p2,0,1),f3.out,$p3)");
+ }
+
+ public void testLookaheadIndefinitely() throws Exception {
+ ExecutorService exec = Executors.newSingleThreadExecutor();
+ Future<Boolean> future = exec.submit(new Callable<Boolean>() {
+ @Override
+ public Boolean call() {
+ try {
+ new RankingExpression("if (fieldMatch(title) < 0.316316, if (now < 1.218627E9, if (now < 1.217667E9, if (now < 1.217244E9, if (rankBoost < 100050.0, 0.1424368, if (match < 0.284921, if (now < 1.217238E9, 0.1528184, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, if (now < 1.217238E9, 0.1, 0.1493261))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))))), 0.1646852)), 0.1850886), if (match < 0.308468, if (firstPhase < 5891.5, 0.08424015, 0.1167076), if (rankBoost < 120050.0, 0.111576, 0.1370456))), if (match < 0.31644, 0.1543837, 0.1727403)), if (now < 1.218088E9, if (now < 1.217244E9, if (fieldMatch(metakeywords).significance < 0.1425405, if (match.totalWeight < 450.0, 0.1712793, 0.1632426), 0.1774488), 0.1895567), if (now < 1.218361E9, if (fieldTermMatch(keywords_1).firstPosition < 1.5, 0.1530005, 0.1370894), 0.1790079)))");
+ return Boolean.TRUE;
+ } catch (ParseException e) {
+ return Boolean.FALSE;
+ }
+ }
+ });
+ assertTrue(future.get(60, TimeUnit.SECONDS));
+ }
+
+ public void testSelfRecursionScript() throws ParseException {
+ List<ExpressionFunction> macros = new ArrayList<>();
+ macros.add(new ExpressionFunction("foo", null, new RankingExpression("foo")));
+
+ RankingExpression exp = new RankingExpression("foo");
+ try {
+ exp.getRankProperties(macros);
+ } catch (RuntimeException e) {
+ assertEquals("Cycle in ranking expression function: [foo[]]", e.getMessage());
+ }
+ }
+
+ public void testMacroCycleScript() throws ParseException {
+ List<ExpressionFunction> macros = new ArrayList<>();
+ macros.add(new ExpressionFunction("foo", null, new RankingExpression("bar")));
+ macros.add(new ExpressionFunction("bar", null, new RankingExpression("foo")));
+
+ RankingExpression exp = new RankingExpression("foo");
+ try {
+ exp.getRankProperties(macros);
+ } catch (RuntimeException e) {
+ assertEquals("Cycle in ranking expression function: [foo[], bar[]]", e.getMessage());
+ }
+ }
+
+ public void testScript() throws ParseException {
+ List<ExpressionFunction> macros = new ArrayList<>();
+ macros.add(new ExpressionFunction("foo", Arrays.asList("arg1", "arg2"), new RankingExpression("min(arg1, pow(arg2, 2))")));
+ macros.add(new ExpressionFunction("bar", Arrays.asList("arg1", "arg2"), new RankingExpression("arg1 * arg1 + 2 * arg1 * arg2 + arg2 * arg2")));
+ macros.add(new ExpressionFunction("baz", Arrays.asList("arg1", "arg2"), new RankingExpression("foo(1, 2) / bar(arg1, arg2)")));
+ macros.add(new ExpressionFunction("cox", null, new RankingExpression("10 + 08 * 1977")));
+
+ assertScript("foo(1,2) + foo(3,4) * foo(5, foo(foo(6, 7), 8))", macros,
+ Arrays.asList(
+ "rankingExpression(foo@e2dc17a89864aed0.12232eb692c6c502) + rankingExpression(foo@af74e3fd9070bd18.a368ed0a5ba3a5d0) * rankingExpression(foo@dbab346efdad5362.e5c39e42ebd91c30)",
+ "min(5,pow(rankingExpression(foo@d1d1417259cdc651.573bbcd4be18f379),2))",
+ "min(6,pow(7,2))",
+ "min(1,pow(2,2))",
+ "min(3,pow(4,2))",
+ "min(rankingExpression(foo@84951be88255b0ec.d0303e061b36fab8),pow(8,2))"
+ ));
+ assertScript("foo(1, 2) + bar(3, 4)", macros,
+ Arrays.asList(
+ "rankingExpression(foo@e2dc17a89864aed0.12232eb692c6c502) + rankingExpression(bar@af74e3fd9070bd18.a368ed0a5ba3a5d0)",
+ "min(1,pow(2,2))",
+ "3 * 3 + 2 * 3 * 4 + 4 * 4"
+ ));
+ assertScript("baz(1, 2)", macros,
+ Arrays.asList(
+ "rankingExpression(baz@e2dc17a89864aed0.12232eb692c6c502)",
+ "min(1,pow(2,2))",
+ "rankingExpression(foo@e2dc17a89864aed0.12232eb692c6c502) / rankingExpression(bar@e2dc17a89864aed0.12232eb692c6c502)",
+ "1 * 1 + 2 * 1 * 2 + 2 * 2"
+ ));
+ assertScript("cox", macros,
+ Arrays.asList(
+ "rankingExpression(cox)",
+ "10 + 08 * 1977"
+ ));
+ }
+
+ public void testBug3464208() throws ParseException {
+ List<ExpressionFunction> macros = new ArrayList<>();
+ macros.add(new ExpressionFunction("log10tweetage", null, new RankingExpression("69")));
+
+ String lhs = "log10(0.01+attribute(user_followers_count)) * log10(socialratio) * " +
+ "log10(userage/(0.01+attribute(user_statuses_count)))";
+ String rhs = "(log10tweetage * log10tweetage * log10tweetage) + 5.0 * " +
+ "attribute(ythl)";
+
+ String expLhs = "log10(0.01 + attribute(user_followers_count)) * log10(socialratio) * " +
+ "log10(userage / (0.01 + attribute(user_statuses_count)))";
+ String expRhs = "(rankingExpression(log10tweetage) * rankingExpression(log10tweetage) * " +
+ "rankingExpression(log10tweetage)) + 5.0 * attribute(ythl)";
+
+ assertScript(lhs + " + " + rhs, macros,
+ Arrays.asList(
+ expLhs + " + " + expRhs,
+ "69"
+ ));
+ assertScript(lhs + " - " + rhs, macros,
+ Arrays.asList(
+ expLhs + " - " + expRhs,
+ "69"
+ ));
+ }
+
+ public void testParse() throws ParseException, IOException {
+ BufferedReader reader = new BufferedReader(new FileReader("src/tests/rankingexpression/rankingexpressionlist"));
+ String line;
+ int lineNumber = 0;
+ while ((line = reader.readLine()) != null) {
+ lineNumber++;
+ if (line.length() == 0 || line.charAt(0) == '#') {
+ continue;
+ }
+ String[] parts = line.split(";");
+ // System.out.println("Parsing '" + parts[0].trim() + "'..");
+ RankingExpression expression = new RankingExpression(parts[0].trim());
+
+ String out = expression.toString();
+ if (parts.length == 1) {
+ assertEquals(parts[0].trim(), out);
+ } else {
+ boolean ok = false;
+ String err = "Expression '" + out + "' not present in { ";
+ for (int i = 1; i < parts.length && !ok; ++i) {
+ err += "'" + parts[i].trim() + "'";
+ if (parts[i].trim().equals(out)) {
+ ok = true;
+ }
+ if (i < parts.length - 1) {
+ err += ", ";
+ }
+ }
+ err += " }.";
+ assertTrue("At line " + lineNumber + ": " + err, ok);
+ }
+ }
+ }
+
+ public void testIssue() throws ParseException {
+ assertEquals("feature.0", new RankingExpression("feature.0").toString());
+ assertEquals("if (1 > 2, 3, 4) + feature(arg1).out.out",
+ new RankingExpression("if ( 1 > 2 , 3 , 4 ) + feature ( arg1 ) . out.out").toString());
+ }
+
+ public void testNegativeConstantArgument() throws ParseException {
+ assertEquals("foo(-1.2)", new RankingExpression("foo(-1.2)").toString());
+ }
+
+ public void testNaming() throws ParseException {
+ RankingExpression test = new RankingExpression("a+b");
+ test.setName("test");
+ assertEquals("test: a + b", test.toString());
+ }
+
+ public void testCondition() throws ParseException {
+ RankingExpression expression = new RankingExpression("if(1<2,3,4)");
+ assertTrue(expression.getRoot() instanceof IfNode);
+ }
+
+ public void testFileImporting() throws ParseException {
+ RankingExpression expression = new RankingExpression(new File("src/test/files/simple.expression"));
+ assertEquals("simple: a + b", expression.toString());
+ }
+
+ public void testNonCanonicalLegalStrings() throws ParseException {
+ assertParse("a * b + c * d", "a* (b) + \nc*d");
+ }
+
+ public void testEquality() throws ParseException {
+ assertEquals(new RankingExpression("if ( attribute(foo)==\"BAR\",log(attribute(popularity)+5),log(fieldMatch(title).proximity)*fieldMatch(title).completeness)"),
+ new RankingExpression("if(attribute(foo)==\"BAR\", log(attribute(popularity)+5),log(fieldMatch(title).proximity) * fieldMatch(title).completeness)"));
+
+ assertFalse(new RankingExpression("if ( attribute(foo)==\"BAR\",log(attribute(popularity)+5),log(fieldMatch(title).proximity)*fieldMatch(title).completeness)").equals(
+ new RankingExpression("if(attribute(foo)==\"BAR\", log(attribute(popularity)+5),log(fieldMatch(title).earliness) * fieldMatch(title).completeness)")));
+ }
+
+ public void testSetMembershipConditions() throws ParseException {
+ assertEquals(new RankingExpression("if ( attribute(foo) in [\"FOO\", \"BAR\"],log(attribute(popularity)+5),log(fieldMatch(title).proximity)*fieldMatch(title).completeness)"),
+ new RankingExpression("if(attribute(foo) in [\"FOO\",\"BAR\"], log(attribute(popularity)+5),log(fieldMatch(title).proximity) * fieldMatch(title).completeness)"));
+
+ assertFalse(new RankingExpression("if ( attribute(foo) in [\"FOO\", \"BAR\"],log(attribute(popularity)+5),log(fieldMatch(title).proximity)*fieldMatch(title).completeness)").equals(
+ new RankingExpression("if(attribute(foo) in [\"FOO\",\"BAR\"], log(attribute(popularity)+5),log(fieldMatch(title).earliness) * fieldMatch(title).completeness)")));
+
+ assertEquals(new RankingExpression("if ( attribute(foo) in [attribute(category), \"BAR\"],log(attribute(popularity)+5),log(fieldMatch(title).proximity)*fieldMatch(title).completeness)"),
+ new RankingExpression("if(attribute(foo) in [attribute(category),\"BAR\"], log(attribute(popularity)+5),log(fieldMatch(title).proximity) * fieldMatch(title).completeness)"));
+ assertEquals(new RankingExpression("if (GENDER$ in [-1.0, 1.0], 1, 0)"), new RankingExpression("if (GENDER$ in [-1.0, 1.0], 1, 0)"));
+ }
+
+ public void testComments() throws ParseException {
+ assertEquals(new RankingExpression("if ( attribute(foo) in [\"FOO\", \"BAR\"],\n" +
+ "# a comment\n" +
+ "log(attribute(popularity)+5),log(fieldMatch(title).proximity)*" +
+ "# a multiline \n" +
+ " # comment\n" +
+ "fieldMatch(title).completeness)"),
+ new RankingExpression("if(attribute(foo) in [\"FOO\",\"BAR\"], log(attribute(popularity)+5),log(fieldMatch(title).proximity) * fieldMatch(title).completeness)"));
+ }
+
+ public void testIsNan() throws ParseException {
+ String strExpr = "if (isNan(attribute(foo)) == 1.0, 1.0, attribute(foo))";
+ RankingExpression expr = new RankingExpression(strExpr);
+ CompositeNode root = (CompositeNode)expr.getRoot();
+ CompositeNode comparison = (CompositeNode)root.children().get(0);
+ ExpressionNode isNan = comparison.children().get(0);
+ assertTrue(isNan instanceof FunctionNode);
+ assertEquals("isNan(attribute(foo))", isNan.toString());
+ }
+
+ protected static void assertParse(String expected, String expression) throws ParseException {
+ assertEquals(expected, new RankingExpression(expression).toString());
+ }
+
+ private void assertScript(String expression, List<ExpressionFunction> macros, List<String> expectedScripts)
+ throws ParseException {
+ boolean print = false;
+ if (print)
+ System.out.println("Parsing expression '" + expression + "'.");
+
+ RankingExpression exp = new RankingExpression(expression);
+ Map<String, String> scripts = exp.getRankProperties(macros);
+ if (print) {
+ for (String key : scripts.keySet()) {
+ System.out.println("Script '" + key + "': " + scripts.get(key));
+ }
+ }
+
+ for (Map.Entry<String, String> m : scripts.entrySet())
+ System.out.println(m);
+ for (int i = 0; i < expectedScripts.size();) {
+ String val = expectedScripts.get(i++);
+ assertTrue("Script contains " + val, scripts.containsValue(val));
+ }
+ if (print)
+ System.out.println("");
+ }
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/Benchmark.java b/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/Benchmark.java
new file mode 100644
index 00000000000..7690efb1112
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/Benchmark.java
@@ -0,0 +1,144 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.rankingexpression.evaluation;
+
+import com.yahoo.searchlib.rankingexpression.RankingExpression;
+import com.yahoo.searchlib.rankingexpression.evaluation.gbdtoptimization.GBDTForestOptimizer;
+import com.yahoo.searchlib.rankingexpression.parser.ParseException;
+import com.yahoo.searchlib.rankingexpression.rule.CompositeNode;
+import com.yahoo.searchlib.rankingexpression.rule.ExpressionNode;
+import com.yahoo.searchlib.rankingexpression.rule.ReferenceNode;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public final class Benchmark {
+
+ public static void main(String[] args) {
+ if (args.length < 1) {
+ System.err.println("Usage: Benchmark <filename> [<iterations>]");
+ System.exit(1);
+ }
+ int numRuns = 1000;
+ if (args.length == 2) {
+ numRuns = Integer.valueOf(args[1]);
+ }
+ List<Result> res = new ArrayList<Result>();
+ try {
+ BufferedReader in = new BufferedReader(new FileReader(args[0]));
+ StringBuilder str = new StringBuilder();
+ String line;
+ while ((line = in.readLine()) != null) {
+ str.append(line);
+ }
+ String exp = str.toString();
+ res.add(evaluateTree(exp, numRuns));
+ res.add(evaluateTreeOptimized(exp, numRuns));
+ res.add(evaluateForestOptimized(exp, numRuns));
+ } catch (IOException e) {
+ System.out.println("An error occured while reading the content of file '" + args[0] + "': " + e);
+ System.exit(1);
+ } catch (ParseException e) {
+ System.out.println("An error occured while parsing the content of file '" + args[0] + "': " + e);
+ System.exit(1);
+ }
+ for (Result lhs : res) {
+ for (Result rhs : res) {
+ if (lhs.res < rhs.res - 1e-6 || lhs.res > rhs.res + 1e-6) {
+ System.err.println("Evaluation of '" + lhs.name + "' and '" + rhs.name + "' disagree on result; " +
+ "expected " + lhs.res + ", got " + rhs.res + ".");
+ System.exit(1);
+ }
+ }
+ System.out.format("%1$-16s : %2$8.04f ms (%3$-6.04f)\n",
+ lhs.name, lhs.millis, res.get(0).millis / lhs.millis);
+ }
+ }
+
+ private static Result evaluateTree(String str, int numRuns) throws ParseException {
+ Result ret = new Result();
+ ret.name = "Unoptimized";
+
+ RankingExpression exp = new RankingExpression(str);
+ List<String> vars = new LinkedList<String>();
+ getFeatures(exp.getRoot(), vars);
+
+ benchmark(exp, vars, new MapContext(), numRuns, ret);
+ return ret;
+ }
+
+ private static Result evaluateTreeOptimized(String str, int numRuns) throws ParseException {
+ Result ret = new Result();
+ ret.name = "Optimized tree";
+
+ RankingExpression exp = new RankingExpression(str);
+ List<String> vars = new LinkedList<String>();
+ getFeatures(exp.getRoot(), vars);
+
+ ArrayContext ctx = new ArrayContext(exp);
+ ExpressionOptimizer optimizer = new ExpressionOptimizer();
+ optimizer.getOptimizer(GBDTForestOptimizer.class).setEnabled(false);
+ optimizer.optimize(exp, ctx);
+
+ benchmark(exp, vars, ctx, numRuns, ret);
+ return ret;
+ }
+
+ private static Result evaluateForestOptimized(String str, int numRuns) throws ParseException {
+ Result ret = new Result();
+ ret.name = "Optimized forest";
+
+ RankingExpression exp = new RankingExpression(str);
+ List<String> vars = new LinkedList<String>();
+ getFeatures(exp.getRoot(), vars);
+
+ ArrayContext ctx = new ArrayContext(exp);
+ ExpressionOptimizer optimizer = new ExpressionOptimizer();
+ optimizer.optimize(exp, ctx);
+
+ benchmark(exp, vars, ctx, numRuns, ret);
+ return ret;
+ }
+
+ private static void benchmark(RankingExpression exp, List<String> vars, Context ctx, int numRuns, Result out) {
+ for (int i = 0, len = vars.size(); i < len; ++i) {
+ ctx.put(vars.get(i), i / (double)len);
+ }
+ for (int i = 0; i < numRuns; ++i) {
+ out.res = exp.evaluate(ctx).asDouble();
+ }
+ long begin = System.nanoTime();
+ for (int i = 0; i < numRuns; ++i) {
+ out.res = exp.evaluate(ctx).asDouble();
+ }
+ long end = System.nanoTime();
+
+ out.millis = (end - begin) / (1000.0 * 1000.0);
+ }
+
+ private static void getFeatures(ExpressionNode node, List<String> out) {
+ if (node instanceof ReferenceNode) {
+ String feature = ((ReferenceNode)node).getName();
+ if (!out.contains(feature)) {
+ out.add(feature);
+ }
+ } else if (node instanceof CompositeNode) {
+ CompositeNode cNode = (CompositeNode)node;
+ for (ExpressionNode child : cNode.children()) {
+ getFeatures(child, out);
+ }
+ }
+ }
+
+ private static class Result {
+ String name = "anonymous";
+ double millis = Double.MAX_VALUE;
+ double res = 0;
+ }
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/EvaluationBenchmark.java b/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/EvaluationBenchmark.java
new file mode 100644
index 00000000000..708235647e6
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/EvaluationBenchmark.java
@@ -0,0 +1,474 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.rankingexpression.evaluation;
+
+import com.yahoo.io.IOUtils;
+import com.yahoo.searchlib.rankingexpression.RankingExpression;
+import com.yahoo.searchlib.rankingexpression.evaluation.gbdtoptimization.GBDTForestOptimizer;
+import com.yahoo.searchlib.rankingexpression.parser.ParseException;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+
+/**
+ * Two small benchmarks of ranking expression evaluation
+ *
+ * @author bratseth
+ */
+public class EvaluationBenchmark {
+
+ public void run() {
+ try {
+ //runNativeComparison(100*1000*1000);
+
+ // benchmark with a large gbdt: Expected tree and forest speedup: 2x, 4x
+ runGBDT(1000*1000, gbdt);
+
+ // benchmark with a large gbdt using set membership tests (on integers) extensively
+ // we simplify the attribute name to make it work with the map context implementation.
+ // Expected tree and forest speedup: 3x, 4x
+ // runGBDT(100*1000, readFile("src/test/files/ranking07.expression").replace("attribute(catid)","catid"));
+ }
+ catch (ParseException e) {
+ throw new RuntimeException("Benchmarking failed",e);
+ }
+ }
+
+ private String readFile(String file) {
+ try {
+ return IOUtils.readFile(new File(file));
+ } catch (IOException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ public void runNativeComparison(int iterations) {
+ oul("Running native expression...");
+ MapContext arguments=new MapContext();
+ arguments.put("one",1d);
+
+ out(" warming up...");
+ double nativeTotal=0;
+ for (int i=0; i<iterations/5; i++) {
+ arguments.put("i",(double)i);
+ nativeTotal+=nativeExpression(arguments);
+ }
+ oul("done");
+
+ out(" running " + iterations + " iterations...");
+ long startTime=System.currentTimeMillis();
+ for (int i=0; i<iterations; i++) {
+ arguments.put("i",(double)i);
+ nativeTotal+=nativeExpression(arguments);
+ }
+ long nativeTotalTime=System.currentTimeMillis()-startTime;
+ oul("done");
+ oul(" Total time running native: " + nativeTotalTime + " ms (" + iterations/nativeTotalTime + " expressions/ms)");
+
+ oul("Running ranking expression...");
+ RankingExpression expression;
+ try {
+ expression=new RankingExpression(comparisonExpression);
+ }
+ catch (ParseException e) {
+ throw new RuntimeException(e);
+ }
+ out(" warming up...");
+ double rankingTotal=0;
+ for (int i=0; i<iterations/5; i++) {
+ arguments.put("i",(double)i);
+ rankingTotal+=expression.evaluate(arguments).asDouble();
+ }
+ oul("done");
+
+ out(" running " + iterations + " iterations...");
+ startTime=System.currentTimeMillis();
+ for (int i=0; i<iterations; i++) {
+ arguments.put("i",(double)i);
+ rankingTotal+=expression.evaluate(arguments).asDouble();
+ }
+ long rankingTotalTime=System.currentTimeMillis()-startTime;
+ if (rankingTotal!=nativeTotal)
+ throw new IllegalStateException("Expressions are not the same, native: " + nativeTotal + " rankingExpression: " + rankingTotal);
+ oul("done");
+ oul(" Total time running expression: " + rankingTotalTime + " ms (" + iterations/rankingTotalTime + " expressions/ms)");
+ oul("Expression % of max possible speed: " + ((int)((100*nativeTotalTime)/rankingTotalTime)) + " %");
+ }
+
+ private static final String comparisonExpression="10*if(i>35,if(i>one,if(i>=670,4,8),if(i>8000,5,3)),if(i==478,90,91))";
+
+ private final double nativeExpression(Context context) {
+ double r;
+ if (context.get("i").asDouble()>35) {
+ if (context.get("i").asDouble()>context.get("one").asDouble()) {
+ if (context.get("i").asDouble()>=670)
+ r=4;
+ else
+ r=8;
+ }
+ else {
+ if (context.get("i").asDouble()>8000)
+ r=5;
+ else
+ r=3;
+ }
+ }
+ else {
+ if (context.get("i").asDouble()==478)
+ r=90;
+ else
+ r=91;
+ }
+ return r*10;
+ }
+
+ private void runGBDT(int iterations, String gbdtString) throws ParseException {
+
+ // Unoptimized...............
+ double total = benchmark(new RankingExpression(gbdtString), new MapContext(), iterations, "Unoptimized");
+ System.out.println("-----------------------------------------------------------------------------------------------------");
+
+ // Tree optimized...................
+ RankingExpression treeOptimized = new RankingExpression(gbdtString);
+ ArrayContext treeContext = new ArrayContext(treeOptimized, true);
+ ExpressionOptimizer optimizer = new ExpressionOptimizer();
+ optimizer.getOptimizer(GBDTForestOptimizer.class).setEnabled(false);
+ System.out.print("Tree optimizing ... ");
+ OptimizationReport treeOptimizationReport = optimizer.optimize(treeOptimized, treeContext);
+ System.out.println("done");
+ System.out.println(treeOptimizationReport);
+ double treeTotal = benchmark(treeOptimized, treeContext, iterations, "Tree optimized");
+ assertEqualish(total, treeTotal);
+ System.out.println("-----------------------------------------------------------------------------------------------------");
+
+ // Forest optimized...................
+ RankingExpression forestOptimized=new RankingExpression(gbdtString);
+ DoubleOnlyArrayContext forestContext = new DoubleOnlyArrayContext(forestOptimized, true);
+ System.out.print("Forest optimizing ... ");
+ OptimizationReport forestOptimizationReport=new ExpressionOptimizer().optimize(forestOptimized, forestContext);
+ System.out.println("done");
+ System.out.println(forestOptimizationReport);
+ double forestTotal=benchmark(forestOptimized,forestContext,iterations,"Forest optimized");
+ assertEqualish(total,forestTotal);
+ System.out.println("-----------------------------------------------------------------------------------------------------");
+ }
+
+ private double benchmark(RankingExpression gbdt, Context context, int iterations, String description) {
+ oul("Running '" + description + "':");
+ out(" Warming up ...");
+ double total=0;
+ total+=benchmarkIterations(gbdt,context,iterations/5);
+ oul("done");
+
+ out(" Running " + iterations + " of '" + description + "' ...");
+ long tStartTime=System.currentTimeMillis();
+ total+=benchmarkIterations(gbdt,context,iterations);
+ long totalTime=System.currentTimeMillis()-tStartTime;
+ oul("done");
+ oul(" Total time running '" + description + "': " + totalTime + " ms (" + totalTime*1000/iterations + " microseconds/expression)");
+ return total;
+ }
+
+ private double benchmarkIterations(RankingExpression gbdt, Context contextPrototype, int iterations) {
+ // This tries to simulate realistic use: The array context can be reused for a series of evaluations in a thread
+ // but each evaluation binds a new set of values.
+ double total=0;
+ Context context = copyForEvaluation(contextPrototype);
+ for (int i=0; i<iterations; i++) {
+ context.put("LW_NEWS_SEARCHES_RATIO",(double)i);
+ context.put("NEWS_USERS",(double)i/1000*1000);
+ context.put("catid",100300102);
+ total+=gbdt.evaluate(context).asDouble();
+ }
+ return total;
+ }
+
+ private Context copyForEvaluation(Context contextPrototype) {
+ if (contextPrototype instanceof AbstractArrayContext) // optimized - contains name to index map
+ return ((AbstractArrayContext)contextPrototype).clone();
+ else if (contextPrototype instanceof MapContext) // Unoptimized - nothing to keep
+ return new MapContext();
+ else
+ throw new RuntimeException("Unknown context type " + contextPrototype.getClass());
+ }
+
+ private void out(String s) {
+ System.out.print(s);
+ }
+
+ private void oul(String s) {
+ System.out.println(s);
+ }
+
+ public static void main(String[] args) {
+ new EvaluationBenchmark().run();
+ }
+
+ private void assertEqualish(double a,double b) {
+ if (Math.abs(a-b) >= Math.abs((a+b)/100000000) )
+ throw new RuntimeException("Expected value " + a + " but optimized evaluation produced " + b);
+ }
+
+ private final String gbdt =
+ "if (LW_NEWS_SEARCHES_RATIO < 1.72971, 0.0697159, if (LW_USERS < 0.10496, if (SEARCHES < 0.0329127, 0.151257, 0.117501), if (SUGG_OVERLAP < 18.5, 0.0897622, 0.0756903))) + \n" +
+ "if (LW_NEWS_SEARCHES_RATIO < 1.73156, if (NEWS_USERS < 0.0737993, -0.00481646, 0.00110018), if (LW_USERS < 0.0844616, 0.0488919, if (SUGG_OVERLAP < 32.5, 0.0136917, 9.85328E-4))) + \n" +
+ "if (LW_NEWS_SEARCHES_RATIO < 1.74451, -0.00298257, if (LW_USERS < 0.116207, if (SEARCHES < 0.0329127, 0.0676105, 0.0340198), if (NUM_WORDS < 1.5, -8.55514E-5, 0.0112406))) + \n" +
+ "if (LW_NEWS_SEARCHES_RATIO < 1.72995, if (NEWS_USERS < 0.0737993, -0.00407515, 0.00139088), if (LW_USERS < 0.0509035, 0.0439466, if (LW_USERS < 0.325818, 0.0187156, 0.00236949))) + \n" +
+ "if (LW_NEWS_SEARCHES_RATIO < 1.72503, -0.00239817, if (LW_USERS < 0.0977572, if (ISABSTRACT_AVG < 0.04, 0.041602, 0.0157381), if (LW_USERS < 0.602112, 0.0118004, 7.92829E-4))) + \n" +
+ "if (LW_NEWS_SEARCHES_RATIO < 1.53348, -0.00227065, if (LW_USERS < 0.0613667, 0.0345214, if (NUM_WORDS < 1.5, -9.25274E-4, if (BIDDED_SEARCHES < 0.538873, 0.0207086, 0.00549622)))) + \n" +
+ "if (LW_NEWS_SEARCHES_RATIO < 1.50465, -0.00206609, if (LW_USERS < 0.183424, if (NUM_WORDS < 1.5, 0.00203703, if (BIDDED_SEARCHES < 0.0686975, 0.0412142, 0.0219894)), 0.00246537)) + \n" +
+ "if (NEWS_USERS < 0.0737993, -0.00298889, if (LW_USERS < 0.212577, if (NUM_WORDS < 1.5, 0.00385669, 0.0260773), if (NUM_WORDS < 1.5, -0.00141889, 0.00565858))) + \n" +
+ "if (NEWS_USERS < 0.0737993, -0.0026984, if (BIDDED_SEARCHES < 0.202548, if (NUM_WORDS < 1.5, 0.00356601, 0.026572), if (SUGG_OVERLAP < 34.5, 0.00642933, -8.83847E-4))) + \n" +
+ "if (NEWS_MAIN_SEARCHES_RATIO < 8.47575, if (NUM_WORDS < 2.5, if (NEWS_USERS < 0.0737993, -0.0031992, if (ISTITLE_AVG < 0.315, 0.0106735, 1.98748E-4)), 0.00717291), 0.0216488) + \n" +
+ "if (PREV_DAY_NEWS_SEARCHES_RATIO < 1.79697, if (NEWS_CTR < 0.659695, -0.0018297, 0.0062345), if (BIDDED_SEARCHES < 0.148816, if (NUM_WORDS < 1.5, 0.00397494, 0.0282706), 0.00287526)) + \n" +
+ "if (PREV_DAY_NEWS_SEARCHES_RATIO < 1.81978, if (NUM_WORDS < 2.5, -0.00183825, 0.00447334), if (SUGG_OVERLAP < 8.5, if (SEARCHES < 0.0692601, 0.0319928, 0.0121653), 0.0010403)) + \n" +
+ "if (NEWS_CTR < 0.660025, if (PREV_DAY_NEWS_CTR_RATIO < 0.502543, if (SEARCHES < 0.245402, 0.0193446, 9.09694E-4), -0.00160176), if (NEWS_MAIN_SEARCHES_RATIO < 1.64873, 0.00264489, 0.0177375)) + \n" +
+ "if (NUM_WORDS < 2.5, if (NEWS_USERS < 0.0737993, -0.00238821, if (LW_USERS < 0.0143922, 0.0188957, 8.0445E-4)), if (LW_NEWS_SEARCHES_RATIO < 1.32846, 0.00349568, 0.015966)) + \n" +
+ "if (NUM_WORDS < 2.5, if (NEWS_USERS < 0.0737993, -0.002169, if (ISTITLE_AVG < 0.625, 0.00906748, -2.5122E-4)), if (PREV_DAY_NEWS_SEARCHES_RATIO < 1.69164, 0.0039487, 0.0174816)) + \n" +
+ "if (NEWS_MAIN_SEARCHES_RATIO < 8.66642, if (NUM_WORDS < 2.5, -8.59968E-4, if (NEWS_CTR < 0.632914, 0.00287223, 0.0148924)), if (SEARCHES < 0.0237478, 0.033539, 0.0071663)) + \n" +
+ "if (NEWS_MAIN_SEARCHES_RATIO < 1.26315, -0.00130179, if (NEWS_CTR < 0.628621, if (PREV_DAY_NEWS_CTR_RATIO < 0.525166, if (SUGG_OVERLAP < 9.5, 0.0171556, 2.36297E-4), 2.29746E-4), 0.0123793)) + \n" +
+ "if (LW_MAIN_SEARCHES_RATIO < 1.88252, if (NEWS_USERS < 0.0737993, -0.00207461, 6.60118E-4), if (NEWS_USERS < 0.0737993, 9.39125E-4, if (SEARCHES < 0.0248661, 0.0272446, 0.00973038))) + \n" +
+ "if (NUM_WORDS < 1.5, -0.0018842, if (NEWS_USERS < 0.0737993, -5.44658E-4, if (PREV_DAY_USERS < 0.43141, if (PREV_DAY_NEWS_CTR < 0.447268, 4.25375E-4, 0.0152695), 0.00230817))) + \n" +
+ "if (NEWS_MAIN_SEARCHES_RATIO < 2.6946, -7.37738E-4, if (NEWS_CTR < 0.618656, if (PREV_DAY_NEWS_CTR_RATIO < 0.522617, if (ISTITLE_AVG < 0.21, 0.0202984, 0.00221158), 8.26792E-4), 0.0131518)) + \n" +
+ "if (NUM_WORDS < 3.5, if (NEWS_CTR < 0.660239, if (PREV_DAY_NEWS_CTR_RATIO < 0.505308, 0.00214801, -0.00113168), if (NEWS_MAIN_SEARCHES_RATIO < 0.9266, 1.28813E-4, 0.0090932)), 0.0111807) + \n" +
+ "if (NEWS_MAIN_SEARCHES_RATIO < 1.27238, -9.46325E-4, if (NEWS_USERS < 0.0737993, 2.20417E-4, if (ISTITLE_AVG < 0.435, 0.0143694, if (MIN_SCORE < 243538.0, 1.76879E-4, 0.00682761)))) + \n" +
+ "if (NUM_WORDS < 3.5, if (NUM_WORDS < 1.5, -0.00153422, if (NEWS_USERS < 0.0737993, -6.54983E-4, if (PREV_DAY_NEWS_CTR < 0.55636, -4.40154E-4, 0.00666305))), 0.00961529) + \n" +
+ "if (LW_MAIN_SEARCHES_RATIO < 1.88316, -6.18023E-4, if (NEWS_USERS < 0.0737993, if (NUM_WORDS < 2.5, -4.22107E-4, 0.00583448), if (SEARCHES < 0.0202227, 0.0218746, 0.0061446))) + \n" +
+ "if (LW_MAIN_SEARCHES_RATIO < 1.91611, if (NEWS_MAIN_SEARCHES_RATIO < 0.384315, -0.0015553, 2.57266E-4), if (NEWS_CTR < 0.659281, if (NUM_WORDS < 2.5, 2.40504E-4, 0.00572176), 0.0105389)) + \n" +
+ "if (NEWS_MAIN_SEARCHES_RATIO < 2.68704, -5.65225E-4, if (NEWS_CTR < 0.782417, if (PREV_DAY_NEWS_CTR_RATIO < 0.990517, if (NEWS_SEARCHES < 0.339382, 0.0135414, 0.00113811), 5.21526E-4), 0.0112535)) + \n" +
+ "if (BIDDED_SEARCHES < 0.00581527, 0.00560086, if (NUM_WORDS < 1.5, -0.00130462, if (NEWS_USERS < 0.0737993, -7.52446E-4, if (BIDDED_SEARCHES < 1.29452, 0.00626868, 1.75195E-4)))) + \n" +
+ "if (NUM_WORDS < 3.5, if (NUM_WORDS < 1.5, -0.00114958, if (NEWS_USERS < 0.0737993, -5.00434E-4, if (PREV_DAY_NEWS_CTR < 0.563721, -6.96671E-4, 0.00517722))), 0.00807433) + \n" +
+ "if (NEWS_MAIN_SEARCHES_RATIO < 0.382901, -0.00122923, if (NEWS_USERS < 0.0737993, -4.15058E-4, if (ISABSTRACT_AVG < 0.095, if (PREV_DAY_NEWS_CTR < 0.557042, 8.71338E-4, 0.00994663), 1.56446E-4))) + \n" +
+ "if (BIDDED_SEARCHES < 0.00581527, if (MAX_SCORE < 379805.0, 0.00362486, 0.0132902), if (NEWS_CTR < 0.913345, -3.53901E-4, if (NEWS_USERS < 2.48409, 0.00191813, 0.013908))) + \n" +
+ "if (HAS_NEWS_QC == 0.0, if (NUM_WORDS < 3.5, if (PREV_DAY_NEWS_SEARCHES_RATIO < 1.90333, -6.26897E-4, if (ISTITLE_AVG < 0.355, 0.00723851, -2.62543E-5)), 0.0058211), 0.00433763) + \n" +
+ "if (NUM_WORDS < 2.5, if (NEWS_USERS < 2.28805, -5.10768E-4, 0.00255996), if (LW_MAIN_SEARCHES_RATIO < 1.84597, 3.31329E-4, if (DAY_WEEK_AVG_RATIO < 2.655, 0.00434755, 0.0196317))) + \n" +
+ "if (HAS_NEWS_QC == 0.0, if (BIDDED_SEARCHES < 0.0119577, if (PREV_DAY_NEWS_CTR_RATIO < 0.928266, 0.0111871, 0.00198432), -3.24627E-4), if (NEWS_MAIN_SEARCHES_RATIO < 2.71304, 0.00196875, 0.00945297)) + \n" +
+ "if (LW_MAIN_SEARCHES_RATIO < 1.82872, -4.20354E-4, if (DAY_PD_HITS_RATIO < 3.61, if (NEWS_MAIN_SEARCHES_RATIO < 12.766, 7.51735E-4, if (LW_NEWS_SEARCHES_RATIO < 6.15807, 0.0147332, -0.0135118)), 0.010677)) + \n" +
+ "if (NEWS_MAIN_SEARCHES_RATIO < 0.327632, -0.00102446, if (NEWS_USERS < 0.0737993, -3.80041E-4, if (ISABSTRACT_AVG < 0.105, if (NEWS_SEARCHES < 0.286926, 0.00928139, 0.00265099), 8.96147E-5))) + \n" +
+ "if (ALGO_CTR < 1.05585, if (HAS_NEWS_QC == 0.0, -4.34462E-4, 0.00319487), if (PREV_DAY_NEWS_CTR_RATIO < 0.541632, if (DAY_PD_HITS_RATIO < 5.75, 0.00845667, 0.0571546), 0.00162096)) + \n" +
+ "if (NUM_WORDS < 3.5, if (LW_NEWS_CTR < 0.59494, -3.29593E-4, if (NEWS_MAIN_SEARCHES_RATIO < 1.24936, 3.83584E-4, if (MAX_SCORE < 263568.0, 0.00219784, 0.0104741))), 0.00532617) + \n" +
+ "if (NUM_WORDS < 3.5, if (MAX_SCORE < 268176.0, -5.00757E-4, if (NEWS_MAIN_SEARCHES_RATIO < 0.812821, -3.72572E-4, if (NEWS_CTR < 0.898792, 0.0017999, 0.00908918))), 0.00538528) + \n" +
+ "if (ISTITLE_AVG < 0.705, if (NEWS_USERS < 0.0737993, 2.51012E-5, if (BIDDED_SEARCHES < 1.61095, if (YSM_N_ALGO_CTR_RATIO < 6.42257E-4, 0.0804317, 0.00586482), -4.26664E-4)), -4.79119E-4) + \n" +
+ "if (NUM_WORDS < 3.5, if (HAS_NEWS_QC == 0.0, -1.93562E-4, if (LW_MAIN_SEARCHES_RATIO < 1.72448, 0.00109732, 0.00738421)), if (NEWS_MAIN_SEARCHES_RATIO < 0.406201, -0.00263026, 0.00733129)) + \n" +
+ "if (BIDDED_SEARCHES < 0.0120163, 0.00278665, if (NEWS_USERS < 2.75198, -3.22197E-4, if (NEWS_MAIN_CTR_RATIO < 1.4679, 0.00148229, if (PREV_DAY_USERS < 0.117185, 0.0517723, 0.010204)))) + \n" +
+ "if (LW_NEWS_CTR < 0.597955, if (SUGG_OVERLAP < 0.5, if (PREV_DAY_NEWS_SEARCHES_RATIO < 1.79767, 6.24799E-4, 0.0051004), -5.51886E-4), if (NEWS_MAIN_SEARCHES_RATIO < 0.660064, 2.21724E-4, 0.00474931)) + \n" +
+ "if (BIDDED_SEARCHES < 0.00581527, 0.0030367, if (NEWS_USERS < 2.65484, -3.02764E-4, if (LW_MAIN_SEARCHES_RATIO < 1.39539, 6.36888E-4, if (NEWS_MAIN_CTR_RATIO < 2.18629, 0.00661051, 0.0228632)))) + \n" +
+ "if (LW_NEWS_CTR < 0.619817, if (LW_USERS < 0.0143922, 0.0012313, -4.11044E-4), if (NEWS_MAIN_SEARCHES_RATIO < 1.63866, 6.94464E-4, if (LW_MAIN_SEARCHES_RATIO < 2.79335, 0.00448877, 0.0171177))) + \n" +
+ "if (HAS_NEWS_QC == 0.0, if (ALGO_CTR < 1.1644, -2.80479E-4, 0.002092), if (NUM_WORDS < 2.5, 9.21741E-4, if (LW_MAIN_CTR_RATIO < 0.771928, 0.018042, 0.00519068))) + \n" +
+ "if (MAX_SCORE < 270938.0, -3.72001E-4, if (NEWS_MAIN_SEARCHES_RATIO < 0.382818, -8.43057E-4, if (NEWS_USERS < 0.0737993, 2.74749E-4, if (ISABSTRACT_AVG < 0.355, 0.00699732, 9.68093E-4)))) + \n" +
+ "if (NEWS_CTR < 0.187967, -0.00236148, if (LW_NEWS_CTR_RATIO < 0.501045, if (ISABSTRACT_AVG < 0.065, if (USERS < 0.79806, 0.00751647, 5.67897E-4), -1.95953E-4), -1.28664E-4)) + \n" +
+ "if (NEWS_CTR < 0.916156, if (NEWS_CTR < 0.131787, -0.00260812, -2.96076E-6), if (LW_MAIN_SEARCHES_RATIO < 1.7079, if (LW_NEWS_CTR < 0.827357, -0.00103106, 0.00752405), 0.00712343)) + \n" +
+ "if (ALGO_CTR < 1.11796, -9.56953E-5, if (LW_NEWS_CTR_RATIO < 0.965768, if (PREV_DAY_NEWS_CTR_RATIO < 0.318964, -0.0068748, if (DAY_PD_HITS_RATIO < 5.9, 0.00781228, 0.0430918)), 0.0010225)) + \n" +
+ "if (ISTITLE_AVG < 0.785, if (PREV_DAY_NEWS_CTR_RATIO < 0.937235, if (BIDDED_SEARCHES < 0.549316, 0.00782989, 5.1726E-4), if (LW_MAIN_SEARCHES_RATIO < 14.3819, -7.98452E-5, 0.00931358)), -3.44667E-4) + \n" +
+ "if (NUM_WORDS < 4.5, if (HAS_NEWS_QC == 0.0, -1.1162E-4, if (LW_NEWS_CTR < 0.625492, 0.00137801, if (NEWS_MAIN_SEARCHES_RATIO < 3.2392, 0.00481811, 0.0203582))), 0.00957663) + \n" +
+ "if (NUM_WORDS < 4.5, if (NEWS_MAIN_SEARCHES_RATIO < 12.878, -7.973E-5, if (SUGG_LW < 0.5, 0.0113112, if (PREV_DAY_NEWS_USERS < 1.63248, -0.0093633, 0.0081117))), 0.00891687) + \n" +
+ "if (NEWS_CTR < 0.260948, -0.00146919, if (PREV_DAY_NEWS_CTR_RATIO < 0.949304, if (NEWS_MAIN_SEARCHES_RATIO < 0.305788, -5.28063E-4, if (MIN_SCORE < 199600.0, 8.23835E-4, 0.00533948)), -1.59293E-4)) + \n" +
+ "if (NEWS_MAIN_SEARCHES_RATIO < 0.116451, -0.00113111, if (PREV_DAY_NEWS_CTR_RATIO < 0.999206, if (NEWS_SEARCHES < 0.30129, if (ISTITLE_AVG < 0.61, 0.00769846, 0.00162987), -2.39796E-4), -1.20795E-4)) + \n" +
+ "if (NEWS_USERS < 2.75198, -1.04934E-4, if (NEWS_CTR < 0.504788, -3.87773E-4, if (BIDDED_SEARCHES < 3.77166, if (LW_MAIN_SEARCHES_RATIO < 1.76307, 0.00639344, 0.0180493), 0.00240808))) + \n" +
+ "if (NUM_WORDS < 4.5, if (LW_NEWS_CTR < 0.789202, -2.11327E-4, if (NEWS_USERS < 0.312345, -4.52231E-4, if (SCIENCE < 0.535, 0.00367411, 0.0491292))), 0.00847389) + \n" +
+ "if (NEWS_CTR < 0.182514, -0.00177053, if (LW_NEWS_CTR_RATIO < 0.501045, if (USERS < 1.36009, if (MIN_SCORE < 187234.0, 3.6643E-4, 0.0055156), -0.0011557), -8.54842E-5)) + \n" +
+ "if (LW_MAIN_SEARCHES_RATIO < 0.32584, if (NEWS_CTR < 1.19657, 0.00362961, if (PREV_DAY_NEWS_CTR_RATIO < 2.37995, if (NEWS_MAIN_SEARCHES_RATIO < 2.07684, 0.0176304, 0.0773353), 0.00489339)), -2.00322E-5) + \n" +
+ "if (AVG_SCORE < 354962.0, -1.53495E-4, if (NEWS_CTR < 0.596437, if (LW_SEARCHES < 0.0532569, 0.00410978, -0.00116517), if (LW_MAIN_CTR_RATIO < 0.779754, 0.0149197, 0.00348209))) + \n" +
+ "if (PREV_DAY_NEWS_USERS < 14.0861, if (BIDDED_SEARCHES < 3.24749, if (PREV_DAY_NEWS_SEARCHES_RATIO < 1.63285, -8.28682E-5, if (NEWS_SEARCHES < 0.317829, 0.00348768, -6.08623E-4)), -0.00114994), 0.00458862) + \n" +
+ "if (ISABSTRACT_AVG < 0.295, if (NEWS_USERS < 0.0737993, -1.36945E-4, if (MIN_SCORE < 233429.0, 2.59393E-5, if (NEWS_MAIN_SEARCHES_RATIO < 0.221135, -7.57098E-4, 0.00463699))), -4.62083E-4) + \n" +
+ "if (ALGO_CTR < 1.01522, -1.09825E-4, if (LW_NEWS_CTR_RATIO < 0.55285, if (LW_MAIN_SEARCHES_RATIO < 5.11061, if (NEWS_SEARCHES < 1.02345, 0.00847552, -0.00437523), -0.0112885), 6.61898E-4)) + \n" +
+ "if (NEWS_USERS < 4.05804, if (LW_NEWS_SEARCHES_RATIO < 6.67644, -1.03466E-5, if (USERS < 0.101853, -0.0245653, -0.00297792)), if (NEWS_MAIN_CTR_RATIO < 1.09325, 6.6298E-4, 0.00723109)) + \n" +
+ "if (NUM_WORDS < 4.5, if (LW_NEWS_USERS < 31.8516, -4.91517E-5, 0.00701562), if (ALGO_CLICKS < 0.012133, 0.020461, if (DAY_WEEK_AVG_RATIO < 2.93, 8.3867E-4, 0.0326788))) + \n" +
+ "if (PREV_DAY_NEWS_SEARCHES_RATIO < 3.9286, if (NEWS_MAIN_SEARCHES_RATIO < 60.9048, 6.59836E-5, 0.0391173), if (NEWS_USERS < 0.223578, -0.0109831, if (NEWS_MAIN_SEARCHES_RATIO < 36.1125, -9.18296E-4, -0.0321067))) + \n" +
+ "if (PREV_DAY_NEWS_SEARCHES_RATIO < 3.92945, if (NEWS_MAIN_SEARCHES_RATIO < 12.878, 3.89745E-5, if (PREV_DAY_NEWS_CTR < 0.537022, -0.00162034, 0.0079279)), if (NEWS_USERS < 0.245347, -0.0101132, -0.00126814)) + \n" +
+ "if (LW_MAIN_SEARCHES_RATIO < 0.480833, if (NEWS_USERS < 0.0737993, 9.57273E-5, if (SUGG_LW < 12.5, if (PUB_TODAY_AVG < 0.355, 0.0161319, -0.00334364), 0.00260343)), -7.52983E-5) + \n" +
+ "if (PREV_DAY_NEWS_USERS < 38.5221, if (BIDDED_SEARCHES < 3.7973, if (PREV_DAY_NEWS_CTR_RATIO < 0.999247, if (ISABSTRACT_AVG < 0.075, 0.00272842, -3.86777E-5), -1.51219E-4), -0.00100249), 0.00670928) + \n" +
+ "if (LW_NEWS_SEARCHES_RATIO < 2.77887, 9.37848E-5, if (NEWS_USERS < 0.245347, if (SEARCHES < 0.013024, if (ENTERTAINMENT_QC == 0.0, 0.0110759, 0.0905384), -0.00681271), -6.6913E-4)) + \n" +
+ "if (NEWS_CTR < 0.916322, if (LW_NEWS_SEARCHES_RATIO < 5.23703, 2.81507E-5, if (SEARCHES < 0.233024, -0.0177547, -0.00220902)), if (NEWS_USERS < 2.30165, 0.00110318, 0.00810944)) + \n" +
+ "if (HAS_NEWS_QC == 0.0, -1.08882E-4, if (MAX_SCORE < 137730.0, if (ALGO_CTR < 0.489733, 0.0199541, 0.0026349), if (NEWS_USERS < 2.20454, -3.16208E-4, 0.00699663))) + \n" +
+ "if (BIDDED_SEARCHES < 0.00581527, if (LW_NEWS_USERS < 1.81124, 0.00173624, if (PREV_DAY_USERS < 1.36892, 0.0405308, -0.00100716)), if (NEWS_MAIN_SEARCHES_RATIO < 58.9771, -1.26569E-4, 0.0286363)) + \n" +
+ "if (LW_NEWS_CTR < 0.621598, -1.10247E-4, if (LW_MAIN_SEARCHES_RATIO < 0.317173, 0.0110308, if (ALGO_CTR < 1.26031, 9.13964E-4, if (ALGO_CTR < 1.27034, 0.0667268, 0.00722662)))) + \n" +
+ "if (NEWS_MAIN_SEARCHES_RATIO < 25.7554, -6.12962E-6, if (LW_NEWS_SEARCHES < 0.765878, if (DAY_WEEK_AVG_RATIO < 1.475, if (PREV_DAY_NEWS_SEARCHES < 0.285188, 0.00389095, -0.0350617), -0.0440429), -7.44561E-4)) + \n" +
+ "if (DAY_PD_HITS_RATIO < 16.25, -5.78971E-5, if (INTLNEWS < 0.235, if (BIDDED_SEARCHES < 0.401931, if (PREV_DAY_MAIN_CTR_RATIO < 0.852642, 0.00517, 0.0517763), 0.00726245), 0.00172079)) + \n" +
+ "if (DAY_PD_HITS_RATIO < 18.89, -9.58573E-5, if (NEWS_MAIN_CTR_RATIO < 4.42646, if (LW_MAIN_SEARCHES_RATIO < 1.64955, -0.00540243, if (PREV_DAY_CTR < 0.823034, 0.0147119, -0.00456252)), 0.0476969)) + \n" +
+ "if (LW_CTR < 1.01377, -9.34648E-5, if (NEWS_USERS < 0.0737993, -6.338E-5, if (MIN_SCORE < 376483.0, 0.00251265, if (LW_MAIN_SEARCHES_RATIO < 0.683623, 0.0350855, 0.00794114)))) + \n" +
+ "if (ISABSTRACT_AVG < 0.315, if (NEWS_USERS < 0.0737993, -1.37636E-4, if (LW_MAIN_SEARCHES_RATIO < 0.661526, if (SUGG_LW < 3.5, 0.0168399, 0.00323338), 9.73973E-4)), -4.12741E-4) + \n" +
+ "if (LW_CTR < 1.01683, -1.32017E-4, if (LW_NEWS_CTR_RATIO < 0.500058, if (SCIENCE < 0.55, 0.0039965, 0.0428649), if (NEWS_CTR < 0.594088, 3.24961E-6, 0.00367602))) + \n" +
+ "if (LW_NEWS_CTR < 0.856244, -1.10246E-4, if (PREV_DAY_MAIN_SEARCHES_RATIO < 10.6833, if (LW_MAIN_SEARCHES_RATIO < 0.31726, if (LW_NEWS_CTR_RATIO < 1.23633, 0.00906872, 0.0473513), 0.00134361), 0.041372)) + \n" +
+ "if (NEWS_MAIN_SEARCHES_RATIO < 6.69974, -1.86907E-5, if (NEWS_MAIN_CTR_RATIO < 1.46029, if (LW_NEWS_SEARCHES_RATIO < 6.53657, if (PREV_DAY_NEWS_SEARCHES_RATIO < 0.316051, 0.0332713, 0.00117973), -0.010984), 0.00761193)) + \n" +
+ "if (NEWS_CTR < 0.237839, if (USERS < 0.0168938, 0.0267063, if (LW_USERS < 0.0827926, if (PREV_DAY_NEWS_CTR < 1.08233, -0.0138873, 0.0330313), -8.56477E-4)), 1.37177E-4) + \n" +
+ "if (LW_NEWS_SEARCHES_RATIO < 7.02911, 5.45191E-5, if (USERS < 0.118739, -0.0243638, if (NEWS_MAIN_CTR_RATIO < 1.63574, if (SEARCHES < 0.478602, -0.0123115, -0.00225071), 0.0054502))) + \n" +
+ "if (BIDDED_SEARCHES < 3.7973, if (NEWS_USERS < 2.20454, 8.53898E-5, if (NEWS_MAIN_CTR_RATIO < 1.9298, 0.00163898, if (SUGG_OVERLAP < 34.0, 0.0222897, 0.00356636))), -8.81981E-4) + \n" +
+ "if (BIDDED_SEARCHES < 0.00581527, if (MIN_SCORE < 253612.0, -5.12189E-4, if (MAX_MIN_SCORE < 35925.0, 0.00252377, if (PREV_DAY_NEWS_SEARCHES_RATIO < 0.610935, 0.0432434, 0.00906418))), -1.01198E-4) + \n" +
+ "if (DAY_PD_HITS_RATIO < 24.585, if (ALGO_CTR < 3.15833, -2.12884E-5, 0.0175937), if (PREV_DAY_CTR < 0.824546, if (LW_NEWS_CTR < 0.651434, 0.011673, 0.0567104), -0.00676867)) + \n" +
+ "if (LW_CTR < 1.551, if (LW_NEWS_USERS < 3.59178, -1.29153E-4, if (SUGG_LW < 46.5, 0.00702818, 2.27956E-4)), if (NEWS_MAIN_SEARCHES_RATIO < 8.86382, 0.0028952, 0.0366156)) + \n" +
+ "if (DAY_PD_HITS_RATIO < 18.89, -5.51307E-6, if (YSM_CTR < 0.0178362, if (ALGO_CLICKS < 0.127132, 0.0471277, if (SUGG_TW < 0.975545, 0.0048341, 0.0335537)), -0.00344397)) + \n" +
+ "if (LW_MAIN_SEARCHES_RATIO < 8.21211, -5.10935E-5, if (DAY_WEEK_AVG_RATIO < 1.205, -4.84709E-4, if (NEWS_MAIN_SEARCHES_RATIO < 2.63328, if (LW_NEWS_SEARCHES_RATIO < 1.83743, 0.0125448, -0.00162932), 0.0144536))) + \n" +
+ "if (ALGO_CTR < 1.01463, -1.17159E-4, if (PREV_DAY_NEWS_CTR_RATIO < 0.780396, if (USERS < 0.614133, if (MAX_MIN_SCORE < 54869.8, 0.00624085, 0.0337856), 7.62548E-4), 3.62126E-4)) + \n" +
+ "if (NUM_WORDS < 3.5, -1.00136E-5, if (PREV_DAY_NEWS_CTR_RATIO < 0.958905, if (PREV_DAY_USERS < 0.377834, if (YSM_N_ALGO_CTR_RATIO < 0.189731, 0.0259994, -0.0142924), 4.37294E-4), 9.62911E-4)) + \n" +
+ "if (LW_MAIN_SEARCHES_RATIO < 92.7164, if (LW_NEWS_CTR < 0.822371, -4.99393E-5, if (PREV_DAY_MAIN_SEARCHES_RATIO < 13.0501, if (NEWS_USERS < 0.309237, -8.38369E-4, 0.00312145), 0.043612)), -0.00674822) + \n" +
+ "if (LW_NEWS_SEARCHES_RATIO < 2.51597, 1.01649E-4, if (SEARCHES < 0.0202227, if (PREV_DAY_MAIN_CTR_RATIO < 1.20113, 0.00953861, 0.0583575), if (USERS < 0.295073, -0.00536031, -4.99861E-4))) + \n" +
+ "if (LW_MAIN_SEARCHES_RATIO < 0.146655, 0.00684325, if (LW_CTR < 1.43439, -5.31424E-5, if (NEWS_MAIN_SEARCHES_RATIO < 11.7367, if (PREV_DAY_NEWS_CTR_RATIO < 0.541013, 0.0101571, 0.0013804), 0.0362471))) + \n" +
+ "if (LW_NEWS_SEARCHES < 5.77429, -9.91104E-5, if (NEWS_CTR < 1.71804, if (SUGG_OVERLAP < 32.5, if (HAS_NEWS_QC == 0.0, 0.00333027, 0.0179206), 4.42358E-4), 0.0445137)) + \n" +
+ "if (ISABSTRACT_AVG < 0.435, if (NEWS_USERS < 0.158915, -2.22842E-5, if (PREV_DAY_NEWS_USERS < 0.0737993, 0.00311367, if (USERS < 0.119577, -0.00919024, 7.29693E-4))), -3.98811E-4) + \n" +
+ "if (ALGO_CLICKS < 4.04596, if (NEWS_USERS < 0.223578, if (NEWS_SEARCHES < 0.452288, 3.21367E-5, -0.00726485), if (LOCAL_QC == 1.0, -0.00144797, 0.00132603)), -9.1988E-4) + \n" +
+ "if (NEWS_CTR < 0.25921, -8.87978E-4, if (PREV_DAY_NEWS_CTR_RATIO < 0.530395, if (USERS < 0.710459, if (MAX_MIN_SCORE < 758.5, 0.00626933, 9.79114E-4), -3.43207E-4), -7.62231E-5)) + \n" +
+ "if (SUGG_TW < 0.0623373, if (LW_NEWS_SEARCHES_RATIO < 6.68433, if (PREV_DAY_NEWS_SEARCHES_RATIO < 1.89603, 1.96789E-4, if (LW_MAIN_SEARCHES_RATIO < 0.719144, 0.013244, 0.00182593)), -0.00570262), -2.16189E-4) + \n" +
+ "if (LW_NEWS_SEARCHES_RATIO < 2.07246, if (NEWS_MAIN_SEARCHES_RATIO < 53.2676, 8.99313E-5, 0.0338743), if (LW_SEARCHES < 0.216881, -0.00282376, if (PREV_DAY_SEARCHES < 0.0712414, 0.0484119, -3.84987E-4))) + \n" +
+ "if (LW_NEWS_SEARCHES_RATIO < 2.51974, 9.68801E-5, if (ALGO_CTR < 1.86978, if (LW_USERS < 0.0798854, if (NEWS_MAIN_CTR_RATIO < 0.42837, -0.0141747, -0.00244278), -4.47252E-4), 0.0201717)) + \n" +
+ "if (LW_MAIN_SEARCHES_RATIO < 10.0121, -1.42949E-5, if (PREV_DAY_MAIN_CTR_RATIO < 1.47714, 9.66134E-4, if (BIDDED_SEARCHES < 0.0585926, if (WEEKAVG < 0.36, 0.00997522, 0.0530748), 0.00387354))) + \n" +
+ "if (SUGG_TW < 0.984769, -3.34988E-5, if (PREV_DAY_NEWS_CTR < 1.13129, 0.0013372, if (BUSINESS < 0.05, 0.00681273, if (LOCAL_QC == 0.0, 0.0221056, 0.13305)))) + \n" +
+ "if (LW_CTR < 1.63323, -1.51312E-5, if (LW_NEWS_SEARCHES_RATIO < 1.28425, 0.00114219, if (ELECTRONICS_QC == 0.0, if (PREV_DAY_MAIN_CTR_RATIO < 0.530832, 0.0312363, 0.00679683), 0.0640472))) + \n" +
+ "if (PREV_DAY_NEWS_USERS < 4.25111, -4.70532E-5, if (PREV_DAY_MAIN_CTR_RATIO < 2.58573, if (YSM_NCTR < 0.00660392, if (NEWS_MAIN_SEARCHES_RATIO < 1.27373, -1.99449E-4, 0.00625635), -5.22971E-4), 0.0405083)) + \n" +
+ "if (PREV_DAY_MAIN_SEARCHES_RATIO < 377.799, if (LW_NEWS_SEARCHES_RATIO < 6.67644, 1.17654E-5, if (PUB_TODAY_AVG < 0.0050, -0.00565339, if (NATIONALNEWS < 0.55, 2.61588E-4, 0.0318784))), 0.0238311) + \n" +
+ "if (PREV_DAY_CTR < 1.16424, -7.76883E-5, if (LW_NEWS_SEARCHES_RATIO < 8.68994, 0.00182771, if (NEWS_SEARCHES < 7.1215, -0.013084, if (NEWS_MAIN_SEARCHES_RATIO < 3.58161, -0.00835768, 0.0377434)))) + \n" +
+ "if (NEWS_MAIN_SEARCHES_RATIO < 26.7481, 4.45294E-5, if (LW_NEWS_SEARCHES_RATIO < 1.57387, if (LW_NEWS_SEARCHES_RATIO < 1.3782, if (LW_CTR < 0.34851, 0.0177335, -0.00964832), 0.024959), -0.016879)) + \n" +
+ "if (LOCAL_QC == 1.0, if (NEWS_USERS < 0.0737993, 1.57459E-4, if (ISTITLE_AVG < 0.515, -0.00580773, if (PREV_DAY_MAIN_SEARCHES_RATIO < 4.81114, -0.00140636, 0.0204618))), 1.02083E-4) + \n" +
+ "if (HAS_NEWS_QC == 0.0, -3.53931E-5, if (ALGO_CTR < 0.5969, if (MIN_SCORE < 30200.0, if (NEWS_CTR < 0.713517, 0.0124535, 0.049838), 0.00304798), -2.6664E-4)) + \n" +
+ "if (NEWS_MAIN_SEARCHES_RATIO < 59.3594, if (NEWS_MAIN_SEARCHES_RATIO < 46.9165, if (PREV_DAY_NEWS_SEARCHES_RATIO < 48.6166, -8.91528E-6, 0.0156096), -0.0194015), if (INTLNEWS < 0.275, 0.0391563, 2.94525E-5)) + \n" +
+ "if (ALGO_CTR < 3.09161, if (NEWS_MAIN_SEARCHES_RATIO < 71.6642, -7.16141E-5, 0.0245016), if (NEWS_MAIN_SEARCHES_RATIO < 5.48496, if (ELECTRONICS_QC == 0.0, 3.80175E-4, 0.134021), 0.0467547)) + \n" +
+ "if (LOCAL_QC == 1.0, if (PREV_DAY_NEWS_CTR_RATIO < 0.55814, if (LW_USERS < 0.179284, -0.0110475, -0.00187986), if (LW_NEWS_SEARCHES_RATIO < 11.9839, -4.62166E-4, 0.0120886)), 4.16986E-5) + \n" +
+ "if (LW_NEWS_USERS < 48.703, if (LW_MAIN_SEARCHES_RATIO < 104.672, -1.11529E-5, if (PUB_TODAY_AVG < 0.645, -0.0109524, if (LW_MAIN_CTR_RATIO < 0.820426, 0.0173264, -0.00598908))), 0.00642443) + \n" +
+ "if (NEWS_USERS < 26.8033, if (USERS < 2.70898, if (NEWS_USERS < 0.212247, if (NEWS_SEARCHES < 0.312345, 1.94111E-5, -0.00494194), 9.66727E-4), -7.27397E-4), 0.00366377) + \n" +
+ "if (PREV_DAY_NEWS_CTR_RATIO < 0.948678, if (ISTITLE_AVG < 0.565, if (PREV_DAY_MAIN_CTR_RATIO < 1.53864, 0.00145357, if (YSM_N_ALGO_CTR_RATIO < 0.00279164, 0.053982, 0.0096231)), 1.01252E-4), -9.24301E-5) + \n" +
+ "if (PREV_DAY_NEWS_CTR_RATIO < 0.999206, 5.03044E-4, if (LW_MAIN_SEARCHES_RATIO < 11.8351, -2.19647E-4, if (DAY_WEEK_AVG_RATIO < 2.785, 0.00174311, if (ISABSTRACT_AVG < 0.73, 0.020265, -0.00658421)))) + \n" +
+ "if (SUGG_OVERLAP < 0.5, if (BIDDED_SEARCHES < 0.00581527, if (SUGG_LW < 8.5, 0.00316453, if (ELECTRONICS_QC == 0.0, 0.0240488, 0.285332)), 2.9583E-4), -1.0113E-4) + \n" +
+ "if (ALGO_CTR < 1.15516, -9.02219E-5, if (LW_NEWS_CTR_RATIO < 0.131516, 0.0416615, if (NEWS_CTR < 0.841155, 5.45051E-4, if (ALGO_CLICKS < 0.0703111, 0.0508979, 0.00584922)))) + \n" +
+ "if (ENTERTAINMENT < 0.305, if (ALGO_CTR < 1.53687, -1.42467E-4, if (PREV_DAY_NEWS_SEARCHES_RATIO < 2.43692, 0.00172748, if (LW_NEWS_CTR_RATIO < 1.09767, 0.0382724, 3.85821E-4))), 9.95127E-4) + \n" +
+ "if (PREV_DAY_NEWS_SEARCHES_RATIO < 3.61514, if (PREV_DAY_NEWS_SEARCHES_RATIO < 1.904, -6.72591E-5, if (USERS < 1.06349, 0.00243637, -8.96343E-4)), if (NEWS_USERS < 0.179867, -0.00813249, -0.0012514)) + \n" +
+ "if (PREV_DAY_NEWS_USERS < 13.0067, -3.50928E-5, if (PREV_DAY_NEWS_CTR < 0.714421, 7.97227E-4, if (USERS < 3.56693, if (YSM_NCTR < 0.036612, 0.0297616, -0.00692722), 0.00476212))) + \n" +
+ "if (LW_NEWS_SEARCHES_RATIO < 2.51803, 5.8313E-5, if (PREV_DAY_MAIN_CTR_RATIO < 2.34354, -0.00134957, if (LW_USERS < 0.0410895, if (AVG_SCORE < 284173.0, 0.046743, 0.00519612), 2.52E-4))) + \n" +
+ "if (YSM_CTR < 0.106731, -1.71864E-4, if (NEWS_MAIN_SEARCHES_RATIO < 9.26668, 5.48603E-4, if (USERS < 0.0145216, if (MAX_SCORE < 273414.0, 0.0139875, -0.0068697), -0.00914662))) + \n" +
+ "if (LW_CTR < 2.10467, if (NEWS_USERS < 0.223578, if (NEWS_SEARCHES < 0.452288, -4.92949E-5, -0.00633483), 4.52239E-4), if (MAX_MIN_SCORE < 36225.0, 0.00340485, 0.0295635)) + \n" +
+ "if (NEWS_MAIN_SEARCHES_RATIO < 27.2801, -3.29416E-5, if (LW_NEWS_USERS < 0.516988, -0.0205183, if (AVG_RANK < 9.5, -0.00354209, if (POLITICS_QC == 0.0, 0.0108605, 0.0656188)))) + \n" +
+ "if (LW_NEWS_CTR_RATIO < 0.130813, if (LW_USERS < 0.0675101, -0.0246242, -0.00263751), if (LW_NEWS_CTR_RATIO < 0.132702, 0.0418786, if (LW_MAIN_SEARCHES_RATIO < 0.4981, 0.0014675, -1.14374E-5))) + \n" +
+ "if (LW_MAIN_SEARCHES_RATIO < 110.393, if (PREV_DAY_MAIN_SEARCHES_RATIO < 32.725, -1.42702E-6, if (NEWS_MAIN_SEARCHES_RATIO < 3.54764, if (DAY_PD_HITS_RATIO < 5.165, -0.00858847, 0.0288169), 0.0673668)), -0.00630045) + \n" +
+ "if (SUGG_TW < 0.0905167, if (LW_NEWS_SEARCHES_RATIO < 5.38735, if (PREV_DAY_NEWS_SEARCHES_RATIO < 1.82076, 1.83185E-4, if (PREV_DAY_USERS < 0.729889, 0.00456522, -6.55502E-4)), -0.00337268), -1.85203E-4) + \n" +
+ "if (SUGG_TW < 0.985223, -7.3888E-5, if (PREV_DAY_NEWS_SEARCHES_RATIO < 0.131265, 0.0410781, if (NEWS_USERS < 0.688593, 6.1809E-4, if (NEWS_USERS < 0.999268, 0.0215243, 0.00200864)))) + \n" +
+ "if (LW_MAIN_SEARCHES_RATIO < 0.164771, if (NEWS_CTR < 0.581094, 0.001345, if (NEWS_MAIN_SEARCHES_RATIO < 4.47209, 0.00479447, 0.0485025)), if (PREV_DAY_MAIN_SEARCHES_RATIO < 0.759041, 6.85213E-4, -1.09858E-4)) + \n" +
+ "if (LW_MAIN_SEARCHES_RATIO < 0.480853, if (NEWS_USERS < 0.0737993, -2.87273E-4, if (SUGG_TW < 0.0811122, if (PREV_DAY_NEWS_SEARCHES_RATIO < 3.02247, 0.00952516, 0.0353053), 0.00133144)), 4.44055E-6) + \n" +
+ "if (LW_MAIN_SEARCHES_RATIO < 10.0035, 1.25006E-5, if (NEWS_CTR < 0.530131, if (SEARCHES < 0.261805, if (ENTERTAINMENT_QC == 0.0, -0.00352081, 0.040869), -0.0108829), 0.00398639)) + \n" +
+ "if (PREV_DAY_NEWS_SEARCHES_RATIO < 1.21358, -1.96787E-4, if (ALGO_CTR < 3.09691, if (NEWS_SEARCHES < 0.542027, 8.12659E-4, if (NEWS_USERS < 0.193619, -0.00715108, -2.37342E-4)), 0.0334561)) + \n" +
+ "if (LW_NEWS_CTR < 0.598979, -7.53961E-5, if (PREV_DAY_CTR < 1.09221, if (SCIENCE < 0.55, 4.60354E-4, if (PREV_DAY_MAIN_SEARCHES_RATIO < 1.34224, 0.00248627, 0.0781891)), 0.00584066)) + \n" +
+ "if (PREV_DAY_NEWS_USERS < 27.8368, if (BIDDED_SEARCHES < 5.85314, 6.36817E-5, -9.02941E-4), if (PREV_DAY_NEWS_CTR < 0.773569, 0.00156477, if (SEARCHES < 4.08125, 0.0385004, 0.00774733))) + \n" +
+ "if (LW_MAIN_SEARCHES_RATIO < 109.125, if (DAY_PD_HITS_RATIO < 18.75, -2.83223E-5, if (YSM_CTR < 0.0174757, if (PREV_DAY_USERS < 0.117185, 0.0405892, 0.0056735), -0.00114963)), -0.00632407) + \n" +
+ "if (NEWS_MAIN_SEARCHES_RATIO < 67.6563, if (PREV_DAY_MAIN_SEARCHES_RATIO < 32.127, 3.92781E-5, if (NEWS_CTR < 0.920256, -0.00180273, if (AVG_RANK < 8.9, 0.0401539, -0.0106621))), 0.0199772) + \n" +
+ "if (YSM_CTR < 0.0299671, if (PREV_DAY_NEWS_USERS < 1.57751, if (NEWS_MAIN_SEARCHES_RATIO < 9.09345, -3.3303E-4, if (PREV_DAY_HITS < 0.5, -0.0132526, 0.00213574)), 0.00135158), 2.86346E-4) + \n" +
+ "if (HAS_NEWS_QC == 0.0, -4.23535E-5, if (NUM_WORDS < 2.5, 1.89805E-4, if (MAX_MIN_SCORE < 34177.2, 0.00313415, if (MAX_MIN_SCORE < 38154.2, 0.0393482, 0.00649343)))) + \n" +
+ "if (NUM_WORDS < 4.5, -1.5123E-5, if (NEWS_MAIN_CTR_RATIO < 0.333385, 0.0256599, if (MIN_SCORE < 261595.0, if (LOCAL_QC == 0.0, 0.0096315, 0.0775677), -8.03435E-4))) + \n" +
+ "if (PREV_DAY_NEWS_SEARCHES_RATIO < 4.7637, 4.65171E-5, if (SEARCHES < 0.273091, if (PREV_DAY_NEWS_SEARCHES_RATIO < 11.1511, if (PREV_DAY_NEWS_CTR_RATIO < 1.59426, -0.0164486, -6.06353E-4), 0.0148189), -7.27497E-4)) + \n" +
+ "if (NEWS_USERS < 1.78862, -9.34176E-5, if (NEWS_CTR < 0.503725, -3.05424E-4, if (SUGG_LW < 92.5, if (ALGO_CTR < 0.866144, 0.00709828, 2.29334E-4), -2.83122E-4))) + \n" +
+ "if (PREV_DAY_MAIN_SEARCHES_RATIO < 31.9116, 1.62331E-5, if (SEARCHES < 0.437086, 0.05257, if (NEWS_MAIN_CTR_RATIO < 1.327, -0.00681457, if (PUB_TODAY_AVG < 0.365, -0.00897547, 0.0268691)))) + \n" +
+ "if (DAY_PD_HITS_RATIO < 79.5, if (BUSINESS < 0.195, 1.77773E-4, if (LW_MAIN_SEARCHES_RATIO < 59.4737, if (LW_MAIN_SEARCHES_RATIO < 50.3613, -3.2627E-4, 0.0335433), -0.0156041)), -0.0188673) + \n" +
+ "if (PREV_DAY_MAIN_SEARCHES_RATIO < 20.9996, 2.5689E-5, if (PREV_DAY_MAIN_CTR_RATIO < 0.692676, if (DAY_WEEK_AVG_RATIO < 2.95, -0.00554275, 0.0235987), if (LW_NEWS_SEARCHES_RATIO < 1.70492, 0.00485286, -0.0165676))) + \n" +
+ "if (PREV_DAY_NEWS_CTR < 0.239879, if (NEWS_MAIN_SEARCHES_RATIO < 7.73296, -5.33314E-4, -0.00970318), if (NEWS_USERS < 0.223578, if (NEWS_SEARCHES < 0.312345, 3.91472E-5, -0.00407912), 6.05168E-4)) + \n" +
+ "if (LW_NEWS_SEARCHES_RATIO < 2.51675, 1.51048E-5, if (LW_USERS < 0.228893, if (PREV_DAY_MAIN_CTR_RATIO < 3.25636, -0.00414565, if (PREV_DAY_USERS < 0.085979, -0.00665102, 0.0314653)), -1.93031E-4)) + \n" +
+ "if (LW_MAIN_SEARCHES_RATIO < 11.633, -3.54622E-5, if (NEWS_CTR < 0.341241, if (SEARCHES < 0.263899, if (DUDE < 0.121324, 0.0225604, -0.023524), -0.0147208), 0.0032981)) + \n" +
+ "if (DAY_WEEK_AVG_RATIO < 14.225, if (YSM_CTR < 0.0816637, -2.14327E-4, if (LW_USERS < 3.05964, if (NEWS_USERS < 0.0737993, 2.02599E-5, 0.00173562), -7.80059E-4)), 0.0180608) + \n" +
+ "if (PREV_DAY_MAIN_SEARCHES_RATIO < 0.743695, if (PREV_DAY_NEWS_CTR < 0.855411, 3.7646E-4, if (PREV_DAY_CTR < 1.27851, if (PREV_DAY_MAIN_SEARCHES_RATIO < 0.633963, 0.0103505, 0.0012006), -0.0123434)), -4.6606E-5) + \n" +
+ "if (DAY_WEEK_AVG_RATIO < 14.225, if (DAY_WEEK_AVG_RATIO < 6.985, -1.74518E-7, if (NEWS_USERS < 0.0737993, 0.00502863, -0.00831447)), if (LW_MAIN_SEARCHES_RATIO < 3.92095, -0.00421267, 0.0344091)) + \n" +
+ "if (PREV_DAY_MAIN_SEARCHES_RATIO < 0.413706, if (PREV_DAY_NEWS_CTR < 0.874719, if (LW_MAIN_SEARCHES_RATIO < 11.2056, 0.00144004, -0.014018), if (ELECTRONICS_QC == 0.0, 0.0123258, 0.119451)), -3.38696E-5) + \n" +
+ "if (LW_NEWS_SEARCHES_RATIO < 1.77722, 4.48781E-5, if (PREV_DAY_NEWS_USERS < 0.0737993, if (SUGG_TW < 0.054069, 0.00321739, -5.83152E-4), if (SEARCHES < 0.120921, -0.0131926, -0.0011303))) + \n" +
+ "if (PREV_DAY_NEWS_SEARCHES_RATIO < 6.83205, 1.51597E-5, if (LW_CTR < 0.831409, -7.90698E-4, if (USERS < 0.249336, -0.0239581, if (PREV_DAY_MAIN_CTR_RATIO < 0.604761, 0.0179905, -0.00651038)))) + \n" +
+ "if (SUGG_TW < 0.967398, -6.92965E-6, if (PREV_DAY_NEWS_SEARCHES_RATIO < 0.135403, 0.0310525, if (DUDE < 0.567766, if (SPORTS < 0.73, 0.00175153, 0.00962597), -0.00111687))) + \n" +
+ "if (LW_MAIN_SEARCHES_RATIO < 8.3318, 4.08033E-5, if (NEWS_CTR < 0.445404, if (USERS < 0.179365, if (TOPSTORY < 0.155, -0.00102, 0.0450773), -0.0100005), 0.00289212)) + \n" +
+ "if (PREV_DAY_NEWS_SEARCHES_RATIO < 1.18832, -1.47618E-4, if (ISABSTRACT_AVG < 0.03, if (PREV_DAY_NEWS_SEARCHES_RATIO < 1.21666, if (LW_MAIN_SEARCHES_RATIO < 0.747696, 0.024334, 0.00586202), 8.905E-4), -1.50915E-4)) + \n" +
+ "if (LW_MAIN_SEARCHES_RATIO < 108.679, -8.69379E-6, if (PREV_DAY_CTR < 1.00967, if (USERS < 1.24806, -0.0260056, if (NATIONALNEWS < 0.225, -0.00279946, 0.0163558)), -0.0151386)) + \n" +
+ "if (LW_CTR < 0.968595, -9.48294E-5, if (PREV_DAY_NEWS_SEARCHES_RATIO < 1.904, 2.43976E-4, if (ISTITLE_AVG < 0.37, if (WEEKAVG < 3.5, 0.00702639, 0.0745663), 8.32579E-4))) + \n" +
+ "if (POLITICS < 0.145, if (PREV_DAY_NEWS_CTR_RATIO < 0.999206, 4.19546E-4, -1.41595E-4), if (LW_NEWS_SEARCHES_RATIO < 2.08879, -0.00110749, if (PREV_DAY_SEARCHES < 0.108517, -0.0335177, -0.00494023))) + \n" +
+ "if (ENTERTAINMENT < 0.315, -5.15746E-5, if (NEWS_MAIN_SEARCHES_RATIO < 8.67009, 6.82081E-4, if (SUGG_TW < 0.0215705, if (NEWS_MAIN_SEARCHES_RATIO < 10.1884, 0.0604503, 0.0100963), -0.00450777))) + \n" +
+ "if (NEWS_MAIN_SEARCHES_RATIO < 66.4948, if (NUM_WORDS < 4.5, -1.49344E-5, if (DAY_WEEK_AVG_RATIO < 0.885, -0.00241279, if (NEWS_MAIN_SEARCHES_RATIO < 0.440079, -0.00883573, 0.0129644))), 0.0201648) + \n" +
+ "if (LW_MAIN_SEARCHES_RATIO < 168.382, if (NEWS_USERS < 1.4626, -9.7013E-5, 6.89976E-4), if (NEWS_MAIN_CTR_RATIO < 0.919145, -0.0160583, if (PREV_DAY_NEWS_SEARCHES_RATIO < 42.1812, -0.00586574, 0.0127268))) + \n" +
+ "if (NEWS_MAIN_SEARCHES_RATIO < 69.7876, if (LW_CTR < 2.10467, -2.15592E-5, if (PREV_DAY_NEWS_CTR < 0.64905, if (SUGG_LW < 1.5, 0.011141, -0.00207262), 0.0231231)), -0.01887) + \n" +
+ "if (LW_MAIN_SEARCHES_RATIO < 28.9579, -1.03443E-5, if (NATIONALNEWS < 0.315, -0.00440798, if (ISTITLE_AVG < 0.685, if (SEARCHES < 0.599257, 0.0596869, 0.0120629), -0.0170673))) + \n" +
+ "if (NEWS_MAIN_SEARCHES_RATIO < 25.791, 1.18225E-5, if (DAY_LW_DAY_HITS_RATIO < 1.96, if (POLITICS_QC == 0.0, if (AVG_SCORE < 377554.0, -0.00736807, 0.0228459), 0.0215737), -0.0192701)) + \n" +
+ "if (BIDDED_SEARCHES < 0.00581527, if (MAX_SCORE < 304693.0, -1.37144E-4, if (WEEKAVG < 0.325, 9.10176E-4, if (NEWS_USERS < 0.737974, 0.0207108, -0.0108051))), -4.27638E-5) + \n" +
+ "if (NATIONALNEWS < 0.215, -4.53121E-5, if (LW_NEWS_CTR < 1.22057, 6.18227E-4, if (NEWS_MAIN_SEARCHES_RATIO < 4.57054, if (MIN_SCORE < 241439.0, -0.00108969, 0.0178961), 0.0489683))) + \n" +
+ "if (LOCAL_QC == 1.0, if (LW_NEWS_CTR_RATIO < 0.592627, if (LW_SEARCHES < 0.101433, -0.0152231, if (PREV_DAY_USERS < 0.0833142, 0.0217818, -0.00211607)), -3.0503E-4), 7.1333E-5) + \n" +
+ "if (PREV_DAY_CTR < 1.27104, -1.52119E-5, if (DAY_PD_HITS_RATIO < 4.25, if (LW_NEWS_CTR_RATIO < 0.659092, if (PREV_DAY_NEWS_CTR_RATIO < 0.316981, -0.00815248, 0.00978334), 3.99397E-5), 0.0164301)) + \n" +
+ "if (YSM_CTR < 0.0209264, if (PREV_DAY_NEWS_USERS < 1.61612, if (NEWS_MAIN_SEARCHES_RATIO < 2.83652, -2.92569E-4, if (USERS < 0.0435647, -0.0269406, -0.00312742)), 0.00154452), 2.03401E-4) + \n" +
+ "if (LW_MAIN_SEARCHES_RATIO < 29.1019, -4.51494E-5, if (NATIONALNEWS < 0.58, if (NEWS_USERS < 0.0737993, 0.00750159, -0.00562872), if (YSM_NCTR < 0.0734117, -0.00843834, 0.0454542))) + \n" +
+ "if (PREV_DAY_NEWS_SEARCHES < 127.689, if (NEWS_MAIN_SEARCHES_RATIO < 20.0978, -5.5152E-5, if (ALGO_CTR < 1.31726, if (SPORTS < 0.55, -0.00726806, 0.0277824), 0.0380699)), 0.00603891) + \n" +
+ "if (NEWS_MAIN_CTR_RATIO < 0.118028, if (MIN_SCORE < 208142.0, 3.24283E-4, if (PREV_DAY_USERS < 0.364978, if (NEWS_SEARCHES < 0.405894, -0.00227332, -0.0219515), -0.00291436)), 7.25864E-5) + \n" +
+ "if (NEWS_MAIN_SEARCHES_RATIO < 59.3594, if (DAY_PD_HITS_RATIO < 20.625, 2.84167E-5, if (PREV_DAY_CTR < 0.822381, 0.00954114, -0.00760958)), if (POLITICS_QC == 0.0, 0.00278641, 0.063001)) + \n" +
+ "if (MIN_RANK < 5.0, if (ALGO_CTR < 1.02929, 1.47687E-4, if (LW_NEWS_CTR_RATIO < 0.131516, 0.0453262, if (PREV_DAY_NEWS_SEARCHES_RATIO < 1.88827, 7.66045E-4, 0.00609536))), -2.16222E-4) + \n" +
+ "if (MIN_SCORE < 730226.0, if (MAX_SCORE < 633968.0, 1.63517E-5, if (ISTITLE_AVG < 0.27, if (SUPERDUPER_AVG < 0.11, 0.00752064, 0.0466919), 3.46353E-4)), -0.0132458) + \n" +
+ "if (HAS_NEWS_QC == 0.0, -4.23514E-5, if (ALGO_CLICKS < 0.0117185, if (SUGG_OVERLAP < 0.5, if (MIN_RANK < 3.0, 0.0232483, 0.00659311), -0.00265598), 4.52324E-4)) + \n" +
+ "if (LW_SEARCHES < 0.189865, if (LW_NEWS_SEARCHES_RATIO < 2.08206, if (NEWS_MAIN_SEARCHES_RATIO < 38.5995, -1.13521E-4, 0.04933), if (LW_NEWS_CTR < 0.873417, -0.00342462, 0.0176586)), 1.50845E-4) + \n" +
+ "if (ALGO_CTR < 0.298421, -5.53583E-4, if (LW_MAIN_CTR_RATIO < 0.511342, if (NEWS_MAIN_SEARCHES_RATIO < 8.48751, if (NUM_WORDS < 2.5, -1.33754E-5, 0.00448769), 0.00836265), -6.6781E-6)) + \n" +
+ "if (BIDDED_SEARCHES < 2.76167, if (LW_NEWS_CTR_RATIO < 0.932077, if (LW_MAIN_SEARCHES_RATIO < 5.40525, if (SUGG_OVERLAP < 16.5, 0.00258433, 2.00886E-4), -0.00529792), -8.74994E-5), -5.14235E-4) + \n" +
+ "if (NEWS_CTR < 4.12971, if (NEWS_MAIN_SEARCHES_RATIO < 8.67009, 1.65575E-5, if (ALGO_CTR < 0.691525, if (PREV_DAY_NEWS_CTR_RATIO < 0.0958437, 0.0404329, 1.86524E-4), -0.00627032)), 0.0202226) + \n" +
+ "if (DAY_PD_HITS_RATIO < 79.5, if (DAY_PD_HITS_RATIO < 6.655, -3.12406E-5, if (USERS < 0.0351556, if (DAY_WEEK_AVG_RATIO < 3.82, 0.045008, 0.00842323), 0.00104995)), -0.0188449) + \n" +
+ "if (NEWS_CTR < 4.12578, if (PREV_DAY_NEWS_USERS < 14.0861, -2.21302E-5, if (LW_USERS < 0.956063, if (NEWS_USERS < 14.236, 0.0231446, 1.33354E-4), 0.00122231)), 0.0216971) + \n" +
+ "if (LW_MAIN_SEARCHES_RATIO < 11.8368, -5.34645E-5, if (TOPSTORY < 0.39, 9.98011E-4, if (DUDE < 0.0709353, 0.00678128, if (DUDE < 1.97964, 0.0552834, -0.00176926)))) + \n" +
+ "if (AVG_RANK < 8.325, 1.49825E-4, if (LW_NEWS_SEARCHES_RATIO < 1.73427, -6.98057E-5, if (LW_MAIN_SEARCHES_RATIO < 0.662255, if (ALGO_CTR < 1.04359, 0.001813, 0.0309613), -0.00160574))) + \n" +
+ "if (NEWS_USERS < 1.4626, if (NEWS_SEARCHES < 1.12712, 1.58784E-5, -0.00187785), if (ALGO_CTR < 1.42277, 7.02003E-4, if (ALGO_CLICKS < 3.32631, 0.048031, -0.003279))) + \n" +
+ "if (POLITICS < 0.24, if (NEWS_MAIN_SEARCHES_RATIO < 35.8437, 5.18183E-5, if (POLITICS_QC == 0.0, -0.0123086, 0.0199861)), if (NEWS_USERS < 0.0737993, -6.26382E-4, -0.00549246)) + \n" +
+ "if (NEWS_MAIN_SEARCHES_RATIO < 71.6642, if (NEWS_MAIN_SEARCHES_RATIO < 15.8372, 6.81273E-7, if (AVG_SCORE < 363153.0, -0.00465733, if (ALGO_CTR < 0.695395, 0.0265484, -0.00829536))), 0.025954) + \n" +
+ "if (LW_MAIN_SEARCHES_RATIO < 11.8213, -4.37253E-6, if (PREV_DAY_MAIN_SEARCHES_RATIO < 0.405918, -0.0124363, if (NEWS_MAIN_SEARCHES_RATIO < 1.5753, if (LW_NEWS_SEARCHES_RATIO < 1.76106, 0.00532177, -0.00463922), 0.00671844))) + \n" +
+ "if (SUGG_TW < 0.999491, -2.8076E-5, if (PREV_DAY_MAIN_SEARCHES_RATIO < 0.216777, -0.0226515, if (PREV_DAY_MAIN_SEARCHES_RATIO < 0.268164, 0.0268326, if (LW_NEWS_SEARCHES < 3.78249, 0.00120701, 0.0234219)))) + \n" +
+ "if (LIFESTYLE < 0.26, -7.02645E-5, if (LW_NEWS_SEARCHES_RATIO < 1.51723, 2.96639E-4, if (USERS < 0.723483, if (LW_MAIN_SEARCHES_RATIO < 1.39783, 0.016844, -0.00205856), 4.40619E-4))) + \n" +
+ "if (NEWS_MAIN_SEARCHES_RATIO < 65.0244, if (BIDDED_SEARCHES < 7.80003, if (NEWS_USERS < 1.78862, 3.10706E-5, if (NEWS_CTR < 1.16946, 0.00108557, 0.00916809)), -7.32103E-4), -0.0167254) + \n" +
+ "if (MIN_SCORE < 706927.0, if (NEWS_MAIN_SEARCHES_RATIO < 34.6252, 2.64245E-7, if (PREV_DAY_USERS < 0.0768826, -0.018824, if (MAX_SCORE < 266368.0, -0.00725209, 0.0196337))), -0.0123073) + \n" +
+ "if (LW_NEWS_SEARCHES_RATIO < 6.50524, 1.03884E-5, if (PREV_DAY_MAIN_CTR_RATIO < 3.78226, if (SUGG_LW < 20.5, -0.00464095, if (LW_MAIN_SEARCHES_RATIO < 1.80191, -0.00156348, 0.00548999)), 0.0381174)) + \n" +
+ "if (PREV_DAY_MAIN_SEARCHES_RATIO < 0.759041, if (PREV_DAY_NEWS_CTR < 0.863395, 2.90623E-4, if (PREV_DAY_NEWS_CTR < 0.888089, if (ELECTRONICS_QC == 0.0, 0.0295237, 0.234098), 0.00434771)), -1.29379E-4) + \n" +
+ "if (NUM_WORDS < 4.5, -1.45879E-5, if (LW_MAIN_SEARCHES_RATIO < 0.500824, 0.0301862, if (PREV_DAY_CTR < 0.774918, -0.00862254, if (NEWS_MAIN_CTR_RATIO < 0.379925, 0.0202843, 0.00289057)))) + \n" +
+ "if (NEWS_USERS < 0.223578, if (NEWS_SEARCHES < 0.368987, -2.11848E-5, if (SUGG_OVERLAP < 27.5, if (SUGG_LW < 0.5, -1.33621E-4, -0.0115821), -9.66948E-4)), 4.18664E-4) + \n" +
+ "if (PREV_DAY_NEWS_CTR < 2.62668, if (NEWS_MAIN_SEARCHES_RATIO < 59.3594, 2.95226E-5, if (POLITICS_QC == 0.0, 0.00292095, 0.0790473)), if (NEWS_MAIN_SEARCHES_RATIO < 1.59073, -0.00436975, -0.0244164)) + \n" +
+ "if (LW_NEWS_SEARCHES_RATIO < 7.02911, 6.98898E-5, if (USERS < 0.407094, if (PREV_DAY_CTR < 0.374015, if (SUGG_OVERLAP < 2.5, 0.0472886, -0.00535839), -0.0102684), -3.2528E-4)) + \n" +
+ "if (LW_MAIN_SEARCHES_RATIO < 29.7446, 3.85782E-5, if (LW_NEWS_SEARCHES_RATIO < 1.21834, 0.0126136, if (NEWS_SEARCHES < 0.476744, -0.0168822, if (NEWS_SEARCHES < 0.555398, 0.020656, -0.00228292)))) + \n" +
+ "if (NEWS_USERS < 0.223578, -1.43802E-4, if (PREV_DAY_NEWS_USERS < 0.150071, if (BIDDED_SEARCHES < 0.867205, if (YSM_CTR < 0.297271, 0.00337525, 0.0192626), 3.30042E-4), -5.02865E-5)) + \n" +
+ "if (DAY_PD_HITS_RATIO < 24.585, -3.60812E-5, if (LW_NEWS_CTR < 0.657508, if (NEWS_SEARCHES < 0.504412, if (PREV_DAY_MAIN_CTR_RATIO < 1.04252, -0.00553346, 0.0408869), -0.0021064), 0.0343193)) + \n" +
+ "if (LW_NEWS_USERS < 53.7995, if (DAY_LW_DAY_HITS_RATIO < 32.5, -3.9933E-5, 0.0108883), if (LW_CTR < 0.355044, if (PREV_DAY_HITS < 50.0, 0.00394348, 0.0296827), 0.0024317)) + \n" +
+ "if (MAX_SCORE < 533059.0, if (NUM_WORDS < 4.5, 5.09566E-6, if (PREV_DAY_NEWS_SEARCHES_RATIO < 1.45664, if (USERS < 0.0408067, 0.0119362, -0.00850073), 0.0335548)), -0.00280973) + \n" +
+ "if (ENTERTAINMENT < 0.385, -7.00557E-5, if (LW_NEWS_CTR_RATIO < 0.0964147, 0.0237102, if (DAY_LW_DAY_HITS_RATIO < 0.32, if (PREV_DAY_NEWS_CTR_RATIO < 0.357356, 0.0317688, 0.00391061), 4.78597E-4))) + \n" +
+ "if (YSM_CTR < 0.0466685, if (LW_NEWS_SEARCHES_RATIO < 1.77997, -3.31782E-5, if (USERS < 0.0159575, -0.0241302, if (LW_SEARCHES < 0.10844, -0.00372346, -5.03317E-4))), 2.60865E-4) + \n" +
+ "if (LW_NEWS_CTR_RATIO < 0.983428, if (PREV_DAY_MAIN_CTR_RATIO < 1.22783, 3.17497E-5, if (BIDDED_SEARCHES < 0.0585926, if (ALGO_CTR < 0.685543, 0.0286017, 0.00458196), 0.00141704)), -1.42453E-4) + \n" +
+ "if (DAY_WEEK_AVG_RATIO < 14.465, if (LW_NEWS_SEARCHES_RATIO < 2.48779, 5.43157E-5, if (LW_USERS < 0.0728421, if (LW_SEARCHES < 0.0462011, -0.00122403, -0.0108271), -1.06335E-4)), 0.0132433) + \n" +
+ "if (POLITICS < 0.24, if (BIDDED_SEARCHES < 7.93667, if (NEWS_MAIN_CTR_RATIO < 0.0682826, -0.003825, 8.27348E-5), -8.14933E-4), if (NEWS_MAIN_SEARCHES_RATIO < 13.1906, -0.00193925, -0.0207467)) + \n" +
+ "if (PREV_DAY_MAIN_SEARCHES_RATIO < 31.9116, -2.99465E-5, if (SEARCHES < 0.43648, 0.044537, if (LW_MAIN_CTR_RATIO < 1.03393, 0.00821746, if (PREV_DAY_NEWS_CTR_RATIO < 1.72904, 9.38031E-5, -0.0300209)))) + \n" +
+ "if (ALGO_CLICKS < 3.95494, if (LW_NEWS_USERS < 3.59178, 9.55661E-5, if (PREV_DAY_MAIN_SEARCHES_RATIO < 1.45041, if (SUGG_OVERLAP < 19.5, 0.00838423, 2.60441E-4), 0.0134882)), -5.72155E-4) + \n" +
+ "if (LW_MAIN_CTR_RATIO < 0.44235, -8.26482E-4, if (LW_MAIN_CTR_RATIO < 0.572619, if (HAS_NEWS_QC == 0.0, 6.82192E-4, if (MIN_RANK < 3.0, 0.0197753, 0.00309895)), -2.28104E-5)) + \n" +
+ "if (PREV_DAY_NEWS_SEARCHES_RATIO < 1.82043, -4.7561E-5, if (MAX_MIN_SCORE < 99.25, if (PREV_DAY_MAIN_CTR_RATIO < 1.23087, 0.00100234, if (LW_NEWS_CTR < 1.55817, 0.00609794, 0.0443022)), -2.12393E-4)) + \n" +
+ "if (PREV_DAY_CTR < 1.25038, -5.38648E-6, if (DAY_WEEK_AVG_RATIO < 3.56, 9.65443E-4, if (ISABSTRACT_AVG < 0.17, if (INTLNEWS < 0.185, 0.0537142, 0.00762876), -0.00561531))) + \n" +
+ "if (NEWS_USERS < 2.76691, -1.93104E-5, if (DUDE < 1.53391, if (PREV_DAY_CTR < 0.546773, if (PREV_DAY_MAIN_CTR_RATIO < 0.500807, 0.0447824, 0.00516532), 0.00151797), -8.85898E-4)) + \n" +
+ "if (LW_NEWS_CTR < 1.82543, if (NUM_WORDS < 4.5, -9.66944E-6, if (ISTITLE_AVG < 0.225, if (NEWS_MAIN_CTR_RATIO < 0.784694, 0.00125644, 0.0174436), -0.00293329)), -0.00426485) + \n" +
+ "if (PREV_DAY_MAIN_SEARCHES_RATIO < 32.8094, -6.29023E-5, if (MAX_MIN_SCORE < 29576.2, if (LW_MAIN_SEARCHES_RATIO < 10.4119, 0.0302981, -0.00444216), if (ALGO_CLICKS < 0.867944, 0.0333789, 0.00409533))) + \n" +
+ "if (LW_CTR < 1.02078, -3.94933E-5, if (LW_NEWS_CTR_RATIO < 0.144121, if (NEWS_CTR < 0.1349, -6.3538E-4, 0.0621699), if (NEWS_USERS < 0.158915, -3.31917E-5, 0.00191984))) + \n" +
+ "if (LW_USERS < 0.154237, if (SUGG_OVERLAP < 0.5, if (PREV_DAY_NEWS_CTR < 1.792, if (PREV_DAY_NEWS_CTR_RATIO < 0.922571, 0.00370056, -1.34817E-5), -0.0231586), -7.99336E-4), 1.05768E-4) + \n" +
+ "if (NEWS_MAIN_SEARCHES_RATIO < 38.9947, if (LW_MAIN_SEARCHES_RATIO < 0.855833, 4.19968E-4, -1.20742E-4), if (POLITICS_QC == 0.0, if (MAX_MIN_SCORE < 18453.8, 0.0234502, -0.00779192), 0.0350307)) + \n" +
+ "if (INTLNEWS < 0.575, 4.64842E-5, if (NEWS_CTR < 0.642032, -1.79661E-4, if (LW_USERS < 0.254706, if (PREV_DAY_MAIN_SEARCHES_RATIO < 13.2143, -0.0124018, 0.0123916), -0.00150583))) + \n" +
+ "if (LOCAL_QC == 1.0, if (NEWS_USERS < 0.0737993, 2.98545E-4, if (ALGO_CTR < 1.47845, if (ISTITLE_AVG < 0.515, -0.00529041, -0.00103532), 0.018429)), 6.65685E-5) + \n" +
+ "if (NEWS_USERS < 0.223578, if (NEWS_SEARCHES < 0.258474, -3.069E-6, if (SEARCHES < 0.0136022, 0.0176599, if (MIN_RANK < 1.0, -0.0134598, -0.00250337))), 3.60727E-4) + \n" +
+ "if (DAY_WEEK_AVG_RATIO < 11.945, if (LW_NEWS_USERS < 48.4961, -4.7398E-5, if (ALGO_CLICKS < 1.87723, 0.0178789, 0.00240131)), if (LW_NEWS_CTR_RATIO < 1.85865, -0.0164323, 0.00680461)) + \n" +
+ "if (PREV_DAY_NEWS_CTR_RATIO < 0.216219, if (PREV_DAY_USERS < 0.0840105, if (LW_NEWS_SEARCHES_RATIO < 1.96746, 0.00527106, -0.0232653), if (PREV_DAY_MAIN_SEARCHES_RATIO < 0.533542, 0.0105354, -0.00147978)), 1.96032E-5) + \n" +
+ "if (LW_CTR < 2.10467, -2.34181E-5, if (ENTERTAINMENT < 0.055, if (LW_MAIN_CTR_RATIO < 0.545111, -0.00186289, if (NEWS_MAIN_SEARCHES_RATIO < 1.20214, -4.63276E-4, 0.0235053)), 0.0256623)) + \n" +
+ "if (NATIONALNEWS < 0.215, -9.95943E-5, if (LW_NEWS_CTR < 1.26504, 5.92433E-4, if (NEWS_MAIN_SEARCHES_RATIO < 4.23613, if (YSM_CTR < 0.00285117, 0.0324961, 0.00265865), 0.0435376))) + \n" +
+ "if (YSM_CTR < 0.0395997, if (LW_NEWS_SEARCHES_RATIO < 1.73969, if (LW_MAIN_SEARCHES_RATIO < 20.0256, -7.93453E-5, if (ALGO_CTR < 1.07219, 0.00480972, 0.0310903)), -9.97104E-4), 2.33769E-4) + \n" +
+ "if (PREV_DAY_MAIN_SEARCHES_RATIO < 0.761868, if (PREV_DAY_NEWS_CTR_RATIO < 0.980053, if (AVG_SCORE < 400606.0, 0.00185437, if (LW_SEARCHES < 0.918927, 0.0198896, -0.0028412)), 6.98434E-6), -6.12361E-5) + \n" +
+ "if (NEWS_CTR < 1.31473, -1.47001E-5, if (LW_MAIN_SEARCHES_RATIO < 0.392613, if (PREV_DAY_NEWS_CTR < 0.855967, 0.00656145, 0.0531645), if (LW_MAIN_CTR_RATIO < 0.6064, 0.0105008, 2.09583E-4))) + \n" +
+ "if (ALGO_CTR < 3.3716, if (NEWS_CTR < 2.18598, 1.88911E-5, if (LW_NEWS_SEARCHES_RATIO < 2.3084, 8.27546E-4, -0.0121609)), if (NEWS_MAIN_SEARCHES_RATIO < 5.23803, -0.00440315, 0.0413186)) + \n" +
+ "if (DAY_PD_HITS_RATIO < 79.5, if (NEWS_MAIN_SEARCHES_RATIO < 72.49, if (PREV_DAY_NEWS_SEARCHES_RATIO < 11.2643, 3.08728E-5, if (DAY_HITS < 40.5, -0.00439766, 0.0200117)), -0.0203816), -0.0250875) + \n" +
+ "if (PREV_DAY_MAIN_SEARCHES_RATIO < 0.422088, if (LW_MAIN_SEARCHES_RATIO < 15.8595, if (PREV_DAY_NEWS_CTR < 0.567401, 5.47179E-4, 0.0106651), if (BIDDED_SEARCHES < 0.77063, -0.0254713, 0.00761775)), -4.33956E-5) + \n" +
+ "if (NEWS_MAIN_SEARCHES_RATIO < 8.67546, -1.7051E-6, if (SEARCHES < 0.0118739, 0.0135766, if (ALGO_CTR < 0.697676, -2.48861E-4, if (PREV_DAY_MAIN_SEARCHES_RATIO < 0.480888, -0.0243516, -0.00457767)))) + \n" +
+ "if (LOCAL_QC == 1.0, if (NEWS_CTR < 0.346638, if (LW_USERS < 0.160728, if (POLITICS_QC == 0.0, -0.0103889, 0.0692409), -0.00150749), 1.47672E-4), 7.59478E-5) + \n" +
+ "if (BIDDED_SEARCHES < 7.79863, if (NEWS_USERS < 0.212247, -7.88991E-5, if (AVG_SCORE < 176423.0, -2.66651E-4, if (LW_MAIN_SEARCHES_RATIO < 0.662064, 0.00587605, 7.70017E-4))), -6.54564E-4) + \n" +
+ "if (NEWS_MAIN_SEARCHES_RATIO < 27.2801, 3.4831E-5, if (BIDDED_SEARCHES < 0.0164564, if (ENTERTAINMENT_QC == 0.0, -0.0319078, 0.0305272), if (ENTERTAINMENT_QC == 1.0, -0.0309537, 4.40641E-4))) + \n" +
+ "if (LW_NEWS_SEARCHES_RATIO < 9.60088, -2.84775E-6, if (MAX_MIN_RANK < 5.0, if (ALGO_CTR < 1.25608, -0.00440719, 0.00573622), if (NEWS_CTR < 1.49071, 0.00109495, 0.0298901))) + \n" +
+ "if (DAY_PD_HITS_RATIO < 80.5, if (LW_NEWS_SEARCHES_RATIO < 45.9165, -1.89532E-5, if (DAY_WEEK_AVG_RATIO < 0.595, 0.0244208, if (DAY_WEEK_AVG_RATIO < 1.15, -0.00810109, 0.00531513))), -0.016232) + \n" +
+ "if (NEWS_MAIN_SEARCHES_RATIO < 65.0244, if (NEWS_CTR < 2.21055, 1.4463E-5, if (PREV_DAY_MAIN_CTR_RATIO < 1.39998, if (USERS < 0.0902444, -0.0288837, -0.00520806), 0.0146236)), -0.0186142) + \n" +
+ "if (DAY_LW_DAY_HITS_RATIO < 33.75, if (AVG_SCORE < 297290.0, 7.35472E-5, -5.61732E-4), if (MAX_SCORE < 200514.0, -0.0113399, if (ALGO_CLICKS < 0.990362, 0.0451975, 0.0062547))) + \n" +
+ "if (LW_NEWS_SEARCHES_RATIO < 1.77794, 7.00143E-5, if (PREV_DAY_NEWS_USERS < 0.0737993, if (MAX_MIN_SCORE < 0.75, 0.00257668, -8.8479E-4), if (SEARCHES < 0.156212, -0.0110319, -9.0476E-4))) + \n" +
+ "if (LW_MAIN_SEARCHES_RATIO < 0.146655, if (NEWS_MAIN_SEARCHES_RATIO < 3.25715, -2.57748E-5, if (USERS < 0.0468741, 4.50433E-4, if (YSM_NCTR < 0.0110028, 0.00633345, 0.0451608))), 4.31141E-5) + \n" +
+ "if (LW_MAIN_SEARCHES_RATIO < 29.7446, 8.17879E-6, if (LW_MAIN_SEARCHES_RATIO < 41.136, -0.0118399, if (NEWS_MAIN_SEARCHES_RATIO < 9.18546, if (ALGO_CLICKS < 0.578097, 0.0106049, -0.0015147), -0.0281154))) + \n" +
+ "if (NEWS_MAIN_SEARCHES_RATIO < 59.3594, if (LW_MAIN_SEARCHES_RATIO < 16.1013, -3.93323E-5, if (PREV_DAY_MAIN_CTR_RATIO < 1.64641, 5.96626E-4, 0.0120328)), if (POLITICS_QC == 0.0, 0.00481287, 0.0549552)) + \n" +
+ "if (DAY_PD_HITS_RATIO < 38.5, 1.00083E-5, if (ISTITLE_AVG < 0.25, if (POLITICS_QC == 0.0, if (HAS_NEWS_QC == 0.0, 0.0110884, 0.0787268), 0.165545), -4.90381E-5)) + \n" +
+ "if (DAY_PD_HITS_RATIO < 52.405, if (LW_MAIN_SEARCHES_RATIO < 21.0036, -2.31432E-6, if (LW_NEWS_SEARCHES_RATIO < 1.10007, 0.00655827, -0.00322249)), if (DUDE < 0.0213316, 0.00165459, 0.0267117)) + \n" +
+ "if (NEWS_CTR < 2.36159, 3.01421E-5, if (PUB_TODAY_AVG < 0.535, if (BIDDED_SEARCHES < 0.0756912, -0.0325427, if (SUGG_LW < 3.5, 0.0120055, -0.00841523)), 0.013018)) + \n" +
+ "if (PREV_DAY_MAIN_CTR_RATIO < 14.1009, if (PREV_DAY_MAIN_SEARCHES_RATIO < 293.154, if (PREV_DAY_MAIN_SEARCHES_RATIO < 106.761, 2.29918E-6, if (PREV_DAY_NEWS_SEARCHES_RATIO < 13.676, -0.0258432, 3.98425E-5)), 0.0146062), 0.0145195) + \n" +
+ "if (ISABSTRACT_AVG < 0.435, if (NEWS_CTR < 0.778514, 6.28755E-5, if (PREV_DAY_MAIN_CTR_RATIO < 0.325327, 0.0171532, 0.00156784)), if (NUM_WORDS < 2.5, -8.6343E-5, -0.00307349)) + \n" +
+ "if (PREV_DAY_NEWS_CTR_RATIO < 0.922298, if (SEARCHES < 0.0120163, if (DAY_PD_HITS_RATIO < 1.25, 0.0163074, -0.0180352), 2.42644E-4), if (LW_NEWS_SEARCHES_RATIO < 1.74561, 9.23406E-7, -0.00112041)) + \n" +
+ "if (PREV_DAY_CTR < 1.06609, -5.66011E-5, if (NEWS_SEARCHES < 1.12712, if (NEWS_USERS < 0.158915, 2.4137E-4, if (SUGG_TW < 0.0829887, 0.00752324, 7.62554E-4)), -0.00259993)) + \n" +
+ "if (NATIONALNEWS < 0.105, -4.51166E-5, if (AVG_SCORE < 359807.0, 3.6945E-4, if (ISTITLE_AVG < 0.885, if (MIN_SCORE < 346564.0, 0.041974, 0.0097136), -9.19818E-4))) + \n" +
+ "if (DAY_LW_DAY_HITS_RATIO < 57.5, if (NEWS_MAIN_SEARCHES_RATIO < 7.52403, -8.69476E-5, if (NEWS_CTR < 1.28406, 4.93978E-4, if (LW_MAIN_CTR_RATIO < 1.5554, 0.00922772, 0.0449952))), 0.0178316) + \n" +
+ "if (NEWS_CTR < 2.81183, -2.30122E-5, if (PEOPLE_QC == 0.0, if (DAY_WEEK_AVG_RATIO < 1.715, if (PREV_DAY_MAIN_SEARCHES_RATIO < 0.906796, 0.0145094, -0.00780837), 0.0292031), 0.166154)) + \n" +
+ "if (LW_MAIN_SEARCHES_RATIO < 19.6323, -9.07867E-6, if (LW_MAIN_CTR_RATIO < 1.34263, if (MIN_SCORE < 229623.0, -0.00765222, 0.00226572), if (LW_NEWS_SEARCHES_RATIO < 1.08241, 0.0130526, -0.0101225))) + \n" +
+ "if (DAY_PD_HITS_RATIO < 4.205, -4.64359E-5, if (LW_NEWS_CTR_RATIO < 0.13101, 0.0518657, if (LW_MAIN_SEARCHES_RATIO < 1.77864, -0.00106588, if (DAY_PD_HITS_RATIO < 4.55, 0.0407926, 0.00454191))))";
+
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/EvaluationTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/EvaluationTestCase.java
new file mode 100644
index 00000000000..19948cad9f2
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/EvaluationTestCase.java
@@ -0,0 +1,399 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.rankingexpression.evaluation;
+
+import com.yahoo.javacc.UnicodeUtilities;
+import com.yahoo.searchlib.rankingexpression.RankingExpression;
+import com.yahoo.tensor.MapTensor;
+import com.yahoo.searchlib.rankingexpression.parser.ParseException;
+import com.yahoo.searchlib.rankingexpression.rule.*;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Tests expression evaluation
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class EvaluationTestCase extends junit.framework.TestCase {
+
+ private Context defaultContext;
+
+ @Override
+ protected void setUp() {
+ Map<String, Value> bindings = new HashMap<String, Value>();
+ bindings.put("zero", DoubleValue.frozen(0d));
+ bindings.put("one", DoubleValue.frozen(1d));
+ bindings.put("one_half", DoubleValue.frozen(0.5d));
+ bindings.put("a_quarter", DoubleValue.frozen(0.25d));
+ bindings.put("foo", StringValue.frozen("foo"));
+ defaultContext = new MapContext(bindings);
+ }
+
+ public void testEvaluation() {
+ assertEvaluates(0.5, "0.5");
+ assertEvaluates(-0.5, "-0.5");
+ assertEvaluates(0.5, "one_half");
+ assertEvaluates(-0.5, "-one_half");
+ assertEvaluates(0, "nonexisting");
+ assertEvaluates(0.75, "0.5 + 0.25");
+ assertEvaluates(0.75, "one_half + a_quarter");
+ assertEvaluates(1.25, "0.5 - 0.25 + one");
+
+ // String
+ assertEvaluates(1, "if(\"a\"==\"a\",1,0)");
+
+ // Precedence
+ assertEvaluates(26, "2*3+4*5");
+ assertEvaluates(1, "2/6+4/6");
+ assertEvaluates(2 * 3 * 4 + 3 * 4 * 5 - 4 * 200 / 10, "2*3*4+3*4*5-4*200/10");
+
+ // Conditionals
+ assertEvaluates(2 * (3 * 4 + 3) * (4 * 5 - 4 * 200) / 10, "2*(3*4+3)*(4*5-4*200)/10");
+ assertEvaluates(0.5, "if( 2<3, one_half, one_quarter)");
+ assertEvaluates(0.25,"if( 2>3, one_half, a_quarter)");
+ assertEvaluates(0.5, "if( 1==1, one_half, a_quarter)");
+ assertEvaluates(0.5, "if( 1<=1, one_half, a_quarter)");
+ assertEvaluates(0.5, "if( 1<=1.1, one_half, a_quarter)");
+ assertEvaluates(0.25,"if( 1>=1.1, one_half, a_quarter)");
+ assertEvaluates(0.5, "if( 0.33333333333333333333~=1/3, one_half, a_quarter)");
+ assertEvaluates(0.25,"if( 0.33333333333333333333~=1/35, one_half, a_quarter)");
+ assertEvaluates(5.5, "if(one_half in [one_quarter,one_half], one_half+5,log(one_quarter) * one_quarter)");
+ assertEvaluates(0.5, "if( 1 in [1,2 , 3], one_half, a_quarter)");
+ assertEvaluates(0.25,"if( 1 in [ 2,3,4], one_half, a_quarter)");
+ assertEvaluates(0.5, "if( \"foo\" in [\"foo\",\"bar\"], one_half, a_quarter)");
+ assertEvaluates(0.5, "if( foo in [\"foo\",\"bar\"], one_half, a_quarter)");
+ assertEvaluates(0.5, "if( \"foo\" in [foo,\"bar\"], one_half, a_quarter)");
+ assertEvaluates(0.5, "if( foo in [foo,\"bar\"], one_half, a_quarter)");
+ assertEvaluates(0.25,"if( \"foo\" in [\"baz\",\"boz\"], one_half, a_quarter)");
+ assertEvaluates(0.5, "if( one in [0, 1, 2], one_half, a_quarter)");
+ assertEvaluates(0.25,"if( one in [2], one_half, a_quarter)");
+ assertEvaluates(2.5, "if(1.0, 2.5, 3.5)");
+ assertEvaluates(3.5, "if(0.0, 2.5, 3.5)");
+ assertEvaluates(2.5, "if(1.0-1.1, 2.5, 3.5)");
+ assertEvaluates(3.5, "if(1.0-1.0, 2.5, 3.5)");
+
+ // Conditionals with branch probabilities
+ RankingExpression e = assertEvaluates(3.5, "if(1.0-1.0, 2.5, 3.5, 0.3)");
+ assertEquals(0.3, ((IfNode) e.getRoot()).getTrueProbability());
+
+ // Conditionals as expressions
+ assertEvaluates(new BooleanValue(true), "2<3");
+ assertEvaluates(new BooleanValue(false), "2>3");
+ assertEvaluates(new BooleanValue(false), "if (3>2, 2>3, 5.0)");
+ assertEvaluates(new BooleanValue(true), "2>3<1"); // The result of 2>3 is converted to 0, which is <1
+ assertEvaluates(2.5, "if(2>3<1, 2.5, 3.5)");
+ assertEvaluates(2.5, "if(1+1>3<1+0, 2.5, 3.5)");
+
+ // Functions
+ assertEvaluates(0, "sin(0)");
+ assertEvaluates(1, "cos(0)");
+ assertEvaluates(8, "pow(4/2,min(cos(0)*3,5))");
+
+ // Combined
+ assertEvaluates(1.25, "5*if(1>=1.1, one_half, if(min(1,2)<max(1,2),if (\"foo\" in [\"foo\",\"bar\"],a_quarter,3000), 0.57345347))");
+
+ }
+
+ @Test
+ public void testTensorEvaluation() {
+ assertEvaluates("{}", "{}"); // empty
+ assertEvaluates("( {{x:-}:1} * {} )", "( {{x:-}:1} * {} )"); // empty with dimensions
+
+ // sum(tensor)
+ assertEvaluates(5.0, "sum({{}:5.0})");
+ assertEvaluates(-5.0, "sum({{}:-5.0})");
+ assertEvaluates(12.5, "sum({ {d1:l1}:5.5, {d2:l2}:7.0 })");
+ assertEvaluates(0.0, "sum({ {d1:l1}:5.0, {d2:l2}:7.0, {}:-12.0})");
+
+ // scalar functions on tensors
+ assertEvaluates("{ {}:1, {d1:l1}:2, {d1:l1,d2:l1 }:3 }",
+ "log10({ {}:10, {d1:l1}:100, {d1:l1,d2:l1}:1000 })");
+ assertEvaluates("{ {}:50, {d1:l1}:500, {d1:l1,d2:l1}:5000 }",
+ "5 * { {}:10, {d1:l1}:100, {d1:l1,d2:l1}:1000 }");
+ assertEvaluates("{ {}:13, {d1:l1}:103, {d1:l1,d2:l1}:1003 }",
+ "{ {}:10, {d1:l1}:100, {d1:l1,d2:l1}:1000 } + 3");
+ assertEvaluates("{ {}:1, {d1:l1}:10, {d1:l1,d2:l1 }:100 }",
+ "{ {}:10, {d1:l1}:100, {d1:l1,d2:l1}:1000 } / 10");
+ assertEvaluates("{ {}:-10, {d1:l1}:-100, {d1:l1,d2:l1 }:-1000 }",
+ "- { {}:10, {d1:l1}:100, {d1:l1,d2:l1}:1000 }");
+ assertEvaluates("{ {}:-10, {d1:l1}:0, {d1:l1,d2:l1 }:0 }",
+ "min({ {}:-10, {d1:l1}:0, {d1:l1,d2:l1}:10 }, 0)");
+ assertEvaluates("{ {}:0, {d1:l1}:0, {d1:l1,d2:l1 }:10 }",
+ "max({ {}:-10, {d1:l1}:0, {d1:l1,d2:l1}:10 }, 0)");
+ assertEvaluates("{ {h:1}:1.5, {h:2}:1.5 }", "0.5 + {{h:1}:1.0,{h:2}:1.0}");
+
+ // sum(tensor, dimension)
+ assertEvaluates("{ {y:1}:4.0, {y:2}:12.0 }",
+ "sum({ {x:1,y:1}:1.0, {x:2,y:1}:3.0, {x:1,y:2}:5.0, {x:2,y:2}:7.0 }, x)");
+ assertEvaluates("{ {x:1}:6.0, {x:2}:10.0 }",
+ "sum({ {x:1,y:1}:1.0, {x:2,y:1}:3.0, {x:1,y:2}:5.0, {x:2,y:2}:7.0 }, y)");
+
+ // tensor sum
+ assertEvaluates("{ }", "{} + {}");
+ assertEvaluates("{ {x:1}:3, {x:2}:5 }",
+ "{ {x:1}:3 } + { {x:2}:5 }");
+ assertEvaluates("{ {x:1}:8 }",
+ "{ {x:1}:3 } + { {x:1}:5 }");
+ assertEvaluates("{ {x:1}:3, {y:1}:5 }",
+ "{ {x:1}:3 } + { {y:1}:5 }");
+ assertEvaluates("{ {x:1}:3, {x:2}:7, {y:1}:5 }",
+ "{ {x:1}:3, {x:2}:7 } + { {y:1}:5 }");
+ assertEvaluates("{ {x:1,y:1}:1, {x:2,y:1}:3, {x:1,y:2}:5, {y:1,z:1}:7, {y:2,z:1}:11, {y:1,z:2}:13 }",
+ "{ {x:1,y:1}:1, {x:2,y:1}:3, {x:1,y:2}:5 } + { {y:1,z:1}:7, {y:2,z:1}:11, {y:1,z:2}:13 }");
+ assertEvaluates("{ {x:1}:5, {x:1,y:1}:1, {y:1,z:1}:7 }",
+ "{ {x:1}:5, {x:1,y:1}:1 } + { {y:1,z:1}:7 }");
+ assertEvaluates("{ {x:1}:5, {x:1,y:1}:1, {z:1}:11, {y:1,z:1}:7 }",
+ "{ {x:1}:5, {x:1,y:1}:1 } + { {z:1}:11, {y:1,z:1}:7 }");
+ assertEvaluates("{ {}:5, {x:1,y:1}:1, {y:1,z:1}:7 }",
+ "{ {}:5, {x:1,y:1}:1 } + { {y:1,z:1}:7 }");
+ assertEvaluates("{ {}:16, {x:1,y:1}:1, {y:1,z:1}:7 }",
+ "{ {}:5, {x:1,y:1}:1 } + { {}:11, {y:1,z:1}:7 }");
+
+ // tensor difference
+ assertEvaluates("{ }", "{} - {}");
+ assertEvaluates("{ {x:1}:3, {x:2}:-5 }",
+ "{ {x:1}:3 } - { {x:2}:5 }");
+ assertEvaluates("{ {x:1}:-2 }",
+ "{ {x:1}:3 } - { {x:1}:5 }");
+ assertEvaluates("{ {x:1}:3, {y:1}:-5 }",
+ "{ {x:1}:3 } - { {y:1}:5 }");
+ assertEvaluates("{ {x:1}:3, {x:2}:7, {y:1}:-5 }",
+ "{ {x:1}:3, {x:2}:7 } - { {y:1}:5 }");
+ assertEvaluates("{ {x:1,y:1}:1, {x:2,y:1}:3, {x:1,y:2}:5, {y:1,z:1}:-7, {y:2,z:1}:-11, {y:1,z:2}:-13 }",
+ "{ {x:1,y:1}:1, {x:2,y:1}:3, {x:1,y:2}:5 } - { {y:1,z:1}:7, {y:2,z:1}:11, {y:1,z:2}:13 }");
+ assertEvaluates("{ {x:1}:5, {x:1,y:1}:1, {y:1,z:1}:-7 }",
+ "{ {x:1}:5, {x:1,y:1}:1 } - { {y:1,z:1}:7 }");
+ assertEvaluates("{ {x:1}:5, {x:1,y:1}:1, {z:1}:-11, {y:1,z:1}:-7 }",
+ "{ {x:1}:5, {x:1,y:1}:1 } - { {z:1}:11, {y:1,z:1}:7 }");
+ assertEvaluates("{ {}:5, {x:1,y:1}:1, {y:1,z:1}:-7 }",
+ "{ {}:5, {x:1,y:1}:1 } - { {y:1,z:1}:7 }");
+ assertEvaluates("{ {}:-6, {x:1,y:1}:1, {y:1,z:1}:-7 }",
+ "{ {}:5, {x:1,y:1}:1 } - { {}:11, {y:1,z:1}:7 }");
+ assertEvaluates("{ {x:1}:0 }",
+ "{ {x:1}:3 } - { {x:1}:3 }");
+ assertEvaluates("{ {x:1}:0, {x:2}:1 }",
+ "{ {x:1}:3, {x:2}:1 } - { {x:1}:3 }");
+
+ // tensor product
+ assertEvaluates("{ }", "{} * {}");
+ assertEvaluates("( {{x:-,y:-,z:-}:1}*{} )", "( {{x:-}:1} * {} ) * ( {{y:-,z:-}:1} * {} )"); // empty dimensions are preserved
+ assertEvaluates("( {{x:-}:1} * {} )",
+ "{ {x:1}:3 } * { {x:2}:5 }");
+ assertEvaluates("{ {x:1}:15 }",
+ "{ {x:1}:3 } * { {x:1}:5 }");
+ assertEvaluates("{ {x:1,y:1}:15 }",
+ "{ {x:1}:3 } * { {y:1}:5 }");
+ assertEvaluates("{ {x:1,y:1}:15, {x:2,y:1}:35 }",
+ "{ {x:1}:3, {x:2}:7 } * { {y:1}:5 }");
+ assertEvaluates("{ {x:1,y:1,z:1}:7, {x:1,y:1,z:2}:13, {x:2,y:1,z:1}:21, {x:2,y:1,z:2}:39, {x:1,y:2,z:1}:55 }",
+ "{ {x:1,y:1}:1, {x:2,y:1}:3, {x:1,y:2}:5 } * { {y:1,z:1}:7, {y:2,z:1}:11, {y:1,z:2}:13 }");
+ assertEvaluates("{ {x:1,y:1,z:1}:7 }",
+ "{ {x:1}:5, {x:1,y:1}:1 } * { {y:1,z:1}:7 }");
+ assertEvaluates("{ {x:1,y:1,z:1}:7, {x:1,z:1}:55 }",
+ "{ {x:1}:5, {x:1,y:1}:1 } * { {z:1}:11, {y:1,z:1}:7 }");
+ assertEvaluates("{ {x:1,y:1,z:1}:7 }",
+ "{ {}:5, {x:1,y:1}:1 } * { {y:1,z:1}:7 }");
+ assertEvaluates("{ {x:1,y:1,z:1}:7, {}:55 }",
+ "{ {}:5, {x:1,y:1}:1 } * { {}:11, {y:1,z:1}:7 }");
+
+ // match product
+ assertEvaluates("{ }", "match({}, {})");
+ assertEvaluates("( {{x:-}:1} * {} )",
+ "match({ {x:1}:3 }, { {x:2}:5 })");
+ assertEvaluates("{ {x:1}:15 }",
+ "match({ {x:1}:3 }, { {x:1}:5 })");
+ assertEvaluates("( {{x:-,y:-}:1} * {} )",
+ "match({ {x:1}:3 }, { {y:1}:5 })");
+ assertEvaluates("( {{x:-,y:-}:1} * {} )",
+ "match({ {x:1}:3, {x:2}:7 }, { {y:1}:5 })");
+ assertEvaluates("( {{x:-,y:-,z:-}:1} * {} )",
+ "match({ {x:1,y:1}:1, {x:2,y:1}:3, {x:1,y:2}:5 }, { {y:1,z:1}:7, {y:2,z:1}:11, {y:1,z:2}:13 })");
+ assertEvaluates("( {{x:-,y:-,z:-}:1} * {} )",
+ "match({ {x:1}:5, {x:1,y:1}:1 }, { {y:1,z:1}:7 })");
+ assertEvaluates("( {{x:-,y:-,z:-}:1} * {} )",
+ "match({ {x:1}:5, {x:1,y:1}:1 }, { {z:1}:11, {y:1,z:1}:7 })");
+ assertEvaluates("( {{x:-,y:-,z:-}:1} * {} )",
+ "match({ {}:5, {x:1,y:1}:1 }, { {y:1,z:1}:7 })");
+ assertEvaluates("( {{x:-,y:-,z:-}:1} * { {}:55 } )",
+ "match({ {}:5, {x:1,y:1}:1 }, { {}:11, {y:1,z:1}:7 })");
+ assertEvaluates("( {{z:-}:1} * { {x:1}:15, {x:1,y:1}:7 } )",
+ "match({ {}:5, {x:1}:3, {x:2}:4, {x:1,y:1}:1, {x:1,y:2}:6 }, { {x:1}:5, {y:1,x:1}:7, {z:1,y:1,x:1}:10 })");
+
+ // min
+ assertEvaluates("{ {x:1}:3, {x:2}:5 }",
+ "min({ {x:1}:3 }, { {x:2}:5 })");
+ assertEvaluates("{ {x:1}:3 }",
+ "min({ {x:1}:3 }, { {x:1}:5 })");
+ assertEvaluates("{ {x:1}:3, {y:1}:5 }",
+ "min({ {x:1}:3 }, { {y:1}:5 })");
+ assertEvaluates("{ {x:1}:3, {x:2}:7, {y:1}:5 }",
+ "min({ {x:1}:3, {x:2}:7 }, { {y:1}:5 })");
+ assertEvaluates("{ {x:1,y:1}:1, {x:2,y:1}:3, {x:1,y:2}:5, {y:1,z:1}:7, {y:2,z:1}:11, {y:1,z:2}:13 }",
+ "min({ {x:1,y:1}:1, {x:2,y:1}:3, {x:1,y:2}:5 }, { {y:1,z:1}:7, {y:2,z:1}:11, {y:1,z:2}:13 })");
+ assertEvaluates("{ {x:1}:5, {x:1,y:1}:1, {y:1,z:1}:7 }",
+ "min({ {x:1}:5, {x:1,y:1}:1 }, { {y:1,z:1}:7 })");
+ assertEvaluates("{ {x:1}:5, {x:1,y:1}:1, {z:1}:11, {y:1,z:1}:7 }",
+ "min({ {x:1}:5, {x:1,y:1}:1 }, { {z:1}:11, {y:1,z:1}:7 })");
+ assertEvaluates("{ {}:5, {x:1,y:1}:1, {y:1,z:1}:7 }",
+ "min({ {}:5, {x:1,y:1}:1 }, { {y:1,z:1}:7 })");
+ assertEvaluates("{ {}:5, {x:1,y:1}:1, {y:1,z:1}:7 }",
+ "min({ {}:5, {x:1,y:1}:1 }, { {}:11, {y:1,z:1}:7 })");
+ assertEvaluates("{ {}:5, {x:1}:3, {x:2}:4, {x:1,y:1}:1, {x:1,y:2}:6, {z:1,y:1,x:1}:10 }",
+ "min({ {}:5, {x:1}:3, {x:2}:4, {x:1,y:1}:1, {x:1,y:2}:6 }, { {x:1}:5, {y:1,x:1}:7, {z:1,y:1,x:1}:10 })");
+
+ // max
+ assertEvaluates("{ {x:1}:3, {x:2}:5 }",
+ "max({ {x:1}:3 }, { {x:2}:5 })");
+ assertEvaluates("{ {x:1}:5 }",
+ "max({ {x:1}:3 }, { {x:1}:5 })");
+ assertEvaluates("{ {x:1}:3, {y:1}:5 }",
+ "max({ {x:1}:3 }, { {y:1}:5 })");
+ assertEvaluates("{ {x:1}:3, {x:2}:7, {y:1}:5 }",
+ "max({ {x:1}:3, {x:2}:7 }, { {y:1}:5 })");
+ assertEvaluates("{ {x:1,y:1}:1, {x:2,y:1}:3, {x:1,y:2}:5, {y:1,z:1}:7, {y:2,z:1}:11, {y:1,z:2}:13 }",
+ "max({ {x:1,y:1}:1, {x:2,y:1}:3, {x:1,y:2}:5 }, { {y:1,z:1}:7, {y:2,z:1}:11, {y:1,z:2}:13 })");
+ assertEvaluates("{ {x:1}:5, {x:1,y:1}:1, {y:1,z:1}:7 }",
+ "max({ {x:1}:5, {x:1,y:1}:1 }, { {y:1,z:1}:7 })");
+ assertEvaluates("{ {x:1}:5, {x:1,y:1}:1, {z:1}:11, {y:1,z:1}:7 }",
+ "max({ {x:1}:5, {x:1,y:1}:1 }, { {z:1}:11, {y:1,z:1}:7 })");
+ assertEvaluates("{ {}:5, {x:1,y:1}:1, {y:1,z:1}:7 }",
+ "max({ {}:5, {x:1,y:1}:1 }, { {y:1,z:1}:7 })");
+ assertEvaluates("{ {}:11, {x:1,y:1}:1, {y:1,z:1}:7 }",
+ "max({ {}:5, {x:1,y:1}:1 }, { {}:11, {y:1,z:1}:7 })");
+ assertEvaluates("{ {}:5, {x:1}:5, {x:2}:4, {x:1,y:1}:7, {x:1,y:2}:6, {z:1,y:1,x:1}:10 }",
+ "max({ {}:5, {x:1}:3, {x:2}:4, {x:1,y:1}:1, {x:1,y:2}:6 }, { {x:1}:5, {y:1,x:1}:7, {z:1,y:1,x:1}:10 })");
+
+ // Combined
+ assertEvaluates(7.5 + 45 + 1.7,
+ "sum( " + // model computation
+ " match( " + // model weight application
+ " { {x:1}:1, {x:2}:2 } * { {y:1}:3, {y:2}:4 } * { {z:1}:5 }, " + // feature combinations
+ " { {x:1,y:1,z:1}:0.5, {x:2,y:1,z:1}:1.5, {x:1,y:1,z:2}:4.5 }" + // model weights
+ "))+1.7");
+
+ // undefined is not the same as 0
+ assertEvaluates(1.0, "sum({ {x:1}:0, {x:2}:0 } * { {x:1}:1, {x:2}:1 } + 0.5)");
+ assertEvaluates(0.0, "sum({ } * { {x:1}:1, {x:2}:1 } + 0.5)");
+
+ // tensor result dimensions are given from argument dimensions, not the resulting values
+ assertEvaluates("x", "( {{x:-}:1.0} * {} )", "{ {x:1}:1 } * { {x:2}:1 }");
+ assertEvaluates("x, y", "( {{y:-}:1.0} * {{x:1}:1.0} )", "{ {x:1}:1 } * { {x:2,y:1}:1, {x:1}:1 }");
+
+ // demonstration of where this produces different results: { {x:1}:1 } with 2 dimensions ...
+ assertEvaluates("x, y", "( {{x:-,y:-}:1.0} * {} )","{ {x:1}:1 } * { {x:2,y:1}:1, {x:1}:1 } * { {x:1,y:1}:1 }");
+ // ... vs { {x:1}:1 } with only one dimension
+ assertEvaluates("x, y", "{{x:1,y:1}:1.0}", "{ {x:1}:1 } * { {x:1,y:1}:1 }");
+
+ // check that dimensions are preserved through other operations
+ String d2 = "{ {x:1}:1 } * { {x:2,y:1}:1, {x:1}:1 }"; // creates a 2d tensor with only an 1d value
+ assertEvaluates("x, y", "( {{x:-,y:-}:1.0} * {} )", "match(" + d2 + ", {})");
+ assertEvaluates("x, y", "( {{y:-}:1.0} * {{x:1}:1.0} )", d2 + " - {}");
+ assertEvaluates("x, y", "( {{y:-}:1.0} * {{x:1}:1.0} )", d2 + " + {}");
+ assertEvaluates("x, y", "( {{y:-}:1.0} * {{x:1}:1.0} )", "min(1.5, " + d2 +")");
+ assertEvaluates("x, y", "( {{y:-}:1.0} * {{x:1}:1.0} )", "max({{x:1}:0}, " + d2 +")");
+ }
+
+ public void testProgrammaticBuildingAndPrecedence() {
+ RankingExpression standardPrecedence = new RankingExpression(new ArithmeticNode(constant(2), ArithmeticOperator.PLUS, new ArithmeticNode(constant(3), ArithmeticOperator.MULTIPLY, constant(4))));
+ RankingExpression oppositePrecedence = new RankingExpression(new ArithmeticNode(new ArithmeticNode(constant(2), ArithmeticOperator.PLUS, constant(3)), ArithmeticOperator.MULTIPLY, constant(4)));
+ assertEquals(14.0, standardPrecedence.evaluate(null).asDouble());
+ assertEquals(20.0, oppositePrecedence.evaluate(null).asDouble());
+ assertEquals("2.0 + 3.0 * 4.0", standardPrecedence.toString());
+ assertEquals("(2.0 + 3.0) * 4.0", oppositePrecedence.toString());
+ }
+
+ private ConstantNode constant(double value) {
+ return new ConstantNode(new DoubleValue(value));
+ }
+
+ public void testStructuredVariableEvaluation() {
+ Context context = new StructuredTestContext();
+ //assertEvaluates(77,"average(6,8)+average(6,8).timesten",context);
+ assertEvaluates(77, "average(\"2*3\",\"pow(2,3)\")+average(\"2*3\",\"pow(2,3)\").timesten", context);
+ }
+
+ private RankingExpression assertEvaluates(String tensorValue, String expressionString) {
+ return assertEvaluates(new TensorValue(MapTensor.from(tensorValue)), expressionString, defaultContext);
+ }
+
+ /** Validate also that the dimension of the resulting tensors are as expected */
+ private RankingExpression assertEvaluates(String tensorDimensions, String resultTensor, String expressionString) {
+ RankingExpression expression = assertEvaluates(new TensorValue(MapTensor.from(resultTensor)), expressionString, defaultContext);
+ TensorValue value = (TensorValue)expression.evaluate(defaultContext);
+ assertEquals(toSet(tensorDimensions), value.asTensor().dimensions());
+ assertEquals("String values are equals", resultTensor, expression.evaluate(defaultContext).toString());
+ return expression;
+ }
+
+ private RankingExpression assertEvaluates(Value value, String expressionString) {
+ return assertEvaluates(value, expressionString, defaultContext);
+ }
+
+ private RankingExpression assertEvaluates(double value, String expressionString) {
+ return assertEvaluates(value, expressionString, defaultContext);
+ }
+
+ private RankingExpression assertEvaluates(double value, String expressionString, Context context) {
+ return assertEvaluates(new DoubleValue(value), expressionString, context);
+ }
+
+ private RankingExpression assertEvaluates(Value value, String expressionString, Context context) {
+ try {
+ RankingExpression expression = new RankingExpression(expressionString);
+ assertEquals(expression.toString(), value, expression.evaluate(context));
+ return expression;
+ }
+ catch (ParseException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /** Turns a comma-separated string into a set of string values */
+ private Set<String> toSet(String values) {
+ Set<String> set = new HashSet<>();
+ for (String value : values.split(","))
+ set.add(value.trim());
+ return set;
+ }
+
+ private static class StructuredTestContext extends MapContext {
+
+ @Override
+ public Value get(String name, Arguments arguments, String output) {
+ if (!name.equals("average")) {
+ throw new IllegalArgumentException("Unknown operation '" + name + "'");
+ }
+ if (arguments.expressions().size() != 2) {
+ throw new IllegalArgumentException("'average' takes 2 arguments");
+ }
+ if (output != null && !output.equals("timesten")) {
+ throw new IllegalArgumentException("Unknown 'average' output '" + output + "'");
+ }
+
+ Value result = evaluateStringAsExpression(0, arguments).add(evaluateStringAsExpression(1, arguments)).divide(new DoubleValue(2));
+ if ("timesten".equals(output)) {
+ result = result.multiply(new DoubleValue(10));
+ }
+ return result;
+ }
+
+ private Value evaluateStringAsExpression(int index, Arguments arguments) {
+ try {
+ ExpressionNode e = arguments.expressions().get(index);
+ if (e instanceof ConstantNode) {
+ return new DoubleValue(new RankingExpression(UnicodeUtilities.unquote(((ConstantNode)e).sourceString())).evaluate(this).asDouble());
+ }
+ return e.evaluate(this);
+ }
+ catch (ParseException e) {
+ throw new RuntimeException("Could not evaluate argument '" + index + "'", e);
+ }
+ }
+
+ }
+
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/NeuralNetEvaluationTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/NeuralNetEvaluationTestCase.java
new file mode 100644
index 00000000000..95c4402a612
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/NeuralNetEvaluationTestCase.java
@@ -0,0 +1,49 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.rankingexpression.evaluation;
+
+import com.yahoo.searchlib.rankingexpression.RankingExpression;
+import com.yahoo.tensor.MapTensor;
+import com.yahoo.searchlib.rankingexpression.parser.ParseException;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Tests evaluating neural nets expressed as tensors
+ *
+ * @author bratseth
+ */
+public class NeuralNetEvaluationTestCase {
+
+ /** "XOR" neural network, separate expression per layer */
+ @Test
+ public void testPerLayerExpression() {
+ String input = "{ {x:1}:0, {x:2}:1 }";
+
+ String firstLayerWeights = "{ {x:1,h:1}:1, {x:1,h:2}:1, {x:2,h:1}:1, {x:2,h:2}:1 }";
+ String firstLayerBias = "{ {h:1}:-0.5, {h:2}:-1.5 }";
+ String firstLayerInput = "sum(" + input + "*" + firstLayerWeights + ", x) + " + firstLayerBias;
+ String firstLayerOutput = "min(1.0, max(0.0, 0.5 + " + firstLayerInput + "))"; // non-linearity, "poor man's sigmoid"
+ assertEvaluates("{ {h:1}:1.0, {h:2}:0.0} }", firstLayerOutput);
+ String secondLayerWeights = "{ {h:1,y:1}:1, {h:2,y:1}:-1 }";
+ String secondLayerBias = "{ {y:1}:-0.5 }";
+ String secondLayerInput = "sum(" + firstLayerOutput + "*" + secondLayerWeights + ", h) + " + secondLayerBias;
+ String secondLayerOutput = "min(1.0, max(0.0, 0.5 + " + secondLayerInput + "))"; // non-linearity, "poor man's sigmoid"
+ assertEvaluates("{ {y:1}:1 }", secondLayerOutput);
+ }
+
+ private RankingExpression assertEvaluates(String tensorValue, String expressionString) {
+ return assertEvaluates(new TensorValue(MapTensor.from(tensorValue)), expressionString, new MapContext());
+ }
+
+ private RankingExpression assertEvaluates(Value value, String expressionString, Context context) {
+ try {
+ RankingExpression expression = new RankingExpression(expressionString);
+ assertEquals(expression.toString(), value, expression.evaluate(context));
+ return expression;
+ }
+ catch (ParseException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/StreamEvaluationBenchmark.java b/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/StreamEvaluationBenchmark.java
new file mode 100644
index 00000000000..837c7401813
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/StreamEvaluationBenchmark.java
@@ -0,0 +1,160 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.rankingexpression.evaluation;
+
+import com.yahoo.io.IOUtils;
+import com.yahoo.searchlib.rankingexpression.RankingExpression;
+import com.yahoo.searchlib.rankingexpression.evaluation.gbdtoptimization.GBDTForestOptimizer;
+import com.yahoo.searchlib.rankingexpression.parser.ParseException;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Two small benchmarks of ranking expression evaluation
+ *
+ * @author bratseth
+ */
+public class StreamEvaluationBenchmark {
+
+ public void run() {
+ try {
+ List<Map<String, Double>> features = readFeatures("/Users/bratseth/development/data/stream/gbdtFeatures");
+ String streamExpression = readFile("/Users/bratseth/development/data/stream/stream.expression");
+ run(streamExpression, features, 10);
+ }
+ catch (ParseException e) {
+ throw new RuntimeException("Benchmarking failed", e);
+ }
+ }
+
+ private String readFile(String file) {
+ try {
+ return IOUtils.readFile(new File(file));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ /** Read an ad-hoc file format with some similarly ad hoc code */
+ private List<Map<String, Double>> readFeatures(String fileName) {
+ try (BufferedReader reader = IOUtils.createReader(fileName)) {
+ List<Map<String, Double>> featureItems = new ArrayList<>();
+ String line;
+ Map<String, Double> featureItem = null;
+ while (null != (line = reader.readLine())) {
+ if (line.trim().equals("Printing Feature Set")) { // new feature item
+ featureItem = new HashMap<>();
+ featureItems.add(featureItem);
+ }
+ else { // a feature
+ line = line.replace("Feature key is ", "");
+ line = line.replace(" Feature Value is ", "=");
+ // now we have featurekey=featurevalue
+ String[] keyValue = line.split("=");
+ if (keyValue.length != 2)
+ System.err.println("Skipping invalid feature line '" + line + "'");
+ else
+ featureItem.put(keyValue[0], Double.parseDouble(keyValue[1]));
+ }
+ }
+ System.out.println("Read " + featureItems.size() + " feature items");
+ return featureItems;
+ }
+ catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private void run(String expressionString, List<Map<String, Double>> features, int iterations) throws ParseException {
+ // Optimize
+ RankingExpression expression = new RankingExpression(expressionString);
+ DoubleOnlyArrayContext contextPrototype = new DoubleOnlyArrayContext(expression, true);
+ OptimizationReport forestOptimizationReport = new ExpressionOptimizer().optimize(expression, contextPrototype);
+ System.out.println(forestOptimizationReport);
+ System.out.println("Optimized expression: " + expression.getRoot());
+
+ // Warm up
+ out("Warming up ...");
+ double total = 0;
+ total += benchmarkIterations(expression , contextPrototype, features, Math.max(iterations/5, 1));
+ oul("done");
+
+ // Benchmark
+ out("Running " + iterations + " of 'stream' ...");
+ long tStartTime = System.currentTimeMillis();
+ total += benchmarkIterations(expression, contextPrototype, features, iterations);
+ long totalTime = System.currentTimeMillis() - tStartTime;
+ oul("done");
+ oul(" Total time running 'stream': " + totalTime +
+ " ms (" + totalTime*1000/(iterations*features.size()) + " microseconds/expression)");
+ }
+
+ private double benchmarkIterations(RankingExpression gbdt, Context contextPrototype,
+ List<Map<String, Double>> features, int iterations) {
+ // Simulate realistic use: The array context can be reused for a series of evaluations in a thread
+ // but each evaluation binds a new set of values.
+ double total=0;
+ Context context = copyForEvaluation(contextPrototype);
+ long totalNanoTime = 0;
+ for (int i=0; i<iterations; i++) {
+ for (Map<String, Double> featureItem : features) {
+ long startTime = System.nanoTime();
+ bindStreamingFeatures(featureItem, context);
+ total += gbdt.evaluate(context).asDouble();
+ totalNanoTime += System.nanoTime() - startTime;
+ blowCaches();
+ }
+ }
+ System.out.println("Total time fine-grain measured: " + totalNanoTime/(1000 * iterations * features.size()));
+ return total;
+ }
+
+ private double blowCaches() {
+ List<Integer> list = new ArrayList<>();
+ for (int i = 0; i < 1000 * 1000; i++) {
+ list.add(i);
+ }
+ double total = 0;
+ for (Integer i : list)
+ total += i;
+ return total;
+ }
+
+ private Context copyForEvaluation(Context contextPrototype) {
+ if (contextPrototype instanceof AbstractArrayContext) // optimized - contains name to index map
+ return ((AbstractArrayContext)contextPrototype).clone();
+ else if (contextPrototype instanceof MapContext) // Unoptimized - nothing to keep
+ return new MapContext();
+ else
+ throw new RuntimeException("Unknown context type " + contextPrototype.getClass());
+ }
+
+ private void out(String s) {
+ System.out.print(s);
+ }
+
+ private void oul(String s) {
+ System.out.println(s);
+ }
+
+ public static void main(String[] args) {
+ new StreamEvaluationBenchmark().run();
+ }
+
+ private void assertEqualish(double a,double b) {
+ if (Math.abs(a-b) >= Math.abs((a+b)/100000000) )
+ throw new RuntimeException("Expected value " + a + " but optimized evaluation produced " + b);
+ }
+
+ private void bindStreamingFeatures(Map<String, Double> featureItem, Context context) {
+ for (Map.Entry<String, Double> feature : featureItem.entrySet())
+ context.put(feature.getKey(), feature.getValue());
+ }
+
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/ContextReuseTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/ContextReuseTestCase.java
new file mode 100644
index 00000000000..998f25b943a
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/ContextReuseTestCase.java
@@ -0,0 +1,61 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.rankingexpression.evaluation.gbdtoptimization;
+
+import com.yahoo.io.IOUtils;
+import com.yahoo.searchlib.rankingexpression.RankingExpression;
+import com.yahoo.searchlib.rankingexpression.evaluation.ArrayContext;
+import com.yahoo.searchlib.rankingexpression.evaluation.ExpressionOptimizer;
+import com.yahoo.searchlib.rankingexpression.parser.ParseException;
+
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * This tests reuse of a optimized context which is not initialized with
+ * all values referenced in the expression.
+ *
+ * @author bratseth
+ */
+public class ContextReuseTestCase extends junit.framework.TestCase {
+
+ private String contextString=
+ "CONCEPTTYPE = 0.0\n" +
+ "REGEXTYPE = 0.0\n" +
+ "POS_18 = 0.0\n" +
+ "POS_19 = 0.0\n" +
+ "ORDER_IN_CLUSTER = 2.0\n" +
+ "GOOD_SYNTAX = 1.0\n" +
+ "POS_20 = 0.0\n" +
+ "POS_11 = 0.0\n" +
+ "POS_10 = 0.0\n" +
+ "CHUNKTYPE = 0.0\n" +
+ "POS_13 = 0.0\n" +
+ "STOP_WORD_1 = 0.0\n" +
+ "TERM_CASE_2 = 0.0\n" +
+ "TERM_CASE_3 = 0.0\n" +
+ "STOP_WORD_3 = 0.0\n" +
+ "POS_15 = 0.0\n" +
+ "TERM_CASE_1 = 0.0\n" +
+ "STOP_WORD_2 = 0.0\n" +
+ "POS_1 = 0.0\n" +
+ "TERM_CASE_4 = 1.0\n" +
+ "LENGTH = 6.0\n" +
+ "EXTENDEDTYPE = 0.0\n" +
+ "ENTITYPLACETYPE = 0.0\n";
+
+ public void testIt() throws ParseException, IOException {
+ // Prepare
+ RankingExpression expression=new RankingExpression(IOUtils.readFile(new File("src/test/files/s-expression.vre")));
+ ArrayContext contextPrototype=new ArrayContext(expression);
+ new ExpressionOptimizer().optimize(expression,contextPrototype);
+
+ // Execute
+ ArrayContext context=contextPrototype.clone();
+ for (String contextValueString : contextString.split("\n")) {
+ String[] contextValueParts = contextValueString.split("=");
+ context.put(contextValueParts[0].trim(), Double.valueOf(contextValueParts[1].trim()));
+ }
+ assertEquals(-2.3450294999999994, expression.evaluate(context).asDouble());
+ }
+
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/GBDTForestOptimizerTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/GBDTForestOptimizerTestCase.java
new file mode 100644
index 00000000000..7058e909ef1
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/GBDTForestOptimizerTestCase.java
@@ -0,0 +1,109 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.rankingexpression.evaluation.gbdtoptimization;
+
+import com.yahoo.searchlib.rankingexpression.RankingExpression;
+import com.yahoo.searchlib.rankingexpression.evaluation.*;
+import com.yahoo.searchlib.rankingexpression.parser.ParseException;
+
+/**
+ * @author bratseth
+ */
+public class GBDTForestOptimizerTestCase extends junit.framework.TestCase {
+
+ public void testForestOptimization() throws ParseException {
+ String gbdtString =
+ "if (LW_NEWS_SEARCHES_RATIO < 1.72971, 0.0697159, if (LW_USERS < 0.10496, if (SEARCHES < 0.0329127, 0.151257, 0.117501), if (SUGG_OVERLAP < 18.5, 0.0897622, 0.0756903))) + \n" +
+ "if (LW_NEWS_SEARCHES_RATIO < 1.73156, if (NEWS_USERS < 0.0737993, -0.00481646, 0.00110018), if (LW_USERS < 0.0844616, 0.0488919, if (SUGG_OVERLAP < 32.5, 0.0136917, 9.85328E-4))) + \n" +
+ "if (LW_NEWS_SEARCHES_RATIO < 1.74451, -0.00298257, if (LW_USERS < 0.116207, if (SEARCHES < 0.0329127, 0.0676105, 0.0340198), if (NUM_WORDS < 1.5, -8.55514E-5, 0.0112406))) + \n" +
+ "if (LW_NEWS_SEARCHES_RATIO < 1.72995, if (NEWS_USERS < 0.0737993, -0.00407515, 0.00139088), if (LW_USERS == 0.0509035, 0.0439466, if (LW_USERS < 0.325818, 0.0187156, 0.00236949)))";
+ RankingExpression gbdt = new RankingExpression(gbdtString);
+
+ // Regular evaluation
+ MapContext arguments = new MapContext();
+ arguments.put("LW_NEWS_SEARCHES_RATIO", 1d);
+ arguments.put("SUGG_OVERLAP", 17d);
+ double result1 = gbdt.evaluate(arguments).asDouble();
+ arguments.put("LW_NEWS_SEARCHES_RATIO", 2d);
+ arguments.put("SUGG_OVERLAP", 20d);
+ double result2 = gbdt.evaluate(arguments).asDouble();
+ arguments.put("LW_NEWS_SEARCHES_RATIO", 2d);
+ arguments.put("SUGG_OVERLAP", 40d);
+ double result3 = gbdt.evaluate(arguments).asDouble();
+
+ // Optimized evaluation
+ ArrayContext fArguments = new ArrayContext(gbdt);
+ ExpressionOptimizer optimizer = new ExpressionOptimizer();
+ OptimizationReport report = optimizer.optimize(gbdt, fArguments);
+ assertEquals(4, report.getMetric("Optimized GDBT trees"));
+ assertEquals(4, report.getMetric("GBDT trees optimized to forests"));
+ assertEquals(1, report.getMetric("Number of forests"));
+ fArguments.put("LW_NEWS_SEARCHES_RATIO", 1d);
+ fArguments.put("SUGG_OVERLAP", 17d);
+ double oResult1 = gbdt.evaluate(fArguments).asDouble();
+ fArguments.put("LW_NEWS_SEARCHES_RATIO", 2d);
+ fArguments.put("SUGG_OVERLAP", 20d);
+ double oResult2 = gbdt.evaluate(fArguments).asDouble();
+ fArguments.put("LW_NEWS_SEARCHES_RATIO", 2d);
+ fArguments.put("SUGG_OVERLAP", 40d);
+ double oResult3 = gbdt.evaluate(fArguments).asDouble();
+
+ // Assert the same results are produced
+ // (adding linearly to one double does not produce exactly the same double
+ // as adding up a tree of stack frames though)
+ assertEqualish(result1, oResult1);
+ assertEqualish(result2, oResult2);
+ assertEqualish(result3, oResult3);
+ }
+
+ public void testForestOptimizationWithSetMembershipConditions() throws ParseException {
+ String gbdtString =
+ "if (MYSTRING in [\"string 1\",\"string 2\"], 0.0697159, if (LW_USERS < 0.10496, if (SEARCHES < 0.0329127, 0.151257, 0.117501), if (MYSTRING in [\"string 2\"], 0.0897622, 0.0756903))) + \n" +
+ "if (LW_NEWS_SEARCHES_RATIO < 1.73156, if (NEWS_USERS < 0.0737993, -0.00481646, 0.00110018), if (LW_USERS < 0.0844616, 0.0488919, if (SUGG_OVERLAP < 32.5, 0.0136917, 9.85328E-4))) + \n" +
+ "if (LW_NEWS_SEARCHES_RATIO < 1.74451, -0.00298257, if (LW_USERS < 0.116207, if (SEARCHES < 0.0329127, 0.0676105, 0.0340198), if (NUM_WORDS < 1.5, -8.55514E-5, 0.0112406))) + \n" +
+ "if (LW_NEWS_SEARCHES_RATIO < 1.72995, if (NEWS_USERS < 0.0737993, -0.00407515, 0.00139088), if (LW_USERS == 0.0509035, 0.0439466, if (LW_USERS < 0.325818, 0.0187156, 0.00236949)))";
+ RankingExpression gbdt = new RankingExpression(gbdtString);
+
+ // Regular evaluation
+ MapContext arguments = new MapContext();
+ arguments.put("MYSTRING", new StringValue("string 1"));
+ arguments.put("LW_NEWS_SEARCHES_RATIO", 1d);
+ arguments.put("SUGG_OVERLAP", 17d);
+ double result1 = gbdt.evaluate(arguments).asDouble();
+ arguments.put("LW_NEWS_SEARCHES_RATIO", 2d);
+ arguments.put("SUGG_OVERLAP", 20d);
+ double result2 = gbdt.evaluate(arguments).asDouble();
+ arguments.put("LW_NEWS_SEARCHES_RATIO", 2d);
+ arguments.put("SUGG_OVERLAP", 40d);
+ double result3 = gbdt.evaluate(arguments).asDouble();
+
+ // Optimized evaluation
+ ArrayContext fArguments = new ArrayContext(gbdt);
+ ExpressionOptimizer optimizer = new ExpressionOptimizer();
+ OptimizationReport report = optimizer.optimize(gbdt, fArguments);
+ assertEquals(4, report.getMetric("Optimized GDBT trees"));
+ assertEquals(4, report.getMetric("GBDT trees optimized to forests"));
+ assertEquals(1, report.getMetric("Number of forests"));
+ fArguments.put("MYSTRING", new StringValue("string 1"));
+ fArguments.put("LW_NEWS_SEARCHES_RATIO", 1d);
+ fArguments.put("SUGG_OVERLAP", 17d);
+ double oResult1 = gbdt.evaluate(fArguments).asDouble();
+ fArguments.put("LW_NEWS_SEARCHES_RATIO", 2d);
+ fArguments.put("SUGG_OVERLAP", 20d);
+ double oResult2 = gbdt.evaluate(fArguments).asDouble();
+ fArguments.put("LW_NEWS_SEARCHES_RATIO", 2d);
+ fArguments.put("SUGG_OVERLAP", 40d);
+ double oResult3 = gbdt.evaluate(fArguments).asDouble();
+
+ // Assert the same results are produced
+ // (adding linearly to one double does not produce exactly the same double
+ // as adding up a tree of stack frames though)
+ assertEqualish(result1, oResult1);
+ assertEqualish(result2, oResult2);
+ assertEqualish(result3, oResult3);
+ }
+
+ private void assertEqualish(double a, double b) {
+ assertTrue("Almost equal to " + a + ": " + b, Math.abs(a - b) < ((a + b) / 100000000));
+ }
+
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/GBDTOptimizerTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/GBDTOptimizerTestCase.java
new file mode 100644
index 00000000000..993262b1241
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/GBDTOptimizerTestCase.java
@@ -0,0 +1,105 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.rankingexpression.evaluation.gbdtoptimization;
+
+import com.yahoo.searchlib.rankingexpression.RankingExpression;
+import com.yahoo.searchlib.rankingexpression.evaluation.ArrayContext;
+import com.yahoo.searchlib.rankingexpression.evaluation.ExpressionOptimizer;
+import com.yahoo.searchlib.rankingexpression.evaluation.MapContext;
+import com.yahoo.searchlib.rankingexpression.evaluation.OptimizationReport;
+import com.yahoo.searchlib.rankingexpression.parser.ParseException;
+
+/**
+ * @author bratseth
+ */
+public class GBDTOptimizerTestCase extends junit.framework.TestCase {
+
+ public void testSimpleNodeOptimization() throws ParseException {
+ RankingExpression gbdt=new RankingExpression("if (a < 2, if (b < 2, 5, 6), 4) + if (a < 3, 7, 8)");
+
+ // Optimized evaluation
+ ArrayContext arguments=new ArrayContext(gbdt);
+ ExpressionOptimizer optimizer=new ExpressionOptimizer();
+ optimizer.getOptimizer(GBDTForestOptimizer.class).setEnabled(false);
+ OptimizationReport report=optimizer.optimize(gbdt,arguments);
+ assertEquals(2,report.getMetric("Optimized GDBT trees"));
+ arguments.put("a",1d);
+ arguments.put("b",2d);
+ assertEquals(13.0,gbdt.evaluate(arguments).asDouble());
+ }
+
+ public void testNodeOptimization() throws ParseException {
+ String gbdtString=
+ "if (LW_NEWS_SEARCHES_RATIO < 1.72971, 0.0697159, if (LW_USERS < 0.10496, if (SEARCHES < 0.0329127, 0.151257, 0.117501), if (SUGG_OVERLAP < 18.5, 0.0897622, 0.0756903))) + \n" +
+ "if (LW_NEWS_SEARCHES_RATIO < 1.73156, if (NEWS_USERS < 0.0737993, -0.00481646, 0.00110018), if (LW_USERS < 0.0844616, 0.0488919, if (SUGG_OVERLAP < 32.5, 0.0136917, 9.85328E-4))) + \n" +
+ "if (LW_NEWS_SEARCHES_RATIO < 1.74451, -0.00298257, if (LW_USERS < 0.116207, if (SEARCHES < 0.0329127, 0.0676105, 0.0340198), if (NUM_WORDS < 1.5, -8.55514E-5, 0.0112406))) + \n" +
+ "if (LW_NEWS_SEARCHES_RATIO < 1.72995, if (NEWS_USERS < 0.0737993, -0.00407515, 0.00139088), if (LW_USERS == 0.0509035, 0.0439466, if (LW_USERS < 0.325818, 0.0187156, 0.00236949)))";
+ RankingExpression gbdt=new RankingExpression(gbdtString);
+
+ // Regular evaluation
+ MapContext arguments=new MapContext();
+ arguments.put("LW_NEWS_SEARCHES_RATIO",1d);
+ arguments.put("SUGG_OVERLAP",17d);
+ double result1=gbdt.evaluate(arguments).asDouble();
+ arguments.put("LW_NEWS_SEARCHES_RATIO",2d);
+ arguments.put("SUGG_OVERLAP",20d);
+ double result2=gbdt.evaluate(arguments).asDouble();
+ arguments.put("LW_NEWS_SEARCHES_RATIO",2d);
+ arguments.put("SUGG_OVERLAP",40d);
+ double result3=gbdt.evaluate(arguments).asDouble();
+
+ // Optimized evaluation
+ ArrayContext fArguments=new ArrayContext(gbdt);
+ ExpressionOptimizer optimizer=new ExpressionOptimizer();
+ optimizer.getOptimizer(GBDTForestOptimizer.class).setEnabled(false);
+ OptimizationReport report=optimizer.optimize(gbdt,fArguments);
+ assertEquals(4,report.getMetric("Optimized GDBT trees"));
+ fArguments.put("LW_NEWS_SEARCHES_RATIO",1d);
+ fArguments.put("SUGG_OVERLAP",17d);
+ double oResult1=gbdt.evaluate(fArguments).asDouble();
+ fArguments.put("LW_NEWS_SEARCHES_RATIO",2d);
+ fArguments.put("SUGG_OVERLAP",20d);
+ double oResult2=gbdt.evaluate(fArguments).asDouble();
+ fArguments.put("LW_NEWS_SEARCHES_RATIO",2d);
+ fArguments.put("SUGG_OVERLAP",40d);
+ double oResult3=gbdt.evaluate(fArguments).asDouble();
+
+ // Assert the same results are produced
+ assertEquals(result1,oResult1);
+ assertEquals(result2,oResult2);
+ assertEquals(result3,oResult3);
+ }
+
+ public void testFeatureNamesWithDots() throws ParseException {
+ String gbdtString=
+ "if (a.b < 1.72971, 0.0697159, if (a.b.c < 0.10496, if (a.c < 0.0329127, 0.151257, 0.117501), if (a < 18.5, 0.0897622, 0.0756903))) + 1";
+ RankingExpression gbdt=new RankingExpression(gbdtString);
+
+ // Regular evaluation
+ MapContext arguments=new MapContext();
+ arguments.put("a.b",1d);
+ arguments.put("a.b.c",0.1d);
+ arguments.put("a.c",0.01d);
+ arguments.put("a",19d);
+ double result=gbdt.evaluate(arguments).asDouble();
+
+ // Optimized evaluation
+ ArrayContext fArguments=new ArrayContext(gbdt);
+ OptimizationReport report=new OptimizationReport();
+ new GBDTOptimizer().optimize(gbdt,fArguments,report);
+ assertEquals("Optimization result is as expected:\n" + report,1,report.getMetric("Optimized GDBT trees"));
+ fArguments.put("a.b",1d);
+ fArguments.put("a.b.c",0.1d);
+ fArguments.put("a.c",0.01d);
+ fArguments.put("a",19d);
+ double oResult=gbdt.evaluate(fArguments).asDouble();
+
+ // Assert the same results are produced
+ assertEquals(result,oResult);
+ }
+
+ public void testBug4009433() throws ParseException {
+ RankingExpression exp = new RankingExpression("10*if(two>35,if(two>one,if(two>=670,4,8),if(two>8000,5,3)),if(two==478,90,91))");
+ new GBDTOptimizer().optimize(exp, new ArrayContext(exp), new OptimizationReport());
+ }
+
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/rule/ArgumentsTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/rule/ArgumentsTestCase.java
new file mode 100644
index 00000000000..5402935697d
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/rule/ArgumentsTestCase.java
@@ -0,0 +1,42 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.rankingexpression.rule;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.Collections;
+
+import static org.junit.Assert.*;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ArgumentsTestCase {
+
+ @Test
+ public void requireThatAccessorsWork() {
+ Arguments args = new Arguments(null);
+ assertTrue(args.expressions().isEmpty());
+
+ args = new Arguments(Collections.<ExpressionNode>emptyList());
+ assertTrue(args.expressions().isEmpty());
+
+ NameNode foo = new NameNode("foo");
+ NameNode bar = new NameNode("bar");
+ args = new Arguments(Arrays.asList(foo, bar));
+ assertEquals(2, args.expressions().size());
+ assertSame(foo, args.expressions().get(0));
+ assertSame(bar, args.expressions().get(1));
+ }
+
+ @Test
+ public void requireThatHashCodeAndEqualsWork() {
+ Arguments arg1 = new Arguments(Arrays.asList(new NameNode("foo"), new NameNode("bar")));
+ Arguments arg2 = new Arguments(Arrays.asList(new NameNode("foo"), new NameNode("bar")));
+ Arguments arg3 = new Arguments(Arrays.asList(new NameNode("foo")));
+
+ assertEquals(arg1.hashCode(), arg2.hashCode());
+ assertTrue(arg1.equals(arg2));
+ assertFalse(arg2.equals(arg3));
+ }
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/rule/ReferenceNodeTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/rule/ReferenceNodeTestCase.java
new file mode 100644
index 00000000000..6070a3805c6
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/rule/ReferenceNodeTestCase.java
@@ -0,0 +1,35 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.rankingexpression.rule;
+
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ReferenceNodeTestCase {
+
+ @Test
+ public void requireThatAccessorsWork() {
+ ReferenceNode node = new ReferenceNode("foo", Arrays.asList(new NameNode("bar"), new NameNode("baz")), "cox");
+ assertEquals("foo", node.getName());
+ List<ExpressionNode> args = node.getArguments().expressions();
+ assertEquals(2, args.size());
+ assertEquals(new NameNode("bar"), args.get(0));
+ assertEquals(new NameNode("baz"), args.get(1));
+ assertEquals("cox", node.getOutput());
+
+ node = node.setArguments(Arrays.<ExpressionNode>asList(new NameNode("bar'")));
+ assertEquals(new NameNode("bar'"), node.getArguments().expressions().get(0));
+
+ node = node.setArguments(Arrays.<ExpressionNode>asList(new NameNode("baz'")));
+ assertEquals(new NameNode("baz'"), node.getArguments().expressions().get(0));
+
+ node = node.setOutput("cox'");
+ assertEquals("cox'", node.getOutput());
+ }
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/transform/ConstantDereferencerTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/transform/ConstantDereferencerTestCase.java
new file mode 100644
index 00000000000..9fbaddaab1e
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/transform/ConstantDereferencerTestCase.java
@@ -0,0 +1,30 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.rankingexpression.transform;
+
+import com.yahoo.searchlib.rankingexpression.RankingExpression;
+import com.yahoo.searchlib.rankingexpression.evaluation.Value;
+import com.yahoo.searchlib.rankingexpression.parser.ParseException;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class ConstantDereferencerTestCase {
+
+ @Test
+ public void testConstantDereferencer() throws ParseException {
+ Map<String, Value> constants = new HashMap<>();
+ constants.put("a", Value.parse("1.0"));
+ constants.put("b", Value.parse("2"));
+ constants.put("c", Value.parse("3.5"));
+ ConstantDereferencer c = new ConstantDereferencer(constants);
+
+ assertEquals("1.0 + 2.0 + 3.5", c.transform(new RankingExpression("a + b + c")).toString());
+ assertEquals("myMacro(1.0,2.0)", c.transform(new RankingExpression("myMacro(a, b)")).toString());
+ }
+
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/transform/SimplifierTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/transform/SimplifierTestCase.java
new file mode 100644
index 00000000000..69ec3a914d1
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/rankingexpression/transform/SimplifierTestCase.java
@@ -0,0 +1,80 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.rankingexpression.transform;
+
+import com.yahoo.searchlib.rankingexpression.RankingExpression;
+import com.yahoo.searchlib.rankingexpression.evaluation.Context;
+import com.yahoo.searchlib.rankingexpression.evaluation.MapContext;
+import com.yahoo.searchlib.rankingexpression.parser.ParseException;
+import com.yahoo.searchlib.rankingexpression.rule.CompositeNode;
+import org.junit.Test;
+import static org.junit.Assert.*;
+
+/**
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class SimplifierTestCase {
+
+ @Test
+ public void testSimplify() throws ParseException {
+ Simplifier s = new Simplifier();
+ assertEquals("a + b", s.transform(new RankingExpression("a + b")).toString());
+ assertEquals("6.5", s.transform(new RankingExpression("1.0 + 2.0 + 3.5")).toString());
+ assertEquals("6.5", s.transform(new RankingExpression("1.0 + ( 2.0 + 3.5 )")).toString());
+ assertEquals("6.5", s.transform(new RankingExpression("( 1.0 + 2.0 ) + 3.5 ")).toString());
+ assertEquals("6.5", s.transform(new RankingExpression("1.0 + ( 2.0 + 3.5 )")).toString());
+ assertEquals("7.5", s.transform(new RankingExpression("1.0 + ( 2.0 + 3.5 ) + 1")).toString());
+ assertEquals("6.5 + a", s.transform(new RankingExpression("1.0 + ( 2.0 + 3.5 ) + a")).toString());
+ assertEquals("7.5", s.transform(new RankingExpression("7.5 + ( 2.0 + 3.5 ) * 0.0")).toString());
+ assertEquals("7.5", s.transform(new RankingExpression("7.5 + ( 2.0 + 3.5 ) * (0.0)")).toString());
+ assertEquals("7.5", s.transform(new RankingExpression("7.5 + ( 2.0 + 3.5 ) * (1.0 - 1.0)")).toString());
+ assertEquals("7.5", s.transform(new RankingExpression("if (2 > 0, 3.5 * 2 + 0.5, a *3 )")).toString());
+ assertEquals("0.0", s.transform(new RankingExpression("0.0 * (1.3 + 7.0)")).toString());
+ assertEquals("6.4", s.transform(new RankingExpression("max(0, 10.0-2.0)*(1-fabs(0.0-0.2))")).toString());
+ assertEquals("(query(d) + query(b) - query(a)) * query(c) / query(e)", s.transform(new RankingExpression("(query(d) + query(b) - query(a)) * query(c) / query(e)")).toString());
+ assertEquals("14.0", s.transform(new RankingExpression("5 + (2 + 3) + 4")).toString());
+ assertEquals("28.0 + bar", s.transform(new RankingExpression("7.0 + 12.0 + 9.0 + bar")).toString());
+ assertEquals("1.0 - 0.001 * attribute(number)", s.transform(new RankingExpression("1.0 - 0.001*attribute(number)")).toString());
+ assertEquals("attribute(number) * 1.5 - 0.001 * attribute(number)", s.transform(new RankingExpression("attribute(number) * 1.5 - 0.001 * attribute(number)")).toString());
+ }
+
+ // A black box test verifying we are not screwing up real expressions
+ @Test
+ public void testSimplifyComplexExpression() throws ParseException {
+ RankingExpression initial = new RankingExpression("sqrt(if (if (INFERRED * 0.9 < INFERRED, GMP, (1 + 1.1) * INFERRED) < INFERRED * INFERRED - INFERRED, if (GMP < 85.80799542793133 * GMP, INFERRED, if (GMP < GMP, tanh(INFERRED), log(76.89956221113943))), tanh(tanh(INFERRED))) * sqrt(sqrt(GMP + INFERRED)) * GMP ) + 13.5 * (1 - GMP) * pow(GMP * 0.1, 2 + 1.1 * 0)");
+ RankingExpression simplified = new Simplifier().transform(initial);
+
+ Context context = new MapContext();
+ context.put("INFERRED", 0.5);
+ context.put("GMP", 80.0);
+ context.put("value", 50.0);
+ assertEquals(initial.evaluate(context), simplified.evaluate(context));
+ context.put("INFERRED", 38.0);
+ context.put("GMP", 80.0);
+ context.put("value", 50.0);
+ assertEquals(initial.evaluate(context), simplified.evaluate(context));
+ context.put("INFERRED", 38.0);
+ context.put("GMP", 90.0);
+ context.put("value", 100.0);
+ assertEquals(initial.evaluate(context), simplified.evaluate(context));
+ context.put("INFERRED", 500.0);
+ context.put("GMP", 90.0);
+ context.put("value", 100.0);
+ assertEquals(initial.evaluate(context), simplified.evaluate(context));
+ }
+
+ @Test
+ public void testParenthesisPreservation() throws ParseException {
+ Simplifier s = new Simplifier();
+ CompositeNode transformed = (CompositeNode)s.transform(new RankingExpression("a + (b + c) / 100000000.0")).getRoot();
+ assertEquals("a + (b + c) / 100000000.0", transformed.toString());
+ }
+
+ @Test
+ public void testSimplificationWithTensorConstants() throws ParseException {
+ new Simplifier().transform(new RankingExpression(
+ "sum(sum((tensorFromWeightedSet(query(wset_query),x)+" +
+ " tensorFromWeightedSet(attribute(wset),x)) * " +
+ " {{x:0,y:0}:54, {x:0,y:1} :69, {x:1,y:0} :72, {x:1,y:1} :93},x))"));
+ }
+
+}
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/treenet/TreeNetParserTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/treenet/TreeNetParserTestCase.java
new file mode 100755
index 00000000000..0e27d53338a
--- /dev/null
+++ b/searchlib/src/test/java/com/yahoo/searchlib/treenet/TreeNetParserTestCase.java
@@ -0,0 +1,79 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.searchlib.treenet;
+
+import com.yahoo.searchlib.rankingexpression.RankingExpression;
+import com.yahoo.searchlib.treenet.parser.ParseException;
+import com.yahoo.searchlib.treenet.parser.TreeNetParser;
+import junit.framework.TestCase;
+
+import java.io.*;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class TreeNetParserTestCase extends TestCase {
+
+ private static final boolean WRITE_FILES = false;
+
+ public void testRankingExpression() {
+ for (int i = 1; i <= 8; ++i) {
+ String inputFile = String.format("src/test/files/treenet%02d.model", i);
+ String outputFile = String.format("src/test/files/ranking%02d.expression", i);
+ String input = readFile(inputFile);
+ String expression = convertModel(inputFile, input);
+ if (WRITE_FILES) {
+ writeFile(outputFile, expression);
+ }
+ else {
+ String output = readFile(outputFile);
+ assertParseable(output, outputFile);
+ assertEquals(output.trim(), expression);
+ }
+ }
+ }
+
+ private void assertParseable(String rankingExpressionString,String fileName) {
+ try {
+ new RankingExpression(rankingExpressionString);
+ }
+ catch (com.yahoo.searchlib.rankingexpression.parser.ParseException e) {
+ throw new RuntimeException("Could not parse ranking expression in '" + fileName + "'",e);
+ }
+ }
+
+ private String convertModel(String modelFile, String model) {
+ try {
+ TreeNetParser parser = new TreeNetParser(new StringReader(model));
+ return parser.treeNet().toRankingExpression();
+ } catch (ParseException e) {
+ throw new AssertionError("In model " + modelFile + ": " + e.getMessage(), e);
+ }
+ }
+
+ private String readFile(String file) {
+ try {
+ StringBuilder ret = new StringBuilder();
+ BufferedReader in = new BufferedReader(new FileReader(file));
+ while (true) {
+ String str = in.readLine();
+ if (str == null) {
+ break;
+ }
+ ret.append(str).append("\n");
+ }
+ return ret.toString();
+ } catch (IOException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ private void writeFile(String file, String content) {
+ try {
+ FileWriter out = new FileWriter(file);
+ out.write(content);
+ out.close();
+ } catch (IOException e) {
+ throw new AssertionError(e);
+ }
+ }
+}