/**
* Copyright (C) 2015 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.strata.calc.runner;
import static com.opengamma.strata.collect.Guavate.toImmutableList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.function.Consumer;
import java.util.function.Supplier;
import com.opengamma.strata.basics.CalculationTarget;
import com.opengamma.strata.basics.ReferenceData;
import com.opengamma.strata.calc.Column;
import com.opengamma.strata.calc.Results;
import com.opengamma.strata.collect.ArgChecker;
import com.opengamma.strata.collect.Messages;
import com.opengamma.strata.collect.result.Result;
import com.opengamma.strata.data.MarketData;
import com.opengamma.strata.data.scenario.ScenarioArray;
import com.opengamma.strata.data.scenario.ScenarioMarketData;
/**
* The default calculation task runner.
* <p>
* This uses a single instance of {@link ExecutorService}.
*/
final class DefaultCalculationTaskRunner implements CalculationTaskRunner {
/**
* Executes the tasks that perform the individual calculations.
* This will typically be multi-threaded, but single or direct executors also work.
*/
private final ExecutorService executor;
//-------------------------------------------------------------------------
/**
* Creates a standard multi-threaded calculation task runner capable of performing calculations.
* <p>
* This factory creates an executor basing the number of threads on the number of available processors.
* It is recommended to use try-with-resources to manage the runner:
* <pre>
* try (DefaultCalculationTaskRunner runner = DefaultCalculationTaskRunner.ofMultiThreaded()) {
* // use the runner
* }
* </pre>
*
* @return the calculation task runner
*/
static DefaultCalculationTaskRunner ofMultiThreaded() {
return new DefaultCalculationTaskRunner(createExecutor(Runtime.getRuntime().availableProcessors()));
}
/**
* Creates a calculation task runner capable of performing calculations, specifying the executor.
* <p>
* It is the callers responsibility to manage the life-cycle of the executor.
*
* @param executor the executor to use
* @return the calculation task runner
*/
static DefaultCalculationTaskRunner of(ExecutorService executor) {
return new DefaultCalculationTaskRunner(executor);
}
// create an executor with daemon threads
private static ExecutorService createExecutor(int threads) {
int effectiveThreads = (threads <= 0 ? Runtime.getRuntime().availableProcessors() : threads);
ThreadFactory threadFactory = r -> {
Thread t = Executors.defaultThreadFactory().newThread(r);
t.setName("CalculationTaskRunner-" + t.getName());
t.setDaemon(true);
return t;
};
return Executors.newFixedThreadPool(effectiveThreads, threadFactory);
}
//-------------------------------------------------------------------------
/**
* Creates an instance specifying the executor to use.
*
* @param executor the executor that is used to perform the calculations
*/
private DefaultCalculationTaskRunner(ExecutorService executor) {
this.executor = ArgChecker.notNull(executor, "executor");
}
//-------------------------------------------------------------------------
@Override
public Results calculate(
CalculationTasks tasks,
MarketData marketData,
ReferenceData refData) {
// perform the calculations
ScenarioMarketData md = ScenarioMarketData.of(1, marketData);
Results results = calculateMultiScenario(tasks, md, refData);
// unwrap the results
// since there is only one scenario it is not desirable to return scenario result containers
List<Result<?>> mappedResults = results.getCells().stream()
.map(r -> unwrapScenarioResult(r))
.collect(toImmutableList());
return Results.of(results.getColumns(), mappedResults);
}
//-------------------------------------------------------------------------
/**
* Unwraps the result from an instance of {@link ScenarioArray} containing a single result.
* <p>
* When the user executes a single scenario the functions are invoked with a set of scenario market data
* of size 1. This means the functions are simpler and always deal with scenarios. But if the user has
* asked for a single set of results they don't want to see a collection of size 1 so the scenario results
* need to be unwrapped.
* <p>
* If {@code result} is a failure or doesn't contain a {@code ScenarioArray} it is returned.
* <p>
* If this method is called with a {@code ScenarioArray} containing more than one value it throws an exception.
*/
private static Result<?> unwrapScenarioResult(Result<?> result) {
if (result.isFailure()) {
return result;
}
Object value = result.getValue();
if (!(value instanceof ScenarioArray)) {
return result;
}
ScenarioArray<?> scenarioResult = (ScenarioArray<?>) value;
if (scenarioResult.getScenarioCount() != 1) {
throw new IllegalArgumentException(Messages.format(
"Expected one result but found {} in {}", scenarioResult.getScenarioCount(), scenarioResult));
}
return Result.success(scenarioResult.get(0));
}
@Override
public void calculateAsync(
CalculationTasks tasks,
MarketData marketData,
ReferenceData refData,
CalculationListener listener) {
// the listener is decorated to unwrap ScenarioArrays containing a single result
ScenarioMarketData md = ScenarioMarketData.of(1, marketData);
UnwrappingListener unwrappingListener = new UnwrappingListener(listener);
calculateMultiScenarioAsync(tasks, md, refData, unwrappingListener);
}
//-------------------------------------------------------------------------
@Override
public Results calculateMultiScenario(
CalculationTasks tasks,
ScenarioMarketData marketData,
ReferenceData refData) {
ResultsListener listener = new ResultsListener();
calculateMultiScenarioAsync(tasks, marketData, refData, listener);
return listener.result();
}
@Override
public void calculateMultiScenarioAsync(
CalculationTasks tasks,
ScenarioMarketData marketData,
ReferenceData refData,
CalculationListener listener) {
List<CalculationTask> taskList = tasks.getTasks();
// the listener is invoked via this wrapper
// the wrapper ensures thread-safety for the listener
// it also calls the listener with single CalculationResult cells, not CalculationResults
Consumer<CalculationResults> consumer =
new ListenerWrapper(listener, taskList.size(), tasks.getTargets(), tasks.getColumns());
// run each task using the executor
taskList.forEach(task -> runTask(task, marketData, refData, consumer));
}
// submits a task to the executor to be run
private void runTask(
CalculationTask task,
ScenarioMarketData marketData,
ReferenceData refData,
Consumer<CalculationResults> consumer) {
// the task is executed, with the result passed to the consumer
// the consumer wraps the listener to ensure thread-safety
Supplier<CalculationResults> taskExecutor = () -> task.execute(marketData, refData);
CompletableFuture.supplyAsync(taskExecutor, executor).thenAccept(consumer);
}
//-------------------------------------------------------------------------
@Override
public void close() {
executor.shutdown();
}
//-------------------------------------------------------------------------
//-------------------------------------------------------------------------
/**
* Listener that decorates another listener and unwraps {@link ScenarioArray} instances
* containing a single value before passing the value to the delegate listener.
* This is used by the single scenario async method.
*/
private static final class UnwrappingListener implements CalculationListener {
private final CalculationListener delegate;
private UnwrappingListener(CalculationListener delegate) {
this.delegate = delegate;
}
@Override
public void calculationsStarted(List<CalculationTarget> targets, List<Column> columns) {
delegate.calculationsStarted(targets, columns);
}
@Override
public void resultReceived(CalculationTarget target, CalculationResult calculationResult) {
Result<?> result = calculationResult.getResult();
Result<?> unwrappedResult = unwrapScenarioResult(result);
CalculationResult unwrappedCalculationResult = calculationResult.withResult(unwrappedResult);
delegate.resultReceived(target, unwrappedCalculationResult);
}
@Override
public void calculationsComplete() {
delegate.calculationsComplete();
}
}
}