class BarrierOptionPricer {

  public double outBarrier(final double spot, final double barrierLevel, final double strike, final double expiry, final double rate, final double carry,
      final double vol, final boolean isCall, final double rebate) {

    final Function1D<Double, Double> intCon = ICP.getEuropeanPayoff(strike, isCall);
    final ConvectionDiffusionPDE1DStandardCoefficients pde = PDE.getBlackScholes(rate, rate - carry, vol);
    final boolean isUp = barrierLevel > spot;

    final double adj = 0.0; // _lambda == 0 ? ZETA * vol * Math.sqrt(expiry / (_nTNodes - 1)) : 0.0;

    double sMin;
    double sMax;
    BoundaryCondition lower;
    BoundaryCondition upper;
    if (isUp) {
      sMin = 0.0;
      sMax = barrierLevel * Math.exp(-adj); //bring the barrier DOWN slightly to adjust for discrete monitoring
      if (isCall) {
        lower = new DirichletBoundaryCondition(0.0, sMin);
      } else {
        final Function1D<Double, Double> lowerValue = new Function1D<Double, Double>() {
          @Override
          public Double evaluate(final Double tau) {
            return Math.exp(-rate * tau) * strike;
          }
        };
        lower = new DirichletBoundaryCondition(lowerValue, sMin);
      }
      upper = new DirichletBoundaryCondition(rebate, sMax);
    } else {
      sMin = barrierLevel * Math.exp(adj); //bring the barrier UP slightly to adjust for discrete monitoring
      sMax = spot * Math.exp(_z * Math.sqrt(expiry));
      lower = new DirichletBoundaryCondition(rebate, sMin);

      if (isCall) {
        final Function1D<Double, Double> upperValue = new Function1D<Double, Double>() {
          @Override
          public Double evaluate(final Double tau) {
            return Math.exp(-rate * tau) * (spot * Math.exp(carry * tau) - strike);
          }
        };
        upper = new DirichletBoundaryCondition(upperValue, sMax);
      } else {
        upper = new DirichletBoundaryCondition(0.0, sMax);
      }
    }

    final MeshingFunction tMesh = new ExponentialMeshing(0, expiry, _nTNodes, _lambda);
    final MeshingFunction xMesh = new HyperbolicMeshing(sMin, sMax, spot, _nXNodes, _bunching);
    final PDEGrid1D grid = new PDEGrid1D(tMesh, xMesh);
    final PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients> pdeData = new PDE1DDataBundle<ConvectionDiffusionPDE1DCoefficients>(pde, intCon, lower, upper, grid);

    final PDEResults1D res = SOLVER.solve(pdeData);
    //for now just do linear interpolation on price. TODO replace this with something more robust
    final double[] xNodes = grid.getSpaceNodes();

    final int index = SurfaceArrayUtils.getLowerBoundIndex(xNodes, spot);
    final double w = (xNodes[index + 1] - spot) / (xNodes[index + 1] - xNodes[index]);
    return w * res.getFunctionValue(index) + (1 - w) * res.getFunctionValue(index + 1);
  }

  public  BarrierOptionPricer();
  public  BarrierOptionPricer(final int numXNodes, final int numTNodes, final double lambda, final double bunching);

}