/** * Copyright (C) 2013 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.engine.depgraph.impl; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import com.google.common.collect.Maps; import com.opengamma.engine.depgraph.DependencyGraph; import com.opengamma.engine.depgraph.DependencyNode; import com.opengamma.engine.value.ValueRequirement; import com.opengamma.engine.value.ValueSpecification; /** * Sub-graphing operation that only keeps nodes which: * <ul> * <li>Pass a filtering test themselves; and * <li>Consume only values from nodes that also pass the filtering test * <ul> */ public abstract class RootDiscardingSubgrapher { /** * The inclusion state of any given node. */ public static enum NodeState { /** * The node is excluded from the graph. This means no nodes from the original graph that depended on this node can remain. */ EXCLUDED, /** * The node is included in the graph as a root node. */ INCLUDED, /** * The node is included in the graph, and is consumed by at least one other. */ INCLUDED_NON_ROOT } /** * Tests whether the given node passes the filter. * <p> * This test is performed before any of the input nodes are considered for inclusion. * * @param node the node to test, never null * @return true if the node passes the filter (and should have its input nodes tested), false otherwise */ protected abstract boolean acceptNode(DependencyNode node); private NodeState acceptNode(final DependencyNode node, final Map<DependencyNode, NodeState> accepted) { NodeState state = accepted.get(node); if (state != null) { return state; } if (!acceptNode(node)) { accepted.put(node, NodeState.EXCLUDED); return NodeState.EXCLUDED; } final int count = node.getInputCount(); boolean inputsAccepted = true; for (int i = 0; i < count; i++) { final DependencyNode input = node.getInputNode(i); if (acceptNode(input, accepted) == NodeState.EXCLUDED) { inputsAccepted = false; } } if (inputsAccepted) { accepted.put(node, NodeState.INCLUDED); for (int i = 0; i < count; i++) { accepted.put(node.getInputNode(i), NodeState.INCLUDED_NON_ROOT); } return NodeState.INCLUDED; } accepted.put(node, NodeState.EXCLUDED); return NodeState.EXCLUDED; } /** * Forms a subgraph of the given graph. * * @param roots the root nodes to process, not null * @param terminals the terminal outputs to update, not null * @param missingRequirements the structure that should receive any requirements that are ejected from the sub-graph, not null * @param accepted the acceptance state buffer, not null * @return the new graph roots, or null for an empty graph */ public Set<DependencyNode> subGraph(Collection<DependencyNode> roots, final Map<ValueSpecification, Set<ValueRequirement>> terminals, final Set<ValueRequirement> missingRequirements, final Map<DependencyNode, NodeState> accepted) { Set<DependencyNode> newRoots = new HashSet<DependencyNode>(); do { Set<DependencyNode> possibleRoots = null; for (DependencyNode root : roots) { final NodeState state = acceptNode(root, accepted); if (state == NodeState.INCLUDED) { // This root is in the new graph newRoots.add(root); } else if (state == NodeState.EXCLUDED) { // This root isn't in the new graph, but one or more of its inputs might become roots if (possibleRoots == null) { possibleRoots = new HashSet<DependencyNode>(); } final int inputs = root.getInputCount(); for (int j = 0; j < inputs; j++) { possibleRoots.add(root.getInputNode(j)); } } } if (possibleRoots != null) { roots = possibleRoots; } else { break; } } while (true); if (newRoots.isEmpty()) { return null; } else { for (Map.Entry<DependencyNode, NodeState> accept : accepted.entrySet()) { final DependencyNode node = accept.getKey(); final int count = node.getOutputCount(); if (accept.getValue() == NodeState.EXCLUDED) { for (int i = 0; i < count; i++) { final Set<ValueRequirement> requirements = terminals.remove(node.getOutputValue(i)); if (requirements != null) { missingRequirements.addAll(requirements); } } } } return newRoots; } } /** * Forms a subgraph of the given graph. * * @param graph the graph to process, not null * @param missingRequirements the structure that should receive any requirements that are ejected from the sub-graph, not null * @param accepted the acceptance state buffer, not null * @return the subgraph, or null if it would be empty */ public DependencyGraph subGraph(final DependencyGraph graph, final Set<ValueRequirement> missingRequirements, final Map<DependencyNode, NodeState> accepted) { final int rootCount = graph.getRootCount(); Set<DependencyNode> newRoots = new HashSet<DependencyNode>(); Set<DependencyNode> possibleRoots = null; for (int i = 0; i < rootCount; i++) { final DependencyNode root = graph.getRootNode(i); final NodeState state = acceptNode(root, accepted); if (state == NodeState.INCLUDED) { // This root is in the new graph newRoots.add(root); } else { assert state == NodeState.EXCLUDED; // This root isn't in the new graph, but one or more of its inputs might become roots if (possibleRoots == null) { possibleRoots = new HashSet<DependencyNode>(); } final int inputs = root.getInputCount(); for (int j = 0; j < inputs; j++) { possibleRoots.add(root.getInputNode(j)); } } } if (possibleRoots == null) { // This gets allocated if any of the roots are absent from the filter, so if unallocated there are no changes to the graph return graph; } while (possibleRoots != null) { final Set<DependencyNode> roots = possibleRoots; possibleRoots = null; for (DependencyNode root : roots) { final NodeState state = acceptNode(root, accepted); if (state == NodeState.INCLUDED) { // This root is in the new graph newRoots.add(root); } else if (state == NodeState.EXCLUDED) { // This root isn't in the new graph, but one or more of its inputs might become roots if (possibleRoots == null) { possibleRoots = new HashSet<DependencyNode>(); } final int inputs = root.getInputCount(); for (int j = 0; j < inputs; j++) { possibleRoots.add(root.getInputNode(j)); } } } } if (newRoots.isEmpty()) { return null; } else { final Map<ValueSpecification, Set<ValueRequirement>> oldTerminals = graph.getTerminalOutputs(); final Map<ValueSpecification, Set<ValueRequirement>> terminals = Maps.newHashMapWithExpectedSize(oldTerminals.size()); int size = 0; for (Map.Entry<DependencyNode, NodeState> accept : accepted.entrySet()) { final DependencyNode node = accept.getKey(); final int count = node.getOutputCount(); if (accept.getValue() == NodeState.EXCLUDED) { for (int i = 0; i < count; i++) { final ValueSpecification value = node.getOutputValue(i); final Set<ValueRequirement> terminal = oldTerminals.get(value); if (terminal != null) { missingRequirements.addAll(terminal); } } } else { for (int i = 0; i < count; i++) { final ValueSpecification value = node.getOutputValue(i); final Set<ValueRequirement> terminal = oldTerminals.get(value); if (terminal != null) { terminals.put(value, terminal); } } size++; } } return new DependencyGraphImpl(graph.getCalculationConfigurationName(), newRoots, size, terminals); } } /** * Forms a subgraph of the given graph. * * @param roots the root nodes to process, not null * @param terminals the terminal outputs to update, not null * @param missingRequirements the structure that should receive any requirements that are ejected from the sub-graph, not null * @return the new graph roots, or null for an empty graph */ public Set<DependencyNode> subGraph(final Collection<DependencyNode> roots, final Map<ValueSpecification, Set<ValueRequirement>> terminals, final Set<ValueRequirement> missingRequirements) { return subGraph(roots, terminals, missingRequirements, new HashMap<DependencyNode, NodeState>()); } /** * Forms a subgraph of the given graph. * * @param graph the graph to process, not null * @param missingRequirements the structure that should receive any requirements that are ejected from the sub-graph, not null * @return the subgraph, or null if it would be empty */ public DependencyGraph subGraph(final DependencyGraph graph, final Set<ValueRequirement> missingRequirements) { return subGraph(graph, missingRequirements, Maps.<DependencyNode, NodeState>newHashMapWithExpectedSize(graph.getSize())); } }