001package org.biopax.paxtools.query.algorithm;
002
003import org.apache.commons.logging.Log;
004import org.apache.commons.logging.LogFactory;
005import org.biopax.paxtools.query.model.Edge;
006import org.biopax.paxtools.query.model.GraphObject;
007import org.biopax.paxtools.query.model.Node;
008
009import java.util.Collection;
010import java.util.HashMap;
011import java.util.LinkedList;
012import java.util.Map;
013import java.util.Set;
014
015/**
016 * Implements breadth-first search. Takes a set of source nodes, distance limit and labels nodes
017 * towards one direction, with their breadth distances.
018 *
019 * @author Ozgun Babur
020 * @author Merve Cakir
021 */
022public class BFS
023{
024        private static Log LOG = LogFactory.getLog(BFS.class); 
025        
026        /**
027         * Distance labels. Missing label interpreted as infinitive.
028         */
029        protected Map<GraphObject, Integer> dist;
030
031        /**
032         * Color labels. Missing color interpreted as white.
033         */
034        protected Map<GraphObject, Integer> colors;
035
036        /**
037         * BFS starts from source nodes. They get the label 0.
038         */
039        protected Set<Node> sourceSet;
040
041        /**
042         * BFS will not further traverse neighbors of any node in the stopSet.
043         */
044        protected Set<Node> stopSet;
045
046        /**
047         * Whether the direction is FORWARD, it is REVERSE otherwise.
048         */
049        protected Direction direction;
050
051        /**
052         * Stop distance.
053         */
054        protected int limit;
055
056        /**
057         * BFS queue.
058         */
059        protected LinkedList<Node> queue;
060
061        /**
062         * Constructor with all parameters.
063         * @param sourceSet Seed of BFS
064         * @param stopSet Nodes that won't be traversed
065         * @param direction Direction of the traversal
066         * @param limit Distance limit
067         */
068        public BFS(Set<Node> sourceSet, Set<Node> stopSet, Direction direction, int limit)
069        {
070                if (direction != Direction.UPSTREAM && direction != Direction.DOWNSTREAM)
071                        throw new IllegalArgumentException("Direction has to be either upstream or downstream");
072
073                this.sourceSet = sourceSet;
074                this.stopSet = stopSet;
075                this.direction = direction;
076                this.limit = limit;
077        }
078
079        /**
080         * Empty constructor for other possible uses.
081         */
082        public BFS()
083        {
084        }
085
086        /**
087         * Executes the algorithm.
088         * @return BFS tree
089         */
090        public Map<GraphObject, Integer> run()
091        {
092                initMaps();
093
094                // Add all source nodes to the queue if traversal is needed
095
096                if (limit > 0)
097                {
098                        queue.addAll(sourceSet);
099                }
100
101                // Initialize dist and color of source set
102
103                for (Node source : sourceSet)
104                {
105                        setLabel(source, 0);
106                        setColor(source, GRAY);
107
108                        labelEquivRecursive(source, UPWARD, 0, true, false);
109                        labelEquivRecursive(source, DOWNWARD, 0, true, false);
110                }
111
112                // Process the queue
113
114                while (!queue.isEmpty())
115                {
116                        Node current = queue.remove(0);
117
118                        processNode(current);
119
120                        // Current node is processed
121                        setColor(current, BLACK);
122                }
123
124                return dist;
125        }
126
127        /**
128         * Initializes maps used during query.
129         */
130        protected void initMaps()
131        {
132                // Initialize label, maps and queue
133
134                dist = new HashMap<GraphObject, Integer>();
135                colors = new HashMap<GraphObject, Integer>();
136                queue = new LinkedList<Node>();
137        }
138
139        /**
140         * Processes a node.
141         * @param current The current node
142         */
143        protected void processNode(Node current)
144        {
145                // Do not process the node if it is ubique
146
147                if (current.isUbique())
148                {
149                        setColor(current, BLACK);
150                        return;
151                }
152
153//              System.out.println("processing = " + current);
154
155                // Process edges towards the direction
156
157                for (Edge edge : direction == Direction.DOWNSTREAM ?
158                        current.getDownstream() : current.getUpstream())
159                {
160                        assert edge != null;
161
162                        // Label the edge considering direction of traversal and type of current node
163
164                        if (direction == Direction.DOWNSTREAM || !current.isBreadthNode())
165                        {
166                                setLabel(edge, getLabel(current));
167                        }
168                        else
169                        {
170                                setLabel(edge, getLabel(current) + 1);
171                        }
172
173                        // Get the other end of the edge
174                        Node neigh = direction == Direction.DOWNSTREAM ?
175                                edge.getTargetNode() : edge.getSourceNode();
176
177                        assert neigh != null;
178
179                        // Decide neighbor label according to the search direction and node type
180                        int dist = getLabel(edge);
181                        if (neigh.isBreadthNode() && direction == Direction.DOWNSTREAM) dist++;
182
183                        // Check if we need to stop traversing the neighbor, enqueue otherwise
184                        boolean further = (stopSet == null || !isEquivalentInTheSet(neigh, stopSet)) &&
185                                (!neigh.isBreadthNode() || dist < limit) && !neigh.isUbique();
186
187                        // Process the neighbor if not processed or not in queue
188
189                        if (getColor(neigh) == WHITE)
190                        {
191                                // Label the neighbor
192                                setLabel(neigh, dist);
193
194                                if (further)
195                                {
196                                        setColor(neigh, GRAY);
197
198                                        // Enqueue the node according to its type
199
200                                        if (neigh.isBreadthNode())
201                                        {
202                                                queue.addLast(neigh);
203                                        }
204                                        else
205                                        {
206                                                // Non-breadth nodes are added in front of the queue
207                                                queue.addFirst(neigh);
208                                        }
209                                }
210                                else
211                                {
212                                        // If we do not want to traverse this neighbor, we paint it black
213                                        setColor(neigh, BLACK);
214                                }
215                        }
216
217
218                        labelEquivRecursive(neigh, UPWARD, getLabel(neigh), further, !neigh.isBreadthNode());
219                        labelEquivRecursive(neigh, DOWNWARD, getLabel(neigh), further, !neigh.isBreadthNode());
220                }
221        }
222
223        /**
224         * Labels equivalent nodes recursively.
225         * @param node Node to label equivalents
226         * @param up Traversing direction. Up means towards parents, if false then towards children
227         * @param dist The label
228         * @param enqueue Whether to enqueue equivalents
229         * @param head Where to enqueue. Head or tail.
230         */
231        protected void labelEquivRecursive(Node node, boolean up, int dist,
232                boolean enqueue, boolean head)
233        {
234                if(node == null) {
235                        LOG.error("labelEquivRecursive: null (Node)");
236                        return;
237                }
238                
239                for (Node equiv : up ? node.getUpperEquivalent() : node.getLowerEquivalent())
240                {
241                        if (getColor(equiv) == WHITE)
242                        {
243                                setLabel(equiv, dist);
244
245                                if (enqueue)
246                                {
247                                        setColor(equiv, GRAY);
248
249                                        if (head) queue.addFirst(equiv);
250                                        else queue.add(equiv);
251                                }
252                                else
253                                {
254                                        setColor(equiv, BLACK);
255                                }
256                        }
257
258                        labelEquivRecursive(equiv, up, dist, enqueue, head);
259                }
260        }
261
262        /**
263         * Checks if an equivalent of the given node is in the set.
264         * @param node Node to check equivalents
265         * @param set Node set
266         * @return true if an equivalent is in the set
267         */
268        protected boolean isEquivalentInTheSet(Node node, Set<Node> set)
269        {
270                return set.contains(node) ||
271                        isEquivalentInTheSet(node, UPWARD, set) || isEquivalentInTheSet(node, DOWNWARD, set); 
272        }
273
274        /**
275         * Checks if an equivalent of the given node is in the set.
276         * @param node Node to check equivalents
277         * @param direction Direction to go to get equivalents
278         * @param set Node set
279         * @return true if an equivalent is in the set
280         */
281        protected boolean isEquivalentInTheSet(Node node, boolean direction, Set<Node> set)
282        {
283                for (Node eq : direction == UPWARD ? node.getUpperEquivalent() : node.getLowerEquivalent())
284                {
285                        if (set.contains(eq)) return true;
286                        boolean isIn = isEquivalentInTheSet(eq, direction, set);
287                        if (isIn) return true;
288                }
289                return false;
290        }
291
292        /**
293         * Gets color tag of the node
294         * @param node Node to get color tag
295         * @return color tag
296         */
297        protected int getColor(Node node)
298        {
299                if (!colors.containsKey(node))
300                {
301                        // Absence of color is interpreted as white
302                        return WHITE;
303                }
304                else
305                {
306                        return colors.get(node);
307                }
308        }
309
310        /**
311         * Sets color tag
312         * @param node node to set color tag
313         * @param color the color tag
314         */
315        protected void setColor(Node node, int color)
316        {
317                colors.put(node, color);
318        }
319
320        /**
321         * Gets the distance label of the object.
322         * @param go object to get the distance
323         * @return the distance label
324         */
325        public int getLabel(GraphObject go)
326        {
327                if (!dist.containsKey(go))
328                {
329                        // Absence of label is interpreted as infinite
330                        return Integer.MAX_VALUE-(limit*2);
331                }
332                else
333                {
334                        return dist.get(go);
335                }
336        }
337
338        /**
339         * Sets the distance label.
340         * @param go object to set the distance label
341         * @param label the distance label
342         */
343        protected void setLabel(GraphObject go, int label)
344        {
345//              System.out.println("Labeling(" + label + "): " + go);
346                dist.put(go, label);
347        }
348
349        /**
350         * Forward traversal direction.
351         */
352        public static final boolean FORWARD = true;
353
354        /**
355         * Backward traversal direction.
356         */
357        public static final boolean BACKWARD = false;
358
359        /**
360         * Forward traversal direction.
361         */
362        public static final boolean UPWARD = true;
363
364        /**
365         * Backward traversal direction.
366         */
367        public static final boolean DOWNWARD = false;
368
369        /**
370         * Color white indicates the node is not processed.
371         */
372        public static final int WHITE = 0;
373
374        /**
375         * Color gray indicates that the node is in queue waiting to be procecessed.
376         */
377        public static final int GRAY = 1;
378
379        /**
380         * Color black indicates that the node was processed.
381         */
382        public static final int BLACK = 2;
383}