File Coverage

blib/lib/Algorithm/ClusterPoints.pm
Criterion Covered Total %
statement 421 467 90.1
branch 118 174 67.8
condition 30 44 68.1
subroutine 28 34 82.3
pod 10 15 66.6
total 607 734 82.7


line stmt bran cond sub pod time code
1             package Algorithm::ClusterPoints;
2              
3             our $VERSION = '0.08';
4              
5 4     4   105570 use strict;
  4         10  
  4         181  
6 4     4   22 use warnings;
  4         7  
  4         161  
7              
8 4     4   20 use constant sqr2 => sqrt(2);
  4         11  
  4         397  
9 4     4   89 use constant isqr2 => 1/sqr2;
  4         6  
  4         187  
10              
11 4     4   3587 use POSIX qw(floor ceil);
  4         29571  
  4         28  
12 4     4   4886 use List::Util qw(max min);
  4         10  
  4         477  
13 4     4   23 use Carp;
  4         7  
  4         210  
14              
15 4     4   4447 use Data::Dumper;
  4         48750  
  4         3910  
16              
17             my $packing = ($] >= 5.008 ? 'w*' : 'V*');
18              
19             sub new {
20 15 50   15 1 65662 @_ & 1 or croak 'Usage: Algorithm::ClusterPoints->new(%options)';
21 15         108 my ($class, %opts) = @_;
22              
23 15         62 my $dimension = delete $opts{dimension};
24 15 50       61 $dimension = 2 unless defined $dimension;
25 15 50       66 $dimension < 1 and croak "positive dimension required";
26 15         44 my $radius = delete $opts{radius};
27 15         48 my $minimum_size = delete $opts{minimum_size};
28 15         32 my $ordered = delete $opts{ordered};
29 15         32 my $scales = delete $opts{scales};
30 15         37 my $dimensional_groups = delete $opts{dimensional_groups};
31              
32 15 50       66 %opts and croak "unknown constructor option(s) '".join("', '", sort keys %opts)."'";
33              
34 15         271 my $self = bless { radius => 1.0,
35             minimum_size => 1,
36             ordered => 0,
37             dimension => $dimension,
38             coords => [ map [], 1..$dimension ],
39             scales => [ map 1, 1..$dimension ],
40             dimensional_groups => [[0..$dimension-1]],
41             }, $class;
42              
43 15 50       108 $self->radius($radius) if defined $radius;
44 15 100       86 $self->minimum_size($minimum_size) if defined $minimum_size;
45 15 50       104 $self->ordered($ordered) if defined $ordered;
46 15 50       64 if (defined $scales) {
47 0 0       0 ref $scales eq 'ARRAY' or croak 'ARRAY reference expected for "scales" option';
48 0         0 $self->scales(@$scales);
49             }
50 15 100       98 if (defined $dimensional_groups) {
51 1 50       4 ref $dimensional_groups eq 'ARRAY' or croak 'ARRAY reference expected for "dimensional_groups" option';
52 1         6 $self->dimensional_groups(@$dimensional_groups);
53             }
54 15         55 $self;
55             }
56              
57             sub add_point {
58 162     162 1 594 my $self = shift;
59 162         236 my $dimension = $self->{dimension};
60 162 50       361 @_ % $dimension and croak 'coordinates list size is not a multiple of the problem dimension';
61 162         199 delete $self->{_clusters};
62 162         166 my $ix = @{$self->{coords}[0]};
  162         259  
63 162         326 while (@_) {
64 558         2537 push @$_, shift
65 558         547 for (@{$self->{coords}});
66             }
67 162         300 $ix;
68             }
69              
70             *add_points = \&add_point;
71              
72             sub point_coords {
73 0 0   0 1 0 @_ == 2 or croak 'Usage: $clp->point_coords($index)';
74 0         0 my ($self, $ix) = @_;
75 0         0 my $top = $#{$self->{coords}[0]};
  0         0  
76 0 0 0     0 croak "point index $ix out of range [0, $top]"
77             if ($ix > $top or $ix < 0);
78 0 0       0 return $self->{coords}[0][$ix]
79             if $self->{dimension} == 1;
80 0 0       0 wantarray or croak 'method requires list context';
81 0         0 map $_->[$ix], @{$self->{coords}};
  0         0  
82             }
83              
84 0     0 0 0 sub reset { delete shift->{_clusters} }
85              
86             sub radius {
87 51 50   51 1 88646 @_ > 2 and croak 'Usage: $clp->radius([$new_radius])';
88 51         81 my $self = shift;
89 51 50       131 if (@_) {
90 51         69 my $radius = shift;
91 51 50       165 $radius > 0.0 or croak 'positive radius required';
92 51         129 $self->{radius} = $radius;
93 51         301 delete $self->{_clusters};
94             }
95 51         125 $self->{radius};
96             }
97              
98             sub ordered {
99 15 50   15 1 47 @_ > 2 and croak 'Usage: $clp->ordered([$ordered])';
100 15         41 my $self = shift;
101 15 50       56 if (@_) {
102 15         110 $self->{ordered} = !!shift;
103 15         31 delete $self->{_clusters};
104             }
105 15         26 $self->{ordered};
106             }
107              
108             sub minimum_size {
109 12 50   12 1 36 @_ > 2 and croak 'Usage: $clp->minimum_size([$size])';
110 12         18 my $self = shift;
111 12 50       40 if (@_) {
112 12         18 my $minimum_size = shift;
113 12 50       39 $minimum_size > 0 or croak 'positive minimum_size required';
114 12         25 $self->{minimum_size} = $minimum_size;
115 12         23 delete $self->{_clusters};
116             }
117 12         23 $self->{minimum_size};
118             }
119              
120             sub scales {
121 0     0 1 0 my $self = shift;
122 0         0 my $dimension = $self->{dimension};
123 0 0       0 if (@_) {
124 0 0       0 @_ == $dimension or croak 'number of scales does not match problem dimension';
125 0 0       0 grep($_ <= 0, @_) and croak 'positive number required';
126 0         0 @{$self->{scales}} = @_;
  0         0  
127 0         0 delete $self->{_clusters};
128             }
129 0         0 return @{$self->{scales}};
  0         0  
130             }
131              
132             sub dimensional_groups {
133 763     763 1 1005624 my $self = shift;
134 763         1492 my $hcb = $self->{dimensional_groups};
135 763         1646 my $dimension = $self->{dimension};
136 763 100       2302 if (@_) {
137 253         431 my @all = eval {
138 4     4   39 no warnings;
  4         6  
  4         16486  
139 253         2140 sort { $a <=> $b } map @$_, @_;
  1440         2418  
140             };
141 253 50 33     3317 croak 'bad dimension groups'
142             unless (@all == $dimension and
143             join('|', @all) eq join('|', 0..$dimension-1));
144 253         1646 $self->{dimensional_groups} = [map [@$_], @_];
145 253         4149 delete $self->{_clusters};
146             }
147 763         1074 map [@$_], @{$self->{dimensional_groups}};
  763         5760  
148             }
149              
150             sub clusters {
151 0     0 1 0 my $self = shift;
152 0   0     0 my $clusters = $self->{_clusters} ||= $self->_make_clusters_ix;
153 0         0 my $ax = $self->{x};
154 0         0 my $ay = $self->{y};
155 0         0 my $coords = $self->{coords};
156 0         0 my @out;
157 0         0 for my $cluster (@$clusters) {
158 0         0 my @cluster_coords;
159 0         0 for my $ix (@$cluster) {
160 0         0 push @cluster_coords, map $_->[$ix], @$coords;
161             }
162 0         0 push @out, \@cluster_coords;
163             }
164 0         0 @out;
165             }
166              
167             sub clusters_ix {
168 255     255 1 1562 my $self = shift;
169 255   33     1362 my $clusters = $self->{_clusters} ||= $self->_make_clusters_ix;
170             # dup arrays:
171 255         6937 map [@$_], @$clusters;
172             }
173              
174             sub _touch_2 {
175 245     245   377 my ($c1, $c2, $ax, $ay) = @_;
176              
177 245         290 my $c1_xmin = min @{$ax}[@$c1];
  245         551  
178 245         326 my $c2_xmax = max @{$ax}[@$c2];
  245         454  
179 245 100       710 return 0 if $c1_xmin - $c2_xmax > 1;
180              
181 196         220 my $c1_xmax = max @{$ax}[@$c1];
  196         364  
182 196         231 my $c2_xmin = min @{$ax}[@$c2];
  196         1064  
183 196 100       502 return 0 if $c2_xmin - $c1_xmax > 1;
184              
185 171         179 my $c1_ymin = min @{$ay}[@$c1];
  171         317  
186 171         207 my $c2_ymax = max @{$ay}[@$c2];
  171         285  
187 171 100       477 return 0 if $c1_ymin - $c2_ymax > 1;
188              
189 137         145 my $c1_ymax = max @{$ay}[@$c1];
  137         245  
190 137         153 my $c2_ymin = min @{$ay}[@$c2];
  137         233  
191 137 100       407 return 0 if $c2_ymin - $c1_ymax > 1;
192              
193 111         169 for my $i (@$c1) {
194 122         174 for my $j (@$c2) {
195 139         217 my $dx = $ax->[$i] - $ax->[$j];
196 139         187 my $dy = $ay->[$i] - $ay->[$j];
197 139 100       675 return 1 if ($dx * $dx + $dy * $dy <= 1);
198             }
199             }
200 27         125 0;
201             }
202              
203             sub _touch {
204 1601     1601   3445 my ($c1, $c2, $coords, $groups) = @_;
205             # print STDERR "touch($c1->[0], $c2->[0])\n";
206              
207 1601         3464 for my $coord (@$coords) {
208 4796         6415 my $c1_min = min @{$coord}[@$c1];
  4796         14375  
209 4796         7434 my $c2_max = max @{$coord}[@$c2];
  4796         12082  
210 4796 100       14111 return 0 if $c1_min - $c2_max > 1;
211              
212 4447         6039 my $c1_max = max @{$coord}[@$c1];
  4447         10013  
213 4447         5806 my $c2_min = min @{$coord}[@$c2];
  4447         9106  
214 4447 100       16144 return 0 if $c2_min - $c1_max > 1;
215             }
216              
217 923         2167 for my $i (@$c1) {
218 957         1605 J: for my $j (@$c2) {
219 1105         2041 for my $group (@$groups) {
220 1807         2395 my $sum = 0;
221 1807         2725 for (@{$coords}[@$group]) {
  1807         4225  
222 4114         6715 my $delta = $_->[$i] - $_->[$j];
223 4114         7238 $sum += $delta * $delta;
224             }
225 1807 100       6115 next J if $sum > 1;
226             }
227             # print STDERR "they touch\n";
228 768         4320 return 1;
229             }
230             }
231 155         967 0;
232             }
233              
234             sub _scaled_coords {
235 510     510   933 my $self = shift;
236 510         811 my @coords = @{$self->{coords}};
  510         2113  
237 510         957 my $scales = $self->{scales};
238 510         1248 my $ir = 1.0 / $self->{radius};
239 510         2603 for my $dimension (0..$#coords) {
240 2034         3612 my $scale = abs($ir * $scales->[$dimension]);
241 2034 100       5157 next if $scale == 1;
242 1362         1769 $coords[$dimension] = [ map $scale * $_, @{$coords[$dimension]} ];
  1362         25711  
243             }
244 510         2253 @coords;
245             }
246              
247 751     751   13391 sub _hypercylinder_id { join '|', map join(',', @$_), @_ }
248              
249             sub _make_clusters_ix {
250 255     255   474 my $self = shift;
251             # print STDERR Dumper $self;
252 255 100       568 _hypercylinder_id($self->dimensional_groups) eq '0,1'
253             ? $self->_make_clusters_ix_2
254             : $self->_make_clusters_ix_any;
255             }
256              
257             sub _make_clusters_ix_2 {
258 14     14   24 my $self = shift;
259              
260 14 50       41 $self->{dimension} == 2
261             or croak 'internal error: _make_clusters_ix_2 called but dimension is not 2';
262              
263 14         31 my ($ax, $ay) = $self->_scaled_coords;
264 14 50       38 @$ax or croak "points have not been added";
265              
266 14         69 my $xmin = min @$ax;
267 14         34 my $ymin = min @$ay;
268             # my $xmax = max @$ax;
269             # my $ymax = max @$ay;
270              
271 14         21 my $istep = 1.00001*sqr2;
272 14         21 my @fx = map { floor($istep * ($_ - $xmin)) } @$ax;
  377         766  
273 14         38 my @fy = map { floor($istep * ($_ - $ymin)) } @$ay;
  377         845  
274              
275 14         26 my (%ifx, %ify, $c);
276 14   66     29 $c = 1; $ifx{$_} ||= $c++ for @fx;
  14         1265  
277 14   66     29 $c = 1; $ify{$_} ||= $c++ for @fy;
  14         1155  
278 14         166 my %rifx = reverse %ifx;
279 14         187 my %rify = reverse %ify;
280              
281 14         27 my %cell;
282             # my %cellid;
283 14         20 my $cellid = 1;
284 14         35 for my $i (0..$#$ax) {
285 377         1583 my $cell = pack $packing => $ifx{$fx[$i]}, $ify{$fy[$i]};
286 377         395 push @{$cell{$cell}}, $i;
  377         1103  
287             # $cellid{$cell} ||= $cellid++;
288             # print STDERR "i: $i, x: $ax->[$i], y: $ay->[$i], fx: $fx[$i], fy: $fy[$i], ifx: $ifx{$fx[$i]}, ify: $ify{$fy[$i]}, cellid: $cellid{$cell}\n";
289             }
290              
291 14         28 my %cell2cluster; # n to 1 relation
292             my %cluster2cell; # when $cluster2cell{$foo} does not exists
293              
294 14         45 while(defined (my $cell = each %cell)) {
295 267         265 my %cluster;
296 267         554 my ($ifx, $ify) = unpack $packing => $cell;
297 267         462 my $fx = $rifx{$ifx};
298 267         374 my $fy = $rify{$ify};
299 267         424 for my $dx (-2, -1, 0, 1, 2) {
300 1335         2115 my $ifx = $ifx{$fx + $dx};
301 1335 100       2838 defined $ifx or next;
302 876         1163 my $filter = 6 - $dx * $dx;
303 876         1254 for my $dy (-2, -1, 0, 1, 2) {
304             # next if $dx * $dx + $dy * $dy > 5;
305 4380 100       8813 next if $dy * $dy > $filter;
306 3778         5377 my $ify = $ify{$fy + $dy};
307 3778 100       11184 defined $ify or next;
308 2825         4472 my $neighbor = pack $packing => $ifx, $ify;
309 2825         3590 my $cluster = $cell2cluster{$neighbor};
310 2825 100 100     8745 if ( defined $cluster and
      100        
311             !$cluster{$cluster} and
312             _touch_2($cell{$cell}, $cell{$neighbor}, $ax, $ay) ) {
313 84         237 $cluster{$cluster} = 1;
314             }
315             }
316             }
317 267 100       550 if (%cluster) {
318 72         76 my ($to, $to_cells);
319 72 100       125 if (keys %cluster > 1) {
320 12         14 my $max = 0;
321 12         29 for (keys %cluster) {
322 24         31 my $cells = $cluster2cell{$_};
323 24 100       46 my $n = defined($cells) ? @$cells : 1;
324 24 100       53 if ($n > $max) {
325 17         15 $max = $n;
326 17         35 $to = $_;
327             }
328             }
329 12         24 delete $cluster{$to};
330 12   100     47 $to_cells = ($cluster2cell{$to} ||= [$to]);
331 12         22 for (keys %cluster) {
332 12         18 my $neighbors = delete $cluster2cell{$_};
333 12 100       18 if (defined $neighbors) {
334 3         10 push @$to_cells, @$neighbors;
335 3         18 $cell2cluster{$_} = $to for @$neighbors;
336             }
337             else {
338 9         19 push @$to_cells, $_;
339 9         26 $cell2cluster{$_} = $to;
340             }
341             }
342             }
343             else {
344 60         93 $to = each %cluster;
345 60   100     227 $to_cells = ($cluster2cell{$to} ||= [$to]);
346             }
347 72         134 push @$to_cells, $cell;
348 72         340 $cell2cluster{$cell} = $to;
349             }
350             else {
351 195         977 $cell2cluster{$cell} = $cell;
352             }
353             }
354              
355 14         20 my @clusters;
356 14         48 while (my ($cluster, $cells) = each %cluster2cell) {
357 28         49 my @points = map @{delete $cell{$_}}, @$cells;
  112         289  
358 28 50       84 if (@points >= $self->{minimum_size}) {
359 28 50       107 @points = sort { $a <=> $b } @points if $self->{ordered};
  583         633  
360 28         129 push @clusters, \@points;
361             }
362             }
363 14         32 push @clusters, grep { @$_ >= $self->{minimum_size} } values %cell;
  155         479  
364              
365 14 50       58 @clusters = sort { $a->[0] <=> $b->[0] } @clusters if $self->{ordered};
  755         1059  
366              
367 14         432 return \@clusters;
368             }
369              
370             my %delta_hypercylinder; # cache
371              
372             sub _delta_hypercylinder {
373              
374 241   66 241   635 my $dhc = $delta_hypercylinder{_hypercylinder_id @_} ||= do {
375 53         80 my @subdimension;
376 53         145 for my $group (@_) {
377 140         543 $subdimension[$_] = @$group for @$group;
378             }
379 53         550 my @top = map ceil(sqrt($_)), @subdimension;
380             # calculate minimum hypercube
381 53         207 my @delta_hypercylinder = [];
382 53         147 for my $dimension (0..$#subdimension) {
383 246         301 my @next;
384 246         332 for my $dhc (@delta_hypercylinder) {
385 25260         34585 my $top = $top[$dimension];
386 25260         204760 push @next, map [@$dhc, $_], -$top..$top;
387             }
388 246         31784 @delta_hypercylinder = @next;
389             }
390              
391             # filter out hyperpixels out of the hypercylinder
392 53         212 for my $group (@_) {
393 200310         270485 @delta_hypercylinder = grep {
394 140         836 my $sum = 0;
395 200310         401760 for (@$_[@$group]) {
396 445888 100       839323 my $min = ($_ ? abs($_) - 1 : 0);
397 445888         789630 $sum += $min * $min;
398             }
399 200310         511411 $sum < @$group;
400             } @delta_hypercylinder;
401             }
402              
403             # print Data::Dumper->Dump([\@delta_hypercylinder], [qw($hc)]);
404              
405             \@delta_hypercylinder
406 53         5293 };
407             # print Data::Dumper->Dump([$dhc], [qw($hc)]);
408 241         73502 @$dhc;
409             }
410              
411             sub _print_clusters {
412 0     0   0 print join(',', @$_), "\n" for sort { $a->[0] <=> $b->[0] } @_;
  0         0  
413             }
414              
415             sub _make_clusters_ix_any {
416 241     241   465 my $self = shift;
417              
418 241         510 my $dimension = $self->{dimension};
419 241         878 my @coords = $self->_scaled_coords;
420 241         517 my $coords = \@coords;
421 241         331 my $top = $#{$coords[0]};
  241         658  
422 241 50       819 $top >= 0 or croak "points have not been added";
423 241         959 my $groups = $self->{dimensional_groups};
424              
425 241         343 my (@fls, @ifls, @rifls);
426              
427 241         659 for my $group (@$groups) {
428 560         1340 my $istep = 1.000001 * sqrt(@$group);
429 560         988 for my $dimension (@$group) {
430 989         1518 my $coord = $coords[$dimension];
431 989         3396 my $min = min @$coord;
432 989         39788 my @fl = map floor($istep * ($_ - $min)), @$coord;
433 989         2641 $fls[$dimension] = \@fl;
434 989         1713 my %ifl;
435 989         1314 my $c = 1;
436 989   66     108025 $ifl{$_} ||= $c++ for @fl;
437 989         2023 $ifls[$dimension] = \%ifl;
438 989         13000 my %rifl = reverse %ifl;
439 989         4515 $rifls[$dimension] = \%rifl;
440             }
441             }
442              
443 241         1660 my %cell;
444 241         492 my $dimension_top = $dimension - 1;
445 241         642 for my $i (0..$top) {
446 8341         81827 my $cell = pack $packing => map $ifls[$_]{$fls[$_][$i]}, 0..$dimension_top;
447 8341         25174 push @{$cell{$cell}}, $i;
  8341         24903  
448             }
449             # print STDERR "\%cell:\n";
450             # _print_clusters(values %cell);
451              
452 241         419 my %cell2cluster; # n to 1 relation
453             my %cluster2cell;
454              
455 241         1023 my @delta_hypercylinder = _delta_hypercylinder @$groups;
456             # print STDERR "delta_hypercylinder\n", Dumper \@delta_hypercylinder;
457              
458 241         9766 while(defined (my $cell = each %cell)) {
459 6053         8117 my %cluster;
460 6053         29619 my @ifl = unpack $packing => $cell;
461 6053         69986 my @fl = map $rifls[$_]{$ifl[$_]}, 0..$dimension_top;
462              
463 6053         16241 for my $delta (@delta_hypercylinder) {
464             # print STDERR "\$delta: @$delta\n";
465 7442241 100       11333111 my @ifl = map { $ifls[$_]{$fl[$_] + $delta->[$_]} || next } 0..$dimension_top;
  21258579         68482477  
466             # next if grep !defined, @ifl;
467 2219271         5940819 my $neighbor = pack $packing => @ifl;
468 2219271         2997169 my $cluster = $cell2cluster{$neighbor};
469 2219271 100 100     7721730 if ( defined $cluster and
      100        
470             !$cluster{$cluster} and
471             _touch($cell{$cell}, $cell{$neighbor}, $coords, $groups) ) {
472 768         3039 $cluster{$cluster} = 1;
473             }
474             }
475 6053 100       22596 if (%cluster) {
476 727         1466 my ($to, $to_cells);
477 727 100       2327 if (keys %cluster > 1) {
478 35         61 my $max = 0;
479 35         102 for (keys %cluster) {
480 76         114 my $cells = $cluster2cell{$_};
481 76 100       137 my $n = defined($cells) ? @$cells : 1;
482 76 100       179 if ($n > $max) {
483 48         63 $max = $n;
484 48         100 $to = $_;
485             }
486             }
487 35         101 delete $cluster{$to};
488 35   100     148 $to_cells = ($cluster2cell{$to} ||= [$to]);
489 35         99 for (keys %cluster) {
490 41         56 my $neighbors = delete $cluster2cell{$_};
491 41 100       100 if (defined $neighbors) {
492 11         32 push @$to_cells, @$neighbors;
493 11         99 $cell2cluster{$_} = $to for @$neighbors;
494             }
495             else {
496 30         79 push @$to_cells, $_;
497 30         107 $cell2cluster{$_} = $to;
498             }
499             }
500             }
501             else {
502 692         1665 $to = each %cluster;
503 692   100     3554 $to_cells = ($cluster2cell{$to} ||= [$to]);
504             }
505 727         2108 push @$to_cells, $cell;
506 727         7583 $cell2cluster{$cell} = $to;
507             }
508             else {
509 5326         63996 $cell2cluster{$cell} = $cell;
510             }
511             }
512              
513 241         467 my @clusters;
514 241         1165 while (my ($cluster, $cells) = each %cluster2cell) {
515 186         345 my @points = map @{delete $cell{$_}}, @$cells;
  954         3079  
516 186 50       738 if (@points >= $self->{minimum_size}) {
517 186 50       1194 @points = sort { $a <=> $b } @points if $self->{ordered};
  11438         11528  
518 186         864 push @clusters, \@points;
519             }
520             }
521 241         1032 push @clusters, grep { @$_ >= $self->{minimum_size} } values %cell;
  5099         10795  
522              
523 241 50       2614 @clusters = sort { $a->[0] <=> $b->[0] } @clusters if $self->{ordered};
  25023         35056  
524              
525 241         57957 return \@clusters;
526             }
527              
528             sub brute_force_clusters_ix {
529 255     255 0 2343 my $self = shift;
530 255 100       1630 _hypercylinder_id($self->dimensional_groups) eq '0,1'
531             ? $self->brute_force_clusters_ix_2
532             : $self->brute_force_clusters_ix_any
533             }
534              
535             sub brute_force_clusters_ix_2 {
536 14 50   14 0 43 @_ == 1 or croak 'Usage: $clp->brute_force_clusters_ix_2';
537 14         19 my $self = shift;
538              
539 14 50       39 $self->{dimension} == 2
540             or croak "brute_force_clusters_ix_2 called but dimension is not equal to 2";
541              
542 14         32 my ($ax, $ay) = $self->_scaled_coords;
543 14 50       33 @$ax or croak "points have not been added";
544              
545 14         74 my @ix = 0..$#$ax;
546 14         30 my @clusters;
547 14         51 while (@ix) {
548             # print STDERR "\@ix 1: ".join("-", @ix).".\n";
549 183         370 my @current = shift @ix;
550 183         209 my @done;
551 183         370 while (@ix) {
552             # print STDERR "\@ix 2: ".join("-", @ix).".\n";
553 6005         7479 my $ix = shift @ix;
554 6005         6322 my @queue;
555 6005         8106 for my $current (@current) {
556             # print STDERR "ix: $ix, current: $current\n";
557 7335         10822 my $dx = $ax->[$ix] - $ax->[$current];
558 7335         9914 my $dy = $ay->[$ix] - $ay->[$current];
559 7335 100       24524 if ($dx * $dx + $dy * $dy <= 1) {
560             # print STDERR "they are together\n";
561 150         176 push @queue, $ix;
562 150         226 last;
563             }
564             }
565 6005 100       11608 if (@queue) {
566 150         326 while (defined($ix = shift @queue)) {
567 194         287 for my $done (@done) {
568 3950 100       6923 next unless defined $done;
569             # print STDERR "ix: $ix, done: $done\n";
570 3313         4496 my $dx = $ax->[$ix] - $ax->[$done];
571 3313         4126 my $dy = $ay->[$ix] - $ay->[$done];
572 3313 100       8155 if ($dx * $dx + $dy * $dy <= 1) {
573             # print STDERR "they are together\n";
574 44         69 push @queue, $done;
575 44         69 undef $done;
576             }
577             }
578 194         799 push @current, $ix;
579             }
580             }
581             else {
582 5855         15274 push @done, $ix;
583             }
584             }
585 183 50       496 if (@current >= $self->{minimum_size}) {
586 183 50       481 @current = sort { $a <=> $b } @current if $self->{ordered};
  367         385  
587 183         287 push @clusters, \@current;
588             }
589 183         1745 @ix = grep defined($_), @done;
590             }
591 14 50       55 @clusters = sort { $a->[0] <=> $b->[0] } @clusters if $self->{ordered};
  198         251  
592 14         116 return @clusters;
593             }
594              
595             sub _points_touch {
596 223105     223105   307499 my ($ix0, $ix1, $coords, $groups) = @_;
597 223105         286512 for my $group (@$groups) {
598 231134         251203 my $sum = 0;
599 231134         263864 for (@{$coords}[@$group]) {
  231134         381637  
600 546628         701710 my $delta = $_->[$ix0] - $_->[$ix1];
601 546628         874056 $sum += $delta * $delta;
602             }
603             # print "sum: $sum\n";
604 231134 100       1024726 return 0 if $sum > 1;
605             }
606             # printf STDERR "points %d and %d touch\n", $ix0, $ix1;
607 3056         7872 return 1;
608             }
609              
610             sub brute_force_clusters_ix_any {
611 241 50   241 0 1193 @_ == 1 or croak 'Usage: $clp->brute_force_clusters_ix_any';
612 241         397 my $self = shift;
613              
614 241         478 my $dimension = $self->{dimension};
615 241         916 my $coords = [ $self->_scaled_coords ];
616 241         482 my @ix = 0..$#{$coords->[0]};
  241         1633  
617 241 50       1561 @ix or croak "points have not been added";
618              
619 241         553 my $groups = $self->{dimensional_groups};
620              
621 241         322 my @clusters;
622 241         847 while (@ix) {
623             # print STDERR "\@ix 1: ".join("-", @ix).".\n";
624 5285         11343 my @current = shift @ix;
625 5285         5413 my @done;
626 5285         10066 while (@ix) {
627             # print STDERR "\@ix 2: ".join("-", @ix).".\n";
628 206240         245039 my $ix = shift @ix;
629 206240         206224 my @queue;
630 206240         254780 for my $current (@current) {
631 213077 100       345330 if (_points_touch($ix, $current, $coords, $groups)) {
632 2947         3427 push @queue, $ix;
633 2947         3467 last;
634             }
635             }
636 206240 100       399920 if (@queue) {
637 2947         9982 while (defined($ix = shift @queue)) {
638 3056         5129 for my $done (@done) {
639 12309 100       25711 next unless defined $done;
640             # print STDERR "ix: $ix, done: $done\n";
641 10028 100       15601 if (_points_touch($ix, $done, $coords, $groups)) {
642 109         155 push @queue, $done;
643 109         178 undef $done;
644             }
645             }
646 3056         11723 push @current, $ix;
647             }
648             }
649             else {
650 203293         486677 push @done, $ix;
651             }
652             }
653 5285 50       15067 if (@current >= $self->{minimum_size}) {
654 5285 50       13588 @current = sort { $a <=> $b } @current if $self->{ordered};
  3630         3814  
655 5285         7921 push @clusters, \@current;
656             }
657 5285         56692 @ix = grep defined($_), @done;
658             }
659 241 50       1560 @clusters = sort { $a->[0] <=> $b->[0] } @clusters if $self->{ordered};
  5294         7346  
660 241         4935 return @clusters;
661             }
662              
663             sub distance {
664 0 0   0 0   @_ == 3 or croak 'Usage: $clp->distance($ix0, $ix1)';
665 0           my ($self, $ix0, $ix1) = @_;
666 0           my $coords = $self->{coords};
667 0           my $scales = $self->{scales};
668 0           my $sum = 0;
669 0           for my $dimension (0..$#{$coords}) {
  0            
670 0           my $delta = $scales->[$dimension] * ($coords->[$dimension][$ix0] - $coords->[$dimension][$ix1]);
671 0           $sum += $delta * $delta;
672             }
673 0           sqrt($sum);
674             }
675              
676             1;
677             __END__