fix distributed KMeans bugs and add javasingle version
[IRC.git] / Robust / src / Benchmarks / Prefetch / KMeans / javasingle / KMeans.java
1 /* =============================================================================
2  *
3  * kmeans.java
4  *
5  * =============================================================================
6  *
7  * Description:
8  *
9  * Takes as input a file:
10  *   ascii  file: containing 1 data point per line
11  *   binary file: first int is the number of objects
12  *                2nd int is the no. of features of each object
13  *
14  * This example performs a fuzzy c-means clustering on the data. Fuzzy clustering
15  * is performed using min to max clusters and the clustering that gets the best
16  * score according to a compactness and separation criterion are returned.
17  *
18  *
19  * Author:
20  *
21  * Wei-keng Liao
22  * ECE Department Northwestern University
23  * email: wkliao@ece.northwestern.edu
24  *
25  *
26  * Edited by:
27  *
28  * Jay Pisharath
29  * Northwestern University
30  *
31  * Chi Cao Minh
32  * Stanford University
33  *
34  * Port to Java version
35  * Alokika Dash
36  * University of California, Irvine
37  *
38  * =============================================================================
39  *
40  * ------------------------------------------------------------------------
41  * 
42  * For the license of kmeans, please see kmeans/LICENSE.kmeans
43  * 
44  * ------------------------------------------------------------------------
45  * 
46  * Unless otherwise noted, the following license applies to STAMP files:
47  * 
48  * Copyright (c) 2007, Stanford University
49  * All rights reserved.
50  * 
51  * Redistribution and use in source and binary forms, with or without
52  * modification, are permitted provided that the following conditions are
53  * met:
54  * 
55  *     * Redistributions of source code must retain the above copyright
56  *       notice, this list of conditions and the following disclaimer.
57  * 
58  *     * Redistributions in binary form must reproduce the above copyright
59  *       notice, this list of conditions and the following disclaimer in
60  *       the documentation and/or other materials provided with the
61  *       distribution.
62  * 
63  *     * Neither the name of Stanford University nor the names of its
64  *       contributors may be used to endorse or promote products derived
65  *       from this software without specific prior written permission.
66  * 
67  * THIS SOFTWARE IS PROVIDED BY STANFORD UNIVERSITY ``AS IS'' AND ANY
68  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
69  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
70  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL STANFORD UNIVERSITY BE LIABLE
71  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
72  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
73  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
74  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
75  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
76  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
77  * THE POSSIBILITY OF SUCH DAMAGE.
78  *
79  * =============================================================================
80  */
81
82 public class KMeans extends Thread {
83   /**
84    * User input for max clusters
85    **/
86   int max_nclusters;
87
88   /**
89    * User input for min clusters
90    **/
91   int min_nclusters;
92
93   /**
94    * Check for Binary file
95    **/
96   int isBinaryFile;
97
98   /**
99    * Using zscore transformation for cluster center 
100    * deviating from distribution's mean
101    **/
102   int use_zscore_transform;
103
104   /**
105    * Input file name used for clustering
106    **/
107   String filename;
108
109   /**
110    * Total number of threads
111    **/
112   int nthreads;
113
114   /**
115    * threshold until which kmeans cluster continues
116    **/
117   float threshold;
118
119   /**
120    * thread id
121    **/
122   int threadid;
123
124   /**
125    * Global arguments for threads 
126    **/
127   GlobalArgs g_args;
128
129   /**
130    * Output:  Number of best clusters
131    **/
132   int best_nclusters;
133
134   /**
135    * Output: Cluster centers
136    **/
137   float[][] cluster_centres;
138
139   /**
140    *
141    **/
142   float[][] attributes;
143
144   int numObjects;
145
146   int numAttributes;
147
148   public KMeans() {
149     max_nclusters = 13;
150     min_nclusters = 4;
151     isBinaryFile = 0;
152     use_zscore_transform = 1;
153     threshold = (float) 0.001;
154     best_nclusters = 0;
155   }
156
157   public KMeans(int threadid, GlobalArgs g_args, int nthreads, int use_zscore_transform, 
158       int max_nclusters, int min_nclusters, float threshold,
159       float[][] attributes, int numObjects, int numAttributes) {
160     this.threadid = threadid;
161     this.g_args = g_args;
162   }
163
164   public void run() {
165     while(true) {
166       Normal.work(threadid, g_args);
167     }
168   }
169
170   /* =============================================================================
171    * main
172    * =============================================================================
173    */
174   public static void main(String[] args) {
175     int nthreads;
176     int MAX_LINE_LENGTH = 1000000; /* max input is 400000 one digit input + spaces */
177     
178     /**
179      * Read options fron the command prompt 
180      **/
181     KMeans kms;
182     kms = new KMeans();
183     KMeans.parseCmdLine(args, kms);
184     nthreads = kms.nthreads;
185     System.out.println("nthreads= " + kms.nthreads);
186
187     if (kms.max_nclusters < kms.min_nclusters) {
188       System.out.println("Error: max_clusters must be >= min_clusters\n");
189       System.exit(0);
190     }
191     
192     int numAttributes = 0;
193     int numObjects = 0;
194
195     /*
196      * From the input file, get the numAttributes (columns in txt file) and numObjects (rows in txt file)
197      */
198     if (kms.isBinaryFile == 1) {
199       System.out.println("TODO: Unimplemented Binary file option\n");
200       System.exit(0);
201     }
202
203     FileInputStream inputFile = new FileInputStream(kms.filename);
204     byte b[] = new byte[MAX_LINE_LENGTH];
205     int n;
206     while ((n = inputFile.read(b)) != 0) {
207       for (int i = 0; i < n; i++) {
208         if (b[i] == '\n')
209           numObjects++;
210       }
211     }
212     inputFile.close();
213     inputFile = new FileInputStream(kms.filename);
214     String line = null;
215     if((line = inputFile.readLine()) != null) {
216       int index = 0;
217       boolean prevWhiteSpace = true;
218       while(index < line.length()) {
219         char c = line.charAt(index++);
220         boolean currWhiteSpace = Character.isWhitespace(c);
221         if(prevWhiteSpace && !currWhiteSpace){
222           numAttributes++;
223         }   
224         prevWhiteSpace = currWhiteSpace;
225       }   
226     }   
227     inputFile.close();
228
229     /* Ignore the first attribute: numAttributes = 1; */
230     numAttributes = numAttributes - 1; 
231
232     /* Allocate objects and read attributes of all objects */
233     float[][] buf = new float[numObjects][numAttributes];
234     float[][] attributes = new float[numObjects][numAttributes];
235     KMeans.readFromFile(inputFile, kms.filename, buf, MAX_LINE_LENGTH);
236     System.out.println("Finished Reading from file ......");
237
238     /*
239      * The core of the clustering
240      */
241
242     int len = kms.max_nclusters - kms.min_nclusters + 1;
243
244     int nloops = 1;
245
246     KMeans[] km = new KMeans[nthreads];
247     GlobalArgs g_args = new GlobalArgs();
248     g_args.nthreads = nthreads;
249     for(int x = 0; x < numObjects; x++) {
250       for(int y = 0; y < numAttributes; y++) {
251         attributes[x][y] = buf[x][y];
252       }
253     }
254
255     /* Create and Start Threads */
256     for(int i = 1; i<nthreads; i++) {
257       km[i] = new KMeans(i, g_args, nthreads, kms.use_zscore_transform, 
258           kms.max_nclusters, kms.min_nclusters, kms.threshold, attributes, numObjects, numAttributes);
259     }
260
261     KMeans tmp;
262     for(int i = 1; i<nthreads; i++) {
263       tmp = km[i];
264       tmp.run();
265     }
266
267     System.out.println("Finished Starting threads......");
268
269     for (int i = 0; i < nloops; i++) {
270       //
271       // Since zscore transform may perform in cluster() which modifies the
272       // contents of attributes[][], we need to re-store the originals
273       //
274       for(int x = 0; x < numObjects; x++) {
275         for(int y = 0; y < numAttributes; y++) {
276           attributes[x][y] = buf[x][y];
277         }
278       }
279
280
281       Cluster.cluster_exec(nthreads,
282           numObjects,
283           numAttributes,
284           attributes,             // [numObjects][numAttributes] 
285           kms,                    //main class that holds users inputs from command prompt and output arrays that need to be filled
286           g_args);                // Global arguments common to all threads
287     }
288
289     System.out.println("Printing output......");
290     System.out.println("Best_nclusters= " + kms.best_nclusters);
291
292     /* Output: the coordinates of the cluster centres */
293     
294     {
295       for (int i = 0; i < kms.best_nclusters; i++) {
296         System.out.print(i + " ");
297         for (int j = 0; j < numAttributes; j++) {
298           System.out.print(kms.cluster_centres[i][j] + " ");
299         }
300         System.out.println("\n");
301       }
302     }
303
304     System.out.println("Finished......\n");
305     System.exit(0);
306   }
307
308   public static void parseCmdLine(String args[], KMeans km) {
309     int i = 0;
310     String arg;
311     while (i < args.length && args[i].startsWith("-")) {
312       arg = args[i++];
313       //check options
314       if(arg.equals("-m")) {
315         if(i < args.length) {
316           km.max_nclusters = new Integer(args[i++]).intValue();
317         }
318       } else if(arg.equals("-n")) {
319         if(i < args.length) {
320           km.min_nclusters = new Integer(args[i++]).intValue();
321         }
322       } else if(arg.equals("-t")) {
323         if(i < args.length) {
324           km.threshold = new Integer(args[i++]).intValue();
325         }
326       } else if(arg.equals("-i")) {
327         if(i < args.length) {
328           km.filename = args[i++];
329         }
330       } else if(arg.equals("-b")) {
331         if(i < args.length) {
332           km.isBinaryFile = new Integer(args[i++]).intValue();
333         }
334       } else if(arg.equals("-z")) {
335         km.use_zscore_transform=0;
336       } else if(arg.equals("-nthreads")) {
337         if(i < args.length) {
338           km.nthreads = new Integer(args[i++]).intValue();
339         }
340       } else if(arg.equals("-h")) {
341         km.usage();
342       }
343     }
344     if(km.nthreads == 0 || km.filename == null) {
345       km.usage();
346     }
347   }
348
349   /**
350    * The usage routine which describes the program options.
351    **/
352   public void usage() {
353     System.out.println("usage: ./kmeans -m <max_clusters> -n <min_clusters> -t <threshold> -i <filename> -nthreads <threads>\n");
354     System.out.println(                   "  -i filename:     file containing data to be clustered\n");
355     System.out.println(                   "  -b               input file is in binary format\n");
356     System.out.println(                   "  -m max_clusters: maximum number of clusters allowed\n");
357     System.out.println(                   "  -n min_clusters: minimum number of clusters allowed\n");
358     System.out.println(                   "  -z             : don't zscore transform data\n");
359     System.out.println(                   "  -t threshold   : threshold value\n");
360     System.out.println(                   "  -nthreads      : number of threads\n");
361   }
362
363   /**
364    * readFromFile()
365    * Read attributes from the input file into an array
366    **/
367   public static void readFromFile(FileInputStream inputFile, String filename, float[][] buf, int MAX_LINE_LENGTH) {
368     inputFile = new FileInputStream(filename);
369     int j;
370     int i = 0;
371
372     byte b[] = new byte[MAX_LINE_LENGTH];
373     int n;
374     byte oldbytes[]=null;
375
376
377     // transaction will never abort because it is only executed
378     // on master machine and therefore the fileread native call is
379     //allowed as a warning
380     j = -1;
381     while ((n = inputFile.read(b)) != 0) {
382       int x=0;
383
384       if (oldbytes!=null) {
385         //find space
386         boolean cr=false;
387         for (;x < n; x++) {
388           if (b[x] == ' ')
389             break;
390           if (b[x] == '\n') {
391             cr=true;
392             break;
393           }
394         }
395         byte newbytes[]= new byte[x+oldbytes.length];
396         boolean isnumber=false;
397         for(int ii=0;ii<oldbytes.length;ii++) {
398           if (oldbytes[ii]>='0'&&oldbytes[ii]<='9')
399             isnumber=true;
400           newbytes[ii]=oldbytes[ii];
401         }
402         for(int ii=0;ii<x;ii++) {
403           if (b[ii]>='0'&&b[ii]<='9')
404             isnumber=true;
405           newbytes[ii+oldbytes.length]=b[ii];
406         }
407         if (x!=n)
408           x++; //skip past space or cr
409         if (isnumber) {
410           if (j>=0) {
411             buf[i][j]=(float)Double.parseDouble(new String(newbytes, 0, newbytes.length));
412           }
413           j++;
414         }
415         if (cr) {
416           j=-1;
417           i++;
418         }
419         oldbytes=null;
420       }
421
422       while (x < n) {
423         int y=x;
424         boolean cr=false;
425         boolean isnumber=false;
426         for(y=x;y<n;y++) {
427           if ((b[y]>='0')&&(b[y]<='9'))
428             isnumber=true;
429           if (b[y]==' ')
430             break;
431           if (b[y]=='\n') {
432             cr=true;
433             break;
434           }
435         }
436         if (y==n) {
437           //need to continue for another read
438           oldbytes=new byte[y-x];
439           for(int ii=0;ii<(y-x);ii++)
440             oldbytes[ii]=b[ii+x];
441           break;
442         }
443
444         //otherwise x is beginning of character string, y is end
445         if (isnumber) {
446           if (j>=0) {
447             buf[i][j]=(float)Double.parseDouble(new String(b,x,y-x));
448           }
449           j++;
450         }
451         if (cr) {
452           i++;//skip to next line
453           j = -1;//don't store line number
454           x=y;//skip to end of number
455           x++;//skip past return
456         } else {
457           x=y;//skip to end of number
458           x++;//skip past space
459         }
460       }
461     }
462     inputFile.close();
463   }
464 }
465
466 /* =============================================================================
467  *
468  * End of kmeans.java
469  *
470  * =============================================================================
471  */