/** * Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.analytics.financial.calculator; import it.unimi.dsi.fastutil.doubles.DoubleArrayList; import java.util.Arrays; import java.util.Iterator; import java.util.LinkedHashSet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import cern.colt.list.IntArrayList; import com.opengamma.analytics.financial.model.interestrate.curve.DiscountCurve; import com.opengamma.analytics.financial.model.interestrate.curve.YieldAndDiscountAddZeroFixedCurve; import com.opengamma.analytics.financial.model.interestrate.curve.YieldAndDiscountAddZeroSpreadCurve; import com.opengamma.analytics.financial.model.interestrate.curve.YieldAndDiscountCurve; import com.opengamma.analytics.financial.model.interestrate.curve.YieldCurve; import com.opengamma.analytics.financial.model.interestrate.curve.YieldPeriodicCurve; import com.opengamma.analytics.financial.provider.description.interestrate.MulticurveProviderDiscount; import com.opengamma.analytics.math.matrix.DoubleMatrix2D; import com.opengamma.util.ArgumentChecker; import com.opengamma.util.tuple.Pair; /** * Compute weight matrix used in {@link PortfolioHedgingCalculator} */ public class ParameterSensitivityWeightMatrixCalculator { private static final Logger s_logger = LoggerFactory.getLogger(ParameterSensitivityWeightMatrixCalculator.class); private static final double TOL = 1.0e-8; /** * Matrix projecting the nodes, to which portfolio has sensitivities, to new nodes (objNodes), to which sensitivity is accounted for, where the risks of each maturity * is considered separately for each curve. * @param curves The multicurve * @param order The ordered set of name with number of parameters, should be the same as "order" used in {@link PortfolioHedgingCalculator} * @param objNodes The objective nodes on which sensitivity is to be accounted for * @return The matrix */ public DoubleMatrix2D projectCurveNodes(final MulticurveProviderDiscount curves, final LinkedHashSet<Pair<String, Integer>> order, final double[] objNodes) { ArgumentChecker.notNull(curves, "curves"); ArgumentChecker.notNull(order, "order"); ArgumentChecker.notNull(objNodes, "objNodes"); int nCurves = order.size(); ArgumentChecker.isTrue(nCurves > 0, "order should not be empty"); Iterator<Pair<String, Integer>> it = order.iterator(); DoubleMatrix2D[] resArray = new DoubleMatrix2D[nCurves]; int nRowsTotal = 0; int nColsTotal = 0; for (int i = 0; i < nCurves; ++i) { resArray[i] = projectSingleCurveNodes(curves, it.next(), objNodes); nRowsTotal += resArray[i].getNumberOfRows(); nColsTotal += resArray[i].getNumberOfColumns(); } double[][] res = new double[nRowsTotal][nColsTotal]; int offsetRow = 0; int offsetCol = 0; for (int i = 0; i < nCurves; ++i) { int nRows = resArray[i].getNumberOfRows(); int nCols = resArray[i].getNumberOfColumns(); for (int j = 0; j < nRows; ++j) { for (int k = 0; k < nCols; ++k) { res[j + offsetRow][k + offsetCol] = resArray[i].getData()[j][k]; } } offsetRow += nRows; offsetCol += nCols; } return new DoubleMatrix2D(res); } /** * Matrix projecting the nodes, to which portfolio has sensitivities, to new nodes (objNodes), to which sensitivity is accounted for, where the * total risk of each maturity for all the relevant curves is considered by summing them up. * NOTE THAT all of the relevant curves should contain all of the nodes in objNodes. * @param curves The multicurve * @param order The ordered set of name with number of parameters, should be the same as "order" used in {@link PortfolioHedgingCalculator} * @param objNodes The objective nodes on which sensitivity is to be accounted for * @return The matrix */ public DoubleMatrix2D projectCurvesAndNodes(final MulticurveProviderDiscount curves, final LinkedHashSet<Pair<String, Integer>> order, final double[] objNodes) { ArgumentChecker.notNull(curves, "curves"); ArgumentChecker.notNull(order, "order"); ArgumentChecker.notNull(objNodes, "objNodes"); int nCurves = order.size(); ArgumentChecker.isTrue(nCurves > 0, "order should not be empty"); Iterator<Pair<String, Integer>> it = order.iterator(); DoubleMatrix2D[] resArray = new DoubleMatrix2D[nCurves]; int nColsTotal = 0; for (int i = 0; i < nCurves; ++i) { resArray[i] = projectSingleCurveNodes(curves, it.next(), objNodes); nColsTotal += resArray[i].getNumberOfColumns(); } for (int i = 0; i < nCurves; ++i) { ArgumentChecker.isTrue(objNodes.length == resArray[i].getNumberOfRows(), "All of the elements in objNodes should be found in the curves for this method"); } int nRows = resArray[0].getNumberOfRows(); double[][] res = new double[nRows][nColsTotal]; int offsetCol = 0; for (int i = 0; i < nCurves; ++i) { int nCols = resArray[i].getNumberOfColumns(); for (int j = 0; j < nRows; ++j) { for (int k = 0; k < nCols; ++k) { res[j][k + offsetCol] = resArray[i].getData()[j][k]; } } offsetCol += nCols; } return new DoubleMatrix2D(res); } /** * Matrix reducing the nodes, to which portfolio has sensitivities, to new nodes (objNodes), to which sensitivity is accounted for. * Thus the sensitivities to nodes which are not in objNodes are * @param curves The multicurve * @param order The ordered set of name with number of parameters, should be the same as "order" used in {@link PortfolioHedgingCalculator} * @param objNodes The objective nodes on which sensitivity is to be accounted for * @return The matrix */ public DoubleMatrix2D reduceCurveNodes(final MulticurveProviderDiscount curves, final LinkedHashSet<Pair<String, Integer>> order, final double[] objNodes) { ArgumentChecker.notNull(curves, "curves"); ArgumentChecker.notNull(order, "order"); ArgumentChecker.notNull(objNodes, "objNodes"); int nCurves = order.size(); ArgumentChecker.isTrue(nCurves > 0, "order should not be empty"); YieldAndDiscountCurve[] objCurves = new YieldAndDiscountCurve[nCurves]; Iterator<Pair<String, Integer>> it = order.iterator(); DoubleArrayList result = new DoubleArrayList(); for (int i = 0; i < nCurves; ++i) { objCurves[i] = curves.getCurve(it.next().getFirst()); Double[] nodes = getNodes(objCurves[i]); for (final double element : nodes) { result.add(element); } } int totalNum = result.size(); int[] tmp = toPositions(objNodes, result.toDoubleArray()); int objNum = tmp.length; double[][] res = new double[objNum][totalNum]; for (int i = 0; i < objNum; ++i) { Arrays.fill(res[i], 0.0); res[i][tmp[i]] = 1.0; } return new DoubleMatrix2D(res); } /** * Matrix reducing the nodes, to which portfolio has sensitivities, to new nodes (objNodes), for which sensitivity is accounted for, when relevant curves are already known * @param curves The relevant curves * @param objNodes The objective nodes on which sensitivity is to be accounted for * @return The matrix */ public DoubleMatrix2D reduceCurveNodes(final YieldAndDiscountCurve[] curves, final double[] objNodes) { ArgumentChecker.notNull(curves, "curves"); ArgumentChecker.notNull(objNodes, "objNodes"); int nCurves = curves.length; DoubleArrayList result = new DoubleArrayList(); for (int i = 0; i < nCurves; ++i) { Double[] nodes = getNodes(curves[i]); for (final double element : nodes) { result.add(element); } } int totalNum = result.size(); int[] tmp = toPositions(objNodes, result.toDoubleArray()); int objNum = tmp.length; double[][] res = new double[objNum][totalNum]; for (int i = 0; i < objNum; ++i) { Arrays.fill(res[i], 0.0); res[i][tmp[i]] = 1.0; } return new DoubleMatrix2D(res); } /** * Matrix reducing the nodes, to which portfolio has sensitivities, to new nodes (objNodes), for which sensitivity is accounted for, when total nodes are already known * @param totalNodes The original total nodes on which sensitivity is accounted for * @param objNodes The objective nodes on which sensitivity is to be accounted for * @return The matrix */ public DoubleMatrix2D reduceCurveNodes(final double[] totalNodes, final double[] objNodes) { ArgumentChecker.notNull(totalNodes, "totalNodes"); ArgumentChecker.notNull(objNodes, "objNodes"); int totalNum = totalNodes.length; int[] tmp = toPositions(objNodes, totalNodes); int objNum = tmp.length; double[][] res = new double[objNum][totalNum]; for (int i = 0; i < objNum; ++i) { Arrays.fill(res[i], 0.0); res[i][tmp[i]] = 1.0; } return new DoubleMatrix2D(res); } private DoubleMatrix2D projectSingleCurveNodes(final MulticurveProviderDiscount curves, final Pair<String, Integer> curveName, final double[] objNodes) { YieldAndDiscountCurve objCurves = curves.getCurve(curveName.getFirst()); Double[] nodes = getNodes(objCurves); int nNodes = nodes.length; int[] tmp = toPositions(objNodes, nodes); Arrays.sort(tmp); int objNum = tmp.length; double[][] res = new double[objNum][nNodes]; if (objNum == 1) { Arrays.fill(res[0], 1.0); return new DoubleMatrix2D(res); } for (int i = 1; i < objNum - 1; ++i) { Arrays.fill(res[i], 0.0); int start = tmp[i - 1]; for (int j = start; j < tmp[i]; ++j) { int position = j + 1; res[i][position] = 1.0; } } Arrays.fill(res[0], 0.0); for (int j = 0; j < tmp[0] + 1; ++j) { res[0][j] = 1.0; } Arrays.fill(res[objNum - 1], 0.0); int start = tmp[objNum - 2] + 1; for (int j = start; j < nNodes; ++j) { res[objNum - 1][j] = 1.0; } return new DoubleMatrix2D(res); } private int[] toPositions(final double[] objNodes, final Double[] totalNodes) { int nObjNodes = objNodes.length; int nTotalNodes = totalNodes.length; IntArrayList result = new IntArrayList(); for (int i = 0; i < nObjNodes; ++i) { boolean findNodes = false; for (int j = 0; j < nTotalNodes; ++j) { if (Math.abs(objNodes[i] - totalNodes[j]) < TOL) { result.add(j); findNodes = true; } } if (!findNodes) { s_logger.info(i + "-th objective node with value " + objNodes[i] + " is not found in curve nodes"); } } int nObjInt = result.size(); ArgumentChecker.isTrue(nObjInt > 0, "None of the objective nodes are found in curve nodes"); int[] res = new int[nObjInt]; for (int i = 0; i < nObjInt; ++i) { res[i] = result.get(i); } return res; } private int[] toPositions(final double[] objNodes, final double[] totalNodes) { int nObjNodes = objNodes.length; int nTotalNodes = totalNodes.length; IntArrayList result = new IntArrayList(); for (int i = 0; i < nObjNodes; ++i) { boolean findNodes = false; for (int j = 0; j < nTotalNodes; ++j) { if (Math.abs(objNodes[i] - totalNodes[j]) < TOL) { result.add(j); findNodes = true; } } if (!findNodes) { s_logger.info(i + "-th objective node with value " + objNodes[i] + " is not found in curve nodes"); } } int nObjInt = result.size(); ArgumentChecker.isTrue(nObjInt > 0, "None of the objective nodes are found in curve nodes"); int[] res = new int[nObjInt]; for (int i = 0; i < nObjInt; ++i) { res[i] = result.get(i); } return res; } private Double[] getNodes(final YieldAndDiscountCurve curve) { if (curve instanceof YieldCurve) { return ((YieldCurve) curve).getCurve().getXData(); } if (curve instanceof DiscountCurve) { return ((DiscountCurve) curve).getCurve().getXData(); } if (curve instanceof YieldAndDiscountAddZeroFixedCurve) { return getNodes(((YieldAndDiscountAddZeroFixedCurve) curve).getCurve()); } if (curve instanceof YieldPeriodicCurve) { return ((YieldPeriodicCurve) curve).getCurve().getXData(); } if (curve instanceof YieldAndDiscountAddZeroSpreadCurve) { YieldAndDiscountAddZeroSpreadCurve castCurve = (YieldAndDiscountAddZeroSpreadCurve) curve; YieldAndDiscountCurve[] curves = castCurve.getCurves(); DoubleArrayList result = new DoubleArrayList(); int nCurves = curves.length; for (int i = 0; i < nCurves; ++i) { Double[] nodes = getNodes(curves[i]); int nNodes = nodes.length; for (int j = 0; j < nNodes; ++j) { result.add(nodes[j]); } } } throw new IllegalArgumentException("node points can not be extracted"); } }