diff options
Diffstat (limited to 'model-evaluation')
13 files changed, 1651 insertions, 0 deletions
diff --git a/model-evaluation/pom.xml b/model-evaluation/pom.xml new file mode 100644 index 00000000000..6fdc25f3786 --- /dev/null +++ b/model-evaluation/pom.xml @@ -0,0 +1,96 @@ +<?xml version="1.0"?> +<!-- Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. --> +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 + http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>com.yahoo.vespa</groupId> + <artifactId>parent</artifactId> + <version>6-SNAPSHOT</version> + <relativePath>../parent/pom.xml</relativePath> + </parent> + <artifactId>model-evaluation</artifactId> + <version>6-SNAPSHOT</version> + <packaging>container-plugin</packaging> + <dependencies> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + <dependency> + <groupId>com.yahoo.vespa</groupId> + <artifactId>component</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>com.yahoo.vespa</groupId> + <artifactId>jdisc_core</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>com.yahoo.vespa</groupId> + <artifactId>container-core</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>com.yahoo.vespa</groupId> + <artifactId>config</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>com.yahoo.vespa</groupId> + <artifactId>configdefinitions</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>com.yahoo.vespa</groupId> + <artifactId>vespajlib</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>com.yahoo.vespa</groupId> + <artifactId>searchlib</artifactId> + <version>${project.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + <scope>provided</scope> + </dependency> + </dependencies> + <build> + <plugins> + <plugin> + <groupId>com.yahoo.vespa</groupId> + <artifactId>bundle-plugin</artifactId> + <extensions>true</extensions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-jar-plugin</artifactId> + <configuration> + <archive> + <manifestEntries> + <Bundle-SymbolicName>${project.artifactId}</Bundle-SymbolicName> + <Vespa-Version>${project.version}</Vespa-Version> + </manifestEntries> + </archive> + </configuration> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + </plugin> + </plugins> + </build> +</project> diff --git a/model-evaluation/src/main/java/ai/vespa/models/evaluation/FunctionEvaluator.java b/model-evaluation/src/main/java/ai/vespa/models/evaluation/FunctionEvaluator.java new file mode 100644 index 00000000000..4acd6e483b4 --- /dev/null +++ b/model-evaluation/src/main/java/ai/vespa/models/evaluation/FunctionEvaluator.java @@ -0,0 +1,57 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.models.evaluation; + +import com.yahoo.searchlib.rankingexpression.ExpressionFunction; +import com.yahoo.searchlib.rankingexpression.evaluation.TensorValue; +import com.yahoo.tensor.Tensor; +import com.yahoo.tensor.TensorType; + +/** + * An evaluator which can be used to evaluate a single function once. + * + * @author bratseth + */ +// This wraps all access to the context and the ranking expression to avoid incorrect usage +public class FunctionEvaluator { + + private final ExpressionFunction function; + private final LazyArrayContext context; + private boolean evaluated = false; + + FunctionEvaluator(ExpressionFunction function, LazyArrayContext context) { + this.function = function; + this.context = context; + } + + /** + * Binds the given variable referred in this expression to the given value. + * + * @param name the variable to bind + * @param value the value this becomes bound to + * @return this for chaining + */ + public FunctionEvaluator bind(String name, Tensor value) { + if (evaluated) + throw new IllegalStateException("You cannot bind a value in a used evaluator"); + context.put(name, new TensorValue(value)); + return this; + } + + /** + * Binds the given variable referred in this expression to the given value. + * This is equivalent to <code>bind(name, Tensor.Builder.of(TensorType.empty).cell(value).build())</code> + * + * @param name the variable to bind + * @param value the value this becomes bound to + * @return this for chaining + */ + public FunctionEvaluator bind(String name, double value) { + return bind(name, Tensor.Builder.of(TensorType.empty).cell(value).build()); + } + + public Tensor evaluate() { + evaluated = true; + return function.getBody().evaluate(context).asTensor(); + } + +} diff --git a/model-evaluation/src/main/java/ai/vespa/models/evaluation/FunctionReference.java b/model-evaluation/src/main/java/ai/vespa/models/evaluation/FunctionReference.java new file mode 100644 index 00000000000..3b50cef6e2e --- /dev/null +++ b/model-evaluation/src/main/java/ai/vespa/models/evaluation/FunctionReference.java @@ -0,0 +1,76 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.models.evaluation; + +import java.util.Objects; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * A reference to a function. + * The function may be + * - free: Callable from users of models, or + * - bound: Representing a specific invocation from another ranking expression. + * In bound functions, any arguments are replaced by the values supplied in the function invocation. + * Function references has a serial form (textual representation) used in ranking expressions received in ranking + * expression configurations. + * + * This is immutable. + * + * @author bratseth + */ +class FunctionReference { + + private static final Pattern referencePattern = + Pattern.compile("rankingExpression\\(([a-zA-Z0-9_]+)(@[a-f0-9]+\\.[a-f0-9]+)?\\)(\\.rankingScript)?"); + + /** The name of the function referenced */ + private final String name; + + /** The id of the specific invocation of the function, or null if it is free */ + private final String instance; + + private FunctionReference(String name, String instance) { + this.name = name; + this.instance = instance; + } + + /** Returns the name of the function referenced */ + String functionName() { return name; } + + boolean isFree() { + return instance == null; + } + + String serialForm() { + return "rankingExpression(" + name + (instance != null ? instance : "") + ")"; + } + + @Override + public String toString() { return "reference to function '" + name + "'" + + ( instance != null ? " instance '" + instance + "'" : ""); } + + @Override + public int hashCode() { return Objects.hash(name, instance); } + + @Override + public boolean equals(Object o) { + if (o == this) return true; + if ( ! (o instanceof FunctionReference)) return false; + FunctionReference other = (FunctionReference)o; + if ( ! Objects.equals(this.name, other.name)) return false; + if ( ! Objects.equals(this.instance, other.instance)) return false; + return true; + } + + /** Returns a function reference from the given serial form, or empty if the string is not a valid reference */ + static Optional<FunctionReference> fromSerial(String serialForm) { + Matcher expressionMatcher = referencePattern.matcher(serialForm); + if ( ! expressionMatcher.matches()) return Optional.empty(); + + String name = expressionMatcher.group(1); + String instance = expressionMatcher.group(2); + return Optional.of(new FunctionReference(name, instance)); + } + +} diff --git a/model-evaluation/src/main/java/ai/vespa/models/evaluation/LazyArrayContext.java b/model-evaluation/src/main/java/ai/vespa/models/evaluation/LazyArrayContext.java new file mode 100644 index 00000000000..2dcfd204077 --- /dev/null +++ b/model-evaluation/src/main/java/ai/vespa/models/evaluation/LazyArrayContext.java @@ -0,0 +1,213 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.models.evaluation; + +import com.google.common.collect.ImmutableMap; +import com.yahoo.searchlib.rankingexpression.ExpressionFunction; +import com.yahoo.searchlib.rankingexpression.RankingExpression; +import com.yahoo.searchlib.rankingexpression.Reference; +import com.yahoo.searchlib.rankingexpression.evaluation.Context; +import com.yahoo.searchlib.rankingexpression.evaluation.ContextIndex; +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.ReferenceNode; +import com.yahoo.tensor.TensorType; + +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +/** + * An array context supporting functions invocations implemented as lazy values. + * + * @author bratseth + */ +final class LazyArrayContext extends Context implements ContextIndex { + + private final IndexedBindings indexedBindings; + + private LazyArrayContext(IndexedBindings indexedBindings) { + this.indexedBindings = indexedBindings.copy(this); + } + + /** + * Create a fast lookup, lazy context for an expression. + * + * @param expression the expression to create a context for + */ + LazyArrayContext(RankingExpression expression, Map<FunctionReference, ExpressionFunction> functions, Model model) { + this.indexedBindings = new IndexedBindings(expression, functions, this, model); + } + + /** + * 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 + */ + @Override + public void put(String name, Value value) { + put(requireIndexOf(name), 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. + */ + public void put(int index, Value value) { + indexedBindings.set(index, value.freeze()); + } + + @Override + public TensorType getType(Reference reference) { + // TODO: Add type information so we do not need to evaluate to get this + return get(requireIndexOf(reference.toString())).type(); + } + + /** Perform a slow lookup by name */ + @Override + public Value get(String name) { + return get(requireIndexOf(name)); + } + + /** Perform a fast lookup by index */ + @Override + public Value get(int index) { + return indexedBindings.get(index); + } + + @Override + public double getDouble(int index) { + double value = get(index).asDouble(); + if (value == Double.NaN) + throw new UnsupportedOperationException("Value at " + index + " has no double representation"); + return value; + } + + @Override + public int getIndex(String name) { + return requireIndexOf(name); + } + + @Override + public int size() { + return indexedBindings.names().size(); + } + + @Override + public Set<String> names() { return indexedBindings.names(); } + + private Integer requireIndexOf(String name) { + Integer index = indexedBindings.indexOf(name); + if (index == null) + throw new IllegalArgumentException("Value '" + name + "' can not be bound in " + this); + return index; + } + + /** + * Creates a copy of this context suitable for evaluating against the same ranking expression + * in a different thread or for re-binding free variables. + */ + LazyArrayContext copy() { + return new LazyArrayContext(indexedBindings); + } + + private static class IndexedBindings { + + /** The mapping from variable name to index */ + private final ImmutableMap<String, Integer> nameToIndex; + + /** The current values set, pre-converted to doubles */ + private final Value[] values; + + private IndexedBindings(ImmutableMap<String, Integer> nameToIndex, Value[] values) { + this.nameToIndex = nameToIndex; + this.values = values; + } + + /** + * Creates indexed bindings for the given expressions. + * The given expression and functions may be inspected but cannot be stored. + */ + IndexedBindings(RankingExpression expression, + Map<FunctionReference, ExpressionFunction> functions, + LazyArrayContext owner, + Model model) { + Set<String> bindTargets = new LinkedHashSet<>(); + extractBindTargets(expression.getRoot(), functions, bindTargets); + + values = new Value[bindTargets.size()]; + Arrays.fill(values, DoubleValue.zero); + + int i = 0; + ImmutableMap.Builder<String, Integer> nameToIndexBuilder = new ImmutableMap.Builder<>(); + for (String variable : bindTargets) + nameToIndexBuilder.put(variable,i++); + nameToIndex = nameToIndexBuilder.build(); + + for (Map.Entry<FunctionReference, ExpressionFunction> function : functions.entrySet()) { + Integer index = nameToIndex.get(function.getKey().serialForm()); + if (index != null) // Referenced in this, so bind it + values[index] = new LazyValue(function.getKey(), owner, model); + } + } + + private void extractBindTargets(ExpressionNode node, + Map<FunctionReference, ExpressionFunction> functions, + Set<String> bindTargets) { + if (isFunctionReference(node)) { + FunctionReference reference = FunctionReference.fromSerial(node.toString()).get(); + bindTargets.add(reference.serialForm()); + + extractBindTargets(functions.get(reference).getBody().getRoot(), functions, bindTargets); + } + else if (isConstant(node)) { + // Ignore + } + else if (node instanceof ReferenceNode) { + bindTargets.add(node.toString()); + } + else if (node instanceof CompositeNode) { + CompositeNode cNode = (CompositeNode)node; + for (ExpressionNode child : cNode.children()) + extractBindTargets(child, functions, bindTargets); + } + } + + private boolean isFunctionReference(ExpressionNode node) { + if ( ! (node instanceof ReferenceNode)) return false; + + ReferenceNode reference = (ReferenceNode)node; + return reference.getName().equals("rankingExpression") && reference.getArguments().size() == 1; + } + + private boolean isConstant(ExpressionNode node) { + if ( ! (node instanceof ReferenceNode)) return false; + + ReferenceNode reference = (ReferenceNode)node; + return reference.getName().equals("value") && reference.getArguments().size() == 1; + } + + Value get(int index) { return values[index]; } + void set(int index, Value value) { values[index] = value; } + Set<String> names() { return nameToIndex.keySet(); } + Integer indexOf(String name) { return nameToIndex.get(name); } + + IndexedBindings copy(Context context) { + Value[] valueCopy = new Value[values.length]; + for (int i = 0; i < values.length; i++) + valueCopy[i] = values[i] instanceof LazyValue ? ((LazyValue)values[i]).copyFor(context) : values[i]; + return new IndexedBindings(nameToIndex, valueCopy); + } + + } + +} diff --git a/model-evaluation/src/main/java/ai/vespa/models/evaluation/LazyValue.java b/model-evaluation/src/main/java/ai/vespa/models/evaluation/LazyValue.java new file mode 100644 index 00000000000..4a1ee22d288 --- /dev/null +++ b/model-evaluation/src/main/java/ai/vespa/models/evaluation/LazyValue.java @@ -0,0 +1,154 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.models.evaluation; + +import com.yahoo.searchlib.rankingexpression.ExpressionFunction; +import com.yahoo.searchlib.rankingexpression.evaluation.Context; +import com.yahoo.searchlib.rankingexpression.evaluation.Value; +import com.yahoo.searchlib.rankingexpression.rule.Function; +import com.yahoo.searchlib.rankingexpression.rule.TruthOperator; +import com.yahoo.tensor.Tensor; +import com.yahoo.tensor.TensorType; + +/** + * A Value which is computed from an expression when first requested. + * This is not multithread safe. + * + * @author bratseth + */ +class LazyValue extends Value { + + /** The reference to the function computing the value of this */ + private final FunctionReference function; + + /** The context used to compute the function of this */ + private final Context context; + + /** The model this is part of */ + private final Model model; + + private Value computedValue = null; + + public LazyValue(FunctionReference function, Context context, Model model) { + this.function = function; + this.context = context; + this.model = model; + } + + private Value computedValue() { + if (computedValue == null) + computedValue = model.requireReferencedFunction(function).getBody().evaluate(context); + return computedValue; + } + + @Override + public TensorType type() { + return computedValue().type(); // TODO: Keep type information in this/ExpressionFunction to avoid computing here + } + + @Override + public double asDouble() { + return computedValue().asDouble(); + } + + @Override + public Tensor asTensor() { + return computedValue().asTensor(); + } + + @Override + public boolean hasDouble() { + return type().rank() == 0; + } + + @Override + public boolean asBoolean() { + return computedValue().asBoolean(); + } + + @Override + public Value negate() { + return computedValue().negate(); + } + + @Override + public Value add(Value value) { + return computedValue().add(value); + } + + @Override + public Value subtract(Value value) { + return computedValue().subtract(value); + } + + @Override + public Value multiply(Value value) { + return computedValue().multiply(value); + } + + @Override + public Value divide(Value value) { + return computedValue().divide(value); + } + + @Override + public Value modulo(Value value) { + return computedValue().modulo(value); + } + + @Override + public Value and(Value value) { + return computedValue().and(value); + } + + @Override + public Value or(Value value) { + return computedValue().or(value); + } + + @Override + public Value not() { + return computedValue().not(); + } + + @Override + public Value power(Value value) { + return computedValue().power(value); + } + + @Override + public Value compare(TruthOperator operator, Value value) { + return computedValue().compare(operator, value); + } + + @Override + public Value function(Function function, Value value) { + return computedValue().function(function, value); + } + + @Override + public Value asMutable() { + return computedValue().asMutable(); + } + + @Override + public String toString() { + return "value of " + function; + } + + @Override + public boolean equals(Object other) { + if (other == this) return true; + if (!(other instanceof Value)) return false; + return computedValue().equals(other); + } + + @Override + public int hashCode() { + return computedValue().hashCode(); + } + + LazyValue copyFor(Context context) { + return new LazyValue(this.function, context, model); + } + +} diff --git a/model-evaluation/src/main/java/ai/vespa/models/evaluation/Model.java b/model-evaluation/src/main/java/ai/vespa/models/evaluation/Model.java new file mode 100644 index 00000000000..ca739195867 --- /dev/null +++ b/model-evaluation/src/main/java/ai/vespa/models/evaluation/Model.java @@ -0,0 +1,132 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.models.evaluation; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.yahoo.searchlib.rankingexpression.ExpressionFunction; +import com.yahoo.searchlib.rankingexpression.evaluation.ContextIndex; +import com.yahoo.searchlib.rankingexpression.evaluation.ExpressionOptimizer; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * A named collection of functions + * + * @author bratseth + */ +public class Model { + + private final String name; + + /** Free functions */ + private final ImmutableList<ExpressionFunction> functions; + + /** Instances of each usage of the above function, where variables (if any) are replaced by their bindings */ + private final ImmutableMap<FunctionReference, ExpressionFunction> referencedFunctions; + + /** Context prototypes, indexed by function name (as all invocations of the same function share the same context prototype) */ + private final ImmutableMap<String, LazyArrayContext> contextPrototypes; + + private final ExpressionOptimizer expressionOptimizer = new ExpressionOptimizer(); + + public Model(String name, Collection<ExpressionFunction> functions) { + this(name, functions, Collections.emptyMap()); + } + + Model(String name, Collection<ExpressionFunction> functions, Map<FunctionReference, ExpressionFunction> referencedFunctions) { + // TODO: Optimize functions + this.name = name; + this.functions = ImmutableList.copyOf(functions); + + ImmutableMap.Builder<String, LazyArrayContext> contextBuilder = new ImmutableMap.Builder<>(); + for (ExpressionFunction function : functions) { + try { + contextBuilder.put(function.getName(), new LazyArrayContext(function.getBody(), referencedFunctions, this)); + } + catch (RuntimeException e) { + throw new IllegalArgumentException("Could not prepare an evaluation context for " + function, e); + } + } + this.contextPrototypes = contextBuilder.build(); + + ImmutableMap.Builder<FunctionReference, ExpressionFunction> functionsBuilder = new ImmutableMap.Builder<>(); + for (Map.Entry<FunctionReference, ExpressionFunction> function : referencedFunctions.entrySet()) { + ExpressionFunction optimizedFunction = optimize(function.getValue(), + contextPrototypes.get(function.getKey().functionName())); + functionsBuilder.put(function.getKey(), optimizedFunction); + } + this.referencedFunctions = functionsBuilder.build(); + } + + /** Returns an optimized version of the given function */ + private ExpressionFunction optimize(ExpressionFunction function, ContextIndex context) { + // Note: Optimization is in-place but we do not depend on that outside this method + expressionOptimizer.optimize(function.getBody(), context); + return function; + } + + public String name() { return name; } + + /** Returns an immutable list of the free functions of this */ + public List<ExpressionFunction> functions() { return functions; } + + /** Returns the given function, or throws a IllegalArgumentException if it does not exist */ + ExpressionFunction requireFunction(String name) { + ExpressionFunction function = function(name); + if (function == null) + throw new IllegalArgumentException("No function named '" + name + "' in " + this + ". Available functions: " + + functions.stream().map(f -> f.getName()).collect(Collectors.joining(", "))); + return function; + } + + /** Returns the given function, or throws a IllegalArgumentException if it does not exist */ + private LazyArrayContext requireContextProprotype(String name) { + LazyArrayContext context = contextPrototypes.get(name); + if (context == null) // Implies function is not present + throw new IllegalArgumentException("No function named '" + name + "' in " + this + ". Available functions: " + + functions.stream().map(f -> f.getName()).collect(Collectors.joining(", "))); + return context; + } + + /** Returns the function withe the given name, or null if none */ // TODO: Parameter overloading? + ExpressionFunction function(String name) { + for (ExpressionFunction function : functions) + if (function.getName().equals(name)) + return function; + return null; + } + + /** Returns an immutable map of the referenced function instances of this */ + Map<FunctionReference, ExpressionFunction> referencedFunctions() { return referencedFunctions; } + + /** Returns the given referred function, or throws a IllegalArgumentException if it does not exist */ + ExpressionFunction requireReferencedFunction(FunctionReference reference) { + ExpressionFunction function = referencedFunctions.get(reference); + if (function == null) + throw new IllegalArgumentException("No " + reference + " in " + this + ". References: " + + referencedFunctions.keySet().stream() + .map(FunctionReference::serialForm) + .collect(Collectors.joining(", "))); + return function; + } + + /** + * Returns an evaluator which can be used to evaluate the given function in a single thread once. + + * Usage: + * <code>Tensor result = model.evaluatorOf("myFunction").bind("foo", value).bind("bar", value).evaluate()</code> + * + * @throws IllegalArgumentException if the function is not present + */ + public FunctionEvaluator evaluatorOf(String function) { // TODO: Parameter overloading? + return new FunctionEvaluator(requireFunction(function), requireContextProprotype(function).copy()); + } + + @Override + public String toString() { return "model '" + name + "'"; } + +} diff --git a/model-evaluation/src/main/java/ai/vespa/models/evaluation/ModelsEvaluator.java b/model-evaluation/src/main/java/ai/vespa/models/evaluation/ModelsEvaluator.java new file mode 100644 index 00000000000..b36e06e5505 --- /dev/null +++ b/model-evaluation/src/main/java/ai/vespa/models/evaluation/ModelsEvaluator.java @@ -0,0 +1,46 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.models.evaluation; + +import com.google.common.collect.ImmutableMap; +import com.yahoo.vespa.config.search.RankProfilesConfig; + +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Evaluates machine-learned models added to Vespa applications and available as config form. + * Usage: + * <code>Tensor result = evaluator.bind("foo", value).bind("bar", value").evaluate()</code> + * + * @author bratseth + */ +public class ModelsEvaluator { + + private final ImmutableMap<String, Model> models; + + public ModelsEvaluator(RankProfilesConfig config) { + models = ImmutableMap.copyOf(new RankProfilesConfigImporter().importFrom(config)); + } + + /** Returns the models of this as an immutable map */ + public Map<String, Model> models() { return models; } + + /** + * Returns a function which can be used to evaluate the given function in the given model + * + * @throws IllegalArgumentException if the function or model is not present + */ + public FunctionEvaluator evaluatorOf(String modelName, String functionName) { + return requireModel(modelName).evaluatorOf(functionName); + } + + /** Returns the given model, or throws a IllegalArgumentException if it does not exist */ + Model requireModel(String name) { + Model model = models.get(name); + if (model == null) + throw new IllegalArgumentException("No model named '" + name + ". Available models: " + + models.keySet().stream().collect(Collectors.joining(", "))); + return model; + } + +} diff --git a/model-evaluation/src/main/java/ai/vespa/models/evaluation/RankProfilesConfigImporter.java b/model-evaluation/src/main/java/ai/vespa/models/evaluation/RankProfilesConfigImporter.java new file mode 100644 index 00000000000..bfd6342218a --- /dev/null +++ b/model-evaluation/src/main/java/ai/vespa/models/evaluation/RankProfilesConfigImporter.java @@ -0,0 +1,86 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.models.evaluation; + +import com.yahoo.searchlib.rankingexpression.ExpressionFunction; +import com.yahoo.searchlib.rankingexpression.RankingExpression; +import com.yahoo.searchlib.rankingexpression.parser.ParseException; +import com.yahoo.vespa.config.search.RankProfilesConfig; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * Converts RankProfilesConfig instances to RankingExpressions for evaluation + * + * @author bratseth + */ +class RankProfilesConfigImporter { + + /** + * Returns a map of the models contained in this config, indexed on name. + * The map is modifiable and owned by the caller. + */ + Map<String, Model> importFrom(RankProfilesConfig config) { + try { + Map<String, Model> models = new HashMap<>(); + for (RankProfilesConfig.Rankprofile profile : config.rankprofile()) { + Model model = importProfile(profile); + models.put(model.name(), model); + } + return models; + } + catch (ParseException e) { + throw new IllegalArgumentException("Could not read rank profiles config - version mismatch?", e); + } + } + + private Model importProfile(RankProfilesConfig.Rankprofile profile) throws ParseException { + List<ExpressionFunction> functions = new ArrayList<>(); + Map<FunctionReference, ExpressionFunction> referencedFunctions = new HashMap<>(); + ExpressionFunction firstPhase = null; + ExpressionFunction secondPhase = null; + for (RankProfilesConfig.Rankprofile.Fef.Property property : profile.fef().property()) { + Optional<FunctionReference> reference = FunctionReference.fromSerial(property.name()); + if ( reference.isPresent()) { + List<String> arguments = new ArrayList<>(); // TODO: Arguments? + RankingExpression expression = new RankingExpression(reference.get().functionName(), property.value()); + + if (reference.get().isFree()) // make available in model under configured name + functions.add(new ExpressionFunction(reference.get().functionName(), arguments, expression)); // + + // Make all functions, bound or not available under the name they are referenced by in expressions + referencedFunctions.put(reference.get(), new ExpressionFunction(reference.get().serialForm(), arguments, expression)); + } + else if (property.name().equals("vespa.rank.firstphase")) { // Include in addition to macros + firstPhase = new ExpressionFunction("firstphase", new ArrayList<>(), + new RankingExpression("first-phase", property.value())); + } + else if (property.name().equals("vespa.rank.secondphase")) { // Include in addition to macros + secondPhase = new ExpressionFunction("secondphase", new ArrayList<>(), + new RankingExpression("second-phase", property.value())); + } + } + if (functionByName("firstphase", functions) == null && firstPhase != null) // may be already included, depending on body + functions.add(firstPhase); + if (functionByName("secondphase", functions) == null && secondPhase != null) // may be already included, depending on body + functions.add(secondPhase); + + try { + return new Model(profile.name(), functions, referencedFunctions); + } + catch (RuntimeException e) { + throw new IllegalArgumentException("Could not load model '" + profile.name() + "'", e); + } + } + + private ExpressionFunction functionByName(String name, List<ExpressionFunction> functions) { + for (ExpressionFunction function : functions) + if (function.getName().equals(name)) + return function; + return null; + } + +} diff --git a/model-evaluation/src/main/java/ai/vespa/models/handler/ModelsEvaluationHandler.java b/model-evaluation/src/main/java/ai/vespa/models/handler/ModelsEvaluationHandler.java new file mode 100644 index 00000000000..78c46864d7b --- /dev/null +++ b/model-evaluation/src/main/java/ai/vespa/models/handler/ModelsEvaluationHandler.java @@ -0,0 +1,57 @@ +package ai.vespa.models.handler; + +import ai.vespa.models.evaluation.ModelsEvaluator; +import com.yahoo.container.jdisc.HttpRequest; +import com.yahoo.container.jdisc.HttpResponse; +import com.yahoo.container.jdisc.LoggingRequestHandler; +import com.yahoo.tensor.Tensor; +import com.yahoo.tensor.serialization.JsonFormat; + +import java.io.IOException; +import java.io.OutputStream; + +public class ModelsEvaluationHandler extends LoggingRequestHandler { + + private final ModelsEvaluator modelsEvaluator; + + public ModelsEvaluationHandler(ModelsEvaluator modelsEvaluator, Context context) { + super(context); + this.modelsEvaluator = modelsEvaluator; + } + + @Override + public HttpResponse handle(HttpRequest request) { + Tensor result = modelsEvaluator.evaluatorOf(property("model", "serving_default", request), + request.getProperty("function")) + .evaluate(); + return new RawResponse(JsonFormat.encode(result)); + } + + private String property(String name, String defaultValue, HttpRequest request) { + String value = request.getProperty(name); + if (value == null) return defaultValue; + return value; + } + + private static class RawResponse extends HttpResponse { + + private final byte[] data; + + RawResponse(byte[] data) { + super(200); + this.data = data; + } + + @Override + public String getContentType() { + return "application/json"; + } + + @Override + public void render(OutputStream outputStream) throws IOException { + outputStream.write(data); + } + } + +} + diff --git a/model-evaluation/src/test/java/ai/vespa/models/evaluation/ModelsEvaluatorTest.java b/model-evaluation/src/test/java/ai/vespa/models/evaluation/ModelsEvaluatorTest.java new file mode 100644 index 00000000000..60cf0d25ded --- /dev/null +++ b/model-evaluation/src/test/java/ai/vespa/models/evaluation/ModelsEvaluatorTest.java @@ -0,0 +1,51 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.models.evaluation; + +import com.yahoo.config.subscription.ConfigGetter; +import com.yahoo.config.subscription.FileSource; +import com.yahoo.tensor.Tensor; +import com.yahoo.vespa.config.search.RankProfilesConfig; +import org.junit.Test; + +import java.io.File; + +import static org.junit.Assert.assertEquals; + +/** + * @author bratseth + */ +public class ModelsEvaluatorTest { + + private static final double delta = 0.00000000001; + + private ModelsEvaluator createModels() { + String configPath = "src/test/resources/config/rankexpression/rank-profiles.cfg"; + RankProfilesConfig config = new ConfigGetter<>(new FileSource(new File(configPath)), RankProfilesConfig.class).getConfig(""); + return new ModelsEvaluator(config); + } + + @Test + public void testTensorEvaluation() { + ModelsEvaluator models = createModels(); + FunctionEvaluator function = models.evaluatorOf("macros", "fourtimessum"); + function.bind("var1", Tensor.from("{{x:0}:3,{x:1}:5}")); + function.bind("var2", Tensor.from("{{x:0}:7,{x:1}:11}")); + assertEquals(Tensor.from("{{x:0}:40.0,{x:1}:64.0}"), function.evaluate()); + } + + @Test + public void testEvaluationDependingOnMacroTakingArguments() { + ModelsEvaluator models = createModels(); + FunctionEvaluator function = models.evaluatorOf("macros", "secondphase"); + function.bind("match", 3); + function.bind("rankBoost", 5); + assertEquals(32.0, function.evaluate().asDouble(), delta); + } + + // TODO: Test argument-less function + // TODO: Test that binding nonexisting variable doesn't work + // TODO: Test that rebinding doesn't work + // TODO: Test with nested macros + // TODO: Test TF/ONNX model + +} diff --git a/model-evaluation/src/test/java/ai/vespa/models/evaluation/RankProfilesImporterTest.java b/model-evaluation/src/test/java/ai/vespa/models/evaluation/RankProfilesImporterTest.java new file mode 100644 index 00000000000..d45372fc7da --- /dev/null +++ b/model-evaluation/src/test/java/ai/vespa/models/evaluation/RankProfilesImporterTest.java @@ -0,0 +1,60 @@ +// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.models.evaluation; + +import com.yahoo.config.subscription.ConfigGetter; +import com.yahoo.config.subscription.FileSource; +import com.yahoo.searchlib.rankingexpression.ExpressionFunction; +import com.yahoo.vespa.config.search.RankProfilesConfig; +import org.junit.Test; + +import java.io.File; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +/** + * Tests instantiating models from rank-profiles configs. + * + * @author bratseth + */ +public class RankProfilesImporterTest { + + @Test + public void testImporting() { + String configPath = "src/test/resources/config/rankexpression/rank-profiles.cfg"; + RankProfilesConfig config = new ConfigGetter<>(new FileSource(new File(configPath)), RankProfilesConfig.class).getConfig(""); + Map<String, Model> models = new RankProfilesConfigImporter().importFrom(config); + assertEquals(18, models.size()); + + Model macros = models.get("macros"); + assertNotNull(macros); + assertEquals("macros", macros.name()); + assertEquals(4, macros.functions().size()); + assertFunction("fourtimessum", "4 * (var1 + var2)", macros); + assertFunction("firstphase", "match + fieldMatch(title) + rankingExpression(myfeature)", macros); + assertFunction("secondphase", "rankingExpression(fourtimessum@5cf279212355b980.67f1e87166cfef86)", macros); + assertFunction("myfeature", + "70 * fieldMatch(title).completeness * pow(0 - fieldMatch(title).earliness,2) + " + + "30 * pow(0 - fieldMatch(description).earliness,2)", + macros); + assertEquals(4, macros.referencedFunctions().size()); + assertBoundFunction("rankingExpression(fourtimessum@5cf279212355b980.67f1e87166cfef86)", + "4 * (match + rankBoost)", macros); + } + + private void assertFunction(String name, String expression, Model model) { + ExpressionFunction function = model.function(name); + assertNotNull(function); + assertEquals(name, function.getName()); + assertEquals(expression, function.getBody().getRoot().toString()); + } + + private void assertBoundFunction(String name, String expression, Model model) { + ExpressionFunction function = model.referencedFunctions().get(FunctionReference.fromSerial(name).get()); + assertNotNull("Function '" + name + "' is present", function); + assertEquals(name, function.getName()); + assertEquals(expression, function.getBody().getRoot().toString()); + } + +} diff --git a/model-evaluation/src/test/resources/config/rankexpression/rank-profiles.cfg b/model-evaluation/src/test/resources/config/rankexpression/rank-profiles.cfg new file mode 100644 index 00000000000..f5652c31d2a --- /dev/null +++ b/model-evaluation/src/test/resources/config/rankexpression/rank-profiles.cfg @@ -0,0 +1,296 @@ +rankprofile[0].name "default" +rankprofile[0].fef.property[0].name "foo" +rankprofile[0].fef.property[0].value "bar, baz" +rankprofile[0].fef.property[1].name "foo" +rankprofile[0].fef.property[1].value "foobar" +rankprofile[0].fef.property[2].name "qux" +rankprofile[0].fef.property[2].value "quux" +rankprofile[0].fef.property[3].name "foo.bar" +rankprofile[0].fef.property[3].value "foo.bar" +rankprofile[0].fef.property[4].name "foo.bar.baz" +rankprofile[0].fef.property[4].value "123" +rankprofile[0].fef.property[5].name "foo(bar).baz.2" +rankprofile[0].fef.property[5].value "123.4" +rankprofile[0].fef.property[6].name "foo(bar).baz.qux" +rankprofile[0].fef.property[6].value "foo(bar)" +rankprofile[0].fef.property[7].name "nud" +rankprofile[0].fef.property[7].value "ity" +rankprofile[0].fef.property[8].name "vespa.rank.firstphase" +rankprofile[0].fef.property[8].value "classicRank" +rankprofile[0].fef.property[9].name "vespa.rank.secondphase" +rankprofile[0].fef.property[9].value "rankingExpression(secondphase)" +rankprofile[0].fef.property[10].name "rankingExpression(secondphase).rankingScript" +rankprofile[0].fef.property[10].value "4" +rankprofile[0].fef.property[11].name "vespa.dump.feature" +rankprofile[0].fef.property[11].value "attribute(foo1).out" +rankprofile[0].fef.property[12].name "vespa.dump.feature" +rankprofile[0].fef.property[12].value "attribute(bar1)" +rankprofile[0].fef.property[13].name "vespa.dump.feature" +rankprofile[0].fef.property[13].value "attribute(foo2).out" +rankprofile[0].fef.property[14].name "vespa.dump.feature" +rankprofile[0].fef.property[14].value "attribute(bar2).out" +rankprofile[0].fef.property[15].name "vespa.dump.feature" +rankprofile[0].fef.property[15].value "attribute(foo3).out" +rankprofile[0].fef.property[16].name "vespa.dump.feature" +rankprofile[0].fef.property[16].value "attribute(bar3).out" +rankprofile[0].fef.property[17].name "vespa.dump.feature" +rankprofile[0].fef.property[17].value "attribute(foo4).out" +rankprofile[0].fef.property[18].name "vespa.dump.feature" +rankprofile[0].fef.property[18].value "attribute(bar4).out" +rankprofile[0].fef.property[19].name "vespa.hitcollector.heapsize" +rankprofile[0].fef.property[19].value "10" +rankprofile[0].fef.property[20].name "vespa.hitcollector.arraysize" +rankprofile[0].fef.property[20].value "20" +rankprofile[0].fef.property[21].name "vespa.hitcollector.rankscoredroplimit" +rankprofile[0].fef.property[21].value "-0.5" +rankprofile[0].fef.property[22].name "vespa.dump.ignoredefaultfeatures" +rankprofile[0].fef.property[22].value "true" +rankprofile[1].name "unranked" +rankprofile[1].fef.property[0].name "vespa.rank.firstphase" +rankprofile[1].fef.property[0].value "value(0)" +rankprofile[1].fef.property[1].name "vespa.hitcollector.heapsize" +rankprofile[1].fef.property[1].value "0" +rankprofile[1].fef.property[2].name "vespa.hitcollector.arraysize" +rankprofile[1].fef.property[2].value "0" +rankprofile[1].fef.property[3].name "vespa.dump.ignoredefaultfeatures" +rankprofile[1].fef.property[3].value "true" +rankprofile[2].name "static" +rankprofile[2].fef.property[0].name "vespa.rank.firstphase" +rankprofile[2].fef.property[0].value "attribute" +rankprofile[2].fef.property[1].name "vespa.rank.secondphase" +rankprofile[2].fef.property[1].value "rankingExpression(secondphase)" +rankprofile[2].fef.property[2].name "rankingExpression(secondphase).rankingScript" +rankprofile[2].fef.property[2].value "10 + feature(arg1).out.out" +rankprofile[2].fef.property[3].name "vespa.summary.feature" +rankprofile[2].fef.property[3].value "attribute(foo1).out" +rankprofile[2].fef.property[4].name "vespa.summary.feature" +rankprofile[2].fef.property[4].value "attribute(bar1)" +rankprofile[2].fef.property[5].name "vespa.summary.feature" +rankprofile[2].fef.property[5].value "attribute(foo2).out" +rankprofile[2].fef.property[6].name "vespa.summary.feature" +rankprofile[2].fef.property[6].value "attribute(bar2).out" +rankprofile[2].fef.property[7].name "vespa.summary.feature" +rankprofile[2].fef.property[7].value "attribute(foo3).out" +rankprofile[2].fef.property[8].name "vespa.summary.feature" +rankprofile[2].fef.property[8].value "attribute(bar3).out" +rankprofile[2].fef.property[9].name "vespa.summary.feature" +rankprofile[2].fef.property[9].value "attribute(foo4).out" +rankprofile[2].fef.property[10].name "vespa.summary.feature" +rankprofile[2].fef.property[10].value "attribute(bar4).out" +rankprofile[3].name "overflow" +rankprofile[3].fef.property[0].name "vespa.rank.firstphase" +rankprofile[3].fef.property[0].value "rankingExpression(firstphase)" +rankprofile[3].fef.property[1].name "rankingExpression(firstphase).rankingScript" +rankprofile[3].fef.property[1].value "feature1(argument1,argument2,argument3,argument4).output + feature2(argument1,argument2,argument3,argument4).output + feature3(argument1,argument2,argument3,argument4).output + feature4(argument1,argument2,argument3,argument4).output + feature5(argument1,argument2,argument3,argument4).output + feature6(argument1,argument2,argument3,argument4).output + feature7(argument1,argument2,argument3,argument4).output + feature8(argument1,argument2,argument3,argument4).output + feature9(argument1,argument2,argument3,argument4).output + feature10(argument1,argument2,argument3,argument4).output + feature11(argument1,argument2,argument3,argument4).output + feature12(argument1,argument2,argument3,argument4).output + feature13(argument1,argument2,argument3,argument4).output + feature14(argument1,argument2,argument3,argument4).output + feature15(argument1,argument2,argument3,argument4).output + feature16(argument1,argument2,argument3,argument4).output + feature17(argument1,argument2,argument3,argument4).output + feature18(argument1,argument2,argument3,argument4).output + feature19(argument1,argument2,argument3,argument4).output + feature20(argument1,argument2,argument3,argument4).output + feature21(argument1,argument2,argument3,argument4).output + feature22(argument1,argument2,argument3,argument4).output + feature23(argument1,argument2,argument3,argument4).output + feature24(argument1,argument2,argument3,argument4).output + feature25(argument1,argument2,argument3,argument4).output + feature26(argument1,argument2,argument3,argument4).output + feature27(argument1,argument2,argument3,argument4).output + feature28(argument1,argument2,argument3,argument4).output + feature29(argument1,argument2,argument3,argument4).output + feature30(argument1,argument2,argument3,argument4).output + feature31(argument1,argument2,argument3,argument4).output + feature32(argument1,argument2,argument3,argument4).output + feature33(argument1,argument2,argument3,argument4).output + feature34(argument1,argument2,argument3,argument4).output + feature35(argument1,argument2,argument3,argument4).output + feature36(argument1,argument2,argument3,argument4).output + feature37(argument1,argument2,argument3,argument4).output + feature38(argument1,argument2,argument3,argument4).output + feature39(argument1,argument2,argument3,argument4).output + feature40(argument1,argument2,argument3,argument4).output + feature41(argument1,argument2,argument3,argument4).output + feature42(argument1,argument2,argument3,argument4).output + feature43(argument1,argument2,argument3,argument4).output + feature44(argument1,argument2,argument3,argument4).output + feature45(argument1,argument2,argument3,argument4).output + feature46(argument1,argument2,argument3,argument4).output + feature47(argument1,argument2,argument3,argument4).output + feature48(argument1,argument2,argument3,argument4).output + feature49(argument1,argument2,argument3,argument4).output + feature50(argument1,argument2,argument3,argument4).output + feature51(argument1,argument2,argument3,argument4).output + feature52(argument1,argument2,argument3,argument4).output + feature53(argument1,argument2,argument3,argument4).output + feature54(argument1,argument2,argument3,argument4).output + feature55(argument1,argument2,argument3,argument4).output + feature56(argument1,argument2,argument3,argument4).output + feature57(argument1,argument2,argument3,argument4).output + feature58(argument1,argument2,argument3,argument4).output + feature59(argument1,argument2,argument3,argument4).output + feature60(argument1,argument2,argument3,argument4).output + feature61(argument1,argument2,argument3,argument4).output + feature62(argument1,argument2,argument3,argument4).output + feature63(argument1,argument2,argument3,argument4).output + feature64(argument1,argument2,argument3,argument4).output + feature65(argument1,argument2,argument3,argument4).output + feature66(argument1,argument2,argument3,argument4).output + feature67(argument1,argument2,argument3,argument4).output + feature68(argument1,argument2,argument3,argument4).output + feature69(argument1,argument2,argument3,argument4).output + feature70(argument1,argument2,argument3,argument4).output + feature71(argument1,argument2,argument3,argument4).output + feature72(argument1,argument2,argument3,argument4).output + feature73(argument1,argument2,argument3,argument4).output + feature74(argument1,argument2,argument3,argument4).output + feature75(argument1,argument2,argument3,argument4).output + feature76(argument1,argument2,argument3,argument4).output + feature77(argument1,argument2,argument3,argument4).output + feature78(argument1,argument2,argument3,argument4).output + feature79(argument1,argument2,argument3,argument4).output + feature80(argument1,argument2,argument3,argument4).output + feature81(argument1,argument2,argument3,argument4).output + feature82(argument1,argument2,argument3,argument4).output + feature83(argument1,argument2,argument3,argument4).output + feature84(argument1,argument2,argument3,argument4).output + feature85(argument1,argument2,argument3,argument4).output + feature86(argument1,argument2,argument3,argument4).output + feature87(argument1,argument2,argument3,argument4).output + feature88(argument1,argument2,argument3,argument4).output + feature89(argument1,argument2,argument3,argument4).output + feature90(argument1,argument2,argument3,argument4).output + feature91(argument1,argument2,argument3,argument4).output + feature92(argument1,argument2,argument3,argument4).output + feature93(argument1,argument2,argument3,argument4).output + feature94(argument1,argument2,argument3,argument4).output + feature95(argument1,argument2,argument3,argument4).output + feature96(argument1,argument2,argument3,argument4).output + feature97(argument1,argument2,argument3,argument4).output + feature98(argument1,argument2,argument3,argument4).output + feature99(argument1,argument2,argument3,argument4).output + feature100(argument1,argument2,argument3,argument4).output + feature101(argument1,argument2,argument3,argument4).output + feature102(argument1,argument2,argument3,argument4).output + feature103(argument1,argument2,argument3,argument4).output + feature104(argument1,argument2,argument3,argument4).output + feature105(argument1,argument2,argument3,argument4).output + feature106(argument1,argument2,argument3,argument4).output + feature107(argument1,argument2,argument3,argument4).output + feature108(argument1,argument2,argument3,argument4).output + feature109(argument1,argument2,argument3,argument4).output + feature110(argument1,argument2,argument3,argument4).output + feature111(argument1,argument2,argument3,argument4).output + feature112(argument1,argument2,argument3,argument4).output + feature113(argument1,argument2,argument3,argument4).output + feature114(argument1,argument2,argument3,argument4).output + feature115(argument1,argument2,argument3,argument4).output + feature116(argument1,argument2,argument3,argument4).output + feature117(argument1,argument2,argument3,argument4).output + feature118(argument1,argument2,argument3,argument4).output + feature119(argument1,argument2,argument3,argument4).output + feature120(argument1,argument2,argument3,argument4).output + feature121(argument1,argument2,argument3,argument4).output + feature122(argument1,argument2,argument3,argument4).output + feature123(argument1,argument2,argument3,argument4).output + feature124(argument1,argument2,argument3,argument4).output + feature125(argument1,argument2,argument3,argument4).output + feature126(argument1,argument2,argument3,argument4).output + feature127(argument1,argument2,argument3,argument4).output + feature128(argument1,argument2,argument3,argument4).output + feature129(argument1,argument2,argument3,argument4).output + feature130(argument1,argument2,argument3,argument4).output + feature131(argument1,argument2,argument3,argument4).output + feature132(argument1,argument2,argument3,argument4).output + feature133(argument1,argument2,argument3,argument4).output + feature134(argument1,argument2,argument3,argument4).output + feature135(argument1,argument2,argument3,argument4).output + feature136(argument1,argument2,argument3,argument4).output + feature137(argument1,argument2,argument3,argument4).output + feature138(argument1,argument2,argument3,argument4).output + feature139(argument1,argument2,argument3,argument4).output + feature140(argument1,argument2,argument3,argument4).output + feature141(argument1,argument2,argument3,argument4).output + feature142(argument1,argument2,argument3,argument4).output + feature143(argument1,argument2,argument3,argument4).output + feature144(argument1,argument2,argument3,argument4).output + feature145(argument1,argument2,argument3,argument4).output + feature146(argument1,argument2,argument3,argument4).output + feature147(argument1,argument2,argument3,argument4).output + feature148(argument1,argument2,argument3,argument4).output + feature149(argument1,argument2,argument3,argument4).output + feature150(argument1,argument2,argument3,argument4).output + feature151(argument1,argument2,argument3,argument4).output + feature152(argument1,argument2,argument3,argument4).output + feature153(argument1,argument2,argument3,argument4).output + feature154(argument1,argument2,argument3,argument4).output + feature155(argument1,argument2,argument3,argument4).output + feature156(argument1,argument2,argument3,argument4).output + feature157(argument1,argument2,argument3,argument4).output + feature158(argument1,argument2,argument3,argument4).output + feature159(argument1,argument2,argument3,argument4).output + feature160(argument1,argument2,argument3,argument4).output + feature161(argument1,argument2,argument3,argument4).output + feature162(argument1,argument2,argument3,argument4).output + feature163(argument1,argument2,argument3,argument4).output + feature164(argument1,argument2,argument3,argument4).output + feature165(argument1,argument2,argument3,argument4).output + feature166(argument1,argument2,argument3,argument4).output + feature167(argument1,argument2,argument3,argument4).output + feature168(argument1,argument2,argument3,argument4).output + feature169(argument1,argument2,argument3,argument4).output + feature170(argument1,argument2,argument3,argument4).output + feature171(argument1,argument2,argument3,argument4).output + feature172(argument1,argument2,argument3,argument4).output + feature173(argument1,argument2,argument3,argument4).output + feature174(argument1,argument2,argument3,argument4).output + feature175(argument1,argument2,argument3,argument4).output + feature176(argument1,argument2,argument3,argument4).output + feature177(argument1,argument2,argument3,argument4).output + feature178(argument1,argument2,argument3,argument4).output + feature179(argument1,argument2,argument3,argument4).output + feature180(argument1,argument2,argument3,argument4).output + feature181(argument1,argument2,argument3,argument4).output + feature182(argument1,argument2,argument3,argument4).output + feature183(argument1,argument2,argument3,argument4).output + feature184(argument1,argument2,argument3,argument4).output + feature185(argument1,argument2,argument3,argument4).output + feature186(argument1,argument2,argument3,argument4).output + feature187(argument1,argument2,argument3,argument4).output + feature188(argument1,argument2,argument3,argument4).output + feature189(argument1,argument2,argument3,argument4).output + feature190(argument1,argument2,argument3,argument4).output + feature191(argument1,argument2,argument3,argument4).output + feature192(argument1,argument2,argument3,argument4).output + feature193(argument1,argument2,argument3,argument4).output + feature194(argument1,argument2,argument3,argument4).output + feature195(argument1,argument2,argument3,argument4).output + feature196(argument1,argument2,argument3,argument4).output + feature197(argument1,argument2,argument3,argument4).output + feature198(argument1,argument2,argument3,argument4).output + feature199(argument1,argument2,argument3,argument4).output + feature200(argument1,argument2,argument3,argument4).output + feature201(argument1,argument2,argument3,argument4).output + feature202(argument1,argument2,argument3,argument4).output + feature203(argument1,argument2,argument3,argument4).output + feature204(argument1,argument2,argument3,argument4).output + feature205(argument1,argument2,argument3,argument4).output + feature206(argument1,argument2,argument3,argument4).output + feature207(argument1,argument2,argument3,argument4).output + feature208(argument1,argument2,argument3,argument4).output + feature209(argument1,argument2,argument3,argument4).output + feature210(argument1,argument2,argument3,argument4).output + feature211(argument1,argument2,argument3,argument4).output + feature212(argument1,argument2,argument3,argument4).output + feature213(argument1,argument2,argument3,argument4).output + feature214(argument1,argument2,argument3,argument4).output + feature215(argument1,argument2,argument3,argument4).output + feature216(argument1,argument2,argument3,argument4).output + feature217(argument1,argument2,argument3,argument4).output + feature218(argument1,argument2,argument3,argument4).output + feature219(argument1,argument2,argument3,argument4).output + feature220(argument1,argument2,argument3,argument4).output + feature221(argument1,argument2,argument3,argument4).output + feature222(argument1,argument2,argument3,argument4).output + feature223(argument1,argument2,argument3,argument4).output + feature224(argument1,argument2,argument3,argument4).output + feature225(argument1,argument2,argument3,argument4).output + feature226(argument1,argument2,argument3,argument4).output + feature227(argument1,argument2,argument3,argument4).output + feature228(argument1,argument2,argument3,argument4).output + feature229(argument1,argument2,argument3,argument4).output + feature230(argument1,argument2,argument3,argument4).output + feature231(argument1,argument2,argument3,argument4).output + feature232(argument1,argument2,argument3,argument4).output + feature233(argument1,argument2,argument3,argument4).output + feature234(argument1,argument2,argument3,argument4).output + feature235(argument1,argument2,argument3,argument4).output + feature236(argument1,argument2,argument3,argument4).output + feature237(argument1,argument2,argument3,argument4).output + feature238(argument1,argument2,argument3,argument4).output + feature239(argument1,argument2,argument3,argument4).output + feature240(argument1,argument2,argument3,argument4).output + feature241(argument1,argument2,argument3,argument4).output + feature242(argument1,argument2,argument3,argument4).output + feature243(argument1,argument2,argument3,argument4).output + feature244(argument1,argument2,argument3,argument4).output + feature245(argument1,argument2,argument3,argument4).output + feature246(argument1,argument2,argument3,argument4).output + feature247(argument1,argument2,argument3,argument4).output + feature248(argument1,argument2,argument3,argument4).output + feature249(argument1,argument2,argument3,argument4).output + feature250(argument1,argument2,argument3,argument4).output + feature251(argument1,argument2,argument3,argument4).output + feature252(argument1,argument2,argument3,argument4).output + feature253(argument1,argument2,argument3,argument4).output + feature254(argument1,argument2,argument3,argument4).output + feature255(argument1,argument2,argument3,argument4).output + feature256(argument1,argument2,argument3,argument4).output + feature257(argument1,argument2,argument3,argument4).output + feature258(argument1,argument2,argument3,argument4).output + feature259(argument1,argument2,argument3,argument4).output + feature260(argument1,argument2,argument3,argument4).output + feature261(argument1,argument2,argument3,argument4).output + feature262(argument1,argument2,argument3,argument4).output + feature263(argument1,argument2,argument3,argument4).output + feature264(argument1,argument2,argument3,argument4).output + feature265(argument1,argument2,argument3,argument4).output + feature266(argument1,argument2,argument3,argument4).output + feature267(argument1,argument2,argument3,argument4).output + feature268(argument1,argument2,argument3,argument4).output + feature269(argument1,argument2,argument3,argument4).output + feature270(argument1,argument2,argument3,argument4).output + feature271(argument1,argument2,argument3,argument4).output + feature272(argument1,argument2,argument3,argument4).output + feature273(argument1,argument2,argument3,argument4).output + feature274(argument1,argument2,argument3,argument4).output + feature275(argument1,argument2,argument3,argument4).output + feature276(argument1,argument2,argument3,argument4).output + feature277(argument1,argument2,argument3,argument4).output + feature278(argument1,argument2,argument3,argument4).output + feature279(argument1,argument2,argument3,argument4).output + feature280(argument1,argument2,argument3,argument4).output + feature281(argument1,argument2,argument3,argument4).output + feature282(argument1,argument2,argument3,argument4).output + feature283(argument1,argument2,argument3,argument4).output + feature284(argument1,argument2,argument3,argument4).output + feature285(argument1,argument2,argument3,argument4).output + feature286(argument1,argument2,argument3,argument4).output + feature287(argument1,argument2,argument3,argument4).output + feature288(argument1,argument2,argument3,argument4).output + feature289(argument1,argument2,argument3,argument4).output + feature290(argument1,argument2,argument3,argument4).output + feature291(argument1,argument2,argument3,argument4).output + feature292(argument1,argument2,argument3,argument4).output + feature293(argument1,argument2,argument3,argument4).output + feature294(argument1,argument2,argument3,argument4).output + feature295(argument1,argument2,argument3,argument4).output + feature296(argument1,argument2,argument3,argument4).output + feature297(argument1,argument2,argument3,argument4).output + feature298(argument1,argument2,argument3,argument4).output + feature299(argument1,argument2,argument3,argument4).output + feature300(argument1,argument2,argument3,argument4).output" +rankprofile[3].fef.property[2].name "vespa.rank.secondphase" +rankprofile[3].fef.property[2].value "rankingExpression(secondphase)" +rankprofile[3].fef.property[3].name "rankingExpression(secondphase).rankingScript" +rankprofile[3].fef.property[3].value "exp(0) + mysum(attribute(foo),\"attribute( bar )\",\"attribute( \\\"baz\\\" )\")" +rankprofile[3].fef.property[4].name "vespa.hitcollector.heapsize" +rankprofile[3].fef.property[4].value "101" +rankprofile[3].fef.property[5].name "vespa.hitcollector.arraysize" +rankprofile[3].fef.property[5].value "201" +rankprofile[3].fef.property[6].name "vespa.hitcollector.rankscoredroplimit" +rankprofile[3].fef.property[6].value "501.5" +rankprofile[4].name "duplicates" +rankprofile[4].fef.property[0].name "fieldMatch(a).proximityLimit" +rankprofile[4].fef.property[0].value "4" +rankprofile[4].fef.property[1].name "fieldMatch(a).proximityTable" +rankprofile[4].fef.property[1].value "0.2" +rankprofile[4].fef.property[2].name "fieldMatch(a).proximityTable" +rankprofile[4].fef.property[2].value "0.4" +rankprofile[4].fef.property[3].name "fieldMatch(a).proximityTable" +rankprofile[4].fef.property[3].value "0.6" +rankprofile[4].fef.property[4].name "fieldMatch(a).proximityTable" +rankprofile[4].fef.property[4].value "0.8" +rankprofile[4].fef.property[5].name "fieldMatch(a).proximityTable" +rankprofile[4].fef.property[5].value "1" +rankprofile[4].fef.property[6].name "fieldMatch(a).proximityTable" +rankprofile[4].fef.property[6].value "0.8" +rankprofile[4].fef.property[7].name "fieldMatch(a).proximityTable" +rankprofile[4].fef.property[7].value "0.6" +rankprofile[4].fef.property[8].name "fieldMatch(a).proximityTable" +rankprofile[4].fef.property[8].value "0.4" +rankprofile[4].fef.property[9].name "fieldMatch(a).proximityTable" +rankprofile[4].fef.property[9].value "0.2" +rankprofile[5].name "whitespace1" +rankprofile[5].fef.property[0].name "vespa.rank.firstphase" +rankprofile[5].fef.property[0].value "rankingExpression(firstphase)" +rankprofile[5].fef.property[1].name "rankingExpression(firstphase).rankingScript" +rankprofile[5].fef.property[1].value "1" +rankprofile[6].name "whitespace2" +rankprofile[6].fef.property[0].name "vespa.rank.firstphase" +rankprofile[6].fef.property[0].value "rankingExpression(firstphase)" +rankprofile[6].fef.property[1].name "rankingExpression(firstphase).rankingScript" +rankprofile[6].fef.property[1].value "1" +rankprofile[7].name "macros" +rankprofile[7].fef.property[0].name "rankingExpression(fourtimessum).rankingScript" +rankprofile[7].fef.property[0].value "4 * (var1 + var2)" +rankprofile[7].fef.property[1].name "rankingExpression(myfeature).rankingScript" +rankprofile[7].fef.property[1].value "70 * fieldMatch(title).completeness * pow(0 - fieldMatch(title).earliness,2) + 30 * pow(0 - fieldMatch(description).earliness,2)" +rankprofile[7].fef.property[2].name "rankingExpression(fourtimessum@5cf279212355b980.67f1e87166cfef86).rankingScript" +rankprofile[7].fef.property[2].value "4 * (match + rankBoost)" +rankprofile[7].fef.property[3].name "vespa.rank.firstphase" +rankprofile[7].fef.property[3].value "rankingExpression(firstphase)" +rankprofile[7].fef.property[4].name "rankingExpression(firstphase).rankingScript" +rankprofile[7].fef.property[4].value "match + fieldMatch(title) + rankingExpression(myfeature)" +rankprofile[7].fef.property[5].name "vespa.rank.secondphase" +rankprofile[7].fef.property[5].value "rankingExpression(fourtimessum@5cf279212355b980.67f1e87166cfef86)" +rankprofile[7].fef.property[6].name "vespa.summary.feature" +rankprofile[7].fef.property[6].value "fieldMatch(title)" +rankprofile[8].name "macros2" +rankprofile[8].fef.property[0].name "foo" +rankprofile[8].fef.property[0].value "some, list" +rankprofile[8].fef.property[1].name "rankingExpression(fourtimessum).rankingScript" +rankprofile[8].fef.property[1].value "4 * (var1 + var2)" +rankprofile[8].fef.property[2].name "rankingExpression(myfeature).rankingScript" +rankprofile[8].fef.property[2].value "70 * fieldMatch(title).completeness * pow(0 - fieldMatch(title).earliness,2) + 30 * pow(0 - fieldMatch(description).earliness,2)" +rankprofile[8].fef.property[3].name "rankingExpression(mysummaryfeature).rankingScript" +rankprofile[8].fef.property[3].value "70 * fieldMatch(title).completeness" +rankprofile[8].fef.property[4].name "rankingExpression(mysummaryfeature2).rankingScript" +rankprofile[8].fef.property[4].value "71 * fieldMatch(title).completeness" +rankprofile[8].fef.property[5].name "rankingExpression(fourtimessum@2b1138e8965e7ff5.67f1e87166cfef86).rankingScript" +rankprofile[8].fef.property[5].value "4 * (match + match)" +rankprofile[8].fef.property[6].name "vespa.rank.firstphase" +rankprofile[8].fef.property[6].value "classicRank" +rankprofile[8].fef.property[7].name "vespa.rank.secondphase" +rankprofile[8].fef.property[7].value "rankingExpression(secondphase)" +rankprofile[8].fef.property[8].name "rankingExpression(secondphase).rankingScript" +rankprofile[8].fef.property[8].value "rankingExpression(fourtimessum@2b1138e8965e7ff5.67f1e87166cfef86) + rankingExpression(mysummaryfeature) + rankingExpression(myfeature)" +rankprofile[8].fef.property[9].name "vespa.summary.feature" +rankprofile[8].fef.property[9].value "rankingExpression(mysummaryfeature2)" +rankprofile[8].fef.property[10].name "vespa.summary.feature" +rankprofile[8].fef.property[10].value "rankingExpression(mysummaryfeature)" +rankprofile[9].name "macros3" +rankprofile[9].fef.property[0].name "rankingExpression(onlyusedinsummaryfeature).rankingScript" +rankprofile[9].fef.property[0].value "5" +rankprofile[9].fef.property[1].name "vespa.summary.feature" +rankprofile[9].fef.property[1].value "rankingExpression(matches(title,rankingExpression(onlyusedinsummaryfeature)))" +rankprofile[10].name "macros3-inherited" +rankprofile[10].fef.property[0].name "rankingExpression(onlyusedinsummaryfeature).rankingScript" +rankprofile[10].fef.property[0].value "5" +rankprofile[10].fef.property[1].name "vespa.summary.feature" +rankprofile[10].fef.property[1].value "rankingExpression(matches(title,rankingExpression(onlyusedinsummaryfeature)))" +rankprofile[11].name "macros-inherited" +rankprofile[11].fef.property[0].name "foo" +rankprofile[11].fef.property[0].value "some, list" +rankprofile[11].fef.property[1].name "rankingExpression(fourtimessum).rankingScript" +rankprofile[11].fef.property[1].value "4 * (var1 + var2)" +rankprofile[11].fef.property[2].name "rankingExpression(myfeature).rankingScript" +rankprofile[11].fef.property[2].value "70 * fieldMatch(title).completeness * pow(0 - fieldMatch(title).earliness,2) + 30 * pow(0 - fieldMatch(description).earliness,2)" +rankprofile[11].fef.property[3].name "rankingExpression(mysummaryfeature).rankingScript" +rankprofile[11].fef.property[3].value "80 * fieldMatch(title).completeness" +rankprofile[11].fef.property[4].name "rankingExpression(mysummaryfeature2).rankingScript" +rankprofile[11].fef.property[4].value "71 * fieldMatch(title).completeness" +rankprofile[11].fef.property[5].name "rankingExpression(fourtimessum@2b1138e8965e7ff5.67f1e87166cfef86).rankingScript" +rankprofile[11].fef.property[5].value "4 * (match + match)" +rankprofile[11].fef.property[6].name "vespa.rank.firstphase" +rankprofile[11].fef.property[6].value "rankingExpression(firstphase)" +rankprofile[11].fef.property[7].name "rankingExpression(firstphase).rankingScript" +rankprofile[11].fef.property[7].value "20000 * rankingExpression(myfeature) + rankingExpression(mysummaryfeature)" +rankprofile[11].fef.property[8].name "vespa.rank.secondphase" +rankprofile[11].fef.property[8].value "rankingExpression(secondphase)" +rankprofile[11].fef.property[9].name "rankingExpression(secondphase).rankingScript" +rankprofile[11].fef.property[9].value "rankingExpression(fourtimessum@2b1138e8965e7ff5.67f1e87166cfef86) + rankingExpression(mysummaryfeature) + rankingExpression(myfeature)" +rankprofile[11].fef.property[10].name "vespa.summary.feature" +rankprofile[11].fef.property[10].value "rankingExpression(mysummaryfeature2)" +rankprofile[11].fef.property[11].name "vespa.summary.feature" +rankprofile[11].fef.property[11].value "rankingExpression(mysummaryfeature)" +rankprofile[12].name "macros-inherited2" +rankprofile[12].fef.property[0].name "foo" +rankprofile[12].fef.property[0].value "some, list" +rankprofile[12].fef.property[1].name "rankingExpression(fourtimessum).rankingScript" +rankprofile[12].fef.property[1].value "4 * (var1 + var2)" +rankprofile[12].fef.property[2].name "rankingExpression(myfeature).rankingScript" +rankprofile[12].fef.property[2].value "70 * fieldMatch(title).completeness * pow(0 - fieldMatch(title).earliness,2) + 30 * pow(0 - fieldMatch(description).earliness,2)" +rankprofile[12].fef.property[3].name "rankingExpression(mysummaryfeature).rankingScript" +rankprofile[12].fef.property[3].value "80 * fieldMatch(title).completeness" +rankprofile[12].fef.property[4].name "rankingExpression(mysummaryfeature2).rankingScript" +rankprofile[12].fef.property[4].value "71 * fieldMatch(title).completeness" +rankprofile[12].fef.property[5].name "rankingExpression(fourtimessum@2b1138e8965e7ff5.67f1e87166cfef86).rankingScript" +rankprofile[12].fef.property[5].value "4 * (match + match)" +rankprofile[12].fef.property[6].name "vespa.rank.firstphase" +rankprofile[12].fef.property[6].value "rankingExpression(firstphase)" +rankprofile[12].fef.property[7].name "rankingExpression(firstphase).rankingScript" +rankprofile[12].fef.property[7].value "30000 * rankingExpression(mysummaryfeature) + rankingExpression(myfeature)" +rankprofile[12].fef.property[8].name "vespa.rank.secondphase" +rankprofile[12].fef.property[8].value "rankingExpression(secondphase)" +rankprofile[12].fef.property[9].name "rankingExpression(secondphase).rankingScript" +rankprofile[12].fef.property[9].value "rankingExpression(fourtimessum@2b1138e8965e7ff5.67f1e87166cfef86) + rankingExpression(mysummaryfeature) + rankingExpression(myfeature)" +rankprofile[12].fef.property[10].name "vespa.summary.feature" +rankprofile[12].fef.property[10].value "rankingExpression(mysummaryfeature2)" +rankprofile[12].fef.property[11].name "vespa.summary.feature" +rankprofile[12].fef.property[11].value "rankingExpression(mysummaryfeature)" +rankprofile[13].name "macros-inherited3" +rankprofile[13].fef.property[0].name "foo" +rankprofile[13].fef.property[0].value "some, list" +rankprofile[13].fef.property[1].name "rankingExpression(fourtimessum).rankingScript" +rankprofile[13].fef.property[1].value "4 * (var1 + var2)" +rankprofile[13].fef.property[2].name "rankingExpression(myfeature).rankingScript" +rankprofile[13].fef.property[2].value "700 * fieldMatch(title).completeness" +rankprofile[13].fef.property[3].name "rankingExpression(mysummaryfeature).rankingScript" +rankprofile[13].fef.property[3].value "80 * fieldMatch(title).completeness" +rankprofile[13].fef.property[4].name "rankingExpression(mysummaryfeature2).rankingScript" +rankprofile[13].fef.property[4].value "71 * fieldMatch(title).completeness" +rankprofile[13].fef.property[5].name "vespa.rank.firstphase" +rankprofile[13].fef.property[5].value "rankingExpression(firstphase)" +rankprofile[13].fef.property[6].name "rankingExpression(firstphase).rankingScript" +rankprofile[13].fef.property[6].value "30000 * rankingExpression(mysummaryfeature) + rankingExpression(myfeature)" +rankprofile[13].fef.property[7].name "vespa.rank.secondphase" +rankprofile[13].fef.property[7].value "rankingExpression(secondphase)" +rankprofile[13].fef.property[8].name "rankingExpression(secondphase).rankingScript" +rankprofile[13].fef.property[8].value "40000 * rankingExpression(mysummaryfeature) + rankingExpression(myfeature)" +rankprofile[13].fef.property[9].name "vespa.summary.feature" +rankprofile[13].fef.property[9].value "rankingExpression(mysummaryfeature2)" +rankprofile[13].fef.property[10].name "vespa.summary.feature" +rankprofile[13].fef.property[10].value "rankingExpression(mysummaryfeature)" +rankprofile[14].name "macros-refering-macros" +rankprofile[14].fef.property[0].name "rankingExpression(m1).rankingScript" +rankprofile[14].fef.property[0].value "700 * fieldMatch(title).completeness" +rankprofile[14].fef.property[1].name "rankingExpression(m2).rankingScript" +rankprofile[14].fef.property[1].value "rankingExpression(m1) * 67" +rankprofile[14].fef.property[2].name "rankingExpression(m4).rankingScript" +rankprofile[14].fef.property[2].value "703 * fieldMatch(fromfile).completeness" +rankprofile[14].fef.property[3].name "vespa.rank.secondphase" +rankprofile[14].fef.property[3].value "rankingExpression(secondphase)" +rankprofile[14].fef.property[4].name "rankingExpression(secondphase).rankingScript" +rankprofile[14].fef.property[4].value "40000 * rankingExpression(m2)" +rankprofile[15].name "macros-refering-macros-inherited" +rankprofile[15].fef.property[0].name "rankingExpression(m1).rankingScript" +rankprofile[15].fef.property[0].value "700 * fieldMatch(title).completeness" +rankprofile[15].fef.property[1].name "rankingExpression(m2).rankingScript" +rankprofile[15].fef.property[1].value "rankingExpression(m1) * 67" +rankprofile[15].fef.property[2].name "rankingExpression(m4).rankingScript" +rankprofile[15].fef.property[2].value "701 * fieldMatch(title).completeness" +rankprofile[15].fef.property[3].name "rankingExpression(m3).rankingScript" +rankprofile[15].fef.property[3].value "if (isNan(attribute(nrtgmp)) == 1, 0.0, rankingExpression(m2))" +rankprofile[15].fef.property[4].name "vespa.rank.secondphase" +rankprofile[15].fef.property[4].value "rankingExpression(secondphase)" +rankprofile[15].fef.property[5].name "rankingExpression(secondphase).rankingScript" +rankprofile[15].fef.property[5].value "3000 * rankingExpression(m2)" +rankprofile[16].name "macros-refering-macros-inherited2" +rankprofile[16].fef.property[0].name "rankingExpression(m1).rankingScript" +rankprofile[16].fef.property[0].value "700 * fieldMatch(title).completeness" +rankprofile[16].fef.property[1].name "rankingExpression(m2).rankingScript" +rankprofile[16].fef.property[1].value "rankingExpression(m1) * 67" +rankprofile[16].fef.property[2].name "rankingExpression(m4).rankingScript" +rankprofile[16].fef.property[2].value "703 * fieldMatch(fromfile).completeness" +rankprofile[16].fef.property[3].name "vespa.rank.secondphase" +rankprofile[16].fef.property[3].value "rankingExpression(secondphase)" +rankprofile[16].fef.property[4].name "rankingExpression(secondphase).rankingScript" +rankprofile[16].fef.property[4].value "3002 * rankingExpression(m2)" +rankprofile[17].name "macros-refering-macros-inherited-two-levels" +rankprofile[17].fef.property[0].name "rankingExpression(m1).rankingScript" +rankprofile[17].fef.property[0].value "700 * fieldMatch(title).completeness" +rankprofile[17].fef.property[1].name "rankingExpression(m2).rankingScript" +rankprofile[17].fef.property[1].value "rankingExpression(m1) * 67" +rankprofile[17].fef.property[2].name "rankingExpression(m4).rankingScript" +rankprofile[17].fef.property[2].value "701 * fieldMatch(title).completeness" +rankprofile[17].fef.property[3].name "rankingExpression(m3).rankingScript" +rankprofile[17].fef.property[3].value "if (isNan(attribute(nrtgmp)) == 1, 0.0, rankingExpression(m2))" +rankprofile[17].fef.property[4].name "rankingExpression(m5).rankingScript" +rankprofile[17].fef.property[4].value "if (isNan(attribute(glmpfw)) == 1, rankingExpression(m1), rankingExpression(m4))" +rankprofile[17].fef.property[5].name "vespa.rank.secondphase" +rankprofile[17].fef.property[5].value "rankingExpression(secondphase)" +rankprofile[17].fef.property[6].name "rankingExpression(secondphase).rankingScript" +rankprofile[17].fef.property[6].value "3000 * rankingExpression(m2)"
\ No newline at end of file diff --git a/model-evaluation/src/test/resources/config/rankexpression/rankexpression.sd b/model-evaluation/src/test/resources/config/rankexpression/rankexpression.sd new file mode 100644 index 00000000000..d3e0057cfe1 --- /dev/null +++ b/model-evaluation/src/test/resources/config/rankexpression/rankexpression.sd @@ -0,0 +1,327 @@ +# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +search rankexpression { + + document rankexpression { + + field artist type string { + indexing: summary | index + } + + field title type string { + indexing: summary | index + } + + field surl type string { + indexing: summary + } + + field year type int { + indexing: summary | attribute + } + + field foo1 type int { + indexing: attribute + } + + field foo2 type int { + indexing: attribute + } + + field foo3 type int { + indexing: attribute + } + + field foo4 type int { + indexing: attribute + } + + field bar1 type int { + indexing: attribute + } + + field bar2 type int { + indexing: attribute + } + + field bar3 type int { + indexing: attribute + } + + field bar4 type int { + indexing: attribute + } + + } + + rank-profile default { + first-phase { + expression: classicRank + keep-rank-count: 20 + rank-score-drop-limit: -0.5 + } + second-phase { + expression: if(3>2,4,2) + rerank-count: 10 + } + rank-features: attribute(foo1).out attribute(bar1) + rank-features { attribute(foo2).out attribute(bar2).out } + rank-features { + attribute(foo3).out attribute(bar3).out } + rank-features { + attribute(foo4).out + attribute(bar4).out + } + ignore-default-rank-features + + rank-properties { + foo: "bar, baz" + qux: "quux" + foo: "foobar" + foo.bar: "foo.bar" + foo.bar.baz: 123 + foo ( bar ) . baz.2 : 123.4 + foo(bar).baz.qux: "foo(bar)" + "nud":"ity" + } + + } + + rank-profile static { + first-phase { + expression { attribute } + } + second-phase { + expression { + file:rankexpression + } + } + summary-features: attribute(foo1).out attribute(bar1) + summary-features { attribute(foo2).out attribute(bar2).out } + summary-features { + attribute(foo3).out attribute(bar3).out } + summary-features { + attribute(foo4).out + attribute(bar4).out + } + } + + rank-profile overflow { + first-phase { + expression: file:overflow.expression + keep-rank-count: 201 + rank-score-drop-limit: 501.5 + } + second-phase { + expression { + exp(0) + + mysum(attribute(foo), + "attribute( bar )", + "attribute( \"baz\" )") + } + rerank-count: 101 + } + } + + rank-profile duplicates { + rank-properties { + fieldMatch(a).proximityLimit: 4 + fieldMatch(a).proximityTable: 0.2 + fieldMatch(a).proximityTable: 0.4 + fieldMatch(a).proximityTable: 0.6 + fieldMatch(a).proximityTable: 0.8 + fieldMatch(a).proximityTable: 1 + fieldMatch(a).proximityTable: 0.8 + fieldMatch(a).proximityTable: 0.6 + fieldMatch(a).proximityTable: 0.4 + fieldMatch(a).proximityTable: 0.2 + } + } + + rank-profile whitespace1 { + first-phase { + expression + { + + 1 + }}} + + rank-profile whitespace2 { + first-phase + { + expression { 1 } + } + } + + rank-profile macros { + first-phase { + expression: match + fieldMatch(title) + myfeature + } + second-phase { + expression: fourtimessum(match,rankBoost) + } + macro fourtimessum(var1, var2) { + expression: 4*(var1+var2) + } + macro myfeature() { + expression { + 70 * fieldMatch(title).completeness * pow(0 - fieldMatch(title).earliness, 2) + + 30 * pow(0 - fieldMatch(description).earliness, 2) + } + } + summary-features { + fieldMatch(title) + } + } + + rank-profile macros2 { + first-phase { + expression: classicRank + } + rank-properties { + foo: "some, list" + } + + second-phase { + expression: fourtimessum(match,match) + mysummaryfeature + myfeature + } + macro fourtimessum(var1, var2) { + expression: 4*(var1+var2) + } + macro myfeature() { + expression { + 70 * fieldMatch(title).completeness * pow(0 - fieldMatch(title).earliness, 2) + + 30 * pow(0 - fieldMatch(description).earliness, 2) + } + } + macro mysummaryfeature() { + expression { + 70 * fieldMatch(title).completeness + } + } + macro mysummaryfeature2() { + expression { + 71 * fieldMatch(title).completeness + } + } + summary-features { + mysummaryfeature + rankingExpression(mysummaryfeature2) # Required form earlier + } + } + + rank-profile macros3 { + macro onlyusedinsummaryfeature() { + expression: 5 + } + summary-features { + rankingExpression(matches(title,rankingExpression(onlyusedinsummaryfeature))) + } + + } + + rank-profile macros3-inherited inherits macros3 { + summary-features { + rankingExpression(matches(title,rankingExpression(onlyusedinsummaryfeature))) + } + } + + rank-profile macros-inherited inherits macros2 { + macro mysummaryfeature() { + expression { + 80 * fieldMatch(title).completeness + } + } + first-phase { + expression { + 20000 * myfeature + mysummaryfeature + } + } + } + + rank-profile macros-inherited2 inherits macros-inherited { + first-phase { + expression { + 30000 * mysummaryfeature + myfeature + } + } + } + + rank-profile macros-inherited3 inherits macros-inherited2 { + macro myfeature() { + expression { + 700 * fieldMatch(title).completeness + } + } + second-phase { + expression { + 40000 * mysummaryfeature + myfeature + } + } + } + + rank-profile macros-refering-macros { + macro m2() { + expression: m1 * 67 + } + + macro m1() { + expression { + 700 * fieldMatch(title).completeness + } + } + + macro m4() { + expression: file:macro.expression + } + + second-phase { + expression { + 40000 * m2 + } + } + + } + + rank-profile macros-refering-macros-inherited inherits macros-refering-macros { + macro m3() { + expression { + if(isNan(attribute(nrtgmp))==1, + 0.0, + (m2) + ) + } + } + macro m4() { + expression { + 701 * fieldMatch(title).completeness + } + } + second-phase { + expression { + 3000 * m2 + } + } + } + + rank-profile macros-refering-macros-inherited2 inherits macros-refering-macros { + second-phase { + expression { + 3002 * m2 + } + } + } + + rank-profile macros-refering-macros-inherited-two-levels inherits macros-refering-macros-inherited { + macro m5() { + expression { + if(isNan(attribute(glmpfw))==1, + m1, + (m4) + ) + } + } + } + +} + + |