public FdmBlackScholesMultiStrikeMesher(int size,
                                                GeneralizedBlackScholesProcess process,
                                                double maturity,
                                                List <double> strikes,
                                                double eps         = 0.0001,
                                                double scaleFactor = 1.5,
                                                Pair <double?, double?> cPoint
                                                = null)
            : base(size)
        {
            double spot = process.x0();

            Utils.QL_REQUIRE(spot > 0.0, () => "negative or null underlying given");

            double d = process.dividendYield().currentLink().discount(maturity)
                       / process.riskFreeRate().currentLink().discount(maturity);
            double minStrike = strikes.Min();
            double maxStrike = strikes.Max();

            double Fmin = spot * spot / maxStrike * d;
            double Fmax = spot * spot / minStrike * d;

            Utils.QL_REQUIRE(Fmin > 0.0, () => "negative forward given");

            // Set the grid boundaries
            double normInvEps = new InverseCumulativeNormal().value(1 - eps);
            double sigmaSqrtTmin
                = process.blackVolatility().currentLink().blackVol(maturity, minStrike)
                  * Math.Sqrt(maturity);
            double sigmaSqrtTmax
                = process.blackVolatility().currentLink().blackVol(maturity, maxStrike)
                  * Math.Sqrt(maturity);

            double xMin
                = Math.Min(0.8 * Math.Log(0.8 * spot * spot / maxStrike),
                           Math.Log(Fmin) - sigmaSqrtTmin * normInvEps * scaleFactor
                           - sigmaSqrtTmin * sigmaSqrtTmin / 2.0);
            double xMax
                = Math.Max(1.2 * Math.Log(0.8 * spot * spot / minStrike),
                           Math.Log(Fmax) + sigmaSqrtTmax * normInvEps * scaleFactor
                           - sigmaSqrtTmax * sigmaSqrtTmax / 2.0);

            Fdm1dMesher helper;

            if (cPoint.first != null &&
                Math.Log(cPoint.first.Value) >= xMin && Math.Log(cPoint.first.Value) <= xMax)
            {
                helper = new Concentrating1dMesher(xMin, xMax, size,
                                                   new Pair <double?, double?>(Math.Log(cPoint.first.Value), cPoint.second));
            }
            else
            {
                helper = new Uniform1dMesher(xMin, xMax, size);
            }

            locations_ = helper.locations();
            for (int i = 0; i < locations_.Count; ++i)
            {
                dplus_[i]  = helper.dplus(i);
                dminus_[i] = helper.dminus(i);
            }
        }
        public FdmBlackScholesMesher(int size,
                                     GeneralizedBlackScholesProcess process,
                                     double maturity, double strike,
                                     double?xMinConstraint = null,
                                     double?xMaxConstraint = null,
                                     double eps            = 0.0001,
                                     double scaleFactor    = 1.5,
                                     Pair <double?, double?> cPoint
                                     = null,
                                     DividendSchedule dividendSchedule = null,
                                     FdmQuantoHelper fdmQuantoHelper   = null,
                                     double spotAdjustment             = 0.0)
            : base(size)
        {
            double S = process.x0();

            Utils.QL_REQUIRE(S > 0.0, () => "negative or null underlying given");

            dividendSchedule = dividendSchedule == null ? new DividendSchedule() : dividendSchedule;
            List <pair_double> intermediateSteps = new List <pair_double>();

            for (int i = 0; i < dividendSchedule.Count &&
                 process.time(dividendSchedule[i].date()) <= maturity; ++i)
            {
                intermediateSteps.Add(
                    new pair_double(
                        process.time(dividendSchedule[i].date()),
                        dividendSchedule[i].amount()
                        ));
            }

            int intermediateTimeSteps = (int)Math.Max(2, 24.0 * maturity);

            for (int i = 0; i < intermediateTimeSteps; ++i)
            {
                intermediateSteps.Add(
                    new pair_double((i + 1) * (maturity / intermediateTimeSteps), 0.0));
            }

            intermediateSteps.Sort();

            Handle <YieldTermStructure> rTS = process.riskFreeRate();
            Handle <YieldTermStructure> qTS = fdmQuantoHelper != null
                                          ? new Handle <YieldTermStructure>(
                new QuantoTermStructure(process.dividendYield(),
                                        process.riskFreeRate(),
                                        new Handle <YieldTermStructure>(fdmQuantoHelper.foreignTermStructure()),
                                        process.blackVolatility(),
                                        strike,
                                        new Handle <BlackVolTermStructure>(fdmQuantoHelper.fxVolatilityTermStructure()),
                                        fdmQuantoHelper.exchRateATMlevel(),
                                        fdmQuantoHelper.equityFxCorrelation()))
                                          : process.dividendYield();

            double lastDivTime = 0.0;
            double fwd = S + spotAdjustment;
            double mi = fwd, ma = fwd;

            for (int i = 0; i < intermediateSteps.Count; ++i)
            {
                double divTime   = intermediateSteps[i].first;
                double divAmount = intermediateSteps[i].second;

                fwd = fwd / rTS.currentLink().discount(divTime) * rTS.currentLink().discount(lastDivTime)
                      * qTS.currentLink().discount(divTime) / qTS.currentLink().discount(lastDivTime);

                mi = Math.Min(mi, fwd); ma = Math.Max(ma, fwd);

                fwd -= divAmount;

                mi = Math.Min(mi, fwd); ma = Math.Max(ma, fwd);

                lastDivTime = divTime;
            }

            // Set the grid boundaries
            double normInvEps = new InverseCumulativeNormal().value(1 - eps);
            double sigmaSqrtT
                = process.blackVolatility().currentLink().blackVol(maturity, strike)
                  * Math.Sqrt(maturity);

            double?xMin = Math.Log(mi) - sigmaSqrtT * normInvEps * scaleFactor;
            double?xMax = Math.Log(ma) + sigmaSqrtT * normInvEps * scaleFactor;

            if (xMinConstraint != null)
            {
                xMin = xMinConstraint;
            }
            if (xMaxConstraint != null)
            {
                xMax = xMaxConstraint;
            }

            Fdm1dMesher helper;

            if (cPoint != null &&
                cPoint.first != null &&
                Math.Log(cPoint.first.Value) >= xMin && Math.Log(cPoint.first.Value) <= xMax)
            {
                helper = new Concentrating1dMesher(xMin.Value, xMax.Value, size,
                                                   new Pair <double?, double?>(Math.Log(cPoint.first.Value), cPoint.second));
            }
            else
            {
                helper = new Uniform1dMesher(xMin.Value, xMax.Value, size);
            }

            locations_ = helper.locations();
            for (int i = 0; i < locations_.Count; ++i)
            {
                dplus_[i]  = helper.dplus(i);
                dminus_[i] = helper.dminus(i);
            }
        }