Index: trunk/Ohana/src/relastro/Makefile
===================================================================
--- trunk/Ohana/src/relastro/Makefile	(revision 35105)
+++ trunk/Ohana/src/relastro/Makefile	(revision 35416)
@@ -64,4 +64,6 @@
 $(SRC)/high_speed_objects.$(ARCH).o  \
 $(SRC)/high_speed_utils.$(ARCH).o  \
+$(SRC)/hpm_catalogs.$(ARCH).o  \
+$(SRC)/hpm_objects.$(ARCH).o  \
 $(SRC)/relastro_merge_source.$(ARCH).o  \
 $(SRC)/resort_catalog.$(ARCH).o  \
@@ -106,4 +108,6 @@
 $(SRC)/high_speed_objects.$(ARCH).o  \
 $(SRC)/high_speed_utils.$(ARCH).o  \
+$(SRC)/hpm_catalogs.$(ARCH).o  \
+$(SRC)/hpm_objects.$(ARCH).o  \
 $(SRC)/relastro_merge_source.$(ARCH).o  \
 $(SRC)/resort_catalog.$(ARCH).o  \
Index: trunk/Ohana/src/relastro/include/relastro.h
===================================================================
--- trunk/Ohana/src/relastro/include/relastro.h	(revision 35105)
+++ trunk/Ohana/src/relastro/include/relastro.h	(revision 35416)
@@ -20,5 +20,5 @@
 typedef enum {FIT_NONE, FIT_AVERAGE, FIT_PM_ONLY, FIT_PAR_ONLY, FIT_PM_AND_PAR} FitMode;
 
-typedef enum {TARGET_NONE, TARGET_SIMPLE, TARGET_CHIPS, TARGET_MOSAICS, TARGET_HIGH_SPEED, TARGET_MERGE_SOURCE, TARGET_UPDATE_OBJECTS, TARGET_UPDATE_OFFSETS, TARGET_LOAD_OBJECTS} FitTarget;
+typedef enum {TARGET_NONE, TARGET_SIMPLE, TARGET_CHIPS, TARGET_MOSAICS, TARGET_HIGH_SPEED, TARGET_MERGE_SOURCE, TARGET_UPDATE_OBJECTS, TARGET_UPDATE_OFFSETS, TARGET_LOAD_OBJECTS, TARGET_HPM} FitTarget;
 
 typedef enum {
@@ -442,2 +442,6 @@
 
 PhotCode **ParsePhotcodeList (char *rawlist, int *nphotcodes, int needAve);
+
+int hpm_catalogs (SkyTable *sky, SkyList *skylist, int hostID, char *hostpath);
+int hpm_catalogs_parallel (SkyList *skylist);
+int hpm_objects (SkyRegion *region, Catalog *catalog);
Index: trunk/Ohana/src/relastro/src/UpdateObjects.c
===================================================================
--- trunk/Ohana/src/relastro/src/UpdateObjects.c	(revision 35105)
+++ trunk/Ohana/src/relastro/src/UpdateObjects.c	(revision 35416)
@@ -224,5 +224,5 @@
 
       if ((mode == FIT_PM_ONLY) || (mode == FIT_PM_AND_PAR)) {
-	// project all of the R,D coordinates to a plane centered on this coordinate set
+	// project all of the R,D coordinates to a plane centered on this coordinate. set
 	// the times to be relative to Tmean (this is required for parallax as well)
 	for (k = 0; k < N; k++) {
@@ -270,4 +270,7 @@
 	  catalog[i].average[j].flags |= ID_STAR_FIT_PAR;
 	  Npar ++;
+	} else {
+	  // need to set mode = FIT_PM_ONLY if we do not fit for parallax
+	  mode = FIT_PM_ONLY;
 	}
       }	  
@@ -321,5 +324,5 @@
 			  fit.uD);
 
-      //make sure that the fit succeeded
+      // make sure that the fit succeeded
       status = TRUE;
       status &= finite(fit.Ro);
@@ -364,6 +367,6 @@
       catalog[i].average[j].ChiSqPM   = fitPM.chisq;
       catalog[i].average[j].ChiSqPar  = fitPAR.chisq;
-      catalog[i].average[j].Tmean = (Tmean * 86400 * 365.26) + T2000;
-      catalog[i].average[j].Trange = (Trange * 86400 * 365.26);
+      catalog[i].average[j].Tmean = (Tmean * 86400 * 365.25) + T2000;
+      catalog[i].average[j].Trange = (Trange * 86400 * 365.25);
       catalog[i].average[j].Npos = fit.Nfit;
       if (XVERB) fprintf (stderr, "%f %f -> %f %f (%f,%f) pm=(%f %f) chisq=(%f, %f, %f)\n",
@@ -385,5 +388,5 @@
   }
 
-  fprintf (stderr, "fitted "OFF_T_FMT" objects ("OFF_T_FMT" ave, "OFF_T_FMT" pm, "OFF_T_FMT" par), skipped "OFF_T_FMT"\n",  (NaveSum + NpmSum + NparSum),  NaveSum,  NpmSum,  NparSum,  NskipSum);
+  if (VERBOSE) fprintf (stderr, "fitted "OFF_T_FMT" objects ("OFF_T_FMT" ave, "OFF_T_FMT" pm, "OFF_T_FMT" par), skipped "OFF_T_FMT"\n",  (NaveSum + NpmSum + NparSum),  NaveSum,  NpmSum,  NparSum,  NskipSum);
   return (TRUE);
 }
Index: trunk/Ohana/src/relastro/src/args.c
===================================================================
--- trunk/Ohana/src/relastro/src/args.c	(revision 35105)
+++ trunk/Ohana/src/relastro/src/args.c	(revision 35416)
@@ -89,5 +89,5 @@
   if ((N = get_argument (argc, argv, "-high-speed"))) {
     // XXX include a parallax / no-parallax option
-    if (N >= argc - 3) usage();
+    if (N >= argc - 4) usage();
     FIT_TARGET = TARGET_HIGH_SPEED;
     remove_argument (N, &argc, argv);
@@ -95,4 +95,14 @@
     remove_argument (N, &argc, argv);
     PHOTCODE_B_LIST = strcreate(argv[N]);
+    remove_argument (N, &argc, argv);
+    RADIUS = atof(argv[N]);
+    remove_argument (N, &argc, argv);
+    HIGH_SPEED_DIR = abspath(argv[N], DVO_MAX_PATH);
+    remove_argument (N, &argc, argv);
+  }
+
+  if ((N = get_argument (argc, argv, "-hpm"))) {
+    if (N >= argc - 2) usage();
+    FIT_TARGET = TARGET_HPM;
     remove_argument (N, &argc, argv);
     RADIUS = atof(argv[N]);
@@ -106,6 +116,6 @@
     remove_argument (N, &argc, argv);
     PARALLEL_OUTPUT = TRUE;
-    if (FIT_TARGET != TARGET_HIGH_SPEED) {
-      fprintf (stderr, "-parallel-output only valid for -high-speed mode\n");
+    if ((FIT_TARGET != TARGET_HIGH_SPEED) && (FIT_TARGET != TARGET_HPM)) {
+      fprintf (stderr, "-parallel-output only valid for -high-speed or -hpm modes\n");
       exit (1);
     }
@@ -456,5 +466,5 @@
   if ((N = get_argument (argc, argv, "-high-speed"))) {
     // XXX include a parallax / no-parallax option
-    if (N >= argc - 3) usage_client();
+    if (N >= argc - 5) usage_client();
     FIT_TARGET = TARGET_HIGH_SPEED;
     remove_argument (N, &argc, argv);
@@ -466,4 +476,14 @@
     remove_argument (N, &argc, argv);
     HIGH_SPEED_DIR = strcreate(argv[N]);
+    remove_argument (N, &argc, argv);
+  }
+
+  if ((N = get_argument (argc, argv, "-hpm"))) {
+    if (N >= argc - 3) usage();
+    FIT_TARGET = TARGET_HPM;
+    remove_argument (N, &argc, argv);
+    RADIUS = atof(argv[N]);
+    remove_argument (N, &argc, argv);
+    HIGH_SPEED_DIR = abspath(argv[N], DVO_MAX_PATH);
     remove_argument (N, &argc, argv);
   }
@@ -668,4 +688,5 @@
   fprintf (stderr, "       OR:    relastro -update-objects [options]\n");
   fprintf (stderr, "       OR:    relastro -high-speed [options]\n");
+  fprintf (stderr, "       OR:    relastro -hpm [options]\n");
   fprintf (stderr, "       OR:    relastro -merge-source [options]\n\n");
 
@@ -679,4 +700,5 @@
   fprintf (stderr, "  -update-mosaics\n");
   fprintf (stderr, "  -high-speed (code[,code,code]) (code[,code,code]) (radius) (output catdir)\n");
+  fprintf (stderr, "  -hpm (radius) (output catdir)\n");
   fprintf (stderr, "  -merge-source (objID) (catID) into (objID) (catID)\n\n");
 
Index: trunk/Ohana/src/relastro/src/hpm_catalogs.c
===================================================================
--- trunk/Ohana/src/relastro/src/hpm_catalogs.c	(revision 35416)
+++ trunk/Ohana/src/relastro/src/hpm_catalogs.c	(revision 35416)
@@ -0,0 +1,203 @@
+# include "relastro.h"
+
+int hpm_catalogs_parallel (SkyList *sky);
+
+int hpm_catalogs (SkyTable *sky, SkyList *skylist, int hostID, char *hostpath) {
+
+  int i;
+  Catalog catalog;
+
+  // tell libdvo the CATDIR
+  dvo_set_catdir(CATDIR);
+
+  // XXX need to decide how to determine PARALLEL mode...
+  if (PARALLEL && !hostID) {
+    hpm_catalogs_parallel (skylist);
+    goto finish;
+  }
+
+  // load data from each region file, only use bright stars
+  for (i = 0; i < skylist[0].Nregions; i++) {
+
+    // does this host ID match the desired location for the table?
+    if (!HostTableTestHost(skylist[0].regions[i], hostID)) continue;
+
+    // set up the basic catalog info
+    char hostfile[1024];
+    snprintf (hostfile, 1024, "%s/%s.cpt", hostpath, skylist[0].regions[i]->name);
+    catalog.filename  = hostID ? hostfile : 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 | LOAD_MISS | LOAD_SECF;
+    catalog.Nsecfilt  = GetPhotcodeNsecfilt ();
+
+    if (!dvo_catalog_open (&catalog, skylist[0].regions[i], VERBOSE2, "r")) {
+      fprintf (stderr, "ERROR: failure reading catalog %s\n", catalog.filename);
+      exit (1);
+    }
+    if (!catalog.Naves_disk) {
+      if (VERBOSE2) fprintf (stderr, "no data in %s, skipping\n", catalog.filename);
+      dvo_catalog_unlock (&catalog);
+      dvo_catalog_free (&catalog);
+      continue;
+    }
+
+    hpm_objects (skylist[0].regions[i], &catalog);
+
+    dvo_catalog_unlock (&catalog);
+    dvo_catalog_free (&catalog);
+  }
+  
+finish:
+
+  // in relastro (NOT relastr_client), save the Images.dat, Photcodes.dat, SkyTable.fits files
+  if (!hostID) {
+    char photcodeFile[1024];
+    snprintf (photcodeFile, 1024, "%s/Photcodes.dat", HIGH_SPEED_DIR);
+    if (!SavePhotcodesFITS (photcodeFile)) {
+      fprintf (stderr, "error saving photcode table %s\n", photcodeFile);
+      exit (1);
+    }
+    
+    // need to copy across the Images, SkyTable, and Photcode tables:
+    char *skyfile = SkyTableFilename (HIGH_SPEED_DIR);
+    SkyTableSave (sky, skyfile);
+    
+    char line[2048];
+    snprintf (line, 2048, "cp %s/Images.dat %s/Images.dat", CATDIR, HIGH_SPEED_DIR);
+    int status = system (line);
+    if (status) {
+      fprintf (stderr, "copy of Images.dat failed\n");
+      exit (3);
+    }
+  }
+
+  return (TRUE);
+}
+
+// CATDIR is supplied globally
+# define DEBUG 1
+int hpm_catalogs_parallel (SkyList *skylist) {
+
+  // launch the setphot_client jobs to the parallel hosts
+
+  // load the list of hosts
+  HostTable *table = HostTableLoad (CATDIR, skylist->hosts);
+  if (!table) {
+    fprintf (stderr, "ERROR: failure reading Host Table %s for database %s\n", skylist->hosts, CATDIR);
+    exit (1);
+  }    
+
+  // If we want to parallelize the output, the remote client needs to load the HostTable
+  // for the output catdir.  Require the output HostTable to match the input (ie, same
+  // hostIDs must exist).  Preferred, but not required, that they match in the host
+  // location.
+  HostTable *tableOut = NULL;
+  if (PARALLEL_OUTPUT) {
+    // load the list of hosts
+    tableOut = HostTableLoad (HIGH_SPEED_DIR, skylist->hosts);
+    if (!tableOut) {
+      fprintf (stderr, "ERROR: failure reading Host Table %s for output database %s\n", skylist->hosts, HIGH_SPEED_DIR);
+      exit (1);
+    }    
+    if (table->Nhosts != tableOut->Nhosts) {
+      fprintf (stderr, "ERROR: output HostTable must have matching hosts (%d in, %d out)\n", table->Nhosts, tableOut->Nhosts);
+      exit (1);
+    }
+  }
+
+  int i;
+  for (i = 0; i < table->Nhosts; i++) {
+
+    // ensure that the paths are absolute path names
+    char *tmppath = abspath (table->hosts[i].pathname, DVO_MAX_PATH);
+    free (table->hosts[i].pathname);
+    table->hosts[i].pathname = tmppath;
+
+    // use this directory to save the output catalogs (distributed if parallel)
+    char *outputDir = strcreate(HIGH_SPEED_DIR);
+    if (PARALLEL_OUTPUT) {
+      // find the matching host entry in the output HostTable (match by ID, not index)
+      int hostID = table->hosts[i].hostID;
+      int index = table->index[hostID];
+      if (index == -1) {
+	fprintf (stderr, "ERROR: output HostTable must have matching hosts (host ID %d not found)\n", hostID);
+	exit (1);
+      }
+
+      free (outputDir);
+      outputDir = abspath (tableOut->hosts[index].pathname, DVO_MAX_PATH);
+    }
+
+    char command[1024];
+    snprintf (command, 1024, "relastro_client -hpm %f %s -hostID %d -D CATDIR %s -hostdir %s -region %f %f %f %f", 
+	      RADIUS, outputDir, table->hosts[i].hostID, CATDIR, table->hosts[i].pathname, UserPatch.Rmin, UserPatch.Rmax, UserPatch.Dmin, UserPatch.Dmax);
+
+    free (outputDir);
+
+    // options / arguments that can affect relastro_client -high-speed
+    char tmpline[1024];
+    if (FIT_MODE == FIT_PM_ONLY)  	 { snprintf (tmpline, 1024, "%s -pm",    command);           	strcpy (command, tmpline); }
+    if (FIT_MODE == FIT_PAR_ONLY) 	 { snprintf (tmpline, 1024, "%s -par",   command);           	strcpy (command, tmpline); }
+    if (FIT_MODE == FIT_PM_AND_PAR)      { snprintf (tmpline, 1024, "%s -pmpar", command);           	strcpy (command, tmpline); }
+
+    if (VERBOSE)       { snprintf (tmpline, 1024, "%s -v",              command);                    	strcpy (command, tmpline); }
+    if (VERBOSE2)      { snprintf (tmpline, 1024, "%s -vv",             command); 		     	strcpy (command, tmpline); }
+    if (RESET)         { snprintf (tmpline, 1024, "%s -reset",          command); 		     	strcpy (command, tmpline); }
+    if (ImagSelect)    { snprintf (tmpline, 1024, "%s -instmag %f %f",  command, ImagMin, ImagMax);  	strcpy (command, tmpline); }
+    if (MaxDensityUse) { snprintf (tmpline, 1024, "%s -max-density %f", command, MaxDensityValue);   	strcpy (command, tmpline); }
+    
+    if (USE_BASIC_CHECK) { snprintf (tmpline, 1024, "%s -basic-image-search", command);              	strcpy (command, tmpline); }
+
+    if (FlagOutlier)   { snprintf (tmpline, 1024, "%s -clip %d",        command, CLIP_THRESH);       	strcpy (command, tmpline); }
+    
+    if (USE_FIXED_PIXCOORDS) { snprintf (tmpline, 1024, "%s -D USE_FIXED_PIXCOORDS 1", command);     strcpy (command, tmpline); }
+
+    if (PHOTCODE_KEEP_LIST) { snprintf (tmpline, 1024, "%s +photcode %s", command, PHOTCODE_KEEP_LIST); strcpy (command, tmpline); }
+    if (PHOTCODE_SKIP_LIST) { snprintf (tmpline, 1024, "%s -photcode %s", command, PHOTCODE_SKIP_LIST); strcpy (command, tmpline); }
+    if (PhotFlagSelect)    { snprintf (tmpline, 1024, "%s +photflags",   command);                      strcpy (command, tmpline); }
+    if (PhotFlagBad)       { snprintf (tmpline, 1024, "%s +photflagbad %d", command, PhotFlagBad);      strcpy (command, tmpline); }
+    if (PhotFlagPoor)      { snprintf (tmpline, 1024, "%s +photflagpoor %d", command, PhotFlagPoor);    strcpy (command, tmpline); }
+    // XXX note that the above pass in the flag as decimal -- also note that args.c cannot handle 0xHEX values
+
+    if (TimeSelect) { 
+      char *tstart = ohana_sec_to_date (TSTART);
+      char *tstop  = ohana_sec_to_date (TSTOP);
+      snprintf (tmpline, 1024, "%s -time %s %s", command, tstart, tstop); 
+      free (tstart);
+      free (tstop);
+      strcpy (command, tmpline); 
+    }
+
+    fprintf (stderr, "command: %s\n", command);
+
+    if (PARALLEL_MANUAL) continue;
+
+    if (PARALLEL_SERIAL) {
+      int status = system (command);
+      if (status) {
+	fprintf (stderr, "ERROR running relastro_client\n");
+	exit (2);
+      }
+    } else {
+      // launch the job on the remote machine (no handshake)
+      int errorInfo = 0;
+      int pid = rconnect ("ssh", table->hosts[i].hostname, command, table->hosts[i].stdio, &errorInfo, FALSE);
+      if (!pid) {
+	if (DEBUG) fprintf (stderr, "failure to start %s (error %d)\n", table->hosts[i].hostname, errorInfo);
+	exit (1);
+      }
+      table->hosts[i].pid = pid; // save for future reference
+    }
+  }
+
+  if (PARALLEL_MANUAL) {
+    fprintf (stderr, "run the relastro_client commands above.  when these are done, hit return\n");
+    getchar();
+  }
+  if (!PARALLEL_MANUAL && !PARALLEL_SERIAL) {
+    HostTableWaitJobsGetIO (table, __FILE__, __LINE__, VERBOSE);
+  }
+
+  return TRUE;
+}      
Index: trunk/Ohana/src/relastro/src/hpm_objects.c
===================================================================
--- trunk/Ohana/src/relastro/src/hpm_objects.c	(revision 35416)
+++ trunk/Ohana/src/relastro/src/hpm_objects.c	(revision 35416)
@@ -0,0 +1,298 @@
+# include "relastro.h"
+# define MAX_TRANGE 180.0
+# define MIN_PS1_DET 3
+# define MAX_CHISQ_PM 1000000.0
+# define MIN_NPOS_FRAC 1.0
+
+# define NEXT_I { if (Ngroup < 2) slowMoving[ni] = TRUE; newI = TRUE; i++; continue; }
+# define NEXT_J { j++; continue; }
+
+int hpm_objects (SkyRegion *region, Catalog *catalog) {
+
+  off_t i, j, J, ni, nj, *N1, k, m;
+  int *slowMoving;
+  double *X1, *Y1;
+  double dX, dY, dR, RADIUS2;
+  Coords tcoords;
+  Catalog catalogOut, testcat;
+
+  int Nsecfilt;
+  char filename[1024];
+
+  // we need at least 2 objects if we are going to match anything...
+  if (catalog[0].Naverage < 2) return (TRUE);
+  if (VERBOSE) fprintf (stderr, "checking "OFF_T_FMT" objects\n",  catalog[0].Naverage);
+
+  // we save the best objects in an hpm dvodb
+  snprintf (filename, 1024, "%s/%s.cpt", HIGH_SPEED_DIR, region[0].name);
+
+  dvo_catalog_init (&catalogOut, TRUE); /* init new catalog */
+  catalogOut.filename = strcreate(filename);
+
+  Nsecfilt = GetPhotcodeNsecfilt();
+  catalogOut.filename = filename;
+  catalogOut.Nsecfilt = Nsecfilt;
+  catalogOut.catflags = LOAD_AVES | LOAD_MEAS | LOAD_MISS | LOAD_SECF; // load all data
+  
+  catalogOut.catformat = dvo_catalog_catformat (CATFORMAT);  // set the default catformat from config data
+  catalogOut.catmode   = dvo_catalog_catmode (CATMODE);      // set the default catmode from config data
+
+  if (!dvo_catalog_open (&catalogOut, region, VERBOSE2,"w")) {
+    fprintf (stderr, "ERROR: failure to open catalog file %s\n",
+	     filename);
+    exit (2);
+  }
+
+  off_t NAVERAGE = 1000;
+  off_t NMEASURE = 10000;
+  REALLOCATE (catalogOut.average, Average, NAVERAGE);
+  REALLOCATE (catalogOut.measure, Measure, NMEASURE);
+  REALLOCATE (catalogOut.secfilt, SecFilt, NAVERAGE*Nsecfilt);
+  catalogOut.Naverage = 0;
+  catalogOut.Nmeasure = 0;
+
+  // testcat is used to determine the fit for a single object group
+  // objects which do not have a high-quality testcat fit are not kept
+  dvo_catalog_init (&testcat, TRUE);
+  testcat.Naverage = 1; // this is fixed -- only one obj in testcat
+  testcat.Nsecfilt = Nsecfilt;
+  off_t NMEASURE_TESTCAT = 1000;
+  ALLOCATE (testcat.average, Average, 1);
+  ALLOCATE (testcat.measure, Measure, NMEASURE_TESTCAT);
+  ALLOCATE (testcat.secfilt, SecFilt, Nsecfilt);
+
+  // mask with which to mark objects to be ignored
+  ALLOCATE (slowMoving, int, catalog[0].Naverage);
+  memset (slowMoving, 0, catalog[0].Naverage*sizeof(int));
+  off_t Nslow = 0;
+
+  time_t T2000 = ohana_date_to_sec ("2000/01/01");
+
+  // mark (exclude) objects which do not meet the selection criterion
+  for (i = 0; i < catalog[0].Naverage; i++) {
+
+    int XVERB = FALSE;
+    XVERB |= (catalog[0].average[i].objID == OBJ_ID_SRC) && (catalog[0].average[i].catID == CAT_ID_SRC);
+    XVERB |= (catalog[0].average[i].objID == OBJ_ID_DST) && (catalog[0].average[i].catID == CAT_ID_DST);
+    if (XVERB) {
+      fprintf (stderr, "test object\n");
+    }
+
+    // count the PS1 detections via explicit photcode ranges?
+    // XXX this is a total hard-wired hack...
+    int Nps1 = 0;
+    double Tmin = +100.0*365.0;  // +/- 100 years
+    double Tmax = -100.0*365.0;  
+    m = catalog[0].average[i].measureOffset;
+    for (j = 0; j < catalog[0].average[i].Nmeasure; j++) {
+      if (catalog[0].measure[m+j].photcode < 10000) continue;
+      if (catalog[0].measure[m+j].photcode > 10600) continue;
+      double To = (catalog[0].measure[m+j].t - T2000) / 86400.0 ; // time in days relative to J2000 in years
+      Tmin = MIN(Tmin, To);
+      Tmax = MAX(Tmax, To);
+      Nps1 ++;
+    }
+    if (Nps1 < MIN_PS1_DET) {
+      slowMoving[i] = TRUE;
+      Nslow ++;
+      continue;
+    }      
+    // selection criteria : (Nps1 > 2) && (Trange < 180)
+    double Trange = Tmax - Tmin;
+    if (Trange > MAX_TRANGE) {
+      slowMoving[i] = TRUE;
+      Nslow ++;
+      continue;
+    }
+  }
+
+  fprintf (stderr, OFF_T_FMT" slow, "OFF_T_FMT" total objects; "OFF_T_FMT" possible fast\n",  Nslow,  catalog[0].Naverage,  catalog[0].Naverage - Nslow);
+  if (catalog[0].Naverage == Nslow) {
+    fprintf (stderr, "no possible fast objects, skipping this catalog\n");
+    dvo_catalog_save (&catalogOut, VERBOSE2);
+    dvo_catalog_unlock (&catalogOut);
+    dvo_catalog_free (&catalogOut);
+    free (slowMoving);
+    return TRUE;
+  }
+    
+  // double loop over unmarked objects (sorted in RA / X)
+  // record all pairs within the desired match distance
+
+  // create tmp local positional index
+  ALLOCATE (X1, double, catalog[0].Naverage);
+  ALLOCATE (Y1, double, catalog[0].Naverage);
+  ALLOCATE (N1, off_t,  catalog[0].Naverage);
+
+  // define a local projection
+  tcoords.crval1 = 0.5*(region[0].Rmin + region[0].Rmax);
+  if (region[0].Dmax < 90) {
+    tcoords.crval2 = 0.5*(region[0].Dmin + region[0].Dmax);
+  } else {
+    tcoords.crval2 = 90.0;
+  }
+  tcoords.crpix1 = 0;
+  tcoords.crpix2 = 0;
+  tcoords.cdelt1 = tcoords.cdelt2 = 1.0 / 3600.0;
+  tcoords.pc1_1 = tcoords.pc2_2 = 1.0;
+  tcoords.pc1_2 = tcoords.pc2_1 = 0.0;
+  tcoords.Npolyterms = 1;
+  strcpy (tcoords.ctype, "RA---ARC");
+
+  /* build spatial index (RA sort) referencing input array sequence */
+  for (i = 0; i < catalog[0].Naverage; i++) {
+    int status = RD_to_XY (&X1[i], &Y1[i], catalog[0].average[i].R, catalog[0].average[i].D, &tcoords);
+    N1[i] = i;
+    assert (status);
+  }
+  sort_coords_index (X1, Y1, N1, catalog[0].Naverage);
+
+  RADIUS2 = SQ(RADIUS);
+
+  // group is a list of objects that are within a clump.  this set will be tested
+  // via a clipped fit to the measurements after the group is identified
+  int Ngroup = 0;
+  int NGROUP = 100;
+  int *group = NULL;
+  ALLOCATE (group, int, NGROUP);
+
+  // in the loop below, we need to do a bunch of things when we go to the next main object
+  int newI = TRUE;
+
+  // mark (skip) objects with both sets of target photcodes
+  // the loop below is attempting to find associations of multiple objects which have 
+  // passed the cuts above.  the index i is following the primary object of interest
+  // the index j is used to explore possible near neighbors.  
+  // When we go to the next object 'i', Nmatch is reset 
+  for (i = j = 0; (i < catalog[0].Naverage) && (j < catalog[0].Naverage);) {
+
+    ni = N1[i];
+    nj = N1[j];
+
+    if (newI) {
+      Ngroup = 1;
+      group[0] = ni;
+      newI = FALSE;
+    }
+
+    int XVERB = FALSE;
+    XVERB |= (catalog[0].average[ni].objID == OBJ_ID_SRC) && (catalog[0].average[ni].catID == CAT_ID_SRC);
+    XVERB |= (catalog[0].average[nj].objID == OBJ_ID_DST) && (catalog[0].average[nj].catID == CAT_ID_DST);
+    if (XVERB) {
+      fprintf (stderr, "test object %d or %d\n", (int) ni, (int) nj);
+    }
+
+    if (slowMoving[ni]) NEXT_I;
+    if (slowMoving[nj]) NEXT_J;
+
+    if (!finite(X1[i]) || !finite(Y1[i])) NEXT_I;
+    if (!finite(X1[j]) || !finite(Y1[j])) NEXT_J;
+
+    // look for pairs that are within the maximum separation
+    dX = X1[i] - X1[j];
+    if (XVERB) {
+      fprintf (stderr, "%d %d : %f\n", (int) ni, (int) nj, dX);
+    }
+
+    if (dX <= -1.02*RADIUS) NEXT_I; // negative dX: i is too small
+    if (dX >= +1.02*RADIUS) NEXT_J; // positive dX: j is too small
+
+    // within match range; look for valid matches & accumulate the group
+    for (J = j; (dX > -1.02*RADIUS) && (J < catalog[0].Naverage); J++) {     
+      if (J == i) continue;  // avoid auto-matches
+      nj = N1[J];
+
+      dX = X1[i] - X1[J];
+
+      if (slowMoving[nj]) continue;
+
+      XVERB  = (catalog[0].average[ni].objID == OBJ_ID_SRC) && (catalog[0].average[ni].catID == CAT_ID_SRC);
+      XVERB |= (catalog[0].average[nj].objID == OBJ_ID_DST) && (catalog[0].average[nj].catID == CAT_ID_DST);
+      if (XVERB) {
+	fprintf (stderr, "test object pt2 %d or %d : %f\n", (int) ni, (int) nj, dX);
+	fprintf (stderr, ".\n");
+      }
+
+      dY = Y1[i] - Y1[J];
+      dR = dX*dX + dY*dY;
+      if (dR > RADIUS2) continue;
+
+      /*** a match is found ***/
+      group[Ngroup] = nj;
+      Ngroup ++;
+      CHECK_REALLOCATE (group, int, NGROUP, Ngroup, 100);
+    }
+
+    if (Ngroup < 2) NEXT_I;
+
+    // we now have spatially associated group of objects.  now we need to see if the set of
+    // measurements can be fitted reasonably with a proper motion (& parallax?)
+
+    // the mean object will start with info from the primary object
+    // remember: testcat.Naverage = 1 -- does not change
+    ni = group[0];
+    int Nmatchmeas = 0;
+    testcat.average[0] = catalog[0].average[ni];
+    testcat.average[0].measureOffset = 0;
+    for (J = 0; J < Ngroup; J++) {
+      nj = group[J];
+      m = catalog[0].average[nj].measureOffset;
+      for (k = 0; k < catalog[0].average[nj].Nmeasure; k++) {
+	testcat.measure[Nmatchmeas] = catalog[0].measure[m+k];
+	/* Set offset RA and Dec wrt correct average value*/
+	testcat.measure[Nmatchmeas].dR = catalog[0].measure[m+k].dR + 3600.0*(catalog[0].average[ni].R - catalog[0].average[nj].R);
+	testcat.measure[Nmatchmeas].dD = catalog[0].measure[m+k].dD + 3600.0*(catalog[0].average[ni].D - catalog[0].average[nj].D);
+	testcat.measure[Nmatchmeas].averef = 0;
+	Nmatchmeas++;
+	CHECK_REALLOCATE (testcat.measure, Measure, NMEASURE_TESTCAT, Nmatchmeas, 1000);
+      }
+    }
+    testcat.average[0].Nmeasure = Nmatchmeas;
+    testcat.Nmeasure = Nmatchmeas;
+    populate_tiny_values (&testcat, DVO_TV_MEASURE);
+
+    // we have now accumulated the measurements for this group, let's try a fit
+    // this needs to be a (fairly robust) clipped fit or we will have a hard time
+    // distinguishing a bad fit from a fit with 1 or 2 bad points
+    FIT_MODE = FIT_PM_ONLY;
+    UpdateObjects (&testcat, 1);
+
+    // logic for keeping the fit (anything else?)
+    int good = FALSE;
+    good |= (testcat.average[0].ChiSqPM < MAX_CHISQ_PM);
+    good |= (testcat.average[0].Npos > MIN_NPOS_FRAC * testcat.average[0].Nmeasure);
+    good &= ((testcat.average[0].flags & ID_STAR_FIT_PM) > 0);
+
+    if (good) {
+      // save the new object on catalogOut
+      // (note: there is only one object in testcat and thus measureOffset = 0
+      catalogOut.average[catalogOut.Naverage] = testcat.average[0];
+      catalogOut.average[catalogOut.Naverage].measureOffset = catalogOut.Nmeasure;
+      for (k = 0; k < testcat.average[0].Nmeasure; k++) {
+	catalogOut.measure[catalogOut.Nmeasure] = testcat.measure[k];
+	catalogOut.measure[catalogOut.Nmeasure].averef = catalogOut.Naverage;
+	catalogOut.Nmeasure ++;
+	CHECK_REALLOCATE (catalogOut.measure, Measure, NMEASURE, catalogOut.Nmeasure, 100);
+      }
+      catalogOut.Naverage ++;
+      CHECK_REALLOCATE (catalogOut.average, Average, NAVERAGE, catalogOut.Naverage, 100);
+    }
+    NEXT_I;
+  }
+  catalogOut.Nsecfilt = Nsecfilt;
+  catalogOut.Nsecf_mem = Nsecfilt * catalogOut.Naverage;
+
+  populate_tiny_values (&catalogOut, DVO_TV_MEASURE);
+  UpdateObjects (&catalogOut, 1);
+
+  fprintf (stderr, "found "OFF_T_FMT" matches with "OFF_T_FMT" detections\n", catalogOut.Naverage, catalogOut.Nmeasure);
+  dvo_catalog_save (&catalogOut, VERBOSE2);
+  dvo_catalog_unlock (&catalogOut);
+  dvo_catalog_free (&catalogOut);
+  free (slowMoving);
+  free (X1);
+  free (Y1);
+  free (N1);
+
+  return TRUE;
+}
Index: trunk/Ohana/src/relastro/src/relastro.c
===================================================================
--- trunk/Ohana/src/relastro/src/relastro.c	(revision 35105)
+++ trunk/Ohana/src/relastro/src/relastro.c	(revision 35416)
@@ -19,4 +19,8 @@
       /* high-speed is a 2pt cross-correlation process for linking moving objects (high PM) */
       high_speed_catalogs (sky, skylist, 0, NULL);
+      exit (0);
+
+    case TARGET_HPM:
+      hpm_catalogs (sky, skylist, 0, NULL);
       exit (0);
 
Index: trunk/Ohana/src/relastro/src/relastro_client.c
===================================================================
--- trunk/Ohana/src/relastro/src/relastro_client.c	(revision 35105)
+++ trunk/Ohana/src/relastro/src/relastro_client.c	(revision 35416)
@@ -58,4 +58,10 @@
     }
 
+    case TARGET_HPM: {
+      // USAGE: relastro_client -high-speed
+      hpm_catalogs (sky, skylist, HOST_ID, HOSTDIR);
+      break;
+    }
+
       // XXX loading the images is fairly costly -- see if we can do an image subset
     case TARGET_UPDATE_OFFSETS: {
