/*
 * Aleph Toolkit
 *
 * Copyright 1997,1998 Brown University, Providence, RI.
 * 
 *                         All Rights Reserved
 * 
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose other than its incorporation into a commercial
 * product is hereby granted without fee, provided that the above copyright
 * notice appear in all copies and that both that copyright notice and this
 * permission notice appear in supporting documentation, and that the name of
 * Brown University not be used in advertising or publicity pertaining to
 * distribution of the software without specific, written prior permission.
 * 
 * BROWN UNIVERSITY DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ANY
 * PARTICULAR PURPOSE.  IN NO EVENT SHALL BROWN UNIVERSITY BE LIABLE FOR ANY
 * SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER
 * RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
 * CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
 * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE
 */

package aleph.examples;

import aleph.Aleph;
import aleph.AlephDebug;
import aleph.AlephException;
import aleph.GlobalObject;
import aleph.Join;
import aleph.PE;
import aleph.RemoteThread;
import java.io.Serializable;
import java.util.Enumeration;

/**
 * This example is Travelling Saleman Problem
 * @author Zhihao Zhang
 * @date   November 1997
 **/

public class TSP {
  private static final double M_E   = 2.7182818284590452354;
  private static final double M_E2  = 7.3890560989306502274;
  private static final double M_E3  = 20.08553692318766774179;
  private static final double M_E6  = 403.42879349273512264299;
  private static final double M_E12 = 162754.79141900392083592475;

  public GlobalObject _root;

  TSP(int nodeNum) {
    _root = buildTree(nodeNum, 0, 0, PE.numPEs(), 0.0, 1.0, 0.0, 1.0);
  }

  // Return an estimate of median of n values distributed in [min,max)
  private static double median(double min, double max, int n) {
    double t;
    double retval;
      
    t = Math.random(); // in [0.0,1.0) 
    if (t > 0.5) {
      retval = Math.log(1.0-(2.0*(M_E12-1)*(t-0.5)/M_E12))/12.0;
    }
    else {
      retval = -Math.log(1.0-(2.0*(M_E12-1)*t/M_E12))/12.0;
    }
    // We now have something distributed on (-1.0,1.0) 
    retval = (retval+1.0) * (max-min)/2.0;
    retval = retval + min;
    return retval;
  }
  
  // Get double uniformly distributed over [min,max)
  private static double uniform(double min, double max) {
    double retval;
    
    retval = Math.random(); // in [0.0,1.0)
    retval = retval * (max-min);
    return retval + min;
  }
  
  // Builds a 2D tree of n nodes in specified range with dir as primary
  // axis (0 for x, 1 for y)
  public static GlobalObject buildTree(int n, int dir, int peNo, int numPE, double minX, 
				       double maxX, double minY, double maxY) 
  {
    double med;
    Tree t = new Tree();
    Join jv = new Join();
    PE me = PE.thisPE();
    GlobalObject gb;
    int leftN, rightN;
    
    if (Aleph.getVerbosity() > 0)
      System.out.println("n:"+n+" dir:"+dir+" peNo:"+peNo+" numPE:"+numPE);
    if (n == 0) return null;
    gb = new GlobalObject(t);
    
    t = (Tree) gb.open("w");
    leftN = (n-1)/2;
    rightN = n-1-leftN;
    if (dir == 1) {
      dir = 0;
      med = median(minX, maxX, n);

      if (leftN == 0) 
	t._left = null;
      else {
	if (me.equals(PE.getPE(peNo+numPE/2))) {  // Local PE
	  if (Aleph.getVerbosity() > 0)
	    System.out.print("Left ");
	  t._left = buildTree(leftN, dir, peNo+numPE/2, numPE/2, minX, med, minY, maxY);
	} else {  // Remote PE
	  try {
	    gb.release();
	  } catch (AlephException ale) {
	    System.err.println("Release failed: " + ale.getMessage());
	  }
	  Builder builder = new Builder(gb, leftN, dir, peNo+numPE/2, numPE/2, 
					minX, med, minY, maxY);
	  builder.start(PE.getPE(peNo+numPE/2), jv);
	  jv.waitFor();
	  t = (Tree) gb.open("w");
	}
      }
      
      if (rightN == 0)
	t._right = null;
      else {
	if (Aleph.getVerbosity() > 0)
	  System.out.print("Right ");
	// Right subtree always in local PE
	t._right = buildTree(rightN, dir, peNo, numPE/2, med, maxX, minY, maxY);
      }

      t._x = med;
      t._y = uniform(minY, maxY);

    } else {
      dir = 1;
      med = median(minY, maxY, n);

      if (leftN == 0) 
	t._left = null;
      else {
	if (me.equals(PE.getPE(peNo+numPE/2))) {  // Local PE
	  if (Aleph.getVerbosity() > 0)
	    System.out.print("Left ");
	  t._left = buildTree(leftN, dir, peNo+numPE/2, numPE/2, minX, maxX, minY, med);
	} else {  // Remote PE
	  try {
	    gb.release();
	  } catch (AlephException ale) {
	    System.err.println("Release failed: " + ale.getMessage());
	  }
	  Builder builder = new Builder(gb, leftN, dir, peNo+numPE/2, numPE/2, 
					minX, maxX, minY, med);
	  builder.start(PE.getPE(peNo+numPE/2), jv);
	  jv.waitFor();
	  t = (Tree) gb.open("w");
	}
      }

      if (rightN == 0)
	t._right = null;
      else {
	if (Aleph.getVerbosity() > 0)
	  System.out.print("Right ");
	// Right subtree always in local PE
	t._right = buildTree(rightN, dir, peNo, numPE/2, minX, maxX, med, maxY);
      }

      t._y = med;
      t._x = uniform(minX, maxX);
    }

    t._size = n;
    t._next = null;
    t._prev = null;
    jv.waitFor();
    
    try {
      gb.release();
    } catch (AlephException ale) {
      System.err.println("Release failed: " + ale.getMessage());
    }
    return gb;
    //new GlobalObject(t);      
  }

  // sling tree nodes into a list -- requires root to be tail of list
  // only fills in next field, not prev
  private static GlobalObject makeList(GlobalObject root) 
  {
    Tree t;
    GlobalObject gleft;
    GlobalObject gright;
    GlobalObject gttmp;
    GlobalObject retval = root;
    Tree ttmp;
    Join jv = new Join();
 
    if (root == null) return null;
    t = (Tree) root.open("w");
    gleft = makeList(t._left); /* head of left list */
    gright = makeList(t._right); /* head of right list */
    
    if (gright != null) {
      retval = gright;
      gttmp = t._right;
      ttmp = (Tree) gttmp.open("w");
      ttmp._next = root;
      try {
	gttmp.release();
      } catch (AlephException ale) {
	System.err.println("Release failed: " + ale.getMessage());
      }
    }
    if (gleft != null) {
      retval = gleft;
      gttmp = t._left;
      ttmp = (Tree) gttmp.open("w");
      ttmp._next = (gright==null)? root : gright;
      try {
	gttmp.release();
      } catch (AlephException ale) {
	System.err.println("Release failed: " + ale.getMessage());
      }
    }
    
    t._next = null;
    try {
      root.release();
    } catch (AlephException ale) {
      System.err.println("Release failed: " + ale.getMessage());
    }
    return retval;
  }
 
  public void printTree()
  {
    Tree t = (Tree) _root.open("r");
    if (Aleph.getVerbosity() > 0) {
      System.out.println("Building tree of size " + t._size);
      t.print();
    }
    try {
      _root.release();
    } catch (AlephException ale) {
      System.err.println("Release failed: " + ale.getMessage());
    }
  }

  public void printList()
  {
    System.out.println("List: ");
    printList(_root);
  }

  public void printList(GlobalObject root)
  {
    Tree tmp, t;
    GlobalObject gtmp, oldgtmp;
   
    if (root == null) return;
    t = (Tree) root.open("r");
    System.out.println("x = " + t._x + ", y = " + t._y);
    gtmp = t._next;
    try {
      root.release();
    } catch (AlephException ale) {
      System.err.println("Release failed: " + ale.getMessage());
    }

    while(gtmp != root) {
      tmp = (Tree) gtmp.open("r");
      if (Aleph.getVerbosity() > 0)
	System.out.println("x = " + tmp._x + ", y = " + tmp._y);
      oldgtmp = gtmp;
      gtmp = tmp._next;
      try {
	oldgtmp.release();
      } catch (AlephException ale) {
	System.err.println("Release failed: " + ale.getMessage());
      }
    }
  }
  
  // reverse orientation of list
  private static void reverseList(GlobalObject root)
  {
    GlobalObject gprev, gback, gnext, gtmp;
    Tree prev, back, next, tmp, t;
     
    if (root == null) return;
    t = (Tree) root.open("w");

    gprev = t._prev;
    prev = (Tree) gprev.open("w");
    prev._next = null;
    t._prev = null;
    try {
      gprev.release();
    } catch (AlephException ale) {
      System.err.println("Release failed: " + ale.getMessage());
    }

    gback = root;
    gtmp = root;
    root = t._next;
    back = t;

    while(root != null) {
      t = (Tree) root.open("w");
      gnext = t._next;
      t._next = gback;
      back._prev = root;
      try {
	gback.release();
      } catch (AlephException ale) {
	System.err.println("Release failed: " + ale.getMessage());
      }
      gback = root;
      back = t;
      root = gnext;
    }

    try {
      gback.release();
    } catch (AlephException ale) {
      System.err.println("Release failed: " + ale.getMessage());
    }

    tmp = (Tree) gtmp.open("w");
    tmp._next = gprev;
    try {
      gtmp.release();
    } catch (AlephException ale) {
      System.err.println("Release failed: " + ale.getMessage());
    }

    prev = (Tree) gprev.open("w");
    prev._prev = gtmp;
    try {
      gprev.release();
    } catch (AlephException ale) {
      System.err.println("Release failed: " + ale.getMessage());
    }

  }

  private static double distance(Tree a, Tree b) {
    double ax,ay,bx,by;
    
    ax = a._x; ay = a._y;
    bx = b._x; by = b._y;

    return (Math.sqrt((ax-bx)*(ax-bx)+(ay-by)*(ay-by)));
  }
 
  // Use closest-point heuristic from Cormen Leiserson and Rivest
  private static GlobalObject conquer(GlobalObject gt) 
  {  
    Tree cycle, tmp, min, prev, next;
    GlobalObject gdonext;
    GlobalObject gcycle, gmin, gtmp, gnext, gprev;
    Tree t;
    double mindist,test;
    double mintonext, mintoprev, ttonext, ttoprev;
 
    if (gt == null) return null;
    gt = makeList(gt);
    
    // Create initial cycle 
    t = (Tree) gt.open("w");
    gcycle = gt;
    gt = t._next;
    t._next = gcycle;
    t._prev = gcycle;
    try {
      gcycle.release();
    } catch (AlephException ale) {
      System.err.println("Release failed: " + ale.getMessage());
    }

    for (; gt != null; gt=gdonext) { // loop over remaining points 
      t = (Tree) gt.open("w");
      gdonext = t._next; // value won't be around later
      gmin = gcycle;
      cycle = (Tree) gcycle.open("r");
      mindist = distance(t, cycle);
      gtmp = cycle._next;
      try {
	gcycle.release();
      } catch (AlephException ale) {
	System.err.println("Release failed: " + ale.getMessage());
      }

      while (!gtmp.equals(gcycle)) {
	GlobalObject oldgtmp;
	tmp = (Tree) gtmp.open("r");
	test = distance(tmp, t);
	if (test < mindist) {
	  mindist = test;
	  gmin = gtmp;
        } /* if */
	oldgtmp = gtmp;
	gtmp = tmp._next;
	try {
	  oldgtmp.release();
	} catch (AlephException ale) {
	  System.err.println("Release failed: " + ale.getMessage());
	}
      } /* while gtmp... */

      min = (Tree) gmin.open("w");
      gnext = min._next;
      gprev = min._prev;
      /* if next is same as prev they are all min */
      if (!gprev.equals(gmin)) {
	prev = (Tree) gprev.open("w");
	if (!gnext.equals(gprev)) {
	  next = (Tree) gnext.open("w");
	} else {
	  next = prev;
	}
      } else {
	next = min;
	prev = min;
      }
      mintonext = distance(min, next);
      mintoprev = distance(min, prev);
      ttonext = distance(t, next);
      ttoprev = distance(t, prev);
      if ((ttoprev - mintoprev) < (ttonext - mintonext)) {
	/* insert between min and prev */
	prev._next = gt;
	t._next = gmin;
	t._prev = gprev;
	min._prev = gt;
      } else {
	next._prev = gt;
	t._next = gnext;
	min._next = gt;
	t._prev = gmin;
      }
      try {
	gmin.release();
      } catch (AlephException ale) {
	System.err.println("Release failed: " + ale.getMessage());
      }
      if (!gprev.equals(gmin)) {
	try {
	  gprev.release();
	} catch (AlephException ale) {
	  System.err.println("Release failed: " + ale.getMessage());
	}
	if (!gprev.equals(gnext)) {
	  try {
	    gnext.release();
	  } catch (AlephException ale) {
	    System.err.println("Release failed: " + ale.getMessage());
	  }
	}
      }
      try {
	gt.release();
      } catch (AlephException ale) {
	System.err.println("Release failed: " + ale.getMessage());
      }

    } /* for t... */

    return gcycle;
  }
  
  // Merge two cycles as per Karp 
  private static GlobalObject merge(GlobalObject ga, GlobalObject gb, 
				    GlobalObject gt, int nproc)
  {  
    Tree min, next, prev, tmp;
    GlobalObject gmin, gnext, gprev, gtmp;
    Tree a, b, t;
    double mindist, test, mintonext, mintoprev, ttonext, ttoprev;
    Tree n1, p1, n2, p2;
    GlobalObject gn1, gp1, gn2, gp2;
    double tton1, ttop1, tton2, ttop2;
    double n1ton2, n1top2, p1ton2, p1top2;
    int choice;
    int i;
    
    /* Compute location for first cycle */
    gmin = ga;
    a = (Tree) ga.open("r");
    t = (Tree) gt.open("w");
    mindist = distance(t, a);
    gtmp = ga;
    ga = a._next;
    try {
      gtmp.release(); /* release ga from above */
    } catch (AlephException ale) {
      System.err.println("Release failed: " + ale.getMessage());
    }

    for (; !ga.equals(gtmp); ) {
      GlobalObject oldga;
      a = (Tree) ga.open("r");
      test = distance(a, t);
      if (test < mindist) {
	mindist = test;
	gmin = ga;
      } /* if */
      oldga = ga;
      ga = a._next;
      try {
	oldga.release();
      } catch (AlephException ale) {
	System.err.println("Release failed: " + ale.getMessage());
      }
    } /* for ga... */

    min = (Tree) gmin.open("w");
    gnext = min._next;
    gprev = min._prev;
    next = (Tree) gnext.open("w");
    prev = (Tree) gprev.open("w");
    mintonext = distance(min, next);
    mintoprev = distance(min, prev);
    ttonext = distance(t, next);
    ttoprev = distance(t, prev);
    if ((ttoprev - mintoprev) < (ttonext - mintonext)) {
      /* would insert between min and prev */
      gp1 = gprev;
      p1 = prev;
      gn1 = gmin;
      n1 = min;
      tton1 = mindist;
      ttop1 = ttoprev;
      try {
	gnext.release();
      } catch (AlephException ale) {
	System.err.println("Release failed: " + ale.getMessage());
      }
    }
    else { /* would insert between min and next */
      gp1 = gmin;
      p1 = min;
      gn1 = gnext;
      n1 = next;
      ttop1 = mindist;
      tton1 = ttonext;
      try {
	gprev.release();
      } catch (AlephException ale) {
	System.err.println("Release failed: " + ale.getMessage());
      }
    }
    
    /* At this point, I am holding gt,gn1,gp1 in write mode */
    /* Compute location for second cycle */
    gmin = gb;
    b = (Tree) gb.open("r");
    mindist = distance(t, b);
    gtmp = gb;
    gb = b._next;
    try {
      gtmp.release(); /* gb from above */
    } catch (AlephException ale) {
      System.err.println("Release failed: " + ale.getMessage());
    }

    for (; !gb.equals(gtmp); ) {
      GlobalObject oldgb;
      
      b = (Tree) gb.open("r");
      test = distance(b, t);
      if (test < mindist) {
	mindist = test;
	gmin = gb;
      } /* if */
      oldgb = gb;
      gb = b._next;
      try {
	oldgb.release();
      } catch (AlephException ale) {
	System.err.println("Release failed: " + ale.getMessage());
      }
    } /* for tmp... */

    min = (Tree) gmin.open("w");
    gnext = min._next;
    gprev = min._prev;
    next = (Tree) gnext.open("w");
    prev = (Tree) gprev.open("w");
    mintonext = distance(min, next);
    mintoprev = distance(min, prev);
    ttonext = distance(t, next);
    ttoprev = distance(t, prev);
    if ((ttoprev - mintoprev) < (ttonext - mintonext)) {
      /* would insert between min and prev */
      gp2 = gprev;
      p2 = prev;
      gn2 = gmin;
      n2 = min;
      tton2 = mindist;
      ttop2 = ttoprev;
      try {
	gnext.release();
      } catch (AlephException ale) {
	System.err.println("Release failed: " + ale.getMessage());
      }
    }
    else { /* would insert between min and next */
      gp2 = gmin;
      p2 = min;
      gn2 = gnext;
      n2 = next;
      ttop2 = mindist;
      tton2 = ttonext;
      try {
	gprev.release();
      } catch (AlephException ale) {
	System.err.println("Release failed: " + ale.getMessage());
      }
    }
    /* At this point, I am holding gt,gn1,gp1,gn2,gp2 in write mode */
    
    /* Now we have 4 choices to complete:
       1:t,p1 t,p2 n1,n2
       2:t,p1 t,n2 n1,p2
       3:t,n1 t,p2 p1,n2
       4:t,n1 t,n2 p1,p2 */
    n1ton2 = distance(n1, n2);
    n1top2 = distance(n1, p2);
    p1ton2 = distance(p1, n2);
    p1top2 = distance(p1, p2);
    
    mindist = ttop1+ttop2+n1ton2;
    choice = 1;
    
    test = ttop1+tton2+n1top2;
    if (test < mindist) {
      choice = 2;
      mindist = test;
    }
    
    test = tton1+ttop2+p1ton2;
    if (test < mindist) {
      choice = 3;
      mindist = test;
    }
    
    test = tton1+tton2+p1top2;
    if (test < mindist) choice = 4;

    switch (choice) {
    case 1:
      /* 1:p1,t t,p2 n2,n1 -- reverse 2!*/
      try {
	gn2.release();
	gp2.release();
      } catch (AlephException ale) {
	System.err.println("Release failed: " + ale.getMessage());
      }
      reverseList(gn2);

      n2 = (Tree) gn2.open("w");
      p2 = (Tree) gp2.open("w");
      p1._next = gt;
      t._prev = gp1;
      t._next = gp2;
      p2._prev = gt;
      n2._next = gn1;
      n1._prev = gn2;
      break;

    case 2:
      /* 2:p1,t t,n2 p2,n1 -- OK*/
      p1._next = gt;
      t._prev = gp1;
      t._next = gn2;
      n2._prev = gt;
      p2._next = gn1;
      n1._prev = gp2;
      break;

    case 3:
      /* 3:p2,t t,n1 p1,n2 -- OK*/
      p2._next = gt;
      t._prev = gp2;
      t._next = gn1;
      n1._prev = gt;
      p1._next = gn2;
      n2._prev = gp1;
      break;

    case 4:
      /* 4:n1,t t,n2 p2,p1 -- reverse 1!*/
      try {
	gn1.release();
	gp1.release();
      } catch (AlephException ale) {
	System.err.println("Release failed: " + ale.getMessage());
      }
      reverseList(gn1);
      
      n1 = (Tree) gn1.open("w");
      p1 = (Tree) gp1.open("w");
      n1._next = gt;
      t._prev = gn1;
      t._next = gn2;
      n2._prev = gt;
      p2._next = gp1;
      p1._prev = gp2;
      break;
    }

    try {
      gt.release();
      gn1.release();
      gp1.release();
      gn2.release();
      gp2.release();
    } catch (AlephException ale) {
      System.err.println("Release failed: " + ale.getMessage());
    }

    return gt;
  }
  
  // Compute TSP for the tree t -- use conquer for problems <= sz
  public void go() {
    go(_root, 150, PE.numPEs());
  }

  private static GlobalObject go(GlobalObject gt,int sz,int nproc) {
    GlobalObject gleftval;
    GlobalObject grightval;
    GlobalObject gtleft,gtright;
    Tree t;
    int nproc_2 = nproc/2;
    try {
      t = (Tree) gt.open("r");
      gtleft = t._left;
      gtright = t._right;
      try {
	gt.release();
      } catch (AlephException ale) {
	System.err.println("Release failed: " + ale.getMessage());
      }
      if (t._size <= sz) return conquer(gt);
    
      Join jv = new Join();
      PE pid = gtleft.getHome();
      if (pid.equals(PE.thisPE())) {  // Local PE
	gleftval = go(gtleft, sz, nproc_2);
      } else {  // Remote PE
	gleftval = new GlobalObject(new Tree());
	GlobalObject gb = new GlobalObject(gleftval);
	Traveler traveler = new Traveler(gb, gtleft, sz, nproc_2);
	traveler.start(pid, jv);
      }
    
      grightval = go(gtright, sz, nproc_2);
      jv.waitFor();
      return merge(gleftval, grightval, gt, nproc);
    } catch (Exception e) {
      Aleph.panic(e);
      return null;		// not reached
    }
  }
  
  public static void main(String[] args) {
    int nodeNum = 15;
    int flag    = 0;
    try {
      if ( args.length > 1)
	flag = Integer.parseInt(args[1]);
      if ( args.length > 0)
	nodeNum = Integer.parseInt(args[0]);
    } catch (NumberFormatException e) {
      System.err.println("usage: TSP [#nodeNum] [#flag]");
      Aleph.exit(1);
    }

    TSP tsp = new TSP(nodeNum);
    if (flag>=1) tsp.printTree();
    
    long start = System.currentTimeMillis();
    tsp.go();
    long end = System.currentTimeMillis();
    if (flag>=1) tsp.printList();
    System.out.println("Elapsed time: " +
		       ((double) (end - start)) / 1000.0
		       + " seconds");
  }

  static class Tree implements Serializable{

    public int _size;
    public double _x;
    public double _y;
    public GlobalObject _left;
    public GlobalObject _right;
    public GlobalObject _next;
    public GlobalObject _prev;

    Tree() {
    }
     
    public void print() {
      print(0);
    }

    public void print(int level) {
      Tree t;
      
      for (int i=0; i<level; i++)
	System.out.print("    ");
	System.out.println("x = " + _x + ", y = " + _y);

      if (_left != null) {
	for (int i=0; i<level; i++)
	  System.out.print("    ");
	System.out.println("Left");
	t = (Tree) _left.open("r");
	t.print(level+1);
	try {
	  _left.release();
	} catch (AlephException ale) {
	  System.err.println("Release failed: " + ale.getMessage());
	}
      }
      if (_right != null) {
	for (int i=0; i<level; i++)
	  System.out.print("    ");
	System.out.println("Right");
	t = (Tree) _right.open("r");
	t.print(level+1);
	try {
	  _right.release();
	} catch (AlephException ale) {
	  System.err.println("Release failed: " + ale.getMessage());
	}
      }
    }

  }  // Tree
  
  static class Builder extends RemoteThread {
    GlobalObject root;
    int n, dir, peNo, numPE;
    double minX, maxX, minY, maxY;
    
    Builder(GlobalObject root, int n, int dir, int peNo, int numPE, double minX, 
	    double maxX, double minY, double maxY) {
      this.root = root;
      this.n = n;
      this.dir = dir;
      this.peNo = peNo;
      this.numPE = numPE;
      this.minX = minX;
      this.maxX = maxX;
      this.minY = minY;
      this.maxY = maxY;
    }

    public void run() {
      Tree t = (Tree) root.open("w");
      if (Aleph.getVerbosity() > 0)
	System.out.print("Left ");
      t._left = buildTree(n, dir, peNo, numPE, minX, maxX, minY, maxY);
      try {
      root.release();
      } catch (AlephException ale) {
	System.err.println("Release failed: " + ale.getMessage());
      }
    }
  }  // Builder

  static class Traveler extends RemoteThread {
    GlobalObject gg, gt;
    int sz, nproc;
    
    Traveler(GlobalObject gg, GlobalObject gt, int sz, int nproc) {
      this.gg = gg;
      this.gt = gt;
      this.sz = sz;
      this.nproc = nproc;
    }

    public void run() {
      GlobalObject gleftval = (GlobalObject) gg.open("w");
      GlobalObject gtleft = (GlobalObject) gt.open("r");
      gleftval = go(gtleft, sz, nproc);
      try {
	gg.release();
	gt.release();
      } catch (AlephException ale) {
	System.err.println("Release failed: " + ale.getMessage());
      }
    }
  }  // Traveler

}
