aboutsummaryrefslogtreecommitdiffstats
path: root/searchlib/src/main/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/main/java/com/yahoo
Publish
Diffstat (limited to 'searchlib/src/main/java/com/yahoo')
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/aggregation/AggregationResult.java161
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/aggregation/AverageAggregationResult.java157
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/aggregation/CountAggregationResult.java99
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/aggregation/ExpressionCountAggregationResult.java116
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/aggregation/FS4Hit.java132
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/aggregation/ForceLoad.java39
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/aggregation/Group.java518
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/aggregation/Grouping.java445
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/aggregation/GroupingLevel.java184
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/aggregation/Hit.java104
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/aggregation/HitsAggregationResult.java218
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/aggregation/MaxAggregationResult.java103
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/aggregation/MinAggregationResult.java103
-rwxr-xr-xsearchlib/src/main/java/com/yahoo/searchlib/aggregation/RawData.java130
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/aggregation/SumAggregationResult.java103
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/aggregation/VdsHit.java91
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/aggregation/XorAggregationResult.java99
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/aggregation/hll/BiasEstimator.java131
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/aggregation/hll/HyperLogLog.java18
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/aggregation/hll/HyperLogLogEstimator.java172
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/aggregation/hll/NormalSketch.java190
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/aggregation/hll/Sketch.java32
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/aggregation/hll/SketchMerger.java60
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/aggregation/hll/SparseSketch.java105
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/aggregation/hll/UniqueCountEstimator.java12
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/aggregation/package-info.java4
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/document/package-info.java5
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/AddFunctionNode.java23
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/AggregationRefNode.java115
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/AndFunctionNode.java22
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/ArithmeticTypeConversion.java66
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/ArrayAtLookupNode.java94
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/AttributeNode.java90
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/BitFunctionNode.java36
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/BucketResultNode.java47
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/CatFunctionNode.java42
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/ConstantNode.java82
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/DebugWaitFunctionNode.java104
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/DivideFunctionNode.java23
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/DocumentAccessorNode.java19
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/DocumentFieldNode.java116
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/ExpressionNode.java104
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/FixedWidthBucketFunctionNode.java82
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/FloatBucketResultNode.java118
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/FloatBucketResultNodeVector.java80
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/FloatResultNode.java182
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/FloatResultNodeVector.java80
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/ForceLoad.java89
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/FunctionNode.java74
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/GetDocIdNamespaceSpecificFunctionNode.java88
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/GetYMUMChecksumFunctionNode.java60
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/Int16ResultNode.java149
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/Int16ResultNodeVector.java79
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/Int32ResultNode.java149
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/Int32ResultNodeVector.java80
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/Int8ResultNode.java149
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/Int8ResultNodeVector.java80
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/IntegerBucketResultNode.java102
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/IntegerBucketResultNodeVector.java80
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/IntegerResultNode.java183
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/IntegerResultNodeVector.java80
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/InterpolatedLookupNode.java94
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/MD5BitFunctionNode.java35
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/MathFunctionNode.java185
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/MaxFunctionNode.java23
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/MinFunctionNode.java23
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/ModuloFunctionNode.java23
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/MultiArgFunctionNode.java176
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/MultiplyFunctionNode.java23
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/NegateFunctionNode.java52
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/NormalizeSubjectFunctionNode.java65
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/NullResultNode.java56
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/NumElemFunctionNode.java50
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/NumericFunctionNode.java31
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/NumericResultNode.java52
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/OrFunctionNode.java22
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/PositiveInfinityResultNode.java44
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/RangeBucketPreDefFunctionNode.java82
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/RawBucketResultNode.java101
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/RawBucketResultNodeVector.java75
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/RawResultNode.java184
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/RawResultNodeVector.java80
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/RelevanceNode.java72
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/ResultNode.java82
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/ResultNodeVector.java45
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/ReverseFunctionNode.java39
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/SingleResultNode.java38
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/SortFunctionNode.java36
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/StrCatFunctionNode.java42
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/StrLenFunctionNode.java55
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/StringBucketResultNode.java114
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/StringBucketResultNodeVector.java80
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/StringResultNode.java177
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/StringResultNodeVector.java80
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/TimeStampFunctionNode.java116
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/ToFloatFunctionNode.java39
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/ToIntFunctionNode.java44
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/ToRawFunctionNode.java38
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/ToStringFunctionNode.java51
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/UcaFunctionNode.java84
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/UnaryBitFunctionNode.java89
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/UnaryFunctionNode.java44
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/XorBitFunctionNode.java38
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/XorFunctionNode.java22
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/ZCurveFunctionNode.java92
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/package-info.java4
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/gbdt/CategoryFeatureNode.java34
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/gbdt/FeatureNode.java95
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/gbdt/GbdtConverter.java34
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/gbdt/GbdtModel.java92
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/gbdt/NumericFeatureNode.java34
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/gbdt/ResponseNode.java33
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/gbdt/TreeNode.java43
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/gbdt/XmlHelper.java110
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/CaseList.java15
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/Evolvable.java26
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/Individual.java69
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/KeyboardChecker.java50
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/Main.java73
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/Population.java60
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/PrintingTracker.java91
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/RankingExpressionCaseList.java33
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/Recombiner.java200
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/Species.java93
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/SpeciesName.java54
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/Tracker.java25
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/Trainer.java57
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/TrainingEnvironment.java31
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/TrainingParameters.java100
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/TrainingSet.java122
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/caselist/CsvFileCaseList.java56
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/caselist/FileCaseList.java73
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/caselist/FvFileCaseList.java59
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/mlr/gbdt/ExpressionAnalysis.java425
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/package-info.java5
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/ranking/features/ElementCompleteness.java96
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/ranking/features/Features.java30
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/ranking/features/FieldTermMatch.java48
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/Field.java60
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/FieldMatchMetrics.java536
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/FieldMatchMetricsComputer.java433
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/FieldMatchMetricsParameters.java198
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/Main.java39
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/Query.java72
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/QueryTerm.java67
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/SegmentStartPoint.java145
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/Trace.java22
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/package-info.java12
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/ranking/features/package-info.java10
-rwxr-xr-xsearchlib/src/main/java/com/yahoo/searchlib/rankingexpression/ExpressionFunction.java139
-rwxr-xr-xsearchlib/src/main/java/com/yahoo/searchlib/rankingexpression/FeatureList.java140
-rwxr-xr-xsearchlib/src/main/java/com/yahoo/searchlib/rankingexpression/RankingExpression.java250
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/AbstractArrayContext.java131
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/ArrayContext.java120
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/BooleanValue.java61
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/Context.java107
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/DoubleCompatibleValue.java51
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/DoubleOnlyArrayContext.java96
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/DoubleValue.java158
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/ExpressionOptimizer.java55
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/MapContext.java95
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/OptimizationReport.java63
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/Optimizer.java23
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/StringValue.java108
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/TensorValue.java168
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/Value.java96
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/.gitignore0
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/GBDTForestNode.java43
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/GBDTForestOptimizer.java124
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/GBDTNode.java98
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/GBDTOptimizer.java184
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/test/.gitignore0
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/package-info.java10
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/mlr/.gitignore0
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/package-info.java10
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/parser/package-info.java10
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/Arguments.java81
-rwxr-xr-xsearchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/ArithmeticNode.java129
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/ArithmeticOperator.java62
-rwxr-xr-xsearchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/BooleanNode.java11
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/ComparisonNode.java62
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/CompositeNode.java27
-rwxr-xr-xsearchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/ConstantNode.java54
-rwxr-xr-xsearchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/EmbracedNode.java57
-rwxr-xr-xsearchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/ExpressionNode.java51
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/Function.java55
-rwxr-xr-xsearchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/FunctionNode.java90
-rwxr-xr-xsearchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/IfNode.java86
-rwxr-xr-xsearchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/NameNode.java37
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/NegativeNode.java49
-rwxr-xr-xsearchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/ReferenceNode.java119
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/SerializationContext.java116
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/SetMembershipNode.java72
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/TensorMatchNode.java59
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/TensorSumNode.java65
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/TruthOperator.java48
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/package-info.java7
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/transform/ConstantDereferencer.java62
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/transform/ExpressionTransformer.java38
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/transform/Simplifier.java131
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/transform/package-info.java6
-rwxr-xr-xsearchlib/src/main/java/com/yahoo/searchlib/treenet/TreeNetConverter.java35
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/treenet/package-info.java5
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/treenet/parser/package-info.java5
-rwxr-xr-xsearchlib/src/main/java/com/yahoo/searchlib/treenet/rule/ComparisonCondition.java39
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/treenet/rule/Condition.java54
-rwxr-xr-xsearchlib/src/main/java/com/yahoo/searchlib/treenet/rule/Response.java45
-rwxr-xr-xsearchlib/src/main/java/com/yahoo/searchlib/treenet/rule/SetMembershipCondition.java57
-rwxr-xr-xsearchlib/src/main/java/com/yahoo/searchlib/treenet/rule/Tree.java110
-rwxr-xr-xsearchlib/src/main/java/com/yahoo/searchlib/treenet/rule/TreeNet.java63
-rwxr-xr-xsearchlib/src/main/java/com/yahoo/searchlib/treenet/rule/TreeNode.java34
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/treenet/rule/package-info.java5
212 files changed, 17821 insertions, 0 deletions
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/aggregation/AggregationResult.java b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/AggregationResult.java
new file mode 100644
index 00000000000..b877a88fc8d
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/AggregationResult.java
@@ -0,0 +1,161 @@
+// 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.ExpressionNode;
+import com.yahoo.searchlib.expression.ResultNode;
+import com.yahoo.vespa.objects.Deserializer;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+/**
+ * <p>This is the aggregation super-class from which all types of aggregation inherits.</p>
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public abstract class AggregationResult extends ExpressionNode {
+
+ public static final int classId = registerClass(0x4000 + 80, AggregationResult.class);
+ private ExpressionNode expression = null;
+ private int tag = -1;
+
+ /**
+ * <p>Returns the tag of this aggregation result. This is useful for uniquely identifying a result.</p>
+ *
+ * @return The numerical tag.
+ */
+ public int getTag() {
+ return tag;
+ }
+
+ /**
+ * <p>Assigns a tag to this group.</p>
+ *
+ * @param tag The numerical tag to set.
+ * @return This, to allow chaining.
+ */
+ public AggregationResult setTag(int tag) {
+ this.tag = tag;
+ return this;
+ }
+
+ /**
+ * <p>This method is called when merging aggregation results. This method is simply a proxy for the abstract {@link
+ * #onMerge(AggregationResult)} method.</p>
+ *
+ * @param result The result to merge with.
+ */
+ public void merge(AggregationResult result) {
+ onMerge(result);
+ }
+
+ /**
+ * <p>This method is called when all aggregation results have been merged. This method can be overloaded by
+ * subclasses that need special behaviour to occur after merge.</p>
+ */
+ public void postMerge() {
+ // empty
+ }
+
+ /**
+ * <p>This method returns a value that can be used for ranking.</p>
+ *
+ * @return The rankable result.
+ */
+ public abstract ResultNode getRank();
+
+ /**
+ * <p>Sets the expression to aggregate on.</p>
+ *
+ * @param exp The expression.
+ * @return This, to allow chaining.
+ */
+ public AggregationResult setExpression(ExpressionNode exp) {
+ expression = exp;
+ return this;
+ }
+
+ /**
+ * <p>Returns the expression to aggregate on.</p>
+ *
+ * @return The expression.
+ */
+ public ExpressionNode getExpression() {
+ return expression;
+ }
+
+ /**
+ * <p>This method must be implemented by subclasses to support merge. It is called as the {@link
+ * #merge(AggregationResult)} method is invoked.</p>
+ *
+ * @param result The result to merge with.
+ */
+ protected abstract void onMerge(AggregationResult result);
+
+ @Override
+ public ResultNode getResult() {
+ return getRank();
+ }
+
+ @Override
+ public void onPrepare() {
+
+ }
+
+ @Override
+ public boolean onExecute() {
+ return true;
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ serializeOptional(buf, expression);
+ buf.putInt(null, tag);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ expression = (ExpressionNode)deserializeOptional(buf);
+ tag = buf.getInt(null);
+ }
+
+ @Override
+ public AggregationResult clone() {
+ AggregationResult obj = (AggregationResult)super.clone();
+ if (expression != null) {
+ obj.expression = expression.clone();
+ }
+ return obj;
+ }
+
+ @Override
+ protected final boolean equalsExpression(ExpressionNode obj) {
+ AggregationResult rhs = (AggregationResult)obj;
+ if (!equals(expression, rhs.expression)) {
+ return false;
+ }
+ if (tag != rhs.tag) {
+ return false;
+ }
+ if (!equalsAggregation(rhs)) {
+ return false;
+ }
+ return true;
+ }
+
+ protected abstract boolean equalsAggregation(AggregationResult obj);
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("expression", expression);
+ visitor.visit("tag", tag);
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/aggregation/AverageAggregationResult.java b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/AverageAggregationResult.java
new file mode 100644
index 00000000000..651ab192786
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/AverageAggregationResult.java
@@ -0,0 +1,157 @@
+// 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.IntegerResultNode;
+import com.yahoo.searchlib.expression.NumericResultNode;
+import com.yahoo.searchlib.expression.ResultNode;
+import com.yahoo.vespa.objects.Deserializer;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+/**
+ * This is an aggregated result holding the average of all results.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class AverageAggregationResult extends AggregationResult {
+
+ public static final int classId = registerClass(0x4000 + 85, AverageAggregationResult.class);
+ private NumericResultNode sum;
+ private long count;
+
+ /**
+ * Constructs an empty result node. <b>NOTE:</b> This instance is broken until non-optional member data is set.
+ */
+ public AverageAggregationResult() {
+
+ }
+
+ /**
+ * Constructs an instance of this class with given sum and count values.
+ *
+ * @param sum The initial sum to set.
+ * @param count The initial number of results.
+ */
+ public AverageAggregationResult(NumericResultNode sum, long count) {
+ setSum(sum);
+ setCount(count);
+ }
+
+ /**
+ * Returns the sum of all results in this.
+ *
+ * @return The numeric sum.
+ */
+ public final NumericResultNode getSum() {
+ return sum;
+ }
+
+ /**
+ * Sets the sum of all results in this.
+ *
+ * @param sum The sum to set.
+ * @return This, to allow chaining.
+ */
+ public final AverageAggregationResult setSum(NumericResultNode sum) {
+ this.sum = sum;
+ return this;
+ }
+
+ /**
+ * Returns the number of results in this.
+ *
+ * @return The number of results.
+ */
+ public final long getCount() {
+ return count;
+ }
+
+ /**
+ * Sets the number of results in this.
+ *
+ * @param count The number of results.
+ * @return This, to allow chaining.
+ */
+ public final AverageAggregationResult setCount(long count) {
+ this.count = count;
+ return this;
+ }
+
+ /**
+ * Returns the average value of the results. Because the result can be any numeric type, this method returns a
+ * {@link NumericResultNode} object.
+ *
+ * @return The average result value.
+ */
+ public final NumericResultNode getAverage() {
+ NumericResultNode sum = (NumericResultNode)this.sum.clone();
+ if (count != 0) {
+ sum.divide(new IntegerResultNode(count));
+ }
+ return sum;
+ }
+
+ @Override
+ public ResultNode getRank() {
+ return getAverage();
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ buf.putLong(null, count);
+ serializeOptional(buf, sum);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ count = buf.getLong(null);
+ sum = (NumericResultNode)deserializeOptional(buf);
+ }
+
+ @Override
+ protected void onMerge(AggregationResult result) {
+ sum.add(((AverageAggregationResult)result).sum);
+ count += ((AverageAggregationResult)result).count;
+ }
+
+ @Override
+ public AverageAggregationResult clone() {
+ AverageAggregationResult obj = (AverageAggregationResult)super.clone();
+ if (sum != null) {
+ obj.sum = (NumericResultNode)sum.clone();
+ }
+ return obj;
+ }
+
+ @Override
+ protected boolean equalsAggregation(AggregationResult obj) {
+ AverageAggregationResult rhs = (AverageAggregationResult)obj;
+ if (!equals(sum, rhs.sum)) {
+ return false;
+ }
+ if (count != rhs.count) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + (int)count;
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("sum", sum);
+ visitor.visit("count", count);
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/aggregation/CountAggregationResult.java b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/CountAggregationResult.java
new file mode 100644
index 00000000000..5f90c126115
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/CountAggregationResult.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.aggregation;
+
+import com.yahoo.searchlib.expression.IntegerResultNode;
+import com.yahoo.searchlib.expression.ResultNode;
+import com.yahoo.vespa.objects.Deserializer;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+/**
+ * This is an aggregated result holding the number of aggregated hits.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class CountAggregationResult extends AggregationResult {
+
+ public static final int classId = registerClass(0x4000 + 81, CountAggregationResult.class);
+ private long count = 0;
+
+ /**
+ * Constructs an empty result node. <b>NOTE:</b> This instance is broken until non-optional member data is set.
+ */
+ public CountAggregationResult() {
+
+ }
+
+ /**
+ * Constructs an instance of this class with given count value.
+ *
+ * @param count The initial number of hits.
+ */
+ public CountAggregationResult(long count) {
+ setCount(count);
+ }
+
+ /**
+ * Returns the number of aggregated hits.
+ *
+ * @return The count.
+ */
+ public final long getCount() {
+ return count;
+ }
+
+ /**
+ * Sets the number of aggregated hits.
+ *
+ * @param count The count.
+ * @return This, to allow chaining.
+ */
+ public final CountAggregationResult setCount(long count) {
+ this.count = count;
+ return this;
+ }
+
+ @Override
+ public ResultNode getRank() {
+ return new IntegerResultNode(count);
+ }
+
+ @Override
+ protected void onMerge(AggregationResult result) {
+ count += ((CountAggregationResult)result).count;
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ buf.putLong(null, count);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ count = buf.getLong(null);
+ }
+
+ @Override
+ protected boolean equalsAggregation(AggregationResult obj) {
+ return count == ((CountAggregationResult)obj).count;
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + (int)count;
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("count", count);
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/aggregation/ExpressionCountAggregationResult.java b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/ExpressionCountAggregationResult.java
new file mode 100644
index 00000000000..d6c76087e4e
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/ExpressionCountAggregationResult.java
@@ -0,0 +1,116 @@
+// 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.searchlib.expression.IntegerResultNode;
+import com.yahoo.searchlib.expression.ResultNode;
+import com.yahoo.vespa.objects.Deserializer;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+/**
+ * This is an aggregated result holding the number of unique documents matching a given expression.
+ *
+ * @author bjorncs
+ */
+public class ExpressionCountAggregationResult extends AggregationResult {
+
+ public static final int classId = registerClass(0x4000 + 88, ExpressionCountAggregationResult.class);
+ private static final int UNDEFINED = -1;
+
+ // The unique count estimator
+ private final UniqueCountEstimator<Sketch<?>> estimator;
+ // Sketch merger
+ private final SketchMerger sketchMerger = new SketchMerger();
+ // The sketch used as basis for the unique count calculation. The sketch is populated with data by the search nodes.
+ private Sketch<?> sketch;
+ // The estimated unique count. This value will not be serialized / deserialized.
+ private long estimatedUniqueCount = UNDEFINED;
+
+
+ /**
+ * Constructor used for deserialization. Will be instantiated with a default sketch.
+ */
+ @SuppressWarnings("UnusedDeclaration")
+ public ExpressionCountAggregationResult() {
+ this(new SparseSketch(), new HyperLogLogEstimator());
+ }
+
+ /**
+ * Constructs an instance with a given sketch, sketch merger and unique count estimator. For test purposes.
+ *
+ * @param initialSketch The HLL sketch.
+ */
+ public ExpressionCountAggregationResult(Sketch<?> initialSketch, UniqueCountEstimator<Sketch<?>> estimator) {
+ this.sketch = initialSketch;
+ this.estimator = estimator;
+ }
+
+ /**
+ * @return The unique count estimated by the HyperLogLog algorithm.
+ */
+ public long getEstimatedUniqueCount() {
+ if (estimatedUniqueCount == UNDEFINED) {
+ updateEstimate();
+ }
+ return estimatedUniqueCount;
+ }
+
+ @Override
+ public ResultNode getRank() {
+ return new IntegerResultNode(getEstimatedUniqueCount());
+ }
+
+ @Override
+ protected void onMerge(AggregationResult result) {
+ ExpressionCountAggregationResult other = (ExpressionCountAggregationResult) result;
+ sketch = sketchMerger.merge(sketch, other.sketch);
+ // Any cached result should be invalidated.
+ estimatedUniqueCount = UNDEFINED;
+ }
+
+ public Sketch<?> getSketch() {
+ return sketch;
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ sketch.serializeWithId(buf);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ sketch = (Sketch<?>) create(buf);
+ }
+
+ @Override
+ protected boolean equalsAggregation(AggregationResult obj) {
+ // obj is assumed to always be of correct type.
+ ExpressionCountAggregationResult other = (ExpressionCountAggregationResult) obj;
+ return sketch.equals(other.sketch);
+ }
+
+ private void updateEstimate() {
+ estimatedUniqueCount = estimator.estimateCount(sketch);
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("sketch", sketch);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = super.hashCode();
+ result = 31 * result + sketch.hashCode();
+ return result;
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/aggregation/FS4Hit.java b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/FS4Hit.java
new file mode 100644
index 00000000000..8b0704eea9b
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/FS4Hit.java
@@ -0,0 +1,132 @@
+// 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.GlobalId;
+import com.yahoo.vespa.objects.Deserializer;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+/**
+ * This class represents a single hit from the fastserver4 backend
+ *
+ * @author <a href="mailto:havardpe@yahoo-inc.com">Haavard Pettersen</a>
+ */
+public class FS4Hit extends Hit {
+
+ public static final int classId = registerClass(0x4000 + 95, FS4Hit.class); // shared with c++
+ private int path = 0;
+ private GlobalId globalId = new GlobalId(new byte[GlobalId.LENGTH]);
+ private int distributionKey = -1;
+
+ /**
+ * Constructs an empty result node.
+ */
+ public FS4Hit() {
+ }
+
+ /**
+ * Create a hit with the given path and document id.
+ *
+ * @param path The mangled search node path.
+ * @param globalId The local document id.
+ * @param rank The rank of this hit.
+ */
+ public FS4Hit(int path, GlobalId globalId, double rank) {
+ this(path, globalId, rank, -1);
+ }
+
+ /**
+ * Create a hit with the given path and document id.
+ *
+ * @param path The mangled search node path.
+ * @param globalId The local document id.
+ * @param rank The rank of this hit.
+ * @param distributionKey The doc stamp.
+ */
+ public FS4Hit(int path, GlobalId globalId, double rank, int distributionKey) {
+ super(rank);
+ this.path = path;
+ this.globalId = globalId;
+ this.distributionKey = distributionKey;
+ }
+
+ /**
+ * Obtain the (mangled) network path back to the search node returning this hit.
+ *
+ * @return The mangled search node path.
+ */
+ public int getPath() {
+ return path;
+ }
+
+ /**
+ * Obtain the global document id on the search node returning this hit.
+ *
+ * @return The global document id.
+ */
+ public GlobalId getGlobalId() {
+ return globalId;
+ }
+
+ /**
+ * Obtain the distribution key for the node producing this hit.
+ *
+ * @return distribution key
+ */
+ public int getDistributionKey() {
+ return distributionKey;
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ buf.putInt(null, path);
+ buf.put(null, globalId.getRawId());
+ buf.putInt(null, distributionKey);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ path = buf.getInt(null);
+ globalId = new GlobalId(buf.getBytes(null, GlobalId.LENGTH));
+ distributionKey = buf.getInt(null);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + path + globalId.hashCode() + distributionKey;
+ }
+
+ @SuppressWarnings({ "EqualsWhichDoesntCheckParameterClass", "RedundantIfStatement" })
+ @Override
+ public boolean equals(Object obj) {
+ if (!super.equals(obj)) {
+ return false;
+ }
+ FS4Hit rhs = (FS4Hit)obj;
+ if (path != rhs.path) {
+ return false;
+ }
+ if (!globalId.equals(rhs.globalId)) {
+ return false;
+ }
+ if (distributionKey != rhs.distributionKey) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("path", path);
+ visitor.visit("globalId", globalId.toString());
+ visitor.visit("distributionKey", distributionKey);
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/aggregation/ForceLoad.java b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/ForceLoad.java
new file mode 100644
index 00000000000..ecbab688821
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/ForceLoad.java
@@ -0,0 +1,39 @@
+// 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;
+
+/**
+ * FIXME: Really ugly hack to force class loading for subclasses of Identifiable.
+ * This should be fixed by doing the all class registration in a single place (similar to how its done in C++).
+ */
+public class ForceLoad {
+
+ static {
+ String pkg = "com.yahoo.searchlib.aggregation";
+ String[] classes = {
+ "XorAggregationResult",
+ "SumAggregationResult",
+ "Group",
+ "HitsAggregationResult",
+ "AggregationResult",
+ "FS4Hit",
+ "VdsHit",
+ "Grouping",
+ "Hit",
+ "ForceLoad",
+ "MinAggregationResult",
+ "GroupingLevel",
+ "MaxAggregationResult",
+ "CountAggregationResult",
+ "AverageAggregationResult",
+ "ExpressionCountAggregationResult",
+ "hll.SparseSketch",
+ "hll.NormalSketch",
+ "ForceLoad"
+ };
+ com.yahoo.system.ForceLoad.forceLoad(pkg, classes);
+ }
+
+ public static boolean forceLoad() {
+ return true;
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/aggregation/Group.java b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/Group.java
new file mode 100644
index 00000000000..03836d75efc
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/Group.java
@@ -0,0 +1,518 @@
+// 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.AggregationRefNode;
+import com.yahoo.searchlib.expression.ExpressionNode;
+import com.yahoo.searchlib.expression.ResultNode;
+import com.yahoo.vespa.objects.*;
+
+import java.util.*;
+
+public class Group extends Identifiable {
+
+ public static final int classId = registerClass(0x4000 + 90, Group.class);
+ private static final ObjectPredicate REF_LOCATOR = new RefLocator();
+ private List<Integer> orderByIdx = new ArrayList<Integer>();
+ private List<ExpressionNode> orderByExp = new ArrayList<ExpressionNode>();
+ private List<AggregationResult> aggregationResults = new ArrayList<AggregationResult>();
+ private List<Group> children = new ArrayList<Group>();
+ private ResultNode id = null;
+ private double rank;
+ private int tag = -1;
+ private SortType sortType = SortType.UNSORTED;
+
+ /**
+ * <p>This tells you if the children are ranked by the pure relevance or by a more complex expression. That
+ * indicates if the rank score from the child can be used for ordering.</p>
+ *
+ * @return True if it ranked by pure relevance.
+ */
+ public boolean isRankedByRelevance() {
+ return orderByIdx.isEmpty();
+ }
+
+ /**
+ * <p>Merges the content of the given group <b>into</b> this. When this function returns, make sure to call {@link
+ * #postMerge(java.util.List, int, int)}.</p>
+ *
+ * @param firstLevel The first level to merge.
+ * @param currentLevel The current level.
+ * @param rhs The group to merge with.
+ */
+ public void merge(int firstLevel, int currentLevel, Group rhs) {
+ if (rhs.rank > rank) {
+ rank = rhs.rank; // keep highest rank
+ }
+ if (currentLevel >= firstLevel) {
+ for (int i = 0, len = aggregationResults.size(); i < len; ++i) {
+ aggregationResults.get(i).merge(rhs.aggregationResults.get(i));
+ }
+ }
+
+ ArrayList<Group> merged = new ArrayList<Group>();
+ Iterator<Group> lhsChild = children.iterator(), rhsChild = rhs.children.iterator();
+ if (lhsChild.hasNext() && rhsChild.hasNext()) {
+ Group lhsGroup = lhsChild.next();
+ Group rhsGroup = rhsChild.next();
+ for (; (lhsGroup != null) && (rhsGroup != null); ) {
+ int cmp = lhsGroup.getId().compareTo(rhsGroup.getId());
+ if (cmp < 0) {
+ merged.add(lhsGroup);
+ lhsGroup = lhsChild.hasNext() ? lhsChild.next() : null;
+ } else if (cmp > 0) {
+ merged.add(rhsGroup);
+ rhsGroup = rhsChild.hasNext() ? rhsChild.next() : null;
+ } else {
+ lhsGroup.merge(firstLevel, currentLevel + 1, rhsGroup);
+ merged.add(lhsGroup);
+ lhsGroup = lhsChild.hasNext() ? lhsChild.next() : null;
+ rhsGroup = rhsChild.hasNext() ? rhsChild.next() : null;
+ }
+ }
+ if (lhsGroup != null) {
+ merged.add(lhsGroup);
+ }
+ if (rhsGroup != null) {
+ merged.add(rhsGroup);
+ }
+ }
+ while (lhsChild.hasNext()) {
+ merged.add(lhsChild.next());
+ }
+ while (rhsChild.hasNext()) {
+ merged.add(rhsChild.next());
+ }
+ children = merged;
+ }
+
+ private void executeOrderBy() {
+ for (ExpressionNode node : orderByExp) {
+ node.prepare();
+ node.execute();
+ }
+ }
+
+ /**
+ * <p>After merging, this method will prune all levels so that they do not exceed the configured maximum number of
+ * groups per level.</p>
+ *
+ * @param levels The specs of all grouping levels.
+ * @param firstLevel The first level to merge.
+ * @param currentLevel The current level.
+ */
+ public void postMerge(List<GroupingLevel> levels, int firstLevel, int currentLevel) {
+ if (currentLevel >= firstLevel) {
+ for (AggregationResult result : aggregationResults) {
+ result.postMerge();
+ }
+ for (ExpressionNode result : orderByExp) {
+ result.execute();
+ }
+ }
+ if (currentLevel < levels.size()) {
+ int maxGroups = (int)levels.get(currentLevel).getMaxGroups();
+ for (Group group : children) {
+ group.executeOrderBy();
+ }
+ if (maxGroups >= 0 && children.size() > maxGroups) {
+ // prune groups
+ sortChildrenByRank();
+ children = children.subList(0, maxGroups);
+ sortChildrenById();
+ }
+ for (Group group : children) {
+ group.postMerge(levels, firstLevel, currentLevel + 1);
+ }
+ }
+
+ }
+
+ /**
+ * <p>Will sort the children by their id, if they are not sorted already.</p>
+ */
+ public void sortChildrenById() {
+ if (sortType == SortType.BYID) {
+ return;
+ }
+ Collections.sort(children, new Comparator<Group>() {
+ public int compare(Group lhs, Group rhs) {
+ return lhs.compareId(rhs);
+ }
+ });
+ sortType = SortType.BYID;
+ }
+
+ /**
+ * <p>Will sort the children by their rank, if they are not sorted already.</p>
+ */
+ public void sortChildrenByRank() {
+ if (sortType == SortType.BYRANK) {
+ return;
+ }
+ Collections.sort(children, new Comparator<Group>() {
+ public int compare(Group lhs, Group rhs) {
+ return lhs.compareRank(rhs);
+ }
+ });
+ sortType = SortType.BYRANK;
+ }
+
+ /**
+ * <p>Returns the label to use for this group. See comment on {@link #setId(com.yahoo.searchlib.expression.ResultNode)}
+ * on the rationale of this being a {@link ResultNode}.</p>
+ *
+ * @return The label.
+ */
+ public ResultNode getId() {
+ return id;
+ }
+
+ /**
+ * <p>Sets the label to use for this group. This is a {@link ResultNode} so that a group can be labeled with
+ * whatever value the classifier expression returns.</p>
+ *
+ * @param id The label to set.
+ * @return This, to allow chaining.
+ */
+ public Group setId(ResultNode id) {
+ this.id = id;
+ return this;
+ }
+
+ /**
+ * <p>Sets the relevancy to use for this group.</p>
+ *
+ * @param rank The rank to set.
+ * @return This, to allow chaining.
+ */
+ public Group setRank(double rank) {
+ this.rank = rank;
+ return this;
+ }
+
+ /**
+ * <p>Return the relevancy of this group.</p>
+ *
+ * @return Relevance.
+ */
+ public double getRank() {
+ return rank;
+ }
+
+ /**
+ * <p>Adds a child group to this.</p>
+ *
+ * @param child The group to add.
+ * @return This, to allow chaining.
+ */
+ public Group addChild(Group child) {
+ if (child == null) {
+ throw new IllegalArgumentException("Child can not be null.");
+ }
+ children.add(child);
+ return this;
+ }
+
+ /**
+ * <p>Returns the list of child groups to this.</p>
+ *
+ * @return The children.
+ */
+ public List<Group> getChildren() {
+ return children;
+ }
+
+ /**
+ * <p>Returns the tag of this group. This value is set per-level in the grouping request, and then becomes assigned
+ * to each group of that level in the grouping result as they are copied from the prototype.</p>
+ *
+ * @return The numerical tag.
+ */
+ public int getTag() {
+ return tag;
+ }
+
+ /**
+ * <p>Assigns a tag to this group.</p>
+ *
+ * @param tag The numerical tag to set.
+ * @return This, to allow chaining.
+ */
+ public Group setTag(int tag) {
+ this.tag = tag;
+ return this;
+ }
+
+ /**
+ * <p>Returns this group's aggregation results.</p>
+ *
+ * @return The aggregation results.
+ */
+ public List<AggregationResult> getAggregationResults() {
+ return aggregationResults;
+ }
+
+ /**
+ * <p>Adds an aggregation result to this group.</p>
+ *
+ * @param result The result to add.
+ * @return This, to allow chaining.
+ */
+ public Group addAggregationResult(AggregationResult result) {
+ aggregationResults.add(result);
+ return this;
+ }
+
+ /**
+ * <p>Adds an order-by expression to this group. If the expression is an AggregationResult, it will be added to the
+ * list of this group's AggregationResults, and a reference to that expression is added instead. If the
+ * AggregationResult is already present, a reference to THAT result is created instead.</p>
+ *
+ * @param exp The result to add.
+ * @param asc True to sort ascending, false to sort descending.
+ * @return This, to allow chaining.
+ */
+ public Group addOrderBy(ExpressionNode exp, boolean asc) {
+ if (exp instanceof AggregationResult) {
+ exp = new AggregationRefNode((AggregationResult)exp);
+ }
+ exp.select(REF_LOCATOR, new RefResolver(this));
+ orderByExp.add(exp);
+ orderByIdx.add((asc ? 1 : -1) * orderByExp.size());
+ return this;
+ }
+
+ public List<Integer> getOrderByIndexes() {
+ return Collections.unmodifiableList(orderByIdx);
+ }
+
+ public List<ExpressionNode> getOrderByExpressions() {
+ return Collections.unmodifiableList(orderByExp);
+ }
+
+ private int compareId(Group rhs) {
+ return getId().compareTo(rhs.getId());
+ }
+
+ private int compareRank(Group rhs) {
+ long diff = 0;
+ for (int i = 0, m = orderByIdx.size(); (diff == 0) && (i < m); i++) {
+ int rawIndex = orderByIdx.get(i);
+ int index = ((rawIndex < 0) ? -rawIndex : rawIndex) - 1;
+ diff = orderByExp.get(index).getResult().compareTo(rhs.orderByExp.get(index).getResult());
+ diff = diff * rawIndex;
+ }
+ if (diff < 0) {
+ return -1;
+ }
+ if (diff > 0) {
+ return 1;
+ }
+ if (rank > rhs.rank) {
+ return -1;
+ }
+ if (rank < rhs.rank) {
+ return 1;
+ }
+ return 0;
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ serializeOptional(buf, id);
+ buf.putDouble(null, rank);
+ int sz = orderByIdx.size();
+ buf.putInt(null, sz);
+ for (Integer index : orderByIdx) {
+ buf.putInt(null, index);
+ }
+ int numResults = aggregationResults.size();
+ buf.putInt(null, numResults);
+ for (AggregationResult a : aggregationResults) {
+ serializeOptional(buf, a);
+ }
+ int numExpressionResults = orderByExp.size();
+ buf.putInt(null, numExpressionResults);
+ for (ExpressionNode e : orderByExp) {
+ serializeOptional(buf, e);
+ }
+ int numGroups = children.size();
+ buf.putInt(null, numGroups);
+ for (Group g : children) {
+ g.serializeWithId(buf);
+ }
+ buf.putInt(null, tag);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ id = (ResultNode)deserializeOptional(buf);
+ rank = buf.getDouble(null);
+ orderByIdx.clear();
+ int orderByCount = buf.getInt(null);
+ for (int i = 0; i < orderByCount; i++) {
+ orderByIdx.add(buf.getInt(null));
+ }
+ int numResults = buf.getInt(null);
+ for (int i = 0; i < numResults; i++) {
+ AggregationResult e = (AggregationResult)deserializeOptional(buf);
+ aggregationResults.add(e);
+ }
+ int numExpressionResults = buf.getInt(null);
+ RefResolver resolver = new RefResolver(this);
+ for (int i = 0; i < numExpressionResults; i++) {
+ ExpressionNode exp = (ExpressionNode)deserializeOptional(buf);
+ exp.select(REF_LOCATOR, resolver);
+ orderByExp.add(exp);
+ }
+ int numGroups = buf.getInt(null);
+ for (int i = 0; i < numGroups; i++) {
+ Group g = new Group();
+ g.deserializeWithId(buf);
+ children.add(g);
+ }
+ tag = buf.getInt(null);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + aggregationResults.hashCode() + children.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!super.equals(obj)) {
+ return false;
+ }
+ Group rhs = (Group)obj;
+ if (!equals(id, rhs.id)) {
+ return false;
+ }
+ if (rank != rhs.rank) {
+ return false;
+ }
+ if (!aggregationResults.equals(rhs.aggregationResults)) {
+ return false;
+ }
+ if (!orderByIdx.equals(rhs.orderByIdx)) {
+ return false;
+ }
+ if (!orderByExp.equals(rhs.orderByExp)) {
+ return false;
+ }
+ if (!children.equals(rhs.children)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public Group clone() {
+ Group obj = (Group)super.clone();
+ if (id != null) {
+ obj.id = (ResultNode)id.clone();
+ }
+ obj.aggregationResults = new ArrayList<AggregationResult>();
+ for (AggregationResult result : aggregationResults) {
+ obj.aggregationResults.add(result.clone());
+ }
+ obj.orderByIdx = new ArrayList<Integer>();
+ for (Integer idx : orderByIdx) {
+ obj.orderByIdx.add(idx);
+ }
+ obj.orderByExp = new ArrayList<ExpressionNode>();
+ RefResolver resolver = new RefResolver(obj);
+ for (ExpressionNode exp : orderByExp) {
+ exp = exp.clone();
+ exp.select(REF_LOCATOR, resolver);
+ obj.orderByExp.add(exp);
+ }
+ obj.children = new ArrayList<Group>();
+ for (Group child : children) {
+ obj.children.add(child.clone());
+ }
+ return obj;
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("id", id);
+ visitor.visit("rank", rank);
+ visitor.visit("aggregationresults", aggregationResults);
+ visitor.visit("orderby-idx", orderByIdx);
+ visitor.visit("orderby-exp", orderByExp);
+ visitor.visit("children", children);
+ visitor.visit("tag", tag);
+ }
+
+ @Override
+ public void selectMembers(ObjectPredicate predicate, ObjectOperation operation) {
+ for (AggregationResult result : aggregationResults) {
+ result.select(predicate, operation);
+ }
+ for (ExpressionNode exp : orderByExp) {
+ exp.select(predicate, operation);
+ }
+ }
+
+ private static enum SortType {
+ UNSORTED,
+ BYRANK,
+ BYID
+ }
+
+ private static class RefLocator implements ObjectPredicate {
+
+ @Override
+ public boolean check(Object obj) {
+ return obj instanceof AggregationRefNode;
+ }
+ }
+
+ private static class RefResolver implements ObjectOperation {
+
+ final List<AggregationResult> results;
+
+ RefResolver(Group group) {
+ this.results = group.aggregationResults;
+ }
+
+ @Override
+ public void execute(Object obj) {
+ AggregationRefNode ref = (AggregationRefNode)obj;
+ int idx = ref.getIndex();
+ if (idx < 0) {
+ AggregationResult res = ref.getExpression();
+ idx = indexOf(res);
+ if (idx < 0) {
+ idx = results.size();
+ results.add(res);
+ }
+ ref.setIndex(idx);
+ } else {
+ ref.setExpression(results.get(idx));
+ }
+ }
+
+ int indexOf(AggregationResult lhs) {
+ int prevTag = lhs.getTag();
+ for (int i = 0, len = results.size(); i < len; ++i) {
+ AggregationResult rhs = results.get(i);
+ lhs.setTag(rhs.getTag());
+ if (lhs.equals(rhs)) {
+ return i;
+ }
+ }
+ lhs.setTag(prevTag);
+ return -1;
+ }
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/aggregation/Grouping.java b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/Grouping.java
new file mode 100644
index 00000000000..6e384e6e0b5
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/Grouping.java
@@ -0,0 +1,445 @@
+// 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.BucketResultNode;
+import com.yahoo.searchlib.expression.NullResultNode;
+import com.yahoo.searchlib.expression.ResultNode;
+import com.yahoo.vespa.objects.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class Grouping extends Identifiable {
+
+ // Force load all of expression and aggregation when using this class.
+ static {
+ com.yahoo.searchlib.aggregation.ForceLoad.forceLoad();
+ com.yahoo.searchlib.expression.ForceLoad.forceLoad();
+ }
+
+ // The global class identifier shared with C++.
+ public static final int classId = registerClass(0x4000 + 91, Grouping.class);
+
+ // The client id for this grouping request.
+ private int id = 0;
+
+ // Whether or not this grouping is valid.
+ private boolean valid = true;
+
+ // Whether or not to group all hits or only those with hits. Only applicable for streaming search.
+ private boolean all = false;
+
+ // How many hits to group per backend node.
+ private long topN = -1;
+
+ // The level to start grouping in backend. This also instantiates the next level, if any.
+ private int firstLevel = 0;
+
+ // The last level to group in backend.
+ private int lastLevel = 0;
+
+ private boolean forceSinglePass = false;
+
+ // Details for each level except root.
+ private List<GroupingLevel> groupingLevels = new ArrayList<>();
+
+ // Actual root group, does not require level details.
+ private Group root = new Group();
+
+ /**
+ * <p>Constructs an empty result node. <b>NOTE:</b> This instance is broken until non-optional member data is
+ * set.</p>
+ */
+ public Grouping() {
+ super();
+ }
+
+ /**
+ * <p>Constructs an instance of this class with given client id.</p>
+ *
+ * @param id The client id for this grouping request.
+ */
+ public Grouping(int id) {
+ super();
+ setId(id);
+ }
+
+ /**
+ * <p>Merges the content of the given grouping <b>into</b> this.</p>
+ *
+ * @param rhs The grouping to merge with.
+ */
+ public void merge(Grouping rhs) {
+ root.merge(firstLevel, 0, rhs.root);
+ }
+
+ /**
+ * <p>This method is invoked after merging is done. It is intended used for resolving any dependencies or derivates
+ * that might have changes due to the merge.</p>
+ */
+ public void postMerge() {
+ root.postMerge(groupingLevels, firstLevel, 0);
+ }
+
+ /**
+ * <p>Returns the client id of this grouping request.</p>
+ *
+ * @return The identifier.
+ */
+ public int getId() {
+ return id;
+ }
+
+ /**
+ * <p>Sets the client id for this grouping request.</p>
+ *
+ * @param id The identifier to set.
+ * @return This, to allow chaining.
+ */
+ public Grouping setId(int id) {
+ this.id = id;
+ return this;
+ }
+
+ /**
+ * <p>Returns whether or not this grouping request is valid.</p>
+ *
+ * @return True if valid.
+ */
+ public boolean valid() {
+ return valid;
+ }
+
+ /**
+ * <p>Returns whether or not to perform grouping on the entire document corpus instead of only those matching the
+ * search criteria. Please see note on {@link #setAll(boolean)}.</p>
+ *
+ * @return True if grouping all documents.
+ */
+ public boolean getAll() {
+ return all;
+ }
+
+ /**
+ * <p>Sets whether or not to perform grouping on the entire document corpus instead of only those matching the
+ * search criteria. <b>NOTE:</b> This is only possible with streaming search.</p>
+ *
+ * @param all True to group all documents.
+ * @return This, to allow chaining.
+ */
+ public Grouping setAll(boolean all) {
+ this.all = all;
+ return this;
+ }
+
+ /**
+ * <p>Returns the number of candidate documents to group.</p>
+ *
+ * @return The number.
+ */
+ public long getTopN() {
+ return topN;
+ }
+
+ /**
+ * <p>Sets the number of candidate documents to group.</p>
+ *
+ * @param topN The number to set.
+ * @return This, to allow chaining.
+ */
+ public Grouping setTopN(long topN) {
+ this.topN = topN;
+ return this;
+ }
+
+ /**
+ * <p>Returns the first level to start grouping work. See note on {@link #setFirstLevel(int)}.</p>
+ *
+ * @return The first level.
+ */
+ public int getFirstLevel() {
+ return firstLevel;
+ }
+
+ /**
+ * <p>Sets the first level to start grouping work. All the necessary work above this group level is expected to be
+ * already done.</p>
+ *
+ * @param level The level to set.
+ * @return This, to allow chaining.
+ */
+ public Grouping setFirstLevel(int level) {
+ firstLevel = level;
+ return this;
+ }
+
+ /**
+ * <p>Returns the last level to do grouping work. See note on {@link #setLastLevel(int)}.</p>
+ *
+ * @return The last level.
+ */
+ public int getLastLevel() {
+ return lastLevel;
+ }
+
+ /**
+ * <p>Sets the last level to do grouping work. Executing a level will instantiate the {@link Group} objects for the
+ * next level, if there is any. This means that grouping work ends at this level, but also instantiates the groups
+ * for level (lastLevel + 1).</p>
+ *
+ * @param level The level to set.
+ * @return This, to allow chaining.
+ */
+ public Grouping setLastLevel(int level) {
+ lastLevel = level;
+ return this;
+ }
+
+ /**
+ * <p>Returns the list of grouping levels that make up this grouping request.</p>
+ *
+ * @return The list.
+ */
+ public List<GroupingLevel> getLevels() {
+ return groupingLevels;
+ }
+
+ /**
+ * <p>Appends the given grouping level specification to the list of levels.</p>
+ *
+ * @param level The level to add.
+ * @return This, to allow chaining.
+ * @throws NullPointerException If <tt>level</tt> argument is null.
+ */
+ public Grouping addLevel(GroupingLevel level) {
+ level.getClass(); // throws NullPointerException
+ groupingLevels.add(level);
+ return this;
+ }
+
+ /**
+ * <p>Returns the root group.</p>
+ *
+ * @return The root.
+ */
+ public Group getRoot() {
+ return root;
+ }
+
+ /**
+ * <p>Sets the root group.</p>
+ *
+ * @param root The group to set as root.
+ * @return This, to allow chaining.
+ * @throws NullPointerException If <tt>root</tt> argument is null.
+ */
+ public Grouping setRoot(Group root) {
+ root.getClass(); // throws NullPointerException
+ this.root = root;
+ return this;
+ }
+
+ /**
+ * <p>Returns whether or not single pass execution of grouping is forced.</p>
+ *
+ * @return True if single pass grouping is forced.
+ */
+ public boolean getForceSinglePass() {
+ return forceSinglePass;
+ }
+
+ /**
+ * <p>Sets whether or not grouping should be forced to execute in a single pass. If false, this <tt>Grouping</tt>
+ * might still execute in a single pass due to other constraints.</p>
+ *
+ * @param forceSinglePass True to force execution in single pass.
+ * @return This, to allow chaining.
+ */
+ public Grouping setForceSinglePass(boolean forceSinglePass) {
+ this.forceSinglePass = forceSinglePass;
+ return this;
+ }
+
+ /**
+ * <p>Returns whether or not grouping should be executed in a single pass.</p>
+ *
+ * @return True if grouping should be executed in a single pass.
+ */
+ public boolean useSinglePass() {
+ return needDeepResultCollection() || getForceSinglePass();
+ }
+
+ /**
+ * <p>Tell if ordering will need results collected in children. in that case we will probably just do a single
+ * pass.</p>
+ *
+ * @return If deeper resultcollection is needed.
+ */
+ public boolean needDeepResultCollection() {
+ if (forceSinglePass) {
+ return true;
+ }
+ for (GroupingLevel level : groupingLevels) {
+ if (level.needResultCollection()) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ buf.putInt(null, id);
+ byte tmp = valid ? (byte)1 : (byte)0;
+ buf.putByte(null, tmp);
+ tmp = all ? (byte)1 : (byte)0;
+ buf.putByte(null, tmp);
+ buf.putLong(null, topN);
+ buf.putInt(null, firstLevel);
+ buf.putInt(null, lastLevel);
+ buf.putInt(null, groupingLevels.size());
+ for (GroupingLevel level : groupingLevels) {
+ level.serializeWithId(buf);
+ }
+ root.serializeWithId(buf);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ id = buf.getInt(null);
+ byte tmp = buf.getByte(null);
+ valid = (tmp != 0);
+ tmp = buf.getByte(null);
+ all = (tmp != 0);
+ topN = buf.getLong(null);
+ firstLevel = buf.getInt(null);
+ lastLevel = buf.getInt(null);
+ int numLevels = buf.getInt(null);
+ for (int i = 0; i < numLevels; i++) {
+ GroupingLevel level = new GroupingLevel();
+ level.deserializeWithId(buf);
+ groupingLevels.add(level);
+ }
+ root.deserializeWithId(buf);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + id + (valid ? 66 : 99) + (all ? 666 : 999) + (int)topN + groupingLevels.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!super.equals(obj)) {
+ return false;
+ }
+ Grouping rhs = (Grouping)obj;
+ if (id != rhs.id) {
+ return false;
+ }
+ if (valid != rhs.valid) {
+ return false;
+ }
+ if (all != rhs.all) {
+ return false;
+ }
+ if (topN != rhs.topN) {
+ return false;
+ }
+ if (firstLevel != rhs.firstLevel) {
+ return false;
+ }
+ if (lastLevel != rhs.lastLevel) {
+ return false;
+ }
+ if (!groupingLevels.equals(rhs.groupingLevels)) {
+ return false;
+ }
+ if (!root.equals(rhs.root)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public Grouping clone() {
+ Grouping obj = (Grouping)super.clone();
+ obj.groupingLevels = new ArrayList<>();
+ for (GroupingLevel level : groupingLevels) {
+ obj.groupingLevels.add(level.clone());
+ }
+ obj.root = root.clone();
+ return obj;
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("id", id);
+ visitor.visit("valid", valid);
+ visitor.visit("all", all);
+ visitor.visit("topN", topN);
+ visitor.visit("firstLevel", firstLevel);
+ visitor.visit("lastLevel", lastLevel);
+ visitor.visit("groupingLevels", groupingLevels);
+ visitor.visit("root", root);
+ }
+
+ @Override
+ public void selectMembers(ObjectPredicate predicate, ObjectOperation operation) {
+ selectGroups(predicate, operation, root, firstLevel, lastLevel, 0);
+ }
+
+ public void unifyNull() {
+ class FindGroup implements ObjectPredicate {
+
+ @Override
+ public boolean check(Object obj) {
+ return obj instanceof Group;
+ }
+ }
+ class UnifyNullGroupId implements ObjectOperation {
+
+ @Override
+ public void execute(Object obj) {
+ Group group = (Group)obj;
+ ResultNode id = group.getId();
+ if (id instanceof BucketResultNode && ((BucketResultNode)id).empty()) {
+ group.setId(new NullResultNode());
+ }
+ }
+ }
+ selectMembers(new FindGroup(), new UnifyNullGroupId());
+ }
+
+ /**
+ * <p>This is a helper function to perform recursive traversal of all groups contained in this grouping object. It
+ * is invoked by the {@link #selectMembers(ObjectPredicate, ObjectOperation)} method and itself. This method will
+ * only evaluate the groups that belong to active levels.</p>
+ *
+ * @param predicate The object predicate to evaluate.
+ * @param operation The operation to execute when the predicate is true.
+ * @param group The group to evaluate.
+ * @param first The first active level.
+ * @param last The last active level.
+ * @param current The level being evaluated.
+ */
+ private static void selectGroups(ObjectPredicate predicate, ObjectOperation operation,
+ Group group, int first, int last, int current)
+ {
+ if (current > last) {
+ return;
+ }
+ if (current >= first) {
+ group.select(predicate, operation);
+ }
+ for (Group child : group.getChildren()) {
+ selectGroups(predicate, operation, child, first, last, current + 1);
+ }
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/aggregation/GroupingLevel.java b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/GroupingLevel.java
new file mode 100644
index 00000000000..7e10507a57a
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/GroupingLevel.java
@@ -0,0 +1,184 @@
+// 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.ExpressionNode;
+import com.yahoo.vespa.objects.Deserializer;
+import com.yahoo.vespa.objects.Identifiable;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+public class GroupingLevel extends Identifiable {
+
+ // The global class identifier shared with C++.
+ public static final int classId = registerClass(0x4000 + 93, GroupingLevel.class);
+
+ // The maximum number of groups allowed at this level.
+ private long maxGroups = -1;
+
+ // The precsicion used for estimation. This is number of groups returned up when using orderby that need more info to get it correct.
+ private long precision = -1;
+
+ // The classifier expression; the result of this is the group key.
+ private ExpressionNode classify = null;
+
+ // The prototype of the groups to create for each class.
+ private Group collect = new Group();
+
+ /**
+ * <p>Returns the presicion (i.e number of groups) returned up from this level.</p>
+ *
+ * @return The precision.
+ */
+ public long getPrecision() {
+ return precision;
+ }
+
+ /**
+ * <p>Returns the maximum number of groups allowed at this level.</p>
+ *
+ * @return The maximum number.
+ */
+ public long getMaxGroups() {
+ return maxGroups;
+ }
+
+ /**
+ * <p>Sets the maximum number of groups allowed at this level.</p>
+ *
+ * @param max The maximum number to set.
+ * @return This, to allow chaining.
+ */
+ public GroupingLevel setMaxGroups(long max) {
+ maxGroups = max;
+ if (precision < maxGroups) {
+ precision = maxGroups;
+ }
+ return this;
+ }
+
+ /**
+ * <p>Sets the presicion (i.e number of groups) returned up from this level.</p>
+ *
+ * @param precision The precision to set.
+ * @return This, to allow chaining.
+ */
+ public GroupingLevel setPrecision(long precision) {
+ this.precision = precision;
+ return this;
+ }
+
+ /**
+ * <p>Returns the expression used to classify hits into groups.</p>
+ *
+ * @return The classifier expression.
+ */
+ public ExpressionNode getExpression() {
+ return classify;
+ }
+
+ /**
+ * <p>Sets the expression used to classify hits into groups.</p>
+ *
+ * @param exp The classifier expression to set.
+ * @return This, to allow chaining.
+ */
+ public GroupingLevel setExpression(ExpressionNode exp) {
+ classify = exp;
+ return this;
+ }
+
+ /**
+ * <p>Sets the prototype to use when creating groups at this level.</p>
+ *
+ * @param group The group prototype.
+ * @return This, to allow chaining.
+ */
+ public GroupingLevel setGroupPrototype(Group group) {
+ this.collect = group;
+ return this;
+ }
+
+ /**
+ * <p>Returns the prototype to use when creating groups at this level.</p>
+ *
+ * @return The group prototype.
+ */
+ public Group getGroupPrototype() {
+ return collect;
+ }
+
+ /**
+ * <p>Tell if ordering will need results collected in children.</p>
+ *
+ * @return If deeper resultcollection is needed.
+ */
+ public boolean needResultCollection() {
+ return !collect.isRankedByRelevance();
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ buf.putLong(null, maxGroups);
+ buf.putLong(null, precision);
+ serializeOptional(buf, classify);
+ collect.serializeWithId(buf);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ maxGroups = buf.getLong(null);
+ precision = buf.getLong(null);
+ classify = (ExpressionNode)deserializeOptional(buf);
+ collect.deserializeWithId(buf);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + (int)maxGroups + (int)precision + collect.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!super.equals(obj)) {
+ return false;
+ }
+ GroupingLevel rhs = (GroupingLevel)obj;
+ if (maxGroups != rhs.maxGroups) {
+ return false;
+ }
+ if (precision != rhs.precision) {
+ return false;
+ }
+ if (!equals(classify, rhs.classify)) {
+ return false;
+ }
+ if (!collect.equals(rhs.collect)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public GroupingLevel clone() {
+ GroupingLevel obj = (GroupingLevel)super.clone();
+ if (classify != null) {
+ obj.classify = classify.clone();
+ }
+ obj.collect = collect.clone();
+ return obj;
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("maxGroups", maxGroups);
+ visitor.visit("precision", precision);
+ visitor.visit("classify", classify);
+ visitor.visit("collect", collect);
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/aggregation/Hit.java b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/Hit.java
new file mode 100644
index 00000000000..8c5db8a6ecc
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/Hit.java
@@ -0,0 +1,104 @@
+// 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.vespa.objects.Deserializer;
+import com.yahoo.vespa.objects.Identifiable;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+/**
+ * This class represents a generic hit with a rank value. Actual hits are represented using subclasses of this class.
+ *
+ * @author <a href="mailto:havardpe@yahoo-inc.com">Haavard Pettersen</a>
+ */
+public abstract class Hit extends Identifiable {
+
+ public static final int classId = registerClass(0x4000 + 94, Hit.class); // shared with c++
+ private Object context = null;
+ private double rank = 0.0;
+
+ /**
+ * Constructs an empty result node.
+ */
+ public Hit() {
+ // empty
+ }
+
+ /**
+ * Create a new hit with the given rank
+ *
+ * @param rank generic rank value
+ */
+ public Hit(double rank) {
+ this.rank = rank;
+ }
+
+ /**
+ * Obtain the rank of this hit. This is a comparable rank to allow multilevel sorting on arbitrary rank type.
+ *
+ * @return generic rank value
+ */
+ public double getRank() {
+ return rank;
+ }
+
+ /**
+ * Returns the context object of this hit.
+ *
+ * @return The context object.
+ */
+ public Object getContext() {
+ return context;
+ }
+
+ /**
+ * Sets the context object of this hit. This is not serialized, and is merely a tag used by the QRS.
+ *
+ * @param context The context to set.
+ * @return This, to allow chaining.
+ */
+ public Hit setContext(Object context) {
+ this.context = context;
+ return this;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ buf.putDouble(null, rank);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ rank = buf.getDouble(null);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + (int)rank;
+ }
+
+ @SuppressWarnings({ "RedundantIfStatement", "EqualsWhichDoesntCheckParameterClass" })
+ @Override
+ public boolean equals(Object obj) {
+ if (!super.equals(obj)) {
+ return false;
+ }
+ Hit rhs = (Hit)obj;
+ if (rank != rhs.rank) {
+ return false;
+ }
+ if (!equals(context, rhs.context)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("rank", rank);
+ visitor.visit("context", context);
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/aggregation/HitsAggregationResult.java b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/HitsAggregationResult.java
new file mode 100644
index 00000000000..6d5d95bbcc0
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/HitsAggregationResult.java
@@ -0,0 +1,218 @@
+// 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.FloatResultNode;
+import com.yahoo.searchlib.expression.ResultNode;
+import com.yahoo.text.Utf8;
+import com.yahoo.vespa.objects.*;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+
+/**
+ * This is an aggregated result holding the top n hits for a single group.
+ *
+ * @author <a href="mailto:havardpe@yahoo-inc.com">Haavard Pettersen</a>
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class HitsAggregationResult extends AggregationResult {
+
+ public static final int classId = registerClass(0x4000 + 87, HitsAggregationResult.class);
+ private String summaryClass = "default";
+ private int maxHits = -1;
+ private List<Hit> hits = new ArrayList<Hit>();
+
+ /**
+ * Constructs an empty result node.
+ */
+ public HitsAggregationResult() {
+ // empty
+ }
+
+ /**
+ * Create a hits aggregation result that will collect the given number of hits
+ *
+ * @param maxHits maximum number of hits to collect
+ */
+ public HitsAggregationResult(int maxHits) {
+ this.maxHits = maxHits;
+ }
+
+ /**
+ * Create a hits aggregation result that will collect the given number of hits of the summaryClass asked.
+ *
+ * @param maxHits maximum number of hits to collect
+ * @param summaryClass SummaryClass to use for hits to collect
+ */
+ public HitsAggregationResult(int maxHits, String summaryClass) {
+ this.summaryClass = summaryClass;
+ this.maxHits = maxHits;
+ }
+
+ /**
+ * Obtain the summary class used to collect the hits.
+ *
+ * @return The summary class id.
+ */
+ public String getSummaryClass() {
+ return summaryClass;
+ }
+
+ /**
+ * Obtain the maximum number of hits to collect.
+ *
+ * @return Max number of hits to collect.
+ */
+ public int getMaxHits() {
+ return maxHits;
+ }
+
+ /**
+ * Sets the summary class of hits to collect.
+ *
+ * @param summaryClass The summary class to collect.
+ * @return This, to allow chaining.
+ */
+ public HitsAggregationResult setSummaryClass(String summaryClass) {
+ this.summaryClass = summaryClass;
+ return this;
+ }
+
+ /**
+ * Sets the maximum number of hits to collect.
+ *
+ * @param maxHits The number of hits to collect.
+ * @return This, to allow chaining.
+ */
+ public HitsAggregationResult setMaxHits(int maxHits) {
+ this.maxHits = maxHits;
+ return this;
+ }
+
+ /**
+ * Obtain the hits collected by this aggregation result
+ *
+ * @return collected hits
+ */
+ public List<Hit> getHits() {
+ return hits;
+ }
+
+ /**
+ * Add a hit to this aggregation result
+ *
+ * @param h the hit
+ * @return this object
+ */
+ public HitsAggregationResult addHit(Hit h) {
+ hits.add(h);
+ return this;
+ }
+
+ @Override
+ public ResultNode getRank() {
+ if (hits.isEmpty()) {
+ return new FloatResultNode(0);
+ }
+ return new FloatResultNode(hits.get(0).getRank());
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ byte[] raw = Utf8.toBytes(summaryClass);
+ buf.putInt(null, raw.length);
+ buf.put(null, raw);
+
+ buf.putInt(null, maxHits);
+ int numHits = hits.size();
+ buf.putInt(null, numHits);
+ for (Hit h : hits) {
+ serializeOptional(buf, h);
+ }
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ summaryClass = getUtf8(buf);
+ maxHits = buf.getInt(null);
+ int numHits = buf.getInt(null);
+ for (int i = 0; i < numHits; i++) {
+ Hit h = (Hit)deserializeOptional(buf);
+ hits.add(h);
+ }
+ }
+
+ @Override
+ protected void onMerge(AggregationResult result) {
+ hits.addAll(((HitsAggregationResult)result).hits);
+ }
+
+ @Override
+ public void postMerge() {
+ Collections.sort(hits, new Comparator<Hit>() {
+ public int compare(Hit lhs, Hit rhs) {
+ return (lhs.getRank() > rhs.getRank()) ? -1 : (lhs.getRank() < rhs.getRank()) ? 1 : 0;
+ }
+ });
+ if ((maxHits >= 0) && (hits.size() > maxHits)) {
+ hits = hits.subList(0, maxHits);
+ }
+ }
+
+ @Override
+ protected boolean equalsAggregation(AggregationResult obj) {
+ HitsAggregationResult rhs = (HitsAggregationResult)obj;
+ if (!summaryClass.equals(rhs.summaryClass)) {
+ return false;
+ }
+ if (maxHits != rhs.maxHits) {
+ return false;
+ }
+ if (!hits.equals(rhs.hits)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + summaryClass.hashCode() + maxHits + hits.hashCode();
+ }
+
+ @Override
+ public HitsAggregationResult clone() {
+ HitsAggregationResult obj = (HitsAggregationResult)super.clone();
+ obj.summaryClass = summaryClass;
+ obj.maxHits = maxHits;
+ obj.hits = new ArrayList<Hit>();
+ for (Hit hit : hits) {
+ obj.hits.add((Hit)hit.clone());
+ }
+ return obj;
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("summaryClass", summaryClass);
+ visitor.visit("maxHits", maxHits);
+ visitor.visit("hits", hits);
+ }
+
+ @Override
+ public void selectMembers(ObjectPredicate predicate, ObjectOperation operation) {
+ for (Hit hit : hits) {
+ hit.select(predicate, operation);
+ }
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/aggregation/MaxAggregationResult.java b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/MaxAggregationResult.java
new file mode 100644
index 00000000000..dba44dcf023
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/MaxAggregationResult.java
@@ -0,0 +1,103 @@
+// 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.ResultNode;
+import com.yahoo.searchlib.expression.SingleResultNode;
+import com.yahoo.vespa.objects.Deserializer;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+/**
+ * This is an aggregated result holding the maximum result of the matching hits.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class MaxAggregationResult extends AggregationResult {
+
+ public static final int classId = registerClass(0x4000 + 83, MaxAggregationResult.class);
+ private SingleResultNode max;
+
+ /**
+ * Constructs an empty result node. <b>NOTE:</b> This instance is broken until non-optional member data is set.
+ */
+ public MaxAggregationResult() {
+
+ }
+
+ /**
+ * Constructs an instance of this class with given max value.
+ *
+ * @param max The initial maximum to set.
+ */
+ public MaxAggregationResult(SingleResultNode max) {
+ setMax(max);
+ }
+
+ /**
+ * Returns the maximum value found in all matching hits.
+ *
+ * @return The value.
+ */
+ public final SingleResultNode getMax() {
+ return max;
+ }
+
+ /**
+ * Sets the maximum value found in all matching hits.
+ *
+ * @param max The value.
+ * @return This, to allow chaining.
+ */
+ public final MaxAggregationResult setMax(SingleResultNode max) {
+ this.max = max;
+ return this;
+ }
+
+ @Override
+ public ResultNode getRank() {
+ return max;
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ serializeOptional(buf, max);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ max = (SingleResultNode)deserializeOptional(buf);
+ }
+
+ @Override
+ protected void onMerge(AggregationResult result) {
+ max.max(((MaxAggregationResult)result).max);
+ }
+
+ @Override
+ protected boolean equalsAggregation(AggregationResult obj) {
+ return equals(max, ((MaxAggregationResult)obj).max);
+ }
+
+ @Override
+ public MaxAggregationResult clone() {
+ MaxAggregationResult obj = (MaxAggregationResult)super.clone();
+ if (max != null) {
+ obj.max = (SingleResultNode)max.clone();
+ }
+ return obj;
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("max", max);
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/aggregation/MinAggregationResult.java b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/MinAggregationResult.java
new file mode 100644
index 00000000000..ca8c71e6ede
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/MinAggregationResult.java
@@ -0,0 +1,103 @@
+// 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.ResultNode;
+import com.yahoo.searchlib.expression.SingleResultNode;
+import com.yahoo.vespa.objects.Deserializer;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+/**
+ * This is an aggregated result holding the minimum result of the matching hits.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class MinAggregationResult extends AggregationResult {
+
+ public static final int classId = registerClass(0x4000 + 84, MinAggregationResult.class);
+ private SingleResultNode min;
+
+ /**
+ * Constructs an empty result node. <b>NOTE:</b> This instance is broken until non-optional member data is set.
+ */
+ public MinAggregationResult() {
+
+ }
+
+ /**
+ * Constructs an instance of this class with given min value.
+ *
+ * @param min The initial minimum to set.
+ */
+ public MinAggregationResult(SingleResultNode min) {
+ setMin(min);
+ }
+
+ /**
+ * Returns the minimum value found in all matching hits.
+ *
+ * @return The value.
+ */
+ public final SingleResultNode getMin() {
+ return min;
+ }
+
+ /**
+ * Sets the minimum value found in all matching hits.
+ *
+ * @param min The value.
+ * @return This, to allow chaining.
+ */
+ public final MinAggregationResult setMin(SingleResultNode min) {
+ this.min = min;
+ return this;
+ }
+
+ @Override
+ public ResultNode getRank() {
+ return min;
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ serializeOptional(buf, min);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ min = (SingleResultNode)deserializeOptional(buf);
+ }
+
+ @Override
+ protected void onMerge(AggregationResult result) {
+ min.min(((MinAggregationResult)result).min);
+ }
+
+ @Override
+ protected boolean equalsAggregation(AggregationResult obj) {
+ return equals(min, ((MinAggregationResult)obj).min);
+ }
+
+ @Override
+ public MinAggregationResult clone() {
+ MinAggregationResult obj = (MinAggregationResult)super.clone();
+ if (min != null) {
+ obj.min = (SingleResultNode)min.clone();
+ }
+ return obj;
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("min", min);
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/aggregation/RawData.java b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/RawData.java
new file mode 100755
index 00000000000..7c9dd33477b
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/RawData.java
@@ -0,0 +1,130 @@
+// 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.vespa.objects.Deserializer;
+import com.yahoo.vespa.objects.Serializer;
+
+import java.util.Arrays;
+
+/**
+ * <p>This class encapsulates a byte array into a cloneable and comparable object. It also implements a sane {@link
+ * #hashCode()} and {@link #toString()}.</p>
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class RawData implements Cloneable, Comparable<RawData> {
+
+ private byte[] data;
+
+ /**
+ * <p>Constructs an empty data object.</p>
+ */
+ public RawData() {
+ data = new byte[0];
+ }
+
+ /**
+ * <p>Constructs a raw data object that holds the given byte array.</p>
+ *
+ * @param data The rank to set.
+ */
+ public RawData(byte[] data) {
+ setData(data);
+ }
+
+ /**
+ * <p>Serializes the content of this data into the given byte buffer.</p>
+ *
+ * @param buf The buffer to serialize to.
+ */
+ public void serialize(Serializer buf) {
+ buf.putInt(null, data.length);
+ buf.put(null, data);
+ }
+
+ /**
+ * <p>Deserializes the content for this data from the given byte buffer.</p>
+ *
+ * @param buf The buffer to deserialize from.
+ */
+ public void deserialize(Deserializer buf) {
+ int len = buf.getInt(null);
+ data = buf.getBytes(null, len);
+ }
+
+ /**
+ * <p>Returns the byte array that constitutes this data.</p>
+ *
+ * @return The byte array.
+ */
+ public byte[] getData() {
+ return data;
+ }
+
+ /**
+ * <p>Sets the byte array that constitutes this data. This does <b>not</b> copy the given array, it simply assigns
+ * it to this.</p>
+ *
+ * @param data The data to set.
+ * @return This, to allow chaining.
+ */
+ public RawData setData(byte[] data) {
+ if (data == null) {
+ throw new IllegalArgumentException("Data can not be null.");
+ }
+ this.data = data;
+ return this;
+ }
+
+ @Override
+ public int compareTo(RawData rhs) {
+ return compare(data, rhs.data);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof RawData)) {
+ return false;
+ }
+ RawData rhs = (RawData)obj;
+ if (!Arrays.equals(data, rhs.data)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(data);
+ }
+
+ @Override
+ public String toString() {
+ return "RawData(data = " + Arrays.toString(data) + ")";
+ }
+
+ @Override
+ public Object clone() {
+ return new RawData(Arrays.copyOf(data, data.length));
+ }
+
+ /**
+ * <p>Implements comparison of two byte arrays.</p>
+ *
+ * @param lhs The left-hand-side of the comparison.
+ * @param rhs The right-hand-side of the comparison.
+ * @return The result of comparing the two byte arrays.
+ */
+ public static int compare(byte[] lhs, byte[] rhs) {
+ int cmp = 0;
+ for (int i = 0, len = Math.min(lhs.length, rhs.length); (i < len) && (cmp == 0); i++) {
+ int a = lhs[i] & 0xFF;
+ int b = rhs[i] & 0xFF;
+ cmp = a - b;
+ }
+ if (cmp == 0) {
+ cmp = lhs.length - rhs.length;
+ }
+ return cmp;
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/aggregation/SumAggregationResult.java b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/SumAggregationResult.java
new file mode 100644
index 00000000000..88e61d98ba0
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/SumAggregationResult.java
@@ -0,0 +1,103 @@
+// 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.ResultNode;
+import com.yahoo.searchlib.expression.SingleResultNode;
+import com.yahoo.vespa.objects.Deserializer;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+/**
+ * This is an aggregated result holding the sum of the aggregating expression for all matching hits.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class SumAggregationResult extends AggregationResult {
+
+ public static final int classId = registerClass(0x4000 + 82, SumAggregationResult.class);
+ private SingleResultNode sum;
+
+ /**
+ * Constructs an empty result node. <b>NOTE:</b> This instance is broken until non-optional member data is set.
+ */
+ public SumAggregationResult() {
+
+ }
+
+ /**
+ * Constructs an instance of this class with given sum.
+ *
+ * @param sum The initial sum to set.
+ */
+ public SumAggregationResult(SingleResultNode sum) {
+ setSum(sum);
+ }
+
+ /**
+ * Returns the sum of all results in this.
+ *
+ * @return The numeric sum.
+ */
+ public final SingleResultNode getSum() {
+ return sum;
+ }
+
+ /**
+ * Sets the sum of all results in this.
+ *
+ * @param sum The sum to set.
+ * @return This, to allow chaining.
+ */
+ public final SumAggregationResult setSum(SingleResultNode sum) {
+ this.sum = sum;
+ return this;
+ }
+
+ @Override
+ public ResultNode getRank() {
+ return sum;
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ serializeOptional(buf, sum);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ sum = (SingleResultNode)deserializeOptional(buf);
+ }
+
+ @Override
+ protected void onMerge(AggregationResult result) {
+ sum.add(((SumAggregationResult)result).sum);
+ }
+
+ @Override
+ public SumAggregationResult clone() {
+ SumAggregationResult obj = (SumAggregationResult)super.clone();
+ if (sum != null) {
+ obj.sum = (SingleResultNode)sum.clone();
+ }
+ return obj;
+ }
+
+ @Override
+ protected boolean equalsAggregation(AggregationResult obj) {
+ return equals(sum, ((SumAggregationResult)obj).sum);
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("sum", sum);
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/aggregation/VdsHit.java b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/VdsHit.java
new file mode 100644
index 00000000000..adecdee8401
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/VdsHit.java
@@ -0,0 +1,91 @@
+// 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.text.Utf8;
+import com.yahoo.vespa.objects.Deserializer;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+public class VdsHit extends Hit {
+
+ public static final int classId = registerClass(0x4000 + 96, VdsHit.class);
+ private String docId = "";
+ private RawData summary = new RawData();
+
+ @SuppressWarnings("UnusedDeclaration")
+ public VdsHit() {
+ // user by deserializer
+ }
+
+ /**
+ * Create a hit with the given path and document id.
+ *
+ * @param summary The summary blob standard fs4 coding.
+ * @param docId The local document id.
+ * @param rank The rank of this hit.
+ */
+ public VdsHit(String docId, byte[] summary, double rank) {
+ super(rank);
+ this.docId = docId;
+ this.summary = new RawData(summary);
+ }
+
+ /**
+ * Obtain the summary blob for this hit.
+ *
+ * @return The summary blob.
+ */
+ public RawData getSummary() {
+ return summary;
+ }
+
+ /**
+ * Obtain the local document id of this hit.
+ *
+ * @return The local document id.
+ */
+ public String getDocId() {
+ return docId;
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ byte[] utf8 = Utf8.toBytes(docId);
+ buf.putInt(null, utf8.length);
+ buf.put(null, utf8);
+ summary.serialize(buf);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ docId = getUtf8(buf);
+ summary.deserialize(buf);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + docId.hashCode() + summary.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ VdsHit rhs = (VdsHit)obj;
+ return super.equals(obj) &&
+ docId.equals(rhs.docId) &&
+ summary.equals(rhs.summary);
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("docId", docId);
+ visitor.visit("summary", summary);
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/aggregation/XorAggregationResult.java b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/XorAggregationResult.java
new file mode 100644
index 00000000000..ee171be0c4b
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/XorAggregationResult.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.aggregation;
+
+import com.yahoo.searchlib.expression.IntegerResultNode;
+import com.yahoo.searchlib.expression.ResultNode;
+import com.yahoo.vespa.objects.Deserializer;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+/**
+ * This is an aggregated result holding the xor of the aggregating expression for all matching hits.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class XorAggregationResult extends AggregationResult {
+
+ public static final int classId = registerClass(0x4000 + 86, XorAggregationResult.class);
+ private long xor = 0;
+
+ /**
+ * Constructs an empty result node. <b>NOTE:</b> This instance is broken until non-optional member data is set.
+ */
+ public XorAggregationResult() {
+
+ }
+
+ /**
+ * Constructs an instance of this class with given xor value.
+ *
+ * @param xor The initial xor value to set.
+ */
+ public XorAggregationResult(long xor) {
+ setXor(xor);
+ }
+
+ /**
+ * Returns the current xor value.
+ *
+ * @return The value.
+ */
+ public long getXor() {
+ return xor;
+ }
+
+ /**
+ * Sets the current xor value.
+ *
+ * @param xor The value to set.
+ * @return This, to allow chaining.
+ */
+ public XorAggregationResult setXor(long xor) {
+ this.xor = xor;
+ return this;
+ }
+
+ @Override
+ public ResultNode getRank() {
+ return new IntegerResultNode(xor);
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ buf.putLong(null, xor);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ xor = buf.getLong(null);
+ }
+
+ @Override
+ protected void onMerge(AggregationResult result) {
+ xor = xor ^ ((XorAggregationResult)result).xor;
+ }
+
+ @Override
+ protected boolean equalsAggregation(AggregationResult obj) {
+ return xor == ((XorAggregationResult)obj).xor;
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + (int)xor;
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("xor", xor);
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/aggregation/hll/BiasEstimator.java b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/hll/BiasEstimator.java
new file mode 100644
index 00000000000..54651bdfae4
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/hll/BiasEstimator.java
@@ -0,0 +1,131 @@
+// 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.google.common.base.Preconditions;
+
+import java.util.Arrays;
+
+/**
+ * Performs bias correction for a given precision and raw estimate.
+ * The values are taken from Google's HLL++ paper:
+ * https://docs.google.com/document/d/1gyjfMHy43U9OWBXxfaeG-3MjGzejW1dlpyMwEYAAWEI/view?fullscreen#
+ *
+ * @author bjorncs
+ */
+public class BiasEstimator {
+ // Raw estimate data for given precision
+ private final double[] rawEstimateData;
+ // Raw bias data for a given precision
+ private final double[] biasData;
+
+ /**
+ * Constructs the BiasEstimator for a given HLL precision.
+ *
+ * @param precision HLL precision
+ */
+ public BiasEstimator(int precision) {
+ Preconditions.checkArgument(precision >= 4 && precision <= 18,
+ "Invalid precision: %s. Expected 4 <= precision <= 18.", precision);
+ this.rawEstimateData = rawEstimateDataAllPrecisions[precision - 4];
+ this.biasData = biasDataAllPrecisions[precision - 4];
+ }
+
+ /**
+ * Maps a given raw estimate to a bias correction value. The callee should subtract the bias from the raw estimate
+ * to get a bias corrected HLL estimate. Uses linear interpolation when no exact value exist.
+ *
+ * @param rawEstimate The raw HLL estimate
+ * @return The estimated bias for the given raw estimate.
+ */
+ public double estimateBias(double rawEstimate) {
+ int index = Arrays.binarySearch(rawEstimateData, rawEstimate);
+ // Check if the value is in rawEstimate or not.
+ if (index >= 0) {
+ return biasData[index];
+ } else {
+ int insertionIndex = -index - 1;
+ if (insertionIndex == 0) {
+ return biasData[0];
+ } else if (insertionIndex == biasData.length) {
+ return biasData[biasData.length - 1];
+ } else {
+ //Perform linear interpolation
+ double x0 = rawEstimateData[insertionIndex - 1];
+ double x1 = rawEstimateData[insertionIndex];
+ double f0 = biasData[insertionIndex - 1];
+ double f1 = biasData[insertionIndex];
+ return linearInterpolationOf(x0, x1, f0, f1, rawEstimate);
+ }
+ }
+ }
+
+ private static double linearInterpolationOf(double x0, double x1, double f0, double f1, double x) {
+ return f0 + (f1 - f0) / (x1 - x0) * (x - x0);
+ }
+
+ private static final double[][] rawEstimateDataAllPrecisions = {
+ // precision 4
+ { 11, 11.717, 12.207, 12.7896, 13.2882, 13.8204, 14.3772, 14.9342, 15.5202, 16.161, 16.7722, 17.4636, 18.0396, 18.6766, 19.3566, 20.0454, 20.7936, 21.4856, 22.2666, 22.9946, 23.766, 24.4692, 25.3638, 26.0764, 26.7864, 27.7602, 28.4814, 29.433, 30.2926, 31.0664, 31.9996, 32.7956, 33.5366, 34.5894, 35.5738, 36.2698, 37.3682, 38.0544, 39.2342, 40.0108, 40.7966, 41.9298, 42.8704, 43.6358, 44.5194, 45.773, 46.6772, 47.6174, 48.4888, 49.3304, 50.2506, 51.4996, 52.3824, 53.3078, 54.3984, 55.5838, 56.6618, 57.2174, 58.3514, 59.0802, 60.1482, 61.0376, 62.3598, 62.8078, 63.9744, 64.914, 65.781, 67.1806, 68.0594, 68.8446, 69.7928, 70.8248, 71.8324, 72.8598, 73.6246, 74.7014, 75.393, 76.6708, 77.2394, },
+ // precision 5
+ { 23, 23.1194, 23.8208, 24.2318, 24.77, 25.2436, 25.7774, 26.2848, 26.8224, 27.3742, 27.9336, 28.503, 29.0494, 29.6292, 30.2124, 30.798, 31.367, 31.9728, 32.5944, 33.217, 33.8438, 34.3696, 35.0956, 35.7044, 36.324, 37.0668, 37.6698, 38.3644, 39.049, 39.6918, 40.4146, 41.082, 41.687, 42.5398, 43.2462, 43.857, 44.6606, 45.4168, 46.1248, 46.9222, 47.6804, 48.447, 49.3454, 49.9594, 50.7636, 51.5776, 52.331, 53.19, 53.9676, 54.7564, 55.5314, 56.4442, 57.3708, 57.9774, 58.9624, 59.8796, 60.755, 61.472, 62.2076, 63.1024, 63.8908, 64.7338, 65.7728, 66.629, 67.413, 68.3266, 69.1524, 70.2642, 71.1806, 72.0566, 72.9192, 73.7598, 74.3516, 75.5802, 76.4386, 77.4916, 78.1524, 79.1892, 79.8414, 80.8798, 81.8376, 82.4698, 83.7656, 84.331, 85.5914, 86.6012, 87.7016, 88.5582, 89.3394, 90.3544, 91.4912, 92.308, 93.3552, 93.9746, 95.2052, 95.727, 97.1322, 98.3944, 98.7588, 100.242, 101.1914, 102.2538, 102.8776, 103.6292, 105.1932, 105.9152, 107.0868, 107.6728, 108.7144, 110.3114, 110.8716, 111.245, 112.7908, 113.7064, 114.636, 115.7464, 116.1788, 117.7464, 118.4896, 119.6166, 120.5082, 121.7798, 122.9028, 123.4426, 124.8854, 125.705, 126.4652, 128.3464, 128.3462, 130.0398, 131.0342, 131.0042, 132.4766, 133.511, 134.7252, 135.425, 136.5172, 138.0572, 138.6694, 139.3712, 140.8598, 141.4594, 142.554, 143.4006, 144.7374, 146.1634, 146.8994, 147.605, 147.9304, 149.1636, 150.2468, 151.5876, 152.2096, 153.7032, 154.7146, 155.807, 156.9228, 157.0372, 158.5852, },
+ // precision 6
+ { 46, 46.1902, 47.271, 47.8358, 48.8142, 49.2854, 50.317, 51.354, 51.8924, 52.9436, 53.4596, 54.5262, 55.6248, 56.1574, 57.2822, 57.837, 58.9636, 60.074, 60.7042, 61.7976, 62.4772, 63.6564, 64.7942, 65.5004, 66.686, 67.291, 68.5672, 69.8556, 70.4982, 71.8204, 72.4252, 73.7744, 75.0786, 75.8344, 77.0294, 77.8098, 79.0794, 80.5732, 81.1878, 82.5648, 83.2902, 84.6784, 85.3352, 86.8946, 88.3712, 89.0852, 90.499, 91.2686, 92.6844, 94.2234, 94.9732, 96.3356, 97.2286, 98.7262, 100.3284, 101.1048, 102.5962, 103.3562, 105.1272, 106.4184, 107.4974, 109.0822, 109.856, 111.48, 113.2834, 114.0208, 115.637, 116.5174, 118.0576, 119.7476, 120.427, 122.1326, 123.2372, 125.2788, 126.6776, 127.7926, 129.1952, 129.9564, 131.6454, 133.87, 134.5428, 136.2, 137.0294, 138.6278, 139.6782, 141.792, 143.3516, 144.2832, 146.0394, 147.0748, 148.4912, 150.849, 151.696, 153.5404, 154.073, 156.3714, 157.7216, 158.7328, 160.4208, 161.4184, 163.9424, 165.2772, 166.411, 168.1308, 168.769, 170.9258, 172.6828, 173.7502, 175.706, 176.3886, 179.0186, 180.4518, 181.927, 183.4172, 184.4114, 186.033, 188.5124, 189.5564, 191.6008, 192.4172, 193.8044, 194.997, 197.4548, 198.8948, 200.2346, 202.3086, 203.1548, 204.8842, 206.6508, 206.6772, 209.7254, 210.4752, 212.7228, 214.6614, 215.1676, 217.793, 218.0006, 219.9052, 221.66, 223.5588, 225.1636, 225.6882, 227.7126, 229.4502, 231.1978, 232.9756, 233.1654, 236.727, 238.1974, 237.7474, 241.1346, 242.3048, 244.1948, 245.3134, 246.879, 249.1204, 249.853, 252.6792, 253.857, 254.4486, 257.2362, 257.9534, 260.0286, 260.5632, 262.663, 264.723, 265.7566, 267.2566, 267.1624, 270.62, 272.8216, 273.2166, 275.2056, 276.2202, 278.3726, 280.3344, 281.9284, 283.9728, 284.1924, 286.4872, 287.587, 289.807, 291.1206, 292.769, 294.8708, 296.665, 297.1182, 299.4012, 300.6352, 302.1354, 304.1756, 306.1606, 307.3462, 308.5214, 309.4134, 310.8352, 313.9684, 315.837, 316.7796, 318.9858, },
+ // precision 7
+ { 92, 93.4934, 94.9758, 96.4574, 97.9718, 99.4954, 101.5302, 103.0756, 104.6374, 106.1782, 107.7888, 109.9522, 111.592, 113.2532, 114.9086, 116.5938, 118.9474, 120.6796, 122.4394, 124.2176, 125.9768, 128.4214, 130.2528, 132.0102, 133.8658, 135.7278, 138.3044, 140.1316, 142.093, 144.0032, 145.9092, 148.6306, 150.5294, 152.5756, 154.6508, 156.662, 159.552, 161.3724, 163.617, 165.5754, 167.7872, 169.8444, 172.7988, 174.8606, 177.2118, 179.3566, 181.4476, 184.5882, 186.6816, 189.0824, 191.0258, 193.6048, 196.4436, 198.7274, 200.957, 203.147, 205.4364, 208.7592, 211.3386, 213.781, 215.8028, 218.656, 221.6544, 223.996, 226.4718, 229.1544, 231.6098, 234.5956, 237.0616, 239.5758, 242.4878, 244.5244, 248.2146, 250.724, 252.8722, 255.5198, 258.0414, 261.941, 264.9048, 266.87, 269.4304, 272.028, 274.4708, 278.37, 281.0624, 283.4668, 286.5532, 289.4352, 293.2564, 295.2744, 298.2118, 300.7472, 304.1456, 307.2928, 309.7504, 312.5528, 315.979, 318.2102, 322.1834, 324.3494, 327.325, 330.6614, 332.903, 337.2544, 339.9042, 343.215, 345.2864, 348.0814, 352.6764, 355.301, 357.139, 360.658, 363.1732, 366.5902, 369.9538, 373.0828, 375.922, 378.9902, 382.7328, 386.4538, 388.1136, 391.2234, 394.0878, 396.708, 401.1556, 404.1852, 406.6372, 409.6822, 412.7796, 416.6078, 418.4916, 422.131, 424.5376, 428.1988, 432.211, 434.4502, 438.5282, 440.912, 444.0448, 447.7432, 450.8524, 453.7988, 456.7858, 458.8868, 463.9886, 466.5064, 468.9124, 472.6616, 475.4682, 478.582, 481.304, 485.2738, 488.6894, 490.329, 496.106, 497.6908, 501.1374, 504.5322, 506.8848, 510.3324, 513.4512, 516.179, 520.4412, 522.6066, 526.167, 528.7794, 533.379, 536.067, 538.46, 542.9116, 545.692, 547.9546, 552.493, 555.2722, 557.335, 562.449, 564.2014, 569.0738, 571.0974, 574.8564, 578.2996, 581.409, 583.9704, 585.8098, 589.6528, 594.5998, 595.958, 600.068, 603.3278, 608.2016, 609.9632, 612.864, 615.43, 620.7794, 621.272, 625.8644, 629.206, 633.219, 634.5154, 638.6102, },
+ // precision 8
+ { 184.2152, 187.2454, 190.2096, 193.6652, 196.6312, 199.6822, 203.249, 206.3296, 210.0038, 213.2074, 216.4612, 220.27, 223.5178, 227.4412, 230.8032, 234.1634, 238.1688, 241.6074, 245.6946, 249.2664, 252.8228, 257.0432, 260.6824, 264.9464, 268.6268, 272.2626, 276.8376, 280.4034, 284.8956, 288.8522, 292.7638, 297.3552, 301.3556, 305.7526, 309.9292, 313.8954, 318.8198, 322.7668, 327.298, 331.6688, 335.9466, 340.9746, 345.1672, 349.3474, 354.3028, 358.8912, 364.114, 368.4646, 372.9744, 378.4092, 382.6022, 387.843, 392.5684, 397.1652, 402.5426, 407.4152, 412.5388, 417.3592, 422.1366, 427.486, 432.3918, 437.5076, 442.509, 447.3834, 453.3498, 458.0668, 463.7346, 469.1228, 473.4528, 479.7, 484.644, 491.0518, 495.5774, 500.9068, 506.432, 512.1666, 517.434, 522.6644, 527.4894, 533.6312, 538.3804, 544.292, 550.5496, 556.0234, 562.8206, 566.6146, 572.4188, 579.117, 583.6762, 590.6576, 595.7864, 601.509, 607.5334, 612.9204, 619.772, 624.2924, 630.8654, 636.1836, 642.745, 649.1316, 655.0386, 660.0136, 666.6342, 671.6196, 678.1866, 684.4282, 689.3324, 695.4794, 702.5038, 708.129, 713.528, 720.3204, 726.463, 732.7928, 739.123, 744.7418, 751.2192, 756.5102, 762.6066, 769.0184, 775.2224, 781.4014, 787.7618, 794.1436, 798.6506, 805.6378, 811.766, 819.7514, 824.5776, 828.7322, 837.8048, 843.6302, 849.9336, 854.4798, 861.3388, 867.9894, 873.8196, 880.3136, 886.2308, 892.4588, 899.0816, 905.4076, 912.0064, 917.3878, 923.619, 929.998, 937.3482, 943.9506, 947.991, 955.1144, 962.203, 968.8222, 975.7324, 981.7826, 988.7666, 994.2648, 1000.3128, 1007.4082, 1013.7536, 1020.3376, 1026.7156, 1031.7478, 1037.4292, 1045.393, 1051.2278, 1058.3434, 1062.8726, 1071.884, 1076.806, 1082.9176, 1089.1678, 1095.5032, 1102.525, 1107.2264, 1115.315, 1120.93, 1127.252, 1134.1496, 1139.0408, 1147.5448, 1153.3296, 1158.1974, 1166.5262, 1174.3328, 1175.657, 1184.4222, 1190.9172, 1197.1292, 1204.4606, 1210.4578, 1218.8728, 1225.3336, 1226.6592, 1236.5768, 1241.363, 1249.4074, 1254.6566, 1260.8014, 1266.5454, 1274.5192, },
+ // precision 9
+ { 369, 374.8294, 381.2452, 387.6698, 394.1464, 400.2024, 406.8782, 413.6598, 420.462, 427.2826, 433.7102, 440.7416, 447.9366, 455.1046, 462.285, 469.0668, 476.306, 483.8448, 491.301, 498.9886, 506.2422, 513.8138, 521.7074, 529.7428, 537.8402, 545.1664, 553.3534, 561.594, 569.6886, 577.7876, 585.65, 594.228, 602.8036, 611.1666, 620.0818, 628.0824, 637.2574, 646.302, 655.1644, 664.0056, 672.3802, 681.7192, 690.5234, 700.2084, 708.831, 718.485, 728.1112, 737.4764, 746.76, 756.3368, 766.5538, 775.5058, 785.2646, 795.5902, 804.3818, 814.8998, 824.9532, 835.2062, 845.2798, 854.4728, 864.9582, 875.3292, 886.171, 896.781, 906.5716, 916.7048, 927.5322, 937.875, 949.3972, 958.3464, 969.7274, 980.2834, 992.1444, 1003.4264, 1013.0166, 1024.018, 1035.0438, 1046.34, 1057.6856, 1068.9836, 1079.0312, 1091.677, 1102.3188, 1113.4846, 1124.4424, 1135.739, 1147.1488, 1158.9202, 1169.406, 1181.5342, 1193.2834, 1203.8954, 1216.3286, 1226.2146, 1239.6684, 1251.9946, 1262.123, 1275.4338, 1285.7378, 1296.076, 1308.9692, 1320.4964, 1333.0998, 1343.9864, 1357.7754, 1368.3208, 1380.4838, 1392.7388, 1406.0758, 1416.9098, 1428.9728, 1440.9228, 1453.9292, 1462.617, 1476.05, 1490.2996, 1500.6128, 1513.7392, 1524.5174, 1536.6322, 1548.2584, 1562.3766, 1572.423, 1587.1232, 1596.5164, 1610.5938, 1622.5972, 1633.1222, 1647.7674, 1658.5044, 1671.57, 1683.7044, 1695.4142, 1708.7102, 1720.6094, 1732.6522, 1747.841, 1756.4072, 1769.9786, 1782.3276, 1797.5216, 1808.3186, 1819.0694, 1834.354, 1844.575, 1856.2808, 1871.1288, 1880.7852, 1893.9622, 1906.3418, 1920.6548, 1932.9302, 1945.8584, 1955.473, 1968.8248, 1980.6446, 1995.9598, 2008.349, 2019.8556, 2033.0334, 2044.0206, 2059.3956, 2069.9174, 2082.6084, 2093.7036, 2106.6108, 2118.9124, 2132.301, 2144.7628, 2159.8422, 2171.0212, 2183.101, 2193.5112, 2208.052, 2221.3194, 2233.3282, 2247.295, 2257.7222, 2273.342, 2286.5638, 2299.6786, 2310.8114, 2322.3312, 2335.516, 2349.874, 2363.5968, 2373.865, 2387.1918, 2401.8328, 2414.8496, 2424.544, 2436.7592, 2447.1682, 2464.1958, 2474.3438, 2489.0006, 2497.4526, 2513.6586, 2527.19, 2540.7028, 2553.768, },
+ // precision 10
+ { 738.1256, 750.4234, 763.1064, 775.4732, 788.4636, 801.0644, 814.488, 827.9654, 841.0832, 854.7864, 868.1992, 882.2176, 896.5228, 910.1716, 924.7752, 938.899, 953.6126, 968.6492, 982.9474, 998.5214, 1013.1064, 1028.6364, 1044.2468, 1059.4588, 1075.3832, 1091.0584, 1106.8606, 1123.3868, 1139.5062, 1156.1862, 1172.463, 1189.339, 1206.1936, 1223.1292, 1240.1854, 1257.2908, 1275.3324, 1292.8518, 1310.5204, 1328.4854, 1345.9318, 1364.552, 1381.4658, 1400.4256, 1419.849, 1438.152, 1456.8956, 1474.8792, 1494.118, 1513.62, 1532.5132, 1551.9322, 1570.7726, 1590.6086, 1610.5332, 1630.5918, 1650.4294, 1669.7662, 1690.4106, 1710.7338, 1730.9012, 1750.4486, 1770.1556, 1791.6338, 1812.7312, 1833.6264, 1853.9526, 1874.8742, 1896.8326, 1918.1966, 1939.5594, 1961.07, 1983.037, 2003.1804, 2026.071, 2047.4884, 2070.0848, 2091.2944, 2114.333, 2135.9626, 2158.2902, 2181.0814, 2202.0334, 2224.4832, 2246.39, 2269.7202, 2292.1714, 2314.2358, 2338.9346, 2360.891, 2384.0264, 2408.3834, 2430.1544, 2454.8684, 2476.9896, 2501.4368, 2522.8702, 2548.0408, 2570.6738, 2593.5208, 2617.0158, 2640.2302, 2664.0962, 2687.4986, 2714.2588, 2735.3914, 2759.6244, 2781.8378, 2808.0072, 2830.6516, 2856.2454, 2877.2136, 2903.4546, 2926.785, 2951.2294, 2976.468, 3000.867, 3023.6508, 3049.91, 3073.5984, 3098.162, 3121.5564, 3146.2328, 3170.9484, 3195.5902, 3221.3346, 3242.7032, 3271.6112, 3296.5546, 3317.7376, 3345.072, 3369.9518, 3394.326, 3418.1818, 3444.6926, 3469.086, 3494.2754, 3517.8698, 3544.248, 3565.3768, 3588.7234, 3616.979, 3643.7504, 3668.6812, 3695.72, 3719.7392, 3742.6224, 3770.4456, 3795.6602, 3819.9058, 3844.002, 3869.517, 3895.6824, 3920.8622, 3947.1364, 3973.985, 3995.4772, 4021.62, 4046.628, 4074.65, 4096.2256, 4121.831, 4146.6406, 4173.276, 4195.0744, 4223.9696, 4251.3708, 4272.9966, 4300.8046, 4326.302, 4353.1248, 4374.312, 4403.0322, 4426.819, 4450.0598, 4478.5206, 4504.8116, 4528.8928, 4553.9584, 4578.8712, 4603.8384, 4632.3872, 4655.5128, 4675.821, 4704.6222, 4731.9862, 4755.4174, 4781.2628, 4804.332, 4832.3048, 4862.8752, 4883.4148, 4906.9544, 4935.3516, 4954.3532, 4984.0248, 5011.217, 5035.3258, 5057.3672, 5084.1828, },
+ // precision 11
+ { 1477, 1501.6014, 1526.5802, 1551.7942, 1577.3042, 1603.2062, 1629.8402, 1656.2292, 1682.9462, 1709.9926, 1737.3026, 1765.4252, 1793.0578, 1821.6092, 1849.626, 1878.5568, 1908.527, 1937.5154, 1967.1874, 1997.3878, 2027.37, 2058.1972, 2089.5728, 2120.1012, 2151.9668, 2183.292, 2216.0772, 2247.8578, 2280.6562, 2313.041, 2345.714, 2380.3112, 2414.1806, 2447.9854, 2481.656, 2516.346, 2551.5154, 2586.8378, 2621.7448, 2656.6722, 2693.5722, 2729.1462, 2765.4124, 2802.8728, 2838.898, 2876.408, 2913.4926, 2951.4938, 2989.6776, 3026.282, 3065.7704, 3104.1012, 3143.7388, 3181.6876, 3221.1872, 3261.5048, 3300.0214, 3339.806, 3381.409, 3421.4144, 3461.4294, 3502.2286, 3544.651, 3586.6156, 3627.337, 3670.083, 3711.1538, 3753.5094, 3797.01, 3838.6686, 3882.1678, 3922.8116, 3967.9978, 4009.9204, 4054.3286, 4097.5706, 4140.6014, 4185.544, 4229.5976, 4274.583, 4316.9438, 4361.672, 4406.2786, 4451.8628, 4496.1834, 4543.505, 4589.1816, 4632.5188, 4678.2294, 4724.8908, 4769.0194, 4817.052, 4861.4588, 4910.1596, 4956.4344, 5002.5238, 5048.13, 5093.6374, 5142.8162, 5187.7894, 5237.3984, 5285.6078, 5331.0858, 5379.1036, 5428.6258, 5474.6018, 5522.7618, 5571.5822, 5618.59, 5667.9992, 5714.88, 5763.454, 5808.6982, 5860.3644, 5910.2914, 5953.571, 6005.9232, 6055.1914, 6104.5882, 6154.5702, 6199.7036, 6251.1764, 6298.7596, 6350.0302, 6398.061, 6448.4694, 6495.933, 6548.0474, 6597.7166, 6646.9416, 6695.9208, 6742.6328, 6793.5276, 6842.1934, 6894.2372, 6945.3864, 6996.9228, 7044.2372, 7094.1374, 7142.2272, 7192.2942, 7238.8338, 7288.9006, 7344.0908, 7394.8544, 7443.5176, 7490.4148, 7542.9314, 7595.6738, 7641.9878, 7694.3688, 7743.0448, 7797.522, 7845.53, 7899.594, 7950.3132, 7996.455, 8050.9442, 8092.9114, 8153.1374, 8197.4472, 8252.8278, 8301.8728, 8348.6776, 8401.4698, 8453.551, 8504.6598, 8553.8944, 8604.1276, 8657.6514, 8710.3062, 8758.908, 8807.8706, 8862.1702, 8910.4668, 8960.77, 9007.2766, 9063.164, 9121.0534, 9164.1354, 9218.1594, 9267.767, 9319.0594, 9372.155, 9419.7126, 9474.3722, 9520.1338, 9572.368, 9622.7702, 9675.8448, 9726.5396, 9778.7378, 9827.6554, 9878.1922, 9928.7782, 9978.3984, 10026.578, 10076.5626, 10137.1618, 10177.5244, 10229.9176, },
+ // precision 12
+ { 2954, 3003.4782, 3053.3568, 3104.3666, 3155.324, 3206.9598, 3259.648, 3312.539, 3366.1474, 3420.2576, 3474.8376, 3530.6076, 3586.451, 3643.38, 3700.4104, 3757.5638, 3815.9676, 3875.193, 3934.838, 3994.8548, 4055.018, 4117.1742, 4178.4482, 4241.1294, 4304.4776, 4367.4044, 4431.8724, 4496.3732, 4561.4304, 4627.5326, 4693.949, 4761.5532, 4828.7256, 4897.6182, 4965.5186, 5034.4528, 5104.865, 5174.7164, 5244.6828, 5316.6708, 5387.8312, 5459.9036, 5532.476, 5604.8652, 5679.6718, 5753.757, 5830.2072, 5905.2828, 5980.0434, 6056.6264, 6134.3192, 6211.5746, 6290.0816, 6367.1176, 6447.9796, 6526.5576, 6606.1858, 6686.9144, 6766.1142, 6847.0818, 6927.9664, 7010.9096, 7091.0816, 7175.3962, 7260.3454, 7344.018, 7426.4214, 7511.3106, 7596.0686, 7679.8094, 7765.818, 7852.4248, 7936.834, 8022.363, 8109.5066, 8200.4554, 8288.5832, 8373.366, 8463.4808, 8549.7682, 8642.0522, 8728.3288, 8820.9528, 8907.727, 9001.0794, 9091.2522, 9179.988, 9269.852, 9362.6394, 9453.642, 9546.9024, 9640.6616, 9732.6622, 9824.3254, 9917.7484, 10007.9392, 10106.7508, 10196.2152, 10289.8114, 10383.5494, 10482.3064, 10576.8734, 10668.7872, 10764.7156, 10862.0196, 10952.793, 11049.9748, 11146.0702, 11241.4492, 11339.2772, 11434.2336, 11530.741, 11627.6136, 11726.311, 11821.5964, 11918.837, 12015.3724, 12113.0162, 12213.0424, 12306.9804, 12408.4518, 12504.8968, 12604.586, 12700.9332, 12798.705, 12898.5142, 12997.0488, 13094.788, 13198.475, 13292.7764, 13392.9698, 13486.8574, 13590.1616, 13686.5838, 13783.6264, 13887.2638, 13992.0978, 14081.0844, 14189.9956, 14280.0912, 14382.4956, 14486.4384, 14588.1082, 14686.2392, 14782.276, 14888.0284, 14985.1864, 15088.8596, 15187.0998, 15285.027, 15383.6694, 15495.8266, 15591.3736, 15694.2008, 15790.3246, 15898.4116, 15997.4522, 16095.5014, 16198.8514, 16291.7492, 16402.6424, 16499.1266, 16606.2436, 16697.7186, 16796.3946, 16902.3376, 17005.7672, 17100.814, 17206.8282, 17305.8262, 17416.0744, 17508.4092, 17617.0178, 17715.4554, 17816.758, 17920.1748, 18012.9236, 18119.7984, 18223.2248, 18324.2482, 18426.6276, 18525.0932, 18629.8976, 18733.2588, 18831.0466, 18940.1366, 19032.2696, 19131.729, 19243.4864, 19349.6932, 19442.866, 19547.9448, 19653.2798, 19754.4034, 19854.0692, 19965.1224, 20065.1774, 20158.2212, 20253.353, 20366.3264, 20463.22, },
+ // precision 13
+ { 5908.5052, 6007.2672, 6107.347, 6208.5794, 6311.2622, 6414.5514, 6519.3376, 6625.6952, 6732.5988, 6841.3552, 6950.5972, 7061.3082, 7173.5646, 7287.109, 7401.8216, 7516.4344, 7633.3802, 7751.2962, 7870.3784, 7990.292, 8110.79, 8233.4574, 8356.6036, 8482.2712, 8607.7708, 8735.099, 8863.1858, 8993.4746, 9123.8496, 9255.6794, 9388.5448, 9522.7516, 9657.3106, 9792.6094, 9930.5642, 10068.794, 10206.7256, 10347.81, 10490.3196, 10632.0778, 10775.9916, 10920.4662, 11066.124, 11213.073, 11358.0362, 11508.1006, 11659.1716, 11808.7514, 11959.4884, 12112.1314, 12265.037, 12420.3756, 12578.933, 12734.311, 12890.0006, 13047.2144, 13207.3096, 13368.5144, 13528.024, 13689.847, 13852.7528, 14018.3168, 14180.5372, 14346.9668, 14513.5074, 14677.867, 14846.2186, 15017.4186, 15184.9716, 15356.339, 15529.2972, 15697.3578, 15871.8686, 16042.187, 16216.4094, 16389.4188, 16565.9126, 16742.3272, 16919.0042, 17094.7592, 17273.965, 17451.8342, 17634.4254, 17810.5984, 17988.9242, 18171.051, 18354.7938, 18539.466, 18721.0408, 18904.9972, 19081.867, 19271.9118, 19451.8694, 19637.9816, 19821.2922, 20013.1292, 20199.3858, 20387.8726, 20572.9514, 20770.7764, 20955.1714, 21144.751, 21329.9952, 21520.709, 21712.7016, 21906.3868, 22096.2626, 22286.0524, 22475.051, 22665.5098, 22862.8492, 23055.5294, 23249.6138, 23437.848, 23636.273, 23826.093, 24020.3296, 24213.3896, 24411.7392, 24602.9614, 24805.7952, 24998.1552, 25193.9588, 25389.0166, 25585.8392, 25780.6976, 25981.2728, 26175.977, 26376.5252, 26570.1964, 26773.387, 26962.9812, 27163.0586, 27368.164, 27565.0534, 27758.7428, 27961.1276, 28163.2324, 28362.3816, 28565.7668, 28758.644, 28956.9768, 29163.4722, 29354.7026, 29561.1186, 29767.9948, 29959.9986, 30164.0492, 30366.9818, 30562.5338, 30762.9928, 30976.1592, 31166.274, 31376.722, 31570.3734, 31770.809, 31974.8934, 32179.5286, 32387.5442, 32582.3504, 32794.076, 32989.9528, 33191.842, 33392.4684, 33595.659, 33801.8672, 34000.3414, 34200.0922, 34402.6792, 34610.0638, 34804.0084, 35011.13, 35218.669, 35418.6634, 35619.0792, 35830.6534, 36028.4966, 36229.7902, 36438.6422, 36630.7764, 36833.3102, 37048.6728, 37247.3916, 37453.5904, 37669.3614, 37854.5526, 38059.305, 38268.0936, 38470.2516, 38674.7064, 38876.167, 39068.3794, 39281.9144, 39492.8566, 39684.8628, 39898.4108, 40093.1836, 40297.6858, 40489.7086, 40717.2424, },
+ // precision 14
+ { 11817.475, 12015.0046, 12215.3792, 12417.7504, 12623.1814, 12830.0086, 13040.0072, 13252.503, 13466.178, 13683.2738, 13902.0344, 14123.9798, 14347.394, 14573.7784, 14802.6894, 15033.6824, 15266.9134, 15502.8624, 15741.4944, 15980.7956, 16223.8916, 16468.6316, 16715.733, 16965.5726, 17217.204, 17470.666, 17727.8516, 17986.7886, 18247.6902, 18510.9632, 18775.304, 19044.7486, 19314.4408, 19587.202, 19862.2576, 20135.924, 20417.0324, 20697.9788, 20979.6112, 21265.0274, 21550.723, 21841.6906, 22132.162, 22428.1406, 22722.127, 23020.5606, 23319.7394, 23620.4014, 23925.2728, 24226.9224, 24535.581, 24845.505, 25155.9618, 25470.3828, 25785.9702, 26103.7764, 26420.4132, 26742.0186, 27062.8852, 27388.415, 27714.6024, 28042.296, 28365.4494, 28701.1526, 29031.8008, 29364.2156, 29704.497, 30037.1458, 30380.111, 30723.8168, 31059.5114, 31404.9498, 31751.6752, 32095.2686, 32444.7792, 32794.767, 33145.204, 33498.4226, 33847.6502, 34209.006, 34560.849, 34919.4838, 35274.9778, 35635.1322, 35996.3266, 36359.1394, 36722.8266, 37082.8516, 37447.7354, 37815.9606, 38191.0692, 38559.4106, 38924.8112, 39294.6726, 39663.973, 40042.261, 40416.2036, 40779.2036, 41161.6436, 41540.9014, 41921.1998, 42294.7698, 42678.5264, 43061.3464, 43432.375, 43818.432, 44198.6598, 44583.0138, 44970.4794, 45353.924, 45729.858, 46118.2224, 46511.5724, 46900.7386, 47280.6964, 47668.1472, 48055.6796, 48446.9436, 48838.7146, 49217.7296, 49613.7796, 50010.7508, 50410.0208, 50793.7886, 51190.2456, 51583.1882, 51971.0796, 52376.5338, 52763.319, 53165.5534, 53556.5594, 53948.2702, 54346.352, 54748.7914, 55138.577, 55543.4824, 55941.1748, 56333.7746, 56745.1552, 57142.7944, 57545.2236, 57935.9956, 58348.5268, 58737.5474, 59158.5962, 59542.6896, 59958.8004, 60349.3788, 60755.0212, 61147.6144, 61548.194, 61946.0696, 62348.6042, 62763.603, 63162.781, 63560.635, 63974.3482, 64366.4908, 64771.5876, 65176.7346, 65597.3916, 65995.915, 66394.0384, 66822.9396, 67203.6336, 67612.2032, 68019.0078, 68420.0388, 68821.22, 69235.8388, 69640.0724, 70055.155, 70466.357, 70863.4266, 71276.2482, 71677.0306, 72080.2006, 72493.0214, 72893.5952, 73314.5856, 73714.9852, 74125.3022, 74521.2122, 74933.6814, 75341.5904, 75743.0244, 76166.0278, 76572.1322, 76973.1028, 77381.6284, 77800.6092, 78189.328, 78607.0962, 79012.2508, 79407.8358, 79825.725, 80238.701, 80646.891, 81035.6436, 81460.0448, 81876.3884, },
+ // precision 15
+ { 23635.0036, 24030.8034, 24431.4744, 24837.1524, 25246.7928, 25661.326, 26081.3532, 26505.2806, 26933.9892, 27367.7098, 27805.318, 28248.799, 28696.4382, 29148.8244, 29605.5138, 30066.8668, 30534.2344, 31006.32, 31480.778, 31962.2418, 32447.3324, 32938.0232, 33432.731, 33930.728, 34433.9896, 34944.1402, 35457.5588, 35974.5958, 36497.3296, 37021.9096, 37554.326, 38088.0826, 38628.8816, 39171.3192, 39723.2326, 40274.5554, 40832.3142, 41390.613, 41959.5908, 42532.5466, 43102.0344, 43683.5072, 44266.694, 44851.2822, 45440.7862, 46038.0586, 46640.3164, 47241.064, 47846.155, 48454.7396, 49076.9168, 49692.542, 50317.4778, 50939.65, 51572.5596, 52210.2906, 52843.7396, 53481.3996, 54127.236, 54770.406, 55422.6598, 56078.7958, 56736.7174, 57397.6784, 58064.5784, 58730.308, 59404.9784, 60077.0864, 60751.9158, 61444.1386, 62115.817, 62808.7742, 63501.4774, 64187.5454, 64883.6622, 65582.7468, 66274.5318, 66976.9276, 67688.7764, 68402.138, 69109.6274, 69822.9706, 70543.6108, 71265.5202, 71983.3848, 72708.4656, 73433.384, 74158.4664, 74896.4868, 75620.9564, 76362.1434, 77098.3204, 77835.7662, 78582.6114, 79323.9902, 80067.8658, 80814.9246, 81567.0136, 82310.8536, 83061.9952, 83821.4096, 84580.8608, 85335.547, 86092.5802, 86851.6506, 87612.311, 88381.2016, 89146.3296, 89907.8974, 90676.846, 91451.4152, 92224.5518, 92995.8686, 93763.5066, 94551.2796, 95315.1944, 96096.1806, 96881.0918, 97665.679, 98442.68, 99229.3002, 100011.0994, 100790.6386, 101580.1564, 102377.7484, 103152.1392, 103944.2712, 104730.216, 105528.6336, 106324.9398, 107117.6706, 107890.3988, 108695.2266, 109485.238, 110294.7876, 111075.0958, 111878.0496, 112695.2864, 113464.5486, 114270.0474, 115068.608, 115884.3626, 116673.2588, 117483.3716, 118275.097, 119085.4092, 119879.2808, 120687.5868, 121499.9944, 122284.916, 123095.9254, 123912.5038, 124709.0454, 125503.7182, 126323.259, 127138.9412, 127943.8294, 128755.646, 129556.5354, 130375.3298, 131161.4734, 131971.1962, 132787.5458, 133588.1056, 134431.351, 135220.2906, 136023.398, 136846.6558, 137667.0004, 138463.663, 139283.7154, 140074.6146, 140901.3072, 141721.8548, 142543.2322, 143356.1096, 144173.7412, 144973.0948, 145794.3162, 146609.5714, 147420.003, 148237.9784, 149050.5696, 149854.761, 150663.1966, 151494.0754, 152313.1416, 153112.6902, 153935.7206, 154746.9262, 155559.547, 156401.9746, 157228.7036, 158008.7254, 158820.75, 159646.9184, 160470.4458, 161279.5348, 162093.3114, 162918.542, 163729.2842, },
+ // precision 16
+ { 47271, 48062.3584, 48862.7074, 49673.152, 50492.8416, 51322.9514, 52161.03, 53009.407, 53867.6348, 54734.206, 55610.5144, 56496.2096, 57390.795, 58297.268, 59210.6448, 60134.665, 61068.0248, 62010.4472, 62962.5204, 63923.5742, 64895.0194, 65876.4182, 66862.6136, 67862.6968, 68868.8908, 69882.8544, 70911.271, 71944.0924, 72990.0326, 74040.692, 75100.6336, 76174.7826, 77252.5998, 78340.2974, 79438.2572, 80545.4976, 81657.2796, 82784.6336, 83915.515, 85059.7362, 86205.9368, 87364.4424, 88530.3358, 89707.3744, 90885.9638, 92080.197, 93275.5738, 94479.391, 95695.918, 96919.2236, 98148.4602, 99382.3474, 100625.6974, 101878.0284, 103141.6278, 104409.4588, 105686.2882, 106967.5402, 108261.6032, 109548.1578, 110852.0728, 112162.231, 113479.0072, 114806.2626, 116137.9072, 117469.5048, 118813.5186, 120165.4876, 121516.2556, 122875.766, 124250.5444, 125621.2222, 127003.2352, 128387.848, 129775.2644, 131181.7776, 132577.3086, 133979.9458, 135394.1132, 136800.9078, 138233.217, 139668.5308, 141085.212, 142535.2122, 143969.0684, 145420.2872, 146878.1542, 148332.7572, 149800.3202, 151269.66, 152743.6104, 154213.0948, 155690.288, 157169.4246, 158672.1756, 160160.059, 161650.6854, 163145.7772, 164645.6726, 166159.1952, 167682.1578, 169177.3328, 170700.0118, 172228.8964, 173732.6664, 175265.5556, 176787.799, 178317.111, 179856.6914, 181400.865, 182943.4612, 184486.742, 186033.4698, 187583.7886, 189148.1868, 190688.4526, 192250.1926, 193810.9042, 195354.2972, 196938.7682, 198493.5898, 200079.2824, 201618.912, 203205.5492, 204765.5798, 206356.1124, 207929.3064, 209498.7196, 211086.229, 212675.1324, 214256.7892, 215826.2392, 217412.8474, 218995.6724, 220618.6038, 222207.1166, 223781.0364, 225387.4332, 227005.7928, 228590.4336, 230217.8738, 231805.1054, 233408.9, 234995.3432, 236601.4956, 238190.7904, 239817.2548, 241411.2832, 243002.4066, 244640.1884, 246255.3128, 247849.3508, 249479.9734, 251106.8822, 252705.027, 254332.9242, 255935.129, 257526.9014, 259154.772, 260777.625, 262390.253, 264004.4906, 265643.59, 267255.4076, 268873.426, 270470.7252, 272106.4804, 273722.4456, 275337.794, 276945.7038, 278592.9154, 280204.3726, 281841.1606, 283489.171, 285130.1716, 286735.3362, 288364.7164, 289961.1814, 291595.5524, 293285.683, 294899.6668, 296499.3434, 298128.0462, 299761.8946, 301394.2424, 302997.6748, 304615.1478, 306269.7724, 307886.114, 309543.1028, 311153.2862, 312782.8546, 314421.2008, 316033.2438, 317692.9636, 319305.2648, 320948.7406, 322566.3364, 324228.4224, 325847.1542, },
+ // precision 17
+ { 94542, 96125.811, 97728.019, 99348.558, 100987.9705, 102646.7565, 104324.5125, 106021.7435, 107736.7865, 109469.272, 111223.9465, 112995.219, 114787.432, 116593.152, 118422.71, 120267.2345, 122134.6765, 124020.937, 125927.2705, 127851.255, 129788.9485, 131751.016, 133726.8225, 135722.592, 137736.789, 139770.568, 141821.518, 143891.343, 145982.1415, 148095.387, 150207.526, 152355.649, 154515.6415, 156696.05, 158887.7575, 161098.159, 163329.852, 165569.053, 167837.4005, 170121.6165, 172420.4595, 174732.6265, 177062.77, 179412.502, 181774.035, 184151.939, 186551.6895, 188965.691, 191402.8095, 193857.949, 196305.0775, 198774.6715, 201271.2585, 203764.78, 206299.3695, 208818.1365, 211373.115, 213946.7465, 216532.076, 219105.541, 221714.5375, 224337.5135, 226977.5125, 229613.0655, 232270.2685, 234952.2065, 237645.3555, 240331.1925, 243034.517, 245756.0725, 248517.6865, 251232.737, 254011.3955, 256785.995, 259556.44, 262368.335, 265156.911, 267965.266, 270785.583, 273616.0495, 276487.4835, 279346.639, 282202.509, 285074.3885, 287942.2855, 290856.018, 293774.0345, 296678.5145, 299603.6355, 302552.6575, 305492.9785, 308466.8605, 311392.581, 314347.538, 317319.4295, 320285.9785, 323301.7325, 326298.3235, 329301.3105, 332301.987, 335309.791, 338370.762, 341382.923, 344431.1265, 347464.1545, 350507.28, 353619.2345, 356631.2005, 359685.203, 362776.7845, 365886.488, 368958.2255, 372060.6825, 375165.4335, 378237.935, 381328.311, 384430.5225, 387576.425, 390683.242, 393839.648, 396977.8425, 400101.9805, 403271.296, 406409.8425, 409529.5485, 412678.7, 415847.423, 419020.8035, 422157.081, 425337.749, 428479.6165, 431700.902, 434893.1915, 438049.582, 441210.5415, 444379.2545, 447577.356, 450741.931, 453959.548, 457137.0935, 460329.846, 463537.4815, 466732.3345, 469960.5615, 473164.681, 476347.6345, 479496.173, 482813.1645, 486025.6995, 489249.4885, 492460.1945, 495675.8805, 498908.0075, 502131.802, 505374.3855, 508550.9915, 511806.7305, 515026.776, 518217.0005, 521523.9855, 524705.9855, 527950.997, 531210.0265, 534472.497, 537750.7315, 540926.922, 544207.094, 547429.4345, 550666.3745, 553975.3475, 557150.7185, 560399.6165, 563662.697, 566916.7395, 570146.1215, 573447.425, 576689.6245, 579874.5745, 583202.337, 586503.0255, 589715.635, 592910.161, 596214.3885, 599488.035, 602740.92, 605983.0685, 609248.67, 612491.3605, 615787.912, 619107.5245, 622307.9555, 625577.333, 628840.4385, 632085.2155, 635317.6135, 638691.7195, 641887.467, 645139.9405, 648441.546, 651666.252, 654941.845, },
+ // precision 18
+ { 189084, 192250.913, 195456.774, 198696.946, 201977.762, 205294.444, 208651.754, 212042.099, 215472.269, 218941.91, 222443.912, 225996.845, 229568.199, 233193.568, 236844.457, 240543.233, 244279.475, 248044.27, 251854.588, 255693.2, 259583.619, 263494.621, 267445.385, 271454.061, 275468.769, 279549.456, 283646.446, 287788.198, 291966.099, 296181.164, 300431.469, 304718.618, 309024.004, 313393.508, 317760.803, 322209.731, 326675.061, 331160.627, 335654.47, 340241.442, 344841.833, 349467.132, 354130.629, 358819.432, 363574.626, 368296.587, 373118.482, 377914.93, 382782.301, 387680.669, 392601.981, 397544.323, 402529.115, 407546.018, 412593.658, 417638.657, 422762.865, 427886.169, 433017.167, 438213.273, 443441.254, 448692.421, 453937.533, 459239.049, 464529.569, 469910.083, 475274.03, 480684.473, 486070.26, 491515.237, 496995.651, 502476.617, 507973.609, 513497.19, 519083.233, 524726.509, 530305.505, 535945.728, 541584.404, 547274.055, 552967.236, 558667.862, 564360.216, 570128.148, 575965.08, 581701.952, 587532.523, 593361.144, 599246.128, 605033.418, 610958.779, 616837.117, 622772.818, 628672.04, 634675.369, 640574.831, 646585.739, 652574.547, 658611.217, 664642.684, 670713.914, 676737.681, 682797.313, 688837.897, 694917.874, 701009.882, 707173.648, 713257.254, 719415.392, 725636.761, 731710.697, 737906.209, 744103.074, 750313.39, 756504.185, 762712.579, 768876.985, 775167.859, 781359, 787615.959, 793863.597, 800245.477, 806464.582, 812785.294, 819005.925, 825403.057, 831676.197, 837936.284, 844266.968, 850642.711, 856959.756, 863322.774, 869699.931, 876102.478, 882355.787, 888694.463, 895159.952, 901536.143, 907872.631, 914293.672, 920615.14, 927130.974, 933409.404, 939922.178, 946331.47, 952745.93, 959209.264, 965590.224, 972077.284, 978501.961, 984953.19, 991413.271, 997817.479, 1004222.658, 1010725.676, 1017177.138, 1023612.529, 1030098.236, 1036493.719, 1043112.207, 1049537.036, 1056008.096, 1062476.184, 1068942.337, 1075524.95, 1081932.864, 1088426.025, 1094776.005, 1101327.448, 1107901.673, 1114423.639, 1120884.602, 1127324.923, 1133794.24, 1140328.886, 1146849.376, 1153346.682, 1159836.502, 1166478.703, 1172953.304, 1179391.502, 1185950.982, 1192544.052, 1198913.41, 1205430.994, 1212015.525, 1218674.042, 1225121.683, 1231551.101, 1238126.379, 1244673.795, 1251260.649, 1257697.86, 1264320.983, 1270736.319, 1277274.694, 1283804.95, 1290211.514, 1296858.568, 1303455.691, }
+ };
+
+ private static final double[][] biasDataAllPrecisions = {
+ // precision 4
+ { 10, 9.717, 9.207, 8.7896, 8.2882, 7.8204, 7.3772, 6.9342, 6.5202, 6.161, 5.7722, 5.4636, 5.0396, 4.6766, 4.3566, 4.0454, 3.7936, 3.4856, 3.2666, 2.9946, 2.766, 2.4692, 2.3638, 2.0764, 1.7864, 1.7602, 1.4814, 1.433, 1.2926, 1.0664, 0.999600000000001, 0.7956, 0.5366, 0.589399999999998, 0.573799999999999, 0.269799999999996, 0.368200000000002, 0.0544000000000011, 0.234200000000001, 0.0108000000000033, -0.203400000000002, -0.0701999999999998, -0.129600000000003, -0.364199999999997, -0.480600000000003, -0.226999999999997, -0.322800000000001, -0.382599999999996, -0.511200000000002, -0.669600000000003, -0.749400000000001, -0.500399999999999, -0.617600000000003, -0.6922, -0.601599999999998, -0.416200000000003, -0.338200000000001, -0.782600000000002, -0.648600000000002, -0.919800000000002, -0.851799999999997, -0.962400000000002, -0.6402, -1.1922, -1.0256, -1.086, -1.21899999999999, -0.819400000000002, -0.940600000000003, -1.1554, -1.2072, -1.1752, -1.16759999999999, -1.14019999999999, -1.3754, -1.29859999999999, -1.607, -1.3292, -1.7606, },
+ // precision 5
+ { 22, 21.1194, 20.8208, 20.2318, 19.77, 19.2436, 18.7774, 18.2848, 17.8224, 17.3742, 16.9336, 16.503, 16.0494, 15.6292, 15.2124, 14.798, 14.367, 13.9728, 13.5944, 13.217, 12.8438, 12.3696, 12.0956, 11.7044, 11.324, 11.0668, 10.6698, 10.3644, 10.049, 9.6918, 9.4146, 9.082, 8.687, 8.5398, 8.2462, 7.857, 7.6606, 7.4168, 7.1248, 6.9222, 6.6804, 6.447, 6.3454, 5.9594, 5.7636, 5.5776, 5.331, 5.19, 4.9676, 4.7564, 4.5314, 4.4442, 4.3708, 3.9774, 3.9624, 3.8796, 3.755, 3.472, 3.2076, 3.1024, 2.8908, 2.7338, 2.7728, 2.629, 2.413, 2.3266, 2.1524, 2.2642, 2.1806, 2.0566, 1.9192, 1.7598, 1.3516, 1.5802, 1.43859999999999, 1.49160000000001, 1.1524, 1.1892, 0.841399999999993, 0.879800000000003, 0.837599999999995, 0.469800000000006, 0.765600000000006, 0.331000000000003, 0.591399999999993, 0.601200000000006, 0.701599999999999, 0.558199999999999, 0.339399999999998, 0.354399999999998, 0.491200000000006, 0.308000000000007, 0.355199999999996, -0.0254000000000048, 0.205200000000005, -0.272999999999996, 0.132199999999997, 0.394400000000005, -0.241200000000006, 0.242000000000004, 0.191400000000002, 0.253799999999998, -0.122399999999999, -0.370800000000003, 0.193200000000004, -0.0848000000000013, 0.0867999999999967, -0.327200000000005, -0.285600000000002, 0.311400000000006, -0.128399999999999, -0.754999999999995, -0.209199999999996, -0.293599999999998, -0.364000000000004, -0.253600000000006, -0.821200000000005, -0.253600000000006, -0.510400000000004, -0.383399999999995, -0.491799999999998, -0.220200000000006, -0.0972000000000008, -0.557400000000001, -0.114599999999996, -0.295000000000002, -0.534800000000004, 0.346399999999988, -0.65379999999999, 0.0398000000000138, 0.0341999999999985, -0.995800000000003, -0.523400000000009, -0.489000000000004, -0.274799999999999, -0.574999999999989, -0.482799999999997, 0.0571999999999946, -0.330600000000004, -0.628800000000012, -0.140199999999993, -0.540600000000012, -0.445999999999998, -0.599400000000003, -0.262599999999992, 0.163399999999996, -0.100599999999986, -0.39500000000001, -1.06960000000001, -0.836399999999998, -0.753199999999993, -0.412399999999991, -0.790400000000005, -0.29679999999999, -0.28540000000001, -0.193000000000012, -0.0772000000000048, -0.962799999999987, -0.414800000000014, },
+ // precision 6
+ { 45, 44.1902, 43.271, 42.8358, 41.8142, 41.2854, 40.317, 39.354, 38.8924, 37.9436, 37.4596, 36.5262, 35.6248, 35.1574, 34.2822, 33.837, 32.9636, 32.074, 31.7042, 30.7976, 30.4772, 29.6564, 28.7942, 28.5004, 27.686, 27.291, 26.5672, 25.8556, 25.4982, 24.8204, 24.4252, 23.7744, 23.0786, 22.8344, 22.0294, 21.8098, 21.0794, 20.5732, 20.1878, 19.5648, 19.2902, 18.6784, 18.3352, 17.8946, 17.3712, 17.0852, 16.499, 16.2686, 15.6844, 15.2234, 14.9732, 14.3356, 14.2286, 13.7262, 13.3284, 13.1048, 12.5962, 12.3562, 12.1272, 11.4184, 11.4974, 11.0822, 10.856, 10.48, 10.2834, 10.0208, 9.637, 9.51739999999999, 9.05759999999999, 8.74760000000001, 8.42700000000001, 8.1326, 8.2372, 8.2788, 7.6776, 7.79259999999999, 7.1952, 6.9564, 6.6454, 6.87, 6.5428, 6.19999999999999, 6.02940000000001, 5.62780000000001, 5.6782, 5.792, 5.35159999999999, 5.28319999999999, 5.0394, 5.07480000000001, 4.49119999999999, 4.84899999999999, 4.696, 4.54040000000001, 4.07300000000001, 4.37139999999999, 3.7216, 3.7328, 3.42080000000001, 3.41839999999999, 3.94239999999999, 3.27719999999999, 3.411, 3.13079999999999, 2.76900000000001, 2.92580000000001, 2.68279999999999, 2.75020000000001, 2.70599999999999, 2.3886, 3.01859999999999, 2.45179999999999, 2.92699999999999, 2.41720000000001, 2.41139999999999, 2.03299999999999, 2.51240000000001, 2.5564, 2.60079999999999, 2.41720000000001, 1.80439999999999, 1.99700000000001, 2.45480000000001, 1.8948, 2.2346, 2.30860000000001, 2.15479999999999, 1.88419999999999, 1.6508, 0.677199999999999, 1.72540000000001, 1.4752, 1.72280000000001, 1.66139999999999, 1.16759999999999, 1.79300000000001, 1.00059999999999, 0.905200000000008, 0.659999999999997, 1.55879999999999, 1.1636, 0.688199999999995, 0.712600000000009, 0.450199999999995, 1.1978, 0.975599999999986, 0.165400000000005, 1.727, 1.19739999999999, -0.252600000000001, 1.13460000000001, 1.3048, 1.19479999999999, 0.313400000000001, 0.878999999999991, 1.12039999999999, 0.853000000000009, 1.67920000000001, 0.856999999999999, 0.448599999999999, 1.2362, 0.953399999999988, 1.02859999999998, 0.563199999999995, 0.663000000000011, 0.723000000000013, 0.756599999999992, 0.256599999999992, -0.837600000000009, 0.620000000000005, 0.821599999999989, 0.216600000000028, 0.205600000000004, 0.220199999999977, 0.372599999999977, 0.334400000000016, 0.928400000000011, 0.972800000000007, 0.192400000000021, 0.487199999999973, -0.413000000000011, 0.807000000000016, 0.120600000000024, 0.769000000000005, 0.870799999999974, 0.66500000000002, 0.118200000000002, 0.401200000000017, 0.635199999999998, 0.135400000000004, 0.175599999999974, 1.16059999999999, 0.34620000000001, 0.521400000000028, -0.586599999999976, -1.16480000000001, 0.968399999999974, 0.836999999999989, 0.779600000000016, 0.985799999999983, },
+ // precision 7
+ { 91, 89.4934, 87.9758, 86.4574, 84.9718, 83.4954, 81.5302, 80.0756, 78.6374, 77.1782, 75.7888, 73.9522, 72.592, 71.2532, 69.9086, 68.5938, 66.9474, 65.6796, 64.4394, 63.2176, 61.9768, 60.4214, 59.2528, 58.0102, 56.8658, 55.7278, 54.3044, 53.1316, 52.093, 51.0032, 49.9092, 48.6306, 47.5294, 46.5756, 45.6508, 44.662, 43.552, 42.3724, 41.617, 40.5754, 39.7872, 38.8444, 37.7988, 36.8606, 36.2118, 35.3566, 34.4476, 33.5882, 32.6816, 32.0824, 31.0258, 30.6048, 29.4436, 28.7274, 27.957, 27.147, 26.4364, 25.7592, 25.3386, 24.781, 23.8028, 23.656, 22.6544, 21.996, 21.4718, 21.1544, 20.6098, 19.5956, 19.0616, 18.5758, 18.4878, 17.5244, 17.2146, 16.724, 15.8722, 15.5198, 15.0414, 14.941, 14.9048, 13.87, 13.4304, 13.028, 12.4708, 12.37, 12.0624, 11.4668, 11.5532, 11.4352, 11.2564, 10.2744, 10.2118, 9.74720000000002, 10.1456, 9.2928, 8.75040000000001, 8.55279999999999, 8.97899999999998, 8.21019999999999, 8.18340000000001, 7.3494, 7.32499999999999, 7.66140000000001, 6.90300000000002, 7.25439999999998, 6.9042, 7.21499999999997, 6.28640000000001, 6.08139999999997, 6.6764, 6.30099999999999, 5.13900000000001, 5.65800000000002, 5.17320000000001, 4.59019999999998, 4.9538, 5.08280000000002, 4.92200000000003, 4.99020000000002, 4.7328, 5.4538, 4.11360000000002, 4.22340000000003, 4.08780000000002, 3.70800000000003, 4.15559999999999, 4.18520000000001, 3.63720000000001, 3.68220000000002, 3.77960000000002, 3.6078, 2.49160000000001, 3.13099999999997, 2.5376, 3.19880000000001, 3.21100000000001, 2.4502, 3.52820000000003, 2.91199999999998, 3.04480000000001, 2.7432, 2.85239999999999, 2.79880000000003, 2.78579999999999, 1.88679999999999, 2.98860000000002, 2.50639999999999, 1.91239999999999, 2.66160000000002, 2.46820000000002, 1.58199999999999, 1.30399999999997, 2.27379999999999, 2.68939999999998, 1.32900000000001, 3.10599999999999, 1.69080000000002, 2.13740000000001, 2.53219999999999, 1.88479999999998, 1.33240000000001, 1.45119999999997, 1.17899999999997, 2.44119999999998, 1.60659999999996, 2.16700000000003, 0.77940000000001, 2.37900000000002, 2.06700000000001, 1.46000000000004, 2.91160000000002, 1.69200000000001, 0.954600000000028, 2.49300000000005, 2.2722, 1.33500000000004, 2.44899999999996, 1.20140000000004, 3.07380000000001, 2.09739999999999, 2.85640000000001, 2.29960000000005, 2.40899999999999, 1.97040000000004, 0.809799999999996, 1.65279999999996, 2.59979999999996, 0.95799999999997, 2.06799999999998, 2.32780000000002, 4.20159999999998, 1.96320000000003, 1.86400000000003, 1.42999999999995, 3.77940000000001, 1.27200000000005, 1.86440000000005, 2.20600000000002, 3.21900000000005, 1.5154, 2.61019999999996, },
+ // precision 8
+ { 183.2152, 180.2454, 177.2096, 173.6652, 170.6312, 167.6822, 164.249, 161.3296, 158.0038, 155.2074, 152.4612, 149.27, 146.5178, 143.4412, 140.8032, 138.1634, 135.1688, 132.6074, 129.6946, 127.2664, 124.8228, 122.0432, 119.6824, 116.9464, 114.6268, 112.2626, 109.8376, 107.4034, 104.8956, 102.8522, 100.7638, 98.3552, 96.3556, 93.7526, 91.9292, 89.8954, 87.8198, 85.7668, 83.298, 81.6688, 79.9466, 77.9746, 76.1672, 74.3474, 72.3028, 70.8912, 69.114, 67.4646, 65.9744, 64.4092, 62.6022, 60.843, 59.5684, 58.1652, 56.5426, 55.4152, 53.5388, 52.3592, 51.1366, 49.486, 48.3918, 46.5076, 45.509, 44.3834, 43.3498, 42.0668, 40.7346, 40.1228, 38.4528, 37.7, 36.644, 36.0518, 34.5774, 33.9068, 32.432, 32.1666, 30.434, 29.6644, 28.4894, 27.6312, 26.3804, 26.292, 25.5496000000001, 25.0234, 24.8206, 22.6146, 22.4188, 22.117, 20.6762, 20.6576, 19.7864, 19.509, 18.5334, 17.9204, 17.772, 16.2924, 16.8654, 15.1836, 15.745, 15.1316, 15.0386, 14.0136, 13.6342, 12.6196, 12.1866, 12.4281999999999, 11.3324, 10.4794000000001, 11.5038, 10.129, 9.52800000000002, 10.3203999999999, 9.46299999999997, 9.79280000000006, 9.12300000000005, 8.74180000000001, 9.2192, 7.51020000000005, 7.60659999999996, 7.01840000000004, 7.22239999999999, 7.40139999999997, 6.76179999999999, 7.14359999999999, 5.65060000000005, 5.63779999999997, 5.76599999999996, 6.75139999999999, 5.57759999999996, 3.73220000000003, 5.8048, 5.63019999999995, 4.93359999999996, 3.47979999999995, 4.33879999999999, 3.98940000000005, 3.81960000000004, 3.31359999999995, 3.23080000000004, 3.4588, 3.08159999999998, 3.4076, 3.00639999999999, 2.38779999999997, 2.61900000000003, 1.99800000000005, 3.34820000000002, 2.95060000000001, 0.990999999999985, 2.11440000000005, 2.20299999999997, 2.82219999999995, 2.73239999999998, 2.7826, 3.76660000000004, 2.26480000000004, 2.31280000000004, 2.40819999999997, 2.75360000000001, 3.33759999999995, 2.71559999999999, 1.7478000000001, 1.42920000000004, 2.39300000000003, 2.22779999999989, 2.34339999999997, 0.87259999999992, 3.88400000000001, 1.80600000000004, 1.91759999999999, 1.16779999999994, 1.50320000000011, 2.52500000000009, 0.226400000000012, 2.31500000000005, 0.930000000000064, 1.25199999999995, 2.14959999999996, 0.0407999999999902, 2.5447999999999, 1.32960000000003, 0.197400000000016, 2.52620000000002, 3.33279999999991, -1.34300000000007, 0.422199999999975, 0.917200000000093, 1.12920000000008, 1.46060000000011, 1.45779999999991, 2.8728000000001, 3.33359999999993, -1.34079999999994, 1.57680000000005, 0.363000000000056, 1.40740000000005, 0.656600000000026, 0.801400000000058, -0.454600000000028, 1.51919999999996, },
+ // precision 9
+ { 368, 361.8294, 355.2452, 348.6698, 342.1464, 336.2024, 329.8782, 323.6598, 317.462, 311.2826, 305.7102, 299.7416, 293.9366, 288.1046, 282.285, 277.0668, 271.306, 265.8448, 260.301, 254.9886, 250.2422, 244.8138, 239.7074, 234.7428, 229.8402, 225.1664, 220.3534, 215.594, 210.6886, 205.7876, 201.65, 197.228, 192.8036, 188.1666, 184.0818, 180.0824, 176.2574, 172.302, 168.1644, 164.0056, 160.3802, 156.7192, 152.5234, 149.2084, 145.831, 142.485, 139.1112, 135.4764, 131.76, 129.3368, 126.5538, 122.5058, 119.2646, 116.5902, 113.3818, 110.8998, 107.9532, 105.2062, 102.2798, 99.4728, 96.9582, 94.3292, 92.171, 89.7809999999999, 87.5716, 84.7048, 82.5322, 79.875, 78.3972, 75.3464, 73.7274, 71.2834, 70.1444, 68.4263999999999, 66.0166, 64.018, 62.0437999999999, 60.3399999999999, 58.6856, 57.9836, 55.0311999999999, 54.6769999999999, 52.3188, 51.4846, 49.4423999999999, 47.739, 46.1487999999999, 44.9202, 43.4059999999999, 42.5342000000001, 41.2834, 38.8954000000001, 38.3286000000001, 36.2146, 36.6684, 35.9946, 33.123, 33.4338, 31.7378000000001, 29.076, 28.9692, 27.4964, 27.0998, 25.9864, 26.7754, 24.3208, 23.4838, 22.7388000000001, 24.0758000000001, 21.9097999999999, 20.9728, 19.9228000000001, 19.9292, 16.617, 17.05, 18.2996000000001, 15.6128000000001, 15.7392, 14.5174, 13.6322, 12.2583999999999, 13.3766000000001, 11.423, 13.1232, 9.51639999999998, 10.5938000000001, 9.59719999999993, 8.12220000000002, 9.76739999999995, 7.50440000000003, 7.56999999999994, 6.70440000000008, 6.41419999999994, 6.71019999999999, 5.60940000000005, 4.65219999999999, 6.84099999999989, 3.4072000000001, 3.97859999999991, 3.32760000000007, 5.52160000000003, 3.31860000000006, 2.06940000000009, 4.35400000000004, 1.57500000000005, 0.280799999999999, 2.12879999999996, -0.214799999999968, -0.0378000000000611, -0.658200000000079, 0.654800000000023, -0.0697999999999865, 0.858400000000074, -2.52700000000004, -2.1751999999999, -3.35539999999992, -1.04019999999991, -0.651000000000067, -2.14439999999991, -1.96659999999997, -3.97939999999994, -0.604400000000169, -3.08260000000018, -3.39159999999993, -5.29640000000018, -5.38920000000007, -5.08759999999984, -4.69900000000007, -5.23720000000003, -3.15779999999995, -4.97879999999986, -4.89899999999989, -7.48880000000008, -5.94799999999987, -5.68060000000014, -6.67180000000008, -4.70499999999993, -7.27779999999984, -4.6579999999999, -4.4362000000001, -4.32139999999981, -5.18859999999995, -6.66879999999992, -6.48399999999992, -5.1260000000002, -4.4032000000002, -6.13500000000022, -5.80819999999994, -4.16719999999987, -4.15039999999999, -7.45600000000013, -7.24080000000004, -9.83179999999993, -5.80420000000004, -8.6561999999999, -6.99940000000015, -10.5473999999999, -7.34139999999979, -6.80999999999995, -6.29719999999998, -6.23199999999997, },
+ // precision 10
+ { 737.1256, 724.4234, 711.1064, 698.4732, 685.4636, 673.0644, 660.488, 647.9654, 636.0832, 623.7864, 612.1992, 600.2176, 588.5228, 577.1716, 565.7752, 554.899, 543.6126, 532.6492, 521.9474, 511.5214, 501.1064, 490.6364, 480.2468, 470.4588, 460.3832, 451.0584, 440.8606, 431.3868, 422.5062, 413.1862, 404.463, 395.339, 386.1936, 378.1292, 369.1854, 361.2908, 353.3324, 344.8518, 337.5204, 329.4854, 321.9318, 314.552, 306.4658, 299.4256, 292.849, 286.152, 278.8956, 271.8792, 265.118, 258.62, 252.5132, 245.9322, 239.7726, 233.6086, 227.5332, 222.5918, 216.4294, 210.7662, 205.4106, 199.7338, 194.9012, 188.4486, 183.1556, 178.6338, 173.7312, 169.6264, 163.9526, 159.8742, 155.8326, 151.1966, 147.5594, 143.07, 140.037, 134.1804, 131.071, 127.4884, 124.0848, 120.2944, 117.333, 112.9626, 110.2902, 107.0814, 103.0334, 99.4832000000001, 96.3899999999999, 93.7202000000002, 90.1714000000002, 87.2357999999999, 85.9346, 82.8910000000001, 80.0264000000002, 78.3834000000002, 75.1543999999999, 73.8683999999998, 70.9895999999999, 69.4367999999999, 64.8701999999998, 65.0408000000002, 61.6738, 59.5207999999998, 57.0158000000001, 54.2302, 53.0962, 50.4985999999999, 52.2588000000001, 47.3914, 45.6244000000002, 42.8377999999998, 43.0072, 40.6516000000001, 40.2453999999998, 35.2136, 36.4546, 33.7849999999999, 33.2294000000002, 32.4679999999998, 30.8670000000002, 28.6507999999999, 28.9099999999999, 27.5983999999999, 26.1619999999998, 24.5563999999999, 23.2328000000002, 21.9484000000002, 21.5902000000001, 21.3346000000001, 17.7031999999999, 20.6111999999998, 19.5545999999999, 15.7375999999999, 17.0720000000001, 16.9517999999998, 15.326, 13.1817999999998, 14.6925999999999, 13.0859999999998, 13.2754, 10.8697999999999, 11.248, 7.3768, 4.72339999999986, 7.97899999999981, 8.7503999999999, 7.68119999999999, 9.7199999999998, 7.73919999999998, 5.6224000000002, 7.44560000000001, 6.6601999999998, 5.9058, 4.00199999999995, 4.51699999999983, 4.68240000000014, 3.86220000000003, 5.13639999999987, 5.98500000000013, 2.47719999999981, 2.61999999999989, 1.62800000000016, 4.65000000000009, 0.225599999999758, 0.831000000000131, -0.359400000000278, 1.27599999999984, -2.92559999999958, -0.0303999999996449, 2.37079999999969, -2.0033999999996, 0.804600000000391, 0.30199999999968, 1.1247999999996, -2.6880000000001, 0.0321999999996478, -1.18099999999959, -3.9402, -1.47940000000017, -0.188400000000001, -2.10720000000038, -2.04159999999956, -3.12880000000041, -4.16160000000036, -0.612799999999879, -3.48719999999958, -8.17900000000009, -5.37780000000021, -4.01379999999972, -5.58259999999973, -5.73719999999958, -7.66799999999967, -5.69520000000011, -1.1247999999996, -5.58520000000044, -8.04560000000038, -4.64840000000004, -11.6468000000004, -7.97519999999986, -5.78300000000036, -7.67420000000038, -10.6328000000003, -9.81720000000041, },
+ // precision 11
+ { 1476, 1449.6014, 1423.5802, 1397.7942, 1372.3042, 1347.2062, 1321.8402, 1297.2292, 1272.9462, 1248.9926, 1225.3026, 1201.4252, 1178.0578, 1155.6092, 1132.626, 1110.5568, 1088.527, 1066.5154, 1045.1874, 1024.3878, 1003.37, 982.1972, 962.5728, 942.1012, 922.9668, 903.292, 884.0772, 864.8578, 846.6562, 828.041, 809.714, 792.3112, 775.1806, 757.9854, 740.656, 724.346, 707.5154, 691.8378, 675.7448, 659.6722, 645.5722, 630.1462, 614.4124, 600.8728, 585.898, 572.408, 558.4926, 544.4938, 531.6776, 517.282, 505.7704, 493.1012, 480.7388, 467.6876, 456.1872, 445.5048, 433.0214, 420.806, 411.409, 400.4144, 389.4294, 379.2286, 369.651, 360.6156, 350.337, 342.083, 332.1538, 322.5094, 315.01, 305.6686, 298.1678, 287.8116, 280.9978, 271.9204, 265.3286, 257.5706, 249.6014, 242.544, 235.5976, 229.583, 220.9438, 214.672, 208.2786, 201.8628, 195.1834, 191.505, 186.1816, 178.5188, 172.2294, 167.8908, 161.0194, 158.052, 151.4588, 148.1596, 143.4344, 138.5238, 133.13, 127.6374, 124.8162, 118.7894, 117.3984, 114.6078, 109.0858, 105.1036, 103.6258, 98.6018000000004, 95.7618000000002, 93.5821999999998, 88.5900000000001, 86.9992000000002, 82.8800000000001, 80.4539999999997, 74.6981999999998, 74.3644000000004, 73.2914000000001, 65.5709999999999, 66.9232000000002, 65.1913999999997, 62.5882000000001, 61.5702000000001, 55.7035999999998, 56.1764000000003, 52.7596000000003, 53.0302000000001, 49.0609999999997, 48.4694, 44.933, 46.0474000000004, 44.7165999999997, 41.9416000000001, 39.9207999999999, 35.6328000000003, 35.5276000000003, 33.1934000000001, 33.2371999999996, 33.3864000000003, 33.9228000000003, 30.2371999999996, 29.1373999999996, 25.2272000000003, 24.2942000000003, 19.8338000000003, 18.9005999999999, 23.0907999999999, 21.8544000000002, 19.5176000000001, 15.4147999999996, 16.9314000000004, 18.6737999999996, 12.9877999999999, 14.3688000000002, 12.0447999999997, 15.5219999999999, 12.5299999999997, 14.5940000000001, 14.3131999999996, 9.45499999999993, 12.9441999999999, 3.91139999999996, 13.1373999999996, 5.44720000000052, 9.82779999999912, 7.87279999999919, 3.67760000000089, 5.46980000000076, 5.55099999999948, 5.65979999999945, 3.89439999999922, 3.1275999999998, 5.65140000000065, 6.3062000000009, 3.90799999999945, 1.87060000000019, 5.17020000000048, 2.46680000000015, 0.770000000000437, -3.72340000000077, 1.16400000000067, 8.05340000000069, 0.135399999999208, 2.15940000000046, 0.766999999999825, 1.0594000000001, 3.15500000000065, -0.287399999999252, 2.37219999999979, -2.86620000000039, -1.63199999999961, -2.22979999999916, -0.15519999999924, -1.46039999999994, -0.262199999999211, -2.34460000000036, -2.8078000000005, -3.22179999999935, -5.60159999999996, -8.42200000000048, -9.43740000000071, 0.161799999999857, -10.4755999999998, -10.0823999999993, },
+ // precision 12
+ { 2953, 2900.4782, 2848.3568, 2796.3666, 2745.324, 2694.9598, 2644.648, 2595.539, 2546.1474, 2498.2576, 2450.8376, 2403.6076, 2357.451, 2311.38, 2266.4104, 2221.5638, 2176.9676, 2134.193, 2090.838, 2048.8548, 2007.018, 1966.1742, 1925.4482, 1885.1294, 1846.4776, 1807.4044, 1768.8724, 1731.3732, 1693.4304, 1657.5326, 1621.949, 1586.5532, 1551.7256, 1517.6182, 1483.5186, 1450.4528, 1417.865, 1385.7164, 1352.6828, 1322.6708, 1291.8312, 1260.9036, 1231.476, 1201.8652, 1173.6718, 1145.757, 1119.2072, 1092.2828, 1065.0434, 1038.6264, 1014.3192, 988.5746, 965.0816, 940.1176, 917.9796, 894.5576, 871.1858, 849.9144, 827.1142, 805.0818, 783.9664, 763.9096, 742.0816, 724.3962, 706.3454, 688.018, 667.4214, 650.3106, 633.0686, 613.8094, 597.818, 581.4248, 563.834, 547.363, 531.5066, 520.455400000001, 505.583199999999, 488.366, 476.480799999999, 459.7682, 450.0522, 434.328799999999, 423.952799999999, 408.727000000001, 399.079400000001, 387.252200000001, 373.987999999999, 360.852000000001, 351.6394, 339.642, 330.902400000001, 322.661599999999, 311.662200000001, 301.3254, 291.7484, 279.939200000001, 276.7508, 263.215200000001, 254.811400000001, 245.5494, 242.306399999999, 234.8734, 223.787200000001, 217.7156, 212.0196, 200.793, 195.9748, 189.0702, 182.449199999999, 177.2772, 170.2336, 164.741, 158.613600000001, 155.311, 147.5964, 142.837, 137.3724, 132.0162, 130.0424, 121.9804, 120.451800000001, 114.8968, 111.585999999999, 105.933199999999, 101.705, 98.5141999999996, 95.0488000000005, 89.7880000000005, 91.4750000000004, 83.7764000000006, 80.9698000000008, 72.8574000000008, 73.1615999999995, 67.5838000000003, 62.6263999999992, 63.2638000000006, 66.0977999999996, 52.0843999999997, 58.9956000000002, 47.0912000000008, 46.4956000000002, 48.4383999999991, 47.1082000000006, 43.2392, 37.2759999999998, 40.0283999999992, 35.1864000000005, 35.8595999999998, 32.0998, 28.027, 23.6694000000007, 33.8266000000003, 26.3736000000008, 27.2008000000005, 21.3245999999999, 26.4115999999995, 23.4521999999997, 19.5013999999992, 19.8513999999996, 10.7492000000002, 18.6424000000006, 13.1265999999996, 18.2436000000016, 6.71860000000015, 3.39459999999963, 6.33759999999893, 7.76719999999841, 0.813999999998487, 3.82819999999992, 0.826199999999517, 8.07440000000133, -1.59080000000176, 5.01780000000144, 0.455399999998917, -0.24199999999837, 0.174800000000687, -9.07640000000174, -4.20160000000033, -3.77520000000004, -4.75179999999818, -5.3724000000002, -8.90680000000066, -6.10239999999976, -5.74120000000039, -9.95339999999851, -3.86339999999836, -13.7304000000004, -16.2710000000006, -7.51359999999841, -3.30679999999847, -13.1339999999982, -10.0551999999989, -6.72019999999975, -8.59660000000076, -10.9307999999983, -1.8775999999998, -4.82259999999951, -13.7788, -21.6470000000008, -10.6735999999983, -15.7799999999988, },
+ // precision 13
+ { 5907.5052, 5802.2672, 5697.347, 5593.5794, 5491.2622, 5390.5514, 5290.3376, 5191.6952, 5093.5988, 4997.3552, 4902.5972, 4808.3082, 4715.5646, 4624.109, 4533.8216, 4444.4344, 4356.3802, 4269.2962, 4183.3784, 4098.292, 4014.79, 3932.4574, 3850.6036, 3771.2712, 3691.7708, 3615.099, 3538.1858, 3463.4746, 3388.8496, 3315.6794, 3244.5448, 3173.7516, 3103.3106, 3033.6094, 2966.5642, 2900.794, 2833.7256, 2769.81, 2707.3196, 2644.0778, 2583.9916, 2523.4662, 2464.124, 2406.073, 2347.0362, 2292.1006, 2238.1716, 2182.7514, 2128.4884, 2077.1314, 2025.037, 1975.3756, 1928.933, 1879.311, 1831.0006, 1783.2144, 1738.3096, 1694.5144, 1649.024, 1606.847, 1564.7528, 1525.3168, 1482.5372, 1443.9668, 1406.5074, 1365.867, 1329.2186, 1295.4186, 1257.9716, 1225.339, 1193.2972, 1156.3578, 1125.8686, 1091.187, 1061.4094, 1029.4188, 1000.9126, 972.3272, 944.004199999999, 915.7592, 889.965, 862.834200000001, 840.4254, 812.598399999999, 785.924200000001, 763.050999999999, 741.793799999999, 721.466, 699.040799999999, 677.997200000002, 649.866999999998, 634.911800000002, 609.8694, 591.981599999999, 570.2922, 557.129199999999, 538.3858, 521.872599999999, 502.951400000002, 495.776399999999, 475.171399999999, 459.751, 439.995200000001, 426.708999999999, 413.7016, 402.3868, 387.262599999998, 372.0524, 357.050999999999, 342.5098, 334.849200000001, 322.529399999999, 311.613799999999, 295.848000000002, 289.273000000001, 274.093000000001, 263.329600000001, 251.389599999999, 245.7392, 231.9614, 229.7952, 217.155200000001, 208.9588, 199.016599999999, 190.839199999999, 180.6976, 176.272799999999, 166.976999999999, 162.5252, 151.196400000001, 149.386999999999, 133.981199999998, 130.0586, 130.164000000001, 122.053400000001, 110.7428, 108.1276, 106.232400000001, 100.381600000001, 98.7668000000012, 86.6440000000002, 79.9768000000004, 82.4722000000002, 68.7026000000005, 70.1186000000016, 71.9948000000004, 58.998599999999, 59.0492000000013, 56.9818000000014, 47.5338000000011, 42.9928, 51.1591999999982, 37.2740000000013, 42.7220000000016, 31.3734000000004, 26.8090000000011, 25.8934000000008, 26.5286000000015, 29.5442000000003, 19.3503999999994, 26.0760000000009, 17.9527999999991, 14.8419999999969, 10.4683999999979, 8.65899999999965, 9.86720000000059, 4.34139999999752, -0.907800000000861, -3.32080000000133, -0.936199999996461, -11.9916000000012, -8.87000000000262, -6.33099999999831, -11.3366000000024, -15.9207999999999, -9.34659999999712, -15.5034000000014, -19.2097999999969, -15.357799999998, -28.2235999999975, -30.6898000000001, -19.3271999999997, -25.6083999999973, -24.409599999999, -13.6385999999984, -33.4473999999973, -32.6949999999997, -28.9063999999998, -31.7483999999968, -32.2935999999972, -35.8329999999987, -47.620600000002, -39.0855999999985, -33.1434000000008, -46.1371999999974, -37.5892000000022, -46.8164000000033, -47.3142000000007, -60.2914000000019, -37.7575999999972, },
+ // precision 14
+ { 11816.475, 11605.0046, 11395.3792, 11188.7504, 10984.1814, 10782.0086, 10582.0072, 10384.503, 10189.178, 9996.2738, 9806.0344, 9617.9798, 9431.394, 9248.7784, 9067.6894, 8889.6824, 8712.9134, 8538.8624, 8368.4944, 8197.7956, 8031.8916, 7866.6316, 7703.733, 7544.5726, 7386.204, 7230.666, 7077.8516, 6926.7886, 6778.6902, 6631.9632, 6487.304, 6346.7486, 6206.4408, 6070.202, 5935.2576, 5799.924, 5671.0324, 5541.9788, 5414.6112, 5290.0274, 5166.723, 5047.6906, 4929.162, 4815.1406, 4699.127, 4588.5606, 4477.7394, 4369.4014, 4264.2728, 4155.9224, 4055.581, 3955.505, 3856.9618, 3761.3828, 3666.9702, 3575.7764, 3482.4132, 3395.0186, 3305.8852, 3221.415, 3138.6024, 3056.296, 2970.4494, 2896.1526, 2816.8008, 2740.2156, 2670.497, 2594.1458, 2527.111, 2460.8168, 2387.5114, 2322.9498, 2260.6752, 2194.2686, 2133.7792, 2074.767, 2015.204, 1959.4226, 1898.6502, 1850.006, 1792.849, 1741.4838, 1687.9778, 1638.1322, 1589.3266, 1543.1394, 1496.8266, 1447.8516, 1402.7354, 1361.9606, 1327.0692, 1285.4106, 1241.8112, 1201.6726, 1161.973, 1130.261, 1094.2036, 1048.2036, 1020.6436, 990.901400000002, 961.199800000002, 924.769800000002, 899.526400000002, 872.346400000002, 834.375, 810.432000000001, 780.659800000001, 756.013800000001, 733.479399999997, 707.923999999999, 673.858, 652.222399999999, 636.572399999997, 615.738599999997, 586.696400000001, 564.147199999999, 541.679600000003, 523.943599999999, 505.714599999999, 475.729599999999, 461.779600000002, 449.750800000002, 439.020799999998, 412.7886, 400.245600000002, 383.188199999997, 362.079599999997, 357.533799999997, 334.319000000003, 327.553399999997, 308.559399999998, 291.270199999999, 279.351999999999, 271.791400000002, 252.576999999997, 247.482400000001, 236.174800000001, 218.774599999997, 220.155200000001, 208.794399999999, 201.223599999998, 182.995600000002, 185.5268, 164.547400000003, 176.5962, 150.689599999998, 157.8004, 138.378799999999, 134.021200000003, 117.614399999999, 108.194000000003, 97.0696000000025, 89.6042000000016, 95.6030000000028, 84.7810000000027, 72.635000000002, 77.3482000000004, 59.4907999999996, 55.5875999999989, 50.7346000000034, 61.3916000000027, 50.9149999999936, 39.0384000000049, 58.9395999999979, 29.633600000001, 28.2032000000036, 26.0078000000067, 17.0387999999948, 9.22000000000116, 13.8387999999977, 8.07240000000456, 14.1549999999988, 15.3570000000036, 3.42660000000615, 6.24820000000182, -2.96940000000177, -8.79940000000352, -5.97860000000219, -14.4048000000039, -3.4143999999942, -13.0148000000045, -11.6977999999945, -25.7878000000055, -22.3185999999987, -24.409599999999, -31.9756000000052, -18.9722000000038, -22.8678000000073, -30.8972000000067, -32.3715999999986, -22.3907999999938, -43.6720000000059, -35.9038, -39.7492000000057, -54.1641999999993, -45.2749999999942, -42.2989999999991, -44.1089999999967, -64.3564000000042, -49.9551999999967, -42.6116000000038, },
+ // precision 15
+ { 23634.0036, 23210.8034, 22792.4744, 22379.1524, 21969.7928, 21565.326, 21165.3532, 20770.2806, 20379.9892, 19994.7098, 19613.318, 19236.799, 18865.4382, 18498.8244, 18136.5138, 17778.8668, 17426.2344, 17079.32, 16734.778, 16397.2418, 16063.3324, 15734.0232, 15409.731, 15088.728, 14772.9896, 14464.1402, 14157.5588, 13855.5958, 13559.3296, 13264.9096, 12978.326, 12692.0826, 12413.8816, 12137.3192, 11870.2326, 11602.5554, 11340.3142, 11079.613, 10829.5908, 10583.5466, 10334.0344, 10095.5072, 9859.694, 9625.2822, 9395.7862, 9174.0586, 8957.3164, 8738.064, 8524.155, 8313.7396, 8116.9168, 7913.542, 7718.4778, 7521.65, 7335.5596, 7154.2906, 6968.7396, 6786.3996, 6613.236, 6437.406, 6270.6598, 6107.7958, 5945.7174, 5787.6784, 5635.5784, 5482.308, 5337.9784, 5190.0864, 5045.9158, 4919.1386, 4771.817, 4645.7742, 4518.4774, 4385.5454, 4262.6622, 4142.74679999999, 4015.5318, 3897.9276, 3790.7764, 3685.13800000001, 3573.6274, 3467.9706, 3368.61079999999, 3271.5202, 3170.3848, 3076.4656, 2982.38400000001, 2888.4664, 2806.4868, 2711.9564, 2634.1434, 2551.3204, 2469.7662, 2396.61139999999, 2318.9902, 2243.8658, 2171.9246, 2105.01360000001, 2028.8536, 1960.9952, 1901.4096, 1841.86079999999, 1777.54700000001, 1714.5802, 1654.65059999999, 1596.311, 1546.2016, 1492.3296, 1433.8974, 1383.84600000001, 1339.4152, 1293.5518, 1245.8686, 1193.50659999999, 1162.27959999999, 1107.19439999999, 1069.18060000001, 1035.09179999999, 999.679000000004, 957.679999999993, 925.300199999998, 888.099400000006, 848.638600000006, 818.156400000007, 796.748399999997, 752.139200000005, 725.271200000003, 692.216, 671.633600000001, 647.939799999993, 621.670599999998, 575.398799999995, 561.226599999995, 532.237999999998, 521.787599999996, 483.095799999996, 467.049599999998, 465.286399999997, 415.548599999995, 401.047399999996, 380.607999999993, 377.362599999993, 347.258799999996, 338.371599999999, 310.096999999994, 301.409199999995, 276.280799999993, 265.586800000005, 258.994399999996, 223.915999999997, 215.925399999993, 213.503800000006, 191.045400000003, 166.718200000003, 166.259000000005, 162.941200000001, 148.829400000002, 141.645999999993, 123.535399999993, 122.329800000007, 89.473399999988, 80.1962000000058, 77.5457999999926, 59.1056000000099, 83.3509999999951, 52.2906000000075, 36.3979999999865, 40.6558000000077, 42.0003999999899, 19.6630000000005, 19.7153999999864, -8.38539999999921, -0.692799999989802, 0.854800000000978, 3.23219999999856, -3.89040000000386, -5.25880000001052, -24.9052000000083, -22.6837999999989, -26.4286000000138, -34.997000000003, -37.0216000000073, -43.430400000012, -58.2390000000014, -68.8034000000043, -56.9245999999985, -57.8583999999973, -77.3097999999882, -73.2793999999994, -81.0738000000129, -87.4530000000086, -65.0254000000132, -57.296399999992, -96.2746000000043, -103.25, -96.081600000005, -91.5542000000132, -102.465200000006, -107.688599999994, -101.458000000013, -109.715800000005, },
+ // precision 16
+ { 47270, 46423.3584, 45585.7074, 44757.152, 43938.8416, 43130.9514, 42330.03, 41540.407, 40759.6348, 39988.206, 39226.5144, 38473.2096, 37729.795, 36997.268, 36272.6448, 35558.665, 34853.0248, 34157.4472, 33470.5204, 32793.5742, 32127.0194, 31469.4182, 30817.6136, 30178.6968, 29546.8908, 28922.8544, 28312.271, 27707.0924, 27114.0326, 26526.692, 25948.6336, 25383.7826, 24823.5998, 24272.2974, 23732.2572, 23201.4976, 22674.2796, 22163.6336, 21656.515, 21161.7362, 20669.9368, 20189.4424, 19717.3358, 19256.3744, 18795.9638, 18352.197, 17908.5738, 17474.391, 17052.918, 16637.2236, 16228.4602, 15823.3474, 15428.6974, 15043.0284, 14667.6278, 14297.4588, 13935.2882, 13578.5402, 13234.6032, 12882.1578, 12548.0728, 12219.231, 11898.0072, 11587.2626, 11279.9072, 10973.5048, 10678.5186, 10392.4876, 10105.2556, 9825.766, 9562.5444, 9294.2222, 9038.2352, 8784.848, 8533.2644, 8301.7776, 8058.30859999999, 7822.94579999999, 7599.11319999999, 7366.90779999999, 7161.217, 6957.53080000001, 6736.212, 6548.21220000001, 6343.06839999999, 6156.28719999999, 5975.15419999999, 5791.75719999999, 5621.32019999999, 5451.66, 5287.61040000001, 5118.09479999999, 4957.288, 4798.4246, 4662.17559999999, 4512.05900000001, 4364.68539999999, 4220.77720000001, 4082.67259999999, 3957.19519999999, 3842.15779999999, 3699.3328, 3583.01180000001, 3473.8964, 3338.66639999999, 3233.55559999999, 3117.799, 3008.111, 2909.69140000001, 2814.86499999999, 2719.46119999999, 2624.742, 2532.46979999999, 2444.7886, 2370.1868, 2272.45259999999, 2196.19260000001, 2117.90419999999, 2023.2972, 1969.76819999999, 1885.58979999999, 1833.2824, 1733.91200000001, 1682.54920000001, 1604.57980000001, 1556.11240000001, 1491.3064, 1421.71960000001, 1371.22899999999, 1322.1324, 1264.7892, 1196.23920000001, 1143.8474, 1088.67240000001, 1073.60380000001, 1023.11660000001, 959.036400000012, 927.433199999999, 906.792799999996, 853.433599999989, 841.873800000001, 791.1054, 756.899999999994, 704.343200000003, 672.495599999995, 622.790399999998, 611.254799999995, 567.283200000005, 519.406599999988, 519.188400000014, 495.312800000014, 451.350799999986, 443.973399999988, 431.882199999993, 392.027000000002, 380.924200000009, 345.128999999986, 298.901400000002, 287.771999999997, 272.625, 247.253000000026, 222.490600000019, 223.590000000026, 196.407599999977, 176.425999999978, 134.725199999986, 132.4804, 110.445599999977, 86.7939999999944, 56.7038000000175, 64.915399999998, 38.3726000000024, 37.1606000000029, 46.170999999973, 49.1716000000015, 15.3362000000197, 6.71639999997569, -34.8185999999987, -39.4476000000141, 12.6830000000191, -12.3331999999937, -50.6565999999875, -59.9538000000175, -65.1054000000004, -70.7576000000117, -106.325200000021, -126.852200000023, -110.227599999984, -132.885999999999, -113.897200000007, -142.713800000027, -151.145399999979, -150.799200000009, -177.756200000003, -156.036399999983, -182.735199999996, -177.259399999981, -198.663600000029, -174.577600000019, -193.84580000001, },
+ // precision 17
+ { 94541, 92848.811, 91174.019, 89517.558, 87879.9705, 86262.7565, 84663.5125, 83083.7435, 81521.7865, 79977.272, 78455.9465, 76950.219, 75465.432, 73994.152, 72546.71, 71115.2345, 69705.6765, 68314.937, 66944.2705, 65591.255, 64252.9485, 62938.016, 61636.8225, 60355.592, 59092.789, 57850.568, 56624.518, 55417.343, 54231.1415, 53067.387, 51903.526, 50774.649, 49657.6415, 48561.05, 47475.7575, 46410.159, 45364.852, 44327.053, 43318.4005, 42325.6165, 41348.4595, 40383.6265, 39436.77, 38509.502, 37594.035, 36695.939, 35818.6895, 34955.691, 34115.8095, 33293.949, 32465.0775, 31657.6715, 30877.2585, 30093.78, 29351.3695, 28594.1365, 27872.115, 27168.7465, 26477.076, 25774.541, 25106.5375, 24452.5135, 23815.5125, 23174.0655, 22555.2685, 21960.2065, 21376.3555, 20785.1925, 20211.517, 19657.0725, 19141.6865, 18579.737, 18081.3955, 17578.995, 17073.44, 16608.335, 16119.911, 15651.266, 15194.583, 14749.0495, 14343.4835, 13925.639, 13504.509, 13099.3885, 12691.2855, 12328.018, 11969.0345, 11596.5145, 11245.6355, 10917.6575, 10580.9785, 10277.8605, 9926.58100000001, 9605.538, 9300.42950000003, 8989.97850000003, 8728.73249999998, 8448.3235, 8175.31050000002, 7898.98700000002, 7629.79100000003, 7413.76199999999, 7149.92300000001, 6921.12650000001, 6677.1545, 6443.28000000003, 6278.23450000002, 6014.20049999998, 5791.20299999998, 5605.78450000001, 5438.48800000001, 5234.2255, 5059.6825, 4887.43349999998, 4682.935, 4496.31099999999, 4322.52250000002, 4191.42499999999, 4021.24200000003, 3900.64799999999, 3762.84250000003, 3609.98050000001, 3502.29599999997, 3363.84250000003, 3206.54849999998, 3079.70000000001, 2971.42300000001, 2867.80349999998, 2727.08100000001, 2630.74900000001, 2496.6165, 2440.902, 2356.19150000002, 2235.58199999999, 2120.54149999999, 2012.25449999998, 1933.35600000003, 1820.93099999998, 1761.54800000001, 1663.09350000002, 1578.84600000002, 1509.48149999999, 1427.3345, 1379.56150000001, 1306.68099999998, 1212.63449999999, 1084.17300000001, 1124.16450000001, 1060.69949999999, 1007.48849999998, 941.194499999983, 879.880500000028, 836.007500000007, 782.802000000025, 748.385499999975, 647.991500000004, 626.730500000005, 570.776000000013, 484.000500000024, 513.98550000001, 418.985499999952, 386.996999999974, 370.026500000036, 355.496999999974, 356.731499999994, 255.92200000002, 259.094000000041, 205.434499999974, 165.374500000034, 197.347500000033, 95.718499999959, 67.6165000000037, 54.6970000000438, 31.7395000000251, -15.8784999999916, 8.42500000004657, -26.3754999999655, -118.425500000012, -66.6629999999423, -42.9745000000112, -107.364999999991, -189.839000000036, -162.611499999999, -164.964999999967, -189.079999999958, -223.931499999948, -235.329999999958, -269.639500000048, -249.087999999989, -206.475499999942, -283.04449999996, -290.667000000016, -304.561499999953, -336.784499999951, -380.386500000022, -283.280499999993, -364.533000000054, -389.059499999974, -364.454000000027, -415.748000000021, -417.155000000028, },
+ // precision 18
+ { 189083, 185696.913, 182348.774, 179035.946, 175762.762, 172526.444, 169329.754, 166166.099, 163043.269, 159958.91, 156907.912, 153906.845, 150924.199, 147996.568, 145093.457, 142239.233, 139421.475, 136632.27, 133889.588, 131174.2, 128511.619, 125868.621, 123265.385, 120721.061, 118181.769, 115709.456, 113252.446, 110840.198, 108465.099, 106126.164, 103823.469, 101556.618, 99308.004, 97124.508, 94937.803, 92833.731, 90745.061, 88677.627, 86617.47, 84650.442, 82697.833, 80769.132, 78879.629, 77014.432, 75215.626, 73384.587, 71652.482, 69895.93, 68209.301, 66553.669, 64921.981, 63310.323, 61742.115, 60205.018, 58698.658, 57190.657, 55760.865, 54331.169, 52908.167, 51550.273, 50225.254, 48922.421, 47614.533, 46362.049, 45098.569, 43926.083, 42736.03, 41593.473, 40425.26, 39316.237, 38243.651, 37170.617, 36114.609, 35084.19, 34117.233, 33206.509, 32231.505, 31318.728, 30403.404, 29540.0550000001, 28679.236, 27825.862, 26965.216, 26179.148, 25462.08, 24645.952, 23922.523, 23198.144, 22529.128, 21762.4179999999, 21134.779, 20459.117, 19840.818, 19187.04, 18636.3689999999, 17982.831, 17439.7389999999, 16874.547, 16358.2169999999, 15835.684, 15352.914, 14823.681, 14329.313, 13816.897, 13342.874, 12880.882, 12491.648, 12021.254, 11625.392, 11293.7610000001, 10813.697, 10456.209, 10099.074, 9755.39000000001, 9393.18500000006, 9047.57900000003, 8657.98499999999, 8395.85900000005, 8033, 7736.95900000003, 7430.59699999995, 7258.47699999996, 6924.58200000005, 6691.29399999999, 6357.92500000005, 6202.05700000003, 5921.19700000004, 5628.28399999999, 5404.96799999999, 5226.71100000001, 4990.75600000005, 4799.77399999998, 4622.93099999998, 4472.478, 4171.78700000001, 3957.46299999999, 3868.95200000005, 3691.14300000004, 3474.63100000005, 3341.67200000002, 3109.14000000001, 3071.97400000005, 2796.40399999998, 2756.17799999996, 2611.46999999997, 2471.93000000005, 2382.26399999997, 2209.22400000005, 2142.28399999999, 2013.96100000001, 1911.18999999994, 1818.27099999995, 1668.47900000005, 1519.65800000005, 1469.67599999998, 1367.13800000004, 1248.52899999998, 1181.23600000003, 1022.71900000004, 1088.20700000005, 959.03600000008, 876.095999999903, 791.183999999892, 703.337000000058, 731.949999999953, 586.86400000006, 526.024999999907, 323.004999999888, 320.448000000091, 340.672999999952, 309.638999999966, 216.601999999955, 102.922999999952, 19.2399999999907, -0.114000000059605, -32.6240000000689, -89.3179999999702, -153.497999999905, -64.2970000000205, -143.695999999996, -259.497999999905, -253.017999999924, -213.948000000091, -397.590000000084, -434.006000000052, -403.475000000093, -297.958000000101, -404.317000000039, -528.898999999976, -506.621000000043, -513.205000000075, -479.351000000024, -596.139999999898, -527.016999999993, -664.681000000099, -680.306000000099, -704.050000000047, -850.486000000034, -757.43200000003, -713.308999999892, }
+ };
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/aggregation/hll/HyperLogLog.java b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/hll/HyperLogLog.java
new file mode 100644
index 00000000000..20beb642fde
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/hll/HyperLogLog.java
@@ -0,0 +1,18 @@
+// 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;
+
+/**
+ * Contain constants relevant for HyperLogLog classes.
+ *
+ * @author bjorncs
+ */
+public interface HyperLogLog {
+ /**
+ * Default HLL precision.
+ */
+ int DEFAULT_PRECISION = 10;
+ /**
+ * Threshold to convert sparse sketch to normal sketch.
+ */
+ int SPARSE_SKETCH_CONVERSION_THRESHOLD = 256;
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/aggregation/hll/HyperLogLogEstimator.java b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/hll/HyperLogLogEstimator.java
new file mode 100644
index 00000000000..7055686a4c0
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/hll/HyperLogLogEstimator.java
@@ -0,0 +1,172 @@
+// 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.google.common.base.Preconditions;
+
+/**
+ * Implementation of the result computation phase of the HyperLogLog algorithm.
+ * Based on the pseudo code from: http://www.dmtcs.org/dmtcs-ojs/index.php/proceedings/article/viewArticle/914
+ *
+ * @author bjorncs
+ */
+public class HyperLogLogEstimator implements UniqueCountEstimator<Sketch<?>> {
+
+ // Number of buckets in sketch.
+ private final int nBuckets;
+ // The bias estimator used to bias correct the raw estimate.
+ private final BiasEstimator biasEstimator;
+ // Linear counting threshold. Linear counting will only be used if raw estimate is equal or below this threshold.
+ private final int linearCountingThreshold;
+ // A bias correcting constant used in calculation of raw estimate.
+ private final double alphaCoefficient;
+
+ /**
+ * Creates the estimator for a given precision. The resulting memory consumption is the exponential to the precision.
+ *
+ * @param precision The precision parameter as defined in HLL algorithm.
+ */
+ public HyperLogLogEstimator(int precision) {
+ Preconditions.checkArgument(precision >= 4 && precision <= 18, "Invalid precision: %s.", precision);
+ this.nBuckets = 1 << precision;
+ this.biasEstimator = new BiasEstimator(precision);
+ this.linearCountingThreshold = getLinearCountingThreshold(precision);
+ this.alphaCoefficient = getAlphaCoefficient(nBuckets);
+ }
+
+
+ /**
+ * Creates the estimator with the default precision ({@link HyperLogLog#DEFAULT_PRECISION}.
+ */
+ public HyperLogLogEstimator() {
+ this(HyperLogLog.DEFAULT_PRECISION);
+ }
+
+ /**
+ * Estimates the number of unique elements.
+ *
+ * @param sketch A sketch populated with values from the aggregation phase of HLL.
+ * @return The estimated number of unique elements.
+ */
+ @Override
+ public long estimateCount(Sketch<?> sketch) {
+ if (sketch instanceof NormalSketch) {
+ return estimateCount((NormalSketch) sketch);
+ } else {
+ return estimateCount((SparseSketch) sketch);
+ }
+ }
+
+ // The sparse sketch contains a set of unique hash values. The size of this set is a good estimator as the
+ // probability for hash collision is very low.
+ private long estimateCount(SparseSketch sketch) {
+ return sketch.size();
+ }
+
+
+ // Performs the result calculation phase of HLL. Note that the {@link NormalSketch}
+ // precision must match the one supplied in the constructor.
+ private long estimateCount(NormalSketch sketch) {
+ Preconditions.checkArgument(sketch.size() == nBuckets,
+ "Sketch has invalid size. Expected %s, actual %s.", nBuckets, sketch.size());
+ double rawEstimate = calculateRawEstimate(sketch);
+ if (shouldPerformBiasCorrection(rawEstimate)) {
+ rawEstimate -= biasEstimator.estimateBias(rawEstimate);
+ }
+
+ // Use linear counting if sketch contains buckets with 0 value.
+ int nZeroBuckets = countZeroBuckets(sketch);
+ if (nZeroBuckets > 0) {
+ double linearCountingEstimate = calculateLinearCountingEstimate(nZeroBuckets);
+ if (linearCountingEstimate <= linearCountingThreshold) {
+ rawEstimate = linearCountingEstimate;
+ }
+ }
+
+ return Math.round(rawEstimate);
+ }
+
+ private double calculateLinearCountingEstimate(int nZeroBuckets) {
+ return nBuckets * Math.log(nBuckets / (double) nZeroBuckets);
+ }
+
+ private boolean shouldPerformBiasCorrection(double rawEstimate) {
+ return rawEstimate <= 5 * nBuckets;
+ }
+
+ private double calculateRawEstimate(NormalSketch sketch) {
+ double indicator = calculateIndicator(sketch);
+ return alphaCoefficient * nBuckets * nBuckets * indicator;
+ }
+
+ // Calculates the raw indicator, summing up the probabilities for each bucket.
+ // indicator == 1 / sum(2^(-S[i]) where i = 0 to n
+ private static double calculateIndicator(NormalSketch sketch) {
+ double sum = 0;
+ for (byte prefixLength : sketch.data()) {
+ sum += Math.pow(2, -prefixLength);
+ }
+ return 1 / sum;
+ }
+
+ private static int countZeroBuckets(NormalSketch sketch) {
+ int nZeroBuckets = 0;
+ for (byte prefixLength : sketch.data()) {
+ if (prefixLength == 0) {
+ ++nZeroBuckets;
+ }
+ }
+ return nZeroBuckets;
+ }
+
+ // Empirically determined values from Google HLL++ paper. Decides whether to use linear counting instead of raw HLL estimate.
+ private static int getLinearCountingThreshold(int precision) {
+ switch (precision) {
+ case 4:
+ return 10;
+ case 5:
+ return 20;
+ case 6:
+ return 40;
+ case 7:
+ return 80;
+ case 8:
+ return 220;
+ case 9:
+ return 400;
+ case 10:
+ return 900;
+ case 11:
+ return 1800;
+ case 12:
+ return 3100;
+ case 13:
+ return 6500;
+ case 14:
+ return 11500;
+ case 15:
+ return 22000;
+ case 16:
+ return 50000;
+ case 17:
+ return 120000;
+ case 18:
+ return 350000;
+ default:
+ // Unreachable code.
+ throw new RuntimeException();
+ }
+ }
+
+ private static double getAlphaCoefficient(int nBuckets) {
+ switch (nBuckets) {
+ case 16:
+ return 0.673;
+ case 32:
+ return 0.697;
+ case 64:
+ return 0.709;
+ default: /* nBuckets >= 128 */
+ return 0.7213 / (1 + 1.079 / nBuckets);
+ }
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/aggregation/hll/NormalSketch.java b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/hll/NormalSketch.java
new file mode 100644
index 00000000000..c91f1e82a3b
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/hll/NormalSketch.java
@@ -0,0 +1,190 @@
+// 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.google.common.base.Preconditions;
+import com.yahoo.vespa.objects.Deserializer;
+import com.yahoo.vespa.objects.Serializer;
+import net.jpountz.lz4.LZ4Compressor;
+import net.jpountz.lz4.LZ4Exception;
+import net.jpountz.lz4.LZ4Factory;
+import net.jpountz.lz4.LZ4FastDecompressor;
+
+import java.util.Arrays;
+
+/**
+ * Sketch used by the HyperLogLog (HLL) algorithm.
+ *
+ * @author bjorncs
+ */
+public class NormalSketch extends Sketch<NormalSketch> {
+
+ public static final int classId = registerClass(0x4000 + 170, NormalSketch.class);
+
+ private final byte[] data;
+ private final int bucketMask;
+
+ /**
+ * Create a sketch with the default precision given by {@link HyperLogLog#DEFAULT_PRECISION}.
+ * */
+ public NormalSketch() {
+ this(HyperLogLog.DEFAULT_PRECISION);
+ }
+
+ /**
+ * Create a sketch with a given HLL precision parameter.
+ *
+ * @param precision The precision parameter used by HLL. Determines the size of the sketch.
+ */
+ public NormalSketch(int precision) {
+ this.data = new byte[1 << precision];
+ this.bucketMask = (1 << precision) - 1; // A mask where the lowest `precision` bits are 1.
+ }
+
+ /**
+ * Lossless merge of sketches. Performs a pairwise maximum on the underlying data array.
+ *
+ * @param other Other sketch
+ */
+ @Override
+ public void merge(NormalSketch other) {
+ Preconditions.checkArgument(data.length == other.data.length,
+ "Trying to merge sketch with one of different size. Expected %s, actual %s", data.length, other.data.length);
+ for (int i = 0; i < data.length; i++) {
+ data[i] = (byte) Math.max(data[i], other.data[i]);
+ }
+ }
+
+ /**
+ * Aggregates the hash values.
+ *
+ * @param hashValues Provides an iterator for the hash values
+ */
+ @Override
+ public void aggregate(Iterable<Integer> hashValues) {
+ for (int hash : hashValues) {
+ aggregate(hash);
+ }
+ }
+
+ /**
+ * Aggregates the hash value.
+ *
+ * @param hash Hash value.
+ */
+ @Override
+ public void aggregate(int hash) {
+ int existingValue = data[hash & bucketMask];
+ int newValue = Integer.numberOfLeadingZeros(hash | bucketMask) + 1;
+ data[hash & bucketMask] = (byte) Math.max(newValue, existingValue);
+ }
+
+ /**
+ * Serializes the Sketch.
+ *
+ * Serialization format
+ * ==================
+ * Original size: 4 bytes
+ * Compressed size: 4 bytes
+ * Compressed data: N * 1 bytes
+ *
+ * Invariant:
+ * compressed size &lt;= original size
+ *
+ * Special case:
+ * compressed size == original size =&gt; data is uncompressed
+ *
+ * @param buf Serializer
+ */
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ buf.putInt(null, data.length);
+ try {
+ LZ4Compressor c = LZ4Factory.safeInstance().highCompressor();
+ byte[] compressedData = new byte[data.length];
+ int compressedSize = c.compress(data, compressedData);
+ serializeDataArray(compressedData, compressedSize, buf);
+ } catch (LZ4Exception e) {
+ // LZ4Compressor.compress will throw this exception if it is unable to compress
+ // into compressedData (when compressed size >= original size)
+ serializeDataArray(data, data.length, buf);
+ }
+ }
+
+ private static void serializeDataArray(byte[] source, int length, Serializer buf) {
+ buf.putInt(null, length);
+ for (int i = 0; i < length; i++) {
+ buf.putByte(null, source[i]);
+ }
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ int length = buf.getInt(null);
+ int compressedLength = buf.getInt(null);
+ Preconditions.checkState(length == data.length,
+ "Size of serialized sketch does not match expected value. Expected %s, actual %s.", data.length, length);
+
+ if (length == compressedLength) {
+ deserializeDataArray(data, length, buf);
+ } else {
+ LZ4FastDecompressor c = LZ4Factory.safeInstance().fastDecompressor();
+ byte[] compressedData = buf.getBytes(null, compressedLength);
+ c.decompress(compressedData, data);
+ }
+ }
+
+ private static void deserializeDataArray(byte[] destination, int length, Deserializer buf) {
+ for (int i = 0; i < length; i++) {
+ destination[i] = buf.getByte(null);
+ }
+ }
+
+ /**
+ * Returns the underlying byte array backing the sketch.
+ *
+ * @return The underlying sketch data
+ */
+ public byte[] data() {
+ return data;
+ }
+
+ /**
+ * Sketch size.
+ *
+ * @return Number of buckets in the sketch.
+ */
+ public int size() {
+ return data.length;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ NormalSketch sketch = (NormalSketch) o;
+
+ if (!Arrays.equals(data, sketch.data)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(data);
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ public String toString() {
+ return "NormalSketch{" +
+ "data=" + Arrays.toString(data) +
+ '}';
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/aggregation/hll/Sketch.java b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/hll/Sketch.java
new file mode 100644
index 00000000000..523942f1e3e
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/hll/Sketch.java
@@ -0,0 +1,32 @@
+// 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.Identifiable;
+
+/**
+ * Represents a sketch. All sketch types must provide a merge method.
+ *
+ * @param <T> The type of the sub-class.
+ */
+public abstract class Sketch<T extends Sketch<T>> extends Identifiable {
+ /**
+ * Merge content of other into 'this'.
+ *
+ * @param other Other sketch
+ */
+ public abstract void merge(T other);
+
+ /**
+ * Aggregates the hash values.
+ *
+ * @param hashValues Provides an iterator for the hash values
+ */
+ public abstract void aggregate(Iterable<Integer> hashValues);
+
+ /**
+ * Aggregates the hash value.
+ *
+ * @param hash Hash value.
+ */
+ public abstract void aggregate(int hash);
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/aggregation/hll/SketchMerger.java b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/hll/SketchMerger.java
new file mode 100644
index 00000000000..9d9a67edafb
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/hll/SketchMerger.java
@@ -0,0 +1,60 @@
+// 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;
+
+/**
+ * This class is responsible for merging any combinations of two {@link Sketch} instances.
+ */
+public class SketchMerger {
+
+ /**
+ * Merges one of the two sketches into the other. The merge operation is performed in-place is possible.
+ *
+ * @param left Either a {@link NormalSketch} or {@link SparseSketch}.
+ * @param right Either a {@link NormalSketch} or {@link SparseSketch}.
+ * @return The merged sketch. Is either first parameter, the other parameter or a new instance.
+ */
+ public Sketch<?> merge(Sketch<?> left, Sketch<?> right) {
+ if (left instanceof NormalSketch && right instanceof NormalSketch) {
+ return mergeNormalWithNormal(asNormal(left), asNormal(right));
+ } else if (left instanceof NormalSketch && right instanceof SparseSketch) {
+ return mergeNormalWithSparse(asNormal(left), asSparse(right));
+ } else if (left instanceof SparseSketch && right instanceof NormalSketch) {
+ return mergeNormalWithSparse(asNormal(right), asSparse(left));
+ } else if (left instanceof SparseSketch && right instanceof SparseSketch) {
+ return mergeSparseWithSparse(asSparse(left), asSparse(right));
+ } else {
+ throw new IllegalArgumentException(
+ String.format("Invalid sketch types: left=%s, right=%s", right.getClass(), left.getClass()));
+ }
+ }
+
+ private Sketch<?> mergeSparseWithSparse(SparseSketch dest, SparseSketch other) {
+ dest.merge(other);
+ if (dest.size() > HyperLogLog.SPARSE_SKETCH_CONVERSION_THRESHOLD) {
+ NormalSketch newSketch = new NormalSketch();
+ newSketch.aggregate(dest.data());
+ return newSketch;
+ }
+ return dest;
+ }
+
+ private NormalSketch mergeNormalWithNormal(NormalSketch dest, NormalSketch other) {
+ dest.merge(other);
+ return dest;
+ }
+
+ private NormalSketch mergeNormalWithSparse(NormalSketch dest, SparseSketch other) {
+ NormalSketch newSketch = new NormalSketch();
+ newSketch.aggregate(other.data());
+ dest.merge(newSketch);
+ return dest;
+ }
+
+ private static NormalSketch asNormal(Sketch<?> sketch) {
+ return (NormalSketch) sketch;
+ }
+
+ private static SparseSketch asSparse(Sketch<?> sketch) {
+ return (SparseSketch) sketch;
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/aggregation/hll/SparseSketch.java b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/hll/SparseSketch.java
new file mode 100644
index 00000000000..fbfd08be6b0
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/hll/SparseSketch.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.aggregation.hll;
+
+import com.yahoo.vespa.objects.Deserializer;
+import com.yahoo.vespa.objects.Serializer;
+
+import java.util.HashSet;
+
+public class SparseSketch extends Sketch<SparseSketch> {
+
+ public static final int classId = registerClass(0x4000 + 171, SparseSketch.class);
+ private final HashSet<Integer> values = new HashSet<>();
+
+ @Override
+ public void merge(SparseSketch other) {
+ values.addAll(other.values);
+ }
+
+ /**
+ * Aggregates the hash values.
+ *
+ * @param hashValues Provides an iterator for the hash values
+ */
+ @Override
+ public void aggregate(Iterable<Integer> hashValues) {
+ for (int hash: hashValues) {
+ aggregate(hash);
+ }
+ }
+
+ /**
+ * Aggregates the hash value.
+ *
+ * @param hash Hash value.
+ */
+ @Override
+ public void aggregate(int hash) {
+ values.add(hash);
+ }
+
+ /**
+ * Serializes the Sketch.
+ *
+ * Serialization format
+ * ==================
+ * Number of elements: 4 bytes
+ * Elements: N * 4 bytes
+ * @param buf Serializer
+ */
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ buf.putInt(null, values.size());
+ for (int value : values) {
+ buf.putInt(null, value);
+ }
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ values.clear();
+ int nElements = buf.getInt(null);
+ for (int i = 0; i < nElements; i++) {
+ values.add(buf.getInt(null));
+ }
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ public HashSet<Integer> data() {
+ return values;
+ }
+
+ public int size() {
+ return values.size();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ SparseSketch sketch = (SparseSketch) o;
+
+ if (!values.equals(sketch.values)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return values.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "SparseSketch{" +
+ "values=" + values +
+ '}';
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/aggregation/hll/UniqueCountEstimator.java b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/hll/UniqueCountEstimator.java
new file mode 100644
index 00000000000..b6edd72c40c
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/hll/UniqueCountEstimator.java
@@ -0,0 +1,12 @@
+// 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;
+
+/**
+ * A interface for unique count estimation algorithms. The goal of this interface is
+ * to aid unit testing of {@link HyperLogLogEstimator} users.
+ *
+ * @author bjorncs
+ */
+public interface UniqueCountEstimator<T> {
+ long estimateCount(T sketch);
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/aggregation/package-info.java b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/package-info.java
new file mode 100644
index 00000000000..2a974a4a3da
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/aggregation/package-info.java
@@ -0,0 +1,4 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage package com.yahoo.searchlib.aggregation;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/document/package-info.java b/searchlib/src/main/java/com/yahoo/searchlib/document/package-info.java
new file mode 100644
index 00000000000..adfc4da0b7e
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/document/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.searchlib.document;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/AddFunctionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/AddFunctionNode.java
new file mode 100644
index 00000000000..a56215a6991
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/AddFunctionNode.java
@@ -0,0 +1,23 @@
+// 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;
+
+/**
+ * This function is an instruction to add all arguments.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class AddFunctionNode extends NumericFunctionNode {
+
+ public static final int classId = registerClass(0x4000 + 61, AddFunctionNode.class);
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onArgument(final ResultNode arg, ResultNode result) {
+ ((NumericResultNode)result).add(arg);
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/AggregationRefNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/AggregationRefNode.java
new file mode 100644
index 00000000000..d16058afde2
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/AggregationRefNode.java
@@ -0,0 +1,115 @@
+// 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.searchlib.aggregation.AggregationResult;
+import com.yahoo.vespa.objects.Deserializer;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+/**
+ * This node holds the index of an ExpressionNode in an external array, and is used as a proxy in the back-end to allow
+ * aggregators to be used in expressions.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Ulf Lilleengen</a>
+ */
+public class AggregationRefNode extends ExpressionNode {
+
+ public static final int classId = registerClass(0x4000 + 142, AggregationRefNode.class);
+ private AggregationResult result = null;
+ private int index = - 1;
+
+ @SuppressWarnings("UnusedDeclaration")
+ public AggregationRefNode() {
+ // Used by deserializer.
+ }
+
+ public AggregationRefNode(int index) {
+ this.index = index;
+ }
+
+ public AggregationRefNode(AggregationResult result) {
+ this.result = result;
+ }
+
+ public AggregationResult getExpression() {
+ return result;
+ }
+
+ public AggregationRefNode setExpression(AggregationResult result) {
+ this.result = result;
+ return this;
+ }
+
+ public AggregationRefNode setIndex(int index) {
+ this.index = index;
+ return this;
+ }
+
+ public int getIndex() {
+ return index;
+ }
+
+ @Override
+ public boolean onExecute() {
+ return result.execute();
+ }
+
+ @Override
+ public void onPrepare() {
+ result.prepare();
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ buf.putInt(null, index);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ index = buf.getInt(null);
+ result = null;
+ }
+
+ @Override
+ public AggregationRefNode clone() {
+ AggregationRefNode obj = (AggregationRefNode)super.clone();
+ obj.index = this.index;
+ obj.result = this.result.clone();
+ return obj;
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("index", index);
+ }
+
+ @Override
+ public ResultNode getResult() {
+ return result.getResult();
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + index;
+ }
+
+ @Override
+ public boolean equalsExpression(ExpressionNode obj) {
+ AggregationRefNode rhs = (AggregationRefNode)obj;
+ if (index != rhs.index) {
+ return false;
+ }
+ if (!equals(result, rhs.result)) {
+ return false;
+ }
+ return true;
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/AndFunctionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/AndFunctionNode.java
new file mode 100644
index 00000000000..f54b8fba9ea
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/AndFunctionNode.java
@@ -0,0 +1,22 @@
+// 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;
+
+/**
+ * This function is an instruction to perform bitwise AND on the result of all arguments in order.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class AndFunctionNode extends BitFunctionNode {
+
+ public static final int classId = registerClass(0x4000 + 67, AndFunctionNode.class);
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ public void onArgument(final ResultNode arg, IntegerResultNode result) {
+ result.andOp(arg);
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/ArithmeticTypeConversion.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/ArithmeticTypeConversion.java
new file mode 100644
index 00000000000..a8484a1245e
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/ArithmeticTypeConversion.java
@@ -0,0 +1,66 @@
+// 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 java.util.HashMap;
+import java.util.Map;
+
+/**
+ * This class implements a lookup table for result node type conversion.
+ *
+ * @author <a href="mailto:lulf@yahoo-inc.com">Ulf Lilleengen</a>
+ */
+public class ArithmeticTypeConversion {
+ private static final Map<Integer, Map<Integer, Integer>> types = new HashMap<Integer, Map<Integer, Integer>>();
+
+ static {
+ add(IntegerResultNode.classId, IntegerResultNode.classId, IntegerResultNode.classId);
+ add(IntegerResultNode.classId, FloatResultNode.classId, FloatResultNode.classId);
+ add(IntegerResultNode.classId, StringResultNode.classId, IntegerResultNode.classId);
+ add(IntegerResultNode.classId, RawResultNode.classId, IntegerResultNode.classId);
+ add(FloatResultNode.classId, IntegerResultNode.classId, FloatResultNode.classId);
+ add(FloatResultNode.classId, FloatResultNode.classId, FloatResultNode.classId);
+ add(FloatResultNode.classId, StringResultNode.classId, FloatResultNode.classId);
+ add(FloatResultNode.classId, RawResultNode.classId, FloatResultNode.classId);
+ add(StringResultNode.classId, IntegerResultNode.classId, IntegerResultNode.classId);
+ add(StringResultNode.classId, FloatResultNode.classId, FloatResultNode.classId);
+ add(StringResultNode.classId, StringResultNode.classId, StringResultNode.classId);
+ add(StringResultNode.classId, RawResultNode.classId, StringResultNode.classId);
+ add(RawResultNode.classId, IntegerResultNode.classId, IntegerResultNode.classId);
+ add(RawResultNode.classId, FloatResultNode.classId, FloatResultNode.classId);
+ add(RawResultNode.classId, StringResultNode.classId, StringResultNode.classId);
+ add(RawResultNode.classId, RawResultNode.classId, RawResultNode.classId);
+ }
+
+ private static void add(int a, int b, int c) {
+ Map<Integer, Integer> entry;
+ if (types.containsKey(a)) {
+ entry = types.get(a);
+ } else {
+ entry = new HashMap<Integer, Integer>();
+ }
+ entry.put(b, c);
+ types.put(a, entry);
+ }
+
+ public static ResultNode getType(ResultNode arg) {
+ return (ResultNode)ResultNode.createFromId(getBaseType(arg));
+ }
+
+ public static ResultNode getType(ResultNode arg1, ResultNode arg2) {
+ return (ResultNode)ResultNode.createFromId(types.get(getBaseType(arg1)).get(getBaseType(arg2)));
+ }
+
+ public static int getBaseType(ResultNode arg) {
+ if (arg instanceof IntegerResultNode) {
+ return IntegerResultNode.classId;
+ } else if (arg instanceof FloatResultNode) {
+ return FloatResultNode.classId;
+ } else if (arg instanceof StringResultNode) {
+ return StringResultNode.classId;
+ } else if (arg instanceof RawResultNode) {
+ return RawResultNode.classId;
+ } else {
+ return ResultNode.classId;
+ }
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/ArrayAtLookupNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/ArrayAtLookupNode.java
new file mode 100644
index 00000000000..0d005e06326
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/ArrayAtLookupNode.java
@@ -0,0 +1,94 @@
+// 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.Deserializer;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+/**
+ * This function is an instruction to retrieve an index a named array attribute.
+ *
+ * @author arnej27959
+ */
+public class ArrayAtLookupNode extends UnaryFunctionNode {
+
+ public static final int classId = registerClass(0x4000 + 38, ArrayAtLookupNode.class);
+ private String attribute;
+
+ /**
+ * Constructs an empty result node.
+ * <b>NOTE:</b> This instance is broken until non-optional member data is set.
+ */
+ public ArrayAtLookupNode() { }
+
+ /**
+ * Constructs an instance of this class with given attribute name
+ * and index argument.
+ *
+ * @param attribute The attribute to retrieve.
+ * @param arg Expression evaluating to the index argument.
+ */
+ public ArrayAtLookupNode(String attribute, ExpressionNode arg) {
+ setAttributeName(attribute);
+ addArg(arg);
+ }
+
+ /**
+ * Returns the name of the attribute whose value we do index in.
+ *
+ * @return The attribute name.
+ */
+ public String getAttributeName() {
+ return attribute;
+ }
+
+ /**
+ * Sets the name of the attribute whose value we do index in.
+ *
+ * @param attribute The attribute to retrieve.
+ * @return This, to allow chaining.
+ */
+ public ArrayAtLookupNode setAttributeName(String attribute) {
+ if (attribute == null) {
+ throw new IllegalArgumentException("Attribute name can not be null.");
+ }
+ this.attribute = attribute;
+ return this;
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ putUtf8(buf, attribute);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ attribute = getUtf8(buf);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + attribute.hashCode();
+ }
+
+ @Override
+ protected boolean equalsUnaryFunction(UnaryFunctionNode obj) {
+ // "arg" checked by superclass
+ String otherAttr = ((ArrayAtLookupNode)obj).getAttributeName();
+ return attribute.equals(otherAttr);
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("attribute", attribute);
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/AttributeNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/AttributeNode.java
new file mode 100644
index 00000000000..e58bf1e317d
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/AttributeNode.java
@@ -0,0 +1,90 @@
+// 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.Deserializer;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+/**
+ * This function is an instruction to retrieve the value of a named attribute.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class AttributeNode extends FunctionNode {
+
+ public static final int classId = registerClass(0x4000 + 55, AttributeNode.class);
+ private String attribute;
+
+ /**
+ * Constructs an empty result node. <b>NOTE:</b> This instance is broken until non-optional member data is set.
+ */
+ public AttributeNode() {
+
+ }
+
+ /**
+ * Constructs an instance of this class with given attribute name.
+ *
+ * @param attribute The attribute to retrieve.
+ */
+ public AttributeNode(String attribute) {
+ setAttributeName(attribute);
+ }
+
+ /**
+ * Returns the name of the attribute whose value this function is to retrieve.
+ *
+ * @return The attribute name.
+ */
+ public String getAttributeName() {
+ return attribute;
+ }
+
+ /**
+ * Sets the name of the attribute whose value this function is to retrieve.
+ *
+ * @param attribute The attribute to retrieve.
+ * @return This, to allow chaining.
+ */
+ public AttributeNode setAttributeName(String attribute) {
+ if (attribute == null) {
+ throw new IllegalArgumentException("Attribute name can not be null.");
+ }
+ this.attribute = attribute;
+ return this;
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ putUtf8(buf, attribute);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ attribute = getUtf8(buf);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + attribute.hashCode();
+ }
+
+ @Override
+ protected boolean equalsFunction(FunctionNode obj) {
+ return attribute.equals(((AttributeNode)obj).attribute);
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("attribute", attribute);
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/BitFunctionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/BitFunctionNode.java
new file mode 100644
index 00000000000..830b74bbb5f
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/BitFunctionNode.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;
+
+/**
+ * This is an abstract super-class for all non-unary functions that operator on bit values.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public abstract class BitFunctionNode extends NumericFunctionNode {
+
+ public static final int classId = registerClass(0x4000 + 47, BitFunctionNode.class);
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onArgument(final ResultNode arg, ResultNode result) {
+ onArgument(arg, (IntegerResultNode)result);
+ }
+
+ @Override
+ protected void onPrepareResult() {
+ setResult(new IntegerResultNode(0));
+ }
+
+ /**
+ * Method for performing onArgument on integers, the only type supported for bit operations.
+ *
+ * @param arg Argument given to the bit function.
+ * @param result Place to store the result.
+ */
+ protected abstract void onArgument(final ResultNode arg, IntegerResultNode result);
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/BucketResultNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/BucketResultNode.java
new file mode 100644
index 00000000000..0dc7f49a826
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/BucketResultNode.java
@@ -0,0 +1,47 @@
+// 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;
+
+/**
+ * This is the superclass of all bucket values
+ *
+ * @author <a href="mailto:havardpe@yahoo-inc.com">Haavard Pettersen</a>
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+abstract public class BucketResultNode extends ResultNode {
+
+ // The global class identifier shared with C++.
+ public static final int classId = registerClass(0x4000 + 100, BucketResultNode.class);
+
+ @Override
+ public long getInteger() {
+ return 0;
+ }
+
+ @Override
+ public double getFloat() {
+ return 0.0;
+ }
+
+ @Override
+ public String getString() {
+ return "";
+ }
+
+ @Override
+ public byte[] getRaw() {
+ return new byte[0];
+ }
+
+ @Override
+ public void set(ResultNode rhs) {
+ }
+
+ /**
+ * Tell if this bucket has zero width. Indicates that is has no value and can be considered a NULL range. An empty
+ * range is used by the backend to represent hits that end in no buckets.
+ *
+ * @return If this bucket has zero width.
+ */
+ public abstract boolean empty();
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/CatFunctionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/CatFunctionNode.java
new file mode 100644
index 00000000000..98c3ba0580e
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/CatFunctionNode.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.expression;
+
+/**
+ * This function is an instruction to concatenate the bits of all arguments in order.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class CatFunctionNode extends MultiArgFunctionNode {
+
+ public static final int classId = registerClass(0x4000 + 72, CatFunctionNode.class);
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected boolean equalsMultiArgFunction(MultiArgFunctionNode obj) {
+ return true;
+ }
+
+ @Override
+ protected void onPrepareResult() {
+ setResult(new RawResultNode());
+ }
+
+ @Override
+ protected void onPrepare() {
+ super.onPrepare();
+ }
+
+ @Override
+ protected boolean onExecute() {
+ for (int i = 0; i < getNumArgs(); i++) {
+ getArg(i).execute();
+ ((RawResultNode)getResult()).add(getArg(i).getResult());
+ }
+ return true;
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/ConstantNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/ConstantNode.java
new file mode 100644
index 00000000000..2ba6ee6e1c3
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/ConstantNode.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.expression;
+
+import com.yahoo.vespa.objects.Deserializer;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+/**
+ * This abstract expression node represents a function to execute.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ConstantNode extends ExpressionNode {
+
+ public static final int classId = registerClass(0x4000 + 49, ConstantNode.class);
+ private ResultNode value = null;
+
+ public ConstantNode() {
+
+ }
+
+ public ConstantNode(ResultNode value) {
+ this.value = value;
+ }
+
+ public ResultNode getValue() {
+ return value;
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ serializeOptional(buf, value);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ value = (ResultNode)deserializeOptional(buf);
+ }
+
+ @Override
+ public ConstantNode clone() {
+ ConstantNode obj = (ConstantNode)super.clone();
+ if (value != null) {
+ obj.value = (ResultNode)value.clone();
+ }
+ return obj;
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("value", value);
+ }
+
+ @Override
+ protected void onPrepare() {
+
+ }
+
+ @Override
+ protected boolean onExecute() {
+ return true;
+ }
+
+ @Override
+ public ResultNode getResult() {
+ return value;
+ }
+
+ @Override
+ protected boolean equalsExpression(ExpressionNode obj) {
+ return equals(value, ((ConstantNode)obj).value);
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/DebugWaitFunctionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/DebugWaitFunctionNode.java
new file mode 100644
index 00000000000..c24e6fa1acd
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/DebugWaitFunctionNode.java
@@ -0,0 +1,104 @@
+// 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.Deserializer;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+/**
+ * This is a debug wait function node that waits for a specified amount of time before executing its expression.
+ *
+ * @author <a href="mailto:lulf@yahoo-inc.com">Ulf Lilleengen</a>
+ */
+public class DebugWaitFunctionNode extends UnaryFunctionNode {
+
+ public static final int classId = registerClass(0x4000 + 144, DebugWaitFunctionNode.class);
+ private double waitTime;
+ private boolean busyWait;
+
+ @SuppressWarnings("UnusedDeclaration")
+ public DebugWaitFunctionNode() {
+ // used by deserializer
+ }
+
+ /**
+ * Constructs an instance of this class with given argument and wait parameters.
+ *
+ * @param arg The argument for this function.
+ * @param waitTime The time to wait before executing expression.
+ * @param busyWait true if busy wait, false if not.
+ */
+ public DebugWaitFunctionNode(ExpressionNode arg, double waitTime, boolean busyWait) {
+ addArg(arg);
+ this.waitTime = waitTime;
+ this.busyWait = busyWait;
+ }
+
+ @Override
+ public void onPrepare() {
+ super.onPrepare();
+ }
+
+ @Override
+ public boolean onExecute() {
+ // TODO: Add wait code.
+ double millis = waitTime * 1000.0;
+ long start = System.currentTimeMillis();
+ try {
+ while ((System.currentTimeMillis() - start) < millis) {
+ if (busyWait) {
+ for (int i = 0; i < 1000; i++) {
+ ;
+ }
+ } else {
+ long rem = (long)(millis - (System.currentTimeMillis() - start));
+ Thread.sleep(rem);
+ }
+ }
+ } catch (InterruptedException ie) {
+ // Not critical
+ }
+ getArg().execute();
+ getResult().set(getArg().getResult());
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + (int)waitTime + (busyWait ? 1 : 0);
+ }
+
+ @Override
+ protected boolean equalsUnaryFunction(UnaryFunctionNode obj) {
+ DebugWaitFunctionNode rhs = (DebugWaitFunctionNode)obj;
+ return waitTime == rhs.waitTime && busyWait == rhs.busyWait;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ buf.putDouble(null, waitTime);
+ byte tmp = busyWait ? (byte)1 : (byte)0;
+ buf.putByte(null, tmp);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ waitTime = buf.getDouble(null);
+ byte tmp = buf.getByte(null);
+ busyWait = (tmp != 0);
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("waitTime", waitTime);
+ visitor.visit("busyWait", busyWait);
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/DivideFunctionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/DivideFunctionNode.java
new file mode 100644
index 00000000000..2a99e9f1edb
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/DivideFunctionNode.java
@@ -0,0 +1,23 @@
+// 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;
+
+/**
+ * This function is an instruction to divide the arguments in order.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class DivideFunctionNode extends NumericFunctionNode {
+
+ public static final int classId = registerClass(0x4000 + 63, DivideFunctionNode.class);
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onArgument(final ResultNode arg, ResultNode result) {
+ ((NumericResultNode)result).divide(arg);
+ }
+} \ No newline at end of file
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/DocumentAccessorNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/DocumentAccessorNode.java
new file mode 100644
index 00000000000..dabbf8d622b
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/DocumentAccessorNode.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;
+
+/**
+ * This abstract expression node represents a document whose content is accessed depending on the subclass
+ * implementation of this.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public abstract class DocumentAccessorNode extends ExpressionNode {
+
+ public static final int classId = registerClass(0x4000 + 48, FunctionNode.class);
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/DocumentFieldNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/DocumentFieldNode.java
new file mode 100644
index 00000000000..c33ef6cd7fd
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/DocumentFieldNode.java
@@ -0,0 +1,116 @@
+// 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.Deserializer;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+/**
+ * The node is a request to retrieve the content of a document field.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class DocumentFieldNode extends DocumentAccessorNode {
+
+ public static final int classId = registerClass(0x4000 + 56, DocumentFieldNode.class);
+ private String fieldName;
+ private ResultNode result;
+
+ /**
+ * Constructs an empty result node. <b>NOTE:</b> This instance is broken until non-optional member data is set.
+ */
+ public DocumentFieldNode() {
+ super();
+ }
+
+ /**
+ * Constructs an instance of this class with given field name.
+ *
+ * @param fieldName The field whose value to retrieve.
+ */
+ public DocumentFieldNode(String fieldName) {
+ super();
+ setDocumentFieldName(fieldName);
+ }
+
+ /**
+ * Returns the name of the field whose value to retrieve.
+ *
+ * @return The field name.
+ */
+ public String getDocumentFieldName() {
+ return fieldName;
+ }
+
+ /**
+ * Sets the name of the field whose value to retrieve.
+ *
+ * @param fieldName The field name to set.
+ * @return This, to allow chaining.
+ */
+ public DocumentFieldNode setDocumentFieldName(String fieldName) {
+ if (fieldName == null) {
+ throw new IllegalArgumentException("Field name can not be null.");
+ }
+ this.fieldName = fieldName;
+ return this;
+ }
+
+ @Override
+ public ResultNode getResult() {
+ return result;
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ putUtf8(buf, fieldName);
+ serializeOptional(buf, result);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ fieldName = getUtf8(buf);
+ result = (ResultNode)deserializeOptional(buf);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + fieldName.hashCode();
+ }
+
+ @Override
+ protected boolean equalsExpression(ExpressionNode obj) {
+ DocumentFieldNode rhs = (DocumentFieldNode)obj;
+ if (!fieldName.equals(rhs.fieldName)) {
+ return false;
+ }
+ if (!equals(result, rhs.result)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public DocumentFieldNode clone() {
+ DocumentFieldNode obj = (DocumentFieldNode)super.clone();
+ if (result != null) {
+ obj.result = (ResultNode)result.clone();
+ }
+ return obj;
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("fieldName", fieldName);
+ visitor.visit("result", result);
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/ExpressionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/ExpressionNode.java
new file mode 100644
index 00000000000..07aa4c8d580
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/ExpressionNode.java
@@ -0,0 +1,104 @@
+// 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.Deserializer;
+import com.yahoo.vespa.objects.Identifiable;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+import java.io.Serializable;
+
+/**
+ * This is the base class for all expression node types. There is no execution logic implemented in Java, since that all
+ * happens in the C++ backend. This class hierarchy is for <b>building</b> the expression tree to pass to the backend.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public abstract class ExpressionNode extends Identifiable implements Serializable {
+
+ public static final int classId = registerClass(0x4000 + 40, ExpressionNode.class);
+
+ /**
+ * Prepare expression for execution.
+ */
+ public void prepare() {
+ onPrepare();
+ }
+
+ /**
+ * Execute expression.
+ *
+ * @return true if successful, false if not.
+ */
+ public boolean execute() {
+ return onExecute();
+ }
+
+ /**
+ * Give an argument to this expression and store the result.
+ *
+ * @param arg Argument to use for expression.
+ * @param result Node to contain the result.
+ */
+ protected void executeIterative(final ResultNode arg, ResultNode result) {
+ onArgument(arg, result);
+ }
+
+ protected boolean onExecute() {
+ throw new RuntimeException("Class " + this.getClass().getName() + " does not implement onExecute().");
+ }
+
+ protected void onPrepare() {
+ throw new RuntimeException("Class " + this.getClass().getName() + " does not implement onPrepare().");
+ }
+
+ protected void onArgument(final ResultNode arg, ResultNode result) {
+ throw new RuntimeException("Class " + this.getClass().getName() + " does not implement onArgument().");
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ }
+
+ @Override
+ public ExpressionNode clone() {
+ return (ExpressionNode)super.clone();
+ }
+
+ @Override
+ public final boolean equals(Object obj) {
+ if (!super.equals(obj)) {
+ return false;
+ }
+ if (!equalsExpression((ExpressionNode)obj)) {
+ return false;
+ }
+ return true;
+ }
+
+ protected abstract boolean equalsExpression(ExpressionNode obj);
+
+ /**
+ * Get the result of this expression.
+ *
+ * @return the result as a ResultNode.
+ */
+ abstract public ResultNode getResult();
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/FixedWidthBucketFunctionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/FixedWidthBucketFunctionNode.java
new file mode 100644
index 00000000000..5c8a526291d
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/FixedWidthBucketFunctionNode.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.expression;
+
+import com.yahoo.vespa.objects.Deserializer;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+/**
+ * This function assign a fixed width bucket to each input value
+ *
+ * @author <a href="mailto:havardpe@yahoo-inc.com">Haavard Pettersen</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class FixedWidthBucketFunctionNode extends UnaryFunctionNode {
+
+ public static final int classId = registerClass(0x4000 + 77, FixedWidthBucketFunctionNode.class);
+ private NumericResultNode width = null;
+
+ /**
+ * Constructs an empty result node.
+ */
+ public FixedWidthBucketFunctionNode() {
+ // empty
+ }
+
+ /**
+ * Create a bucket expression with the given width and the given subexpression
+ *
+ * @param w bucket width
+ * @param arg The argument for this function.
+ */
+ public FixedWidthBucketFunctionNode(NumericResultNode w, ExpressionNode arg) {
+ addArg(arg);
+ width = w;
+ }
+
+ /**
+ * Obtain the width of this bucket expression
+ *
+ * @return bucket width for this expression
+ */
+ public NumericResultNode getWidth() {
+ return width;
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ serializeOptional(buf, width);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ width = (NumericResultNode)deserializeOptional(buf);
+ }
+
+ @Override
+ protected boolean equalsUnaryFunction(UnaryFunctionNode obj) {
+ return equals(width, ((FixedWidthBucketFunctionNode)obj).width);
+ }
+
+ @Override
+ public FixedWidthBucketFunctionNode clone() {
+ FixedWidthBucketFunctionNode obj = (FixedWidthBucketFunctionNode)super.clone();
+ if (width != null) {
+ obj.width = (NumericResultNode)width.clone();
+ }
+ return obj;
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("width", width);
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/FloatBucketResultNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/FloatBucketResultNode.java
new file mode 100644
index 00000000000..e5088e27a2e
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/FloatBucketResultNode.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.Deserializer;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+/**
+ * This result holds a float value.
+ *
+ * @author <a href="mailto:havardpe@yahoo-inc.com">Haavard Pettersen</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class FloatBucketResultNode extends BucketResultNode {
+
+ // The global class identifier shared with C++.
+ public static final int classId = registerClass(0x4000 + 102, FloatBucketResultNode.class);
+
+ // bucket start, inclusive
+ private double from = 0;
+
+ // bucket end, exclusive
+ private double to = 0;
+
+ @Override
+ public boolean empty() {
+ return to == from;
+ }
+
+ /**
+ * Constructs an empty result node.
+ */
+ public FloatBucketResultNode() {
+ // empty
+ }
+
+ /**
+ * Create a bucket with the given limits
+ *
+ * @param from bucket start
+ * @param to bucket end
+ */
+ public FloatBucketResultNode(double from, double to) {
+ this.from = from;
+ this.to = to;
+ }
+
+ /**
+ * Obtain the bucket start
+ *
+ * @return bucket start
+ */
+ public double getFrom() {
+ return from;
+ }
+
+ /**
+ * Obtain the bucket end
+ *
+ * @return bucket end
+ */
+ public double getTo() {
+ return to;
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ buf.putDouble(null, from);
+ buf.putDouble(null, to);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ from = buf.getDouble(null);
+ to = buf.getDouble(null);
+ }
+
+ @Override
+ protected int onCmp(ResultNode rhs) {
+ if (classId != rhs.getClassId()) {
+ return (classId - rhs.getClassId());
+ }
+ FloatBucketResultNode b = (FloatBucketResultNode)rhs;
+ double f1 = from;
+ double f2 = b.from;
+ if (f1 < f2) {
+ return -1;
+ } else if (f1 > f2) {
+ return 1;
+ } else {
+ double t1 = to;
+ double t2 = b.to;
+ if (t1 < t2) {
+ return -1;
+ } else if (t1 > t2) {
+ return 1;
+ }
+ }
+ return 0;
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + (int)from + (int)to;
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("from", from);
+ visitor.visit("to", to);
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/FloatBucketResultNodeVector.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/FloatBucketResultNodeVector.java
new file mode 100644
index 00000000000..9d6d83ccc5c
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/FloatBucketResultNodeVector.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.expression;
+
+import com.yahoo.vespa.objects.Deserializer;
+import com.yahoo.vespa.objects.Serializer;
+
+import java.util.ArrayList;
+
+/**
+ * This result holds nothing.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class FloatBucketResultNodeVector extends ResultNodeVector {
+
+ // The global class identifier shared with C++.
+ public static final int classId = registerClass(0x4000 + 113, FloatBucketResultNodeVector.class);
+ private ArrayList<FloatBucketResultNode> vector = new ArrayList<FloatBucketResultNode>();
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ public FloatBucketResultNodeVector() {
+ }
+
+ public FloatBucketResultNodeVector add(FloatBucketResultNode v) {
+ vector.add(v);
+ return this;
+ }
+
+ public ResultNodeVector add(ResultNode r) {
+ return add((FloatBucketResultNode)r);
+ }
+
+ public ArrayList<FloatBucketResultNode> getVector() {
+ return vector;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ buf.putInt(null, vector.size());
+ for (FloatBucketResultNode node : vector) {
+ node.serialize(buf);
+ }
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ int sz = buf.getInt(null);
+ vector = new ArrayList<FloatBucketResultNode>();
+ for (int i = 0; i < sz; i++) {
+ FloatBucketResultNode node = new FloatBucketResultNode(0, 0);
+ node.deserialize(buf);
+ vector.add(node);
+ }
+ }
+
+ @Override
+ protected int onCmp(ResultNode rhs) {
+ if (classId != rhs.getClassId()) {
+ return (classId - rhs.getClassId());
+ }
+ FloatBucketResultNodeVector b = (FloatBucketResultNodeVector)rhs;
+ int minLength = vector.size();
+ if (b.vector.size() < minLength) {
+ minLength = b.vector.size();
+ }
+ int diff = 0;
+ for (int i = 0; (diff == 0) && (i < minLength); i++) {
+ diff = vector.get(i).compareTo(b.vector.get(i));
+ }
+ return (diff == 0) ? (vector.size() - b.vector.size()) : diff;
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/FloatResultNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/FloatResultNode.java
new file mode 100644
index 00000000000..6e44f113eed
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/FloatResultNode.java
@@ -0,0 +1,182 @@
+// 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.Deserializer;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+import java.nio.ByteBuffer;
+
+/**
+ * This result holds a float value.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class FloatResultNode extends NumericResultNode {
+
+ // The global class identifier shared with C++.
+ public static final int classId = registerClass(0x4000 + 52, FloatResultNode.class);
+ private static FloatResultNode negativeInfinity = new FloatResultNode(Double.NEGATIVE_INFINITY);
+ private static FloatResultNode positiveInfinity = new FloatResultNode(Double.POSITIVE_INFINITY);
+ // The numeric value of this node.
+ private double value;
+
+ /**
+ * Constructs an empty result node.
+ */
+ public FloatResultNode() {
+ super();
+ }
+
+ /**
+ * Constructs an instance of this class with given value.
+ *
+ * @param value The value to assign to this.
+ */
+ public FloatResultNode(double value) {
+ super();
+ setValue(value);
+ }
+
+ /**
+ * Sets the value of this result.
+ *
+ * @param value The value to set.
+ * @return This, to allow chaining.
+ */
+ public FloatResultNode setValue(double value) {
+ this.value = value;
+ return this;
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ buf.putDouble(null, value);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ value = buf.getDouble(null);
+ }
+
+ @Override
+ public long getInteger() {
+ return Math.round(value);
+ }
+
+ @Override
+ public double getFloat() {
+ return value;
+ }
+
+ @Override
+ public String getString() {
+ return String.valueOf(value);
+ }
+
+ @Override
+ public byte[] getRaw() {
+ return ByteBuffer.allocate(8).putDouble(value).array();
+ }
+
+ @Override
+ public void add(ResultNode rhs) {
+ value += rhs.getFloat();
+ }
+
+ @Override
+ public void negate() {
+ value = -value;
+ }
+
+ @Override
+ public void multiply(ResultNode rhs) {
+ value *= rhs.getFloat();
+ }
+
+ @Override
+ public void divide(ResultNode rhs) {
+ double val = rhs.getFloat();
+ value = (val == 0.0) ? 0.0 : (value / val);
+ }
+
+ @Override
+ public void modulo(ResultNode rhs) {
+ value %= rhs.getInteger();
+ }
+
+ @Override
+ public void min(ResultNode rhs) {
+ double value = rhs.getFloat();
+ if (value < this.value) {
+ this.value = value;
+ }
+ }
+
+ @Override
+ public void max(ResultNode rhs) {
+ double value = rhs.getFloat();
+ if (value > this.value) {
+ this.value = value;
+ }
+ }
+
+ @Override
+ public Object getNumber() {
+ return new Double(value);
+ }
+
+ @Override
+ protected int onCmp(ResultNode rhs) {
+ double b = rhs.getFloat();
+ if (Double.isNaN(value)) {
+ return Double.isNaN(b) ? 0 : -1;
+ } else {
+ if (Double.isNaN(b)) {
+ return 1;
+ } else {
+ return (value < b) ? -1 : (value > b) ? 1 : 0;
+ }
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + (int)value;
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("value", value);
+ }
+
+ @Override
+ public void set(ResultNode rhs) {
+ value = rhs.getFloat();
+ }
+
+ /**
+ * Will provide the smallest possible value
+ *
+ * @return the smallest possible FloatResultNode
+ */
+ public static FloatResultNode getNegativeInfinity() {
+ return negativeInfinity;
+ }
+
+ /**
+ * Will provide the largest possible value
+ *
+ * @return the smallest largest FloatResultNode
+ */
+ public static FloatResultNode getPositiveInfinity() {
+ return positiveInfinity;
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/FloatResultNodeVector.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/FloatResultNodeVector.java
new file mode 100644
index 00000000000..ae57aeb6a7f
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/FloatResultNodeVector.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.expression;
+
+import com.yahoo.vespa.objects.Deserializer;
+import com.yahoo.vespa.objects.Serializer;
+
+import java.util.ArrayList;
+
+/**
+ * This result holds nothing.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class FloatResultNodeVector extends ResultNodeVector {
+
+ // The global class identifier shared with C++.
+ public static final int classId = registerClass(0x4000 + 110, FloatResultNodeVector.class);
+ private ArrayList<FloatResultNode> vector = new ArrayList<FloatResultNode>();
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ public FloatResultNodeVector() {
+ }
+
+ public FloatResultNodeVector add(FloatResultNode v) {
+ vector.add(v);
+ return this;
+ }
+
+ public ResultNodeVector add(ResultNode r) {
+ return add((FloatResultNode)r);
+ }
+
+ public ArrayList<FloatResultNode> getVector() {
+ return vector;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ buf.putInt(null, vector.size());
+ for (FloatResultNode node : vector) {
+ node.serialize(buf);
+ }
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ int sz = buf.getInt(null);
+ vector = new ArrayList<FloatResultNode>();
+ for (int i = 0; i < sz; i++) {
+ FloatResultNode node = new FloatResultNode(0);
+ node.deserialize(buf);
+ vector.add(node);
+ }
+ }
+
+ @Override
+ protected int onCmp(ResultNode rhs) {
+ if (classId != rhs.getClassId()) {
+ return (classId - rhs.getClassId());
+ }
+ FloatResultNodeVector b = (FloatResultNodeVector)rhs;
+ int minLength = vector.size();
+ if (b.vector.size() < minLength) {
+ minLength = b.vector.size();
+ }
+ int diff = 0;
+ for (int i = 0; (diff == 0) && (i < minLength); i++) {
+ diff = vector.get(i).compareTo(b.vector.get(i));
+ }
+ return (diff == 0) ? (vector.size() - b.vector.size()) : diff;
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/ForceLoad.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/ForceLoad.java
new file mode 100644
index 00000000000..6ebb4c672c8
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/ForceLoad.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.expression;
+
+/**
+ * This file was generated by ../../../../../forceload.sh
+ */
+public class ForceLoad {
+
+ static {
+ String pkg = "com.yahoo.searchlib.expression";
+ String[] classes = {
+ "StringResultNode",
+ "BucketResultNode",
+ "MaxFunctionNode",
+ "FloatResultNode",
+ "RawResultNode",
+ "NegateFunctionNode",
+ "SortFunctionNode",
+ "ReverseFunctionNode",
+ "ToIntFunctionNode",
+ "ToFloatFunctionNode",
+ "MathFunctionNode",
+ "StrLenFunctionNode",
+ "NormalizeSubjectFunctionNode",
+ "StrCatFunctionNode",
+ "ToStringFunctionNode",
+ "NumElemFunctionNode",
+ "CatFunctionNode",
+ "ResultNode",
+ "AddFunctionNode",
+ "DivideFunctionNode",
+ "XorFunctionNode",
+ "MD5BitFunctionNode",
+ "UnaryBitFunctionNode",
+ "AttributeNode",
+ "MinFunctionNode",
+ "BitFunctionNode",
+ "FixedWidthBucketFunctionNode",
+ "RangeBucketPreDefFunctionNode",
+ "GetYMUMChecksumFunctionNode",
+ "DocumentFieldNode",
+ "NullResultNode",
+ "FunctionNode",
+ "ConstantNode",
+ "RawResultNode",
+ "OrFunctionNode",
+ "ExpressionNode",
+ "AggregationRefNode",
+ "IntegerResultNode",
+ "Int32ResultNode",
+ "Int16ResultNode",
+ "Int8ResultNode",
+ "ModuloFunctionNode",
+ "IntegerResultNodeVector",
+ "Int32ResultNodeVector",
+ "Int16ResultNodeVector",
+ "Int8ResultNodeVector",
+ "FloatResultNodeVector",
+ "StringResultNodeVector",
+ "RawResultNodeVector",
+ "ForceLoad",
+ "MultiplyFunctionNode",
+ "IntegerBucketResultNode",
+ "FloatBucketResultNode",
+ "StringBucketResultNode",
+ "RawBucketResultNode",
+ "RawBucketResultNodeVector",
+ "IntegerBucketResultNodeVector",
+ "FloatBucketResultNodeVector",
+ "StringBucketResultNodeVector",
+ "AndFunctionNode",
+ "DocumentAccessorNode",
+ "GetDocIdNamespaceSpecificFunctionNode",
+ "NumericResultNode",
+ "UnaryFunctionNode",
+ "TimeStampFunctionNode",
+ "ZCurveFunctionNode",
+ "XorBitFunctionNode",
+ "MultiArgFunctionNode",
+ "DebugWaitFunctionNode",
+ "ForceLoad"
+ };
+ com.yahoo.system.ForceLoad.forceLoad(pkg, classes);
+ }
+
+ public static boolean forceLoad() {
+ return true;
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/FunctionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/FunctionNode.java
new file mode 100644
index 00000000000..13f7f8e11a2
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/FunctionNode.java
@@ -0,0 +1,74 @@
+// 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.Deserializer;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+/**
+ * This abstract expression node represents a function to execute.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public abstract class FunctionNode extends ExpressionNode {
+
+ public static final int classId = registerClass(0x4000 + 42, FunctionNode.class);
+ private ResultNode result = null;
+
+ public FunctionNode setResult(ResultNode res) {
+ this.result = res;
+ return this;
+ }
+
+ @Override
+ public final ResultNode getResult() {
+ return result;
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ serializeOptional(buf, result);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ result = (ResultNode)deserializeOptional(buf);
+ }
+
+ @Override
+ public FunctionNode clone() {
+ FunctionNode obj = (FunctionNode)super.clone();
+ if (result != null) {
+ obj.result = (ResultNode)result.clone();
+ }
+ return obj;
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("result", result);
+ }
+
+ @Override
+ protected final boolean equalsExpression(ExpressionNode obj) {
+ FunctionNode rhs = (FunctionNode)obj;
+ if (!equals(result, rhs.result)) {
+ return false;
+ }
+ if (!equalsFunction(rhs)) {
+ return false;
+ }
+ return true;
+ }
+
+ protected abstract boolean equalsFunction(FunctionNode obj);
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/GetDocIdNamespaceSpecificFunctionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/GetDocIdNamespaceSpecificFunctionNode.java
new file mode 100644
index 00000000000..1308e668d3b
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/GetDocIdNamespaceSpecificFunctionNode.java
@@ -0,0 +1,88 @@
+// 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.Deserializer;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+/**
+ * The node is a request to retrieve the namespace-specific content of a document id.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class GetDocIdNamespaceSpecificFunctionNode extends DocumentAccessorNode {
+
+ public static final int classId = registerClass(0x4000 + 73, GetDocIdNamespaceSpecificFunctionNode.class);
+ private ResultNode result = null;
+
+ /**
+ * Constructs an empty result node. <b>NOTE:</b> This instance is broken until non-optional member data is set.
+ */
+ public GetDocIdNamespaceSpecificFunctionNode() {
+ super();
+ }
+
+ /**
+ * Constructs an instance of this class with given result.
+ *
+ * @param result The result to assign to this.
+ */
+ public GetDocIdNamespaceSpecificFunctionNode(ResultNode result) {
+ super();
+ setResult(result);
+ }
+
+ /**
+ * Sets the result of this function.
+ *
+ * @param result The result to set.
+ * @return This, to allow chaining.
+ */
+ public GetDocIdNamespaceSpecificFunctionNode setResult(ResultNode result) {
+ this.result = result;
+ return this;
+ }
+
+ @Override
+ public ResultNode getResult() {
+ return result;
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ serializeOptional(buf, result);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ result = (ResultNode)deserializeOptional(buf);
+ }
+
+ @Override
+ public GetDocIdNamespaceSpecificFunctionNode clone() {
+ GetDocIdNamespaceSpecificFunctionNode obj = (GetDocIdNamespaceSpecificFunctionNode)super.clone();
+ if (result != null) {
+ obj.result = (ResultNode)result.clone();
+ }
+ return obj;
+ }
+
+ @Override
+ protected boolean equalsExpression(ExpressionNode obj) {
+ return equals(result, ((GetDocIdNamespaceSpecificFunctionNode)obj).result);
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("result", result);
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/GetYMUMChecksumFunctionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/GetYMUMChecksumFunctionNode.java
new file mode 100644
index 00000000000..89b1f477706
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/GetYMUMChecksumFunctionNode.java
@@ -0,0 +1,60 @@
+// 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.Deserializer;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+/**
+ * This node is a request to retrieve the YMUM checksum of a document.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class GetYMUMChecksumFunctionNode extends DocumentAccessorNode {
+
+ public static final int classId = registerClass(0x4000 + 74, GetYMUMChecksumFunctionNode.class);
+ private IntegerResultNode result = new IntegerResultNode(0);
+
+ @Override
+ public ResultNode getResult() {
+ return result;
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ result.serialize(buf);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ result.deserialize(buf);
+ }
+
+ @Override
+ public GetYMUMChecksumFunctionNode clone() {
+ GetYMUMChecksumFunctionNode obj = (GetYMUMChecksumFunctionNode)super.clone();
+ if (result != null) {
+ obj.result = (IntegerResultNode)result.clone();
+ }
+ return obj;
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("result", result);
+ }
+
+ @Override
+ protected boolean equalsExpression(ExpressionNode obj) {
+ return equals(result, ((GetYMUMChecksumFunctionNode)obj).result);
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/Int16ResultNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/Int16ResultNode.java
new file mode 100644
index 00000000000..53455fe26ec
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/Int16ResultNode.java
@@ -0,0 +1,149 @@
+// 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.Deserializer;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+import java.nio.ByteBuffer;
+
+/**
+ * This result holds an integer value.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class Int16ResultNode extends NumericResultNode {
+
+ public static final int classId = registerClass(0x4000 + 105, Int16ResultNode.class);
+ private short value = 0;
+
+ @SuppressWarnings("UnusedDeclaration")
+ public Int16ResultNode() {
+ // used by deserializer
+ }
+
+ /**
+ * Constructs an instance of this class with given value.
+ *
+ * @param value The value to assign to this.
+ */
+ public Int16ResultNode(short value) {
+ this.value = value;
+ }
+
+ /**
+ * Sets the value of this result.
+ *
+ * @param value The value to set.
+ * @return This, to allow chaining.
+ */
+ public Int16ResultNode setValue(short value) {
+ this.value = value;
+ return this;
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ buf.putShort(null, value);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ value = buf.getShort(null);
+ }
+
+ @Override
+ public long getInteger() {
+ return value;
+ }
+
+ @Override
+ public double getFloat() {
+ return value;
+ }
+
+ @Override
+ public String getString() {
+ return String.valueOf(value);
+ }
+
+ @Override
+ public byte[] getRaw() {
+ return ByteBuffer.allocate(8).putLong(value).array();
+ }
+
+ @Override
+ public void add(ResultNode rhs) {
+ value += rhs.getInteger();
+ }
+
+ @Override
+ public void negate() {
+ value = (short)-value;
+ }
+
+ @Override
+ public void multiply(ResultNode rhs) {
+ value *= rhs.getInteger();
+ }
+
+ @Override
+ public void divide(ResultNode rhs) {
+ short val = (short)rhs.getInteger();
+ value = (short)((val == 0) ? 0 : (value / val));
+ }
+
+ @Override
+ public void modulo(ResultNode rhs) {
+ value %= rhs.getInteger();
+ }
+
+ @Override
+ public void min(ResultNode rhs) {
+ short value = (short)rhs.getInteger();
+ if (value < this.value) {
+ this.value = value;
+ }
+ }
+
+ @Override
+ public void max(ResultNode rhs) {
+ short value = (short)rhs.getInteger();
+ if (value > this.value) {
+ this.value = value;
+ }
+ }
+
+ @Override
+ public Object getNumber() {
+ return new Integer(value);
+ }
+
+ @Override
+ protected int onCmp(ResultNode rhs) {
+ long value = rhs.getInteger();
+ return (this.value < value) ? -1 : (this.value > value) ? 1 : 0;
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + (int)value;
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("value", value);
+ }
+
+ @Override
+ public void set(ResultNode rhs) {
+ value = (short)rhs.getInteger();
+ }
+} \ No newline at end of file
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/Int16ResultNodeVector.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/Int16ResultNodeVector.java
new file mode 100644
index 00000000000..7e67f80b5e0
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/Int16ResultNodeVector.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.expression;
+
+import com.yahoo.vespa.objects.Deserializer;
+import com.yahoo.vespa.objects.Serializer;
+
+import java.util.ArrayList;
+
+/**
+ * This result holds nothing.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class Int16ResultNodeVector extends ResultNodeVector {
+
+ public static final int classId = registerClass(0x4000 + 117, Int16ResultNodeVector.class);
+ private ArrayList<Int16ResultNode> vector = new ArrayList<Int16ResultNode>();
+
+ public Int16ResultNodeVector() {
+ }
+
+ public Int16ResultNodeVector add(Int16ResultNode v) {
+ vector.add(v);
+ return this;
+ }
+
+ public ArrayList<Int16ResultNode> getVector() {
+ return vector;
+ }
+
+ @Override
+ public ResultNodeVector add(ResultNode r) {
+ return add((Int16ResultNode)r);
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ buf.putInt(null, vector.size());
+ for (Int16ResultNode node : vector) {
+ node.serialize(buf);
+ }
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ int sz = buf.getInt(null);
+ vector = new ArrayList<Int16ResultNode>();
+ for (int i = 0; i < sz; i++) {
+ Int16ResultNode node = new Int16ResultNode((short)0);
+ node.deserialize(buf);
+ vector.add(node);
+ }
+ }
+
+ @Override
+ protected int onCmp(ResultNode rhs) {
+ if (classId != rhs.getClassId()) {
+ return (classId - rhs.getClassId());
+ }
+ Int16ResultNodeVector b = (Int16ResultNodeVector)rhs;
+ int minLength = vector.size();
+ if (b.vector.size() < minLength) {
+ minLength = b.vector.size();
+ }
+ int diff = 0;
+ for (int i = 0; (diff == 0) && (i < minLength); i++) {
+ diff = vector.get(i).compareTo(b.vector.get(i));
+ }
+ return (diff == 0) ? (vector.size() - b.vector.size()) : diff;
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/Int32ResultNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/Int32ResultNode.java
new file mode 100644
index 00000000000..e2acb243714
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/Int32ResultNode.java
@@ -0,0 +1,149 @@
+// 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.Deserializer;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+import java.nio.ByteBuffer;
+
+/**
+ * This result holds an integer value.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class Int32ResultNode extends NumericResultNode {
+
+ public static final int classId = registerClass(0x4000 + 106, Int32ResultNode.class);
+ private int value = 0;
+
+ @SuppressWarnings("UnusedDeclaration")
+ public Int32ResultNode() {
+ // used by deserializer
+ }
+
+ /**
+ * Constructs an instance of this class with given value.
+ *
+ * @param value The value to assign to this.
+ */
+ public Int32ResultNode(int value) {
+ this.value = value;
+ }
+
+ /**
+ * Sets the value of this result.
+ *
+ * @param value The value to set.
+ * @return This, to allow chaining.
+ */
+ public Int32ResultNode setValue(int value) {
+ this.value = value;
+ return this;
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ buf.putInt(null, value);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ value = buf.getInt(null);
+ }
+
+ @Override
+ public long getInteger() {
+ return value;
+ }
+
+ @Override
+ public double getFloat() {
+ return value;
+ }
+
+ @Override
+ public String getString() {
+ return String.valueOf(value);
+ }
+
+ @Override
+ public byte[] getRaw() {
+ return ByteBuffer.allocate(8).putLong(value).array();
+ }
+
+ @Override
+ public void add(ResultNode rhs) {
+ value += rhs.getInteger();
+ }
+
+ @Override
+ public void negate() {
+ value = -value;
+ }
+
+ @Override
+ public void multiply(ResultNode rhs) {
+ value *= rhs.getInteger();
+ }
+
+ @Override
+ public void divide(ResultNode rhs) {
+ int val = (int)rhs.getInteger();
+ value = (val == 0) ? 0 : (value / val);
+ }
+
+ @Override
+ public void modulo(ResultNode rhs) {
+ value %= rhs.getInteger();
+ }
+
+ @Override
+ public void min(ResultNode rhs) {
+ int value = (int)rhs.getInteger();
+ if (value < this.value) {
+ this.value = value;
+ }
+ }
+
+ @Override
+ public void max(ResultNode rhs) {
+ int value = (int)rhs.getInteger();
+ if (value > this.value) {
+ this.value = value;
+ }
+ }
+
+ @Override
+ public Object getNumber() {
+ return new Integer(value);
+ }
+
+ @Override
+ protected int onCmp(ResultNode rhs) {
+ long value = rhs.getInteger();
+ return (this.value < value) ? -1 : (this.value > value) ? 1 : 0;
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + value;
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("value", value);
+ }
+
+ @Override
+ public void set(ResultNode rhs) {
+ value = (int)rhs.getInteger();
+ }
+} \ No newline at end of file
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/Int32ResultNodeVector.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/Int32ResultNodeVector.java
new file mode 100644
index 00000000000..f9166ac63da
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/Int32ResultNodeVector.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.expression;
+
+import com.yahoo.vespa.objects.Deserializer;
+import com.yahoo.vespa.objects.Serializer;
+
+import java.util.ArrayList;
+
+/**
+ * This result holds nothing.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class Int32ResultNodeVector extends ResultNodeVector {
+
+ public static final int classId = registerClass(0x4000 + 118, Int32ResultNodeVector.class);
+ private ArrayList<Int32ResultNode> vector = new ArrayList<Int32ResultNode>();
+
+ public Int32ResultNodeVector() {
+
+ }
+
+ public Int32ResultNodeVector add(Int32ResultNode v) {
+ vector.add(v);
+ return this;
+ }
+
+ public ArrayList<Int32ResultNode> getVector() {
+ return vector;
+ }
+
+ @Override
+ public ResultNodeVector add(ResultNode r) {
+ return add((Int32ResultNode)r);
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ buf.putInt(null, vector.size());
+ for (Int32ResultNode node : vector) {
+ node.serialize(buf);
+ }
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ int sz = buf.getInt(null);
+ vector = new ArrayList<Int32ResultNode>();
+ for (int i = 0; i < sz; i++) {
+ Int32ResultNode node = new Int32ResultNode(0);
+ node.deserialize(buf);
+ vector.add(node);
+ }
+ }
+
+ @Override
+ protected int onCmp(ResultNode rhs) {
+ if (classId != rhs.getClassId()) {
+ return (classId - rhs.getClassId());
+ }
+ Int32ResultNodeVector b = (Int32ResultNodeVector)rhs;
+ int minLength = vector.size();
+ if (b.vector.size() < minLength) {
+ minLength = b.vector.size();
+ }
+ int diff = 0;
+ for (int i = 0; (diff == 0) && (i < minLength); i++) {
+ diff = vector.get(i).compareTo(b.vector.get(i));
+ }
+ return (diff == 0) ? (vector.size() - b.vector.size()) : diff;
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/Int8ResultNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/Int8ResultNode.java
new file mode 100644
index 00000000000..dedb2f3ddbc
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/Int8ResultNode.java
@@ -0,0 +1,149 @@
+// 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.Deserializer;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+import java.nio.ByteBuffer;
+
+/**
+ * This result holds an integer value.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class Int8ResultNode extends NumericResultNode {
+
+ public static final int classId = registerClass(0x4000 + 104, Int8ResultNode.class);
+ private byte value = 0;
+
+ @SuppressWarnings("UnusedDeclaration")
+ public Int8ResultNode() {
+ // used by deserializer
+ }
+
+ /**
+ * Constructs an instance of this class with given value.
+ *
+ * @param value The value to assign to this.
+ */
+ public Int8ResultNode(byte value) {
+ this.value = value;
+ }
+
+ /**
+ * Sets the value of this result.
+ *
+ * @param value The value to set.
+ * @return This, to allow chaining.
+ */
+ public Int8ResultNode setValue(byte value) {
+ this.value = value;
+ return this;
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ buf.putByte(null, value);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ value = buf.getByte(null);
+ }
+
+ @Override
+ public long getInteger() {
+ return value;
+ }
+
+ @Override
+ public double getFloat() {
+ return value;
+ }
+
+ @Override
+ public String getString() {
+ return String.valueOf(value);
+ }
+
+ @Override
+ public byte[] getRaw() {
+ return ByteBuffer.allocate(8).putLong(value).array();
+ }
+
+ @Override
+ public void add(ResultNode rhs) {
+ value += rhs.getInteger();
+ }
+
+ @Override
+ public void negate() {
+ value = (byte)-value;
+ }
+
+ @Override
+ public void multiply(ResultNode rhs) {
+ value *= rhs.getInteger();
+ }
+
+ @Override
+ public void divide(ResultNode rhs) {
+ int val = (int)rhs.getInteger();
+ value = (byte)((val == 0) ? 0 : (value / val));
+ }
+
+ @Override
+ public void modulo(ResultNode rhs) {
+ value %= rhs.getInteger();
+ }
+
+ @Override
+ public void min(ResultNode rhs) {
+ byte value = (byte)rhs.getInteger();
+ if (value < this.value) {
+ this.value = value;
+ }
+ }
+
+ @Override
+ public void max(ResultNode rhs) {
+ byte value = (byte)rhs.getInteger();
+ if (value > this.value) {
+ this.value = value;
+ }
+ }
+
+ @Override
+ public Object getNumber() {
+ return new Integer(value);
+ }
+
+ @Override
+ protected int onCmp(ResultNode rhs) {
+ long value = rhs.getInteger();
+ return (this.value < value) ? -1 : (this.value > value) ? 1 : 0;
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + (int)value;
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("value", value);
+ }
+
+ @Override
+ public void set(ResultNode rhs) {
+ value = (byte)rhs.getInteger();
+ }
+} \ No newline at end of file
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/Int8ResultNodeVector.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/Int8ResultNodeVector.java
new file mode 100644
index 00000000000..da1edfc5a3a
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/Int8ResultNodeVector.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.expression;
+
+import com.yahoo.vespa.objects.Deserializer;
+import com.yahoo.vespa.objects.Serializer;
+
+import java.util.ArrayList;
+
+/**
+ * This result holds nothing.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class Int8ResultNodeVector extends ResultNodeVector {
+
+ public static final int classId = registerClass(0x4000 + 116, Int8ResultNodeVector.class);
+ private ArrayList<Int8ResultNode> vector = new ArrayList<Int8ResultNode>();
+
+ public Int8ResultNodeVector() {
+
+ }
+
+ public Int8ResultNodeVector add(Int8ResultNode v) {
+ vector.add(v);
+ return this;
+ }
+
+ public ArrayList<Int8ResultNode> getVector() {
+ return vector;
+ }
+
+ @Override
+ public ResultNodeVector add(ResultNode r) {
+ return add((Int8ResultNode)r);
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ buf.putInt(null, vector.size());
+ for (Int8ResultNode node : vector) {
+ node.serialize(buf);
+ }
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ int sz = buf.getInt(null);
+ vector = new ArrayList<Int8ResultNode>();
+ for (int i = 0; i < sz; i++) {
+ Int8ResultNode node = new Int8ResultNode((byte)0);
+ node.deserialize(buf);
+ vector.add(node);
+ }
+ }
+
+ @Override
+ protected int onCmp(ResultNode rhs) {
+ if (classId != rhs.getClassId()) {
+ return (classId - rhs.getClassId());
+ }
+ Int8ResultNodeVector b = (Int8ResultNodeVector)rhs;
+ int minLength = vector.size();
+ if (b.vector.size() < minLength) {
+ minLength = b.vector.size();
+ }
+ int diff = 0;
+ for (int i = 0; (diff == 0) && (i < minLength); i++) {
+ diff = vector.get(i).compareTo(b.vector.get(i));
+ }
+ return (diff == 0) ? (vector.size() - b.vector.size()) : diff;
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/IntegerBucketResultNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/IntegerBucketResultNode.java
new file mode 100644
index 00000000000..08a85375e7c
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/IntegerBucketResultNode.java
@@ -0,0 +1,102 @@
+// 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.Deserializer;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+/**
+ * This is an integer bucket value
+ *
+ * @author <a href="mailto:havardpe@yahoo-inc.com">Haavard Pettersen</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class IntegerBucketResultNode extends BucketResultNode {
+
+ public static final int classId = registerClass(0x4000 + 101, IntegerBucketResultNode.class);
+ private long from = 0; // bucket start, inclusive
+ private long to = 0; // bucket end, exclusive
+
+ /**
+ * Constructs an empty result node.
+ */
+ public IntegerBucketResultNode() {
+ // empty
+ }
+
+ /**
+ * Create a bucket with the given limits
+ *
+ * @param from bucket start
+ * @param to bucket end
+ */
+ public IntegerBucketResultNode(long from, long to) {
+ this.from = from;
+ this.to = to;
+ }
+
+ /**
+ * Obtain the bucket start
+ *
+ * @return bucket start
+ */
+ public long getFrom() {
+ return from;
+ }
+
+ /**
+ * Obtain the bucket end
+ *
+ * @return bucket end
+ */
+ public long getTo() {
+ return to;
+ }
+
+ @Override
+ public boolean empty() {
+ return to == from;
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ buf.putLong(null, from);
+ buf.putLong(null, to);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ from = buf.getLong(null);
+ to = buf.getLong(null);
+ }
+
+ @Override
+ protected int onCmp(ResultNode rhs) {
+ if (classId != rhs.getClassId()) {
+ return (classId - rhs.getClassId());
+ }
+ IntegerBucketResultNode b = (IntegerBucketResultNode)rhs;
+ long diff = from - b.from;
+ if (diff == 0) {
+ diff = to - b.to;
+ }
+ return ((diff == 0) ? 0 : ((diff < 0) ? -1 : 1));
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + (int)from + (int)to;
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("from", from);
+ visitor.visit("to", to);
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/IntegerBucketResultNodeVector.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/IntegerBucketResultNodeVector.java
new file mode 100644
index 00000000000..1ea639bd67f
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/IntegerBucketResultNodeVector.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.expression;
+
+import com.yahoo.vespa.objects.Deserializer;
+import com.yahoo.vespa.objects.Serializer;
+
+import java.util.ArrayList;
+
+/**
+ * This result holds nothing.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class IntegerBucketResultNodeVector extends ResultNodeVector {
+
+ public static final int classId = registerClass(0x4000 + 112, IntegerBucketResultNodeVector.class);
+ private ArrayList<IntegerBucketResultNode> vector = new ArrayList<IntegerBucketResultNode>();
+
+ public IntegerBucketResultNodeVector() {
+
+ }
+
+ public IntegerBucketResultNodeVector add(IntegerBucketResultNode v) {
+ vector.add(v);
+ return this;
+ }
+
+ public ArrayList<IntegerBucketResultNode> getVector() {
+ return vector;
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ public ResultNodeVector add(ResultNode r) {
+ return add((IntegerBucketResultNode)r);
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ buf.putInt(null, vector.size());
+ for (IntegerBucketResultNode node : vector) {
+ node.serialize(buf);
+ }
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ int sz = buf.getInt(null);
+ vector = new ArrayList<IntegerBucketResultNode>();
+ for (int i = 0; i < sz; i++) {
+ IntegerBucketResultNode node = new IntegerBucketResultNode(0, 0);
+ node.deserialize(buf);
+ vector.add(node);
+ }
+ }
+
+ @Override
+ protected int onCmp(ResultNode rhs) {
+ if (classId != rhs.getClassId()) {
+ return (classId - rhs.getClassId());
+ }
+ IntegerBucketResultNodeVector b = (IntegerBucketResultNodeVector)rhs;
+ int minLength = vector.size();
+ if (b.vector.size() < minLength) {
+ minLength = b.vector.size();
+ }
+ int diff = 0;
+ for (int i = 0; (diff == 0) && (i < minLength); i++) {
+ diff = vector.get(i).compareTo(b.vector.get(i));
+ }
+ return (diff == 0) ? (vector.size() - b.vector.size()) : diff;
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/IntegerResultNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/IntegerResultNode.java
new file mode 100644
index 00000000000..4ca5dfc4139
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/IntegerResultNode.java
@@ -0,0 +1,183 @@
+// 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.Deserializer;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+import java.nio.ByteBuffer;
+
+/**
+ * This result holds an integer value.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class IntegerResultNode extends NumericResultNode {
+
+ public static final int classId = registerClass(0x4000 + 107, IntegerResultNode.class);
+ private static IntegerResultNode negativeInfinity = new IntegerResultNode(Long.MIN_VALUE);
+ private static IntegerResultNode positiveInfinity = new IntegerResultNode(Long.MAX_VALUE);
+ private long value;
+
+ /**
+ * Constructs an empty result node.
+ */
+ public IntegerResultNode() {
+
+ }
+
+ /**
+ * Constructs an instance of this class with given value.
+ *
+ * @param value The value to assign to this.
+ */
+ public IntegerResultNode(long value) {
+ setValue(value);
+ }
+
+ /**
+ * Sets the value of this result.
+ *
+ * @param value The value to set.
+ * @return This, to allow chaining.
+ */
+ public IntegerResultNode setValue(long value) {
+ this.value = value;
+ return this;
+ }
+
+ void andOp(final ResultNode b) {
+ value &= b.getInteger();
+ }
+
+ void orOp(final ResultNode b) {
+ value |= b.getInteger();
+ }
+
+ void xorOp(final ResultNode b) {
+ value ^= b.getInteger();
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ buf.putLong(null, value);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ value = buf.getLong(null);
+ }
+
+ @Override
+ public long getInteger() {
+ return value;
+ }
+
+ @Override
+ public double getFloat() {
+ return value;
+ }
+
+ @Override
+ public String getString() {
+ return String.valueOf(value);
+ }
+
+ @Override
+ public byte[] getRaw() {
+ return ByteBuffer.allocate(8).putLong(value).array();
+ }
+
+ @Override
+ public void add(ResultNode rhs) {
+ value += rhs.getInteger();
+ }
+
+ @Override
+ public void negate() {
+ value = -value;
+ }
+
+ @Override
+ public void multiply(ResultNode rhs) {
+ value *= rhs.getInteger();
+ }
+
+ @Override
+ public void divide(ResultNode rhs) {
+ long val = rhs.getInteger();
+ value = (val == 0) ? 0 : (value / val);
+ }
+
+ @Override
+ public void modulo(ResultNode rhs) {
+ value %= rhs.getInteger();
+ }
+
+ @Override
+ public void min(ResultNode rhs) {
+ long value = rhs.getInteger();
+ if (value < this.value) {
+ this.value = value;
+ }
+ }
+
+ @Override
+ public void max(ResultNode rhs) {
+ long value = rhs.getInteger();
+ if (value > this.value) {
+ this.value = value;
+ }
+ }
+
+ @Override
+ public Object getNumber() {
+ return new Long(value);
+ }
+
+ @Override
+ protected int onCmp(ResultNode rhs) {
+ long value = rhs.getInteger();
+ return (this.value < value) ? -1 : (this.value > value) ? 1 : 0;
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + (int)value;
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("value", value);
+ }
+
+ @Override
+ public void set(ResultNode rhs) {
+ value = rhs.getInteger();
+ }
+
+ /**
+ * Will provide the smallest possible value
+ *
+ * @return the smallest possible IntegerResultNode
+ */
+ public static IntegerResultNode getNegativeInfinity() {
+ return negativeInfinity;
+ }
+
+ /**
+ * Will provide the largest possible value
+ *
+ * @return the smallest largest IntegerResultNode
+ */
+ public static IntegerResultNode getPositiveInfinity() {
+ return positiveInfinity;
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/IntegerResultNodeVector.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/IntegerResultNodeVector.java
new file mode 100644
index 00000000000..ac55a4e7d8b
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/IntegerResultNodeVector.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.expression;
+
+import com.yahoo.vespa.objects.Deserializer;
+import com.yahoo.vespa.objects.Serializer;
+
+import java.util.ArrayList;
+
+/**
+ * This result holds nothing.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class IntegerResultNodeVector extends ResultNodeVector {
+
+ public static final int classId = registerClass(0x4000 + 119, IntegerResultNodeVector.class);
+ private ArrayList<IntegerResultNode> vector = new ArrayList<IntegerResultNode>();
+
+ public IntegerResultNodeVector() {
+
+ }
+
+ public IntegerResultNodeVector add(IntegerResultNode v) {
+ vector.add(v);
+ return this;
+ }
+
+ public ArrayList<IntegerResultNode> getVector() {
+ return vector;
+ }
+
+ @Override
+ public ResultNodeVector add(ResultNode r) {
+ return add((IntegerResultNode)r);
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ buf.putInt(null, vector.size());
+ for (IntegerResultNode node : vector) {
+ node.serialize(buf);
+ }
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ int sz = buf.getInt(null);
+ vector = new ArrayList<IntegerResultNode>();
+ for (int i = 0; i < sz; i++) {
+ IntegerResultNode node = new IntegerResultNode(0);
+ node.deserialize(buf);
+ vector.add(node);
+ }
+ }
+
+ @Override
+ protected int onCmp(ResultNode rhs) {
+ if (classId != rhs.getClassId()) {
+ return (classId - rhs.getClassId());
+ }
+ IntegerResultNodeVector b = (IntegerResultNodeVector)rhs;
+ int minLength = vector.size();
+ if (b.vector.size() < minLength) {
+ minLength = b.vector.size();
+ }
+ int diff = 0;
+ for (int i = 0; (diff == 0) && (i < minLength); i++) {
+ diff = vector.get(i).compareTo(b.vector.get(i));
+ }
+ return (diff == 0) ? (vector.size() - b.vector.size()) : diff;
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/InterpolatedLookupNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/InterpolatedLookupNode.java
new file mode 100644
index 00000000000..6bd9e10a75a
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/InterpolatedLookupNode.java
@@ -0,0 +1,94 @@
+// 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.Deserializer;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+/**
+ * This function is an instruction to retrieve the value of a named attribute.
+ *
+ * @author arnej27959
+ */
+public class InterpolatedLookupNode extends UnaryFunctionNode {
+
+ public static final int classId = registerClass(0x4000 + 39, InterpolatedLookupNode.class);
+ private String attribute;
+
+ /**
+ * Constructs an empty result node.
+ * <b>NOTE:</b> This instance is broken until non-optional member data is set.
+ */
+ public InterpolatedLookupNode() { }
+
+ /**
+ * Constructs an instance of this class with given attribute name
+ * and lookup argument.
+ *
+ * @param attribute The attribute to retrieve.
+ * @param arg Expression evaluating to the lookup argument.
+ */
+ public InterpolatedLookupNode(String attribute, ExpressionNode arg) {
+ setAttributeName(attribute);
+ addArg(arg);
+ }
+
+ /**
+ * Returns the name of the attribute whose value we do lookup in.
+ *
+ * @return The attribute name.
+ */
+ public String getAttributeName() {
+ return attribute;
+ }
+
+ /**
+ * Sets the name of the attribute whose value we do lookup in.
+ *
+ * @param attribute The attribute to retrieve.
+ * @return This, to allow chaining.
+ */
+ public InterpolatedLookupNode setAttributeName(String attribute) {
+ if (attribute == null) {
+ throw new IllegalArgumentException("Attribute name can not be null.");
+ }
+ this.attribute = attribute;
+ return this;
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ putUtf8(buf, attribute);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ attribute = getUtf8(buf);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + attribute.hashCode();
+ }
+
+ @Override
+ protected boolean equalsUnaryFunction(UnaryFunctionNode obj) {
+ // "arg" checked by superclass
+ String otherAttr = ((InterpolatedLookupNode)obj).getAttributeName();
+ return attribute.equals(otherAttr);
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("attribute", attribute);
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/MD5BitFunctionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/MD5BitFunctionNode.java
new file mode 100644
index 00000000000..64c81072714
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/MD5BitFunctionNode.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;
+
+/**
+ * This function is a request to calculate the MD5 of the result of its argument.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class MD5BitFunctionNode extends UnaryBitFunctionNode {
+
+ public static final int classId = registerClass(0x4000 + 70, MD5BitFunctionNode.class);
+
+ /**
+ * Constructs an empty result node. <b>NOTE:</b> This instance is broken until non-optional member data is set.
+ */
+ public MD5BitFunctionNode() {
+
+ }
+
+ /**
+ * Constructs an instance of this class with given argument and number of bits.
+ *
+ * @param arg The argument for this function.
+ * @param numBits The number of bits to operate on.
+ */
+ public MD5BitFunctionNode(ExpressionNode arg, int numBits) {
+ super(arg, numBits);
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/MathFunctionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/MathFunctionNode.java
new file mode 100644
index 00000000000..0d82b6a260e
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/MathFunctionNode.java
@@ -0,0 +1,185 @@
+// 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.Deserializer;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+/**
+ * This function is an instruction to negate its argument.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class MathFunctionNode extends MultiArgFunctionNode {
+
+ // Make sure these match the definition in c++ searchlib/src/searchlib/expression/mathfunctionnode.h.
+ public static enum Function {
+ EXP(0),
+ POW(1),
+ LOG(2),
+ LOG1P(3),
+ LOG10(4),
+ SIN(5),
+ ASIN(6),
+ COS(7),
+ ACOS(8),
+ TAN(9),
+ ATAN(10),
+ SQRT(11),
+ SINH(12),
+ ASINH(13),
+ COSH(14),
+ ACOSH(15),
+ TANH(16),
+ ATANH(17),
+ CBRT(18),
+ HYPOT(19),
+ FLOOR(20);
+
+ private final int id;
+
+ private Function(int id) {
+ this.id = id;
+ }
+
+ private static Function valueOf(int id) {
+ for (Function fnc : values()) {
+ if (id == fnc.id) {
+ return fnc;
+ }
+ }
+ return null;
+ }
+ }
+
+ public static final int classId = registerClass(0x4000 + 136, MathFunctionNode.class);
+ private Function fnc;
+
+ @SuppressWarnings("UnusedDeclaration")
+ public MathFunctionNode() {
+ this(Function.LOG);
+ }
+
+ public MathFunctionNode(Function fnc) {
+ this(null, fnc);
+ }
+
+ public MathFunctionNode(ExpressionNode exp, Function fnc) {
+ this.fnc = fnc;
+ if (exp != null) {
+ addArg(exp);
+ }
+ }
+
+ @Override
+ protected boolean onExecute() {
+ getArg(0).execute();
+ double result = 0.0;
+ switch (fnc) {
+ case EXP:
+ result = Math.exp(getArg(0).getResult().getFloat());
+ break;
+ case POW:
+ result = Math.pow(getArg(0).getResult().getFloat(), getArg(1).getResult().getFloat());
+ break;
+ case LOG:
+ result = Math.log(getArg(0).getResult().getFloat());
+ break;
+ case LOG1P:
+ result = Math.log1p(getArg(0).getResult().getFloat());
+ break;
+ case LOG10:
+ result = Math.log10(getArg(0).getResult().getFloat());
+ break;
+ case SIN:
+ result = Math.sin(getArg(0).getResult().getFloat());
+ break;
+ case ASIN:
+ result = Math.asin(getArg(0).getResult().getFloat());
+ break;
+ case COS:
+ result = Math.cos(getArg(0).getResult().getFloat());
+ break;
+ case ACOS:
+ result = Math.acos(getArg(0).getResult().getFloat());
+ break;
+ case TAN:
+ result = Math.tan(getArg(0).getResult().getFloat());
+ break;
+ case ATAN:
+ result = Math.atan(getArg(0).getResult().getFloat());
+ break;
+ case SQRT:
+ result = Math.sqrt(getArg(0).getResult().getFloat());
+ break;
+ case SINH:
+ result = Math.sinh(getArg(0).getResult().getFloat());
+ break;
+ case ASINH:
+ throw new IllegalArgumentException("Inverse hyperbolic sine(asinh) is not supported in java");
+ case COSH:
+ result = Math.cosh(getArg(0).getResult().getFloat());
+ break;
+ case ACOSH:
+ throw new IllegalArgumentException("Inverse hyperbolic cosine (acosh) is not supported in java");
+ case TANH:
+ result = Math.tanh(getArg(0).getResult().getFloat());
+ break;
+ case ATANH:
+ throw new IllegalArgumentException("Inverse hyperbolic tangents (atanh) is not supported in java");
+ case FLOOR:
+ result = Math.floor(getArg(0).getResult().getFloat());
+ break;
+ case CBRT:
+ result = Math.cbrt(getArg(0).getResult().getFloat());
+ break;
+ case HYPOT:
+ result = Math.hypot(getArg(0).getResult().getFloat(), getArg(1).getResult().getFloat());
+ break;
+ }
+ ((FloatResultNode)getResult()).setValue(result);
+ return true;
+ }
+
+ @Override
+ public void onPrepareResult() {
+ setResult(new FloatResultNode());
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ buf.putByte(null, (byte)fnc.id);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ int b = buf.getByte(null);
+ fnc = Function.valueOf(b & 0xff);
+ }
+
+ @Override
+ protected boolean equalsMultiArgFunction(MultiArgFunctionNode obj) {
+ return fnc == ((MathFunctionNode)obj).fnc;
+ }
+
+ @Override
+ public MathFunctionNode clone() {
+ MathFunctionNode obj = (MathFunctionNode)super.clone();
+ obj.fnc = fnc;
+ return obj;
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("function", fnc);
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/MaxFunctionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/MaxFunctionNode.java
new file mode 100644
index 00000000000..8496f88eb1c
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/MaxFunctionNode.java
@@ -0,0 +1,23 @@
+// 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;
+
+/**
+ * This function is an instruction to return the maximum value of all its arguments.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class MaxFunctionNode extends NumericFunctionNode {
+
+ public static final int classId = registerClass(0x4000 + 66, MaxFunctionNode.class);
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onArgument(final ResultNode arg, ResultNode result) {
+ ((NumericResultNode)result).max(arg);
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/MinFunctionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/MinFunctionNode.java
new file mode 100644
index 00000000000..f7c18077791
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/MinFunctionNode.java
@@ -0,0 +1,23 @@
+// 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;
+
+/**
+ * This function is an instruction to return the minimum value of all its arguments.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class MinFunctionNode extends NumericFunctionNode {
+
+ public static final int classId = registerClass(0x4000 + 65, MinFunctionNode.class);
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onArgument(final ResultNode arg, ResultNode result) {
+ ((NumericResultNode)result).min(arg);
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/ModuloFunctionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/ModuloFunctionNode.java
new file mode 100644
index 00000000000..a2c919b1d4d
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/ModuloFunctionNode.java
@@ -0,0 +1,23 @@
+// 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;
+
+/**
+ * This function is an instruction to modulo the arguments in order.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ModuloFunctionNode extends NumericFunctionNode {
+
+ public static final int classId = registerClass(0x4000 + 64, ModuloFunctionNode.class);
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onArgument(final ResultNode arg, ResultNode result) {
+ ((NumericResultNode)result).modulo(arg);
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/MultiArgFunctionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/MultiArgFunctionNode.java
new file mode 100644
index 00000000000..4f201e98bfb
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/MultiArgFunctionNode.java
@@ -0,0 +1,176 @@
+// 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.*;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * <p>This is an abstract super-class for all functions that accepts multiple arguments. This node implements the
+ * necessary API for manipulating arguments.</p>
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public abstract class MultiArgFunctionNode extends FunctionNode {
+
+ public static final int classId = registerClass(0x4000 + 45, MultiArgFunctionNode.class);
+ private List<ExpressionNode> args = new ArrayList<ExpressionNode>();
+
+ /**
+ * <p>Adds the given argument to this function.</p>
+ *
+ * @param arg The argument to add.
+ * @return This, to allow chaining.
+ */
+ public MultiArgFunctionNode addArg(ExpressionNode arg) {
+ arg.getClass(); // throws NullPointerException
+ args.add(arg);
+ return this;
+ }
+
+ /**
+ * <p>Returns the argument at the given index.</p>
+ *
+ * @param i The index of the argument to return.
+ * @return The argument.
+ */
+ public ExpressionNode getArg(int i) {
+ return args.get(i);
+ }
+
+ /**
+ * <p>Returns the number of arguments this function has.</p>
+ *
+ * @return The size of the argument list.
+ */
+ public int getNumArgs() {
+ return args.size();
+ }
+
+ @Override
+ protected boolean onExecute() {
+ for (int i = 0; i < args.size(); i++) {
+ args.get(i).execute();
+ }
+ return calculate(args, getResult());
+ }
+
+ @Override
+ protected void onPrepare() {
+ for (int i = 0; i < args.size(); i++) {
+ args.get(i).prepare();
+ }
+ prepareResult();
+ }
+
+ /**
+ * <p>Perform the appropriate calculation of the arguments into a result node.</p>
+ *
+ * @param args A list of operands.
+ * @param result Place to put the result.
+ * @return True if successful, false if not.
+ */
+ private boolean calculate(final List<ExpressionNode> args, ResultNode result) {
+ return onCalculate(args, result);
+ }
+
+ private void prepareResult() {
+ onPrepareResult();
+ }
+
+ protected boolean onCalculate(final List<ExpressionNode> args, ResultNode result) {
+ result.set(args.get(0).getResult());
+ for (int i = 1; i < args.size(); i++) {
+ executeIterative(args.get(i).getResult(), result);
+ }
+ return true;
+ }
+
+ protected void onPrepareResult() {
+ if (args.size() == 1) {
+ setResult(ArithmeticTypeConversion.getType(args.get(0).getResult()));
+ } else if (args.size() > 1) {
+ setResult((ResultNode)args.get(0).getResult().clone());
+ for (int i = 1; i < args.size(); i++) {
+ if (args.get(i).getResult() != null) {
+ setResult(ArithmeticTypeConversion.getType(getResult(), args.get(i).getResult()));
+ }
+ }
+ }
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ int numArgs = args.size();
+ buf.putInt(null, numArgs);
+ for (ExpressionNode node : args) {
+ serializeOptional(buf, node); // TODO: Not optional.
+ }
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ args.clear();
+ int numArgs = buf.getInt(null);
+ for (int i = 0; i < numArgs; i++) {
+ ExpressionNode node = (ExpressionNode)deserializeOptional(buf); // TODO: Not optional.
+ args.add(node);
+ }
+ }
+
+ @Override
+ public int hashCode() {
+ int ret = super.hashCode();
+ for (ExpressionNode node : args) {
+ ret += node.hashCode();
+ }
+ return ret;
+ }
+
+ @Override
+ protected final boolean equalsFunction(FunctionNode obj) {
+ MultiArgFunctionNode rhs = (MultiArgFunctionNode)obj;
+ if (!args.equals(rhs.args)) {
+ return false;
+ }
+ if (!equalsMultiArgFunction(rhs)) {
+ return false;
+ }
+ return true;
+ }
+
+ protected abstract boolean equalsMultiArgFunction(MultiArgFunctionNode obj);
+
+ @Override
+ public MultiArgFunctionNode clone() {
+ MultiArgFunctionNode obj = (MultiArgFunctionNode)super.clone();
+ obj.args = new ArrayList<ExpressionNode>();
+ for (ExpressionNode node : args) {
+ obj.args.add(node.clone());
+ }
+ return obj;
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("args", args);
+ }
+
+ @Override
+ public void selectMembers(ObjectPredicate predicate, ObjectOperation operation) {
+ super.selectMembers(predicate, operation);
+ for (ExpressionNode arg : args) {
+ arg.select(predicate, operation);
+ }
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/MultiplyFunctionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/MultiplyFunctionNode.java
new file mode 100644
index 00000000000..b55e86ba5fe
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/MultiplyFunctionNode.java
@@ -0,0 +1,23 @@
+// 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;
+
+/**
+ * This function is an instruction to multiply all arguments.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class MultiplyFunctionNode extends NumericFunctionNode {
+
+ public static final int classId = registerClass(0x4000 + 62, MultiplyFunctionNode.class);
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onArgument(final ResultNode arg, ResultNode result) {
+ ((NumericResultNode)result).multiply(arg);
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/NegateFunctionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/NegateFunctionNode.java
new file mode 100644
index 00000000000..0fdf07d6291
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/NegateFunctionNode.java
@@ -0,0 +1,52 @@
+// 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;
+
+/**
+ * This function is an instruction to negate its argument.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class NegateFunctionNode extends UnaryFunctionNode {
+
+ public static final int classId = registerClass(0x4000 + 60, NegateFunctionNode.class);
+
+ /**
+ * Constructs an empty result node. <b>NOTE:</b> This instance is broken until non-optional member data is set.
+ */
+ public NegateFunctionNode() {
+
+ }
+
+ /**
+ * Constructs an instance of this class with given argument.
+ *
+ * @param arg The argument for this function.
+ */
+ public NegateFunctionNode(ExpressionNode arg) {
+ addArg(arg);
+ }
+
+ @Override
+ public void onPrepare() {
+ super.onPrepare();
+ }
+
+ @Override
+ public boolean onExecute() {
+ getArg().execute();
+ getResult().set(getArg().getResult());
+ getResult().negate();
+ return true;
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected boolean equalsUnaryFunction(UnaryFunctionNode obj) {
+ return true;
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/NormalizeSubjectFunctionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/NormalizeSubjectFunctionNode.java
new file mode 100644
index 00000000000..dd24c1f9efe
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/NormalizeSubjectFunctionNode.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.expression;
+
+/**
+ * This function is an instruction to negate its argument.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class NormalizeSubjectFunctionNode extends UnaryFunctionNode {
+
+ public static final int classId = registerClass(0x4000 + 143, NormalizeSubjectFunctionNode.class);
+
+ /**
+ * Constructs an empty result node. <b>NOTE:</b> This instance is broken until non-optional member data is set.
+ */
+ public NormalizeSubjectFunctionNode() {
+
+ }
+
+ /**
+ * Constructs an instance of this class with given argument.
+ *
+ * @param arg The argument for this function.
+ */
+ public NormalizeSubjectFunctionNode(ExpressionNode arg) {
+ addArg(arg);
+ }
+
+ @Override
+ public void onPrepareResult() {
+ setResult(new StringResultNode());
+ }
+
+ @Override
+ public void onPrepare() {
+ super.onPrepare();
+ }
+
+ @Override
+ public boolean onExecute() {
+ String result = getArg().getResult().getString();
+
+ if (result.startsWith("Re: ") || result.startsWith("RE: ") || result.startsWith("Fw: ") ||
+ result.startsWith("FW: "))
+ {
+ result = result.substring(4);
+ } else if (result.startsWith("Fwd: ")) {
+ result = result.substring(5);
+ }
+
+ ((StringResultNode)getResult()).setValue(result);
+ return true;
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected boolean equalsUnaryFunction(UnaryFunctionNode obj) {
+ return true;
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/NullResultNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/NullResultNode.java
new file mode 100644
index 00000000000..bc66e0d1899
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/NullResultNode.java
@@ -0,0 +1,56 @@
+// 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.ObjectVisitor;
+
+/**
+ * This result holds nothing.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class NullResultNode extends ResultNode {
+
+ // The global class identifier shared with C++.
+ public static final int classId = registerClass(0x4000 + 57, NullResultNode.class);
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ public long getInteger() {
+ return 0;
+ }
+
+ @Override
+ public double getFloat() {
+ return 0.0;
+ }
+
+ @Override
+ public String getString() {
+ return "";
+ }
+
+ @Override
+ public byte[] getRaw() {
+ return new byte[0];
+ }
+
+ @Override
+ protected int onCmp(ResultNode rhs) {
+ return classId - rhs.getClassId();
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("result", null);
+ }
+
+ @Override
+ public void set(ResultNode rhs) {
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/NumElemFunctionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/NumElemFunctionNode.java
new file mode 100644
index 00000000000..f949dc67936
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/NumElemFunctionNode.java
@@ -0,0 +1,50 @@
+// 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;
+
+/**
+ * This function is an instruction to negate its argument.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class NumElemFunctionNode extends UnaryFunctionNode {
+
+ public static final int classId = registerClass(0x4000 + 132, NumElemFunctionNode.class);
+
+ /**
+ * Constructs an empty result node. <b>NOTE:</b> This instance is broken until non-optional member data is set.
+ */
+ public NumElemFunctionNode() {
+
+ }
+
+ /**
+ * Constructs an instance of this class with given argument.
+ *
+ * @param arg The argument for this function.
+ */
+ public NumElemFunctionNode(ExpressionNode arg) {
+ addArg(arg);
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ public void onPrepareResult() {
+ setResult(new IntegerResultNode(1));
+ }
+
+ @Override
+ public boolean onExecute() {
+ getArg().execute();
+ return true;
+ }
+
+ @Override
+ protected boolean equalsUnaryFunction(UnaryFunctionNode obj) {
+ return true;
+ }
+} \ No newline at end of file
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/NumericFunctionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/NumericFunctionNode.java
new file mode 100644
index 00000000000..a3312313733
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/NumericFunctionNode.java
@@ -0,0 +1,31 @@
+// 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;
+
+/**
+ * This is an abstract class for all functions that perform arithmetics. This node implements the necessary API for
+ * doing arithmetic operations.
+ *
+ * @author <a href="mailto:lulf@yahoo-inc.com">Ulf Lilleengen</a>
+ */
+public abstract class NumericFunctionNode extends MultiArgFunctionNode {
+
+ @Override
+ public void onPrepare() {
+ super.onPrepare();
+
+ ResultNode result = getResult();
+ if (!(result instanceof IntegerResultNode) &&
+ !(result instanceof FloatResultNode) &&
+ !(result instanceof StringResultNode) &&
+ !(result instanceof RawResultNode))
+ {
+ throw new RuntimeException("Can not perform numeric function on value of type '" +
+ getResult().getClass().getName() + "'.");
+ }
+ }
+
+ @Override
+ protected final boolean equalsMultiArgFunction(MultiArgFunctionNode obj) {
+ return true;
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/NumericResultNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/NumericResultNode.java
new file mode 100644
index 00000000000..70a5cdcaf98
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/NumericResultNode.java
@@ -0,0 +1,52 @@
+// 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;
+
+/**
+ * This is a superclass for all numerical results.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+abstract public class NumericResultNode extends SingleResultNode {
+
+ // The global class identifier shared with C++.
+ public static final int classId = registerClass(0x4000 + 50, NumericResultNode.class);
+
+ /**
+ * In-place multiplication of this result with another.
+ *
+ * @param rhs The result to multiply with this.
+ */
+ public abstract void multiply(ResultNode rhs);
+
+ /**
+ * In-place division of this result with another.
+ *
+ * @param rhs The result to divide this by.
+ */
+ public abstract void divide(ResultNode rhs);
+
+ /**
+ * In-place modulo of this result with another.
+ *
+ * @param rhs The result to modulo this with.
+ */
+ public abstract void modulo(ResultNode rhs);
+
+ /**
+ * Return a java numeric, either Double or Long, depending on the underlying container.
+ *
+ * @return The underlying numeric value.
+ */
+ public abstract Object getNumber();
+
+ @Override
+ public Object getValue() {
+ return getNumber();
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/OrFunctionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/OrFunctionNode.java
new file mode 100644
index 00000000000..6f34f261543
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/OrFunctionNode.java
@@ -0,0 +1,22 @@
+// 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;
+
+/**
+ * This function is an instruction to perform bitwise OR on the result of all arguments.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class OrFunctionNode extends BitFunctionNode {
+
+ public static final int classId = registerClass(0x4000 + 68, OrFunctionNode.class);
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ public void onArgument(final ResultNode arg, IntegerResultNode result) {
+ result.orOp(arg);
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/PositiveInfinityResultNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/PositiveInfinityResultNode.java
new file mode 100644
index 00000000000..a72d9d41318
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/PositiveInfinityResultNode.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;
+
+/**
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ */
+public class PositiveInfinityResultNode extends ResultNode {
+ // The global class identifier shared with C++.
+ public static final int classId = registerClass(0x4000 + 124, PositiveInfinityResultNode.class);
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ public long getInteger() {
+ return Long.MAX_VALUE;
+ }
+
+ @Override
+ public double getFloat() {
+ return Double.MAX_VALUE;
+ }
+
+ @Override
+ public byte[] getRaw() {
+ return new byte[0];
+ }
+
+ @Override
+ public String getString() {
+ return "";
+ }
+
+ @Override
+ protected int onCmp(ResultNode rhs) {
+ return rhs instanceof PositiveInfinityResultNode ? 0 : 1;
+ }
+
+ @Override
+ public void set(ResultNode rhs) {
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/RangeBucketPreDefFunctionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/RangeBucketPreDefFunctionNode.java
new file mode 100644
index 00000000000..dab0221fcb5
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/RangeBucketPreDefFunctionNode.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.expression;
+
+import com.yahoo.vespa.objects.Deserializer;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+/**
+ * This function assign a fixed width bucket to each input value
+ *
+ * @author <a href="mailto:havardpe@yahoo-inc.com">Haavard Pettersen</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class RangeBucketPreDefFunctionNode extends UnaryFunctionNode {
+
+ public static final int classId = registerClass(0x4000 + 76, RangeBucketPreDefFunctionNode.class);
+ private ResultNodeVector predef = null;
+
+ /**
+ * Constructs an empty result node.
+ */
+ public RangeBucketPreDefFunctionNode() {
+ // empty
+ }
+
+ /**
+ * Create a bucket expression with the given width and the given subexpression
+ *
+ * @param v predefined bucket list
+ * @param arg The argument for this function.
+ */
+ public RangeBucketPreDefFunctionNode(ResultNodeVector v, ExpressionNode arg) {
+ addArg(arg);
+ predef = v;
+ }
+
+ /**
+ * Obtain the predefined bucket list of this bucket expression
+ *
+ * @return predefined bucket list for this expression
+ */
+ public ResultNodeVector getBucketList() {
+ return predef;
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ serializeOptional(buf, predef);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ predef = (ResultNodeVector)deserializeOptional(buf);
+ }
+
+ @Override
+ protected boolean equalsUnaryFunction(UnaryFunctionNode obj) {
+ return equals(predef, ((RangeBucketPreDefFunctionNode)obj).predef);
+ }
+
+ @Override
+ public RangeBucketPreDefFunctionNode clone() {
+ RangeBucketPreDefFunctionNode obj = (RangeBucketPreDefFunctionNode)super.clone();
+ if (predef != null) {
+ obj.predef = (ResultNodeVector)predef.clone();
+ }
+ return obj;
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("predef", predef);
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/RawBucketResultNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/RawBucketResultNode.java
new file mode 100644
index 00000000000..eef386735a1
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/RawBucketResultNode.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.expression;
+
+import com.yahoo.vespa.objects.Deserializer;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+/**
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ */
+public class RawBucketResultNode extends BucketResultNode {
+
+ // The global class identifier shared with C++.
+ public static final int classId = registerClass(0x4000 + 125, RawBucketResultNode.class);
+
+ // bucket start, inclusive
+ private ResultNode from = RawResultNode.getNegativeInfinity();
+
+ // bucket end, exclusive
+ private ResultNode to = RawResultNode.getNegativeInfinity();
+
+ @Override
+ public boolean empty() {
+ return to.equals(from);
+ }
+
+ /**
+ * Constructs an empty result node.
+ */
+ public RawBucketResultNode() {
+ // empty
+ }
+
+ /**
+ * Create a bucket with the given limits
+ *
+ * @param from bucket start
+ * @param to bucket end
+ */
+ public RawBucketResultNode(ResultNode from, ResultNode to) {
+ this.from = from;
+ this.to = to;
+ }
+
+ /**
+ * Obtain the bucket start
+ *
+ * @return bucket start
+ */
+ public byte[] getFrom() {
+ return from.getRaw();
+ }
+
+ /**
+ * Obtain the bucket end
+ *
+ * @return bucket end
+ */
+ public byte[] getTo() {
+ return to.getRaw();
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ serializeOptional(buf, from);
+ serializeOptional(buf, to);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ from = (ResultNode)deserializeOptional(buf);
+ to = (ResultNode)deserializeOptional(buf);
+ }
+
+ @Override
+ protected int onCmp(ResultNode rhs) {
+ if (classId != rhs.getClassId()) {
+ return (classId - rhs.getClassId());
+ }
+ RawBucketResultNode b = (RawBucketResultNode)rhs;
+ int diff = from.compareTo(b.from);
+ return (diff == 0) ? to.compareTo(b.to) : diff;
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + from.hashCode() + to.hashCode();
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("from", from);
+ visitor.visit("to", to);
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/RawBucketResultNodeVector.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/RawBucketResultNodeVector.java
new file mode 100644
index 00000000000..caed1de4134
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/RawBucketResultNodeVector.java
@@ -0,0 +1,75 @@
+// 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.Deserializer;
+import com.yahoo.vespa.objects.Serializer;
+
+import java.util.ArrayList;
+
+/**
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ */
+public class RawBucketResultNodeVector extends ResultNodeVector {
+ // The global class identifier shared with C++.
+ public static final int classId = registerClass(0x4000 + 126, RawBucketResultNodeVector.class);
+ private ArrayList<RawBucketResultNode> vector = new ArrayList<RawBucketResultNode>();
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ public RawBucketResultNodeVector() {
+ }
+
+ public RawBucketResultNodeVector add(RawBucketResultNode v) {
+ vector.add(v);
+ return this;
+ }
+
+ public ResultNodeVector add(ResultNode r) {
+ return add((RawBucketResultNode)r);
+ }
+
+ public ArrayList<RawBucketResultNode> getVector() {
+ return vector;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ buf.putInt(null, vector.size());
+ for (RawBucketResultNode node : vector) {
+ node.serialize(buf);
+ }
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ int sz = buf.getInt(null);
+ vector = new ArrayList<RawBucketResultNode>();
+ for (int i = 0; i < sz; i++) {
+ RawBucketResultNode node = new RawBucketResultNode();
+ node.deserialize(buf);
+ vector.add(node);
+ }
+ }
+
+ @Override
+ protected int onCmp(ResultNode rhs) {
+ if (classId != rhs.getClassId()) {
+ return (classId - rhs.getClassId());
+ }
+ RawBucketResultNodeVector b = (RawBucketResultNodeVector)rhs;
+ int minLength = vector.size();
+ if (b.vector.size() < minLength) {
+ minLength = b.vector.size();
+ }
+ int diff = 0;
+ for (int i = 0; (diff == 0) && (i < minLength); i++) {
+ diff = vector.get(i).compareTo(b.vector.get(i));
+ }
+ return (diff == 0) ? (vector.size() - b.vector.size()) : diff;
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/RawResultNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/RawResultNode.java
new file mode 100644
index 00000000000..ad40fc5026f
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/RawResultNode.java
@@ -0,0 +1,184 @@
+// 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.searchlib.aggregation.RawData;
+import com.yahoo.vespa.objects.Deserializer;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+import java.util.Arrays;
+
+/**
+ * This result holds a byte array value.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class RawResultNode extends SingleResultNode {
+
+ // The global class identifier shared with C++.
+ public static final int classId = registerClass(0x4000 + 54, RawResultNode.class);
+ private static RawResultNode negativeInfinity = new RawResultNode();
+ private static PositiveInfinityResultNode positiveInfinity = new PositiveInfinityResultNode();
+
+ // The raw value of this node.
+ private RawData value = null;
+
+ /**
+ * Constructs an empty result node. <b>NOTE:</b> This instance is broken until non-optional member data is set.
+ */
+ public RawResultNode() {
+ super();
+ value = new RawData();
+ }
+
+ /**
+ * Constructs an instance of this class with given byte buffer.
+ *
+ * @param value The value to assign to this.
+ */
+ public RawResultNode(byte[] value) {
+ super();
+ setValue(value);
+ }
+
+ /**
+ * Sets the value of this result.
+ *
+ * @param value The value to set.
+ * @return This, to allow chaining.
+ */
+ public RawResultNode setValue(byte[] value) {
+ this.value = new RawData(value);
+ return this;
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ value.serialize(buf);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ value = new RawData();
+ value.deserialize(buf);
+ }
+
+ @Override
+ public long getInteger() {
+ return 0;
+ }
+
+ @Override
+ public double getFloat() {
+ return 0;
+ }
+
+ @Override
+ public String getString() {
+ return new String(value.getData());
+ }
+
+ @Override
+ public byte[] getRaw() {
+ return value.getData();
+ }
+
+ @Override
+ public String toString() {
+ if (value != null) {
+ return Arrays.toString(value.getData());
+ }
+ return "[]";
+ }
+
+ @Override
+ protected int onCmp(ResultNode rhs) {
+ return (rhs instanceof PositiveInfinityResultNode)
+ ? -1
+ : RawData.compare(value.getData(), rhs.getRaw());
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + value.hashCode();
+ }
+
+ @Override
+ public RawResultNode clone() {
+ RawResultNode obj = (RawResultNode)super.clone();
+ if (value != null) {
+ obj.value = (RawData)value.clone();
+ }
+ return obj;
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("value", value);
+ }
+
+ public void add(ResultNode rhs) {
+ byte[] nb = new byte[value.getData().length + rhs.getRaw().length];
+ System.arraycopy(value.getData(), 0, nb, 0, value.getData().length);
+ System.arraycopy(rhs.getRaw(), 0, nb, value.getData().length, rhs.getRaw().length);
+ value = new RawData(nb);
+ }
+
+ public void min(ResultNode rhs) {
+ RawData b = new RawData(rhs.getRaw());
+ if (value.compareTo(b) > 0) {
+ value = b;
+ }
+ }
+
+ public void max(ResultNode rhs) {
+ RawData b = new RawData(rhs.getRaw());
+ if (value.compareTo(b) < 0) {
+ value = b;
+ }
+ }
+
+ @Override
+ public Object getValue() {
+ return getString();
+ }
+
+ @Override
+ public void set(ResultNode rhs) {
+ value = new RawData(rhs.getRaw());
+ }
+
+ @Override
+ public void negate() {
+ byte[] data = value.getData();
+ for (int i = 0; i < data.length; i++) {
+ data[i] = (byte)-data[i];
+ }
+ }
+
+ /**
+ * Will provide the smallest possible value
+ *
+ * @return the smallest possible IntegerResultNode
+ */
+ public static RawResultNode getNegativeInfinity() {
+ return negativeInfinity;
+ }
+
+ /**
+ * Will provide the largest possible value
+ *
+ * @return the smallest largest IntegerResultNode
+ */
+ public static PositiveInfinityResultNode getPositiveInfinity() {
+ return positiveInfinity;
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/RawResultNodeVector.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/RawResultNodeVector.java
new file mode 100644
index 00000000000..dc791b7ce69
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/RawResultNodeVector.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.expression;
+
+import com.yahoo.vespa.objects.Deserializer;
+import com.yahoo.vespa.objects.Serializer;
+
+import java.util.ArrayList;
+
+/**
+ * This result holds nothing.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class RawResultNodeVector extends ResultNodeVector {
+
+ // The global class identifier shared with C++.
+ public static final int classId = registerClass(0x4000 + 115, RawResultNodeVector.class);
+ private ArrayList<RawResultNode> vector = new ArrayList<RawResultNode>();
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ public RawResultNodeVector() {
+ }
+
+ public RawResultNodeVector add(RawResultNode v) {
+ vector.add(v);
+ return this;
+ }
+
+ public ResultNodeVector add(ResultNode r) {
+ return add((RawResultNode)r);
+ }
+
+ public ArrayList<RawResultNode> getVector() {
+ return vector;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ buf.putInt(null, vector.size());
+ for (RawResultNode node : vector) {
+ node.serialize(buf);
+ }
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ int sz = buf.getInt(null);
+ vector = new ArrayList<RawResultNode>();
+ for (int i = 0; i < sz; i++) {
+ RawResultNode node = new RawResultNode();
+ node.deserialize(buf);
+ vector.add(node);
+ }
+ }
+
+ @Override
+ protected int onCmp(ResultNode rhs) {
+ if (classId != rhs.getClassId()) {
+ return (classId - rhs.getClassId());
+ }
+ RawResultNodeVector b = (RawResultNodeVector)rhs;
+ int minLength = vector.size();
+ if (b.vector.size() < minLength) {
+ minLength = b.vector.size();
+ }
+ int diff = 0;
+ for (int i = 0; (diff == 0) && (i < minLength); i++) {
+ diff = vector.get(i).compareTo(b.vector.get(i));
+ }
+ return (diff == 0) ? (vector.size() - b.vector.size()) : diff;
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/RelevanceNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/RelevanceNode.java
new file mode 100644
index 00000000000..90077238925
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/RelevanceNode.java
@@ -0,0 +1,72 @@
+// 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.Deserializer;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+/**
+ * This abstract expression node represents a function to execute.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class RelevanceNode extends ExpressionNode {
+
+ public static final int classId = registerClass(0x4000 + 59, RelevanceNode.class);
+ private FloatResultNode relevance = new FloatResultNode();
+
+ public RelevanceNode() {
+
+ }
+
+ @Override
+ public void onPrepare() {
+
+ }
+
+ @Override
+ public boolean onExecute() {
+ return true;
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ relevance.serialize(buf);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ relevance.deserialize(buf);
+ }
+
+ @Override
+ public RelevanceNode clone() {
+ RelevanceNode obj = (RelevanceNode)super.clone();
+ obj.relevance = (FloatResultNode)relevance.clone();
+ return obj;
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("relevance", relevance);
+ }
+
+ @Override
+ public ResultNode getResult() {
+ return relevance;
+ }
+
+ @Override
+ protected boolean equalsExpression(ExpressionNode obj) {
+ return relevance.equals(((RelevanceNode)obj).relevance);
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/ResultNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/ResultNode.java
new file mode 100644
index 00000000000..7a31e1598f6
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/ResultNode.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.expression;
+
+import com.yahoo.vespa.objects.Identifiable;
+
+/**
+ * This abstract expression node represents the result value of execution.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public abstract class ResultNode extends Identifiable implements Comparable<ResultNode> {
+
+ // The global class identifier shared with C++.
+ public static final int classId = registerClass(0x4000 + 41, ResultNode.class);
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ public final int compareTo(ResultNode b) {
+ return onCmp(b);
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof ResultNode && compareTo((ResultNode)obj) == 0;
+ }
+
+ /**
+ * This method must be implemented by all subclasses of this to allow new results to be calculated.
+ *
+ * @param rhs The node to get the result from.
+ */
+ protected abstract void set(ResultNode rhs);
+
+ /**
+ * This method must be implemented by all subclasses of this to allow ordering of results. This method is used by
+ * the {@link Cloneable} implementation.
+ *
+ * @param rhs The other node to compare with.
+ * @return Comparable result.
+ */
+ protected abstract int onCmp(ResultNode rhs);
+
+ /**
+ * Returns the integer representation of this result.
+ *
+ * @return The value of this.
+ */
+ public abstract long getInteger();
+
+ /**
+ * Returns the float representation of this result.
+ *
+ * @return The value of this.
+ */
+ public abstract double getFloat();
+
+ /**
+ * Returns the string representation of this result.
+ *
+ * @return The value of this.
+ */
+ public abstract String getString();
+
+ /**
+ * Returns the raw byte array representation of this result.
+ *
+ * @return The value of this.
+ */
+ public abstract byte[] getRaw();
+
+ /**
+ * Negate the value contained within the result node.
+ */
+ public void negate() {
+ throw new RuntimeException("Class " + getClass().getName() + " does not implement negate");
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/ResultNodeVector.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/ResultNodeVector.java
new file mode 100644
index 00000000000..e6d2818e39d
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/ResultNodeVector.java
@@ -0,0 +1,45 @@
+// 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;
+
+/**
+ * This result holds nothing.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public abstract class ResultNodeVector extends ResultNode {
+
+ // The global class identifier shared with C++.
+ public static final int classId = registerClass(0x4000 + 108, ResultNodeVector.class);
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ public long getInteger() {
+ return 0;
+ }
+
+ @Override
+ public double getFloat() {
+ return 0.0;
+ }
+
+ @Override
+ public String getString() {
+ return "";
+ }
+
+ @Override
+ public byte[] getRaw() {
+ return new byte[0];
+ }
+
+ @Override
+ public void set(ResultNode rhs) {
+ }
+
+ public abstract ResultNodeVector add(ResultNode r);
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/ReverseFunctionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/ReverseFunctionNode.java
new file mode 100644
index 00000000000..7aa9cd92163
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/ReverseFunctionNode.java
@@ -0,0 +1,39 @@
+// 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;
+
+/**
+ * This class will revert the order on any multivalues. Nothing is done to single value types such as integers, float,
+ * strings and Raw values.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ */
+public class ReverseFunctionNode extends UnaryFunctionNode {
+
+ public static final int classId = registerClass(0x4000 + 138, ReverseFunctionNode.class);
+
+ /**
+ * Constructs an empty result node. <b>NOTE:</b> This instance is broken until non-optional member data is set.
+ */
+ public ReverseFunctionNode() {
+
+ }
+
+ /**
+ * Constructs an instance of this class with given argument.
+ *
+ * @param arg The argument for this function.
+ */
+ public ReverseFunctionNode(ExpressionNode arg) {
+ addArg(arg);
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected boolean equalsUnaryFunction(UnaryFunctionNode obj) {
+ return true;
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/SingleResultNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/SingleResultNode.java
new file mode 100644
index 00000000000..2c9b940cbf0
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/SingleResultNode.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.expression;
+
+/**
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ */
+public abstract class SingleResultNode extends ResultNode {
+ // The global class identifier shared with C++.
+ public static final int classId = registerClass(0x4000 + 121, NumericResultNode.class);
+
+ /**
+ * In-place addition of this result with another.
+ *
+ * @param rhs The result to add to this.
+ */
+ public abstract void add(ResultNode rhs);
+
+ /**
+ * Swaps the numerical value of this node with the smaller of this and the other.
+ *
+ * @param rhs The other result to evaluate.
+ */
+ public abstract void min(ResultNode rhs);
+
+ /**
+ * Swaps the numerical value of this node with the larger of this and the other.
+ *
+ * @param rhs The other result to evaluate.
+ */
+ public abstract void max(ResultNode rhs);
+
+ /**
+ * Return a java native, either String, Double or Long, depending on the underlying container.
+ *
+ * @return The underlying numeric value.
+ */
+ public abstract Object getValue();
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/SortFunctionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/SortFunctionNode.java
new file mode 100644
index 00000000000..0b0f1e1ed5b
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/SortFunctionNode.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;
+
+/**
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ */
+public class SortFunctionNode extends UnaryFunctionNode {
+
+ public static final int classId = registerClass(0x4000 + 137, SortFunctionNode.class);
+
+ /**
+ * Constructs an empty result node. <b>NOTE:</b> This instance is broken until non-optional member data is set.
+ */
+ public SortFunctionNode() {
+
+ }
+
+ /**
+ * Constructs an instance of this class with given argument.
+ *
+ * @param arg The argument for this function.
+ */
+ public SortFunctionNode(ExpressionNode arg) {
+ addArg(arg);
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected boolean equalsUnaryFunction(UnaryFunctionNode obj) {
+ return true;
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/StrCatFunctionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/StrCatFunctionNode.java
new file mode 100644
index 00000000000..de748394ca3
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/StrCatFunctionNode.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.expression;
+
+/**
+ * This function is an instruction to concatenate the bits of all arguments in order.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class StrCatFunctionNode extends MultiArgFunctionNode {
+
+ public static final int classId = registerClass(0x4000 + 133, StrCatFunctionNode.class);
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected boolean equalsMultiArgFunction(MultiArgFunctionNode obj) {
+ return true;
+ }
+
+ @Override
+ protected void onPrepareResult() {
+ setResult(new StringResultNode());
+ }
+
+ @Override
+ protected void onPrepare() {
+ super.onPrepare();
+ }
+
+ @Override
+ protected boolean onExecute() {
+ for (int i = 0; i < getNumArgs(); i++) {
+ getArg(i).execute();
+ ((StringResultNode)getResult()).append(getArg(i).getResult());
+ }
+ return true;
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/StrLenFunctionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/StrLenFunctionNode.java
new file mode 100644
index 00000000000..dbec8903177
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/StrLenFunctionNode.java
@@ -0,0 +1,55 @@
+// 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;
+
+/**
+ * This function is an instruction to negate its argument.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class StrLenFunctionNode extends UnaryFunctionNode {
+
+ public static final int classId = registerClass(0x4000 + 130, StrLenFunctionNode.class);
+
+ /**
+ * Constructs an empty result node. <b>NOTE:</b> This instance is broken until non-optional member data is set.
+ */
+ public StrLenFunctionNode() {
+
+ }
+
+ /**
+ * Constructs an instance of this class with given argument.
+ *
+ * @param arg The argument for this function.
+ */
+ public StrLenFunctionNode(ExpressionNode arg) {
+ addArg(arg);
+ }
+
+ @Override
+ public void onPrepareResult() {
+ setResult(new IntegerResultNode(0));
+ }
+
+ @Override
+ public void onPrepare() {
+ super.onPrepare();
+ }
+
+ @Override
+ public boolean onExecute() {
+ ((IntegerResultNode)getResult()).setValue(getArg().getResult().getString().length());
+ return true;
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected boolean equalsUnaryFunction(UnaryFunctionNode obj) {
+ return true;
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/StringBucketResultNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/StringBucketResultNode.java
new file mode 100644
index 00000000000..d830cb0f2c4
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/StringBucketResultNode.java
@@ -0,0 +1,114 @@
+// 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.Deserializer;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+/**
+ * This is an integer bucket value
+ *
+ * @author <a href="mailto:havardpe@yahoo-inc.com">Haavard Pettersen</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class StringBucketResultNode extends BucketResultNode {
+
+ // The global class identifier shared with C++.
+ public static final int classId = registerClass(0x4000 + 103, StringBucketResultNode.class);
+
+ // bucket start, inclusive
+ private ResultNode from = StringResultNode.getNegativeInfinity();
+
+ // bucket end, exclusive
+ private ResultNode to = StringResultNode.getNegativeInfinity();
+
+ @Override
+ public boolean empty() {
+ return to.equals(from);
+ }
+
+ /**
+ * Constructs an empty result node.
+ */
+ public StringBucketResultNode() {
+ // empty
+ }
+
+ /**
+ * Create a bucket with the given limits
+ *
+ * @param from bucket start
+ * @param to bucket end
+ */
+ public StringBucketResultNode(ResultNode from, ResultNode to) {
+ this.from = from;
+ this.to = to;
+ }
+
+ /**
+ * Create a bucket with the given limits
+ *
+ * @param from bucket start
+ * @param to bucket end
+ */
+ public StringBucketResultNode(String from, String to) {
+ this(new StringResultNode(from), new StringResultNode(to));
+ }
+
+ /**
+ * Obtain the bucket start
+ *
+ * @return bucket start
+ */
+ public String getFrom() {
+ return from.getString();
+ }
+
+ /**
+ * Obtain the bucket end
+ *
+ * @return bucket end
+ */
+ public String getTo() {
+ return to.getString();
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ serializeOptional(buf, from);
+ serializeOptional(buf, to);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ from = (ResultNode)deserializeOptional(buf);
+ to = (ResultNode)deserializeOptional(buf);
+ }
+
+ @Override
+ protected int onCmp(ResultNode rhs) {
+ if (classId != rhs.getClassId()) {
+ return (classId - rhs.getClassId());
+ }
+ StringBucketResultNode b = (StringBucketResultNode)rhs;
+ int diff = from.compareTo(b.from);
+ return (diff == 0) ? to.compareTo(b.to) : diff;
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + from.hashCode() + to.hashCode();
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("from", from);
+ visitor.visit("to", to);
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/StringBucketResultNodeVector.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/StringBucketResultNodeVector.java
new file mode 100644
index 00000000000..89570c702ec
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/StringBucketResultNodeVector.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.expression;
+
+import com.yahoo.vespa.objects.Deserializer;
+import com.yahoo.vespa.objects.Serializer;
+
+import java.util.ArrayList;
+
+/**
+ * This result holds nothing.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class StringBucketResultNodeVector extends ResultNodeVector {
+
+ // The global class identifier shared with C++.
+ public static final int classId = registerClass(0x4000 + 114, StringBucketResultNodeVector.class);
+ private ArrayList<StringBucketResultNode> vector = new ArrayList<StringBucketResultNode>();
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ public StringBucketResultNodeVector() {
+ }
+
+ public StringBucketResultNodeVector add(StringBucketResultNode v) {
+ vector.add(v);
+ return this;
+ }
+
+ public ResultNodeVector add(ResultNode r) {
+ return add((StringBucketResultNode)r);
+ }
+
+ public ArrayList<StringBucketResultNode> getVector() {
+ return vector;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ buf.putInt(null, vector.size());
+ for (StringBucketResultNode node : vector) {
+ node.serialize(buf);
+ }
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ int sz = buf.getInt(null);
+ vector = new ArrayList<StringBucketResultNode>();
+ for (int i = 0; i < sz; i++) {
+ StringBucketResultNode node = new StringBucketResultNode();
+ node.deserialize(buf);
+ vector.add(node);
+ }
+ }
+
+ @Override
+ protected int onCmp(ResultNode rhs) {
+ if (classId != rhs.getClassId()) {
+ return (classId - rhs.getClassId());
+ }
+ StringBucketResultNodeVector b = (StringBucketResultNodeVector)rhs;
+ int minLength = vector.size();
+ if (b.vector.size() < minLength) {
+ minLength = b.vector.size();
+ }
+ int diff = 0;
+ for (int i = 0; (diff == 0) && (i < minLength); i++) {
+ diff = vector.get(i).compareTo(b.vector.get(i));
+ }
+ return (diff == 0) ? (vector.size() - b.vector.size()) : diff;
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/StringResultNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/StringResultNode.java
new file mode 100644
index 00000000000..f428e2aef9f
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/StringResultNode.java
@@ -0,0 +1,177 @@
+// 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.text.Utf8;
+import com.yahoo.vespa.objects.Deserializer;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+/**
+ * This result holds a string.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class StringResultNode extends SingleResultNode {
+
+ // The global class identifier shared with C++.
+ public static final int classId = registerClass(0x4000 + 53, StringResultNode.class);
+ private static StringResultNode negativeInfinity = new StringResultNode("");
+ private static PositiveInfinityResultNode positiveInfinity = new PositiveInfinityResultNode();
+
+ // The string value of this node.
+ private String value;
+
+ /**
+ * Constructs an empty result node. <b>NOTE:</b> This instance is broken until non-optional member data is set.
+ */
+ public StringResultNode() {
+ super();
+ value = "";
+ }
+
+ /**
+ * Constructs an instance of this class with given value.
+ *
+ * @param value The value to assign to this.
+ */
+ public StringResultNode(String value) {
+ super();
+ setValue(value);
+ }
+
+ /**
+ * Sets the value of this result.
+ *
+ * @param value The value to set.
+ * @return This, to allow chaining.
+ */
+ public StringResultNode setValue(String value) {
+ if (value == null) {
+ throw new IllegalArgumentException("Value can not be null.");
+ }
+ this.value = value;
+ return this;
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ byte[] raw = getRaw();
+ buf.putInt(null, raw.length);
+ buf.put(null, raw);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ value = getUtf8(buf);
+ }
+
+ @Override
+ public long getInteger() {
+ try {
+ return Integer.valueOf(value);
+ } catch (java.lang.NumberFormatException e) {
+ return 0;
+ }
+ }
+
+ @Override
+ public double getFloat() {
+ try {
+ return Double.valueOf(value);
+ } catch (java.lang.NumberFormatException e) {
+ return 0;
+ }
+ }
+
+ @Override
+ public String getString() {
+ return value;
+ }
+
+ @Override
+ public byte[] getRaw() {
+ return Utf8.toBytes(value);
+ }
+
+ @Override
+ protected int onCmp(ResultNode rhs) {
+ return (rhs instanceof PositiveInfinityResultNode)
+ ? -1
+ : value.compareTo(rhs.getString());
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + value.hashCode();
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("value", value);
+ }
+
+ public void add(ResultNode rhs) {
+ value += rhs.getString();
+ }
+
+ public void min(ResultNode rhs) {
+ if (value.compareTo(rhs.getString()) > 0) {
+ value = rhs.getString();
+ }
+ }
+
+ public void max(ResultNode rhs) {
+ if (value.compareTo(rhs.getString()) < 0) {
+ value = rhs.getString();
+ }
+ }
+
+ public void append(ResultNode rhs) {
+ value += rhs.getString();
+ }
+
+ @Override
+ public Object getValue() {
+ return getString();
+ }
+
+ @Override
+ public void set(ResultNode rhs) {
+ value = rhs.getString();
+ }
+
+ @Override
+ public void negate() {
+ char a[] = value.toCharArray();
+ for (int i = 0; i < a.length; i++) {
+ a[i] = (char)-a[i];
+ }
+ value = new String(a);
+ }
+
+ /**
+ * Will provide the smallest possible value
+ *
+ * @return the smallest possible IntegerResultNode
+ */
+ public static StringResultNode getNegativeInfinity() {
+ return negativeInfinity;
+ }
+
+ /**
+ * Will provide the largest possible value
+ *
+ * @return the smallest largest IntegerResultNode
+ */
+ public static PositiveInfinityResultNode getPositiveInfinity() {
+ return positiveInfinity;
+ }
+}
+
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/StringResultNodeVector.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/StringResultNodeVector.java
new file mode 100644
index 00000000000..ba172f5db01
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/StringResultNodeVector.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.expression;
+
+import com.yahoo.vespa.objects.Deserializer;
+import com.yahoo.vespa.objects.Serializer;
+
+import java.util.ArrayList;
+
+/**
+ * This result holds nothing.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class StringResultNodeVector extends ResultNodeVector {
+
+ // The global class identifier shared with C++.
+ public static final int classId = registerClass(0x4000 + 111, StringResultNodeVector.class);
+ private ArrayList<StringResultNode> vector = new ArrayList<StringResultNode>();
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ public StringResultNodeVector() {
+ }
+
+ public StringResultNodeVector add(StringResultNode v) {
+ vector.add(v);
+ return this;
+ }
+
+ public ResultNodeVector add(ResultNode r) {
+ return add((StringResultNode)r);
+ }
+
+ public ArrayList<StringResultNode> getVector() {
+ return vector;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ buf.putInt(null, vector.size());
+ for (StringResultNode node : vector) {
+ node.serialize(buf);
+ }
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ int sz = buf.getInt(null);
+ vector = new ArrayList<StringResultNode>();
+ for (int i = 0; i < sz; i++) {
+ StringResultNode node = new StringResultNode();
+ node.deserialize(buf);
+ vector.add(node);
+ }
+ }
+
+ @Override
+ protected int onCmp(ResultNode rhs) {
+ if (classId != rhs.getClassId()) {
+ return (classId - rhs.getClassId());
+ }
+ StringResultNodeVector b = (StringResultNodeVector)rhs;
+ int minLength = vector.size();
+ if (b.vector.size() < minLength) {
+ minLength = b.vector.size();
+ }
+ int diff = 0;
+ for (int i = 0; (diff == 0) && (i < minLength); i++) {
+ diff = vector.get(i).compareTo(b.vector.get(i));
+ }
+ return (diff == 0) ? (vector.size() - b.vector.size()) : diff;
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/TimeStampFunctionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/TimeStampFunctionNode.java
new file mode 100644
index 00000000000..b84fa124841
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/TimeStampFunctionNode.java
@@ -0,0 +1,116 @@
+// 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.Deserializer;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+/**
+ * <p>This function assign a fixed width bucket to each input value.</p>
+ *
+ * @author <a href="mailto:havardpe@yahoo-inc.com">Haavard Pettersen</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class TimeStampFunctionNode extends UnaryFunctionNode {
+
+ public static enum TimePart {
+ Year(0),
+ Month(1),
+ MonthDay(2),
+ WeekDay(3),
+ Hour(4),
+ Minute(5),
+ Second(6),
+ YearDay(7),
+ IsDST(8);
+
+ private final int id;
+
+ private TimePart(int id) {
+ this.id = id;
+ }
+
+ private static TimePart valueOf(int id) {
+ for (TimePart part : values()) {
+ if (id == part.id) {
+ return part;
+ }
+ }
+ return null;
+ }
+ }
+
+ public static final int classId = registerClass(0x4000 + 75, TimeStampFunctionNode.class);
+ private TimePart timePart = TimePart.Year;
+ private boolean isGmt = false;
+
+ @SuppressWarnings("UnusedDeclaration")
+ public TimeStampFunctionNode() {
+ // used by deserializer
+ }
+
+ /**
+ * <p>Create a bucket expression with the given width and the given subexpression.</p>
+ *
+ * @param arg The argument for this function.
+ * @param part The part of time to retrieve.
+ * @param gmt Whether or not to treat time as GMT.
+ */
+ public TimeStampFunctionNode(ExpressionNode arg, TimePart part, boolean gmt) {
+ addArg(arg);
+ timePart = part;
+ isGmt = gmt;
+ }
+
+ public TimePart getTimePart() {
+ return timePart;
+ }
+
+ public boolean isGmt() {
+ return isGmt;
+ }
+
+ public boolean isLocal() {
+ return !isGmt;
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ buf.putByte(null, (byte)(timePart.id | (isGmt ? 0x80 : 0)));
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ int b = buf.getByte(null);
+ timePart = TimePart.valueOf(b & 0x7f);
+ isGmt = (b & 0x80) != 0;
+ }
+
+ @Override
+ protected boolean equalsUnaryFunction(UnaryFunctionNode obj) {
+ TimeStampFunctionNode rhs = (TimeStampFunctionNode)obj;
+ return timePart == rhs.timePart && isGmt == rhs.isGmt;
+ }
+
+ @Override
+ public TimeStampFunctionNode clone() {
+ TimeStampFunctionNode obj = (TimeStampFunctionNode)super.clone();
+ obj.timePart = timePart;
+ obj.isGmt = isGmt;
+ return obj;
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("timepart", timePart);
+ visitor.visit("islocal", isGmt);
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/ToFloatFunctionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/ToFloatFunctionNode.java
new file mode 100644
index 00000000000..4511797d3dd
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/ToFloatFunctionNode.java
@@ -0,0 +1,39 @@
+// 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;
+
+/**
+ * This function is an instruction to negate its argument.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ToFloatFunctionNode extends UnaryFunctionNode {
+
+ public static final int classId = registerClass(0x4000 + 134, ToFloatFunctionNode.class);
+
+ /**
+ * Constructs an empty result node. <b>NOTE:</b> This instance is broken until non-optional member data is set.
+ */
+ public ToFloatFunctionNode() {
+
+ }
+
+ /**
+ * Constructs an instance of this class with given argument.
+ *
+ * @param arg The argument for this function.
+ */
+ public ToFloatFunctionNode(ExpressionNode arg) {
+ addArg(arg);
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected boolean equalsUnaryFunction(UnaryFunctionNode obj) {
+ return true;
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/ToIntFunctionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/ToIntFunctionNode.java
new file mode 100644
index 00000000000..8ff20216374
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/ToIntFunctionNode.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;
+
+/**
+ * This function is an instruction to negate its argument.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ToIntFunctionNode extends UnaryFunctionNode {
+
+ public static final int classId = registerClass(0x4000 + 135, ToIntFunctionNode.class);
+
+ /**
+ * Constructs an empty result node. <b>NOTE:</b> This instance is broken until non-optional member data is set.
+ */
+ public ToIntFunctionNode() {
+
+ }
+
+ /**
+ * Constructs an instance of this class with given argument.
+ *
+ * @param arg The argument for this function.
+ */
+ public ToIntFunctionNode(ExpressionNode arg) {
+ addArg(arg);
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ public void onPrepareResult() {
+ setResult(new IntegerResultNode());
+ }
+
+ @Override
+ protected boolean equalsUnaryFunction(UnaryFunctionNode obj) {
+ return true;
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/ToRawFunctionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/ToRawFunctionNode.java
new file mode 100644
index 00000000000..0ee1fd1cb71
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/ToRawFunctionNode.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.expression;
+
+/**
+ * This function converts its argument to a raw function node.
+ *
+ * @author <a href="mailto:lulf@yahoo-inc.com">Ulf Lilleengen</a>
+ */
+public class ToRawFunctionNode extends UnaryFunctionNode {
+
+ public static final int classId = registerClass(0x4000 + 141, ToRawFunctionNode.class);
+
+ /**
+ * Constructs an empty result node. <b>NOTE:</b> This instance is broken until non-optional member data is set.
+ */
+ public ToRawFunctionNode() {
+
+ }
+
+ /**
+ * Constructs an instance of this class with given argument.
+ *
+ * @param arg The argument for this function.
+ */
+ public ToRawFunctionNode(ExpressionNode arg) {
+ addArg(arg);
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected boolean equalsUnaryFunction(UnaryFunctionNode obj) {
+ return true;
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/ToStringFunctionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/ToStringFunctionNode.java
new file mode 100644
index 00000000000..490d19ad9a8
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/ToStringFunctionNode.java
@@ -0,0 +1,51 @@
+// 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;
+
+/**
+ * This function is an instruction to negate its argument.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ToStringFunctionNode extends UnaryFunctionNode {
+
+ public static final int classId = registerClass(0x4000 + 131, ToStringFunctionNode.class);
+
+ /**
+ * Constructs an empty result node. <b>NOTE:</b> This instance is broken until non-optional member data is set.
+ */
+ public ToStringFunctionNode() {
+
+ }
+
+ /**
+ * Constructs an instance of this class with given argument.
+ *
+ * @param arg The argument for this function.
+ */
+ public ToStringFunctionNode(ExpressionNode arg) {
+ addArg(arg);
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ public void onPrepareResult() {
+ setResult(new StringResultNode());
+ }
+
+ @Override
+ public boolean onExecute() {
+ getArg().execute();
+ ((StringResultNode)getResult()).setValue(getArg().getResult().getString());
+ return true;
+ }
+
+ @Override
+ protected boolean equalsUnaryFunction(UnaryFunctionNode obj) {
+ return true;
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/UcaFunctionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/UcaFunctionNode.java
new file mode 100644
index 00000000000..233023d1a2e
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/UcaFunctionNode.java
@@ -0,0 +1,84 @@
+// 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.Deserializer;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+/**
+ * This function is a request to use the Unicode Collation Algorithm specification when sorting this field.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ */
+public class UcaFunctionNode extends UnaryFunctionNode {
+
+ public static final int classId = registerClass(0x4000 + 140, UcaFunctionNode.class);
+ private String locale = "en-US";
+ private String strength = "TERTIARY";
+
+ /**
+ * Constructs an empty result node.
+ */
+ public UcaFunctionNode() {
+ // empty
+ }
+
+ /**
+ * Create an UCA node with a specific locale.
+ *
+ * @param arg The argument for this function.
+ * @param locale The locale to use.
+ */
+ public UcaFunctionNode(ExpressionNode arg, String locale) {
+ this(arg, locale, "TERTIARY");
+ }
+
+ /**
+ * Create an UCA node with a specific locale and strength setting.
+ *
+ * @param arg The argument for this function.
+ * @param locale The locale to use.
+ * @param strength The strength setting to use.
+ */
+ public UcaFunctionNode(ExpressionNode arg, String locale, String strength) {
+ addArg(arg);
+ this.locale = locale;
+ this.strength = strength;
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ putUtf8(buf, locale);
+ putUtf8(buf, strength);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ locale = getUtf8(buf);
+ strength = getUtf8(buf);
+ }
+
+ @Override
+ protected boolean equalsUnaryFunction(UnaryFunctionNode obj) {
+ return true;
+ }
+
+ @Override
+ public UcaFunctionNode clone() {
+ return (UcaFunctionNode)super.clone();
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("locale", locale);
+ visitor.visit("strength", strength);
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/UnaryBitFunctionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/UnaryBitFunctionNode.java
new file mode 100644
index 00000000000..05afc5d99b9
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/UnaryBitFunctionNode.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.expression;
+
+import com.yahoo.vespa.objects.Deserializer;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+/**
+ * This is an abstract super-class for all unary functions that operator on bit values.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public abstract class UnaryBitFunctionNode extends UnaryFunctionNode {
+
+ public static final int classId = registerClass(0x4000 + 46, UnaryBitFunctionNode.class);
+ private int numBits = 0;
+
+ /**
+ * Constructs an empty result node. <b>NOTE:</b> This instance is broken until non-optional member data is set.
+ */
+ public UnaryBitFunctionNode() {
+
+ }
+
+ /**
+ * Constructs an instance of this class with given argument and number of bits.
+ *
+ * @param arg The argument for this function.
+ * @param numBits The number of bits to operate on.
+ */
+ public UnaryBitFunctionNode(ExpressionNode arg, int numBits) {
+ addArg(arg);
+ setNumBits(numBits);
+ }
+
+ /**
+ * Returns the number of bits to operate on.
+ *
+ * @return The number of bits.
+ */
+ public final int getNumBits() {
+ return numBits;
+ }
+
+ /**
+ * Sets the number of bits to operate on.
+ *
+ * @param numBits The number of bits.
+ * @return This, to allow chaining.
+ */
+ public UnaryBitFunctionNode setNumBits(int numBits) {
+ this.numBits = numBits;
+ return this;
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ buf.putInt(null, numBits);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ numBits = buf.getInt(null);
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() + numBits;
+ }
+
+ @Override
+ protected final boolean equalsUnaryFunction(UnaryFunctionNode obj) {
+ return numBits == ((UnaryBitFunctionNode)obj).numBits;
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("numBits", numBits);
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/UnaryFunctionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/UnaryFunctionNode.java
new file mode 100644
index 00000000000..84264f47ef4
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/UnaryFunctionNode.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;
+
+/**
+ * This is an abstract super-class for all functions that accept only a single argument.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public abstract class UnaryFunctionNode extends MultiArgFunctionNode {
+
+ public static final int classId = registerClass(0x4000 + 43, UnaryFunctionNode.class);
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ /**
+ * Return the single argument given to this function.
+ *
+ * @return The argument to this function
+ */
+ public ExpressionNode getArg() {
+ return getArg(0);
+ }
+
+ @Override
+ public void onPrepareResult() {
+ setResult((ResultNode)getArg().getResult().clone());
+ }
+
+ @Override
+ public void onPrepare() {
+ super.onPrepare();
+ }
+
+ @Override
+ protected final boolean equalsMultiArgFunction(MultiArgFunctionNode obj) {
+ return equalsUnaryFunction((UnaryFunctionNode)obj);
+ }
+
+ protected abstract boolean equalsUnaryFunction(UnaryFunctionNode obj);
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/XorBitFunctionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/XorBitFunctionNode.java
new file mode 100644
index 00000000000..57fa01c97de
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/XorBitFunctionNode.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.expression;
+
+/**
+ * This function is a request to bitwise XOR the result of its first argument with itself in chunks of the second
+ * argument number of bits. If the result to XOR is a 24 bit value, and the second argument is 8, this function will XOR
+ * the first 8 bits of the result with the next 8 bits of the result, and then XOR that number with the next 8 bits of
+ * the result.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class XorBitFunctionNode extends UnaryBitFunctionNode {
+
+ public static final int classId = registerClass(0x4000 + 71, XorBitFunctionNode.class);
+
+ /**
+ * Constructs an empty result node. <b>NOTE:</b> This instance is broken until non-optional member data is set.
+ */
+ public XorBitFunctionNode() {
+
+ }
+
+ /**
+ * Constructs an instance of this class with given argument and number of bits.
+ *
+ * @param arg The argument for this function.
+ * @param numBits The number of bits to operate on.
+ */
+ public XorBitFunctionNode(ExpressionNode arg, int numBits) {
+ super(arg, numBits);
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/XorFunctionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/XorFunctionNode.java
new file mode 100644
index 00000000000..036d7fc8f16
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/XorFunctionNode.java
@@ -0,0 +1,22 @@
+// 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;
+
+/**
+ * This function is an instruction to perform bitwise XOR on the result of all arguments in order.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class XorFunctionNode extends BitFunctionNode {
+
+ public static final int classId = registerClass(0x4000 + 69, XorFunctionNode.class);
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ public void onArgument(final ResultNode arg, IntegerResultNode result) {
+ result.xorOp(arg);
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/ZCurveFunctionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/ZCurveFunctionNode.java
new file mode 100644
index 00000000000..54e86f8353c
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/ZCurveFunctionNode.java
@@ -0,0 +1,92 @@
+// 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.Deserializer;
+import com.yahoo.vespa.objects.ObjectVisitor;
+import com.yahoo.vespa.objects.Serializer;
+
+/**
+ * This function decompose two-dimensonal zcurve values into x and y values.
+ *
+ * @author <a href="mailto:balder@yahoo-inc.com">Henning Baldersheim</a>
+ */
+public class ZCurveFunctionNode extends UnaryFunctionNode {
+
+ public static enum Dimension {
+ X(0),
+ Y(1);
+
+ private final int id;
+
+ private Dimension(int id) {
+ this.id = id;
+ }
+
+ private static Dimension valueOf(int id) {
+ for (Dimension dim : values()) {
+ if (id == dim.id) {
+ return dim;
+ }
+ }
+ return null;
+ }
+ }
+
+ public static final int classId = registerClass(0x4000 + 139, ZCurveFunctionNode.class);
+ private Dimension dim = Dimension.X;
+
+ @SuppressWarnings("UnusedDeclaration")
+ public ZCurveFunctionNode() {
+ // used by deserializer
+ }
+
+ public ZCurveFunctionNode(ExpressionNode arg, Dimension dimension) {
+ addArg(arg);
+ dim = dimension;
+ }
+
+ /**
+ * Obtain the predefined bucket list of this bucket expression
+ *
+ * @return what part of the time you have requested
+ */
+ public final Dimension getDimension() {
+ return dim;
+ }
+
+ @Override
+ protected int onGetClassId() {
+ return classId;
+ }
+
+ @Override
+ protected void onSerialize(Serializer buf) {
+ super.onSerialize(buf);
+ buf.putByte(null, (byte)dim.id);
+ }
+
+ @Override
+ protected void onDeserialize(Deserializer buf) {
+ super.onDeserialize(buf);
+ int b = buf.getByte(null);
+ dim = Dimension.valueOf(b);
+ }
+
+ @Override
+ protected boolean equalsUnaryFunction(UnaryFunctionNode obj) {
+ return dim == ((ZCurveFunctionNode)obj).dim;
+ }
+
+ @Override
+ public ZCurveFunctionNode clone() {
+ ZCurveFunctionNode obj = (ZCurveFunctionNode)super.clone();
+ obj.dim = dim;
+ return obj;
+ }
+
+ @Override
+ public void visitMembers(ObjectVisitor visitor) {
+ super.visitMembers(visitor);
+ visitor.visit("dimension", dim);
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/package-info.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/package-info.java
new file mode 100644
index 00000000000..ebe2448ebf0
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/package-info.java
@@ -0,0 +1,4 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage package com.yahoo.searchlib.expression;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/gbdt/CategoryFeatureNode.java b/searchlib/src/main/java/com/yahoo/searchlib/gbdt/CategoryFeatureNode.java
new file mode 100644
index 00000000000..285b39cbfbb
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/gbdt/CategoryFeatureNode.java
@@ -0,0 +1,34 @@
+// 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 com.yahoo.searchlib.rankingexpression.evaluation.Value;
+
+import java.util.Arrays;
+import java.util.Optional;
+
+/**
+ * A GBDT node representing a set inclusion test: feature IN [value-list] where values can be strings or numbers.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public final class CategoryFeatureNode extends FeatureNode {
+
+ private final Value[] values;
+
+ public CategoryFeatureNode(String feature, Value[] values, Optional<Integer> samples, TreeNode left, TreeNode right) {
+ super(feature, samples, left, right);
+ this.values = Arrays.copyOf(values, values.length);
+ }
+
+ /** Returns a copy of the array of values in this */
+ public Value[] values() {
+ return Arrays.copyOf(values, values.length);
+ }
+
+ @Override
+ protected String rankingExpressionCondition() {
+ return " in " + Arrays.toString(values);
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/gbdt/FeatureNode.java b/searchlib/src/main/java/com/yahoo/searchlib/gbdt/FeatureNode.java
new file mode 100644
index 00000000000..2d69624726c
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/gbdt/FeatureNode.java
@@ -0,0 +1,95 @@
+// 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.StringValue;
+import com.yahoo.searchlib.rankingexpression.evaluation.Value;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * A node in a GBDT tree which references a feature value
+ *
+ * @author bratseth
+ */
+public abstract class FeatureNode extends TreeNode {
+
+ private final String feature;
+
+ private final TreeNode left;
+ private final TreeNode right;
+
+ public FeatureNode(String feature, Optional<Integer> samples, TreeNode left, TreeNode right) {
+ super(samples);
+ this.feature = feature;
+ this.left = left;
+ this.right = right;
+ }
+
+ public String feature() { return feature; }
+
+ public TreeNode left() { return left; }
+
+ public TreeNode right() { return right; }
+
+ // TODO: Integrate with programmatic API rather than strings
+ @Override
+ public String toRankingExpression() {
+ StringBuilder expression = new StringBuilder();
+ expression.append("if (").append(feature).append(rankingExpressionCondition());
+ expression.append(", ").append(left.toRankingExpression());
+ expression.append(", ").append(right.toRankingExpression());
+
+ Optional<Float> trueProbability = trueProbability();
+ if (trueProbability.isPresent())
+ expression.append(", ").append(trueProbability.get());
+
+ expression.append(")");
+ return expression.toString();
+ }
+
+ private Optional<Float> trueProbability() {
+ if (left.samples().isPresent() && right.samples().isPresent())
+ return Optional.of((float)left.samples().get() / (left.samples().get() + right.samples().get()));
+ return Optional.empty();
+ }
+
+ protected abstract String rankingExpressionCondition();
+
+ public static FeatureNode fromDom(Node node) {
+ List<Element> children = XmlHelper.getChildElements(node, null);
+ if (children.size() != 2) {
+ throw new IllegalArgumentException("Expected 2 children in element '" + node.getNodeName() + "', got " +
+ children.size() + ".");
+ }
+
+ String name = XmlHelper.getAttributeText(node, "feature");
+ Value[] values = toValues(XmlHelper.getAttributeText(node, "value"));
+ Optional<Integer> samples = toInteger(XmlHelper.getOptionalAttributeText(node, "nSamples"));
+ TreeNode left = TreeNode.fromDom(children.get(0));
+ TreeNode right = TreeNode.fromDom(children.get(1));
+
+ if (name.endsWith("$") || values.length>1 || values[0] instanceof StringValue)
+ return new CategoryFeatureNode(name, values, samples, left, right);
+ else
+ return new NumericFeatureNode(name, values[0], samples, left, right);
+ }
+
+ /** Converts one or more comma-separated values into an array of values */
+ private static Value[] toValues(String valueListString) {
+ String[] valueStrings = valueListString.split(",");
+ Value[] values = new Value[valueStrings.length];
+ for (int i=0; i<valueStrings.length; i++) {
+ try {
+ values[i] = Value.parse(valueStrings[i]);
+ }
+ catch (NumberFormatException e) { // allow un(double)quoted string values in Gbdt XML trees
+ values[i] = new StringValue(valueStrings[i]);
+ }
+ }
+ return values;
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/gbdt/GbdtConverter.java b/searchlib/src/main/java/com/yahoo/searchlib/gbdt/GbdtConverter.java
new file mode 100644
index 00000000000..3625ee4252b
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/gbdt/GbdtConverter.java
@@ -0,0 +1,34 @@
+// 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.yolean.Exceptions;
+
+import java.io.FileNotFoundException;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class GbdtConverter {
+
+ /**
+ * Implements an application main function so that the converter can be used as a command-line tool.
+ *
+ * @param args List of arguments.
+ */
+ public static void main(String[] args) {
+ if (args.length != 1) {
+ System.err.println("Usage: GbdtConverter <filename>");
+ System.exit(1);
+ }
+ try {
+ System.out.println(GbdtModel.fromXmlFile(args[0]).toRankingExpression());
+ } catch (FileNotFoundException e) {
+ System.err.println("Could not find file '" + args[0] + "'.");
+ System.exit(1);
+ } catch (Exception e) {
+ System.err.println("An error occurred while parsing the content of file '" + args[0] + "': " +
+ Exceptions.toMessageString(e));
+ System.exit(1);
+ }
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/gbdt/GbdtModel.java b/searchlib/src/main/java/com/yahoo/searchlib/gbdt/GbdtModel.java
new file mode 100644
index 00000000000..0e40fe33b03
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/gbdt/GbdtModel.java
@@ -0,0 +1,92 @@
+// 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.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.xml.sax.SAXException;
+
+import javax.xml.parsers.ParserConfigurationException;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class GbdtModel {
+
+ private final List<TreeNode> trees;
+
+ public GbdtModel(List<TreeNode> trees) {
+ this.trees = asForest(trees);
+ }
+
+ public List<TreeNode> trees() {
+ return trees;
+ }
+
+ public String toRankingExpression() {
+ if ( ! hasSampleInformation())
+ System.err.println("The model nodes does not have the 'nSamples' attribute. " +
+ "For optimal runtime performance use an 'ext' model which has this information.");
+ StringBuilder ret = new StringBuilder();
+ for (TreeNode tree : trees) {
+ if (ret.length() > 0) {
+ ret.append(" +\n");
+ }
+ ret.append(tree.toRankingExpression());
+ }
+ ret.append("\n");
+ return ret.toString();
+ }
+
+ /**
+ * Return whether this model has sample information.
+ * Don't bother to check every node as files either has this for all nodes or for none.
+ */
+ private boolean hasSampleInformation() {
+ if (trees.size() == 0) return true; // no matter
+ return trees.get(0).samples() !=null;
+ }
+
+ public static GbdtModel fromXml(String xml) throws ParserConfigurationException, IOException, SAXException {
+ return fromDom(XmlHelper.parseXml(xml));
+ }
+
+ public static GbdtModel fromXmlFile(String fileName) throws ParserConfigurationException, IOException, SAXException {
+ return fromDom(XmlHelper.parseXmlFile(fileName));
+ }
+
+ public static GbdtModel fromDom(Node doc) {
+ Element dtree = XmlHelper.getSingleElement(doc, "DecisionTree");
+ Element forest = XmlHelper.getSingleElement(dtree, "Forest");
+ List<Element> trees = XmlHelper.getChildElements(forest, "Tree");
+ if (trees.isEmpty()) {
+ throw new IllegalArgumentException("Forest has no trees.");
+ }
+ List<TreeNode> model = new ArrayList<>();
+ for (Node tree : trees) {
+ if (XmlHelper.getChildElements(tree, null).isEmpty()) continue; // ignore
+ model.add(TreeNode.fromDom(XmlHelper.getSingleElement(tree, null)));
+ }
+ return new GbdtModel(model);
+ }
+
+ private static List<TreeNode> asForest(List<TreeNode> in) {
+ List<TreeNode> out = new ArrayList<>(in.size());
+ for (TreeNode node : in) {
+ if (node instanceof FeatureNode) {
+ out.add(node);
+ } else if (node instanceof ResponseNode) { // TODO): We should stop this sillyness ...
+ out.add(new NumericFeatureNode("value(0)", new DoubleValue(1), node.samples(), node,
+ new ResponseNode(0, Optional.of(0))));
+ } else {
+ throw new UnsupportedOperationException(node.getClass().getName());
+ }
+ }
+ return Collections.unmodifiableList(out);
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/gbdt/NumericFeatureNode.java b/searchlib/src/main/java/com/yahoo/searchlib/gbdt/NumericFeatureNode.java
new file mode 100644
index 00000000000..b78b9ed4224
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/gbdt/NumericFeatureNode.java
@@ -0,0 +1,34 @@
+// 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 com.yahoo.searchlib.rankingexpression.evaluation.Value;
+
+import java.util.Arrays;
+import java.util.Optional;
+
+/**
+ * A GBDT node representing a numeric "less than" comparison: feature &lt; numeric-value
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public final class NumericFeatureNode extends FeatureNode {
+
+ private final Value value;
+
+ public NumericFeatureNode(String feature, Value value, Optional<Integer> samples, TreeNode left, TreeNode right) {
+ super(feature, samples, left, right);
+ this.value = value;
+ }
+
+ /** Returns a copy of the array of values in this */
+ public Value value() {
+ return value;
+ }
+
+ @Override
+ public String rankingExpressionCondition() {
+ return " < " + value;
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/gbdt/ResponseNode.java b/searchlib/src/main/java/com/yahoo/searchlib/gbdt/ResponseNode.java
new file mode 100644
index 00000000000..fa4ef2b38e0
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/gbdt/ResponseNode.java
@@ -0,0 +1,33 @@
+// 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.w3c.dom.Node;
+
+import java.util.Optional;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ResponseNode extends TreeNode {
+
+ private final double value;
+
+ public ResponseNode(double value, Optional<Integer> samples) {
+ super(samples);
+ this.value = value;
+ }
+
+ public double value() {
+ return value;
+ }
+
+ @Override
+ public String toRankingExpression() {
+ return String.valueOf(value);
+ }
+
+ public static ResponseNode fromDom(Node node) {
+ return new ResponseNode(Double.valueOf(XmlHelper.getAttributeText(node, "value")),
+ toInteger(XmlHelper.getOptionalAttributeText(node, "nSamples")));
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/gbdt/TreeNode.java b/searchlib/src/main/java/com/yahoo/searchlib/gbdt/TreeNode.java
new file mode 100644
index 00000000000..a8a6add87cd
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/gbdt/TreeNode.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.gbdt;
+
+import org.w3c.dom.Node;
+
+import java.util.Optional;
+
+/**
+ * @author bratseth
+ */
+public abstract class TreeNode {
+
+ private final Optional<Integer> samples;
+
+ public TreeNode(Optional<Integer> samples) {
+ this.samples = samples;
+ }
+
+ public abstract String toRankingExpression();
+
+ /**
+ * Returns the number of samples in the training set that matches this node
+ * if this model does not contain this information (i.e if it is not an "ext" model).
+ */
+ public Optional<Integer> samples() { return samples; }
+
+ public static TreeNode fromDom(Node node) {
+ String nodeName = node.getNodeName();
+ if (nodeName.equalsIgnoreCase("node")) {
+ return FeatureNode.fromDom(node);
+ } else if (nodeName.equalsIgnoreCase("response")) {
+ return ResponseNode.fromDom(node);
+ } else {
+ throw new UnsupportedOperationException(nodeName);
+ }
+ }
+
+ static Optional<Integer> toInteger(Optional<String> integerText) {
+ if ( ! integerText.isPresent()) return Optional.empty();
+ return Optional.of(Integer.parseInt(integerText.get()));
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/gbdt/XmlHelper.java b/searchlib/src/main/java/com/yahoo/searchlib/gbdt/XmlHelper.java
new file mode 100644
index 00000000000..4ed0106e7ae
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/gbdt/XmlHelper.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.gbdt;
+
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.xml.sax.SAXException;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.ParserConfigurationException;
+import java.io.ByteArrayInputStream;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+abstract class XmlHelper {
+
+ private static final Charset UTF8 = Charset.forName("UTF-8");
+
+ public static Element parseXml(String xml)
+ throws ParserConfigurationException, IOException, SAXException
+ {
+ return parseXmlStream(new ByteArrayInputStream(xml.getBytes(UTF8)));
+ }
+
+ public static Element parseXmlFile(String fileName)
+ throws ParserConfigurationException, IOException, SAXException
+ {
+ return parseXmlStream(new FileInputStream(fileName));
+ }
+
+ public static Element parseXmlStream(InputStream in)
+ throws ParserConfigurationException, IOException, SAXException
+ {
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder builder = factory.newDocumentBuilder();
+ Document doc = builder.parse(in);
+ return doc.getDocumentElement();
+ }
+
+ public static String getAttributeText(Node node, String name) {
+ Node valueNode = node.getAttributes().getNamedItem(name);
+ if (valueNode == null) {
+ throw new IllegalArgumentException("Missing '" + name + "' attribute in element '" +
+ node.getNodeName() + "'.");
+ }
+ String valueText = valueNode.getTextContent();
+ if (valueText == null || valueText.isEmpty()) {
+ throw new IllegalArgumentException("Attribute '" + name + "' in element '" +
+ node.getNodeName() + "' is empty.");
+ }
+ return valueText;
+ }
+
+ public static String getAttributeTextOrNull(Node node, String name) {
+ Node valueNode = node.getAttributes().getNamedItem(name);
+ if (valueNode == null) return null;
+ return valueNode.getTextContent();
+ }
+
+ public static Optional<String> getOptionalAttributeText(Node node, String name) {
+ Node valueNode = node.getAttributes().getNamedItem(name);
+ if (valueNode == null) return Optional.empty();
+ return Optional.of(valueNode.getTextContent());
+ }
+
+ public static Element getSingleElement(Node node, String name) {
+ List<Element> children = getChildElements(node, name);
+ if (children.isEmpty()) {
+ if (name != null) {
+ throw new IllegalArgumentException("Node '" + node.getNodeName() + "' has no '" + name + "' children.");
+ } else {
+ throw new IllegalArgumentException("Node '" + node.getNodeName() + "' has no children.");
+ }
+ }
+ if (children.size() != 1) {
+ if (name != null) {
+ throw new IllegalArgumentException("Expected 1 '" + name + "' child, got " + children.size() + ".");
+ } else {
+ throw new IllegalArgumentException("Expected 1 child, got " + children.size() + ".");
+ }
+ }
+ return children.get(0);
+ }
+
+ public static List<Element> getChildElements(Node node, String name) {
+ NodeList children = node.getChildNodes();
+ List<Element> lst = new LinkedList<>();
+ for (int i = 0, len = children.getLength(); i < len; ++i) {
+ Node child = children.item(i);
+ if (!(child instanceof Element)) {
+ continue;
+ }
+ if (name != null && !child.getNodeName().equalsIgnoreCase(name)) {
+ continue;
+ }
+ lst.add((Element)child);
+ }
+ return lst;
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/CaseList.java b/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/CaseList.java
new file mode 100644
index 00000000000..608a4b499ed
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/CaseList.java
@@ -0,0 +1,15 @@
+// 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;
+
+import java.util.List;
+
+/**
+ * A producer of a list of cases for function training.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public interface CaseList {
+
+ public List<TrainingSet.Case> cases();
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/Evolvable.java b/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/Evolvable.java
new file mode 100644
index 00000000000..0ccce4ad2ad
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/Evolvable.java
@@ -0,0 +1,26 @@
+// 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;
+
+import com.yahoo.searchlib.rankingexpression.RankingExpression;
+
+import java.util.List;
+
+/**
+ * An entity which may evolve over time
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public abstract class Evolvable implements Comparable<Evolvable> {
+
+ public abstract Evolvable makeSuccessor(int memberNumber, List<RankingExpression> genepool, TrainingEnvironment environment);
+
+ public abstract RankingExpression getGenepool();
+
+ @Override
+ public int compareTo(Evolvable other) {
+ return -Double.compare(getFitness(), other.getFitness());
+ }
+
+ public abstract double getFitness();
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/Individual.java b/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/Individual.java
new file mode 100644
index 00000000000..416e2da4c82
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/Individual.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.mlr.ga;
+
+import com.yahoo.searchlib.rankingexpression.RankingExpression;
+
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * An individual in an evolving population - a genome with a fitness score.
+ * Individuals are comparable by decreasing fitness.
+ * <p>
+ * As we are training ranking expressions, the genome, here, is the ranking expression.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class Individual extends Evolvable {
+
+ private final RankingExpression genome;
+ private final TrainingSet trainingSet;
+ private final double fitness;
+
+ public Individual(RankingExpression genome, TrainingSet trainingSet) {
+ this.genome = genome;
+ this.trainingSet = trainingSet;
+ this.fitness = trainingSet.evaluate(genome);
+ }
+
+ public RankingExpression getGenome() { return genome; }
+
+ public double calculateAverageError() {
+ return trainingSet.calculateAverageError(genome);
+ }
+
+ public double calculateAverageErrorPercentage() {
+ return trainingSet.calculateAverageErrorPercentage(genome);
+ }
+
+ @Override
+ public double getFitness() { return fitness; }
+
+ @Override
+ public Individual makeSuccessor(int memberNumber, List<RankingExpression> genepool, TrainingEnvironment environment) {
+ return new Individual(environment.recombiner().recombine(genome, genepool), trainingSet);
+ }
+
+ @Override
+ public RankingExpression getGenepool() {
+ return genome;
+ }
+
+ @Override
+ public String toString() {
+ return toSomewhatShortString() + ", expression: " + genome;
+ }
+
+ /** Returns a shorter string describing this (not including the expression */
+ public String toSomewhatShortString() {
+ return "Error % " + calculateAverageErrorPercentage() +
+ " average error " + calculateAverageError() +
+ " fitness " + getFitness();
+ }
+
+ /** Returns a shorter string describing this (not including the expression */
+ public String toShortString() {
+ return "Error: " + calculateAverageErrorPercentage() + " %";
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/KeyboardChecker.java b/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/KeyboardChecker.java
new file mode 100644
index 00000000000..7f2e3645076
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/KeyboardChecker.java
@@ -0,0 +1,50 @@
+// 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;
+
+import java.awt.KeyEventDispatcher;
+import java.awt.KeyboardFocusManager;
+import java.awt.event.KeyEvent;
+
+/**
+ * TODO
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class KeyboardChecker {
+
+ private static boolean qPressed = false;
+
+ private final Object lock = new Object();
+
+ public KeyboardChecker() {
+ KeyboardFocusManager.getCurrentKeyboardFocusManager().addKeyEventDispatcher(new KeyEventDispatcher() {
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent ke) {
+ synchronized (lock) {
+ switch (ke.getID()) {
+ case KeyEvent.KEY_PRESSED:
+ if (ke.getKeyCode() == KeyEvent.VK_Q) {
+ qPressed = true;
+ }
+ break;
+
+ case KeyEvent.KEY_RELEASED:
+ if (ke.getKeyCode() == KeyEvent.VK_Q) {
+ qPressed = false;
+ }
+ break;
+ }
+ return false;
+ }
+ }
+ });
+ }
+
+ public boolean isQPressed() {
+ synchronized (lock) {
+ return qPressed;
+ }
+ }
+
+} \ No newline at end of file
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/Main.java b/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/Main.java
new file mode 100644
index 00000000000..c62462d0c3d
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/Main.java
@@ -0,0 +1,73 @@
+// 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;
+
+import com.yahoo.io.IOUtils;
+import com.yahoo.searchlib.mlr.ga.caselist.FileCaseList;
+import com.yahoo.searchlib.rankingexpression.RankingExpression;
+import com.yahoo.searchlib.rankingexpression.parser.ParseException;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+
+/**
+ * Command line runner for training sessions
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+/*
+TODO: Switch order of generation and sequence in names
+TODO: Output fitness improvement on each step (esp useful for species evolution)
+TODO: Detect local optima (no improvement for n rounds) and stop early
+TODO: Split into training and validation sets
+ */
+public class Main {
+
+ public Main(String[] args, Tracker tracker) {
+ if (args.length < 1 || args[0].trim().equals("help")) {
+ System.out.println(
+ "Finds a ranking expression matching a training set given as a case file.\n" +
+ "Run until the expression seems good enough.\n" +
+ "Usage: ga <case-file> - \n" +
+ " where case-file is a file containing case lines on the form \n" +
+ " targetValue, argument1:value1, ...\n" +
+ " (comment lines starting by # are also permitted)\n");
+ return;
+ }
+
+ TrainingParameters parameters = new TrainingParameters();
+ //parameters.setAllowConditions(false);
+ parameters.setErrorIsRelative(false);
+ parameters.setInitialSpeciesSize(40);
+ parameters.setSpeciesLifespan(100);
+ parameters.setExcludeFeatures("F7,F9,F10,F11,F12,F13,F14,F15,F16,F17,F18,F19,F21,F23,F24,F25,F26,F27,F29,F30,F32,F33,F34,F35,F36,F37,F38,F39,F40,F41,F42,F44,F46,F47,F48,F49,F50,F52,F53,F55,F56,F57,F58,F59,F60,F61,F62,F63,F64,F65,F67,F69,F70,F71,F72,F73,F75,F76,F78,F79,F80,F81,F82,F83,F84,F85,F86,F87,F88,F90,F92,F93,F94,F95,F96,F98,F99,F100,F101,F102,F103,F104,F105,F106,F107,F108,F109,F66,F89,F110");
+ //parameters.setInitialSpeciesSize(20);
+
+ String caseFile = args[0];
+ TrainingSet trainingSet = new TrainingSet(FileCaseList.create(caseFile, parameters), parameters);
+ Trainer trainer = new Trainer(trainingSet);
+
+ if (args.length > 1) { // Evaluate given expression
+ try {
+ Individual given = new Individual(new RankingExpression(new BufferedReader(new FileReader(args[1]))), trainingSet);
+ System.out.println("Error in '" + args[1] + "': error % " + given.calculateAverageErrorPercentage() +
+ " average error " + given.calculateAverageError() +
+ " fitness " + given.getFitness());
+ }
+ catch (IOException | ParseException e) {
+ throw new IllegalArgumentException("Could not evaluate expression in argument 2", e);
+ }
+ }
+ else { // Train expression
+ // TODO: Move system outs to tracker
+ System.out.println("Learning ...");
+ RankingExpression learntExpression = trainer.train(parameters, tracker);
+ System.out.println("Learnt expression: " + learntExpression);
+ }
+ }
+
+ public static void main(String[] args) {
+ new Main(args, new PrintingTracker(10, 0, 1));
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/Population.java b/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/Population.java
new file mode 100644
index 00000000000..484a0747e24
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/Population.java
@@ -0,0 +1,60 @@
+// 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;
+
+import com.yahoo.searchlib.rankingexpression.RankingExpression;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A collection of evolvables
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class Population {
+
+ /** The current members of this population, always sorted by decreasing fitness */
+ private List<Evolvable> members;
+
+ public Population(List<Evolvable> initialMembers) {
+ members = new ArrayList<>(initialMembers);
+ Collections.sort(members);
+ }
+
+ /** Returns the most fit member of this population (never null) */
+ public Evolvable best() {
+ return members.get(0);
+ }
+
+ /** Returns the members of this population as an unmodifiable list sorted by decreasing fitness*/
+ public List<Evolvable> members() { return Collections.unmodifiableList(members); }
+
+ public void evolve(int generation, TrainingEnvironment environment) {
+ TrainingParameters p = environment.parameters();
+ int generationSize = p.getInitialSpeciesSize() -
+ (int)Math.round((p.getInitialSpeciesSize() - p.getFinalSpeciesSize()) * generation/p.getSpeciesLifespan());
+ members = breed(members, generationSize * p.getGenerationCandidatesFactor(), environment);
+ Collections.sort(members);
+ members = members.subList(0, Math.min(generationSize, members.size()));
+ }
+
+ private List<Evolvable> breed(List<Evolvable> members, int offspringCount, TrainingEnvironment environment) {
+ List<Evolvable> offspring = new ArrayList<>(offspringCount); // TODO: Can we do this inline and keep the list forever (and then also the immutable view)
+ offspring.add(members.get(0)); // keep the best as-is
+ List<RankingExpression> genePool = collectGenepool(members);
+ for (int i = 0; i < offspringCount - 1; i++) {
+ Evolvable child = members.get(i % members.size()).makeSuccessor(i, genePool, environment);
+ offspring.add(child);
+ }
+ return offspring;
+ }
+
+ private List<RankingExpression> collectGenepool(List<Evolvable> members) {
+ List<RankingExpression> genepool = new ArrayList<>();
+ for (Evolvable member : members)
+ genepool.add(member.getGenepool());
+ return genepool;
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/PrintingTracker.java b/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/PrintingTracker.java
new file mode 100644
index 00000000000..4a3edd35a8d
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/PrintingTracker.java
@@ -0,0 +1,91 @@
+// 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;
+
+import com.yahoo.searchlib.rankingexpression.RankingExpression;
+import com.yahoo.yolean.Exceptions;
+
+import java.util.List;
+
+/**
+ * A tracker which prints a summary of training events to standard out
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class PrintingTracker implements Tracker {
+
+ private final int iterationEvery;
+ private final int survivorsEvery;
+ private final int printSpeciesCreationLevel;
+ private final int printSpeciesCompletionLevel;
+
+ public PrintingTracker() {
+ this(0, 1);
+ }
+
+ public PrintingTracker(int printSpeciesCreationLevel, int printSpeciesCompletionLevel) {
+ this(Integer.MAX_VALUE, Integer.MAX_VALUE, printSpeciesCreationLevel, printSpeciesCompletionLevel);
+ }
+
+ public PrintingTracker(int iterationEvery, int printSpeciesCreationLevel, int printSpeciesCompletionLevel) {
+ this(iterationEvery, Integer.MAX_VALUE, printSpeciesCreationLevel, printSpeciesCompletionLevel);
+ }
+
+ public PrintingTracker(int iterationEvery, int survivorsEvery, int printSpeciesCreationLevel, int printSpeciesCompletionLevel) {
+ this.iterationEvery = iterationEvery;
+ this.survivorsEvery = survivorsEvery;
+ this.printSpeciesCreationLevel = printSpeciesCreationLevel;
+ this.printSpeciesCompletionLevel = printSpeciesCompletionLevel;
+ }
+
+ @Override
+ public void newSpecies(Species predecessor, int initialSize, List<RankingExpression> genePool) {
+ if (predecessor.name().level() > printSpeciesCreationLevel) return;
+ System.out.println(spaces(predecessor.name().level()*2) + "Creating new species of size " + initialSize + " and a gene pool of size " + genePool.size() + " from predecessor " + predecessor);
+ }
+
+ @Override
+ public void newSpeciesCreated(Species species) {
+ if (species.name().level() > printSpeciesCreationLevel) return;
+ System.out.println(spaces(species.name().level()*2) + "Created and will now evolve " + species);
+ }
+
+ @Override
+ public void speciesCompleted(Species species) {
+ if (species.name().level() > printSpeciesCompletionLevel) return;
+ System.out.println(spaces(species.name().level()*2) + "--> Evolution completed for " + species);
+ }
+
+ /** Called each time a species (or super-species) have completed one generation */
+ @Override
+ public void iteration(Species species, int generation) {
+ try {
+ new RankingExpression(species.bestIndividual().getGenome().toString());
+ }
+ catch (Exception e) {
+ System.err.println("ERROR: " + Exceptions.toMessageString(e) + ": " + species.bestIndividual().getGenome());
+ }
+
+ if ( (generation % iterationEvery) == 0)
+ System.out.println(spaces(species.name().level()*2) + "Gen " + generation + " of " + species);
+
+ if ( (generation % survivorsEvery) == 0)
+ printPopulation(species.name().level(), species.population().members());
+ }
+
+ @Override
+ public void result(Evolvable winner) {
+ System.out.println("Learnt expression: " + winner);
+ }
+
+ private String spaces(int spaces) {
+ return " ".substring(0,spaces);
+ }
+
+ private void printPopulation(int level, List<Evolvable> survivors) {
+ if (survivors.size()<=1) return;
+ System.out.println(" Population:");
+ for (Evolvable individual : survivors)
+ System.out.println(spaces(level*2) + " " + individual);
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/RankingExpressionCaseList.java b/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/RankingExpressionCaseList.java
new file mode 100644
index 00000000000..a4421595917
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/RankingExpressionCaseList.java
@@ -0,0 +1,33 @@
+// 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;
+
+import com.yahoo.searchlib.mlr.ga.CaseList;
+import com.yahoo.searchlib.rankingexpression.RankingExpression;
+import com.yahoo.searchlib.rankingexpression.evaluation.Context;
+import com.yahoo.searchlib.mlr.ga.TrainingSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Produces a list of training cases (argument and target value pairs)
+ * from a Ranking Expression.
+ * Useful for testing.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class RankingExpressionCaseList implements CaseList {
+
+ private final List<TrainingSet.Case> cases = new ArrayList<TrainingSet.Case>();
+
+ public RankingExpressionCaseList(List<Context> arguments, RankingExpression targetFunction) {
+ for (Context argument : arguments)
+ cases.add(new TrainingSet.Case(argument,targetFunction.evaluate(argument).asDouble()));
+ }
+
+ /** Returns the list of cases generated from the ranking expression */
+ @Override
+ public List<TrainingSet.Case> cases() { return Collections.unmodifiableList(cases); }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/Recombiner.java b/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/Recombiner.java
new file mode 100644
index 00000000000..d67afddd3c5
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/Recombiner.java
@@ -0,0 +1,200 @@
+// 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;
+
+import com.yahoo.searchlib.rankingexpression.RankingExpression;
+import com.yahoo.searchlib.rankingexpression.evaluation.DoubleValue;
+import com.yahoo.searchlib.rankingexpression.rule.*;
+
+import java.util.*;
+import java.util.logging.Logger;
+
+import static java.lang.Math.abs;
+import static java.lang.Math.max;
+import static java.lang.Math.min;
+
+/**
+ * A class which returns a mutated, recombined genome from a list of parent genomes.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class Recombiner {
+
+ // TODO: Either make ranking expressions immutable and get rid of parent pointer, or do clone everywhere below
+
+ private static final Logger log = Logger.getLogger(Trainer.class.getName());
+
+ private final Random random = new Random();
+
+ private final List<String> features;
+
+ private final TrainingParameters parameters;
+
+ /**
+ * Creates a recombiner
+ *
+ * @param features the list of feature names which are possible within the space we are training,
+ * such that these may be spontaneously added to expressions.
+ */
+ public Recombiner(Collection<String> features, TrainingParameters trainingParameters) {
+ this.features = Collections.unmodifiableList(new ArrayList<>(features));
+ this.parameters = trainingParameters;
+ }
+
+ public RankingExpression recombine(RankingExpression genome, List<RankingExpression> genePool) {
+ List<ExpressionNode> genePoolRoots = new ArrayList<>();
+ for (RankingExpression genePoolGenome : genePool)
+ genePoolRoots.add(genePoolGenome.getRoot());
+ return new RankingExpression(mutate(genome.getRoot(), genePoolRoots, 0));
+ }
+
+ private ExpressionNode mutate(ExpressionNode gene, List<ExpressionNode> genePool, int depth) {
+ // TODO: Extract insert level
+ if (gene instanceof BooleanNode)
+ return simplifyCondition(mutateChildren((CompositeNode)gene,genePool,depth+1));
+ if (gene instanceof CompositeNode)
+ return insertNodeLevel(simplify(removeNodeLevel(mutateChildren((CompositeNode)gene,genePool,depth+1))), genePool, depth+1);
+ else
+ return insertNodeLevel(mutateLeaf(gene), genePool, depth+1);
+ }
+
+ private BooleanNode simplifyCondition(ExpressionNode node) {
+ // Nothing yet
+ return (BooleanNode)node;
+ }
+
+ /** Very basic algorithmic simplification */
+ private ExpressionNode simplify(ExpressionNode node) {
+ if (! (node instanceof CompositeNode)) return node;
+ CompositeNode composite = (CompositeNode)node;
+ if (maxDepth(composite)>2) return composite;
+ List<ExpressionNode> children = composite.children();
+ if (children.size()!=2) return composite;
+ if ( ! (children.get(0) instanceof ConstantNode)) return composite;
+ if ( ! (children.get(1) instanceof ConstantNode)) return composite;
+ return new ConstantNode(composite.evaluate(null));
+ }
+
+ private CompositeNode mutateChildren(CompositeNode gene, List<ExpressionNode> genePool, int depth) {
+ if (gene instanceof ReferenceNode) return gene; // TODO: Remove if we make this a non-composite
+
+ List<ExpressionNode> mutatedChildren = new ArrayList<>();
+ for (ExpressionNode child : gene.children())
+ mutatedChildren.add(mutate(child, genePool, depth));
+ return gene.setChildren(mutatedChildren);
+ }
+
+ private ExpressionNode insertNodeLevel(ExpressionNode gene, List<ExpressionNode> genePool, int depth) {
+ if (probability() < 0.9) return gene;
+ if (depth + maxDepth(gene) >= parameters.getMaxExpressionDepth()) return gene;
+ ExpressionNode newChild = generateChild(genePool, depth);
+ if (probability() < 0.5)
+ return generateComposite(gene, newChild, genePool, depth);
+ else
+ return generateComposite(newChild, gene, genePool, depth);
+ }
+
+ private ExpressionNode removeNodeLevel(CompositeNode gene) {
+ if (gene instanceof ReferenceNode) return gene; // TODO: Remove if we make featurenode a non-composite
+ if (probability() < 0.9) return gene;
+ return randomFrom(gene.children());
+ }
+
+ private ExpressionNode generateComposite(ExpressionNode left, ExpressionNode right, List<ExpressionNode> genePool, int depth) {
+ int type = random.nextInt(2 + ( parameters.getAllowConditions() ? 1:0 ) ); // pick equally between 2 or 3 types
+ if (type == 0) {
+ return new ArithmeticNode(left, pickArithmeticOperator(), right);
+ }
+ else if (type == 1) {
+ Function function = pickFunction();
+ if (function.arity() == 1)
+ return new FunctionNode(function, left);
+ else // arity==2
+ return new FunctionNode(function, left, right);
+ }
+ else {
+ return new IfNode(generateCondition(genePool, depth + 1), left, right);
+ }
+ }
+
+ private BooleanNode generateCondition(List<ExpressionNode> genePool, int depth) {
+ // TODO: Add set membership nodes
+ return new ComparisonNode(generateChild(genePool, depth), TruthOperator.SMALLER, generateChild(genePool, depth));
+ }
+
+ private ExpressionNode generateChild(List<ExpressionNode> genePool, int depth) {
+ if (genePool.isEmpty() || probability() < 0.1) { // entirely new child
+ return generateLeaf();
+ }
+ else { // pick from gene pool
+ ExpressionNode picked = randomFrom(genePool);
+ int pickedDepth = 0;
+ // descend until we are at at least the same depth as this depth
+ // to make sure branches spliced in are shallow enough that we avoid growing
+ // larger than maxDepth
+ while (picked instanceof CompositeNode && (pickedDepth++ < depth || probability() < 0.5)) {
+ if (picked instanceof ReferenceNode) continue; // TODO: Remove if we make referencenode a noncomposite
+ picked = randomFrom(((CompositeNode)picked).children());
+ }
+ return picked;
+ }
+ }
+
+ public ExpressionNode mutateLeaf(ExpressionNode leaf) {
+ if (probability() < 0.5) return leaf; // TODO: For performance. Drop?
+ // TODO: Other leaves
+ ConstantNode constant = (ConstantNode)leaf;
+ return new ConstantNode(DoubleValue.frozen(constant.getValue().asDouble()*aboutOne()));
+ }
+
+ public ExpressionNode generateLeaf() {
+ if (probability()<0.5 || features.size() == 0)
+ return new ConstantNode(DoubleValue.frozen(random.nextDouble() * 2000 - 1000)); // TODO: Use some non-uniform distribution
+ else
+ return new ReferenceNode(randomFrom(features));
+ }
+
+ private double aboutOne() {
+ return 1 + Math.pow(-0.1, random.nextInt(4) + 1);
+ }
+
+ private double probability() {
+ return random.nextDouble();
+ }
+
+ private <T> T randomFrom(List<T> expressionList) {
+ return expressionList.get(random.nextInt(expressionList.size()));
+ }
+
+ private ArithmeticOperator pickArithmeticOperator() {
+ switch (random.nextInt(4)) {
+ case 0: return ArithmeticOperator.PLUS;
+ case 1: return ArithmeticOperator.MINUS;
+ case 2: return ArithmeticOperator.MULTIPLY;
+ case 3: return ArithmeticOperator.DIVIDE;
+ }
+ throw new RuntimeException("This cannot happen");
+ }
+
+ /** Pick among the subset of functions which are probably useful */
+ private Function pickFunction() {
+ switch (random.nextInt(5)) {
+ case 0: return Function.tanh;
+ case 1: return Function.exp;
+ case 2: return Function.log;
+ case 3: return Function.pow;
+ case 4: return Function.sqrt;
+ }
+ throw new RuntimeException("This cannot happen");
+ }
+
+ // TODO: Make ranking expressions immutable and compute this on creation?
+ private int maxDepth(ExpressionNode node) {
+ if ( ! (node instanceof CompositeNode)) return 1;
+
+ int maxChildDepth = 0;
+ for (ExpressionNode child : ((CompositeNode)node).children())
+ maxChildDepth = Math.max(maxDepth(child), maxChildDepth);
+ return maxChildDepth + 1;
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/Species.java b/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/Species.java
new file mode 100644
index 00000000000..39694b6253f
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/Species.java
@@ -0,0 +1,93 @@
+// 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;
+
+import com.yahoo.searchlib.rankingexpression.RankingExpression;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A species is a population of evolvables.
+ * Contrary to a real species, a species population may contain (sub)species
+ * rather than individuals - at all levels but the lowest.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class Species extends Evolvable {
+
+ private SpeciesName name;
+ private final Population population;
+
+ /** Create a species having a given initial population */
+ public Species(SpeciesName name, Population population) {
+ this.name = name;
+ this.population = population;
+ }
+
+ /** Create a species evolved from a predecessor species, using the given gene pool for mutating it */
+ private Species(SpeciesName name, Species predecessor, List<RankingExpression> genepool, TrainingEnvironment environment) {
+ this.name = name;
+ environment.tracker().newSpecies(predecessor, environment.parameters().getInitialSpeciesSize(), genepool);
+
+ // Initialize new species with members generated from the predecessor species
+ List<Evolvable> initialMembers = new ArrayList<>();
+ for (int i = 0; i < environment.parameters().getInitialSpeciesSize(); i++)
+ initialMembers.add(drawFrom(predecessor.population, i).makeSuccessor(i, genepool, environment));
+ population = new Population(initialMembers);
+
+ // Evolve the population of this species for the configured number of generations
+ environment.tracker().newSpeciesCreated(this);
+ for (int generation = 0; generation < environment.parameters().getSpeciesLifespan(); generation++) {
+ environment.tracker().iteration(this, generation+1);
+ population.evolve(generation, environment);
+ if (Double.isInfinite(bestIndividual().getFitness())) break; // jackpot
+ // if (keyboardChecker.isQPressed()) break; // user quit TODO: Make work
+ }
+ environment.tracker().speciesCompleted(this);
+ }
+
+ /**
+ * Draws a member from the given population, where the probability of being drawn is proportional to the
+ * fitness of the member
+ */
+ private Evolvable drawFrom(Population population, int succession) {
+ return population.members().get(Math.min(succession % 3, population.members().size() - 1)); // TODO: Probabilistic by fitness?
+ }
+
+ public SpeciesName name() { return name; }
+
+ /** The fitness of the fittest individual in the population */
+ @Override
+ public double getFitness() {
+ return population.best().getFitness();
+ }
+
+ /** Creates the successor of this, using its genes, mutated drawing from the given gene pool */
+ @Override
+ public Evolvable makeSuccessor(int memberNumber, List<RankingExpression> genepool, TrainingEnvironment environment) {
+ return new Species(name.successor(memberNumber), this, genepool, environment);
+ }
+
+ /** Returns the members of this species */
+ public Population population() { return population; }
+
+ /** The genes of the fittest individual in the population of this */
+ @Override
+ public RankingExpression getGenepool() { // TODO: Less sharp?
+ return population.best().getGenepool();
+ }
+
+ /** Returns the best individual below this in the species hierarchy (e.g recursively the best leaf) */
+ public Individual bestIndividual() {
+ Evolvable child = this;
+ while (child instanceof Species)
+ child = ((Species)child).population.best();
+ return (Individual)child; // it is when it is not instanceof Species
+ }
+
+ @Override
+ public String toString() {
+ return "species " + name + ", best member: " + population.best();
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/SpeciesName.java b/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/SpeciesName.java
new file mode 100644
index 00000000000..3bd8ae5e55f
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/SpeciesName.java
@@ -0,0 +1,54 @@
+// 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;
+
+/**
+ * The name of a species. For tracking purposes.
+ * A name has the form superSpeciesName + "/" + serialNumber.generationNumber.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class SpeciesName {
+
+ private final int level, serial, generation;
+
+ private final String name, prefixName;
+
+ private SpeciesName(int level, int serial, int generation, String prefixName) {
+ this.level = level;
+ this.serial = serial;
+ this.generation = generation;
+ this.prefixName = prefixName;
+ if (level == 0)
+ this.name = "";
+ else
+ this.name = prefixName + (prefixName.isEmpty() ? "" : "/") + serial + "." + generation;
+ }
+
+ /**
+ * The level in the species hierarchy of the species having this name.
+ * The root species has level 0.
+ */
+ public int level() { return level; }
+
+ /** Returns the name of the root species: The empty string at level 0 */
+ public static SpeciesName createRoot() {
+ return new SpeciesName(0 ,0 ,0, "");
+ }
+
+ @Override
+ public String toString() {
+ if (level == 0) return "(root)";
+ return name;
+ }
+
+ /** Returns the name of a new subspecies */
+ public SpeciesName subspecies(int serial) {
+ return new SpeciesName(level+1, serial, 0, name);
+ }
+
+ /** Returns the name of the successor of this species */
+ public SpeciesName successor(int serial) {
+ return new SpeciesName(level, serial, generation+1, prefixName);
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/Tracker.java b/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/Tracker.java
new file mode 100644
index 00000000000..d86af40b805
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/Tracker.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.mlr.ga;
+
+import com.yahoo.searchlib.rankingexpression.RankingExpression;
+
+import java.util.List;
+
+/**
+ * A tracker receives callbacks about events happening during a training session.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public interface Tracker {
+
+ public void newSpecies(Species predecessor, int initialSize, List<RankingExpression> genePool);
+
+ public void newSpeciesCreated(Species species);
+
+ public void speciesCompleted(Species species);
+
+ public void iteration(Species species, int generation);
+
+ public void result(Evolvable winner);
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/Trainer.java b/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/Trainer.java
new file mode 100644
index 00000000000..7e2551eccb2
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/Trainer.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;
+
+import com.yahoo.searchlib.rankingexpression.RankingExpression;
+import com.yahoo.searchlib.rankingexpression.evaluation.DoubleValue;
+import com.yahoo.searchlib.rankingexpression.parser.ParseException;
+import com.yahoo.searchlib.rankingexpression.rule.ConstantNode;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Learns a ranking expression from some seed expressions and a training set.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class Trainer {
+
+ // TODO: Simplify this to constructor only ... or maybe remove ... or combine with TrainingEnvironment
+ // TODO: Also: Rename to Training?
+
+ private final TrainingSet trainingSet;
+ private final Set<String> argumentNames;
+
+ /**
+ * Creates a new trainer.
+ */
+ public Trainer(TrainingSet trainingSet) {
+ this(trainingSet, trainingSet.argumentNames());
+ }
+
+ /**
+ * Creates a new trainer which uses a specified list of expression argument names
+ * rather than the argument names given by the training set.
+ */
+ public Trainer(TrainingSet trainingSet, Set<String> argumentNames) {
+ this.trainingSet = trainingSet;
+ this.argumentNames = new HashSet<>(argumentNames);
+ }
+
+ public RankingExpression train(TrainingParameters parameters, Tracker tracker) {
+ TrainingEnvironment environment = new TrainingEnvironment(new Recombiner(argumentNames, parameters), tracker, trainingSet, parameters);
+ SpeciesName rootName = SpeciesName.createRoot();
+ Species genesisSubSpecies = new Species(rootName.subspecies(0), new Population(Collections.<Evolvable>singletonList(new Individual(new RankingExpression(new ConstantNode(new DoubleValue(1))), trainingSet))));
+ Species rootSpecies = (Species) new Species(rootName, new Population(Collections.<Evolvable>singletonList(genesisSubSpecies)))
+ .makeSuccessor(0, Collections.<RankingExpression>emptyList(), environment);
+ Individual winner = rootSpecies.bestIndividual();
+ tracker.result(winner);
+ return winner.getGenome();
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/TrainingEnvironment.java b/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/TrainingEnvironment.java
new file mode 100644
index 00000000000..757a2e4d3d2
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/TrainingEnvironment.java
@@ -0,0 +1,31 @@
+// 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;
+
+/**
+ * The static environment of a training session
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class TrainingEnvironment {
+
+ // TODO: Not sure if this belongs ... or should even be an instance
+ // TODO: maybe collapse Trainer into this and call it TrainingSession
+ private final Recombiner recombiner;
+ private final Tracker tracker;
+ private final TrainingSet trainingSet;
+ private final TrainingParameters parameters;
+
+ public TrainingEnvironment(Recombiner recombiner, Tracker tracker,
+ TrainingSet trainingSet, TrainingParameters parameters) {
+ this.recombiner = recombiner;
+ this.tracker = tracker;
+ this.trainingSet = trainingSet;
+ this.parameters = parameters;
+ }
+
+ public Recombiner recombiner() { return recombiner; }
+ public Tracker tracker() { return tracker; }
+ public TrainingSet trainingSet() { return trainingSet; }
+ public TrainingParameters parameters() { return parameters; }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/TrainingParameters.java b/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/TrainingParameters.java
new file mode 100644
index 00000000000..e18f560878e
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/TrainingParameters.java
@@ -0,0 +1,100 @@
+// 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;
+
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class TrainingParameters {
+
+ // A note:
+ // The total number of species generated and evaluated is
+ // (generationCandidatesFactor * speciesLifespan * (initialSpeciesSize-finalSpeciesSize)/2 ) ^ speciesLevels
+ // (speciesLevel is hardcoded to 2 atm)
+
+ private int speciesLifespan = 1000;
+ private int initialSpeciesSize = 10;
+ private double finalSpeciesSize = 1;
+ private int generationCandidatesFactor = 3;
+ private int maxExpressionDepth = 6;
+ private boolean allowConditions = true;
+ private boolean errorIsRelative = true;
+ private Set<String> excludeFeatures = new HashSet<>();
+ private String trainingSetFormat = null;
+ private double validationFraction = 0.2;
+
+ /** The number of generation which a given species (or super-species at any level) lives. Default:1000 */
+ public int getSpeciesLifespan() { return speciesLifespan; }
+ public void setSpeciesLifespan(int generations) { this.speciesLifespan = generations; }
+
+ /** The number of members in a species (or super-species at any level) as it is created. Default: 10 */
+ public int getInitialSpeciesSize() { return initialSpeciesSize; }
+ public void setInitialSpeciesSize(int initialSpeciesSize) { this.initialSpeciesSize = initialSpeciesSize; }
+
+ /**
+ * The number of members in a species in its final generation.
+ * The size of the species will be reduced linearly in each generation to go from initial size to final size.
+ * Default: 1
+ */
+ public double getFinalSpeciesSize() { return finalSpeciesSize; }
+ public void setFinalSpeciesSize(int finalSpeciesSize) { this.finalSpeciesSize = finalSpeciesSize; }
+
+ /*
+ * The factor determining how many more members are generated than are allowed to survive in each generation of a species.
+ * Default: 3
+ */
+ public int getGenerationCandidatesFactor() { return generationCandidatesFactor; }
+ public void setGenerationCandidatesFactor(int generationCandidatesFactor) { this.generationCandidatesFactor = generationCandidatesFactor; }
+
+ /**
+ * The max depth of expressions this is allowed to generate.
+ * Default: 6
+ */
+ public int getMaxExpressionDepth() { return maxExpressionDepth; }
+ public void setMaxExpressionDepth(int maxExpressionDepth) { this.maxExpressionDepth = maxExpressionDepth; }
+
+ /**
+ * Whether mutation should allow creation of condition (if) expressions.
+ * Default: true
+ */
+ public boolean getAllowConditions() { return allowConditions; }
+ public void setAllowConditions(boolean allowConditions) { this.allowConditions = allowConditions; }
+
+ /**
+ * Whether errors are relative to the absolute value of the function at that point or not.
+ * If true, training will assign equal weight to the error of 1.1 for 1 and 110 for 100.
+ * If false, training will instead assign a 10x weight to the latter.
+ * Default: True.
+ */
+ public boolean getErrorIsRelative() { return errorIsRelative; }
+ public void setErrorIsRelative(boolean errorIsRelative) { this.errorIsRelative = errorIsRelative; }
+
+ /**
+ * Returns the set of features to exclude during training.
+ * Returned as an immutable set, never null.
+ */
+ public Set<String> getExcludeFeatures() { return excludeFeatures; }
+ /** Sets the features to exclude from a comma-separated string */
+ public void setExcludeFeatures(String excludeFeatureString) {
+ for (String featureName : excludeFeatureString.split(","))
+ excludeFeatures.add(featureName.trim());
+ }
+
+ /**
+ * Returns the format of the training set to read. "fv" or "cvs" is supported.
+ * If this is null the format name is taken from the last name of the file instead.
+ * Default: null.
+ */
+ public String getTrainingSetFormat() { return trainingSetFormat; }
+ public void setTrainingSetFormat(String trainingSetFormat) { this.trainingSetFormat = trainingSetFormat; }
+
+ /**
+ * Returns the fraction of the result set to hold out of training and use for validation.
+ * Default 0.2
+ */
+ public double getValidationFraction() { return validationFraction; }
+ public void setValidationFraction(double validationFraction) { this.validationFraction = validationFraction; }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/TrainingSet.java b/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/TrainingSet.java
new file mode 100644
index 00000000000..507ab26806a
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/TrainingSet.java
@@ -0,0 +1,122 @@
+// 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;
+
+import com.yahoo.searchlib.rankingexpression.RankingExpression;
+import com.yahoo.searchlib.rankingexpression.evaluation.Context;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A training set: a set of <i>cases</i>: Input data to output value pairs
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class TrainingSet {
+
+ private final TrainingParameters parameters;
+ private final List<Case> trainingCases;
+ private final List<Case> validationCases;
+ private final Set<String> argumentNames = new HashSet<>();
+
+ /**
+ * Creates a training set from a list of cases.
+ * The ownership of the argument list and all the cases are transferred to this by this call.
+ */
+ public TrainingSet(CaseList caseList, TrainingParameters parameters) {
+ List<Case> cases = caseList.cases();
+
+ this.parameters = parameters;
+ for (Case aCase : cases)
+ argumentNames.addAll(aCase.arguments().names());
+ argumentNames.removeAll(parameters.getExcludeFeatures());
+
+ int validationCaseCount = (int)Math.round((cases.size() * parameters.getValidationFraction()));
+ this.validationCases = cases.subList(0, validationCaseCount);
+ this.trainingCases = cases.subList(validationCaseCount, cases.size());
+ }
+
+ public Set<String> argumentNames() {
+ return Collections.unmodifiableSet(argumentNames);
+ }
+
+ /**
+ * Returns the fitness of a genome (ranking expression) according to this training set.
+ * The fitness to be returned by this is the inverse of the average squared difference between the
+ * target function result and the function result returned by the genome function.
+ */
+ // TODO: Take expression length into account.
+ public double evaluate(RankingExpression genome) {
+ boolean constantExpressionGenome = true;
+ double squaredErrorSum = 0;
+ Double previousValue = null;
+ for (Case trainingCase : trainingCases) {
+ double value = genome.evaluate(trainingCase.arguments()).asDouble();
+ double error = saneAbs(effectiveError(trainingCase.targetValue(), value));
+ squaredErrorSum += Math.pow(error, 2);
+
+ if (previousValue != null && previousValue != value)
+ constantExpressionGenome = false;
+ previousValue = value;
+ }
+ if (constantExpressionGenome) return 0; // Disqualify constant expressions as we know we're not looking for them
+ return 1 / (squaredErrorSum / trainingCases.size());
+ }
+
+ private double effectiveError(double a, double b) {
+ return parameters.getErrorIsRelative() ? errorFraction(a, b) : a - b;
+ }
+
+ /** Calculate error in a way which is easy to understand (but which behaves badly when the target is around 0 */
+ public double calculateAverageError(RankingExpression genome) {
+ double errorSum=0;
+ for (Case trainingCase : trainingCases)
+ errorSum += saneAbs(trainingCase.targetValue() - genome.evaluate(trainingCase.arguments()).asDouble());
+ return errorSum/(double) trainingCases.size();
+ }
+
+ /** Calculate error in a way which is easy to understand (but which behaves badly when the target is around 0 */
+ public double calculateAverageErrorPercentage(RankingExpression genome) {
+ double errorFractionSum = 0;
+ for (Case trainingCase : trainingCases) {
+ double errorFraction = saneAbs(errorFraction(trainingCase.targetValue(), genome.evaluate(trainingCase.arguments()).asDouble()));
+ // System.out.println("Error %: " + (100 * errorFraction + " Target: " + trainingCase.targetValue() + " Learned: " + genome.evaluate(trainingCase.arguments()).asDouble()));
+ errorFractionSum += errorFraction;
+ }
+ return ( errorFractionSum/(double) trainingCases.size() ) *100;
+ }
+
+ private double errorFraction(double a, double b) {
+ double error = a - b;
+ if (error == 0 ) return 0; // otherwise a or b is different from 0
+ if (a != 0)
+ return error / a;
+ else
+ return error / b;
+ }
+
+ private double saneAbs(double d) {
+ if (Double.isInfinite(d) || Double.isNaN(d)) return Double.MAX_VALUE;
+ return Math.abs(d);
+ }
+
+ public static class Case {
+
+ private Context arguments;
+
+ private double targetValue;
+
+ public Case(Context arguments, double targetValue) {
+ this.arguments = arguments;
+ this.targetValue = targetValue;
+ }
+
+ public double targetValue() { return targetValue; }
+
+ public Context arguments() { return arguments; }
+
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/caselist/CsvFileCaseList.java b/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/caselist/CsvFileCaseList.java
new file mode 100644
index 00000000000..78291768380
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/caselist/CsvFileCaseList.java
@@ -0,0 +1,56 @@
+// 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.caselist;
+
+import com.yahoo.searchlib.mlr.ga.TrainingSet;
+import com.yahoo.searchlib.rankingexpression.evaluation.Context;
+import com.yahoo.searchlib.rankingexpression.evaluation.MapContext;
+
+import java.util.Optional;
+
+/**
+ * <p>A list of training set cases created by reading a file containing lines specifying a case
+ * per line using the following syntax
+ * <code>targetValue, argument1:value, argument2:value2, ...</code>
+ * where arguments are identifiers and values are doubles.</p>
+ *
+ * <p>Comment lines starting with "#" are ignored.</p>
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class CsvFileCaseList extends FileCaseList {
+
+ public CsvFileCaseList(String fileName) {
+ super(fileName);
+ }
+
+ protected Optional<TrainingSet.Case> lineToCase(String line, int lineNumber) {
+ String[] elements = line.split(",");
+ if (elements.length<2)
+ throw new IllegalArgumentException("At line " + lineNumber + ": Expected a comma-separated case on the " +
+ "form 'targetValue, argument1:value1, ...', but got '" + line );
+
+ double target;
+ try {
+ target = Double.parseDouble(elements[0].trim());
+ }
+ catch (NumberFormatException e) {
+ throw new IllegalArgumentException("At line " + lineNumber + ": Expected a target value double " +
+ "at the start of the line, got '" + elements[0] + "'");
+ }
+
+ Context context = new MapContext();
+ for (int i=1; i<elements.length; i++) {
+ String[] argumentPair = elements[i].split(":");
+ try {
+ if (argumentPair.length != 2) throw new IllegalArgumentException();
+ context.put(argumentPair[0].trim(),Double.parseDouble(argumentPair[1].trim()));
+ }
+ catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException("At line " + lineNumber + ", element " + (i+1) +
+ ": Expected argument on the form 'identifier:double', got '" + elements[i] + "'");
+ }
+ }
+ return Optional.of(new TrainingSet.Case(context, target));
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/caselist/FileCaseList.java b/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/caselist/FileCaseList.java
new file mode 100644
index 00000000000..264f8f33075
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/caselist/FileCaseList.java
@@ -0,0 +1,73 @@
+// 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.caselist;
+
+import com.yahoo.searchlib.mlr.ga.CaseList;
+import com.yahoo.searchlib.mlr.ga.TrainingParameters;
+import com.yahoo.searchlib.mlr.ga.TrainingSet;
+
+import java.io.BufferedReader;
+import java.io.FileReader;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * @author bratseth
+ */
+public abstract class FileCaseList implements CaseList {
+
+ private List<TrainingSet.Case> cases = new ArrayList<>();
+
+ /**
+ * Reads a case list from file.
+ *
+ * @throws IllegalArgumentException if the file could not be found or opened
+ */
+ public FileCaseList(String fileName) {
+ try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
+ String line;
+ int lineNumber=0;
+ while (null != (line=reader.readLine())) {
+ lineNumber++;
+ line = line.trim();
+ if (line.startsWith("#")) continue;
+ if (line.isEmpty()) continue;
+ Optional<TrainingSet.Case> newCase = lineToCase(line, lineNumber);
+ if (newCase.isPresent())
+ cases.add(newCase.get());
+
+ }
+ }
+ catch (IOException | IllegalArgumentException e) {
+ throw new IllegalArgumentException("Could not create a case list from file '" + fileName + "'", e);
+ }
+ }
+
+ /** Returns the case constructed from reading a line, if any */
+ protected abstract Optional<TrainingSet.Case> lineToCase(String line, int lineNumber);
+
+ @Override
+ public List<TrainingSet.Case> cases() { return Collections.unmodifiableList(cases); }
+
+ /** Creates a file case list of the type specified in the parameters */
+ public static FileCaseList create(String fileName, TrainingParameters parameters) {
+ String format = parameters.getTrainingSetFormat();
+ if (format == null)
+ format = ending(fileName);
+
+ switch (format) {
+ case "csv" : return new CsvFileCaseList(fileName);
+ case "fv" : return new FvFileCaseList(fileName);
+ default : throw new IllegalArgumentException("Unknown file format '" + format + "'");
+ }
+ }
+
+ private static String ending(String fileName) {
+ int lastDot = fileName.lastIndexOf(".");
+ if (lastDot <= 0) return null;
+ return fileName.substring(lastDot + 1, fileName.length());
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/caselist/FvFileCaseList.java b/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/caselist/FvFileCaseList.java
new file mode 100644
index 00000000000..ec07a939932
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/mlr/ga/caselist/FvFileCaseList.java
@@ -0,0 +1,59 @@
+// 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.caselist;
+
+import com.yahoo.searchlib.mlr.ga.TrainingSet;
+import com.yahoo.searchlib.rankingexpression.evaluation.Context;
+import com.yahoo.searchlib.rankingexpression.evaluation.MapContext;
+
+import java.util.Optional;
+
+/**
+ * A list of training set cases created by reading a file containing lines specifying a case
+ * per line using the following syntax
+ * <code>feature1\tfeature2\tfeature3\t...\ttarget1</code>
+ * <p>
+ * The first line contains the name of each feature in the same order.
+ *
+ * <p>Comment lines starting with "#" are ignored.</p>
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+// NOTE: If we get another type of case list it is time to abstract into a common CaseList base class
+public class FvFileCaseList extends FileCaseList {
+
+ private String[] argumentNames;
+
+ public FvFileCaseList(String fileName) {
+ super(fileName);
+ }
+
+ protected Optional<TrainingSet.Case> lineToCase(String line, int lineNumber) {
+ String[] values = line.split("\t");
+
+ if (argumentNames == null) { // first line
+ argumentNames = values;
+ return Optional.empty();
+ }
+
+ if (argumentNames.length != values.length)
+ throw new IllegalArgumentException("Wrong number of values at line " + lineNumber);
+
+
+ Context context = new MapContext();
+ for (int i = 0; i < values.length-1; i++)
+ context.put(argumentNames[i], toDouble(values[i], lineNumber));
+
+ double target = toDouble(values[values.length-1], lineNumber);
+ return Optional.of(new TrainingSet.Case(context, target));
+ }
+
+ private double toDouble(String s, int lineNumber) {
+ try {
+ return Double.parseDouble(s.trim());
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("At line " + lineNumber + ": Expected only double values, " +
+ "got '" + s + "'");
+ }
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/mlr/gbdt/ExpressionAnalysis.java b/searchlib/src/main/java/com/yahoo/searchlib/mlr/gbdt/ExpressionAnalysis.java
new file mode 100644
index 00000000000..874f8e8666b
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/mlr/gbdt/ExpressionAnalysis.java
@@ -0,0 +1,425 @@
+// 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 com.yahoo.searchlib.rankingexpression.rule.SetMembershipNode;
+import com.yahoo.yolean.Exceptions;
+import com.yahoo.searchlib.mlr.ga.Individual;
+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.evaluation.Value;
+import com.yahoo.searchlib.rankingexpression.parser.ParseException;
+import com.yahoo.searchlib.rankingexpression.rule.Arguments;
+import com.yahoo.searchlib.rankingexpression.rule.ArithmeticNode;
+import com.yahoo.searchlib.rankingexpression.rule.ComparisonNode;
+import com.yahoo.searchlib.rankingexpression.rule.CompositeNode;
+import com.yahoo.searchlib.rankingexpression.rule.ConstantNode;
+import com.yahoo.searchlib.rankingexpression.rule.ExpressionNode;
+import com.yahoo.searchlib.rankingexpression.rule.ReferenceNode;
+import com.yahoo.searchlib.rankingexpression.rule.IfNode;
+import com.yahoo.searchlib.rankingexpression.rule.NegativeNode;
+import com.yahoo.searchlib.rankingexpression.rule.TruthOperator;
+
+import java.io.BufferedReader;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.Reader;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Random;
+
+/**
+ * A standalone tool which analyzes a GBDT form ranking expression
+ *
+ * @author bratseth
+ */
+public class ExpressionAnalysis {
+
+ private final Map<String, Feature> features = new HashMap<>();
+
+ private int currentTree;
+
+ private final RankingExpression expression;
+
+ public ExpressionAnalysis(RankingExpression expression) {
+ this.expression = expression;
+ if ( ! instanceOf(expression.getRoot(), ArithmeticNode.class)) return;
+ analyzeSum((ArithmeticNode)expression.getRoot());
+ }
+
+ /** Returns the expression analyzed by this */
+ public RankingExpression expression() { return expression; }
+
+ /** Returns the analysis of each feature in this expression as a read-only map indexed by feature name */
+ private Map<String, Feature> featureMap() {
+ return Collections.unmodifiableMap(features);
+ }
+
+ /** Returns list containing the analysis of each feature, sorted by decreasing usage */
+ private List<Feature> features() {
+ List<Feature> featureList = new ArrayList<>(features.values());
+ Collections.sort(featureList);
+ return featureList;
+ }
+
+ /** Returns the name of each feature, sorted by decreasing usage */
+ private List<String> featureNames() {
+ List<String> featureNameList = new ArrayList<>(features.values().size());
+ for (Feature feature : features())
+ featureNameList.add(feature.name());
+ return featureNameList;
+ }
+
+ private void analyzeSum(ArithmeticNode node) {
+ for (ExpressionNode child : node.children()) {
+ currentTree++;
+ analyze(child);
+ }
+ }
+
+ private void analyze(ExpressionNode node) {
+ if (node instanceof IfNode) {
+ analyzeIf((IfNode)node);
+ }
+
+ if (node instanceof CompositeNode) {
+ for (ExpressionNode child : ((CompositeNode)node).children())
+ analyze(child);
+ }
+ }
+
+ private void analyzeIf(IfNode node) {
+ if (node.getCondition() instanceof ComparisonNode)
+ analyzeComparisonIf(node);
+ else if (node.getCondition() instanceof SetMembershipNode)
+ analyzeSetMembershipIf(node);
+ else
+ System.err.println("Warning: Expected a comparison or set membership test, got " + node.getCondition().getClass());
+ }
+
+ private void analyzeComparisonIf(IfNode node) {
+ ComparisonNode comparison = (ComparisonNode)node.getCondition();
+
+ if (comparison.getOperator() != TruthOperator.SMALLER) {
+ System.err.println("Warning: This expression has " + comparison.getOperator() + " where we expect < :" +
+ comparison);
+ return;
+ }
+
+ if ( ! instanceOf(comparison.getLeftCondition(), ReferenceNode.class)) return;
+ String featureName = ((ReferenceNode)comparison.getLeftCondition()).getName();
+
+ Double value = nodeValue(comparison.getRightCondition());
+ if (value == null) return;
+
+ ComparisonFeature feature = (ComparisonFeature)features.get(featureName);
+ if (feature == null) {
+ feature = new ComparisonFeature(featureName);
+ features.put(featureName, feature);
+ }
+ feature.isComparedTo(value, currentTree, average(node.getTrueExpression()), average(node.getFalseExpression()));
+ }
+
+ private void analyzeSetMembershipIf(IfNode node) {
+ SetMembershipNode membershipTest = (SetMembershipNode)node.getCondition();
+
+ if ( ! instanceOf(membershipTest.getTestValue(), ReferenceNode.class)) return;
+ String featureName = ((ReferenceNode)membershipTest.getTestValue()).getName();
+
+ SetMembershipFeature feature = (SetMembershipFeature)features.get(featureName);
+ if (feature == null) {
+ feature = new SetMembershipFeature(featureName);
+ features.put(featureName, feature);
+ }
+ }
+
+ /**
+ * Returns the value of a constant node, or a negative wrapping a constant.
+ * Warns and returns null if it is neither.
+ */
+ private Double nodeValue(ExpressionNode node) {
+ if (node instanceof NegativeNode) {
+ NegativeNode negativeNode = (NegativeNode)node;
+ if ( ! instanceOf(negativeNode.getValue(), ConstantNode.class)) return null;
+ return - ((ConstantNode)negativeNode.getValue()).getValue().asDouble();
+ }
+ else {
+ if ( ! instanceOf(node, ConstantNode.class)) return null;
+ return ((ConstantNode)node).getValue().asDouble();
+ }
+ }
+
+
+ /** Returns the average value of all the leaf constants below this */
+ private double average(ExpressionNode node) {
+ Sum sum = new Sum();
+ average(node, sum);
+ return sum.average();
+ }
+
+ private void average(ExpressionNode node, Sum sum) {
+ if (node instanceof CompositeNode) {
+ for (ExpressionNode child : ((CompositeNode)node).children())
+ average(child, sum);
+ }
+ else {
+ Double value = nodeValue(node);
+ if (value == null) return;
+ sum.add(value);
+ }
+ }
+
+ private boolean instanceOf(Object object, Class<?> clazz) {
+ if (clazz.isAssignableFrom(object.getClass())) return true;
+ System.err.println("Warning: This expression has " + object.getClass() + " where we expect " + clazz +
+ ": Instance " + object);
+ return false;
+ }
+
+ private List<Context> generateArgumentSets(int count) {
+ List<Context> argumentSets = new ArrayList<>(count);
+ for (int i=0; i<count; i++) {
+ ArgumentIgnoringMapContext context = new ArgumentIgnoringMapContext();
+ for (Feature feature : features()) {
+ if (feature instanceof ComparisonFeature) {
+ ComparisonFeature comparison = (ComparisonFeature)feature;
+ context.put(comparison.name(),randomBetween(comparison.lowerBound(), comparison.upperBound()));
+ }
+ // TODO: else if (feature instanceof SetMembershipFeature)
+ }
+ argumentSets.add(context);
+ }
+ return argumentSets;
+ }
+
+ private Random random = new Random();
+ /** Returns a random value in [lowerBound, upperBound&gt; */
+ private double randomBetween(double lowerBound, double upperBound) {
+ return random.nextDouble()*(upperBound-lowerBound)+lowerBound;
+ }
+
+ private static class ArgumentIgnoringMapContext extends MapContext {
+
+ @Override
+ public Value get(String name, Arguments arguments,String output) {
+ return super.get(name, null, output);
+ }
+
+ }
+
+ /** Generates a textual report from analyzing this expression */
+ public String report() {
+ StringBuilder b = new StringBuilder();
+ b.append("Trees: " + currentTree).append("\n");
+ b.append("Features:\n");
+ for (Feature feature : features())
+ b.append(" " + feature).append("\n");
+ return b.toString();
+ }
+
+ private static final String usage = "\nUsage: ExpressionAnalysis [myExpressionFile.expression]";
+
+ public static void main(String[] args) {
+ if (args.length == 0) error("No arguments." + usage);
+
+ ExpressionAnalysis analysis = analysisFromFile(args[0]);
+
+ if (1==1) return; // Turn off ga training
+ if (args.length == 1) {
+ new GATraining(analysis);
+ }
+ else if (args.length == 2) {
+ try {
+ new LearntExpressionAnalysis(analysis, new RankingExpression(args[1]));
+ }
+ catch (ParseException e) {
+ error("Syntax error in argument expression: " + Exceptions.toMessageString(e));
+ }
+ }
+ else {
+ error("Unexpectedly got more than 2 arguments." + usage);
+ }
+
+ }
+
+ private static ExpressionAnalysis analysisFromFile(String fileName) {
+ try (Reader fileReader = new BufferedReader(new FileReader(fileName))) {
+ System.out.println("Analyzing " + fileName + "...");
+ ExpressionAnalysis analysis = new ExpressionAnalysis(new RankingExpression(fileReader));
+ System.out.println(analysis.report());
+ return analysis;
+ }
+ catch (FileNotFoundException e) {
+ error("Could not find '" + fileName + "'");
+ }
+ catch (IOException e) {
+ error("Failed reading '" + fileName + "': " + Exceptions.toMessageString(e));
+ }
+ catch (ParseException e) {
+ error("Syntax error in '" + fileName + "': " + Exceptions.toMessageString(e));
+ }
+ return null;
+ }
+
+ private static class LearntExpressionAnalysis {
+
+ public LearntExpressionAnalysis(ExpressionAnalysis analysis, RankingExpression learntExpression) {
+ int cases = 1000;
+ TrainingSet newTrainingSet = new TrainingSet(new RankingExpressionCaseList(analysis.generateArgumentSets(cases),
+ analysis.expression()), new TrainingParameters());
+ Individual winner = new Individual(learntExpression, newTrainingSet);
+ System.out.println("With separate training set: " + winner.toShortString() + " (" + winner.calculateAverageError() + ")");
+ }
+
+ }
+
+ private static class GATraining {
+
+ public GATraining(ExpressionAnalysis analysis) {
+ int skipFeatures = 0;
+ int featureCount = analysis.featureNames().size();
+ int cases = 1000;
+ TrainingParameters parameters = new TrainingParameters();
+ parameters.setInitialSpeciesSize(50);
+ parameters.setSpeciesLifespan(50);
+ //parameters.setAllowConditions(false); // disallow non-smooth functions
+ parameters.setMaxExpressionDepth(8);
+ TrainingSet trainingSet = new TrainingSet(new RankingExpressionCaseList(analysis.generateArgumentSets(cases),
+ analysis.expression()), parameters);
+ Trainer trainer = new Trainer(trainingSet, new HashSet<>(analysis.featureNames().subList(skipFeatures, featureCount)));
+
+ System.out.println("Learning ...");
+ RankingExpression learntExpression = trainer.train(parameters, new PrintingTracker(100, 0, 1));
+ System.out.println("Learnt expression: " + learntExpression);
+
+ // Check for overtraining
+ new LearntExpressionAnalysis(analysis, learntExpression);
+ }
+
+ }
+
+ private static void error(String message) {
+ System.err.println(message);
+ System.exit(1);
+ }
+
+ public abstract static class Feature implements Comparable<Feature> {
+
+ private final String name;
+
+ protected Feature(String name) {
+ this.name = name;
+ }
+
+ public String name() { return name; }
+
+ /** Primary sort by type, secondary by name */
+ @Override
+ public int compareTo(Feature other) {
+ int typeComparison = this.getClass().getName().compareTo(other.getClass().getName());
+ if (typeComparison != 0) return typeComparison;
+ return this.name.compareTo(other.name);
+ }
+
+ }
+
+ /** A feature used in comparisons. These are the ones on which our serious analysis is focused */
+ public static class ComparisonFeature extends Feature {
+
+ private double lowerBound = Double.MAX_VALUE;
+ private double upperBound = Double.MIN_VALUE;
+
+ /** The number of usages of this feature */
+ private int usages = 0;
+
+ /** The sum of the tree numbers where this is accessed */
+ private int treeNumberSum = 0;
+
+ /**
+ * The net times where the left values are smaller than the right values for this
+ * (which is a measure of correlation between input and output because the comparison is &lt;)
+ */
+ private int correlationCount = 0;
+
+ /**
+ * The sum difference in returned value between choosing the right and left branch due to this feature
+ */
+ private double netSum = 0;
+
+ public ComparisonFeature(String name) {
+ super(name);
+ }
+
+ public double lowerBound() { return lowerBound; }
+ public double upperBound() { return upperBound; }
+
+ public void isComparedTo(double value, int inTreeNumber, double leftAverage, double rightAverage) {
+ lowerBound = Math.min(lowerBound, value);
+ upperBound = Math.max(upperBound, value);
+ usages++;
+ treeNumberSum += inTreeNumber;
+ correlationCount += leftAverage < rightAverage ? 1 : -1;
+ netSum += rightAverage - leftAverage;
+ }
+
+ /** Override to do secondary sort by usages */
+ public int compareTo(Feature o) {
+ if ( ! (o instanceof ComparisonFeature)) return super.compareTo(o);
+ ComparisonFeature other = (ComparisonFeature)o;
+ return - Integer.compare(this.usages, other.usages);
+ }
+
+ @Override
+ public String toString() {
+ return "Numeric feature: " + name() +
+ ": range [" + lowerBound + ", " + upperBound + "]" +
+ ", usages " + usages +
+ ", average tree occurrence " + (treeNumberSum / usages) +
+ ", correlation: " + (correlationCount / (double)usages) +
+ ", net contribution: " + netSum;
+ }
+
+ }
+
+ /** A feature used in set membership tests */
+ public static class SetMembershipFeature extends Feature {
+
+ public SetMembershipFeature(String name) {
+ super(name);
+ }
+
+ @Override
+ public String toString() {
+ return "Categorical feature: " + name();
+ }
+
+ }
+
+ /** A sum which can returns its average */
+ private static class Sum {
+
+ private double sum;
+ private int count;
+
+ public void add(double value) {
+ sum+=value;
+ count++;
+ }
+
+ public double average() {
+ return sum / count;
+ }
+
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/package-info.java b/searchlib/src/main/java/com/yahoo/searchlib/package-info.java
new file mode 100644
index 00000000000..63343d425b6
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.searchlib;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/ElementCompleteness.java b/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/ElementCompleteness.java
new file mode 100644
index 00000000000..fb74fb4de6b
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/ElementCompleteness.java
@@ -0,0 +1,96 @@
+// 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 com.google.common.annotations.Beta;
+import com.yahoo.searchlib.rankingexpression.evaluation.DoubleValue;
+import com.yahoo.searchlib.rankingexpression.evaluation.Value;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Calculates the elementCompleteness features
+ *
+ * @author bratseth
+ */
+public class ElementCompleteness {
+
+ /** Hardcoded to default for now */
+ private static final double fieldCompletenessImportance = 0.05;
+
+ /**
+ * Computes the following elementCompleteness features:
+ * <ul>
+ * <li><code>completeness</code>
+ * <li><code>fieldCompleteness</code>
+ * <li><code>queryCompleteness</code>
+ * <li><code>elementWeight</code>
+ * </ul>
+ *
+ * @param queryTerms the query terms with associated weights to compute over
+ * @param field a set of weighted field values, where each is taken to be a space-separated string of tokens
+ * @return a features object containing the values listed above
+ */
+ public static Features compute(Map<String, Integer> queryTerms, Item[] field) {
+ double completeness = 0;
+ double fieldCompleteness = 0;
+ double queryCompleteness = 0;
+ double elementWeight = 0;
+
+ double queryTermWeightSum = sum(queryTerms.values());
+
+ for (Item item : field) {
+ String[] itemTokens =item.value().split(" ");
+ int matchCount = 0;
+ int matchWeightSum = 0;
+ for (String token : itemTokens) {
+ Integer weight = queryTerms.get(token);
+ if (weight == null) continue;
+ matchCount++;
+ matchWeightSum += weight;
+ }
+ double itemFieldCompleteness = (double)matchCount / itemTokens.length;
+ double itemQueryCompleteness = matchWeightSum / queryTermWeightSum;
+ double itemCompleteness =
+ fieldCompletenessImportance * itemFieldCompleteness +
+ (1 - fieldCompletenessImportance) * itemQueryCompleteness;
+ if (itemCompleteness > completeness) {
+ completeness = itemCompleteness;
+ fieldCompleteness = itemFieldCompleteness;
+ queryCompleteness = itemQueryCompleteness;
+ elementWeight = item.weight();
+ }
+ }
+
+ Map<String, Value> features = new HashMap<>();
+ features.put("completeness", new DoubleValue(completeness));
+ features.put("fieldCompleteness", new DoubleValue(fieldCompleteness));
+ features.put("queryCompleteness", new DoubleValue(queryCompleteness));
+ features.put("elementWeight", new DoubleValue(elementWeight));
+ return new Features(features);
+ }
+
+ private static int sum(Collection<Integer> integers) {
+ int sum = 0;
+ for (int integer : integers)
+ sum += integer;
+ return sum;
+ }
+
+ public static class Item {
+
+ private final String value;
+ private final double weight;
+
+ public Item(String value, double weight) {
+ this.value = value;
+ this.weight = weight;
+ }
+
+ public String value() { return value; }
+ public double weight() { return weight; }
+
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/Features.java b/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/Features.java
new file mode 100644
index 00000000000..9dac3db11c8
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/Features.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 com.google.common.annotations.Beta;
+import com.yahoo.searchlib.rankingexpression.evaluation.Value;
+
+import java.util.Collections;
+import java.util.Map;
+
+/**
+ * A set of (immutable) computed features
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+@Beta
+public class Features {
+
+ private Map<String, Value> features;
+
+ /** Creates a set of features by assigning ownership of map of features to this */
+ Features(Map<String, Value> features) {
+ this.features = Collections.unmodifiableMap(features);
+ }
+
+ /** Returns the Value of a feature, or null if it is not present in this */
+ public Value get(String featureName) {
+ return features.get(featureName);
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/FieldTermMatch.java b/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/FieldTermMatch.java
new file mode 100644
index 00000000000..e5b4a899844
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/FieldTermMatch.java
@@ -0,0 +1,48 @@
+// 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 com.google.common.annotations.Beta;
+import com.yahoo.searchlib.rankingexpression.evaluation.DoubleValue;
+import com.yahoo.searchlib.rankingexpression.evaluation.Value;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Calculates the fieldTermMatch features
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+@Beta
+public class FieldTermMatch {
+
+ /**
+ * Computes the fieldTermMatch features:
+ * <ul>
+ * <li><code>firstPosition</code> - the position of the first occurrence of this query term in this index field</li>
+ * <li><code>occurrences</code> - the position of the first occurrence of this query term in this index field</li>
+ * </ul>
+ * @param queryTerm the term to return these features for
+ * @param field the field value to compute over, assumed to be a space-separated string of tokens
+ * @return a features object containing the two values described above
+ */
+ public static Features compute(String queryTerm, String field) {
+ Map<String, Value> features = new HashMap<>();
+
+ String[] tokens = field.split(" ");
+
+ int occurrences = 0;
+ int firstPosition = 1000000;
+ for (int i = 0; i < tokens.length; i++) {
+ if (tokens[i].equals(queryTerm)) {
+ if (occurrences == 0)
+ firstPosition = i;
+ occurrences++;
+ }
+ }
+ features.put("firstPosition", new DoubleValue(firstPosition));
+ features.put("occurrences", new DoubleValue(occurrences));
+ return new Features(features);
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/Field.java b/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/Field.java
new file mode 100644
index 00000000000..b71eff8ffde
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/Field.java
@@ -0,0 +1,60 @@
+// 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.google.common.collect.ImmutableList;
+
+import java.util.List;
+
+/**
+ * Represents a document field which can be matched and ranked against.
+ *
+ * @author bratseth
+ */
+public class Field {
+
+ private final ImmutableList<Term> terms;
+
+ /** Creates a field from a space-separated string */
+ public Field(String fieldString) {
+ ImmutableList.Builder<Term> list = new ImmutableList.Builder<>();
+ for (String term : fieldString.split(" "))
+ list.add(new Term(term));
+ this.terms = list.build();
+ }
+
+ /** Creates a field from a list of terms */
+ public Field(List<Term> terms) {
+ this.terms = ImmutableList.copyOf(terms);
+ }
+
+ /** Returns an immutable list of the terms in this */
+ public List<Term> terms() { return terms; }
+
+ /** A term in a field */
+ public static class Term {
+
+ private final String value;
+ private final float exactness;
+
+ /** Creates a term with the given value and full exactness (1.0) */
+ public Term(String value) {
+ this(value, 1.0f);
+ }
+
+ public Term(String value, float exactness) {
+ this.value = value;
+ this.exactness = exactness;
+ }
+
+ /** Returns the string value of this term */
+ public String value() { return value; }
+
+ /**
+ * Returns the degree to which this term is exactly what was in the document (1.0),
+ * or some stemmed form (closer to 0)
+ */
+ public float exactness() { return exactness; }
+
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/FieldMatchMetrics.java b/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/FieldMatchMetrics.java
new file mode 100644
index 00000000000..77083d4edb4
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/FieldMatchMetrics.java
@@ -0,0 +1,536 @@
+// 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 java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static java.lang.Math.*;
+
+/**
+ * The collection of metrics calculated by the string match metric calculator.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public final class FieldMatchMetrics implements Cloneable {
+
+ /** The calculator creating this - given on initialization */
+ private FieldMatchMetricsComputer source;
+
+ /** The trace accumulated during execution - empty if no tracing */
+ private final Trace trace=new Trace();
+
+ private boolean complete=false;
+
+ // Metrics
+ private int outOfOrder;
+ private int segments;
+ private int gaps;
+ private int gapLength;
+ private int longestSequence;
+ private int head;
+ private int tail;
+ private int matches;
+ private float proximity;
+ private float unweightedProximity;
+ private float segmentDistance;
+ private int pairs;
+ private float weight;
+ private float significance;
+ private float occurrence;
+ private float weightedOccurrence;
+ private float absoluteOccurrence;
+ private float weightedAbsoluteOccurrence;
+ private float significantOccurrence;
+ private float weightedExactnessSum;
+ private int weightSum;
+
+ // Temporary variables
+ private int currentSequence;
+ private List<Integer> segmentStarts=new ArrayList<>();
+ private int queryLength;
+
+ public FieldMatchMetrics(FieldMatchMetricsComputer source) {
+ this.source=source;
+
+ complete=false;
+
+ outOfOrder = 0;
+ segments = 0;
+ gaps = 0;
+ gapLength = 0;
+ longestSequence = 1;
+ head = -1;
+ tail = -1;
+ proximity = 0;
+ unweightedProximity = 0;
+ segmentDistance = 0;
+ matches = 0;
+ pairs = 0;
+ weight = 0;
+ significance = 0;
+ weightedExactnessSum = 0;
+ weightSum = 0;
+
+ currentSequence=0;
+ segmentStarts.clear();
+ queryLength=source.getQuery().getTerms().length;
+ }
+
+ /** Are these metrics representing a complete match */
+ public boolean isComplete() { return complete; }
+
+ public void setComplete(boolean complete) { this.complete=complete; }
+
+ /** Returns the segment start points */
+ public List<Integer> getSegmentStarts() { return segmentStarts; }
+
+ /**
+ * Returns a metric by name
+ *
+ * @throws IllegalArgumentException if the metric name (case sensitive) is not present
+ */
+ public float get(String name) {
+ try {
+ Method getter=getClass().getMethod("get" + name.substring(0,1).toUpperCase() + name.substring(1));
+ return ((Number)getter.invoke(this)).floatValue();
+ }
+ catch (NoSuchMethodException e) {
+ throw new IllegalArgumentException("No metric named '" + name + "' is known");
+ }
+ catch (Exception e) {
+ throw new RuntimeException("Error getting metric '" + name + "'",e);
+ }
+ }
+
+ // Base metrics ----------------------------------------------------------------------------------------------
+
+ /** Returns the total number of out of order token sequences within field segments */
+ public int getOutOfOrder() { return outOfOrder; }
+
+ /** Returns the number of field text segments which are needed to match the query as completely as possible */
+ public int getSegments() { return segments; }
+
+ /** Returns the total number of position jumps (backward or forward) within document segments */
+ public int getGaps() { return gaps; }
+
+ /** Returns the summed size of all gaps within segments */
+ public int getGapLength() { return gapLength; }
+
+ /** Returns the size of the longest matched continuous, in-order sequence in the document */
+ public int getLongestSequence() { return longestSequence; }
+
+ /** Returns the number of tokens in the field preceding the start of the first matched segment */
+ public int getHead() { return head; }
+
+ /** Returns the number of tokens in the field following the end of the last matched segment */
+ public int getTail() { return tail; }
+
+ /** Returns the number of query terms which was matched in this field */
+ public int getMatches() { return matches; }
+
+ /** Returns the number of in-segment token pairs */
+ public int getPairs() { return pairs; }
+
+ /**
+ * Returns the normalized proximity of the matched terms, weighted by the connectedness of the query terms.
+ * This number is 0.1 if all the matched terms are and have default or lower connectedness, close to 1 if they
+ * are following in sequence and have a high connectedness, and close to 0 if they are far from each other in the
+ * segment or out of order
+ */
+ public float getAbsoluteProximity() {
+ if (pairs <1) return 0.1f;
+
+ return proximity/pairs;
+ }
+
+ /**
+ * Returns the normalized proximity of the matched terms, not taking term connectedness into account.
+ * This number is close to 1 if all the matched terms are
+ * following each other in sequence, and close to 0 if they are far from each other or out of order
+ */
+ public float getUnweightedProximity() {
+ if (pairs <1) return 1f;
+ return unweightedProximity/pairs;
+ }
+
+ /**
+ * Returns the sum of the distance between all segments making up a match to the query, measured
+ * as the sum of the number of token positions separating the <i>start</i> of each field adjacent segment.
+ */
+ public float getSegmentDistance() { return segmentDistance; }
+
+ /**
+ * <p>Returns the normalized weight of this match relative to the whole query:
+ * The sum of the weights of all <i>matched</i> terms/the sum of the weights of all <i>query</i> terms
+ * If all the query terms were matched, this is 1. If no terms were matched, or these matches has weight zero,
+ * this is 0.</p>
+ *
+ * <p>As the sum of this number over all the terms of the query is always 1, sums over all fields of
+ * normalized rank features for each field multiplied by this number for the same field will produce a
+ * normalized number.</p>
+ *
+ * <p>Note that this scales with the number of matched query terms in the field. If you want a component which does
+ * not, divide by matches.</p>
+ */
+ public float getWeight() { return weight; }
+
+ /**
+ * <p>Returns the normalized term significance (1-frequency) of the terms of this match relative to the whole query:
+ * The sum of the significance of all <i>matched</i> terms/the sum of the significance of all <i>query</i> terms
+ * If all the query terms were matched, this is 1. If no terms were matched, or if the significance of all the matched terms
+ * is zero (they are present in all (possible) documents), this number is zero.</p>
+ *
+ * <p>As the sum of this number over all the terms of the query is always 1, sums over all fields of
+ * normalized rank features for each field multiplied by this number for the same field will produce a
+ * normalized number.</p>
+ *
+ * <p>Note that this scales with the number of matched query terms in the field. If you want a component which does
+ * not, divide by matches.</p>
+ */
+ public float getSignificance() { return significance; }
+
+ /**
+ * <p>Returns a normalized measure of the number of occurrence of the terms of the query.
+ * This number is 1 if there are many occurrences of the query terms <i>in absolute terms,
+ * or relative to the total content of the field</i>, and 0 if there are none.</p>
+ *
+ * <p>This is suitable for occurrence in fields containing regular text.</p>
+ */
+ public float getOccurrence() { return occurrence; }
+
+ /**
+ * <p>Returns a normalized measure of the number of occurrence of the terms of the query:
+ *
+ * <code>sum over all query terms(min(number of occurrences of the term,maxOccurrences))/(query term count*100)</code>
+ *
+ * <p>This number is 1 if there are many occurrences of the query terms, and 0 if there are none.
+ * This number does not take the actual length of the field into account, so it is suitable for uses of occurrence
+ * to denote importance across multiple terms.</p>
+ */
+ public float getAbsoluteOccurrence() { return absoluteOccurrence; }
+
+ /**
+ * <p>Returns a normalized measure of the number of occurrence of the terms of the query, weighted by term weight.
+ * This number is close to 1 if there are many occurrences of highly weighted query terms,
+ * in absolute terms, or relative to the total content of the field, and 0 if there are none.</p>
+ */
+ public float getWeightedOccurrence() { return weightedOccurrence; }
+
+ /**
+ * <p>Returns a normalized measure of the number of occurrence of the terms of the query, taking weights
+ * into account so that occurrences of higher weighted query terms has more impact than lower weighted terms.</p>
+ *
+ * <p>This number is 1 if there are many occurrences of the highly weighted terms, and 0 if there are none.
+ * This number does not take the actual length of the field into account, so it is suitable for uses of occurrence
+ * to denote importance across multiple terms.</p>
+ */
+ public float getWeightedAbsoluteOccurrence() { return weightedAbsoluteOccurrence; }
+
+ /**
+ * <p>Returns a normalized measure of the number of occurrence of the terms of the query
+ * <i>in absolute terms,
+ * or relative to the total content of the field</i>, weighted by term significance.
+ *
+ * <p>This number is 1 if there are many occurrences of the highly significant terms, and 0 if there are none.</p>
+ */
+ public float getSignificantOccurrence() { return significantOccurrence; }
+
+ /**
+ * <p>Returns the degree to which the query terms submitted matched exactly terms contained in the document.
+ * This is 1 if all the terms matched exactly, and closer to 0 as more of the terms was matched only as stem forms.
+ * </p>
+ *
+ * <p>This is the query term weighted average of the exactness of each match, where the exactness of a match is
+ * the product of the exactness of the matching query term and the matching field term:
+ * <code>
+ * sum over matching query terms(query term weight * query term exactness * field term exactness) /
+ * sum over matching query terms(query term weight)
+ * </code>
+ */
+ public float getExactness() {
+ if (matches == 0) return 0;
+ return weightedExactnessSum / weightSum;
+ }
+
+ // Derived metrics ----------------------------------------------------------------------------------------------
+
+ /** The ratio of query tokens which was matched in the field: <code>matches/queryLength</code> */
+ public float getQueryCompleteness() {
+ return (float)matches/source.getQuery().getTerms().length;
+ }
+
+ /** The ratio of query tokens which was matched in the field: <code>matches/fieldLength</code> */
+ public float getFieldCompleteness() {
+ return (float)matches/source.getField().terms().size();
+ }
+
+ /**
+ * Total completeness, where field completeness is more important:
+ * <code>queryCompleteness * ( 1 - fieldCompletenessImportance) + fieldCompletenessImportance * fieldCompleteness</code>
+ */
+ public float getCompleteness() {
+ float fieldCompletenessImportance=source.getParameters().getFieldCompletenessImportance();
+ return getQueryCompleteness() * ( 1 - fieldCompletenessImportance) + fieldCompletenessImportance*getFieldCompleteness();
+ }
+
+ /** Returns how well the order of the terms agreed in segments: <code>1-outOfOrder/pairs</code> */
+ public float getOrderness() {
+ if (pairs ==0) return 1f;
+ return 1-(float)outOfOrder/pairs;
+ }
+
+ /** Returns the degree to which different terms are related (occurring in the same segment): <code>1-segments/(matches-1)</code> */
+ public float getRelatedness() {
+ if (matches==0) return 0;
+ if (matches==1) return 1;
+ return 1-(float)(segments-1)/(matches-1);
+ }
+
+ /** Returns <code>longestSequence/matches</code> */
+ public float getLongestSequenceRatio() {
+ if (matches==0) return 0;
+ return (float)longestSequence/matches;
+ }
+
+ /** Returns the closeness of the segments in the field: <code>1-segmentDistance/fieldLength</code> */
+ public float getSegmentProximity() {
+ if (matches==0) return 0;
+ return 1-(float)segmentDistance/source.getField().terms().size();
+ }
+
+ /**
+ * Returns a value which is close to 1 when matched terms are close and close to zero when they are far apart
+ * in the segment. Relatively more connected terms influence this value more.
+ * This is absoluteProximity/average connectedness.
+ */
+ public float getProximity() {
+ float totalConnectedness=0;
+ for (int i=1; i<queryLength; i++) {
+ totalConnectedness+=Math.max(0.1,source.getQuery().getTerms()[i].getConnectedness());
+ }
+ float averageConnectedness=0.1f;
+ if (queryLength>1)
+ averageConnectedness=totalConnectedness/(queryLength-1);
+ return getAbsoluteProximity()/averageConnectedness;
+ }
+
+ /**
+ * <p>Returns the average of significance and weight.</p>
+ *
+ * <p>As the sum of this number over all the terms of the query is always 1, sums over all fields of
+ * normalized rank features for each field multiplied by this number for the same field will produce a
+ * normalized number.</p>
+ *
+ * <p>Note that this scales with the number of matched query terms in the field. If you want a component which does
+ * not, divide by matches.</p>
+ */
+ public float getImportance() {
+ return (getSignificance() + getWeight()) / 2;
+ }
+
+ /** A normalized measure of how early the first segment occurs in this field: <code>1-head/(max(6,field.length)-1)</code> */
+ public float getEarliness() {
+ if (matches == 0) return 0; // Covers field.length==0 too
+ if (source.getField().terms().size() == 1) return 1;
+ return 1 - (float)head/(max(6, source.getField().terms().size()) - 1);
+ }
+
+ /**
+ * <p>A ready-to-use aggregate match score. Use this if you don't have time to find a better application specific
+ * aggregate score of the fine grained match metrics.</p>
+ *
+ * <p>The current formula is
+ *
+ * <code>
+ * ( proximityCompletenessImportance * (1-relatednessImportance + relatednessImportance*relatedness)
+ * proximity * exactness * completeness^2 + earlinessImportance * earliness + segmentProximityImportance * segmentProximity )
+ * / (proximityCompletenessImportance + earlinessImportance + relatednessImportance)</code>
+ *
+ * but this is subject to change (i.e improvement) at any time.
+ * </p>
+ *
+ *
+ * <p>Weight and significance are not taken into account because this is mean to capture tha quality of the
+ * match in this field, while those measures relate this match to matches in other fields. This number
+ * can be multiplied with those values when combining with other field match scores.</p>
+ */
+ public float getMatch() {
+ float proximityCompletenessImportance = source.getParameters().getProximityCompletenessImportance();
+ float earlinessImportance = source.getParameters().getEarlinessImportance();
+ float relatednessImportance = source.getParameters().getRelatednessImportance();
+ float segmentProximityImportance = source.getParameters().getSegmentProximityImportance();
+ float occurrenceImportance = source.getParameters().getOccurrenceImportance();
+ float scaledRelatedness = 1 - relatednessImportance + relatednessImportance*getRelatedness();
+
+ return ( proximityCompletenessImportance * scaledRelatedness * getProximity() * getExactness() * getCompleteness() * getCompleteness()
+ + earlinessImportance * getEarliness()
+ + segmentProximityImportance * getSegmentProximity()
+ + occurrenceImportance * getOccurrence())
+ / (proximityCompletenessImportance + earlinessImportance + segmentProximityImportance + occurrenceImportance);
+ }
+
+ /**
+ * <p>The metric use to select the best segments during execution of the string match metric algorithm.</p>
+ *
+ * <p>This metric, and any metric it depends on, must be correct each time a segment is completed,
+ * not only when the metrics are complete, because this metric is used to choose segments during calculation.</p>
+ */
+ float getSegmentationScore() {
+ if (segments==0) return 0;
+ return getAbsoluteProximity() * getExactness() / (segments * segments);
+ }
+
+ // Events emitted from the computer while matching strings ----------------------------------------------------
+ // Note that one move in the computer may cause multiple events
+
+ // Events on single positions ----------
+
+ /** Called once for every match */
+ void onMatch(int i, int j) {
+ if (matches>=source.getField().terms().size()) return;
+ matches++;
+ weight += (float)source.getQuery().getTerms()[i].getWeight() / source.getQuery().getTotalTermWeight();
+ significance += source.getQuery().getTerms()[i].getSignificance() / source.getQuery().getTotalSignificance();
+ int queryTermWeight = source.getQuery().getTerms()[i].getWeight();
+ weightedExactnessSum += queryTermWeight * source.getQuery().getTerms()[i].getExactness() * source.getField().terms().get(j).exactness();
+ weightSum += queryTermWeight;
+ }
+
+ /** Called once per sequence, when the sequence starts */
+ void onSequenceStart(int j) {
+ if (head==-1 || j<head)
+ head=j;
+
+ currentSequence=1;
+ }
+
+ /** Called once per sequence when the sequence ends */
+ void onSequenceEnd(int j) {
+ int sequenceTail = source.getField().terms().size() - j - 1;
+ if (tail ==-1 || sequenceTail < tail)
+ tail = sequenceTail;
+
+ if (currentSequence > longestSequence)
+ longestSequence = currentSequence;
+ currentSequence = 0;
+ }
+
+ /** Called once when this value is calculated, before onComplete */
+ void setOccurrence(float occurrence) { this.occurrence=occurrence; }
+
+ /** Called once when this value is calculated, before onComplete */
+ void setWeightedOccurrence(float weightedOccurrence) { this.weightedOccurrence=weightedOccurrence; }
+
+ /** Called once when this value is calculated, before onComplete */
+ void setAbsoluteOccurrence(float absoluteOccurrence) { this.absoluteOccurrence=absoluteOccurrence; }
+
+ /** Called once when this value is calculated, before onComplete */
+ void setWeightedAbsoluteOccurrence(float weightedAbsoluteOccurrence) { this.weightedAbsoluteOccurrence=weightedAbsoluteOccurrence; }
+
+ /** Called once when this value is calculated, before onComplete */
+ void setSignificantOccurrence(float significantOccurrence) { this.significantOccurrence =significantOccurrence; }
+
+ /** Called once when matching is complete */
+ void onComplete() {
+ // segment distance - calculated from sorted segment starts
+ if (segmentStarts.size()<=1) {
+ segmentDistance=0;
+ }
+ else {
+ Collections.sort(segmentStarts);
+ for (int i=1; i<segmentStarts.size(); i++) {
+ segmentDistance+=segmentStarts.get(i)-segmentStarts.get(i-1)+1;
+ }
+ }
+
+ if (head==-1) head=0;
+ if (tail==-1) tail=0;
+ }
+
+ // Events on pairs ----------
+
+ /** Called when <i>any</i> pair is encountered */
+ void onPair(int i, int j, int previousJ) {
+ int distance = j-previousJ-1;
+ if (distance < 0) distance++; // Discontinuity where the two terms are in the same position
+ if (abs(distance) > source.getParameters().getProximityLimit()) return; // Contribution=0
+
+ // We have an in-segment pair
+ float pairProximity = source.getParameters().getProximity(distance + source.getParameters().getProximityLimit());
+
+ unweightedProximity += pairProximity;
+
+ float connectedness = source.getQuery().getTerms()[i].getConnectedness();
+ proximity += pow(pairProximity, connectedness/0.1) * max(0.1, connectedness);
+
+ pairs++;
+ }
+
+ /** Called when an in-sequence pair is encountered */
+ void onInSequence(int i, int j, int previousJ) {
+ currentSequence++;
+ }
+
+ /** Called when a gap (within a sequence) is encountered */
+ void onInSegmentGap(int i, int j, int previousJ) {
+ gaps++;
+ if (j>previousJ) {
+ gapLength+=abs(j-previousJ)-1; // gap length may be 0 if the gap was in the query
+ }
+ else {
+ outOfOrder++;
+ gapLength+=abs(j-previousJ);
+ }
+ }
+
+ /**
+ * Called when a new segment is started
+ *
+ * @param previousJ the end of the previous segment, or -1 if this is the first segment
+ * */
+ void onNewSegment(int i, int j, int previousJ) {
+ segments++;
+ segmentStarts.add(j);
+ }
+
+ @Override
+ public FieldMatchMetrics clone() {
+ try {
+ FieldMatchMetrics clone=(FieldMatchMetrics)super.clone();
+ clone.segmentStarts=new ArrayList<>(segmentStarts);
+ return clone;
+ }
+ catch (CloneNotSupportedException e) {
+ throw new RuntimeException("Programming error",e);
+ }
+ }
+
+ @Override
+ public String toString() {
+ return "Metrics: [match: " + getMatch() + "]";
+ }
+
+ public String toStringDump() {
+ try {
+ StringBuilder b=new StringBuilder();
+ for (Method m : this.getClass().getDeclaredMethods()) {
+ if ( ! m.getName().startsWith("get")) continue;
+ if (m.getReturnType()!=Integer.TYPE && m.getReturnType()!=Float.TYPE) continue;
+ if ( m.getParameterTypes().length!=0 ) continue;
+
+ Object value=m.invoke(this,new Object[0]);
+ b.append(m.getName().substring(3,4).toLowerCase() + m.getName().substring(4) + ": " + value + "\n");
+ }
+ return b.toString();
+ }
+ catch (Exception e) {
+ throw new RuntimeException("Programming error",e);
+ }
+ }
+
+ /** Returns the trace of this computation. This is empty (never null) if tracing is off */
+ public Trace trace() { return trace; }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/FieldMatchMetricsComputer.java b/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/FieldMatchMetricsComputer.java
new file mode 100644
index 00000000000..3fc3780151a
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/FieldMatchMetricsComputer.java
@@ -0,0 +1,433 @@
+// 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 java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * <p>Calculates a set of metrics capturing information about the degree of agreement between a query
+ * and a field string. This algorithm attempts to capture the property of text that very close tokens
+ * are usually part of the same semantic structure, while tokens farther apart are much more loosely related.
+ * The algorithm will locate alternative such regions containing multiple query tokens (segments), do a more
+ * detailed analysis of these segments and choose the ones producing the best overall set of match metrics
+ * (subject to certain resource constraints).</p>
+ *
+ * <p>Such segments are found by looking at query terms in sequence from
+ * left to right and finding matches in the field. All alternative segment start points are explored, and the
+ * segmentation achieving the best overall string match metric score is preferred. Dynamic programming
+ * is used to avoid redoing work on segmentations.</p>
+ *
+ * <p>When a segment start point is found, subsequent tokens from the query are searched in the field
+ * from this starting point in "semantic order". This search order can be defined independently of the
+ * algorithm. The current order searches <i>proximityLimit</i> tokens ahead first, then the same distance backwards
+ * (so if you need to go two steps backwards in the field from the segment starting point, the real distance is -2,
+ * but the "semantic distance" is proximityLimit+2).</p>
+ *
+ * <p>The actual metrics are calculated during execution of this algorithm by the {@link FieldMatchMetrics} class,
+ * by receiving events emitted from the algorithm. Any set of metrics derivable from these events are computable using
+ * this algorithm.</p>
+ *
+ * <p>Terminology:
+ * <ul>
+ * <li><b>Sequence</b> - A set of adjacent matched tokens in the field
+ * <li><b>Segment</b> - A field area containing matches to a continuous section of the query
+ * <li><b>Gap</b> - A chunk of adjacent tokens <i>inside a segment</i> separating two matched characters
+ * <li><b>Semantic distance</b> - A non-continuous distance between tokens in j, where the non-continuousness is
+ * mean to capture the semantic similarity between the query and those tokens.
+ * </ul>
+ *
+ * <p>Notation: A position index in the query is denoted <code>i</code>. A position index in the field is
+ * denoted <code>j</code>.</p>
+ *
+ * <p>This class is not multithread safe, but is reusable across queries for a single thread.</p>
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public final class FieldMatchMetricsComputer {
+
+ private Query query;
+
+ private Field field;
+
+ private final FieldMatchMetricsParameters parameters;
+
+ /** The metrics of the currently explored segmentation */
+ private FieldMatchMetrics metrics;
+
+ /**
+ * Known segment starting points. The array is 0..i, one element per starting point query item i,
+ * and a last element representing the entire query.
+ */
+ private List<SegmentStartPoint> segmentStartPoints=new ArrayList<>();
+
+ /** True to collect trace */
+ private boolean collectTrace;
+
+ private int alternativeSegmentationsTried=0;
+
+ /** Creates a feature computer using default settings */
+ public FieldMatchMetricsComputer() {
+ this(FieldMatchMetricsParameters.defaultParameters());
+ }
+
+ /**
+ * Creates a feature computer with the given parameters.
+ * The parameters are frozen if they were not already, this may cause
+ * validation exceptions to be thrown from this.
+ */
+ public FieldMatchMetricsComputer(FieldMatchMetricsParameters parameters) {
+ this.parameters = parameters;
+ }
+
+ /** Computes the string match metrics from a query and field string. */
+ public FieldMatchMetrics compute(String queryString,String fieldString) {
+ return compute(new Query(queryString), fieldString);
+ }
+
+ /** Computes the string match metrics from a query and field string. */
+ public FieldMatchMetrics compute(Query query, String fieldString) {
+ return compute(query,fieldString,false);
+ }
+
+ /**
+ * Computes the string match metrics from a query and field string.
+ *
+ * @param query the query to compute over
+ * @param fieldString the field value to compute over - tokenized by splitting on space
+ * @param collectTrace true to accumulate trace information in the trace returned with the metrics
+ */
+ public FieldMatchMetrics compute(Query query, String fieldString, boolean collectTrace) {
+ return compute(query, new Field(fieldString), collectTrace);
+ }
+
+ /**
+ * Computes the string match metrics from a query and field.
+ *
+ * @param query the query to compute over
+ * @param field the field value to compute over
+ * @param collectTrace true to accumulate trace information in the trace returned with the metrics
+ */
+ public FieldMatchMetrics compute(Query query, Field field, boolean collectTrace) {
+ // 1. Reset state
+ this.collectTrace = collectTrace;
+ this.query = query;
+ this.field = field;
+ segmentStartPoints.clear();
+ for (int i = 0; i <= query.getTerms().length; i++)
+ segmentStartPoints.add(null);
+ alternativeSegmentationsTried = 0;
+ metrics = new FieldMatchMetrics(this);
+
+ // 2. Compute
+ exploreSegments();
+ return metrics;
+ }
+
+ /** Finds segment candidates and explores them until we have the best segmentation history of the entire query */
+ private void exploreSegments() {
+ if (collectTrace)
+ metrics.trace().add("Calculating matches for\n " + query + "\n " + field + "\n");
+
+ // Create an initial start point
+ SegmentStartPoint segmentStartPoint=new SegmentStartPoint(metrics,this);
+ segmentStartPoints.set(0,segmentStartPoint);
+
+ // Explore segmentations
+ while (segmentStartPoint!=null) {
+ metrics =segmentStartPoint.getMetrics().clone();
+ if (collectTrace)
+ metrics.trace().add("\nLooking for segment from " + segmentStartPoint + "..." + "\n");
+ boolean found=findAlternativeSegmentFrom(segmentStartPoint);
+ if (collectTrace)
+ metrics.trace().add(found ? "...found segment: " + metrics.getSegmentStarts() + " score: " +
+ metrics.getSegmentationScore() : "...no complete and improved segment existed" + "\n");
+ if (!found)
+ segmentStartPoint.setOpen(false);
+ segmentStartPoint=findOpenSegment(segmentStartPoint.getI());
+ }
+
+ metrics=findLastStartPoint().getMetrics(); // these metrics are the final set
+ setOccurrenceCounts(metrics);
+ metrics.onComplete();
+ metrics.setComplete(true);
+ }
+
+ /**
+ * Find correspondences from a segment starting point
+ *
+ * @return true if a segment was found, false if none could be found
+ */
+ private boolean findAlternativeSegmentFrom(SegmentStartPoint segmentStartPoint) {
+ // i: index into the query
+ // j: index into the field
+ int semanticDistanceExplored=segmentStartPoint.getSemanticDistanceExplored();
+ int previousI=-1;
+ int previousJ=segmentStartPoint.getPreviousJ();
+ boolean hasOpenSequence=false;
+ boolean isFirst=true;
+
+ for (int i=segmentStartPoint.getStartI(); i<query.getTerms().length; i++) {
+ int semanticDistance=findClosestInFieldBySemanticDistance(i,previousJ,semanticDistanceExplored);
+ int j=semanticDistanceToFieldIndex(semanticDistance,previousJ);
+
+ if (j==-1 && semanticDistanceExplored>0 && isFirst) {
+ return false; // Segment explored before, and no more matches found
+ }
+
+ if ( hasOpenSequence && ( j==-1 || j!=previousJ+1 ) ) {
+ metrics.onSequenceEnd(previousJ);
+ hasOpenSequence=false;
+ }
+
+ if (isFirst) {
+ if (j!=-1) {
+ segmentStart(i,j,isFirst ? -1 : previousJ);
+ segmentStartPoint.exploredTo(j);
+ isFirst=false;
+ }
+ else {
+ segmentStartPoint.incrementStartI(); // Remember that there are no matches for this i
+ }
+ }
+ else {
+ if (Math.abs(j-previousJ) >= parameters.getProximityLimit()) {
+ segmentEnd(i-1,previousJ);
+ return true;
+ }
+ else if (j!=-1) {
+ inSegment(i,j,previousJ,previousI);
+ }
+ }
+
+ if (j!=-1)
+ metrics.onMatch(i,j);
+
+ if (j!=-1 && !hasOpenSequence) {
+ metrics.onSequenceStart(j);
+ hasOpenSequence=true;
+ }
+
+ if (j!=-1)
+ semanticDistanceExplored=1; // Skip the current match when looking for the next
+ else
+ semanticDistanceExplored=0;
+
+ if (j>=0) {
+ previousI=i;
+ previousJ=j;
+ }
+ }
+
+ if (hasOpenSequence)
+ metrics.onSequenceEnd(previousJ);
+
+ if (!isFirst) {
+ segmentEnd(query.getTerms().length-1,previousJ);
+ return true;
+ }
+ else {
+ return false;
+ }
+ }
+
+ /**
+ * Implements the preferred search order for finding a match to a query item - first
+ * looking close in the right order, then close in the reverse order, then far in the right order
+ * and lastly far in the reverse order.
+ *
+ * @param startSemanticDistance is the semantic distance we must be larger than or equal to
+ * @return the semantic distance of the next mathing j larger than startSemanticDistance, or -1 if
+ * there are no matches larger than startSemanticDistance
+ */
+ private int findClosestInFieldBySemanticDistance(int i,int previousJ,int startSemanticDistance) {
+ String term=query.getTerms()[i].getTerm();
+ for (int distance=startSemanticDistance; distance<field.terms().size(); distance++) {
+ int j=semanticDistanceToFieldIndex(distance,previousJ);
+ if (term.equals(field.terms().get(j).value()))
+ return distance;
+ }
+ return -1;
+ }
+
+ /**
+ * Returns the field index (j) from a starting point zeroJ and the distance form zeroJ in the
+ * semantic distance space
+ *
+ * @return the field index, or -1 (undefined) if the semanticDistance is -1
+ */
+ int semanticDistanceToFieldIndex(int semanticDistance,int zeroJ) {
+ if (semanticDistance == -1) return -1;
+ int firstSegmentLength = Math.min(parameters.getProximityLimit(),field.terms().size() - zeroJ);
+ int secondSegmentLength = Math.min(parameters.getProximityLimit(), zeroJ);
+ if (semanticDistance < firstSegmentLength)
+ return zeroJ + semanticDistance;
+ else if (semanticDistance < firstSegmentLength+secondSegmentLength)
+ return zeroJ - semanticDistance - 1 + firstSegmentLength;
+ else if (semanticDistance < field.terms().size() - zeroJ+secondSegmentLength)
+ return zeroJ + semanticDistance - secondSegmentLength;
+ else
+ return field.terms().size() - semanticDistance - 1;
+ }
+
+ /**
+ * Returns the semantic distance from a starting point zeroJ to a field index j
+ *
+ * @return the semantic distance, or -1 (undefined) if j is -1
+ */
+ int fieldIndexToSemanticDistance(int j,int zeroJ) {
+ if (j == -1) return -1;
+ int firstSegmentLength = Math.min(parameters.getProximityLimit(), field.terms().size() - zeroJ);
+ int secondSegmentLength = Math.min(parameters.getProximityLimit(), zeroJ);
+ if (j >= zeroJ) {
+ if ( (j - zeroJ) < firstSegmentLength )
+ return j - zeroJ; // 0..limit
+ else
+ return j - zeroJ+secondSegmentLength; // limit*2..field.length-zeroJ
+ }
+ else {
+ if ( (zeroJ - j - 1) < secondSegmentLength )
+ return zeroJ - j + firstSegmentLength-1; // limit..limit*2
+ else
+ return (zeroJ - j - 1) + field.terms().size() - zeroJ; // field.length-zeroJ..
+ }
+
+ }
+
+ private void inSegment(int i, int j, int previousJ, int previousI) {
+ metrics.onPair(i, j, previousJ);
+ if (j==previousJ+1 && i==previousI+1) {
+ metrics.onInSequence(i, j, previousJ);
+ }
+ else {
+ metrics.onInSegmentGap(i, j, previousJ);
+ if (collectTrace)
+ metrics.trace().add(" in segment gap: " + i + "->" + j + " (" + query.getTerms()[i] + ")" + "\n");
+ }
+ }
+
+ /** Returns whether this segment was accepted as a starting point */
+ private boolean segmentStart(int i,int j,int previousJ) {
+ metrics.onNewSegment(i, j, previousJ);
+
+ if (previousJ>=0)
+ metrics.onPair(i,j,previousJ);
+
+ if (collectTrace)
+ metrics.trace().add(" new segment at: " + i + "->" + j + " (" + query.getTerms()[i] + ")" + "\n");
+ return true;
+ }
+
+ /**
+ * Registers an end of a segment
+ *
+ * @param i the i at which this segment ends
+ * @param j the j at which this segment ends
+ */
+ private void segmentEnd(int i,int j) {
+ if (collectTrace)
+ metrics.trace().add(" segment ended at: " + i + "->" + j + " (" + query.getTerms()[i] + ")" + "\n");
+ SegmentStartPoint startOfNext=segmentStartPoints.get(i + 1);
+ if (startOfNext==null)
+ segmentStartPoints.set(i+1,new SegmentStartPoint(i+1,j, metrics,this));
+ else
+ startOfNext.offerHistory(j, metrics, collectTrace);
+ }
+
+ /** Returns the next open segment to explore, or null if no more segments exists or should be explored */
+ private SegmentStartPoint findOpenSegment(int startI) {
+ for (int i=startI; i<segmentStartPoints.size(); i++) {
+ SegmentStartPoint startPoint=segmentStartPoints.get(i);
+ if (startPoint==null || !startPoint.isOpen()) continue;
+
+ if (startPoint.getSemanticDistanceExplored()==0) return startPoint; // First attempt
+
+ if (alternativeSegmentationsTried>=parameters.getMaxAlternativeSegmentations()) continue;
+ alternativeSegmentationsTried++;
+ return startPoint;
+ }
+
+ return null;
+ }
+
+ private SegmentStartPoint findLastStartPoint() {
+ for (int i=segmentStartPoints.size()-1; i>=0; i--) {
+ SegmentStartPoint startPoint=segmentStartPoints.get(i);
+ if (startPoint!=null)
+ return startPoint;
+ }
+ return null; // Impossible
+ }
+
+ /** Counts all occurrences of terms of the query in the field and set those metrics */
+ private void setOccurrenceCounts(FieldMatchMetrics metrics) {
+ Set<QueryTerm> uniqueQueryTerms=new HashSet<>();
+ for (QueryTerm queryTerm : query.getTerms())
+ uniqueQueryTerms.add(queryTerm);
+
+ List<Float> weightedOccurrences=new ArrayList<Float>();
+ List<Float> significantOccurrences=new ArrayList<Float>();
+
+ int divider = Math.min(field.terms().size(),parameters.getMaxOccurrences()*uniqueQueryTerms.size());
+ int maxOccurence = Math.min(field.terms().size(),parameters.getMaxOccurrences());
+
+ float occurrence=0;
+ float absoluteOccurrence=0;
+ float weightedAbsoluteOccurrence=0;
+ int totalWeight=0;
+ float totalWeightedOccurrences=0;
+ float totalSignificantOccurrences=0;
+
+ for (QueryTerm queryTerm : uniqueQueryTerms) {
+ int termOccurrences=0;
+ for (Field.Term fieldTerm : field.terms()) {
+ if (fieldTerm.value().equals(queryTerm.getTerm()))
+ termOccurrences++;
+ if (termOccurrences == parameters.getMaxOccurrences()) break;
+ }
+ occurrence+=(float)termOccurrences/divider;
+
+ absoluteOccurrence+=(float)termOccurrences/(parameters.getMaxOccurrences()*uniqueQueryTerms.size());
+
+ weightedAbsoluteOccurrence+=(float)termOccurrences*queryTerm.getWeight()/parameters.getMaxOccurrences();
+ totalWeight+=queryTerm.getWeight();
+
+ totalWeightedOccurrences+=(float)maxOccurence*queryTerm.getWeight()/divider;
+ weightedOccurrences.add((float)termOccurrences*queryTerm.getWeight()/divider);
+
+ totalSignificantOccurrences+=(float)maxOccurence*queryTerm.getSignificance()/divider;
+ significantOccurrences.add((float)termOccurrences*queryTerm.getSignificance()/divider);
+ }
+
+ float weightedOccurrenceSum=0;
+ for (float weightedOccurence : weightedOccurrences)
+ weightedOccurrenceSum+=weightedOccurence/totalWeightedOccurrences;
+
+ float significantOccurrenceSum=0;
+ for (float significantOccurence : significantOccurrences)
+ significantOccurrenceSum+=significantOccurence/totalSignificantOccurrences;
+
+ if (totalWeight>0)
+ weightedAbsoluteOccurrence=weightedAbsoluteOccurrence/totalWeight;
+
+ metrics.setOccurrence(occurrence);
+ metrics.setAbsoluteOccurrence(absoluteOccurrence);
+ metrics.setWeightedOccurrence(weightedOccurrenceSum);
+ metrics.setWeightedAbsoluteOccurrence(weightedAbsoluteOccurrence);
+ metrics.setSignificantOccurrence(significantOccurrenceSum);
+ }
+
+ /** Returns the parameter settings of this */
+ public FieldMatchMetricsParameters getParameters() { return parameters; }
+
+ Query getQuery() { return query; }
+
+ Field getField() { return field; }
+
+ @Override
+ public String toString() {
+ return query + "\n" + field + "\n" + metrics + "\n";
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/FieldMatchMetricsParameters.java b/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/FieldMatchMetricsParameters.java
new file mode 100644
index 00000000000..4ab8565a285
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/FieldMatchMetricsParameters.java
@@ -0,0 +1,198 @@
+// 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;
+
+/**
+ * The parameters to a string match metric calculator.
+ * Mutable until frozen.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public final class FieldMatchMetricsParameters {
+
+ private boolean frozen=false;
+
+ private int proximityLimit=10;
+
+ private int maxAlternativeSegmentations = 10000;
+
+ private int maxOccurrences=100;
+
+ private float proximityCompletenessImportance =0.9f;
+
+ private float relatednessImportance =0.9f;
+
+ private float earlinessImportance =0.05f;
+
+ private float segmentProximityImportance =0.05f;
+
+ private float occurrenceImportance =0.05f;
+
+ private float fieldCompletenessImportance =0.05f;
+
+ private float[] proximityTable= new float[] { 0.01f, 0.02f, 0.03f, 0.04f, 0.06f, 0.08f, 0.12f, 0.17f, 0.24f, 0.33f, 1,
+ 0.71f, 0.50f, 0.35f, 0.25f, 0.18f, 0.13f, 0.09f, 0.06f, 0.04f, 0.03f };
+
+ /* Calculation of the table above:
+ static {
+ System.out.println("Right order");
+ for (float i=0; i<=10; i++)
+ System.out.println(1/Math.pow(2,i/2));
+
+ System.out.println("Reverse order");
+ for (float i=0; i<=10; i++)
+ System.out.println(1/Math.pow(2,i/2)/3);
+ }
+ */
+
+ private static FieldMatchMetricsParameters defaultParameters;
+
+ static {
+ defaultParameters=new FieldMatchMetricsParameters();
+ defaultParameters.freeze();
+ }
+
+ /** Returns the frozen default parameters */
+ public static FieldMatchMetricsParameters defaultParameters() {
+ return defaultParameters;
+ }
+
+ /** Creates an unfrozen marcg metrics object initialized to the default values */
+ public FieldMatchMetricsParameters() { }
+
+ /** Sets the maximum allowed gap within a segment. Default: 10 */
+ public void setProximityLimit(int proximityLimit) {
+ ensureNotFrozen();
+ this.proximityLimit=proximityLimit;
+ }
+
+ /** Returns the maximum allowed gap within a segment. Default: 10 */
+ public int getProximityLimit() { return proximityLimit; }
+
+ /**
+ * Sets the proximity table deciding the importance of separations of various distances,
+ * The table must have size proximityLimit*2+1, where the first half is for reverse direction
+ * distances. The table must only contain values between 0 and 1, where 1 is "perfect" and 0 is "worst".
+ */
+ public void setProximityTable(float[] proximityTable) {
+ ensureNotFrozen();
+ this.proximityTable=proximityTable;
+ }
+
+ /**
+ * Returns the current proxmity table.
+ * The default table is calculated by
+ * <code>1/2^(n/2)</code> on the right order side, and
+ * <code>1/2^(n/2) /3</code> on the reverse order side
+ * where n is the distance between the tokens.
+ */
+ public float[] getProximityTable() { return proximityTable; }
+
+ /** Returns the proximity table value at an index */
+ public float getProximity(int index) { return proximityTable[index]; }
+
+ /**
+ * Returns the maximal number of <i>alternative</i> segmentations allowed in addition to the first one found.
+ * Default is 10000. This will prefer to not consider iterations on segments that are far out in the field,
+ * and which starts late in the query.
+ */
+ public int getMaxAlternativeSegmentations() { return maxAlternativeSegmentations; }
+
+ public void setMaxAlternativeSegmentations(int maxAlternativeSegmentations) {
+ ensureNotFrozen();
+ this.maxAlternativeSegmentations = maxAlternativeSegmentations;
+ }
+
+ /**
+ * Returns the number of occurrences the number of occurrences of each word is normalized against.
+ * This should be set as the number above which additional occurrences of the term has no real significance.
+ * The default is 100.
+ */
+ public int getMaxOccurrences() { return maxOccurrences; }
+
+ public void setMaxOccurrences(int maxOccurrences) { this.maxOccurrences=maxOccurrences; }
+
+ /**
+ * Returns a number between 0 and 1 which determines the importancy of field completeness in relation to
+ * query completeness in the <code>match</code> and <code>completeness</code> metrics. Default is 0.05
+ */
+ public float getFieldCompletenessImportance() { return fieldCompletenessImportance; }
+
+ public void setFieldCompletenessImportance(float fieldCompletenessImportance) {
+ ensureNotFrozen();
+ this.fieldCompletenessImportance = fieldCompletenessImportance;
+ }
+
+ /**
+ * Returns the importance of the match having high proximity and being complete, relative to segmentProximityImportance,
+ * occurrenceImportance and earlinessImportance in the <code>match</code> metric. Default: 0.9
+ */
+ public float getProximityCompletenessImportance() { return proximityCompletenessImportance; }
+
+ public void setProximityCompletenessImportance(float proximityCompletenessImportance) {
+ ensureNotFrozen();
+ this.proximityCompletenessImportance = proximityCompletenessImportance;
+ }
+
+ /**
+ * Returns the importance of the match occuring early in the query, relative to segmentProximityImportance,
+ * occurrenceImportance and proximityCompletenessImportance in the <code>match</code> metric. Default: 0.05
+ */
+ public float getEarlinessImportance() { return earlinessImportance; }
+
+ public void setEarlinessImportance(float earlinessImportance) {
+ ensureNotFrozen();
+ this.earlinessImportance = earlinessImportance;
+ }
+
+ /**
+ * Returns the importance of multiple segments being close to each other, relative to earlinessImportance,
+ * occurrenceImportance and proximityCompletenessImportance in the <code>match</code> metric. Default: 0.05
+ */
+ public float getSegmentProximityImportance() { return segmentProximityImportance; }
+
+ public void setSegmentProximityImportance(float segmentProximityImportance) {
+ ensureNotFrozen();
+ this.segmentProximityImportance = segmentProximityImportance;
+ }
+
+ /**
+ * Returns the importance of having many occurrences of the query terms, relative to earlinessImportance,
+ * segmentProximityImportance and proximityCompletenessImportance in the <code>match</code> metric. Default: 0.05
+ */
+ public float getOccurrenceImportance() { return occurrenceImportance; }
+
+ public void setOccurrenceImportance(float occurrenceImportance) {
+ ensureNotFrozen();
+ this.occurrenceImportance = occurrenceImportance;
+ }
+
+ /** Returns the normalized importance of relatedness used in the <code>match</code> metric. Default: 0.9 */
+ public float getRelatednessImportance() { return relatednessImportance; }
+
+ public void setRelatednessImportance(float relatednessImportance) {
+ ensureNotFrozen();
+ this.relatednessImportance = relatednessImportance;
+ }
+
+
+ /** Throws IllegalStateException if this is frozen. Does nothing otherwise */
+ private void ensureNotFrozen() {
+ if (frozen)
+ throw new IllegalStateException(this + " is frozen");
+ }
+
+ /**
+ * Freezes this object. All changes after this point will cause an IllegalStateException.
+ * This must be frozen before being handed to a calculator.
+ *
+ * @throws IllegalStateException if this parameter object is inconsistent. In this case, this is not frozen.
+ */
+ public void freeze() {
+ if (proximityTable.length!=proximityLimit*2+1)
+ throw new IllegalStateException("Proximity table length is " + proximityTable.length + ". Must be " +
+ (proximityLimit*2+1) +
+ " (proximityLimit*2+1), because the proximity limit is " + proximityLimit);
+ frozen=true;
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/Main.java b/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/Main.java
new file mode 100644
index 00000000000..f101448a3dd
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/Main.java
@@ -0,0 +1,39 @@
+// 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;
+
+/**
+ * Helper for computing metrics from the command line.
+ */
+public class Main {
+
+ public static void main(String[] args) {
+ FieldMatchMetricsComputer c=new FieldMatchMetricsComputer();
+ String query=getQuery(args);
+ String field=getField(args);
+ if (query==null || field==null) {
+ printUsage();
+ return;
+ }
+
+ FieldMatchMetrics metrics = c.compute(query,field);
+ System.out.println(metrics.toStringDump());
+ }
+
+ private static String getQuery(String[] args) {
+ if (args.length<1) return null;
+ if (args[0].equals("-h") || args[0].equals("-help")) return null;
+ return args[0];
+ }
+
+ private static String getField(String[] args) {
+ if (args.length<2) return null;
+ return args[1];
+ }
+
+ private static void printUsage() {
+ System.out.println("Computes the string segment match metrics of a query and field.");
+ System.out.println("Usage: java -jar searchlib.jar query field");
+ System.out.println("By: Jon Bratseth (bratseth@yahoo-inc.com)");
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/Query.java b/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/Query.java
new file mode 100644
index 00000000000..6cd9d651a09
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/Query.java
@@ -0,0 +1,72 @@
+// 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.QueryTerm;
+
+import java.util.Arrays;
+
+/**
+ * A query: An array of the QueryTerms which searches the field we are calculating for,
+ * <p>
+ * In addition the sum of the term weights of <i>all</i> the query terms can be set
+ * explicitly. This allows us to model the matchWeight rank feature of a field as dependent of
+ * the weights of all the terms in the query.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class Query {
+
+ private QueryTerm[] terms;
+
+ private int totalTermWeight=0;
+
+ private float totalSignificance=0;
+
+ public Query(String query) {
+ this(splitQuery(query));
+ }
+
+ /** Creates a query with a list of query terms. The query terms are not, and must not be subsequently modified */
+ public Query(QueryTerm[] terms) {
+ this.terms=terms;
+
+ for (QueryTerm term : terms) {
+ totalTermWeight+=term.getWeight();
+ totalSignificance+=term.getSignificance();
+ }
+ }
+
+ private static QueryTerm[] splitQuery(String queryString) {
+ String[] queryTerms=queryString.split(" ");
+ QueryTerm[] query=new QueryTerm[queryTerms.length];
+ for (int i=0; i<query.length; i++)
+ query[i]=new QueryTerm(queryTerms[i]);
+ return query;
+ }
+
+ /** Returns the query terms we are calculating features of */
+ public QueryTerm[] getTerms() { return terms; }
+
+ /**
+ * Returns the total term weight for this query.
+ * This is the sum of the weights of the terms if not set explicitly, or if set explicitly a higher
+ * number which also models a query which also has terms going to other indexes.
+ */
+ public int getTotalTermWeight() { return totalTermWeight; }
+
+ public void setTotalTermWeight(int totalTermWeight) { this.totalTermWeight=totalTermWeight; }
+
+ /**
+ * Returns the total term significance for this query.
+ * This is the sum of the significance of the terms if not set explicitly, or if set explicitly a higher
+ * number which also models a query which also has terms going to other indexes.
+ */
+ public float getTotalSignificance() { return totalSignificance; }
+
+ public void setTotalSignificance(float totalSignificance) { this.totalSignificance=totalSignificance; }
+
+ public String toString() {
+ return "query: " + Arrays.toString(terms);
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/QueryTerm.java b/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/QueryTerm.java
new file mode 100644
index 00000000000..803aaf52964
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/QueryTerm.java
@@ -0,0 +1,67 @@
+// 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;
+
+/**
+ * A query term. Query terms are equal if they have the same term string.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public final class QueryTerm {
+
+ private String term;
+
+ private float connectedness = 0.1f;
+
+ private int weight = 100;
+
+ private float significance = 0.1f;
+
+ private float exactness = 1.0f;
+
+ public QueryTerm(String term) {
+ this.term=term;
+ }
+
+ public QueryTerm(String term,float connectedness) {
+ this.term=term;
+ this.connectedness=connectedness;
+ }
+
+ public void setTerm(String term) { this.term=term; }
+
+ public String getTerm() { return term; }
+
+ /**
+ * Returns how connected this term is to the previous term in the query.
+ * Default: 0.1. This is always a number between 0 (not connected at all) and 1 (virtually inseparable)
+ */
+ public float getConnectedness() { return connectedness; }
+
+ public void setConnectedness(float connectedness) { this.connectedness=connectedness; }
+
+ public void setWeight(int weight) { this.weight=weight; }
+
+ public int getWeight() { return weight; }
+
+ /** The significance of this term: 1-term frequency */
+ public void setSignificance(float significance) { this.significance=significance; }
+
+ public float getSignificance() { return significance; }
+
+ /** The degree to which this is exactly the term the user specified (1), or a stemmed form (closer to 0) */
+ public float getExactness() { return exactness; }
+
+ public @Override int hashCode() { return term.hashCode(); }
+
+ public @Override boolean equals(Object object) {
+ if (! (object instanceof QueryTerm)) return false;
+
+ return this.term.equals(((QueryTerm)object).term);
+ }
+
+ public @Override String toString() {
+ if (connectedness==0.1f) return term;
+ return connectedness + ":" + term;
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/SegmentStartPoint.java b/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/SegmentStartPoint.java
new file mode 100644
index 00000000000..9f6e81a04bc
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/SegmentStartPoint.java
@@ -0,0 +1,145 @@
+// 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;
+
+/**
+ * <p>Information on segment start points stored temporarily during string match metric calculation.</p>
+ *
+ * <p>Given that we want to start a segment at i, this holdes the best known metrics up to i
+ * and the end of the previous segment. In addition it holds information on how far we have tried
+ * to look for alternative segments from this starting point (skipI and previousJ).</p>
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+final class SegmentStartPoint {
+
+ private FieldMatchMetricsComputer owner;
+
+ /** The i for which this is the possible segment starting points */
+ private int i;
+
+ private int skipI;
+
+ /** The best known metrics up to this starting point */
+ private FieldMatchMetrics metrics;
+
+ /** The j ending the previous segmentation producing those best metrics */
+ private int previousJ;
+
+ /** The semantic distance from the current previousJ which is already explored */
+ private int semanticDistanceExplored=0;
+
+ /** There are possibly more j's to try at this starting point */
+ boolean open=true;
+
+ /** Creates a segment start point for the first segment */
+ public SegmentStartPoint(FieldMatchMetrics metrics, FieldMatchMetricsComputer owner) {
+ this.i=0;
+ this.previousJ=0;
+ this.metrics=metrics;
+ this.owner=owner;
+ this.semanticDistanceExplored=0;
+ }
+
+ /** Creates a segment start point for any i position where the j is not known */
+ public SegmentStartPoint(int i,int previousJ,FieldMatchMetrics metrics, FieldMatchMetricsComputer owner) {
+ this.i=i;
+ this.previousJ=previousJ;
+ this.metrics=metrics;
+ this.owner=owner;
+ this.semanticDistanceExplored=0;
+ }
+
+ /** Creates a segment start point for any position, where the j of the start point is known */
+ public SegmentStartPoint(int i,int j,int previousJ,FieldMatchMetrics metrics, FieldMatchMetricsComputer owner) {
+ this.i=i;
+ this.previousJ=previousJ;
+ this.metrics=metrics;
+ this.owner=owner;
+ this.semanticDistanceExplored=owner.fieldIndexToSemanticDistance(j,previousJ)+1;
+ }
+
+ /** Returns the current best metrics for this starting point */
+ public FieldMatchMetrics getMetrics() { return metrics; }
+
+ /**
+ * Stores that we have explored to a certain j from the current previousJ.
+ */
+ public void exploredTo(int j) {
+ semanticDistanceExplored=owner.fieldIndexToSemanticDistance(j,previousJ)+1;
+ }
+
+ /**
+ * Offers an alternative history leading up to this point, which is accepted and stored if it is
+ * better than the current history
+ */
+ public void offerHistory(int offeredPreviousJ,FieldMatchMetrics offeredMetrics,boolean collectTrace) {
+ if (offeredMetrics.getSegmentationScore()<=metrics.getSegmentationScore()) {
+ if (collectTrace)
+ offeredMetrics.trace().add(" rejected offered history [match: " + offeredMetrics.getSegmentationScore() +
+ " ending at:" + previousJ + "] at " + this + "\n");
+ return; // Reject
+ }
+
+ /*
+ if (previousJ!=offeredPreviousJ) { // Starting over like this achieves higher correctness if
+ semanticDistanceExplored=0; // the match metric is dependent on relative distance between segments
+ open=true; // but is more expensive
+ }
+ */
+
+ if (collectTrace)
+ offeredMetrics.trace().add(" accepted offered history [match: " + offeredMetrics.getSegmentationScore() +
+ " ending at:" + previousJ + "] at " + this + "\n");
+
+ previousJ=offeredPreviousJ;
+ metrics=offeredMetrics;
+ }
+
+ /**
+ * Returns whether there are possibly still unexplored j's for this i
+ */
+ public boolean isOpen() { return open; }
+
+ public void setOpen(boolean open) { this.open=open; }
+
+ /** Returns the i for which this is the possible segment starting points */
+ public int getI() { return i; }
+
+ /**
+ * Returns the j ending the previous segmentation producing those best metrics,
+ */
+ public int getPreviousJ() { return previousJ; }
+
+ /**
+ * Returns the semantic distance from the previous j which is explored so far, exclusive
+ * (meaning, if the value is 0, 0 is <i>not</i> explored yet)
+ */
+ public int getSemanticDistanceExplored() { return semanticDistanceExplored; }
+
+ public void setSemanticDistanceExplored(int distance) { this.semanticDistanceExplored=distance; }
+
+ /**
+ * Returns the position startI we should start at from this start point i.
+ * startI==i except when there are i's from this starting point which are not found anywhere in
+ * the field. In that case, startI==i+the number of terms following i which are known not to be present
+ */
+ public int getStartI() {
+ return i+skipI;
+ }
+
+ /**
+ * Increments the startI by one because we have discovered that the term at the current startI is not
+ * present in the field
+ */
+ public void incrementStartI() { skipI++; }
+
+ public String toString() {
+ if (i==owner.getQuery().getTerms().length)
+ return "last segment: Complete match: " + metrics.getMatch() + " previous j: " + previousJ +
+ " (" + (open ? "open" : "closed") + ")";
+ return "segment at " + i + " (" + owner.getQuery().getTerms()[i] + "): Match up to here: " + metrics.getMatch() + " previous j: " +
+ previousJ + " explored to: " + semanticDistanceExplored +
+ " (" + (open ? "open" : "closed") + ")";
+ }
+
+} \ No newline at end of file
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/Trace.java b/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/Trace.java
new file mode 100644
index 00000000000..775c7d1d687
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/Trace.java
@@ -0,0 +1,22 @@
+// 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;
+
+/**
+ * A computation trace
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class Trace {
+
+ private StringBuilder b = new StringBuilder();
+
+ public void add(String s) {
+ b.append(b);
+ }
+
+ @Override
+ public String toString() {
+ return b.toString();
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/package-info.java b/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/package-info.java
new file mode 100644
index 00000000000..c16fbb4521e
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/package-info.java
@@ -0,0 +1,12 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * Reference implementation of the
+ * string segment match algorithm
+ * which creates the fieldMatch feature set.
+ */
+@ExportPackage
+@PublicApi
+package com.yahoo.searchlib.ranking.features.fieldmatch;
+
+import com.yahoo.api.annotations.PublicApi;
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/package-info.java b/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/package-info.java
new file mode 100644
index 00000000000..028bf3337f0
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/package-info.java
@@ -0,0 +1,10 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * Java implementations for various Vespa rank features
+ */
+@ExportPackage
+@PublicApi
+package com.yahoo.searchlib.ranking.features;
+
+import com.yahoo.api.annotations.PublicApi;
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/ExpressionFunction.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/ExpressionFunction.java
new file mode 100755
index 00000000000..86ac53a1e44
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/ExpressionFunction.java
@@ -0,0 +1,139 @@
+// 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.google.common.collect.ImmutableList;
+import com.yahoo.searchlib.rankingexpression.rule.ExpressionNode;
+import com.yahoo.searchlib.rankingexpression.rule.SerializationContext;
+import com.yahoo.text.Utf8;
+
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.*;
+
+/**
+ * <p>A function defined by a ranking expression</p>
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ * @author bratseth
+ */
+public class ExpressionFunction {
+
+ private final String name;
+ private final ImmutableList<String> arguments;
+ private final RankingExpression body;
+
+ /**
+ * <p>Constructs a new function</p>
+ *
+ * @param name the name of this function
+ * @param arguments its argument names
+ * @param body the ranking expression that defines this function
+ */
+ public ExpressionFunction(String name, List<String> arguments, RankingExpression body) {
+ this.name = name;
+ this.arguments = arguments==null ? ImmutableList.of() : ImmutableList.copyOf(arguments);
+ this.body = body;
+ }
+
+ public String getName() { return name; }
+
+ /** Returns an immutable list of the arguments of this */
+ public List<String> arguments() { return arguments; }
+
+ public RankingExpression getBody() { return body; }
+
+ /**
+ * <p>Create and return an instance of this function based on the given
+ * arguments. If function calls are nested, this call might produce
+ * additional scripts.</p>
+ *
+ * @param context the context used to expand this
+ * @param arguments the arguments to instantiate on.
+ * @param path the expansion path leading to this.
+ * @return the script function instance created.
+ */
+ public Instance expand(SerializationContext context, List<ExpressionNode> arguments, Deque<String> path) {
+ Map<String, String> argumentBindings = new HashMap<>();
+ for (int i = 0; i < this.arguments.size() && i < arguments.size(); ++i) {
+ argumentBindings.put(this.arguments.get(i), arguments.get(i).toString(context, path, null));
+ }
+ return new Instance(toSymbol(argumentBindings), body.getRoot().toString(context.createBinding(argumentBindings), path, null));
+ }
+
+ /**
+ * Returns a symbolic string that represents this function with a given
+ * list of arguments. The arguments are mangled by hashing the string
+ * representation of the argument expressions, so we might need to revisit
+ * this if we start seeing collisions.
+ *
+ * @param argumentBindings the bound arguments to include in the symbolic name.
+ * @return the symbolic name for an instance of this function
+ */
+ private String toSymbol(Map<String, String> argumentBindings) {
+ if (argumentBindings.isEmpty()) return name;
+
+ StringBuilder ret = new StringBuilder();
+ ret.append(name).append("@");
+ for (Map.Entry<String,String> argumentBinding : argumentBindings.entrySet()) {
+ ret.append(Long.toHexString(symbolCode(argumentBinding.getKey() + "=" + argumentBinding.getValue())));
+ ret.append(".");
+ }
+ if (ret.toString().endsWith("."))
+ ret.setLength(ret.length()-1);
+ return ret.toString();
+ }
+
+
+ /**
+ * <p>Returns a more unique hash code than what Java's own {@link
+ * String#hashCode()} method would produce.</p>
+ *
+ * @param str The string to hash.
+ * @return A 64 bit long hash code.
+ */
+ private static long symbolCode(String str) {
+ try {
+ MessageDigest md = java.security.MessageDigest.getInstance("SHA-1");
+ byte[] buf = md.digest(Utf8.toBytes(str));
+ if (buf.length >= 8) {
+ long ret = 0;
+ for (int i = 0; i < 8; ++i) {
+ ret = (ret << 8) + (buf[i] & 0xff);
+ }
+ return ret;
+ }
+ } catch (NoSuchAlgorithmException e) {
+ throw new Error("java must always support SHA-1 message digest format", e);
+ }
+ return str.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return name;
+ }
+
+ /**
+ * An instance of a serialization of this function, using a particular serialization context (by {@link
+ * ExpressionFunction#expand})
+ */
+ public class Instance {
+
+ private final String name;
+ private final String expressionString;
+
+ public Instance(String name, String expressionString) {
+ this.name = name;
+ this.expressionString = expressionString;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getExpressionString() {
+ return expressionString;
+ }
+
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/FeatureList.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/FeatureList.java
new file mode 100755
index 00000000000..527a908da73
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/FeatureList.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.rankingexpression;
+
+import com.google.common.annotations.Beta;
+import com.yahoo.searchlib.rankingexpression.parser.ParseException;
+import com.yahoo.searchlib.rankingexpression.parser.RankingExpressionParser;
+import com.yahoo.searchlib.rankingexpression.parser.TokenMgrError;
+import com.yahoo.searchlib.rankingexpression.rule.ReferenceNode;
+
+import java.io.*;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Encapsulates the production rule 'featureList()' int the RankingExpressionParser.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+@Beta
+public class FeatureList implements Iterable<ReferenceNode> {
+
+ private final List<ReferenceNode> features = new ArrayList<>();
+
+ /**
+ * Creates a new feature list by consuming from a reader object.
+ *
+ * @param reader The reader that contains the string to parse.
+ * @throws ParseException Thrown if the string could not be parsed.
+ */
+ public FeatureList(Reader reader) throws ParseException {
+ features.addAll(parse(reader));
+ }
+
+ /**
+ * Creates a new feature list by parsing a string.
+ *
+ * @param list The string to parse.
+ * @throws ParseException Thrown if the string could not be parsed.
+ */
+ public FeatureList(String list) throws ParseException {
+ features.addAll(parse(new StringReader(list)));
+ }
+
+ /**
+ * Creates a new feature list by reading the content of a file.
+ *
+ * @param file The file whose content to parse.
+ * @throws ParseException Thrown if the string could not be parsed.
+ * @throws FileNotFoundException Thrown if the file specified could not be found.
+ */
+ public FeatureList(File file) throws ParseException, FileNotFoundException {
+ features.addAll(parse(new FileReader(file)));
+ }
+
+ /**
+ * Parses the content of a reader object as a list of feature nodes.
+ *
+ * @param reader A reader object that contains an feature list.
+ * @return A list of those features named in the string.
+ * @throws ParseException if the string could not be parsed.
+ */
+ private static List<ReferenceNode> parse(Reader reader) throws ParseException {
+ List<ReferenceNode> lst;
+ try {
+ lst = new RankingExpressionParser(reader).featureList();
+ }
+ catch (TokenMgrError e) {
+ ParseException t = new ParseException();
+ throw (ParseException)t.initCause(e);
+ }
+ List<ReferenceNode> ret = new ArrayList<ReferenceNode>(lst.size());
+ for (Object obj : lst) {
+ if (!(obj instanceof ReferenceNode)) {
+ throw new IllegalStateException("Feature list contains a " + obj.getClass().getName() + ".");
+ }
+ ret.add((ReferenceNode)obj);
+ }
+ return ret;
+ }
+
+ /**
+ * Returns the number of features in this list.
+ *
+ * @return The size.
+ */
+ public int size() {
+ return features.size();
+ }
+
+ /**
+ * Returns the feature at the given index.
+ *
+ * @param i The index of the feature to return.
+ * @return The featuer at the given index.
+ */
+ public ReferenceNode get(int i) {
+ return features.get(i);
+ }
+
+ @Override
+ public int hashCode() {
+ int ret = 0;
+ for (ReferenceNode node : features) {
+ ret += node.hashCode() * 17;
+ }
+ return ret;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof FeatureList)) {
+ return false;
+ }
+ FeatureList lst = (FeatureList)obj;
+ if (features.size() != lst.features.size()) {
+ return false;
+ }
+ for (int i = 0; i < features.size(); ++i) {
+ if (!features.get(i).equals(lst.features.get(i))) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder ret = new StringBuilder();
+ for (ReferenceNode node : this) {
+ ret.append(node).append(" ");
+ }
+ return ret.toString();
+ }
+
+ @Override
+ public Iterator<ReferenceNode> iterator() {
+ return features.iterator();
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/RankingExpression.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/RankingExpression.java
new file mode 100755
index 00000000000..e17d524e906
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/RankingExpression.java
@@ -0,0 +1,250 @@
+// 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.evaluation.Context;
+import com.yahoo.searchlib.rankingexpression.evaluation.Value;
+import com.yahoo.searchlib.rankingexpression.parser.ParseException;
+import com.yahoo.searchlib.rankingexpression.parser.RankingExpressionParser;
+import com.yahoo.searchlib.rankingexpression.parser.TokenMgrError;
+import com.yahoo.searchlib.rankingexpression.rule.ExpressionNode;
+import com.yahoo.searchlib.rankingexpression.rule.SerializationContext;
+
+import java.io.*;
+import java.util.*;
+
+/**
+ * <p>A ranking expression. Ranking expressions are used to calculate a rank score for a searched instance from a set of
+ * <i>rank features</i>.</p>
+ *
+ * <p>A ranking expression wraps a expression node tree and may also optionally have a name.</p>
+ *
+ * <p>The identity of a ranking expression is decided by both its name and expression tree. Two expressions which
+ * looks the same in string form are the same.</p>
+ *
+ * <h3>Simple usage</h3>
+<pre><code>
+try {
+ MapContext context=new MapContext();
+ context.put("one",1d);
+ RankingExpression expression=new RankingExpression("10*if(i&gt;35,if(i&gt;one,if(i&gt;=670,4,8),if(i&gt;8000,5,3)),if(i==478,90,91))");
+ double result=expression.evaluate(context);
+ }
+catch (ParseException e) {
+ throw new RuntimeException(e);
+}
+</code></pre>
+ *
+ * <h3>Or, usage optimized for repeated evaluation of the same expression</h3>
+<pre><code>
+// Members in a class living across multiple evaluations
+RankingExpression expression;
+ArrayContext contextPrototype;
+
+...
+
+// Initialization of the above members (once)
+// Create reusable, gbdt optimized expression and context.
+// The expression is multithread-safe while the context created is not
+try {
+ RankingExpression expression=new RankingExpression("10*if(i&gt;35,if(i&gt;one,if(i&gt;=670,4,8),if(i&gt;8000,5,3)),if(i==478,90,91))");
+ ArrayContext contextPrototype=new ArrayContext(expression);
+ ExpressionOptimizer optimizer=new ExpressionOptimizer(); // Increases evaluation speed of gbdt form expressions by 3-4x
+ OptimizationReport triviaAboutTheOptimization=optimizer.optimize(expression,contextPrototype);
+}
+catch (ParseException e) {
+ throw new RuntimeException(e);
+}
+
+...
+
+// Execution (many)
+context=contextPrototype.clone(); // If evaluation is multithreaded - skip this if execution is single-threaded
+context.put("one",1d);
+double result=expression.evaluate(context);
+</code></pre>
+ *
+ * @author Simon Thoresen
+ * @author bratseth
+ */
+public class RankingExpression implements Serializable {
+
+ private String name = "";
+ private ExpressionNode root;
+
+ /**
+ * Creates a new ranking expression by consuming from the reader
+ *
+ * @param reader the reader that contains the string to parse.
+ * @throws ParseException if the string could not be parsed.
+ */
+ public RankingExpression(Reader reader) throws ParseException {
+ root = parse(reader);
+ }
+
+ /**
+ * Creates a ranking expression from a string
+ *
+ * @param expression The reader that contains the string to parse.
+ * @throws ParseException if the string could not be parsed.
+ */
+ public RankingExpression(String expression) throws ParseException {
+ try {
+ if (expression == null || expression.length() == 0) {
+ throw new IllegalArgumentException("Empty ranking expressions are not allowed");
+ }
+ root = parse(new StringReader(expression));
+ }
+ catch (ParseException e) {
+ ParseException p = new ParseException("Could not parse '" + expression + "'");
+ p.initCause(e);
+ throw p;
+ }
+ }
+
+ /**
+ * Creates a ranking expression from a file. For convenience, the file.getName() up to any dot becomes the name of
+ * this expression.
+ *
+ * @param file the name of the file whose content to parse.
+ * @throws ParseException if the string could not be parsed.
+ * @throws IllegalArgumentException if the file could not be found
+ */
+ public RankingExpression(File file) throws ParseException {
+ try {
+ name = file.getName().split("\\.")[0];
+ root = parse(new FileReader(file));
+ }
+ catch (FileNotFoundException e) {
+ throw new IllegalArgumentException("Could not create a ranking expression", e);
+ }
+ }
+
+ /**
+ * Creates a named ranking expression from an expression root node.
+ */
+ public RankingExpression(String name, ExpressionNode root) {
+ this.name = name;
+ this.root = root;
+ }
+
+ /**
+ * Creates a ranking expression from an expression root node.
+ *
+ * @param root The root node.
+ */
+ public RankingExpression(ExpressionNode root) {
+ this.root = root;
+ }
+
+ /**
+ * Parses the content of the reader object as an expression string.
+ *
+ * @param reader A reader object that contains an expression string.
+ * @return An expression node that corresponds to the given string.
+ * @throws ParseException if the string could not be parsed.
+ */
+ private static ExpressionNode parse(Reader reader) throws ParseException {
+ try {
+ return new RankingExpressionParser(reader).rankingExpression();
+ }
+ catch (TokenMgrError e) {
+ throw new ParseException(e.getMessage());
+ }
+ }
+
+ /**
+ * Returns the name of this ranking expression, or "" if no name is set.
+ *
+ * @return The name of this expression.
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * Sets the name of this ranking expression.
+ *
+ * @param name The name to set.
+ */
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ /**
+ * Returns the root of the expression tree of this expression.
+ *
+ * @return The root node.
+ */
+ public ExpressionNode getRoot() {
+ return root;
+ }
+
+ /**
+ * Sets the root of the expression tree of this expression.
+ *
+ * @param root The root node to set.
+ */
+ public void setRoot(ExpressionNode root) {
+ this.root = root;
+ }
+
+ @Override
+ public int hashCode() {
+ return toString().hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ return obj instanceof RankingExpression && toString().equals(obj.toString());
+ }
+
+ @Override
+ public String toString() {
+ if ("".equals(name)) {
+ return root.toString();
+ } else {
+ return name + ": " + root.toString();
+ }
+ }
+
+ /**
+ * Creates the necessary rank properties required to implement this expression.
+ *
+ * @param macros the expression macros to expand.
+ * @return a list of named rank properties required to implement this expression.
+ */
+ public Map<String, String> getRankProperties(List<ExpressionFunction> macros) {
+ Map<String, ExpressionFunction> arg = new HashMap<>();
+ for (ExpressionFunction function : macros) {
+ arg.put(function.getName(), function);
+ }
+ Deque<String> path = new LinkedList<>();
+ SerializationContext context = new SerializationContext(macros);
+ String serializedRoot = root.toString(context, path, null);
+ Map<String, String> serializedExpressions = context.serializedFunctions();
+ serializedExpressions.put(propertyName(name), serializedRoot);
+ return serializedExpressions;
+ }
+
+ /**
+ * Returns the rank-property name for a given expression name.
+ *
+ * @param expressionName The expression name to mangle.
+ * @return The property name.
+ */
+ public static String propertyName(String expressionName) {
+ return "rankingExpression(" + expressionName + ").rankingScript";
+ }
+
+ /**
+ * Returns the value of evaluating this expression over the given context.
+ *
+ * @param context The variable bindings to use for this evaluation.
+ * @return The evaluation result.
+ * @throws IllegalArgumentException if there are variables which are not bound in the given map
+ */
+ public Value evaluate(Context context) {
+ return root.evaluate(context);
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/AbstractArrayContext.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/AbstractArrayContext.java
new file mode 100644
index 00000000000..f4d21fd634b
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/AbstractArrayContext.java
@@ -0,0 +1,131 @@
+// 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.google.common.collect.ImmutableMap;
+import com.yahoo.searchlib.rankingexpression.RankingExpression;
+import com.yahoo.searchlib.rankingexpression.rule.CompositeNode;
+import com.yahoo.searchlib.rankingexpression.rule.ExpressionNode;
+import com.yahoo.searchlib.rankingexpression.rule.ReferenceNode;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Superclass of contexts which supports array index based lookup.
+ * Instances may be reused indefinitely for evaluations of a single
+ * ranking expression, in a single thread at the time.
+ *
+ * @author bratseth
+ */
+public abstract class AbstractArrayContext extends Context implements Cloneable {
+
+ private final boolean ignoreUnknownValues;
+
+ /** The mapping from variable name to index */
+ private final ImmutableMap<String, Integer> nameToIndex;
+
+ /** The current values set, pre-converted to doubles */
+ private double[] doubleValues;
+
+ /** The name of the ranking expression this was created for */
+ private final String rankingExpressionName;
+
+ /**
+ * Create a fast lookup context for an expression.
+ * This instance should be reused indefinitely by a single thread.
+ * This will fail if unknown values are attempted added.
+ */
+ protected AbstractArrayContext(RankingExpression expression) {
+ this(expression, false);
+ }
+
+ /**
+ * Create a fast lookup context for an expression.
+ * This instance should be reused indefinitely by a single thread.
+ *
+ * @param expression the expression to create a context for
+ * @param ignoreUnknownValues whether attempts to put values not present in this expression
+ * should fail (false - the default), or be ignored (true)
+ */
+ protected AbstractArrayContext(RankingExpression expression, boolean ignoreUnknownValues) {
+ this.ignoreUnknownValues = ignoreUnknownValues;
+ this.rankingExpressionName = expression.getName();
+ Set<String> variables = new LinkedHashSet<>();
+ extractVariables(expression.getRoot(),variables);
+
+ doubleValues = new double[variables.size()];
+
+ int i = 0;
+ ImmutableMap.Builder<String, Integer> nameToIndexBuilder = new ImmutableMap.Builder<>();
+ for (String variable : variables)
+ nameToIndexBuilder.put(variable,i++);
+ nameToIndex = nameToIndexBuilder.build();
+ }
+
+ private void extractVariables(ExpressionNode node,Set<String> variables) {
+ if (node instanceof ReferenceNode) {
+ ReferenceNode fNode=(ReferenceNode)node;
+ if (fNode.getArguments().expressions().size()>0)
+ throw new UnsupportedOperationException("Array lookup is not supported with features having arguments)");
+ variables.add(fNode.toString());
+ }
+ else if (node instanceof CompositeNode) {
+ CompositeNode cNode=(CompositeNode)node;
+ for (ExpressionNode child : cNode.children())
+ extractVariables(child,variables);
+ }
+ }
+
+ protected final Map<String, Integer> nameToIndex() { return nameToIndex; }
+ protected final double[] doubleValues() { return doubleValues; }
+ protected final boolean ignoreUnknownValues() { return ignoreUnknownValues; }
+
+ /**
+ * Creates a clone of this context suitable for evaluating against the same ranking expression
+ * in a different thread (i.e, name name to index map, different value set.
+ */
+ public AbstractArrayContext clone() {
+ try {
+ AbstractArrayContext clone=(AbstractArrayContext)super.clone();
+ clone.doubleValues=new double[nameToIndex.size()];
+ return clone;
+ }
+ catch (CloneNotSupportedException e) {
+ throw new RuntimeException("Programming error");
+ }
+ }
+
+ public Set<String> names() {
+ return nameToIndex.keySet();
+ }
+
+ /**
+ * Returns the index from a name.
+ *
+ * @throws NullPointerException is this name is not known to this context
+ */
+ public final int getIndex(String name) {
+ return nameToIndex.get(name);
+ }
+
+ /** Returns the max number of variables which may be set in this */
+ public int size() {
+ return doubleValues.length;
+ }
+
+ /** Perform a fast lookup directly of the value as a double. This is faster than get(index).asDouble() */
+ @Override
+ public double getDouble(int index) {
+ return doubleValues[index];
+ }
+
+ @Override
+ public String toString() {
+ return "fast lookup context for ranking expression '" + rankingExpressionName +
+ "' [" + doubleValues.length + " variables]";
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/ArrayContext.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/ArrayContext.java
new file mode 100644
index 00000000000..b9ff630198e
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/ArrayContext.java
@@ -0,0 +1,120 @@
+// 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 java.util.Arrays;
+
+/**
+ * Creates a context which supports array index based lookup.
+ * This instance may be reused indefinitely for evaluations of a single
+ * ranking expression, in a single thread at the time.
+ *
+ * @author bratseth
+ */
+public class ArrayContext extends AbstractArrayContext implements Cloneable {
+
+ /** The current values set */
+ private Value[] values;
+
+ private static DoubleValue constantZero = DoubleValue.frozen(0);
+
+ /**
+ * Create a fast lookup context for an expression.
+ * This instance should be reused indefinitely by a single thread.
+ * This will fail if unknown values are attempted added.
+ */
+ public ArrayContext(RankingExpression expression) {
+ this(expression, false);
+ }
+
+ /**
+ * Create a fast lookup context for an expression.
+ * This instance should be reused indefinitely by a single thread.
+ *
+ * @param expression the expression to create a context for
+ * @param ignoreUnknownValues whether attempts to put values not present in this expression
+ * should fail (false - the default), or be ignored (true)
+ */
+ public ArrayContext(RankingExpression expression, boolean ignoreUnknownValues) {
+ super(expression, ignoreUnknownValues);
+ values = new Value[doubleValues().length];
+ Arrays.fill(values, DoubleValue.zero);
+ }
+
+ /**
+ * Puts a value by name.
+ * The value will be frozen if it isn't already.
+ *
+ * @throws IllegalArgumentException if the name is not present in the ranking expression this was created with, and
+ * ignoredUnknownValues is false
+ * @since 5.1.5
+ */
+ @Override
+ public final void put(String name, Value value) {
+ Integer index = nameToIndex().get(name);
+ if (index==null) {
+ if (ignoreUnknownValues())
+ return;
+ else
+ throw new IllegalArgumentException("Value '" + name + "' is not known to " + this);
+ }
+ put(index, value);
+ }
+
+ /** Same as put(index,DoubleValue.frozen(value)) */
+ public final void put(int index, double value) {
+ put(index, DoubleValue.frozen(value));
+ }
+
+ /**
+ * Puts a value by index.
+ * The value will be frozen if it isn't already.
+ *
+ * @since 5.1.5
+ */
+ public final void put(int index, Value value) {
+ values[index]=value.freeze();
+ try {
+ doubleValues()[index]=value.asDouble();
+ }
+ catch (UnsupportedOperationException e) {
+ doubleValues()[index]=Double.NaN; // see getDouble below
+ }
+ }
+
+ /** Perform a slow lookup by name */
+ @Override
+ public Value get(String name) {
+ Integer index=nameToIndex().get(name);
+ if (index==null) return DoubleValue.zero;
+ return values[index];
+ }
+
+ /** Perform a fast lookup by index */
+ @Override
+ public final Value get(int index) {
+ return values[index];
+ }
+
+ /** Perform a fast lookup directly of the value as a double. This is faster than get(index).asDouble() */
+ @Override
+ public final double getDouble(int index) {
+ double value=doubleValues()[index];
+ if (value==Double.NaN)
+ throw new UnsupportedOperationException("Value at " + index + " has no double representation");
+ return value;
+ }
+
+ /**
+ * Creates a clone of this context suitable for evaluating against the same ranking expression
+ * in a different thread (i.e, name name to index map, different value set.
+ */
+ public ArrayContext clone() {
+ ArrayContext clone=(ArrayContext)super.clone();
+ clone.values = new Value[nameToIndex().size()];
+ Arrays.fill(values,constantZero);
+ return clone;
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/BooleanValue.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/BooleanValue.java
new file mode 100644
index 00000000000..8b456b9236b
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/BooleanValue.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;
+
+import com.yahoo.searchlib.rankingexpression.rule.Function;
+import com.yahoo.searchlib.rankingexpression.rule.TruthOperator;
+
+/**
+ * A value which is either true or false.
+ * In numerical context true is interpreted as 1 and false as 0.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ * @since 5.1.21
+ */
+public class BooleanValue extends DoubleCompatibleValue {
+
+ private boolean value;
+
+ /**
+ * Create a boolean value which is frozen at the outset.
+ */
+ public static BooleanValue frozen(boolean value) {
+ BooleanValue booleanValue=new BooleanValue(value);
+ booleanValue.freeze();
+ return booleanValue;
+ }
+
+ public BooleanValue(boolean value) {
+ this.value = value;
+ }
+
+ public boolean asBoolean() { return value; };
+
+ @Override
+ public double asDouble() {
+ return value ? 1 : 0;
+ }
+
+ @Override
+ public Value asMutable() {
+ if ( ! isFrozen()) return this;
+ return new BooleanValue(value);
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(value);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this==other) return true;
+ if ( ! (other instanceof BooleanValue)) return false;
+ return ((BooleanValue)other).value==this.value;
+ }
+
+ @Override
+ public int hashCode() {
+ return value ? 1 : 3;
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/Context.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/Context.java
new file mode 100644
index 00000000000..0dff0414ac2
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/Context.java
@@ -0,0 +1,107 @@
+// 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.rule.Arguments;
+
+import java.util.Set;
+
+/**
+ * <p>The context providing value bindings for an expression evaluation.</p>
+ *
+ * @author bratseth
+ */
+public abstract class Context {
+
+ /**
+ * <p>Returns the value of a simple variable name.</p>
+ *
+ * @param name The name of the variable whose value to return.
+ * @return The value of the named variable.
+ */
+ public abstract Value get(String name);
+
+ /**
+ * <p>Returns the value of a <i>structured variable</i> on the form
+ * <code>name(argument*)(.output)?</code>, where <i>argument</i> is any
+ * string. This may be used to implement more advanced variables whose
+ * values are calculated at runtime from arguments. Supporting this in a
+ * context is optional. Implementations may choose to throw
+ * UnsupportedOperationException or always return null, or to handle outputs
+ * but not arguments.</p>
+ *
+ * <p>This default implementation does the latter - if arguments is non-null
+ * and non-empty an UnsupportedOperationException is thrown, otherwise
+ * get(name + "." + output) is called (or just get(name)) if output is also
+ * null.</p>
+ *
+ * @param name The name of this variable.
+ * @param arguments The parsed arguments as given in the textual expression.
+ * @param output The name of the value to output (to enable one named
+ * calculation to output several), or null to output the
+ * "main" (or only) value.
+ */
+ public Value get(String name, Arguments arguments,String output) {
+ if (arguments!=null && arguments.expressions().size()>0)
+ throw new UnsupportedOperationException(this + " does not support structured ranking expression variables, attempted to reference '" +
+ name + arguments + "'");
+ if (output==null)
+ return get(name);
+ return get(name + "." + output);
+ }
+
+ /**
+ * <p>Lookup by index rather than name. This is supported by some optimized
+ * context subclasses. This default implementation throws
+ * UnsupportedOperationException.</p>
+ *
+ * @param index The index of the variable whose value to return.
+ * @return The value of the indexed variable.
+ */
+ public Value get(int index) {
+ throw new UnsupportedOperationException(this + " does not support variable lookup by index");
+ }
+
+ /**
+ * <p>Lookup by index rather than name directly to a double. This is supported by some optimized
+ * context subclasses. This default implementation throws
+ * UnsupportedOperationException.</p>
+ *
+ * @param index The index of the variable whose value to return.
+ * @return The value of the indexed variable.
+ */
+ public double getDouble(int index) {
+ throw new UnsupportedOperationException(this + " does not support variable lookup by index");
+ }
+
+ /**
+ * Same as put(name,DoubleValue.frozen(value))
+ */
+ public final void put(String name, double value) {
+ put(name, DoubleValue.frozen(value));
+ }
+
+ /**
+ * <p>Sets a value to this, or throws an UnsupportedOperationException if
+ * this is not supported. This default implementation does the latter.</p> *
+ *
+ * @param name The name of the variable to set.
+ * @param value the value to set. Ownership of this value is transferred to this - if it is mutable
+ * (not frozen) it may be modified during execution
+ * @since 5.1.5
+ */
+ public void put(String name, Value value) {
+ throw new UnsupportedOperationException(this + " does not support variable assignment");
+ }
+
+ /**
+ * <p>Returns all the names available in this, or throws an
+ * UnsupportedOperationException if this operation is not supported. This
+ * default implementation does the latter.</p>
+ *
+ * @return The set of all variable names.
+ */
+ public Set<String> names() {
+ throw new UnsupportedOperationException(this + " does not support return a list of its names");
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/DoubleCompatibleValue.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/DoubleCompatibleValue.java
new file mode 100644
index 00000000000..3129bfa05a3
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/DoubleCompatibleValue.java
@@ -0,0 +1,51 @@
+// 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.rule.Function;
+import com.yahoo.searchlib.rankingexpression.rule.TruthOperator;
+
+/**
+ * A value which acts as a double in numerical context.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ * @since 5.1.21
+ */
+public abstract class DoubleCompatibleValue extends Value {
+
+ @Override
+ public boolean hasDouble() { return true; }
+
+ @Override
+ public Value negate() { return new DoubleValue(-asDouble()); }
+
+ @Override
+ public Value add(Value value) {
+ return new DoubleValue(asDouble() + value.asDouble());
+ }
+
+ @Override
+ public Value subtract(Value value) {
+ return new DoubleValue(asDouble() - value.asDouble());
+ }
+
+ @Override
+ public Value multiply(Value value) {
+ return new DoubleValue(asDouble() * value.asDouble());
+ }
+
+ @Override
+ public Value divide(Value value) {
+ return new DoubleValue(asDouble() / value.asDouble());
+ }
+
+ @Override
+ public boolean compare(TruthOperator operator, Value value) {
+ return operator.evaluate(asDouble(), value.asDouble());
+ }
+
+ @Override
+ public Value function(Function function, Value value) {
+ return new DoubleValue(function.evaluate(asDouble(),value.asDouble()));
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/DoubleOnlyArrayContext.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/DoubleOnlyArrayContext.java
new file mode 100644
index 00000000000..2a9a6173125
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/DoubleOnlyArrayContext.java
@@ -0,0 +1,96 @@
+// 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;
+
+/**
+ * A variant of an array context variant which supports faster binding of variables but slower lookup
+ * from non-gbdt-optimized ranking expressions.
+ *
+ * @author bratseth
+ */
+public class DoubleOnlyArrayContext extends AbstractArrayContext {
+
+ /**
+ * Create a fast lookup context for an expression.
+ * This instance should be reused indefinitely by a single thread.
+ * This will fail if unknown values are attempted added.
+ */
+ public DoubleOnlyArrayContext(RankingExpression expression) {
+ this(expression, false);
+ }
+
+ /**
+ * Create a fast lookup context for an expression.
+ * This instance should be reused indefinitely by a single thread.
+ *
+ * @param expression the expression to create a context for
+ * @param ignoreUnknownValues whether attempts to put values not present in this expression
+ * should fail (false - the default), or be ignored (true)
+ */
+ public DoubleOnlyArrayContext(RankingExpression expression, boolean ignoreUnknownValues) {
+ super(expression, ignoreUnknownValues);
+ }
+
+ /**
+ * Puts a value by name.
+ * The value will be frozen if it isn't already.
+ *
+ * @throws IllegalArgumentException if the name is not present in the ranking expression this was created with, and
+ * ignoredUnknownValues is false
+ * @since 5.1.5
+ */
+ @Override
+ public final void put(String name, Value value) {
+ Integer index = nameToIndex().get(name);
+ if (index == null) {
+ if (ignoreUnknownValues())
+ return;
+ else
+ throw new IllegalArgumentException("Value '" + name + "' is not known to " + this);
+ }
+ put(index, value);
+ }
+
+ /** Same as put(index,DoubleValue.frozen(value)) */
+ public final void put(int index, double value) {
+ doubleValues()[index] = value;
+ }
+
+ /**
+ * Puts a value by index.
+ *
+ * @since 5.1.5
+ */
+ public final void put(int index, Value value) {
+ try {
+ put(index, value.asDouble());
+ }
+ catch (UnsupportedOperationException e) {
+ throw new IllegalArgumentException("This context only supports doubles, not " + value);
+ }
+ }
+
+ /** Perform a slow lookup by name */
+ @Override
+ public Value get(String name) {
+ Integer index = nameToIndex().get(name);
+ if (index==null) return DoubleValue.zero;
+ return new DoubleValue(getDouble(index));
+ }
+
+ /** Perform a faster lookup by index */
+ @Override
+ public final Value get(int index) {
+ return new DoubleValue(getDouble(index));
+ }
+
+ /**
+ * Creates a clone of this context suitable for evaluating against the same ranking expression
+ * in a different thread (i.e, name name to index map, different value set.
+ */
+ public DoubleOnlyArrayContext clone() {
+ return (DoubleOnlyArrayContext)super.clone();
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/DoubleValue.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/DoubleValue.java
new file mode 100644
index 00000000000..1cd65c3133a
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/DoubleValue.java
@@ -0,0 +1,158 @@
+// 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.rule.Function;
+import com.yahoo.searchlib.rankingexpression.rule.TruthOperator;
+
+/**
+ * A double value result of a ranking expression evaluation.
+ * In a boolean context doubles are true if they are different from 0.0
+ *
+ * @author bratseth
+ * @since 5.1.5
+ */
+public final class DoubleValue extends DoubleCompatibleValue {
+
+ // A note on performance: Reusing double values like below is actually slightly slower per evaluation,
+ // but the reduced garbage cost seems to regain this plus some additional percentages
+
+ private double value;
+
+ /** The double value instance for 0 */
+ public final static DoubleValue zero=DoubleValue.frozen(0);
+
+ public DoubleValue(double value) {
+ this.value=value;
+ }
+
+ /**
+ * Create a double which is frozen at the outset.
+ */
+ public static DoubleValue frozen(double value) {
+ DoubleValue doubleValue=new DoubleValue(value);
+ doubleValue.freeze();
+ return doubleValue;
+ }
+
+ @Override
+ public double asDouble() { return value; }
+
+ @Override
+ public DoubleValue asDoubleValue() { return this; }
+
+ @Override
+ public boolean asBoolean() { return value != 0.0; }
+
+ @Override
+ public DoubleValue negate() {
+ return mutable(-value);
+ }
+
+ @Override
+ public Value add(Value value) {
+ if (value instanceof TensorValue)
+ return value.add(this);
+
+ try {
+ return mutable(this.value + value.asDouble());
+ }
+ catch (UnsupportedOperationException e) {
+ throw unsupported("add",value);
+ }
+ }
+
+ @Override
+ public Value subtract(Value value) {
+ if (value instanceof TensorValue)
+ return value.negate().add(this);
+
+ try {
+ return mutable(this.value - value.asDouble());
+ }
+ catch (UnsupportedOperationException e) {
+ throw unsupported("subtract",value);
+ }
+ }
+
+ @Override
+ public Value multiply(Value value) {
+ if (value instanceof TensorValue)
+ return value.multiply(this);
+
+ try {
+ return mutable(this.value * value.asDouble());
+ }
+ catch (UnsupportedOperationException e) {
+ throw unsupported("multiply", value);
+ }
+ }
+
+ @Override
+ public Value divide(Value value) {
+ try {
+ return mutable(this.value / value.asDouble());
+ }
+ catch (UnsupportedOperationException e) {
+ throw unsupported("divide",value);
+ }
+ }
+
+ @Override
+ public boolean compare(TruthOperator operator, Value value) {
+ try {
+ return operator.evaluate(this.value, value.asDouble());
+ }
+ catch (UnsupportedOperationException e) {
+ throw unsupported("comparison",value);
+ }
+ }
+
+ @Override
+ public Value function(Function function, Value value) {
+ // use the tensor implementation of max and min if the argument is a tensor
+ if ( (function.equals(Function.min) || function.equals(Function.max)) && value instanceof TensorValue)
+ return value.function(function, this);
+
+ try {
+ return mutable(function.evaluate(this.value, value.asDouble()));
+ }
+ catch (UnsupportedOperationException e) {
+ throw unsupported("function " + function.toString(), value);
+ }
+ }
+
+ private UnsupportedOperationException unsupported(String operation, Value value) {
+ return new UnsupportedOperationException("Cannot perform " + operation + " on " + value + " and " + this);
+ }
+
+ /** Returns this or a mutable copy assigned the given value */
+ private DoubleValue mutable(double value) {
+ DoubleValue mutable=this.asMutable();
+ mutable.value=value;
+ return mutable;
+ }
+
+ @Override
+ public DoubleValue asMutable() {
+ if ( ! isFrozen()) return this;
+ return new DoubleValue(value);
+ }
+
+ @Override
+ public String toString() {
+ return String.valueOf(value);
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this==other) return true;
+ if ( ! (other instanceof DoubleValue)) return false;
+ return ((DoubleValue)other).value==this.value;
+ }
+
+ @Override
+ public int hashCode() {
+ return toString().hashCode();
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/ExpressionOptimizer.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/ExpressionOptimizer.java
new file mode 100644
index 00000000000..6730053e9fe
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/ExpressionOptimizer.java
@@ -0,0 +1,55 @@
+// 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.evaluation.gbdtoptimization.GBDTOptimizer;
+
+/**
+ * This class will perform various optimizations on the ranking expressions. Clients using optimized expressions
+ * will do
+ *
+ * <code>
+ * // Set up once
+ * RankingExpression expression = new RankingExpression(myExpressionString);
+ * ArrayContext context = new ArrayContext(expression);
+ * new ExpressionOptimizer().optimize(expression, context);
+ *
+ * // Execute repeatedly
+ * context.put("featureName1", value1);
+ * ...
+ * expression.evaluate(context);
+ *
+ * // Note that the expression may be used by multiple threads at the same time, while the
+ * // context is single-threaded. To create a context for another tread, use the above context as a prototype,
+ * // contextForOtherThread = context.clone();
+ * </code>
+ * <p>
+ * Instances of this class are not multithread safe.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class ExpressionOptimizer {
+
+ private GBDTOptimizer gbdtOptimizer = new GBDTOptimizer();
+
+ private GBDTForestOptimizer gbdtForestOptimizer = new GBDTForestOptimizer();
+
+ /** Gets an optimizer instance used by this by class name, or null if the optimizer is not known */
+ public Optimizer getOptimizer(Class<?> clazz) {
+ if (clazz == gbdtOptimizer.getClass())
+ return gbdtOptimizer;
+ if (clazz == gbdtForestOptimizer.getClass())
+ return gbdtForestOptimizer;
+ return null;
+ }
+
+ public OptimizationReport optimize(RankingExpression expression, AbstractArrayContext arrayContext) {
+ OptimizationReport report = new OptimizationReport();
+ // Note: Order of optimizations matter
+ gbdtOptimizer.optimize(expression, arrayContext, report);
+ gbdtForestOptimizer.optimize(expression, arrayContext, report);
+ return report;
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/MapContext.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/MapContext.java
new file mode 100644
index 00000000000..9ee9a1f7a71
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/MapContext.java
@@ -0,0 +1,95 @@
+// 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 java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A context backed by a Map
+ *
+ * @author bratseth
+ */
+public class MapContext extends Context {
+
+ private Map<String,Value> bindings=new HashMap<>();
+
+ private boolean frozen = false;
+
+ public MapContext() {
+ }
+
+ /**
+ * Freezes this.
+ * Returns this for convenience.
+ */
+ public MapContext freeze() {
+ if ( ! frozen)
+ bindings = Collections.unmodifiableMap(bindings);
+ return this;
+ }
+
+ /**
+ * Creates a map context from a map.
+ * The ownership of the map is transferred to this - it cannot be further modified by the caller.
+ * All the Values of the map will be frozen.
+ *
+ * @since 5.1.5
+ */
+ public MapContext(Map<String,Value> bindings) {
+ this.bindings=bindings;
+ for (Value boundValue : bindings.values())
+ boundValue.freeze();
+ }
+
+ /**
+ * Returns the value of a key. 0 is returned if the given key is not bound in this.
+ */
+ public @Override Value get(String key) {
+ Value value=bindings.get(key);
+ if (value==null) return DoubleValue.zero;
+ return value;
+ }
+
+ /**
+ * Sets the value of a key.
+ * The value is frozen by this.
+ *
+ * @since 5.1.5
+ */
+ public @Override void put(String key,Value value) {
+ bindings.put(key,value.freeze());
+ }
+
+ /** Returns an immutable view of the bindings of this. */
+ public Map<String,Value> bindings() {
+ if (frozen) return bindings;
+ return Collections.unmodifiableMap(bindings);
+ }
+
+ /** Returns an unmodifiable map of the names of this */
+ public @Override Set<String> names() {
+ if (frozen) return bindings.keySet();
+ return Collections.unmodifiableMap(bindings).keySet();
+ }
+
+ public @Override String toString() {
+ return "a map context [" + bindings.size() + " bindings]";
+ }
+
+ /**
+ * A convenience constructor which returns a map context from a string on the form
+ * <code>name1:value1, name2:value2 ...</code>.
+ * Extra spaces are allowed anywhere. Any other deviation from the syntax causes an exception to be thrown.
+ */
+ public static MapContext fromString(String contextString) {
+ MapContext mapContext = new MapContext();
+ for (String keyValueString : contextString.split(",")) {
+ String[] strings = keyValueString.trim().split(":");
+ mapContext.put(strings[0].trim(), Value.parse(strings[1].trim()));
+ }
+ return mapContext;
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/OptimizationReport.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/OptimizationReport.java
new file mode 100644
index 00000000000..340a074f179
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/OptimizationReport.java
@@ -0,0 +1,63 @@
+// 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 java.util.ArrayList;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Reports the result of optimizations of a ranking expression.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class OptimizationReport {
+
+ private Map<String,Integer> metrics=new LinkedHashMap<String,Integer>();
+
+ private List<String> notes=new ArrayList<String>();
+
+ public void setMetric(String name,int value) {
+ metrics.put(name,value);
+ }
+
+ /** Returns the value of a metric, or null if it is not set */
+ public int getMetric(String name) {
+ return metrics.get(name);
+ }
+
+ /**
+ * Increases the metric by the given name by increment, if the metric is not previously set,
+ * this will assign it the value increment as expected
+ */
+ public void incMetric(String name,int increment) {
+ Integer currentValue=metrics.get(name);
+ if (currentValue==null)
+ currentValue=0;
+ metrics.put(name,currentValue+increment);
+ }
+
+ public void note(String note) {
+ notes.add(note);
+ }
+
+ /** Returns all the content of this report as a multiline string */
+ public String toString() {
+ StringBuilder b=new StringBuilder();
+
+ if (notes.size()>0) {
+ b.append("Optimization notes:\n");
+ List<String> displayedNotes=notes.subList(0,Math.min(5,notes.size()));
+ for (String note : displayedNotes)
+ b.append(" ").append(note).append("\n");
+ if (notes.size()>displayedNotes.size())
+ b.append(" ...\n");
+ }
+
+ b.append("Optimization metrics:\n");
+ for (Map.Entry<String,Integer> metric : metrics.entrySet())
+ b.append(" " + metric.getKey() + ": " + metric.getValue() + "\n");
+ return b.toString();
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/Optimizer.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/Optimizer.java
new file mode 100644
index 00000000000..337e2f84774
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/Optimizer.java
@@ -0,0 +1,23 @@
+// 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;
+
+/**
+ * Superclass of ranking expression optimizers
+ *
+ * @author bratseth
+ */
+public abstract class Optimizer {
+
+ private boolean enabled=true;
+
+ /** Sets whether this optimizer is enabled. Default true */
+ public void setEnabled(boolean enabled) { this.enabled=enabled; }
+
+ /** Returns whether this is enabled */
+ public boolean isEnabled() { return enabled; }
+
+ public abstract void optimize(RankingExpression expression, AbstractArrayContext context, OptimizationReport report);
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/StringValue.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/StringValue.java
new file mode 100644
index 00000000000..ff935031149
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/StringValue.java
@@ -0,0 +1,108 @@
+// 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.rule.Function;
+import com.yahoo.searchlib.rankingexpression.rule.TruthOperator;
+
+/**
+ * A string value.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ * @since 5.1.21
+ */
+public class StringValue extends Value {
+
+ private final String value;
+
+ /**
+ * Create a string value which is frozen at the outset.
+ */
+ public static StringValue frozen(String value) {
+ StringValue stringValue=new StringValue(value);
+ stringValue.freeze();
+ return stringValue;
+ }
+
+ public StringValue(String value) {
+ this.value = value;
+ }
+
+ /** Returns the hashcode of this, to enable strings to be encoded (with reasonable safely) as doubles for optimization */
+ @Override
+ public double asDouble() {
+ return UnicodeUtilities.unquote(value.toString()).hashCode();
+ }
+
+ @Override
+ public boolean hasDouble() { return true; }
+
+ @Override
+ public boolean asBoolean() {
+ throw new UnsupportedOperationException("A string value ('" + value + "') does not have a boolean value");
+ }
+
+ @Override
+ public Value negate() {
+ throw new UnsupportedOperationException("A string value ('" + value + "') cannot be negated");
+ }
+
+ @Override
+ public Value add(Value value) {
+ return new StringValue(value + value.toString());
+ }
+
+ @Override
+ public Value subtract(Value value) {
+ throw new UnsupportedOperationException("String values ('" + value + "') does not support subtraction");
+ }
+
+ @Override
+ public Value multiply(Value value) {
+ throw new UnsupportedOperationException("String values ('" + value + "') does not support multiplication");
+ }
+
+ @Override
+ public Value divide(Value value) {
+ throw new UnsupportedOperationException("String values ('" + value + "') does not support division");
+ }
+
+ @Override
+ public boolean compare(TruthOperator operator, Value value) {
+ if (operator.equals(TruthOperator.EQUAL))
+ return this.equals(value);
+ throw new UnsupportedOperationException("String values ('" + value + "') cannot be compared except with '='");
+ }
+
+ @Override
+ public Value function(Function function, Value value) {
+ throw new UnsupportedOperationException("Mathematical functions cannot be applied on strings ('" + value + "')");
+ }
+
+ @Override
+ public Value asMutable() {
+ if ( ! isFrozen()) return this;
+ return new StringValue(value);
+ }
+
+ @Override
+ public String toString() {
+ return "\"" + value + "\"";
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (this==other) return true;
+ if ( ! (other instanceof StringValue)) return false;
+ return ((StringValue)other).value.equals(this.value);
+ }
+
+ @Override
+ public int hashCode() {
+ return value.hashCode();
+ }
+
+ /** Returns the value of this as a string */
+ public String asString() { return value; }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/TensorValue.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/TensorValue.java
new file mode 100644
index 00000000000..12bede95aae
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/TensorValue.java
@@ -0,0 +1,168 @@
+// 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.google.common.annotations.Beta;
+import com.yahoo.tensor.Tensor;
+import com.yahoo.tensor.TensorAddress;
+import com.yahoo.searchlib.rankingexpression.rule.Function;
+import com.yahoo.searchlib.rankingexpression.rule.TruthOperator;
+import com.yahoo.tensor.TensorType;
+
+import java.util.Optional;
+
+/**
+ * A Value containing a tensor.
+ * See {@link com.yahoo.tensor.Tensor} for definition of a tensor
+ * and the operations supported.
+ *
+ * @author bratseth
+ */
+ @Beta
+public class TensorValue extends Value {
+
+ /** The tensor value of this */
+ private final Tensor value;
+ private final Optional<TensorType> type;
+
+ public TensorValue(Tensor value) {
+ this.value = value;
+ this.type = Optional.empty();
+ }
+
+ public TensorValue(Tensor value, TensorType type) {
+ this.value = value;
+ this.type = Optional.of(type);
+ }
+
+ @Override
+ public double asDouble() {
+ if (value.dimensions().size() == 0)
+ return value.get(TensorAddress.empty);
+ throw new UnsupportedOperationException("Requires a double value from a tensor with dimensions " +
+ value.dimensions() + ", but a tensor of order > 0 does " +
+ "not have a double value. Input tensor: " + this);
+ }
+
+ @Override
+ public boolean hasDouble() { return value.dimensions().size() == 0; }
+
+ @Override
+ public boolean asBoolean() {
+ throw new UnsupportedOperationException("A tensor does not have a boolean value");
+ }
+
+ @Override
+ public Value negate() {
+ return new TensorValue(value.apply((Double value) -> -value));
+ }
+
+ @Override
+ public Value add(Value argument) {
+ if (argument instanceof TensorValue)
+ return new TensorValue(value.add(((TensorValue)argument).value));
+ else
+ return new TensorValue(value.apply((Double value) -> value + argument.asDouble()));
+ }
+
+ @Override
+ public Value subtract(Value argument) {
+ if (argument instanceof TensorValue)
+ return new TensorValue(value.subtract(((TensorValue) argument).value));
+ else
+ return new TensorValue(value.apply((Double value) -> value - argument.asDouble()));
+ }
+
+ @Override
+ public Value multiply(Value argument) {
+ if (argument instanceof TensorValue)
+ return new TensorValue(value.multiply(((TensorValue) argument).value));
+ else
+ return new TensorValue(value.apply((Double value) -> value * argument.asDouble()));
+ }
+
+ @Override
+ public Value divide(Value argument) {
+ if (argument instanceof TensorValue)
+ throw new UnsupportedOperationException("Two tensors cannot be divided");
+ else
+ return new TensorValue(value.apply((Double value) -> value / argument.asDouble()));
+ }
+
+ public Value match(Value argument) {
+ return new TensorValue(value.match(asTensor(argument, "match")));
+ }
+
+ public Value min(Value argument) {
+ return new TensorValue(value.min(asTensor(argument, "min")));
+ }
+
+ public Value max(Value argument) {
+ return new TensorValue(value.max(asTensor(argument, "max")));
+ }
+
+ public Value sum(String dimension) {
+ return new TensorValue(value.sum(dimension));
+ }
+
+ public Value sum() {
+ return new DoubleValue(value.sum());
+ }
+
+ private Tensor asTensor(Value value, String operationName) {
+ if ( ! (value instanceof TensorValue))
+ throw new UnsupportedOperationException("Could not perform " + operationName +
+ ": The second argument must be a tensor but was " + value);
+ return ((TensorValue)value).value;
+ }
+
+ public Tensor asTensor() { return value; }
+
+ public Optional<TensorType> getType() {
+ return type;
+ }
+
+ @Override
+ public boolean compare(TruthOperator operator, Value value) {
+ throw new UnsupportedOperationException("A tensor cannot be compared with any value");
+ }
+
+ @Override
+ public Value function(Function function, Value argument) {
+ if (function.equals(Function.min) && argument instanceof TensorValue)
+ return min(argument);
+ else if (function.equals(Function.max) && argument instanceof TensorValue)
+ return max(argument);
+ else
+ return new TensorValue(value.apply((Double value) -> function.evaluate(value, argument.asDouble())));
+ }
+
+ @Override
+ public Value asMutable() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String toString() {
+ return value.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ TensorValue that = (TensorValue) o;
+
+ if (!type.equals(that.type)) return false;
+ if (!value.equals(that.value)) return false;
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = value.hashCode();
+ result = 31 * result + type.hashCode();
+ return result;
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/Value.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/Value.java
new file mode 100644
index 00000000000..e56c005cdf7
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/Value.java
@@ -0,0 +1,96 @@
+// 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.rule.Function;
+import com.yahoo.searchlib.rankingexpression.rule.TruthOperator;
+import com.yahoo.tensor.MapTensor;
+
+/**
+ * The result of a ranking expression evaluation.
+ * Concrete subclasses of this provides implementations of these methods or throws
+ * UnsupportedOperationException if the operation is not supported.
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ * @since 5.1.5
+ */
+public abstract class Value {
+
+ private boolean frozen=false;
+
+ /** Returns this value as a double, or throws UnsupportedOperationException if it cannot be represented as a double */
+ public abstract double asDouble();
+
+ /** Returns this value as a double value, or throws UnsupportedOperationException if it cannot be represented as a double */
+ public DoubleValue asDoubleValue() {
+ return new DoubleValue(asDouble());
+ }
+
+ /** Returns true if this value can return itself as a double, i.e asDoubleValue will return a value and not throw */
+ public abstract boolean hasDouble();
+
+ /** Returns this value as a boolean. */
+ public abstract boolean asBoolean();
+
+ public abstract Value negate();
+
+ public abstract Value add(Value value);
+
+ public abstract Value subtract(Value value);
+
+ public abstract Value multiply(Value value);
+
+ public abstract Value divide(Value value);
+
+ /** Perform the comparison specified by the operator between this value and the given value */
+ public abstract boolean compare(TruthOperator operator,Value value);
+
+ /** Perform the given binary function on this value and the given value */
+ public abstract Value function(Function function,Value value);
+
+ /**
+ * Irreversibly makes this immutable. Overriders must always call super.freeze() and return this
+ *
+ * @return this for convenience
+ */
+ public Value freeze() {
+ frozen=true;
+ return this;
+ }
+
+ /** Returns true if this is immutable, false otherwise */
+ public final boolean isFrozen() { return frozen; }
+
+ /** Returns this is mutable, or a mutable copy otherwise */
+ public abstract Value asMutable();
+
+ @Override
+ public abstract String toString();
+
+ @Override
+ public abstract boolean equals(Object other);
+
+ @Override
+ public abstract int hashCode();
+
+ /**
+ * Parses the given string to a value and returns it.
+ * Different subtypes of Value will be returned depending on the string.
+ *
+ * @return a mutable Value
+ * @throws IllegalArgumentException if the given string is not parseable as a value
+ */
+ public static Value parse(String value) {
+ if (value.equals("true"))
+ return new BooleanValue(true);
+ else if (value.equals("false"))
+ return new BooleanValue(false);
+ else if (value.startsWith("\"") || value.startsWith("'"))
+ return new StringValue(UnicodeUtilities.unquote(value));
+ else if (value.startsWith("{"))
+ return new TensorValue(MapTensor.from(value));
+ else
+ return new DoubleValue(Double.parseDouble(value));
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/.gitignore b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/.gitignore
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/.gitignore
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/GBDTForestNode.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/GBDTForestNode.java
new file mode 100644
index 00000000000..3e138aa7d72
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/GBDTForestNode.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.rankingexpression.evaluation.gbdtoptimization;
+
+import com.yahoo.searchlib.rankingexpression.evaluation.Context;
+import com.yahoo.searchlib.rankingexpression.evaluation.DoubleValue;
+import com.yahoo.searchlib.rankingexpression.evaluation.Value;
+import com.yahoo.searchlib.rankingexpression.rule.CompositeNode;
+import com.yahoo.searchlib.rankingexpression.rule.ExpressionNode;
+import com.yahoo.searchlib.rankingexpression.rule.SerializationContext;
+
+import java.util.Deque;
+
+/**
+ * An optimized version of a sum of consecutive decision trees.
+ *
+ * @author bratseth
+ */
+public class GBDTForestNode extends ExpressionNode {
+
+ private final double[] values;
+
+ public GBDTForestNode(double[] values) {
+ this.values=values;
+ }
+
+ @Override
+ public final Value evaluate(Context context) {
+ int pc = 0;
+ double treeSum = 0;
+ while (pc < values.length) {
+ int nextTree = (int)values[pc++];
+ treeSum += GBDTNode.evaluate(values, pc, context);
+ pc += nextTree;
+ }
+ return new DoubleValue(treeSum);
+ }
+
+ /** Returns (optimized sum of condition trees) */
+ public String toString(SerializationContext context, Deque<String> path, CompositeNode parent) {
+ return "(optimized sum of condition trees of size " + (values.length*8) + " bytes)";
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/GBDTForestOptimizer.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/GBDTForestOptimizer.java
new file mode 100644
index 00000000000..7d84124f2af
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/GBDTForestOptimizer.java
@@ -0,0 +1,124 @@
+// 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.AbstractArrayContext;
+import com.yahoo.searchlib.rankingexpression.evaluation.ArrayContext;
+import com.yahoo.searchlib.rankingexpression.evaluation.OptimizationReport;
+import com.yahoo.searchlib.rankingexpression.evaluation.Optimizer;
+import com.yahoo.searchlib.rankingexpression.rule.ArithmeticNode;
+import com.yahoo.searchlib.rankingexpression.rule.ArithmeticOperator;
+import com.yahoo.searchlib.rankingexpression.rule.CompositeNode;
+import com.yahoo.searchlib.rankingexpression.rule.ExpressionNode;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * @author bratseth
+ */
+public class GBDTForestOptimizer extends Optimizer {
+
+ private OptimizationReport report;
+
+ /**
+ * A temporary value used within the algorithm
+ */
+ private int currentTreesOptimized = 0;
+
+ /**
+ * Optimizes sums of GBDTNodes by replacing them by a single GBDTForestNode
+ *
+ * @param expression the expression to destructively optimize
+ * @param context a fast lookup context created from the given expression
+ * @param report the optimization report to which actions of this is logged
+ */
+ @Override
+ public void optimize(RankingExpression expression, AbstractArrayContext context, OptimizationReport report) {
+ if ( ! isEnabled()) return;
+
+ this.report = report;
+ expression.setRoot(findAndOptimize(expression.getRoot()));
+ report.note("GBDT forest optimization done");
+ }
+
+ /**
+ * Recursively descend and optimize gbdt forest nodes.
+ *
+ * @return the resulting node, which may be the input node if no optimizations were found
+ */
+ private ExpressionNode findAndOptimize(ExpressionNode node) {
+ ExpressionNode newNode = optimize(node);
+ if ( ! (newNode instanceof CompositeNode)) return newNode; //
+
+ CompositeNode newComposite = (CompositeNode)newNode;
+ List<ExpressionNode> newChildren = new ArrayList<>();
+ for (ExpressionNode child : newComposite.children()) {
+ newChildren.add(findAndOptimize(child));
+ }
+ return newComposite.setChildren(newChildren);
+ }
+
+ /**
+ * Optimize the given node (only)
+ *
+ * @return the resulting node, which may be the input node if it could not be optimized
+ */
+ private ExpressionNode optimize(ExpressionNode node) {
+ currentTreesOptimized = 0;
+ List<Double> forest = new ArrayList<>();
+ boolean optimized = optimize(node, forest);
+ if ( ! optimized ) return node;
+
+ GBDTForestNode forestNode = new GBDTForestNode(toArray(forest));
+ report.incMetric("Number of forests", 1);
+ report.incMetric("GBDT trees optimized to forests", currentTreesOptimized);
+ return forestNode;
+ }
+
+ /**
+ * Optimize the given node, if it is the root of a gdbt forest. Otherwise do nothing and return false
+ */
+ private boolean optimize(ExpressionNode node, List<Double> forest) {
+ if (node instanceof GBDTNode) {
+ addTo(forest, (GBDTNode)node);
+ currentTreesOptimized++;
+ return true;
+ }
+ if (!(node instanceof ArithmeticNode)) {
+ return false;
+ }
+ ArithmeticNode aNode = (ArithmeticNode)node;
+ for (ArithmeticOperator op : aNode.operators()) {
+ if (op != ArithmeticOperator.PLUS) {
+ return false;
+ }
+ }
+ for (ExpressionNode child : aNode.children()) {
+ if (!optimize(child, forest)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private void addTo(List<Double> forest, GBDTNode tree) {
+ forest.add((double)tree.values().length);
+ addAll(tree.values(), forest);
+ }
+
+ private void addAll(double[] values, List<Double> forest) {
+ for (double value : values) {
+ forest.add(value);
+ }
+ }
+
+ private double[] toArray(List<Double> valueList) {
+ double[] valueArray = new double[valueList.size()];
+ for (int i = 0; i < valueList.size(); i++) {
+ valueArray[i] = valueList.get(i);
+ }
+ return valueArray;
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/GBDTNode.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/GBDTNode.java
new file mode 100644
index 00000000000..607b4dc55cb
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/GBDTNode.java
@@ -0,0 +1,98 @@
+// 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.evaluation.Context;
+import com.yahoo.searchlib.rankingexpression.evaluation.DoubleValue;
+import com.yahoo.searchlib.rankingexpression.evaluation.Value;
+import com.yahoo.searchlib.rankingexpression.rule.CompositeNode;
+import com.yahoo.searchlib.rankingexpression.rule.ExpressionNode;
+import com.yahoo.searchlib.rankingexpression.rule.SerializationContext;
+
+import java.util.Deque;
+
+/**
+ * An optimized version of a decision tree.
+ *
+ * @author bratseth
+ */
+public final class GBDTNode extends ExpressionNode {
+
+ // The GBDT node vm works by reading doubles one at a time and interpreting them
+ // as either constant data or a mangling of opcode and variable reference:
+ // The value space is as follows:
+ // n=[0,MAX_LEAF_VALUE> : n is data (tree leaf constant value)
+ // n=[MAX_LEAF_VALUE+MAX_VARIABLES*0,MAX_LEAF_VALUE+MAX_VARIABLES*1>: < than var at index n
+ // n=[MAX_LEAF_VALUE+MAX_VARIABLES*1,MAX_LEAF_VALUE+MAX_VARIABLES*2>: = to var at index n-MAX_VARIABLES
+ // n=[MAX_LEAF_VALUE+MAX_VARIABLES*2,MAX_LEAF_VALUE+MAX_VARIABLES*3]: n-MAX_VARIABLES*2 is IN the following set
+
+ // The full layout of an IF instruction is
+ // COMPARISON,TRUE_BRANCH_LENGTH,TRUE_BRANCH,FALSE_BRANCH
+ // where COMPARISON is VARIABLE_AND_OPCODE,COMPARE_CONSTANT if the opcode is < or =,
+ // and VARIABLE_AND_OPCODE,COMPARE_CONSTANTS_LENGTH,COMPARE_CONSTANTS if the opcode is IN
+
+
+ // If any change is made to this encoding, this change must also be reflected in GBDTNodeOptimizer
+
+ /** The max (absolute) supported value an optimized leaf may have */
+ public final static int MAX_LEAF_VALUE=2*1000*1000*1000;
+
+ /** The max number of variables (features) supported in the context */
+ public final static int MAX_VARIABLES=1*1000*1000;
+
+ private final double[] values;
+
+ public GBDTNode(double[] values) {
+ this.values=values;
+ }
+
+ /** Returns a direct reference to the values of this. The returned array must not be modified. */
+ public final double[] values() { return values; }
+
+ @Override
+ public final Value evaluate(Context context) {
+ return new DoubleValue(evaluate(values,0,context));
+ }
+
+ public static double evaluate(double[] values, int startOffset, Context context) {
+ int pc = startOffset;
+ while (true) {
+ double nextValue = values[pc++];
+ if (nextValue >= MAX_LEAF_VALUE) { // a condition node
+ int offset = (int)nextValue - MAX_LEAF_VALUE;
+ boolean comparisonIsTrue = false;
+ if (offset < MAX_VARIABLES) {
+ comparisonIsTrue = context.getDouble(offset)<values[pc++];
+ }
+ else if (offset < MAX_VARIABLES*2) {
+ comparisonIsTrue = context.getDouble(offset-MAX_VARIABLES)==values[pc++];
+ }
+ else { // offset<MAX_VARIABLES*3
+ double testValue = context.getDouble(offset-MAX_VARIABLES*2);
+ int setValuesLeft = (int)values[pc++];
+ while (setValuesLeft > 0) { // test each value in the set
+ setValuesLeft--;
+ if (testValue == values[pc++]) {
+ comparisonIsTrue=true;
+ break;
+ }
+ }
+ pc += setValuesLeft; // jump to after the set
+ }
+
+ if (comparisonIsTrue)
+ pc++; // true branch - skip the jump value
+ else
+ pc += values[pc]; // false branch - jump
+ }
+ else { // a leaf
+ return nextValue;
+ }
+ }
+ }
+
+ /** Returns "(optimized condition tree)" */
+ @Override
+ public String toString(SerializationContext context, Deque<String> path, CompositeNode parent) {
+ return "(optimized condition tree)";
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/GBDTOptimizer.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/GBDTOptimizer.java
new file mode 100644
index 00000000000..7e74bdce9e6
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/GBDTOptimizer.java
@@ -0,0 +1,184 @@
+// 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.yolean.Exceptions;
+import com.yahoo.searchlib.rankingexpression.RankingExpression;
+import com.yahoo.searchlib.rankingexpression.evaluation.*;
+import com.yahoo.searchlib.rankingexpression.rule.*;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * <p>This ranking expression processor recognizes and optimizes GBDT expressions. Note that this optimization is
+ * destructive - inspection is not possible into optimized subtrees.</p>
+ *
+ * <p>This class is not multithread safe.</p>
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class GBDTOptimizer extends Optimizer {
+
+ private OptimizationReport report;
+
+ /**
+ * Optimizes this by replacing GBDT sub-expressions by GBDTNodes. These optimized expressions <b>must</b> be
+ * executed using an instance of {@link com.yahoo.searchlib.rankingexpression.evaluation.ArrayContext} as context.
+ * These thread locally reusable contexts must be created from the ranking expression <i>before</i> the ranking
+ * expression is optimized.
+ *
+ * @param expression the expression to destructively optimize
+ * @param context a fast lookup context created from the given expression
+ * @param report the optimization report to which actions of this is logged
+ */
+ @Override
+ public void optimize(RankingExpression expression, AbstractArrayContext context, OptimizationReport report) {
+ if (!isEnabled()) return;
+
+ this.report = report;
+
+ if (context.size() > GBDTNode.MAX_VARIABLES) {
+ report.note("Can not optimize expressions referencing more than " + GBDTNode.MAX_VARIABLES + " features: " +
+ expression + " has " + context.size());
+ return;
+ }
+
+ expression.setRoot(optimize(expression.getRoot(), context));
+ report.note("GBDT tree optimization done");
+ }
+
+ /**
+ * <p>Recursively optimize nodes of the form ArithmeticNode(IfNode,ArithmeticNode(IfNode)) etc., ignore
+ * anything else.</p>
+ *
+ * <p>Each condition node is converted to the double sequence [(OperatorIsEquals ? GBDTNode.MAX_VARIABLES : 0) +
+ * IndexOfLeftComparisonFeature+GBDTNode.MAX_LEAFT_VALUE, ValueOfRightComparisonValue,#OfValuesInTrueBranch,true
+ * branch values,false branch values]</p>
+ *
+ * <p>Each value node is converted to the double value of the value node itself.</p>
+ *
+ * @return the optimized expression
+ */
+ private ExpressionNode optimize(ExpressionNode node, AbstractArrayContext context) {
+ if (node instanceof ArithmeticNode) {
+ Iterator<ExpressionNode> childIt = ((ArithmeticNode)node).children().iterator();
+ ExpressionNode ret = optimize(childIt.next(), context);
+
+ Iterator<ArithmeticOperator> operIt = ((ArithmeticNode)node).operators().iterator();
+ while (childIt.hasNext() && operIt.hasNext()) {
+ ret = ArithmeticNode.resolve(ret, operIt.next(), optimize(childIt.next(), context));
+ }
+ return ret;
+ }
+ if (node instanceof IfNode) {
+ return createGBDTNode((IfNode)node, context);
+ }
+ return node;
+ }
+
+ private ExpressionNode createGBDTNode(IfNode cNode, AbstractArrayContext context) {
+ List<Double> values = new ArrayList<>();
+ try {
+ consumeNode(cNode, values, context);
+ }
+ catch (IllegalArgumentException e) { // Conversion was impossible
+ report.note("Skipped optimization: " + Exceptions.toMessageString(e) + ". Expression: " + cNode);
+ return cNode;
+ }
+ report.incMetric("Optimized GDBT trees",1);
+ return new GBDTNode(toArray(values));
+ }
+
+ /**
+ * Recursively consume nodes into the value list Returns the number of values produced by this.
+ */
+ private int consumeNode(ExpressionNode node, List<Double> values, AbstractArrayContext context) {
+ int beforeIndex = values.size();
+ if ( node instanceof IfNode) {
+ IfNode ifNode = (IfNode)node;
+ int jumpValueIndex = consumeIfCondition(ifNode.getCondition(), values, context);
+ values.add(0d); // jumpValue goes here after the next line
+ int jumpValue = consumeNode(ifNode.getTrueExpression(), values, context) + 1;
+ values.set(jumpValueIndex, (double) jumpValue);
+ consumeNode(ifNode.getFalseExpression(), values, context);
+ } else {
+ double value = toValue(node);
+ if (Math.abs(value) > GBDTNode.MAX_LEAF_VALUE) {
+ throw new IllegalArgumentException("Leaf value is too large for optimization: " + value);
+ }
+ values.add(toValue(node));
+ }
+ return values.size() - beforeIndex;
+ }
+
+ /** Consumes the if condition and return the size of the values resulting, for convenience */
+ private int consumeIfCondition(ExpressionNode condition, List<Double> values, AbstractArrayContext context) {
+ if (condition instanceof ComparisonNode) {
+ ComparisonNode comparison = (ComparisonNode)condition;
+ if (comparison.getOperator() == TruthOperator.SMALLER)
+ values.add(GBDTNode.MAX_LEAF_VALUE + GBDTNode.MAX_VARIABLES*0 + getVariableIndex(comparison.getLeftCondition(), context));
+ else if (comparison.getOperator() == TruthOperator.EQUAL)
+ values.add(GBDTNode.MAX_LEAF_VALUE + GBDTNode.MAX_VARIABLES*1 + getVariableIndex(comparison.getLeftCondition(), context));
+ else
+ throw new IllegalArgumentException("Cannot optimize other conditions than < and ==, encountered: " + comparison.getOperator());
+ values.add(toValue(comparison.getRightCondition()));
+ }
+ else if (condition instanceof SetMembershipNode) {
+ SetMembershipNode setMembership = (SetMembershipNode)condition;
+ values.add(GBDTNode.MAX_LEAF_VALUE + GBDTNode.MAX_VARIABLES*2 + getVariableIndex(setMembership.getTestValue(),context));
+ values.add((double)setMembership.getSetValues().size());
+ for (ExpressionNode setElementNode : setMembership.getSetValues())
+ values.add(toValue(setElementNode));
+ }
+ else {
+ throw new IllegalArgumentException("Node condition could not be optimized: " + condition);
+ }
+
+ return values.size();
+ }
+
+ private double getVariableIndex(ExpressionNode node, AbstractArrayContext context) {
+ if (!(node instanceof ReferenceNode)) {
+ throw new IllegalArgumentException("Contained a left-hand comparison expression " +
+ "which was not a feature value but was: " + node);
+ }
+ ReferenceNode fNode = (ReferenceNode)node;
+ Integer index = context.getIndex(fNode.toString());
+ if (index == null) {
+ throw new IllegalStateException("The ranking expression contained feature '" + fNode.getName() +
+ "', which is not known to " + context + ": The context must be created" +
+ "from the same ranking expression which is to be optimized");
+ }
+ return index;
+ }
+
+ private double toValue(ExpressionNode node) {
+ if (node instanceof ConstantNode) {
+ Value value = ((ConstantNode)node).getValue();
+ if (value instanceof DoubleCompatibleValue || value instanceof StringValue)
+ return value.asDouble();
+ else
+ throw new IllegalArgumentException("Cannot optimize a node containing a value of type " +
+ value.getClass().getSimpleName() + " (" + value + ") in a set test: " + node);
+ }
+
+ if (node instanceof NegativeNode) {
+ NegativeNode nNode = (NegativeNode)node;
+ if (!(nNode.getValue() instanceof ConstantNode)) {
+ throw new IllegalArgumentException("Contained a negation of a non-number: " + nNode.getValue());
+ }
+ return -((ConstantNode)nNode.getValue()).getValue().asDouble();
+ }
+ throw new IllegalArgumentException("Node could not be optimized: " + node);
+ }
+
+ private double[] toArray(List<Double> valueList) {
+ double[] valueArray = new double[valueList.size()];
+ for (int i = 0; i < valueList.size(); i++) {
+ valueArray[i] = valueList.get(i);
+ }
+ return valueArray;
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/test/.gitignore b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/test/.gitignore
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/test/.gitignore
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/package-info.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/package-info.java
new file mode 100644
index 00000000000..b744b884e0f
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/package-info.java
@@ -0,0 +1,10 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * Execution engine for ranking expressions
+ */
+@ExportPackage
+@PublicApi
+package com.yahoo.searchlib.rankingexpression.evaluation;
+
+import com.yahoo.api.annotations.PublicApi;
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/mlr/.gitignore b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/mlr/.gitignore
new file mode 100644
index 00000000000..e69de29bb2d
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/mlr/.gitignore
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/package-info.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/package-info.java
new file mode 100644
index 00000000000..95099876eb4
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/package-info.java
@@ -0,0 +1,10 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * Ranking expression execution library, see {@link com.yahoo.searchlib.rankingexpression.RankingExpression}.
+ */
+@ExportPackage
+@PublicApi
+package com.yahoo.searchlib.rankingexpression;
+
+import com.yahoo.api.annotations.PublicApi;
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/parser/package-info.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/parser/package-info.java
new file mode 100644
index 00000000000..01af7c12ae4
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/parser/package-info.java
@@ -0,0 +1,10 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+/**
+ * Ranking expression parser
+ */
+@ExportPackage
+@PublicApi
+package com.yahoo.searchlib.rankingexpression.parser;
+
+import com.yahoo.api.annotations.PublicApi;
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/Arguments.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/Arguments.java
new file mode 100644
index 00000000000..a5d04c0f3b9
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/Arguments.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.rankingexpression.rule;
+
+import com.google.common.collect.ImmutableList;
+import com.yahoo.searchlib.rankingexpression.evaluation.Context;
+import com.yahoo.searchlib.rankingexpression.evaluation.Value;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A set of argument expressions to a function or feature.
+ * This is immutable.
+ *
+ * @author bratseth
+ */
+public final class Arguments implements Serializable {
+
+ private final ImmutableList<ExpressionNode> expressions;
+
+ public Arguments() {
+ this(null);
+ }
+
+ public Arguments(List<? extends ExpressionNode> expressions) {
+ if (expressions == null) {
+ this.expressions = ImmutableList.of();
+ return;
+ }
+
+ // Build in a roundabout way because java generics and lists
+ ImmutableList.Builder<ExpressionNode> b = ImmutableList.builder();
+ for (ExpressionNode node : expressions)
+ b.add(node);
+ this.expressions = b.build();
+ }
+
+ /** Returns an unmodifiable list of the expressions in this */
+ public List<ExpressionNode> expressions() { return expressions; }
+
+ /** Evaluate all arguments in this */
+ public Value[] evaluate(Context context) {
+ Value[] values=new Value[expressions.size()];
+ for (int i=0; i<expressions.size(); i++)
+ values[i]=expressions.get(i).evaluate(context);
+ return values;
+ }
+
+ /** Evaluate the i'th argument */
+ public Value evaluate(int i,Context context) {
+ return expressions.get(i).evaluate(context);
+ }
+
+ public boolean isEmpty() { return expressions.isEmpty(); }
+
+ @Override
+ public int hashCode() {
+ return expressions.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object rhs) {
+ return rhs instanceof Arguments && expressions.equals(((Arguments)rhs).expressions);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder b = new StringBuilder();
+ b.append("(");
+ for (ExpressionNode argument : expressions)
+ b.append(argument).append(",");
+ b.setLength(b.length()-1);
+ if (b.length() > 0)
+ b.append(")");
+ return b.toString();
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/ArithmeticNode.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/ArithmeticNode.java
new file mode 100755
index 00000000000..c6669d87d1b
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/ArithmeticNode.java
@@ -0,0 +1,129 @@
+// 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 com.google.common.collect.ImmutableList;
+import com.yahoo.searchlib.rankingexpression.evaluation.Context;
+import com.yahoo.searchlib.rankingexpression.evaluation.Value;
+
+import java.util.*;
+
+/**
+ * A binary mathematical operation
+ *
+ * @author bratseth
+ */
+public final class ArithmeticNode extends CompositeNode {
+
+ private final ImmutableList<ExpressionNode> children;
+ private final ImmutableList<ArithmeticOperator> operators;
+
+ public ArithmeticNode(List<ExpressionNode> children, List<ArithmeticOperator> operators) {
+ this.children = ImmutableList.copyOf(children);
+ this.operators = ImmutableList.copyOf(operators);
+ }
+
+ public ArithmeticNode(ExpressionNode leftExpression, ArithmeticOperator operator, ExpressionNode rightExpression) {
+ this.children = ImmutableList.of(leftExpression, rightExpression);
+ this.operators = ImmutableList.of(operator);
+ }
+
+ public List<ArithmeticOperator> operators() { return operators; }
+
+ @Override
+ public List<ExpressionNode> children() { return children; }
+
+ @Override
+ public String toString(SerializationContext context, Deque<String> path, CompositeNode parent) {
+ StringBuilder string = new StringBuilder();
+
+ boolean nonDefaultPrecedence = nonDefaultPrecedence(parent);
+ if (nonDefaultPrecedence)
+ string.append("(");
+
+ Iterator<ExpressionNode> child = children.iterator();
+ string.append(child.next().toString(context, path, this)).append(" ");
+ for (Iterator<ArithmeticOperator> op = operators.iterator(); op.hasNext() && child.hasNext();) {
+ string.append(op.next().toString()).append(" ");
+ string.append(child.next().toString(context, path, this));
+ if (op.hasNext())
+ string.append(" ");
+ }
+ if (nonDefaultPrecedence)
+ string.append(")");
+ string.append(" ");
+
+ return string.toString().trim();
+ }
+
+ /**
+ * Returns true if this node has lower precedence than the parent
+ * (even though by virtue of being a node it will be calculated before the parent).
+ */
+ private boolean nonDefaultPrecedence(CompositeNode parent) {
+ if ( parent==null) return false;
+ if ( ! (parent instanceof ArithmeticNode)) return false;
+
+ return ((ArithmeticNode)parent).operators.get(0).hasPrecedenceOver(this.operators.get(0));
+ }
+
+ @Override
+ public Value evaluate(Context context) {
+ Iterator<ExpressionNode> child = children.iterator();
+
+ Deque<ValueItem> stack = new ArrayDeque<>();
+ stack.push(new ValueItem(ArithmeticOperator.PLUS, child.next().evaluate(context)));
+ for (Iterator<ArithmeticOperator> it = operators.iterator(); it.hasNext() && child.hasNext();) {
+ ArithmeticOperator op = it.next();
+ if (!stack.isEmpty()) {
+ while (stack.peek().op.hasPrecedenceOver(op)) {
+ popStack(stack);
+ }
+ }
+ stack.push(new ValueItem(op, child.next().evaluate(context)));
+ }
+ while (stack.size() > 1) {
+ popStack(stack);
+ }
+ return stack.getFirst().value;
+ }
+
+ private void popStack(Deque<ValueItem> stack) {
+ ValueItem rhs = stack.pop();
+ ValueItem lhs = stack.peek();
+ lhs.value = rhs.op.evaluate(lhs.value, rhs.value);
+ }
+
+ public static ArithmeticNode resolve(ExpressionNode left, ArithmeticOperator op, ExpressionNode right) {
+ if ( ! (left instanceof ArithmeticNode)) return new ArithmeticNode(left, op, right);
+
+ ArithmeticNode leftArithmetic = (ArithmeticNode)left;
+
+ List<ExpressionNode> newChildren = new ArrayList<>(leftArithmetic.children());
+ newChildren.add(right);
+
+ List<ArithmeticOperator> newOperators = new ArrayList<>(leftArithmetic.operators());
+ newOperators.add(op);
+
+ return new ArithmeticNode(newChildren, newOperators);
+ }
+
+ private static class ValueItem {
+
+ final ArithmeticOperator op;
+ Value value;
+
+ public ValueItem(ArithmeticOperator op, Value value) {
+ this.op = op;
+ this.value = value;
+ }
+ }
+
+ @Override
+ public CompositeNode setChildren(List<ExpressionNode> newChildren) {
+ if (children.size() != newChildren.size())
+ throw new IllegalArgumentException("Expected " + children.size() + " children but got " + newChildren.size());
+ return new ArithmeticNode(newChildren, operators);
+ }
+
+}
+
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/ArithmeticOperator.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/ArithmeticOperator.java
new file mode 100644
index 00000000000..e5a794ab53e
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/ArithmeticOperator.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.rankingexpression.rule;
+
+import com.yahoo.searchlib.rankingexpression.evaluation.Value;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A mathematical operator
+ *
+ * @author bratseth
+ */
+public enum ArithmeticOperator {
+
+ PLUS(0, "+") { public Value evaluate(Value x, Value y) {
+ return x.add(y);
+ }},
+ MINUS(1, "-") { public Value evaluate(Value x, Value y) {
+ return x.subtract(y);
+ }},
+ MULTIPLY(2, "*") { public Value evaluate(Value x, Value y) {
+ return x.multiply(y);
+ }},
+ DIVIDE(3, "/") { public Value evaluate(Value x, Value y) {
+ return x.divide(y);
+ }};
+
+ /** A list of all the operators in this in order of decreasing precedence */
+ public static final List<ArithmeticOperator> operatorsByPrecedence = operatorsByPrecedence();
+
+ private final int precedence;
+ private final String image;
+
+ private ArithmeticOperator(int precedence, String image) {
+ this.precedence = precedence;
+ this.image = image;
+ }
+
+ /** Returns true if this operator has precedence over the given operator */
+ public boolean hasPrecedenceOver(ArithmeticOperator op) {
+ return precedence > op.precedence;
+ }
+
+ public abstract Value evaluate(Value x, Value y);
+
+ @Override
+ public String toString() {
+ return image;
+ }
+
+ private static List<ArithmeticOperator> operatorsByPrecedence() {
+ List<ArithmeticOperator> operators = new ArrayList<>();
+ operators.add(DIVIDE);
+ operators.add(MULTIPLY);
+ operators.add(MINUS);
+ operators.add(PLUS);
+ return Collections.unmodifiableList(operators);
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/BooleanNode.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/BooleanNode.java
new file mode 100755
index 00000000000..22b777d4b9d
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/BooleanNode.java
@@ -0,0 +1,11 @@
+// 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;
+
+/**
+ * A node which produces a boolean value when evaluated.
+ *
+ * @author bratseth
+ * @since 5.1.21
+ */
+public abstract class BooleanNode extends CompositeNode {
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/ComparisonNode.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/ComparisonNode.java
new file mode 100644
index 00000000000..882d16ebc1c
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/ComparisonNode.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.rankingexpression.rule;
+
+import com.yahoo.searchlib.rankingexpression.evaluation.BooleanValue;
+import com.yahoo.searchlib.rankingexpression.evaluation.Context;
+import com.yahoo.searchlib.rankingexpression.evaluation.Value;
+
+import java.util.*;
+
+/**
+ * A node which returns true or false depending on the outcome of a comparison.
+ *
+ * @author bratseth
+ * @since 5.1.21
+ */
+public class ComparisonNode extends BooleanNode {
+
+ /** The operator string of this condition. */
+ private final TruthOperator operator;
+
+ private final ExpressionNode leftCondition, rightCondition;
+
+ public ComparisonNode(ExpressionNode leftCondition, TruthOperator operator, ExpressionNode rightCondition) {
+ this.leftCondition = leftCondition;
+ this.operator = operator;
+ this.rightCondition = rightCondition;
+ }
+
+ @Override
+ public List<ExpressionNode> children() {
+ List<ExpressionNode> children = new ArrayList<>(2);
+ children.add(leftCondition);
+ children.add(rightCondition);
+ return children;
+ }
+
+ public TruthOperator getOperator() { return operator; }
+
+ public ExpressionNode getLeftCondition() { return leftCondition; }
+
+ public ExpressionNode getRightCondition() { return rightCondition; }
+
+ @Override
+ public String toString(SerializationContext context, Deque<String> path, CompositeNode parent) {
+ return leftCondition.toString(context, path, this) + " " + operator + " " +
+ rightCondition.toString(context, path, this);
+ }
+
+ @Override
+ public Value evaluate(Context context) {
+ Value leftValue=leftCondition.evaluate(context);
+ Value rightValue=rightCondition.evaluate(context);
+ return new BooleanValue(leftValue.compare(operator,rightValue));
+ }
+
+ @Override
+ public ComparisonNode setChildren(List<ExpressionNode> children) {
+ if (children.size() != 2) throw new IllegalArgumentException("A comparison test must have 2 children");
+ return new ComparisonNode(children.get(0), operator, children.get(1));
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/CompositeNode.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/CompositeNode.java
new file mode 100644
index 00000000000..d181c29b516
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/CompositeNode.java
@@ -0,0 +1,27 @@
+// 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 java.util.List;
+
+/**
+ * <p>The parent of all node types which contains child nodes.</p>
+ *
+ * @author bratseth
+ */
+public abstract class CompositeNode extends ExpressionNode {
+
+ /**
+ * <p>Returns a read-only list containing the immediate children of this composite</p>
+ *
+ * @return The children of this.
+ */
+ public abstract List<ExpressionNode> children();
+
+ /**
+ * Returns a copy of this where the children is replaced by the given children.
+ *
+ * @throws IllegalArgumentException if the given list of children has different size than children()
+ */
+ public abstract CompositeNode setChildren(List<ExpressionNode> children);
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/ConstantNode.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/ConstantNode.java
new file mode 100755
index 00000000000..e51519059ed
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/ConstantNode.java
@@ -0,0 +1,54 @@
+// 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 com.yahoo.searchlib.rankingexpression.evaluation.Context;
+import com.yahoo.searchlib.rankingexpression.evaluation.Value;
+
+import java.util.Deque;
+
+/**
+ * A node which holds a constant (frozen) value.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public final class ConstantNode extends ExpressionNode {
+
+ private final String sourceImage;
+
+ private final Value value;
+
+ public ConstantNode(Value value) {
+ this(value,null);
+ }
+
+ /**
+ * Creates a constant value
+ *
+ * @param value the value. Ownership of this value is transferred to this.
+ * @param sourceImage the source string image producing this value
+ */
+ public ConstantNode(Value value, String sourceImage) {
+ value.freeze();
+ this.value=value;
+ this.sourceImage=sourceImage;
+ }
+
+ public Value getValue() { return value; }
+
+ @Override
+ public String toString(SerializationContext context, Deque<String> path, CompositeNode parent) {
+ return sourceString();
+ }
+
+ /** Returns the string which created this, or the value.toString() if not known */
+ public String sourceString() {
+ if (sourceImage != null) return sourceImage;
+ return value.toString();
+ }
+
+ @Override
+ public Value evaluate(Context context) {
+ return value;
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/EmbracedNode.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/EmbracedNode.java
new file mode 100755
index 00000000000..7e9e1cb2825
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/EmbracedNode.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.rankingexpression.rule;
+
+import com.yahoo.searchlib.rankingexpression.evaluation.Context;
+import com.yahoo.searchlib.rankingexpression.evaluation.Value;
+
+import java.util.Collections;
+import java.util.Deque;
+import java.util.List;
+
+/**
+ * This class represents another expression enclosed in braces.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public final class EmbracedNode extends CompositeNode {
+
+ // The node to embrace.
+ private final ExpressionNode value;
+
+ /**
+ * Creates a new expression node that embraces another.
+ *
+ * @param value The node to embrace.
+ */
+ public EmbracedNode(ExpressionNode value) {
+ this.value=value;
+ }
+
+ /** Returns the node enclosed by this */
+ public ExpressionNode getValue() { return value; }
+
+ @Override
+ public List<ExpressionNode> children() {
+ return Collections.singletonList(value);
+ }
+
+ @Override
+ public String toString(SerializationContext context, Deque<String> path, CompositeNode parent) {
+ String expression = value.toString(context, path, this);
+ if (value instanceof ReferenceNode) return expression;
+ return "(" + expression + ")";
+ }
+
+ @Override
+ public Value evaluate(Context context) {
+ return value.evaluate(context);
+ }
+
+ @Override
+ public CompositeNode setChildren(List<ExpressionNode> newChildren) {
+ if (newChildren.size() != 1)
+ throw new IllegalArgumentException("Expected 1 child but got " + newChildren.size());
+ return new EmbracedNode(newChildren.get(0));
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/ExpressionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/ExpressionNode.java
new file mode 100755
index 00000000000..05d998afd35
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/ExpressionNode.java
@@ -0,0 +1,51 @@
+// 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 com.yahoo.searchlib.rankingexpression.evaluation.Context;
+import com.yahoo.searchlib.rankingexpression.evaluation.Value;
+
+import java.io.Serializable;
+import java.util.Deque;
+
+/**
+ * Superclass of all expression nodes. Expression nodes have their identity determined by their content.
+ * All expression nodes are immutable.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public abstract class ExpressionNode implements Serializable {
+
+ @Override
+ public final int hashCode() {
+ return toString().hashCode();
+ }
+
+ @Override
+ public final boolean equals(Object obj) {
+ return obj instanceof ExpressionNode && toString().equals(obj.toString());
+ }
+
+ @Override
+ public final String toString() {
+ return toString(new SerializationContext(), null, null);
+ }
+
+ /**
+ * Returns a script instance of this based on the supplied script functions.
+ *
+ * @param context the serialization context
+ * @param path the call path to this, used for cycle detection, or null if this is a root
+ * @param parent the parent node of this, or null if it a root
+ * @return the main script, referring to script instances.
+ */
+ public abstract String toString(SerializationContext context, Deque<String> path, CompositeNode parent);
+
+ /**
+ * Returns the value of evaluating this expression over the given context.
+ *
+ * @param context the variable bindings to use for this evaluation
+ * @throws IllegalArgumentException if there are variables which are not bound in the given map
+ */
+ public abstract Value evaluate(Context context);
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/Function.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/Function.java
new file mode 100644
index 00000000000..ecd8182a108
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/Function.java
@@ -0,0 +1,55 @@
+// 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 java.io.Serializable;
+
+import static java.lang.Math.*;
+
+/**
+ * A scalar function
+ *
+ * @author bratseth
+ */
+public enum Function implements Serializable {
+
+ cosh { public double evaluate(double x, double y) { return cosh(x); } },
+ sinh { public double evaluate(double x, double y) { return sinh(x); } },
+ tanh { public double evaluate(double x, double y) { return tanh(x); } },
+ cos { public double evaluate(double x, double y) { return cos(x); } },
+ sin { public double evaluate(double x, double y) { return sin(x); } },
+ tan { public double evaluate(double x, double y) { return tan(x); } },
+ acos { public double evaluate(double x, double y) { return acos(x); } },
+ asin { public double evaluate(double x, double y) { return asin(x); } },
+ atan { public double evaluate(double x, double y) { return atan(x); } },
+ exp { public double evaluate(double x, double y) { return exp(x); } },
+ log10 { public double evaluate(double x, double y) { return log10(x); } },
+ log { public double evaluate(double x, double y) { return log(x); } },
+ sqrt { public double evaluate(double x, double y) { return sqrt(x); } },
+ ceil { public double evaluate(double x, double y) { return ceil(x); } },
+ fabs { public double evaluate(double x, double y) { return abs(x); } },
+ floor { public double evaluate(double x, double y) { return floor(x); } },
+ isNan { public double evaluate(double x, double y) { return Double.isNaN(x) ? 1.0 : 0.0; } },
+ atan2(2) { public double evaluate(double x, double y) { return atan2(x,y); } },
+ pow(2) { public double evaluate(double x, double y) { return pow(x,y); } },
+ ldexp(2) { public double evaluate(double x, double y) { return x*pow(2,y); } },
+ fmod(2) { public double evaluate(double x, double y) { return IEEEremainder(x,y); } },
+ min(2) { public double evaluate(double x, double y) { return min(x,y); } },
+ max(2) { public double evaluate(double x, double y) { return max(x,y); } };
+
+ private final int arity;
+
+ private Function() {
+ this(1);
+ }
+
+ private Function(int arity) {
+ this.arity = arity;
+ }
+
+ /** Perform the function on the input */
+ public abstract double evaluate(double x, double y);
+
+ /** Returns the number of arguments this function takes */
+ public int arity() { return arity; }
+
+} \ No newline at end of file
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/FunctionNode.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/FunctionNode.java
new file mode 100755
index 00000000000..8ab403bff7a
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/FunctionNode.java
@@ -0,0 +1,90 @@
+// 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 com.yahoo.searchlib.rankingexpression.evaluation.Context;
+import com.yahoo.searchlib.rankingexpression.evaluation.DoubleValue;
+import com.yahoo.searchlib.rankingexpression.evaluation.Value;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Deque;
+import java.util.List;
+
+/**
+ * Invocation of a native function.
+ *
+ * @author simon
+ * @author bratseth
+ */
+public final class FunctionNode extends CompositeNode {
+
+ /** The type of function. */
+ private final Function function;
+
+ /** The arguments to this function. */
+ private final Arguments arguments;
+
+ /* Creates an unary function node */
+ public FunctionNode(Function function, ExpressionNode argument) {
+ if (function.arity() != 1) throw new IllegalArgumentException(function + " is not unary");
+ this.function = function;
+ this.arguments = new Arguments(Collections.singletonList(argument));
+ }
+
+ /** Creates a binary function node */
+ public FunctionNode(Function function, ExpressionNode argument1, ExpressionNode argument2) {
+ if (function.arity() != 2) throw new IllegalArgumentException(function + " is not binary");
+ this.function = function;
+ List<ExpressionNode> argumentList = new ArrayList<>();
+ argumentList.add(argument1);
+ argumentList.add(argument2);
+ arguments=new Arguments(argumentList);
+ }
+
+ public Function getFunction() { return function; }
+
+ /** Returns the arguments of this */
+ @Override
+ public List<ExpressionNode> children() {
+ return arguments.expressions();
+ }
+
+ @Override
+ public String toString(SerializationContext context, Deque<String> path, CompositeNode parent) {
+ StringBuilder b=new StringBuilder(function.toString());
+ b.append("(");
+ for (int i = 0; i < this.arguments.expressions().size(); ++i) {
+ b.append(this.arguments.expressions().get(i).toString(context, path, this));
+ if (i < this.arguments.expressions().size() - 1) {
+ b.append(",");
+ }
+ }
+ b.append(")");
+ return b.toString();
+ }
+
+ @Override
+ public Value evaluate(Context context) {
+ if (arguments.expressions().size() == 0)
+ return DoubleValue.zero.function(function,DoubleValue.zero);
+
+ Value argument1 = arguments.expressions().get(0).evaluate(context);
+ if (arguments.expressions().size() == 1)
+ return argument1.function(function, DoubleValue.zero);
+
+ Value argument2 = arguments.expressions().get(1).evaluate(context);
+ return argument1.function(function,argument2);
+ }
+
+ /** Returns a new function node with the children replaced by the given children */
+ @Override
+ public FunctionNode setChildren(List<ExpressionNode> children) {
+ if (arguments.expressions().size() != children.size())
+ throw new IllegalArgumentException("Expected " + arguments.expressions().size() + " children but got " + children.size());
+ if (children.size() == 1)
+ return new FunctionNode(function, children.get(0));
+ else // binary
+ return new FunctionNode(function, children.get(0), children.get(1));
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/IfNode.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/IfNode.java
new file mode 100755
index 00000000000..994c3db9bac
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/IfNode.java
@@ -0,0 +1,86 @@
+// 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 com.yahoo.searchlib.rankingexpression.evaluation.Context;
+import com.yahoo.searchlib.rankingexpression.evaluation.Value;
+
+import java.util.*;
+
+/**
+ * A conditional branch of a ranking expression.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ * @author bratseth
+ */
+public final class IfNode extends CompositeNode {
+
+ /** The expression nodes that make up this condition. */
+ private final ExpressionNode condition, trueExpression, falseExpression;
+
+ private final Double trueProbability;
+
+ public IfNode(ExpressionNode condition, ExpressionNode trueExpression, ExpressionNode falseExpression) {
+ this(condition, trueExpression, falseExpression, null);
+ }
+
+ /**
+ * Creates a new condition node.
+ *
+ * @param condition the condition of this
+ * @param trueExpression the expression to evaluate if the comparison is true
+ * @param falseExpression the expression to evaluate if the comparison is false
+ * @param trueProbability the probability that the condition will evaluate to true, or null if not known.
+ * @throws IllegalArgumentException if trueProbability is non-null and not between 0.0 and 1.0
+ */
+ public IfNode(ExpressionNode condition, ExpressionNode trueExpression, ExpressionNode falseExpression,
+ Double trueProbability) {
+ if (trueProbability != null && ( trueProbability < 0.0 || trueProbability > 1.0) )
+ throw new IllegalArgumentException("trueProbability must be a between 0.0 and 1.0, not " + trueProbability);
+ this.condition = condition;
+ this.trueProbability = trueProbability;
+ this.trueExpression = trueExpression;
+ this.falseExpression = falseExpression;
+ }
+
+ @Override
+ public List<ExpressionNode> children() {
+ List<ExpressionNode> children = new ArrayList<ExpressionNode>(4);
+ children.add(condition);
+ children.add(trueExpression);
+ children.add(falseExpression);
+ return Collections.unmodifiableList(children);
+ }
+
+ public ExpressionNode getCondition() { return condition; }
+
+ public ExpressionNode getTrueExpression() { return trueExpression; }
+
+ public ExpressionNode getFalseExpression() { return falseExpression; }
+
+ /** The average probability that the condition of this node will evaluate to true, or null if not known */
+ public Double getTrueProbability() { return trueProbability; }
+
+ @Override
+ public String toString(SerializationContext context, Deque<String> path, CompositeNode parent) {
+ return "if (" +
+ condition.toString(context, path, this) + ", " +
+ trueExpression.toString(context, path, this) + ", " +
+ falseExpression.toString(context, path, this) +
+ (trueProbability != null ? ", " + trueProbability : "") + ")";
+ }
+
+ @Override
+ public Value evaluate(Context context) {
+ if (condition.evaluate(context).asBoolean())
+ return trueExpression.evaluate(context);
+ else
+ return falseExpression.evaluate(context);
+ }
+
+ @Override
+ public IfNode setChildren(List<ExpressionNode> children) {
+ if (children.size() != 3) throw new IllegalArgumentException("Expected 3 children but got " + children.size());
+ return new IfNode(children.get(0), children.get(1), children.get(2));
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/NameNode.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/NameNode.java
new file mode 100755
index 00000000000..eee729fa3a8
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/NameNode.java
@@ -0,0 +1,37 @@
+// 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 com.yahoo.searchlib.rankingexpression.evaluation.Context;
+import com.yahoo.searchlib.rankingexpression.evaluation.Value;
+
+import java.util.Deque;
+
+/**
+ * An opaque name in a ranking expression. This is used to represent names passed to the context
+ * and interpreted by the given context in a way which is opaque to the ranking expressions.
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public final class NameNode extends ExpressionNode {
+
+ private final String name;
+
+ public NameNode(String name) {
+ this.name = name;
+ }
+
+ public String getValue() {
+ return name;
+ }
+
+ @Override
+ public String toString(SerializationContext context, Deque<String> path, CompositeNode parent) {
+ return name;
+ }
+
+ @Override
+ public Value evaluate(Context context) {
+ throw new RuntimeException("Name nodes should never be evaluated");
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/NegativeNode.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/NegativeNode.java
new file mode 100644
index 00000000000..11feddb919e
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/NegativeNode.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.rule;
+
+import com.yahoo.searchlib.rankingexpression.evaluation.Context;
+import com.yahoo.searchlib.rankingexpression.evaluation.Value;
+
+import java.util.Collections;
+import java.util.Deque;
+import java.util.List;
+
+/**
+ * A node which flips the sign of the value produced from the nested expression
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class NegativeNode extends CompositeNode {
+
+ private final ExpressionNode value;
+
+ /** Constructs a new negative node */
+ public NegativeNode(ExpressionNode value) {
+ this.value = value;
+ }
+
+ /** Returns the node creating the value negated by this */
+ public ExpressionNode getValue() { return value; }
+
+ @Override
+ public List<ExpressionNode> children() {
+ return Collections.singletonList(value);
+ }
+
+ @Override
+ public String toString(SerializationContext context, Deque<String> path, CompositeNode parent) {
+ return "-" + value.toString(context, path, parent);
+ }
+
+ @Override
+ public Value evaluate(Context context) {
+ return value.evaluate(context).negate();
+ }
+
+ @Override
+ public NegativeNode setChildren(List<ExpressionNode> children) {
+ if (children.size() != 1) throw new IllegalArgumentException("Expected 1 children but got " + children.size());
+ return new NegativeNode(children.get(0));
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/ReferenceNode.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/ReferenceNode.java
new file mode 100755
index 00000000000..2968b414cb8
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/ReferenceNode.java
@@ -0,0 +1,119 @@
+// 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 com.yahoo.searchlib.rankingexpression.ExpressionFunction;
+import com.yahoo.searchlib.rankingexpression.RankingExpression;
+import com.yahoo.searchlib.rankingexpression.evaluation.Context;
+import com.yahoo.searchlib.rankingexpression.evaluation.Value;
+
+import java.util.ArrayDeque;
+import java.util.Deque;
+import java.util.List;
+
+/**
+ * A node referring either to a value in the context or to another named ranking expression.
+ *
+ * @author simon
+ * @author bratseth
+ */
+public final class ReferenceNode extends CompositeNode {
+
+ private final String name, output;
+
+ private final Arguments arguments;
+
+ public ReferenceNode(String name) {
+ this(name, null, null);
+ }
+
+ public ReferenceNode(String name, List<? extends ExpressionNode> arguments, String output) {
+ this.name = name;
+ this.arguments = arguments != null ? new Arguments(arguments) : new Arguments();
+ this.output = output;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ /** Returns the arguments, never null */
+ public Arguments getArguments() { return arguments; }
+
+ /** Returns a copy of this where the arguments are replaced by the given arguments */
+ public ReferenceNode setArguments(List<ExpressionNode> arguments) {
+ return new ReferenceNode(name, arguments, output);
+ }
+
+ public String getOutput() {
+ return output;
+ }
+
+ /** Returns a copy of this node with a modified output */
+ public ReferenceNode setOutput(String output) {
+ return new ReferenceNode(name, arguments.expressions(), output);
+ }
+
+ /** Returns an empty list as this has no children */
+ @Override
+ public List<ExpressionNode> children() { return arguments.expressions(); }
+
+ @Override
+ public String toString(SerializationContext context, Deque<String> path, CompositeNode parent) {
+ if (path == null)
+ path = new ArrayDeque<>();
+ String myName = this.name;
+ String myOutput = this.output;
+ List<ExpressionNode> myArguments = this.arguments.expressions();
+
+ String resolvedArgument = context.getBinding(myName);
+ if (resolvedArgument != null && this.arguments.expressions().size() == 0 && myOutput == null) {
+ // Replace this whole node with the value of the argument value that it maps to
+ myName = resolvedArgument;
+ myArguments = null;
+ myOutput = null;
+ } else if (context.getFunction(myName) != null) {
+ // Replace this whole node with a reference to another script.
+ ExpressionFunction function = context.getFunction(myName);
+ if (function != null && myArguments != null && function.arguments().size() == myArguments.size() && myOutput == null) {
+ String myPath = name + this.arguments.expressions();
+ if (path.contains(myPath)) {
+ throw new IllegalStateException("Cycle in ranking expression function: " + path);
+ }
+ path.addLast(myPath);
+ ExpressionFunction.Instance instance = function.expand(context, myArguments, path);
+ path.removeLast();
+ context.addFunctionSerialization(RankingExpression.propertyName(instance.getName()), instance.getExpressionString());
+ myName = "rankingExpression(" + instance.getName() + ")";
+ myArguments = null;
+ myOutput = null;
+ }
+ }
+ // Always print the same way, the magic is already done.
+ StringBuilder ret = new StringBuilder(myName);
+ if (myArguments != null && myArguments.size() > 0) {
+ ret.append("(");
+ for (int i = 0; i < myArguments.size(); ++i) {
+ ret.append(myArguments.get(i).toString(context, path, this));
+ if (i < myArguments.size() - 1) {
+ ret.append(",");
+ }
+ }
+ ret.append(")");
+ }
+ ret.append(myOutput != null ? "." + myOutput : "");
+ return ret.toString();
+ }
+
+ @Override
+ public Value evaluate(Context context) {
+ if (arguments.expressions().size()==0 && output==null)
+ return context.get(name);
+ return context.get(name, arguments, output);
+ }
+
+ @Override
+ public CompositeNode setChildren(List<ExpressionNode> newChildren) {
+ return new ReferenceNode(name, newChildren, output);
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/SerializationContext.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/SerializationContext.java
new file mode 100644
index 00000000000..8ea0a886b65
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/SerializationContext.java
@@ -0,0 +1,116 @@
+// 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 com.google.common.collect.ImmutableMap;
+import com.yahoo.searchlib.rankingexpression.ExpressionFunction;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Context needed to serialize an expression to a string. This has the lifetime of a single serialization
+ *
+ * @author bratseth
+ */
+public class SerializationContext {
+
+ /** Expression functions indexed by name */
+ private final ImmutableMap<String, ExpressionFunction> functions;
+
+ /** A cache of already serialized expressions indexed by name */
+ private final Map<String, String> serializedFunctions;
+
+ /** Mapping from argument names to the expressions they resolve to */
+ public final Map<String, String> bindings = new HashMap<>();
+
+ /** Create a context for a single serialization task */
+ public SerializationContext() {
+ this(Collections.emptyList());
+ }
+
+ /** Create a context for a single serialization task */
+ public SerializationContext(Collection<ExpressionFunction> functions) {
+ this(functions, Collections.emptyMap(), new LinkedHashMap<>());
+ }
+
+ /** Create a context for a single serialization task */
+ public SerializationContext(Map<String, ExpressionFunction> functions) {
+ this(functions.values());
+ }
+
+ /** Create a context for a single serialization task */
+ public SerializationContext(List<ExpressionFunction> functions, Map<String, String> bindings) {
+ this(functions, bindings, new LinkedHashMap<>());
+ }
+
+ /**
+ * Create a context for a single serialization task
+ *
+ * @param functions the functions of this
+ * @param bindings the arguments of this
+ * @param serializedFunctions a cache of serializedFunctions - the ownership of this map
+ * is <b>transferred</b> to this and will be modified in it
+ */
+ public SerializationContext(Collection<ExpressionFunction> functions, Map<String, String> bindings,
+ Map<String, String> serializedFunctions) {
+ this(toMap(functions), bindings, serializedFunctions);
+ }
+
+ private static ImmutableMap<String, ExpressionFunction> toMap(Collection<ExpressionFunction> list) {
+ ImmutableMap.Builder<String,ExpressionFunction> mapBuilder = new ImmutableMap.Builder<>();
+ for (ExpressionFunction function : list)
+ mapBuilder.put(function.getName(), function);
+ return mapBuilder.build();
+ }
+
+ /**
+ * Create a context for a single serialization task
+ *
+ * @param functions the functions of this
+ * @param bindings the arguments of this
+ * @param serializedFunctions a cache of serializedFunctions - the ownership of this map
+ * is <b>transferred</b> to this and will be modified in it
+ */
+ public SerializationContext(ImmutableMap<String,ExpressionFunction> functions, Map<String, String> bindings,
+ Map<String, String> serializedFunctions) {
+ this.functions = functions;
+ this.serializedFunctions = serializedFunctions;
+ if (bindings != null)
+ this.bindings.putAll(bindings);
+ }
+
+ /**
+ * Returns a function or null if it isn't defined in this context
+ */
+ public ExpressionFunction getFunction(String name) { return functions.get(name); }
+
+ /** Adds the serialization of a function */
+ public void addFunctionSerialization(String name, String expressionString) {
+ serializedFunctions.put(name, expressionString);
+ }
+
+ /** Returns the existing serialization of a function, or null if none */
+ public String getFunctionSerialization(String name) {
+ return serializedFunctions.get(name);
+ }
+
+ /**
+ * Returns the resolution of an argument, or null if it isn't defined in this context
+ */
+ public String getBinding(String name) { return bindings.get(name); }
+
+ /**
+ * Returns a new context which shares the functions and serialized function map with this but has different
+ * arguments.
+ */
+ public SerializationContext createBinding(Map<String, String> arguments) {
+ return new SerializationContext(this.functions, arguments, this.serializedFunctions);
+ }
+
+ public Map<String, String> serializedFunctions() { return serializedFunctions; }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/SetMembershipNode.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/SetMembershipNode.java
new file mode 100644
index 00000000000..bb3b028f696
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/SetMembershipNode.java
@@ -0,0 +1,72 @@
+// 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 com.google.common.collect.ImmutableList;
+import com.yahoo.searchlib.rankingexpression.evaluation.BooleanValue;
+import com.yahoo.searchlib.rankingexpression.evaluation.Context;
+import com.yahoo.searchlib.rankingexpression.evaluation.Value;
+
+import java.util.*;
+
+/**
+ * A node which returns true or false depending on a set membership test
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ * @since 5.1.21
+ */
+public class SetMembershipNode extends BooleanNode {
+
+ private final ExpressionNode testValue;
+
+ private final ImmutableList<ExpressionNode> setValues;
+
+ public SetMembershipNode(ExpressionNode testValue, List<ExpressionNode> setValues) {
+ this.testValue = testValue;
+ this.setValues = ImmutableList.copyOf(setValues);
+ }
+
+ /** The value to check for membership in the set */
+ public ExpressionNode getTestValue() { return testValue; }
+
+ /** Returns an immutable list of the values of the set */
+ public List<ExpressionNode> getSetValues() { return setValues; }
+
+ @Override
+ public List<ExpressionNode> children() {
+ ArrayList<ExpressionNode> children = new ArrayList<>();
+ children.add(testValue);
+ children.addAll(setValues);
+ return children;
+ }
+
+ @Override
+ public String toString(SerializationContext context, Deque<String> path, CompositeNode parent) {
+ StringBuilder b = new StringBuilder(testValue.toString(context, path, this));
+ b.append(" in [");
+ for (int i = 0, len = setValues.size(); i < len; ++i) {
+ b.append(setValues.get(i).toString(context, path, this));
+ if (i < len - 1) {
+ b.append(", ");
+ }
+ }
+ b.append("]");
+ return b.toString();
+ }
+
+ @Override
+ public Value evaluate(Context context) {
+ Value value = testValue.evaluate(context);
+ for (ExpressionNode setValue : setValues) {
+ if (setValue.evaluate(context).equals(value))
+ return new BooleanValue(true);
+ }
+ return new BooleanValue(false);
+ }
+
+ @Override
+ public SetMembershipNode setChildren(List<ExpressionNode> children) {
+ if (children.size()<1) throw new IllegalArgumentException("A set membership test must have at least 1 child");
+ return new SetMembershipNode(children.get(0), children.subList(1, children.size()));
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/TensorMatchNode.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/TensorMatchNode.java
new file mode 100644
index 00000000000..af309b3e8d8
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/TensorMatchNode.java
@@ -0,0 +1,59 @@
+// 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 com.google.common.annotations.Beta;
+import com.yahoo.searchlib.rankingexpression.evaluation.Context;
+import com.yahoo.searchlib.rankingexpression.evaluation.TensorValue;
+import com.yahoo.searchlib.rankingexpression.evaluation.Value;
+
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.List;
+
+/**
+ * @author bratseth
+ */
+ @Beta
+public class TensorMatchNode extends CompositeNode {
+
+ private final ExpressionNode left, right;
+
+ public TensorMatchNode(ExpressionNode left, ExpressionNode right) {
+ this.left = left;
+ this.right = right;
+ }
+
+ @Override
+ public List<ExpressionNode> children() {
+ List<ExpressionNode> children = new ArrayList<>(2);
+ children.add(left);
+ children.add(right);
+ return children;
+ }
+
+ @Override
+ public CompositeNode setChildren(List<ExpressionNode> children) {
+ if ( children.size() != 2)
+ throw new IllegalArgumentException("A match product must have two children");
+ return new TensorMatchNode(children.get(0), children.get(1));
+
+ }
+
+ @Override
+ public String toString(SerializationContext context, Deque<String> path, CompositeNode parent) {
+ return "match(" + left.toString(context, path, parent) + ", " + right.toString(context, path, parent) + ")";
+ }
+
+ @Override
+ public Value evaluate(Context context) {
+ return asTensor(left.evaluate(context)).match(asTensor(right.evaluate(context)));
+ }
+
+ private TensorValue asTensor(Value value) {
+ if ( ! (value instanceof TensorValue))
+ throw new IllegalArgumentException("Attempted to take the tensor product with an argument which is " +
+ "not a tensor: " + value);
+ return (TensorValue)value;
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/TensorSumNode.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/TensorSumNode.java
new file mode 100644
index 00000000000..a1f83157e20
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/TensorSumNode.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.rankingexpression.rule;
+
+import com.google.common.annotations.Beta;
+import com.yahoo.searchlib.rankingexpression.evaluation.Context;
+import com.yahoo.searchlib.rankingexpression.evaluation.TensorValue;
+import com.yahoo.searchlib.rankingexpression.evaluation.Value;
+
+import java.util.Collections;
+import java.util.Deque;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * A node which sums over all cells in the argument tensor
+ *
+ * @author bratseth
+ */
+ @Beta
+public class TensorSumNode extends CompositeNode {
+
+ /** The tensor to sum */
+ private final ExpressionNode argument;
+
+ /** The dimension to sum over, or empty to sum all cells to a scalar */
+ private final Optional<String> dimension;
+
+ public TensorSumNode(ExpressionNode argument, Optional<String> dimension) {
+ this.argument = argument;
+ this.dimension = dimension;
+ }
+
+ @Override
+ public List<ExpressionNode> children() {
+ return Collections.singletonList(argument);
+ }
+
+ @Override
+ public CompositeNode setChildren(List<ExpressionNode> children) {
+ if (children.size() != 1) throw new IllegalArgumentException("A tensor sum node must have one tensor argument");
+ return new TensorSumNode(children.get(0), dimension);
+ }
+
+ @Override
+ public String toString(SerializationContext context, Deque<String> path, CompositeNode parent) {
+ return "sum(" +
+ argument.toString(context, path, parent) +
+ ( dimension.isPresent() ? ", " + dimension.get() : "" ) +
+ ")";
+ }
+
+ @Override
+ public Value evaluate(Context context) {
+ Value argumentValue = argument.evaluate(context);
+ if ( ! ( argumentValue instanceof TensorValue))
+ throw new IllegalArgumentException("Attempted to take the tensor sum of argument '" + argument + "', " +
+ "but this returns " + argumentValue + ", not a tensor");
+ TensorValue tensorArgument = (TensorValue)argumentValue;
+ if (dimension.isPresent())
+ return tensorArgument.sum(dimension.get());
+ else
+ return tensorArgument.sum();
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/TruthOperator.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/TruthOperator.java
new file mode 100644
index 00000000000..26e8b183c21
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/TruthOperator.java
@@ -0,0 +1,48 @@
+// 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 java.io.Serializable;
+
+/**
+ * A mathematical operator
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public enum TruthOperator implements Serializable {
+
+ SMALLER("<") { public boolean evaluate(double x, double y) { return x<y; } },
+ SMALLEREQUAL("<=") { public boolean evaluate(double x, double y) { return x<=y; } },
+ EQUAL("==") { public boolean evaluate(double x, double y) { return x==y; } },
+ APPROX_EQUAL("~=") { public boolean evaluate(double x, double y) { return approxEqual(x,y); } },
+ LARGER(">") { public boolean evaluate(double x, double y) { return x>y; } },
+ LARGEREQUAL(">=") { public boolean evaluate(double x, double y) { return x>=y; } };
+
+ private final String operatorString;
+
+ TruthOperator(String operatorString) {
+ this.operatorString=operatorString;
+ }
+
+ /** Perform the truth operation on the input */
+ public abstract boolean evaluate(double x, double y);
+
+ public @Override String toString() { return operatorString; }
+
+ public static TruthOperator fromString(String string) {
+ for (TruthOperator operator : values())
+ if (operator.toString().equals(string))
+ return operator;
+ throw new IllegalArgumentException("Illegal truth operator '" + string + "'");
+ }
+
+ private static boolean approxEqual(double x,double y) {
+ if (y < -1.0 || y > 1.0) {
+ x = Math.nextAfter(x/y, 1.0);
+ y = 1.0;
+ } else {
+ x = Math.nextAfter(x, y);
+ }
+ return x==y;
+ }
+
+} \ No newline at end of file
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/package-info.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/package-info.java
new file mode 100644
index 00000000000..d6a27aae0f8
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/rule/package-info.java
@@ -0,0 +1,7 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@PublicApi
+@ExportPackage
+package com.yahoo.searchlib.rankingexpression.rule;
+
+import com.yahoo.api.annotations.PublicApi;
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/transform/ConstantDereferencer.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/transform/ConstantDereferencer.java
new file mode 100644
index 00000000000..bd9ad43f155
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/transform/ConstantDereferencer.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.rankingexpression.transform;
+
+import com.yahoo.searchlib.rankingexpression.evaluation.TensorValue;
+import com.yahoo.searchlib.rankingexpression.evaluation.Value;
+import com.yahoo.searchlib.rankingexpression.rule.CompositeNode;
+import com.yahoo.searchlib.rankingexpression.rule.ConstantNode;
+import com.yahoo.searchlib.rankingexpression.rule.ExpressionNode;
+import com.yahoo.searchlib.rankingexpression.rule.ReferenceNode;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Replaces "features" which found in the given constants by their constant value
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class ConstantDereferencer extends ExpressionTransformer {
+
+ /** The map of constants to dereference */
+ private final Map<String, Value> constants;
+
+ public ConstantDereferencer(Map<String, Value> constants) {
+ this.constants = constants;
+ }
+
+ @Override
+ public ExpressionNode transform(ExpressionNode node) {
+ if (node instanceof ReferenceNode)
+ return transformFeature((ReferenceNode) node);
+ else if (node instanceof CompositeNode)
+ return transformChildren((CompositeNode)node);
+ else
+ return node;
+ }
+
+ private ExpressionNode transformFeature(ReferenceNode node) {
+ if (!node.getArguments().isEmpty())
+ return transformArguments(node);
+ else
+ return transformConstantReference(node);
+ }
+
+ private ExpressionNode transformArguments(ReferenceNode node) {
+ List<ExpressionNode> arguments = node.getArguments().expressions();
+ List<ExpressionNode> transformedArguments = new ArrayList<>(arguments.size());
+ for (ExpressionNode argument : arguments)
+ transformedArguments.add(transform(argument));
+ return node.setArguments(transformedArguments);
+ }
+
+ private ExpressionNode transformConstantReference(ReferenceNode node) {
+ Value value = constants.get(node.getName());
+ if (value == null || (value instanceof TensorValue)) {
+ return node; // not a value constant reference
+ }
+ return new ConstantNode(value.freeze());
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/transform/ExpressionTransformer.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/transform/ExpressionTransformer.java
new file mode 100644
index 00000000000..d8995bd8752
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/transform/ExpressionTransformer.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.rankingexpression.transform;
+
+import com.yahoo.searchlib.rankingexpression.RankingExpression;
+import com.yahoo.searchlib.rankingexpression.rule.CompositeNode;
+import com.yahoo.searchlib.rankingexpression.rule.ExpressionNode;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Superclass of expression transformers
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public abstract class ExpressionTransformer {
+
+ public RankingExpression transform(RankingExpression expression) {
+ return new RankingExpression(expression.getName(), transform(expression.getRoot()));
+ }
+
+ /** Transforms an expression node and returns the transformed node */
+ public abstract ExpressionNode transform(ExpressionNode node);
+
+ /**
+ * Utility method which calls transform on each child of the given node and return the resulting transformed
+ * composite
+ */
+ protected CompositeNode transformChildren(CompositeNode node) {
+ List<ExpressionNode> children = node.children();
+ List<ExpressionNode> transformedChildren = new ArrayList<>(children.size());
+ for (ExpressionNode child : children)
+ transformedChildren.add(transform(child));
+ return node.setChildren(transformedChildren);
+ }
+
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/transform/Simplifier.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/transform/Simplifier.java
new file mode 100644
index 00000000000..5b5a06c99bf
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/transform/Simplifier.java
@@ -0,0 +1,131 @@
+// 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.evaluation.BooleanValue;
+import com.yahoo.searchlib.rankingexpression.evaluation.DoubleValue;
+import com.yahoo.searchlib.rankingexpression.evaluation.Value;
+import com.yahoo.searchlib.rankingexpression.rule.ArithmeticNode;
+import com.yahoo.searchlib.rankingexpression.rule.ArithmeticOperator;
+import com.yahoo.searchlib.rankingexpression.rule.CompositeNode;
+import com.yahoo.searchlib.rankingexpression.rule.ConstantNode;
+import com.yahoo.searchlib.rankingexpression.rule.EmbracedNode;
+import com.yahoo.searchlib.rankingexpression.rule.ExpressionNode;
+import com.yahoo.searchlib.rankingexpression.rule.ReferenceNode;
+import com.yahoo.searchlib.rankingexpression.rule.IfNode;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Performs simple algebraic simplification of expressions
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public class Simplifier extends ExpressionTransformer {
+
+ @Override
+ public ExpressionNode transform(ExpressionNode node) {
+ if (node instanceof CompositeNode)
+ node = transformChildren((CompositeNode) node); // depth first
+ if (node instanceof IfNode)
+ node = transformIf((IfNode) node);
+ if (node instanceof EmbracedNode && hasSingleUndividableChild((EmbracedNode)node))
+ node = ((EmbracedNode)node).children().get(0);
+ if (node instanceof ArithmeticNode)
+ node = transformArithmetic((ArithmeticNode) node);
+ return node;
+ }
+
+ private boolean hasSingleUndividableChild(EmbracedNode node) {
+ if (node.children().size() > 1) return false;
+ if (node.children().get(0) instanceof ArithmeticNode) return false;
+ return true;
+ }
+
+ private ExpressionNode transformArithmetic(ArithmeticNode node) {
+ if (node.children().size() > 1) {
+ List<ExpressionNode> children = new ArrayList<>(node.children());
+ List<ArithmeticOperator> operators = new ArrayList<>(node.operators());
+ for (ArithmeticOperator operator : ArithmeticOperator.operatorsByPrecedence)
+ transform(operator, children, operators);
+ node = new ArithmeticNode(children, operators);
+ }
+
+ if (isConstant(node))
+ return new ConstantNode(node.evaluate(null));
+ else if (allMultiplicationOrDivision(node) && hasZero(node)) // disregarding the /0 case
+ return new ConstantNode(new DoubleValue(0));
+ else
+ return node;
+ }
+
+ private void transform(ArithmeticOperator operator, List<ExpressionNode> children, List<ArithmeticOperator> operators) {
+ int i = 0;
+ while (i < children.size()-1) {
+ if ( ! operators.get(i).equals(operator)) {
+ i++;
+ continue;
+ }
+
+ ExpressionNode child1 = children.get(i);
+ ExpressionNode child2 = children.get(i + 1);
+ if (isConstant(child1) && isConstant(child2) && hasPrecedence(operators, i)) {
+ Value evaluated = new ArithmeticNode(child1, operators.remove(i), child2).evaluate(null);
+ children.set(i, new ConstantNode(evaluated.freeze()));
+ children.remove(i+1);
+ }
+ else { // try the next index
+ i++;
+ }
+ }
+ }
+
+ /**
+ * Returns true if the operator at i binds at least as strongly as the neighbouring operators on each side (if any).
+ * This check works because we simplify by decreasing precedence, so neighbours will either be single constant values
+ * or a more complex expression that can't be simplified and hence also prevents the simplification in question here.
+ */
+ private boolean hasPrecedence(List<ArithmeticOperator> operators, int i) {
+ if (i > 0 && operators.get(i-1).hasPrecedenceOver(operators.get(i))) return false;
+ if (i < operators.size()-1 && operators.get(i+1).hasPrecedenceOver(operators.get(i))) return false;
+ return true;
+ }
+
+ private ExpressionNode transformIf(IfNode node) {
+ if ( ! isConstant(node.getCondition())) return node;
+
+ if (((BooleanValue)node.getCondition().evaluate(null)).asBoolean())
+ return node.getTrueExpression();
+ else
+ return node.getFalseExpression();
+ }
+
+ private boolean allMultiplicationOrDivision(ArithmeticNode node) {
+ for (ArithmeticOperator o : node.operators())
+ if (o == ArithmeticOperator.PLUS || o == ArithmeticOperator.MINUS)
+ return false;
+ return true;
+ }
+
+ private boolean hasZero(ArithmeticNode node) {
+ for (ExpressionNode child : node.children()) {
+ if ( ! (child instanceof ConstantNode)) continue;
+ ConstantNode constant = (ConstantNode)child;
+ if ( ! constant.getValue().hasDouble()) return false;
+ if (constant.getValue().asDouble() == 0.0)
+ return true;
+ }
+ return false;
+ }
+
+ private boolean isConstant(ExpressionNode node) {
+ if (node instanceof ConstantNode) return true;
+ if (node instanceof ReferenceNode) return false;
+ if ( ! (node instanceof CompositeNode)) return false;
+ for (ExpressionNode child : ((CompositeNode)node).children()) {
+ if ( ! isConstant(child)) return false;
+ }
+ return true;
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/transform/package-info.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/transform/package-info.java
new file mode 100644
index 00000000000..da4e4f64615
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/transform/package-info.java
@@ -0,0 +1,6 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.searchlib.rankingexpression.transform;
+
+import com.yahoo.api.annotations.PublicApi;
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/treenet/TreeNetConverter.java b/searchlib/src/main/java/com/yahoo/searchlib/treenet/TreeNetConverter.java
new file mode 100755
index 00000000000..c147c3a33b8
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/treenet/TreeNetConverter.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.treenet;
+
+import com.yahoo.searchlib.treenet.parser.TreeNetParser;
+
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class TreeNetConverter {
+
+ /**
+ * Implements an application main function so that the converter can be used as a command-line tool.
+ *
+ * @param args List of arguments.
+ */
+ public static void main(String[] args) {
+ if (args.length != 1) {
+ System.err.println("Usage: TreeNetConverter <filename>");
+ System.exit(1);
+ }
+ try {
+ TreeNetParser parser = new TreeNetParser(new FileReader(args[0]));
+ System.out.println(parser.treeNet().toRankingExpression());
+ } catch (FileNotFoundException e) {
+ System.err.println("Could not find file '" + args[0] + "'.");
+ System.exit(1);
+ } catch (Exception e) {
+ System.err.println("An error occured while parsing the content of file '" + args[0] + "': " + e);
+ System.exit(1);
+ }
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/treenet/package-info.java b/searchlib/src/main/java/com/yahoo/searchlib/treenet/package-info.java
new file mode 100644
index 00000000000..debffbdcf5b
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/treenet/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.searchlib.treenet;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/treenet/parser/package-info.java b/searchlib/src/main/java/com/yahoo/searchlib/treenet/parser/package-info.java
new file mode 100644
index 00000000000..f3244457c66
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/treenet/parser/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.searchlib.treenet.parser;
+
+import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/treenet/rule/ComparisonCondition.java b/searchlib/src/main/java/com/yahoo/searchlib/treenet/rule/ComparisonCondition.java
new file mode 100755
index 00000000000..1855a8a5674
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/treenet/rule/ComparisonCondition.java
@@ -0,0 +1,39 @@
+// 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.rule;
+
+import com.yahoo.java7compat.Util;
+
+/**
+ * Represents a condition which comparing two values
+ *
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class ComparisonCondition extends Condition {
+
+ private final double rhs;
+
+ /**
+ * Constructs a new instance of this class.
+ *
+ * @param lhs The name of the feature to compare to a constant.
+ * @param rhs The constant to compare the feature with.
+ * @param ift The label to jump to if left &lt; right.
+ * @param iff The label to jump to if left &gt;= right;
+ */
+ public ComparisonCondition(String lhs, double rhs, String ift, String iff) {
+ super(lhs, ift, iff);
+ this.rhs = rhs;
+ }
+
+ /**
+ * Returns the constant to compare the feature with.
+ *
+ * @return The constant.
+ */
+ public double getConstant() { return rhs; }
+
+ @Override
+ public String conditionToRankingExpression() {
+ return "< " + Util.toJava7String(rhs);
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/treenet/rule/Condition.java b/searchlib/src/main/java/com/yahoo/searchlib/treenet/rule/Condition.java
new file mode 100644
index 00000000000..4506f4970b0
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/treenet/rule/Condition.java
@@ -0,0 +1,54 @@
+// 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.rule;
+
+import java.util.Iterator;
+
+/**
+ * Represents a condition
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ */
+public abstract class Condition extends TreeNode {
+
+ private final String leftValue;
+ private final String trueLabel;
+ private final String falseLabel;
+
+ public Condition(String leftValue, String trueLabel, String falseLabel) {
+ this.leftValue = leftValue;
+ this.trueLabel = trueLabel;
+ this.falseLabel = falseLabel;
+ }
+
+ /** Returns the name of the feature to compare to a constant. */
+ public String getLeftValue() { return leftValue; }
+
+ /** Return the label to jump to if this condition is true. */
+ public String getTrueLabel() { return trueLabel; }
+
+ /** Return the label to jump to if this condition is false. */
+ public String getFalseLabel() { return falseLabel; }
+
+ @Override
+ public final String toRankingExpression() {
+ StringBuilder b = new StringBuilder("if (");
+ b.append(getLeftValue());
+ b.append(" ");
+ b.append(conditionToRankingExpression());
+ b.append(", ");
+ b.append(getParent().getNodes().get(getTrueLabel()).toRankingExpression());
+ b.append(", ");
+ b.append(getParent().getNodes().get(getFalseLabel()).toRankingExpression());
+ b.append(")");
+ return b.toString();
+ }
+
+ /**
+ * Returns the ranking expression string for the condition part of this condition, i.e the ... part of
+ * <pre>
+ * if(leftValue ..., trueExpression, falseExpression)
+ * </pre>
+ */
+ protected abstract String conditionToRankingExpression();
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/treenet/rule/Response.java b/searchlib/src/main/java/com/yahoo/searchlib/treenet/rule/Response.java
new file mode 100755
index 00000000000..347dd84f419
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/treenet/rule/Response.java
@@ -0,0 +1,45 @@
+// 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.rule;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class Response extends TreeNode {
+
+ // The id of the next tree to run after this.
+ private final Double value;
+
+ // The value of this response.
+ private final String next;
+
+ /**
+ * Constructs a new response.
+ *
+ * @param next The id of the next tree to run after this.
+ * @param value The value of this response.
+ */
+ public Response(Double value, String next) {
+ super();
+ this.value = value;
+ this.next = next;
+ }
+
+ /**
+ * Returns the value of this response.
+ */
+ public Double getValue() {
+ return value;
+ }
+
+ /**
+ * Returns the id of the next tree to run after this.
+ */
+ public String getNext() {
+ return next;
+ }
+
+ @Override
+ public String toRankingExpression() {
+ return value.toString();
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/treenet/rule/SetMembershipCondition.java b/searchlib/src/main/java/com/yahoo/searchlib/treenet/rule/SetMembershipCondition.java
new file mode 100755
index 00000000000..95841bf829f
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/treenet/rule/SetMembershipCondition.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.treenet.rule;
+
+import com.yahoo.java7compat.Util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Represents a set membership test on the form <code>feature IN (integer1, integer2 ...)</code>
+ *
+ * @author <a href="mailto:bratseth@yahoo-inc.com">Jon Bratseth</a>
+ * @since 5.1.21
+ */
+public class SetMembershipCondition extends Condition {
+
+ private final List<Object> setValues;
+
+ /**
+ * Constructs a new instance of this class.
+ *
+ * @param testValue the name of the feature to test
+ * @param setValues the set of values to compare to
+ * @param trueLabel the label to jump to if the value is in the set
+ * @param falseLabel the label to jumt to if the value is not in the set
+ */
+ public SetMembershipCondition(String testValue, List<Object> setValues, String trueLabel, String falseLabel) {
+ super(testValue, trueLabel, falseLabel);
+ this.setValues = Collections.unmodifiableList(new ArrayList<>(setValues));
+ }
+
+ /** Returns the unmodifiable set of values to check */
+ public List<Object> getSetValues() { return setValues; }
+
+ @Override
+ protected String conditionToRankingExpression() {
+ StringBuilder b = new StringBuilder("in [");
+ for (Iterator<Object> i = setValues.iterator(); i.hasNext(); ) {
+ Object value = i.next();
+ if (value instanceof String)
+ b.append("\"").append(value).append("\"");
+ else if (value instanceof Integer)
+ b.append(value);
+ else
+ throw new RuntimeException("Excepted a string or integer in a set membership test, not a " +
+ value.getClass() + ": " + value);
+
+ if (i.hasNext())
+ b.append(",");
+ }
+ b.append("]");
+ return b.toString();
+ }
+
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/treenet/rule/Tree.java b/searchlib/src/main/java/com/yahoo/searchlib/treenet/rule/Tree.java
new file mode 100755
index 00000000000..2a7191baeba
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/treenet/rule/Tree.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.treenet.rule;
+
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class Tree {
+
+ private final String name;
+
+ // The parent tree net of this.
+ private TreeNet parent;
+
+ // Returns the id of the next tree to run after this.
+ private String next;
+
+ // The initial response value of this tree, may be null.
+ private final Double value;
+
+ // The id of the first condition or response to run in this tree.
+ private final String begin;
+
+ // All named nodes of this tree.
+ private final Map<String, TreeNode> nodes;
+
+ /**
+ * Constructs a new tree.
+ *
+ * @param name The name of this tree, used for error outputs.
+ * @param value The initial response value of this tree, may be null.
+ * @param begin The id of the first condition or response to run in this tree.
+ * @param nodes All named nodes of this tree.
+ */
+ public Tree(String name, Double value, String begin, Map<String, TreeNode> nodes) {
+ this.name = name;
+ this.value = value;
+ this.begin = begin;
+ this.nodes = nodes;
+
+ this.next = null;
+ for (TreeNode node : this.nodes.values()) {
+ node.setParent(this);
+ if (node instanceof Response) {
+ String next = ((Response)node).getNext();
+ if (this.next == null) {
+ this.next = next;
+ } else if (!this.next.equals(next)) {
+ throw new IllegalStateException("Not all child nodes of tree '" + name + "' agree on the next " +
+ "tree to run. Initial name was '" + this.next + "', conflicting " +
+ "name is '" + next + "'.");
+ }
+ }
+ }
+ }
+
+ public String getName() { return name; }
+
+ /**
+ * Returns the parent tree net of this.
+ */
+ public TreeNet getParent() { return parent; }
+
+ /**
+ * Sets the parent tree net of this.
+ *
+ * @param parent The parent tree net.
+ * @return This, to allow chaining.
+ */
+ public Tree setParent(TreeNet parent) {
+ this.parent = parent;
+ return this;
+ }
+
+ /**
+ * Returns the id of the next tree to run after this.
+ */
+ public String getNext() {
+ return next;
+ }
+
+ /**
+ * Returns the initial response value of this tree, may be null.
+ */
+ public Double getValue() {
+ return value;
+ }
+
+ /**
+ * Returns the id of the first condition or response to run in this tree.
+ */
+ public String getBegin() {
+ return begin;
+ }
+
+ /**
+ * Returns all named nodes of this tree.
+ */
+ public Map<String, TreeNode> getNodes() {
+ return nodes;
+ }
+
+ /**
+ * Returns a ranking expression equivalent of this tree.
+ */
+ public String toRankingExpression() {
+ return nodes.get(begin).toRankingExpression();
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/treenet/rule/TreeNet.java b/searchlib/src/main/java/com/yahoo/searchlib/treenet/rule/TreeNet.java
new file mode 100755
index 00000000000..1db13b6c12e
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/treenet/rule/TreeNet.java
@@ -0,0 +1,63 @@
+// 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.rule;
+
+import java.util.Map;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public class TreeNet {
+
+ // The id of the first tree to run in this net.
+ private String begin;
+
+ // All named trees of this net.
+ private final Map<String, Tree> trees;
+
+ /**
+ * Constructs a new tree net.
+ *
+ * @param begin The id of the first tree to run in this net.
+ * @param trees All named trees of this net.
+ */
+ public TreeNet(String begin, Map<String, Tree> trees) {
+ this.begin = begin;
+ this.trees = trees;
+ for (Tree tree : this.trees.values()) {
+ tree.setParent(this);
+ }
+ }
+
+ /**
+ * Returns the id of the first tree to run in this net.
+ */
+ public String getBegin() {
+ return begin;
+ }
+
+ /**
+ * Returns all named trees of this net.
+ */
+ public Map<String, Tree> getTrees() {
+ return trees;
+ }
+
+ /**
+ * Returns a ranking expression equivalent of this net.
+ */
+ public String toRankingExpression() {
+ StringBuilder ret = new StringBuilder();
+ String next = begin;
+ while (next != null) {
+ Tree tree = trees.get(next);
+ if (tree.getBegin() != null) {
+ if (ret.length() > 0) {
+ ret.append(" + \n");
+ }
+ ret.append(tree.toRankingExpression());
+ }
+ next = tree.getNext();
+ }
+ return ret.toString();
+ }
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/treenet/rule/TreeNode.java b/searchlib/src/main/java/com/yahoo/searchlib/treenet/rule/TreeNode.java
new file mode 100755
index 00000000000..a637adafc73
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/treenet/rule/TreeNode.java
@@ -0,0 +1,34 @@
+// 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.rule;
+
+/**
+ * @author <a href="mailto:simon@yahoo-inc.com">Simon Thoresen</a>
+ */
+public abstract class TreeNode {
+
+ // The parent tree of this.
+ private Tree parent = null;
+
+ /**
+ * Returns the parent tree of this.
+ */
+ public Tree getParent() {
+ return parent;
+ }
+
+ /**
+ * Sets the parent tree net of this.
+ *
+ * @param parent The parent tree net.
+ * @return This, to allow chaining.
+ */
+ public TreeNode setParent(Tree parent) {
+ this.parent = parent;
+ return this;
+ }
+
+ /**
+ * Returns a ranking expression equivalent of this net.
+ */
+ public abstract String toRankingExpression();
+}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/treenet/rule/package-info.java b/searchlib/src/main/java/com/yahoo/searchlib/treenet/rule/package-info.java
new file mode 100644
index 00000000000..aae05b07627
--- /dev/null
+++ b/searchlib/src/main/java/com/yahoo/searchlib/treenet/rule/package-info.java
@@ -0,0 +1,5 @@
+// Copyright 2016 Yahoo Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+package com.yahoo.searchlib.treenet.rule;
+
+import com.yahoo.osgi.annotation.ExportPackage;