in reply to Data visualisation.

BrowserUk:

I think I have my code in fairly decent shape, functionality-wise. (It's still ugly right now, though.)

However, to get most of the points in your dataset located, I had to open up the tolerance to accept up to a 15% error. (It misses only 3 points at that setting.)

Do you have a way to check whether the coordinates look reasonable against your original data? I'm curious at how good the program works. I've still got some testing to do, but so far, it's been giving decent results with my test sets.

I've uglified the code a bit more to draw the coordinates in a bitmap so I could do a visual check with my original coordinate sets, and it comes out good on them (three different point sets).

Code in readmore tags...

#!/usr/bin/perl # # trv_slsmn_triangulate.pl <FName> # # Given the distance matrix, compute a set of 2D locations for the p +oints that yields # the specified distance map. # # Since any coordinates will do, we'll first set point A at (0,0), a +nd point B at (aDb,0). # Finally, we'll make sure the angle of point C is counterclockwise +from the line AB. # Obviously, we have to be sure that aDc+bDc > aDb, or it won't make + a triangle, so we # have to use a bit of care to select our first points. # use strict; use warnings; use Image::Magick; my $tolerance = 0.15; # 5% error my @Quadrants = ( [1, 1], [1, -1], [-1, 1], [-1, -1] ); # Prepare data source my $FName = shift; my $IFH; if (defined $FName) { open $IFH, '<', $FName; } else { $IFH = \*DATA; } # Read data my %D; my ($R, $C) = (0); my $max_dist = [ 0 ]; my @LOCs; while (<$IFH>) { my @t = split /\s+/, $_; $C=0; $LOCs[$R] = [ ]; while (@t) { my ($dist, $a, $b); $dist = shift @t; ($a,$b) = ($R>$C) ? ($C, $R) : ($R, $C); if ($a != $b) { if (exists $D{$a}{$b}) { die "Mismatch ($R,$C)=$dist, but ($C,$R)=$D{$a}{$b}\n" + if $dist != $D{$a}{$b}; } $D{$a}{$b} = $dist; if ($dist > $$max_dist[0]) { $max_dist = [ $dist, $a, $b ]; } } ++$C; } ++$R; } # OK, points A and B are the points in max_dist, set their initial loc +ations: my ($ptA, $ptB) = ($max_dist->[1], $max_dist->[2]); $LOCs[$ptA] = [0, 0]; $LOCs[$ptB] = [$max_dist->[0], 0]; print "Point A is $ptA (0, 0); point B is $ptB ($max_dist->[0], 0)\n"; my $dAB = dist($ptA, $ptB); # For point C, we'll find the point giving the triangle with the large +st area. # Presumably, that'll get us a point sufficiently far away from the AB + baseline # to give us the best ability to resolve the other points. $max_dist = [ 0 ]; for my $i (0 .. $#LOCs) { next if defined $LOCs[$i][0]; # skip A and B my ($dCA, $dCB) = (dist($i,$ptA), dist($i,$ptB) ); my $s = ($dCA+$dCB+$dAB)/2.0; my $dsq = sqrt( $s * ($s-$dCA) * ($s-$dCB) * ($s-$dAB) ); #print "$i: dCA=$dCA, dCB=$dCB, Area=$dsq"; if ($dsq > $max_dist->[0]) { $max_dist = [$dsq, $i]; #print " new best!"; } #print "\n"; } my $ptC = $max_dist->[1]; # Find position of c via law of cosines (CAB) and sin(arccos x)=sqrt(1 +-x**2) my ($dCA, $dCB) = (dist($ptC,$ptA), dist($ptC,$ptB)); print "dCA=$dCA, dAB=$dAB, dCB=$dCB\n"; my $cosCAB = ($dCA*$dCA + $dAB*$dAB - $dCB*$dCB) / (2*$dCA*$dAB); my $sinCAB = sqrt(1 - $cosCAB**2); $LOCs[$ptC] = [ $dCA * $cosCAB, $dCA * $sinCAB ]; print "Point C is $ptC ($LOCs[$ptC][0], $LOCs[$ptC][1])\n"; #dump_LOCs(); #dump_distance_map(); # remaining locations can be found by triangulation from the three kno +wn points for my $i (0 .. $#LOCs) { next if $i==$ptA or $i==$ptB or $i==$ptC; my ($dIA, $dIB, $dIC) = (dist($i,$ptA), dist($i,$ptB), dist($i,$pt +C)); if ($dIA+$dIB < $dAB) { print "$i: ($dIA, $dIB, $dIC) *** Fails triangle inequality ** +*\n"; next; } print "$i: ($dIA, $dIB, $dIC)\n"; my $cosIAB = ($dIA*$dIA + $dAB*$dAB - $dIB*$dIB) / (2*$dIA*$dAB); my $sinIAB = sqrt(1 - $cosIAB**2); my @best = (1000000); for my $q (@Quadrants) { my ($x_sign, $y_sign) = @$q; my ($x, $y) = ($dIA*$cosIAB*$x_sign, $dIA*$sinIAB*$y_sign); my $check = sqrt( ($LOCs[$ptC][0]-$x)**2 + ($LOCs[$ptC][1]-$y) +**2 ); my $err = abs($check-$dIC); my $err_pct = 100*$err / $dIC; if ($err < $dIC*$tolerance) { if ($err < $best[0]) { @best = ($err, $err_pct, $x, $y); } print "\t($x, $y) <case 1: $err, $err_pct>\n"; $LOCs[$i] = [$x, $y]; #last; } else { print "\t($x, $y) miss ($dIC, $err)\n"; } } $LOCs[$i] = [ $best[2], $best[3] ] if @best>1; } dump_LOCs(); my ($x_min, $x_max, $y_min, $y_max); ($x_min,$y_min) = ($x_max,$y_max) = @{$LOCs[0]}; for my $i (1 .. $#LOCs) { my ($x, $y) = @{$LOCs[$i]}; next if ! defined $y; $x_min = $x if $x_min>$x; $y_min = $y if $y_min>$y; $x_max = $x if $x_max<$x; $y_max = $y if $y_max<$y; } print "($x_min,$y_min) - ($x_max,$y_max)\n"; my ($x_offs, $y_offs, $x_scale, $y_scale); $x_scale = 1400 / ($x_max - $x_min); $y_scale = 900 / ($y_max - $y_min); $x_offs = 50 - $x_min; $y_offs = 50 - $y_min; if ($x_scale < $y_scale) { ($x_scale, $y_scale) = ($x_scale, $x_scale); } else { ($x_scale, $y_scale) = ($y_scale, $y_scale); } print "$x_scale . $y_scale; $x_offs, $y_offs\n"; for my $i (0 .. $#LOCs) { if (defined $LOCs[$i][0]) { my ($x,$y) = @{$LOCs[$i]}; my ($x1,$y1,$x2,$y2) = ( $x_scale*($x+$x_offs), 1000-$y_scale*($y+$y_offs) ); $LOCs[$i][2]=$x1; $LOCs[$i][3]=$y1; } } my $q = Image::Magick->new; $q->Set(size=>'1500x1000'); $q->Read('gradient:#000185-#0053f8'); my $pts="$LOCs[$ptA][2],$LOCs[$ptA][3] $LOCs[$ptB][2],$LOCs[$ptB][3]"; $q->Draw(stroke=>'white', fill=>'white', primitive=>'line', points=>$p +ts); $pts="$LOCs[$ptA][2],$LOCs[$ptA][3] $LOCs[$ptC][2],$LOCs[$ptC][3]"; $q->Draw(stroke=>'red', fill=>'red', primitive=>'line', points=>$pts); $pts="$LOCs[$ptB][2],$LOCs[$ptB][3] $LOCs[$ptC][2],$LOCs[$ptC][3]"; $q->Draw(stroke=>'green', fill=>'green', primitive=>'line', points=>$p +ts); for my $i (0 .. $#LOCs) { if (defined $LOCs[$i][0]) { my (undef, undef, $x,$y) = @{$LOCs[$i]}; my ($x1,$y1,$x2,$y2) = ($x-5, $y-5, $x+5, $y+5); my $fill = 'black'; $fill = 'red' if $i eq $ptA; $fill = 'green' if $i eq $ptB; $fill = 'yellow' if $i eq $ptC; my $pts = "$x1,$y1 $x2,$y2"; print "$i: $pts\n"; $q->Draw(stroke=>'white', fill=>$fill, primitive=>'rectangle', + points=>$pts); } } $q->Write('map.gif'); sub dist { my ($r, $c) = @_; return 0 if $r eq $c; if (defined($LOCs[$r][0]) and defined($LOCs[$c][0]) ) { return sqrt( ($LOCs[$r][0]-$LOCs[$c][0])**2 + ($LOCs[$r][1]-$L +OCs[$c][1])**2 ); } ($r, $c) = ($c, $r) if $c < $r; die "$r,$c entry DNE?\n" unless exists $D{$r}{$c}; $D{$r}{$c}; } sub dump_LOCs { for my $i (0 .. $#LOCs) { print dump_helper($i), ": "; if (defined($LOCs[$i][0])) { print "($LOCs[$i][0], $LOCs[$i][1])\n"; } else { print "undef\n"; } } } sub dump_helper { my $i = shift; return "<A>" if $i eq $ptA; return "<B>" if $i eq $ptB; return "<C>" if $i eq $ptC; return "<$i>"; } sub dump_distance_map { print " "; printf "%6s ", dump_helper($_) for 0 .. $#LOCs; print "\n"; for my $i (0 .. $#LOCs) { printf "%-4.4s", dump_helper($i); for my $j (0 .. $#LOCs) { my $d = dist($i,$j); printf "% 6u ", $d; } print "\n"; } } # Matrix shows distance from pt on left to dest column __DATA__ 0 3812 13902 8619 15811 5015 5230 9615 10624 13346 7575 6170 6 +812 9487 18135 8030 5409 17959 12822 17136 3267 12882 11223 11078 3812 0 11527 12431 15446 8057 4519 8761 14398 12569 3764 6668 + 10603 11117 14805 5154 8276 18175 9367 14840 7056 9698 13603 7 +266 13902 11527 0 13638 10220 18405 8675 4611 12993 11970 8798 822 +6 15087 16591 6859 6381 11223 7602 3236 3457 14535 8830 14748 +6655 8619 12431 13638 0 9965 5555 11256 11549 2157 10917 16194 9609 + 1926 7111 12565 14906 5737 9378 16868 11899 5402 15921 5765 19 +675 15811 15446 10220 9965 0 11122 18683 14599 7873 2940 13119 1779 +3 11057 6374 3517 14973 15565 3627 10307 7077 14478 6475 5155 1 +0235 5015 8057 18405 5555 11122 0 10109 13856 6744 9617 11348 10270 + 3811 4858 14594 12975 7186 13212 17301 17106 3881 12658 6210 14 +138 5230 4519 8675 11256 18683 10109 0 4584 13284 16923 6018 2299 + 10267 14709 15205 3741 5549 15863 8377 11981 6865 12543 16177 8 +600 9615 8761 4611 11549 14599 13856 4584 0 12503 16548 8240 3619 + 11888 18524 11444 4627 6948 11282 6102 7523 9992 12321 16782 8 +550 10624 14398 12993 2157 7873 6744 13284 12503 0 9231 18070 1140 +5 3813 6447 10427 16699 7735 7340 15982 10421 7492 14151 4374 1 +8092 13346 12569 11970 10917 2940 9617 16923 16548 9231 0 11052 1922 +0 11199 4874 5269 14093 16358 6543 10709 9458 13488 5008 5170 +9146 7575 3764 8798 16194 13119 11348 6018 8240 18070 11052 0 8220 + 14354 12490 11186 3614 11306 14452 5954 11533 10801 6649 14987 3 +508 6170 6668 8226 9609 17793 10270 2299 3619 11405 19220 8220 0 + 9124 15115 15059 5330 3975 14165 9100 10994 6469 14453 15268 10 +326 6812 10603 15087 1926 11057 3811 10267 11888 3813 11199 14354 9124 + 0 6645 14126 13999 5168 11153 17968 13808 3728 15782 6168 17 +749 9487 11117 16591 7111 6374 4858 14709 18524 6447 4874 12490 15115 + 6645 0 9754 15942 11588 9288 15400 13331 8652 9157 2621 12 +782 18135 14805 6859 12565 3517 14594 15205 11444 10427 5269 11186 1505 +9 14126 9754 0 11725 16484 3379 6883 4250 17835 5311 8643 +7729 8030 5154 6381 14906 14973 12975 3741 4627 16699 14093 3614 5330 + 13999 15942 11725 0 9173 13841 4874 9780 10451 9122 18555 5 +003 5409 8276 11223 5737 15565 7186 5549 6948 7735 16358 11306 3975 + 5168 11588 16484 9173 0 13413 12955 12642 3450 17931 11293 14 +142 17959 18175 7602 9378 3627 13212 15863 11282 7340 6543 14452 1416 +5 11153 9288 3379 13841 13413 0 9181 4145 14775 8581 7147 1 +0945 12822 9367 3236 16868 10307 17301 8377 6102 15982 10709 5954 910 +0 17968 15400 6883 4874 12955 9181 0 5593 15242 6278 15447 +3419 17136 14840 3457 11899 7077 17106 11981 7523 10421 9458 11533 1099 +4 13808 13331 4250 9780 12642 4145 5593 0 15914 8478 11291 +8453 3267 7056 14535 5402 14478 3881 6865 9992 7492 13488 10801 6469 + 3728 8652 17835 10451 3450 14775 15242 15914 0 15624 9326 14 +308 12882 9698 8830 15921 6475 12658 12543 12321 14151 5008 6649 1445 +3 15782 9157 5311 9122 17931 8581 6278 8478 15624 0 10157 +4139 11223 13603 14748 5765 5155 6210 16177 16782 4374 5170 14987 1526 +8 6168 2621 8643 18555 11293 7147 15447 11291 9326 10157 0 1 +4265 11078 7266 6655 19675 10235 14138 8600 8550 18092 9146 3508 1032 +6 17749 12782 7729 5003 14142 10945 3419 8453 14308 4139 14265 + 0

...roboticus

When your only tool is a hammer, all problems look like your thumb.

Replies are listed 'Best First'.
Re^2: Data visualisation.
by BrowserUk (Patriarch) on Jan 04, 2014 at 08:44 UTC
    Do you have a way to check whether the coordinates look reasonable against your original data? I'm curious at how good the program works.

    Sorry for the delay in getting back to you Roboticus, but I was caught up with my own efforts. I tried C&ping my dataset into your code but something broke and I couldn't immediately see what.

    I finally just got back to it and it was simply that you use split /\s+/ rather than split ' ' and my data had leading whitespace which screwed everything up. Fixed that, ran it and got:

    <0>: (600.804026845638, 199.30760478734) <A>: (0, 0) <2>: undef <3>: (628.038255033557, 206.128479872154) <4>: undef <5>: (443.404697986577, 203.804498977408) <6>: (518.110738255034, 242.374220792608) <7>: (479.277852348993, 226.258127473328) <8>: (551.842953020134, 59.1130713295962) <C>: (113.638255033557, 265.720430138385) <10>: (228.714765100671, 164.965318248851) <11>: (629.140939597315, 105.9513007122) <12>: (532.210067114094, 195.554198273552) <13>: undef <14>: (334.319463087248, 254.225287099954) <B>: (745, 0) <16>: (476.814093959732, 202.416204394214)

    Those undefs are the points you had trouble with I assume. Everyone has found problems with the dataset -- it comes from TSPLIB and there was never any guarantee that it was a plottable dataset; that's part of the reason for whating to try and visualise it.

    However, everyone seems to have problems with different points.

    I eventually gave up with the Law of Cosines method. Not only are there four quadrants that each point could be in, there are two points (+-y) that need to be considered. Instead I went the Circle-Circle intersection route, which proved to be far simpler.

    This animated gif shows the problem with the dataset quite nicely. The green arcs are the distances from B & P. The cyan arc is from the 3rd point (in this case the A point), which is used to decide (nearest wins) which of the two intersect points of the green arcs is chosen. It also highlights the accuracy or lack thereof of the correspondence between them.

    It shows that -- with A as the third point -- D is a particularly bad fit; but chose a different 3rd reference point and D might be spot on but some other previously good fit point becomes bad.

    Anyway, many thanks for your code -- you're first attempt was the cluebat I needed to get started. I've added my code below, but it is also very obfuscated with the image generation code. I need to separate the two, but it was very useful when coding.

    My code:

    #! perl -slw use strict; use Data::Dump qw[ pp ]; $Data::Dump::WIDTH = 200; use List::Util qw[ min max sum ]; use GD; use enum qw[ X Y ]; use constant PI => 3.1415926535897932384626433832795; my @N2A; @N2A[ 0 .. 25 ] = 'A'..'Z'; sub acos { atan2( sqrt( 1 - $_[0] * $_[0] ), $_[0] ) } sub asin { atan2( $_[0], sqrt( 1 - $_[0] * $_[0] ) ) } sub rgb2n{ local $^W; unpack 'N', pack 'CCCC', 0, @_ } my $BLACK = rgb2n( 0,0,0 ); my $RED = rgb2n( 255, 0, 0 ); my $GREEN = rgb2n( 0, 255, 0 ); my $BLUE = rgb2n( 0, 0, 255 ); my $YELLOW = rgb2n( 255, 255, 0 ); my $MAGENTA = rgb2n( 255, 0, 255 ); my $CYAN = rgb2n( 0, 255, 255 ); my $WHITE = rgb2n( 255,255,255 ); my( $xOrg, $yOrg, @pts, @dists ); sub plotPt{ my( $im, $pt, $label ) = @_; $im->filledArc( $pt->[X] +$xOrg, $pt->[Y] +$yOrg, 14, +14, 0, 360, $RED ); $im->string( gdSmallFont, $pt->[X]-1+$xOrg, $pt->[Y]-7+$yOrg, $lab +el, $BLACK ) } sub plotRoute{ my( $im, $pt1, $pt2 ) = @_; $im->line( $pt1->[X]+$xOrg, $pt1->[Y]+$yOrg, $pt2->[X]+$xOrg, $pt2 +->[Y]+$yOrg, $BLUE ); } sub plotArc { my( $im, $p1, $p2, $color ) = @_; $im->arc( $pts[ $p1 ][X]+$xOrg, $pts[ $p1 ][Y]+$yOrg, ( $dists[ $p1 ][ $p2 ]*2 )x2, 0,360, $color ); } @dists = map[ split ' ' ], <DATA>; shift @dists; shift @$_ for @dists; sub d{ $dists[ $_[0] ][ $_[1] ] } my $dMax = max( map max( @$_ ), @dists ); my $xMax = 200+$dMax; my $yMax = 40+sqrt( $dMax**2 - ( $dMax / 2 )**2 ) * 2; ( $xOrg, $yOrg ) = ( 100, $yMax / 2 ); my $im = GD::Image->new( $xMax, $yMax, 1 ); $im->fill( 0,0, $WHITE ); $im->line( $xOrg, 0, $xOrg, $yMax, $BLACK ); $im->line( 0, $yOrg, $xMax, $yOrg, $BLACK ); my( $p1, $p2 ) = map{ my $y = $_; map{ $dists[$y][$_] == $dMax ? ( $_, $y ) : () } 0 .. $#dists; } 0 .. $#dists; print "$p1, $p2"; $pts[ $p2 ] = [ 0, 0]; $pts[ $p1 ] = [ $dMax, 0 ]; $pts[ 0 ] = do { my( $d, $r, $R ) = ( $dMax, d( $p1, 0 ), d( $p2, 0 ) ); my $x = ( $d**2 - $r**2 + $R**2 ) / ( 2 * $d ); my $y = ( 1/$d * sqrt( (-$d+$r-$R)*(-$d-$r+$R)*(-$d+$r+$R)*($d+$r+ +$R) ) ) / 2; [ $x, $y ]; }; plotPt( $im, $pts[ 0 ], $N2A[ 0 ] ); plotRoute( $im, @pts[ $p1, $p2 ] ); plotPt( $im, $pts[ $p1 ], $N2A[ $p1 ] ); plotPt( $im, $pts[ $p2 ], $N2A[ $p2 ] ); my $ani = $im->gifanimbegin( 1, 10 ); for my $p ( 1 .. $#dists ) { next if $p == $p1 or $p == $p2 ; my( $d, $r, $R ) = ( $dMax, d( $p1, $p ), d( $p2, $p ) ); my $x = ( $d**2 - $r**2 + $R**2 ) / ( 2 * $d ); my $y = ( 1/$d * sqrt( (-$d+$r-$R)*(-$d-$r+$R)*(-$d+$r+$R)*($d+$r+ +$R) ) ) / 2; plotArc( $im, $p1, $p, $GREEN ); plotArc( $im, $p2, $p, $GREEN ); plotArc( $im, 0, $p, $CYAN ); my $checkD1 = sqrt( ( $pts[0][X] - $x )**2 + ( $pts[0][Y] - $y )** +2 ); my $checkD2 = sqrt( ( $pts[0][X] - $x )**2 + ( $pts[0][Y] + $y )** +2 ); printf "$N2A[ $p ]: %u $checkD1 $checkD2\n", d( 0, $p ); $pts[ $p ] = [ $x, ( abs( $checkD1 - d( 0, $p ) ) < abs( $checkD2 +- d( 0, $p ) ) ) ? $y : -$y ]; plotPt( $im, $pts[ $p ], $N2A[ $p ] ); $ani .= $im->gifanimadd( 1, 0, 0, 300, ); open PNG, '>:raw', "$0.png" or die $!; print PNG $im->png; close PNG; system "$0.png"; plotArc( $im, $p1, $p, $WHITE ); plotArc( $im, $p2, $p, $WHITE ); plotArc( $im, 0, $p, $WHITE ); } $ani .= $im->gifanimend(); open GIF, '>:raw', "$0.gif" or die $!; print GIF $ani; close GIF; system "$0.gif"; my @route = ( 0, 15, 11, 8, 3, 12, 6, 7, 5, 2, 10, 4, 1, 9, 14, 13, 16 + ); plotRoute( $im, @pts[ @route[ $_-1, $_ ] ] ) for 1..$#route; plotPt( $im, $pts[ $_ ], $N2A[ $_ ] ) for 0 .. $#pts; open PNG, '>:raw', "$0.png" or die $!; print PNG $im->png; close PNG; system "$0.png"; print ' ', join ' ' x 26, 'B' .. 'Q'; for my $i ( 0 .. $#dists ) { printf "$N2A[ $i ]: "; for my $j ( $i+1 .. $#dists ) { my $checkD1 = sqrt( ( $pts[$i][X] - $pts[$j][X] )**2 + ( $pts[ +$i][Y] - $pts[$j][Y] )**2 ); my $checkD2 = sqrt( ( $pts[$i][X] - $pts[$j][X] )**2 + ( $pts[ +$i][Y] + $pts[$j][Y] )**2 ); my $checkD3 = sqrt( ( $pts[$i][X] + $pts[$j][X] )**2 + ( $pts[ +$i][Y] - $pts[$j][Y] )**2 ); my $checkD4 = sqrt( ( $pts[$i][X] + $pts[$j][X] )**2 + ( $pts[ +$i][Y] + $pts[$j][Y] )**2 ); printf "%4u (%4.f %4.f %4.f %4.f) ", d( $i, $j ), $checkD1, $c +heckD2, $checkD3, $checkD4; } print ''; } __DATA__ A B C D E F G H I J K L M +N O P Q A: 0 633 257 91 412 150 80 134 259 505 353 324 70 21 +1 268 246 121 B: 633 0 390 661 227 488 572 530 555 289 282 638 567 46 +6 420 745 518 C: 257 390 0 228 169 112 196 154 372 262 110 437 191 7 +4 53 472 142 D: 91 661 228 0 383 120 77 105 175 476 324 240 27 18 +2 239 237 84 E: 412 227 169 383 0 267 351 309 338 196 61 421 346 24 +3 199 528 297 F: 150 488 112 120 267 0 63 34 264 360 208 329 83 10 +5 123 364 35 G: 80 572 196 77 351 63 0 29 232 444 292 297 47 15 +0 207 332 29 H: 134 530 154 105 309 34 29 0 249 402 250 314 68 10 +8 165 349 36 I: 259 555 372 175 338 264 232 249 0 495 352 95 189 32 +6 383 202 236 J: 505 289 262 476 196 360 444 402 495 0 154 578 439 33 +6 240 685 390 K: 353 282 110 324 61 208 292 250 352 154 0 435 287 18 +4 140 542 238 L: 324 638 437 240 421 329 297 314 95 578 435 0 254 39 +1 448 157 301 M: 70 567 191 27 346 83 47 68 189 439 287 254 0 14 +5 202 289 55 N: 211 466 74 182 243 105 150 108 326 336 184 391 145 +0 57 426 96 O: 268 420 53 239 199 123 207 165 383 240 140 448 202 5 +7 0 483 153 P: 246 745 472 237 528 364 332 349 202 685 542 157 289 42 +6 483 0 336 Q: 121 518 142 84 297 35 29 36 236 390 238 301 55 9 +6 153 336 0

    With the rise and rise of 'Social' network sites: 'Computers are making people easier to use everyday'
    Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
    "Science is about questioning the status quo. Questioning authority".
    In the absence of evidence, opinion is indistinguishable from prejudice.