FAST_AR_TRENDING_SCRIPT = """
double softenLBC(double lbc) { return (Math.pow(lbc, 1.0 / 3.0)); }

double logsumexp(double x, double y)
{
    double top;
    if(x > y)
        top = x;
    else
        top = y;
    double result = top + Math.log(Math.exp(x-top) + Math.exp(y-top));
    return(result);
}

double logdiffexp(double big, double small)
{
    return big + Math.log(1.0 - Math.exp(small - big));
}

double squash(double x)
{
    if(x < 0.0)
return -Math.log(1.0 - x);
    else
return Math.log(x + 1.0);
}

double unsquash(double x)
{
    if(x < 0.0)
        return 1.0 - Math.exp(-x);
    else
        return Math.exp(x) - 1.0;
}

double log_to_squash(double x)
{
    return logsumexp(x, 0.0);
}

double squash_to_log(double x)
{
    //assert x > 0.0;
    return logdiffexp(x, 0.0);
}

double squashed_add(double x, double y)
{
    // squash(unsquash(x) + unsquash(y)) but avoiding overflow.
    // Cases where the signs are the same
    if (x < 0.0 && y < 0.0)
        return -logsumexp(-x, logdiffexp(-y, 0.0));
    if (x >= 0.0 && y >= 0.0)
        return logsumexp(x, logdiffexp(y, 0.0));
    // Where the signs differ
    if (x >= 0.0 && y < 0.0)
        if (Math.abs(x) >= Math.abs(y))
            return logsumexp(0.0, logdiffexp(x, -y));
        else
            return -logsumexp(0.0, logdiffexp(-y, x));
    if (x < 0.0 && y >= 0.0)
    {
        // Addition is commutative, hooray for new math
        return squashed_add(y, x);
    }
    return 0.0;
}

double squashed_multiply(double x, double y)
{
    // squash(unsquash(x)*unsquash(y)) but avoiding overflow.
    int sign;
    if(x*y >= 0.0)
sign = 1;
    else
sign = -1;
    return sign*logsumexp(squash_to_log(Math.abs(x))
    + squash_to_log(Math.abs(y)), 0.0);
}

// Squashed inflated units
double inflateUnits(int height) {
    double timescale = 576.0; // Half life of 400 = e-folding time of a day
      // by coincidence, so may as well go with it
    return log_to_squash(height / timescale);
}

double spikePower(double newAmount) {
    if (newAmount < 50.0) {
        return(0.5);
    } else if (newAmount < 85.0) {
        return(newAmount / 100.0);
    } else {
        return(0.85);
    }
}

double spikeMass(double oldAmount, double newAmount) {
    double softenedChange = softenLBC(Math.abs(newAmount - oldAmount));
    double changeInSoftened = Math.abs(softenLBC(newAmount) - softenLBC(oldAmount));
    double power = spikePower(newAmount);
    if (oldAmount > newAmount) {
        -1.0 * Math.pow(changeInSoftened, power) * Math.pow(softenedChange, 1.0 - power)
    } else {
        Math.pow(changeInSoftened, power) * Math.pow(softenedChange, 1.0 - power)
    }
}

for (i in params.src.changes) {
    double units = inflateUnits(i.height);
    if (ctx._source.trending_score == null) {
        ctx._source.trending_score = 0.0;
    }
    double bigSpike = squashed_multiply(units, squash(spikeMass(i.prev_amount, i.new_amount)));
    ctx._source.trending_score = squashed_add(ctx._source.trending_score, bigSpike);
}
"""