// Action_ClusterDihedral::ReadDihedrals()
int Action_ClusterDihedral::ReadDihedrals(std::string const& fname) {
  CpptrajFile infile;
  char buffer[256];
  int a1, a2, a3, a4, bins;
  double min;

  if ( infile.OpenRead( fname ) ) return 1;
  mprintf("\tReading dihedral information from %s\n", fname.c_str());
  while (infile.Gets(buffer, 256)==0) {
    // Expected line format: At#1 At#2 At#3 At#4 Bins Min
    // ATOM NUMBERS SHOULD START FROM 1!
    int nvals = sscanf(buffer, "%i %i %i %i %i %lf", &a1, &a2, &a3, &a4, &bins, &min);
    if (nvals < 5) {
      mprinterr("Error: Dihedral file %s: Expected at least 5 values, got %i\n", 
                fname.c_str(), nvals);
      mprinterr("Error: Problem line: [%s]\n",buffer);
      mprinterr("Error: Expected format: At#1 At#2 At#3 At#4 Bins [Min]\n");
      return 1; // This should automatically close infile through destructor.
    }
    if (nvals < 6)
      min = minimum_;
    DCmasks_.push_back( DCmask(a1-1, a2-1, a3-1, a4-1, bins, min ) );
    mprintf("\t\t(%i)-(%i)-(%i)-(%i) Bins=%i Min=%.3f\n",a1,a2,a3,a4,bins,min);
  }
  mprintf("\tRead %zu dihedrals.\n", DCmasks_.size());
  infile.CloseFile();
  return 0;
}
/** Open the Charmm PSF file specified by filename and set up topology data.
  * Mask selection requires natom, nres, names, resnames, resnums.
  */
int Parm_CharmmPsf::ReadParm(FileName const& fname, Topology &parmOut) {
  const size_t TAGSIZE = 10; 
  char tag[TAGSIZE];
  tag[0]='\0';

  CpptrajFile infile;
  if (infile.OpenRead(fname)) return 1;
  mprintf("    Reading Charmm PSF file %s as topology file.\n",infile.Filename().base());
  // Read the first line, should contain PSF...
  const char* buffer = 0;
  if ( (buffer=infile.NextLine()) == 0 ) return 1;
  // Advance to <ntitle> !NTITLE
  int ntitle = FindTag(tag, "!NTITLE", 7, infile); 
  // Only read in 1st title. Skip any asterisks.
  std::string psftitle;
  if (ntitle > 0) {
    buffer = infile.NextLine();
    const char* ptr = buffer;
    while (*ptr != '\0' && (*ptr == ' ' || *ptr == '*')) ++ptr;
    psftitle.assign( ptr );
  }
  parmOut.SetParmName( NoTrailingWhitespace(psftitle), infile.Filename() );
  // Advance to <natom> !NATOM
  int natom = FindTag(tag, "!NATOM", 6, infile);
  if (debug_>0) mprintf("\tPSF: !NATOM tag found, natom=%i\n", natom);
  // If no atoms, probably issue with PSF file
  if (natom < 1) {
    mprinterr("Error: No atoms in PSF file.\n");
    return 1;
  }
  // Read the next natom lines
  int psfresnum = 0;
  char psfresname[6];
  char psfname[6];
  char psftype[6];
  double psfcharge;
  double psfmass;
  for (int atom=0; atom < natom; atom++) {
    if ( (buffer=infile.NextLine()) == 0 ) {
      mprinterr("Error: ReadParmPSF(): Reading atom %i\n",atom+1);
      return 1;
    }
    // Read line
    // ATOM# SEGID RES# RES ATNAME ATTYPE CHRG MASS (REST OF COLUMNS ARE LIKELY FOR CMAP AND CHEQ)
    sscanf(buffer,"%*i %*s %i %s %s %s %lf %lf",&psfresnum, psfresname, 
           psfname, psftype, &psfcharge, &psfmass);
    parmOut.AddTopAtom( Atom( psfname, psfcharge, psfmass, psftype), 
                        Residue( psfresname, psfresnum, ' ', ' '), 0 );
  } // END loop over atoms 
  // Advance to <nbond> !NBOND
  int bondatoms[9];
  int nbond = FindTag(tag, "!NBOND", 6, infile);
  if (nbond > 0) {
    if (debug_>0) mprintf("\tPSF: !NBOND tag found, nbond=%i\n", nbond);
    int nlines = nbond / 4;
    if ( (nbond % 4) != 0) nlines++;
    for (int bondline=0; bondline < nlines; bondline++) {
      if ( (buffer=infile.NextLine()) == 0 ) {
        mprinterr("Error: ReadParmPSF(): Reading bond line %i\n",bondline+1);
        return 1;
      }
      // Each line has 4 pairs of atom numbers
      int nbondsread = sscanf(buffer,"%i %i %i %i %i %i %i %i",bondatoms,bondatoms+1,
                              bondatoms+2,bondatoms+3, bondatoms+4,bondatoms+5,
                              bondatoms+6,bondatoms+7);
      // NOTE: Charmm atom nums start from 1
      for (int bondidx=0; bondidx < nbondsread; bondidx+=2)
        parmOut.AddBond(bondatoms[bondidx]-1, bondatoms[bondidx+1]-1);
    }
  } else
    mprintf("Warning: PSF has no bonds.\n");
  // Advance to <nangles> !NTHETA
  int nangle = FindTag(tag, "!NTHETA", 7, infile);
  if (nangle > 0) {
    if (debug_>0) mprintf("\tPSF: !NTHETA tag found, nangle=%i\n", nangle);
    int nlines = nangle / 3;
    if ( (nangle % 3) != 0) nlines++;
    for (int angleline=0; angleline < nlines; angleline++) {
      if ( (buffer=infile.NextLine()) == 0) {
        mprinterr("Error: Reading angle line %i\n", angleline+1);
        return 1;
      }
      // Each line has 3 groups of 3 atom numbers
      int nanglesread = sscanf(buffer,"%i %i %i %i %i %i %i %i %i",bondatoms,bondatoms+1,
                              bondatoms+2,bondatoms+3, bondatoms+4,bondatoms+5,
                              bondatoms+6,bondatoms+7, bondatoms+8);
      for (int angleidx=0; angleidx < nanglesread; angleidx += 3)
        parmOut.AddAngle( bondatoms[angleidx  ]-1,
                          bondatoms[angleidx+1]-1,
                          bondatoms[angleidx+2]-1 );
    }
  } else
    mprintf("Warning: PSF has no angles.\n");
  // Advance to <ndihedrals> !NPHI
  int ndihedral = FindTag(tag, "!NPHI", 5, infile);
  if (ndihedral > 0) {
    if (debug_>0) mprintf("\tPSF: !NPHI tag found, ndihedral=%i\n", ndihedral);
    int nlines = ndihedral / 2;
    if ( (ndihedral % 2) != 0) nlines++;
    for (int dihline = 0; dihline < nlines; dihline++) {
      if ( (buffer=infile.NextLine()) == 0) {
        mprinterr("Error: Reading dihedral line %i\n", dihline+1);
        return 1;
      }
      // Each line has 2 groups of 4 atom numbers
      int ndihread = sscanf(buffer,"%i %i %i %i %i %i %i %i",bondatoms,bondatoms+1,
                              bondatoms+2,bondatoms+3, bondatoms+4,bondatoms+5,
                              bondatoms+6,bondatoms+7);
      for (int dihidx=0; dihidx < ndihread; dihidx += 4)
        parmOut.AddDihedral( bondatoms[dihidx  ]-1,
                             bondatoms[dihidx+1]-1,
                             bondatoms[dihidx+2]-1,
                             bondatoms[dihidx+3]-1 );
    }
  } else
    mprintf("Warning: PSF has no dihedrals.\n");
  mprintf("\tPSF contains %i atoms, %i residues.\n", parmOut.Natom(), parmOut.Nres());

  infile.CloseFile();

  return 0;
}
// Action_Spam::init()
Action::RetType Action_Spam::Init(ArgList& actionArgs, TopologyList* PFL, DataSetList* DSL, DataFileList* DFL, int debugIn)
{
  // Always use imaged distances
  InitImaging(true);
  // This is needed everywhere in this function scope
  FileName filename;

  // See if we're doing pure water. If so, we don't need a peak file
  purewater_ = actionArgs.hasKey("purewater");

  if (purewater_) {
    // We still need the cutoff
    double cut = actionArgs.getKeyDouble("cut", 12.0);
    cut2_ = cut * cut;
    doublecut_ = 2 * cut;
    onecut2_ = 1 / cut2_;
    // See if we write to a data file
    datafile_ = actionArgs.GetStringKey("out");
    // Generate the data set name, and hold onto the master data set list
    std::string ds_name = actionArgs.GetStringKey("name");
    if (ds_name.empty())
      ds_name = myDSL_.GenerateDefaultName("SPAM");
    // We only have one data set averaging over every water. Add it here
    myDSL_.AddSet(DataSet::DOUBLE, ds_name, NULL);
    solvname_ = actionArgs.GetStringKey("solv");
    if (solvname_.empty())
      solvname_ = std::string("WAT");
  }else {
    // Get the file name with the peaks defined in it
    filename.SetFileName( actionArgs.GetStringNext() );

    if (filename.empty() || !File::Exists(filename)) {
      mprinterr("Spam: Error: Peak file [%s] does not exist!\n", filename.full());
      return Action::ERR;
    }

    // Get the remaining optional arguments
    solvname_ = actionArgs.GetStringKey("solv");
    if (solvname_.empty())
      solvname_ = std::string("WAT");
    reorder_ = actionArgs.hasKey("reorder");
    bulk_ = actionArgs.getKeyDouble("bulk", 0.0);
    double cut = actionArgs.getKeyDouble("cut", 12.0);
    cut2_ = cut * cut;
    doublecut_ = 2 * cut;
    onecut2_ = 1 / cut2_;
    std::string infoname = actionArgs.GetStringKey("info");
    if (infoname.empty())
      infoname = std::string("spam.info");
    infofile_ = DFL->AddCpptrajFile(infoname, "SPAM info");
    if (infofile_ == 0) return Action::ERR;
    // The default maskstr is the Oxygen atom of the solvent
    summaryfile_ = actionArgs.GetStringKey("summary");
    // Divide site size by 2 to make it half the edge length (or radius)
    site_size_ = actionArgs.getKeyDouble("site_size", 2.5) / 2.0;
    sphere_ = actionArgs.hasKey("sphere");
    // If it's a sphere, square the radius to compare with
    if (sphere_)
      site_size_ *= site_size_;
    datafile_ = actionArgs.GetStringKey("out");
    std::string ds_name = actionArgs.GetStringKey("name");
    if (ds_name.empty())
      ds_name = myDSL_.GenerateDefaultName("SPAM");

    // Parse through the peaks file and extract the peaks
    CpptrajFile peakfile;
    if (peakfile.OpenRead(filename)) {
      mprinterr("SPAM: Error: Could not open %s for reading!\n", filename.full());
      return Action::ERR;
    }
    std::string line = peakfile.GetLine();
    int npeaks = 0;
    while (!line.empty()) {
      if (sscanf(line.c_str(), "%d", &npeaks) != 1) {
        line = peakfile.GetLine();
        continue;
      }
      line = peakfile.GetLine();
      break;
    }
    while (!line.empty()) {
      double x, y, z, dens;
      if (sscanf(line.c_str(), "C %lg %lg %lg %lg", &x, &y, &z, &dens) != 4) {
        line = peakfile.GetLine();
        continue;
      }
      line = peakfile.GetLine();
      peaks_.push_back(Vec3(x, y, z));
    }
    peakfile.CloseFile();
    // Check that our initial number of peaks matches our parsed peaks. Warn
    // otherwise
    if (npeaks != (int)peaks_.size())
      mprinterr("SPAM: Warning: %s claims to have %d peaks, but really has %d!\n",
                filename.full(), npeaks, peaks_.size());
    // Now add all of the data sets
    MetaData md(ds_name);
    for (int i = 0; i < (int)peaks_.size(); i++) {
      md.SetAspect( integerToString(i+1) ); // TODO: Should this be Idx?
      if (myDSL_.AddSet(DataSet::DOUBLE, md) == 0) return Action::ERR;
      // Add a new list of integers to keep track of omitted frames
      std::vector<int> vec;
      peakFrameData_.push_back(vec);
    }
  }

  // Print info now
  if (purewater_) {
    mprintf("SPAM: Calculating bulk value for pure solvent\n");
    if (!datafile_.empty())
      mprintf("SPAM: Printing solvent energies to %s\n", datafile_.c_str());
    mprintf("SPAM: Using a %.2f Angstrom non-bonded cutoff with shifted EEL.\n",
            sqrt(cut2_));
    if (reorder_)
      mprintf("SPAM: Warning: Re-ordering makes no sense for pure solvent.\n");
    if (!summaryfile_.empty())
      mprintf("SPAM: Printing solvent SPAM summary to %s\n", summaryfile_.c_str());
  }else {
    mprintf("SPAM: Solvent [%s] density peaks taken from %s.\n",
            solvname_.c_str(), filename.base());
    mprintf("SPAM: %d density peaks will be analyzed from %s.\n",
            peaks_.size(), filename.base());
    mprintf("SPAM: Occupation information printed to %s.\n", infofile_->Filename().full());
    mprintf("SPAM: Sites are ");
    if (sphere_)
      mprintf("spheres with diameter %.3lf\n", site_size_);
    else
      mprintf("boxes with edge length %.3lf\n", site_size_);
    if (reorder_) {
      mprintf("SPAM: Re-ordering trajectory so each site always has ");
      mprintf("the same water molecule.\n");
    }
    if (summaryfile_.empty() && datafile_.empty()) {
      if (!reorder_) {
        mprinterr("SPAM: Error: Not re-ordering trajectory or calculating energies. ");
        mprinterr("Nothing to do!\n");
        return Action::ERR;
      }
      mprintf("SPAM: Not calculating any SPAM energies\n");
    }else {
      mprintf("SPAM: Using a non-bonded cutoff of %.2lf Ang. with a EEL shifting function.\n",
              sqrt(cut2_));
      mprintf("SPAM: Bulk solvent SPAM energy taken as %.3lf kcal/mol\n", bulk_);
    }
  }
  mprintf("#Citation: Cui, G.; Swails, J.M.; Manas, E.S.; \"SPAM: A Simple Approach\n"
          "#          for Profiling Bound Water Molecules\"\n"
          "#          J. Chem. Theory Comput., 2013, 9 (12), pp 5539–5549.\n");

  return Action::OK;
}
Exemple #4
0
/** Header is 256 4-byte words. Integer unless otherwise noted. First 56 words are:
  *    0-2: columns, rows, sections (fastest changing to slowest)
  *      3: mode: 0 = envelope stored as signed bytes (from -128 lowest to 127 highest)
  *               1 = Image     stored as Integer*2
  *               2 = Image     stored as Reals
  *               3 = Transform stored as Complex Integer*2
  *               4 = Transform stored as Complex Reals
  *               5 == 0
  *    4-6: Column, row, and section offsets
  *    7-9: Intervals along X, Y, Z
  *  10-15: float; 3x cell lengths (Ang) and 3x cell angles (deg)
  *  16-18: Map of which axes correspond to cols, rows, sections (1,2,3 = x,y,z)
  *  19-21: float; Min, max, and mean density
  *  22-24: Space group, bytes used for storing symm ops, flag for skew transform
  *         If skew flag != 0, skew transformation is from standard orthogonal
  *         coordinate frame (as used for atoms) to orthogonal map frame, as:
  *             Xo(map) = S * (Xo(atoms) - t)
  *  25-33: Skew matrix 'S' (in order S11, S12, S13, S21 etc)
  *  34-36: Skew translation 't'
  *  37-51: For future use and can be skipped.
  *     52: char; 'MAP '
  *     53: char; machine stamp for determining endianness
  *     54: float; RMS deviation of map from mean
  *     55: Number of labels
  */
int DataIO_CCP4::ReadData(FileName const& fname,
                          DataSetList& datasetlist, std::string const& dsname)
{
    CpptrajFile infile;
    if (infile.OpenRead( fname )) return 1;
    // Read first 56 words of the header into a buffer.
    headerbyte buffer;
    if (infile.Read(buffer.i, 224*sizeof(unsigned char)) < 1) {
        mprinterr("Error: Could not buffer CCP4 header.\n");
        return 1;
    }
    if (debug_ > 0)
        mprintf("DEBUG: MAP= '%c %c %c %c'  MACHST= '%x %x %x %x'\n",
                buffer.c[208], buffer.c[209], buffer.c[210], buffer.c[211],
                buffer.c[212], buffer.c[213], buffer.c[214], buffer.c[215]);
    // SANITY CHECK
    if (!MapCharsValid(buffer.c + 208)) {
        mprinterr("Error: CCP4 file missing 'MAP ' string at word 53\n");
        return 1;
    }
    // Check endianess
    bool isBigEndian = (buffer.c[212] == 0x11 && buffer.c[213] == 0x11 &&
                        buffer.c[214] == 0x00 && buffer.c[215] == 0x00);
    if (!isBigEndian) {
        if (debug_ > 0) mprintf("DEBUG: Little endian.\n");
        // SANITY CHECK
        if ( !(buffer.c[212] == 0x44 && buffer.c[213] == 0x41 &&
                buffer.c[214] == 0x00 && buffer.c[215] == 0x00) )
            mprintf("Warning: Invalid machine stamp: %x %x %x %x : assuming little endian.\n",
                    buffer.c[212], buffer.c[213], buffer.c[214], buffer.c[215]);
    } else {
        if (debug_ > 0) mprintf("DEBUG: Big endian.\n");
        // Perform endian swapping on header if necessary
        endian_swap(buffer.i, 56);
    }

    // Print DEBUG info
    if (debug_ > 0) {
        mprintf("DEBUG: Columns=%i  Rows=%i  Sections=%i\n", buffer.i[0], buffer.i[1], buffer.i[2]);
        mprintf("DEBUG: Mode=%i\n", buffer.i[3]);
        mprintf("DEBUG: Offsets: C=%i  R=%i  S=%i\n", buffer.i[4], buffer.i[5], buffer.i[6]);
        mprintf("DEBUG: NXYZ={ %i %i %i }\n", buffer.i[7], buffer.i[8], buffer.i[9]);
        mprintf("DEBUG: Box XYZ={ %f %f %f }  ABG={ %f %f %f }\n",
                buffer.f[10], buffer.f[11], buffer.f[12],
                buffer.f[13], buffer.f[14], buffer.f[15]);
        mprintf("DEBUG: Map: ColAxis=%i  RowAxis=%i  SecAxis=%i\n",
                buffer.i[16], buffer.i[17], buffer.i[18]);
        mprintf("DEBUG: SpaceGroup#=%i  SymmOpBytes=%i  SkewFlag=%i\n",
                buffer.i[22], buffer.i[23], buffer.i[24]);
        const int* MSKEW = buffer.i + 25;
        mprintf("DEBUG: Skew matrix: %i %i %i\n"
                "                    %i %i %i\n"
                "                    %i %i %i\n", MSKEW[0], MSKEW[1], MSKEW[2], MSKEW[3],
                MSKEW[4], MSKEW[5], MSKEW[6], MSKEW[7], MSKEW[8]);
        const int* TSKEW = buffer.i + 34;
        mprintf("DEBUG: Skew translation: %i %i %i\n", TSKEW[0], TSKEW[1], TSKEW[2]);
        mprintf("DEBUG: Nlabels=%i\n", buffer.i[55]);
    }
    // Check input data. Only support mode 2 for now.
    if (buffer.i[3] != 2) {
        mprinterr("Error: Mode %i; currently only mode 2 for CCP4 files is supported.\n", buffer.i[3]);
        return 1;
    }
    // Check offsets.
    if (buffer.i[4] != 0 || buffer.i[5] != 0 || buffer.i[6] != 0)
        mprintf("Warning: Non-zero offsets present. This is not yet supported and will be ignored.\n");
    // Check that mapping is col=x row=y section=z
    if (buffer.i[16] != 1 || buffer.i[17] != 2 || buffer.i[18] != 3) {
        mprinterr("Error: Currently only support cols=X, rows=Y, sections=Z\n");
        return 1;
    }
    if (buffer.i[24] != 0) {
        mprintf("Warning: Skew information present but not yet supported and will be ignored.\n");
        return 1;
    }

    // Read 10 80 character text labels
    char Labels[801];
    Labels[800] = '\0';
    infile.Read( Labels, 200*wSize );
    mprintf("\t%s\n", Labels);
    // Symmetry records: operators separated by * and grouped into 'lines' of 80 characters
    int NsymmRecords = buffer.i[23] / 80;
    if (NsymmRecords > 0) {
        char symBuffer[81];
        mprintf("\t%i symmetry records.\n", NsymmRecords);
        for (int ib = 0; ib != NsymmRecords; ib++) {
            infile.Gets( symBuffer, 80 );
            mprintf("\t%s\n", symBuffer);
        }
    }

    // Add grid data set. Default to float for now.
    DataSet* gridDS = datasetlist.AddSet( DataSet::GRID_FLT, dsname, "GRID" );
    if (gridDS == 0) return 1;
    DataSet_GridFlt& grid = static_cast<DataSet_GridFlt&>( *gridDS );
    // Allocate grid from dims and spacing. FIXME OK to assume zero origin?
    if (grid.Allocate_N_O_Box( buffer.i[7], buffer.i[8], buffer.i[9],
                               Vec3(0.0), Box(buffer.f + 10) ) != 0)
    {
        mprinterr("Error: Could not allocate grid.\n");
        return 1;
    }
    // FIXME: Grids are currently indexed so Z is fastest changing.
    //        Should be able to change indexing in grid DataSet.
    size_t mapSize = buffer.i[7] * buffer.i[8] * buffer.i[9];
    mprintf("\tCCP4 map has %zu elements\n", mapSize);
    mprintf("\tDensity: Min=%f  Max=%f  Mean=%f  RMS=%f\n",
            buffer.f[19], buffer.f[20], buffer.f[21], buffer.f[54]);
    std::vector<float> mapbuffer( mapSize );
    int mapBytes = mapSize * wSize;
    int numRead = infile.Read( &mapbuffer[0], mapBytes );
    if (numRead < 1) {
        mprinterr("Error: Could not read CCP4 map data.\n");
        return 1;
    } else if (numRead < mapBytes)
        mprintf("Warning: Expected %i bytes, read only %i bytes\n", mapBytes, numRead);
    if (isBigEndian) endian_swap(&mapbuffer[0], mapSize);

    // FIXME: Place data into grid DataSet with correct ordering.
    int gidx = 0;
    int NXY = buffer.i[7] * buffer.i[8];
    for (int ix = 0; ix != buffer.i[7]; ix++)
        for (int iy = 0; iy != buffer.i[8]; iy++)
            for (int iz = 0; iz != buffer.i[9]; iz++) {
                int midx = (iz * NXY) + (iy * buffer.i[7]) + ix;
                grid[gidx++] = mapbuffer[midx];
            }

    infile.CloseFile();
    return 0;
}
/** Load Karplus parameters from input file.
  * Expected format:
  * - {type}<+|-| ><a[4]><+|-| ><b[4]><+|-| ><c[4]><+|-| ><d[4]><A[6]><B[6]><C[6]>{<D[6]>}
  *   <reslabel[4]>* 
  * \return 0 on success, 1 on error
  */
int Action_Jcoupling::loadKarplus(std::string filename) {
  char buffer[512],residue[5];
  char *end, *ptr;
  int i;
  CpptrajFile KarplusFile;
  karplusConstant KC;
  karplusConstantList* currentResList=0;
  std::string CurrentRes;
  karplusConstantMap::iterator reslist;

  if (filename.empty()) {
    mprinterr("Error: jcoupling: Could not find Karplus parameter file.\n");
    return 1;
  }
  if (KarplusFile.OpenRead( filename )) {
    mprinterr("Error: jcoupling: Could not read Karplus parameter file %s\n",
              filename.c_str());
    mprinterr("Error: Ensure the file exists and is readable.\n");
    return 1;
  }
  // residue is only for reading in 4 chars for residue names
  residue[4]='\0'; 
  // Read through all lines of the file
  while (KarplusFile.Gets(buffer,512)==0) {
    // Skip newlines and comments
    if (buffer[0]=='\n' || buffer[0]=='#') continue;
    ptr=buffer;
    // First char is optional type. If optional type is C, then the Karplus 
    // function specified in Perez et al. JACS (2001) 123 will be used, and 
    // A, B, and C will be taken as C0, C1, and C2.
    if(ptr[0]=='C') {
      KC.type=1;
      ptr++;
    } else {
      KC.type=0;
    }
    // Read atom names with optional preceding character (+, -)
    for (i=0; i<4; i++) {
      if      (*ptr=='+') KC.offset[i]=1;
      else if (*ptr=='-') KC.offset[i]=-1;
      else                     KC.offset[i]=0;
      ++ptr;
      char *endchar = ptr + 4;
      char savechar = *endchar;
      *endchar = '\0';
      KC.atomName[i] = ptr;
      *endchar = savechar;
      ptr += 4;
      //mprintf("DEBUG:\tAtomName %i [%s]\n",i,KC.atomName[i]);
    }
    // Read parameters
    // NOTE: Using sscanf here instead of atof since the 4th parameter is
    //       optional, behavior is undefined for accessing uninitialized
    //       portion of buffer.
    i = sscanf(ptr, "%6lf%6lf%6lf%6lf",KC.C,KC.C+1,KC.C+2,KC.C+3);
    if (i<3) {
      mprintf("Error: jcoupling: Expected at least 3 Karplus parameters, got %i\n",i);
      mprintf("       Line: [%s]\n",buffer);
      return 1;
    } else if (i==3) KC.C[3]=0.0;
    KC.C[3]*=Constants::DEGRAD;
    // Place the read-in karplus constants in a map indexed by residue name 
    // so that all karplus constants for a given residue are in one place. 
    KarplusFile.Gets(buffer,512);
    // end will hold the end of the read-in buffer string
    end = buffer + strlen(buffer);
    for (ptr = buffer; ptr < end; ptr+=4) {
      if (*ptr=='\n') continue;
      residue[0] = ptr[0];
      residue[1] = ptr[1];
      residue[2] = ptr[2];
      residue[3] = ptr[3];
      CurrentRes.assign(residue);
      //mprintf("DEBUG:\t[%s]\n",CurrentRes.c_str());
      reslist = KarplusConstants_.find(CurrentRes);
      if (reslist == KarplusConstants_.end() ) {
        // List does not exist for residue yet, create it.
        currentResList = new karplusConstantList;
        KarplusConstants_.insert( reslist, 
                                  std::pair<std::string,karplusConstantList*>(
                                    CurrentRes,currentResList) );
      } else
        // Retrieve list for residue.
        currentResList = (*reslist).second;

      currentResList->push_back(KC);
      ++Nconstants_;
    } // END loop over residues in residue line 
  } // END Gets over input file
  KarplusFile.CloseFile();
  // DEBUG - Print out all parameters
  if (debug_>0) {
      mprintf("    KARPLUS PARAMETERS:\n");
      for (reslist=KarplusConstants_.begin(); reslist!=KarplusConstants_.end(); ++reslist) 
      {
        mprintf("\t[%4s]\n",(*reslist).first.c_str());
        for (karplusConstantList::iterator kc = currentResList->begin();
                                           kc != currentResList->end(); ++kc) 
        {
          mprintf("\t\t%1i",(*kc).type);
          mprintf(" %4s",*((*kc).atomName[0]));
          mprintf(" %4s",*((*kc).atomName[1]));
          mprintf(" %4s",*((*kc).atomName[2]));
          mprintf(" %4s",*((*kc).atomName[3]));
          mprintf(" %i %i %i %i",(*kc).offset[0],(*kc).offset[1],(*kc).offset[2],(*kc).offset[3]);
          mprintf(" %6.2lf %6.2lf %6.2lf %6.2lf\n",(*kc).C[0],(*kc).C[1],(*kc).C[2],(*kc).C[3]);
        }
      }
  }
  return 0;
}