//# CTPatchedInterp.cc: Implementation of CTPatchedInterp.h
//# Copyright (C) 1996,1997,1998,1999,2000,2001,2002,2003
//# Associated Universities, Inc. Washington DC, USA.
//#
//# This library is free software; you can redistribute it and/or modify it
//# under the terms of the GNU Library General Public License as published by
//# the Free Software Foundation; either version 2 of the License, or (at your
//# option) any later version.
//#
//# This library 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 Library General Public
//# License for more details.
//#
//# You should have received a copy of the GNU Library General Public License
//# along with this library; if not, write to the Free Software Foundation,
//# Inc., 675 Massachusetts Ave, Cambridge, MA 02139, USA.
//#
//# Correspondence concerning AIPS++ should be adressed as follows:
//#        Internet email: aips2-request@nrao.edu.
//#        Postal address: AIPS++ Project Office
//#                        National Radio Astronomy Observatory
//#                        520 Edgemont Road
//#                        Charlottesville, VA 22903-2475 USA
//#
//#

#include <synthesis/CalTables/CTPatchedInterp.h>
#include <synthesis/CalTables/CTIter.h>

#include <casacore/scimath/Mathematics/InterpolateArray1D.h>
#include <casacore/casa/OS/Path.h>
#include <casacore/casa/Utilities/GenSort.h>
#include <casacore/casa/aips.h>

#define CTPATCHEDINTERPVERB false

#define NEAREST InterpolateArray1D<Double,Float>::nearestNeighbour
#define LINEAR InterpolateArray1D<Double,Float>::linear
#define CUBIC InterpolateArray1D<Double,Float>::cubic
#define SPLINE InterpolateArray1D<Double,Float>::spline

#include <casacore/ms/MSOper/MSMetaData.h>
#include <synthesis/MeasurementComponents/MSMetaInfoForCal.h>
#include <casacore/casa/Logging/LogMessage.h>
#include <casacore/casa/Logging/LogIO.h>

using namespace casacore;
namespace casa { //# NAMESPACE CASA - BEGIN

// Ctor
CTPatchedInterp::CTPatchedInterp(NewCalTable& ct,
				 VisCalEnum::MatrixType mtype,
				 Int nPar,
				 const String& timetype,
				 const String& freqtype,
				 const String& fieldtype,
				 Vector<Int> spwmap,
				 Vector<Int> fldmap,
				 const CTTIFactoryPtr cttifactoryptr) :
  ct_(ct),
  ctname_(),
  msmc_(NULL),
  mtype_(mtype),
  isCmplx_(false),
  nPar_(nPar),
  nFPar_(nPar),
  timeType_(timetype),
  freqTypeStr_(freqtype),
  relativeFreq_(freqtype.contains("rel")),
  freqInterpMethod0_(ftype(freqTypeStr_)),
  freqInterpMethod_(freqInterpMethod0_),
  freqInterpMethodVec_(),
  byObs_(timetype.contains("perobs")), // detect slicing by obs
  byScan_(timetype.contains("perscan")), // detect slicing by scan
  byField_(fieldtype=="nearest" || fieldtype=="map"), 
  nChanIn_(),
  freqIn_(),
  nMSTimeSeg_(1), // byObs_?ct.observation().nrow():1),  // assume CT shapes for MS shapes
  nMSFld_(ct.field().nrow()),                 
  nMSSpw_(ct.spectralWindow().nrow()),
  nMSAnt_(ct.antenna().nrow()),
  altFld_(),
  nCTTimeSeg_(1), // byObs_?ct.observation().nrow():1),
  nCTFld_(byField_?ct.field().nrow():1),
  nCTSpw_(ct.spectralWindow().nrow()),
  nCTAnt_(ct.antenna().nrow()),
  nCTElem_(0),
  spwInOK_(),
  fldMap_(),
  spwMap_(),
  antMap_(),
  elemMap_(),
  conjTab_(),
  result_(),
  resFlag_(),
  tI_(),
  tIdel_(),
  tIMissingLogged_(),
  lastFld_(ct.spectralWindow().nrow(),-1),
  lastObs_(ct.spectralWindow().nrow(),-1),
  lastScan_(ct.spectralWindow().nrow(),-1),
  cttifactoryptr_(cttifactoryptr)
{
  if (CTPATCHEDINTERPVERB) cout << "CTPatchedInterp::CTPatchedInterp(<no MS>)" << endl;

  //  cout << "freqInterpMethod_ = " << freqInterpMethod_ << endl;

  freqInterpMethodVec_.resize(nMSSpw_);
  freqInterpMethodVec_.set(freqInterpMethod0_);
    
  switch(mtype_) {
  case VisCalEnum::GLOBAL: {

    throw(AipsError("CTPatchedInterp::ctor: No non-Mueller/Jones yet."));

    nCTElem_=1;
    nMSElem_=1;
    break;
  }
  case VisCalEnum::MUELLER: {
    nCTElem_=nCTAnt_*(nCTAnt_+1)/2;
    nMSElem_=nMSAnt_*(nMSAnt_+1)/2;
    break;
  }
  case VisCalEnum::JONES: {
    nCTElem_=nCTAnt_;
    nMSElem_=nMSAnt_;
    break;
  }
  }

  // How many _Float_ parameters?
  isCmplx_=(ct_.keywordSet().asString("ParType")=="Complex");
  if (isCmplx_)  // Complex input
    nFPar_*=2;  // interpolating 2X as many Float values


  // Set channel/freq info
  CTSpWindowColumns ctspw(ct_.spectralWindow());
  ctspw.numChan().getColumn(nChanIn_);
  freqIn_.resize(nCTSpw_);
  for (uInt iCTspw=0;iCTspw<ctspw.nrow();++iCTspw) {
    ctspw.chanFreq().get(iCTspw,freqIn_(iCTspw),true);
    if (relativeFreq_) {
      Vector<Double>& fIn(freqIn_(iCTspw));
      fIn-=mean(fIn); //  assume mean freq is center, and apply offset
      // Flip LSB
      if (fIn.nelements()>1 && fIn(0)>fIn(1)) {
	//cout << " spw=" << iCTspw << " LSB!" << endl;
	fIn*=Double(-1.0);
      }
    }
    //cout << " freqIn_(" << iCTspw << ")=" << freqIn_(iCTspw) << endl;
  }

  // byScan_ supercedes byObs_, so turn OFF byObs_, if necessary
  if (byObs_ && byScan_) {
    byObs_=false;
    LogIO log;
    ostringstream msg;
    msg << "For " << Path(ct_.tableName()).baseName().before(".tempMemCal")
	<< " 'perscan' interpolation supercedes 'perobs' interpolation. ";
    log << msg.str() << LogIO::WARN;
  }
  
  // Manage 'byObs_' carefully
  if (byObs_) {

    Int nMSObs=ct_.observation().nrow(); // assume CT shapes for MS shapes
    Int nCTObs=ct_.observation().nrow();

    // Count _available_ obsids in caltable
    ROCTMainColumns ctmc(ct_);
    Vector<Int> obsid;
    ctmc.obsId().getColumn(obsid);
    Int nctobsavail=genSort(obsid,Sort::Ascending,
			    (Sort::QuickSort | Sort::NoDuplicates));


    LogIO log;
    ostringstream msg;
    
    if (nctobsavail==1) {
      byObs_=false;
      msg << "Only one ObsId found in "
	  << Path(ct_.tableName()).baseName().before(".tempMemCal")
	  << "; ignoring 'perobs' interpolation.";
      log << msg.str() << LogIO::WARN;
    }
    else {

      // Verify consistency between CT and MS
      if (nctobsavail==nCTObs &&
	  nctobsavail==nMSObs) {
	// Everything ok
	nCTTimeSeg_=nCTObs;
	nMSTimeSeg_=nMSObs;
      }
      else {
	// only 1 obs, or available nobs doesn't match MS
	byObs_=false;
	msg << "Multiple ObsIds found in "
	    << Path(ct_.tableName()).baseName().before(".tempMemCal")
	    << ", but they do not match the MS ObsIds;"
	    << " turning off 'perobs'.";
	log << msg.str() << LogIO::WARN;
      }
    }

  }

  // Manage 'byScan_' even more carefully
  if (byScan_) {

    LogIO log;
    ostringstream msg;

    // Count _availale_ scan numbers in caltable
    ROCTMainColumns ctmc(ct_);
    Vector<Int> scan;
    Int minScan, maxScan;
    ctmc.scanNo().getColumn(scan);
    minMax(minScan, maxScan, scan);
    if (minScan == -1) {
      byScan_=false;
      msg << "No scan numbers found in "
	  << Path(ct_.tableName()).baseName().before(".tempMemCal")
	  << "; ignoring 'perscan' interpolation.";
      log << msg.str() << LogIO::WARN;
    } else {
      nCTTimeSeg_=maxScan+1;
      nMSTimeSeg_=maxScan+1; // assume CT shapes for MS shapes
    }
  }

  // Initialize caltable slices
  sliceTable();

  // Set spwmap
  setSpwMap(spwmap);

  // Set fldmap
  if (byField_) {
    if (fieldtype=="map")
      setFldMap(fldmap);  // Use specified map
    else
      setFldMap(ct.field());  // Use CalTable's fields ('nearest')
  }
  else
    setDefFldMap();

  // Set defaultmaps
  setDefAntMap();
  setElemMap();

  // Resize working arrays
  result_.resize(nMSSpw_,nMSFld_,nMSTimeSeg_);
  resFlag_.resize(nMSSpw_,nMSFld_,nMSTimeSeg_);
  timeResult_.resize(nMSSpw_,nMSFld_,nMSTimeSeg_);
  timeResFlag_.resize(nMSSpw_,nMSFld_,nMSTimeSeg_);
  freqResult_.resize(nMSSpw_,nMSFld_,nMSTimeSeg_);
  freqResFlag_.resize(nMSSpw_,nMSFld_,nMSTimeSeg_);

  // Figure out where we can duplicate field interpolators
  calcAltFld();

  // Setup mapped interpolators
  // TBD: defer this to later, so that spwmap, etc. can be revised
  //   before committing to the interpolation engines
  makeInterpolators();

  //  state();
}

CTPatchedInterp::CTPatchedInterp(NewCalTable& ct,
				 VisCalEnum::MatrixType mtype,
				 Int nPar,
				 const String& timetype,
				 const String& freqtype,
				 const String& fieldtype,
				 const MeasurementSet& ms,
                 const MSMetaInfoForCal& msmc,
				 Vector<Int> spwmap,
				 const CTTIFactoryPtr cttifactoryptr) :
  ct_(ct),
  ctname_(),
  msmc_(&msmc),
  mtype_(mtype),
  isCmplx_(false),
  nPar_(nPar),
  nFPar_(nPar),
  timeType_(timetype),
  freqTypeStr_(freqtype),
  relativeFreq_(freqtype.contains("rel")),
  freqInterpMethod0_(ftype(freqTypeStr_)),
  freqInterpMethod_(freqInterpMethod0_),
  freqInterpMethodVec_(),
  byObs_(timetype.contains("perobs")), // detect slicing by obs
  byScan_(timetype.contains("perscan")), // detect slicing by scan
  byField_(fieldtype=="nearest"),  // for now we are NOT slicing by field
  nChanIn_(),
  freqIn_(),
  nMSTimeSeg_(1), // byObs_?ms.observation().nrow():1),
  nMSFld_(ms.field().nrow()),  
  nMSSpw_(ms.spectralWindow().nrow()),
  nMSAnt_(ms.antenna().nrow()),
  altFld_(),
  nCTTimeSeg_(1),  // byObs_?ct.observation().nrow():1),
  nCTFld_(byField_?ct.field().nrow():1),
  nCTSpw_(ct.spectralWindow().nrow()),
  nCTAnt_(ct.antenna().nrow()),
  nCTElem_(0),
  spwInOK_(),
  fldMap_(),
  spwMap_(),
  antMap_(),
  elemMap_(),
  conjTab_(),
  result_(),
  resFlag_(),
  tI_(),
  tIdel_(),
  tIMissingLogged_(),
  lastFld_(ms.spectralWindow().nrow(),-1),
  lastObs_(ms.spectralWindow().nrow(),-1),
  lastScan_(ms.spectralWindow().nrow(),-1),
  cttifactoryptr_(cttifactoryptr)
{

  if (CTPATCHEDINTERPVERB) cout << "CTPatchedInterp::CTPatchedInterp(CT,MS)" << endl;

  //freqInterpMethod_=ftype(freqTypeStr_);

  freqInterpMethodVec_.resize(nMSSpw_);
  freqInterpMethodVec_.set(freqInterpMethod0_);

  //  cout << "freqInterpMethod_ = " << freqInterpMethod_ << endl;

  switch(mtype_) {
  case VisCalEnum::GLOBAL: {

    throw(AipsError("CTPatchedInterp::ctor: No non-Mueller/Jones yet."));

    nCTElem_=1;
    nMSElem_=1;
    break;
  }
  case VisCalEnum::MUELLER: {
    nCTElem_=nCTAnt_*(nCTAnt_+1)/2;
    nMSElem_=nMSAnt_*(nMSAnt_+1)/2;
    break;
  }
  case VisCalEnum::JONES: {
    nCTElem_=nCTAnt_;
    nMSElem_=nMSAnt_;
    break;
  }
  }

  // How many _Float_ parameters?
  isCmplx_=(ct_.keywordSet().asString("ParType")=="Complex");  // Complex input
  if (isCmplx_)
    nFPar_*=2;  // interpolating 2X as many Float values

  // Set channel/freq info
  CTSpWindowColumns ctspw(ct_.spectralWindow());
  ctspw.numChan().getColumn(nChanIn_);
  freqIn_.resize(nCTSpw_);
  for (uInt iCTspw=0;iCTspw<ctspw.nrow();++iCTspw) {
    ctspw.chanFreq().get(iCTspw,freqIn_(iCTspw),true);
    if (relativeFreq_) {
      Vector<Double>& fIn(freqIn_(iCTspw));
      fIn-=mean(fIn); //  assume mean freq is center, and apply offset
      // Flip LSB
      if (fIn.nelements()>1 && fIn(0)>fIn(1)) {
	//cout << " spw=" << iCTspw << " LSB!" << endl;
	fIn*=Double(-1.0);
      }
    }
    //cout << " freqIn_(" << iCTspw << ")=" << freqIn_(iCTspw) << endl;
  }

  // byScan_ supercedes byObs_, so turn OFF byObs_, if necessary
  if (byObs_ && byScan_) {
    byObs_=false;
    LogIO log;
    ostringstream msg;
    msg << "For " << Path(ct_.tableName()).baseName().before(".tempMemCal")
	<< " 'perscan' interpolation supercedes 'perobs' interpolation. ";
    log << msg.str() << LogIO::WARN;
  }
    
  // Manage 'byObs_' carefully
  if (byObs_) {
    Int nMSObs=ms.observation().nrow();
    Int nCTObs=ct_.observation().nrow();

    // Count _available_ obsids in caltable
    ROCTMainColumns ctmc(ct_);
    Vector<Int> obsid;
    ctmc.obsId().getColumn(obsid);
    Int nctobsavail=genSort(obsid,Sort::Ascending,
			    (Sort::QuickSort | Sort::NoDuplicates));


    LogIO log;
    ostringstream msg;
    
    if (nctobsavail==1) {
      byObs_=false;
      msg << "Only one ObsId found in "
	  << Path(ct_.tableName()).baseName().before(".tempMemCal")
	  << "; ignoring 'perobs' interpolation.";
      log << msg.str() << LogIO::WARN;
    }
    else {

      // Verify consistency between CT and MS
      if (nctobsavail==nCTObs &&
	  nctobsavail==nMSObs) {
	// Everything ok
	nCTTimeSeg_=nCTObs;
	nMSTimeSeg_=nMSObs;
      }
      else {
	// only 1 obs, or available nobs doesn't match MS
	byObs_=false;
	msg << "Multiple ObsIds found in "
	    << Path(ct_.tableName()).baseName().before(".tempMemCal")
	    << ", but they do not match the MS ObsIds;"
	    << " turning off 'perobs'.";
	log << msg.str() << LogIO::WARN;
      }
    }
  }

  // Manage 'byScan_' even more carefully
  if (byScan_) {

    LogIO log;
    ostringstream msg;

    // Count _availale_ scan numbers in caltable
    ROCTMainColumns ctmc(ct_);
    Vector<Int> scan;
    Int minScan, maxScan;
    ctmc.scanNo().getColumn(scan);
    minMax(minScan, maxScan, scan);
    if (minScan == -1) {
      byScan_=false;
      msg << "No scan numbers found in "
	  << Path(ct_.tableName()).baseName().before(".tempMemCal")
	  << "; ignoring 'perscan' interpolation.";
      log << msg.str() << LogIO::WARN;
    } else {
      std::set<Int> scanNumbers = msmc.msmd().getScanNumbers(0,0);
      nCTTimeSeg_=maxScan+1;
      nMSTimeSeg_=*scanNumbers.rbegin()+1;

    }
  }
  
  // Initialize caltable slices
  sliceTable();

  // Set spwmap
  setSpwMap(spwmap);

  // Set fldmap
  if (byField_)
    setFldMap(ms.field());  // on a trial basis
  else
    setDefFldMap();

  // Set defaultmaps
  setDefAntMap();
  setElemMap();

  // Resize working arrays
  result_.resize(nMSSpw_,nMSFld_,nMSTimeSeg_);
  resFlag_.resize(nMSSpw_,nMSFld_,nMSTimeSeg_);
  timeResult_.resize(nMSSpw_,nMSFld_,nMSTimeSeg_);
  timeResFlag_.resize(nMSSpw_,nMSFld_,nMSTimeSeg_);
  freqResult_.resize(nMSSpw_,nMSFld_,nMSTimeSeg_);
  freqResFlag_.resize(nMSSpw_,nMSFld_,nMSTimeSeg_);

  // Figure out where we can duplicate field interpolators
  calcAltFld();
  
  // Setup mapped interpolators
  // TBD: defer this to later, so that spwmap, etc. can be revised
  //   before committing to the interpolation engines
  makeInterpolators();

  //  state();

}

CTPatchedInterp::CTPatchedInterp(NewCalTable& ct,
				 VisCalEnum::MatrixType mtype,
				 Int nPar,
				 const String& timetype,
				 const String& freqtype,
				 const String& fieldtype,
				 const MSColumns& mscol,
				 Vector<Int> spwmap,
				 const CTTIFactoryPtr cttifactoryptr) :
  ct_(ct),
  ctname_(),
  msmc_(NULL),
  mtype_(mtype),
  isCmplx_(false),
  nPar_(nPar),
  nFPar_(nPar),
  timeType_(timetype),
  freqTypeStr_(freqtype),
  relativeFreq_(freqtype.contains("rel")),
  freqInterpMethod0_(ftype(freqTypeStr_)),
  freqInterpMethod_(freqInterpMethod0_),
  freqInterpMethodVec_(),
  byObs_(false),                // turn off for old-fashioned
  byScan_(false),               // turn off for old-fashioned
  byField_(fieldtype=="nearest"),  // for now we are NOT slicing by field
  nChanIn_(),
  freqIn_(),
  nMSFld_(mscol.field().nrow()),  
  nMSSpw_(mscol.spectralWindow().nrow()),
  nMSAnt_(mscol.antenna().nrow()),
  altFld_(),
  nCTFld_(byField_?ct.field().nrow():1),
  nCTSpw_(ct.spectralWindow().nrow()),
  nCTAnt_(ct.antenna().nrow()),
  nCTElem_(0),
  spwInOK_(),
  fldMap_(),
  spwMap_(),
  antMap_(),
  elemMap_(),
  conjTab_(),
  result_(),
  resFlag_(),
  tI_(),
  tIdel_(),
  tIMissingLogged_(),
  lastFld_(mscol.spectralWindow().nrow(),-1),
  lastObs_(mscol.spectralWindow().nrow(),-1),
  lastScan_(mscol.spectralWindow().nrow(),-1),
  cttifactoryptr_(cttifactoryptr)
{
  if (CTPATCHEDINTERPVERB) cout << "CTPatchedInterp::CTPatchedInterp(mscol)" << endl;

  //  cout << "freqInterpMethod_ = " << freqInterpMethod_ << endl;
  freqInterpMethodVec_.resize(nMSSpw_);
  freqInterpMethodVec_.set(freqInterpMethod0_);

  switch(mtype_) {
  case VisCalEnum::GLOBAL: {

    throw(AipsError("CTPatchedInterp::ctor: No non-Mueller/Jones yet."));

    nCTElem_=1;
    nMSElem_=1;
    break;
  }
  case VisCalEnum::MUELLER: {
    nCTElem_=nCTAnt_*(nCTAnt_+1)/2;
    nMSElem_=nMSAnt_*(nMSAnt_+1)/2;
    break;
  }
  case VisCalEnum::JONES: {
    nCTElem_=nCTAnt_;
    nMSElem_=nMSAnt_;
    break;
  }
  }

  // How many _Float_ parameters?
  isCmplx_=(ct_.keywordSet().asString("ParType")=="Complex");
  if (isCmplx_)  // Complex input
    nFPar_*=2;  // interpolating 2X as many Float values

  // Set channel/freq info
  CTSpWindowColumns ctspw(ct_.spectralWindow());
  ctspw.numChan().getColumn(nChanIn_);
  freqIn_.resize(nCTSpw_);
  for (uInt iCTspw=0;iCTspw<ctspw.nrow();++iCTspw) {
    ctspw.chanFreq().get(iCTspw,freqIn_(iCTspw),true);
    if (relativeFreq_) {
      Vector<Double>& fIn(freqIn_(iCTspw));
      fIn-=mean(fIn); //  assume mean freq is center, and apply offset
      // Flip LSB
      if (fIn.nelements()>1 && fIn(0)>fIn(1)) {
	//cout << " spw=" << iCTspw << " LSB!" << endl;
	fIn*=Double(-1.0);
      }
    }
    //    cout << " freqIn_(" << iCTspw << ")=" << freqIn_(iCTspw) << endl;
  }

  // Initialize caltable slices
  sliceTable();

  // Set spwmap
  setSpwMap(spwmap);

  // Set fldmap
  if (byField_)
    setFldMap(mscol.field());  // on a trial basis
  else
    setDefFldMap();


  // Set defaultmaps
  setDefAntMap();
  setElemMap();

  // Resize working arrays
  result_.resize(nMSSpw_,nMSFld_,nMSTimeSeg_);
  resFlag_.resize(nMSSpw_,nMSFld_,nMSTimeSeg_);
  timeResult_.resize(nMSSpw_,nMSFld_,nMSTimeSeg_);
  timeResFlag_.resize(nMSSpw_,nMSFld_,nMSTimeSeg_);
  freqResult_.resize(nMSSpw_,nMSFld_,nMSTimeSeg_);
  freqResFlag_.resize(nMSSpw_,nMSFld_,nMSTimeSeg_);

  // Figure out where we can duplicate field interpolators
  calcAltFld();

  // Setup mapped interpolators
  // TBD: defer this to later, so that spwmap, etc. can be revised
  //   before committing to the interpolation engines
  makeInterpolators();

  //  state();

}
    
CTPatchedInterp::CTPatchedInterp(NewCalTable& ct,
                 VisCalEnum::MatrixType mtype,
                 Int nPar,
                 const String& timetype,
                 const String& freqtype,
                 const String& fieldtype,
                 const MeasurementSet& ms,
                 Vector<Int> spwmap,
                 const CTTIFactoryPtr cttifactoryptr) :
  ct_(ct),
  ctname_(),
  msmc_(NULL),
  mtype_(mtype),
  isCmplx_(false),
  nPar_(nPar),
  nFPar_(nPar),
  timeType_(timetype),
  freqTypeStr_(freqtype),
  relativeFreq_(freqtype.contains("rel")),
  freqInterpMethod0_(ftype(freqTypeStr_)),
  freqInterpMethod_(freqInterpMethod0_),
  freqInterpMethodVec_(),
  byObs_(timetype.contains("perobs")), // detect slicing by obs
  byScan_(timetype.contains("perscan")), // detect slicing by scan
  byField_(fieldtype=="nearest"),  // for now we are NOT slicing by field
  nChanIn_(),
  freqIn_(),
  nMSTimeSeg_(1), // byObs_?ms.observation().nrow():1),
  nMSFld_(ms.field().nrow()),
  nMSSpw_(ms.spectralWindow().nrow()),
  nMSAnt_(ms.antenna().nrow()),
  altFld_(),
  nCTTimeSeg_(1),  // byObs_?ct.observation().nrow():1),
  nCTFld_(byField_?ct.field().nrow():1),
  nCTSpw_(ct.spectralWindow().nrow()),
  nCTAnt_(ct.antenna().nrow()),
  nCTElem_(0),
  spwInOK_(),
  fldMap_(),
  spwMap_(),
  antMap_(),
  elemMap_(),
  conjTab_(),
  result_(),
  resFlag_(),
  tI_(),
  tIdel_(),
  tIMissingLogged_(),
  lastFld_(ms.spectralWindow().nrow(),-1),
  lastObs_(ms.spectralWindow().nrow(),-1),
  lastScan_(ms.spectralWindow().nrow(),-1),
  cttifactoryptr_(cttifactoryptr)
{
    
  if (CTPATCHEDINTERPVERB) cout << "CTPatchedInterp::CTPatchedInterp(CT,MS)" << endl;
    
  //freqInterpMethod_=ftype(freqTypeStr_);
    
  freqInterpMethodVec_.resize(nMSSpw_);
  freqInterpMethodVec_.set(freqInterpMethod0_);
    
  //  cout << "freqInterpMethod_ = " << freqInterpMethod_ << endl;
    
  switch(mtype_) {
    case VisCalEnum::GLOBAL: {
            
      throw(AipsError("CTPatchedInterp::ctor: No non-Mueller/Jones yet."));
            
      nCTElem_=1;
      nMSElem_=1;
      break;
    }
    case VisCalEnum::MUELLER: {
      nCTElem_=nCTAnt_*(nCTAnt_+1)/2;
      nMSElem_=nMSAnt_*(nMSAnt_+1)/2;
      break;
    }
    case VisCalEnum::JONES: {
      nCTElem_=nCTAnt_;
      nMSElem_=nMSAnt_;
      break;
    }
    }
    
    // How many _Float_ parameters?
    isCmplx_=(ct_.keywordSet().asString("ParType")=="Complex");  // Complex input
    if (isCmplx_)
        nFPar_*=2;  // interpolating 2X as many Float values
    
    // Set channel/freq info
    CTSpWindowColumns ctspw(ct_.spectralWindow());
    ctspw.numChan().getColumn(nChanIn_);
    freqIn_.resize(nCTSpw_);
    for (uInt iCTspw=0;iCTspw<ctspw.nrow();++iCTspw) {
        ctspw.chanFreq().get(iCTspw,freqIn_(iCTspw),true);
        if (relativeFreq_) {
            Vector<Double>& fIn(freqIn_(iCTspw));
            fIn-=mean(fIn); //  assume mean freq is center, and apply offset
            // Flip LSB
            if (fIn.nelements()>1 && fIn(0)>fIn(1)) {
                //cout << " spw=" << iCTspw << " LSB!" << endl;
                fIn*=Double(-1.0);
            }
        }
        //cout << " freqIn_(" << iCTspw << ")=" << freqIn_(iCTspw) << endl;
    }
    
    // Manage 'byObs_' carefully
    if (byObs_) {
        Int nMSObs=ms.observation().nrow();
        Int nCTObs=ct_.observation().nrow();
        
        // Count _available_ obsids in caltable
        ROCTMainColumns ctmc(ct_);
        Vector<Int> obsid;
        ctmc.obsId().getColumn(obsid);
        Int nctobsavail=genSort(obsid,Sort::Ascending,
                                (Sort::QuickSort | Sort::NoDuplicates));
        
        
        LogIO log;
        ostringstream msg;
        
        if (nctobsavail==1) {
            byObs_=false;
            msg << "Only one ObsId found in "
            << Path(ct_.tableName()).baseName().before(".tempMemCal")
            << "; ignoring 'perobs' interpolation.";
            log << msg.str() << LogIO::WARN;
        }
        else {
            
            // Verify consistency between CT and MS
            if (nctobsavail==nCTObs &&
                nctobsavail==nMSObs) {
                // Everything ok
                nCTTimeSeg_=nCTObs;
                nMSTimeSeg_=nMSObs;
            }
            else {
                // only 1 obs, or available nobs doesn't match MS
                byObs_=false;
                msg << "Multiple ObsIds found in "
                << Path(ct_.tableName()).baseName().before(".tempMemCal")
                << ", but they do not match the MS ObsIds;"
                << " turning off 'perobs'.";
                log << msg.str() << LogIO::WARN;
            }
        }
    }
    
    // Initialize caltable slices
    sliceTable();
    
    // Set spwmap
    setSpwMap(spwmap);
    
    // Set fldmap
    if (byField_)
        setFldMap(ms.field());  // on a trial basis
    else
        setDefFldMap();
    
    // Set defaultmaps
    setDefAntMap();
    setElemMap();
    
    // Resize working arrays
    result_.resize(nMSSpw_,nMSFld_,nMSTimeSeg_);
    resFlag_.resize(nMSSpw_,nMSFld_,nMSTimeSeg_);
    timeResult_.resize(nMSSpw_,nMSFld_,nMSTimeSeg_);
    timeResFlag_.resize(nMSSpw_,nMSFld_,nMSTimeSeg_);
    freqResult_.resize(nMSSpw_,nMSFld_,nMSTimeSeg_);
    freqResFlag_.resize(nMSSpw_,nMSFld_,nMSTimeSeg_);
    
    // Figure out where we can duplicate field interpolators
    calcAltFld();

    // Setup mapped interpolators
    // TBD: defer this to later, so that spwmap, etc. can be revised
    //   before committing to the interpolation engines
    makeInterpolators();

    //state();
    
}


// Destructor
CTPatchedInterp::~CTPatchedInterp() {

  if (CTPATCHEDINTERPVERB) cout << "CTPatchedInterp::~CTPatchedInterp()" << endl;

  {  
    IPosition sh(tI_.shape());
    for (Int l=0;l<sh(3);++l)
      for (Int k=0;k<sh(2);++k)
	for (Int j=0;j<sh(1);++j)
	  for (Int i=0;i<sh(0);++i) {
	    IPosition ip(4,i,j,k,l);
	    if (tIdel_(ip))
	      delete tI_(ip);
	  }
    tI_.resize();
  }
  {
    IPosition sh(ctSlices_.shape());
    for (Int l=0;l<sh(3);++l)
      for (Int k=0;k<sh(2);++k)
	for (Int j=0;j<sh(1);++j)
	  for (Int i=0;i<sh(0);++i) {
	    IPosition ip(4,i,j,k,l);
	    if (ctSlices_(ip)) 
	      delete ctSlices_(ip);
	  }
    ctSlices_.resize();
  }
}

Bool CTPatchedInterp::interpolate(Int msobs, Int msscan, Int msfld, Int msspw, Double time, Double freq) {

  if (CTPATCHEDINTERPVERB) cout << "CTPatchedInterp::interpolate(...)" << endl;

  Bool newcal(false);
  Int scanOrObsInt=thisTimeSeg(msobs,msscan);
  IPosition ip(4,0,msspw,msfld,scanOrObsInt);

  // Loop over _output_ elements
  for (Int iMSElem=0;iMSElem<nMSElem_;++iMSElem) {
    // Call fully _patched_ time-interpolator, keeping track of 'newness'
    //  fills timeResult_/timeResFlag_ implicitly
    ip(0)=iMSElem;
    
    if (!tI_(ip)) {
      //if (iMSElem==0) cout << "Flagging: " << ip << endl;

      // casa log post
      if (!tIMissingLogged_(ip)) {  // only if these coords not logged before
	LogIO log;
	ostringstream msg;
	String scanOrObsStr=(byScan_ ? "scan" : "obs");
	msg  << "MS " << scanOrObsStr << "=" << scanOrObsInt;
	if (byField_) msg << " in fld=" << msfld;
	msg << ",spw=" << msspw
	    << ",ant=" << iMSElem
	    << " is selected for processing, but has no available calibration in " << ctname_
	    << " as mapped, and will be flagged.";
	log << msg.str() << LogIO::WARN;
	tIMissingLogged_(ip)=true;
      }
      newcal=true;
    }
    else {
      if (freq>0.0)
	newcal|=tI_(ip)->interpolate(time,freq);
      else
	newcal|=tI_(ip)->interpolate(time);
    }
  }

  // Whole result referred to time result:
  result_(msspw,msfld,scanOrObsInt).reference(timeResult_(msspw,msfld,scanOrObsInt));
  resFlag_(msspw,msfld,scanOrObsInt).reference(timeResFlag_(msspw,msfld,scanOrObsInt));

  // Detect if obs or fld changed, and cal is obs- or fld-dep
  Bool diffobsfld(false);
  diffobsfld|=(byField_ && msfld!=lastFld_(msspw));   // field-dep, and field changed
  diffobsfld|=(byObs_ && msobs!=lastObs_(msspw));     // obs-dep, and obs changed
  diffobsfld|=(byScan_ && msscan!=lastScan_(msspw));  // scan-dep, and scan changed
  newcal|=diffobsfld;  // update newcal for return

  // Remember for next pass
  lastFld_(msspw)=msfld;
  lastObs_(msspw)=msobs;
  lastScan_(msspw)=msscan;

  return newcal;
}

Bool CTPatchedInterp::interpolate(Int msobs, Int msscan, Int msfld, Int msspw, Double time, const Vector<Double>& freq) {

  if (CTPATCHEDINTERPVERB) cout << "CTPatchedInterp::interpolate(...,freq)" << endl;

  // obsid non-degenerate only if byObs_

  // The number of requested channels
  uInt nMSChan=freq.nelements();

  // Ensure freq result Array is properly sized
  if (freqResult_(msspw,msfld,thisTimeSeg(msobs,msscan)).nelements()==0) {
     Int thisAltFld=altFld_(msfld);
     if (freqResult_(msspw,thisAltFld,thisTimeSeg(msobs,msscan)).nelements()==0) {
       freqResult_(msspw,thisAltFld,thisTimeSeg(msobs,msscan)).resize(nFPar_,nMSChan,nMSElem_);
       freqResFlag_(msspw,thisAltFld,thisTimeSeg(msobs,msscan)).resize(nPar_,nMSChan,nMSElem_);
       freqResFlag_(msspw,thisAltFld,thisTimeSeg(msobs,msscan)).set(true);
     }
     if (thisAltFld!=msfld) {
       freqResult_(msspw,msfld,thisTimeSeg(msobs,msscan)).reference(freqResult_(msspw,thisAltFld,thisTimeSeg(msobs,msscan)));
       freqResFlag_(msspw,msfld,thisTimeSeg(msobs,msscan)).reference(freqResFlag_(msspw,thisAltFld,thisTimeSeg(msobs,msscan)));
     }
  }


  
  
  {

    // CAS-11744
    // In the following, we verify that there are sufficient soln channels to 
    // do the requested freq-dep interpolation.  If there are not, we
    // change the freq-dep interp mode to one that will work (linear or
    // nearest, for nchan=2 or 1, respectively), and issue a logger warning.
    // This is necessary specifically for the current _flag_ interpolation code.
    // The warning is thought warranted since freq-dep calibration (B, Tsys)
    // usually has adequate nchan, and if it does not, this may be unexpected
    // (e.g., use of too large a freq solint).
    
    // Check freq sampling adequate for specified freq interpolation
    Int nSolChan=timeResult_(msspw,msfld,thisTimeSeg(msobs,msscan)).shape()(1);
    LogIO log;
    ostringstream msg;

    switch (freqInterpMethod0_) {
    case LINEAR: {
      if (nSolChan<2 && freqInterpMethodVec_(msspw)!=NEAREST) {
	freqInterpMethodVec_(msspw)=NEAREST;  // change to nearest for this msspw
	ostringstream spwmapstr;
	spwmapstr << " (mapped to CT spw=" << spwMap_(msspw) << ")";
	msg << "In caltable "
	    << Path(ct_.tableName()).baseName().before(".tempMemCal")
	    << " (" << ct_.keywordSet().asString("VisCal") << ")"
	    << ":" << endl
	    << " Insufficient solution channel sampling (nchan=" << nSolChan << ") for frequency-dependent LINEAR interpolation " << endl
	    << " of calibration for MS spw=" << msspw
	    << ( msspw!=spwMap_(msspw) ? String(spwmapstr) : "" ) 
	    << "; using NEAREST instead.";
	log << LogOrigin("CTPatchedInterp","interpolate") << msg.str() << LogIO::WARN;
      }
      break;
    }
    case CUBIC:
    case SPLINE: {
      if (nSolChan<2 && freqInterpMethodVec_(msspw)>NEAREST) {
	freqInterpMethodVec_(msspw)=NEAREST;  // change to nearest for this msspw
	ostringstream spwmapstr;
	spwmapstr << " (mapped to CT spw=" << spwMap_(msspw) << ")";
	msg << "In caltable "
	    << Path(ct_.tableName()).baseName().before(".tempMemCal")
	    << " (" << ct_.keywordSet().asString("VisCal") << ")"
	    << ":" << endl
	    << " Insufficient solution channel sampling (nchan=" << nSolChan << ") for frequency-dependent CUBIC/SPLINE interpolation " << endl
	    << " of calibration for MS spw=" << msspw
	    << ( msspw!=spwMap_(msspw) ? String(spwmapstr) : "" ) 
	    << "; using NEAREST instead.";
	log << LogOrigin("CTPatchedInterp","interpolate") << msg.str() << LogIO::WARN;
      }
      else if (nSolChan<4 && freqInterpMethodVec_(msspw)>LINEAR) {
	freqInterpMethodVec_(msspw)=LINEAR;  // change to nearest for this msspw
	ostringstream spwmapstr;
	spwmapstr << " (mapped to CT spw=" << spwMap_(msspw) << ")";
	msg << "In caltable "
	    << Path(ct_.tableName()).baseName().before(".tempMemCal")
	    << " (" << ct_.keywordSet().asString("VisCal") << ")"
	    << ":" << endl
	    << " Insufficient solution channel sampling (nchan=" << nSolChan << ") for frequency-dependent CUBIC/SPLINE interpolation " << endl
	    << " of calibration for MS spw=" << msspw
	    << ( msspw!=spwMap_(msspw) ? String(spwmapstr) : "" ) 
	    << "; using LINEAR instead.";
	log << LogOrigin("CTPatchedInterp","interpolate") << msg.str() << LogIO::WARN;
      }
      break;
    }
    default: {
      break;
    }
    }
  }

  // Use the msspw-specific one!
  freqInterpMethod_=freqInterpMethodVec_(msspw);

  Bool newcal(false);
  Int scanOrObsInt=thisTimeSeg(msobs,msscan);
  IPosition ip(4,0,msspw,msfld,scanOrObsInt);

  // Loop over _output_ antennas
  for (Int iMSElem=0;iMSElem<nMSElem_;++iMSElem) {
    // Call time interpolation calculation; resample in freq if new
    //   (fills timeResult_/timeResFlag_ implicitly)
    ip(0)=iMSElem;
    if (!tI_(ip)) {
      //if (iMSElem==0) cout << "Flagging: " << ip << endl;

      // casa log post
      if (!tIMissingLogged_(ip)) {  // only if these coords not logged before
	LogIO log;
	ostringstream msg;
	String scanOrObsStr=(byScan_ ? "scan" : "obs");
	msg  << "MS " << scanOrObsStr << "=" << scanOrObsInt;
	if (byField_) msg << " in fld=" << msfld;
	msg << ",spw=" << msspw
	    << ",ant=" << iMSElem
	    << " is selected for processing, but has no available calibration in " << ctname_
	    << " as mapped, and will be flagged.";
	log << msg.str() << LogIO::WARN;
	tIMissingLogged_(ip)=true;
      }
      
      
      newcal=true;
    }
    else {
      
      if (tI_(ip)->interpolate(time)) { 
	// Resample in frequency
	Matrix<Float> fR(freqResult_(msspw,msfld,scanOrObsInt).xyPlane(iMSElem));
	Matrix<Bool> fRflg(freqResFlag_(msspw,msfld,scanOrObsInt).xyPlane(iMSElem));
	Matrix<Float> tR(timeResult_(msspw,msfld,scanOrObsInt).xyPlane(iMSElem));
	Matrix<Bool> tRflg(timeResFlag_(msspw,msfld,scanOrObsInt).xyPlane(iMSElem));
	resampleInFreq(fR,fRflg,freq,tR,tRflg,freqIn_(spwMap_(msspw)));
	
	// Calibration is new
	newcal=true;
      }
    }
  }

  // Whole result referred to freq result:
  result_(msspw,msfld,scanOrObsInt).reference(freqResult_(msspw,msfld,scanOrObsInt));
  resFlag_(msspw,msfld,scanOrObsInt).reference(freqResFlag_(msspw,msfld,scanOrObsInt));

  // Detect if obs or fld changed, and cal is obs- or fld-dep
  Bool diffobsfld(false);
  diffobsfld|=(byField_ && msfld!=lastFld_(msspw));   // field-dep, and field changed
  diffobsfld|=(byObs_ && msobs!=lastObs_(msspw));     // obs-dep, and obs changed
  diffobsfld|=(byScan_ && msscan!=lastScan_(msspw));  // scan-dep, and scan changed
  newcal|=diffobsfld;  //  update newcal for return

  /*
  if (newcal) {
    Double t0(86400.0*floor(time/86400.0));
    cout << boolalpha
	 << "fld="<<msfld
	 << " obs="<<scanOrObsInt
	 << " spw="<<msspw
	 << " time="<< time-t0
	 << " diffobsfld=" << diffobsfld
	 << " new=" << newcal
	 << " tI_(ip)=" << tI_(ip)
	 << " chan="<< nMSChan/2
	 << " result=" << result_(msspw,msfld,scanOrObsInt)(0,nMSChan/2,0)
	 << " addr=" << &result_(msspw,msfld,scanOrObsInt)(0,nMSChan/2,0)
	 << endl;
  }
  */
  // Remember for next pass
  lastFld_(msspw)=msfld;
  lastObs_(msspw)=msobs;
  lastScan_(msspw)=msscan;

  return newcal;
}

// spwOK info for users
Bool CTPatchedInterp::spwOK(Int spw) const {

  if (spw<Int(spwMap_.nelements()))
    return this->spwInOK(spwMap_(spw));

  // Something wrong...
  return false;

}
Bool CTPatchedInterp::spwInOK(Int spw) const {

  if (spw<Int(spwInOK_.nelements()))
    return spwInOK_(spw);

  // Something wrong
  return false;

}


  // Report state
void CTPatchedInterp::state() {

  if (CTPATCHEDINTERPVERB) cout << "CTPatchedInterp::state()" << endl;

  cout << "-state--------" << endl;
  cout << " ct_      = " << Path(ct_.tableName()).baseName().before(".tempMemCal") << endl;
  cout << boolalpha;
  cout << " isCmplx_ = " << isCmplx_ << endl;
  cout << " nPar_    = " << nPar_ << endl;
  cout << " nFPar_   = " << nFPar_ << endl;
  cout << " nMSTimeSeg_  = " << nMSTimeSeg_ << endl;
  cout << " nMSFld_  = " << nMSFld_ << endl;
  cout << " nMSSpw_  = " << nMSSpw_ << endl;
  cout << " nMSAnt_  = " << nMSAnt_ << endl;
  cout << " nMSElem_ = " << nMSElem_ << endl;
  cout << " nCTTimeSeg_  = " << nCTTimeSeg_ << endl;
  cout << " nCTFld_  = " << nCTFld_ << endl;
  cout << " nCTSpw_  = " << nCTSpw_ << endl;
  cout << " nCTAnt_  = " << nCTAnt_ << endl;
  cout << " nCTElem_ = " << nCTElem_ << endl;
  cout << " fldMap_  = " << fldMap_ << endl;
  cout << " spwMap_  = " << spwMap_ << endl;
  cout << " antMap_  = " << antMap_ << endl;
  cout << " byObs_   = " << byObs_ << endl;
  cout << " byScan_  = " << byScan_ << endl;
  cout << " byField_ = " << byField_ << endl;
  cout << " altFld_  = " << altFld_ << endl;
  cout << " timeType_ = " << timeType_ << endl;
  cout << " freqTypeStr_ = " << freqTypeStr_ << endl;
  cout << " freqInterpMethod0_ = " << freqInterpMethod0_ << endl;
  cout << " freqInterpMethodVec_ = " << freqInterpMethodVec_ << endl;
  cout << " spwInOK_ = " << spwInOK_ << endl;
}

void CTPatchedInterp::sliceTable() {

  if (CTPATCHEDINTERPVERB) cout << "  CTPatchedInterp::sliceTable()" << endl;

  // This method generates per-spw, per-antenna (and eventually per-field?)
  //   caltables.  

  // Ensure time sort of input table
  //  TBD (here or inside loop?)

  // Indexed by the fields, spws, ants in the cal table (pre-mapped)
  ctSlices_.resize(IPosition(4,nCTElem_,nCTSpw_,nCTFld_,nCTTimeSeg_));
  ctSlices_.set(NULL);

  // Initialize spwInOK_
  spwInOK_.resize(nCTSpw_);
  spwInOK_.set(false);

  // Set up iterator
  //  TBD: handle baseline-based case!
  Block<String> sortcol;
  Int addobs( (byObs_ ? 1 : 0) ); // slicing by obs?
  Int addscan( (byScan_ ? 1 : 0) ); // slicing by scan?
  Int addfld( (byField_ ? 1 : 0) ); // slicing by field?

  switch(mtype_) {
  case VisCalEnum::GLOBAL: {

    throw(AipsError("CTPatchedInterp::sliceTable: No non-Mueller/Jones yet."));

    sortcol.resize(1+addobs+addscan+addfld);
    if (byObs_) sortcol[0]="OBSERVATION_ID";  // slicing by obs
    if (byScan_) sortcol[0+addobs]="SCAN_NUMBER";  // slicing by scan
    if (byField_) sortcol[0+addobs+addscan]="FIELD_ID";  // slicing by field
    sortcol[0+addobs+addscan+addfld]="SPECTRAL_WINDOW_ID";
    ROCTIter ctiter(ct_,sortcol);
    while (!ctiter.pastEnd()) {
      Int ispw=ctiter.thisSpw();
      Int ifld = (byField_ ? ctiter.thisField() : 0); // use 0 if not slicing by field
      Int iobs = (byObs_ ? ctiter.thisObs() : 0); // use 0 if not slicing by obs
      iobs = (byScan_ ? ctiter.thisScan() : iobs); // XXX perscan overrides perobs?
      IPosition ip(4,0,ispw,ifld,iobs);
      ctSlices_(ip)= new NewCalTable(ctiter.table());
      spwInOK_(ispw)=(spwInOK_(ispw) || ctSlices_(ip)->nrow()>0);
      ctiter.next();
    }
    break;
  }
  case VisCalEnum::MUELLER: {
    sortcol.resize(3+addobs+addscan+addfld);
    if (byObs_) sortcol[0]="OBSERVATION_ID";  // slicing by obs
    if (byScan_) sortcol[0+addobs]="SCAN_NUMBER";  // slicing by scan
    if (byField_) sortcol[0+addobs+addscan]="FIELD_ID";  // slicing by field
    sortcol[0+addobs+addscan+addfld]="SPECTRAL_WINDOW_ID";
    sortcol[1+addobs+addscan+addfld]="ANTENNA1";
    sortcol[2+addobs+addscan+addfld]="ANTENNA2";
    ROCTIter ctiter(ct_,sortcol);
    while (!ctiter.pastEnd()) {
      Int ispw=ctiter.thisSpw();
      Int iant1=ctiter.thisAntenna1();
      Int iant2=ctiter.thisAntenna2();
      Int ibln=blnidx(iant1,iant2,nCTAnt_);
      Int ifld = (byField_ ? ctiter.thisField() : 0); // use 0 if not slicing by field
      Int iobs = (byObs_ ? ctiter.thisObs() : 0); // use 0 if not slicing by obs
      iobs = (byScan_ ? ctiter.thisScan() : iobs); // XXX perscan overrides perobs?
      IPosition ip(4,ibln,ispw,ifld,iobs);
      ctSlices_(ip)=new NewCalTable(ctiter.table());
      spwInOK_(ispw)=(spwInOK_(ispw) || ctSlices_(ip)->nrow()>0);
      ctiter.next();
    }    
    break;
  }
  case VisCalEnum::JONES: {
    sortcol.resize(2+addobs+addscan+addfld);
    if (byObs_) sortcol[0]="OBSERVATION_ID";  // slicing by obs
    if (byScan_) sortcol[0+addobs]="SCAN_NUMBER";  // slicing by scan
    if (byField_) sortcol[0+addobs+addscan]="FIELD_ID";  // slicing by field
    sortcol[0+addobs+addscan+addfld]="SPECTRAL_WINDOW_ID";
    sortcol[1+addobs+addscan+addfld]="ANTENNA1";
    ROCTIter ctiter(ct_,sortcol);
    while (!ctiter.pastEnd()) {
      Int ispw=ctiter.thisSpw();
      Int iant=ctiter.thisAntenna1();
      Int ifld = (byField_ ? ctiter.thisField() : 0); // use 0 if not slicing by field
      Int iobs = (byObs_ ? ctiter.thisObs() : 0); // use 0 if not slicing by obs
      iobs = (byScan_ ? ctiter.thisScan() : iobs); // XXX perscan overrides perobs?
      IPosition ip(4,iant,ispw,ifld,iobs);
      ctSlices_(ip)= new NewCalTable(ctiter.table());
      spwInOK_(ispw)=(spwInOK_(ispw) || ctSlices_(ip)->nrow()>0);
      ctiter.next();
    }    
    break;
  }
  }
  
}

// Initialize by iterating over the supplied table
void CTPatchedInterp::makeInterpolators() {

  if (CTPATCHEDINTERPVERB) cout << "  CTPatchedInterp::initialize()" << endl;

  // Save caltable name for log messages
  ctname_=Path(ct_.antenna().tableName().before(".tempMem")).baseName();
  
  // Size/initialize interpolation engines
  IPosition tIsize(4,nMSElem_,nMSSpw_,nMSFld_,nMSTimeSeg_);
  tI_.resize(tIsize);
  tI_.set(NULL);
  tIdel_.resize(tIsize);
  tIdel_.set(false);
  tIMissingLogged_.resize(tIsize);
  tIMissingLogged_.set(false);

  Bool reportBadSpw(false);
  for (Int iMSTimeSeg=0;iMSTimeSeg<nMSTimeSeg_;++iMSTimeSeg) {
  for (Int iMSFld=0;iMSFld<nMSFld_;++iMSFld) {

    if (altFld_(iMSFld)==iMSFld) {  // i.e., this field has its own solutions

      //      cout << "Making  interpolators for        " << iMSFld << " (mapped from " << fldMap_(iMSFld) << ")" << endl;

      std::set<uInt> spws;
      if (msmc_)
	spws = msmc_->msmd().getSpwsForField(iMSFld);

      for (Int iMSSpw=0;iMSSpw<nMSSpw_;++iMSSpw) { 
	
	// Only if the required CT spw is available
	//  (spwmap applied in spwOK method)
	if (this->spwOK(iMSSpw)) {
	  
	  // Size up the timeResult_ Cube (NB: channel shape matches Cal Table)
	  if (timeResult_(iMSSpw,iMSFld,iMSTimeSeg).nelements()==0) {
	    timeResult_(iMSSpw,iMSFld,iMSTimeSeg).resize(nFPar_,nChanIn_(spwMap_(iMSSpw)),nMSElem_);
	    timeResFlag_(iMSSpw,iMSFld,iMSTimeSeg).resize(nPar_,nChanIn_(spwMap_(iMSSpw)),nMSElem_);
	    timeResFlag_(iMSSpw,iMSFld,iMSTimeSeg).set(true);
	  }
	  for (Int iMSElem=0;iMSElem<nMSElem_;++iMSElem) {
	    // Realize the mapping 
	    IPosition ictip(4,elemMap_(iMSElem),spwMap_(iMSSpw),fldMap_(iMSFld),iMSTimeSeg);
	    IPosition tIip(4,iMSElem,iMSSpw,iMSFld,iMSTimeSeg);
	    Matrix<Float> tR(timeResult_(iMSSpw,iMSFld,iMSTimeSeg).xyPlane(iMSElem));
	    Matrix<Bool> tRf(timeResFlag_(iMSSpw,iMSFld,iMSTimeSeg).xyPlane(iMSElem));

	    // If the ct slice exists, set up an interpolator
	    if (ictip(0) >= 0 && ictip(0) < nCTElem_ &&
		ictip(1) >= 0 && ictip(1) < nCTSpw_ &&
		ictip(2) >= 0 && ictip(2) < nCTFld_ &&
		ictip(3) >= 0 && ictip(3) < nCTTimeSeg_ &&
		ctSlices_(ictip)) {
	      NewCalTable& ict(*ctSlices_(ictip));
	      if (!ict.isNull()) {
		tI_(tIip)=(*cttifactoryptr_)(ict,timeType_,tR,tRf);
		tIdel_(tIip)=true;
	      }
	    }
	    else {
	      // the required ct slice is empty, so arrange to flag it
	      tI_(tIip)=NULL; 
	      tR.set(0.0);
	      tRf.set(true);
	    }
	  } // iMSElem
	} // spwOK
	else
	  reportBadSpw=true;
      } // iMSSpw

    } // not re-using
    else {  // re-using
      // Point to an existing interpolator group
      Int thisAltFld=altFld_(iMSFld);

      //      cout << "Reusing interpolators from " << thisAltFld << " for " << iMSFld << " (mapped to   " << fldMap_(iMSFld) << ")" << endl;

      for (Int iMSSpw=0;iMSSpw<nMSSpw_;++iMSSpw) { 
	timeResult_(iMSSpw,iMSFld,iMSTimeSeg).reference(timeResult_(iMSSpw,thisAltFld,iMSTimeSeg));
	timeResFlag_(iMSSpw,iMSFld,iMSTimeSeg).reference(timeResFlag_(iMSSpw,thisAltFld,iMSTimeSeg));
	for (Int iMSElem=0;iMSElem<nMSElem_;++iMSElem) {
	  IPosition tIip0(4,iMSElem,iMSSpw,iMSFld,iMSTimeSeg),tIip1(4,iMSElem,iMSSpw,thisAltFld,iMSTimeSeg);
	  tI_(tIip0)=tI_(tIip1);
	  //	  if (!tI_(tIip0) && iMSElem==0)  cout << "ouch---------------------" << "iMSTimeSeg="<<iMSTimeSeg<<" iMSFld="<<iMSFld<<" spw="<< iMSSpw << " ant="<<iMSElem<< endl;
	}
      }
    }
  } // iMSFld
  } // iMSTimeSeg


  if (reportBadSpw) {
    cout << "The following MS spws have no corresponding cal spws in " << ctname_ << ": ";
    for (Int iMSSpw=0;iMSSpw<nMSSpw_;++iMSSpw)
      //  (spwmap applied in spwOK method)
      if (!this->spwOK(iMSSpw)) cout << iMSSpw << " ";
    cout << endl;
  }

}



void CTPatchedInterp::setFldMap(const MSField& msfld) {
  
  MSFieldColumns fcol(msfld);
  setFldMap(fcol);

}

void CTPatchedInterp::setFldMap(const MSFieldColumns& fcol) {

   // Set the default fldmap
   setDefFldMap();
   //   cout << "Nominal fldMap_ = " << fldMap_ << endl;

   ROCTColumns ctcol(ct_);

   // Discern _available_ fields in the CT
   Vector<Int> ctFlds;
   ctcol.fieldId().getColumn(ctFlds);
   Int nAvFlds=genSort(ctFlds,Sort::Ascending,(Sort::QuickSort | Sort::NoDuplicates));
   ctFlds.resize(nAvFlds,true);

   //cout << "nAvFlds = " << nAvFlds << endl;
   //cout << "ctFlds  = " << ctFlds << endl;

   // If only one CT field, just use it

   Vector<Double> fseps(0);
   if (nAvFlds==1) 
     fldMap_.set(ctFlds(0));
   else {
     // For each MS field, find the nearest available CT field 
     Int nMSFlds=fcol.nrow();
     fseps.resize(nMSFlds);
     fseps.set(0.0);
     MDirection msdir,ctdir;
     Vector<Double> sep(nAvFlds);
     IPosition ipos(1,0);  // get the first direction stored (no poly yet)
     for (Int iMSFld=0;iMSFld<nMSFlds;++iMSFld) {
       msdir=fcol.phaseDirMeas(iMSFld);
       //cout << iMSFld << ":" << msdir.getValue() << endl;
       sep.set(DBL_MAX);
       for (Int iCTFld=0;iCTFld<nAvFlds;++iCTFld) {
	 // Get cal field direction, converted to ms field frame
	 ctdir=ctcol.field().phaseDirMeas(ctFlds(iCTFld));
	 MDirection::Convert(ctdir,msdir.getRef());
	 //cout << "   c:" << ctFlds(iCTFld) << ":" << ctdir.getValue() << endl;
	 sep(iCTFld)=ctdir.getValue().separation(msdir.getValue());
       }
       // Sort separations
       Vector<uInt> ord;
       Int nsep=genSort(ord,sep,Sort::Ascending,(Sort::QuickSort | Sort::Ascending));

       /*
       cout << "    ord=" << ord << endl;
       cout << "   nsep=" << nsep << endl;
       cout << "    sep=" << sep << " " << sep*(180.0/C::pi)<< endl;
       */

       // Trap case of duplication of nearest separation
       if (nsep>1 && sep(ord(1))==sep(ord(0)))
	 throw(AipsError("Found more than one field at minimum distance, can't decide!"));
       
       fseps(iMSFld)=sep(ord(0));
       fldMap_(iMSFld)=ctFlds(ord(0));
     }   
   }
   fseps*=(180.0/C::pi);
   LogIO log;
   ostringstream msg;
   msg << "Calibration field mapping for "
       << Path(ct_.tableName()).baseName().before(".tempMemCal")
       << " (via gainfield='nearest'): "
       << fldMap_ << endl
       << " Separations (deg): " << fseps;
   log << msg.str() << LogIO::POST;

   //   cout << ct_.tableName() << ": fldMap_ = " << fldMap_ << endl;
}   

void CTPatchedInterp::setFldMap(Vector<Int>& fldmap) {

  // Set the default spwmap first, then we'll ammend it
  setDefFldMap();

  Int nfld=fldmap.nelements();

  // Must specify no more than needed, but at least one
  AlwaysAssert(nfld>0,AipsError);
  AlwaysAssert(nfld<=nMSFld_,AipsError);

  // Discern _available_ fields in the CT
  ROCTColumns ctcol(ct_);
  Vector<Int> ctFlds;
  ctcol.fieldId().getColumn(ctFlds);
  Int nAvFlds=genSort(ctFlds,Sort::Ascending,(Sort::QuickSort | Sort::NoDuplicates));
  ctFlds.resize(nAvFlds,true);
  
  for (Int i=0;i<nfld;++i) {
    if (!anyEQ(ctFlds,fldmap(i)))
      throw(AipsError("Specified fldmap contains an unavailable field: "+String(fldmap(i))));
    else
      fldMap_(i)=fldmap(i);
  }
  if (nfld<nMSFld_)
  // Fill in the rest with last-specified
    fldMap_(Slice(nfld,nMSFld_-nfld,1))=fldMap_(nfld-1);


   LogIO log;
   ostringstream msg;
   msg << "Calibration field mapping for "
       << Path(ct_.tableName()).baseName().before(".tempMemCal")
       << " (via user specification): "
       << fldMap_;
   log << msg.str() << LogIO::POST;

}

// Calculate fldmap redundancy
void CTPatchedInterp::calcAltFld() {

  altFld_.resize(nMSFld_);
  for (Int iMSFld=0;iMSFld<nMSFld_;++iMSFld) {
     altFld_(iMSFld)=iMSFld;  // nominally
     for (Int ifld=0;ifld<iMSFld;++ifld) 
       if (fldMap_(ifld)==fldMap_(iMSFld))
         //altFld_(iMSFld)=ifld;  
	 altFld_(iMSFld)=altFld_(ifld);
  }
  /*
  cout << "------------" << endl;
  cout << "fldMap_ = " << fldMap_ << endl;
  cout << "altFld_ = " << altFld_ << endl;
  */
}



void CTPatchedInterp::setSpwMap(Vector<Int>& spwmap) {

  // Set the default spwmap first, then we'll ammend it
  setDefSpwMap();

  Int nspec=spwmap.nelements();

  // Do nothing, if nothing specified (and rely on default)
  if (nspec==0) return;

  // Do nothing f a single -1 is specified
  if (nspec==1 && spwmap(0)==-1) return;


  // Alert user if too many spws specified
  if (spwmap.nelements()>uInt(nMSSpw_))
    throw(AipsError("Specified spwmap has more elements ("+String::toString(spwmap.nelements())+") than the number of spectral windows in the MS ("+String::toString(nMSSpw_)+")."));
    
  // Handle auto-fanout
  if (spwmap(0)==-999) {
    // Use first OK spw for all MS spws
    Int gspw(0);
    while (!spwInOK(gspw)) ++gspw;
    spwMap_.set(gspw);
  }
  else {
    // First trap out-of-range values
    if (anyLT(spwmap,0))
      throw(AipsError("Please specify positive indices in spwmap."));
    if (anyGE(spwmap,nCTSpw_)) {
      ostringstream o;
      o << "Please specify spw indices <= maximum available ("
	<< (nCTSpw_-1) << " in " << Path(ct_.tableName()).baseName().before(".tempMemCal") << ")";
      throw(AipsError(o.str()));
    }

    // Now fill from spwmap
    if (nspec==1)
      // Use one value for all
      spwMap_.set(spwmap(0));
    else {
      // set as many as are specified
      IPosition blc(1,0);
      IPosition trc(1,min(nspec-1,nMSSpw_-1));
      spwMap_(blc,trc)=spwmap(blc,trc);
    }
  }

  //cout << "CTPatchedInterp::setSpwMap: Realized spwMap_ = " << spwMap_ << endl;

}


// Resample in frequency
void CTPatchedInterp::resampleInFreq(Matrix<Float>& fres,Matrix<Bool>& fflg,const Vector<Double>& fout,
				     Matrix<Float>& tres,Matrix<Bool>& tflg,const Vector<Double>& fin) {

  if (CTPATCHEDINTERPVERB) cout << "  CTPatchedInterp::resampleInFreq(...)" << endl;

  // if no good solutions coming in, return flagged
  if (nfalse(tflg)==0) {
    fflg.set(true);
    return;
  }


  Int flparmod=nFPar_/nPar_;    // for indexing the flag Matrices on the par axis

  Bool unWrapPhase=flparmod>1;

  //  cout << "nFPar_,nPar_,flparmod = " << nFPar_ << "," << nPar_ << "," << flparmod << endl;

  fres=0.0;

  for (Int ifpar=0;ifpar<nFPar_;++ifpar) {

    // Slice by par (each Matrix row)
    Vector<Float> fresi(fres.row(ifpar)), tresi(tres.row(ifpar));
    Vector<Bool> fflgi(fflg.row(ifpar/flparmod)), tflgi(tflg.row(ifpar/flparmod));

    // Mask time result by flags
    Vector<Double> mfin=fin(!tflgi).getCompressedArray();

    if (mfin.nelements()==0) {
      //   cout << ifpar << " All chans flagged!" << endl;
      // Everything flagged this par
      //  Just flag, zero and go on to the next one
      fflgi.set(true);
      fresi.set(0.0);
      continue;
    }

    mfin/=1.0e9; // in GHz
    Vector<Float> mtresi=tresi(!tflgi).getCompressedArray();

    // Trap case of same in/out frequencies
    if (fout.nelements()==mfin.nelements() && allNear(fout,mfin,1.e-10)) {
      // Just copy
      fresi=mtresi;
      fflgi.set(false);  // none are flagged
      continue;
    }

    if (ifpar%2==1 && unWrapPhase) {
      for (uInt i=1;i<mtresi.nelements();++i) {
        while ( (mtresi(i)-mtresi(i-1))>C::pi ) mtresi(i)-=C::_2pi;
        while ( (mtresi(i)-mtresi(i-1))<-C::pi ) mtresi(i)+=C::_2pi;
      }
    }

    // Set flags carefully
    resampleFlagsInFreq(fflgi,fout,tflgi,fin);


    // Always use nearest on edges
    // TBD: trap cases where frequencies don't overlap at all
    //     (fout(hi)<mfin(0) || fout(lo)> mfin(ihi))..... already OK (lo>hi)?
    // TBD: optimize the following by forming Slices in the
    //     while loops and doing Array assignment once afterwords

    Int nfreq=fout.nelements();
    Int lo=0;
    Int hi=fresi.nelements()-1;
    Double inlo(mfin(0));
    Int ihi=mtresi.nelements()-1;
    Double inhi(mfin(ihi));

    // Handle 'nearest' extrapolation in sideband-dep way
    Bool inUSB(inhi>inlo);
    Bool outUSB(fout(hi)>fout(lo));
    if (inUSB) {
      if (outUSB) {
	while (lo<nfreq && fout(lo)<=inlo) fresi(lo++)=mtresi(0);
	while (hi>-1 && fout(hi)>=inhi) fresi(hi--)=mtresi(ihi);
      }
      else { // "outLSB"
	while (lo<nfreq && fout(lo)>=inhi) fresi(lo++)=mtresi(ihi);
	while (hi>-1 && fout(hi)<=inlo) fresi(hi--)=mtresi(0);
      }
    }
    else {  // "inLSB"
      if (outUSB) {
	while (lo<nfreq && fout(lo)<=inhi) fresi(lo++)=mtresi(ihi);
	while (hi>-1 && fout(hi)>=inlo) fresi(hi--)=mtresi(0);
      }
      else {  // "outLSB"
	while (lo<nfreq && fout(lo)>=inlo) fresi(lo++)=mtresi(0);
	while (hi>-1 && fout(hi)<=inhi) fresi(hi--)=mtresi(ihi);
      }
    }

    //    cout << "lo, hi = " << lo << ","<<hi << endl;

    if (lo>hi) continue; // Frequencies didn't overlap, nearest was used

    // Use InterpolateArray1D to fill in the middle
    IPosition blc(1,lo), trc(1,hi);
    Vector<Float> slfresi(fresi(blc,trc));
    Vector<Double> slfout(fout(blc,trc));

    InterpolateArray1D<Double,Float>::interpolate(slfresi,slfout,mfin,mtresi,freqInterpMethod_);

  }
}

void CTPatchedInterp::resampleFlagsInFreq(Vector<Bool>& flgout,const Vector<Double>& fout,
					  Vector<Bool>& flgin,const Vector<Double>& fin) {

  //  cout << "resampleFlagsInFreq" << endl;

  Vector<Double> finGHz=fin/1e9;

  // Handle chan-dep flags
  if (freqTypeStr_.contains("flag")) {
    
    // Determine implied mode-dep flags indexed by channel registration
    uInt nflg=flgin.nelements();
    Vector<Bool> flreg(nflg,false);
    switch (freqInterpMethod_) {
    case NEAREST: {
      // Just use input flags
      flreg.reference(flgin);
      break;
    }
    case LINEAR: {
      for (uInt i=0;i<nflg-1;++i)
	flreg[i]=(flgin[i] || flgin[i+1]);
      flreg[nflg-1]=flreg[nflg-2];
      break;
    }
    case CUBIC:
    case SPLINE: {
      for (uInt i=1;i<nflg-2;++i)
	flreg[i]=(flgin[i-1] || flgin[i] || flgin[i+1] || flgin[i+2]);
      flreg[0]=flreg[1];
      flreg[nflg-2]=flreg[nflg-3];
      flreg[nflg-1]=flreg[nflg-3];
      break;
    }
    }
    
    // Now step through requested chans, setting flags
    uInt ireg=0;
    uInt nflgout=flgout.nelements();
    for (uInt iflgout=0;iflgout<nflgout;++iflgout) {
      
      // Find nominal registration (the _index_ just left)
      Bool exact(false);
      ireg=binarySearch(exact,finGHz,fout(iflgout),nflg,0);

      // If registration is exact, assign verbatim
      // NB: the calibration value calculation occurs agnostically w.r.t. flags,
      //     so the calculated value should also match
      // TBD: Add "|| near(finGHz[ireg],fout(iflgout),1e-10) in case "exact" has
      //      precision issues?
      if (exact) {
	flgout[iflgout]=flgin[ireg];
	continue;
      }

      // Not exact, so carefully handle bracketing
      if (ireg>0)
	ireg-=1;
      ireg=min(ireg,nflg-1);

      //while (finGHz(ireg)<=fout(iflgout) && ireg<nflg-1) {
      //  ireg+=1;  // USB specific!
      //}
      //if (ireg>0 && finGHz(ireg)!=fout(iflgout)) --ireg;  // registration is one sample prior

      // refine registration by interp type
      switch (freqInterpMethod_) {
      case NEAREST: {
	// nearest might be forward sample
	if ( ireg<(nflg-1) &&
	     abs(fout[iflgout]-finGHz[ireg])>abs(finGHz[ireg+1]-fout[iflgout]) )
	  ireg+=1;
	break;
      }
      case LINEAR: {
	if (ireg==(nflg-1)) // need one more sample to the right
	  ireg-=1;
	break;
      }
      case CUBIC:
      case SPLINE: {
	if (ireg==0) ireg+=1;  // need one more sample to the left
	if (ireg>(nflg-3)) ireg=nflg-3;  // need two more samples to the right
	break;
      }
      }
	
      // Assign effective flag
      flgout[iflgout]=flreg[ireg];

      /*
      cout << iflgout << " "
	   << ireg << " "
	   << flreg[ireg] 
	   << endl;
      */

    }

  }
  else 
    // We are interp/extrap-olating gaps absolutely
    flgout.set(false);

}

void CTPatchedInterp::setElemMap() {
 
  // Ensure the antMap_ is set
  if (antMap_.nelements()!=uInt(nMSAnt_))
    setDefAntMap();

  // Handle cases
  switch(mtype_) {
  case VisCalEnum::GLOBAL: {

    throw(AipsError("CTPatchedInterp::sliceTable: No non-Mueller/Jones yet."));

    // There is only 1
    AlwaysAssert(nMSElem_==1,AipsError);
    elemMap_.resize(nMSElem_);
    elemMap_.set(0);

    break;
  }
  case VisCalEnum::MUELLER: {
    elemMap_.resize(nMSElem_);
    conjTab_.resize(nMSElem_);
    conjTab_.set(false);
    Int iMSElem(0),a1in(0),a2in(0);
    for (Int iMSAnt=0;iMSAnt<nMSAnt_;++iMSAnt) {
      a1in=antMap_(iMSAnt);
      for (Int jAntOut=iMSAnt;jAntOut<nMSAnt_;++jAntOut) {
	a2in=antMap_(jAntOut);
	if (a1in<=a2in)
	  elemMap_(iMSElem)=blnidx(a1in,a2in,nMSAnt_);
	else {
	  elemMap_(iMSElem)=blnidx(a2in,a1in,nMSAnt_);
	  conjTab_(iMSElem)=true;  // we must conjugate Complex params!
	}
	++iMSElem;
      } // jAntOut
    } // iMSAnt
    break;
  }    
  case VisCalEnum::JONES: {
    // Just reference the antMap_
    elemMap_.reference(antMap_);
    break;
  }
  } // switch
}


InterpolateArray1D<Double,Float>::InterpolationMethod CTPatchedInterp::ftype(String& strtype) {
  if (strtype.contains("nearest"))
    return InterpolateArray1D<Double,Float>::nearestNeighbour;
  if (strtype.contains("linear"))
    return InterpolateArray1D<Double,Float>::linear;
  if (strtype.contains("cubic"))
    return InterpolateArray1D<Double,Float>::cubic;
  if (strtype.contains("spline"))
    return InterpolateArray1D<Double,Float>::spline;

  //  cout << "Using linear for freq interpolation as last resort." << endl;
  return InterpolateArray1D<Double,Float>::linear;


}


} //# NAMESPACE CASA - END