class FastCreditCurveCalibrator extends IsdaCompliantCreditCurveCalibrator {

  @Override
  public NodalCurve calibrate(
      List<ResolvedCdsTrade> calibrationCDSs,
      DoubleArray flactionalSpreads,
      DoubleArray pointsUpfront,
      CurveName name,
      LocalDate valuationDate,
      CreditDiscountFactors discountFactors,
      RecoveryRates recoveryRates,
      ReferenceData refData) {

    int n = calibrationCDSs.size();
    double[] guess = new double[n];
    double[] t = new double[n];
    double[] lgd = new double[n];
    for (int i = 0; i < n; i++) {
      LocalDate endDate = calibrationCDSs.get(i).getProduct().getProtectionEndDate();
      t[i] = discountFactors.relativeYearFraction(endDate);
      lgd[i] = 1d - recoveryRates.recoveryRate(endDate);
      guess[i] = (flactionalSpreads.get(i) + pointsUpfront.get(i) / t[i]) / lgd[i];
    }
    DoubleArray times = DoubleArray.ofUnsafe(t);
    CurveMetadata baseMetadata = DefaultCurveMetadata.builder()
        .xValueType(ValueType.YEAR_FRACTION)
        .yValueType(ValueType.ZERO_RATE)
        .curveName(name)
        .dayCount(discountFactors.getDayCount())
        .build();
    NodalCurve creditCurve = n == 1 ?
        ConstantNodalCurve.of(baseMetadata, t[0], guess[0]) :
        InterpolatedNodalCurve.of(
            baseMetadata,
            times,
            DoubleArray.ofUnsafe(guess),
            CurveInterpolators.PRODUCT_LINEAR,
            CurveExtrapolators.FLAT,
            CurveExtrapolators.PRODUCT_LINEAR);

    for (int i = 0; i < n; i++) {
      ResolvedCds cds = calibrationCDSs.get(i).getProduct();
      LocalDate stepinDate = cds.getStepinDateOffset().adjust(valuationDate, refData);
      LocalDate effectiveStartDate = cds.calculateEffectiveStartDate(stepinDate);
      LocalDate settlementDate = calibrationCDSs.get(i).getInfo().getSettlementDate()
          .orElse(cds.getSettlementDateOffset().adjust(valuationDate, refData));
      double accrued = cds.accruedYearFraction(stepinDate);

      Pricer pricer = new Pricer(cds, discountFactors, times, flactionalSpreads.get(i), pointsUpfront.get(i), lgd[i], stepinDate,
          effectiveStartDate, settlementDate, accrued);
      Function<Double, Double> func = pricer.getPointFunction(i, creditCurve);

      switch (getArbitrageHandling()) {
        case IGNORE: {
          try {
            double[] bracket = BRACKETER.getBracketedPoints(
                func, 0.8 * guess[i], 1.25 * guess[i], Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
            double zeroRate = bracket[0] > bracket[1] ?
                ROOTFINDER.getRoot(func, bracket[1], bracket[0]) :
                ROOTFINDER.getRoot(func, bracket[0], bracket[1]); //Negative guess handled
            creditCurve = creditCurve.withParameter(i, zeroRate);
          } catch (final MathException e) { //handling bracketing failure due to small survival probability
            if (Math.abs(func.apply(creditCurve.getYValues().get(i - 1))) < 1.e-12) {
              creditCurve = creditCurve.withParameter(i, creditCurve.getYValues().get(i - 1));
            } else {
              throw new MathException(e);
            }
          }
          break;
        }
        case FAIL: {
          final double minValue = i == 0 ?
              0d :
              creditCurve.getYValues().get(i - 1) * creditCurve.getXValues().get(i - 1) / creditCurve.getXValues().get(i);
          if (i > 0 && func.apply(minValue) > 0.0) { //can never fail on the first spread
            final StringBuilder msg = new StringBuilder();
            if (pointsUpfront.get(i) == 0.0) {
              msg.append("The par spread of " + flactionalSpreads.get(i) + " at index " + i);
            } else {
              msg.append(
                  "The premium of " + flactionalSpreads.get(i) +
                      "and points up-front of " + pointsUpfront.get(i) + " at index " + i);
            }
            msg.append(" is an arbitrage; cannot fit a curve with positive forward hazard rate. ");
            throw new IllegalArgumentException(msg.toString());
          }
          guess[i] = Math.max(minValue, guess[i]);
          double[] bracket = BRACKETER.getBracketedPoints(func, guess[i], 1.2 * guess[i], minValue, Double.POSITIVE_INFINITY);
          double zeroRate = ROOTFINDER.getRoot(func, bracket[0], bracket[1]);
          creditCurve = creditCurve.withParameter(i, zeroRate);
          break;
        }
        case ZERO_HAZARD_RATE: {
          final double minValue = i == 0 ?
              0.0 :
              creditCurve.getYValues().get(i - 1) * creditCurve.getXValues().get(i - 1) / creditCurve.getXValues().get(i);
          if (i > 0 && func.apply(minValue) > 0.0) { //can never fail on the first spread
            creditCurve = creditCurve.withParameter(i, minValue);
          } else {
            guess[i] = Math.max(minValue, guess[i]);
            final double[] bracket =
                BRACKETER.getBracketedPoints(func, guess[i], 1.2 * guess[i], minValue, Double.POSITIVE_INFINITY);
            final double zeroRate = ROOTFINDER.getRoot(func, bracket[0], bracket[1]);
            creditCurve = creditCurve.withParameter(i, zeroRate);
          }
          break;
        }
        default:
          throw new IllegalArgumentException("unknown case " + getArbitrageHandling());
      }
    }
    return creditCurve;
  }

  private  FastCreditCurveCalibrator();
  public  FastCreditCurveCalibrator(AccrualOnDefaultFormula formula);
  public  FastCreditCurveCalibrator(AccrualOnDefaultFormula formula, ArbitrageHandling arbHandling);

}