/**
* Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.sesame.engine;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import org.threeten.bp.ZonedDateTime;
import com.google.common.base.Function;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.engine.marketdata.spec.MarketDataSpecification;
import com.opengamma.sesame.config.EngineUtils;
import com.opengamma.sesame.config.ViewConfig;
import com.opengamma.sesame.marketdata.MapScenarioMarketDataEnvironment;
import com.opengamma.sesame.marketdata.MarketDataEnvironment;
import com.opengamma.sesame.marketdata.MarketDataRequirement;
import com.opengamma.sesame.marketdata.ScenarioMarketDataEnvironment;
import com.opengamma.sesame.marketdata.builders.MarketDataEnvironmentFactory;
import com.opengamma.sesame.marketdata.scenarios.ScenarioDefinition;
import com.opengamma.sesame.marketdata.scenarios.SingleScenarioDefinition;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.tuple.Pair;
import com.opengamma.util.tuple.Pairs;
/**
* Default implementation of {@link Engine} which provides the main entry point to the OpenGamma calculation engine.
*/
public class DefaultEngine implements Engine {
/** Builds market data in response to requirements gathered from the functions. */
private final MarketDataEnvironmentFactory _environmentFactory;
/** For creating views for performing calculations using configuration passed into the run methods. */
private final ViewFactory _viewFactory;
/** Runs tasks to gather market data requirements and build the data. */
private final ListeningExecutorService _executor;
/**
* @param viewFactory for creating views for performing calculations using configuration passed into the run methods
* @param environmentFactory builds market data in response to requirements gathered from the functions
* @param executor run tasks to gather market data requirements and build the data
*/
public DefaultEngine(
ViewFactory viewFactory,
MarketDataEnvironmentFactory environmentFactory,
ExecutorService executor) {
_environmentFactory = ArgumentChecker.notNull(environmentFactory, "bundleBuilder");
_viewFactory = ArgumentChecker.notNull(viewFactory, "viewFactory");
_executor = MoreExecutors.listeningDecorator(ArgumentChecker.notNull(executor, "executor"));
}
@Override
public Results runView(
ViewConfig viewConfig,
CalculationArguments calculationArguments,
MarketDataEnvironment marketData,
List<?> portfolio) {
View view = _viewFactory.createView(viewConfig, EngineUtils.getInputTypes(portfolio));
return view.run(calculationArguments, marketData, portfolio);
}
@Override
public ScenarioResults runScenarios(
ViewConfig viewConfig,
ScenarioMarketDataEnvironment scenarioMarketData,
CalculationArguments calculationArguments,
List<?> portfolio) {
// the outer set is the cycles, the list holds the perturbations to apply in that cycle
View view = _viewFactory.createView(viewConfig, EngineUtils.getInputTypes(portfolio));
List<ListenableFuture<Pair<String, Results>>> resultFutures = new ArrayList<>();
for (Map.Entry<String, MarketDataEnvironment> entry : scenarioMarketData.getData().entrySet()) {
String scenarioName = entry.getKey();
MarketDataEnvironment marketData = entry.getValue();
// start running the view and return a future of the results
ListenableFuture<Results> resultsFuture = view.runAsync(calculationArguments, marketData, portfolio);
// create a future that wraps the results into a pair that includes the scenario name
ListenableFuture<Pair<String, Results>> namedResultsFuture = futureWithScenarioName(resultsFuture, scenarioName);
// add the future to the list of futures for all scenarios
resultFutures.add(namedResultsFuture);
}
try {
// create a future that combines the futures for all scenarios and return its result, blocking until it completes
return scenarioResultsFuture(resultFutures).get();
} catch (InterruptedException | ExecutionException e) {
// this will only happen if there's a bug in the engine, all exceptions should be caught and converted to results
throw new OpenGammaRuntimeException("Failed to run scenarios", e);
}
}
@Override
public MarketDataEnvironment buildMarketData(
ViewConfig viewConfig,
MarketDataEnvironment suppliedData,
CalculationArguments calculationArguments,
List<?> portfolio) {
View view = _viewFactory.createView(viewConfig, EngineUtils.getInputTypes(portfolio));
Set<MarketDataRequirement> requirements = view.gatherRequirements(suppliedData, calculationArguments, portfolio);
return _environmentFactory.build(
suppliedData,
requirements,
SingleScenarioDefinition.base(),
calculationArguments.getMarketDataSpecification(),
calculationArguments.getValuationTime());
}
@Override
public ScenarioMarketDataEnvironment buildScenarioMarketData(
ViewConfig viewConfig,
MarketDataEnvironment baseData,
ScenarioDefinition scenarioDefinition,
CalculationArguments calculationArguments,
List<?> portfolio) {
View view = _viewFactory.createView(viewConfig, EngineUtils.getInputTypes(portfolio));
// TODO when multiple valuation times are supported, gather one set of requirements for each valuation time
Set<MarketDataRequirement> requirements = view.gatherRequirements(baseData, calculationArguments, portfolio);
List<SingleScenarioDefinition> scenarios = scenarioDefinition.getScenarios();
MarketDataSpecification marketDataSpecification = calculationArguments.getMarketDataSpecification();
ZonedDateTime valuationTime = calculationArguments.getValuationTime();
List<ListenableFuture<MarketDataEnvironment>> marketDataFutures = Lists.newArrayListWithExpectedSize(scenarios.size());
for (SingleScenarioDefinition scenario : scenarios) {
// create a future for building the market data for this scenario
marketDataFutures.add(marketDataFuture(baseData, requirements, scenario, marketDataSpecification, valuationTime));
}
// combine the market data futures for all scenarios into a single future
ListenableFuture<List<MarketDataEnvironment>> combinedFuture = Futures.allAsList(marketDataFutures);
try {
// transform the future into a different future that yields a ScenarioMarketDataEnvironment
return Futures.transform(
combinedFuture,
// TODO Java 8 - use a method reference this::buildScenarioData
new Function<List<MarketDataEnvironment>, ScenarioMarketDataEnvironment>() {
@Override
public ScenarioMarketDataEnvironment apply(List<MarketDataEnvironment> marketDataList) {
return buildScenarioData(marketDataList);
}
}).get();
} catch (InterruptedException | ExecutionException e) {
// this shouldn't happen unless there's a bug in the engine
throw new OpenGammaRuntimeException("Failed to build market data", e);
}
}
/**
* Builds the market data for a set of scenarios given the market data for each of the individual scenarios.
*
* @param marketDataList list of market data for individual scenarios
* @return the market data for a set of scenarios
*/
private ScenarioMarketDataEnvironment buildScenarioData(List<MarketDataEnvironment> marketDataList) {
ImmutableMap.Builder<String, MarketDataEnvironment> builder = ImmutableMap.builder();
int scenarioIndex = 1;
for (MarketDataEnvironment marketData : marketDataList) {
// TODO a better way to name scenarios
String scenarioName = Integer.toString(scenarioIndex);
scenarioIndex++;
builder.put(scenarioName, marketData);
}
return new MapScenarioMarketDataEnvironment(builder.build());
}
/**
* Wraps a future so its return value includes the name of the scenario that produced its results.
*
* @param future a futures producing results for a calculation cycle
* @param scenarioName the name of the scenario used in the calculation cycle
* @return a future producing the scenario name and the scenario's calculation results
*/
private ListenableFuture<Pair<String, Results>> futureWithScenarioName(
ListenableFuture<Results> future,
final String scenarioName) {
// creates a new future that wraps the original future so it returns the scenario name along with its results
return Futures.transform(
future, new Function<Results, Pair<String, Results>>() {
@Override
public Pair<String, Results> apply(Results results) {
return Pairs.of(scenarioName, results);
}
});
}
/**
* Combines a list of futures producing a scenario name and results into one future producing {@link ScenarioResults}.
*
* @param futures futures producing a scenario name and results from running the scenario
* @return a future producing results for all the scenarios
*/
private ListenableFuture<ScenarioResults> scenarioResultsFuture(List<ListenableFuture<Pair<String, Results>>> futures) {
ListenableFuture<List<Pair<String, Results>>> combinedFuture = Futures.allAsList(futures);
return Futures.transform(
combinedFuture, new Function<List<Pair<String, Results>>, ScenarioResults>() {
@Override
public ScenarioResults apply(List<Pair<String, Results>> scenarioResults) {
ImmutableMap.Builder<String, Results> resultsBuilder = ImmutableMap.builder();
for (Pair<String, Results> nameAndResults : scenarioResults) {
resultsBuilder.put(nameAndResults.getKey(), nameAndResults.getValue());
}
return new ScenarioResults(resultsBuilder.build());
}
});
}
/**
* Returns a future for asynchronously building the market data required to perform some calculations.
* <p>
* The future's task will be executed using the engine's thread pool.
*
* @param suppliedData market data supplied by the user
* @param requirements requirements for data needed to perform the calculations but not supplied by the user
* @param scenario contains perturbations to apply to the market data in the current calculation cycle
* @param marketDataSpec specifies which market data providers should be used to look up the underlying market data
* @param valuationTime valuation time for the calculations
* @return a future for building the market data required to perform some calculations.
*/
private ListenableFuture<MarketDataEnvironment> marketDataFuture(
final MarketDataEnvironment suppliedData,
final Set<MarketDataRequirement> requirements,
final SingleScenarioDefinition scenario,
final MarketDataSpecification marketDataSpec,
final ZonedDateTime valuationTime) {
// create a future that asynchronously builds the market data
return _executor.submit(
new Callable<MarketDataEnvironment>() {
@Override
public MarketDataEnvironment call() throws Exception {
return _environmentFactory.build(suppliedData, requirements, scenario, marketDataSpec, valuationTime);
}
});
}
//--------------------------------------------------------------------------------------------------------------------
// Everything below here is temporary and intended to ease migration from 2.8 to 2.9.
// It will be removed in 2.10 or 3.0 at the latest
@Override
public ScenarioMarketDataEnvironment buildScenarioMarketData(
ViewConfig viewConfig,
ScenarioMarketDataEnvironment suppliedData,
CalculationArguments calculationArguments,
List<?> portfolio) {
Map<String, MarketDataEnvironment> scenarioData = suppliedData.getData();
if (scenarioData.isEmpty()) {
return suppliedData;
}
Map.Entry<String, MarketDataEnvironment> firstScenario = scenarioData.entrySet().iterator().next();
MarketDataEnvironment firstScenarioData = firstScenario.getValue();
View view = _viewFactory.createView(viewConfig, EngineUtils.getInputTypes(portfolio));
Set<MarketDataRequirement> requirements = view.gatherRequirements(firstScenarioData, calculationArguments, portfolio);
MarketDataSpecification marketDataSpecification = calculationArguments.getMarketDataSpecification();
ZonedDateTime valuationTime = calculationArguments.getValuationTime();
List<ListenableFuture<Pair<String, MarketDataEnvironment>>> marketDataFutures =
Lists.newArrayListWithExpectedSize(scenarioData.size());
for (Map.Entry<String, MarketDataEnvironment> entry : scenarioData.entrySet()) {
MarketDataEnvironment baseData = entry.getValue();
String scenarioName = entry.getKey();
// create a future for building the market data for this scenario
marketDataFutures.add(marketDataFuture(scenarioName, baseData, requirements, marketDataSpecification, valuationTime));
}
// combine the market data futures for all scenarios into a single future
ListenableFuture<List<Pair<String, MarketDataEnvironment>>> combinedFuture = Futures.allAsList(marketDataFutures);
try {
// transform the future into a different future that yields a ScenarioMarketDataEnvironment
return Futures.transform(
combinedFuture,
new Function<List<Pair<String, MarketDataEnvironment>>, ScenarioMarketDataEnvironment>() {
@Override
public ScenarioMarketDataEnvironment apply(List<Pair<String, MarketDataEnvironment>> marketDataList) {
return buildNamedScenarioData(marketDataList);
}
}).get();
} catch (InterruptedException | ExecutionException e) {
// this shouldn't happen unless there's a bug in the engine
throw new OpenGammaRuntimeException("Failed to build market data", e);
}
}
/**
* Returns a future for asynchronously building the market data required to perform some calculations.
* <p>
* The future's task will be executed using the engine's thread pool.
*
* @param scenarioName the name of the scenario that will use the market data
* @param suppliedData market data supplied by the user
* @param requirements requirements for data needed to perform the calculations but not supplied by the user
* @param marketDataSpec specifies which market data providers should be used to look up the underlying market data
* @param valuationTime valuation time for the calculations
* @return a futures for building the market data required to perform some calculations.
*/
private ListenableFuture<Pair<String, MarketDataEnvironment>> marketDataFuture(
final String scenarioName,
final MarketDataEnvironment suppliedData,
final Set<MarketDataRequirement> requirements,
final MarketDataSpecification marketDataSpec,
final ZonedDateTime valuationTime) {
// create a future that asynchronously builds the market data
return _executor.submit(
new Callable<Pair<String, MarketDataEnvironment>>() {
@Override
public Pair<String, MarketDataEnvironment> call() throws Exception {
MarketDataEnvironment marketData =
_environmentFactory.build(
suppliedData,
requirements,
SingleScenarioDefinition.base(),
marketDataSpec,
valuationTime);
return Pairs.of(scenarioName, marketData);
}
});
}
/**
* Builds the market data for a set of scenarios given the market data for each of the individual scenarios.
*
* @param marketDataList list of scenario names and the market data for the corresponding scenario
* @return the market data for the scenarios
*/
private ScenarioMarketDataEnvironment buildNamedScenarioData(List<Pair<String, MarketDataEnvironment>> marketDataList) {
ImmutableMap.Builder<String, MarketDataEnvironment> builder = ImmutableMap.builder();
for (Pair<String, MarketDataEnvironment> pair : marketDataList) {
builder.put(pair.getKey(), pair.getValue());
}
return new MapScenarioMarketDataEnvironment(builder.build());
}
}