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 | class LogScaleBoostFunctionScoreBuilder extends FunctionScoreBuilder { |
| 23 | /** @var string */ |
| 24 | private $field; |
| 25 | /** @var float */ |
| 26 | private $midpoint; |
| 27 | /** @var float */ |
| 28 | private $scale; |
| 29 | |
| 30 | /** |
| 31 | * @param SearchConfig $config |
| 32 | * @param float $weight |
| 33 | * @param array $profile |
| 34 | * @throws InvalidRescoreProfileException |
| 35 | */ |
| 36 | public function __construct( SearchConfig $config, $weight, $profile ) { |
| 37 | parent::__construct( $config, $weight ); |
| 38 | |
| 39 | if ( isset( $profile['midpoint'] ) ) { |
| 40 | $this->midpoint = $this->getOverriddenFactor( $profile['midpoint'] ); |
| 41 | } else { |
| 42 | throw new InvalidRescoreProfileException( 'midpoint is mandatory' ); |
| 43 | } |
| 44 | |
| 45 | if ( isset( $profile['scale'] ) ) { |
| 46 | $this->scale = $this->getOverriddenFactor( $profile['scale'] ); |
| 47 | } else { |
| 48 | throw new InvalidRescoreProfileException( 'scale is mandatory' ); |
| 49 | } |
| 50 | |
| 51 | if ( isset( $profile['field'] ) ) { |
| 52 | $this->field = $profile['field']; |
| 53 | } else { |
| 54 | throw new InvalidRescoreProfileException( 'field is mandatory' ); |
| 55 | } |
| 56 | } |
| 57 | |
| 58 | /** |
| 59 | * find the factor to adjust the scale center, |
| 60 | * it's like finding the log base to have f(N) = 0.5 |
| 61 | * |
| 62 | * @param float $M |
| 63 | * @param float $N |
| 64 | * @return float |
| 65 | * @throws InvalidRescoreProfileException |
| 66 | */ |
| 67 | private function findCenterFactor( $M, $N ) { |
| 68 | // Neutral point is found by resolving |
| 69 | // log10( x . N + 1 ) / log10( x . M + 1 ) = 0.5 |
| 70 | // it's equivalent to resolving: |
| 71 | // N²x² + (2N - M)x + 1 = 0 |
| 72 | // so we we use the quadratic formula: |
| 73 | // (-(2N-M) + sqrt((2N-M)²-4N²)) / 2N² |
| 74 | if ( 4 * $N >= $M ) { |
| 75 | throw new InvalidRescoreProfileException( 'The midpoint point cannot be higher than scale/4' ); |
| 76 | } |
| 77 | |
| 78 | return ( -( 2 * $N - $M ) + sqrt( ( 2 * $N - $M ) * ( 2 * $N - $M ) - 4 * $N * $N ) ) / |
| 79 | ( 2 * $N * $N ); |
| 80 | } |
| 81 | |
| 82 | public function append( FunctionScore $functionScore ) { |
| 83 | $formula = $this->getScript(); |
| 84 | |
| 85 | $functionScore->addScriptScoreFunction( new \Elastica\Script\Script( $formula, null, |
| 86 | 'expression' ), null, $this->weight ); |
| 87 | } |
| 88 | |
| 89 | /** |
| 90 | * @return string |
| 91 | */ |
| 92 | public function getScript() { |
| 93 | $midFactor = $this->findCenterFactor( $this->scale, $this->midpoint ); |
| 94 | $formula = "log10($midFactor * min(doc['{$this->field}'].value,{$this->scale}) + 1)"; |
| 95 | $formula .= "/log10($midFactor * {$this->scale} + 1)"; |
| 96 | |
| 97 | return $formula; |
| 98 | } |
| 99 | } |