/**
* Copyright (C) 2011 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.financial.analytics;
import java.util.Collections;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import com.opengamma.core.position.Portfolio;
import com.opengamma.core.position.PortfolioNode;
import com.opengamma.core.position.Position;
import com.opengamma.engine.ComputationTarget;
import com.opengamma.engine.function.AbstractFunction;
import com.opengamma.engine.function.CompiledFunctionDefinition;
import com.opengamma.engine.function.FunctionCompilationContext;
import com.opengamma.engine.function.FunctionExecutionContext;
import com.opengamma.engine.function.FunctionInputs;
import com.opengamma.engine.target.ComputationTargetType;
import com.opengamma.engine.value.ComputedValue;
import com.opengamma.engine.value.ValueProperties;
import com.opengamma.engine.value.ValuePropertyNames;
import com.opengamma.engine.value.ValueRequirement;
import com.opengamma.engine.value.ValueSpecification;
import com.opengamma.financial.property.UnitProperties;
/**
* Able to sum a particular requirement name from a set of underlying positions. If any values are not produced (because of missing market data or computation errors) a partial sum is produced.
*/
public class SummingFunction extends MissingInputsFunction {
public static final String IGNORE_ROOT_NODE = "SummingFunction.IGNORE_ROOT_NODE";
/**
* The number of positions that made up the sum.
*/
private static final String POSITION_COUNT = "PositionCount";
/**
* Main implementation.
*/
protected static class Impl extends AbstractFunction.NonCompiledInvoker {
private final String _requirementName;
private final String[] _homogenousProperties = UnitProperties.unitPropertyNames();
protected Impl(final String requirementName) {
_requirementName = requirementName;
}
private static CompiledFunctionDefinition of(final String requirementName) {
return new Impl(requirementName);
}
protected String getRequirementName() {
return _requirementName;
}
@Override
public String getShortName() {
return "Sum(" + _requirementName + ")";
}
@Override
public ComputationTargetType getTargetType() {
return ComputationTargetType.PORTFOLIO_NODE;
}
@Override
public boolean canApplyTo(final FunctionCompilationContext context, final ComputationTarget target) {
// Applies to any portfolio node, except the root if "Don't aggregate root node" is set
Portfolio portfolio = context.getPortfolio();
if (portfolio == null || portfolio.getAttributes().get(IGNORE_ROOT_NODE) == null) {
return true;
} else {
return target.getPortfolioNode().getParentNodeId() != null;
}
}
@Override
public Set<ValueSpecification> getResults(final FunctionCompilationContext context, final ComputationTarget target) {
return Collections.singleton(new ValueSpecification(getRequirementName(), target.toSpecification(), ValueProperties.all()));
}
@Override
public Set<ValueRequirement> getRequirements(final FunctionCompilationContext context, final ComputationTarget target, final ValueRequirement desiredValue) {
final PortfolioNode node = target.getPortfolioNode();
final Set<ValueRequirement> requirements = new HashSet<ValueRequirement>();
// Requirement has all constraints asked of us
final ValueProperties.Builder resultConstraintsBuilder = desiredValue.getConstraints().copy();
for (final String homogenousProperty : _homogenousProperties) {
// TODO: this should probably only be optional if absent from the desired constraints
resultConstraintsBuilder.withOptional(homogenousProperty);
}
ValueProperties resultConstraints = resultConstraintsBuilder.get();
for (final Position position : node.getPositions()) {
requirements.add(new ValueRequirement(getRequirementName(), ComputationTargetType.POSITION, position.getUniqueId().toLatest(), resultConstraints));
}
for (final PortfolioNode childNode : node.getChildNodes()) {
requirements.add(new ValueRequirement(getRequirementName(), ComputationTargetType.PORTFOLIO_NODE, childNode.getUniqueId(), resultConstraints));
}
return requirements;
}
protected Object addValue(final Object previousSum, final Object currentValue) {
return SumUtils.addValue(previousSum, currentValue, getRequirementName());
}
protected ValueProperties.Builder createValueProperties(final ValueProperties inputProperties, final int componentCount) {
return inputProperties.copy().withoutAny(ValuePropertyNames.FUNCTION).with(ValuePropertyNames.FUNCTION, getUniqueId()).withoutAny(POSITION_COUNT)
.with(POSITION_COUNT, Integer.toString(componentCount));
}
@Override
public Set<ValueSpecification> getResults(final FunctionCompilationContext context, final ComputationTarget target, final Map<ValueSpecification, ValueRequirement> inputs) {
int positionCount = 0;
// Result properties are anything that was common to the input requirements
ValueProperties common = null;
final boolean[] homogenousProperties = new boolean[_homogenousProperties.length];
for (final ValueSpecification input : inputs.keySet()) {
final ValueProperties properties = input.getProperties();
if (properties.isDefined(POSITION_COUNT)) {
final String inputPositionCountValue = properties.getStrictValue(POSITION_COUNT);
if (inputPositionCountValue != null) {
final int inputPositionCount = Integer.parseInt(inputPositionCountValue);
if (inputPositionCount == 0) {
// Ignore this one
continue;
}
positionCount += inputPositionCount;
}
} else {
positionCount++;
}
if (common == null) {
common = properties;
for (int i = 0; i < homogenousProperties.length; i++) {
homogenousProperties[i] = properties.isDefined(_homogenousProperties[i]);
}
} else {
for (int i = 0; i < homogenousProperties.length; i++) {
if (properties.isDefined(_homogenousProperties[i]) != homogenousProperties[i]) {
// Either defines one of the properties that something else doesn't, or doesn't define
// one that something else does
return null;
}
}
common = SumUtils.addProperties(common, properties);
}
}
if (common == null) {
// Can't have been any inputs - the sum will be zero with any properties the caller wants
return Collections.singleton(new ValueSpecification(getRequirementName(), target.toSpecification(), createValueProperties().with(POSITION_COUNT, "0").get()));
}
for (int i = 0; i < homogenousProperties.length; i++) {
if (homogenousProperties[i] == Boolean.TRUE) {
if (!common.isDefined(_homogenousProperties[i])) {
// No common intersection of values for homogenous property
return null;
}
}
}
return Collections.singleton(new ValueSpecification(getRequirementName(), target.toSpecification(), createValueProperties(common, positionCount).get()));
}
@Override
public Set<ComputedValue> execute(final FunctionExecutionContext executionContext, final FunctionInputs inputs, final ComputationTarget target, final Set<ValueRequirement> desiredValues) {
final ValueRequirement desiredValue = desiredValues.iterator().next();
Object value = null;
for (final ComputedValue input : inputs.getAllValues()) {
final Object inputValue = input.getValue();
if (inputValue instanceof String) {
// Treat the empty string as a special case - it's used in place of 0 when there are no valid inputs
if (((String) inputValue).length() == 0) {
continue;
}
}
value = addValue(value, input.getValue());
}
if (value == null) {
// Can't have been any non-zero inputs - the sum is logical zero
value = "";
}
return Collections.singleton(new ComputedValue(new ValueSpecification(getRequirementName(), target.toSpecification(), desiredValue.getConstraints()), value));
}
}
public SummingFunction(final String requirementName) {
super(Impl.of(requirementName));
}
}