Skip to content

Commit e42b453

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

File tree

6 files changed

+202
-28
lines changed

6 files changed

+202
-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

+59-11
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ setup:
4141

4242
- do:
4343
search:
44-
rest_total_hits_as_int: true
44+
index: test
4545
body:
4646
query:
4747
rank_feature:
@@ -50,7 +50,7 @@ setup:
5050
scaling_factor: 3
5151

5252
- match:
53-
hits.total: 2
53+
hits.total.value: 2
5454

5555
- match:
5656
hits.hits.0._id: "2"
@@ -63,7 +63,7 @@ setup:
6363

6464
- do:
6565
search:
66-
rest_total_hits_as_int: true
66+
index: test
6767
body:
6868
query:
6969
rank_feature:
@@ -72,7 +72,7 @@ setup:
7272
pivot: 20
7373

7474
- match:
75-
hits.total: 2
75+
hits.total.value: 2
7676

7777
- match:
7878
hits.hits.0._id: "2"
@@ -85,7 +85,7 @@ setup:
8585

8686
- do:
8787
search:
88-
rest_total_hits_as_int: true
88+
index: test
8989
body:
9090
query:
9191
rank_feature:
@@ -95,7 +95,31 @@ setup:
9595
exponent: 0.6
9696

9797
- match:
98-
hits.total: 2
98+
hits.total.value: 2
99+
100+
- match:
101+
hits.hits.0._id: "2"
102+
103+
- match:
104+
hits.hits.1._id: "1"
105+
106+
---
107+
"Positive linear":
108+
- skip:
109+
version: " - 7.11.99"
110+
reason: Linear function was added in 7.12
111+
112+
- do:
113+
search:
114+
index: test
115+
body:
116+
query:
117+
rank_feature:
118+
field: pagerank
119+
linear: {}
120+
121+
- match:
122+
hits.total.value: 2
99123

100124
- match:
101125
hits.hits.0._id: "2"
@@ -109,7 +133,7 @@ setup:
109133
- do:
110134
catch: bad_request
111135
search:
112-
rest_total_hits_as_int: true
136+
index: test
113137
body:
114138
query:
115139
rank_feature:
@@ -122,7 +146,7 @@ setup:
122146

123147
- do:
124148
search:
125-
rest_total_hits_as_int: true
149+
index: test
126150
body:
127151
query:
128152
rank_feature:
@@ -131,7 +155,7 @@ setup:
131155
pivot: 20
132156

133157
- match:
134-
hits.total: 2
158+
hits.total.value: 2
135159

136160
- match:
137161
hits.hits.0._id: "2"
@@ -144,7 +168,7 @@ setup:
144168

145169
- do:
146170
search:
147-
rest_total_hits_as_int: true
171+
index: test
148172
body:
149173
query:
150174
rank_feature:
@@ -154,7 +178,31 @@ setup:
154178
exponent: 0.6
155179

156180
- match:
157-
hits.total: 2
181+
hits.total.value: 2
182+
183+
- match:
184+
hits.hits.0._id: "2"
185+
186+
- match:
187+
hits.hits.1._id: "1"
188+
189+
---
190+
"Negative linear":
191+
- skip:
192+
version: " - 7.11.99"
193+
reason: Linear function was added in 7.12
194+
195+
- do:
196+
search:
197+
index: test
198+
body:
199+
query:
200+
rank_feature:
201+
field: url_length
202+
linear: {}
203+
204+
- match:
205+
hits.total.value: 2
158206

159207
- match:
160208
hits.hits.0._id: "2"

0 commit comments

Comments
 (0)