/******************************************************************************* * Copyright 2012 Analog Devices, Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ********************************************************************************/ package com.analog.lyric.dimple.model.core; import static java.util.Objects.*; import java.util.ArrayDeque; import java.util.Deque; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Objects; import java.util.Set; import org.eclipse.jdt.annotation.Nullable; /** * An iterator that walks through connected nodes in a {@link FactorGraph} * using specified {@link #searchOrder} starting from an optionally provided * factor or variable node. This will only visit nodes that are reachable from * the initial node and that are contained within the specified graph or one * of its direct or indirect subgraphs. */ public class FactorGraphWalker implements Iterator<INode> { /** * Specifies a search order for visiting nodes in the {@link FactorGraph}. Both * use preorder traversal: i.e. a node is always visited before going on to * its adjacent nodes. Both will also visit a node no more than once, skipping * nodes it has seen before and will increment the cycle count ({@link #getCycleCount}) * every time it does so. Both methods will honor the specified {@link #maxSearchDepth} * and will not visit nodes that take more than that many steps to reach. * <dl> * <dt>{@literal DEPTH_FIRST}</dt> * <dd>After visiting a given node each adjacent node * (excluding the one that lead to the node) and nodes connected to it are explored * up the specified {@link #maxSearchDepth} before visiting the next adjacent * node. * </dd> * <dt>{@literal BREADH_FIRST}</dt> * <dd>Visits all nodes at depth n from the starting node before visiting those at * distance n + 1. * </dd> * </dl> */ public enum Order { DEPTH_FIRST, BREADTH_FIRST } /* * State */ private @Nullable FactorGraph _rootGraph; private @Nullable INode _firstNode; private Order _searchOrder = Order.BREADTH_FIRST; private int _maxSearchDepth = Integer.MAX_VALUE; private int _maxRelativeNestingDepth; private static class IncomingPort { private final INode node; private int siblingNumber; @SuppressWarnings("null") private IncomingPort() { this(null, -1); } private IncomingPort(INode node, int siblingNumber) { this.node = node; this.siblingNumber = siblingNumber; } } private Set<INode> _visitedNodes = new LinkedHashSet<INode>(); private boolean _visitedNodesWasExposed = true; private Deque<IncomingPort> _portDeque = new ArrayDeque<>(); private int _currentDepth; private int _maxDepthSeen; private @Nullable INode _nextNode; private int _cycleCount; private final IncomingPort _depthChangeSentinel = new IncomingPort(); /* * Construction/initialization */ /** * Creates a new walker initialized using {@link #init(FactorGraph, INode)}. */ public FactorGraphWalker(FactorGraph graph, INode firstNode) { this.init(graph, firstNode); } /** * Creates a new walker initialized using {@link #init(FactorGraph)}. */ public FactorGraphWalker(FactorGraph graph) { this.init(graph); } /** * Creates a new walker initialized using {@link #init()}. */ public FactorGraphWalker() { this.init(); } /** * Starts search over again using previously specified values * for root graph ({@link #getRootGraph}), starting node {@link #getFirstNode}, * {@link #searchOrder}, {@link #maxSearchDepth} and * {@link #maxRelativeNestingDepth}. */ public FactorGraphWalker init() { if (this._visitedNodesWasExposed) { // Someone might still be using the old copy, so we have to make a new one. this._visitedNodes = new LinkedHashSet<INode>(); } else { this._visitedNodes.clear(); } this._portDeque.clear(); this._currentDepth = 0; this._maxDepthSeen = 0; this._nextNode = null; this._cycleCount= 0; FactorGraph root = this._rootGraph; if (root != null) { INode node = this._firstNode != null ? this._firstNode : root.getFirstNode(); if (node != null) { this._portDeque.add(new IncomingPort(node,-1)); } } return this; } /** * Starts search over using given root graph {@link #getRootGraph}. The * previous value of {@link #getFirstNode} will be cleared. It is ok * for {@code graph} to be null. Otherwise the same as {@link #init()}. */ public FactorGraphWalker init(FactorGraph graph) { return this.init(graph, null); } /** * Starts search over using given root graph {@link #getRootGraph} and * starting node ({@link #getFirstNode}). It is ok * for {@code graph} and/or {@code firstNode} to be null, but if non-null * {@code firstNode} must have {@code graph} as an ancestor. * Otherwise the same as {@link #init()}. */ public FactorGraphWalker init(@Nullable FactorGraph graph, @Nullable INode firstNode) { this._rootGraph = graph; this._firstNode = firstNode; if (graph != null && firstNode != null) { assert(graph.isAncestorOf(firstNode)); } return this.init(); } /** * Returns the maximum {@link FactorGraph} nesting depth that will * be explored by the search. * @see #maxRelativeNestingDepth(int) */ public final int maxRelativeNestingDepth() { return this._maxRelativeNestingDepth; } /** * Sets the maximum {@link FactorGraph} nesting depth that will * be explored by the search. When set to the maximum (the default) * every connected node in any direct or indirect subgraph will be * included. Setting it to 0 will restrict the search to nodes that * are contained directly in the root graph. * <p> * Changing this value after visiting some nodes may produce * different results than setting it to that value initially. */ public final FactorGraphWalker maxRelativeNestingDepth(int depth) { this._maxRelativeNestingDepth = depth; return this; } /** * Returns the maximum depth in the graph that will be searched with * respect to the starting node. The default is the maximum integer value. */ public final int maxSearchDepth() { return this._maxSearchDepth; } /** * Sets the maximum depth in the graph that will be searched with * respect to the starting node. Changing this value after the * search has started may result in a different set of nodes visited * than if that depth had been specified initially. */ public final FactorGraphWalker maxSearchDepth(int depth) { this._maxSearchDepth = depth; return this; } /** * Cause nodes to be visited using {@literal BREADH_FIRST} search {@link Order}. * Changing the search order after calling {@link #hasNext} or {@link #next} * will result in unpredictable behavior. * @see #useBreadthFirst * @see #searchOrder */ public final FactorGraphWalker useBreadthFirst() { assert(getVisitedNodesSize() == 0); this._searchOrder = Order.BREADTH_FIRST; return this; } /** * Cause nodes to be visited using {@literal DEPTH_FIRST} search {@link Order}. * Changing the search order after calling {@link #hasNext} or {@link #next} * will result in unpredictable behavior. * @see #useBreadthFirst * @see #searchOrder */ public final FactorGraphWalker useDepthFirst() { assert(getVisitedNodesSize() == 0); this._searchOrder = Order.DEPTH_FIRST; return this; } /** * Returns the search {@link Order} in which nodes will be visited. * The default value is {@literal BREADTH_FIRST}. * @see #useBreadthFirst * @see #useDepthFirst */ public final Order searchOrder() { return this._searchOrder; } /* * Iterator methods */ /** True if call to {@link #next} will return a non-null value. */ @Override public boolean hasNext() { if (this._nextNode != null) { return true; } if (this._portDeque.isEmpty()) { return false; } this._nextNode = this.next(); return this._nextNode != null; } /** * Returns the next node in the graph in the given search order. * Nodes that do not meet the {@link #maxSearchDepth} and * {@link #maxRelativeNestingDepth} constraints will not be included. * Returns null when there are no more nodes to visit. It is not * necessary to call {@link FactorGraphWalker#hasNext} before calling * this method. */ @Override public @Nullable INode next() { INode node = this._nextNode; if (node != null) { this._nextNode = null; return node; } switch (this._searchOrder) { case DEPTH_FIRST: node = this.nextDepthFirst(); break; case BREADTH_FIRST: node = this.nextBreadthFirst(); break; } return node; } /** Default implementation throws {@link UnsupportedOperationException} */ @Override public void remove() { throw new UnsupportedOperationException(); } /* * FactorGraphWalker methods */ /** * True if the walker detected any cycles in the graph when walking the * graph up to the current point. */ public final int getCycleCount() { return this._cycleCount; } /** * Returns the first node to be visited in the graph traversal. If * null, an arbitrary node will be selected from the graph. */ public final @Nullable INode getFirstNode() { return this._firstNode; } /** * Returns the root graph that is being traversed. Set by * constructor or {@link #init(FactorGraph)} method. */ public final @Nullable FactorGraph getRootGraph() { return this._rootGraph; } /** * Returns collection of nodes that were visited by the walker so far in the order in which * they were visited. * @see #getVisitedNodesSize */ public final Set<INode> getVisitedNodes() { this._visitedNodesWasExposed = true; return this._visitedNodes; } /** * Returns the number of unique nodes that have been visited by the walker so far. * @see #getVisitedNodes */ public final int getVisitedNodesSize() { return this._visitedNodes.size(); } /* * Private methods */ private @Nullable INode nextBreadthFirst() { INode node = null; Deque<IncomingPort> queue = this._portDeque; while (!queue.isEmpty()) { IncomingPort portIn = queue.removeFirst(); if (portIn == _depthChangeSentinel) { ++this._currentDepth; continue; } node = portIn.node; int relativeDepth = node.getDepthBelowAncestor(Objects.requireNonNull(this._rootGraph)); if (relativeDepth < 0) { // Skip nodes not descended from root. continue; } if (relativeDepth > this._maxRelativeNestingDepth) { // If node is too deep, replace with ancestor at appropriate depth. node = node.getAncestorAtHeight(relativeDepth - this._maxRelativeNestingDepth); } if (!this._visitedNodes.add(node)) { // Skip nodes that have already been visited but add to cycle count. ++ this._cycleCount; continue; } // List<? extends INode> siblingNodes = requireNonNull(node).getSiblings(); final int nSiblings = requireNonNull(node).getSiblingCount(); if (nSiblings > 0) { int newDepth = this._currentDepth + 1; if (newDepth <= this._maxSearchDepth) { if (newDepth > this._maxDepthSeen) { this._maxDepthSeen = newDepth; queue.add(_depthChangeSentinel); } for (int i = 0, size = nSiblings; i < size; ++i) { if (i != portIn.siblingNumber) { queue.add(new IncomingPort(node.getSibling(i), node.getReverseSiblingNumber(i))); } } } } return node; } return null; } private @Nullable INode nextDepthFirst() { INode node = null; // TODO implement using FactorGraphEdgeState instead of Ports Deque<IncomingPort> stack = this._portDeque; while (!stack.isEmpty()) { IncomingPort portIn = stack.pop(); if (portIn == _depthChangeSentinel) { --this._currentDepth; continue; } node = portIn.node; int relativeDepth = node.getDepthBelowAncestor(Objects.requireNonNull(this._rootGraph)); if (relativeDepth < 0) { // Skip nodes not descended from given root. continue; } if (relativeDepth > this._maxRelativeNestingDepth) { // If node is too deep, replace with ancestor at appropriate depth. node = node.getAncestorAtHeight(relativeDepth - this._maxRelativeNestingDepth); } if (!this._visitedNodes.add(node)) { // Skip nodes that have already been visited but add to cycle count. ++this._cycleCount; continue; } final int nPortsOut = requireNonNull(node).getSiblingCount(); if (nPortsOut > 0) { int newDepth = this._currentDepth + 1; if (newDepth <= this._maxSearchDepth) { stack.push(_depthChangeSentinel); this._currentDepth = newDepth; this._maxDepthSeen = Math.max(this._maxDepthSeen, this._currentDepth); for (int i = 0, size = nPortsOut; i < size; ++i) { if (i != portIn.siblingNumber) { stack.push(new IncomingPort(node.getSibling(i), node.getReverseSiblingNumber(i))); } } } } return node; } return null; } }