File: ExtendPath.h

package info (click to toggle)
abyss 2.3.10-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 8,284 kB
  • sloc: cpp: 78,182; ansic: 6,512; makefile: 2,252; perl: 672; sh: 509; haskell: 412; python: 4
file content (722 lines) | stat: -rw-r--r-- 22,212 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722

#ifndef _EXTENDPATH_H_
#define _EXTENDPATH_H_

#include "Graph/Path.h"
#include "Common/UnorderedSet.h"
#include "Common/UnorderedMap.h"
#include "Common/Hash.h"
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/graph_concepts.hpp>
#include <cassert>
#include <cstdio>
#include <iostream>
#include <algorithm>

/**
 * Parameters for path extension.
 */
struct ExtendPathParams
{
	/* ignore branches shorter than or equal to this length */
	unsigned trimLen;
	/* longest branch of Bloom filter false positives */
	unsigned fpTrim;
	/* maximum length after extension */
	unsigned maxLen;
	/*
	 * if true, multiple incoming branches > trimLen
	 * will cause a path extension to halt
	 */
	bool lookBehind;
	/*
	 * If false, ignore incoming branches for the starting vertex.
	 * This is useful when when we are intentionally starting our
	 * path extension from a branching point.
	 */
    bool lookBehindStartVertex;

	/* constructor */
	ExtendPathParams() : trimLen(0), fpTrim(0), maxLen(NO_LIMIT), lookBehind(true),
		lookBehindStartVertex(true) {}
};

/**
 * The result of attempting to extend a path.
 */
enum PathExtensionResultCode {
	/** stopped path extension at a vertex with multiple incoming branches */
	ER_AMBI_IN,
	/** stopped path extension at a vertex with multiple outgoing branches */
    ER_AMBI_OUT,
	/** stopped path extension at a vertex with no outgoing branches */
	ER_DEAD_END,
	/** stopped path extension after completing a cycle */
	ER_CYCLE,
	/** stopped path extension at caller-specified length limit */
	ER_LENGTH_LIMIT,
};

/**
 * Translate path extension result code to a string.
 */
static inline const char* pathExtensionResultStr(PathExtensionResultCode result)
{
	switch(result) {
	case ER_AMBI_IN:
		return "AMBI_IN";
	case ER_AMBI_OUT:
		return "AMBI_OUT";
	case ER_DEAD_END:
		return "DEAD_END";
	case ER_CYCLE:
		return "CYCLE";
	case ER_LENGTH_LIMIT:
		return "LENGTH_LIMIT";
	default:
		assert(false);
	}
	return "";
}

/** length of path extension (in vertices) and reason for stopping */
typedef std::pair<unsigned, PathExtensionResultCode> PathExtensionResult;

/**
 * Return true if there is a path of at least depthLimit vertices
 * that extends from given vertex u, otherwise return false.
 * Implemented using a bounded depth first search.
 *
 * @param start starting vertex for traversal
 * @param dir direction for traversal (FORWARD or REVERSE)
 * @param depth depth of current vertex u
 * @param depthLimit maximum depth to probe
 * @param g graph to use for traversal
 * @param visited vertices that have already been visited by the DFS
 * @return true if at least one path with length >= len
 * extends from v in direction dir, false otherwise
 */
template <class Graph>
static inline bool lookAhead(
	const typename boost::graph_traits<Graph>::vertex_descriptor& u,
	Direction dir, unsigned depth, unsigned depthLimit,
	unordered_set< typename boost::graph_traits<Graph>::vertex_descriptor,
	hash<typename boost::graph_traits<Graph>::vertex_descriptor> >& visited, const Graph& g)
{
    typedef typename boost::graph_traits<Graph>::vertex_descriptor V;
    typedef typename boost::graph_traits<Graph>::out_edge_iterator OutEdgeIter;
    typedef typename boost::graph_traits<Graph>::in_edge_iterator InEdgeIter;

	OutEdgeIter oei, oei_end;
	InEdgeIter iei, iei_end;

	visited.insert(u);
	if (depth >= depthLimit)
		return true;

	if (dir == FORWARD) {
		for (boost::tie(oei, oei_end) = out_edges(u, g);
			oei != oei_end; ++oei) {
			const V& v = target(*oei, g);
			if (visited.find(v) == visited.end()) {
				if(lookAhead(v, dir, depth+1, depthLimit, visited, g))
					return true;
			}
		}
	} else {
		assert(dir == REVERSE);
		for (boost::tie(iei, iei_end) = in_edges(u, g);
			 iei != iei_end; ++iei) {
			const V& v = source(*iei, g);
			if (visited.find(v) == visited.end()) {
				if(lookAhead(v, dir, depth+1, depthLimit, visited, g))
					return true;
			}
		}
	}

	return false;
}

/**
 * Return true if there is a path of at least 'depth' vertices
 * that extends from given vertex v, otherwise return false.
 * Implemented using a bounded depth first search.
 *
 * @param start starting vertex for traversal
 * @param dir direction for traversal (FORWARD or REVERSE)
 * @param depth length of path to test for
 * @param g graph to use for traversal
 * @return true if at least one path with length >= len
 * extends from v in direction dir, false otherwise
 */
template <class Graph>
static inline bool lookAhead(
	const typename boost::graph_traits<Graph>::vertex_descriptor& start,
	Direction dir, unsigned depth, const Graph& g)
{
	typedef typename boost::graph_traits<Graph>::vertex_descriptor V;
	unordered_set< V, hash<V> > visited;
	return lookAhead(start, dir, 0, depth, visited, g);
}

/**
 * Return true if the given edge represents the beginning of a "true branch".
 *
 * A path is a true branch if it has length >= `trim` or terminates in a
 * branching node, where a branching node is (recursively) defined to be
 * a node with either >= 2 incoming true branches or >= 2 outgoing true branches.
 *
 * This method is similar to `lookAhead`, but it additionally changes traversal
 * direction when a dead-end is encountered.
 */
template <class Graph>
static inline bool trueBranch(
	const typename boost::graph_traits<Graph>::edge_descriptor& e,
	unsigned depth, Direction dir, const Graph& g, unsigned trim,
	unsigned fpTrim, unordered_set<typename boost::graph_traits<Graph>::vertex_descriptor>& visited)
{
	typedef typename boost::graph_traits<Graph>::vertex_descriptor V;

	typename boost::graph_traits<Graph>::out_edge_iterator oei, oei_end;
	typename boost::graph_traits<Graph>::in_edge_iterator iei, iei_end;

	const V& u = (dir == FORWARD) ? source(e, g) : target(e, g);
	const V& v = (dir == FORWARD) ? target(e, g) : source(e, g);

	/* branches with bubbles/cycles are considered true branches */
	if (visited.find(v) != visited.end())
		return true;

	if (depth >= trim)
		return true;

	visited.insert(v);

	if (dir == FORWARD) {
		for (boost::tie(oei, oei_end) = out_edges(v, g);
			oei != oei_end; ++oei) {
			if (trueBranch(*oei, depth+1, FORWARD, g, trim, fpTrim, visited))
				return true;
		}
		/*
		 * Note: The test for depth/lookAhead >= fpTrim before changing
		 * traversal direction is needed to deal with an X-shaped
		 * graph pattern that is frequently created by Bloom false positives.
		 * See the test for `trueBranch` in `ExtendPathTest.h` for an example.
		 */
		if (depth >= fpTrim || lookAhead(v, FORWARD, fpTrim, g)) {
			for (boost::tie(iei, iei_end) = in_edges(v, g);
				iei != iei_end; ++iei) {
				if (source(*iei, g) == u)
					continue;
				if (trueBranch(*iei, 0, REVERSE, g, trim, fpTrim, visited))
					return true;
			}
		}
	} else {
		assert(dir == REVERSE);
		for (boost::tie(iei, iei_end) = in_edges(v, g);
			iei != iei_end; ++iei) {
			if (trueBranch(*iei, depth+1, REVERSE, g, trim, fpTrim, visited))
				return true;
		}
		/*
		 * Note: The test for depth/lookAhead >= fpTrim before changing
		 * traversal direction is needed to deal with an X-shaped
		 * graph pattern that is frequently created by Bloom false positives.
		 * See the test for `trueBranch` in `ExtendPathTest.h` for an example.
		 */
		if (depth >= fpTrim || lookAhead(v, REVERSE, fpTrim, g)) {
			for (boost::tie(oei, oei_end) = out_edges(v, g);
				 oei != oei_end; ++oei) {
				if (target(*oei, g) == u)
					continue;
				if (trueBranch(*oei, 0, FORWARD, g, trim, fpTrim, visited))
					return true;
			}
		}
	}

	visited.erase(v);

	return false;
}

/**
 * Return true if the given edge represents the start of a "true branch".
 * Roughly speaking, a path is a true branch if it has length >= trim
 * or terminates in a branching node, where a branching node is (recursively)
 * defined to be a node with either >= 2 incoming true branches or >= outgoing
 * true branches.
 */
template <class Graph>
static inline bool trueBranch(
	const typename boost::graph_traits<Graph>::edge_descriptor& e,
	Direction dir, const Graph& g, unsigned trim, unsigned fpTrim)
{
	typedef typename boost::graph_traits<Graph>::vertex_descriptor V;
	unordered_set<V> visited;
	return trueBranch(e, 0, dir, g, trim, fpTrim, visited);
}

/**
 * Return neighbour vertices that begin branches that are longer than trimLen.
 *
 * @param u root vertex
 * @param dir direction for neighbours (FORWARD or REVERSE)
 * @param g graph
 * @param trimLen ignore all branches less than or equal to this length
 * @return std::vector of neighbour vertices that start branches that are
 * greater than trimLen vertices in length
 */
template <class BidirectionalGraph>
static inline std::vector<typename boost::graph_traits<BidirectionalGraph>::vertex_descriptor>
trueBranches(const typename boost::graph_traits<BidirectionalGraph>::vertex_descriptor& u,
	Direction dir, const BidirectionalGraph& g, unsigned trim, unsigned fpTrim)
{
	typedef BidirectionalGraph G;
	typedef boost::graph_traits<G> graph_traits;
	typedef typename graph_traits::vertex_descriptor V;

	typename graph_traits::out_edge_iterator oei, oei_end;
	typename graph_traits::in_edge_iterator iei, iei_end;

	std::vector<V> branchRoots;

	if (dir == FORWARD) {
		for (boost::tie(oei, oei_end) = out_edges(u, g);
			oei != oei_end; ++oei) {
			const V& v = target(*oei, g);
			if (trueBranch(*oei, dir, g, trim, fpTrim))
				branchRoots.push_back(v);
		}
	} else {
		assert(dir == REVERSE);
		for (boost::tie(iei, iei_end) = in_edges(u, g);
			iei != iei_end; ++iei) {
			const V& v = source(*iei, g);
			if (trueBranch(*iei, dir, g, trim, fpTrim)) {
				branchRoots.push_back(v);
			}
		}
	}

	return branchRoots;
}

/**
 * Return the unique predecessor/successor of a given vertex. In
 * cases where a predecessor/successor does not exist (i.e. a dead end)
 * or is not unique (i.e. a branching point), return an result code indicating
 * why a unique successor could not be returned.
 */
template <class Graph>
static inline std::pair<typename boost::graph_traits<Graph>::vertex_descriptor,
	PathExtensionResultCode>
successor(const typename boost::graph_traits<Graph>::vertex_descriptor& u,
	Direction dir, const Graph& g, unsigned trim, unsigned fpTrim)
{
	typedef typename boost::graph_traits<Graph>::vertex_descriptor V;
	typedef typename boost::graph_traits<Graph>::in_edge_iterator InEdgeIt;
	typedef typename boost::graph_traits<Graph>::out_edge_iterator OutEdgeIt;

	InEdgeIt iei, iei_end;
    OutEdgeIt oei, oei_end;

	/* assign u to suppress uninitialized warning */
	V v = u;
	for (unsigned i = 0; true; i = (i == 0) ? 1 : std::min(trim, 2*i))
	{
		unsigned trueBranches = 0;

		if (dir == FORWARD) {
			for (boost::tie(oei, oei_end) = out_edges(u, g); oei != oei_end; ++oei) {
				if (trueBranch(*oei, FORWARD, g, i, fpTrim)) {
					v = target(*oei, g);
					++trueBranches;
					if (trueBranches >= 2)
						break;
				}
			}
		} else {
			assert(dir == REVERSE);
			for (boost::tie(iei, iei_end) = in_edges(u, g); iei != iei_end; ++iei) {
				if (trueBranch(*iei, REVERSE, g, i, fpTrim)) {
					v = source(*iei, g);
					++trueBranches;
					if (trueBranches >= 2)
						break;
				}
			}
		}

		if (trueBranches == 0)
			return std::make_pair(v, ER_DEAD_END);
		else if (trueBranches == 1)
			return std::make_pair(v, ER_LENGTH_LIMIT);
		else if (i == trim)
			return std::make_pair(v, ER_AMBI_OUT);
	}

}

/**
 * Return true if the given vertex has more than one possible
 * predecessor/successor in the graph.
 */
template <class Graph>
static inline bool
ambiguous(const typename boost::graph_traits<Graph>::vertex_descriptor& u,
	Direction dir, const Graph& g, unsigned trim, unsigned fpTrim)
{
	return successor(u, dir, g, trim, fpTrim).second == ER_AMBI_OUT;
}

/**
 * Return true if the given vertex has more than one possible
 * predecessor/successor in the graph.
 *
 * @param expected always include this vertex in the set of possible
 * predecssors/successors, even if it is not a true branch.
 */
template <class Graph>
static inline bool
ambiguous(const typename boost::graph_traits<Graph>::vertex_descriptor& u,
	const typename boost::graph_traits<Graph>::vertex_descriptor& expected,
	Direction dir, const Graph& g, unsigned trim, unsigned fpTrim)
{
	typedef typename boost::graph_traits<Graph>::vertex_descriptor V;

	V v;
	PathExtensionResultCode result;

	boost::tie(v, result) = successor(u, dir, g, trim, fpTrim);

	return result == ER_AMBI_OUT || (result == ER_LENGTH_LIMIT && v != expected);
}

/**
 * Extend path by a single vertex, if there is a unique predecessor/successor
 * in the direction of extension.
 */
template <class Graph>
static inline PathExtensionResultCode
extendPathBySingleVertex(
	Path<typename boost::graph_traits<Graph>::vertex_descriptor>& path,
	Direction dir, const Graph& g, unsigned trim, unsigned fpTrim,
	bool lookBehind)
{
	assert(!path.empty());

	typedef typename boost::graph_traits<Graph>::vertex_descriptor V;

	V t, v;
	PathExtensionResultCode result;

	const V& head = (dir == FORWARD) ? path.back() : path.front();

	if (lookBehind) {

		Direction otherDir = (dir == FORWARD) ? REVERSE : FORWARD;
		boost::tie(t, result) = successor(head, otherDir, g, trim, fpTrim);

		if (result == ER_AMBI_OUT)
			return ER_AMBI_IN;

		/*
		 * Tricky: If our path was seeded on a tip, we want to stop the
		 * extension when we reconnect to the graph. We can detect that
		 * we are on tip if we reach a branching point where the predecessor
		 * vertex in the path does not match the expected predecessor `t`.
		 */
		if (path.size() > 1) {
			if (result == ER_DEAD_END) {
				/* no predecessors or all predecessors were tips */
				return ER_AMBI_IN;
			} else {
				/* check if we are on a tip */
				assert(result == ER_LENGTH_LIMIT);
				const V& prev = (dir == FORWARD) ?
					*(path.rbegin() + 1) : *(path.begin() + 1);
				if (prev != t)
					return ER_AMBI_IN;
			}
		}

	}

	boost::tie(v, result) = successor(head, dir, g, trim, fpTrim);
	if (result != ER_LENGTH_LIMIT)
		return result;

	if (dir == FORWARD)
		path.push_back(v);
	else
		path.push_front(v);

	return ER_LENGTH_LIMIT;
}

/**
 * Return the depth of the graph from the given source vertex,
 * i.e. the distance of the furthest node.  The depth is measured
 * by means of an exhaustive breadth first search.
 *
 * @param root starting vertex for traversal
 * @param dir direction for traversal (FORWARD or REVERSE)
 * @param g graph to use for traversal
 * @return the distance of the furthest vertex from root
 */
template <typename Graph>
static inline size_t depth(
	typename boost::graph_traits<Graph>::vertex_descriptor root,
	Direction dir, const Graph& g)
{
    typedef typename boost::graph_traits<Graph>::vertex_descriptor V;
    typedef typename boost::graph_traits<Graph>::out_edge_iterator OutEdgeIter;
    typedef typename boost::graph_traits<Graph>::in_edge_iterator InEdgeIter;

	OutEdgeIter oei, oei_end;
	InEdgeIter iei, iei_end;

	unordered_set<V, hash<V> > visited;
	typedef unordered_map<V, size_t> DepthMap;
	DepthMap depthMap;
	std::deque<V> q;

	q.push_back(root);

	visited.insert(root);
	std::pair<typename DepthMap::iterator, bool> inserted =
		depthMap.insert(std::make_pair(root, 0));
	assert(inserted.second);

	size_t maxDepth = 0;
	while (!q.empty()) {
		V& u = q.front();
		visited.insert(u);
		typename DepthMap::const_iterator it = depthMap.find(u);
		assert(it != depthMap.end());
		size_t depth = it->second;
		if (depth > maxDepth)
			maxDepth = depth;
		if (dir == FORWARD) {
			for (boost::tie(oei, oei_end) = out_edges(u, g);
				oei != oei_end; ++oei) {
				V v = target(*oei, g);
				if (visited.find(v) == visited.end()) {
					visited.insert(v);
					std::pair<typename DepthMap::iterator, bool> inserted =
						depthMap.insert(std::make_pair(v, depth+1));
					assert(inserted.second);
					q.push_back(v);
				}
			}
		} else {
			assert(dir == REVERSE);
			for (boost::tie(iei, iei_end) = in_edges(u, g);
				iei != iei_end; ++iei) {
				V v = source(*iei, g);
				if (visited.find(v) == visited.end()) {
					visited.insert(v);
					std::pair<typename DepthMap::iterator, bool> inserted =
						depthMap.insert(std::make_pair(v, depth+1));
					assert(inserted.second);
					q.push_back(v);
				}
			}
		}
		q.pop_front();
	}

	return maxDepth;
}

/**
 * Return the neighbor vertex corresponding to the longest branch.  If there
 * are no neighbour vertices, an assertion will be thrown. If there
 * is a tie between branch lengths, the "winning" branch is chosen arbitrarily.
 *
 * @param u root vertex
 * @param dir direction of branches to consider (FORWARD or REVERSE)
 * @param g the graph
 * @return the vertex at the head of the longest branch
 */
template <typename Graph>
inline static
std::pair<typename boost::graph_traits<Graph>::vertex_descriptor, bool>
longestBranch(const typename boost::graph_traits<Graph>::vertex_descriptor& u,
	Direction dir, const Graph& g)
{
	typedef typename boost::graph_traits<Graph>::vertex_descriptor V;
    typedef typename boost::graph_traits<Graph>::out_edge_iterator OutEdgeIter;
    typedef typename boost::graph_traits<Graph>::in_edge_iterator InEdgeIter;

	OutEdgeIter oei, oei_end;
	InEdgeIter iei, iei_end;
	size_t maxDepth = 0;
	unsigned degree = 0;
	bool tie = false;
	/* note: had to initialize to prevent compiler warnings */
	V longestBranch = u;
	if (dir == FORWARD) {
		for (boost::tie(oei, oei_end) = out_edges(u, g);
			 oei != oei_end; ++oei) {
			degree++;
			const V& v = target(*oei, g);
			size_t d = depth(v, dir, g) + 1;
			if (d > maxDepth) {
				maxDepth = d;
				longestBranch = v;
				tie = false;
			} else if (d == maxDepth && v < longestBranch) {
				/*
				 * make an arbitrary choice among branches
				 * of equal length using the vertex comparison
				 * operator (operator<).
				 */
				longestBranch = v;
				tie = true;
			}
		}
	} else {
		assert(dir == REVERSE);
		for (boost::tie(iei, iei_end) = in_edges(u, g);
			 iei != iei_end; ++iei) {
			degree++;
			const V& v = source(*iei, g);
			size_t d = depth(v, dir, g) + 1;
			if (d > maxDepth) {
				maxDepth = d;
				longestBranch = v;
				tie = false;
			} else if (d == maxDepth && v < longestBranch) {
				/*
				 * make an arbitrary choice among branches
				 * of equal length using the vertex comparison
				 * operator (operator<).
				 */
				longestBranch = v;
				tie = true;
			}
		}
	}
	assert(degree > 0);
	return std::make_pair(longestBranch, tie);
}

/**
 * Extend a path up to the next branching point in the graph.
 *
 * @param path path to extend (modified by this function)
 * @param dir direction to extend path (FORWARD or REVERSE)
 * @param g graph in which to perform the extension
 * @param visited set of previously visited vertices (used
 * to detect cycles in the de Bruijn graph)
 * @param params parameters controlling extension (e.g. trimLen)
 * @return PathExtensionResult: NO_EXTENSION, HIT_BRANCHING_POINT,
 * or EXTENDED.
 */
template <class BidirectionalGraph>
static inline PathExtensionResult extendPath(
	Path<typename boost::graph_traits<BidirectionalGraph>::vertex_descriptor>& path,
	Direction dir, const BidirectionalGraph& g,
	unordered_set<typename boost::graph_traits<BidirectionalGraph>::vertex_descriptor>& visited,
	const ExtendPathParams& params)
{
	typedef BidirectionalGraph G;
	typedef boost::graph_traits<G> graph_traits;
	typedef typename graph_traits::vertex_descriptor V;
	typename graph_traits::out_edge_iterator oei, oei_end;
	typename graph_traits::in_edge_iterator iei, iei_end;

	assert(path.size() > 0);
	size_t origPathLen = path.size();
	PathExtensionResultCode result = ER_DEAD_END;
	bool lookBehind = params.lookBehindStartVertex;

	assert(!path.empty());
	while (path.size() < params.maxLen)
	{
		result = extendPathBySingleVertex(path, dir, g,
			params.trimLen, params.fpTrim, lookBehind);

		if (result != ER_LENGTH_LIMIT)
			break;

		const V& head = (dir == FORWARD) ? path.back() : path.front();
		bool inserted;
		boost::tie(boost::tuples::ignore, inserted) = visited.insert(head);
		if (!inserted) {
			result = ER_CYCLE;
			if (dir == FORWARD)
				path.pop_back();
			else
				path.pop_front();
			break;
		}

		/* override `lookBehindStartVertex` after first extension */
		lookBehind = params.lookBehind;
	}

	if (params.maxLen != NO_LIMIT && path.size() == params.maxLen)
		result = ER_LENGTH_LIMIT;

	assert(path.size() >= origPathLen);
	unsigned extension = path.size() - origPathLen;

	/*
	 * Sanity check: If no length limit was imposed, we must have stopped
	 * the extension for some other reason (e.g. dead end)
	 */
	assert(params.maxLen != NO_LIMIT || result != ER_LENGTH_LIMIT);

	return std::make_pair(extension, result);
}

/**
 * Extend a path up to the next branching point in the graph.
 *
 * @param path path to extend (modified by this function)
 * @param dir direction to extend path (FORWARD or REVERSE)
 * @param g graph in which to perform the extension
 * @param params parameters controlling extension (e.g. trimLen)
 * @return PathExtensionResult: NO_EXTENSION, HIT_BRANCHING_POINT,
 * or EXTENDED.
 */
template <class BidirectionalGraph>
PathExtensionResult extendPath(
	Path<typename boost::graph_traits<BidirectionalGraph>::vertex_descriptor>& path,
	Direction dir, const BidirectionalGraph& g, const ExtendPathParams& params)
{
	typedef typename boost::graph_traits<BidirectionalGraph>::vertex_descriptor V;

	/* track visited nodes to avoid infinite traversal of cycles */
	unordered_set<V> visited;
	visited.insert(path.begin(), path.end());

	return extendPath(path, dir, g, visited, params);
}

/**
 * Extend a path up to the next branching point in the graph.
 *
 * @param path path to extend (modified by this function)
 * @param dir direction to extend path (FORWARD or REVERSE)
 * @param g graph in which to perform the extension
 * @return PathExtensionResult: NO_EXTENSION, HIT_BRANCHING_POINT,
 * or EXTENDED.
 */
template <class BidirectionalGraph>
PathExtensionResult extendPath(
	Path<typename boost::graph_traits<BidirectionalGraph>::vertex_descriptor>& path,
	Direction dir, const BidirectionalGraph& g)
{
	/* default extension params */
	ExtendPathParams params;
	return extendPath(path, dir, g, params);
}

#endif