File Coverage

blib/lib/Algorithm/TravelingSalesman/BitonicTour.pm
Criterion Covered Total %
statement 128 128 100.0
branch 42 42 100.0
condition n/a
subroutine 25 25 100.0
pod 16 16 100.0
total 211 211 100.0


line stmt bran cond sub pod time code
1             package Algorithm::TravelingSalesman::BitonicTour;
2              
3 9     9   89427 use strict;
  9         20  
  9         351  
4 9     9   46 use warnings FATAL => 'all';
  9         19  
  9         407  
5 9     9   61 use base 'Class::Accessor::Fast';
  9         15  
  9         11463  
6 9     9   36231 use Carp 'croak';
  9         20  
  9         704  
7 9     9   555 use List::Util 'reduce';
  9         22  
  9         1218  
8 9     9   10508 use Params::Validate qw/ validate_pos SCALAR /;
  9         107525  
  9         930  
9 9     9   8420 use Regexp::Common;
  9         42879  
  9         50  
10              
11             our $VERSION = '0.05';
12              
13             __PACKAGE__->mk_accessors(qw/ _points _sorted_points _tour /);
14              
15             =head1 NAME
16              
17             Algorithm::TravelingSalesman::BitonicTour - solve the euclidean traveling-salesman problem with bitonic tours
18              
19             =head1 SYNOPSIS
20              
21             use Algorithm::TravelingSalesman::BitonicTour;
22             my $bt = Algorithm::TravelingSalesman::BitonicTour->new;
23             $bt->add_point($x1,$y1);
24             $bt->add_point($x2,$y2);
25             $bt->add_point($x3,$y3);
26             # ...add other points as needed...
27              
28             # get and print the solution
29             my ($len, @coords) = $bt->solve;
30             print "optimal path length: $len\n";
31             print "coordinates of optimal path:\n";
32             print " ($_->[0], $_->[1])\n" for @coords;
33              
34             =head1 THE PROBLEM
35              
36             From I, 2nd ed., T. H. Cormen, C. E. Leiserson, R.
37             Rivest, and C. Stein, MIT Press, 2001, problem 15-1, p. 364:
38              
39             =over 4
40              
41             The B is the problem of determining the
42             shortest closed tour that connects a given set of I points in the plane.
43             Figure 15.9(a) shows the solution to a 7-point problem. The general problem is
44             NP-complete, and its solution is therefore believed to require more than
45             polynomial time (see Chapter 34).
46              
47             J. L. Bentley has suggested that we simplify the problem by restricting our
48             attention to B, that is, tours that start at the leftmost point,
49             go strictly left to right to the rightmost point, and then go strictly right to
50             left back to the starting point. Figure 15.9(b) shows the shortest bitonic
51             tour of the same 7 points. In this case, a polynomial-time algorithm is
52             possible.
53              
54             Describe an I(n^2)-time algorithm for determining an optimal bitonic tour.
55             You may assume that no two points have the same I-coordinate. (I
56             Scan left to right, maintaining optimal possibilities for the two parts of the
57             tour.)
58              
59             =back
60              
61             From Wikipedia (L):
62              
63             =over 4
64              
65             In computational geometry, a B of a set of point sites in the
66             Euclidean plane is a closed polygonal chain that has each site as one of its
67             vertices, such that any vertical line crosses the chain at most twice.
68              
69             =back
70              
71             =head1 THE SOLUTION
72              
73             =head2 Conventions
74              
75             Points are numbered from left to right, starting with "0", then "1", and so on.
76             For convenience I call the rightmost point C.
77              
78             =head2 Key Insights Into the Problem
79              
80             =over 4
81              
82             =item 1. Focus on optimal B bitonic tours.
83              
84             B have endpoints (i,j) where C<< i < j < R >>, and
85             they are the building blocks of the optimal closed bitonic tour we're trying to
86             find.
87              
88             An open bitonic tour, optimal or not, has these properties:
89              
90             * it's bitonic (a vertical line crosses the tour at most twice)
91             * it's open (it has endpoints), which we call "i" and "j" (i < j)
92             * all points to the left of "j" are visited by the tour
93             * points i and j are endpoints (connected to exactly one edge)
94             * all other points in the tour are connected to two edges
95              
96             For a given set of points there may be many open bitonic tours with endpoints
97             (i,j), but we are only interested in the I open tour--the tour with
98             the shortest length. Let's call this tour B>.
99              
100             =item 2. Grok the (slightly) messy recurrence relation.
101              
102             A concrete example helps clarify this. Assume we know the optimal tour lengths
103             for all (i,j) to the right of point C<5>:
104              
105             T(0,1)
106             T(0,2) T(1,2)
107             T(0,3) T(1,3) T(2,3)
108             T(0,4) T(1,4) T(2,4) T(3,4)
109              
110             From this information, we can find C, C, ... C.
111              
112             =over 4
113              
114             =item B>
115              
116             From our definition, C must have endpoints C<0> and C<5>, and must also
117             include all intermediate points C<1>-C<4>. This means C is composed of
118             points C<0> through C<5> in order. This is also the union of C and the
119             line segment C<4>-C<5>.
120              
121             =item B>
122              
123             C has endpoints at C<1> and C<5>, and visits all points to the left of
124             C<5>. To preserve the bitonicity of C, the only possibility for the
125             tour is the union of C and line segment C<4>-C<5>. I have included a
126             short proof by contradiction of this in the source code.
127              
128             =begin comment
129              
130             Proof by contradiction:
131              
132             1. Assume the following:
133             * T(1,5) is an optimal open bitonic tour having endpoints 1 and 5.
134             * Points 4 and 5 are not adjacent in the tour, i.e. the final segment in
135             the tour joins points "P" and 5, where "P" is to the left of point 4.
136             2. Since T(1,5) is an optimal open bitonic tour, point 4 is included in the
137             middle of the tour, not at an endpoint, and is connected to two edges.
138             3. Since 4 is not connected to 5, both its edges connect to points to its
139             left.
140             4. Combined with the segment from 5 to P, a vertical line immediately to the
141             left of point 4 must cross at least three line segments, thus our proposed
142             T(1,5) is not bitonic.
143              
144             Thus points 4 and 5 must be adjacent in tour T(1,5), so the tour must be the
145             optimal tour from 1 to 4 (more convenently called "T(1,4)"), plus the segment
146             from 4 to 5.
147              
148             =end comment
149              
150             =item B>
151              
152             Our logic for finding C applies to these cases as well.
153              
154             =item B>
155              
156             Tour C breaks the pattern seen in C through C, because
157             the leftmost point (point 4) must be an endpoint in the tour. Via proof by
158             contradiction similar to the above, our choices for constructing C are:
159              
160             T(0,4) + line segment from 0 to 5
161             T(1,4) + line segment from 1 to 5
162             T(2,4) + line segment from 2 to 5
163             T(3,4) + line segment from 3 to 5
164              
165             All of them meet our bitonicity requirements, so we choose the shortest of
166             these for C.
167              
168             =back
169              
170             To summarize the recurrence, and using function C to calculate the
171             distance between points:
172              
173             =over 5
174              
175             =item if i < j-1:
176              
177             C<< T(i,j) = T(i,j-1) + delta(j-1,j) >>
178              
179             =item if i == j-1:
180              
181             C<< T(i,j) = min{ T(k,i) + delta(k,j) } >>, for all k < i
182              
183             =back
184              
185             =item 3. The base case.
186              
187             The open tour C has to be the line segment from 0 to 1.
188              
189             =back
190              
191             =head2 Dynamic Programming
192              
193             This problem exhibits the classic features suggesting a dynamic programming
194             solution: I and I.
195              
196             =head3 Overlapping Subproblems
197              
198             The construction of tour C depends on knowledge of tours to the left of
199             C:
200              
201             to construct: one must know:
202             ------------- --------------
203             T(0,5) T(0,4)
204             T(1,5) T(1,4)
205             T(2,5) T(2,4)
206             T(3,5) T(3,4)
207             T(4,5) T(0,4), T(1,4), T(2,4), T(3,4)
208              
209             We also see that a given optimal tour is used I in constructing
210             longer tours. For example, C is used in the construction of both
211             C and C.
212              
213             =head3 Optimal Substructure
214              
215             As shown in the above analysis, constructing a given optimal tour depends on
216             knowledge of shorter "included" optimal tours; suboptimal tours are irrelevant.
217              
218             =head1 EXERCISES
219              
220             These exercises may clarify the above analysis.
221              
222             =over 4
223              
224             =item Exercise 1.
225              
226             Consider the parallelogram ((0,0), (1,1), (2,0), (3,1)).
227              
228             a. Draw it on graph paper.
229             b. Label points "0" through "3"
230             c. Draw t[0,1]. Calculate its length.
231             d. Draw t[0,2] and t[1,2]. Calculate their lengths.
232             e. Draw t[0,3], t[1,3], and t[2,3]. Calculate their lengths.
233             f. What is the optimal bitonic tour?
234             g. Draw the suboptimal bitonic tour.
235             h. Why does the above algorithm find the optimal tour,
236             and not the suboptimal tour?
237              
238             =item Exercise 2.
239              
240             Repeat Exercise 1 with pentagon ((0,2), (1,0), (2,3), (3,0), (4,2)).
241              
242             =back
243              
244             =head1 METHODS
245              
246             =head2 $class->new()
247              
248             Constructs a new solution object.
249              
250             Example:
251              
252             my $ts = Algorithm::TravelingSalesman::BitonicTour->new;
253              
254             =cut
255              
256             sub new {
257 25     25 1 23049 my $class = shift;
258 25         131 my $self = bless { _tour => {}, _points => {} }, $class;
259 25         68 return $self;
260             }
261              
262             =head2 $ts->add_point($x,$y)
263              
264             Adds a point at position (C<$x>, C<$y>) to be included in the solution. Method
265             C checks to make sure that no two points have the same
266             I-coordinate. This method will C with a descriptive error message
267             if anything goes wrong.
268              
269             Example:
270              
271             # add point at position (x=2, y=3) to the problem
272             $ts->add_point(2,3);
273              
274             =cut
275              
276             sub add_point {
277 247     247 1 9106 my $self = shift;
278 247         1060 my $valid = { type => SCALAR, regexp => $RE{num}{real} };
279 247         12533 my ($x, $y) = validate_pos(@_, ($valid) x 2);
280 247 100       964 if (exists $self->_points->{$x}) {
281 4         34 my $py = $self->_points->{$x};
282 4         73 croak "FAIL: point ($x,$y) duplicates previous point ($x,$py)";
283             }
284             else {
285 243         2162 $self->_sorted_points(undef); # clear any previous cache of sorted points
286 243         1499 $self->_points->{$x} = $y;
287 243         2700 return [$x, $y];
288             }
289             }
290              
291             =head2 $ts->N()
292              
293             Returns the number of points that have been added to the object (mnemonic:
294             Bumber).
295              
296             Example:
297              
298             print "I have %d points.\n", $ts->N;
299              
300             =cut
301              
302             sub N {
303 281854     281854 1 354079 my $self = shift;
304 281854         271918 return scalar keys %{ $self->_points };
  281854         674829  
305             }
306              
307             =head2 $ts->R()
308              
309             Returns the index of the rightmost point that has been added to the object
310             (mnemonic: Bightmost). This is always one less than C<< $ts->N >>.
311              
312             =cut
313              
314             sub R {
315 468     468 1 4278 my $self = shift;
316 468 100       797 die 'Problem has no rightmost point (N < 1)' if $self->N < 1;
317 467         2695 return $self->N - 1;
318             }
319              
320              
321             =head2 $ts->sorted_points()
322              
323             Returns an array of points sorted by increasing I-coordinate. The first
324             ("zeroI") array element returned is thus the leftmost point in the problem.
325              
326             Each point is represented as an arrayref containing (x,y) coordinates. The
327             sorted array of points is cached, but the cache is cleared by each call to
328             C.
329              
330             Example:
331              
332             my $ts = Algorithm::TravelingSalesman::BitonicTour->new;
333             $ts->add_point(1,1);
334             $ts->add_point(0,0);
335             $ts->add_point(2,0);
336             my @sorted = $ts->sorted_points;
337             # @sorted contains ([0,0], [1,1], [2,0])
338              
339             =cut
340              
341             sub sorted_points {
342 80353     80353 1 93397 my $self = shift;
343 80353 100       185571 unless ($self->_sorted_points) {
344 27         145 my @x = sort { $a <=> $b } keys %{ $self->_points };
  1322         1584  
  27         71  
345 27         187 my @p = map [ $_, $self->_points->{$_} ], @x;
346 27         913 $self->_sorted_points(\@p);
347             }
348 80353         422341 return @{ $self->_sorted_points };
  80353         172126  
349             }
350              
351             =head2 coord($n)
352              
353             Returns an array containing the coordinates of point C<$n>.
354              
355             Examples:
356              
357             my ($x0, $y0) = $ts->coord(0); # coords of leftmost point
358             my ($x1, $y1) = $ts->coord(1); # coords of next point
359             # ...
360             my ($xn, $yn) = $ts->coord(-1); # coords of rightmost point--CLEVER!
361              
362             =cut
363              
364             sub coord {
365 80348     80348 1 106349 my ($self, $n) = @_;
366 80348         91929 return @{ ($self->sorted_points)[$n] };
  80348         138112  
367             }
368              
369             =head2 $ts->solve()
370              
371             Solves the problem as configured. Returns a list containing the length of the
372             minimum tour, plus the coordinates of the points in the tour in traversal
373             order.
374              
375             Example:
376              
377             my ($length, @points) = $ts->solve();
378             print "length: $length\n";
379             for (@points) {
380             my ($x,$y) = @$_;
381             print "($x,$y)\n";
382             }
383              
384             =cut
385              
386             sub solve {
387 23     23 1 764 my $self = shift;
388 23         29 my ($length, @points);
389 23 100       59 if ($self->N < 1) {
    100          
390 1         28 die "ERROR: you need to add some points!";
391             }
392             elsif ($self->N == 1) {
393 10         76 ($length, @points) = (0,0);
394             }
395             else {
396 12         76 ($length, @points) = $self->optimal_closed_tour;
397             }
398 22         65 my @coords = map { [ $self->coord($_) ] } @points;
  247         5352  
399 22         339 return ($length, @coords);
400             }
401              
402             =head2 $ts->optimal_closed_tour
403              
404             Find the length of the optimal complete (closed) bitonic tour. This is done by
405             choosing the shortest tour from the set of all possible complete tours.
406              
407             A possible closed tour is composed of a partial tour with rightmost point C
408             as one of its endpoints plus the final return segment from R to the other
409             endpoint of the tour.
410              
411             T(0,R) + delta(0,R)
412             T(1,R) + delta(1,R)
413             ...
414             T(i,R) + delta(i,R)
415             ...
416             T(R-1,R) + delta(R-1,R)
417              
418             =cut
419              
420             sub optimal_closed_tour {
421 12     12 1 16 my $self = shift;
422 12         38 $self->populate_open_tours;
423 12         89 my $R = $self->R;
424 213         457 my @tours = map {
425 12         67 my $cost = $self->tour_length($_,$self->R) + $self->delta($_,$self->R);
426 213         538 my @points = ($self->tour_points($_,$R), $_);
427 213         14637 [ $cost, @points ];
428             } 0 .. $self->R - 1;
429 12 100   201   112 my $tour = reduce { $a->[0] < $b->[0] ? $a : $b } @tours;
  201         353  
430 12         821 return @$tour;
431             }
432              
433             =head2 $ts->populate_open_tours
434              
435             Populates internal data structure C describing all possible
436             optimal open tour costs and paths for this problem configuration.
437              
438             =cut
439              
440             sub populate_open_tours {
441 13     13 1 1083 my $self = shift;
442              
443             # Base case: tour(0,1) has to be the segment (0,1). It would be nice if
444             # the loop indices handled this case correctly, but they don't.
445 13         39 $self->tour_length(0, 1, $self->delta(0,1) );
446 13         93 $self->tour_points(0, 1, 0, 1);
447              
448             # find optimal tours for all points to the left of 2, 3, ... up to R
449 13         90 foreach my $k (2 .. $self->R) {
450              
451             # for each point "i" to the left of "k", find (and save) the optimal
452             # open bitonic tour from "i" to "k".
453 203         2658 foreach my $i (0 .. $k - 1) {
454 20109         200315 my ($len, @points) = $self->optimal_open_tour($i,$k);
455 20109         116524 $self->tour_length($i, $k, $len);
456 20109         206925 $self->tour_points($i, $k, @points);
457             }
458             }
459             }
460              
461             =head2 $ts->optimal_open_tour($i,$j)
462              
463             Determines the optimal open tour from point C<$i> to point C<$j>, based on the
464             values of previously calculated optimal tours to the left of C<$j>.
465              
466             As discussed above, there are two distinct cases for this calculation: when C<<
467             $i == $j - 1 >> and when C<< $i < $j - 1 >>.
468              
469             # determine the length of and points in the tour
470             # starting at point 20 and ending at point 25
471             my ($length,@points) = $ts->optimal_open_tour(20,25);
472              
473             =cut
474              
475             sub optimal_open_tour {
476 20113     20113 1 40048 my ($self, $i, $j) = @_;
477 20113         31637 local $" = q(,);
478              
479             # we want $i to be strictly less than $j (we can actually be more lax with
480             # the inputs, but this stricture halves our storage needs)
481 20113 100       39568 croak "ERROR: bad call, optimal_open_tour(@_)" unless $i < $j;
482              
483             # if $i and $j are adjacent, many valid bitonic tours (i => x => j) are
484             # possible; choose the shortest one.
485 20112 100       42254 return $self->optimal_open_tour_adjacent($i, $j) if $i + 1 == $j;
486              
487             # if $i and $j are NOT adjacent, then only one bitonic tour (i => j-1 => j)
488             # is possible.
489 19908 100       55776 return $self->optimal_open_tour_nonadjacent($i, $j) if $i + 1 < $j;
490              
491 1         28 croak "ERROR: bad call, optimal_open_tour(@_)";
492             }
493              
494             =head2 $obj->optimal_open_tour_adjacent($i,$j)
495              
496             Uses information about optimal open tours to the left of <$j> to find the
497             optimal tour with endpoints (C<$i>, C<$j>).
498              
499             This method handles the case where C<$i> and C<$j> are adjacent, i.e. C<< $i =
500             $j - 1 >>. In this case there are many possible bitonic tours, all going from
501             C<$i> to "C<$x>" to C<$j>. All points C<$x> in the range C<(0 .. $i - 1)> are
502             examined to find the optimal tour.
503              
504             =cut
505              
506             sub optimal_open_tour_adjacent {
507 204     204 1 367 my ($self, $i, $j) = @_;
508 19907         27201 my @tours = map {
509 204         2510 my $x = $_;
510 19907         36644 my $len = $self->tour_length($x,$i) + $self->delta($x,$j);
511 19907         46616 my @path = reverse($j, $self->tour_points($x, $i) );
512 19907         843537 [ $len, @path ];
513             } 0 .. $i - 1;
514 204 100   19703   5467 my $min_tour = reduce { $a->[0] < $b->[0] ? $a : $b } @tours;
  19703         36890  
515 204         65090 return @$min_tour;
516             }
517              
518             =head2 $obj->optimal_open_tour_nonadjacent($i,$j)
519              
520             Uses information about optimal open tours to the left of <$j> to find the
521             optimal tour with endpoints (C<$i>, C<$j>).
522              
523             This method handles the case where C<$i> and C<$j> are not adjacent, i.e. C<<
524             $i < $j - 1 >>. In this case there is only one bitonic tour possible, going
525             from C<$i> to C<$j-1> to C<$j>.
526              
527             =cut
528              
529             sub optimal_open_tour_nonadjacent {
530 19907     19907 1 27264 my ($self, $i, $j) = @_;
531 19907         41607 my $len = $self->tour_length($i, $j - 1) + $self->delta($j - 1,$j);
532 19907         64970 my @points = ($self->tour_points($i, $j - 1), $j);
533 19907         897318 return($len, @points);
534             }
535              
536              
537             =head2 $b->tour($i,$j)
538              
539             Returns the data structure associated with the optimal open bitonic tour with
540             endpoints (C<$i>, C<$j>).
541              
542             =cut
543              
544             sub tour {
545 280868     280868 1 401386 my ($self, $i, $j) = @_;
546 280868 100       592456 croak "ERROR: tour($i,$j) ($i >= $j)"
547             unless $i < $j;
548 280867 100       516236 croak "ERROR: tour($i,$j,...) ($j >= @{[ $self->N ]})"
  1         12  
549             unless $j < $self->N;
550 280866 100       1932973 $self->_tour->{$i}{$j} = [] unless $self->_tour->{$i}{$j};
551 280866         2013032 return $self->_tour->{$i}{$j};
552             }
553              
554             =head2 $b->tour_length($i, $j, [$len])
555              
556             Returns the length of the optimal open bitonic tour with endpoints (C<$i>,
557             C<$j>). If C<$len> is specified, set the length to C<$len>.
558              
559             =cut
560              
561             sub tour_length {
562 60158     60158 1 94125 my $self = shift;
563 60158         65800 my $i = shift;
564 60158         57680 my $j = shift;
565 60158 100       118567 if (@_) {
566 20123 100       42591 croak "ERROR: tour_length($i,$j,$_[0]) has length <= 0 ($_[0])"
567             unless $_[0] > 0;
568 20122         36141 $self->tour($i,$j)->[0] = $_[0];
569             }
570 60157 100       202774 if (exists $self->tour($i,$j)->[0]) {
571 60155         393776 return $self->tour($i,$j)->[0];
572             }
573             else {
574 1         37 croak "Don't know the length of tour($i,$j)";
575             }
576             }
577              
578             =head2 $b->tour_points($i, $j, [@points])
579              
580             Returns an array containing the indices of the points in the optimal open
581             bitonic tour with endpoints (C<$i>, C<$j>).
582              
583             If C<@points> is specified, set the endpoints to C<@points>.
584              
585             =cut
586              
587             sub tour_points {
588 60159     60159 1 85510 my $self = shift;
589 60159         63763 my $i = shift;
590 60159         64235 my $j = shift;
591 60159 100       135368 if (@_) {
592 20125 100       37044 croak "ERROR: tour_points($i,$j,@_) ($i != first point)"
593             unless $i == $_[0];
594 20124 100       38882 croak "ERROR: tour_points($i,$j,@_) ($j != last point)"
595             unless $j == $_[-1];
596 20123 100       39182 croak "ERROR: tour_points($i,$j,@_) (wrong number of points in @_)"
597             unless scalar(@_) == $j + 1;
598 20122         56025 $self->tour($i,$j)->[1] = \@_;
599             }
600 60156 100       200177 if (exists $self->tour($i,$j)->[1]) {
601 60155         325973 return @{ $self->tour($i,$j)->[1] };
  60155         109133  
602             }
603             else {
604 1         21 croak "Don't know the points for tour($i,$j)";
605             }
606             }
607              
608             =head2 $b->delta($p1,$p2);
609              
610             Returns the euclidean distance from point C<$p1> to point C<$p2>.
611              
612             Examples:
613              
614             # print the distance from the leftmost to the next point
615             print $b->delta(0,1);
616             # print the distance from the leftmost to the rightmost point
617             print $b->delta(0,-1);
618              
619             =cut
620              
621             sub delta {
622 40048     40048 1 262834 my ($self, $p1, $p2) = @_;
623 40048         72347 my ($x1, $y1) = $self->coord($p1);
624 40048         993006 my ($x2, $y2) = $self->coord($p2);
625 40048         971719 return sqrt((($x1-$x2)*($x1-$x2))+(($y1-$y2)*($y1-$y2)));
626             }
627              
628              
629             =head1 RESOURCES
630              
631             =over 4
632              
633             =item
634              
635             Cormen, Thomas H.; Leiserson, Charles E.; Rivest, Ronald L.; Stein, Clifford
636             (2001). Introduction to Algorithms, second edition, MIT Press and McGraw-Hill.
637             ISBN 978-0-262-53196-2.
638              
639             =item
640              
641             Bentley, Jon L. (1990), "Experiments on traveling salesman heuristics", Proc.
642             1st ACM-SIAM Symp. Discrete Algorithms (SODA), pp. 91-99,
643             L.
644              
645             =item
646              
647             L
648              
649             =item
650              
651             L
652              
653             =item
654              
655             L
656              
657             =item
658              
659             L
660              
661             =back
662              
663             =head1 AUTHOR
664              
665             John Trammell, C<< >>
666              
667             =head1 BUGS
668              
669             Please report any bugs or feature requests to C
670             rt.cpan.org>, or through the web interface at
671             L.
672             I will be notified, and then you'll automatically be notified of progress on
673             your bug as
674             I make changes.
675              
676             =head1 SUPPORT
677              
678             You can find documentation for this module with the perldoc command.
679              
680             perldoc Algorithm::TravelingSalesman::BitonicTour
681              
682             You can also look for information at:
683              
684             =over 4
685              
686             =item * RT: CPAN's request tracker
687              
688             L
689              
690             =item * AnnoCPAN: Annotated CPAN documentation
691              
692             L
693              
694             =item * CPAN Ratings
695              
696             L
697              
698             =item * Search CPAN
699              
700             L
701              
702             =back
703              
704             =head1 COPYRIGHT & LICENSE
705              
706             Copyright 2008 John Trammell, all rights reserved.
707              
708             This program is free software; you can redistribute it and/or modify it
709             under the same terms as Perl itself.
710              
711             =cut
712              
713             1;
714