protected double getResidual(
      final double fwd, final double expiry, final double[] ks, final double[] vols) {

    // Check for trivial case where cutoff is so low that there's no effective value in the option
    final double cutoffPrice =
        BlackFormulaRepository.price(fwd, ks[0], expiry, vols[0], ks[0] > fwd);
    if (CompareUtils.closeEquals(cutoffPrice, 0)) {
      return 0.0; // i.e. the tail function is never used
    }
    // The typical case - fit a  ShiftedLognormal to the two strike-vol pairs
    final ShiftedLognormalVolModel leftExtrapolator =
        new ShiftedLognormalVolModel(fwd, expiry, ks[0], vols[0], ks[1], vols[1]);

    // Now, handle behaviour near zero strike. ShiftedLognormalVolModel has non-zero put price for
    // zero strike.
    // What we do is to find the strike, k_min, at which f(k) = p(k)/k^2 begins to blow up, by
    // finding the minimum of this function, k_min
    // then setting f(k) = f(k_min) for k < k_min. This ensures the implied volatility and the
    // integrand are well behaved in the limit k -> 0.
    final Function1D<Double, Double> shiftedLnIntegrand =
        new Function1D<Double, Double>() {
          @Override
          public Double evaluate(final Double strike) {
            return leftExtrapolator.priceFromFixedStrike(strike) / (strike * strike);
          }
        };
    final double kMin = new BrentMinimizer1D().minimize(shiftedLnIntegrand, EPS, EPS, ks[0]);
    final double fMin = shiftedLnIntegrand.evaluate(kMin);
    double res = fMin * kMin; // the (hopefully) very small rectangular bit between zero and kMin

    res += _integrator.integrate(shiftedLnIntegrand, kMin, ks[0]);

    return res;
  }