includes/Search/Rescore/LogScaleBoostFunctionScoreBuilder.php
<?php
namespace CirrusSearch\Search\Rescore;
use CirrusSearch\SearchConfig;
use Elastica\Query\FunctionScore;
/**
* Normalize values in the [0,1] range
* Allows to set:
* - a scale
* - midpoint
* It will generate a log scale factor where :
* - f(0) = 0
* - f(scale) = 1
* - f(midpoint) = 0.5
*
* Based on log10( a . x + 1 ) / log10( a . M + 1 )
* a: a factor used to adjust the midpoint
* M: the max value used to scale
*
*/
class LogScaleBoostFunctionScoreBuilder extends FunctionScoreBuilder {
/** @var string */
private $field;
/** @var float */
private $midpoint;
/** @var float */
private $scale;
/**
* @param SearchConfig $config
* @param float $weight
* @param array $profile
* @throws InvalidRescoreProfileException
*/
public function __construct( SearchConfig $config, $weight, $profile ) {
parent::__construct( $config, $weight );
if ( isset( $profile['midpoint'] ) ) {
$this->midpoint = $this->getOverriddenFactor( $profile['midpoint'] );
} else {
throw new InvalidRescoreProfileException( 'midpoint is mandatory' );
}
if ( isset( $profile['scale'] ) ) {
$this->scale = $this->getOverriddenFactor( $profile['scale'] );
} else {
throw new InvalidRescoreProfileException( 'scale is mandatory' );
}
if ( isset( $profile['field'] ) ) {
$this->field = $profile['field'];
} else {
throw new InvalidRescoreProfileException( 'field is mandatory' );
}
}
/**
* find the factor to adjust the scale center,
* it's like finding the log base to have f(N) = 0.5
*
* @param float $M
* @param float $N
* @return float
* @throws InvalidRescoreProfileException
*/
private function findCenterFactor( $M, $N ) {
// Neutral point is found by resolving
// log10( x . N + 1 ) / log10( x . M + 1 ) = 0.5
// it's equivalent to resolving:
// N²x² + (2N - M)x + 1 = 0
// so we we use the quadratic formula:
// (-(2N-M) + sqrt((2N-M)²-4N²)) / 2N²
if ( 4 * $N >= $M ) {
throw new InvalidRescoreProfileException( 'The midpoint point cannot be higher than scale/4' );
}
return ( -( 2 * $N - $M ) + sqrt( ( 2 * $N - $M ) * ( 2 * $N - $M ) - 4 * $N * $N ) ) /
( 2 * $N * $N );
}
public function append( FunctionScore $functionScore ) {
$formula = $this->getScript();
$functionScore->addScriptScoreFunction( new \Elastica\Script\Script( $formula, null,
'expression' ), null, $this->weight );
}
/**
* @return string
*/
public function getScript() {
$midFactor = $this->findCenterFactor( $this->scale, $this->midpoint );
$formula = "log10($midFactor * min(doc['{$this->field}'].value,{$this->scale}) + 1)";
$formula .= "/log10($midFactor * {$this->scale} + 1)";
return $formula;
}
}