//-------------------------------------------------------------------------
        // bumping a node point at (nodeExpiry, nodeStrike)
        private double nodeSensitivity(BlackFxOptionSurfaceVolatilities provider, CurrencyPair pair, ZonedDateTime expiry, double strike, double forward, double nodeExpiry, double nodeStrike)
        {
            NodalSurface surface = (NodalSurface)provider.Surface;
            DoubleArray  xValues = surface.XValues;
            DoubleArray  yValues = surface.YValues;
            DoubleArray  zValues = surface.ZValues;
            int          nData   = xValues.size();
            int          index   = -1;

            for (int i = 0; i < nData; ++i)
            {
                if (Math.Abs(xValues.get(i) - nodeExpiry) < TOLERANCE && Math.Abs(yValues.get(i) - nodeStrike) < TOLERANCE)
                {
                    index = i;
                }
            }
            Surface surfaceUp = surface.withZValues(zValues.with(index, zValues.get(index) + EPS));
            Surface surfaceDw = surface.withZValues(zValues.with(index, zValues.get(index) - EPS));
            BlackFxOptionSurfaceVolatilities provUp = BlackFxOptionSurfaceVolatilities.of(CURRENCY_PAIR, VAL_DATE_TIME, surfaceUp);
            BlackFxOptionSurfaceVolatilities provDw = BlackFxOptionSurfaceVolatilities.of(CURRENCY_PAIR, VAL_DATE_TIME, surfaceDw);
            double volUp = provUp.volatility(pair, expiry, strike, forward);
            double volDw = provDw.volatility(pair, expiry, strike, forward);

            return(0.5 * (volUp - volDw) / EPS);
        }
        private double volFromFormulaPrice(double r, double q, double time, double strike, NodalSurface surface)
        {
            double p   = surface.zValue(time, strike);
            double pT  = 0.5 / FD_EPS * (surface.zValue(time + FD_EPS, strike) - surface.zValue(time - FD_EPS, strike));
            double pK  = 0.5 / FD_EPS * (surface.zValue(time, strike + FD_EPS) - surface.zValue(time, strike - FD_EPS));
            double pKK = (surface.zValue(time, strike + FD_EPS) + surface.zValue(time, strike - FD_EPS) - 2d * p) / FD_EPS / FD_EPS;
            double var = 2d * (pT + (r - q) * strike * pK + q * p) / (strike * strike * pKK);

            return(Math.Sqrt(var));
        }
        private double volFromFormula(double r, double q, double time, double strike, NodalSurface surface)
        {
            double vol   = surface.zValue(time, strike);
            double volT  = 0.5 / FD_EPS * (surface.zValue(time + FD_EPS, strike) - surface.zValue(time - FD_EPS, strike));
            double volK  = 0.5 / FD_EPS * (surface.zValue(time, strike + FD_EPS) - surface.zValue(time, strike - FD_EPS));
            double volKK = (surface.zValue(time, strike + FD_EPS) + surface.zValue(time, strike - FD_EPS) - 2d * vol) / FD_EPS / FD_EPS;
            double rootT = Math.Sqrt(time);
            double d1    = (Math.Log(SPOT / strike) + (r - q + 0.5 * vol * vol) * time) / vol / rootT;
            double d2    = (Math.Log(SPOT / strike) + (r - q - 0.5 * vol * vol) * time) / vol / rootT;
            double den   = 1d + 2d * d1 * strike * rootT * volK + strike * strike * time * (d1 * d2 * volK * volK + vol * volKK);
            double var   = (vol * vol + 2d * vol * time * (volT + (r - q) * strike * volK)) / den;

            return(Math.Sqrt(var));
        }