* @param ccy The currency.
* @param hwData The Hull-White data (curves and Hull-White parameters).
* @return The curve sensitivity.
*/
public MultipleCurrencyMulticurveSensitivity presentValueCurveSensitivity(final InstrumentDerivative instrument, final Currency ccy, final HullWhiteOneFactorProviderInterface hwData) {
final MulticurveProviderInterface multicurves = hwData.getMulticurveProvider();
final HullWhiteOneFactorPiecewiseConstantParameters parameters = hwData.getHullWhiteParameters();
// Forward sweep
final DecisionScheduleDerivative decision = instrument.accept(DDC, multicurves);
final double[] decisionTime = decision.getDecisionTime();
final double[][] impactTime = decision.getImpactTime();
final int nbJump = decisionTime.length;
final double numeraireTime = decisionTime[nbJump - 1];
final double pDN = multicurves.getDiscountFactor(ccy, numeraireTime);
// Discount factor to numeraire date for rebasing.
final double[][] pDI = new double[nbJump][];
// Initial discount factors to each impact date.
for (int loopjump = 0; loopjump < nbJump; loopjump++) {
pDI[loopjump] = new double[impactTime[loopjump].length];
for (int i = 0; i < impactTime[loopjump].length; i++) {
pDI[loopjump][i] = multicurves.getDiscountFactor(ccy, impactTime[loopjump][i]) / pDN;
}
}
final double[] gamma = new double[nbJump];
final double[][] cov = new double[nbJump][nbJump];
for (int loopjump = 0; loopjump < nbJump; loopjump++) {
gamma[loopjump] = MODEL.beta(parameters, 0.0, decisionTime[loopjump]);
gamma[loopjump] = gamma[loopjump] * gamma[loopjump];
cov[loopjump][loopjump] = gamma[loopjump];
for (int j = loopjump + 1; j < nbJump; j++) {
cov[j][loopjump] = gamma[loopjump];
cov[loopjump][j] = gamma[loopjump];
}
}
final double[][] h = MODEL.volatilityMaturityPart(parameters, numeraireTime, impactTime); // jump/cf
final double[][] h2 = new double[nbJump][];
for (int i = 0; i < nbJump; i++) {
h2[i] = new double[h[i].length];
for (int j = 0; j < h[i].length; j++) {
h2[i][j] = h[i][j] * h[i][j] / 2;
}
}
// To remove the 0 (fixed coupons)
int nbZero = 0;
while (cov[nbZero][nbZero] < 1.0E-12) {
nbZero++;
}
final double[][] cov2 = new double[nbJump - nbZero][nbJump - nbZero];
for (int loopjump = 0; loopjump < nbJump - nbZero; loopjump++) {
for (int loopjump2 = 0; loopjump2 < nbJump - nbZero; loopjump2++) {
cov2[loopjump][loopjump2] = cov[loopjump + nbZero][loopjump2 + nbZero];
}
}
final CholeskyDecompositionCommons cd = new CholeskyDecompositionCommons();
final CholeskyDecompositionResult cdr2 = cd.evaluate(new DoubleMatrix2D(cov2));
final double[][] covCD2 = cdr2.getL().toArray();
final double[][] covCD = new double[nbJump][nbJump];
for (int loopjump = 0; loopjump < nbJump - nbZero; loopjump++) {
for (int loopjump2 = 0; loopjump2 < nbJump - nbZero; loopjump2++) {
covCD[loopjump + nbZero][loopjump2 + nbZero] = covCD2[loopjump][loopjump2];
}
}
final int nbBlock = (int) Math.round(Math.ceil(getNbPath() / ((double) BLOCK_SIZE)));
final int[] nbPath2 = new int[nbBlock];
for (int i = 0; i < nbBlock - 1; i++) {
nbPath2[i] = BLOCK_SIZE;
}
nbPath2[nbBlock - 1] = getNbPath() - (nbBlock - 1) * BLOCK_SIZE;
final double[][] impactAmount = decision.getImpactAmount();
double pv = 0;
final double[] pvBlock = new double[nbBlock];
// Backward sweep (init)
final double pvBar = 1.0;
final double[] pvBlockBar = new double[nbBlock];
for (int loopblock = 0; loopblock < nbBlock; loopblock++) {
pvBlockBar[loopblock] = pDN / getNbPath() * pvBar;
}
final double[][] impactAmountBar = new double[nbJump][];
for (int loopjump = 0; loopjump < nbJump; loopjump++) {
impactAmountBar[loopjump] = new double[impactAmount[loopjump].length];
}
// Forward sweep (end) and backward sweep (main)
final double[][] pDIBar = new double[nbJump][];
for (int loopjump = 0; loopjump < nbJump; loopjump++) {
pDIBar[loopjump] = new double[impactAmount[loopjump].length];
}
for (int loopblock = 0; loopblock < nbBlock; loopblock++) {
final double[][] x = getNormalArray(nbJump, nbPath2[loopblock]);
final double[][] y = new double[nbJump][nbPath2[loopblock]]; // jump/path
for (int looppath = 0; looppath < nbPath2[loopblock]; looppath++) {
for (int i = 0; i < nbJump; i++) {
for (int j = 0; j < nbJump; j++) {
y[i][looppath] += x[j][looppath] * covCD[i][j];
}
}
}
final Double[][][] pD = pathGeneratorDiscount(pDI, y, h, h2, gamma);
final MonteCarloDiscountFactorDerivativeDataBundle mcdDB = new MonteCarloDiscountFactorDerivativeDataBundle(pD, impactAmount);
pvBlock[loopblock] = instrument.accept(MCDC, mcdDB) * nbPath2[loopblock];
pv += pvBlock[loopblock];
// Backward sweep (in block loop)
for (int loopjump = 0; loopjump < nbJump; loopjump++) {
for (int loopimp = 0; loopimp < impactAmount[loopjump].length; loopimp++) {
impactAmountBar[loopjump][loopimp] += mcdDB.getImpactAmountDerivative()[loopjump][loopimp] * nbPath2[loopblock] * pvBlockBar[loopblock];
}
}
final Double[][][] pDBar = new Double[nbPath2[loopblock]][nbJump][];
for (int looppath = 0; looppath < nbPath2[loopblock]; looppath++) {
for (int loopjump = 0; loopjump < nbJump; loopjump++) {
pDBar[looppath][loopjump] = new Double[impactAmount[loopjump].length];
for (int loopimp = 0; loopimp < impactAmount[loopjump].length; loopimp++) {
pDBar[looppath][loopjump][loopimp] = mcdDB.getPathDiscountingFactorDerivative()[looppath][loopjump][loopimp] * nbPath2[loopblock] * pvBlockBar[loopblock];
}
}
}
final double[][] pDIBarTemp = pathGeneratorDiscountAdjointIDF(pDI, y, h, h2, gamma, pDBar);
for (int loopjump = 0; loopjump < nbJump; loopjump++) {
for (int loopimp = 0; loopimp < impactAmount[loopjump].length; loopimp++) {
pDIBar[loopjump][loopimp] += pDIBarTemp[loopjump][loopimp];
}
}
}
pv *= pDN / getNbPath(); // Multiply by the numeraire.
// Backward sweep (end)
double pDNBar = pv / pDN * pvBar;
for (int loopjump = 0; loopjump < nbJump; loopjump++) {
for (int loopimp = 0; loopimp < impactTime[loopjump].length; loopimp++) {
pDNBar += -multicurves.getDiscountFactor(ccy, impactTime[loopjump][loopimp]) / (pDN * pDN) * pDIBar[loopjump][loopimp];
}
}
final Map<String, List<DoublesPair>> resultMap = new HashMap<>();
final List<DoublesPair> listDiscounting = new ArrayList<>();
listDiscounting.add(new DoublesPair(numeraireTime, -numeraireTime * pDN * pDNBar));
for (int loopjump = 0; loopjump < nbJump; loopjump++) {
for (int loopimp = 0; loopimp < impactTime[loopjump].length; loopimp++) {
listDiscounting.add(new DoublesPair(impactTime[loopjump][loopimp], -impactTime[loopjump][loopimp] * pDI[loopjump][loopimp] * pDIBar[loopjump][loopimp]));
}
}
resultMap.put(multicurves.getName(ccy), listDiscounting);
MulticurveSensitivity result = MulticurveSensitivity.ofYieldDiscounting(resultMap);
// Adding sensitivity due to cash flow equivalent sensitivity to curves.
for (int loopjump = 0; loopjump < nbJump; loopjump++) {
final Map<Double, MulticurveSensitivity> impactAmountDerivative = decision.getImpactAmountDerivative().get(loopjump);
for (int loopimp = 0; loopimp < impactTime[loopjump].length; loopimp++) {