/* Main entry point . */ void smf_put_global0I( const char *name, int value, int *status ){ /* Check inherited status. */ if( *status != SAI__OK ) return; /* Lock the smurf globals keymap pointer for use by the current thread. If it is currently locked by another thread, wait until it is released. */ astLock( smurf_global_keymap, 1 ); /* Put the value into the KeyMap. */ astMapPut0I( smurf_global_keymap, name, value, NULL ); /* Unlock the smurf globals keymap pointer so that it can be used by other threads. */ astUnlock( smurf_global_keymap, 1 ); }
void smf_pread( Grp *igrp, const char *param, int *status ){ /* Local Variables: */ AstMapping *dlatmap; AstMapping *dlonmap; AstMapping *taimap; AstTable *table; char file[ GRP__SZNAM + 1 ]; char pbuf[ GRP__SZNAM + 1 ]; const char *system; void *p; /* Before we check the error status, see if we are annulling previously created Mappings. If so, get each formatted pointer from the group metadata, get an Object pointer form it, lock it for use by the current thread, and then annul it. Remove the metadata item from the group. */ if( !param ) { pbuf[ 0 ] = 0; smf_get_grp_metadata( igrp, "DLONMAP", pbuf, status ); if( pbuf[ 0 ] ) { sscanf( pbuf, "%p", &p ); dlonmap = (AstMapping *) p; astLock( dlonmap, 0 ); dlonmap = astAnnul( dlonmap ); smf_remove_grp_metadata( igrp, "DLONMAP", status ); } pbuf[ 0 ] = 0; smf_get_grp_metadata( igrp, "DLATMAP", pbuf, status ); if( pbuf[ 0 ] ) { sscanf( pbuf, "%p", &p ); dlatmap = (AstMapping *) p; astLock( dlatmap, 0 ); dlatmap = astAnnul( dlatmap ); smf_remove_grp_metadata( igrp, "DLATMAP", status ); } return; } /* Check the inherited status. */ if( *status != SAI__OK ) return; /* Use the specified parameter to get the name of the text file containing the table of pointing corrections. */ parGet0c( param, file, sizeof( file ) - 1, status ); /* If no file was specified, annul the error. */ if( *status == PAR__NULL ) { errAnnul( status ); /* If a file was obtained sccuesfully, read it. */ } else if( *status == SAI__OK ) { /* Start an AST context. */ astBegin; /* Attempt to read an AST Table from the text file. */ table = atlReadTable( file, status ); /* Create a LutMap from each of the three columns. */ taimap = (AstMapping *) atlTablelutMap( table, "TAI", status ); dlonmap = (AstMapping *) atlTablelutMap( table, "DLON", status ); dlatmap = (AstMapping *) atlTablelutMap( table, "DLAT", status ); /* Create Mappings that transforms TAI into a DLON and DLAT. These use linear interpolation for non-tabulated TAI values. */ astInvert( taimap ); dlonmap = (AstMapping *) astCmpMap( taimap, dlonmap, 1, " " ); dlatmap = (AstMapping *) astCmpMap( taimap, dlatmap, 1, " " ); /* Format the pointers to these two Mappings and store them in the supplied group using names "DLONMAP" and "DLATMAP". */ sprintf( pbuf, "%p", (void *) dlonmap ); smf_add_grp_metadata( igrp, "DLONMAP", pbuf, status ); sprintf( pbuf, "%p", (void *) dlatmap ); smf_add_grp_metadata( igrp, "DLATMAP", pbuf, status ); /* See what system the DLON/DLAT values refer to (default to AZEL). Store it in the group. */ if( !astMapGet0C( table, "SYSTEM", &system ) ) system = "AZEL"; smf_add_grp_metadata( igrp, "PSYSTEM", system, status ); /* Unlock the pointers to the Mappings so that they can be used by a different thread. This also exempts the pointers from AST context handling (until they are re-locked) so the following call to astEnd will not annull them. */ astUnlock( dlonmap, 1 ); astUnlock( dlatmap, 1 ); /* End the AST context. This annuls all Objects created during the context, except for the unlocked Mappings. */ astEnd; /* Debug message. */ msgSetc( "F", file ); msgSetc( "S", system ); msgOutif( MSG__DEBUG, " ", "^S pointing corrections read from file ^F", status ); /* Issue a context message if anything went wrong. */ if( *status != SAI__OK ) { msgSetc( "F", file ); errRep( " ", "Failed to read pointing corrections from text file ^F.", status ); } } }
void smf_calc_mapcoord( ThrWorkForce *wf, AstKeyMap *config, smfData *data, AstFrameSet *outfset, int moving, int *lbnd_out, int *ubnd_out, fts2Port fts_port, int flags, int *status ) { /* Local Variables */ AstSkyFrame *abskyfrm = NULL;/* Output SkyFrame (always absolute) */ AstMapping *bolo2map=NULL; /* Combined mapping bolo->map coordinates */ int bndndf=NDF__NOID; /* NDF identifier for map bounds */ void *data_pntr[1]; /* Array of pointers to mapped arrays in ndf */ int *data_index; /* Mapped DATA_ARRAY part of NDF */ int docalc=1; /* If set calculate the LUT */ int doextension=0; /* Try to write LUT to MAPCOORD extension */ smfFile *file=NULL; /* smfFile pointer */ AstObject *fstemp = NULL; /* AstObject version of outfset */ int ii; /* loop counter */ int indf_lat = NDF__NOID; /* Identifier for NDF to receive lat values */ int indf_lon = NDF__NOID; /* Identifier for NDF to receive lon values */ smfCalcMapcoordData *job_data=NULL; /* Array of job */ int lbnd[1]; /* Pixel bounds for 1d pointing array */ int lbnd_old[2]; /* Pixel bounds for existing LUT */ int lbnd_temp[1]; /* Bounds for bounds NDF component */ int lutndf=NDF__NOID; /* NDF identifier for coordinates */ AstMapping *map2sky_old=NULL;/* Existing mapping map->celestial coord. */ HDSLoc *mapcoordloc=NULL; /* HDS locator to the MAPCOORD extension */ int nw; /* Number of worker threads */ AstFrameSet *oldfset=NULL; /* Pointer to existing WCS info */ AstSkyFrame *oskyfrm = NULL; /* SkyFrame from the output WCS Frameset */ smfCalcMapcoordData *pdata=NULL; /* Pointer to job data */ double *lat_ptr = NULL; /* Pointer to array to receive lat values */ double *lon_ptr = NULL; /* Pointer to array to receive lon values */ int ubnd[1]; /* Pixel bounds for 1d pointing array */ int ubnd_old[2]; /* Pixel bounds for existing LUT */ int ubnd_temp[1]; /* Bounds for bounds NDF component */ int *lut = NULL; /* The lookup table */ dim_t nbolo=0; /* Number of bolometers */ dim_t ntslice=0; /* Number of time slices */ int nmap; /* Number of mapped elements */ AstMapping *sky2map=NULL; /* Mapping celestial->map coordinates */ size_t step; /* step size for dividing up work */ AstCmpMap *testcmpmap=NULL; /* Combined forward/inverse mapping */ AstMapping *testsimpmap=NULL;/* Simplified testcmpmap */ double *theta = NULL; /* Scan direction at each time slice */ int tstep; /* Time slices between full Mapping calculations */ int exportlonlat; /* Dump longitude and latitude values? */ /* Main routine */ if (*status != SAI__OK) return; /* How many threads do we get to play with */ nw = wf ? wf->nworker : 1; /* Initialize bounds to avoid compiler warnings */ lbnd_old[0] = 0; lbnd_old[1] = 0; ubnd_old[0] = 0; ubnd_old[1] = 0; /* Check for pre-existing LUT and de-allocate it. This will only waste time if the MAPCOORD extension is found to be valid and it has to be re-loaded from disk. */ smf_close_mapcoord( data, status ); /* Assert ICD data order */ smf_dataOrder( data, 1, status ); /* Get the data dimensions */ smf_get_dims( data, NULL, NULL, &nbolo, &ntslice, NULL, NULL, NULL, status ); /* If SMF__NOCREATE_FILE is not set, and file associated with an NDF, map a new MAPCOORD extension (or verify an existing one) */ if( !(flags & SMF__NOCREATE_FILE) && data->file ) { doextension = 1; } else { doextension = 0; docalc = 1; } /* Create / check for existing MAPCOORD extension */ if( doextension ) { file = data->file; /* Check type of file before proceeding */ if( file->isSc2store ) { *status = SAI__ERROR; errRep(FUNC_NAME, "File was opened by sc2store library (raw data?)", status); } if( !file->isTstream ) { *status = SAI__ERROR; errRep(FUNC_NAME, "File does not contain time stream data",status); } /* Get HDS locator to the MAPCOORD extension */ mapcoordloc = smf_get_xloc( data, "MAPCOORD", "MAP_PROJECTION", "UPDATE", 0, 0, status ); /* Obtain NDF identifier/placeholder for LUT in MAPCOORD extension*/ lbnd[0] = 0; ubnd[0] = nbolo*ntslice-1; lutndf = smf_get_ndfid( mapcoordloc, "LUT", "UPDATE", "UNKNOWN", "_INTEGER", 1, lbnd, ubnd, status ); if( *status == SAI__OK ) { /* store the NDF identifier */ file->mapcoordid = lutndf; /* Create sky to output grid mapping using the base coordinates to get the coordinates of the tangent point if it hasn't been done yet. */ sky2map = astGetMapping( outfset, AST__CURRENT, AST__BASE ); } /* Before mapping the LUT, first check for existing WCS information and LBND/UBND for the output map. If they are already correct don't bother re-calculating the LUT! */ if( *status == SAI__OK ) { /* Try reading in the WCS information */ kpg1Wread( mapcoordloc, "WCS", &fstemp, status ); oldfset = (AstFrameSet*)fstemp; if( *status == SAI__OK ) { /* Check that the old and new mappings are the same by checking that combining one with the inverse of the other reduces to a UnitMap. */ map2sky_old = astGetMapping( oldfset, AST__BASE, AST__CURRENT ); testcmpmap = astCmpMap( map2sky_old, sky2map, 1, " " ); testsimpmap = astSimplify( testcmpmap ); if( astIsAUnitMap( testsimpmap ) ) { /* The mappings are the same, now just check the pixel bounds in the output map */ lbnd_temp[0] = 1; ubnd_temp[0] = 2; bndndf = smf_get_ndfid( mapcoordloc, "LBND", "READ", "UNKNOWN", "_INTEGER", 1, lbnd_temp, ubnd_temp, status ); if( *status == SAI__OK ) { ndfMap( bndndf, "DATA", "_INTEGER", "READ", data_pntr, &nmap, status ); data_index = data_pntr[0]; if( *status == SAI__OK ) { lbnd_old[0] = data_index[0]; lbnd_old[1] = data_index[1]; } ndfAnnul( &bndndf, status ); } bndndf = smf_get_ndfid( mapcoordloc, "UBND", "READ", "UNKNOWN", "_INTEGER", 1, lbnd_temp, ubnd_temp, status ); if( *status == SAI__OK ) { ndfMap( bndndf, "DATA", "_INTEGER", "READ", data_pntr, &nmap, status ); data_index = data_pntr[0]; if( *status == SAI__OK ) { ubnd_old[0] = data_index[0]; ubnd_old[1] = data_index[1]; } ndfAnnul( &bndndf, status ); } if( *status == SAI__OK ) { /* If we get this far finally do the bounds check! */ if( (lbnd_old[0] == lbnd_out[0]) && (lbnd_old[1] == lbnd_out[1]) && (ubnd_old[0] == ubnd_out[0]) && (ubnd_old[1] == ubnd_out[1]) ) { docalc = 0; /* We don't have to re-calculate the LUT */ msgOutif(MSG__VERB," ",FUNC_NAME ": Existing LUT OK", status); } } } /* Bad status / AST errors at this point due to problems with MAPCOORD. Annul and continue calculating new MAPCOORD extension. */ astClearStatus; errAnnul(status); } else { /* Bad status due to non-existence of MAPCOORD. Annul and continue */ errAnnul(status); } } } /* If we need to calculate the LUT do it here */ if( docalc && (*status == SAI__OK) ) { msgOutif(MSG__VERB," ", FUNC_NAME ": Calculate new LUT", status); /* Get the increment in time slices between full Mapping calculations. The Mapping for intermediate time slices will be approximated. */ dim_t dimval; smf_get_nsamp( config, "TSTEP", data, &dimval, status ); tstep = dimval; /* Get space for the LUT */ if( doextension ) { /* Map the LUT array */ ndfMap( lutndf, "DATA", "_INTEGER", "WRITE", data_pntr, &nmap, status ); data_index = data_pntr[0]; if( *status == SAI__OK ) { lut = data_index; } else { errRep( FUNC_NAME, "Unable to map LUT in MAPCOORD extension", status); } } else { /* alloc the LUT and THETA arrays */ lut = astMalloc( (nbolo*ntslice)*sizeof(*(data->lut)) ); theta = astMalloc( ntslice*sizeof(*(data->theta)) ); } /* Retrieve the sky2map mapping from the output frameset (actually map2sky) */ oskyfrm = astGetFrame( outfset, AST__CURRENT ); sky2map = astGetMapping( outfset, AST__BASE, AST__CURRENT ); /* If the longitude and latitude is being dumped, create new NDFs to hold them, and map them. */ if( config ) { astMapGet0I( config, "EXPORTLONLAT", &exportlonlat ); if( exportlonlat ) { lon_ptr = smf1_calc_mapcoord1( data, nbolo, ntslice, oskyfrm, &indf_lon, 1, status ); lat_ptr = smf1_calc_mapcoord1( data, nbolo, ntslice, oskyfrm, &indf_lat, 2, status ); } } /* Invert the mapping to get Output SKY to output map coordinates */ astInvert( sky2map ); /* Create a SkyFrame in absolute coordinates */ abskyfrm = astCopy( oskyfrm ); astClear( abskyfrm, "SkyRefIs" ); astClear( abskyfrm, "SkyRef(1)" ); astClear( abskyfrm, "SkyRef(2)" ); if( *status == SAI__OK ) { /* --- Begin parellelized portion ------------------------------------ */ /* Start a new job context. Each call to thrWait within this context will wait until all jobs created within the context have completed. Jobs created in higher contexts are ignored by thrWait. */ thrBeginJobContext( wf, status ); /* Allocate job data for threads */ job_data = astCalloc( nw, sizeof(*job_data) ); if( *status == SAI__OK ) { /* Set up job data, and start calculating pointing for blocks of time slices in different threads */ if( nw > (int) ntslice ) { step = 1; } else { step = ntslice/nw; } for( ii=0; (*status==SAI__OK)&&(ii<nw); ii++ ) { pdata = job_data + ii; /* Blocks of time slices */ pdata->t1 = ii*step; pdata->t2 = (ii+1)*step-1; /* Ensure that the last thread picks up any left-over tslices */ if( (ii==(nw-1)) && (pdata->t1<(ntslice-1)) ) { pdata->t2=ntslice-1; } pdata->ijob = -1; pdata->lut = lut; pdata->theta = theta; pdata->lbnd_out = lbnd_out; pdata->moving = moving; pdata->ubnd_out = ubnd_out; pdata->tstep = tstep; pdata->lat_ptr = lat_ptr; pdata->lon_ptr = lon_ptr; pdata->fts_port = fts_port; /* Make deep copies of AST objects and unlock them so that each thread can then lock them for their own exclusive use */ pdata->abskyfrm = astCopy( abskyfrm ); astUnlock( pdata->abskyfrm, 1 ); pdata->sky2map = astCopy( sky2map ); astUnlock( pdata->sky2map, 1 ); /* Similarly, make a copy of the smfData, including only the header information which each thread will need in order to make calls to smf_rebin_totmap */ pdata->data = smf_deepcopy_smfData( data, 0, SMF__NOCREATE_FILE | SMF__NOCREATE_DA | SMF__NOCREATE_FTS | SMF__NOCREATE_DATA | SMF__NOCREATE_VARIANCE | SMF__NOCREATE_QUALITY, 0, 0, status ); smf_lock_data( pdata->data, 0, status ); } for( ii=0; ii<nw; ii++ ) { /* Submit the job */ pdata = job_data + ii; pdata->ijob = thrAddJob( wf, THR__REPORT_JOB, pdata, smfCalcMapcoordPar, 0, NULL, status ); } /* Wait until all of the jobs submitted within the current job context have completed */ thrWait( wf, status ); } /* End the current job context. */ thrEndJobContext( wf, status ); /* --- End parellelized portion -------------------------------------- */ /* Set the lut pointer in data to the buffer */ data->lut = lut; data->theta = theta; /* Write the WCS for the projection to the extension */ if( doextension ) { kpg1Wwrt( (AstObject*)outfset, "WCS", mapcoordloc, status ); /* Write the pixel bounds for the map to the extension */ lbnd_temp[0] = 1; /* Don't get confused! Bounds for NDF that will */ ubnd_temp[0] = 2; /* contain the bounds for the output 2d map! */ bndndf = smf_get_ndfid( mapcoordloc, "LBND", "UPDATE", "UNKNOWN", "_INTEGER", 1, lbnd_temp, ubnd_temp, status ); ndfMap( bndndf, "DATA", "_INTEGER", "WRITE", data_pntr, &nmap, status ); data_index = data_pntr[0]; if( *status == SAI__OK ) { data_index[0] = lbnd_out[0]; data_index[1] = lbnd_out[1]; } else { errRep( FUNC_NAME, "Unable to map LBND in MAPCOORD extension", status); } ndfAnnul( &bndndf, status ); bndndf = smf_get_ndfid( mapcoordloc, "UBND", "UPDATE", "UNKNOWN", "_INTEGER", 1, lbnd_temp, ubnd_temp, status ); ndfMap( bndndf, "DATA", "_INTEGER", "WRITE", data_pntr, &nmap, status ); data_index = data_pntr[0]; if( *status == SAI__OK ) { data_index[0] = ubnd_out[0]; data_index[1] = ubnd_out[1]; } else { errRep( FUNC_NAME, "Unable to map UBND in MAPCOORD extension", status); } ndfAnnul( &bndndf, status ); } } } /* Clean Up */ if( testsimpmap ) testsimpmap = astAnnul( testsimpmap ); if( testcmpmap ) testcmpmap = astAnnul( testcmpmap ); if( map2sky_old ) map2sky_old = astAnnul( map2sky_old ); if( oldfset ) oldfset = astAnnul( oldfset ); if (sky2map) sky2map = astAnnul( sky2map ); if (bolo2map) bolo2map = astAnnul( bolo2map ); if( abskyfrm ) abskyfrm = astAnnul( abskyfrm ); if( oskyfrm ) oskyfrm = astAnnul( oskyfrm ); if( mapcoordloc ) datAnnul( &mapcoordloc, status ); if( indf_lat != NDF__NOID ) ndfAnnul( &indf_lat, status ); if( indf_lon != NDF__NOID ) ndfAnnul( &indf_lon, status ); /* If we get this far, docalc=0, and status is OK, there must be a good LUT in there already. Map it so that it is accessible to the caller; "UPDATE" so that the caller can modify it if desired. */ if( (*status == SAI__OK) && (docalc == 0) ) { smf_open_mapcoord( data, "UPDATE", status ); } /* Clean up job data */ if( job_data ) { for( ii=0; (*status==SAI__OK)&&(ii<nw); ii++ ) { pdata = job_data + ii; if( pdata->data ) { smf_lock_data( pdata->data, 1, status ); smf_close_file( &(pdata->data), status ); } astLock( pdata->abskyfrm, 0 ); pdata->abskyfrm = astAnnul( pdata->abskyfrm ); astLock( pdata->sky2map, 0 ); pdata->sky2map = astAnnul( pdata->sky2map ); } job_data = astFree( job_data ); } }
void smfCalcMapcoordPar( void *job_data_ptr, int *status ) { AstSkyFrame *abskyfrm=NULL; smfData *data=NULL; fts2Port fts_port; int *lbnd_out=NULL; int *lut=NULL; int moving; int *ubnd_out=NULL; dim_t nbolo; /* number of bolometers */ dim_t ntslice; /* number of time slices */ smfCalcMapcoordData *pdata=NULL; /* Pointer to job data */ AstMapping *sky2map=NULL; double *theta = NULL; struct timeval tv1; /* Timer */ struct timeval tv2; /* Timer */ if( *status != SAI__OK ) return; /* Pointer to the data that this thread will process */ pdata = job_data_ptr; /* Check for valid inputs */ if( !pdata ) { *status = SAI__ERROR; errRep( "", "smfCalcMapcoordPar: No job data supplied", status ); return; } /* Extract values from pdata */ abskyfrm = pdata->abskyfrm; data = pdata->data; lut = pdata->lut; theta = pdata->theta; lbnd_out = pdata->lbnd_out; moving = pdata->moving; sky2map = pdata->sky2map; ubnd_out = pdata->ubnd_out; fts_port = pdata->fts_port; smf_get_dims( data, NULL, NULL, &nbolo, &ntslice, NULL, NULL, NULL, status ); /* if t1 past end of the work, nothing to do so we return */ if( pdata->t1 >= ntslice ) { msgOutif( SMF__TIMER_MSG, "", "smfCalcMapcoordPar: nothing for thread to do, returning", status); return; } /* Debugging message indicating thread started work */ msgOutiff( SMF__TIMER_MSG, "", "smfCalcMapcoordPar: thread starting on tslices %zu -- %zu", status, pdata->t1, pdata->t2 ); smf_timerinit( &tv1, &tv2, status ); /* Lock the supplied AST object pointers for exclusive use by this thread. The invoking thread should have unlocked them before starting this job. */ astLock( abskyfrm, 0 ); astLock( sky2map, 0 ); smf_lock_data( data, 1, status ); /* Calculate and store the LUT values for the range of time slices being processed by this thread. A generic algorithm is used for moving targets, but a faster algorithm can be used for stationary targets. */ smf_coords_lut( data, pdata->tstep, pdata->t1, pdata->t2, abskyfrm, sky2map, moving, lbnd_out, ubnd_out, fts_port, lut + pdata->t1*nbolo, theta + pdata->t1, pdata->lon_ptr, pdata->lat_ptr, status ); /* Unlock the supplied AST object pointers so that other threads can use them. */ smf_lock_data( data, 0, status ); astUnlock( abskyfrm, 1 ); astUnlock( sky2map, 1 ); msgOutiff( SMF__TIMER_MSG, "", "smfCalcMapcoordPar: thread finishing tslices %zu -- " "%zu (%.3f sec)", status, pdata->t1, pdata->t2, smf_timerupdate(&tv1, &tv2, status) ); }
void smf__calc_wvm_job( void *job_data, int *status ) { struct timeval tv1; struct timeval tv2; smfData * curdata = NULL; smfArray * thesedata; double prevtime = VAL__BADD; double prevtau = VAL__BADD; double lastgoodtau = VAL__BADD; /* most recent good tau */ size_t lastgoodidx = SMF__BADSZT; /* index of most recent good value */ size_t nbadidx = 0; /* number of time slices in the current gap */ size_t maxgap; dim_t t1; dim_t t2; size_t nrelated; double * taudata = NULL; size_t ngood = 0; dim_t i; smfCalcWvmJobData *pdata; double amprev; AstKeyMap * extpars; if (*status != SAI__OK) return; pdata = (smfCalcWvmJobData *)job_data; t1 = pdata->t1; t2 = pdata->t2; amprev = pdata->airmass; thesedata = pdata->thesedata; taudata = pdata->taudata; nrelated = thesedata->ndat; extpars = pdata->extpars; maxgap = pdata->maxgap; /* Lock the AST pointers to this thread */ astLock( extpars, 0 ); for (i=0;i<thesedata->ndat;i++) { smf_lock_data( (thesedata->sdata)[i], 1, status ); } /* Debugging message indicating thread started work */ msgOutiff( SMF__TIMER_MSG, "", "smfCalcSmoothedWVM: thread starting on slices %" DIM_T_FMT " -- %" DIM_T_FMT, status, t1, t2 ); smf_timerinit( &tv1, &tv2, status); for (i=t1; i<=t2; i++) { if (!curdata) { SELECT_DATA( thesedata, curdata, VAL__BADD, wvm_time, i ); } if (curdata) smf_tslice_ast( curdata, i, 0, NO_FTS, status ); if ( !curdata || curdata->hdr->state->wvm_time == VAL__BADD ) { /* Try the other datas */ SELECT_DATA( thesedata, curdata, VAL__BADD, wvm_time, i ); } if (*status != SAI__OK) break; /* if we have no good data we store a bad value */ if (!curdata) { prevtau = VAL__BADD; } else { const JCMTState * state = NULL; state = curdata->hdr->state; /* if we have old values from the WVM or no value we don't trust them */ if ( state->wvm_time != VAL__BADD && (fabs(state->wvm_time - state->rts_end) * SPD) < 60.0 ) { /* Only calculate a tau when we have new values */ if ( prevtime != state->wvm_time ) { double thistau = VAL__BADD; double airmass = VAL__BADD; prevtime = state->wvm_time; airmass = state->tcs_airmass; if (airmass == VAL__BADD) { airmass = amprev; } else { amprev = airmass; } thistau = smf_calc_wvm( curdata->hdr, airmass, extpars, status ); /* Check status and/or value of tau */ if ( thistau == VAL__BADD ) { if ( *status == SAI__OK ) { *status = SAI__ERROR; errRepf("", "Error calculating tau from WVM temperatures at time slice %" DIM_T_FMT, status, i); } } else if ( thistau < 0.0 ) { msgOutiff( MSG__QUIET, "", "WARNING: Negative WVM tau calculated (%g). Ignoring.", status, thistau ); prevtau = VAL__BADD; } else { prevtau = thistau; } } else { /* We use the previous tau since we should have calculated it earlier */ } } else { /* No good reading so tau is bad */ prevtau = VAL__BADD; } } /* Prevtau is the tau that should be assigned to the current position */ /* see about gaps */ if (prevtau == VAL__BADD) { nbadidx++; } else { /* we have a good value so we now have to see if there is a gap to fill */ if (i > 0 && lastgoodidx != (i-1) ) { /* the previous value was bad so we may have to patch up if small */ if ( nbadidx < maxgap ) { size_t j; if (lastgoodidx == SMF__BADSZT) { /* gap is at the start so fill with current value */ for (j=t1; j<i;j++) { taudata[j] = prevtau; ngood++; } } else { /* replace with mean value */ double meantau = (lastgoodtau + prevtau) / 2.0; for (j=lastgoodidx+1; j<i; j++) { taudata[j] = meantau; ngood++; } } } } /* we know this index was good */ lastgoodidx = i; lastgoodtau = prevtau; nbadidx = 0; ngood++; } /* Store the current tau value */ taudata[i] = prevtau; } /* if the last value in the time series was bad we need to see about filling with the last good value */ if (*status == SAI__OK && nbadidx > 0 && nbadidx < maxgap) { for (i=lastgoodidx+1; i<=t2; i++) { taudata[i] = lastgoodtau; ngood++; } } /* Report the time taken in this thread. */ msgOutiff( SMF__TIMER_MSG, "", "smfCalcSmoothedWVM: thread finishing slices %" DIM_T_FMT " -- %" DIM_T_FMT " (%zu good) (%.3f sec)", status, t1, t2, ngood, smf_timerupdate( &tv1, &tv2, status ) ); /* Store number of good values */ pdata->ngood = ngood; /* Unlock the AST pointers from this thread */ astUnlock( extpars, 1 ); for (i=0;i<thesedata->ndat;i++) { smf_lock_data( (thesedata->sdata)[i], 0, status ); } }
void smf_calc_smoothedwvm ( ThrWorkForce *wf, const smfArray * alldata, const smfData * adata, AstKeyMap* extpars, double **wvmtau, size_t *nelems, size_t *ngoodvals, int * status ) { size_t i; size_t nrelated = 0; /* Number of entries in smfArray */ size_t nframes = 0; /* Number of timeslices */ size_t ngood = 0; /* Number of elements with good tau */ double *taudata = NULL; /* Local version of WVM tau */ const smfArray * thesedata = NULL; /* Collection of smfDatas to analyse */ smfArray * tmpthesedata = NULL; /* Local version of adata in a smfArray */ if (*status != SAI__OK) return; if (alldata && adata) { *status = SAI__ERROR; errRep("", "smf_calc_smoothedwvm can not be given non-NULL alldata and non-NULL adata arguments" " (possible programming error)", status ); return; } if (!alldata && !adata) { *status = SAI__ERROR; errRep("", "smf_calc_smoothedwvm: One of alldata or adata must be non-NULL", status); return; } if (!wvmtau) { *status = SAI__ERROR; errRep("", "Must supply a non-NULL pointer for wvmtau argument" " (possible programming error)", status ); return; } /* if we have a single smfData put it in a smfArray */ if (alldata) { if (alldata->ndat == 0 ) { *status = SAI__ERROR; errRep("", "No smfDatas present in supplied smfArray for WVM smoothing" " (possible programming error)", status ); return; } thesedata = alldata; } else { tmpthesedata = smf_create_smfArray( status ); if (tmpthesedata) { tmpthesedata->owndata = 0; /*not owned by the smfArray */ /* we know that the smfData here will not be touched in this function so we do the BAD thing of casting const to non-const */ smf_addto_smfArray( tmpthesedata, (smfData *)adata, status ); } thesedata = tmpthesedata; } /* Check that we have headers and that the smfData are the same length */ nrelated = thesedata->ndat; for (i = 0; i < nrelated; i++ ) { smfData * data = (thesedata->sdata)[i]; smfHead * hdr = data->hdr; dim_t thisframes = 0; if ( !hdr) { *status = SAI__ERROR; errRepf( "", "smfData %zu has no header. Aborting WVM smoothing", status, i ); return; } smf_get_dims( data, NULL, NULL, NULL, &thisframes, NULL, NULL, NULL, status ); if (!nframes) nframes = thisframes; if (thisframes != nframes) { *status = SAI__ERROR; errRepf( "", "smfData %zu has different length. Aborting WVM smoothing", status, i ); return; } } /* We will need the earliest and last airmass value in order to calculate a zenith tau */ /* As a first step, just fill the time series with calculated WVM tau values even though we know there are about 240 fewer tau readings in reality. This initial approach will make it easier to use the smoothed data directly rather than having to interpolate from the 1.2 second data back into the 200 Hz data. */ taudata = astCalloc( nframes, sizeof(*taudata) ); if (*status == SAI__OK) { double amprev = VAL__BADD; double steptime; size_t maxgap; struct timeval tv1; struct timeval tv2; smfCalcWvmJobData *job_data = NULL; int nworker; /* We need to know the steptime so we can define the max good gap in seconds and convert it to steps*/ steptime = (thesedata->sdata)[0]->hdr->steptime; maxgap = (size_t)( 5.0 / steptime ); /* 5 seconds is just larger than 2 WVM readings */ /* Assume all files have the same airmass information */ smf_find_airmass_interval( (thesedata->sdata)[0]->hdr, &rev, NULL, NULL, NULL, status ); smf_timerinit( &tv1, &tv2, status ); /* Create structures used to pass information to the worker threads. */ nworker = wf ? wf->nworker : 1; job_data = astMalloc( nworker*sizeof( *job_data ) ); if (*status == SAI__OK) { dim_t tstep; int iworker; smfCalcWvmJobData *pdata = NULL; /* Get the number of time slices to process in each thread. */ if( nworker > (int) nframes ) { tstep = 1; } else { tstep = nframes/nworker; } /* to return the same values for one thread and multiple threads we need to break the threads on wvm sample boundaries wherever possible. We make an initial estimate of the number of WVM measurements by assuming one every two seconds. */ { smfData * curdata = NULL; size_t nwvm = 0; double prevtime = VAL__BADD; double curtime; size_t *boundaries = astGrow(NULL, nframes*(size_t)(steptime/2.0), sizeof(*boundaries)); for (i=0; i<nframes; i++) { if (!curdata) { SELECT_DATA( thesedata, curdata, VAL__BADD, wvm_time, i ); } if (curdata) smf_tslice_ast( curdata, i, 0, NO_FTS, status ); if ( !curdata || curdata->hdr->state->wvm_time == VAL__BADD ) { /* Try the other datas */ SELECT_DATA( thesedata, curdata, VAL__BADD, wvm_time, i ); } if (*status != SAI__OK) break; if (!curdata) { curtime = VAL__BADD; } else { curtime = curdata->hdr->state->wvm_time; } if (curtime != prevtime || nwvm == 0 ) { /* Store the index in the boundaries array */ nwvm++; boundaries = astGrow(boundaries, nwvm, sizeof(*boundaries)); if (!boundaries) { /* this is serious */ if (*status == SAI__OK) *status = SAI__ERROR; errRep("", "Error allocating temporary memory for WVM calculation\n", status ); break; } boundaries[nwvm-1] = i; prevtime = curtime; } } /* No point using too many threads */ if (*status == SAI__OK) { if (nworker >= (int)nwvm) { nworker = nwvm; /* Allocate a measurement per thread */ for( iworker = 0; iworker < nworker; iworker++ ) { pdata = job_data + iworker; pdata->t1 = boundaries[iworker]; if (iworker+1 < nworker) pdata->t2 = boundaries[iworker+1]-1; } /* Ensure that the last thread picks up any left-over time slices */ pdata->t2 = nframes - 1; } else { /* Allocate the workers to slices of approximate size tstep */ size_t prevend = 0; /* End of previous slice */ size_t prevbnd = 0; /* Index into previous boundaries[] array selection */ for( iworker = 0; iworker < nworker; iworker++ ) { size_t belowidx = prevend+1; size_t aboveidx = nframes; size_t lbnd; size_t ubnd; size_t j; size_t guess; pdata = job_data + iworker; if (iworker == 0) { /* always start at the beginning */ pdata->t1 = 0; } else { /* Start one after the previous block */ pdata->t1 = prevend + 1; } /* Now we have to find the end of this slice */ guess = (iworker*tstep) + tstep - 1; if (guess <= pdata->t1) guess = pdata->t1 + tstep; /* find nearest boundaries */ for (j=prevbnd; j<nwvm; j++) { if ( boundaries[j] > guess ) { aboveidx = boundaries[j]; ubnd = j; if (j>0) { belowidx = boundaries[j-1]; lbnd = j -1 ; } else { lbnd = 0; } break; } } /* Choose the closest, making sure that we are not choosing t1 */ if ( (guess - belowidx < aboveidx - guess) && belowidx > pdata->t1 ) { pdata->t2 = belowidx - 1; prevbnd = lbnd; } else { pdata->t2 = aboveidx - 1; prevbnd = ubnd; } prevend = pdata->t2; if (prevend == nframes - 1 && iworker < nworker-1 ) { /* we have run out of slices so just use fewer workers */ nworker = iworker + 1; break; } } /* Ensure that the last thread picks up any left-over time slices */ pdata->t2 = nframes - 1; } /* Tidy up */ boundaries = astFree( boundaries ); } } /* Store all the other info needed by the worker threads, and submit the jobs to fix the steps in each bolo, and then wait for them to complete. */ for( iworker = 0; iworker < nworker; iworker++ ) { smfArray *thrdata = NULL; pdata = job_data + iworker; pdata->nframes = nframes; pdata->airmass = amprev; /* really need to get it from the start of each chunk */ pdata->taudata = taudata; pdata->maxgap = maxgap; /* Need to copy the smfDatas and create a new smfArray for each thread */ thrdata = smf_create_smfArray( status ); for (i=0;i<nrelated;i++) { smfData *tmpdata = NULL; tmpdata = smf_deepcopy_smfData( wf, (thesedata->sdata)[i], 0, SMF__NOCREATE_FILE | SMF__NOCREATE_DA | SMF__NOCREATE_FTS | SMF__NOCREATE_DATA | SMF__NOCREATE_VARIANCE | SMF__NOCREATE_QUALITY, 0, 0, status ); smf_lock_data( tmpdata, 0, status ); smf_addto_smfArray( thrdata, tmpdata, status ); } pdata->thesedata = thrdata; /* Need to do a deep copy of ast data and unlock them */ pdata->extpars = astCopy(extpars); astUnlock( pdata->extpars, 1 ); /* Pass the job to the workforce for execution. */ thrAddJob( wf, THR__REPORT_JOB, pdata, smf__calc_wvm_job, 0, NULL, status ); } /* Wait for the workforce to complete all jobs. */ thrWait( wf, status ); /* Now free the resources we allocated during job creation and calculate the number of good values */ for( iworker = 0; iworker < nworker; iworker++ ) { smfArray * thrdata; pdata = job_data + iworker; astLock( pdata->extpars, 0 ); pdata->extpars = astAnnul( pdata->extpars ); thrdata = pdata->thesedata; for (i=0;i<thrdata->ndat;i++) { smf_lock_data( (thrdata->sdata)[i], 1, status ); } smf_close_related( wf, &thrdata, status ); ngood += pdata->ngood; } } job_data = astFree( job_data ); msgOutiff( MSG__NORM, "", FUNC_NAME ": %f s to calculate unsmoothed WVM tau values", status, smf_timerupdate(&tv1,&tv2,status) ); } if (*status == SAI__OK && extpars) { /* Read extpars to see if we need to smooth */ double smoothtime = VAL__BADD; if (astMapGet0D( extpars, "SMOOTHWVM", &smoothtime ) ) { if (smoothtime != VAL__BADD && smoothtime > 0.0) { smfData * data = (thesedata->sdata)[0]; double steptime = data->hdr->steptime; dim_t boxcar = (dim_t)( smoothtime / steptime ); msgOutiff( MSG__VERB, "", "Smoothing WVM data with %f s tophat function", status, smoothtime ); smf_tophat1D( taudata, nframes, boxcar, NULL, 0, 0.0, status ); /* The tophat smoothing puts a bad value at the start and end of the time series so we replace that with the adjacent value since the step time is much smaller than WVM readout time. If more than one value is bad we do not try to find the good value. */ taudata[0] = taudata[1]; taudata[nframes-1] = taudata[nframes-2]; } } } /* Use this to get the raw WVM output for debugging */ /* if (*status == SAI__OK) { smfData *data = (thesedata->sdata)[0]; smfHead *hdr = data->hdr; printf("# IDX TAU RTS_NUM RTS_END WVM_TIME\n"); for (i=0; i<nframes;i++) { JCMTState * state; state = &(hdr->allState)[i]; printf("%zu %.*g %d %.*g %.*g\n", i, DBL_DIG, taudata[i], state->rts_num, DBL_DIG, state->rts_end, DBL_DIG, state->wvm_time); } } */ /* Free resources */ if (tmpthesedata) smf_close_related( wf, &tmpthesedata, status ); if (*status != SAI__OK) { if (taudata) taudata = astFree( taudata ); *nelems = 0; *ngoodvals = 0; } else { *wvmtau = taudata; *nelems = nframes; *ngoodvals = ngood; } }
void smf_pcorr( smfHead *head, const Grp *igrp, int *status ){ /* Local Variables: */ AstMapping *dlatmap; AstMapping *dlonmap; JCMTState *state; char buff[ GRP__SZNAM + 1 ]; dim_t iframe; double *dlat; double *dlon; double *tai; double dlat_az; double dlat_tr; double dlon_az; double dlon_tr; int azel; void *p; /* Check the inherited status. */ if( *status != SAI__OK ) return; /* See if the supplied group has a metadata item called "DLONMAP". If not, there are no pointing corrections to apply. */ buff[ 0 ] = 0; smf_get_grp_metadata( igrp, "DLONMAP", buff, status ); if( buff[ 0 ] ) { /* The value of the DLONMAP metadata item in the group is the formatted pointer to an AST Mapping from TAI MJD to arc-distance offsets parallel to the longitude axis (in arc-seconds). Unformat the text to get the usable pointer. */ sscanf( buff, "%p", &p ); dlonmap = (AstMapping *) p; /* Likewise, get a pointer to the DLATMAP Mapping, from TAI MJD to arc-distance offsets parallel to the latitude axis (in arc-seconds). */ buff[ 0 ] = 0; smf_get_grp_metadata( igrp, "DLATMAP", buff, status ); if( !buff[ 0 ] ) { dlatmap = NULL; *status = SAI__ERROR; errRep( " ", "smf_pcorr: DLATMAP not found in group metadata.", status ); } else { sscanf( buff, "%p", &p ); dlatmap = (AstMapping *) p; } /* Also get the system of the axes - AZEL or TRACKING. Default to AZEL. */ azel = 1; buff[ 0 ] = 0; smf_get_grp_metadata( igrp, "PSYSTEM", buff, status ); if( buff[ 0 ] ) { buff[ astChrLen( buff ) ] = 0; if( astChrMatch( buff, "TRACKING" ) ) { azel = 0; } else if( !astChrMatch( buff, "AZEL" ) && *status == SAI__OK ) { *status = SAI__ERROR; msgSetc( "S", buff ); errRep( " ", "smf_pcorr: Bad system (^S).", status ); } } /* Allocate arrays to hold the tai, dlon and dlat values at every frame. */ tai = astMalloc( head->nframes*sizeof( double ) ); dlon = astMalloc( head->nframes*sizeof( double ) ); dlat = astMalloc( head->nframes*sizeof( double ) ); if( *status == SAI__OK ) { /* Store the TAI at every frame. */ for( iframe = 0; iframe < head->nframes; iframe++ ){ state = head->allState + iframe; tai[ iframe ] = state->tcs_tai; } /* Lock the Mappings for use by this thread. Wait for them if they are currently in use by a different thread. */ astLock( dlonmap, 1 ); astLock( dlatmap, 1 ); /* Use the Mappings to transform TAI into DLON and DLAT values at every frame (still in arc-seconds). */ astTran1( dlonmap, head->nframes, tai, 1, dlon ); astTran1( dlatmap, head->nframes, tai, 1, dlat ); /* We have now finished with the Mappings, so unlock them so that hey can be used by other threads. */ astUnlock( dlonmap, 1 ); astUnlock( dlatmap, 1 ); /* Loop round every time slice */ dlon_az = AST__BAD; dlat_az = AST__BAD; dlon_tr = AST__BAD; dlat_tr = AST__BAD; for( iframe = 0; iframe < head->nframes ; iframe++ ){ state = head->allState + iframe; /* Apply the correction */ smf_add_smu_pcorr( state, azel, dlon[ iframe ], dlat[ iframe ], status ); } } /* Free resources. */ tai = astFree( tai ); dlon = astFree( dlon ); dlat = astFree( dlat ); } }
static void smf1_fit_qui_job( void *job_data, int *status ) { /* * Name: * smf1_fit_qui_job * Purpose: * Calculate I, Q and U for a block of bolometers. * Invocation: * void smf1_fit_qui_job( void *job_data, int *status ) * Arguments: * job_data = void * (Given) * Pointer to the data needed by the job. Should be a pointer to a * smfFitQUIJobData structure. * status = int * (Given and Returned) * Pointer to global status. * Description: * This routine calculate the I, Q and U values for each bolometer in * a block of bolometers. It runs within a thread instigated by * smf_fit_qui. */ /* Local Variables: */ AstFrameSet *wcs; /* WCS FrameSet for current time slice */ AstMapping *g2s; /* GRID to SKY mapping */ AstMapping *s2f; /* SKY to focal plane mapping */ const JCMTState *allstates;/* Pointer to array of JCMTState structures */ const JCMTState *state; /* JCMTState info for current time slice */ dim_t b1; /* First bolometer index */ dim_t b2; /* Last bolometer index */ dim_t box_size; /* NFirst time slice in box */ dim_t ibolo; /* Bolometer index */ dim_t ibox; dim_t ncol; dim_t nbolo; /* Total number of bolometers */ double *dat; /* Pointer to start of input data values */ double *din; /* Pointer to input data array for bolo/time */ double *ipi; /* Pointer to output I array */ double *ipq; /* Pointer to output Q array */ double *ipu; /* Pointer to output U array */ double *ipv; /* Pointer to output weights array */ double *pm; double *ps; double angle; /* Phase angle for FFT */ double angrot; /* Angle from focal plane X axis to fixed analyser */ double c1; double c2; double c4; double c8; double cosval; /* Cos of angrot */ double fit; double fx[2]; /* Focal plane X coord at bolometer and northern point*/ double fy[2]; /* Focal plane Y coord at bolometer and northern point*/ double gx; /* GRID X coord at bolometer */ double gy; /* GRID Y coord at bolometer */ double matrix[ NPAR*NPAR ]; double paoff; /* WPLATE value corresponding to POL_ANG=0.0 */ double phi; /* Angle from fixed analyser to effective analyser */ double res; double s1; /* Sum of weighted cosine terms */ double s2; /* Sum of weighted sine terms */ double s4; double s8; double sinval; /* Sin of angrot */ double solution[ NPAR ]; double sum1; /* Sum of squared residuals */ double sums[NSUM]; /* Sum of bolometer values */ double sx[2]; /* SKY X coord at bolometer and northern point*/ double sy[2]; /* SKY Y coord at bolometer and northern point*/ double tr_angle; double twophi; double vector[ NPAR ]; double wplate; /* Angle from fixed analyser to have-wave plate */ gsl_matrix_view gsl_m; gsl_vector_view gsl_b; gsl_vector_view gsl_x; int ipolcrd; /* Reference direction for pol_ang */ int nsum1; int pasign; /* +1 or -1 indicating sense of POL_ANG value */ smfFitQUIJobData *pdata; /* Pointer to job data */ smf_qual_t *qin; /* Pointer to input quality array for bolo/time */ smf_qual_t *qua; /* Pointer to start of input quality values */ /* Check inherited status */ if( *status != SAI__OK ) return; /* Begin an AST context */ astBegin; /* Create views of the matrix and vector buffers that can be used by GSL. */ gsl_m = gsl_matrix_view_array( matrix, NPAR, NPAR ); gsl_b = gsl_vector_view_array( vector, NPAR ); gsl_x = gsl_vector_view_array( solution, NPAR ); /* Get a pointer to the job data, and then extract its contents into a set of local variables. */ pdata = (smfFitQUIJobData *) job_data; b1 = pdata->b1; b2 = pdata->b2; nbolo = pdata->nbolo; ncol = pdata->ncol; dat = pdata->dat + b1; qua = pdata->qua + b1; allstates = pdata->allstates; ipi = pdata->ipi ? pdata->ipi + b1 : NULL; ipq = pdata->ipq + b1; ipu = pdata->ipu + b1; ipv = pdata->ipv + b1; ipolcrd = pdata->ipolcrd; pasign = pdata->pasign; paoff = pdata->paoff; angrot = pdata->angrot; box_size = pdata->box_size; wcs = pdata->wcs; if( wcs ) { astLock( wcs, 0 ); /* Get the mapping from GRID to SKY. */ g2s = astSimplify( astGetMapping( wcs, AST__BASE, AST__CURRENT )); /* Get the mapping from SKY to focal plane (x,y) (the index of the FPLANE Frame is fixed at 3 by file sc2ast.c). */ s2f = astSimplify( astGetMapping( wcs, AST__CURRENT, 3 ) ); } else{ g2s = s2f = NULL; } /* Check we have something to do. */ if( b1 < nbolo && *status == SAI__OK ) { /* Loop round all bolometers to be processed by this thread. */ for( ibolo = b1; ibolo <= b2; ibolo++,qua++,dat++ ) { /* If the returned Stokes parameters are to be with respect to Tracking North, get the angle from tracking north at the current bolometer to focal plane Y, measured positive in the sense of rotation from focal plane Y to focal plane X (note this angle may change across the focal plane due to focal plane distortion). Otherwise, use zero. */ if( pdata->wcs ) { /* Get the grid coords of the current bolometer, and transform them to SKY coordinates using the FrameSet. */ gx = ibolo % ncol + 1; gy = ibolo / ncol + 1; astTran2( g2s, 1, &gx, &gy, 1, sx, sy ); /* Increment the sky position slightly to the north. */ sx[ 1 ] = sx[ 0 ]; sy[ 1 ] = sy[ 0 ] + 1.0E-6; /* Transform both sky positions into focal plane coords. */ astTran2( s2f, 2, sx, sy, 1, fx, fy ); /* Get the angle from north to focal plane Y, measured positive in the sense of rotation from focal plane Y to focal plane X. */ if( fx[0] != VAL__BADD && fy[0] != VAL__BADD && fx[1] != VAL__BADD && fy[1] != VAL__BADD ) { tr_angle = atan2( fx[0] - fx[1], fy[1] - fy[0] ); } else { tr_angle = VAL__BADD; } } else { tr_angle = 0.0; } /* If the whole bolometer is bad, put bad values into the outputs. */ if( *qua & SMF__Q_BADB || tr_angle == VAL__BADD ) { if( ipi ) *(ipi++) = VAL__BADD; *(ipq++) = VAL__BADD; *(ipu++) = VAL__BADD; *(ipv++) = VAL__BADD; /* If the bolometer is good, calculate and store the output i, q and u values. */ } else { /* Initialise pointers to the first input data value, quality value and state info to be used in the current fitting box. */ din = dat; qin = qua; state = allstates; /* Form the sums needed to calculate the best fit Q, U and I. This involves looping over all input samples that fall within the fitting box centred on the current output sample. The 44 sums are stored in the "sums" array. Initialise it to hold zeros. */ memset( sums, 0, NSUM*sizeof(*sums) ); for( ibox = 0; ibox < box_size; ibox++,state++ ) { /* Get the POL_ANG value for this time slice. */ angle = state->pol_ang; /* Check the input sample has not been flagged during cleaning and is not bad. */ if( !( *qin & SMF__Q_FIT ) && *din != VAL__BADD && angle != VAL__BADD ) { /* Following SUN/223 (section "Single-beam polarimetry"/"The Polarimeter"), get the angle from the fixed analyser to the half-waveplate axis, in radians. Positive rotation is from focal plane axis 1 (x) to focal plane axis 2 (y). Not sure about the sign of tcs_az/tr_ang at the moment so do not use them yet. */ wplate = 0.0; if( ipolcrd == 0 ) { wplate = pasign*angle + paoff; } else if( *status == SAI__OK ) { *status = SAI__ERROR; errRepf( "", "smf_fit_qui: currently only POL_CRD = " "FPLANE is supported.", status ); } /* if( ipolcrd == 1 ) { wplate += state->tcs_az_ang; } else if( ipolcrd == 2 ) { wplate += state->tcs_tr_ang; } */ /* Get the angle from the fixed analyser to the effective analyser position (see SUN/223 again). The effective analyser angle rotates twice as fast as the half-wave plate which is why there is a factor of 2 here. */ phi = 2*wplate; twophi = 2*phi; /* Form the trig values needed for the sums. */ s8 = sin( 2*twophi ); c8 = cos( 2*twophi ); s4 = sin( twophi ); c4 = cos( twophi ); s2 = sin( phi ); c2 = cos( phi ); s1 = sin( wplate ); c1 = cos( wplate ); /* Update the sums. The order of the following lines define the index within "sums" at which each sum is stored. */ ps = sums; *(ps++) += s4*s4; *(ps++) += s4*c4; *(ps++) += s4*s2; *(ps++) += s4*c2; *(ps++) += s4*s1; *(ps++) += s4*c1; *(ps++) += s4*ibox; *(ps++) += s4; *(ps++) += s4*(*din); *(ps++) += s2*c4; *(ps++) += s2*s2; *(ps++) += s2*c2; *(ps++) += s2*s1; *(ps++) += s2*c1; *(ps++) += s2*ibox; *(ps++) += s2; *(ps++) += s2*(*din); *(ps++) += s1*c4; *(ps++) += s1*c2; *(ps++) += s1*s1; *(ps++) += s1*c1; *(ps++) += s1*ibox; *(ps++) += s1; *(ps++) += s1*(*din); *(ps++) += c4*c4; *(ps++) += c4*c2; *(ps++) += c4*c1; *(ps++) += c4*ibox; *(ps++) += c4; *(ps++) += c4*(*din); *(ps++) += c2*c2; *(ps++) += c2*c1; *(ps++) += c2*ibox; *(ps++) += c2; *(ps++) += c2*(*din); *(ps++) += c1*c1; *(ps++) += c1*ibox; *(ps++) += c1; *(ps++) += c1*(*din); *(ps++) += ibox*ibox; *(ps++) += ibox; *(ps++) += ibox*(*din); *(ps++) += 1.0; *(ps++) += *din; *(ps++) += s4*s8; *(ps++) += s4*c8; *(ps++) += s2*s8; *(ps++) += s2*c8; *(ps++) += s1*s8; *(ps++) += s1*c8; *(ps++) += s8*c4; *(ps++) += s8*c2; *(ps++) += s8*c1; *(ps++) += s8*ibox; *(ps++) += s8; *(ps++) += s8*(*din); *(ps++) += s8*s8; *(ps++) += s8*c8; *(ps++) += c4*c8; *(ps++) += c2*c8; *(ps++) += c1*c8; *(ps++) += c8*ibox; *(ps++) += c8; *(ps++) += c8*(*din); *(ps++) += c8*c8; } din += nbolo; qin += nbolo; } /* Now find the parameters of the best fit. First check that there were sufficient good samples in the fitting box. */ if( sums[42] > 0.8*box_size ) { /* Copy the sums to the correct elements of the 10x10 matrix. */ pm = matrix; *(pm++) = sums[ 0 ]; *(pm++) = sums[ 1 ]; *(pm++) = sums[ 2 ]; *(pm++) = sums[ 3 ]; *(pm++) = sums[ 4 ]; *(pm++) = sums[ 5 ]; *(pm++) = sums[ 6 ]; *(pm++) = sums[ 7 ]; *(pm++) = sums[ 44 ]; *(pm++) = sums[ 45 ]; *(pm++) = sums[ 1 ]; *(pm++) = sums[ 24 ]; *(pm++) = sums[ 9 ]; *(pm++) = sums[ 25 ]; *(pm++) = sums[ 17 ]; *(pm++) = sums[ 26 ]; *(pm++) = sums[ 27 ]; *(pm++) = sums[ 28 ]; *(pm++) = sums[ 50 ]; *(pm++) = sums[ 58 ]; *(pm++) = sums[ 2 ]; *(pm++) = sums[ 9 ]; *(pm++) = sums[ 10 ]; *(pm++) = sums[ 11 ]; *(pm++) = sums[ 12 ]; *(pm++) = sums[ 13 ]; *(pm++) = sums[ 14 ]; *(pm++) = sums[ 15 ]; *(pm++) = sums[ 46 ]; *(pm++) = sums[ 47 ]; *(pm++) = sums[ 3 ]; *(pm++) = sums[ 25 ]; *(pm++) = sums[ 11 ]; *(pm++) = sums[ 30 ]; *(pm++) = sums[ 18 ]; *(pm++) = sums[ 31 ]; *(pm++) = sums[ 32 ]; *(pm++) = sums[ 33 ]; *(pm++) = sums[ 51 ]; *(pm++) = sums[ 59 ]; *(pm++) = sums[ 4 ]; *(pm++) = sums[ 17 ]; *(pm++) = sums[ 12 ]; *(pm++) = sums[ 18 ]; *(pm++) = sums[ 19 ]; *(pm++) = sums[ 20 ]; *(pm++) = sums[ 21 ]; *(pm++) = sums[ 22 ]; *(pm++) = sums[ 48 ]; *(pm++) = sums[ 49 ]; *(pm++) = sums[ 5 ]; *(pm++) = sums[ 26 ]; *(pm++) = sums[ 13 ]; *(pm++) = sums[ 31 ]; *(pm++) = sums[ 20 ]; *(pm++) = sums[ 35 ]; *(pm++) = sums[ 36 ]; *(pm++) = sums[ 37 ]; *(pm++) = sums[ 52 ]; *(pm++) = sums[ 60 ]; *(pm++) = sums[ 6 ]; *(pm++) = sums[ 27 ]; *(pm++) = sums[ 14 ]; *(pm++) = sums[ 32 ]; *(pm++) = sums[ 21 ]; *(pm++) = sums[ 36 ]; *(pm++) = sums[ 39 ]; *(pm++) = sums[ 40 ]; *(pm++) = sums[ 53 ]; *(pm++) = sums[ 61 ]; *(pm++) = sums[ 7 ]; *(pm++) = sums[ 28 ]; *(pm++) = sums[ 15 ]; *(pm++) = sums[ 33 ]; *(pm++) = sums[ 22 ]; *(pm++) = sums[ 37 ]; *(pm++) = sums[ 40 ]; *(pm++) = sums[ 42 ]; *(pm++) = sums[ 54 ]; *(pm++) = sums[ 62 ]; *(pm++) = sums[ 44 ]; *(pm++) = sums[ 50 ]; *(pm++) = sums[ 46 ]; *(pm++) = sums[ 51 ]; *(pm++) = sums[ 48 ]; *(pm++) = sums[ 52 ]; *(pm++) = sums[ 53 ]; *(pm++) = sums[ 54 ]; *(pm++) = sums[ 56 ]; *(pm++) = sums[ 57 ]; *(pm++) = sums[ 45 ]; *(pm++) = sums[ 58 ]; *(pm++) = sums[ 47 ]; *(pm++) = sums[ 59 ]; *(pm++) = sums[ 49 ]; *(pm++) = sums[ 60 ]; *(pm++) = sums[ 61 ]; *(pm++) = sums[ 62 ]; *(pm++) = sums[ 57 ]; *(pm++) = sums[ 64 ]; /* Copy the remaining sums to the correct elements of the 8 vector. */ pm = vector; *(pm++) = sums[ 8 ]; *(pm++) = sums[ 29 ]; *(pm++) = sums[ 16 ]; *(pm++) = sums[ 34 ]; *(pm++) = sums[ 23 ]; *(pm++) = sums[ 38 ]; *(pm++) = sums[ 41 ]; *(pm++) = sums[ 43 ]; *(pm++) = sums[ 55 ]; *(pm++) = sums[ 63 ]; /* Find the solution to the 10x10 set of linear equations. The matrix is symmetric and positive-definite so use Cholesky decomposition. */ memset( solution, 0, NPAR*sizeof(*solution) ); gsl_linalg_cholesky_decomp( &gsl_m.matrix ); gsl_linalg_cholesky_solve( &gsl_m.matrix, &gsl_b.vector, &gsl_x.vector ); /* Modify Q and U so they use the requested reference direction, and store in the output arrays. */ cosval = cos( 2*( angrot - tr_angle ) ); sinval = sin( 2*( angrot - tr_angle ) ); *(ipq++) = 2*( -solution[ 1 ]*cosval + solution[ 0 ]*sinval ); *(ipu++) = 2*( -solution[ 1 ]*sinval - solution[ 0 ]*cosval ); /* Store the correspoinding I value. */ if( ipi ) *(ipi++) = solution[ 6 ]*box_size + 2*solution[ 7 ]; /* Loop over the data again in the same way to calculate the variance of the residuals between the above fit and the supplied data. */ din = dat; qin = qua; state = allstates; sum1 = 0.0; nsum1 = 0; for( ibox = 0; ibox < box_size; ibox++,state++ ) { angle = state->pol_ang; if( !( *qin & SMF__Q_FIT ) && *din != VAL__BADD && angle != VAL__BADD ) { wplate = pasign*angle + paoff; /* if( ipolcrd == 1 ) { wplate += state->tcs_az_ang; } else if( ipolcrd == 2 ) { wplate += state->tcs_tr_ang; } */ phi = 2*wplate; twophi = 2*phi; s8 = sin( 2*twophi ); c8 = cos( 2*twophi ); s4 = sin( twophi ); c4 = cos( twophi ); s2 = sin( phi ); c2 = cos( phi ); s1 = sin( wplate ); c1 = cos( wplate ); fit = solution[0]*s4 + solution[1]*c4 + solution[2]*s2 + solution[3]*c2 + solution[4]*s1 + solution[5]*c1 + solution[6]*ibox + solution[7] + solution[8]*s8 + solution[9]*c8; res = *din - fit; sum1 += res*res; nsum1++; } din += nbolo; qin += nbolo; } /* Calculate the variance of the residuals, and then scale it to get the notional variance for the returned Q,. U and I values. The scaling factor is determined emprically to get reasonable agreement between these notional variances and the noise actually seen in the Q and U values for 10 test observations. The reason for storing these as Q/U variances rather than as a weights component in the SMURF extension is so that makemap can pick them up easily and use them to initialise the NOI model, which is used for weighting the bolometer data when forming the COM model on the first iteration. */ *(ipv++) = 0.0253*sum1/nsum1; /* Store bad values if there were too few good samples in the fitting box. */ } else { if( ipi ) *(ipi++) = VAL__BADD; *(ipq++) = VAL__BADD; *(ipu++) = VAL__BADD; *(ipv++) = VAL__BADD; } } } } if( wcs ) { g2s = astAnnul( g2s ); s2f = astAnnul( s2f ); astUnlock( wcs, 1 ); } /* End the AST context */ astEnd; }
void smf_fit_qui( ThrWorkForce *wf, smfData *idata, smfData **odataq, smfData **odatau, smfData **odatai, dim_t box, int ipolcrd, int pasign, double paoff, double angrot, int north, int *status ){ /* Local Variables: */ AstFrameSet *wcs; /* WCS FrameSet for current time slice */ JCMTState *instate=NULL; /* Pointer to input JCMTState */ JCMTState *outstate=NULL;/* Pointer to output JCMTState */ const char *usesys; /* Tracking system */ dim_t *box_starts; /* Array holding time slice at start of each box */ dim_t box_size; /* First time slice in box */ dim_t intslice; /* ntslice of idata */ dim_t istart; /* Input time index at start of fitting box */ dim_t itime; /* Time slice index */ dim_t nbolo; /* No. of bolometers */ dim_t ncol; /* No. of columns of bolometers in the array */ dim_t ntime; /* Time slices to check */ dim_t ondata; /* ndata of odata */ dim_t ontslice; /* ntslice of odata */ double scale; /* how much longer new samples are */ int bstep; /* Bolometer step between threads */ int iworker; /* Index of a worker thread */ int nworker; /* No. of worker threads */ size_t i; /* loop counter */ smfData *indksquid=NULL; /* Pointer to input dksquid data */ smfFitQUIJobData *job_data = NULL; /* Pointer to all job data */ smfFitQUIJobData *pdata = NULL;/* Pointer to next job data */ smfHead *hdr; /* Pointer to data header this time slice */ smf_qual_t *qua; /* Input quality pointer */ /* Check inherited status */ if( *status != SAI__OK ) return; /* Check supplied arguments. */ if( !idata || !odataq || !odatau ) { *status = SAI__ERROR; errRep( "", "smf_fit_qui: NULL inputs supplied", status ); return; } if( idata->ndims != 3 ) { *status = SAI__ERROR; errRep( "", "smf_fit_qui: idata is not 3-dimensional", status ); return; } /* Ensure the supplied smfData is time-ordered. So "bstride" is 1 and "tstride" is nbolo. */ smf_dataOrder( wf, idata, 1, status ); /* Dimensions of input. */ smf_get_dims( idata, NULL, &ncol, &nbolo, &intslice, NULL, NULL, NULL, status ); /* Store a pointer to the quality array for the input smfData. */ qua = smf_select_qualpntr( idata, NULL, status );; /* Go through the first thousand POL_ANG values to see if they are in units of radians (new data) or arbitrary encoder units (old data). They are assumed to be in radians if no POL_ANG value is larger than 20. This function can only handle new data. */ hdr = idata->hdr; instate = hdr->allState; ntime = ( intslice > 1000 ) ? 1000 : intslice; for( itime = 0; itime < ntime; itime++,instate++ ) { if( instate->pol_ang > 20 ) { *status = SAI__ERROR; errRep( " "," POL2 data contains POL_ANG values in encoder " "units - connot fit to such old data.", status ); break; } } /* Find the input time slice at which each fitting box starts, and the length of the output time axis (in time-slices). */ smf1_find_boxes( intslice, hdr->allState, box, &ontslice, &box_starts, status ); /* Time axis scaling factor. */ scale = (double) intslice / (double) ontslice; /* First copy everything from input to output except for the data that needs to be downsampled */ /* We want to copy everything in the smfHead except for allState. So we make a copy of the allState pointer, and then set it to NULL in the header before the copy */ if( idata->hdr ) { instate = idata->hdr->allState; idata->hdr->allState = NULL; } /* Similarly, we want everything in the smfDa except for the dksquid. */ if( idata->da ) { indksquid = idata->da->dksquid; idata->da->dksquid = NULL; } /* Create copies, storing them in the supplied output smfData structures. Omit the header for U and I, as we will be copying the Q header into them. */ *odataq = smf_deepcopy_smfData( wf, idata, 0, SMF__NOCREATE_DATA | SMF__NOCREATE_VARIANCE | SMF__NOCREATE_QUALITY, 0, 0, status ); *odatau = smf_deepcopy_smfData( wf, idata, 0, SMF__NOCREATE_DATA | SMF__NOCREATE_VARIANCE | SMF__NOCREATE_QUALITY | SMF__NOCREATE_HEAD, 0, 0, status ); if( odatai ) { *odatai = smf_deepcopy_smfData( wf, idata, 0, SMF__NOCREATE_DATA | SMF__NOCREATE_VARIANCE | SMF__NOCREATE_QUALITY | SMF__NOCREATE_HEAD, 0, 0, status ); } /* Restore values in idata now that we're done */ if( instate ) idata->hdr->allState = instate; if( indksquid ) idata->da->dksquid = indksquid; /* Store the required length for the output time axis. The time axis is axis two because the data is time-ordered. */ (*odataq)->dims[ 2 ] = ontslice; (*odatau)->dims[ 2 ] = ontslice; if( odatai) (*odatai)->dims[ 2 ] = ontslice; /* Get output dimensions - assumed to be the same for all three outputs. */ ondata = ontslice*idata->dims[0]*idata->dims[1]; /* Allocate the data arrays for the outputs. */ (*odataq)->pntr[0] = astCalloc( ondata, sizeof(double) ); (*odatau)->pntr[0] = astCalloc( ondata, sizeof(double) ); if( odatai ) (*odatai)->pntr[0] = astCalloc( ondata, sizeof(double) ); /* Allocate arrays for the output variances. */ (*odataq)->pntr[1] = astCalloc( ondata, sizeof(double) ); (*odatau)->pntr[1] = astCalloc( ondata, sizeof(double) ); if( odatai ) (*odatai)->pntr[1] = astCalloc( ondata, sizeof(double) ); /* Create structures used to pass information to the worker threads. */ nworker = wf ? wf->nworker : 1; job_data = astMalloc( nworker*sizeof( *job_data ) ); if( *status == SAI__OK ) { /* Determine which bolometers are to be processed by which threads. */ bstep = nbolo/nworker; if( bstep < 1 ) bstep = 1; for( iworker = 0; iworker < nworker; iworker++ ) { pdata = job_data + iworker; pdata->b1 = iworker*bstep; pdata->b2 = pdata->b1 + bstep - 1; } /* Ensure that the last thread picks up any left-over bolometers */ pdata->b2 = nbolo - 1; /* Loop round all output time slices. */ for( itime = 0; itime < ontslice; itime++ ) { /* Get the index of the first input time slice that contributes to the current output time slice. */ istart = box_starts[ itime ]; /* Get the number of input time slices that contribute to the output time slice. */ box_size = box_starts[ itime + 1 ] - istart; /* If we are using north as the reference direction, get the WCS FrameSet for the input time slice that is at the middle of the output time slice, and set its current Frame to the tracking frame. */ if( north ) { smf_tslice_ast( idata, istart + box_size/2, 1, NO_FTS, status ); wcs = idata->hdr->wcs; usesys = sc2ast_convert_system( (idata->hdr->allState)[0].tcs_tr_sys, status ); astSetC( wcs, "System", usesys ); } else { wcs = NULL; } /* Now enter the parellel code in which each thread calculates the values for a range of bolometers at the current output slice. */ for( iworker = 0; iworker < nworker; iworker++ ) { pdata = job_data + iworker; pdata->dat = ((double *) idata->pntr[0] ) + istart*nbolo; pdata->qua = qua + istart*nbolo; pdata->allstates = hdr->allState + istart; pdata->ipi = odatai ? ( (double*) (*odatai)->pntr[0] ) + itime*nbolo : NULL; pdata->ipq = ( (double*) (*odataq)->pntr[0] ) + itime*nbolo; pdata->ipu = ( (double*) (*odatau)->pntr[0] ) + itime*nbolo; pdata->ipv = ( (double*) (*odataq)->pntr[1] ) + itime*nbolo; pdata->nbolo = nbolo; pdata->ncol = ncol; pdata->box_size = box_size; pdata->ipolcrd = ipolcrd; pdata->pasign = pasign ? +1: -1; pdata->paoff = paoff; pdata->angrot = angrot; if( wcs ) { pdata->wcs = astCopy( wcs ); astUnlock( pdata->wcs, 1 ); } else { pdata->wcs = NULL; } /* Pass the job to the workforce for execution. */ thrAddJob( wf, THR__REPORT_JOB, pdata, smf1_fit_qui_job, 0, NULL, status ); } /* Wait for the workforce to complete all jobs. */ thrWait( wf, status ); /* Lock and annul the AST objects used by each thread. */ if( wcs ) { for( iworker = 0; iworker < nworker; iworker++ ) { pdata = job_data + iworker; astLock( pdata->wcs, 0 ); pdata->wcs = astAnnul( pdata->wcs ); } } } /* Down-sample the smfHead -------------------------------------------------*/ smfHead *hdr = (*odataq)->hdr; hdr->curframe = (dim_t) (((double) hdr->curframe + 0.5) / scale); hdr->nframes = ontslice; hdr->steptime *= scale; strcpy( hdr->dlabel, "Q" ); strncpy( hdr->title, "POL-2 Stokes parameter Q", SMF__CHARLABEL ); /* Down-sample all the JCMTState values using nearest neighbours */ instate = idata->hdr->allState; if( instate ) { hdr->allState = astCalloc( ontslice, sizeof(*instate) ); outstate = hdr->allState; if( *status == SAI__OK ) { size_t frame; /* index of nearest neighbour JCMTState */ for( i=0; i<ontslice; i++ ) { frame = (size_t) round(((double) i + 0.5)*scale); memcpy( outstate + i, instate + frame, sizeof(*instate) ); } /* Then go back and properly down-sample the more important fast-changing fields like pointing. Note that since there are approximate values there already we need to explicitly re-initialize to 0. */ RESAMPSTATE(instate, outstate, rts_end, intslice, ontslice, 0); RESAMPSTATE(instate, outstate, smu_az_jig_x, intslice, ontslice, 0); RESAMPSTATE(instate, outstate, smu_az_jig_y, intslice, ontslice, 0); RESAMPSTATE(instate, outstate, smu_az_chop_x, intslice, ontslice, 0); RESAMPSTATE(instate, outstate, smu_az_chop_y, intslice, ontslice, 0); RESAMPSTATE(instate, outstate, smu_tr_jig_x, intslice, ontslice, 0); RESAMPSTATE(instate, outstate, smu_tr_jig_y, intslice, ontslice, 0); RESAMPSTATE(instate, outstate, smu_tr_chop_x, intslice, ontslice, 0); RESAMPSTATE(instate, outstate, smu_tr_chop_y, intslice, ontslice, 0); RESAMPSTATE(instate, outstate, tcs_tai, intslice, ontslice, 0); RESAMPSTATE(instate, outstate, tcs_airmass, intslice, ontslice, 0); /* Second coordinates (Dec, El etc) can not wrap 0 to 360 so we do not need to test for those cases */ RESAMPSTATE(instate, outstate, tcs_az_ang, intslice, ontslice, 1); RESAMPSTATE(instate, outstate, tcs_az_ac1, intslice, ontslice, 1); RESAMPSTATE(instate, outstate, tcs_az_ac2, intslice, ontslice, 0); RESAMPSTATE(instate, outstate, tcs_az_dc1, intslice, ontslice, 1); RESAMPSTATE(instate, outstate, tcs_az_dc2, intslice, ontslice, 0); RESAMPSTATE(instate, outstate, tcs_az_bc1, intslice, ontslice, 1); RESAMPSTATE(instate, outstate, tcs_az_bc2, intslice, ontslice, 0); RESAMPSTATE(instate, outstate, tcs_tr_ang, intslice, ontslice, 1); RESAMPSTATE(instate, outstate, tcs_tr_ac1, intslice, ontslice, 1); RESAMPSTATE(instate, outstate, tcs_tr_ac2, intslice, ontslice, 0); RESAMPSTATE(instate, outstate, tcs_tr_dc1, intslice, ontslice, 1); RESAMPSTATE(instate, outstate, tcs_tr_dc2, intslice, ontslice, 0); RESAMPSTATE(instate, outstate, tcs_tr_bc1, intslice, ontslice, 1); RESAMPSTATE(instate, outstate, tcs_tr_bc2, intslice, ontslice, 0); RESAMPSTATE(instate, outstate, tcs_en_dc1, intslice, ontslice, 1); RESAMPSTATE(instate, outstate, tcs_en_dc2, intslice, ontslice, 0); RESAMPSTATE(instate, outstate, tcs_dm_abs, intslice, ontslice, 1); RESAMPSTATE(instate, outstate, tcs_dm_rel, intslice, ontslice, 0); /* Wait for all the above smf_downsamp1 jobs to finish. */ thrWait( wf, status ); } } /* Add a keyword to the Q header indicating the polarimetric reference direction. */ smf_fits_updateL( (*odataq)->hdr, "POLNORTH", north, north ? "Pol ref dir is tracking north" : "Pol ref dir is focal plane Y", status ); /* Copy the Q header to the other outputs. */ hdr = smf_deepcopy_smfHead( (*odataq)->hdr, status ); (*odatau)->hdr = hdr; if( *status == SAI__OK ) { strcpy( hdr->dlabel, "U" ); strncpy( hdr->title, "POL-2 Stokes parameter U", SMF__CHARLABEL ); } if( odatai ) { hdr = smf_deepcopy_smfHead( (*odataq)->hdr, status ); (*odatai)->hdr = hdr; if( *status == SAI__OK ) { strcpy( hdr->dlabel, "I" ); strncpy( hdr->title, "POL-2 Stokes parameter I", SMF__CHARLABEL ); } } } /* Copy the variances from the Q smfData into the U and (and I) smfData. */ if( *odataq && *status == SAI__OK ) { if( *odatau ) { memcpy( (*odatau)->pntr[1], (*odataq)->pntr[1], ondata*sizeof(double)); } if( odatai && *odatai ) { memcpy( (*odatai)->pntr[1], (*odataq)->pntr[1], ondata*sizeof(double)); } } /* Ensure all smfDatas are time-ordered. */ smf_dataOrder( wf, idata, 1, status ); if( odatai && *odatai ) smf_dataOrder( wf, *odatai, 1, status ); if( *odataq ) smf_dataOrder( wf, *odataq, 1, status ); if( *odatau ) smf_dataOrder( wf, *odatau, 1, status ); /* Free resources. */ job_data = astFree( job_data ); box_starts = astFree( box_starts ); }