Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
0.00% |
0 / 21 |
|
0.00% |
0 / 4 |
CRAP | |
0.00% |
0 / 1 |
LogScaleBoostFunctionScoreBuilder | |
0.00% |
0 / 21 |
|
0.00% |
0 / 4 |
72 | |
0.00% |
0 / 1 |
__construct | |
0.00% |
0 / 10 |
|
0.00% |
0 / 1 |
20 | |||
findCenterFactor | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
6 | |||
append | |
0.00% |
0 / 3 |
|
0.00% |
0 / 1 |
2 | |||
getScript | |
0.00% |
0 / 4 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace CirrusSearch\Search\Rescore; |
4 | |
5 | use CirrusSearch\SearchConfig; |
6 | use Elastica\Query\FunctionScore; |
7 | |
8 | /** |
9 | * Normalize values in the [0,1] range |
10 | * Allows to set: |
11 | * - a scale |
12 | * - midpoint |
13 | * It will generate a log scale factor where : |
14 | * - f(0) = 0 |
15 | * - f(scale) = 1 |
16 | * - f(midpoint) = 0.5 |
17 | * |
18 | * Based on log10( a . x + 1 ) / log10( a . M + 1 ) |
19 | * a: a factor used to adjust the midpoint |
20 | * M: the max value used to scale |
21 | * |
22 | */ |
23 | class LogScaleBoostFunctionScoreBuilder extends FunctionScoreBuilder { |
24 | /** @var string */ |
25 | private $field; |
26 | /** @var float */ |
27 | private $midpoint; |
28 | /** @var float */ |
29 | private $scale; |
30 | |
31 | /** |
32 | * @param SearchConfig $config |
33 | * @param float $weight |
34 | * @param array $profile |
35 | * @throws InvalidRescoreProfileException |
36 | */ |
37 | public function __construct( SearchConfig $config, $weight, $profile ) { |
38 | parent::__construct( $config, $weight ); |
39 | |
40 | if ( isset( $profile['midpoint'] ) ) { |
41 | $this->midpoint = $this->getOverriddenFactor( $profile['midpoint'] ); |
42 | } else { |
43 | throw new InvalidRescoreProfileException( 'midpoint is mandatory' ); |
44 | } |
45 | |
46 | if ( isset( $profile['scale'] ) ) { |
47 | $this->scale = $this->getOverriddenFactor( $profile['scale'] ); |
48 | } else { |
49 | throw new InvalidRescoreProfileException( 'scale is mandatory' ); |
50 | } |
51 | |
52 | if ( isset( $profile['field'] ) ) { |
53 | $this->field = $profile['field']; |
54 | } else { |
55 | throw new InvalidRescoreProfileException( 'field is mandatory' ); |
56 | } |
57 | } |
58 | |
59 | /** |
60 | * find the factor to adjust the scale center, |
61 | * it's like finding the log base to have f(N) = 0.5 |
62 | * |
63 | * @param float $M |
64 | * @param float $N |
65 | * @return float |
66 | * @throws InvalidRescoreProfileException |
67 | */ |
68 | private function findCenterFactor( $M, $N ) { |
69 | // Neutral point is found by resolving |
70 | // log10( x . N + 1 ) / log10( x . M + 1 ) = 0.5 |
71 | // it's equivalent to resolving: |
72 | // N²x² + (2N - M)x + 1 = 0 |
73 | // so we we use the quadratic formula: |
74 | // (-(2N-M) + sqrt((2N-M)²-4N²)) / 2N² |
75 | if ( 4 * $N >= $M ) { |
76 | throw new InvalidRescoreProfileException( 'The midpoint point cannot be higher than scale/4' ); |
77 | } |
78 | |
79 | return ( -( 2 * $N - $M ) + sqrt( ( 2 * $N - $M ) * ( 2 * $N - $M ) - 4 * $N * $N ) ) / |
80 | ( 2 * $N * $N ); |
81 | } |
82 | |
83 | public function append( FunctionScore $functionScore ) { |
84 | $formula = $this->getScript(); |
85 | |
86 | $functionScore->addScriptScoreFunction( new \Elastica\Script\Script( $formula, null, |
87 | 'expression' ), null, $this->weight ); |
88 | } |
89 | |
90 | /** |
91 | * @return string |
92 | */ |
93 | public function getScript() { |
94 | $midFactor = $this->findCenterFactor( $this->scale, $this->midpoint ); |
95 | $formula = "log10($midFactor * min(doc['{$this->field}'].value,{$this->scale}) + 1)"; |
96 | $formula .= "/log10($midFactor * {$this->scale} + 1)"; |
97 | |
98 | return $formula; |
99 | } |
100 | } |