/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.engine.depgraph.impl;
import java.io.PrintStream;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.opengamma.engine.depgraph.DependencyGraph;
import com.opengamma.engine.depgraph.DependencyNode;
import com.opengamma.engine.function.MarketDataSourcingFunction;
import com.opengamma.engine.value.ValueRequirement;
import com.opengamma.engine.value.ValueSpecification;
import com.opengamma.util.ArgumentChecker;
/**
* Default implementation of {@link DependencyGraph}.
*/
public class DependencyGraphImpl implements DependencyGraph, Serializable {
// TODO: Change DependencyGraph from an interface to an abstract class and put the static stuff into it rather than have static methods & instanceof checks here
// TODO: Make ExecutionOrderNodeIterator package visible
private static final long serialVersionUID = 1L;
/**
* The calculation configuration this is the graph for. A view definition may require multiple graphs, one for each configuration.
*/
private final String _calculationConfigurationName;
/**
* The terminal outputs required of the graph. Each is associated with the original value requirements that caused their inclusion in the graph.
*/
private final Map<ValueSpecification, Set<ValueRequirement>> _terminalOutputs;
/**
* The roots of the graph. The full set of nodes in the graph can be found by traversing these nodes.
*/
private final DependencyNode[] _roots;
/**
* The cached size of the graph.
*/
private final int _size;
/**
* The cached hash code.
*/
private volatile int _hashCode;
/**
* Creates a new dependency graph for the named configuration with given roots and terminal outputs.
*
* @param calcConfigName the configuration name, not null
* @param roots the roots of the graph, not null and not containing null
* @param size the size of the graph
* @param terminalOutputs the terminal outputs from the graph, not null and not containing null
*/
public DependencyGraphImpl(final String calcConfigName, final Collection<DependencyNode> roots, final int size, final Map<ValueSpecification, Set<ValueRequirement>> terminalOutputs) {
ArgumentChecker.notNull(calcConfigName, "calcConfigName");
ArgumentChecker.noNulls(roots, "roots");
ArgumentChecker.notNull(terminalOutputs, "terminalOutputs");
_calculationConfigurationName = calcConfigName;
_terminalOutputs = Maps.newHashMapWithExpectedSize(terminalOutputs.size());
for (Map.Entry<ValueSpecification, Set<ValueRequirement>> terminalOutput : terminalOutputs.entrySet()) {
ArgumentChecker.notNull(terminalOutput.getKey(), "terminalOutput.key");
ArgumentChecker.notNull(terminalOutput.getValue(), "terminalOutput.value");
_terminalOutputs.put(terminalOutput.getKey(), ImmutableSet.copyOf(terminalOutput.getValue()));
}
_roots = roots.toArray(new DependencyNode[roots.size()]);
_size = size;
}
private DependencyGraphImpl(final String calcConfigName, final DependencyNode[] roots, final int size, final Map<ValueSpecification, Set<ValueRequirement>> terminalOutputs) {
_calculationConfigurationName = calcConfigName;
_roots = roots;
_size = size;
_terminalOutputs = terminalOutputs;
}
@Override
public String getCalculationConfigurationName() {
return _calculationConfigurationName;
}
private static int calculateSize(final DependencyNode node, final Set<DependencyNode> nodes) {
int count = 1;
final int inputs = node.getInputCount();
for (int i = 0; i < inputs; i++) {
if (nodes.add(node.getInputNode(i))) {
count += calculateSize(node.getInputNode(i), nodes);
}
}
return count;
}
private static int calculateSize(final DependencyNode[] roots) {
final Set<DependencyNode> nodes = new HashSet<DependencyNode>();
int size = 0;
for (DependencyNode root : roots) {
size += calculateSize(root, nodes);
}
return size;
}
@Override
public int getSize() {
return _size;
}
@Override
public int getRootCount() {
return _roots.length;
}
@Override
public DependencyNode getRootNode(final int index) {
return _roots[index];
}
@Override
public Map<ValueSpecification, Set<ValueRequirement>> getTerminalOutputs() {
return _terminalOutputs;
}
/**
* Returns the set of terminal outputs produced by a graph.
*
* @param graph the graph to query, not null
* @return the terminal output set, not null
*/
public static Set<ValueSpecification> getTerminalOutputSpecifications(final DependencyGraph graph) {
return graph.getTerminalOutputs().keySet();
}
private Map<ValueSpecification, DependencyNode> getAllOutputs() {
final Map<ValueSpecification, DependencyNode> outputs = new HashMap<ValueSpecification, DependencyNode>();
for (DependencyNode root : _roots) {
DependencyNodeImpl.gatherOutputValues(root, outputs);
}
return outputs;
}
/**
* Returns all outputs produced within a graph with the node that produces each; this is the total set of all terminal and non-terminal outputs.
*
* @param graph the graph to query, not null
* @return the full output set as a map of value specification to the node that produces it, not null
*/
public static Map<ValueSpecification, DependencyNode> getAllOutputs(final DependencyGraph graph) {
if (graph instanceof DependencyGraphImpl) {
return ((DependencyGraphImpl) graph).getAllOutputs();
} else {
final Map<ValueSpecification, DependencyNode> outputs = new HashMap<ValueSpecification, DependencyNode>();
final Iterator<DependencyNode> itr = graph.nodeIterator();
while (itr.hasNext()) {
final DependencyNode node = itr.next();
final int count = node.getOutputCount();
for (int i = 0; i < count; i++) {
outputs.put(node.getOutputValue(i), node);
}
}
return outputs;
}
}
/**
* Returns the set of all outputs produced by a graph; this is the total set of all terminal and non-terminal outputs.
*
* @param graph the graph to query, not null
* @return the full output set, not null
*/
public static Set<ValueSpecification> getAllOutputSpecifications(final DependencyGraph graph) {
return getAllOutputs(graph).keySet();
}
@Override
public Iterator<DependencyNode> nodeIterator() {
// TODO: How often does this get called, and how costly is it? Is it worth creating an array after the iteration so we can do it faster in the future?
// Cheapest way to iterate is depth-first which happens to be execution order
return new ExecutionOrderNodeIterator(this);
}
/**
* Returns all of the nodes from a graph.
* <p>
* This is provided mainly for tests and compatibility with code prior to major changes made to the {@link DependencyGraph} class. It is unlikely to be efficient in terms of memory or the time taken
* to construct the collection as the work may be performed twice; once building a set to ensure that each node is visited only once (see {@link #executionOrderIterator}) and again for the returned
* set.
*
* @param graph the graph to query, not null
* @return all of the nodes in the graph, not null and not containing null
*/
public static Collection<DependencyNode> getDependencyNodes(final DependencyGraph graph) {
final Collection<DependencyNode> nodes = new ArrayList<DependencyNode>(graph.getSize());
final Iterator<DependencyNode> itr = graph.nodeIterator();
while (itr.hasNext()) {
nodes.add(itr.next());
}
return nodes;
}
/**
* Returns all of the root nodes from a graph.
*
* @param graph the graph to query, not null
* @return the root nodes from the graph, not null and not containing null
*/
public static Set<DependencyNode> getRootNodes(final DependencyGraph graph) {
final int count = graph.getRootCount();
final Set<DependencyNode> roots = Sets.newHashSetWithExpectedSize(count);
for (int i = 0; i < count; i++) {
roots.add(graph.getRootNode(i));
}
return roots;
}
/**
* Returns all of the value specifications that correspond to market-data-sourcing functions at the leaves of the graph.
*
* @param graph the graph to query, not null
* @return the value specifications, not null and not containing null
*/
public static Set<ValueSpecification> getMarketData(final DependencyGraph graph) {
// TODO: CompiledViewCalcConfig has a better signature and implementation. Swap to that instead?
final Iterator<DependencyNode> itr = graph.nodeIterator();
final Set<ValueSpecification> marketData = new HashSet<ValueSpecification>();
while (itr.hasNext()) {
final DependencyNode node = itr.next();
if (MarketDataSourcingFunction.UNIQUE_ID.equals(node.getFunction().getFunctionId())) {
final int count = node.getOutputCount();
for (int i = 0; i < count; i++) {
marketData.add(node.getOutputValue(i));
}
}
}
return marketData;
}
@Override
public String toString() {
return "DependencyGraph[calcConf=" + getCalculationConfigurationName() + ",nodes=" + getSize() + ",terminals=" + getTerminalOutputs().size() + "]";
}
private static DependencyNode[] reverseExecution(final DependencyGraph graph) {
final Iterator<DependencyNode> forward = new ExecutionOrderNodeIterator(graph);
final DependencyNode[] backward = new DependencyNode[graph.getSize()];
for (int i = backward.length - 1; i >= 0; i--) {
backward[i] = forward.next();
}
return backward;
}
private static Map<ValueSpecification, DependencyNode> findRoots(final DependencyGraph graph, final Map<ValueSpecification, ?> terminals, final Collection<DependencyNode> roots) {
final DependencyNode[] nodes = reverseExecution(graph);
final Map<ValueSpecification, DependencyNode> necessary = Maps.newHashMapWithExpectedSize(nodes.length);
findNodes: for (DependencyNode node : nodes) { //CSIGNORE
int count = node.getOutputCount();
boolean isTerminal = false;
for (int i = 0; i < count; i++) {
final ValueSpecification output = node.getOutputValue(i);
if (necessary.containsKey(output)) {
// Node produces at least one necessary value; make all of its inputs necessary
count = node.getInputCount();
for (i = 0; i < count; i++) {
necessary.put(node.getInputValue(i), null);
}
continue findNodes;
}
if (!isTerminal && terminals.containsKey(output)) {
isTerminal = true;
}
}
if (isTerminal) {
// Node produces terminal outputs; nothing else is consumed. This is a root with all inputs necessary
roots.add(node);
count = node.getInputCount();
for (int i = 0; i < count; i++) {
necessary.put(node.getInputValue(i), null);
}
}
}
for (Map.Entry<ValueSpecification, ?> terminal : terminals.entrySet()) {
necessary.put(terminal.getKey(), null);
}
return necessary;
}
/**
* Removes any unnecessary values from a graph, returning the new graph. A necessary output is one that is marked as terminal or is consumed by a node that produces a necessary value.
* <p>
* Graph construction may be based on functions which can produce multiple outputs which may not then be needed. Removing them may make execution more efficient. Similarly an incremental graph build
* may have fragments of graph from an earlier iteration producing values which are no longer terminal and no longer need to be calculated, making execution more efficient.
*
* @param graph the graph to remove values from, not null
* @return the new graph object, or the previous graph instance if it only contains necessary values
*/
public static DependencyGraph removeUnnecessaryValues(final DependencyGraph graph) {
final int rootCount = graph.getRootCount();
final Collection<DependencyNode> roots = new ArrayList<DependencyNode>(rootCount);
final Map<ValueSpecification, DependencyNode> necessary = findRoots(graph, graph.getTerminalOutputs(), roots);
final DependencyNode[] newRoots = new DependencyNode[roots.size()];
// Note: this is tightly coupled to how the ExecutionOrderNodeIterator works
int i = newRoots.length;
for (DependencyNode root : roots) {
final DependencyNode newRoot = DependencyNodeImpl.removeUnnecessaryValues(root, necessary);
assert newRoot != null;
newRoots[--i] = newRoot;
}
if (newRoots.length == rootCount) {
// Possibly no changes
boolean same = true;
for (i = 0; i < rootCount; i++) {
if (newRoots[i] != graph.getRootNode(i)) {
same = false;
}
}
if (same) {
return graph;
}
}
return new DependencyGraphImpl(graph.getCalculationConfigurationName(), newRoots, calculateSize(newRoots), graph.getTerminalOutputs());
}
private static void dumpNodeASCII(final PrintStream out, String indent, final DependencyNode node, final Map<DependencyNode, Integer> uidMap) {
Integer uid = uidMap.get(node);
if (uid == null) {
uid = uidMap.size() + 1;
uidMap.put(node, uid);
out.println(indent + uid + " " + node);
} else {
out.println(indent + uid + " " + node + " ...");
return;
}
indent = indent + " ";
int inputs = node.getInputCount();
for (int i = 0; i < inputs; i++) {
out.println(indent + "Iv=" + node.getInputValue(i));
}
int outputs = node.getOutputCount();
for (int i = 0; i < outputs; i++) {
out.println(indent + "Ov=" + node.getOutputValue(i));
}
for (int i = 0; i < inputs; i++) {
dumpNodeASCII(out, indent, node.getInputNode(i), uidMap);
}
}
/**
* Produces an ASCII representation of a dependency graph.
* <p>
* This is provided for diagnostic/debugging purposes only. It must not be used for any form of data interchange or persistence as the formatting may change in future releases without prior notice.
*
* @param graph the graph to dump out, not null
* @param out the stream to write to, not null
*/
public static void dumpStructureASCII(final DependencyGraph graph, final PrintStream out) {
final Map<DependencyNode, Integer> uid = new HashMap<DependencyNode, Integer>(graph.getSize());
final int count = graph.getRootCount();
for (int i = 0; i < count; i++) {
dumpNodeASCII(out, "", graph.getRootNode(i), uid);
}
}
@Override
public int hashCode() {
int hc = _hashCode;
if (hc == 0) {
hc = ((_calculationConfigurationName.hashCode() * 31) + _terminalOutputs.hashCode()) * 31;
for (DependencyNode root : _roots) {
hc += DependencyNode.HASHING_STRATEGY.hashCode(root);
}
if (hc == 0) {
hc = 1;
}
_hashCode = hc;
}
return hc;
}
/**
* Tests for equality with another graph. Two graphs are equal if they:
* <ul>
* <li>Are for the same calculation configuration name;
* <li>Produce the same set of terminal outputs from the given value requirements; and
* <li>Have the same set of root nodes, as per {@link DependencyNode#HASHING_STRATEGY}
* </ul>
* <p>
* Note that comparing two graphs in this way can be an expensive operation.
*
* @param o the object to compare to
* @return true if the object is a graph and equal to this one, false otherwise
*/
@Override
public boolean equals(final Object o) {
if (o == this) {
return true;
}
if (!(o instanceof DependencyGraph)) {
return false;
}
final DependencyGraph other = (DependencyGraph) o;
final int roots = _roots.length;
if (roots != other.getRootCount()) {
return false;
}
if (!_calculationConfigurationName.equals(other.getCalculationConfigurationName())) {
return false;
}
if (!_terminalOutputs.equals(other.getTerminalOutputs())) {
return false;
}
final int[] hashCodesOther = new int[roots];
for (int i = 0; i < roots; i++) {
hashCodesOther[i] = DependencyNode.HASHING_STRATEGY.hashCode(other.getRootNode(i));
}
final boolean[] matched = new boolean[roots];
loopThis: for (int i = 0; i < roots; i++) { //CSIGNORE
final DependencyNode nodeThis = _roots[i];
final int hashCodeThis = DependencyNode.HASHING_STRATEGY.hashCode(nodeThis);
for (int j = i; j < roots; j++) {
if (!matched[j] && (hashCodeThis == hashCodesOther[j]) && DependencyNode.HASHING_STRATEGY.equals(nodeThis, other.getRootNode(j))) {
matched[j] = true;
continue loopThis;
}
}
// No match found for one of our entries
return false;
}
// All root nodes were matched
return true;
}
}