/** * Copyright (C) 2016 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.strata.calc.runner; import static java.util.stream.Collectors.toList; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.collect.Sets; import com.opengamma.strata.basics.CalculationTarget; import com.opengamma.strata.basics.ReferenceData; import com.opengamma.strata.basics.currency.Currency; import com.opengamma.strata.calc.Measure; import com.opengamma.strata.collect.MapStream; import com.opengamma.strata.collect.result.FailureReason; import com.opengamma.strata.collect.result.Result; import com.opengamma.strata.data.scenario.ScenarioMarketData; /** * A {@link CalculationFunction} implementation which wraps a {@link DerivedCalculationFunction}. * <p> * A derived calculation function calculates a measure using the measures calculated by another function. * This functions takes care of calling the delegate function and passing the results to the derived function. * <p> * Most of the logic is concerned with bookkeeping - packing and unpacking maps of measures and results before * passing them on or returning them. */ class DerivedCalculationFunctionWrapper<T extends CalculationTarget, R> implements CalculationFunction<T> { /** * The derived calculation function which calculates one measure. * <p> * The inputs to the measure can include measures calculated by the delegate calculation function. */ private final DerivedCalculationFunction<T, R> derivedFunction; /** * A calculation function whose results can be used by the derived calculation function. */ private final CalculationFunction<T> delegate; /** * The measures supported by this function; the union of the measures supported by the delegate function and * the derived function. */ private final Set<Measure> supportedMeasures; /** * True if the delegate function supports all measures required by the calculation function. * If this is true the calculation function can be invoked. * If it is false the only measures which can be calculated are the measures supported by the delegate. */ private final boolean requiredMeasuresSupported; /** * Creates a new function which invokes the delegate function, passes the result to the derived function * and returns the combined results. * * @param derivedFunction a function which calculates one measure using the measure values calculated by the other function * @param delegate a function which calculates multiple measures */ DerivedCalculationFunctionWrapper( DerivedCalculationFunction<T, R> derivedFunction, CalculationFunction<T> delegate) { this.derivedFunction = derivedFunction; this.delegate = delegate; Set<Measure> delegateMeasures = delegate.supportedMeasures(); this.requiredMeasuresSupported = delegateMeasures.containsAll(derivedFunction.requiredMeasures()); this.supportedMeasures = requiredMeasuresSupported ? ImmutableSet.<Measure>builder().addAll(delegateMeasures).add(derivedFunction.measure()).build() : delegateMeasures; } @Override public Class<T> targetType() { return derivedFunction.targetType(); } @Override public Set<Measure> supportedMeasures() { return supportedMeasures; } @Override public Optional<String> identifier(T target) { return delegate.identifier(target); } @Override public Currency naturalCurrency(T target, ReferenceData refData) { return delegate.naturalCurrency(target, refData); } @Override public FunctionRequirements requirements( T target, Set<Measure> measures, CalculationParameters parameters, ReferenceData refData) { FunctionRequirements delegateRequirements = delegate.requirements(target, measures, parameters, refData); FunctionRequirements functionRequirements = derivedFunction.requirements(target, parameters, refData); return delegateRequirements.combinedWith(functionRequirements); } @Override public Map<Measure, Result<?>> calculate( T target, Set<Measure> measures, CalculationParameters parameters, ScenarioMarketData marketData, ReferenceData refData) { // The caller didn't ask for the derived measure so just return the measures calculated by the delegate Measure derivedMeasure = derivedFunction.measure(); if (!measures.contains(derivedMeasure)) { return delegate.calculate(target, measures, parameters, marketData, refData); } // Add the measures required to calculate the derived measure to the measures requested by the caller Set<Measure> allRequiredMeasures = Sets.union(measures, derivedFunction.requiredMeasures()); Set<Measure> requiredMeasures = Sets.difference(allRequiredMeasures, ImmutableSet.of(derivedMeasure)); Map<Measure, Result<?>> delegateResults = delegate.calculate(target, requiredMeasures, parameters, marketData, refData); // Calculate the derived measure Result<?> result = calculateMeasure(target, delegateResults, parameters, marketData, refData); // The results containing only the requested measures and not including extra measures that were inserted above // Also filter out any results for calculationFunction.measure(). There will be failures from functions below // that don't support that measure. Map<Measure, Result<?>> requestedResults = MapStream.of(delegateResults) .filterKeys(measures::contains) .filterKeys(measure -> !measure.equals(derivedMeasure)) .toMap(); return ImmutableMap.<Measure, Result<?>>builder() .put(derivedMeasure, result) .putAll(requestedResults) .build(); } private Result<?> calculateMeasure( T target, Map<Measure, Result<?>> delegateResults, CalculationParameters parameters, ScenarioMarketData marketData, ReferenceData refData) { if (!requiredMeasuresSupported) { // Can't calculate the measure if the delegate can't calculate its inputs return Result.failure( FailureReason.NOT_APPLICABLE, "The delegate function cannot calculate the required measures. Required measures: {}, " + "supported measures: {}, delegate {}", derivedFunction.requiredMeasures(), delegate.supportedMeasures(), delegate); } if (!delegateResults.keySet().containsAll(derivedFunction.requiredMeasures())) { // There's a bug in the delegate function - it claims to support the required measures but didn't return // a result for all of them. return Result.failure( FailureReason.CALCULATION_FAILED, "Delegate did not return the expected measures. Required {}, actual {}, delegate {}", derivedFunction.requiredMeasures(), delegateResults.keySet(), delegate); } // Check whether all the required measures were successfully calculated List<Result<?>> failures = MapStream.of(delegateResults) .filterKeys(derivedFunction.requiredMeasures()::contains) .map(entry -> entry.getValue()) .filter(result -> result.isFailure()) .collect(toList()); if (!failures.isEmpty()) { return Result.failure(failures); } // Unwrap the results before passing them to the function Map<Measure, Object> resultValues = MapStream.of(delegateResults) .filterKeys(derivedFunction.requiredMeasures()::contains) .mapValues(result -> (Object) result.getValue()) // This compiler needs this cast. Which seems odd. .toMap(); return Result.of(() -> derivedFunction.calculate(target, resultValues, parameters, marketData, refData)); } }