Index: /branches/eam_branches/ipp-20101103/Ohana/src/addstar/Makefile
===================================================================
--- /branches/eam_branches/ipp-20101103/Ohana/src/addstar/Makefile	(revision 29789)
+++ /branches/eam_branches/ipp-20101103/Ohana/src/addstar/Makefile	(revision 29790)
@@ -72,4 +72,6 @@
 $(SRC)/replace_match.$(ARCH).o \
 $(SRC)/resort_catalog.$(ARCH).o \
+$(SRC)/resort_threaded.$(ARCH).o \
+$(SRC)/resort_unthreaded.$(ARCH).o \
 $(SRC)/StarOps.$(ARCH).o \
 $(SRC)/ReadStarsFITS.$(ARCH).o \
Index: /branches/eam_branches/ipp-20101103/Ohana/src/addstar/include/addstar.h
===================================================================
--- /branches/eam_branches/ipp-20101103/Ohana/src/addstar/include/addstar.h	(revision 29789)
+++ /branches/eam_branches/ipp-20101103/Ohana/src/addstar/include/addstar.h	(revision 29790)
@@ -125,4 +125,6 @@
 float   ZPT_ERR_PHU;
 
+int     OLD_RESORT;
+
 // carries the mosaic into gstars
 
@@ -135,4 +137,5 @@
 double MIN_FWHM_X;
 double MIN_FWHM_Y;
+int    NTHREADS;
 
 /* modify server behavior (make this an addstar cleanup mode?) */
@@ -207,5 +210,8 @@
 Stars     *rd_gsc                 PROTO((char *filename, unsigned int *nstars));
 int        replace_match          PROTO((Average *average, Measure *measure, Stars *star));
+int        resort_threaded        PROTO((AddstarClientOptions *options, SkyTable *sky));
+int        resort_unthreaded      PROTO((AddstarClientOptions *options, SkyTable *sky));
 void       resort_catalog         PROTO((Catalog *catalog));
+void       resort_catalog_old     PROTO((Catalog *catalog));
 Stars     *ReadStarsFITS          PROTO((FILE *f, Header *header, Header *in_theader, unsigned int *nstars));
 Stars     *ReadStarsTEXT          PROTO((FILE *f, unsigned int *nstars));
Index: /branches/eam_branches/ipp-20101103/Ohana/src/addstar/src/addstar.c
===================================================================
--- /branches/eam_branches/ipp-20101103/Ohana/src/addstar/src/addstar.c	(revision 29789)
+++ /branches/eam_branches/ipp-20101103/Ohana/src/addstar/src/addstar.c	(revision 29790)
@@ -34,4 +34,13 @@
   SkyTableSetFilenames (sky, CATDIR, "cpt");
   
+  if (options.mode == M_RESORT) {
+    if (NTHREADS == 0) {
+      resort_unthreaded (&options, sky);
+    } else {
+      resort_threaded (&options, sky);
+    }
+    exit (0);
+  }
+
   stars = NULL;
 
@@ -157,5 +166,9 @@
 	}
 
-	resort_catalog (&catalog);
+	if (OLD_RESORT) { 
+	  resort_catalog_old (&catalog);
+	} else {
+	  resort_catalog (&catalog);
+	}
 	Nsubset = 1;
 	break;
Index: /branches/eam_branches/ipp-20101103/Ohana/src/addstar/src/args.c
===================================================================
--- /branches/eam_branches/ipp-20101103/Ohana/src/addstar/src/args.c	(revision 29789)
+++ /branches/eam_branches/ipp-20101103/Ohana/src/addstar/src/args.c	(revision 29790)
@@ -34,4 +34,9 @@
     remove_argument (N, &argc, argv);
   }
+  OLD_RESORT = FALSE;
+  if ((N = get_argument (argc, argv, "-old-resort"))) {
+    remove_argument (N, &argc, argv);
+    OLD_RESORT = TRUE;
+  }
   if ((N = get_argument (argc, argv, "-fakeimage"))) {
     options.mode = M_FAKEIMAGE;
@@ -62,4 +67,12 @@
     remove_argument (N, &argc, argv);
     PMM_CCD_TABLE = strcreate (argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+
+  // number of worker threads for resort (must have at least one worker thread)
+  NTHREADS = 0;
+  if ((N = get_argument (argc, argv, "-threads"))) {
+    remove_argument (N, &argc, argv);
+    NTHREADS = MAX(0, atoi (argv[N]));
     remove_argument (N, &argc, argv);
   }
Index: /branches/eam_branches/ipp-20101103/Ohana/src/addstar/src/resort_catalog.c
===================================================================
--- /branches/eam_branches/ipp-20101103/Ohana/src/addstar/src/resort_catalog.c	(revision 29789)
+++ /branches/eam_branches/ipp-20101103/Ohana/src/addstar/src/resort_catalog.c	(revision 29790)
@@ -1,5 +1,5 @@
 # include "addstar.h"
 
-void resort_catalog (Catalog *catalog) {
+void resort_catalog_old (Catalog *catalog) {
 
   off_t *next_meas;
@@ -28,2 +28,110 @@
   return;
 }
+
+# define myAbort(MSG) { fprintf (stderr, "%s\n", MSG); abort(); }
+# define myAssert(LOGIC,MSG) { if (!(LOGIC)) { fprintf (stderr, "%s\n", MSG); abort(); } }
+
+// sort the measure Sequence based on the average Sequence entries
+void SortAveMeasMatch (off_t *MEAS, off_t *AVE, off_t N) {
+
+# define SWAPFUNC(A,B){ off_t tmp_meas; off_t tmp_ave;	\
+    tmp_meas = MEAS[A]; MEAS[A] = MEAS[B]; MEAS[B] = tmp_meas;		\
+    tmp_ave  = AVE[A];  AVE[A]  = AVE[B];  AVE[B]  = tmp_ave;		\
+  }
+# define COMPARE(A,B)(MEAS[A] < MEAS[B])
+  OHANA_SORT (N, COMPARE, SWAPFUNC);
+# undef SWAPFUNC
+# undef COMPARE
+}
+
+# define MARKTIME(MSG,...) { \
+  float dtime; \
+  gettimeofday (&stop, (void *) NULL); \
+  dtime = DTIME (stop, start); start = stop; \
+  fprintf (stderr, MSG, __VA_ARGS__); }
+
+// XXX : where is the time going?  perhaps the ALLOCATE?
+// XXX : I don't thnk his is getting the right answer yet.
+
+void resort_catalog (Catalog *catalog) {
+
+  off_t Naverage, Nmeasure;
+  Measure *measure;
+  Average *average;
+  off_t i, j, N, currentAve;
+
+  struct timeval start, stop;
+
+  off_t *measureSeq = NULL;
+  off_t *averageSeq = NULL;
+  Measure *measureTMP = NULL;
+
+  if (catalog[0].sorted == TRUE) return;
+
+  gettimeofday (&start, NULL);
+
+  /* internal counters */
+  Nmeasure = catalog[0].Nmeasure;
+  Naverage = catalog[0].Naverage;
+
+  measure = catalog[0].measure;
+  average = catalog[0].average;
+  
+  // we have a table of average objects and an unsorted table of measurements.  each measurement
+  // has a reference to the average object sequence (as well as an ID)
+  // measure[i].averef -> average[averef]
+  // measure[i].objID = average[averef].objID
+  // measure[i].catID = average[averef].catID
+
+  // we want a sorted measure array with all averef entries in sequence
+
+  ALLOCATE (measureSeq, off_t,   Nmeasure);
+  ALLOCATE (averageSeq, off_t,   Nmeasure);
+
+  for (i = 0; i < Nmeasure; i++) {
+    measureSeq[i] = i;
+    averageSeq[i] = measure[i].averef;
+    
+    myAssert(average[averageSeq[i]].objID == measure[measureSeq[i]].objID, "object / detection mismatch");
+    myAssert(average[averageSeq[i]].catID == measure[measureSeq[i]].catID, "object / detection mismatch");
+  }
+  
+  SortAveMeasMatch(measureSeq, averageSeq, Nmeasure);
+  // MARKTIME("sort : %f sec\n", dtime);
+
+  // copy the measurements in the sorted order
+  ALLOCATE (measureTMP, Measure, Nmeasure);
+  for (i = 0; i < Nmeasure; i++) {
+    j = measureSeq[i];
+    measureTMP[i] = measure[j];
+  }
+  // MARKTIME("assign measure : %f sec\n", dtime);
+
+  // update the values of average.measureOffset and average.Nmeasure
+  FREE(measure);
+  catalog[0].measure = measureTMP;
+
+  N = 0;
+  currentAve = averageSeq[0];
+  average[currentAve].measureOffset = 0;
+  for (i = 0; i < Nmeasure; i++) {
+    if (averageSeq[i] != currentAve) {
+      average[currentAve].Nmeasure = N;
+      N = 0;
+      currentAve = averageSeq[i];
+      average[currentAve].measureOffset = i;
+    }
+    N++;
+  }
+  N++;
+  average[currentAve].Nmeasure = N;
+  // MARKTIME("update Nmeasure : %f sec\n", dtime);
+
+  MARKTIME("  match time %9.4f sec for %7lld measures, %6lld average\n", dtime, (long long) Nmeasure, (long long) Naverage);
+
+  FREE (measureSeq);
+  FREE (averageSeq);
+
+  return;
+}
+
Index: /branches/eam_branches/ipp-20101103/Ohana/src/addstar/src/resort_threaded.c
===================================================================
--- /branches/eam_branches/ipp-20101103/Ohana/src/addstar/src/resort_threaded.c	(revision 29790)
+++ /branches/eam_branches/ipp-20101103/Ohana/src/addstar/src/resort_threaded.c	(revision 29790)
@@ -0,0 +1,240 @@
+# include "addstar.h"
+# include <pthread.h>
+
+enum {
+  TS_WAIT,
+  TS_RUN,
+  TS_DONE,
+  TS_FAIL,
+  TS_EXIT,
+};
+
+typedef struct {
+  int iThread;
+  int nThread;
+  int state;
+  int nosort;
+  char *filename;
+  SkyRegion *region;
+  off_t Naverage;
+  off_t Nmeasure;
+} ThreadData;
+
+// the thread manager has three states: 
+// WAIT : no data is available for this thread
+// RUN  : data is available, attempt to process
+// DONE : completed the analysis, wait for harvest
+// FAIL : serious error resulting in system shutdown
+
+void *resort_thread_job (void *inputData) {
+
+  Catalog catalog;
+  ThreadData *threadData = (ThreadData *) inputData;
+
+  while (TRUE) {
+
+    while (threadData->state != TS_RUN) {
+      usleep (10000);
+    }
+
+    // set the parameters which guide catalog open/load/create
+    catalog.filename  = threadData->filename;
+    catalog.catformat = dvo_catalog_catformat (CATFORMAT);  // set the default catformat from config data
+    catalog.catmode   = dvo_catalog_catmode (CATMODE);      // set the default catmode from config data
+    catalog.catflags  = LOAD_AVES | LOAD_MEAS;
+    catalog.Nsecfilt  = GetPhotcodeNsecfilt ();
+  
+    // an error exit status here is a significant error (disk I/O or file access)
+    if (!dvo_catalog_open (&catalog, threadData->region, VERBOSE, "w")) {
+      fprintf (stderr, "ERROR: failure to open/create catalog file %s\n", catalog.filename);
+      threadData->Naverage = 0;
+      threadData->Nmeasure = 0;
+      threadData->state = TS_FAIL;
+      continue;
+    }
+
+    // Naves_disk == 0 implies an empty catalog file, skip empty catalogs
+    if (catalog.Naves_disk == 0) {
+      dvo_catalog_unlock (&catalog);
+      dvo_catalog_free (&catalog);
+      threadData->Naverage = 0;
+      threadData->Nmeasure = 0;
+      threadData->state = TS_DONE;
+      continue;
+    }
+
+    // this is an overloaded value to mean 'force sort'
+    if (threadData->nosort == 3) catalog.sorted = FALSE;
+
+    if (OLD_RESORT) { 
+      resort_catalog_old (&catalog);
+    } else {
+      resort_catalog (&catalog);
+    }
+
+    /* report total updated values */
+    threadData->Naverage = catalog.Naverage;
+    threadData->Nmeasure = catalog.Nmeasure;
+    // fprintf (stderr, "done in thread %d, %d aves, %d meas\n", threadData->iThread, (int) threadData->Naverage, (int) threadData->Nmeasure);
+
+    // write out catalog, if appropriate
+    SetProtect (TRUE);
+    dvo_catalog_save (&catalog, VERBOSE);
+    SetProtect (FALSE);
+    dvo_catalog_unlock (&catalog);
+    dvo_catalog_free (&catalog);
+    threadData->state = TS_DONE;
+  }
+  return NULL;
+}
+
+int resort_threaded (AddstarClientOptions *options, SkyTable *sky) {
+
+  int j, launched, done, Nwait, Nrun;
+
+  off_t i;
+  off_t Naverage, Nmeasure;
+
+  ThreadData *threadData;
+  pthread_t *threads;
+
+  double dtime;
+  struct timeval start, stop;
+
+  gettimeofday (&start, NULL);
+
+  SkyList *skylist = SkyListByPatch (sky, -1, &UserPatch);
+  
+  // in these cases, limit the sky catalogs to an existing subset
+  if (options->existing_regions) {
+    SkyList *tmp;
+    tmp = SkyListExistingSubset (skylist, CATDIR);
+    SkyListFree (skylist);
+    skylist = tmp;
+  }
+  if (VERBOSE) fprintf (stderr, "writing to "OFF_T_FMT" regions\n",  skylist[0].Nregions);
+
+  if (options->only_images) {
+    fprintf (stderr, "-image (only images) makes no sense with -resort\n");
+    exit (2);
+  }
+  if (options->only_match) {
+    fprintf (stderr, "-only-match makes no sense with -resort\n");
+    exit (2);
+  }
+  if (options->nosort == 1) {
+    fprintf (stderr, "-nosort makes no sense with -resort\n");
+    exit (2);
+  }
+  if (options->update) {
+    fprintf (stderr, "-update makes no sense with -resort\n");
+    exit (2);
+  }
+  if (options->calibrate) {
+    fprintf (stderr, "-cal (calibrate) makes no sense with -resort\n");
+    exit (2);
+  }
+
+  ALLOCATE(threads, pthread_t, NTHREADS);
+  ALLOCATE(threadData, ThreadData, NTHREADS);
+  for (i = 0; i < NTHREADS; i++) {
+    threadData[i].state = TS_WAIT;
+    threadData[i].iThread = i;
+  }
+  for (i = 0; i < NTHREADS; i++) {
+    pthread_create (&threads[i], NULL, &resort_thread_job, (void *) &threadData[i]);
+  }
+
+  // if we catch a failure at any point, goto the failure block and wait for threads to finish
+  // continue in this loop until all regions have been launched (though not finished)
+  Naverage = Nmeasure = 0;
+  for (i = 0; i < skylist[0].Nregions; i++) {
+
+    launched = FALSE;
+    while (!launched) {
+
+      // are any threads done? can we harvest them?
+      for (j = 0; j < NTHREADS; j++) {
+	if (threadData[j].state == TS_FAIL) goto failure;
+	if (threadData[j].state != TS_DONE) continue;
+	Naverage += threadData[j].Naverage;
+	Nmeasure += threadData[j].Nmeasure;
+	// fprintf (stderr, "harvested thread %d, %d aves, %d meas (sums: %d aves, %d meas)\n", threadData[j].iThread, (int) threadData[j].Naverage, (int) threadData[j].Nmeasure, (int) Naverage, (int) Nmeasure);
+	threadData[j].state = TS_WAIT;
+      }
+	
+      // are any threads available? can we launch this job?
+      for (j = 0; !launched && (j < NTHREADS); j++) {
+	if (threadData[j].state == TS_FAIL) goto failure;
+	if (threadData[j].state != TS_WAIT) continue;
+	threadData[j].nosort = options->nosort;
+	threadData[j].filename = skylist[0].filename[i];
+	threadData[j].region = skylist[0].regions[i];
+	threadData[j].state = TS_RUN;
+	launched = TRUE;
+      }
+      if (!launched) usleep (10000);
+    }
+  }
+
+  // wait for all threads to be done
+  done = FALSE;
+  while (!done) {
+    // are any threads done? can we harvest them?
+    for (j = 0; j < NTHREADS; j++) {
+      if (threadData[j].state == TS_FAIL) goto failure;
+      if (threadData[j].state != TS_DONE) continue;
+      Naverage += threadData[j].Naverage;
+      Nmeasure += threadData[j].Nmeasure;
+      threadData[j].state = TS_WAIT;
+    }
+
+    // how many are waiting now?
+    Nwait = 0;
+    for (j = 0; j < NTHREADS; j++) {
+      if (threadData[j].state == TS_FAIL) goto failure;
+      if (threadData[j].state != TS_WAIT) continue;
+      Nwait ++;
+    }
+
+    if (Nwait < NTHREADS) { 
+      usleep (10000); 
+    } else {
+      done = TRUE;
+    }
+  }
+
+  gettimeofday (&stop, NULL);
+  dtime = DTIME (stop, start);
+  fprintf (stderr, "SUCCESS: elapsed time %9.4f sec for, "OFF_T_FMT" average, "OFF_T_FMT" measure\n", dtime, Naverage, Nmeasure);
+
+  // for (i = 0; i < NTHREADS; i++) {
+  //   pthread_cancel(threads[i]);
+  // }
+  // free (threads);
+  free (threadData);
+
+  return TRUE;
+
+failure:
+
+  // wait for all threads to be done
+  done = FALSE;
+  while (!done) {
+
+    // how many are still running?
+    Nrun = 0;
+    for (j = 0; j < NTHREADS; j++) {
+      if (threadData[j].state != TS_RUN) continue;
+      Nrun ++;
+    }
+
+    if (Nrun) { 
+      usleep (10000); 
+    } else {
+      done = TRUE;
+    }
+  }
+  exit (1);
+}
+
Index: /branches/eam_branches/ipp-20101103/Ohana/src/addstar/src/resort_unthreaded.c
===================================================================
--- /branches/eam_branches/ipp-20101103/Ohana/src/addstar/src/resort_unthreaded.c	(revision 29790)
+++ /branches/eam_branches/ipp-20101103/Ohana/src/addstar/src/resort_unthreaded.c	(revision 29790)
@@ -0,0 +1,105 @@
+# include "addstar.h"
+
+int resort_unthreaded (AddstarClientOptions *options, SkyTable *sky) {
+
+  off_t i;
+  off_t Naverage, Nmeasure;
+  Catalog catalog;
+
+  double dtime;
+  struct timeval start, stop;
+
+  gettimeofday (&start, NULL);
+
+  SkyList *skylist = SkyListByPatch (sky, -1, &UserPatch);
+  
+  // in these cases, limit the sky catalogs to an existing subset
+  if (options->existing_regions) {
+    SkyList *tmp;
+    tmp = SkyListExistingSubset (skylist, CATDIR);
+    SkyListFree (skylist);
+    skylist = tmp;
+  }
+  if (VERBOSE) fprintf (stderr, "writing to "OFF_T_FMT" regions\n",  skylist[0].Nregions);
+
+  if (options->only_images) {
+    fprintf (stderr, "-image (only images) makes no sense with -resort\n");
+    exit (2);
+  }
+  if (options->only_match) {
+    fprintf (stderr, "-only-match makes no sense with -resort\n");
+    exit (2);
+  }
+  if (options->nosort == 1) {
+    fprintf (stderr, "-nosort makes no sense with -resort\n");
+    exit (2);
+  }
+  if (options->update) {
+    fprintf (stderr, "-update makes no sense with -resort\n");
+    exit (2);
+  }
+  if (options->calibrate) {
+    fprintf (stderr, "-cal (calibrate) makes no sense with -resort\n");
+    exit (2);
+  }
+
+  // XXX ALLOCATE catalog[Nthreads]
+
+  /* match stars to existing catalog data (or otherwise manipulate catalog data) */
+  Naverage = Nmeasure = 0;
+  for (i = 0; i < skylist[0].Nregions; i++) {
+
+    // XXX BLOCK here for an empty thread
+
+    // set the parameters which guide catalog open/load/create
+    catalog.filename  = skylist[0].filename[i];
+    catalog.catformat = dvo_catalog_catformat (CATFORMAT);  // set the default catformat from config data
+    catalog.catmode   = dvo_catalog_catmode (CATMODE);      // set the default catmode from config data
+    catalog.catflags  = LOAD_AVES | LOAD_MEAS;
+    catalog.Nsecfilt  = GetPhotcodeNsecfilt ();
+
+    // an error exit status here is a significant error (disk I/O or file access)
+    if (!dvo_catalog_open (&catalog, skylist[0].regions[i], VERBOSE, "w")) {
+      fprintf (stderr, "ERROR: failure to open/create catalog file %s\n", catalog.filename);
+      exit (2);
+    }
+
+    // Naves_disk == 0 implies an empty catalog file, skip empty catalogs
+    if (catalog.Naves_disk == 0) {
+      dvo_catalog_unlock (&catalog);
+      dvo_catalog_free (&catalog);
+      continue;
+    }
+
+    // this is an overloaded value to mean 'force sort'
+    if (options->nosort == 3) catalog.sorted = FALSE;
+
+    // XXX send these to a free thread
+
+    if (OLD_RESORT) { 
+      resort_catalog_old (&catalog);
+    } else {
+      resort_catalog (&catalog);
+    }
+
+    // XXX wait for completed threads
+
+    /* report total updated values */
+    Naverage += catalog.Naverage;
+    Nmeasure += catalog.Nmeasure;
+
+    // write out catalog, if appropriate
+    SetProtect (TRUE);
+    dvo_catalog_save (&catalog, VERBOSE);
+    SetProtect (FALSE);
+    dvo_catalog_unlock (&catalog);
+    dvo_catalog_free (&catalog);
+  }
+
+  gettimeofday (&stop, NULL);
+  dtime = DTIME (stop, start);
+  fprintf (stderr, "SUCCESS: elapsed time %9.4f sec for, "OFF_T_FMT" average, "OFF_T_FMT" measure\n", dtime, Naverage, Nmeasure);
+
+  return TRUE;
+}
+
Index: /branches/eam_branches/ipp-20101103/Ohana/src/addstar/test/dvomerge.dvo
===================================================================
--- /branches/eam_branches/ipp-20101103/Ohana/src/addstar/test/dvomerge.dvo	(revision 29789)
+++ /branches/eam_branches/ipp-20101103/Ohana/src/addstar/test/dvomerge.dvo	(revision 29790)
@@ -4,5 +4,5 @@
 
 # create 2 populated catdirs, each with a couple of cmf files
-macro test.dvomerge.update
+macro test.dvomerge.continue
 
   tapPLAN 51
@@ -16,4 +16,123 @@
 
   mkinput
+  exec mkcmf test.in.txt test.cmf -date 2008/1/1 -time 01:00:00 -radec 10.0 20.0
+  exec addstar -D CATDIR catdir.test1 -D CAMERA simtest test.cmf
+
+  exec mkcmf test.in.txt test.cmf -date 2008/1/1 -time 02:00:00 -radec 10.0 20.0
+  exec addstar -D CATDIR catdir.test1 -D CAMERA simtest test.cmf
+
+  exec mkcmf test.in.txt test.cmf -date 2008/1/1 -time 03:00:00 -radec 9.9 20.0
+  exec addstar -D CATDIR catdir.test2 -D CAMERA simtest test.cmf
+
+  exec mkcmf test.in.txt test.cmf -date 2008/1/1 -time 04:00:00 -radec 9.9 20.0
+  exec addstar -D CATDIR catdir.test2 -D CAMERA simtest test.cmf
+
+  exec mkcmf test.in.txt test.cmf -date 2008/1/1 -time 05:00:00 -radec 10.0 19.9
+  exec addstar -D CATDIR catdir.test1 -D CAMERA simtest test.cmf
+
+  exec mkcmf test.in.txt test.cmf -date 2008/1/1 -time 06:00:00 -radec 10.0 19.9
+  exec addstar -D CATDIR catdir.test1 -D CAMERA simtest test.cmf
+
+  exec mkcmf test.in.txt test.cmf -date 2008/1/1 -time 07:00:00 -radec 9.9 19.9
+  exec addstar -D CATDIR catdir.test2 -D CAMERA simtest test.cmf
+
+  exec mkcmf test.in.txt test.cmf -date 2008/1/1 -time 08:00:00 -radec 9.9 19.9
+  exec addstar -D CATDIR catdir.test2 -D CAMERA simtest test.cmf
+
+  break
+
+  exec rsync -auc catdir.test2/ catdir.test3/
+
+  date -var t1 -seconds -reftime 1276000000
+  exec dvomerge catdir.test1 into catdir.test3
+  date -var t2 -seconds -reftime 1276000000
+  echo "merge time: {$t2 - $t1}"
+
+  catdir catdir.test3
+  skyregion {$RA-1} {$RA+1} {$DEC-1} {$DEC+1} 
+  mextract ra dec mag
+  create n 0 ra[]
+  subset r0 = ra if (n % 4 == 0)
+  subset r1 = ra if (n % 4 == 1)
+  subset r2 = ra if (n % 4 == 2)
+  subset r3 = ra if (n % 4 == 3)
+
+  catdir catdir.test1/
+  mextract RA DEC MAG
+  create N 0 RA[]
+  subset R0 = RA if (N % 2 == 0)
+  subset R1 = RA if (N % 2 == 1)
+
+  set dr0 = r0 - R0
+  vstat -q dr0
+  tapOK {abs($MEAN)  < 0.001} "ra (in - 0) vs ra (out - 0) (MEAN)"
+  tapOK {abs($SIGMA) < 0.001} "ra (in - 0) vs ra (out - 0) (SIGMA)"
+
+  set dr1 = r1 - R1
+  vstat -q dr1
+  tapOK {abs($MEAN)  < 0.001} "ra (in - 0) vs ra (out - 0) (MEAN)"
+  tapOK {abs($SIGMA) < 0.001} "ra (in - 0) vs ra (out - 0) (SIGMA)"
+
+  catdir catdir.test2/
+  mextract RA DEC MAG
+  create N 0 RA[]
+  subset R2 = RA if (N % 2 == 0)
+  subset R3 = RA if (N % 2 == 1)
+
+  set dr2 = r2 - R2
+  vstat -q dr2
+  tapOK {abs($MEAN)  < 0.001} "ra (in - 0) vs ra (out - 0) (MEAN)"
+  tapOK {abs($SIGMA) < 0.001} "ra (in - 0) vs ra (out - 0) (SIGMA)"
+
+  set dr3 = r3 - R3
+  vstat -q dr3
+  tapOK {abs($MEAN)  < 0.001} "ra (in - 0) vs ra (out - 0) (MEAN)"
+  tapOK {abs($SIGMA) < 0.001} "ra (in - 0) vs ra (out - 0) (SIGMA)"
+
+  # check on updates to imageID
+  catdir catdir.test3
+  imextract imageID
+  sort imageID
+  tapOK {imageID[]  == 4} "image IDs exist"
+  tapOK {imageID[0] == 1} "updated image IDs"
+  tapOK {imageID[1] == 2} "updated image IDs"
+  tapOK {imageID[2] == 3} "updated image IDs"
+  tapOK {imageID[3] == 4} "updated image IDs"
+
+  catdir catdir.test3
+  mextract imageID, time
+  set id = imageID
+  set t = time
+  imextract imageID, time
+
+  for i 0 time[]
+    subset T = t if (id == imageID[$i])
+    set dT = T - time[$i]
+    vstat dT 
+    tapOK {abs($MEAN)  < 0.00001} "time for measure ID $i (MEAN)"
+    tapOK {abs($SIGMA) < 0.00001} "time for measure ID $i (SIGMA)"
+  end
+
+  # exec rm test.in.txt test.cmf
+  # exec rm -rf catdir.test1
+  # exec rm -rf catdir.test2
+  # exec rm -rf catdir.test3
+
+  tapDONE
+end
+
+# create 2 populated catdirs, each with a couple of cmf files
+macro test.dvomerge.update
+
+  tapPLAN 51
+
+  exec rm -rf catdir.test1
+  exec rm -rf catdir.test2
+  exec rm -rf catdir.test3
+
+  $RA = 10.0
+  $DEC = 20.0
+
+  mkinput
   exec mkcmf test.in.txt test.cmf -date 2008/1/1 -time 01:00:00 -radec $RA $DEC
   exec addstar -D CATDIR catdir.test1 -D CAMERA simtest test.cmf
@@ -27,4 +146,6 @@
   exec mkcmf test.in.txt test.cmf -date 2008/1/1 -time 04:00:00 -radec $RA $DEC
   exec addstar -D CATDIR catdir.test2 -D CAMERA simtest test.cmf
+
+  break
 
   exec rsync -auc catdir.test2/ catdir.test3/
