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
|
use Test::More tests => 1196;
# http://rt.cpan.org/NoAuth/Bug.html?id=1179
=head1 NAME
Test program for Graph.
=head2 SYNOPSIS
perl test_graph.pl [size (default 3 works well)]
=head2 DESCRIPTION
This program constructs size x size "square" directed and undirected
graphs, then tests various path-finding related methods:
TransitiveClosure_Floyd_Warshall, APSP_Floyd_Warshall, and SSSP (all
flavors).
You can think of each node as a cell in a matrix. In the directed
case, each node is has edges from its neighbors down and right (the
coordinates growing to down and right), eg, node 1,1 is has edges to
nodes 1,2, node 2,1, and node 2,2. In the undirected case, the down
left diagonal is present as well, eg, from node 1,1 to node 0,2. All
edges have unit weight.
This structure makes it easy to calculate the correct answers. For
example, the all-pairs-shortest-path in the directed case should have
an edge from every node to every other node that is further down or to
the right, and the weight should be equal to the maximun difference in
the coordinates of the nodes. Eg, the weight from node 1,1 to node
3,3 is 2 along the path 1,1 to 2,2 to 3,3.
=head1 AUTHOR
Nathan Goodman
=cut
use Graph;
use Graph::Directed;
use Graph::Undirected;
# set up square graphs
for my $size (0..3) {
print "# size = $size\n";
$g=Graph::Directed->new(compat02 => 1);
$h=Graph::Undirected->new(compat02 => 1);
$g=construct($g, $size);
$h=construct($h, $size);
test_graph($g, $size);
test_graph($h, $size);
test_tc($g, $size);
test_tc($h, $size);
test_apsp($g, $size);
test_apsp($h, $size);
#test_sssp($g,$size,'Dijkstra');
#test_sssp($h,$size,'Dijkstra');
#test_sssp($g,'Bellman_Ford');
#test_sssp($h,'Bellman_Ford');
#test_sssp($g,'DAG');
}
exit;
sub construct {
my($g, $size)=@_;
for (my $i=0;$i<$size;$i++) {
for (my $j=0;$j<$size;$j++) {
my $node=node($i,$j);
$g->add_vertex($node);
$g->add_weighted_edge(node($i-1,$j),1,$node) if $i>0;
$g->add_weighted_edge(node($i,$j-1),1,$node) if $j>0;
$g->add_weighted_edge(node($i-1,$j-1),1,$node) if $i>0 && $j>0; # down-right diagonal
$g->add_weighted_edge(node($i-1,$j+1),1,$node)
if $g->undirected && $i>0 && $j<$size; # down-left diagonal
}
}
return $g;
}
# check graph construction
# all nodes that are distance 1 apart in the rectangle
# should be connected by an edge of weight 1 in the graphs
sub test_graph {
my($g, $size)=@_;
print "# test_graph ",ref $g,"\n";
for (my $i1=0;$i1<$size;$i1++) {
for (my $j1=0;$j1<$size;$j1++) {
my $node1=node($i1,$j1);
for (my $i2=0;$i2<$size;$i2++) {
for (my $j2=0;$j2<$size;$j2++) {
my $node2=node($i2,$j2);
if (dist($g,$node1,$node2)==1) {
ok($g->has_edge($node1,$node2), "edge $node1-$node2");
my $weight=weight($g,$node1,$node2);
is($weight, 1, "edge weight on edge $node1-$node2");
} else {
ok(!$g->has_edge($node1, $node2), "extra edge $node1-$node2");
}}}}}}
# check transitive closure
# all nodes that are distance 0 or more apart in the rectangle
# should be connected by an edge in the grapsh -- weights not used
sub test_tc {
my($g, $size)=@_;
print "# test_tc ",ref $g,"\n";
my $gt=$g->TransitiveClosure_Floyd_Warshall;
for (my $i1=0;$i1<$size;$i1++) {
for (my $j1=0;$j1<$size;$j1++) {
my $node1=node($i1,$j1);
for (my $i2=0;$i2<$size;$i2++) {
for (my $j2=0;$j2<$size;$j2++) {
my $node2=node($i2,$j2);
if (dist($gt,$node1,$node2)>=0) {
ok( $gt->has_edge($node1,$node2), "edge $node1-$node2");
} else {
ok( !$gt->has_edge($node1,$node2), "extra edge $node1-$node2");
}}}}}}
# check all pairs shortest path
# all nodes that are distance 0 or more apart in the rectangle
# should be connected by an edge in the graph with weight equal to distance
sub test_apsp {
my($g, $size)=@_;
print "# test_apsp ",ref $g,"\n";
my $gs=$g->APSP_Floyd_Warshall;
for (my $i1=0;$i1<$size;$i1++) {
for (my $j1=0;$j1<$size;$j1++) {
my $node1=node($i1,$j1);
for (my $i2=0;$i2<$size;$i2++) {
for (my $j2=0;$j2<$size;$j2++) {
my $node2=node($i2,$j2);
my $dist=dist($gs,$node1,$node2);
if ($dist>=0) {
ok($gs->has_edge($node1,$node2), "edge $node1-$node2");
my $weight=weight($gs,$node1,$node2);
is( $weight, $dist, "edge weight $node1-$node2" );
test_path($gs,$node1,$node2,path($gs,$node1,$node2),weight($gs,$node1,$node2));
} else {
ok( !$gs->has_edge($node1,$node2),
"extra edge $node1-$node2" );
}}}}}}
# check single source shortest path
# all nodes that are distance 0 or more apart in the rectangle
# should be connected by an edge in the graph with weight equal to distance
sub test_sssp {
my($g,$size,$alg)=@_;
print "# test_sssp $alg ",ref $g,"\n";
my $sssp=$g->can("SSSP_$alg");
for (my $i1=0;$i1<$size;$i1++) {
for (my $j1=0;$j1<$size;$j1++) {
my $node1=node($i1,$j1);
print "# --- source $node1\n";
my $gs=$g->$sssp($node1);
for (my $i2=0;$i2<$size;$i2++) {
for (my $j2=0;$j2<$size;$j2++) {
my $node2=node($i2,$j2);
my $dist=dist($g,$node1,$node2);
my $weight=weight($gs,$node2);
my $path=path($gs,$node2);
test_path($gs,$node1,$node2,$path,$weight);
if ($dist>0) {
ok( $weight > 0, "path weight $node1-$node2" );
is( $weight, $dist, "path weight $node1-$node2" );
} else {
is( $weight, 0, "path weight $node1-$node2" );
}}}}}}
sub test_path {
my($g,$s,$t,$path,$weight)=@_;
return if $s eq $t;
is( @$path - 1, $weight, "path $s-$t length does not equal weight");
if ($weight) {
# path may be reversed
my @path=@$path;
@path=reverse @path if $path[$#path] eq $s;
is( $path[0], $s, "path $s-$t should start at $s");
for (my $i=0;$i<$#path;$i++) {
is(dist($g,$path->[$i],$path->[$i+1]), 1,
"adjacent nodes in path $s-$t should be distance 1 apart");
}
}
}
sub node {
my($i,$j)=@_;
$j=$i unless defined $j;
"$i/$j";
}
sub weight {
my($g,$node1,$node2)=@_;
return $g->path_length($node1,$node2);
}
sub path {
my($g,$node1,$node2)=@_;
return [ $g->path_vertices($node1,$node2) ];
}
#distances in rectangular graphs with diagonal edges
sub dist {
my($g)=shift @_;
_dist($g->directed,@_);
}
sub _dist {
my($directed)=shift @_;
my($i1,$j1,$i2,$j2);
if (@_==2) {
# args are nodes
my($node1,$node2)=@_;
($i1,$j1)=split('/',$node1);
($i2,$j2)=split('/',$node2);
} else {
# args are indices
($i1,$j1,$i2,$j2)=@_;
}
if ($directed) {
return -1 unless $i1<=$i2 && $j1<=$j2;
return max($i2-$i1,$j2-$j1);
} else {
return max(abs $i2-$i1,abs $j2-$j1);
}
}
sub min {
if ($#_==0) {@_=@{$_[0]} if 'ARRAY' eq ref $_[0];}
return undef unless @_;
if ($#_==1) {my($x,$y)=@_; return ($x<=$y?$x:$y);}
my $min=shift @_;
map {$min=$_ if $_<$min} @_;
$min;
}
sub max {
if ($#_==0) {@_=@{$_[0]} if 'ARRAY' eq ref $_[0];}
return undef unless @_;
if ($#_==1) {my($x,$y)=@_; return ($x>=$y?$x:$y);}
my $max=shift @_;
map {$max=$_ if $_>$max} @_;
$max;
}
|