class TwoStateMarkovChainFitter {

  public LeastSquareResultsWithTransform fit(final ForwardCurve forward, final List<Pair<double[], Double>> marketVols, final DoubleMatrix1D initialGuess) {

    ArgumentChecker.isTrue(initialGuess.getNumberOfElements() == TRANSFORMS.getNumberOfModelParameters(),
        "Number of elements in initial guess {} did not match the number of parameter transforms {}", initialGuess.getNumberOfElements(),
        TRANSFORMS.getNumberOfFittingParameters());
    TRANSFORMS.transform(initialGuess);

    final int nMarketValues = marketVols.size();
    double tminT = Double.POSITIVE_INFINITY;
    double tminK = Double.POSITIVE_INFINITY;
    double tmaxT = 0;
    double tmaxK = 0;

    for (int i = 0; i < nMarketValues; i++) {
      final double[] tk = marketVols.get(i).getFirst();

      if (tk[0] > tmaxT) {
        tmaxT = tk[0];
      }
      if (tk[0] < tminT) {
        tminT = tk[0];
      }
      if (tk[1] > tmaxK) {
        tmaxK = tk[1];
      }
      if (tk[1] < tminK) {
        tminK = tk[1];
      }
    }

    final double minT = 0.6 * tminT;
    final double minK = 0.9 * tminK;
    final double maxT = 1.0 * tmaxT;
    final double maxK = 1.1 * tmaxK;

    final int tNodes = 20;
    final int xNodes = 100;

    //TODO remove hard coded grid
    final MeshingFunction timeMesh = new ExponentialMeshing(0, tmaxT, tNodes, 5.0);
    final MeshingFunction spaceMesh = new HyperbolicMeshing(0, 10.0 * forward.getForward(maxT), forward.getSpot(), xNodes, 0.01);
    final PDEGrid1D grid = new PDEGrid1D(timeMesh, spaceMesh);

    final Function1D<DoubleMatrix1D, DoubleMatrix1D> funcAppox = new Function1D<DoubleMatrix1D, DoubleMatrix1D>() {

      @SuppressWarnings("synthetic-access")
      @Override
      public DoubleMatrix1D evaluate(final DoubleMatrix1D x) {
        final DoubleMatrix1D y = TRANSFORMS.inverseTransform(x);
        final double vol1 = y.getEntry(0);
        final double deltaVol = y.getEntry(1);
        final double lambda12 = y.getEntry(2);
        final double lambda21 = y.getEntry(3);
        final double p0 = y.getEntry(4);
        final double beta = y.getEntry(5);

        final double[] modVols = new double[nMarketValues];
        for (int i = 0; i < nMarketValues; i++) {
          final double[] temp = marketVols.get(i).getFirst();
          final double t = temp[0];
          final double k = temp[1];
          final EuropeanVanillaOption option = new EuropeanVanillaOption(k, t, true);
          final BlackFunctionData data = new BlackFunctionData(forward.getForward(t), 1.0, 0.0);
          final MarkovChainApprox mca = new MarkovChainApprox(vol1, vol1 + deltaVol, lambda12, lambda21, p0, t);
          final double price = mca.priceCEV(data.getForward(), data.getDiscountFactor(), k, beta);
          try {
            modVols[i] = BLACK_IMPLIED_VOL.getImpliedVolatility(data, option, price);
          } catch (final Exception e) {
            modVols[i] = 0.0;
          }
        }
        return new DoubleMatrix1D(modVols);
      }
    };

    final Function1D<DoubleMatrix1D, DoubleMatrix1D> func = new Function1D<DoubleMatrix1D, DoubleMatrix1D>() {

      @SuppressWarnings("synthetic-access")
      @Override
      public DoubleMatrix1D evaluate(final DoubleMatrix1D x) {
        final DoubleMatrix1D y = TRANSFORMS.inverseTransform(x);
        final double vol1 = y.getEntry(0);
        final double deltaVol = y.getEntry(1);
        final double lambda12 = y.getEntry(2);
        final double lambda21 = y.getEntry(3);
        final double p0 = y.getEntry(4);
        final double beta = y.getEntry(5);
        final TwoStateMarkovChainDataBundle chainData = new TwoStateMarkovChainDataBundle(vol1, vol1 + deltaVol, lambda12, lambda21, p0, beta, beta);
        final TwoStateMarkovChainPricer mc = new TwoStateMarkovChainPricer(forward, chainData);
        final PDEFullResults1D res = mc.solve(grid, _theta);
        final Map<DoublesPair, Double> data = PDEUtilityTools.priceToImpliedVol(forward, res, minT, maxT, minK, maxK, true);
        final Map<Double, Interpolator1DDataBundle> dataBundle = GRID_INTERPOLATOR2D.getDataBundle(data);
        final double[] modVols = new double[nMarketValues];
        for (int i = 0; i < nMarketValues; i++) {
          final double[] temp = marketVols.get(i).getFirst();
          final DoublesPair tk = DoublesPair.of(temp[0], temp[1]);
          try {
            modVols[i] = GRID_INTERPOLATOR2D.interpolate(dataBundle, tk);
          } catch (final Exception e) {
            s_logger.error(e.getMessage());
          }
        }
        return new DoubleMatrix1D(modVols);
      }

    };

    final double[] mrkVols = new double[nMarketValues];
    final double[] sigma = new double[nMarketValues];
    for (int i = 0; i < nMarketValues; i++) {
      mrkVols[i] = marketVols.get(i).getSecond();
      sigma[i] = 0.01; //1% error
    }

    final NonLinearLeastSquare ls = new NonLinearLeastSquare();
    //solve approx first
    LeastSquareResults solverRes = ls.solve(new DoubleMatrix1D(mrkVols), new DoubleMatrix1D(sigma), funcAppox, TRANSFORMS.transform(initialGuess));
    // now solve pde model
    solverRes = ls.solve(new DoubleMatrix1D(mrkVols), new DoubleMatrix1D(sigma), func, solverRes.getFitParameters());
    return new LeastSquareResultsWithTransform(solverRes, TRANSFORMS);
  }

  public  TwoStateMarkovChainFitter();
  public  TwoStateMarkovChainFitter(final double theta);

}