private CrossGammaParameterSensitivity computeGammaForCurve(CurveName curveName, Curve curve, Currency sensitivityCurrency, System.Func <Curve, ImmutableLegalEntityDiscountingProvider> ratesProviderFn, System.Func <ImmutableLegalEntityDiscountingProvider, CurrencyParameterSensitivities> sensitivitiesFn)
        {
            System.Func <DoubleArray, DoubleArray> function = (DoubleArray t) =>
            {
                Curve newCurve = curve.withPerturbation((i, v, m) => t.get(i));
                ImmutableLegalEntityDiscountingProvider newRates   = ratesProviderFn(newCurve);
                CurrencyParameterSensitivities          sensiMulti = sensitivitiesFn(newRates);
                return(sensiMulti.getSensitivity(curveName, sensitivityCurrency).Sensitivity);
            };
            int          nParams = curve.ParameterCount;
            DoubleMatrix sensi   = fd.differentiate(function).apply(DoubleArray.of(nParams, n => curve.getParameter(n)));
            IList <ParameterMetadata> metadata = IntStream.range(0, nParams).mapToObj(i => curve.getParameterMetadata(i)).collect(toImmutableList());

            return(CrossGammaParameterSensitivity.of(curveName, metadata, sensitivityCurrency, sensi));
        }
        // computes the sensitivity of baseDeltaSingle to Curve
        internal CrossGammaParameterSensitivity computeGammaForCurve(CurrencyParameterSensitivity baseDeltaSingle, Curve curve, System.Func <Curve, ImmutableRatesProvider> ratesProviderFn, System.Func <ImmutableRatesProvider, CurrencyParameterSensitivities> sensitivitiesFn)
        {
            System.Func <DoubleArray, DoubleArray> function = (DoubleArray t) =>
            {
                Curve newCurve = replaceParameters(curve, t);
                ImmutableRatesProvider         newRates   = ratesProviderFn(newCurve);
                CurrencyParameterSensitivities sensiMulti = sensitivitiesFn(newRates);
                return(sensiMulti.getSensitivity(baseDeltaSingle.MarketDataName, baseDeltaSingle.Currency).Sensitivity);
            };
            int          nParams = curve.ParameterCount;
            DoubleMatrix sensi   = fd.differentiate(function).apply(DoubleArray.of(nParams, n => curve.getParameter(n)));
            IList <ParameterMetadata> metadata = IntStream.range(0, nParams).mapToObj(i => curve.getParameterMetadata(i)).collect(toImmutableList());

            return(CrossGammaParameterSensitivity.of(baseDeltaSingle.MarketDataName, baseDeltaSingle.ParameterMetadata, curve.Name, metadata, baseDeltaSingle.Currency, sensi));
        }
        private CrossGammaParameterSensitivity combineSensitivities(CurrencyParameterSensitivity baseDeltaSingle, CrossGammaParameterSensitivities blockCrossGamma)
        {
            double[][] valuesTotal = new double[baseDeltaSingle.ParameterCount][];
//JAVA TO C# CONVERTER WARNING: Java wildcard generics have no direct equivalent in .NET:
//ORIGINAL LINE: java.util.List<com.opengamma.strata.collect.tuple.Pair<com.opengamma.strata.data.MarketDataName<?>, java.util.List<? extends com.opengamma.strata.market.param.ParameterMetadata>>> order = new java.util.ArrayList<>();
            IList <Pair <MarketDataName <object>, IList <ParameterMetadata> > > order = new List <Pair <MarketDataName <object>, IList <ParameterMetadata> > >();

            for (int i = 0; i < baseDeltaSingle.ParameterCount; ++i)
            {
                List <double> innerList = new List <double>();
                foreach (CrossGammaParameterSensitivity gammaSingle in blockCrossGamma.Sensitivities)
                {
                    innerList.AddRange(gammaSingle.Sensitivity.row(i).toList());
                    if (i == 0)
                    {
                        order.Add(gammaSingle.Order.get(0));
                    }
                }
                valuesTotal[i] = Doubles.toArray(innerList);
            }
            return(CrossGammaParameterSensitivity.of(baseDeltaSingle.MarketDataName, baseDeltaSingle.ParameterMetadata, order, baseDeltaSingle.Currency, DoubleMatrix.ofUnsafe(valuesTotal)));
        }
        //-------------------------------------------------------------------------
        /// <summary>
        /// Computes cross-curve gamma by applying finite difference method to curve delta.
        /// <para>
        /// This computes the cross-curve gamma, i.e., the second order sensitivities to full curves.
        /// Thus the sensitivities of curve delta to other curves are produced.
        /// </para>
        /// <para>
        /// The sensitivities are computed for discount curves, and forward curves for {@code RateIndex} and {@code PriceIndex}.
        /// This implementation works only for single currency trades.
        ///
        /// </para>
        /// </summary>
        /// <param name="ratesProvider">  the rates provider </param>
        /// <param name="sensitivitiesFn">  the sensitivity function </param>
        /// <returns> the cross gamma </returns>
        public CrossGammaParameterSensitivities calculateCrossGammaCrossCurve(RatesProvider ratesProvider, System.Func <ImmutableRatesProvider, CurrencyParameterSensitivities> sensitivitiesFn)
        {
            ImmutableRatesProvider           immProv   = ratesProvider.toImmutableRatesProvider();
            CurrencyParameterSensitivities   baseDelta = sensitivitiesFn(immProv);   // used to check target sensitivity exits.
            CrossGammaParameterSensitivities result    = CrossGammaParameterSensitivities.empty();

            foreach (CurrencyParameterSensitivity baseDeltaSingle in baseDelta.Sensitivities)
            {
                CrossGammaParameterSensitivities resultInner = CrossGammaParameterSensitivities.empty();
                // discount curve
                foreach (KeyValuePair <Currency, Curve> entry in immProv.DiscountCurves.entrySet())
                {
                    Currency currency = entry.Key;
                    Curve    curve    = entry.Value;
                    if (baseDelta.findSensitivity(curve.Name, currency).Present)
                    {
                        CrossGammaParameterSensitivity gammaSingle = computeGammaForCurve(baseDeltaSingle, curve, c => immProv.toBuilder().discountCurve(currency, c).build(), sensitivitiesFn);
                        resultInner = resultInner.combinedWith(gammaSingle);
                    }
                    else if (curve.split().size() > 1)
                    {
                        ImmutableList <Curve> curves = curve.split();
                        int nCurves = curves.size();
                        for (int i = 0; i < nCurves; ++i)
                        {
                            int   currentIndex    = i;
                            Curve underlyingCurve = curves.get(currentIndex);
                            if (baseDelta.findSensitivity(underlyingCurve.Name, currency).Present)
                            {
                                CrossGammaParameterSensitivity gammaSingle = computeGammaForCurve(baseDeltaSingle, underlyingCurve, c => immProv.toBuilder().discountCurve(currency, curve.withUnderlyingCurve(currentIndex, c)).build(), sensitivitiesFn);
                                resultInner = resultInner.combinedWith(gammaSingle);
                            }
                        }
                    }
                }
                // forward curve
                foreach (KeyValuePair <Index, Curve> entry in immProv.IndexCurves.entrySet())
                {
                    Index index = entry.Key;
                    if (index is RateIndex || index is PriceIndex)
                    {
                        Currency currency = getCurrency(index);
                        Curve    curve    = entry.Value;
                        if (baseDelta.findSensitivity(curve.Name, currency).Present)
                        {
                            CrossGammaParameterSensitivity gammaSingle = computeGammaForCurve(baseDeltaSingle, curve, c => immProv.toBuilder().indexCurve(index, c).build(), sensitivitiesFn);
                            resultInner = resultInner.combinedWith(gammaSingle);
                        }
                        else if (curve.split().size() > 1)
                        {
                            ImmutableList <Curve> curves = curve.split();
                            int nCurves = curves.size();
                            for (int i = 0; i < nCurves; ++i)
                            {
                                int   currentIndex    = i;
                                Curve underlyingCurve = curves.get(currentIndex);
                                if (baseDelta.findSensitivity(underlyingCurve.Name, currency).Present)
                                {
                                    CrossGammaParameterSensitivity gammaSingle = computeGammaForCurve(baseDeltaSingle, underlyingCurve, c => immProv.toBuilder().indexCurve(index, curve.withUnderlyingCurve(currentIndex, c)).build(), sensitivitiesFn);
                                    resultInner = resultInner.combinedWith(gammaSingle);
                                }
                            }
                        }
                    }
                }
                result = result.combinedWith(combineSensitivities(baseDeltaSingle, resultInner));
            }
            return(result);
        }
        //-------------------------------------------------------------------------
        /// <summary>
        /// Computes intra-curve cross gamma for bond curves by applying finite difference method to curve delta.
        /// <para>
        /// This computes the intra-curve cross gamma, i.e., the second order sensitivities to individual curves.
        /// Thus the sensitivity of a curve delta to another curve is not produced.
        /// </para>
        /// <para>
        /// The underlying instruments must be single-currency, i.e., the curve currency must be the same as the sensitivity currency.
        ///
        /// </para>
        /// </summary>
        /// <param name="ratesProvider">  the rates provider </param>
        /// <param name="sensitivitiesFn">  the sensitivity function </param>
        /// <returns> the cross gamma </returns>
        public CrossGammaParameterSensitivities calculateCrossGammaIntraCurve(LegalEntityDiscountingProvider ratesProvider, System.Func <ImmutableLegalEntityDiscountingProvider, CurrencyParameterSensitivities> sensitivitiesFn)
        {
            LocalDate valuationDate = ratesProvider.ValuationDate;
            ImmutableLegalEntityDiscountingProvider immProv   = ratesProvider.toImmutableLegalEntityDiscountingProvider();
            CurrencyParameterSensitivities          baseDelta = sensitivitiesFn(immProv); // used to check target sensitivity exits
            CrossGammaParameterSensitivities        result    = CrossGammaParameterSensitivities.empty();

            // issuer curve
            foreach (KeyValuePair <Pair <LegalEntityGroup, Currency>, DiscountFactors> entry in immProv.IssuerCurves.entrySet())
            {
                Pair <LegalEntityGroup, Currency> legCcy = entry.Key;
                Currency  currency  = legCcy.Second;
                Curve     curve     = getCurve(entry.Value);
                CurveName curveName = curve.Name;
                if (baseDelta.findSensitivity(curveName, currency).Present)
                {
                    CrossGammaParameterSensitivity gammaSingle = computeGammaForCurve(curveName, curve, currency, c => replaceIssuerCurve(immProv, legCcy, DiscountFactors.of(currency, valuationDate, c)), sensitivitiesFn);
                    result = result.combinedWith(gammaSingle);
                }
                else
                {
                    ImmutableList <Curve> curves = curve.split();
                    int nCurves = curves.size();
                    if (nCurves > 1)
                    {
                        for (int i = 0; i < nCurves; ++i)
                        {
                            int       currentIndex        = i;
                            Curve     underlyingCurve     = curves.get(currentIndex);
                            CurveName underlyingCurveName = underlyingCurve.Name;
                            if (baseDelta.findSensitivity(underlyingCurveName, currency).Present)
                            {
                                CrossGammaParameterSensitivity gammaSingle = computeGammaForCurve(underlyingCurveName, underlyingCurve, currency, c => replaceIssuerCurve(immProv, legCcy, DiscountFactors.of(currency, valuationDate, curve.withUnderlyingCurve(currentIndex, c))), sensitivitiesFn);
                                result = result.combinedWith(gammaSingle);
                            }
                        }
                    }
                }
            }
            // repo curve
            foreach (KeyValuePair <Pair <RepoGroup, Currency>, DiscountFactors> entry in immProv.RepoCurves.entrySet())
            {
                Pair <RepoGroup, Currency> rgCcy = entry.Key;
                Currency  currency  = rgCcy.Second;
                Curve     curve     = getCurve(entry.Value);
                CurveName curveName = curve.Name;
                if (baseDelta.findSensitivity(curveName, currency).Present)
                {
                    CrossGammaParameterSensitivity gammaSingle = computeGammaForCurve(curveName, curve, currency, c => replaceRepoCurve(immProv, rgCcy, DiscountFactors.of(currency, valuationDate, c)), sensitivitiesFn);
                    result = result.combinedWith(gammaSingle);
                }
                else
                {
                    ImmutableList <Curve> curves = curve.split();
                    int nCurves = curves.size();
                    if (nCurves > 1)
                    {
                        for (int i = 0; i < nCurves; ++i)
                        {
                            int       currentIndex        = i;
                            Curve     underlyingCurve     = curves.get(currentIndex);
                            CurveName underlyingCurveName = underlyingCurve.Name;
                            if (baseDelta.findSensitivity(underlyingCurveName, rgCcy.Second).Present)
                            {
                                CrossGammaParameterSensitivity gammaSingle = computeGammaForCurve(underlyingCurveName, underlyingCurve, currency, c => replaceRepoCurve(immProv, rgCcy, DiscountFactors.of(currency, valuationDate, curve.withUnderlyingCurve(currentIndex, c))), sensitivitiesFn);
                                result = result.combinedWith(gammaSingle);
                            }
                        }
                    }
                }
            }
            return(result);
        }