Exemple #1
0
static void smf1_jsadicer( int indfo, int *olbnd, int *oubnd,
                           AstMapping *tile_map, AstFrame *tile_frm,
                           AstMapping *p2pmap, void *ipd, void *ipv,
                           unsigned char *ipq, int *status ){
/*
*  Name:
*     smf1_jsadicer

*  Purpose:
*     Copy one tile from the input NDF into a specified output NDF.

*  Language:
*     Starlink ANSI C

*  Type of Module:
*     C function

*  Invocation:
*     void smf1_jsadicer( int indfo, int *olbnd, int *oubnd,
*                         AstMapping *tile_map, AstFrame *tile_frm,
*                         AstMapping *p2pmap, void *ipd, void *ipv,
*                         unsigned char *ipq, int *status )

*  Arguments:
*     indfo = int (Given)
*        An identifier for the NDF in which the copied data is to be
*        stored. It's original pixel bounds are used as the bounds of the
*        ipd, ipv and ipq arrays.
*     olbnd = int * (Given)
*        The new lower pixel bounds required for the output NDF. The bounds
*        of the supplied NDF are changed to match these values.
*     oubnd = int * (Given)
*        The new upper pixel bounds required for the output NDF. The bounds
*        of the supplied NDF are changed to match these values.
*     tile_map = AstMapping * (Given)
*        The mapping from pixel coords in the output NDF to WCS coords.
*     tile_frm = AstMapping * (Given)
*        The WCS Frame for the output NDF.
*     p2pmap = AstMapping * (Given)
*        The mapping from pixel coords in the input NDF to pixel coords in
*        the output NDF.
*     ipd = void * (Given)
*        Pointer to the start of the input data array. If this is NULL,
*        the existing contents of the NDF are used as input.
*     ipv = void * (Given)
*        Pointer to the start of the input variance array. Should be NULL
*        if no variances are available.
*     ipq = unsigned char * (Given)
*        Pointer to the start of the input quality array. Should be NULL
*        if no quality is available.
*     status = int * (Given)
*        Pointer to the inherited status variable.
*/

/* Local Variables: */
   AstFrame *use_frm = NULL;
   AstFrameSet *owcs;
   AstMapping *use_map = NULL;
   AstMapping *use_p2pmap = NULL;
   AstShiftMap *sm;
   char type[ NDF__SZTYP + 1 ];
   double shifts[ 3 ];
   int axes[ 2 ];
   int axout[ NDF__MXDIM ];
   int free_arrays;
   int isreal;
   int lbnd_tile[ 3 ];
   int ndim;
   int nel;
   int nin;
   int there;
   int ubnd_tile[ 3 ];
   unsigned char *ipq_out = NULL;
   void *ipd_out = NULL;
   void *ipv_out = NULL;

/* Check inherited status */
   if( *status != SAI__OK ) return;

/* Begin an AST context. */
   astBegin;

/* Get the NDF data type - _REAL or _DOUBLE. */
   ndfType( indfo, "Data", type, sizeof(type), status );
   isreal = !strcmp( type, "_REAL" );

/* Get the existing bounds of the NDF. */
   ndfBound( indfo, 3, lbnd_tile, ubnd_tile, &ndim, status );

/* If no data array has been supplied, take a copy of the original Data,
   Quality and Variance arrays and use these as the input arrays. */
   if( !ipd ) {
      free_arrays = 1;

      ndfMap( indfo, "Data", type, "Read", &ipd_out, &nel, status );
      ipd = astStore( NULL, ipd_out,
                      nel*(isreal?sizeof(float):sizeof(double)) );
      ndfUnmap( indfo, "Data", status );

      ndfState( indfo, "Variance", &there, status );
      if( there ) {
         ndfMap( indfo, "Variance", type, "Read", &ipv_out, &nel, status );
         ipv = astStore( NULL, ipv_out,
                         nel*(isreal?sizeof(float):sizeof(double)) );
         ndfUnmap( indfo, "Variance", status );
      } else {
         ipv = NULL;
      }

      ndfState( indfo, "Quality", &there, status );
      if( there ) {
         ndfMap( indfo, "Quality", "_UBYTE", "Read", (void **) &ipq_out,
                 &nel, status );
         ipq = astStore( NULL, ipq_out, nel*sizeof(*ipq) );
         ndfUnmap( indfo, "Quality", status );
      } else {
         ipq = NULL;
      }

   } else {
      free_arrays = 0;
   }

/* Set the bounds of the NDF to the required values. */
   ndfSbnd( ndim, olbnd, oubnd, indfo, status );

/* Erase the existing WCS FrameSet and then get the default WCS FrameSet. */
   ndfReset( indfo, "WCS", status );
   ndfGtwcs( indfo, &owcs, status );

/* If the supplied mapping and Frame have two many axes, strip some off.
   The orering of pixel axes in the output JSA tile is hardwired by SMURF
   as (ra,dec,spec). */
   nin = astGetI( tile_map, "Nin" );
   if( nin == 3 && ndim == 2 ) {
      axes[ 0 ] = 1;
      axes[ 1 ] = 2;
      astMapSplit( tile_map, 2, axes, axout, &use_map );
      if( use_map ) {
         use_frm = astPickAxes( tile_frm, 2, axout, NULL );
      } else if( *status == SAI__OK ) {
         *status = SAI__ERROR;
         errRepf( " ", "smf1_jsadicer: cannot split mapping (programming "
                  "error).", status );
      }

      astMapSplit( p2pmap, 2, axes, axout, &use_p2pmap );
      if( !use_p2pmap && *status == SAI__OK ) {
         *status = SAI__ERROR;
         errRepf( " ", "smf1_jsadicer: cannot split mapping (programming "
                  "error).", status );
      }

   } else if( nin == ndim ) {
      use_p2pmap = astClone( p2pmap );
      use_map = astClone( tile_map );
      use_frm = astClone( tile_frm );

   } else if( *status == SAI__OK ) {
      *status = SAI__ERROR;
      errRepf( " ", "smf1_jsadicer: unexpected combination of nin (%d) and "
               "ndim (%d) (programming error).", status, nin, ndim );
   }

/* Add the tile WCS Frame into the output NDF's WCS FrameSet, using "tilemap"
   to connect it to the PIXEL Frame (NDF ensure Frame 2 is the PIXEL
   Frame). */
   astAddFrame( owcs, 2, use_map, use_frm );

/* The astResample function is odd in that it assumes that pixel coords
   are defined such that the centre of pixel "I" has integral pixel
   coord "I" (rather than "I-0.5" as is usual in Starlink). So we need to
   use a half-pixel ShiftMap at start and end of the p2pmap Mapping to
   account for this. */
   shifts[ 0 ] = -0.5;
   shifts[ 1 ] = -0.5;
   shifts[ 2 ] = -0.5;
   sm = astShiftMap( ndim, shifts, " " );
   use_p2pmap = (AstMapping *) astCmpMap( sm, use_p2pmap, 1, " " );
   astInvert( sm );
   use_p2pmap = (AstMapping *) astCmpMap( use_p2pmap, sm, 1, " " );

/* Store this modified WCS FrameSet in the output NDF. */
   ndfPtwcs( owcs, indfo, status );

/* Map the required arrays of the output NDF. */
   ndfMap( indfo, "Data", type, "Write", &ipd_out, &nel, status );
   if( ipv ) ndfMap( indfo, "Variance", type, "Write", &ipv_out, &nel,
                     status );
   if( ipq ) ndfMap( indfo, "Quality", "_UBYTE", "Write",
                      (void **) &ipq_out, &nel, status );

/* Copy the input data values to the output, using nearest neighbour
   interpolation (the mapping should always map input pixel centres onto
   output pixel centres). We can set the "tol" argument non-zero (e.g. 0.1)
   without introducing any error because the the p2pmap mapping will be
   piecewise linear. This gives a factor of about 5 decrease in the time
   spent within astResample. */
   if( !strcmp( type, "_REAL" ) ) {
      (void) astResampleF( use_p2pmap, ndim, lbnd_tile, ubnd_tile, (float *) ipd,
                           (float *) ipv, AST__NEAREST, NULL, NULL,
                           AST__USEBAD, 0.1, 1000, VAL__BADR, ndim,
                           olbnd, oubnd, olbnd, oubnd,
                           (float *) ipd_out, (float *) ipv_out );
   } else {
      (void) astResampleD( use_p2pmap, ndim, lbnd_tile, ubnd_tile, (double *) ipd,
                           (double *) ipv, AST__NEAREST, NULL, NULL,
                           AST__USEBAD, 0.1, 1000, VAL__BADD, ndim,
                           olbnd, oubnd, olbnd, oubnd,
                           (double *) ipd_out, (double *) ipv_out );
   }

   if( ipq ) {
      (void) astResampleUB( use_p2pmap, ndim, lbnd_tile, ubnd_tile, ipq, NULL,
                            AST__NEAREST, NULL, NULL, 0, 0.1, 1000, 0,
                            ndim, olbnd, oubnd, olbnd, oubnd, ipq_out,
                            NULL );
   }

/* Unmap everything the output NDF. */
   ndfUnmap( indfo, "*", status );

/* Free the input arrays if they were allocated in this function. */
   if( free_arrays ) {
      ipd = astFree( ipd );
      ipv = astFree( ipv );
      ipq = astFree( ipq );
   }

/* End the AST context. */
   astEnd;
}
Exemple #2
0
static AstTable *ReadNextTable( FILE *fd, const char *fname, int *iline,
                                int *status ) {
    /*
    *  Name:
    *     ReadOneTable

    *  Purpose:
    *     Reads a single Table from a text file.

    *  Description:
    *     This function reads text from the supplied file descriptor until it
    *     reaches the end of file or encounters an end-of-table marker (a line
    *     consisting just of two or more minus signs with no leading spaces).
    *     It creates an AstTable from the text and returns a pointer to it.

    *  Arguments:
    *     fd
    *        The file descriptor.
    *     fname
    *        The file name - used for error messages.
    *     iline
    *        Pointer to an int holding the number of lines read from the
    *        file so far. Updated on exit to include the lines read by the
    *        invocation of this function.
    *     status
    *        Pointer to the global status variable.

    *  Returned Value:
    *     A pointer to the Table read from the file, or NULL if an error occurs.

    */

    /* Local Variables: */
    AstTable *result;
    AstTable *subtable;
    char **cols;
    char **words;
    char *last_com;
    char *line;
    char *p;
    char *tname;
    char key[ 200 ];
    const char *cval;
    const char *oldname;
    const char *newname;
    double dval;
    int *types;
    int blank;
    int c;
    int com;
    int eot;
    int first;
    int icol;
    int irow;
    int ival;
    int iword;
    int line_len;
    int max_line_len;
    int more;
    int nc;
    int ncol;
    int nrow;
    int nword;
    int skip;
    size_t len;

    /* Initialise */
    result = NULL;

    /* Check the inherited status. */
    if( *status != SAI__OK ) return result;

    /* Create an empty Table. */
    result = astTable( " " );

    /* Allocate a buffer for one one line of text. This will be increased in
       size as required. */
    max_line_len = 80;
    line = astMalloc( max_line_len*sizeof( *line ) );

    /* Read each line of text from the file. */
    eot = 0;
    skip = 1;
    line_len = 0;
    more = 1;
    last_com = NULL;
    cols = NULL;
    first = 1;
    irow = 0;
    types = NULL;

    while( more && *status == SAI__OK ) {
        (*iline)++;
        line_len = 0;

        /* Loop reading characters from the file until a newline or the end of file is
           reached. */
        while( ( c = fgetc( fd ) ) != EOF && c != '\n' ) {

            /* Increment the current line length, and double the size of the line buffer
               if it is full. */
            if( ++line_len >= max_line_len ) {
                max_line_len *= 2;
                line = astRealloc( line, max_line_len*sizeof( *line ) );
                if( *status != SAI__OK ) break;
            }

            /* Store the character. Ignore leading white space. */
            if( skip ) {
                if( ! isspace( c ) ) {
                    line[ line_len - 1 ] = c;
                    skip = 0;
                } else {
                    line_len--;
                }
            } else {
                line[ line_len - 1 ] = c;
            }

        }

        /* If the end-of-file was reached indicate that we should leave the main
           loop after processing the current line. */
        if( c == EOF ) more = 0;

        /* Terminate the line. */
        line[ line_len ] = 0;

        /* Terminate it again to exclude trailing white space. */
        line[ astChrLen( line ) ] = 0;

        /* Assume the line is a blank non-comment, and store a pointer to the first
           character to use. */
        blank = 1;
        com = 0;
        p = line;

        /* Skip blank lines. */
        if( line[ 0 ] ) {

            /* If the line starts with a comment character... */
            if( line[ 0 ] == '#' || line[ 0 ] == '!' ) {
                com = 1;

                /* Get a pointer to the first non-space/tab character after the comment
                   character. */
                p = line + 1;
                while( *p == ' ' || *p == '\t' ) p++;

                /* Note if it is blank. */
                if( *p ) blank = 0;

                /* If it is not a comment line, then the line is not blank. */
            } else {
                blank = 0;

                /* See if it is the end-of-table marker - a line containing just two or
                   more minus signs with no leading spaces. */
                eot = ( strspn( line, "-" ) > 1 );
            }
        }

        /* Skip blank lines, whether comment or not. */
        if( ! blank ) {

            /* First handle comment lines. */
            if( com ) {

                /* Does it look like a  parameter assignment... */
                words = astChrSplitRE( p, "^\\s*(\\w+)\\s*=\\s*(.*)$", &nword, NULL );
                if( words ) {

                    /* Add a parameter declaration to the Table. */
                    astAddParameter( result, words[ 0 ] );

                    /* Store the parameter value, using an appropriate data type. */
                    len = strlen( words[ 1 ] );
                    if( nc = 0, ( 1 == astSscanf( words[ 1 ], "%d%n", &ival, &nc ) )
                            && ( nc >= len ) ) {
                        astMapPut0I( result, words[ 0 ], ival, NULL );

                    } else if( nc = 0, ( 1 == astSscanf( words[ 1 ], "%lg%n", &dval, &nc ) )
                               && ( nc >= len ) ) {
                        astMapPut0D( result, words[ 0 ], dval, NULL );

                    } else {
                        astMapPut0C( result, words[ 0 ], words[ 1 ], NULL );

                    }

                    /* Free the words returned by astChrSplitRE. */
                    for( iword = 0; iword < nword; iword++ ) {
                        words[ iword ] = astFree( words[ iword ] );
                    }
                    words = astFree( words );

                    /* If it does not look like a parameter assignment... */
                } else {

                    /* Save a copy of it in case it turns out to be the last non-blank comment
                       line before the first row of data values (in which case it should
                       contain the column names). */
                    last_com = astStore( last_com, p, strlen( p ) + 1 );
                }

                /* If the line is not a comment see if it is an end of table marker. If so
                   indicate that we should leave the loop. */
            } else if( eot ) {
                more = 0;

                /* If the line is not a comment or an end of table marker ... */
            } else {

                /* Get the words from the row. */
                words = astChrSplit( p, &nword );

                /* If this is the first non-blank non-comment line, get the column names from
                   the previous non-blank comment line. */
                if( first ) {
                    if( last_com ) {
                        first = 0;
                        cols = astChrSplit( last_com, &ncol );

                        /* Create an array to hold the data type for each colum, and initialise
                           them to "integer". */
                        types = astMalloc( ncol*sizeof( int ) ) ;
                        for( iword = 0; iword < nword && astOK; iword++ ) {
                            if( iword < ncol ) {
                                types[ iword ] = AST__INTTYPE;

                                /* The columns are stored initially using interim names which have "T_"
                                   prepended to the names given in the file. */
                                tname = NULL;
                                nc = 0;
                                tname = astAppendString( tname, &nc, "T_" );
                                tname = astAppendString( tname, &nc, cols[ iword ] );
                                astFree( cols[ iword ] );
                                cols[ iword ] = tname;

                                /* Create the column definition within the returned Table. We store them
                                   initially as strings and then convert to the appropriate column data type
                                   later (once all rows have been read and the the data types are known). */
                                astAddColumn( result, cols[ iword ], AST__STRINGTYPE,
                                              0, NULL, " " );
                            }
                        }

                    } else if( *status == SAI__OK ) {
                        *status = SAI__ERROR;
                        msgSetc( "F", fname );
                        errRep( " ", "No column headers found in file ^F.", status );
                    }
                }

                /* Report an error if the line has the wrong number of values. */
                if( nword != ncol ) {
                    if( *status == SAI__OK ) {
                        *status = SAI__ERROR;
                        msgSeti( "N", nword );
                        msgSeti( "I", (*iline) );
                        msgSeti( "M", ncol );
                        msgSetc( "F", fname );
                        errRep( " ", "Wrong number of values (^N) at line ^I in "
                                "file ^F (should be ^M).", status );
                    }

                    /* Otherwise increment the number of rows read. */
                } else {
                    irow++;

                    /* Store each string value in the table, excluding "null" strings. Also check
                       the data type of each string an dupdate the column data types if necessary. */
                    for( iword = 0; iword < nword && *status == SAI__OK; iword++ ) {
                        if( strcmp( words[ iword ], "null" ) ) {
                            sprintf( key, "%s(%d)", cols[ iword ], irow );
                            astMapPut0C( result, key, words[ iword ], NULL );

                            /* If the column is currently thought to hold integers, check that the
                               current word looks like an integer. If not, down-grade the column type
                               to double. */
                            len = strlen( words[ iword ] );
                            if( types[ iword ] == AST__INTTYPE ) {
                                if( nc = 0, ( 1 != astSscanf( words[ iword ], "%d%n",
                                                              &ival, &nc ) ) ||
                                        ( nc < len ) ) {
                                    types[ iword ] = AST__DOUBLETYPE;
                                }
                            }

                            /* If the column is currently thought to hold doubles, check that the
                               current word looks like an doubler. If not, down-grade the column type
                               to string. */
                            if( types[ iword ] == AST__DOUBLETYPE ) {
                                if( nc = 0, ( 1 != astSscanf( words[ iword ], "%lg%n",
                                                              &dval, &nc ) ) ||
                                        ( nc < len ) ) {
                                    types[ iword ] = AST__STRINGTYPE;
                                }
                            }
                        }
                    }
                }

                /* Free the words returned by astChrSplit. */
                for( iword = 0; iword < nword; iword++ ) {
                    words[ iword ] = astFree( words[ iword ] );
                }
                words = astFree( words );

            }
        }
    }

    /* The entire file has now been read, and a Table created in which every
       column holds strings. We also have flags indicating whether the values
       in each column are all integers or doubles. Modify the type of the
       column within the Table to match these flags. */
    nrow = astGetI( result, "Nrow" );
    for( icol = 0; icol < ncol && *status == SAI__OK; icol++ ) {

        /* The column will be re-named from "T_<name>" to "<name>". */
        oldname = cols[ icol ];
        newname = oldname + 2;

        /* First convert string columns to integer columns if all the values in
           the column look like integers. */
        if( types[ icol ] == AST__INTTYPE ) {

            /* Create the new column */
            astAddColumn( result, newname, AST__INTTYPE, 0, NULL, " " );

            /* Copy each cell of the current column, converting from string to integer. */
            for( irow = 1; irow <= nrow; irow++ ) {
                sprintf( key, "%s(%d)", oldname, irow );
                if( astMapGet0I( result, key, &ival ) ) {
                    sprintf( key, "%s(%d)", newname, irow );
                    astMapPut0I( result, key, ival, NULL );
                }
            }

            /* Now do double columns in the same way. */
        } else if( types[ icol ] == AST__DOUBLETYPE ) {
            astAddColumn( result, newname, AST__DOUBLETYPE, 0, NULL, " " );
            for( irow = 1; irow <= nrow; irow++ ) {
                sprintf( key, "%s(%d)", oldname, irow );
                if( astMapGet0D( result, key, &dval ) ) {
                    sprintf( key, "%s(%d)", newname, irow );
                    astMapPut0D( result, key, dval, NULL );
                }
            }

            /* Copy string values without change. */
        } else {
            astAddColumn( result, newname, AST__STRINGTYPE, 0, NULL, " " );
            for( irow = 1; irow <= nrow; irow++ ) {
                sprintf( key, "%s(%d)", oldname, irow );
                if( astMapGet0C( result, key, &cval ) ) {
                    sprintf( key, "%s(%d)", newname, irow );
                    astMapPut0C( result, key, cval, NULL );
                }
            }
        }

        /* Remove the old column. */
        astRemoveColumn( result, oldname );

    }

    /* Free resources. */
    line = astFree( line );
    last_com = astFree( last_com );
    types = astFree( types );
    if( cols ) {
        for( icol = 0; icol < ncol; icol++ ) {
            cols[ icol ] = astFree( cols[ icol ] );
        }
        cols = astFree( cols );
    }

    /* If the table ended with an end-of-table marker, there may be another
       Table in the file. Call this function recursively to read it. */
    if( eot ) {
        subtable = ReadNextTable( fd, fname, iline, status );

        /* Store the subtable as a table parameter in the returned table. */
        if( subtable ) {
            astAddParameter( result, "SubTable" );
            astMapPut0A( result, "SubTable", subtable, NULL );

            /* The Table clones the pointer, so we must annull our local copy of it. */
            subtable = astAnnul( subtable );
        }
    }

    /* Return the Table pointer. */
    return result;
}
Exemple #3
0
void smf_svd( ThrWorkForce *wf, dim_t n, double *a, double *sigma,
              double *u, double eps, int sort, int *status ) {

/* Local Variables */
   SmfSvdData *job_data = NULL;
   SmfSvdData *pdata = NULL;
   dim_t *sobhigh = NULL;
   dim_t *soblow = NULL;
   dim_t i;
   dim_t j;
   dim_t k;
   dim_t irow;
   dim_t iter;
   dim_t nbig;
   dim_t nsmall;
   dim_t nstep;
   dim_t p;
   dim_t rpb;
   dim_t s;
   double *aorig;
   double sigold;
   double delta;
   int *dn = NULL;
   int *up = NULL;
   int converged;

/* Check inherited status */
   if( *status != SAI__OK ) return;

/* The number of threads to use. */
   p = wf ? wf->nworker : 1;

/* If we have more processors than blocks in the matrix, limit the number
   of processors. */
   if( 4*p > n ) p = n/4;
   if( p == 0  ) {
      *status = SAI__ERROR;
      errRepf( "", "smf_svd: Too few rows (%zu) in matrix - must "
               "be no fewer than 4", status, n );
   }

/* Allocate required arrays. */
   sobhigh = astMalloc( 2*p*sizeof( *sobhigh ) );
   soblow = astMalloc( 2*p*sizeof( *soblow ) );
   up = astMalloc( p*sizeof( *up ) );
   dn = astMalloc( p*sizeof( *dn ) );
   job_data = astMalloc( 2*p*sizeof( *job_data ) );
   aorig = u ? astStore( NULL, a, n*n*sizeof(*a) ) : NULL;

/* Check pointers can be used safely. */
   if( *status == SAI__OK ) {

/* Decide on the first and last element to be processed by each thread,
   for "simple" tasks. At the same time, set up jobs to find the sum
   of the squares of all data values (a simple task). */
      nstep = (n*n)/p;
      for( i = 0; i < p; i++ ) {
         pdata = job_data + i;
         pdata->i1 = i*nstep;
         if( i < p - 1 ){
            pdata->i2 = pdata->i1 + nstep - 1;
         } else {
            pdata->i2 = n*n - 1;
         }
         pdata->a = a;
         pdata->oper = 0;
         thrAddJob( wf, 0, pdata, smf1_svd, 0, NULL, status );
      }

      thrWait( wf, status );

      delta = 0.0;
      for( i = 0; i < p; i++ ) {
         pdata = job_data + i;
         delta += pdata->delta;
      }
      delta *= eps;

/* Set up the "size of block" (sob) arrays: soblow holds the zero-based index
   of the first matrix row in each block, and sobhigh holds the zero-based
   index of the last matrix row in each block. We want 2*P blocks (i.e. twice
   the number of threads). Distribute any left over rows evenly amongst the
   blocks so that some blocks have n/2p rows (small blocks), and some have
   n/2p+1 rows (big blocks). */
      rpb = n/(2*p);        /* Nominal number of rows per block */
      nbig = n - 2*p*rpb;   /* Number of big blocks */
      nsmall = 2*p - nbig;  /* Number of small blocks */

/* If we have more small blocks than big blocks, we start with "nbig" pairs
   of blocks in which the first block is big and the second block is small,
   and then pad the end with the surplus number of small blocks. */
      if( nbig < nsmall ) {
         s = 0;
         irow = 0;
         for( i = 0; i < nbig; i++ ) {
            soblow[ s ] = irow;
            irow += rpb;
            sobhigh[ s ] = irow;

            irow++;
            s++;

            soblow[ s ] = irow;
            irow += rpb - 1;
            sobhigh[ s ] = irow;

            irow++;
            s++;
         }

         for( i = s; i < 2*p; i++ ) {
            soblow[ i ] = irow;
            irow += rpb - 1;
            sobhigh[ i ] = irow;
            irow++;
         }

/* If we have more big blocks than small blocks, we start with "nsmall" pairs
   of blocks in which the first block is big and the second block is small,
   and then pad the end with the surplus number of big blocks. */
      } else {
         s = 0;
         irow = 0;
         for( i = 0; i < nsmall; i++ ) {
            soblow[ s ] = irow;
            irow += rpb;
            sobhigh[ s ] = irow;

            irow++;
            s++;

            soblow[ s ] = irow;
            irow += rpb - 1;
            sobhigh[ s ] = irow;

            irow++;
            s++;
         }

         for( i = s; i < 2*p; i++ ) {
            soblow[ i ] = irow;
            irow += rpb;
            sobhigh[ i ] = irow;
            irow++;
         }
      }

/* Sanity check. */
      if( ( irow != n || i != 2*p ) && *status == SAI__OK ) {
         *status = SAI__ERROR;
         errRep( "", "smf_svd: Error setting up the SOB arrays.", status );
         goto L999;
      }

/* Now proceed with the "Block JRS Algorithm" algorithm, as described
   in "A Block JRS Algorithm for Highly Parallel Computation of SVDs"
   (Soliman, et al, DOI: 10.1007/978-3-540-75444-2_36). Note, integer
   counters are one-based in the pseudo-code in the paper, but here
   we use zero-based counters as is normal in C. */

      for( i = 0; i < p; i++ ) {
         up[ i ] = 2*i + 1;
         dn[ i ] = 2*i;
      }

      converged = 0;
      while( !converged ) {
         converged = 1;

         for( s = 0; s < 2*p; s++ ) {
            pdata = job_data + s;
            pdata->oper = 1;
            pdata->soblow = soblow[s];
            pdata->sobhigh = sobhigh[s];
            pdata->delta = delta;
            pdata->a = a;
            pdata->n = n;
            thrAddJob( wf, 0, pdata, smf1_svd, 0, NULL, status );
         }
         thrWait( wf, status );

         for( s = 0; s < 2*p; s++ ) {
            if( !job_data[s].converged ) converged = 0;
         }

         for( iter = 1; iter < 2*p; iter++ ) {

            for( s = 0; s < p; s++ ) {
               pdata = job_data + s;
               pdata->oper = 2;
               pdata->upsoblow = soblow[up[s]];
               pdata->upsobhigh = sobhigh[up[s]];
               pdata->dnsoblow = soblow[dn[s]];
               pdata->dnsobhigh = sobhigh[dn[s]];
               thrAddJob( wf, 0, pdata, smf1_svd, 0, NULL, status );
            }
            thrWait( wf, status );

            for( s = 0; s < 2*p; s++ ) {
               if( !job_data[s].converged ) converged = 0;
            }

            smf1_roundrobin( p, up, dn );
         }
      }

      nstep = n/p;
      for( i = 0; i < p; i++ ) {
         pdata = job_data + i;
         pdata->j1 = i*nstep;
         if( i < p - 1 ){
            pdata->j2 = pdata->j1 + nstep - 1;
         } else {
            pdata->j2 = n - 1;
         }
         pdata->sigma = sigma;
         pdata->oper  = 3;
         thrAddJob( wf, 0, pdata, smf1_svd, 0, NULL, status );
      }

      thrWait( wf, status );

/* If required, sort the singular values into descending order. */
      if( sort ) {
         Sigma_array = sigma;

         double *arowold = astMalloc( n*sizeof( *arowold ) );
         int *index = astMalloc( n*sizeof( *index ) );
         if( *status == SAI__OK ) {

            for( i = 0; i < n; i++ ) index[ i ] = i;
            qsort( index, n, sizeof(*index), smf1_compare );
            for( i = 0; i < n; i++ ) {
               sigold = sigma[ i ];
               memcpy( arowold, a + i*n, n*sizeof(*a) );
               j = i;
               while( 1 ) {
                  k = index[ j ];
                  index[ j ] = j;
                  if( k == i ) break;
                  sigma[ j ] = sigma[ k ];
                  memcpy( a + j*n, a + k*n, n*sizeof(*a) );

                  j = k;
               }
               sigma[ j ] = sigold;
               memcpy( a + j*n, arowold, n*sizeof(*a) );
            }
         }
         index = astFree( index );
         arowold = astFree( arowold );
      }

/* If required, calculate the U matrix. */
      if( u ) {
         for( i = 0; i < p; i++ ) {
            pdata = job_data + i;
            pdata->u = u;
            pdata->aorig = aorig;
            pdata->oper = 4;
            thrAddJob( wf, 0, pdata, smf1_svd, 0, NULL, status );
         }

         thrWait( wf, status );
      }

   }

/* Free resources. */
L999:
   job_data = astFree( job_data );
   up = astFree( up );
   dn = astFree( dn );
   soblow = astFree( soblow );
   sobhigh = astFree( sobhigh );
   aorig = astFree( aorig );

}
Exemple #4
0
int smf_initial_sky( ThrWorkForce *wf, AstKeyMap *keymap, smfDIMMData *dat,
                     int *iters, int *status ) {

/* Local Variables: */
   char refparam[ DAT__SZNAM ];/* Name for reference NDF parameter */
   const char *cval;          /* The IMPORTSKY string value */
   double *ptr;               /* Pointer to NDF Data array */
   double *vptr;              /* Pointer to NDF Variance array */
   int indf1;                 /* Id. for supplied reference NDF */
   int indf2;                 /* Id. for used section of reference NDF */
   int nel;                   /* Number of mapped NDF pixels */
   int result;                /* Returned flag */
   int there;                 /* Is there a smurf extension in the NDF? */
   int update;                /* Was NDF opened for UPDATE access? */
   size_t i;                  /* Loop count */
   size_t junk;               /* Unused value */

/* Initialise the returned value to indicate no sky has been subtractred. */
   result = 0;

/* Assume the sky map was not created by an interupted previous run of
   makemap. */
   *iters = -1;

/* Check inherited status. */
   if( *status != SAI__OK ) return result;

/* Begin an AST context. */
   astBegin;

/* The IMPORTSKY config parameter should have the name of the ADAM
   parameter to use for acquiring the NDF that contains the initial sky
   estimate. If IMPORTSKY is "1", use REF. */
   cval = NULL;
   astMapGet0C( keymap, "IMPORTSKY", &cval );
   if( cval ) {
      if( !astChrMatch( cval, "REF" ) &&
          !astChrMatch( cval, "MASK2" ) &&
          !astChrMatch( cval, "MASK3" ) ) {
         astMapGet0I( keymap, "IMPORTSKY", &result );
         cval = ( result > 0 ) ? "REF" : NULL;
      }
      if( cval ) {
         result = 1;
         strcpy( refparam, cval );
         astChrCase( NULL, refparam, 1, 0 );
      }
   }

/* Do nothing more if we are not subtracting an initial sky from the data. */
   if( result && *status == SAI__OK ) {

/* Begin an NDF context. */
      ndfBegin();

/* Get an identifier for the NDF using the associated ADAM parameter.
   First try UPDATE access. If this fails try READ access. */
      ndfAssoc( refparam, "UPDATE", &indf1, status );
      if( *status != SAI__OK ) {
         errAnnul( status );
         ndfAssoc( refparam, "READ", &indf1, status );
         update = 0;
      } else {
         update = 1;
      }

/* Tell the user what we are doing. */
      ndfMsg( "N", indf1 );
      msgOut( "", "Using ^N as the initial guess at the sky", status );

/* Get a section from this NDF that matches the bounds of the map. */
      ndfSect( indf1, 2, dat->lbnd_out, dat->ubnd_out, &indf2, status );

/* Ensure masked values are not set bad in the mapped data array. */
      ndfSbb( 0, indf2, status );

/* Map the data array section, and copy it into the map buffer. */
      ndfMap( indf2, "DATA", "_DOUBLE", "READ", (void **) &ptr, &nel, status );
      if( *status == SAI__OK ) {
         memcpy( dat->map, ptr, dat->msize*sizeof(*ptr));
      }

/* Map the variance array section, and copy it into the map buffer. */
      ndfState( indf2, "VARIANCE", &there, status );
      if( there ) {
         ndfMap( indf2, "VARIANCE", "_DOUBLE", "READ", (void **) &vptr, &nel, status );
         if( *status == SAI__OK ) {
            memcpy( dat->mapvar, vptr, dat->msize*sizeof(*vptr));
         }
      }

/* If the NDF was created by a previous run of makemap that was interupted
   using control-C, it will contain a NUMITER item in the smurf extension,
   which gives the number of iterations that were completed before the
   map was created. Obtain and return this value, if it exists. */
      ndfXstat( indf1, SMURF__EXTNAME, &there, status );
      if( there ) ndfXgt0i( indf1, SMURF__EXTNAME, "NUMITER", iters,
                            status );

/* If the NDF has a Quality component, import it and create initial AST,
   FLT, PCA, SSN and COM masks from it. These will often be over-ridden by
   new masks calculated with smf_calcmodel_ast below, but will not be
   over-written if the masks have been frozen by xxx.zero_freeze. */
      ndfState( indf2, "Quality", &there, status );
      if( there && dat->mapqual ) {
         smf_qual_t *qarray = smf_qual_map( wf, indf2, "Read", NULL, &junk,
                                            status );
         if( *status == SAI__OK ) {
            smf_qual_t *pq = qarray;
            for( i = 0; i < dat->msize; i++,pq++ ) {
               if( *pq & SMF__MAPQ_AST ) {
                  if( !dat->ast_mask ) dat->ast_mask = astCalloc( dat->msize,
                                                  sizeof( *(dat->ast_mask) ) );
                  (dat->ast_mask)[ i ] = 1;
               }
               if( *pq & SMF__MAPQ_FLT ) {
                  if( !dat->flt_mask ) dat->flt_mask = astCalloc( dat->msize,
                                                  sizeof( *(dat->flt_mask) ) );
                  (dat->flt_mask)[ i ] = 1;
               }
               if( *pq & SMF__MAPQ_COM ) {
                  if( !dat->com_mask ) dat->com_mask = astCalloc( dat->msize,
                                                  sizeof( *(dat->com_mask) ) );
                  (dat->com_mask)[ i ] = 1;
               }
               if( *pq & SMF__MAPQ_SSN ) {
                  if( !dat->ssn_mask ) dat->ssn_mask = astCalloc( dat->msize,
                                                  sizeof( *(dat->ssn_mask) ) );
                  (dat->ssn_mask)[ i ] = 1;
               }
               if( *pq & SMF__MAPQ_PCA ) {
                  if( !dat->pca_mask ) dat->pca_mask = astCalloc( dat->msize,
                                                  sizeof( *(dat->pca_mask) ) );
                  (dat->pca_mask)[ i ] = 1;
               }
            }
         }
         qarray = astFree( qarray );
      }

/* Indicate the map arrays within the supplied smfDIMMData structure now
   contain usable values. We need to do this before calling
   smf_calcmodel_ast below so that the right mask gets used in
   smf_calcmodel_ast. */
      dat->mapok = 1;

/* Apply any existinction correction to the cleaned bolometer data. */
      if( dat->ext ) smf_calcmodel_ext( wf, dat, 0, keymap, dat->ext, 0,
                                        status);

/* Sample the above map at the position of each bolometer sample and
   subtract the sampled value from the cleaned bolometer value. */
      smf_calcmodel_ast( wf, dat, 0, keymap, NULL, SMF__DIMM_PREITER, status);

/* Remove any existinction correction to the modifed bolometer data. */
      if( dat->ext ) smf_calcmodel_ext( wf, dat, 0, keymap, dat->ext,
                                        SMF__DIMM_INVERT, status);

/* If the NDF was opened with UPDATE access, update the quality array in
   the NDF to reflect the AST mask created by smf_calcmodel_ast above. */
      if( update ) {
         smf_qual_t *qarray = astStore( NULL, dat->mapqual, dat->msize*sizeof(*qarray) );
         qarray = smf_qual_unmap( wf, indf2, SMF__QFAM_MAP, qarray, status );
      }

/* End the NDF context. */
      ndfEnd( status );
   }

/* End the AST context. */
   astEnd;

/* Return the pointer to the boolean mask. */
   return result;
}
Exemple #5
0
void smf_snrmask( ThrWorkForce *wf, int abssnr, unsigned char *oldmask,
                  const double *map, const double *mapvar,
                  const dim_t *dims, double snr_hi, double snr_lo,
                  unsigned char *mask, int *status ){

/* Local Variables: */
   const double *pm = NULL;
   const double *pv = NULL;
   dim_t i;
   dim_t j;
   double snr;
   int *cindex = NULL;
   int *ps = NULL;
   int *psn = NULL;
   int *table = NULL;
   int iass;
   int iclean;
   int iclump;
   int ineb;
   int itemp;
   int itop1;
   int itop2;
   int iworker;
   int neb_offset[ 4 ];
   int nworker;
   int ok;
   int rowstep;
   int top;
   smfSnrMaskJobData *job_data = NULL;
   smfSnrMaskJobData *pdata = NULL;
   unsigned char *maskold = NULL;

/* Check inherited status */
   if( *status != SAI__OK ) return;

/* Save a copy of the old mask, if supplied. Doing it now, means that the
   old and new mask pointers can be the same. */
   if( oldmask ) maskold = astStore( NULL, oldmask,
                                     sizeof(*oldmask)*dims[0]*dims[1] );

/* Allocate an array to hold a clump index for every map pixel. Initialise
   it to hold zeros. */
   cindex = astCalloc( dims[ 0 ]*dims[ 1 ], sizeof( *cindex ) );

/* Initialise the index to assign to the next clump of pixels found above
   the lower SNR limit. Note, no clump is given an index of zero. */
   top = 1;

/* Initialise the pointer to the table holding associated clump indices.
   The first element is unused, so set it to a safe value of zero (i.e.
   "no clump"). */
   table = astCalloc( top, sizeof( *table ) );

/* Set up the vector offsets to the three neighbouring pixels in the lower
   row, and the left hand neighbour in the current row. */
   neb_offset[ 0 ] = -1;              /* Left neighbour in current row */
   neb_offset[ 1 ] = -dims[ 0 ] - 1;  /* Left neighbour in lower row */
   neb_offset[ 2 ] = -dims[ 0 ];      /* Central neighbour in lower row */
   neb_offset[ 3 ] = -dims[ 0 ] + 1;  /* Right neighbour in lower row */

/* Loop round the map, looking for pixels that are above the lower SNR
   limit. Within this loop we store a positive clump index for each pixel that
   is above the lower SNR limit. Each clump of contiguous pixel above the limit
   has a separate clump index. If two clumps touch each other, we associate
   their indices together using a table to indicate that they are part of the
   same physical clump. */
   pm = map;
   pv = mapvar;
   ps = cindex;
   for( j = 0; j < dims[ 1 ] && *status == SAI__OK; j++ ) {
      for( i = 0; i < dims[ 0 ]; i++, pm++, pv++, ps++ ) {

/* Get the SNR value. */
         if( mapvar ) {
            if( *pm != VAL__BADD && *pv != VAL__BADD && *pv > 0.0 ){
               snr = *pm / sqrt( *pv );
            } else {
               snr = VAL__BADD;
            }
         } else {
            snr = *pm;
         }

/* If source can be negative as well as positive, use the absolute SNR in
   the following check. */
         if( abssnr && snr != VAL__BADD ) snr = fabs( snr );

/* Check the SNR is good and above the lower limit. */
         if( snr != VAL__BADD && snr > snr_lo ){

/* The three neighbouring pixels on row (j-1), and the left hand
   neighbouring pixel on row j, have already been checked on earlier
   passes round this loop. Check each of these four pixels in turn to see
   if they were flagged as being above the lower SNR limit. */
            itop1 = 0;
            for( ineb = 0; ineb < 4; ineb++ ) {

/* Get a pointer to the neighbouring clump index value, checking it is not off
   the edge of the array. */
               if( ineb == 0 ) {
                  ok = ( i > 0 );
               } else if( ineb == 1 ) {
                  ok = ( i > 0 && j > 0 );
               } else if( ineb == 2 ) {
                  ok = ( j > 0 );
               } else {
                  ok = ( i < dims[ 0 ] - 1 && j > 0 );
               }
               if( ok ) {
                  psn = ps + neb_offset[ ineb ];

/* If this neighbour is flagged as above the lower SNR limit (i.e. has a
   positive clump index), and the current pixel has not yet been assigned to
   an existing clump, assign the neighbour's clump index to the current pixel. */
                  if( *psn > 0 ) {
                     if( *ps == 0 ) {
                        *ps = *psn;

/* Find the clump index at the top of the tree containing the neighbouring pixel. */
                        itop1 = *psn;
                        while( table[ itop1 ] ) itop1 = table[ itop1 ];

/* If this neighbour is flagged as above the lower SNR limit, but the
   current pixel has already been assigned to an existing clump, the current
   pixel is adjacent to both clumps and so joins them into one. So record that
   this neighbours clump index should be associated with the clump index of
   the current pixel. */
                     } else {

/* We need to check first that the two clump indices are not already part
   of the same tree of associated clumps. Without this we could produce
   loops in the tree. Find the clump indices at the top of the tree
   containing the neighbouring pixel. */
                        itop2 = *psn;
                        while( table[ itop2 ] ) itop2 = table[ itop2 ];

/* If the two clumps are not in the same tree, indicate that the pixel
   index at the top of the tree for the neighbouring pixels clump index is
   associated with the central pixel's clump index. */
                        if( itop1 != itop2 ) table[ itop2 ] = *ps;
                     }
                  }
               }
            }

/* If the current pixel has no neighbours that are above the lower SNR
   limit, we start a new clump for the current pixel. */
            if( *ps == 0 ) {

/* Assign the next clump index to the current pixel, and then increment
   the next clump index. Report an error if we have reached the max
   allowable clump index value. */
               if( top == INT_MAX ) {
                  *status = SAI__ERROR;
                  errRep( "", "smf_snrmask: Too many low-SNR clumps found.",
                          status );
                  break;
               }
               *ps = top++;

/* Extend the table that holds the associations between clumps. This
   table has one element for each clump index (plus an unused element at the
   start for the unused clump index "0"). The value stored in this table
   for a given clump index is the index of another clump with which the
   first clump should be associated. If two clumps are associated it
   indicates that they are part of the same physical clump. Associations
   form a tree structure. A value of zero in this table indicates that
   the clump is either unassociated with any other clump, or is at the head
   of a tree of associated clumps. */
               table = astGrow( table, top, sizeof( *table ) );
               if( *status != SAI__OK ) break;
               table[ *ps ] = 0;
            }
         }
      }
   }

/* We now loop round the map again, this time looking for pixels that are
   above the higher SNR limit. */
   pm = map;
   pv = mapvar;
   ps = cindex;
   for( j = 0; j < dims[ 1 ]; j++ ) {
      for( i = 0; i < dims[ 0 ]; i++, pm++, pv++, ps++ ) {

/* Get the SNR value. */
         if( mapvar ) {
            if( *pm != VAL__BADD && *pv != VAL__BADD && *pv > 0.0 ){
               snr = *pm / sqrt( *pv );
            } else {
               snr = VAL__BADD;
            }
         } else {
            snr = *pm;
         }

/* If source can be negative as well as positive, use the absolute SNR. */
         if( abssnr && snr != VAL__BADD ) snr = fabs( snr );

/* Check the SNR is good and above the upper limit. */
         if( snr != VAL__BADD && snr > snr_hi ){

/* Since this pixel is above the higher SNR limit, it must also be above
   the lower SNR Limit, and so will have a non-zero clump index. We flag that
   this clump contains "source" pixels by storing a value of -1 for it in the
   clump association table. First record the original value for later use. */
            iass = table[ *ps ];
            table[ *ps ] = -1;

/* If this clump index is associated with another clump (i.e. had a non-zero
   value in the clump association table), the two clumps adjoins each other.
   So indicate that the second clump also contains "source" pixels by
   changing its table value to -1. Enter a loop to do this all the way up
   to the top of the association tree. Note, this is not necessarily all
   adjoining clumps, since we have only gone "up" the tree - there may be
   other adjoining clumps lower down the tree. */
            while( iass > 0 ) {
               itemp =  table[ iass ];
               table[ iass ] = -1;
               iass = itemp;
            }
         }
      }
   }

/* Now check all cumps to see if they adjoin a "source" clump. Note, no
   clumps are given the index zero, so we skip the first element of the
   table. */
   for( iclump = 1; iclump < top; iclump++ ) {
      iass = table[ iclump ];

/* Work up the tree of neighbouring clumps until we find a clump that has
   an index of 0 or -1. If 0, it means that we have reached the top of
   the tree without finding a "source" clump. If -1 it means we have
   reached a source clump. */
      while( iass > 0 ) {
         iass =  table[ iass ];
      }

/* If we have found a source clump, then all clumps above it in the tree
   should already be set to -1. We now walk up the tree from the current
   clump until we reach the source clump, marking all intermediate clumps
   as source clumps by setting them to -1 in the table. */
      if( iass < 0 ) {
         iass = iclump;
         while( iass > 0 ) {
            itemp =  table[ iass ];
            table[ iass ] = -1;
            iass = itemp;
         }

/* If no source clump was found, mark all intermediate clumps as
   non-source by setting theem to zero in the table. This may give us a
   little extra speed (maybe) since subsequent walks will terminate
   sooner. */
      } else {
         iass = iclump;
         while( iass > 0 ) {
            itemp =  table[ iass ];
            table[ iass ] = 0;
            iass = itemp;
         }
      }
   }

/* One last pass, to store the final mask values. We can multi-thread
   this bit. Create structures used to pass information to the worker
   threads. If we have more threads than rows, we will process one row
   in each thread and so we can reduce the number of threads used to
   equal the number of rows. */
   nworker = wf ? wf->nworker : 1;
   if( nworker > (int) dims[ 1 ] ) nworker = dims[ 1 ];
   job_data = astMalloc( nworker*sizeof( *job_data ) );

/* Check we can de-reference the job data pointer safely. */
   if( *status == SAI__OK ) {

/* Decide how many rows to process in each thread. */
      rowstep = dims[ 1 ]/nworker;
      if( rowstep == 0 ) rowstep = 1;

/* Set up the information needed by each thread, */
      for( iworker = 0; iworker < nworker; iworker++ ) {
         pdata = job_data + iworker;
         pdata->operation = 1;
         pdata->cindex = cindex;
         pdata->jlo = iworker*rowstep;
         if( iworker == nworker - 1 ) {
            pdata->jhi = dims[ 1 ] - 1;
         } else {
            pdata->jhi = pdata->jlo + rowstep - 1;
         }
         pdata->rowlen = dims[ 0 ];
         pdata->mask = mask;
         pdata->table = table;

/* Pass the job to the workforce for execution. */
         thrAddJob( wf, 0, pdata, smf1_snrmask_job, 0, NULL, status );
      }

/* Wait for the workforce to complete all jobs. */
      thrWait( wf, status );

/* Now clean up the very crinkly edges of the mask. Also, the mask may
   contain small holes which need to be cleaned. Clean it NCLEAN times. */
      for( iclean = 0; iclean < NCLEAN; iclean++ ) {

/* Clean the mask, putting the cleaned mask into "cindex" array. We
   exclude pixels in the first and last rows since they do not have a
   complete set of neighbours (each worker thread also ignores the first
   and last pixel in each row for the same reason). Decide how many rows
   to process in each thread. */
         rowstep = ( dims[ 1 ] - 2 )/nworker;
         if( rowstep == 0 ) rowstep = 1;

/* Modify the information needed by each thread, */
         for( iworker = 0; iworker < nworker; iworker++ ) {
            pdata = job_data + iworker;
            pdata->operation = 2;
            pdata->jlo = iworker*rowstep + 1;
            if( iworker == nworker - 1 ) {
               pdata->jhi = dims[ 1 ] - 2;
            } else {
               pdata->jhi = pdata->jlo + rowstep - 1;
            }

/* Pass the job to the workforce for execution. */
            thrAddJob( wf, 0, pdata, smf1_snrmask_job, 0, NULL, status );
         }

/* Wait for the workforce to complete all jobs. */
         thrWait( wf, status );

/* Transfer the new mask from the "cindex" array back to the "mask" array.
   Add in any source pixels from the old mask if required. */
         for( iworker = 0; iworker < nworker; iworker++ ) {
            pdata = job_data + iworker;
            pdata->maskold = maskold;
            pdata->operation = 3;
            thrAddJob( wf, 0, pdata, smf1_snrmask_job, 0, NULL, status );
         }
         thrWait( wf, status );

/* If an old mask was supplied, ensure any source pixels in the old mask
   are also source pixels in the new mask. */
         if( oldmask ) {
            for( iworker = 0; iworker < nworker; iworker++ ) {
               pdata = job_data + iworker;
               pdata->maskold = maskold;
               pdata->operation = 4;
               thrAddJob( wf, 0, pdata, smf1_snrmask_job, 0, NULL, status );
            }
            thrWait( wf, status );
         }
      }
   }

/* Free resources. */
   job_data = astFree( job_data );
   maskold = astFree( maskold );
   table = astFree( table );
   cindex = astFree( cindex );
}