001package org.biopax.paxtools.query.algorithm;
002
003import org.biopax.paxtools.query.model.Edge;
004import org.biopax.paxtools.query.model.GraphObject;
005import org.biopax.paxtools.query.model.Node;
006
007import java.util.ArrayList;
008import java.util.Collection;
009import java.util.Set;
010
011/**
012 * When an algorithm searches for a paths between multiple source nodes, or from a source to a
013 * target node (source and targets may overlap), sometimes there comes paths from and to the same
014 * node, i.e cycles. When we want to avoid these cases this class can be used to remove those
015 * cycles in the result set.
016 *
017 * @author Ozgun Babur
018 */
019public class CycleBreaker extends BFS
020{
021        /**
022         * The result set to search for cycles.
023         */
024        Set<GraphObject> result;
025
026        /**
027         * Source and (if exists) target nodes.
028         */
029        Set<Node> ST;
030
031        /**
032         * Constructor with the objects in the result, source and target nodes, and search limit.
033         * @param result Result set to search in
034         * @param ST Source and target nodes
035         * @param limit Search limit
036         */
037        public CycleBreaker(Set<GraphObject> result, Set<Node> ST, int limit)
038        {
039                this.result = result;
040                this.ST = ST;
041                this.limit = limit;
042        }
043
044        /**
045         * Run the algorithm.
046         */
047        public void breakCycles()
048        {
049                for (GraphObject go : new ArrayList<GraphObject>(result))
050                {
051                        if (go instanceof Node)
052                        {
053                                Node node = (Node) go;
054
055                                for (Edge edge : node.getDownstream())
056                                {
057                                        if (result.contains(edge) && !isSafe(node, edge))
058                                        {
059                                                result.remove(edge);
060                                        }
061                                }
062                        }
063                }
064        }
065
066        /**
067         * Checks whether an edge is on an unwanted cycle.
068         * @param node Node that the edge is bound
069         * @param edge The edge to check
070         * @return True if no cycle is detected, false otherwise
071         */
072        public boolean isSafe(Node node, Edge edge)
073        {
074                initMaps();
075
076                setColor(node, BLACK);
077                setLabel(node, 0);
078                setLabel(edge, 0);
079
080                labelEquivRecursive(node, UPWARD, 0, false, false);
081                labelEquivRecursive(node, DOWNWARD, 0, false, false);
082
083                // Initialize dist and color of source set
084
085                Node neigh = edge.getTargetNode();
086
087                if (getColor(neigh) != WHITE) return false;
088
089                setColor(neigh, GRAY);
090                setLabel(neigh, 0);
091
092                queue.add(neigh);
093
094                labelEquivRecursive(neigh, UPWARD, 0, true, false);
095                labelEquivRecursive(neigh, DOWNWARD, 0, true, false);
096
097                // Process the queue
098
099                while (!queue.isEmpty())
100                {
101                        Node current = queue.remove(0);
102
103                        if (ST.contains(current)) return true;
104                        
105                        boolean safe = processNode2(current);
106
107                        if (safe) return true;
108
109                        // Current node is processed
110                        setColor(current, BLACK);
111                }
112
113                return false;
114        }
115
116        /**
117         * Continue the search from the node. 2 is added because a name clash in the parent class.
118         * @param current The node to traverse
119         * @return False if a cycle is detected
120         */
121        protected boolean processNode2(Node current)
122        {
123                return processEdges(current, current.getDownstream()) ||
124                        processEdges(current, current.getUpstream());
125        }
126
127        /**
128         * Continue evaluating the next edge.
129         * @param current Current node
130         * @param edges The edge to evaluate
131         * @return False if a cycle is detected
132         */
133        private boolean processEdges(Node current, Collection<Edge> edges)
134        {
135                for (Edge edge : edges)
136                {
137                        if (!result.contains(edge)) continue;
138                        
139                        // Label the edge considering direction of traversal and type of current node
140
141                        setLabel(edge, getLabel(current));
142
143                        // Get the other end of the edge
144                        Node neigh = edge.getSourceNode() == current ?
145                                edge.getTargetNode() : edge.getSourceNode();
146
147                        // Process the neighbor if not processed or not in queue
148
149                        if (getColor(neigh) == WHITE)
150                        {
151                                // Label the neighbor according to the search direction and node type
152
153                                if (neigh.isBreadthNode())
154                                {
155                                        setLabel(neigh, getLabel(current) + 1);
156                                }
157                                else
158                                {
159                                        setLabel(neigh, getLabel(edge));
160                                }
161
162                                // Check if we need to stop traversing the neighbor, enqueue otherwise
163
164                                if (getLabel(neigh) == limit || isEquivalentInTheSet(neigh, ST))
165                                {
166                                        return true;
167                                }
168
169                                setColor(neigh, GRAY);
170
171                                // Enqueue the node according to its type
172
173                                if (neigh.isBreadthNode())
174                                {
175                                        queue.addLast(neigh);
176                                }
177                                else
178                                {
179                                        // Non-breadth nodes are added in front of the queue
180                                        queue.addFirst(neigh);
181                                }
182
183                                labelEquivRecursive(neigh, UPWARD, getLabel(neigh), true, !neigh.isBreadthNode());
184                                labelEquivRecursive(neigh, DOWNWARD, getLabel(neigh), true, !neigh.isBreadthNode());
185                        }
186                }
187                return false;
188        }
189}