// Action_MultiDihedral::DoAction()
Action::RetType Action_MultiDihedral::DoAction(int frameNum, Frame* currentFrame, 
                                               Frame** frameAddress)
{
  std::vector<DataSet*>::iterator ds = data_.begin();
  for (DihedralSearch::mask_it dih = dihSearch_.begin();
                               dih != dihSearch_.end(); ++dih, ++ds)
  {
    double torsion = Torsion( currentFrame->XYZ((*dih).A0()),
                              currentFrame->XYZ((*dih).A1()),
                              currentFrame->XYZ((*dih).A2()),
                              currentFrame->XYZ((*dih).A3()) );
    torsion *= RADDEG;
    (*ds)->Add(frameNum, &torsion);
  }
  return Action::OK;
}
// Action_ClusterDihedral::DoAction()
Action::RetType Action_ClusterDihedral::DoAction(int frameNum, Frame* currentFrame, Frame** frameAddress) {
  // For each dihedral, calculate which bin it should go into and store bin#
  int bidx = 0;
  for (std::vector<DCmask>::const_iterator dih = DCmasks_.begin(); 
                                           dih != DCmasks_.end(); ++dih)
  {
    double PHI = Torsion( currentFrame->XYZ(dih->A1()),
                          currentFrame->XYZ(dih->A2()),
                          currentFrame->XYZ(dih->A3()),
                          currentFrame->XYZ(dih->A4()) );
    // NOTE: Torsion is in radians; should bins be converted to rads as well?
    PHI *= Constants::RADDEG;
    //mprintf("[%6i]Dihedral=%8.3f", dih->A1(), PHI); // DEBUG
    PHI -= dih->Min();
    //mprintf(" Shifted=%8.3f", PHI); // DEBUG
    if (PHI < 0) PHI += 360;
    //mprintf(" Wrapped=%8.3f", PHI); // DEBUG
    PHI /= dih->Step();
    int phibin = (int)PHI;
    //mprintf(" Bin=%3i\n", phibin); // DEBUG
    Bins_[bidx++] = phibin;
  }
  // DEBUG - print bins
  //mprintf("[");
  //for (std::vector<int>::const_iterator bin = Bins_.begin(); bin != Bins_.end(); ++bin)
  //  mprintf("%3i,",*bin);
  //mprintf("]\n");
  // Now search for this bin combo in the DCarray
  std::vector<DCnode>::iterator DC = dcarray_.begin();
  for (; DC != dcarray_.end(); ++DC)
  {
    if ( DC->BinMatch( Bins_ ) ) break;
  }
  if (DC == dcarray_.end()) {
    // No match; create new bin combo and store frame num
    //mprintf("NEW DCNODE.\n");
    dcarray_.push_back( DCnode( Bins_, frameNum ) );
  } else {
    // Match; increment bin count and store frame num
    //mprintf("DCNODE ALREADY PRESENT.\n");
    DC->Increment();
    DC->AddFrame( frameNum );
  }
  // Store frame number
  lastframe_ = frameNum;
  return Action::OK;
}
// Action_MakeStructure::DoAction()
Action::RetType Action_MakeStructure::DoAction(int frameNum, Frame* currentFrame, 
                                               Frame** frameAddress) 
{
  Matrix_3x3 rotationMatrix;
  for (std::vector<SecStructHolder>::iterator ss = secstruct_.begin();
                                              ss != secstruct_.end(); ++ss)
  {
    std::vector<float>::iterator theta = ss->thetas_.begin();
    std::vector<AtomMask>::iterator Rmask = ss->Rmasks_.begin();
    for (DihedralSearch::mask_it dih = ss->dihSearch_.begin();
                                 dih != ss->dihSearch_.end(); ++dih, ++theta, ++Rmask)
    {
      double theta_in_radians = (double)*theta;
      // Calculate current value of dihedral
      double torsion = Torsion( currentFrame->XYZ( (*dih).A0() ),
                                currentFrame->XYZ( (*dih).A1() ),
                                currentFrame->XYZ( (*dih).A2() ),
                                currentFrame->XYZ( (*dih).A3() ) );
      // Calculate delta needed to get to theta
      double delta = theta_in_radians - torsion;
      // Set axis of rotation
      Vec3 axisOfRotation = currentFrame->SetAxisOfRotation((*dih).A1(), (*dih).A2());
      // Calculate rotation matrix for delta 
      rotationMatrix.CalcRotationMatrix(axisOfRotation, delta);
      if (debug_ > 0) {
        std::string a0name = CurrentParm_->TruncResAtomName( (*dih).A0() );
        std::string a1name = CurrentParm_->TruncResAtomName( (*dih).A1() );
        std::string a2name = CurrentParm_->TruncResAtomName( (*dih).A2() );
        std::string a3name = CurrentParm_->TruncResAtomName( (*dih).A3() );
          mprintf("\tRotating Dih %i:%s (%i-%i-%i-%i) (@%.2f) by %.2f deg to get to %.2f.\n",
                  (*dih).ResNum()+1, (*dih).Name().c_str(),
                  (*dih).A0() + 1, (*dih).A1() + 1, (*dih).A2() + 1, (*dih).A3() + 1, 
                  torsion*Constants::RADDEG, delta*Constants::RADDEG, theta_in_radians*Constants::RADDEG);
      }
      // Rotate around axis
      currentFrame->Rotate(rotationMatrix, *Rmask);
    }
  }
  return Action::OK;
}
/** For each dihedral defined in JcouplingInfo, perform the dihedral and
  * Jcoupling calculation.
  */
Action::RetType Action_Jcoupling::DoAction(int frameNum, ActionFrame& frm) {
  double Jval;

  if (outputfile_ != 0)
    outputfile_->Printf("#Frame %i\n",frameNum+1);

  for (std::vector<jcouplingInfo>::iterator jc = JcouplingInfo_.begin();
                                            jc !=JcouplingInfo_.end(); ++jc)
  {
    double phi = Torsion(frm.Frm().XYZ(jc->atom[0]),
                         frm.Frm().XYZ(jc->atom[1]),
                         frm.Frm().XYZ(jc->atom[2]),
                         frm.Frm().XYZ(jc->atom[3]) );
    if (jc->type==1) {
      //phitemp = phi + jc->C[3]; // Only necessary if offsets become used in perez-type calc
      Jval = jc->C[0] + (jc->C[1] * cos(phi)) + (jc->C[2] * cos(phi * 2.0)); 
    } else {
      double phitemp = cos( phi + jc->C[3] );
      Jval = (jc->C[0] * phitemp * phitemp) + (jc->C[1] * phitemp) + jc->C[2];
    }
    float fval = (float)Jval;
    jc->data_->Add(frameNum, &fval);

    int residue = jc->residue;
    // Output
    if (outputfile_ != 0)
      outputfile_->Printf("%5i %4s%4s%4s%4s%4s%12f%12f\n",
                         residue+1, CurrentParm_->Res(residue).c_str(),
                         (*CurrentParm_)[jc->atom[0]].c_str(), 
                         (*CurrentParm_)[jc->atom[1]].c_str(),
                         (*CurrentParm_)[jc->atom[2]].c_str(), 
                         (*CurrentParm_)[jc->atom[3]].c_str(),
                         phi*Constants::RADDEG, Jval);
  }

  return Action::OK;
} 
// Action_Dihedral::action()
Action::RetType Action_Dihedral::DoAction(int frameNum, Frame* currentFrame, Frame** frameAddress) {
  Vec3 a1, a2, a3, a4;

  if (useMass_) {
    a1 = currentFrame->VCenterOfMass( M1_ );
    a2 = currentFrame->VCenterOfMass( M2_ );
    a3 = currentFrame->VCenterOfMass( M3_ );
    a4 = currentFrame->VCenterOfMass( M4_ );
  } else {
    a1 = currentFrame->VGeometricCenter( M1_ );
    a2 = currentFrame->VGeometricCenter( M2_ );
    a3 = currentFrame->VGeometricCenter( M3_ );
    a4 = currentFrame->VGeometricCenter( M4_ );
  }
  double torsion = Torsion(a1.Dptr(), a2.Dptr(), a3.Dptr(), a4.Dptr());

  torsion *= RADDEG;

  dih_->Add(frameNum, &torsion);

  //fprintf(outfile,"%10i %10.4lf\n",frameNum,D);
  
  return Action::OK;
} 
// Exec_RotateDihedral::Execute()
Exec::RetType Exec_RotateDihedral::Execute(CpptrajState& State, ArgList& argIn) {
  // Get input COORDS set
  std::string setname = argIn.GetStringKey("crdset");
  if (setname.empty()) {
    mprinterr("Error: Specify COORDS dataset name with 'crdset'.\n");
    return CpptrajState::ERR;
  }
  DataSet_Coords* CRD = (DataSet_Coords*)State.DSL().FindCoordsSet( setname );
  if (CRD == 0) {
    mprinterr("Error: Could not find COORDS set '%s'\n", setname.c_str());
    return CpptrajState::ERR;
  }
  if (CRD->Size() < 1) {
    mprinterr("Error: COORDS set is empty.\n");
    return CpptrajState::ERR;
  }
  int frame = argIn.getKeyInt("frame", 0);
  if (frame < 0 || frame >= (int)CRD->Size()) {
    mprinterr("Error: Specified frame %i is out of range.\n", frame+1);
    return CpptrajState::ERR;
  }
  mprintf("    ROTATEDIHEDRAL: Using COORDS '%s', frame %i\n", CRD->legend(), frame+1);
  // Get target frame
  Frame FRM = CRD->AllocateFrame();
  CRD->GetFrame(frame, FRM);
  // Save as reference
  Frame refFrame = FRM;

  // Create output COORDS set if necessary
  DataSet_Coords* OUT = 0;
  int outframe = 0;
  std::string outname = argIn.GetStringKey("name");
  if (outname.empty()) {
    // This will not work for TRAJ data sets
    if (CRD->Type() == DataSet::TRAJ) {
      mprinterr("Error: Using TRAJ as input set requires use of 'name' keyword for output.\n");
      return CpptrajState::ERR;
    }
    OUT = CRD;
    outframe = frame;
  } else {
    // Create new output set with 1 empty frame.
    OUT = (DataSet_Coords*)State.DSL().AddSet( DataSet::COORDS, outname );
    if (OUT == 0) return CpptrajState::ERR;
    OUT->Allocate( DataSet::SizeArray(1, 1) );
    OUT->CoordsSetup( CRD->Top(), CRD->CoordsInfo() );
    OUT->AddFrame( CRD->AllocateFrame() );
    mprintf("\tOutput to set '%s'\n", OUT->legend());
  }

  // Determine whether we are setting or incrementing.
  enum ModeType { SET = 0, INCREMENT };
  ModeType mode = SET;
  if (argIn.Contains("value"))
    mode = SET;
  else if (argIn.Contains("increment"))
    mode = INCREMENT;
  else {
    mprinterr("Error: Specify 'value <value>' or 'increment <increment>'\n");
    return CpptrajState::ERR;
  }
  double value = argIn.getKeyDouble(ModeStr[mode], 0.0);
  switch (mode) {
    case SET: mprintf("\tDihedral will be set to %g degrees.\n", value); break;
    case INCREMENT: mprintf("\tDihedral will be incremented by %g degrees.\n", value); break;
  }
  // Convert to radians
  value *= Constants::DEGRAD;

  // Select dihedral atoms
  int A1, A2, A3, A4;
  if (argIn.Contains("type")) {
    // By type
    ArgList typeArg = argIn.GetStringKey("type");
    if (typeArg.empty()) {
      mprinterr("Error: No dihedral type specified after 'type'\n");
      return CpptrajState::ERR;
    }
    DihedralSearch dihSearch;
    dihSearch.SearchForArgs( typeArg );
    if (dihSearch.NoDihedralTokens()) {
      mprinterr("Error: Specified dihedral type not recognized.\n");
      return CpptrajState::ERR;
    }
    // Get residue
    int res = argIn.getKeyInt("res", -1);
    if (res <= 0) {
      mprinterr("Error: If 'type' specified 'res' must be specified and > 0.\n");
      return CpptrajState::ERR;
    }
    // Search for dihedrals. User residue #s start from 1.
    if (dihSearch.FindDihedrals(CRD->Top(), Range(res-1)))
      return CpptrajState::ERR;
    DihedralSearch::mask_it dih = dihSearch.begin();
    A1 = dih->A0();
    A2 = dih->A1();
    A3 = dih->A2();
    A4 = dih->A3();
  } else {
    // By masks
    AtomMask m1( argIn.GetMaskNext() );
    AtomMask m2( argIn.GetMaskNext() );
    AtomMask m3( argIn.GetMaskNext() );
    AtomMask m4( argIn.GetMaskNext() );
    if (CRD->Top().SetupIntegerMask( m1 )) return CpptrajState::ERR;
    if (CRD->Top().SetupIntegerMask( m2 )) return CpptrajState::ERR;
    if (CRD->Top().SetupIntegerMask( m3 )) return CpptrajState::ERR;
    if (CRD->Top().SetupIntegerMask( m4 )) return CpptrajState::ERR;
    if (m1.Nselected() != 1) return MaskError( m1 );
    if (m2.Nselected() != 1) return MaskError( m2 );
    if (m3.Nselected() != 1) return MaskError( m3 );
    if (m4.Nselected() != 1) return MaskError( m4 );
    A1 = m1[0];
    A2 = m2[0];
    A3 = m3[0];
    A4 = m4[0];
  }
  mprintf("\tRotating dihedral defined by atoms '%s'-'%s'-'%s'-'%s'\n",
          CRD->Top().AtomMaskName(A1).c_str(),
          CRD->Top().AtomMaskName(A2).c_str(),
          CRD->Top().AtomMaskName(A3).c_str(),
          CRD->Top().AtomMaskName(A4).c_str());
  // Set mask of atoms that will move during dihedral rotation
  AtomMask Rmask = DihedralSearch::MovingAtoms(CRD->Top(), A2, A3);
  // Calculate current value of dihedral
  double torsion = Torsion( FRM.XYZ(A1), FRM.XYZ(A2), FRM.XYZ(A3), FRM.XYZ(A4) );
  // Calculate delta needed to get to target value.
  double delta;
  switch (mode) {
    case SET:       delta = value - torsion; break;
    case INCREMENT: delta = value; break;
  }
  mprintf("\tOriginal torsion is %g, rotating by %g degrees.\n",
          torsion*Constants::RADDEG, delta*Constants::RADDEG);
  // Set axis of rotation
  Vec3 axisOfRotation = FRM.SetAxisOfRotation( A2, A3 );
  // Calculate rotation matrix for delta.
  Matrix_3x3 rotationMatrix;
  rotationMatrix.CalcRotationMatrix(axisOfRotation, delta);
  // Rotate around axis
  FRM.Rotate(rotationMatrix, Rmask);
  // RMS-fit the non-moving part of the coords back on original
  AtomMask refMask = Rmask;
  refMask.InvertMask();
  FRM.Align( refFrame, refMask );
  // Update coords
  OUT->SetCRD( outframe, FRM );

  return CpptrajState::OK;
}
Exemple #7
0
/** Given two atommaps and a map relating the two, find chiral centers for
  * which at least 3 of the atoms have been mapped. Assign the remaining
  * two atoms based on improper dihedrals.
  * \return the total number of mapped atoms.
  * NOTE: ONLY WORKS FOR SP3
  */
int Action_AtomMap::mapChiral(AtomMap& Ref, AtomMap& Tgt) {
    int uR[5], uT[5], nR[4], nT[4];
    double dR[4], dT[4];
    int numMappedAtoms=0;

    for (int ratom=0; ratom < Ref.Natom(); ratom++) {
        // Skip non-mapped atoms
        if (!Ref[ratom].IsMapped()) continue;
        //mprintf("DBG: mapChiral: Ref atom %i:%s\n",atom,Ref->P->names[atom]);
        int tatom = AMap_[ratom];
        // Check that map value is valid
        if (tatom<0) {
            mprintf("      Error: mapChiral: Ref atom %i:%s map value is invalid.\n",
                    ratom+1, Ref[ratom].c_str());
            return -1;
        }
        // If this Ref atom already completely mapped, skip
        if (Ref[ratom].Complete()) {
            // Sanity check - if Ref atom is completely mapped, target should be
            // unless # atoms in Tgt and Ref are different.
            if (!Tgt[tatom].Complete()) {
                mprintf("Warning: mapChiral: Ref atom %i:%s is complete but Tgt atom %i:%s is not.\n",
                        ratom+1, Ref[ratom].c_str(), tatom+1, Tgt[tatom].c_str());
                //return 1;
            }
            continue;
        }
        // Check if this is a chiral center
        if (!Ref[ratom].IsChiral()) continue;
        // If target atom is not a chiral center (e.g. due to diff # atoms)
        // mapping by chirality is not important for this reference, let
        // mapByIndex handle it.
        if (!Tgt[tatom].IsChiral()) {
            mprintf("Warning: mapChiral: Ref atom %i:%s is chiral but Tgt atom %i:%s is not!\n",
                    ratom+1, Ref[ratom].c_str(), tatom+1, Tgt[tatom].c_str());
            mprintf("         Marking Ref atom as non-chiral to try and map Tgt.\n");
            Ref[ratom].SetNotChiral();
            continue;
        }
        // Both atoms are chiral centers. Place bonded atoms (starting with
        // central atom) in R and T.
        uR[0] = ratom;
        uT[0] = tatom;
        int nunique = 1;
        int notunique_r = 0;
        // Look for mapped bonded ref and target atoms, and nonmapped reference atoms
        for (Atom::bond_iterator r = Ref[ratom].bondbegin(); r != Ref[ratom].bondend(); r++)
        {
            int t = AMap_[*r];
            if (!Ref[*r].IsMapped()) {
                // Bonded atom r is not mapped
                nR[notunique_r++] = *r;
            } else {
                // Bonded atom r is mapped. If a target was mapped to it
                // (i.e. it is the same atom) store it.
                if (t>=0) {
                    if (Ref[*r].IsMapped() && Tgt[t].IsMapped()) {
                        uR[nunique] = *r;
                        uT[nunique] = t;
                        ++nunique;
                    }
                }
            }
        }
        // Fill nT with nonmapped atoms from target
        int notunique_t = 0;
        for (Atom::bond_iterator tt = Tgt[tatom].bondbegin(); tt != Tgt[tatom].bondend(); tt++)
        {
            if (!Tgt[*tt].IsMapped())
                nT[notunique_t++] = *tt;
        }
        // notunique_r may not be the same as notunique_t if the # atoms is different
        if (notunique_r != notunique_t)
            mprintf("Warning: Ref and Tgt do not have the same # of nonmapped atoms.\n");
        if (debug_>0) {
            mprintf("  Potential Chiral center Ref=%i:%s Tgt=%i:%s  Mapped atoms=%i, non-Mapped=%i/%i\n",
                    ratom+1, Ref[ratom].c_str(), tatom+1, Tgt[tatom].c_str(),
                    nunique, notunique_r, notunique_t);
            for (int i=0; i<nunique; i++)
                mprintf("\t   Mapped\t%4i:%s %4i:%s\n",
                        uR[i]+1, Ref[uR[i]].c_str(), uT[i]+1, Tgt[uT[i]].c_str());
            for (int i=0; i<notunique_r; i++)
                mprintf("\tNotMappedRef\t%4i:%s\n", nR[i]+1, Ref[nR[i]].c_str());
            for (int i=0; i<notunique_t; i++)
                mprintf("\tNotMappedTgt\t         %4i:%4s\n", nT[i]+1, Tgt[nT[i]].c_str());
        }
        // If all atoms are unique no need to map
        // NOTE: Should be handled by complete check above.
        //if (nunique==5) continue;
        // Require at least 3 unique atoms for dihedral calc.
        if (nunique<3) {
            if (debug_>0)
                mprintf("    Warning: Center has < 3 mapped atoms, dihedral cannot be calcd.\n");
            continue;
        }
        // Calculate reference improper dihedrals
        for (int i=0; i<notunique_r; i++) {
            dR[i] = Torsion( RefFrame_->RefFrame().XYZ(uR[0]),
                             RefFrame_->RefFrame().XYZ(uR[1]),
                             RefFrame_->RefFrame().XYZ(uR[2]),
                             RefFrame_->RefFrame().XYZ(nR[i]) );
            if (debug_>1) mprintf("    Ref Improper %i [%3i,%3i,%3i,%3i]= %lf\n",i,
                                      uR[0]+1, uR[1]+1, uR[2]+1, nR[i]+1, dR[i]+1);
        }
        // Calculate target improper dihedrals
        for (int i=0; i<notunique_t; i++) {
            dT[i] = Torsion( TgtFrame_->RefFrame().XYZ(uT[0]),
                             TgtFrame_->RefFrame().XYZ(uT[1]),
                             TgtFrame_->RefFrame().XYZ(uT[2]),
                             TgtFrame_->RefFrame().XYZ(nT[i]) );
            if (debug_>1) mprintf("    Tgt Improper %i [%3i,%3i,%3i,%3i]= %lf\n",i,
                                      uR[0]+1, uR[1]+1, uR[2]+1, nT[i]+1, dT[i]+1);
        }
        // Match impropers to each other using a cutoff. Note that all torsions
        // are in radians.
        // NOTE: 10.0 degrees seems reasonable? Also there is currently no
        //       check for repeated deltas.
        for (int i=0; i<notunique_r; i++) {
            for (int j=0; j<notunique_t; j++) {
                double delta = dR[i] - dT[j];
                if (delta<0.0) delta=-delta;
                if (delta<0.17453292519943295769236907684886) {
                    if (debug_>0)
                        mprintf("    Mapping tgt atom %i:%s to ref atom %i:%s based on chirality.\n",
                                nT[j]+1, Tgt[nT[j]].c_str(), nR[i]+1, Ref[nR[i]].c_str() );
                    AMap_[ nR[i] ] = nT[j];
                    ++numMappedAtoms;
                    // Once an atom has been mapped set its mapped flag
                    Ref[nR[i]].SetMapped();
                    Tgt[nT[j]].SetMapped();
                } else if (notunique_r == 1 && notunique_t == 1) {
                    // This is the only non-mapped atom of the chiral center but for
                    // some reason the improper dihedral doesnt match. Map it but warn
                    // the user.
                    mprintf("Warning: Ref %i:%s and Tgt %i:%s are the only unmapped atoms of chiral\n"
                            "Warning: centers %i:%s | %i:%s, but the improper dihedral angles do not\n"
                            "Warning: match (%.4f rad != %.4f rad). This can indicate structural problems\n"
                            "Warning: in either the target or reference. Mapping atoms, but it is\n"
                            "Warning: recommended the structures be visually inspected for problems.\n",
                            nR[i]+1, Ref[nR[i]].c_str(), nT[j]+1, Tgt[nT[j]].c_str(),
                            ratom+1, Ref[ratom].c_str(), tatom+1, Tgt[tatom].c_str(),
                            dR[i], dT[j]);
                    AMap_[ nR[i] ] = nT[j];
                    ++numMappedAtoms;
                    Ref[nR[i]].SetMapped();
                    Tgt[nT[j]].SetMapped();
                }
            }
        }
        // Check if ref atom or tgt atom is now completely mapped
        Ref.MarkAtomComplete(ratom,false);
        Tgt.MarkAtomComplete(tatom,false);
    } // End loop over ratom

    return numMappedAtoms;
}
// Action_MakeStructure::Init()
Action::RetType Action_MakeStructure::Init(ArgList& actionArgs, TopologyList* PFL, DataSetList* DSL, DataFileList* DFL, int debugIn)
{
  debug_ = debugIn;
  secstruct_.clear();
  // Get all arguments 
  std::string ss_expr = actionArgs.GetStringNext();
  while ( !ss_expr.empty() ) {
    ArgList ss_arg(ss_expr, ":");
    if (ss_arg.Nargs() < 2) {
      mprinterr("Error: Malformed SS arg.\n");
      Help();
      return Action::ERR;
    }
    // Type is 1st arg, range is 2nd arg.
    SecStructHolder ss_holder(ss_arg[1], FindSStype(ss_arg[0]));

    if (ss_arg.Nargs() == 2) {
    // Find SS type: <ss type>:<range>
      if (ss_holder.sstype_idx == SS_EMPTY) {
        mprinterr("Error: SS type %s not found.\n", ss_arg[0].c_str());
        return Action::ERR;
      } 
      ss_holder.dihSearch_.SearchFor(MetaData::PHI);
      ss_holder.dihSearch_.SearchFor(MetaData::PSI);
      secstruct_.push_back( ss_holder );

    } else if (ss_arg[0] == "ref") {
    // Use dihedrals from reference structure
      if (ss_arg.Nargs() < 3) {
        mprinterr("Error: Invalid 'ref' arg. Requires 'ref:<range>:<refname>[:<ref range>]'\n");
        return Action::ERR;
      }
      ss_arg.MarkArg(0);
      ss_arg.MarkArg(1);
      // Sanity check: Currently only unique args of this type are allowed
      if (ss_holder.sstype_idx != SS_EMPTY) {
        mprinterr("Error: Ref backbone types must be unique [%s]\n", ss_arg[0].c_str());
        return Action::ERR;
      }
      // Use backbone phi/psi from reference structure
      ss_holder.dihSearch_.SearchFor(MetaData::PHI);
      ss_holder.dihSearch_.SearchFor(MetaData::PSI);
      // Get reference structure
      DataSet_Coords_REF* REF = (DataSet_Coords_REF*)
                                DSL->FindSetOfType(ss_arg.GetStringNext(),
                                                   DataSet::REF_FRAME); // ss_arg[2]
      if (REF == 0) {
        mprinterr("Error: Could not get reference structure [%s]\n", ss_arg[2].c_str());
        return Action::ERR;
      }
      // Get reference residue range, or use resRange
      Range refRange(ss_arg.GetStringNext(), -1); // ss_arg[3]
      if (!refRange.Empty()) {
        if (ss_holder.resRange.Size() != refRange.Size()) {
          mprinterr("Error: Reference range [%s] must match residue range [%s]\n",
                    refRange.RangeArg(), ss_holder.resRange.RangeArg());
          return Action::ERR;
        }
      } else
        refRange = ss_holder.resRange;
      // Look for phi/psi only in reference
      DihedralSearch refSearch;
      refSearch.SearchFor(MetaData::PHI);
      refSearch.SearchFor(MetaData::PSI);
      if (refSearch.FindDihedrals( REF->Top(), refRange )) return Action::ERR;
      // For each found dihedral, set theta 
      for (DihedralSearch::mask_it dih = refSearch.begin(); dih != refSearch.end(); ++dih)
      {
        double torsion = Torsion( REF->RefFrame().XYZ(dih->A0()),
                                  REF->RefFrame().XYZ(dih->A1()),
                                  REF->RefFrame().XYZ(dih->A2()),
                                  REF->RefFrame().XYZ(dih->A3()) );
        ss_holder.thetas_.push_back( (float)torsion );
      }
      secstruct_.push_back( ss_holder );

    } else if (ss_arg.Nargs() == 4 && isalpha(ss_arg[2][0])) {
    // Single dihedral type: <name>:<range>:<dih type>:<angle>
      DihedralSearch::DihedralType dtype = DihedralSearch::GetType(ss_arg[2]);
      if (ss_holder.sstype_idx == SS_EMPTY) {
        // Type not yet defined. Create new type. 
        if (dtype == MetaData::UNDEFINED) {
          mprinterr("Error: Dihedral type %s not found.\n", ss_arg[2].c_str());
          return Action::ERR;
        }
        if (!validDouble(ss_arg[3])) {
          mprinterr("Error: 4th arg (angle) is not a valid number.\n");
          return Action::ERR;
        }
        SS.push_back( SS_TYPE(convertToDouble(ss_arg[3]), 0.0, 0.0, 0.0, 2, ss_arg[0]) );
        ss_holder.sstype_idx = (int)(SS.size() - 1);
      }
      ss_holder.dihSearch_.SearchFor( dtype ); 
      secstruct_.push_back( ss_holder );

    } else if (ss_arg.Nargs() == 7 || ss_arg.Nargs() == 8) {
    // Single custom dihedral type: <name>:<range>:<at0>:<at1>:<at2>:<at3>:<angle>[:<offset>]
      if (ss_holder.sstype_idx == SS_EMPTY) {
        // Type not yet defined. Create new type.
        if (!validDouble(ss_arg[6])) {
          mprinterr("Error: 7th arg (angle) is not a valid number.\n");
          return Action::ERR;
        }
        SS.push_back( SS_TYPE(convertToDouble(ss_arg[6]), 0.0, 0.0, 0.0, 2, ss_arg[0]) );
        ss_holder.sstype_idx = (int)(SS.size() - 1);
      }
      int offset = 0;
      if (ss_arg.Nargs() == 8) {
        if (!validInteger(ss_arg[7])) {
          mprinterr("Error: 8th arg (offset) is not a valid number.\n");
          return Action::ERR;
        }
        offset = convertToInteger(ss_arg[7]);
      }
      ss_holder.dihSearch_.SearchForNewType(offset,ss_arg[2],ss_arg[3],ss_arg[4],ss_arg[5],
                                            ss_arg[0]);
      secstruct_.push_back( ss_holder );

    } else if (ss_arg.Nargs() == 4 || ss_arg.Nargs() == 6) {
    // Custom SS/turn type: <name>:<range>:<phi1>:<psi1>[:<phi2>:<psi2>]
      if (ss_holder.sstype_idx == SS_EMPTY) {
        // Type not yet defined. Create new type.
        if (!validDouble(ss_arg[2]) || !validDouble(ss_arg[3])) {
          mprinterr("Error: 3rd or 4th arg (phi1/psi1) is not a valid number.\n");
          return Action::ERR;
        }
        double phi1 = convertToDouble(ss_arg[2]);
        double psi1 = convertToDouble(ss_arg[3]);
        int isTurn = 0;
        double phi2 = 0.0;
        double psi2 = 0.0;
        if (ss_arg.Nargs() == 6) {
          isTurn = 1;
          if (!validDouble(ss_arg[4]) || !validDouble(ss_arg[5])) {
            mprinterr("Error: 5th or 6th arg (phi2/psi2) is not a valid number.\n");
            return Action::ERR;
          }
          phi2 = convertToDouble(ss_arg[4]);
          psi2 = convertToDouble(ss_arg[5]);
        }
        SS.push_back(SS_TYPE(phi1, psi1, phi2, psi2, isTurn, ss_arg[0] ));
        ss_holder.sstype_idx = (int)(SS.size() - 1);
      }
      ss_holder.dihSearch_.SearchFor(MetaData::PHI);
      ss_holder.dihSearch_.SearchFor(MetaData::PSI);
      secstruct_.push_back( ss_holder );

    } else {
      mprinterr("Error: SS arg type [%s] not recognized.\n", ss_arg[0].c_str());
      return Action::ERR;
    }
    ss_expr = actionArgs.GetStringNext();
  } // End loop over args
  if (secstruct_.empty()) {
    mprinterr("Error: No SS types defined.\n");
    return Action::ERR;
  }
  mprintf("    MAKESTRUCTURE:\n");
  for (std::vector<SecStructHolder>::iterator ss = secstruct_.begin();
                                              ss != secstruct_.end(); ++ss)
  {
    if (ss->sstype_idx != SS_EMPTY) {
      const SS_TYPE& myType = SS[ss->sstype_idx];
      switch ( myType.isTurn ) {
        case 0:
          mprintf("\tSS type %s will be applied to residue(s) %s\n",
                 myType.type_arg.c_str(), ss->resRange.RangeArg());
          break;
        case 1:
          mprintf("\tTurn type %s will be applied to residue(s) %s\n",
                  myType.type_arg.c_str(), ss->resRange.RangeArg());
          break;
        case 2:
          mprintf("\tDihedral value of %.2f will be applied to %s dihedrals in residue(s) %s\n",
                  myType.phi, myType.type_arg.c_str(), ss->resRange.RangeArg());
      }
    } else 
      mprintf("\tBackbone angles from reference will be applied to residue(s) %s\n",
              ss->resRange.RangeArg());
  }
  return Action::OK;
}