Skip to content

Commit 7648221

Browse files
Add linear function to rank_feature query (#67438)
This adds a linear function to the set of functions available for rank_feature query Closes #49859
1 parent f449b8f commit 7648221

File tree

6 files changed

+192
-28
lines changed

6 files changed

+192
-28
lines changed

docs/reference/query-dsl/rank-feature-query.asciidoc

+40-4
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ query supports the following mathematical functions:
2727
* <<rank-feature-query-saturation,Saturation>>
2828
* <<rank-feature-query-logarithm,Logarithm>>
2929
* <<rank-feature-query-sigmoid,Sigmoid>>
30+
* <<rank-feature-query-linear,Linear>>
3031

3132
If you don't know where to start, we recommend using the `saturation` function.
3233
If no function is provided, the `rank_feature` query uses the `saturation`
@@ -126,7 +127,7 @@ The following query searches for `2016` and boosts relevance scores based on
126127

127128
[source,console]
128129
----
129-
GET /test/_search
130+
GET /test/_search
130131
{
131132
"query": {
132133
"bool": {
@@ -190,7 +191,7 @@ value of the rank feature `field`. If no function is provided, the `rank_feature
190191
query defaults to the `saturation` function. See
191192
<<rank-feature-query-saturation,Saturation>> for more information.
192193

193-
Only one function `saturation`, `log`, or `sigmoid` can be provided.
194+
Only one function `saturation`, `log`, `sigmoid` or `linear` can be provided.
194195
--
195196

196197
`log`::
@@ -201,7 +202,7 @@ function used to boost <<relevance-scores,relevance scores>> based on the
201202
value of the rank feature `field`. See
202203
<<rank-feature-query-logarithm,Logarithm>> for more information.
203204

204-
Only one function `saturation`, `log`, or `sigmoid` can be provided.
205+
Only one function `saturation`, `log`, `sigmoid` or `linear` can be provided.
205206
--
206207

207208
`sigmoid`::
@@ -212,7 +213,18 @@ to boost <<relevance-scores,relevance scores>> based on the value of the
212213
rank feature `field`. See <<rank-feature-query-sigmoid,Sigmoid>> for more
213214
information.
214215

215-
Only one function `saturation`, `log`, or `sigmoid` can be provided.
216+
Only one function `saturation`, `log`, `sigmoid` or `linear` can be provided.
217+
--
218+
219+
`linear`::
220+
+
221+
--
222+
(Optional, <<rank-feature-query-linear,function object>>) Linear function used
223+
to boost <<relevance-scores,relevance scores>> based on the value of the
224+
rank feature `field`. See <<rank-feature-query-linear,Linear>> for more
225+
information.
226+
227+
Only one function `saturation`, `log`, `sigmoid` or `linear` can be provided.
216228
--
217229

218230

@@ -311,3 +323,27 @@ GET /test/_search
311323
}
312324
}
313325
--------------------------------------------------
326+
[[rank-feature-query-linear]]
327+
===== Linear
328+
The `linear` function is the simplest function, and gives a score equal
329+
to the indexed value of `S`, where `S` is the value of the rank feature
330+
field.
331+
If a rank feature field is indexed with `"positive_score_impact": true`,
332+
its indexed value is equal to `S` and rounded to preserve only
333+
9 significant bits for the precision.
334+
If a rank feature field is indexed with `"positive_score_impact": false`,
335+
its indexed value is equal to `1/S` and rounded to preserve only 9 significant
336+
bits for the precision.
337+
338+
[source,console]
339+
--------------------------------------------------
340+
GET /test/_search
341+
{
342+
"query": {
343+
"rank_feature": {
344+
"field": "pagerank",
345+
"linear": {}
346+
}
347+
}
348+
}
349+
--------------------------------------------------

modules/mapper-extras/src/main/java/org/elasticsearch/index/query/RankFeatureQueryBuilder.java

+54-4
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import org.elasticsearch.common.io.stream.StreamInput;
2727
import org.elasticsearch.common.io.stream.StreamOutput;
2828
import org.elasticsearch.common.xcontent.ConstructingObjectParser;
29+
import org.elasticsearch.common.xcontent.ObjectParser;
2930
import org.elasticsearch.common.xcontent.XContentBuilder;
3031
import org.elasticsearch.index.mapper.MappedFieldType;
3132
import org.elasticsearch.index.mapper.RankFeatureFieldMapper.RankFeatureFieldType;
@@ -104,7 +105,7 @@ void doXContent(XContentBuilder builder) throws IOException {
104105
}
105106

106107
@Override
107-
Query toQuery(String field, String feature, boolean positiveScoreImpact) throws IOException {
108+
Query toQuery(String field, String feature, boolean positiveScoreImpact) {
108109
if (positiveScoreImpact == false) {
109110
throw new IllegalArgumentException("Cannot use the [log] function with a field that has a negative score impact as " +
110111
"it would trigger negative scores");
@@ -175,7 +176,7 @@ void doXContent(XContentBuilder builder) throws IOException {
175176
}
176177

177178
@Override
178-
Query toQuery(String field, String feature, boolean positiveScoreImpact) throws IOException {
179+
Query toQuery(String field, String feature, boolean positiveScoreImpact) {
179180
if (pivot == null) {
180181
return FeatureField.newSaturationQuery(field, feature);
181182
} else {
@@ -240,10 +241,55 @@ void doXContent(XContentBuilder builder) throws IOException {
240241
}
241242

242243
@Override
243-
Query toQuery(String field, String feature, boolean positiveScoreImpact) throws IOException {
244+
Query toQuery(String field, String feature, boolean positiveScoreImpact) {
244245
return FeatureField.newSigmoidQuery(field, feature, DEFAULT_BOOST, pivot, exp);
245246
}
246247
}
248+
249+
/**
250+
* A scoring function that scores documents as simply {@code S}
251+
* where S is the indexed value of the static feature.
252+
*/
253+
public static class Linear extends ScoreFunction {
254+
255+
private static final ObjectParser<Linear, Void> PARSER = new ObjectParser<>("linear", Linear::new);
256+
257+
public Linear() {
258+
}
259+
260+
private Linear(StreamInput in) {
261+
this();
262+
}
263+
264+
@Override
265+
public boolean equals(Object obj) {
266+
if (obj == null || getClass() != obj.getClass()) {
267+
return false;
268+
}
269+
return true;
270+
}
271+
272+
@Override
273+
public int hashCode() {
274+
return getClass().hashCode();
275+
}
276+
277+
@Override
278+
void writeTo(StreamOutput out) throws IOException {
279+
out.writeByte((byte) 3);
280+
}
281+
282+
@Override
283+
void doXContent(XContentBuilder builder) throws IOException {
284+
builder.startObject("linear");
285+
builder.endObject();
286+
}
287+
288+
@Override
289+
Query toQuery(String field, String feature, boolean positiveScoreImpact) {
290+
return FeatureField.newLinearQuery(field, feature, DEFAULT_BOOST);
291+
}
292+
}
247293
}
248294

249295
private static ScoreFunction readScoreFunction(StreamInput in) throws IOException {
@@ -255,6 +301,8 @@ private static ScoreFunction readScoreFunction(StreamInput in) throws IOExceptio
255301
return new ScoreFunction.Saturation(in);
256302
case 2:
257303
return new ScoreFunction.Sigmoid(in);
304+
case 3:
305+
return new ScoreFunction.Linear(in);
258306
default:
259307
throw new IOException("Illegal score function id: " + b);
260308
}
@@ -268,7 +316,7 @@ private static ScoreFunction readScoreFunction(StreamInput in) throws IOExceptio
268316
long numNonNulls = Arrays.stream(args, 3, args.length).filter(Objects::nonNull).count();
269317
final RankFeatureQueryBuilder query;
270318
if (numNonNulls > 1) {
271-
throw new IllegalArgumentException("Can only specify one of [log], [saturation] and [sigmoid]");
319+
throw new IllegalArgumentException("Can only specify one of [log], [saturation], [sigmoid] and [linear]");
272320
} else if (numNonNulls == 0) {
273321
query = new RankFeatureQueryBuilder(field, new ScoreFunction.Saturation());
274322
} else {
@@ -292,6 +340,8 @@ private static ScoreFunction readScoreFunction(StreamInput in) throws IOExceptio
292340
ScoreFunction.Saturation.PARSER, new ParseField("saturation"));
293341
PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(),
294342
ScoreFunction.Sigmoid.PARSER, new ParseField("sigmoid"));
343+
PARSER.declareObject(ConstructingObjectParser.optionalConstructorArg(),
344+
ScoreFunction.Linear.PARSER, new ParseField("linear"));
295345
}
296346

297347
public static final String NAME = "rank_feature";

modules/mapper-extras/src/main/java/org/elasticsearch/index/query/RankFeatureQueryBuilders.java

+9
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,13 @@ public static RankFeatureQueryBuilder sigmoid(String fieldName, float pivot, flo
6464
return new RankFeatureQueryBuilder(fieldName, new RankFeatureQueryBuilder.ScoreFunction.Sigmoid(pivot, exp));
6565
}
6666

67+
/**
68+
* Return a new {@link RankFeatureQueryBuilder} that will score documents as
69+
* {@code S)} where S is the indexed value of the static feature.
70+
* @param fieldName field that stores features
71+
*/
72+
public static RankFeatureQueryBuilder linear(String fieldName) {
73+
return new RankFeatureQueryBuilder(fieldName, new RankFeatureQueryBuilder.ScoreFunction.Linear());
74+
}
75+
6776
}

modules/mapper-extras/src/test/java/org/elasticsearch/index/query/RankFeatureQueryBuilderTests.java

+6-3
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ protected Collection<Class<? extends Plugin>> getPlugins() {
6060
protected RankFeatureQueryBuilder doCreateTestQueryBuilder() {
6161
ScoreFunction function;
6262
boolean mayUseNegativeField = true;
63-
switch (random().nextInt(3)) {
63+
switch (random().nextInt(4)) {
6464
case 0:
6565
mayUseNegativeField = false;
6666
function = new ScoreFunction.Log(1 + randomFloat());
@@ -75,6 +75,9 @@ protected RankFeatureQueryBuilder doCreateTestQueryBuilder() {
7575
case 2:
7676
function = new ScoreFunction.Sigmoid(randomFloat(), randomFloat());
7777
break;
78+
case 3:
79+
function = new ScoreFunction.Linear();
80+
break;
7881
default:
7982
throw new AssertionError();
8083
}
@@ -106,7 +109,7 @@ public void testDefaultScoreFunction() throws IOException {
106109
assertEquals(FeatureField.newSaturationQuery("_feature", "my_feature_field"), parsedQuery);
107110
}
108111

109-
public void testIllegalField() throws IOException {
112+
public void testIllegalField() {
110113
String query = "{\n" +
111114
" \"rank_feature\" : {\n" +
112115
" \"field\": \"" + TEXT_FIELD_NAME + "\"\n" +
@@ -118,7 +121,7 @@ public void testIllegalField() throws IOException {
118121
e.getMessage());
119122
}
120123

121-
public void testIllegalCombination() throws IOException {
124+
public void testIllegalCombination() {
122125
String query = "{\n" +
123126
" \"rank_feature\" : {\n" +
124127
" \"field\": \"my_negative_feature_field\",\n" +

modules/mapper-extras/src/yamlRestTest/resources/rest-api-spec/test/rank_feature/10_basic.yml

+52-11
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ setup:
3737

3838
- do:
3939
search:
40-
rest_total_hits_as_int: true
40+
index: test
4141
body:
4242
query:
4343
rank_feature:
@@ -46,7 +46,7 @@ setup:
4646
scaling_factor: 3
4747

4848
- match:
49-
hits.total: 2
49+
hits.total.value: 2
5050

5151
- match:
5252
hits.hits.0._id: "2"
@@ -59,7 +59,7 @@ setup:
5959

6060
- do:
6161
search:
62-
rest_total_hits_as_int: true
62+
index: test
6363
body:
6464
query:
6565
rank_feature:
@@ -68,7 +68,7 @@ setup:
6868
pivot: 20
6969

7070
- match:
71-
hits.total: 2
71+
hits.total.value: 2
7272

7373
- match:
7474
hits.hits.0._id: "2"
@@ -81,7 +81,7 @@ setup:
8181

8282
- do:
8383
search:
84-
rest_total_hits_as_int: true
84+
index: test
8585
body:
8686
query:
8787
rank_feature:
@@ -91,7 +91,27 @@ setup:
9191
exponent: 0.6
9292

9393
- match:
94-
hits.total: 2
94+
hits.total.value: 2
95+
96+
- match:
97+
hits.hits.0._id: "2"
98+
99+
- match:
100+
hits.hits.1._id: "1"
101+
102+
---
103+
"Positive linear":
104+
- do:
105+
search:
106+
index: test
107+
body:
108+
query:
109+
rank_feature:
110+
field: pagerank
111+
linear: {}
112+
113+
- match:
114+
hits.total.value: 2
95115

96116
- match:
97117
hits.hits.0._id: "2"
@@ -105,7 +125,7 @@ setup:
105125
- do:
106126
catch: bad_request
107127
search:
108-
rest_total_hits_as_int: true
128+
index: test
109129
body:
110130
query:
111131
rank_feature:
@@ -118,7 +138,7 @@ setup:
118138

119139
- do:
120140
search:
121-
rest_total_hits_as_int: true
141+
index: test
122142
body:
123143
query:
124144
rank_feature:
@@ -127,7 +147,7 @@ setup:
127147
pivot: 20
128148

129149
- match:
130-
hits.total: 2
150+
hits.total.value: 2
131151

132152
- match:
133153
hits.hits.0._id: "2"
@@ -140,7 +160,7 @@ setup:
140160

141161
- do:
142162
search:
143-
rest_total_hits_as_int: true
163+
index: test
144164
body:
145165
query:
146166
rank_feature:
@@ -150,7 +170,28 @@ setup:
150170
exponent: 0.6
151171

152172
- match:
153-
hits.total: 2
173+
hits.total.value: 2
174+
175+
- match:
176+
hits.hits.0._id: "2"
177+
178+
- match:
179+
hits.hits.1._id: "1"
180+
181+
---
182+
"Negative linear":
183+
184+
- do:
185+
search:
186+
index: test
187+
body:
188+
query:
189+
rank_feature:
190+
field: url_length
191+
linear: {}
192+
193+
- match:
194+
hits.total.value: 2
154195

155196
- match:
156197
hits.hits.0._id: "2"

0 commit comments

Comments
 (0)