1#---------------------------------------------------------------------------
2
3package Algorithm::Cluster;
4
5#---------------------------------------------------------------------------
6# Copyright (c) 2003 John Nolan. All rights reserved.
7# This program is free software.  You may modify and/or
8# distribute it under the same terms as Perl itself.
9# This copyright notice must remain attached to the file.
10#
11# Algorithm::Cluster is a set of Perl wrappers around the
12# C Clustering library.
13#
14#---------------------------------------------------------------------------
15# The C clustering library for cDNA microarray data.
16# Copyright (C) 2002 Michiel Jan Laurens de Hoon.
17#
18# This library was written at the Laboratory of DNA Information Analysis,
19# Human Genome Center, Institute of Medical Science, University of Tokyo,
20# 4-6-1 Shirokanedai, Minato-ku, Tokyo 108-8639, Japan.
21# Contact: michiel.dehoon 'AT' riken.jp
22#
23# The Algorithm::Cluster module for Perl was released under the same terms
24# as the Perl Artistic license. See the file artistic.txt for details.
25#---------------------------------------------------------------------------
26
27
28use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS @EXPORT);
29use vars qw($DEBUG);
30use strict;
31use DynaLoader;
32
33require Exporter;
34
35$VERSION     = '1.59';
36$DEBUG       = 1;
37@ISA         = qw(DynaLoader Exporter);
38
39@EXPORT_OK = qw(
40    mean
41    median
42    kcluster
43    kmedoids
44    somcluster
45    treecluster
46    clusterdistance
47    clustercentroids
48    distancematrix
49    pca
50);
51
52use warnings::register;
53
54bootstrap Algorithm::Cluster $VERSION;
55
56
57#-------------------------------------------------------------
58# Debugging functions
59#
60sub version {
61    return _version();
62}
63
64
65#-------------------------------------------------------------
66# Wrapper for printing warnings
67#
68sub module_warn {
69    return unless warnings::enabled();
70    warnings::warn("Algorithm::Cluster", join '', @_);
71}
72
73#-------------------------------------------------------------
74# Make sure that the first parameter is a reference-to-array,
75# whose first member is itself a reference-to-array,
76# and that that array has at least one member.
77#
78sub data_is_valid_matrix {
79    unless (ref($_[0]) eq 'ARRAY') {
80        module_warn("Wanted array reference, but got a reference to ",
81                    ref($_[0]), ". Cannot parse matrix");
82        return;
83    }
84    my $nrows = scalar @{ $_[0] };
85    unless ($nrows > 0) {
86        module_warn("Matrix has zero rows.  Cannot parse matrix");
87        return;
88    }
89    my $firstrow =  $_[0]->[0];
90    unless (defined $firstrow) {
91        module_warn("First row in matrix is undef scalar (?). Cannot parse matrix",);
92        return;
93    }
94    unless (ref($firstrow) eq 'ARRAY') {
95        module_warn("Wanted array reference, but got a reference to ",
96                     ref($firstrow), ". Cannot parse matrix");
97        return;
98    }
99    my $ncols = scalar @{ $_[0]->[0] };
100    unless ($ncols > 0) {
101        module_warn("Row has zero columns. Cannot parse matrix");
102        return;
103    }
104    unless (defined($_[0]->[0]->[0])) {
105        module_warn("Cell [0,0] is undefined. Cannot parse matrix");
106        return;
107    }
108    return 1;
109}
110
111
112#-------------------------------------------------------------
113# Wrapper for the mean() function
114#
115sub mean {
116    if(ref $_[0] eq 'ARRAY') {
117        return _mean($_[0]);
118    } else {
119        return _mean([@_]);
120    }
121}
122
123#-------------------------------------------------------------
124# Wrapper for the median() function
125#
126sub median {
127    if(ref $_[0] eq 'ARRAY') {
128        return _median($_[0]);
129    } else {
130        return _median([@_]);
131    }
132}
133
134
135#------------------------------------------------------
136# This function is called by the wrappers for library functions.
137# It checks the dimensions of the data, mask and weight parameters.
138#
139# Return false if any errors are found in the data matrix.
140#
141# Detect the dimension (nrows x ncols) of the data matrix,
142# and set values in the parameter hash.
143#
144# Also check the mask matrix and weight arrays, and set
145# the parameters to default values if we find any errors,
146# however, we still return true if we find errors.
147#
148sub check_matrix_dimensions {
149    my ($param, $default) = @_;
150    #----------------------------------
151    # Check the data matrix
152    #
153    return unless data_is_valid_matrix($param->{data});
154    #----------------------------------
155    # Remember the dimensions of the weight array
156    #
157    $param->{nrows}   = scalar @{ $param->{data}      };
158    $param->{ncols}   = scalar @{ $param->{data}->[0] };
159    #----------------------------------
160    # Check the mask matrix
161    #
162    unless (data_is_valid_matrix($param->{mask})) {
163        module_warn("Parameter 'mask' is not a valid matrix, ignoring it.");
164        $param->{mask}      = $default->{mask}
165    } else {
166        my $mask_nrows    = scalar @{ $param->{mask}      };
167        my $mask_ncols    = scalar @{ $param->{mask}->[0] };
168        unless ($param->{nrows} == $mask_nrows and $param->{ncols} == $mask_ncols) {
169            module_warn("Data matrix is $param->{nrows}x$param->{ncols}, but mask matrix is ${mask_nrows}x${mask_ncols}.\nIgnoring the mask.");
170            $param->{mask}      = $default->{mask};
171        }
172    }
173    #----------------------------------
174    # Check the weight array
175    #
176    unless(ref $param->{weight} eq 'ARRAY') {
177            module_warn("Parameter 'weight' does not point to an array, ignoring it.");
178            $param->{weight} = $default->{weight};
179    } else {
180        my $weight_length    = scalar @{ $param->{weight} };
181        if ($param->{transpose} eq 0) {
182            unless ($param->{ncols} == $weight_length) {
183                module_warn("Data matrix has $param->{ncols} columns, but weight array has $weight_length items.\nIgnoring the weight array.");
184                $param->{weight}      = $default->{weight}
185            }
186        }
187        else {
188            unless ($param->{nrows} == $weight_length) {
189                module_warn("Data matrix has $param->{nrows} rows, but weight array has $weight_length items.\nIgnoring the weight array.");
190                $param->{weight}      = $default->{weight}
191            }
192        }
193    }
194    return 1;
195}
196
197
198sub check_distance_matrix {
199    my $distances = $_[0];
200    my $i;
201    my $row;
202    my $column;
203    #----------------------------------
204    # Check the data matrix
205    #
206    my $reference = ref($distances);
207    if (!$reference) {
208        return "Wanted array reference but did not receive a reference";
209    }
210    elsif ($reference ne 'ARRAY') {
211        return "Wanted array reference, but got a $reference";
212    }
213    my $nobjects = scalar @{ $distances };
214    unless ($nobjects > 0) {
215        return "Distance matrix has zero rows";
216    }
217    $i = 0;
218    foreach $row (@{ $distances}) {
219        unless (defined $row) {
220            return "Row $i is undefined";
221        }
222        unless (ref($row) eq 'ARRAY') {
223            return "Row $i is not an array";
224        }
225        unless (@{$row} == $i) {
226            return "Row $i has incorrect columns";
227        }
228        foreach $column (@{$row}) {
229            unless (defined($column)) {
230                return "Row $i contains undefined columns";
231            }
232        }
233        $i++;
234    }
235    return "OK";
236}
237
238sub check_initialid {
239    my ($param, $default, $nobjects) = @_;
240    my $i;
241    my @counter = {};
242    #----------------------------------
243    # Check the initial clustering solution, if specified
244    #
245    if(ref $param->{initialid} ne 'ARRAY') {
246        module_warn("Optional parameter 'initialid' should be an array");
247        return;
248    }
249    if (@{ $param->{initialid}} == 0) {
250        # no initial clustering solution specified
251        if ($param->{nclusters}==-1) {
252            $param->{nclusters} = 2; # default value
253        }
254        if ($param->{nclusters} > $nobjects) {
255            module_warn("More clusters requested than elements available");
256            return;
257        }
258        unless($param->{npass} =~ /^\d+$/ and $param->{npass} > 0) {
259            module_warn("Parameter 'npass' must be a positive integer (got '$param->{npass}')");
260            return;
261        }
262        return 1;
263    }
264    if (@{ $param->{initialid}} != $nobjects) {
265        module_warn("Optional parameter 'initialid' should contain $nobjects elements");
266        return;
267    }
268    foreach $i (@{ $param->{initialid}}) {
269        unless($i =~ /^\d+$/ and $i >= 0) {
270            module_warn("Optional parameter 'initialid' should only contain non-negative integers");
271            return;
272        }
273    }
274    if ($param->{nclusters} == -1) {
275        # number of clusters was not specified. Infer it from initialid
276        foreach $i (@{ $param->{initialid}}) {
277            if ($i > $param->{nclusters}) {
278                $param->{nclusters} = $i;
279            }
280        }
281        $param->{nclusters}++;
282    } else {
283        # check if initialid is consistent with number of clusters
284        foreach $i (@{ $param->{initialid}}) {
285            if ($i >= $param->{nclusters}) {
286                module_warn("Optional parameter 'initialid' inconsistent with nclusters");
287                return;
288            }
289        }
290    }
291    # Check that none of the clusters are empty
292    for ($i = 0; $i < $param->{nclusters}; $i++) {
293        push(@counter, 0);
294    }
295    foreach $i (@{ $param->{initialid}}) {
296        $counter[$i]++;
297    }
298    for ($i = 0; $i < $param->{nclusters}; $i++) {
299        if ($counter[$i]==0) {
300            module_warn("Optional parameter 'initialid' contains empty clusters");
301            return;
302        }
303    }
304    # No errors detected
305    $param->{npass} = 0;
306    return 1;
307}
308
309#-------------------------------------------------------------
310# Wrapper for the kcluster() function
311#
312sub kcluster {
313    #----------------------------------
314    # Define default parameters
315    #
316    my %default = (
317        nclusters =>    -1,
318        data      =>  [[]],
319        mask      =>    '',
320        weight    =>    '',
321        transpose =>     0,
322        npass     =>     1,
323        method    =>   'a',
324        dist      =>   'e',
325        initialid =>    [],
326    );
327    #----------------------------------
328    # Local variable
329    #
330    my $nobjects = 0;
331    #----------------------------------
332    # Accept parameters from caller
333    #
334    my %param = (%default, @_);
335    my @data = @{$param{data}};
336    #----------------------------------
337    # Check the data, matrix and weight parameters
338    #
339    return unless check_matrix_dimensions(\%param, \%default);
340    #----------------------------------
341    # Check the transpose parameter
342    #
343    if ($param{transpose} == 0) {
344        $nobjects = $param{nrows};
345    } elsif ($param{transpose} == 1) {
346        $nobjects = $param{ncols};
347    } else {
348        module_warn("Parameter 'transpose' must be either 0 or 1 (got '$param{transpose}')");
349        return;
350    }
351    #----------------------------------
352    # Check the initial clustering, if specified, and npass
353    #
354    return unless check_initialid(\%param, \%default, $nobjects);
355    #----------------------------------
356    # Check the other parameters
357    #
358    unless($param{method}    =~ /^[am]$/) {
359        module_warn("Parameter 'method' must be either 'a' or 'm' (got '$param{method}')");
360        return;
361    }
362    unless($param{dist}      =~ /^[cauxskeb]$/) {
363        module_warn("Parameter 'dist' must be one of: [cauxskeb] (got '$param{dist}')");
364        return;
365    }
366    #----------------------------------
367    # Invoke the library function
368    #
369    return _kcluster(@param{
370        qw/nclusters nrows ncols data mask weight transpose npass method dist initialid/
371    });
372}
373
374#-------------------------------------------------------------
375# Wrapper for the kmedoids() function
376#
377
378sub kmedoids {
379    #----------------------------------
380    # Define default parameters
381    #
382    my %default = (
383
384        nclusters =>     2,
385        distances =>  [[]],
386        npass     =>     1,
387        initialid =>    [],
388    );
389    #----------------------------------
390    # Accept parameters from caller
391    #
392    my %param = (%default, @_);
393    #----------------------------------
394    # Check the distance matrix
395    #
396    my $message = check_distance_matrix($param{distances});
397    unless ($message eq "OK") {
398        module_warn($message);
399        return;
400    }
401    $param{nobjects} = scalar @{ $param{distances} };
402    #----------------------------------
403    # Check the initial clustering, if specified, and npass
404    #
405    return unless check_initialid(\%param, \%default, $param{nobjects});
406    #----------------------------------
407    # Invoke the library function
408    #
409    return _kmedoids(@param{
410        qw/nclusters nobjects distances npass initialid/
411    });
412}
413
414#-------------------------------------------------------------
415# treecluster(): Wrapper for the treecluster function
416#
417sub treecluster {
418    #----------------------------------
419    # Define default parameters
420    #
421    my %default = (
422        data       =>  [[]],
423        mask       =>    '',
424        weight     =>    '',
425        transpose  =>     0,
426        dist       =>   'e',
427        method     =>   's',
428    );
429    #----------------------------------
430    # Accept parameters from caller
431    #
432    my %param = (%default, @_);
433    #----------------------------------
434    # Check the data, matrix and weight parameters
435    #
436    my $message = check_distance_matrix($param{data});
437    if ($message eq "OK") {
438        $param{nrows}     = scalar @{ $param{data} };
439        $param{ncols}     = scalar @{ $param{data} };
440        $param{mask}      = $default{mask};
441        $param{weight}    = $default{weight};
442        $param{transpose} = $default{transpose};
443        $param{dist}      = $default{dist};
444        #----------------------------------
445        # Check the clustering method
446        #
447        unless($param{method}    =~ /^[sma]$/) {
448            module_warn("Parameter 'method' must be one of [sma] (got '$param{method}')");
449            return;
450        }
451    } else {
452        return unless check_matrix_dimensions(\%param, \%default);
453        unless($param{transpose} =~ /^[01]$/) {
454            module_warn("Parameter 'transpose' must be either 0 or 1 (got '$param{transpose}')");
455            return;
456        }
457        unless($param{dist}      =~ /^[cauxskeb]$/) {
458            module_warn("Parameter 'dist' must be one of: [cauxskeb] (got '$param{dist}')");
459            return;
460        }
461        unless($param{method}    =~ /^[smca]$/) {
462            module_warn("Parameter 'method' must be one of [smca] (got '$param{method}')");
463            return;
464        }
465    }
466    #----------------------------------
467    # Invoke the library function
468    #
469    return _treecluster(@param{
470        qw/nrows ncols data mask weight transpose dist method/
471    });
472}
473
474
475
476#-------------------------------------------------------------
477# Wrapper for the clusterdistance() function
478#
479sub clusterdistance {
480    #----------------------------------
481    # Define default parameters
482    #
483    my %default = (
484        data      =>  [[]],
485        mask      =>    '',
486        weight    =>    '',
487        cluster1  =>    [],
488        cluster2  =>    [],
489        dist      =>   'e',
490        method    =>   'a',
491        transpose =>     0,
492    );
493    #----------------------------------
494    # Accept parameters from caller
495    #
496    my %param = (%default, @_);
497    #----------------------------------
498    # Check the cluster1 and cluster2 arrays
499    #
500    if($param{cluster1} =~ /^\d+$/) {
501        $param{cluster1} = [int($param{cluster1})];
502    } elsif(ref $param{cluster1} ne 'ARRAY') {
503        module_warn("Parameter 'cluster1' does not point to an array. Cannot compute distance.");
504        return;
505    } elsif(@{ $param{cluster1}} <= 0) {
506        module_warn("Parameter 'cluster1' points to an empty array. Cannot compute distance.");
507        return;
508    }
509    if($param{cluster2} =~ /^\d+$/) {
510        $param{cluster2} = [int($param{cluster2})];
511    } elsif(ref $param{cluster2} ne 'ARRAY') {
512        module_warn("Parameter 'cluster2' does not point to an array. Cannot compute distance.");
513        return;
514    } elsif(@{ $param{cluster2}} <= 0) {
515        module_warn("Parameter 'cluster2' points to an empty array. Cannot compute distance.");
516        return;
517    }
518    $param{cluster1_len} = @{ $param{cluster1}};
519    $param{cluster2_len} = @{ $param{cluster2}};
520    #----------------------------------
521    # Check the data, matrix and weight parameters
522    #
523    return unless check_matrix_dimensions(\%param, \%default);
524    #----------------------------------
525    # Check the other parameters
526    #
527    unless($param{transpose} =~ /^[01]$/) {
528        module_warn("Parameter 'transpose' must be either 0 or 1 (got '$param{transpose}')");
529        return;
530    }
531    unless($param{method}    =~ /^[amsxv]$/) {
532        module_warn("Parameter 'method' must be 'a', 'm', 's', 'x', or 'v' (got '$param{method}')");
533        return;
534    }
535    unless($param{dist}      =~ /^[cauxskeb]$/) {
536        module_warn("Parameter 'dist' must be one of: [cauxskeb] (got '$param{dist}')");
537        return;
538    }
539    #----------------------------------
540    # Invoke the library function
541    #
542    return _clusterdistance(@param{
543        qw/nrows ncols data mask weight cluster1_len cluster2_len
544        cluster1 cluster2 dist method transpose/
545    });
546}
547
548
549#-------------------------------------------------------------
550# Wrapper for the clustercentroids() function
551#
552sub clustercentroids {
553    #----------------------------------
554    # Define default parameters
555    #
556    my %default = (
557        data      =>  [[]],
558        mask      =>    '',
559        clusterid =>    [],
560        method    =>   'a',
561        transpose =>     0,
562    );
563    #----------------------------------
564    # Accept parameters from caller
565    #
566    my %param = (%default, @_);
567    #----------------------------------
568    # Check the data, matrix and weight parameters
569    #
570    return unless check_matrix_dimensions(\%param, \%default);
571    #----------------------------------
572    # Check the other parameters
573    #
574    unless($param{transpose} =~ /^[01]$/) {
575        module_warn("Parameter 'transpose' must be either 0 or 1 (got '$param{transpose}')");
576        return;
577    }
578    unless($param{method}    =~ /^[am]$/) {
579        module_warn("Parameter 'method' must be 'a' or 'm' (got '$param{method}')");
580        return;
581    }
582    #----------------------------------
583    # Check the clusterid arrays
584    #
585    if($param{clusterid} =~ /^\d+$/) {
586        $param{clusterid} = [int($param{clusterid})];
587    } elsif(ref $param{clusterid} ne 'ARRAY') {
588        module_warn("Parameter 'clusterid' does not point to an array. Cannot compute distance.");
589        return;
590    } elsif(@{ $param{clusterid}} <= 0) {
591        module_warn("Parameter 'clusterid' points to an empty array. Cannot compute distance.");
592        return;
593    }
594    my $clusterid_len = @{ $param{clusterid}};
595    my $nrows = $param{nrows};
596    my $ncols = $param{ncols};
597    if ($param{transpose}==0 and $clusterid_len != $nrows) {
598        die "Parameter 'clusterid' should have a size of $nrows; found $clusterid_len";
599    }
600    elsif ($param{transpose}==1 and $clusterid_len != $ncols) {
601        die "Parameter 'clusterid' should have a size of $ncols; found $clusterid_len";
602    }
603    my $nclusters = -1;
604    foreach (@{$param{clusterid}}) {
605        if ($_ > $nclusters) {
606            $nclusters = $_;
607        }
608    }
609    $param{nclusters} = $nclusters + 1;
610    #----------------------------------
611    # Invoke the library function
612    #
613    return _clustercentroids(@param{
614        qw/nclusters nrows ncols data mask clusterid transpose method/
615    });
616}
617
618
619#-------------------------------------------------------------
620# Wrapper for the distancematrix() function
621#
622sub distancematrix {
623    #----------------------------------
624    # Define default parameters
625    #
626    my %default = (
627
628        data      =>  [[]],
629        mask      =>    '',
630        weight    =>    '',
631        dist      =>   'e',
632        transpose =>     0,
633    );
634    #----------------------------------
635    # Accept parameters from caller
636    #
637    my %param = (%default, @_);
638    #----------------------------------
639    # Check the data, matrix and weight parameters
640    #
641    return unless check_matrix_dimensions(\%param, \%default);
642    #----------------------------------
643    # Check the transpose parameter
644    #
645    unless($param{transpose} =~ /^[01]$/) {
646        module_warn("Parameter 'transpose' must be either 0 or 1 (got '$param{transpose}')");
647        return;
648    }
649    #----------------------------------
650    # Check the other parameters
651    #
652    unless($param{dist}      =~ /^[cauxskeb]$/) {
653        module_warn("Parameter 'dist' must be one of: [cauxskeb] (got '$param{dist}')");
654        return;
655    }
656    #----------------------------------
657    # Invoke the library function
658    #
659    return _distancematrix(@param{
660        qw/nrows ncols data mask weight transpose dist/
661    });
662}
663
664
665#-------------------------------------------------------------
666# Wrapper for the somcluster() function
667#
668sub somcluster {
669    #----------------------------------
670    # Define default parameters
671    #
672    my %default = (
673        data      =>  [[]],
674        mask      =>    '',
675        weight    =>    '',
676        transpose =>     0,
677        nxgrid    =>    10,
678        nygrid    =>    10,
679        inittau   =>  0.02,
680        niter     =>   100,
681        dist      =>   'e',
682    );
683    #----------------------------------
684    # Accept parameters from caller
685    #
686    my %param = (%default, @_);
687    #----------------------------------
688    # Check the data, matrix and weight parameters
689    #
690    return unless check_matrix_dimensions(\%param, \%default);
691    #----------------------------------
692    # Check the other parameters
693    #
694    unless($param{transpose} =~ /^[01]$/) {
695        module_warn("Parameter 'transpose' must be either 0 or 1 (got '$param{transpose}')");
696        return;
697    }
698    unless($param{nxgrid}     =~ /^\d+$/ and $param{nxgrid} > 0) {
699        module_warn("Parameter 'nxgrid' must be a positive integer (got '$param{nxgrid}')");
700        return;
701    }
702
703    unless($param{nygrid}     =~ /^\d+$/ and $param{nygrid} > 0) {
704        module_warn("Parameter 'nygrid' must be a positive integer (got '$param{nygrid}')");
705        return;
706    }
707    unless($param{inittau}     =~ /^\d+.\d+$/ and $param{inittau} >= 0.0) {
708        module_warn("Parameter 'inittau' must be a non-negative number (got '$param{inittau}')");
709        return;
710    }
711    unless($param{niter}     =~ /^\d+$/ and $param{niter} > 0) {
712        module_warn("Parameter 'niter' must be a positive integer (got '$param{niter}')");
713        return;
714    }
715    unless($param{dist}      =~ /^[cauxskeb]$/) {
716        module_warn("Parameter 'dist' must be one of: [cauxskeb] (got '$param{dist}')");
717        return;
718    }
719    #----------------------------------
720    # Invoke the library function
721    #
722    return _somcluster(@param{
723        qw/nrows ncols data mask weight transpose nxgrid nygrid inittau niter dist/
724    });
725}
726
727
728#-------------------------------------------------------------
729# Wrapper for the pca() function
730#
731sub pca {
732    #----------------------------------
733    # Accept parameters from caller
734    #
735    my $data = shift;
736    #----------------------------------
737    # Check the data matrix
738    #
739    return unless data_is_valid_matrix($data);
740    #----------------------------------
741    # Remember the dimensions of the data array
742    #
743    my $nrows   = scalar @{$data};
744    my $ncols   = scalar @{$data->[0]};
745
746    #----------------------------------
747    # Invoke the library function
748    return _pca($nrows, $ncols, $data);
749}
750
751
7521;
753
754__END__
755
756
757=head1 NAME
758
759Algorithm::Cluster - Perl interface to the C Clustering Library.
760
761
762=head1 DESCRIPTION
763
764This module is an interface to the C Clustering Library,
765a general purpose library implementing functions for hierarchical
766clustering (pairwise simple, complete, average, and centroid linkage),
767along with k-means and k-medians clustering, and 2D self-organizing
768maps.  This library was developed at the Human Genome Center of the
769University of Tokyo. The C Clustering Library is distributed along
770with Cluster 3.0, an enhanced version of the famous
771Cluster program originally written by Michael Eisen
772while at Stanford University.
773
774=head1 EXAMPLES
775
776See the scripts in the examples subdirectory of the package.
777
778=head1 CHANGES
779
780=over 4
781
782=item * C Clustering Library version 1.59
783
784=head1 TO DO
785
786=over
787
788=head1 THANKS
789
790Thanks to Michael Eisen, for creating the software packages
791Cluster and TreeView.
792
793=head1 AUTHOR
794
795John Nolan jpnolan@sonic.net 2003.
796Michiel de Hoon michiel.dehoon "AT" riken.jp 2003-2017.
797Seiya Imoto imoto "AT" ims.u-tokyo.ac.jp 2003-2010.
798Satoru Miyano 2003-2010.
799A copyright statement is contained in the source code itself.
800
801This module is a Perl wrapper for the C clustering library for
802cDNA microarray data, Copyright (C) 2002 Michiel Jan Laurens de Hoon.
803
804See the source of Cluster.pm for a full copyright statement.
805
806=cut
807
8081;
809