
com.opengamma.strata.calc.runner.CalculationTasks Maven / Gradle / Ivy
/*
* 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.lang.invoke.MethodHandles;
import java.util.List;
import org.joda.beans.ImmutableBean;
import org.joda.beans.JodaBeanUtils;
import org.joda.beans.MetaBean;
import org.joda.beans.TypedMetaBean;
import org.joda.beans.gen.BeanDefinition;
import org.joda.beans.gen.PropertyDefinition;
import org.joda.beans.impl.light.LightMetaBean;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ListMultimap;
import com.opengamma.strata.basics.CalculationTarget;
import com.opengamma.strata.basics.ReferenceData;
import com.opengamma.strata.basics.ResolvableCalculationTarget;
import com.opengamma.strata.calc.CalculationRules;
import com.opengamma.strata.calc.CalculationRunner;
import com.opengamma.strata.calc.Column;
import com.opengamma.strata.calc.Measure;
import com.opengamma.strata.calc.ReportingCurrency;
import com.opengamma.strata.calc.marketdata.MarketDataRequirements;
import com.opengamma.strata.calc.marketdata.MarketDataRequirementsBuilder;
import com.opengamma.strata.collect.Messages;
/**
* The tasks that will be used to perform the calculations.
*
* This captures the targets, columns and tasks that define the result grid.
* Each task can be executed to produce the result. Applications will typically
* use {@link CalculationRunner} or {@link CalculationTaskRunner} to execute the tasks.
*/
@BeanDefinition(style = "light")
public final class CalculationTasks implements ImmutableBean {
/**
* The targets that calculations will be performed on.
*
* The result of the calculations will be a grid where each row is taken from this list.
*/
@PropertyDefinition(validate = "notEmpty")
private final List targets;
/**
* The columns that will be calculated.
*
* The result of the calculations will be a grid where each column is taken from this list.
*/
@PropertyDefinition(validate = "notEmpty")
private final List columns;
/**
* The tasks that perform the individual calculations.
*
* The results can be visualized as a grid, with a row for each target and a column for each measure.
* Each task can calculate the result for one or more cells in the grid.
*/
@PropertyDefinition(validate = "notEmpty")
private final List tasks;
//-------------------------------------------------------------------------
/**
* Obtains an instance from a set of targets, columns and rules.
*
* The targets will typically be trades.
* The columns represent the measures to calculate.
*
* Any target that implements {@link ResolvableCalculationTarget} will result in a failed task.
*
* @param rules the rules defining how the calculation is performed
* @param targets the targets for which values of the measures will be calculated
* @param columns the columns that will be calculated
* @return the calculation tasks
*/
public static CalculationTasks of(
CalculationRules rules,
List extends CalculationTarget> targets,
List columns) {
return of(rules, targets, columns, ReferenceData.empty());
}
/**
* Obtains an instance from a set of targets, columns and rules, resolving the targets.
*
* The targets will typically be trades and positions.
* The columns represent the measures to calculate.
*
* The targets will be resolved if they implement {@link ResolvableCalculationTarget}.
*
* @param rules the rules defining how the calculation is performed
* @param targets the targets for which values of the measures will be calculated
* @param columns the columns that will be calculated
* @param refData the reference data to use to resolve the targets
* @return the calculation tasks
*/
public static CalculationTasks of(
CalculationRules rules,
List extends CalculationTarget> targets,
List columns,
ReferenceData refData) {
// create columns that are a combination of the column overrides and the defaults
// this is done once as it is the same for all targets
List effectiveColumns =
columns.stream()
.map(column -> column.combineWithDefaults(rules.getReportingCurrency(), rules.getParameters()))
.collect(toImmutableList());
// loop around the targets, then the columns, to build the tasks
ImmutableList.Builder taskBuilder = ImmutableList.builder();
for (int rowIndex = 0; rowIndex < targets.size(); rowIndex++) {
CalculationTarget target = resolveTarget(targets.get(rowIndex), refData);
// find the applicable function, resolving the target if necessary
CalculationFunction> fn = target instanceof UnresolvableTarget ?
UnresolvableTargetCalculationFunction.INSTANCE :
rules.getFunctions().getFunction(target);
// create the tasks
List targetTasks = createTargetTasks(target, rowIndex, fn, effectiveColumns);
taskBuilder.addAll(targetTasks);
}
// calculation tasks holds the original user-specified columns, not the derived ones
return new CalculationTasks(taskBuilder.build(), columns);
}
// resolves the target
private static CalculationTarget resolveTarget(CalculationTarget target, ReferenceData refData) {
if (target instanceof ResolvableCalculationTarget) {
ResolvableCalculationTarget resolvable = (ResolvableCalculationTarget) target;
try {
return resolvable.resolveTarget(refData);
} catch (RuntimeException ex) {
return new UnresolvableTarget(resolvable, ex.getMessage());
}
}
return target;
}
// creates the tasks for a single target
private static List createTargetTasks(
CalculationTarget resolvedTarget,
int rowIndex,
CalculationFunction> function,
List columns) {
// create the cells and group them
ListMultimap grouped = ArrayListMultimap.create();
for (int colIndex = 0; colIndex < columns.size(); colIndex++) {
Column column = columns.get(colIndex);
Measure measure = column.getMeasure();
ReportingCurrency reportingCurrency = column.getReportingCurrency().orElse(ReportingCurrency.NATURAL);
CalculationTaskCell cell = CalculationTaskCell.of(rowIndex, colIndex, measure, reportingCurrency);
// group to find cells that can be shared, with same mappings and params (minus reporting currency)
CalculationParameters params = column.getParameters().filter(resolvedTarget, measure);
grouped.put(params, cell);
}
// build tasks
ImmutableList.Builder taskBuilder = ImmutableList.builder();
for (CalculationParameters params : grouped.keySet()) {
taskBuilder.add(CalculationTask.of(resolvedTarget, function, params, grouped.get(params)));
}
return taskBuilder.build();
}
//-------------------------------------------------------------------------
/**
* Obtains an instance from a set of tasks and columns.
*
* @param tasks the tasks that perform the calculations
* @param columns the columns that define the calculations
* @return the calculation tasks
*/
public static CalculationTasks of(List tasks, List columns) {
return new CalculationTasks(tasks, columns);
}
//-------------------------------------------------------------------------
/**
* Creates an instance.
*
* @param tasks the tasks that perform the calculations
* @param columns the columns that define the calculations
*/
private CalculationTasks(List tasks, List columns) {
this.columns = ImmutableList.copyOf(columns);
this.tasks = ImmutableList.copyOf(tasks);
// validate the number of tasks and number of columns tally
long cellCount = tasks.stream()
.flatMap(task -> task.getCells().stream())
.count();
int columnCount = columns.size();
if (cellCount != 0) {
if (columnCount == 0) {
throw new IllegalArgumentException("There must be at least one column");
}
if (cellCount % columnCount != 0) {
throw new IllegalArgumentException(
Messages.format(
"Number of cells ({}) must be exactly divisible by the number of columns ({})",
cellCount,
columnCount));
}
}
// pull out the targets from the tasks
int targetCount = (int) cellCount / columnCount;
CalculationTarget[] targets = new CalculationTarget[targetCount];
for (CalculationTask task : tasks) {
int rowIdx = task.getRowIndex();
if (targets[rowIdx] == null) {
targets[rowIdx] = task.getTarget();
} else if (targets[rowIdx] != task.getTarget()) {
throw new IllegalArgumentException(Messages.format(
"Tasks define two different targets for row {}: {} and {}", rowIdx, targets[rowIdx], task.getTarget()));
}
}
this.targets = ImmutableList.copyOf(targets); // missing targets will be caught here by null check
}
//-------------------------------------------------------------------------
/**
* Gets the market data that is required to perform the calculations.
*
* This can be used to pass into the market data system to obtain and calibrate data.
*
* @param refData the reference data
* @return the market data required for all calculations
* @throws RuntimeException if unable to obtain the requirements
*/
public MarketDataRequirements requirements(ReferenceData refData) {
// use for loop not streams for shorter stack traces
MarketDataRequirementsBuilder builder = MarketDataRequirements.builder();
for (CalculationTask task : tasks) {
builder.addRequirements(task.requirements(refData));
}
return builder.build();
}
//-------------------------------------------------------------------------
@Override
public String toString() {
return Messages.format("CalculationTasks[grid={}x{}]", targets.size(), columns.size());
}
//------------------------- AUTOGENERATED START -------------------------
/**
* The meta-bean for {@code CalculationTasks}.
*/
private static final TypedMetaBean META_BEAN =
LightMetaBean.of(
CalculationTasks.class,
MethodHandles.lookup(),
new String[] {
"targets",
"columns",
"tasks"},
ImmutableList.of(),
ImmutableList.of(),
ImmutableList.of());
/**
* The meta-bean for {@code CalculationTasks}.
* @return the meta-bean, not null
*/
public static TypedMetaBean meta() {
return META_BEAN;
}
static {
MetaBean.register(META_BEAN);
}
private CalculationTasks(
List targets,
List columns,
List tasks) {
JodaBeanUtils.notEmpty(targets, "targets");
JodaBeanUtils.notEmpty(columns, "columns");
JodaBeanUtils.notEmpty(tasks, "tasks");
this.targets = ImmutableList.copyOf(targets);
this.columns = ImmutableList.copyOf(columns);
this.tasks = ImmutableList.copyOf(tasks);
}
@Override
public TypedMetaBean metaBean() {
return META_BEAN;
}
//-----------------------------------------------------------------------
/**
* Gets the targets that calculations will be performed on.
*
* The result of the calculations will be a grid where each row is taken from this list.
* @return the value of the property, not empty
*/
public List getTargets() {
return targets;
}
//-----------------------------------------------------------------------
/**
* Gets the columns that will be calculated.
*
* The result of the calculations will be a grid where each column is taken from this list.
* @return the value of the property, not empty
*/
public List getColumns() {
return columns;
}
//-----------------------------------------------------------------------
/**
* Gets the tasks that perform the individual calculations.
*
* The results can be visualized as a grid, with a row for each target and a column for each measure.
* Each task can calculate the result for one or more cells in the grid.
* @return the value of the property, not empty
*/
public List getTasks() {
return tasks;
}
//-----------------------------------------------------------------------
@Override
public boolean equals(Object obj) {
if (obj == this) {
return true;
}
if (obj != null && obj.getClass() == this.getClass()) {
CalculationTasks other = (CalculationTasks) obj;
return JodaBeanUtils.equal(targets, other.targets) &&
JodaBeanUtils.equal(columns, other.columns) &&
JodaBeanUtils.equal(tasks, other.tasks);
}
return false;
}
@Override
public int hashCode() {
int hash = getClass().hashCode();
hash = hash * 31 + JodaBeanUtils.hashCode(targets);
hash = hash * 31 + JodaBeanUtils.hashCode(columns);
hash = hash * 31 + JodaBeanUtils.hashCode(tasks);
return hash;
}
//-------------------------- AUTOGENERATED END --------------------------
}