//#--------------------------------------------------------------------------- //# MBFITSreader.cc: ATNF single-dish RPFITS reader. //#--------------------------------------------------------------------------- //# livedata - processing pipeline for single-dish, multibeam spectral data. //# Copyright (C) 2000-2009, Australia Telescope National Facility, CSIRO //# //# This file is part of livedata. //# //# livedata is free software: you can redistribute it and/or modify it under //# the terms of the GNU General Public License as published by the Free //# Software Foundation, either version 3 of the License, or (at your option) //# any later version. //# //# livedata is distributed in the hope that it will be useful, but WITHOUT //# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or //# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for //# more details. //# //# You should have received a copy of the GNU General Public License along //# with livedata. If not, see . //# //# Correspondence concerning livedata may be directed to: //# Internet email: mcalabre@atnf.csiro.au //# Postal address: Dr. Mark Calabretta //# Australia Telescope National Facility, CSIRO //# PO Box 76 //# Epping NSW 1710 //# AUSTRALIA //# //# http://www.atnf.csiro.au/computing/software/livedata.html //# $Id: MBFITSreader.cc,v 19.57 2009-10-30 06:34:36 cal103 Exp $ //#--------------------------------------------------------------------------- //# The MBFITSreader class reads single dish RPFITS files (such as Parkes //# Multibeam MBFITS files). //# //# Original: 2000/07/28 Mark Calabretta //#--------------------------------------------------------------------------- #include #include #include #include #include #include #include #include #include #include #include using namespace std; // Numerical constants. const double PI = 3.141592653589793238462643; const double TWOPI = 2.0 * PI; const double HALFPI = PI / 2.0; const double R2D = 180.0 / PI; // Class name const string className = "MBFITSreader" ; //------------------------------------------------- MBFITSreader::MBFITSreader // Default constructor. MBFITSreader::MBFITSreader( const int retry, const int interpolate) { cRetry = retry; if (cRetry > 10) { cRetry = 10; } cInterp = interpolate; if (cInterp < 0 || cInterp > 2) { cInterp = 1; } // Initialize pointers. cBeams = 0x0; cIFs = 0x0; cNChan = 0x0; cNPol = 0x0; cHaveXPol = 0x0; cStartChan = 0x0; cEndChan = 0x0; cRefChan = 0x0; cVis = 0x0; cWgt = 0x0; cBeamSel = 0x0; cIFSel = 0x0; cChanOff = 0x0; cXpolOff = 0x0; cBuffer = 0x0; cPosUTC = 0x0; cMBopen = 0; // Tell RPFITSIN not to report errors directly. //iostat_.errlun = -1; } //------------------------------------------------ MBFITSreader::~MBFITSreader // Destructor. MBFITSreader::~MBFITSreader() { close(); } //--------------------------------------------------------- MBFITSreader::open // Open the RPFITS file for reading. int MBFITSreader::open( char *rpname, int &nBeam, int* &beams, int &nIF, int* &IFs, int* &nChan, int* &nPol, int* &haveXPol, int &haveBase, int &haveSpectra, int &extraSysCal) { const string methodName = "open()" ; LogIO os( LogOrigin( className, methodName, WHERE ) ) ; if (cMBopen) { close(); } strcpy(names_.file, rpname); // Open the RPFITS file. int jstat = -3; if (rpfitsin(jstat)) { sprintf(cMsg, "Failed to open MBFITS file\n%s", rpname); //os << LogIO::SEVERE << cMsg << LogIO::POST ; return 1; } cMBopen = 1; // Tell RPFITSIN that we want the OBSTYPE card. int j; param_.ncard = 1; for (j = 0; j < 80; j++) { names_.card[j] = ' '; } strncpy(names_.card, "OBSTYPE", 7); // Read the first header. jstat = -1; if (rpfitsin(jstat)) { sprintf(cMsg, "Failed to read MBFITS header in file\n" "%s", rpname); //os << LogIO::SEVERE << cMsg << LogIO::POST ; close(); return 1; } // Mopra data has some peculiarities. cMopra = strncmp(names_.instrument, "ATMOPRA", 7) == 0; // Non-ATNF data may not store the position in (u,v,w). if (strncmp(names_.sta, "tid", 3) == 0) { sprintf(cMsg, "Found Tidbinbilla data"); cSUpos = 1; } else if (strncmp(names_.sta, "HOB", 3) == 0) { sprintf(cMsg, "Found Hobart data"); cSUpos = 1; } else if (strncmp(names_.sta, "CED", 3) == 0) { sprintf(cMsg, "Found Ceduna data"); cSUpos = 1; } else { cSUpos = 0; } if (cSUpos) { strcat(cMsg, ", using telescope position\n from SU table."); os << LogIO::WARN << cMsg << LogIO::POST ; cInterp = 0; } // Mean scan rate (for timestamp repairs). cNRate = 0; cAvRate[0] = 0.0; cAvRate[1] = 0.0; cCode5 = 0; // Find the maximum beam number. cNBeam = 0; for (int iBeam = 0; iBeam < anten_.nant; iBeam++) { if (anten_.ant_num[iBeam] > cNBeam) { cNBeam = anten_.ant_num[iBeam]; } } if (cNBeam <= 0) { os << LogIO::SEVERE << "Couldn't determine number of beams." << LogIO::POST ; close(); return 1; } // Construct the beam mask. cBeams = new int[cNBeam]; for (int iBeam = 0; iBeam < cNBeam; iBeam++) { cBeams[iBeam] = 0; } // ...beams present in the data. for (int iBeam = 0; iBeam < anten_.nant; iBeam++) { // Guard against dubious beam numbers, e.g. zeroes in // 1999-09-29_1632_024848p14_071b.hpf and the four scans following. // Note that the actual beam number is decoded from the 'baseline' random // parameter for each spectrum and is only used for beam selection. int beamNo = anten_.ant_num[iBeam]; if (beamNo != iBeam+1) { char sta[8]; strncpy(sta, names_.sta+(8*iBeam), 8); char *cp = sta + 7; while (*cp == ' ') *(cp--) = '\0'; sprintf(cMsg, "RPFITSIN returned beam number %2d for AN table\n" "entry %2d with name '%.8s'", beamNo, iBeam+1, sta); char text[8]; sprintf(text, "MB%2.2d", iBeam+1); cp = cMsg + strlen(cMsg); if (strncmp(sta, text, 8) == 0) { beamNo = iBeam + 1; sprintf(cp, "; using beam number %2d.", beamNo); } else { sprintf(cp, "."); } os << LogIO::WARN << cMsg << LogIO::POST ; } if (0 < beamNo && beamNo <= cNBeam) { cBeams[beamNo-1] = 1; } } // Passing back the address of the array allows PKSFITSreader::select() to // modify its elements directly. nBeam = cNBeam; beams = cBeams; // Number of IFs. cNIF = if_.n_if; cIFs = new int[cNIF]; for (int iIF = 0; iIF < cNIF; iIF++) { cIFs[iIF] = 1; } // Passing back the address of the array allows PKSFITSreader::select() to // modify its elements directly. nIF = cNIF; IFs = cIFs; // Number of channels and polarizations. cNChan = new int[cNIF]; cNPol = new int[cNIF]; cHaveXPol = new int[cNIF]; cGetXPol = 0; int maxProd = 0; for (int iIF = 0; iIF < cNIF; iIF++) { cNChan[iIF] = if_.if_nfreq[iIF]; cNPol[iIF] = if_.if_nstok[iIF]; cNChan[iIF] -= cNChan[iIF]%2; // Do we have cross-polarization data? if ((cHaveXPol[iIF] = cNPol[iIF] > 2)) { // Cross-polarization data is handled separately. cNPol[iIF] = 2; // Default is to get it if we have it. cGetXPol = 1; } // Maximum number of spectral products in any IF. int nProd = if_.if_nfreq[iIF] * if_.if_nstok[iIF]; if (maxProd < nProd) maxProd = nProd; } // Allocate memory for RPFITSIN subroutine arguments. if (cVis) delete [] cVis; if (cWgt) delete [] cWgt; cVis = new float[2*maxProd]; cWgt = new float[maxProd]; nChan = cNChan; nPol = cNPol; haveXPol = cHaveXPol; // Default channel range selection. cStartChan = new int[cNIF]; cEndChan = new int[cNIF]; cRefChan = new int[cNIF]; for (int iIF = 0; iIF < cNIF; iIF++) { cStartChan[iIF] = 1; cEndChan[iIF] = cNChan[iIF]; cRefChan[iIF] = cNChan[iIF]/2 + 1; } cGetSpectra = 1; // No baseline parameters in MBFITS. haveBase = 0; // Always have spectra in MBFITS. haveSpectra = cHaveSpectra = 1; // Integration cycle time (s). cIntTime = param_.intime; // Can't deduce binning mode till later. cNBin = 0; // Read the first syscal record. if (rpget(1, cEOS)) { os << LogIO::SEVERE << "Failed to read first syscal record." << LogIO::POST ; close(); return 1; } // Additional information for Parkes Multibeam data? extraSysCal = (sc_.sc_ant > anten_.nant); cFirst = 1; cEOF = 0; cFlushing = 0; return 0; } //---------------------------------------------------- MBFITSreader::getHeader // Get parameters describing the data. int MBFITSreader::getHeader( char observer[32], char project[32], char telescope[32], double antPos[3], char obsType[32], char bunit[32], float &equinox, char radecsys[32], char dopplerFrame[32], char datobs[32], double &utc, double &refFreq, double &bandwidth) { const string methodName = "getHeader()" ; LogIO os( LogOrigin( className, methodName, WHERE ) ) ; if (!cMBopen) { os << LogIO::SEVERE << "An MBFITS file has not been opened." << LogIO::POST ; return 1; } sprintf(observer, "%-16.16s", names_.rp_observer); sprintf(project, "%-16.16s", names_.object); sprintf(telescope, "%-16.16s", names_.instrument); // Observatory coordinates (ITRF), in m. antPos[0] = doubles_.x[0]; antPos[1] = doubles_.y[0]; antPos[2] = doubles_.z[0]; // This is the only sure way to identify the telescope, maybe. if (strncmp(names_.sta, "MB0", 3) == 0) { // Parkes Multibeam. sprintf(telescope, "%-16.16s", "ATPKSMB"); antPos[0] = -4554232.087; antPos[1] = 2816759.046; antPos[2] = -3454035.950; } else if (strncmp(names_.sta, "HOH", 3) == 0) { // Parkes HOH receiver. sprintf(telescope, "%-16.16s", "ATPKSHOH"); antPos[0] = -4554232.087; antPos[1] = 2816759.046; antPos[2] = -3454035.950; } else if (strncmp(names_.sta, "CA0", 3) == 0) { // An ATCA antenna, use the array centre position. sprintf(telescope, "%-16.16s", "ATCA"); antPos[0] = -4750915.837; antPos[1] = 2792906.182; antPos[2] = -3200483.747; // ATCA-104. Updated position at epoch 2007/06/24 from Chris Phillips. // antPos[0] = -4751640.182; // ± 0.008 // antPos[1] = 2791700.322; // ± 0.006 // antPos[2] = -3200490.668; // ± 0.007 // } else if (strncmp(names_.sta, "MOP", 3) == 0) { // Mopra. Updated position at epoch 2007/06/24 from Chris Phillips. sprintf(telescope, "%-16.16s", "ATMOPRA"); antPos[0] = -4682769.444; // ± 0.009 antPos[1] = 2802618.963; // ± 0.006 antPos[2] = -3291758.864; // ± 0.008 } else if (strncmp(names_.sta, "HOB", 3) == 0) { // Hobart. sprintf(telescope, "%-16.16s", "HOBART"); antPos[0] = -3950236.735; antPos[1] = 2522347.567; antPos[2] = -4311562.569; } else if (strncmp(names_.sta, "CED", 3) == 0) { // Ceduna. Updated position at epoch 2007/06/24 from Chris Phillips. sprintf(telescope, "%-16.16s", "CEDUNA"); antPos[0] = -3753443.168; // ± 0.017 antPos[1] = 3912709.794; // ± 0.017 antPos[2] = -3348067.060; // ± 0.016 } else if (strncmp(names_.sta, "tid", 3) == 0) { // DSS. sprintf(telescope, "%-16.16s", "DSS-43"); antPos[0] = -4460894.727; antPos[1] = 2682361.530; antPos[2] = -3674748.424; } // Observation type. int j; for (j = 0; j < 31; j++) { obsType[j] = names_.card[11+j]; if (obsType[j] == '\'') break; } obsType[j] = '\0'; // Brightness unit. sprintf(bunit, "%-16.16s", names_.bunit); if (strcmp(bunit, "JY") == 0) { bunit[1] = 'y'; } else if (strcmp(bunit, "JY/BEAM") == 0) { strcpy(bunit, "Jy/beam"); } // Coordinate frames. equinox = 2000.0f; strcpy(radecsys, "FK5"); strcpy(dopplerFrame, "TOPOCENT"); // Time at start of observation. sprintf(datobs, "%-10.10s", names_.datobs); utc = cUTC; // Spectral parameters. refFreq = doubles_.if_freq[0]; bandwidth = doubles_.if_bw[0]; return 0; } //-------------------------------------------------- MBFITSreader::getFreqInfo // Get frequency parameters for each IF. int MBFITSreader::getFreqInfo( int &nIF, double* &startFreq, double* &endFreq) { // This is RPFITS - can't do it! return 1; } //---------------------------------------------------- MBFITSreader::findRange // Find the range of the data selected in time and position. int MBFITSreader::findRange( int &nRow, int &nSel, char dateSpan[2][32], double utcSpan[2], double* &positions) { // This is RPFITS - can't do it! return 1; } //--------------------------------------------------------- MBFITSreader::read // Read the next data record (if you're feeling lucky). int MBFITSreader::read( MBrecord &MBrec) { const string methodName = "read()" ; LogIO os( LogOrigin( className, methodName, WHERE ) ) ; int beamNo = -1; int haveData, pCode = 0, status; double raRate = 0.0, decRate = 0.0, paRate = 0.0; MBrecord *iMBuff = 0x0; if (!cMBopen) { os << LogIO::SEVERE << "An MBFITS file has not been opened." << LogIO::POST ; return 1; } // Positions recorded in the input records usually do not coincide with the // midpoint of the integration and hence the input must be buffered so that // true positions may be interpolated. // // On the first call nBeamSel buffers of length nBin, are allocated and // filled, where nBin is the number of time bins. // // The input records for binned, single beam data with multiple simultaneous // IFs are ordered by IF within each integration rather than by bin number // and hence are not in time order. No multibeam data exists with // nBin > 1 but the likelihood that the input records would be in beam/IF // order and the requirement that output records be in time order would // force an elaborate double-buffering system and we do not support it. // // Once all buffers are filled, the next record for each beam pertains to // the next integration and should contain new position information allowing // the proper position for each spectrum in the buffer to be interpolated. // The buffers are then flushed in time order. For single beam data there // is only one buffer and reads from the MBFITS file are suspended while the // flush is in progress. For multibeam data each buffer is of unit length // so the flush completes immediately and the new record takes its place. haveData = 0; while (!haveData) { int iBeamSel = -1, iIFSel = -1; if (!cFlushing) { if (cEOF) { return -1; } // Read the next record. pCode = 0; if ((status = rpget(0, cEOS)) == -1) { // EOF. cEOF = 1; cFlushing = 1; cFlushBin = 0; cFlushIF = 0; #ifdef PKSIO_DEBUG os << LogIO::DEBUGGING << "\nEnd-of-file detected, flushing last cycle.\n" << LogIO::POST ; #endif } else if (status) { // IO error. return 1; } else { if (cFirst) { // First data; cBeamSel[] stores the buffer index for each beam. cNBeamSel = 0; cBeamSel = new int[cNBeam]; for (int iBeam = 0; iBeam < cNBeam; iBeam++) { if (cBeams[iBeam]) { // Buffer offset for this beam. cBeamSel[iBeam] = cNBeamSel++; } else { // Signal that the beam is not selected. cBeamSel[iBeam] = -1; } } // Set up bookkeeping arrays for IFs. cIFSel = new int[cNIF]; cChanOff = new int[cNIF]; cXpolOff = new int[cNIF]; int maxChan = 0; int maxXpol = 0; cSimulIF = 0; for (int iIF = 0; iIF < cNIF; iIF++) { if (cIFs[iIF]) { // Buffer index for each IF within each simultaneous set. cIFSel[iIF] = 0; // Array offsets for each IF within each simultaneous set. cChanOff[iIF] = 0; cXpolOff[iIF] = 0; // Look for earlier IFs in the same simultaneous set. for (int jIF = 0; jIF < iIF; jIF++) { if (!cIFs[jIF]) continue; if (if_.if_simul[jIF] == if_.if_simul[iIF]) { // Got one, increment indices. cIFSel[iIF]++; cChanOff[iIF] += cNChan[jIF] * cNPol[jIF]; if (cHaveXPol[jIF]) { cXpolOff[iIF] += 2 * cNChan[jIF]; } } } // Maximum number of selected IFs in any simultaneous set. cSimulIF = max(cSimulIF, cIFSel[iIF]+1); // Maximum memory required for any simultaneous set. maxChan = max(maxChan, cChanOff[iIF] + cNChan[iIF]*cNPol[iIF]); if (cHaveXPol[iIF]) { maxXpol = max(maxXpol, cXpolOff[iIF] + 2*cNChan[iIF]); } } else { // Signal that the IF is not selected. cIFSel[iIF] = -1; } } // Check for binning mode observations. if (param_.intbase > 0.0f) { cNBin = int((cIntTime / param_.intbase) + 0.5); // intbase sometimes contains rubbish. if (cNBin == 0) { cNBin = 1; } } else { cNBin = 1; } if (cNBin > 1 && cNBeamSel > 1) { os << LogIO::SEVERE << "Cannot handle binning mode for multiple beams.\nSelect a single beam for input." << LogIO::POST ; close(); return 1; } // Allocate buffer data storage; the MBrecord constructor zeroes // class members such as cycleNo that are tested in the first pass // below. int nBuff = cNBeamSel * cNBin; cBuffer = new MBrecord[nBuff]; // Allocate memory for spectral arrays. for (int ibuff = 0; ibuff < nBuff; ibuff++) { cBuffer[ibuff].setNIFs(cSimulIF); cBuffer[ibuff].allocate(0, maxChan, maxXpol); // Signal that this IF in this buffer has been flushed. for (int iIF = 0; iIF < cSimulIF; iIF++) { cBuffer[ibuff].IFno[iIF] = 0; } } cPosUTC = new double[cNBeamSel]; cFirst = 0; cScanNo = 1; cCycleNo = 0; cPrevUTC = -1.0; } // Check for end-of-scan. if (cEOS) { cScanNo++; cCycleNo = 0; cPrevUTC = -1.0; } // Apply beam and IF selection before the change-of-day test to allow // a single selected beam and IF to be handled in binning-mode. beamNo = int(cBaseline / 256.0); if (beamNo == 1) { // Store the position of beam 1 for grid convergence corrections. cRA0 = cU; cDec0 = cV; } iBeamSel = cBeamSel[beamNo-1]; if (iBeamSel < 0) continue; // Sanity check (mainly for MOPS). if (cIFno > cNIF) continue; // Apply IF selection; iIFSel == 0 for the first selected IF, == 1 // for the second, etc. iIFSel = cIFSel[cIFno - 1]; if (iIFSel < 0) continue; if (cNBin > 1) { // Binning mode: correct the time. cUTC += param_.intbase * (cBin - (cNBin + 1)/2.0); } // Check for change-of-day. double cod = 0.0; if ((cUTC + 86400.0) < (cPrevUTC + 600.0)) { // cUTC should continue to increase past 86400 during a single scan. // However, if the RPFITS file contains multiple scans that straddle // midnight then cUTC can jump backwards from the end of one scan to // the start of the next. #ifdef PKSIO_DEBUG char buf[256] ; sprintf(buf, "Change-of-day on cUTC: %.1f -> %.1f\n", cPrevUTC, cUTC); os << LogIO::DEBUGGING << buf << LogIO::POST ; #endif // Can't change the recorded value of cUTC directly (without also // changing dateobs) so change-of-day must be recorded separately as // an offset to be applied when comparing integration timestamps. cod = 86400.0; } if ((cUTC+cod) < cPrevUTC - 1.0) { if (cBin == 1 && iIFSel) { // Multiple-IF, binning-mode data is only partially time ordered. #ifdef PKSIO_DEBUG fprintf(stderr, "New IF in multiple-IF, binning-mode data.\n"); #endif cCycleNo -= cNBin; cPrevUTC = -1.0; } else { // All other data should be fully time ordered. sprintf(cMsg, "Cycle %d:%03d-%03d, UTC went backwards from\n" "%.1f to %.1f! Incrementing day number,\n" "positions may be unreliable.", cScanNo, cCycleNo, cCycleNo+1, cPrevUTC, cUTC); //logMsg(cMsg); os << LogIO::WARN << cMsg << LogIO::POST ; cUTC += 86400.0; } } // New integration cycle? if ((cUTC+cod) > cPrevUTC) { cCycleNo++; cPrevUTC = cUTC + 0.0001; } sprintf(cDateObs, "%-10.10s", names_.datobs); cDateObs[10] = '\0'; // Compute buffer number. iMBuff = cBuffer + iBeamSel; if (cNBin > 1) iMBuff += cNBeamSel*(cBin-1); if (cCycleNo < iMBuff->cycleNo) { // Note that if the first beam and IF are not both selected cEOS // will be cleared by rpget() when the next beam/IF is read. cEOS = 1; } // Begin flush cycle? if (cEOS || (iMBuff->nIF && (cUTC+cod) > (iMBuff->utc+0.0001))) { cFlushing = 1; cFlushBin = 0; cFlushIF = 0; } #ifdef PKSIO_DEBUG char rel = '='; double dt = utcDiff(cUTC, cW); if (dt < 0.0) { rel = '<'; } else if (dt > 0.0) { rel = '>'; } sprintf(buf, "\n In:%4d%4d%3d%3d %.3f %c %.3f (%+.3fs) - " "%sflushing\n", cScanNo, cCycleNo, beamNo, cIFno, cUTC, rel, cW, dt, cFlushing ? "" : "not "); os << LogIO::DEBUGGING << buf << LogIO::POST ; if (cEOS) { sprintf(buf, "Start of new scan, flushing previous scan.\n"); os << LogIO::DEBUGGING << buf << LogIO::POST ; } #endif } } if (cFlushing) { // Find the oldest integration to flush, noting that the last // integration cycle may be incomplete. beamNo = 0; int cycleNo = 0; for (; cFlushBin < cNBin; cFlushBin++) { for (iBeamSel = 0; iBeamSel < cNBeamSel; iBeamSel++) { iMBuff = cBuffer + iBeamSel + cNBeamSel*cFlushBin; // iMBuff->nIF is decremented (below) and if zero signals that all // IFs in an integration have been flushed. if (iMBuff->nIF) { if (cycleNo == 0 || iMBuff->cycleNo < cycleNo) { beamNo = iMBuff->beamNo; cycleNo = iMBuff->cycleNo; } } } if (beamNo) { // Found an integration to flush. break; } // Start with the first IF in the next bin. cFlushIF = 0; } if (beamNo) { iBeamSel = cBeamSel[beamNo-1]; iMBuff = cBuffer + iBeamSel + cNBeamSel*cFlushBin; // Find the IF to flush. for (; cFlushIF < cSimulIF; cFlushIF++) { if (iMBuff->IFno[cFlushIF]) break; } } else { // Flush complete. cFlushing = 0; if (cEOF) { return -1; } // The last record read must have been the first of a new cycle. beamNo = int(cBaseline / 256.0); iBeamSel = cBeamSel[beamNo-1]; // Compute buffer number. iMBuff = cBuffer + iBeamSel; if (cNBin > 1) iMBuff += cNBeamSel*(cBin-1); } } if (cInterp && cFlushing == 1) { // Start of flush cycle, interpolate the beam position. // // The position is measured by the control system at a time returned by // RPFITSIN as the 'w' visibility coordinate. The ra and dec, returned // as the 'u' and 'v' visibility coordinates, must be interpolated to // the integration time which RPFITSIN returns as 'cUTC', this usually // being a second or two later. The interpolation method used here is // based on the scan rate. // // "This" RA, Dec, and UTC refers to the position currently stored in // the buffer marked for output (iMBuff). This position is interpolated // to the midpoint of that integration using either // a) the rate currently sitting in iMBuff, which was computed from // the previous integration, otherwise // b) from the position recorded in the "next" integration which is // currently sitting in the RPFITS commons, // so that the position timestamps straddle the midpoint of the // integration and is thereby interpolated rather than extrapolated. // // At the end of a scan, or if the next position has not been updated // or its timestamp does not advance sufficiently, the most recent // determination of the scan rate will be used for extrapolation which // is quantified by the "rate age" measured in seconds beyond the // interval defined by the position timestamps. // At this point, iMBuff contains cU, cV, cW, parAngle and focusRot // stored from the previous call to rpget() for this beam (i.e. "this"), // and also raRate, decRate and paRate computed from that integration // and the previous one. double thisRA = iMBuff->ra; double thisDec = iMBuff->dec; double thisUTC = cPosUTC[iBeamSel]; double thisPA = iMBuff->parAngle + iMBuff->focusRot; #ifdef PKSIO_DEBUG sprintf(buf, "This (%d) ra, dec, UTC: %9.4f %9.4f %10.3f %9.4f\n", iMBuff->cycleNo, thisRA*R2D, thisDec*R2D, thisUTC, thisPA*R2D); os << LogIO::DEBUGGING << buf << LogIO::POST ; #endif if (cEOF || cEOS) { // Use rates from the last cycle. raRate = iMBuff->raRate; decRate = iMBuff->decRate; paRate = iMBuff->paRate; } else { if (cW == thisUTC) { // The control system at Mopra typically does not update the // positions between successive integration cycles at the end of a // scan (nor are they flagged). In this case we use the previously // computed rates, even if from the previous scan since these are // likely to be a better guess than anything else. raRate = iMBuff->raRate; decRate = iMBuff->decRate; paRate = iMBuff->paRate; if (cU == thisRA && cV == thisDec) { // Position and timestamp unchanged. pCode = 1; } else if (fabs(cU-thisRA) < 0.0001 && fabs(cV-thisDec) < 0.0001) { // Allow small rounding errors (seen infrequently). pCode = 1; } else { // (cU,cV) are probably rubbish (not yet seen in practice). pCode = 2; cU = thisRA; cV = thisDec; } #ifdef PKSIO_DEBUG sprintf(buf, "Next (%d) ra, dec, UTC: %9.4f %9.4f %10.3f " "(0.000s)\n", cCycleNo, cU*R2D, cV*R2D, cW); os << LogIO::DEBUGGING << buf << LogIO::POST ; #endif } else { double nextRA = cU; double nextDec = cV; // Check and, if necessary, repair the position timestamp, // remembering that pCode refers to the NEXT cycle. pCode = fixw(cDateObs, cCycleNo, beamNo, cAvRate, thisRA, thisDec, thisUTC, nextRA, nextDec, cW); if (pCode > 0) pCode += 3; double nextUTC = cW; #ifdef PKSIO_DEBUG sprintf(buf, "Next (%d) ra, dec, UTC: %9.4f %9.4f %10.3f " "(%+.3fs)\n", cCycleNo, nextRA*R2D, nextDec*R2D, nextUTC, utcDiff(nextUTC, thisUTC)); os << LogIO::DEBUGGING << buf << LogIO::POST ; #endif // Compute the scan rate for this beam. double dUTC = utcDiff(nextUTC, thisUTC); if ((0.0 < dUTC) && (dUTC < 600.0)) { scanRate(cRA0, cDec0, thisRA, thisDec, nextRA, nextDec, dUTC, raRate, decRate); // Update the mean scan rate. cAvRate[0] = (cAvRate[0]*cNRate + raRate) / (cNRate + 1); cAvRate[1] = (cAvRate[1]*cNRate + decRate) / (cNRate + 1); cNRate++; // Rate of change of position angle. if (sc_.sc_ant <= anten_.nant) { paRate = 0.0; } else { int iOff = sc_.sc_q * (sc_.sc_ant - 1) - 1; double nextPA = sc_.sc_cal[iOff + 4] + sc_.sc_cal[iOff + 7]; double paDiff = nextPA - thisPA; if (paDiff > PI) { paDiff -= TWOPI; } else if (paDiff < -PI) { paDiff += TWOPI; } paRate = paDiff / dUTC; } if (cInterp == 2) { // Use the same interpolation scheme as the original pksmbfits // client. This incorrectly assumed that (nextUTC - thisUTC) is // equal to the integration time and interpolated by computing a // weighted sum of the positions before and after the required // time. double utc = iMBuff->utc; double tw1 = 1.0 - utcDiff(utc, thisUTC) / iMBuff->exposure; double tw2 = 1.0 - utcDiff(nextUTC, utc) / iMBuff->exposure; double gamma = (tw2 / (tw1 + tw2)) * dUTC / (utc - thisUTC); // Guard against RA cycling through 24h in either direction. if (fabs(nextRA - thisRA) > PI) { if (nextRA < thisRA) { nextRA += TWOPI; } else { nextRA -= TWOPI; } } raRate = gamma * (nextRA - thisRA) / dUTC; decRate = gamma * (nextDec - thisDec) / dUTC; } } else { if (cCycleNo == 2 && fabs(utcDiff(cUTC,cW)) < 600.0) { // thisUTC (i.e. cW for the first cycle) is rubbish, and // probably the position as well (extremely rare in practice, // e.g. 97-12-19_1029_235708-18_586e.hpf which actually has the // t/1000 scaling bug in the first cycle). iMBuff->pCode = 3; thisRA = cU; thisDec = cV; thisUTC = cW; raRate = 0.0; decRate = 0.0; paRate = 0.0; } else { // cW is rubbish and probably (cU,cV), and possibly the // parallactic angle and everything else as well (rarely seen // in practice, e.g. 97-12-09_0743_235707-58_327c.hpf and // 97-09-01_0034_123717-42_242b.hpf, the latter with bad // parallactic angle). pCode = 3; cU = thisRA; cV = thisDec; cW = thisUTC; raRate = iMBuff->raRate; decRate = iMBuff->decRate; paRate = iMBuff->paRate; } } } } // Choose the closest rate determination. if (cCycleNo == 1) { // Scan containing a single integration. iMBuff->raRate = 0.0; iMBuff->decRate = 0.0; iMBuff->paRate = 0.0; } else { double dUTC = iMBuff->utc - cPosUTC[iBeamSel]; if (dUTC >= 0.0) { // In HIPASS/ZOA, the position timestamp, which should always occur // on the whole second, normally precedes an integration midpoint // falling on the half-second. Consequently, positive ages are // always half-integral. dUTC = utcDiff(iMBuff->utc, cW); if (dUTC > 0.0) { iMBuff->rateAge = dUTC; } else { iMBuff->rateAge = 0.0f; } iMBuff->raRate = raRate; iMBuff->decRate = decRate; iMBuff->paRate = paRate; } else { // In HIPASS/ZOA, negative ages occur when the integration midpoint, // occurring on the whole second, precedes the position timestamp. // Thus negative ages are always an integral number of seconds. // They have only been seen to occur sporadically in the period // 1999/05/31 to 1999/11/01, e.g. 1999-07-26_1821_005410-74_007c.hpf // // In recent (2008/10/07) Mopra data, small negative ages (~10ms, // occasionally up to ~300ms) seem to be the norm, with both the // position timestamp and integration midpoint falling close to but // not on the integral second. if (cCycleNo == 2) { // We have to start with something! iMBuff->rateAge = dUTC; } else { // Although we did not record the relevant position timestamp // explicitly, it can easily be deduced. double w = iMBuff->utc - utcDiff(cUTC, iMBuff->utc) - iMBuff->rateAge; dUTC = utcDiff(iMBuff->utc, w); if (dUTC > 0.0) { iMBuff->rateAge = 0.0f; } else { iMBuff->rateAge = dUTC; } } iMBuff->raRate = raRate; iMBuff->decRate = decRate; iMBuff->paRate = paRate; } } #ifdef PKSIO_DEBUG double avRate = sqrt(cAvRate[0]*cAvRate[0] + cAvRate[1]*cAvRate[1]); sprintf(buf, "RA, Dec, Av & PA rates: %8.4f %8.4f %8.4f %8.4f " "pCode %d\n", raRate*R2D, decRate*R2D, avRate*R2D, paRate*R2D, pCode); os << LogIO::DEBUGGING << buf << LogIO::POST ; #endif // Compute the position of this beam for all bins. for (int idx = 0; idx < cNBin; idx++) { int jbuff = iBeamSel + cNBeamSel*idx; cBuffer[jbuff].raRate = iMBuff->raRate; cBuffer[jbuff].decRate = iMBuff->decRate; cBuffer[jbuff].paRate = iMBuff->paRate; double dUTC = utcDiff(cBuffer[jbuff].utc, thisUTC); if (dUTC > 100.0) { // Must have cycled through midnight. dUTC -= 86400.0; } applyRate(cRA0, cDec0, thisRA, thisDec, cBuffer[jbuff].raRate, cBuffer[jbuff].decRate, dUTC, cBuffer[jbuff].ra, cBuffer[jbuff].dec); #ifdef PKSIO_DEBUG sprintf(buf, "Intp (%d) ra, dec, UTC: %9.4f %9.4f %10.3f (pCode, " "age: %d %.1fs)\n", iMBuff->cycleNo, cBuffer[jbuff].ra*R2D, cBuffer[jbuff].dec*R2D, cBuffer[jbuff].utc, iMBuff->pCode, iMBuff->rateAge); os << LogIO::DEBUGGING << buf << LogIO::POST ; #endif } cFlushing = 2; } if (cFlushing) { // Copy buffer location out one IF at a time. MBrec.extract(*iMBuff, cFlushIF); haveData = 1; #ifdef PKSIO_DEBUG sprintf(buf, "Out:%4d%4d%3d%3d\n", MBrec.scanNo, MBrec.cycleNo, MBrec.beamNo, MBrec.IFno[0]); os << LogIO::DEBUGGING << buf << LogIO::POST ; #endif // Signal that this IF in this buffer location has been flushed. iMBuff->IFno[cFlushIF] = 0; iMBuff->nIF--; if (iMBuff->nIF == 0) { // All IFs in this buffer location have been flushed. Stop cEOS // being set when the next integration is read. iMBuff->cycleNo = 0; } else { // Carry on flushing the other IFs. continue; } // Has the whole buffer been flushed? if (cFlushBin == cNBin - 1) { if (cEOS || cEOF) { // Carry on flushing other buffers. cFlushIF = 0; continue; } cFlushing = 0; beamNo = int(cBaseline / 256.0); iBeamSel = cBeamSel[beamNo-1]; // Compute buffer number. iMBuff = cBuffer + iBeamSel; if (cNBin > 1) iMBuff += cNBeamSel*(cBin-1); } } if (!cFlushing) { // Buffer this MBrec. if ((cScanNo > iMBuff->scanNo) && iMBuff->IFno[0]) { // Sanity check on the number of IFs in the new scan. if (if_.n_if != cNIF) { sprintf(cMsg, "Scan %d has %d IFs instead of %d, " "continuing.", cScanNo, if_.n_if, cNIF); os << LogIO::WARN << cMsg << LogIO::POST ; } } // Sanity check on incomplete integrations within a scan. if (iMBuff->nIF && (iMBuff->cycleNo != cCycleNo)) { // Force the incomplete integration to be flushed before proceeding. cFlushing = 1; continue; } #ifdef PKSIO_DEBUG sprintf(buf, "Buf:%4d%4d%3d%3d\n", cScanNo, cCycleNo, beamNo, cIFno); os << LogIO::DEBUGGING << buf << LogIO::POST ; #endif // Store IF-independent parameters only for the first IF of a new cycle, // particularly because this is the only one for which the scan rates // are computed above. int firstIF = (iMBuff->nIF == 0); if (firstIF) { iMBuff->scanNo = cScanNo; iMBuff->cycleNo = cCycleNo; // Times. strcpy(iMBuff->datobs, cDateObs); iMBuff->utc = cUTC; iMBuff->exposure = param_.intbase; // Source identification. sprintf(iMBuff->srcName, "%-16.16s", names_.su_name + (cSrcNo-1)*16); iMBuff->srcName[16] = '\0'; iMBuff->srcRA = doubles_.su_ra[cSrcNo-1]; iMBuff->srcDec = doubles_.su_dec[cSrcNo-1]; // Rest frequency of the line of interest. iMBuff->restFreq = doubles_.rfreq; if (strncmp(names_.instrument, "ATPKSMB", 7) == 0) { // Fix the HI rest frequency recorded for Parkes multibeam data. double reffreq = doubles_.freq; double restfreq = doubles_.rfreq; if ((restfreq == 0.0 || fabs(restfreq - reffreq) == 0.0) && fabs(reffreq - 1420.405752e6) < 100.0) { iMBuff->restFreq = 1420.405752e6; } } // Observation type. int j; for (j = 0; j < 15; j++) { iMBuff->obsType[j] = names_.card[11+j]; if (iMBuff->obsType[j] == '\'') break; } iMBuff->obsType[j] = '\0'; // Beam-dependent parameters. iMBuff->beamNo = beamNo; // Beam position at the specified time. if (cSUpos) { // Non-ATNF data that does not store the position in (u,v,w). iMBuff->ra = doubles_.su_ra[cSrcNo-1]; iMBuff->dec = doubles_.su_dec[cSrcNo-1]; } else { iMBuff->ra = cU; iMBuff->dec = cV; } cPosUTC[iBeamSel] = cW; iMBuff->pCode = pCode; // Store rates for next time. iMBuff->raRate = raRate; iMBuff->decRate = decRate; iMBuff->paRate = paRate; } // IF-dependent parameters. int iIF = cIFno - 1; int startChan = cStartChan[iIF]; int endChan = cEndChan[iIF]; int refChan = cRefChan[iIF]; int nChan = abs(endChan - startChan) + 1; iIFSel = cIFSel[iIF]; if (iMBuff->IFno[iIFSel] == 0) { iMBuff->nIF++; iMBuff->IFno[iIFSel] = cIFno; } else { // Integration cycle written to the output file twice (the only known // example is 1999-05-22_1914_000-031805_03v.hpf). sprintf(cMsg, "Integration cycle %d:%d, beam %2d, \n" "IF %d was duplicated.", cScanNo, cCycleNo-1, beamNo, cIFno); os << LogIO::WARN << cMsg << LogIO::POST ; } iMBuff->nChan[iIFSel] = nChan; iMBuff->nPol[iIFSel] = cNPol[iIF]; iMBuff->fqRefPix[iIFSel] = doubles_.if_ref[iIF]; iMBuff->fqRefVal[iIFSel] = doubles_.if_freq[iIF]; iMBuff->fqDelt[iIFSel] = if_.if_invert[iIF] * fabs(doubles_.if_bw[iIF] / (if_.if_nfreq[iIF] - 1)); // Adjust for channel selection. if (iMBuff->fqRefPix[iIFSel] != refChan) { iMBuff->fqRefVal[iIFSel] += (refChan - iMBuff->fqRefPix[iIFSel]) * iMBuff->fqDelt[iIFSel]; iMBuff->fqRefPix[iIFSel] = refChan; } if (endChan < startChan) { iMBuff->fqDelt[iIFSel] = -iMBuff->fqDelt[iIFSel]; } // System temperature. int iBeam = beamNo - 1; int scq = sc_.sc_q; float TsysPol1 = sc_.sc_cal[scq*iBeam + 3]; float TsysPol2 = sc_.sc_cal[scq*iBeam + 4]; iMBuff->tsys[iIFSel][0] = TsysPol1*TsysPol1; iMBuff->tsys[iIFSel][1] = TsysPol2*TsysPol2; // Calibration factor; may be changed later if the data is recalibrated. if (scq > 14) { // Will only be present for Parkes Multibeam or LBA data. iMBuff->calfctr[iIFSel][0] = sc_.sc_cal[scq*iBeam + 14]; iMBuff->calfctr[iIFSel][1] = sc_.sc_cal[scq*iBeam + 15]; } else { iMBuff->calfctr[iIFSel][0] = 0.0f; iMBuff->calfctr[iIFSel][1] = 0.0f; } // Cross-polarization calibration factor (unknown to MBFITS). for (int j = 0; j < 2; j++) { iMBuff->xcalfctr[iIFSel][j] = 0.0f; } // Baseline parameters (unknown to MBFITS). iMBuff->haveBase = 0; // Data (always present in MBFITS). iMBuff->haveSpectra = 1; // Flag: bit 0 set if off source. // bit 1 set if loss of sync in A polarization. // bit 2 set if loss of sync in B polarization. unsigned char rpflag = (unsigned char)(sc_.sc_cal[scq*iBeam + 12] + 0.5f); // The baseline flag may be set independently. if (rpflag == 0) rpflag = cFlag; // Copy and scale data. int inc = 2 * if_.if_nstok[iIF]; if (endChan < startChan) inc = -inc; float TsysF; iMBuff->spectra[iIFSel] = iMBuff->spectra[0] + cChanOff[iIF]; iMBuff->flagged[iIFSel] = iMBuff->flagged[0] + cChanOff[iIF]; float *spectra = iMBuff->spectra[iIFSel]; unsigned char *flagged = iMBuff->flagged[iIFSel]; for (int ipol = 0; ipol < cNPol[iIF]; ipol++) { if (sc_.sc_cal[scq*iBeam + 3 + ipol] > 0.0f) { // The correlator has already applied the calibration. TsysF = 1.0f; } else { // The correlator has normalized cVis[k] to a Tsys of 500K. TsysF = iMBuff->tsys[iIFSel][ipol] / 500.0f; } int k = 2 * (if_.if_nstok[iIF]*(startChan - 1) + ipol); for (int ichan = 0; ichan < nChan; ichan++) { *(spectra++) = TsysF * cVis[k]; *(flagged++) = rpflag; k += inc; } } if (cHaveXPol[iIF]) { int k = 2 * (3*(startChan - 1) + 2); iMBuff->xpol[iIFSel] = iMBuff->xpol[0] + cXpolOff[iIF]; float *xpol = iMBuff->xpol[iIFSel]; for (int ichan = 0; ichan < nChan; ichan++) { *(xpol++) = cVis[k]; *(xpol++) = cVis[k+1]; k += inc; } } // Calibration factor applied to the data by the correlator. if (scq > 14) { // Will only be present for Parkes Multibeam or LBA data. iMBuff->tcal[iIFSel][0] = sc_.sc_cal[scq*iBeam + 14]; iMBuff->tcal[iIFSel][1] = sc_.sc_cal[scq*iBeam + 15]; } else { iMBuff->tcal[iIFSel][0] = 0.0f; iMBuff->tcal[iIFSel][1] = 0.0f; } if (firstIF) { if (sc_.sc_ant <= anten_.nant) { // No extra syscal information present. iMBuff->extraSysCal = 0; iMBuff->azimuth = 0.0f; iMBuff->elevation = 0.0f; iMBuff->parAngle = 0.0f; iMBuff->focusAxi = 0.0f; iMBuff->focusTan = 0.0f; iMBuff->focusRot = 0.0f; iMBuff->temp = 0.0f; iMBuff->pressure = 0.0f; iMBuff->humidity = 0.0f; iMBuff->windSpeed = 0.0f; iMBuff->windAz = 0.0f; strcpy(iMBuff->tcalTime, " "); iMBuff->refBeam = 0; } else { // Additional information for Parkes Multibeam data. int iOff = scq*(sc_.sc_ant - 1) - 1; iMBuff->extraSysCal = 1; iMBuff->azimuth = sc_.sc_cal[iOff + 2]; iMBuff->elevation = sc_.sc_cal[iOff + 3]; iMBuff->parAngle = sc_.sc_cal[iOff + 4]; iMBuff->focusAxi = sc_.sc_cal[iOff + 5] * 1e-3; iMBuff->focusTan = sc_.sc_cal[iOff + 6] * 1e-3; iMBuff->focusRot = sc_.sc_cal[iOff + 7]; iMBuff->temp = sc_.sc_cal[iOff + 8]; iMBuff->pressure = sc_.sc_cal[iOff + 9]; iMBuff->humidity = sc_.sc_cal[iOff + 10]; iMBuff->windSpeed = sc_.sc_cal[iOff + 11]; iMBuff->windAz = sc_.sc_cal[iOff + 12]; char *tcalTime = iMBuff->tcalTime; sprintf(tcalTime, "%-16.16s", (char *)(&sc_.sc_cal[iOff+13])); tcalTime[16] = '\0'; #ifndef AIPS_LITTLE_ENDIAN // Do byte swapping on the ASCII date string. for (int j = 0; j < 16; j += 4) { char ctmp; ctmp = tcalTime[j]; tcalTime[j] = tcalTime[j+3]; tcalTime[j+3] = ctmp; ctmp = tcalTime[j+1]; tcalTime[j+1] = tcalTime[j+2]; tcalTime[j+2] = ctmp; } #endif // Reference beam number. float refbeam = sc_.sc_cal[iOff + 17]; if (refbeam > 0.0f || refbeam < 100.0f) { iMBuff->refBeam = int(refbeam); } else { iMBuff->refBeam = 0; } } } } } return 0; } //-------------------------------------------------------- MBFITSreader::rpget // Read the next data record from the RPFITS file. int MBFITSreader::rpget(int syscalonly, int &EOS) { const string methodName = "rpget()" ; LogIO os( LogOrigin( className, methodName, WHERE ) ) ; EOS = 0; int retries = 0; // Allow 10 read errors. int numErr = 0; int jstat = 0; while (numErr < 10) { int lastjstat = jstat; switch(rpfitsin(jstat)) { case -1: // Read failed; retry. numErr++; os << LogIO::WARN << "RPFITS read failed - retrying." << LogIO::POST ; jstat = 0; break; case 0: // Successful read. if (lastjstat == 0) { if (cBaseline == -1) { // Syscal data. if (syscalonly) { return 0; } } else { if (!syscalonly) { return 0; } } } // Last operation was to read header or FG table; now read data. break; case 1: // Encountered header while trying to read data; read it. EOS = 1; jstat = -1; break; case 2: // End of scan; read past it. jstat = 0; break; case 3: // End-of-file; retry applies to real-time mode. if (retries++ >= cRetry) { return -1; } sleep(10); jstat = 0; break; case 4: // Encountered FG table while trying to read data; read it. jstat = -1; break; case 5: // Illegal data at end of block after close/reopen operation; retry. jstat = 0; break; default: // Shouldn't reach here. sprintf(cMsg, "Unrecognized RPFITSIN return code: %d " "(retrying).", jstat); os << LogIO::WARN << cMsg << LogIO::POST ; jstat = 0; break; } } os << LogIO::SEVERE << "RPFITS read failed too many times." << LogIO::POST ; return 2; } //----------------------------------------------------- MBFITSreader::rpfitsin // Wrapper around RPFITSIN that reports errors. Returned RPFITSIN subroutine // arguments are captured as MBFITSreader member variables. int MBFITSreader::rpfitsin(int &jstat) { rpfitsin_(&jstat, cVis, cWgt, &cBaseline, &cUTC, &cU, &cV, &cW, &cFlag, &cBin, &cIFno, &cSrcNo); // Handle messages from RPFITSIN. /** if (names_.errmsg[0] != ' ') { int i; for (i = 80; i > 0; i--) { if (names_.errmsg[i-1] != ' ') break; } sprintf(cMsg, "WARNING: Cycle %d:%03d, RPFITSIN reported -\n" " %.*s", cScanNo, cCycleNo, i, names_.errmsg); logMsg(cMsg); } **/ return jstat; } //------------------------------------------------------- MBFITSreader::fixPos // Check and, if necessary, repair a position timestamp. // // Problems with the position timestamp manifest themselves via the scan rate: // // 1) Zero scan rate pairs, 1997/02/28 to 1998/01/07 // // These occur because the position timestamp for the first integration // of the pair is erroneous; the value recorded is t/1000, where t is the // true value. // Earliest known: 97-02-28_1725_132653-42_258a.hpf // Latest known: 98-01-02_1923_095644-50_165c.hpf // (time range chosen to encompass observing runs). // // 2) Slow-fast scan rate pairs (0.013 - 0.020 deg/s), // 1997/03/28 to 1998/01/07. // // The UTC position timestamp is 1.0s later than it should be (never // earlier), almost certainly arising from an error in the telescope // control system. // Earliest known: 97-03-28_0150_010420-74_008d.hpf // Latest known: 98-01-04_1502_065150-02_177c.hpf // (time range chosen to encompass observing runs). // // 3) Slow-fast scan rate pairs (0.015 - 0.018 deg/s), // 1999/05/20 to 2001/07/12 (HIPASS and ZOA), // 2001/09/02 to 2001/12/04 (HIPASS and ZOA), // 2002/03/28 to 2002/05/13 (ZOA only), // 2003/04/26 to 2003/06/09 (ZOA only). // Earliest known: 1999-05-20_1818_175720-50_297e.hpf // Latest known: 2001-12-04_1814_065531p14_173e.hpf (HIPASS) // 2003-06-09_1924_352-085940_-6c.hpf (ZOA) // // Caused by the Linux signalling NaN problem. IEEE "signalling" NaNs // are silently transformed to "quiet" NaNs during assignment by setting // bit 22. This affected RPFITS because of its use of VAX-format // floating-point numbers which, with their permuted bytes, may sometimes // appear as signalling NaNs. // // The problem arose when the linux correlator came online and was // fixed with a workaround to the RPFITS library (repeated episodes // are probably due to use of an older version of the library). It // should not have affected the data significantly because of the // low relative error, which ranges from 0.0000038 to 0.0000076, but // it is important for the computation of scan rates which requires // taking the difference of two large UTC timestamps, one or other // of which will have 0.5s added to it. // // The return value identifies which, if any, of these problems was repaired. int MBFITSreader::fixw( const char *datobs, int cycleNo, int beamNo, double avRate[2], double thisRA, double thisDec, double thisUTC, double nextRA, double nextDec, float &nextUTC) { if (strcmp(datobs, "2003-06-09") > 0) { return 0; } else if (strcmp(datobs, "1998-01-07") <= 0) { if (nextUTC < thisUTC && (nextUTC + 86400.0) > (thisUTC + 600.0)) { // Possible scaling problem. double diff = nextUTC*1000.0 - thisUTC; if (0.0 < diff && diff < 600.0) { nextUTC *= 1000.0; return 1; } else { // Irreparable. return -1; } } if (cycleNo > 2) { if (beamNo == 1) { // This test is only reliable for beam 1. double dUTC = nextUTC - thisUTC; if (dUTC < 0.0) dUTC += 86400.0; // Guard against RA cycling through 24h in either direction. if (fabs(nextRA - thisRA) > PI) { if (nextRA < thisRA) { nextRA += TWOPI; } else { nextRA -= TWOPI; } } double dRA = (nextRA - thisRA) * cos(nextDec); double dDec = nextDec - thisDec; double arc = sqrt(dRA*dRA + dDec*dDec); double averate = sqrt(avRate[0]*avRate[0] + avRate[1]*avRate[1]); double diff1 = fabs(averate - arc/(dUTC-1.0)); double diff2 = fabs(averate - arc/dUTC); if ((diff1 < diff2) && (diff1 < 0.05*averate)) { nextUTC -= 1.0; cCode5 = cycleNo; return 2; } else { cCode5 = 0; } } else { if (cycleNo == cCode5) { nextUTC -= 1.0; return 2; } } } } else if ((strcmp(datobs, "1999-05-20") >= 0 && strcmp(datobs, "2001-07-12") <= 0) || (strcmp(datobs, "2001-09-02") >= 0 && strcmp(datobs, "2001-12-04") <= 0) || (strcmp(datobs, "2002-03-28") >= 0 && strcmp(datobs, "2002-05-13") <= 0) || (strcmp(datobs, "2003-04-26") >= 0 && strcmp(datobs, "2003-06-09") <= 0)) { // Signalling NaN problem, e.g. 1999-07-26_1839_011106-74_009c.hpf. // Position timestamps should always be an integral number of seconds. double resid = nextUTC - int(nextUTC); if (resid == 0.5) { nextUTC -= 0.5; return 3; } } return 0; } //-------------------------------------------------------- MBFITSreader::close // Close the input file. void MBFITSreader::close(void) { if (cMBopen) { int jstat = 1; rpfitsin_(&jstat, cVis, cWgt, &cBaseline, &cUTC, &cU, &cV, &cW, &cFlag, &cBin, &cIFno, &cSrcNo); if (cBeams) delete [] cBeams; if (cIFs) delete [] cIFs; if (cNChan) delete [] cNChan; if (cNPol) delete [] cNPol; if (cHaveXPol) delete [] cHaveXPol; if (cStartChan) delete [] cStartChan; if (cEndChan) delete [] cEndChan; if (cRefChan) delete [] cRefChan; if (cVis) delete [] cVis; if (cWgt) delete [] cWgt; if (cBeamSel) delete [] cBeamSel; if (cIFSel) delete [] cIFSel; if (cChanOff) delete [] cChanOff; if (cXpolOff) delete [] cXpolOff; if (cBuffer) delete [] cBuffer; if (cPosUTC) delete [] cPosUTC; cMBopen = 0; } } //-------------------------------------------------------------------- utcDiff // Subtract two UTCs (s) allowing for any plausible number of cycles through // 86400s, returning a result in the range [-43200, +43200]s. double MBFITSreader::utcDiff(double utc1, double utc2) { double diff = utc1 - utc2; if (diff > 43200.0) { diff -= 86400.0; while (diff > 43200.0) diff -= 86400.0; } else if (diff < -43200.0) { diff += 86400.0; while (diff < -43200.0) diff += 86400.0; } return diff; } //------------------------------------------------------- scanRate & applyRate // Compute and apply the scan rate corrected for grid convergence. (ra0,dec0) // are the coordinates of the central beam, assumed to be the tracking centre. // The rate computed in RA will be a rate of change of angular distance in the // direction of increasing RA at the position of the central beam. Similarly // for declination. Angles in radian, time in s. void MBFITSreader::scanRate( double ra0, double dec0, double ra1, double dec1, double ra2, double dec2, double dt, double &raRate, double &decRate) { // Transform to a system where the central beam lies on the equator at 12h. eulerx(ra1, dec1, ra0+HALFPI, -dec0, -HALFPI, ra1, dec1); eulerx(ra2, dec2, ra0+HALFPI, -dec0, -HALFPI, ra2, dec2); raRate = (ra2 - ra1) / dt; decRate = (dec2 - dec1) / dt; } void MBFITSreader::applyRate( double ra0, double dec0, double ra1, double dec1, double raRate, double decRate, double dt, double &ra2, double &dec2) { // Transform to a system where the central beam lies on the equator at 12h. eulerx(ra1, dec1, ra0+HALFPI, -dec0, -HALFPI, ra1, dec1); ra2 = ra1 + (raRate * dt); dec2 = dec1 + (decRate * dt); // Transform back. eulerx(ra2, dec2, -HALFPI, dec0, ra0+HALFPI, ra2, dec2); } //--------------------------------------------------------------------- eulerx void MBFITSreader::eulerx( double lng0, double lat0, double phi0, double theta, double phi, double &lng1, double &lat1) // Applies the Euler angle based transformation of spherical coordinates. // // phi0 Longitude of the ascending node in the old system, radians. The // ascending node is the point of intersection of the equators of // the two systems such that the equator of the new system crosses // from south to north as viewed in the old system. // // theta Angle between the poles of the two systems, radians. THETA is // positive for a positive rotation about the ascending node. // // phi Longitude of the ascending node in the new system, radians. { // Compute intermediaries. double lng0p = lng0 - phi0; double slng0p = sin(lng0p); double clng0p = cos(lng0p); double slat0 = sin(lat0); double clat0 = cos(lat0); double ctheta = cos(theta); double stheta = sin(theta); double x = clat0*clng0p; double y = clat0*slng0p*ctheta + slat0*stheta; // Longitude in the new system. if (x != 0.0 || y != 0.0) { lng1 = phi + atan2(y, x); } else { // Longitude at the poles in the new system is consistent with that // specified in the old system. lng1 = phi + lng0p; } lng1 = fmod(lng1, TWOPI); if (lng1 < 0.0) lng1 += TWOPI; lat1 = asin(slat0*ctheta - clat0*stheta*slng0p); }