// -------------------------------------------------------------------------
 @Override
 public double volatility(
     double forward, double strike, double timeToExpiry, SsviFormulaData data) {
   ArgChecker.isTrue(
       timeToExpiry > MIN_TIME_TO_EXPIRY,
       "time to expiry must not be zero to be able to compute volatility");
   double volatilityAtm = data.getSigma();
   double rho = data.getRho();
   double eta = data.getEta();
   double theta = volatilityAtm * volatilityAtm * timeToExpiry;
   double phi = eta / Math.sqrt(theta);
   double k = Math.log(strike / forward);
   double w =
       0.5
           * theta
           * (1.0d + rho * phi * k + Math.sqrt(1.0d + 2 * rho * phi * k + phi * k * phi * k));
   return Math.sqrt(w / timeToExpiry);
 }
 /**
  * Computes the implied volatility in the SSVI formula and its derivatives.
  *
  * <p>The derivatives are stored in an array with:
  *
  * <ul>
  *   <li>[0] derivative with respect to the forward
  *   <li>[1] derivative with respect to the strike
  *   <li>[2] derivative with respect to the time to expiry
  *   <li>[3] derivative with respect to the sigma (ATM volatility)
  *   <li>[4] derivative with respect to the rho
  *   <li>[5] derivative with respect to the eta
  * </ul>
  *
  * @param forward the forward value of the underlying
  * @param strike the strike value of the option
  * @param timeToExpiry the time to expiry of the option
  * @param data the SSVI data
  * @return the volatility and associated derivatives
  */
 @Override
 public ValueDerivatives volatilityAdjoint(
     double forward, double strike, double timeToExpiry, SsviFormulaData data) {
   ArgChecker.isTrue(
       timeToExpiry > MIN_TIME_TO_EXPIRY,
       "time to expiry must not be zero to be able to compute volatility");
   double volatilityAtm = data.getSigma();
   double rho = data.getRho();
   double eta = data.getEta();
   double theta = volatilityAtm * volatilityAtm * timeToExpiry;
   double stheta = Math.sqrt(theta);
   double phi = eta / stheta;
   double k = Math.log(strike / forward);
   double s = Math.sqrt(1.0d + 2 * rho * phi * k + phi * k * phi * k);
   double w = 0.5 * theta * (1.0d + rho * phi * k + s);
   double volatility = Math.sqrt(w / timeToExpiry);
   // Backward sweep.
   double[] derivatives = new double[6]; // 6 inputs
   double volatilityBar = 1.0;
   double wBar = 0.5 * volatility / w * volatilityBar;
   derivatives[2] += -0.5 * volatility / timeToExpiry * volatilityBar;
   double thetaBar = w / theta * wBar;
   derivatives[4] += 0.5 * theta * phi * k * wBar;
   double phiBar = 0.5 * theta * rho * k * wBar;
   double kBar = 0.5 * theta * rho * phi * wBar;
   double sBar = 0.5 * theta * wBar;
   derivatives[4] += phi * k / s * sBar;
   phiBar += (rho * k + phi * k * k) / s * sBar;
   kBar += (rho * phi + phi * phi * k) / s * sBar;
   derivatives[1] += 1.0d / strike * kBar;
   derivatives[0] += -1.0d / forward * kBar;
   derivatives[5] += phiBar / stheta;
   double sthetaBar = -eta / (stheta * stheta) * phiBar;
   thetaBar += 0.5 / stheta * sthetaBar;
   derivatives[3] += 2 * volatilityAtm * timeToExpiry * thetaBar;
   derivatives[2] += volatilityAtm * volatilityAtm * thetaBar;
   return ValueDerivatives.of(volatility, DoubleArray.ofUnsafe(derivatives));
 }