/** * Copyright (C) 2011 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.analytics.financial.interestrate; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import com.opengamma.util.ArgumentChecker; import com.opengamma.util.tuple.DoublesPair; import com.opengamma.util.tuple.FirstThenSecondDoublesPairComparator; /** * Utilities to manipulate present value sensitivities. * <p> * This is a thread-safe static utility class. */ public class InterestRateCurveSensitivityUtils { /** * Restricted constructor. */ protected InterestRateCurveSensitivityUtils() { super(); } /** * Takes a list of curve sensitivities (i.e. an unordered list of pairs of times and sensitivities) and returns a list order by ascending * time, and with sensitivities that occur at the same time netted (zero net sensitivities are removed) * @param old An unordered list of pairs of times and sensitivities * @param relTol Relative tolerance - if the net divided by gross sensitivity is less than this it is ignored/removed * @param absTol Absolute tolerance - is the net sensitivity is less than this it is ignored/removed * @return A time ordered netted list */ static final List<DoublesPair> clean(final List<DoublesPair> old, final double relTol, final double absTol) { ArgumentChecker.notNull(old, "null list"); ArgumentChecker.isTrue(relTol >= 0.0 && absTol >= 0.0, "Tolerances must be greater than zero"); if (old.size() == 0) { return new ArrayList<>(); } final List<DoublesPair> res = new ArrayList<>(); final DoublesPair[] sort = old.toArray(new DoublesPair[old.size()]); Arrays.sort(sort, FirstThenSecondDoublesPairComparator.INSTANCE); final DoublesPair pairOld = sort[0]; double tOld = pairOld.first; double sum = pairOld.getSecond(); double scale = Math.abs(sum); double t = tOld; for (int i = 1; i < sort.length; i++) { final DoublesPair pair = sort[i]; t = pair.first; if (t > tOld) { if (Math.abs(sum) > absTol && Math.abs(sum) / scale > relTol) { res.add(DoublesPair.of(tOld, sum)); } tOld = t; sum = pair.getSecondDouble(); scale = Math.abs(sum); } else { sum += pair.getSecondDouble(); scale += Math.abs(pair.getSecondDouble()); } } if (Math.abs(sum) > absTol && Math.abs(sum) / scale > relTol) { res.add(DoublesPair.of(t, sum)); } return res; } /** * Takes a map of curve sensitivities (i.e. a map between curve names and a unordered lists of pairs of times and sensitivities) * and returns a similar map where the lists order by ascending time, and with sensitivities that occur at the same time netted * (zero net sensitivities are removed) * @param old A map between curve names and unordered lists of pairs of times and sensitivities * @param relTol Relative tolerance - if the net divided by gross sensitivity is less than this it is ignored/removed * @param absTol Absolute tolerance - is the net sensitivity is less than this it is ignored/removed * @return A map between curve names and time ordered netted lists */ public static Map<String, List<DoublesPair>> clean(final Map<String, List<DoublesPair>> old, final double relTol, final double absTol) { final Map<String, List<DoublesPair>> res = new HashMap<>(); for (final Map.Entry<String, List<DoublesPair>> entry : old.entrySet()) { final List<DoublesPair> cleanList = clean(entry.getValue(), relTol, absTol); if (!cleanList.isEmpty()) { res.put(entry.getKey(), cleanList); } } return res; } /** * Add two list representing sensitivities into one. No attempt is made to net off sensitivities occurring at the same time - Use clean() * to do this * @param sensi1 First list of sensitivities * @param sensi2 Second list of sensitivities * @return combined list */ public static List<DoublesPair> addSensitivity(final List<DoublesPair> sensi1, final List<DoublesPair> sensi2) { final List<DoublesPair> result = new ArrayList<>(sensi1); result.addAll(sensi2); return result; } //------------------------------------------------------------------------- /** * Add two maps representing sensitivities into one. * * @param sensi1 the first sensitivity, not null * @param sensi2 the second sensitivity, not null * @return the total sensitivity, not null */ public static Map<String, List<DoublesPair>> addSensitivity(final Map<String, List<DoublesPair>> sensi1, final Map<String, List<DoublesPair>> sensi2) { ArgumentChecker.notNull(sensi1, "sensitivity"); ArgumentChecker.notNull(sensi2, "sensitivity"); final Map<String, List<DoublesPair>> result = new HashMap<>(); for (final Map.Entry<String, List<DoublesPair>> entry : sensi1.entrySet()) { final String name = entry.getKey(); if (sensi2.containsKey(name)) { result.put(name, addSensitivity(entry.getValue(), sensi2.get(name))); } else { result.put(name, entry.getValue()); } } for (final Map.Entry<String, List<DoublesPair>> entry : sensi2.entrySet()) { final String name = entry.getKey(); if (!result.containsKey(name)) { result.put(name, entry.getValue()); } } return result; } /** * Add the list representing the sensitivity to one curve to the map of sensitivities to several curves. * @param sensi The multi-curves sensitivity. Not null. * @param curveName The name of the curve the sensitivity of which is added. Not null. * @param list The sensitivity as a list. Not null. * @return The total sensitivity, not null */ public static Map<String, List<DoublesPair>> addSensitivity(final Map<String, List<DoublesPair>> sensi, final String curveName, final List<DoublesPair> list) { ArgumentChecker.notNull(sensi, "sensitivity"); ArgumentChecker.notNull(list, "sensitivity"); final Map<String, List<DoublesPair>> result = new HashMap<>(); for (final Map.Entry<String, List<DoublesPair>> entry : sensi.entrySet()) { final String name = entry.getKey(); if (name.equals(curveName)) { result.put(name, addSensitivity(entry.getValue(), list)); } else { result.put(name, entry.getValue()); } } if (!result.containsKey(curveName)) { result.put(curveName, list); } return result; } //TODO smarter way to do this? public static Map<String, List<DoublesPair>> addSensitivity(final Map<String, List<DoublesPair>> sensi1, final Map<String, List<DoublesPair>> sensi2, final Map<String, List<DoublesPair>> sensi3) { return addSensitivity(addSensitivity(sensi1, sensi2), sensi3); } /** * Multiply a sensitivity map by a common factor. * * @param sensitivity the original sensitivity, not null * @param factor the multiplicative factor, not null * @return the multiplied sensitivity, not null */ public static Map<String, List<DoublesPair>> multiplySensitivity(final Map<String, List<DoublesPair>> sensitivity, final double factor) { ArgumentChecker.notNull(sensitivity, "sensitivity"); final Map<String, List<DoublesPair>> result = new HashMap<>(); for (final Map.Entry<String, List<DoublesPair>> entry : sensitivity.entrySet()) { result.put(entry.getKey(), multiplySensitivity(entry.getValue(), factor)); } return result; } public static List<DoublesPair> multiplySensitivity(final List<DoublesPair> sensitivity, final double factor) { ArgumentChecker.notNull(sensitivity, "sensitivity"); final List<DoublesPair> curveSensi = new ArrayList<>(); for (final DoublesPair pair : sensitivity) { curveSensi.add(DoublesPair.of(pair.first, pair.second * factor)); } return curveSensi; } /** * Compare two lists of sensitivities with a given tolerance. The tolerance is used for both the time and the value. The two sensitivities are suppose to be in the same time order. * @param sensi1 The first sensitivity (as a list). * @param sensi2 The second sensitivity (as a list). * @param tolerance The tolerance. * @return True if the difference is below the tolerance and False if not. */ public static boolean compare(final List<DoublesPair> sensi1, final List<DoublesPair> sensi2, final double tolerance) { if (sensi1.size() != sensi2.size()) { return false; } for (int looptime = 0; looptime < sensi1.size(); looptime++) { if ((Math.abs(sensi1.get(looptime).first - sensi2.get(looptime).first) > tolerance) || (Math.abs(sensi1.get(looptime).second - sensi2.get(looptime).second) > tolerance)) { return false; } } return true; } /** * Compare two maps of sensitivities with a given tolerance. The tolerance is used for both the time and the value. The two sensitivities are suppose to be in the same time order. * @param sensi1 The first sensitivity (as a map). * @param sensi2 The second sensitivity (as a map). * @param tolerance The tolerance. * @return True if the difference is below the tolerance and False if not. If the curves are not the same it returns False. */ public static boolean compare(final Map<String, List<DoublesPair>> sensi1, final Map<String, List<DoublesPair>> sensi2, final double tolerance) { ArgumentChecker.notNull(sensi1, "sensitivity"); ArgumentChecker.notNull(sensi2, "sensitivity"); for (final Map.Entry<String, List<DoublesPair>> entry : sensi1.entrySet()) { if (sensi2.containsKey(entry.getKey())) { if (!compare(entry.getValue(), sensi2.get(entry.getKey()), tolerance)) { return false; } } else { return false; } } for (final String name : sensi2.keySet()) { if (!(sensi1.containsKey(name))) { return false; } } return true; } }