ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/jsr166/jsr166/src/test/loops/TSPExchangerTest.java
Revision: 1.2
Committed: Sat Dec 10 20:08:51 2005 UTC (18 years, 5 months ago) by dl
Branch: MAIN
Changes since 1.1: +546 -438 lines
Log Message:
Improved tests

File Contents

# User Rev Content
1 dl 1.1 /*
2 dl 1.2 * Written by Doug Lea and Bill Scherer with assistance from members
3     * of JCP JSR-166 Expert Group and released to the public domain, as
4     * explained at http://creativecommons.org/licenses/publicdomain
5 dl 1.1 */
6    
7 dl 1.2 import java.util.*;
8 dl 1.1 import java.util.concurrent.*;
9     import java.util.concurrent.atomic.*;
10     import java.util.concurrent.locks.*;
11    
12 dl 1.2 /**
13     * A parallel Traveling Salesperson Problem (TSP) program based on a
14     * genetic algorithm using an Exchanger. A population of chromosomes
15     * is distributed among "pools". The chromosomes represent tours, and
16     * their fitness is the total tour length. Each chromosome is
17     * initialized as a random tour. A Task is associated with each pool.
18     * Each task repeatedly does, for a fixed number of iterations
19     * (generations):
20     *
21     * <ol>
22     * <li> Select a breeder b from the pool
23     * <li> Create a strand of its tour with a random starting point and length
24     * <li> Offer the strand to the exchanger, receiving a strand from
25     * another pool
26     * <li> Combine b and the received strand using crossing function to
27     * create new chromosome c.
28     * <li> Replace a chromosome in the pool with c.
29     * </ol>
30     *
31     * See below for more details.
32     * <p>
33     *
34     */
35 dl 1.1 public class TSPExchangerTest {
36 dl 1.2 static final int DEFAULT_MAX_THREADS =
37     (Runtime.getRuntime().availableProcessors() + 2);
38    
39     /**
40     * The problem size. Each city is a random point. The goal is to
41     * find a tour among them with smallest total Euclidean distance.
42     */
43     static final int DEFAULT_CITIES = 144;
44    
45     // Tuning parameters.
46    
47     /**
48     * The number of chromosomes per pool. Must be a power of two.
49     *
50     * Smaller values lead to faster iterations but poorer quality
51     * results
52     */
53     static final int DEFAULT_POOL_SIZE = 32;
54 dl 1.1
55 dl 1.2 /**
56     * The number of iterations per task. Convergence appears
57     * to be roughly proportional to #cities-squared
58     */
59     static final int DEFAULT_GENERATIONS = DEFAULT_CITIES * DEFAULT_CITIES;
60    
61     /**
62     * The number of pools. The total population is #pools * poolSize,
63     * which should be roughly on the order of #cities-squared
64     *
65     * Smaller values lead to faster total runs but poorer quality
66     * results
67     */
68     static final int DEFAULT_NPOOLS = DEFAULT_GENERATIONS / DEFAULT_POOL_SIZE;
69    
70     /**
71     * The minimum length for a random chromosome strand.
72     * Must be at least 1.
73     */
74     static final int MIN_STRAND_LENGTH = 3;
75    
76     /**
77     * The probablility mask value for creating random strands,
78     * that have lengths at least MIN_STRAND_LENGTH, and grow
79     * with exposnential decay 2^(-(1/(RANDOM_STRAND_MASK + 1)
80     * Must be 1 less than a power of two.
81     */
82     static final int RANDOM_STRAND_MASK = 7;
83    
84     /**
85     * Probablility control for selecting breeders.
86     * Breeders are selected starting at the best-fitness chromosome,
87     * with exponentially decaying probablility
88     * 1 / (poolSize >>> BREEDER_DECAY).
89     *
90     * Larger values usually cause faster convergence but poorer
91     * quality results
92     */
93     static final int BREEDER_DECAY = 1;
94    
95     /**
96     * Probablility control for selecting dyers.
97     * Dyers are selected starting at the worst-fitness chromosome,
98     * with exponentially decaying probablility
99     * 1 / (poolSize >>> DYER_DECAY)
100     *
101     * Larger values usually cause faster convergence but poorer
102     * quality results
103     */
104     static final int DYER_DECAY = 1;
105    
106     /**
107     * The probability mask for a task to give up running and
108     * resubmit itself. On each iteration, a task stops iterating
109     * and resubmits itself with probability 1 / (RESUBMIT_MASK+1).
110     * This avoids some tasks running to completion before others
111     * even start when there are more pools than threads.
112     *
113     * Must be 1 less than a power of two.
114     */
115     static final int RESUBMIT_MASK = 63;
116    
117     static final boolean verbose = true;
118     static final long SNAPSHOT_RATE = 10000; // in milliseconds
119    
120     /**
121     * The set of cities. Created once per program run, to
122     * make it easier to compare solutions across different runs.
123     */
124     static CitySet cities;
125 dl 1.1
126     public static void main(String[] args) throws Exception {
127 dl 1.2 int maxThreads = DEFAULT_MAX_THREADS;
128 dl 1.1 int nCities = DEFAULT_CITIES;
129 dl 1.2 int poolSize = DEFAULT_POOL_SIZE;
130     int nGen = nCities * nCities;
131     int nPools = nCities * nCities / poolSize;
132 dl 1.1
133     try {
134 dl 1.2 int argc = 0;
135 dl 1.1 while (argc < args.length) {
136     String option = args[argc++];
137 dl 1.2 if (option.equals("-c")) {
138 dl 1.1 nCities = Integer.parseInt(args[argc]);
139 dl 1.2 nGen = nCities * nCities;
140     nPools = nCities * nCities / poolSize;
141     }
142     else if (option.equals("-p"))
143     poolSize = Integer.parseInt(args[argc]);
144 dl 1.1 else if (option.equals("-g"))
145 dl 1.2 nGen = Integer.parseInt(args[argc]);
146     else if (option.equals("-n"))
147     nPools = Integer.parseInt(args[argc]);
148     else
149 dl 1.1 maxThreads = Integer.parseInt(option);
150     argc++;
151     }
152     }
153     catch (Exception e) {
154     reportUsageErrorAndDie();
155     }
156    
157 dl 1.2 System.out.print("TSPExchangerTest");
158     System.out.print(" -c " + nCities);
159     System.out.print(" -g " + nGen);
160     System.out.print(" -p " + poolSize);
161     System.out.print(" -n " + nPools);
162     System.out.print(" max threads " + maxThreads);
163     System.out.println();
164    
165     cities = new CitySet(nCities);
166 dl 1.1
167 dl 1.2 for (int i = 2; i <= maxThreads; i += 2)
168     oneRun(i, nPools, poolSize, nGen);
169 dl 1.1 }
170    
171 dl 1.2 static void reportUsageErrorAndDie() {
172     System.out.print("usage: TSPExchangerTest");
173     System.out.print(" [-c #cities]");
174     System.out.print(" [-p #poolSize]");
175     System.out.print(" [-g #generations]");
176     System.out.print(" [-n #pools]");
177     System.out.print(" #threads]");
178     System.out.println();
179 dl 1.1 System.exit(0);
180     }
181    
182 dl 1.2 /**
183     * Perform one run with the given parameters. Each run completes
184     * when all but one of the tasks has finished. The last remaining
185     * task may have no one left to exchange with, so the pool is
186     * abruptly terminated.
187     */
188     static void oneRun(int nThreads, int nPools, int poolSize, int nGen)
189     throws InterruptedException {
190     Population p = new Population(nThreads, nPools, poolSize, nGen);
191     ProgressMonitor mon = null;
192     if (verbose) {
193     mon = new ProgressMonitor(p);
194     mon.start();
195     }
196     p.printSnapshot(0);
197     long startTime = System.nanoTime();
198     p.start();
199     p.awaitTasks();
200     long stopTime = System.nanoTime();
201     if (mon != null)
202     mon.interrupt();
203     p.shutdown();
204     Thread.sleep(100);
205    
206 dl 1.1 long elapsed = stopTime - startTime;
207 dl 1.2 long rate = elapsed / (nPools * nGen);
208     double secs = (double)elapsed / 1000000000.0;
209     p.printSnapshot(secs);
210     System.out.printf("%10d ns per transfer\n", rate);
211     }
212 dl 1.1
213    
214 dl 1.2 /**
215     * A Population creates the pools, tasks, and threads for a run
216     * and has control methods to start, stop, and report progress.
217     */
218 dl 1.1 static final class Population {
219 dl 1.2 final Task[] tasks;
220     final Exchanger<Strand> exchanger;
221     final ThreadPoolExecutor exec;
222     final CountDownLatch done;
223     final int nGen;
224     final int poolSize;
225 dl 1.1 final int nThreads;
226    
227 dl 1.2 Population(int nThreads, int nPools, int poolSize, int nGen) {
228 dl 1.1 this.nThreads = nThreads;
229 dl 1.2 this.nGen = nGen;
230     this.poolSize = poolSize;
231     this.exchanger = new Exchanger<Strand>();
232     this.done = new CountDownLatch(nPools-1);
233     this.tasks = new Task[nPools];
234     for (int i = 0; i < nPools; i++)
235     tasks[i] = new Task(this);
236     BlockingQueue<Runnable> tq =
237     new LinkedBlockingQueue<Runnable>();
238     this.exec = new ThreadPoolExecutor(nThreads, nThreads,
239     0L, TimeUnit.MILLISECONDS,
240     tq);
241     exec.prestartAllCoreThreads();
242     }
243    
244     /** Start the tasks */
245     void start() {
246     for (int i = 0; i < tasks.length; i++)
247     exec.execute(tasks[i]);
248     }
249    
250     /** Stop the tasks */
251     void shutdown() {
252     exec.shutdownNow();
253     }
254    
255     /** Called by task upon terminations */
256     void taskDone() {
257     done.countDown();
258     }
259    
260     /** Wait for (all but one) task to complete */
261     void awaitTasks() throws InterruptedException {
262     done.await();
263     }
264    
265     /**
266     * Called by a task to resubmit itself after completing
267     * fewer than nGen iterations.
268     */
269     void resubmit(Task task) {
270     exec.execute(task);
271     }
272    
273     void printSnapshot(double secs) {
274     int gens = 0;
275     double best = Double.MAX_VALUE;
276     double worst = 0;
277     for (int k = 0; k < tasks.length; ++k) {
278     gens += tasks[k].gen;
279     Chromosome[] cs = tasks[k].chromosomes;
280     float b = cs[0].fitness;
281     if (b < best)
282     best = b;
283     float w = cs[cs.length-1].fitness;
284     if (w > worst)
285     worst = w;
286     }
287     int avegen = (done.getCount() == 0)? nGen : gens / tasks.length;
288     System.out.printf("Time:%9.3f Best:%9.3f Worst:%9.3f Gen:%6d Threads:%4d\n",
289     secs, best, worst, avegen, nThreads);
290     }
291    
292     float averageFitness() { // currently unused
293     float total = 0;
294     int count = 0;
295     for (int k = 0; k < tasks.length; ++k) {
296     Chromosome[] cs = tasks[k].chromosomes;
297     for (int i = 0; i < cs.length; i++)
298     total += cs[i].fitness;
299     count += cs.length;
300 dl 1.1 }
301 dl 1.2 return total/(float)count;
302 dl 1.1 }
303 dl 1.2 }
304 dl 1.1
305 dl 1.2 /**
306     * A Task updates its pool of chromosomes..
307     */
308     static final class Task implements Runnable {
309     /** The pool of chromosomes, kept in sorted order */
310     final Chromosome[] chromosomes;
311     final Population pop;
312     /** The common exchanger, same for all tasks */
313     final Exchanger<Strand> exchanger;
314     /** The current strand being exchanged */
315     Strand strand;
316     /** Bitset used in cross */
317     final int[] inTour;
318     final RNG rng;
319     final int poolSize;
320     final int nGen;
321     int gen;
322 dl 1.1
323 dl 1.2 Task(Population pop) {
324     this.pop = pop;
325     this.nGen = pop.nGen;
326     this.gen = 0;
327     this.poolSize = pop.poolSize;
328     this.exchanger = pop.exchanger;
329     this.rng = new RNG();
330     int length = cities.length;
331     this.strand = new Strand(length);
332     this.inTour = new int[(length >>> 5) + 1];
333     this.chromosomes = new Chromosome[poolSize];
334     for (int j = 0; j < poolSize; ++j)
335     chromosomes[j] = new Chromosome(length, rng);
336     Arrays.sort(chromosomes);
337     }
338    
339     /**
340     * Run one or more update cycles.
341     */
342     public void run() {
343 dl 1.1 try {
344 dl 1.2 for (;;) {
345     update();
346     if (++gen >= nGen) {
347     pop.taskDone();
348     return;
349 dl 1.1 }
350 dl 1.2 if ((rng.next() & RESUBMIT_MASK) == 1) {
351     pop.resubmit(this);
352     return;
353     }
354 dl 1.1 }
355 dl 1.2 } catch (InterruptedException ie) {
356     pop.taskDone();
357 dl 1.1 }
358     }
359    
360 dl 1.2 /**
361     * Choose a breeder, exchange strand with another pool, and
362     * cross them to create new chromosome to replace a chosen
363     * dyer.
364     */
365     void update() throws InterruptedException {
366     int b = chooseBreeder();
367     int d = chooseDyer(b);
368     Chromosome breeder = chromosomes[b];
369     Chromosome child = chromosomes[d];
370     chooseStrand(breeder);
371     strand = exchanger.exchange(strand);
372     cross(breeder, child);
373     fixOrder(child, d);
374     }
375    
376     /**
377     * Choose a breeder, with exponentially decreasing probability
378     * starting at best.
379     * @return index of selected breeder
380     */
381     int chooseBreeder() {
382     int mask = (poolSize >>> BREEDER_DECAY) - 1;
383     int b = 0;
384     while ((rng.next() & mask) != mask) {
385     if (++b >= poolSize)
386     b = 0;
387     }
388     return b;
389     }
390    
391     /**
392     * Choose a chromosome that will be replaced, with
393     * exponentially decreasing probablility starting at
394     * worst, ignoring the excluded index
395     * @param exclude index to ignore; use -1 to not exclude any
396     * @return index of selected dyer
397     */
398     int chooseDyer(int exclude) {
399     int mask = (poolSize >>> DYER_DECAY) - 1;
400     int d = poolSize - 1;
401     while (d == exclude || (rng.next() & mask) != mask) {
402     if (--d < 0)
403     d = poolSize - 1;
404     }
405     return d;
406     }
407    
408     /**
409     * Select a random strand of b's.
410     * @param breeder the breeder
411     */
412     void chooseStrand(Chromosome breeder) {
413     int[] bs = breeder.alleles;
414     int length = bs.length;
415     int strandLength = MIN_STRAND_LENGTH;
416     while (strandLength < length &&
417     (rng.next() & RANDOM_STRAND_MASK) != RANDOM_STRAND_MASK)
418     strandLength++;
419     strand.strandLength = strandLength;
420     int[] ss = strand.alleles;
421     int k = (rng.next() & 0x7FFFFFFF) % length;
422     for (int i = 0; i < strandLength; ++i) {
423     ss[i] = bs[k];
424     if (++k >= length) k = 0;
425     }
426     }
427    
428     /**
429     * Copy current strand to start of c's, and then append all
430     * remaining b's that aren't in the strand.
431     * @param breeder the breeder
432     * @param child the child
433     */
434     void cross(Chromosome breeder, Chromosome child) {
435     for (int k = 0; k < inTour.length; ++k) // clear bitset
436     inTour[k] = 0;
437    
438     // Copy current strand to c
439     int[] cs = child.alleles;
440     int ssize = strand.strandLength;
441     int[] ss = strand.alleles;
442     int i;
443     for (i = 0; i < ssize; ++i) {
444     int x = ss[i];
445     cs[i] = x;
446     inTour[x >>> 5] |= 1 << (x & 31); // record in bit set
447     }
448    
449     // Find index of matching origin in b
450     int first = cs[0];
451     int j = 0;
452     int[] bs = breeder.alleles;
453     while (bs[j] != first)
454     ++j;
455    
456     // Append remaining b's that aren't already in tour
457     while (i < cs.length) {
458     if (++j >= bs.length) j = 0;
459     int x = bs[j];
460     if ((inTour[x >>> 5] & (1 << (x & 31))) == 0)
461     cs[i++] = x;
462     }
463 dl 1.1
464     }
465    
466 dl 1.2 /**
467     * Fix the sort order of a changed Chromosome c at position k
468     * @param c the chromosome
469     * @param k the index
470     */
471     void fixOrder(Chromosome c, int k) {
472     Chromosome[] cs = chromosomes;
473     float oldFitness = c.fitness;
474     c.recalcFitness();
475     float newFitness = c.fitness;
476     if (newFitness < oldFitness) {
477     int j = k;
478     int p = j - 1;
479     while (p >= 0 && cs[p].fitness > newFitness) {
480     cs[j] = cs[p];
481     j = p--;
482 dl 1.1 }
483 dl 1.2 cs[j] = c;
484     } else if (newFitness > oldFitness) {
485     int j = k;
486     int n = j + 1;
487     while (n < cs.length && cs[n].fitness < newFitness) {
488     cs[j] = cs[n];
489     j = n++;
490 dl 1.1 }
491 dl 1.2 cs[j] = c;
492 dl 1.1 }
493     }
494     }
495    
496 dl 1.2 /**
497     * A Chromosome is a candidate TSP tour.
498     */
499     static final class Chromosome implements Comparable {
500     /** Index of cities in tour order */
501     final int[] alleles;
502     /** Total tour length */
503     float fitness;
504    
505     /**
506     * Initialize to random tour
507     */
508     Chromosome(int length, RNG random) {
509 dl 1.1 alleles = new int[length];
510     for (int i = 0; i < length; i++)
511     alleles[i] = i;
512     for (int i = length - 1; i > 0; i--) {
513 dl 1.2 int idx = (random.next() & 0x7FFFFFFF) % alleles.length;
514 dl 1.1 int tmp = alleles[i];
515     alleles[i] = alleles[idx];
516     alleles[idx] = tmp;
517     }
518     recalcFitness();
519     }
520 dl 1.2
521     public int compareTo(Object x) { // to enable sorting
522     float xf = ((Chromosome)x).fitness;
523     float f = fitness;
524     return ((f == xf)? 0 :((f < xf)? -1 : 1));
525 dl 1.1 }
526 dl 1.2
527 dl 1.1 void recalcFitness() {
528 dl 1.2 int[] a = alleles;
529     int len = a.length;
530     int p = a[0];
531     float f = cities.distanceBetween(a[len-1], p);
532     for (int i = 1; i < len; i++) {
533     int n = a[i];
534     f += cities.distanceBetween(p, n);
535     p = n;
536     }
537     fitness = f;
538     }
539    
540     void validate() { // Ensure that this is a valid tour.
541     int len = alleles.length;
542     boolean[] used = new boolean[len];
543     for (int i = 0; i < len; ++i)
544     used[alleles[i]] = true;
545     for (int i = 0; i < len; ++i)
546     if (!used[i])
547     throw new Error("Bad tour");
548 dl 1.1 }
549 dl 1.2
550     }
551    
552     /**
553     * A Strand is a random sub-sequence of a Chromosome. Each task
554     * creates only one strand, and then trades it with others,
555     * refilling it on each iteration.
556     */
557     static final class Strand {
558     final int[] alleles;
559     int strandLength;
560     Strand(int length) { alleles = new int[length]; }
561 dl 1.1 }
562 dl 1.2
563 dl 1.1 /**
564 dl 1.2 * A collection of (x,y) points that represent cities. Distances
565     * are scaled in [0,1) to simply checking results. The expected
566     * optimal TSP for random points is believed to be around 0.76 *
567     * sqrt(N). For papers discussing this, see
568     * http://www.densis.fee.unicamp.br/~moscato/TSPBIB_home.html for
569 dl 1.1 */
570     static final class CitySet {
571 dl 1.2 // Scale ints to doubles in [0,1)
572     static final double PSCALE = (double)0x80000000L;
573    
574 dl 1.1 final int length;
575 dl 1.2 final int[] xPts;
576     final int[] yPts;
577     final float[][] distances;
578    
579     CitySet(int n) {
580 dl 1.1 this.length = n;
581 dl 1.2 this.xPts = new int[n];
582     this.yPts = new int[n];
583     this.distances = new float[n][n];
584    
585     RNG random = new RNG();
586 dl 1.1 for (int i = 0; i < n; i++) {
587 dl 1.2 xPts[i] = (random.next() & 0x7FFFFFFF);
588     yPts[i] = (random.next() & 0x7FFFFFFF);
589 dl 1.1 }
590 dl 1.2
591 dl 1.1 for (int i = 0; i < n; i++) {
592     for (int j = 0; j < n; j++) {
593 dl 1.2 double dX = (xPts[i] - xPts[j]) / PSCALE;
594     double dY = (yPts[i] - yPts[j]) / PSCALE;
595     distances[i][j] = (float)Math.hypot(dX, dY);
596 dl 1.1 }
597     }
598     }
599 dl 1.2
600 dl 1.1 // Retrieve the cached distance between a pair of cities
601 dl 1.2 float distanceBetween(int i, int j) {
602     return distances[i][j];
603     }
604     }
605    
606     /**
607     * Cheap XorShift random number generator
608     */
609     static final class RNG {
610     /** Seed generator for XorShift RNGs */
611     static final Random seedGenerator = new Random();
612    
613     int seed;
614     RNG(int seed) { this.seed = seed; }
615     RNG() { this.seed = seedGenerator.nextInt(); }
616    
617     int next() {
618     int x = seed;
619     x ^= x << 6;
620     x ^= x >>> 21;
621     x ^= x << 7;
622     seed = x;
623     return x;
624 dl 1.1 }
625     }
626    
627 dl 1.2 static final class ProgressMonitor extends Thread {
628 dl 1.1 final Population pop;
629 dl 1.2 ProgressMonitor(Population p) { pop = p; }
630 dl 1.1 public void run() {
631 dl 1.2 double time = 0;
632 dl 1.1 try {
633 dl 1.2 while (!Thread.interrupted()) {
634     sleep(SNAPSHOT_RATE);
635     time += SNAPSHOT_RATE;
636     pop.printSnapshot(time / 1000.0);
637 dl 1.1 }
638 dl 1.2 } catch (InterruptedException ie) {}
639 dl 1.1 }
640     }
641     }