Package org.openbel.framework.api

Source Code of org.openbel.framework.api.BasicPathFinder$SetStack

/**
* Copyright (C) 2012-2013 Selventa, Inc.
*
* This file is part of the OpenBEL Framework.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* The OpenBEL Framework is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
* License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with the OpenBEL Framework. If not, see <http://www.gnu.org/licenses/>.
*
* Additional Terms under LGPL v3:
*
* This license does not authorize you and you are prohibited from using the
* name, trademarks, service marks, logos or similar indicia of Selventa, Inc.,
* or, in the discretion of other licensors or authors of the program, the
* name, trademarks, service marks, logos or similar indicia of such authors or
* licensors, in any marketing or advertising materials relating to your
* distribution of the program or any covered product. This restriction does
* not waive or limit your obligation to keep intact all copyright notices set
* forth in the program as delivered to you.
*
* If you distribute the program in whole or in part, or any modified version
* of the program, and you assume contractual liability to the recipient with
* respect to the program or modified version, then you will indemnify the
* authors and licensors of the program for any liabilities that these
* contractual assumptions directly impose on those licensors and authors.
*/
package org.openbel.framework.api;

import static org.openbel.framework.api.EdgeDirectionType.BOTH;
import static org.openbel.framework.common.BELUtilities.sizedHashSet;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Stack;

import org.openbel.framework.api.Kam.KamEdge;
import org.openbel.framework.api.Kam.KamNode;
import org.openbel.framework.common.InvalidArgument;

/**
* Implements a basic {@link PathFinder} implementation with a max depth
* constraint.
*
* <p>
* The max search depth constraint default is defined at
* {@link BasicPathFinder#DEFAULT_MAX_SEARCH_DEPTH}.  To override this
* constraints create with {@link BasicPathFinder#BasicPathFinder(int)}.
* </p>
*
* @author Anthony Bargnesi {@code <abargnesi@selventa.com>}
*/
public class BasicPathFinder implements PathFinder {
    /**
     * Defines the default max search depth constraint to a depth of {@value}.
     */
    public final static int DEFAULT_MAX_SEARCH_DEPTH = 4;

    /**
     * Holds the KAM network to path find over.
     */
    private final Kam kam;

    /**
     * Holds the max search depth to use when path finding.
     */
    private final int maxSearchDepth;

    /**
     * Constructs the path finder using the default max search depth defined
     * in {@link BasicPathFinder#DEFAULT_MAX_SEARCH_DEPTH}.
     *
     * @param kam {@link Kam}, the KAM network to path find over, which cannot
     * be null
     */
    public BasicPathFinder(final Kam kam) {
        this.kam = kam;
        this.maxSearchDepth = DEFAULT_MAX_SEARCH_DEPTH;
    }

    /**
     * Constructs the path finder using the max search depth constraint defined
     * in the value <tt>maxSearchDepth</tt>.  This value must be greater than
     * zero.
     *
     * @param kam {@link Kam}, the KAM network to path find over, which cannot
     * be null
     * @param maxSearchDepth <tt>int</tt> the max search depth constraint to
     * use when path finding, which must be greater than zero
     */
    public BasicPathFinder(final Kam kam, final int maxSearchDepth) {
        this.kam = kam;
        this.maxSearchDepth = maxSearchDepth;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public SimplePath[] findPaths(KamNode source, KamNode target) {
        if (source == null) {
            throw new InvalidArgument("Source kam node cannot be null.");
        }

        if (target == null) {
            throw new InvalidArgument("Target kam node cannot be null.");
        }

        if (!kam.contains(source)) {
            throw new InvalidArgument("Source does not exist in KAM.");
        }

        if (!kam.contains(target)) {
            throw new InvalidArgument("Target does not exist in KAM.");
        }

        final Set<KamNode> targets = sizedHashSet(1);
        targets.add(target);

        List<SimplePath> pathsFound = runDepthFirstSearch(kam, source, targets);
        return pathsFound.toArray(new SimplePath[pathsFound.size()]);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public SimplePath[] findPaths(KamNode[] sources, KamNode[] targets) {
        if (sources == null || sources.length == 0) {
            throw new InvalidArgument(
                    "Source kam nodes cannot be null or empty.");
        }

        if (targets == null || targets.length == 0) {
            throw new InvalidArgument(
                    "Target kam nodes cannot be null or empty.");
        }

        for (final KamNode source : sources) {
            if (!kam.contains(source)) {
                throw new InvalidArgument("Source does not exist in KAM.");
            }
        }

        final Set<KamNode> targetSet = new HashSet<KamNode>(targets.length);
        for (final KamNode target : targets) {
            if (!kam.contains(target)) {
                throw new InvalidArgument("Target does not exist in KAM.");
            }

            targetSet.add(target);
        }

        final List<SimplePath> pathsFound = new ArrayList<SimplePath>();
        for (final KamNode source : sources) {
            if (!kam.contains(source)) {
                throw new InvalidArgument("Source does not exist in KAM.");
            }

            pathsFound.addAll(runDepthFirstSearch(kam, source, targetSet));
        }

        return pathsFound.toArray(new SimplePath[pathsFound.size()]);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public SimplePath[] interconnect(KamNode[] sources) {
        if (sources == null || sources.length < 2) {
            throw new InvalidArgument(
                    "Source kam nodes cannot be null and must contain at least two source nodes.");
        }

        // build out target set, check that each node is in the KAM
        final Set<KamNode> targetSet = new HashSet<KamNode>(sources.length);
        for (int i = 0; i < sources.length; i++) {
            final KamNode source = sources[i];

            if (!kam.contains(source)) {
                throw new InvalidArgument("Source does not exist in KAM.");
            }

            targetSet.add(source);
        }

        final List<SimplePath> pathsFound = new ArrayList<SimplePath>();
        for (final KamNode source : sources) {
            // remove source from target before search to prevent search the same
            // paths twice in the bidirectional search
            targetSet.remove(source);
            pathsFound.addAll(runDepthFirstSearch(kam, source, targetSet));
        }
        return pathsFound.toArray(new SimplePath[pathsFound.size()]);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public SimplePath[] scan(KamNode source) {
        if (source == null) {
            throw new InvalidArgument("Source kam node cannot be null.");
        }

        if (!kam.contains(source)) {
            throw new InvalidArgument("Source does not exist in KAM.");
        }

        final List<SimplePath> pathsFound = runDepthFirstScan(kam, source);
        return pathsFound.toArray(new SimplePath[pathsFound.size()]);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public SimplePath[] scan(KamNode[] sources) {
        if (sources == null || sources.length == 0) {
            throw new InvalidArgument(
                    "Source kam nodes cannot be null or empty.");
        }

        for (final KamNode source : sources) {
            if (!kam.contains(source)) {
                throw new InvalidArgument("Source does not exist in KAM.");
            }
        }

        final List<SimplePath> pathsFound = new ArrayList<SimplePath>();
        for (final KamNode source : sources) {
            pathsFound.addAll(runDepthFirstScan(kam, source));
        }
        return pathsFound.toArray(new SimplePath[pathsFound.size()]);
    }

    /**
     * Initializes and executes the depth-first search from {@link KamNode}
     * source to a {@link Set} of {@link KamNode} targets.
     *
     * @param kam {@link Kam}, the kam to traverse
     * @param source {@link KamNode}, the source kam node
     * @param targets {@link Set} of {@link KamNode}, the target kam nodes
     * @return the resulting paths from this depth-first search
     */
    private List<SimplePath> runDepthFirstSearch(final Kam kam,
            final KamNode source,
            final Set<KamNode> targets) {
        final SetStack<KamNode> nodeStack = new SetStack<KamNode>();
        nodeStack.add(source);

        final SetStack<KamEdge> edgeStack = new SetStack<KamEdge>();
        final List<SimplePath> pathResults = new ArrayList<SimplePath>();

        int initialDepth = 0;
        runDepthFirstSearch(kam, source, source, targets, initialDepth,
                nodeStack,
                edgeStack, pathResults);
        return pathResults;
    }

    /**
     * Initializes and executes a depth-first scan from {@link KamNode} source
     * given a max search depth ({@link BasicPathFinder#maxSearchDepth}).
     *
     * @param kam {@link Kam}, the kam to traverse
     * @param source {@link KamNode}, the source kam node
     * @return the resulting paths from this depth-first scan
     */
    private List<SimplePath> runDepthFirstScan(final Kam kam,
            final KamNode source) {
        final SetStack<KamEdge> edgeStack = new SetStack<KamEdge>();
        final SetStack<KamNode> nodeStack = new SetStack<KamNode>();
        final List<SimplePath> pathResults = new ArrayList<SimplePath>();

        // push on start node
        nodeStack.push(source);

        int initialDepth = 0;
        runDepthFirstScan(kam, source, source, initialDepth, nodeStack,
                edgeStack,
                pathResults);

        return pathResults;
    }

    /**
     * Runs a recursive depth-first search from a {@link KamNode} in search of
     * the <tt>target</tt> node.  When a {@link SimplePath} is found to the
     * <tt>target</tt> the {@link Stack} of {@link KamEdge} is collected and
     * the algorithm continues.<br/><br/>
     * <p>
     * This depth-first search exhaustively walks the entire {@link Kam}
     * and finds all paths from <tt>source</tt> to <tt>target</tt>.
     * </p>
     *
     * @param kam {@link Kam}, the kam to traverse
     * @param cnode {@link KamNode} the current node to evaluate
     * @param source {@link KamNode} the source to search from
     * @param targets {@link Set} of {@link KamNode}, the targets to search to
     * @param nodeStack {@link Stack} of {@link KamNode} that holds the nodes
     * on the current path from the <tt>source</tt>
     * @param edgeStack {@link Stack} of {@link KamEdge} that holds the edges
     * on the current path from the <tt>source</tt>
     * @param pathResults the resulting paths from source to targets
     */
    private void runDepthFirstSearch(final Kam kam,
            final KamNode cnode,
            final KamNode source,
            final Set<KamNode> targets,
            int depth,
            final SetStack<KamNode> nodeStack,
            final SetStack<KamEdge> edgeStack,
            final List<SimplePath> pathResults) {

        depth += 1;

        if (depth > maxSearchDepth) {
            return;
        }

        // get adjacent edges
        final Set<KamEdge> edges = kam.getAdjacentEdges(cnode, BOTH);

        for (final KamEdge edge : edges) {
            if (pushEdge(edge, nodeStack, edgeStack)) {
                final KamNode edgeOppositeNode = nodeStack.peek();

                // we have found a path from source to target
                if (targets.contains(edgeOppositeNode)) {
                    final SimplePath newPath =
                            new SimplePath(kam, source, nodeStack.peek(),
                                    edgeStack.toStack());
                    pathResults.add(newPath);
                } else {
                    runDepthFirstSearch(kam, edgeOppositeNode, source, targets,
                            depth,
                            nodeStack, edgeStack, pathResults);
                }

                nodeStack.pop();
                edgeStack.pop();
            }
        }
    }

    /**
     * Runs a recursive depth-first scan from a {@link KamNode} source until a
     * max search depth ({@link BasicPathFinder#maxSearchDepth}) is reached.
     * When the max search depth is reached a {@link SimplePath} is added,
     * containing the {@link Stack} of {@link KamEdge}, and the algorithm continues.
     *
     * @param kam {@link Kam}, the kam to traverse
     * @param cnode {@link KamNode}, the current node to evaluate
     * @param source {@link KamNode}, the node to search from
     * @param depth <tt>int</tt>, the current depth of this scan recursion
     * @param nodeStack {@link Stack} of {@link KamNode}, the nodes on the
     * current scan from the <tt>source</tt>
     * @param edgeStack {@link Stack} of {@link KamEdge}, the edges on the
     * current scan from the <tt>source</tt>
     * @param pathResults the resulting paths scanned from source
     */
    private void runDepthFirstScan(final Kam kam,
            final KamNode cnode,
            final KamNode source,
            int depth,
            final SetStack<KamNode> nodeStack,
            final SetStack<KamEdge> edgeStack,
            final List<SimplePath> pathResults) {

        depth += 1;

        final Set<KamEdge> edges = kam.getAdjacentEdges(cnode, BOTH);
        for (final KamEdge edge : edges) {
            if (pushEdge(edge, nodeStack, edgeStack)) {

                if (depth == maxSearchDepth) {
                    final SimplePath newPath =
                            new SimplePath(kam, source, nodeStack.peek(),
                                    edgeStack.toStack());
                    pathResults.add(newPath);
                } else {
                    // continue depth first scan
                    runDepthFirstScan(kam, nodeStack.peek(), source, depth,
                            nodeStack,
                            edgeStack, pathResults);
                }

                edgeStack.pop();
                nodeStack.pop();
            } else if (endOfBranch(edgeStack, edge, edges.size())) {
                final SimplePath newPath =
                        new SimplePath(kam, source, nodeStack.peek(),
                                edgeStack.toStack());
                pathResults.add(newPath);
            }
        }
    }

    /**
     * Determines whether the end of a branch was found.  This can indicate
     * that a path should be captures up to the leaf node.
     *
     * @param edgeStack {@link Stack} of {@link KamEdge} that holds the edges
     * on the current path
     * @param edge {@link KamEdge}, the edge to evaluate
     * @param edgeCount <tt>int</tt>, the number of adjacent edges to the
     * last visited {@link KamNode}
     * @return <tt>true</tt> if this edge marks the end of a branch,
     * <tt>false</tt> otherwise if it does not
     */
    private boolean endOfBranch(final SetStack<KamEdge> edgeStack,
            KamEdge edge,
            int edgeCount) {
        if (edgeStack.contains(edge) && edgeCount == 1) {
            return true;
        }

        return false;
    }

    /**
     * Determines whether the edge can be traversed.  This implementation will
     * check if the {@link KamNode} or {@link KamEdge} have been visited to
     * ensure there are no cycles in the resulting paths.
     * <p>
     * If the edge can be travered it will be placed on the edge {@link Stack},
     * and the edge's unvisited node will be placed on the node {@link Stack}.
     * </p>
     *
     * @param edge {@link KamEdge}, the kam edge to evaluate
     * @param nodeStack {@link Stack} of {@link KamNode}, the nodes on the
     * current scan from the <tt>source</tt>
     * @param edgeStack {@link Stack} of {@link KamEdge}, the edges on the
     * current scan from the <tt>source</tt>
     * @return <tt>true</tt> if the edge will be traversed, <tt>false</tt> if
     * not
     */
    private boolean pushEdge(final KamEdge edge,
            final SetStack<KamNode> nodeStack,
            final SetStack<KamEdge> edgeStack) {

        if (edgeStack.contains(edge)) {
            return false;
        }

        final KamNode currentNode = nodeStack.peek();
        final KamNode edgeOppositeNode =
                (edge.getSourceNode() == currentNode ? edge
                        .getTargetNode() : edge.getSourceNode());

        if (nodeStack.contains(edgeOppositeNode)) {
            return false;
        }

        nodeStack.push(edgeOppositeNode);
        edgeStack.push(edge);
        return true;
    }

    /**
     * Data Structure with both (subsets of) Set and Stack APIs that
     * allows for O(1) addition and retrieval as well as O(1) contains. This leads
     * to an approximate 20% performance gain in tested pathfind operations.
     * <br>
     * The unique {@link KamElement} ID is used as the discriminator in an attempt
     * to avoid hashCode collisions. In debugging the full Kam however, this did not
     * lead to significant performance improvements.
     *
     * @author Steve Ungerer
     */
    private final static class SetStack<T extends KamElement> {
        private Stack<T> elements = new Stack<T>();
        private HashSet<Integer> set = new HashSet<Integer>();

        public boolean contains(T o) {
            return set.contains(o.getId());
        }

        public void add(T obj) {
            elements.add(obj);
            set.add(obj.getId());
        }

        public void push(T obj) {
            elements.push(obj);
            set.add(obj.getId());
        }

        public T peek() {
            return elements.peek();
        }

        public T pop() {
            T obj = elements.pop();
            set.remove(obj.getId());
            return obj;
        }

        public Stack<T> toStack() {
            return elements;
        }
    }
}
TOP

Related Classes of org.openbel.framework.api.BasicPathFinder$SetStack

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.