Multi value handling in decay functions

Decay functions currently only use the first value in a field that contains
multiple values to compute the distance to the origin. Instead, it should
consider all distances if more values are in the field and then use
one of min/max/sum/avg which is defined by the user.

Relates to #3960
closes #5940
This commit is contained in:
Simon Willnauer 2014-04-24 16:24:07 +02:00 committed by Britta Weber
parent f993945e5c
commit f285ffc610
6 changed files with 274 additions and 89 deletions

View File

@ -150,6 +150,46 @@ that is initialized with a `seed`.
} }
-------------------------------------------------- --------------------------------------------------
===== Field Value factor
The `field_value_factor` function allows you to use a field from a document to
influence the score. It's similar to using the `script_score` function, however,
it avoids the overhead of scripting. If used on a multi-valued field, only the
first value of the field is used in calculations.
As an example, imagine you have a document indexed with a numeric `popularity`
field and wish in influence the score of a document with this field, an example
doing so would look like:
[source,js]
--------------------------------------------------
"field_value_factor": {
"field": "popularity",
"factor": 1.2,
"modifier": "sqrt"
}
--------------------------------------------------
Which will translate into the following forumla for scoring:
`sqrt(1.2 * doc['popularity'].value)`
There are a number of options for the `field_value_factor` function:
[cols="<,<",options="header",]
|=======================================================================
| Parameter |Description
|`field` |Field to be extracted from the document.
|`factor` |Optional factor to multiply the field value with, defaults to 1.
|`modifier` |Modifier to apply to the field value, can be one of: `none`, `log`,
`log1p`, `log2p`, `ln`, `ln1p`, `ln2p`, `square`, `sqrt`, or `reciprocal`.
Defaults to `none`.
|=======================================================================
Keep in mind that taking the log() of 0, or the square root of a negative number
is an illegal operation, and an exception will be thrown. Be sure to limit the
values of the field with a range filter to avoid this, or use `log1p` and
`ln1p`.
===== Decay functions ===== Decay functions
Decay functions score a document with a function that decays depending Decay functions score a document with a function that decays depending
@ -246,45 +286,31 @@ In contrast to the normal and exponential decay, this function actually
sets the score to 0 if the field value exceeds twice the user given sets the score to 0 if the field value exceeds twice the user given
scale value. scale value.
===== Field Value factor ===== Multiple values:
The `field_value_factor` function allows you to use a field from a document to
influence the score. It's similar to using the `script_score` function, however,
it avoids the overhead of scripting. If used on a multi-valued field, only the
first value of the field is used in calculations.
As an example, imagine you have a document indexed with a numeric `popularity` If a field used for computing the decay contains multiple values, per default the value closest to the origin is chosen for determining the distance.
field and wish in influence the score of a document with this field, an example This can be changed by setting `multi_value_mode`.
doing so would look like:
[horizontal]
`min`:: Distance is the minimum distance
`max`:: Distance is the maximum distance
`avg`:: Distance is the average distance
`sum`:: Distance is the sum of all distances
Example:
[source,js] [source,js]
-------------------------------------------------- --------------------------------------------------
"field_value_factor": { "DECAY_FUNCTION": {
"field": "popularity", "FIELD_NAME": {
"factor": 1.2, "origin": ...,
"modifier": "sqrt" "scale": ...
} },
"multi_value_mode": "avg"
}
-------------------------------------------------- --------------------------------------------------
Which will translate into the following forumla for scoring:
`sqrt(1.2 * doc['popularity'].value)`
There are a number of options for the `field_value_factor` function:
[cols="<,<",options="header",]
|=======================================================================
| Parameter |Description
|`field` |Field to be extracted from the document.
|`factor` |Optional factor to multiply the field value with, defaults to 1.
|`modifier` |Modifier to apply to the field value, can be one of: `none`, `log`,
`log1p`, `log2p`, `ln`, `ln1p`, `ln2p`, `square`, `sqrt`, or `reciprocal`.
Defaults to `none`.
|=======================================================================
Keep in mind that taking the log() of 0, or the square root of a negative number
is an illegal operation, and an exception will be thrown. Be sure to limit the
values of the field with a range filter to avoid this, or use `log1p` and
`ln1p`.
==== Detailed example ==== Detailed example

View File

@ -21,8 +21,10 @@ package org.elasticsearch.index.query.functionscore;
import org.elasticsearch.ElasticsearchIllegalStateException; import org.elasticsearch.ElasticsearchIllegalStateException;
import org.elasticsearch.common.xcontent.XContentBuilder; import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.search.MultiValueMode;
import java.io.IOException; import java.io.IOException;
import java.util.Locale;
public abstract class DecayFunctionBuilder implements ScoreFunctionBuilder { public abstract class DecayFunctionBuilder implements ScoreFunctionBuilder {
@ -36,6 +38,7 @@ public abstract class DecayFunctionBuilder implements ScoreFunctionBuilder {
private Object scale; private Object scale;
private double decay = -1; private double decay = -1;
private Object offset; private Object offset;
private MultiValueMode multiValueMode = null;
public DecayFunctionBuilder(String fieldName, Object origin, Object scale) { public DecayFunctionBuilder(String fieldName, Object origin, Object scale) {
this.fieldName = fieldName; this.fieldName = fieldName;
@ -71,8 +74,20 @@ public abstract class DecayFunctionBuilder implements ScoreFunctionBuilder {
builder.field(OFFSET, offset); builder.field(OFFSET, offset);
} }
builder.endObject(); builder.endObject();
if (multiValueMode != null) {
builder.field(DecayFunctionParser.MULTI_VALUE_MODE.getPreferredName(), multiValueMode.name());
}
builder.endObject(); builder.endObject();
return builder; return builder;
} }
public ScoreFunctionBuilder setMultiValueMode(MultiValueMode multiValueMode) {
this.multiValueMode = multiValueMode;
return this;
}
public ScoreFunctionBuilder setMultiValueMode(String multiValueMode) {
this.multiValueMode = MultiValueMode.fromString(multiValueMode.toUpperCase(Locale.ROOT));
return this;
}
} }

View File

@ -19,11 +19,13 @@
package org.elasticsearch.index.query.functionscore; package org.elasticsearch.index.query.functionscore;
import com.sun.swing.internal.plaf.metal.resources.metal;
import org.apache.lucene.index.AtomicReaderContext; import org.apache.lucene.index.AtomicReaderContext;
import org.apache.lucene.search.ComplexExplanation; import org.apache.lucene.search.ComplexExplanation;
import org.apache.lucene.search.Explanation; import org.apache.lucene.search.Explanation;
import org.elasticsearch.ElasticsearchIllegalArgumentException; import org.elasticsearch.ElasticsearchIllegalArgumentException;
import org.elasticsearch.ElasticsearchParseException; import org.elasticsearch.ElasticsearchParseException;
import org.elasticsearch.common.ParseField;
import org.elasticsearch.common.geo.GeoDistance; import org.elasticsearch.common.geo.GeoDistance;
import org.elasticsearch.common.geo.GeoPoint; import org.elasticsearch.common.geo.GeoPoint;
import org.elasticsearch.common.geo.GeoUtils; import org.elasticsearch.common.geo.GeoUtils;
@ -31,6 +33,8 @@ import org.elasticsearch.common.lucene.search.function.CombineFunction;
import org.elasticsearch.common.lucene.search.function.ScoreFunction; import org.elasticsearch.common.lucene.search.function.ScoreFunction;
import org.elasticsearch.common.unit.DistanceUnit; import org.elasticsearch.common.unit.DistanceUnit;
import org.elasticsearch.common.unit.TimeValue; import org.elasticsearch.common.unit.TimeValue;
import org.elasticsearch.common.xcontent.XContentBuilder;
import org.elasticsearch.common.xcontent.XContentFactory;
import org.elasticsearch.common.xcontent.XContentParser; import org.elasticsearch.common.xcontent.XContentParser;
import org.elasticsearch.index.fielddata.DoubleValues; import org.elasticsearch.index.fielddata.DoubleValues;
import org.elasticsearch.index.fielddata.GeoPointValues; import org.elasticsearch.index.fielddata.GeoPointValues;
@ -45,9 +49,12 @@ import org.elasticsearch.index.query.QueryParseContext;
import org.elasticsearch.index.query.QueryParsingException; import org.elasticsearch.index.query.QueryParsingException;
import org.elasticsearch.index.query.functionscore.gauss.GaussDecayFunctionBuilder; import org.elasticsearch.index.query.functionscore.gauss.GaussDecayFunctionBuilder;
import org.elasticsearch.index.query.functionscore.gauss.GaussDecayFunctionParser; import org.elasticsearch.index.query.functionscore.gauss.GaussDecayFunctionParser;
import org.elasticsearch.search.MultiValueMode;
import org.elasticsearch.search.aggregations.metrics.MetricsAggregation;
import org.elasticsearch.search.internal.SearchContext; import org.elasticsearch.search.internal.SearchContext;
import java.io.IOException; import java.io.IOException;
import java.util.Locale;
/** /**
* This class provides the basic functionality needed for adding a decay * This class provides the basic functionality needed for adding a decay
@ -92,6 +99,8 @@ import java.io.IOException;
public abstract class DecayFunctionParser implements ScoreFunctionParser { public abstract class DecayFunctionParser implements ScoreFunctionParser {
public static final ParseField MULTI_VALUE_MODE = new ParseField("multi_value_mode");
/** /**
* Override this function if you want to produce your own scorer. * Override this function if you want to produce your own scorer.
* */ * */
@ -114,28 +123,34 @@ public abstract class DecayFunctionParser implements ScoreFunctionParser {
* */ * */
@Override @Override
public ScoreFunction parse(QueryParseContext parseContext, XContentParser parser) throws IOException, QueryParsingException { public ScoreFunction parse(QueryParseContext parseContext, XContentParser parser) throws IOException, QueryParsingException {
String currentFieldName = null; String currentFieldName;
XContentParser.Token token; XContentParser.Token token;
ScoreFunction scoreFunction = null; AbstractDistanceScoreFunction scoreFunction = null;
token = parser.nextToken(); String multiValueMode = "MIN";
if (token == XContentParser.Token.FIELD_NAME) { XContentBuilder variableContent = XContentFactory.jsonBuilder();
String fieldName = null;
while ((token = parser.nextToken()) == XContentParser.Token.FIELD_NAME) {
currentFieldName = parser.currentName(); currentFieldName = parser.currentName();
token = parser.nextToken(); token = parser.nextToken();
if (token == XContentParser.Token.START_OBJECT) { if (token == XContentParser.Token.START_OBJECT) {
// parse per field the origin and scale value variableContent.copyCurrentStructure(parser);
scoreFunction = parseVariable(currentFieldName, parser, parseContext); fieldName = currentFieldName;
} else if (MULTI_VALUE_MODE.match(currentFieldName)) {
multiValueMode = parser.text();
} else { } else {
throw new ElasticsearchParseException("Malformed score function score parameters."); throw new ElasticsearchParseException("Malformed score function score parameters.");
} }
} else { }
if (fieldName == null) {
throw new ElasticsearchParseException("Malformed score function score parameters."); throw new ElasticsearchParseException("Malformed score function score parameters.");
} }
parser.nextToken(); XContentParser variableParser = XContentFactory.xContent(variableContent.string()).createParser(variableContent.string());
scoreFunction = parseVariable(fieldName, variableParser, parseContext, MultiValueMode.fromString(multiValueMode.toUpperCase(Locale.ROOT)));
return scoreFunction; return scoreFunction;
} }
// parses origin and scale parameter for field "fieldName" // parses origin and scale parameter for field "fieldName"
private ScoreFunction parseVariable(String fieldName, XContentParser parser, QueryParseContext parseContext) throws IOException { private AbstractDistanceScoreFunction parseVariable(String fieldName, XContentParser parser, QueryParseContext parseContext, MultiValueMode mode) throws IOException {
// now, the field must exist, else we cannot read the value for // now, the field must exist, else we cannot read the value for
// the doc later // the doc later
@ -146,20 +161,21 @@ public abstract class DecayFunctionParser implements ScoreFunctionParser {
FieldMapper<?> mapper = smartMappers.fieldMappers().mapper(); FieldMapper<?> mapper = smartMappers.fieldMappers().mapper();
// dates and time need special handling // dates and time need special handling
parser.nextToken();
if (mapper instanceof DateFieldMapper) { if (mapper instanceof DateFieldMapper) {
return parseDateVariable(fieldName, parser, parseContext, (DateFieldMapper) mapper); return parseDateVariable(fieldName, parser, parseContext, (DateFieldMapper) mapper, mode);
} else if (mapper instanceof GeoPointFieldMapper) { } else if (mapper instanceof GeoPointFieldMapper) {
return parseGeoVariable(fieldName, parser, parseContext, (GeoPointFieldMapper) mapper); return parseGeoVariable(fieldName, parser, parseContext, (GeoPointFieldMapper) mapper, mode);
} else if (mapper instanceof NumberFieldMapper<?>) { } else if (mapper instanceof NumberFieldMapper<?>) {
return parseNumberVariable(fieldName, parser, parseContext, (NumberFieldMapper<?>) mapper); return parseNumberVariable(fieldName, parser, parseContext, (NumberFieldMapper<?>) mapper, mode);
} else { } else {
throw new QueryParsingException(parseContext.index(), "Field " + fieldName + " is of type " + mapper.fieldType() throw new QueryParsingException(parseContext.index(), "Field " + fieldName + " is of type " + mapper.fieldType()
+ ", but only numeric types are supported."); + ", but only numeric types are supported.");
} }
} }
private ScoreFunction parseNumberVariable(String fieldName, XContentParser parser, QueryParseContext parseContext, private AbstractDistanceScoreFunction parseNumberVariable(String fieldName, XContentParser parser, QueryParseContext parseContext,
NumberFieldMapper<?> mapper) throws IOException { NumberFieldMapper<?> mapper, MultiValueMode mode) throws IOException {
XContentParser.Token token; XContentParser.Token token;
String parameterName = null; String parameterName = null;
double scale = 0; double scale = 0;
@ -190,11 +206,11 @@ public abstract class DecayFunctionParser implements ScoreFunctionParser {
+ " must be set for numeric fields."); + " must be set for numeric fields.");
} }
IndexNumericFieldData<?> numericFieldData = parseContext.fieldData().getForField(mapper); IndexNumericFieldData<?> numericFieldData = parseContext.fieldData().getForField(mapper);
return new NumericFieldDataScoreFunction(origin, scale, decay, offset, getDecayFunction(), numericFieldData); return new NumericFieldDataScoreFunction(origin, scale, decay, offset, getDecayFunction(), numericFieldData, mode);
} }
private ScoreFunction parseGeoVariable(String fieldName, XContentParser parser, QueryParseContext parseContext, private AbstractDistanceScoreFunction parseGeoVariable(String fieldName, XContentParser parser, QueryParseContext parseContext,
GeoPointFieldMapper mapper) throws IOException { GeoPointFieldMapper mapper, MultiValueMode mode) throws IOException {
XContentParser.Token token; XContentParser.Token token;
String parameterName = null; String parameterName = null;
GeoPoint origin = new GeoPoint(); GeoPoint origin = new GeoPoint();
@ -222,12 +238,12 @@ public abstract class DecayFunctionParser implements ScoreFunctionParser {
double scale = DistanceUnit.DEFAULT.parse(scaleString, DistanceUnit.DEFAULT); double scale = DistanceUnit.DEFAULT.parse(scaleString, DistanceUnit.DEFAULT);
double offset = DistanceUnit.DEFAULT.parse(offsetString, DistanceUnit.DEFAULT); double offset = DistanceUnit.DEFAULT.parse(offsetString, DistanceUnit.DEFAULT);
IndexGeoPointFieldData<?> indexFieldData = parseContext.fieldData().getForField(mapper); IndexGeoPointFieldData<?> indexFieldData = parseContext.fieldData().getForField(mapper);
return new GeoFieldDataScoreFunction(origin, scale, decay, offset, getDecayFunction(), indexFieldData); return new GeoFieldDataScoreFunction(origin, scale, decay, offset, getDecayFunction(), indexFieldData, mode);
} }
private ScoreFunction parseDateVariable(String fieldName, XContentParser parser, QueryParseContext parseContext, private AbstractDistanceScoreFunction parseDateVariable(String fieldName, XContentParser parser, QueryParseContext parseContext,
DateFieldMapper dateFieldMapper) throws IOException { DateFieldMapper dateFieldMapper, MultiValueMode mode) throws IOException {
XContentParser.Token token; XContentParser.Token token;
String parameterName = null; String parameterName = null;
String scaleString = null; String scaleString = null;
@ -262,7 +278,7 @@ public abstract class DecayFunctionParser implements ScoreFunctionParser {
val = TimeValue.parseTimeValue(offsetString, TimeValue.timeValueHours(24)); val = TimeValue.parseTimeValue(offsetString, TimeValue.timeValueHours(24));
double offset = val.getMillis(); double offset = val.getMillis();
IndexNumericFieldData<?> numericFieldData = parseContext.fieldData().getForField(dateFieldMapper); IndexNumericFieldData<?> numericFieldData = parseContext.fieldData().getForField(dateFieldMapper);
return new NumericFieldDataScoreFunction(origin, scale, decay, offset, getDecayFunction(), numericFieldData); return new NumericFieldDataScoreFunction(origin, scale, decay, offset, getDecayFunction(), numericFieldData, mode);
} }
static class GeoFieldDataScoreFunction extends AbstractDistanceScoreFunction { static class GeoFieldDataScoreFunction extends AbstractDistanceScoreFunction {
@ -274,8 +290,8 @@ public abstract class DecayFunctionParser implements ScoreFunctionParser {
private static final GeoDistance distFunction = GeoDistance.DEFAULT; private static final GeoDistance distFunction = GeoDistance.DEFAULT;
public GeoFieldDataScoreFunction(GeoPoint origin, double scale, double decay, double offset, DecayFunction func, public GeoFieldDataScoreFunction(GeoPoint origin, double scale, double decay, double offset, DecayFunction func,
IndexGeoPointFieldData<?> fieldData) { IndexGeoPointFieldData<?> fieldData, MultiValueMode mode) {
super(scale, decay, offset, func); super(scale, decay, offset, func, mode);
this.origin = origin; this.origin = origin;
this.fieldData = fieldData; this.fieldData = fieldData;
} }
@ -285,28 +301,41 @@ public abstract class DecayFunctionParser implements ScoreFunctionParser {
geoPointValues = fieldData.load(context).getGeoPointValues(); geoPointValues = fieldData.load(context).getGeoPointValues();
} }
private final GeoPoint getValue(int doc, GeoPoint missing) {
final int num = geoPointValues.setDocument(doc);
for (int i = 0; i < num; i++) {
return geoPointValues.nextValue();
}
return missing;
}
@Override @Override
protected double distance(int docId) { protected double distance(int docId) {
GeoPoint other = getValue(docId, origin); final int num = geoPointValues.setDocument(docId);
double distance = Math.abs(distFunction.calculate(origin.lat(), origin.lon(), other.lat(), other.lon(), if (num > 0) {
DistanceUnit.METERS)) - offset; double value = mode.startDouble();
return Math.max(0.0d, distance); for (int i = 0; i < num; i++) {
GeoPoint other = geoPointValues.nextValue();
value = mode.apply(Math.max(0.0d, distFunction.calculate(origin.lat(), origin.lon(), other.lat(), other.lon(),
DistanceUnit.METERS) - offset), value);
}
return mode.reduce(value, num);
} else {
return 0.0;
}
} }
@Override @Override
protected String getDistanceString(int docId) { protected String getDistanceString(int docId) {
final GeoPoint other = getValue(docId, origin); StringBuilder values = new StringBuilder(mode.name());
return "arcDistance(" + other + "(=doc value), " + origin + "(=origin)) - " + offset values.append(" of: [");
+ "(=offset) < 0.0 ? 0.0: arcDistance(" + other + "(=doc value), " + origin + "(=origin)) - " + offset final int num = geoPointValues.setDocument(docId);
+ "(=offset)"; if (num > 0) {
for (int i = 0; i < num; i++) {
GeoPoint value = geoPointValues.nextValue();
values.append("Math.max(arcDistance(");
values.append(value).append("(=doc value),").append(origin).append("(=origin)) - ").append(offset).append("(=offset), 0)");
if (i != num - 1) {
values.append(", ");
}
}
} else {
values.append("0.0");
}
values.append("]");
return values.toString();
} }
@Override @Override
@ -322,8 +351,8 @@ public abstract class DecayFunctionParser implements ScoreFunctionParser {
private DoubleValues doubleValues; private DoubleValues doubleValues;
public NumericFieldDataScoreFunction(double origin, double scale, double decay, double offset, DecayFunction func, public NumericFieldDataScoreFunction(double origin, double scale, double decay, double offset, DecayFunction func,
IndexNumericFieldData<?> fieldData) { IndexNumericFieldData<?> fieldData, MultiValueMode mode) {
super(scale, decay, offset, func); super(scale, decay, offset, func, mode);
this.fieldData = fieldData; this.fieldData = fieldData;
this.origin = origin; this.origin = origin;
} }
@ -332,25 +361,42 @@ public abstract class DecayFunctionParser implements ScoreFunctionParser {
this.doubleValues = this.fieldData.load(context).getDoubleValues(); this.doubleValues = this.fieldData.load(context).getDoubleValues();
} }
private final double getValue(int doc, double missing) {
final int num = doubleValues.setDocument(doc);
for (int i = 0; i < num; i++) {
return doubleValues.nextValue();
}
return missing;
}
@Override @Override
protected double distance(int docId) { protected double distance(int docId) {
double distance = Math.abs(getValue(docId, origin) - origin) - offset; final int num = doubleValues.setDocument(docId);
return Math.max(0.0d, distance); if (num > 0) {
double value = mode.startDouble();
for (int i = 0; i < num; i++) {
final double other = doubleValues.nextValue();
value = mode.apply(Math.max(0.0d, Math.abs(other - origin) - offset), value);
}
return mode.reduce(value, num);
} else {
return 0.0;
}
} }
@Override @Override
protected String getDistanceString(int docId) { protected String getDistanceString(int docId) {
return "Math.abs(" + getValue(docId, origin) + "(=doc value) - " + origin + "(=origin)) - "
+ offset + "(=offset) < 0.0 ? 0.0: Math.abs(" + getValue(docId, origin) + "(=doc value) - " StringBuilder values = new StringBuilder(mode.name());
+ origin + ") - " + offset + "(=offset)"; values.append(" of: [");
final int num = doubleValues.setDocument(docId);
if (num > 0) {
for (int i = 0; i < num; i++) {
double value = doubleValues.nextValue();
values.append("Math.max(Math.abs(");
values.append(value).append("(=doc value) - ").append(origin).append("(=origin))) - ").append(offset).append("(=offset), 0)");
if (i != num - 1) {
values.append(", ");
}
}
} else {
values.append("0.0");
}
values.append("]");
return values.toString();
} }
@Override @Override
@ -368,9 +414,11 @@ public abstract class DecayFunctionParser implements ScoreFunctionParser {
private final double scale; private final double scale;
protected final double offset; protected final double offset;
private final DecayFunction func; private final DecayFunction func;
protected final MultiValueMode mode;
public AbstractDistanceScoreFunction(double userSuppiedScale, double decay, double offset, DecayFunction func) { public AbstractDistanceScoreFunction(double userSuppiedScale, double decay, double offset, DecayFunction func, MultiValueMode mode) {
super(CombineFunction.MULT); super(CombineFunction.MULT);
this.mode = mode;
if (userSuppiedScale <= 0.0) { if (userSuppiedScale <= 0.0) {
throw new ElasticsearchIllegalArgumentException(FunctionScoreQueryParser.NAME + " : scale must be > 0.0."); throw new ElasticsearchIllegalArgumentException(FunctionScoreQueryParser.NAME + " : scale must be > 0.0.");
} }

View File

@ -51,7 +51,7 @@ public class ExponentialDecayFunctionParser extends DecayFunctionParser {
public Explanation explainFunction(String valueExpl, double value, double scale) { public Explanation explainFunction(String valueExpl, double value, double scale) {
ComplexExplanation ce = new ComplexExplanation(); ComplexExplanation ce = new ComplexExplanation();
ce.setValue((float) evaluate(value, scale)); ce.setValue((float) evaluate(value, scale));
ce.setDescription("exp(- abs(" + valueExpl + ") * " + -1 * scale + ")"); ce.setDescription("exp(- " + valueExpl + " * " + -1 * scale + ")");
return ce; return ce;
} }

View File

@ -51,7 +51,7 @@ public class LinearDecayFunctionParser extends DecayFunctionParser {
public Explanation explainFunction(String valueExpl, double value, double scale) { public Explanation explainFunction(String valueExpl, double value, double scale) {
ComplexExplanation ce = new ComplexExplanation(); ComplexExplanation ce = new ComplexExplanation();
ce.setValue((float) evaluate(value, scale)); ce.setValue((float) evaluate(value, scale));
ce.setDescription("max(0.0, ((" + scale + " - abs(" + valueExpl + "))/" + scale + ")"); ce.setDescription("max(0.0, ((" + scale + " - " + valueExpl + ")/" + scale + ")");
return ce; return ce;
} }

View File

@ -703,4 +703,100 @@ public class DecayFunctionScoreTests extends ElasticsearchIntegrationTest {
response.actionGet(); response.actionGet();
} }
@Test
public void testMultiFieldOptions() throws Exception {
assertAcked(prepareCreate("test").addMapping(
"type1",
jsonBuilder().startObject().startObject("type1").startObject("properties").startObject("test").field("type", "string")
.endObject().startObject("loc").field("type", "geo_point").endObject().startObject("num").field("type", "float").endObject().endObject().endObject().endObject()));
ensureYellow();
// Index for testing MIN and MAX
IndexRequestBuilder doc1 = client().prepareIndex()
.setType("type1")
.setId("1")
.setIndex("test")
.setSource(
jsonBuilder().startObject().field("test", "value").startArray("loc").startObject().field("lat", 10).field("lon", 20).endObject().startObject().field("lat", 12).field("lon", 23).endObject().endArray()
.endObject());
IndexRequestBuilder doc2 = client().prepareIndex()
.setType("type1")
.setId("2")
.setIndex("test")
.setSource(
jsonBuilder().startObject().field("test", "value").startObject("loc").field("lat", 11).field("lon", 22).endObject()
.endObject());
indexRandom(true, doc1, doc2);
ActionFuture<SearchResponse> response = client().search(
searchRequest().source(
searchSource().query(constantScoreQuery(termQuery("test", "value")))));
SearchResponse sr = response.actionGet();
assertSearchHits(sr, "1", "2");
SearchHits sh = sr.getHits();
assertThat(sh.getTotalHits(), equalTo((long) (2)));
List<Float> lonlat = new ArrayList<>();
lonlat.add(20f);
lonlat.add(10f);
response = client().search(
searchRequest().source(
searchSource().query(
functionScoreQuery(constantScoreQuery(termQuery("test", "value")), gaussDecayFunction("loc", lonlat, "1000km").setMultiValueMode("min")))));
sr = response.actionGet();
assertSearchHits(sr, "1", "2");
sh = sr.getHits();
assertThat(sh.getAt(0).getId(), equalTo("1"));
assertThat(sh.getAt(1).getId(), equalTo("2"));
response = client().search(
searchRequest().source(
searchSource().query(
functionScoreQuery(constantScoreQuery(termQuery("test", "value")), gaussDecayFunction("loc", lonlat, "1000km").setMultiValueMode("max")))));
sr = response.actionGet();
assertSearchHits(sr, "1", "2");
sh = sr.getHits();
assertThat(sh.getAt(0).getId(), equalTo("2"));
assertThat(sh.getAt(1).getId(), equalTo("1"));
// Now test AVG and SUM
doc1 = client().prepareIndex()
.setType("type1")
.setId("1")
.setIndex("test")
.setSource(
jsonBuilder().startObject().field("test", "value").startArray("num").value(0.0).value(1.0).value(2.0).endArray()
.endObject());
doc2 = client().prepareIndex()
.setType("type1")
.setId("2")
.setIndex("test")
.setSource(
jsonBuilder().startObject().field("test", "value").field("num", 1.0)
.endObject());
indexRandom(true, doc1, doc2);
response = client().search(
searchRequest().source(
searchSource().query(
functionScoreQuery(constantScoreQuery(termQuery("test", "value")), linearDecayFunction("num", "0", "10").setMultiValueMode("sum")))));
sr = response.actionGet();
assertSearchHits(sr, "1", "2");
sh = sr.getHits();
assertThat(sh.getAt(0).getId(), equalTo("2"));
assertThat(sh.getAt(1).getId(), equalTo("1"));
assertThat((double)(1.0 - sh.getAt(0).getScore()), closeTo((double)((1.0 - sh.getAt(1).getScore())/3.0), 1.e-6d));
response = client().search(
searchRequest().source(
searchSource().query(
functionScoreQuery(constantScoreQuery(termQuery("test", "value")), linearDecayFunction("num", "0", "10").setMultiValueMode("avg")))));
sr = response.actionGet();
assertSearchHits(sr, "1", "2");
sh = sr.getHits();
assertThat((double) (sh.getAt(0).getScore()), closeTo((double) (sh.getAt(1).getScore()), 1.e-6d));
}
} }