Skip to content
Snippets Groups Projects
pdb_writer.cc 10.91 KiB
//------------------------------------------------------------------------------
// This file is part of the OpenStructure project <www.openstructure.org>
//
// Copyright (C) 2008-2010 by the OpenStructure authors
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License as published by the Free
// Software Foundation; either version 3.0 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 Lesser General Public License for more
// details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with this library; if not, write to the Free Software Foundation, Inc.,
// 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
//------------------------------------------------------------------------------
/*
  Author: Marco Biasini
 */
#include <locale>
#include <boost/format.hpp>
#include <string.h>

#include <ost/io/io_exception.hh>

#include "pdb_writer.hh"

using boost::format;

namespace ost { namespace io {


namespace {

// determine whether the element name has to be shifted to to the left by one
// column.
bool shift_left(const String& atom_name, bool is_hetatm, 
                const String& element, float mass) 
{
  if (atom_name.length()==4) {
    return true;
  }
  if (is_hetatm==false) {
    return false;
  }

  if (isdigit(atom_name[0]) || atom_name=="UNK") {
    return true;
  }
  if (mass>34) {
    if (element=="W" || element=="V") {
      return false;
    }    
    return true;
  }
  return (element=="K" || element=="CA" || element=="NA" || 
          element=="MG" || element=="LI");
}

void write_atom(std::ostream& ostr, FormattedLine& line, 
                const mol::AtomHandle& atom, int atomnum, 
                bool is_pqr, bool charmm_style)
{
  mol::ResidueHandle res=atom.GetResidue();
  char ins_code=res.GetNumber().GetInsCode();
  StringRef record_name(atom.IsHetAtom() ? "HETATM" : "ATOM  ", 6);
  std::vector<String> names=atom.GetAltGroupNames();
  
  geom::Vec3 p=atom.GetPos();
  line( 0, 6)=record_name;
  line( 6, 5)=fmt::LPaddedInt(atomnum);
  String atom_name=atom.GetName();
  if (atom_name.size()>4) {
    throw IOException("Atom name '"+atom.GetQualifiedName()+
                      "' is too long for PDB output. At most 4 character "
                      "are allowed");
  }
  if (shift_left(atom_name, atom.IsHetAtom(), atom.GetElement(), 
                 atom.GetMass())) {
    line(12, 4)=fmt::RPadded(atom_name);
  } else {
    line(13, 3)=fmt::RPadded(atom_name);
  }
  if (charmm_style) {
    if (res.GetKey().size()>4) {
      throw IOException("Residue name '"+res.GetName()+
                        "' is too long for CHARMM-PDB output. At most 4 "
                        "characters are allowed");
    }
    line(17, 4)=fmt::LPadded(res.GetKey());
  } else {
    if (res.GetKey().size()>3) {
      throw IOException("Residue name '"+res.GetName()+
                        "' is too long for PDB output. At most 3 characters "
                        "are allowed");
    }
    line(17, 3)=fmt::LPadded(res.GetKey());    
  }

  
  String chain_name=res.GetChain().GetName();
  if (!charmm_style) {
    if (chain_name.size()>0) {
      if (chain_name.size()==1) {
        line[21]=chain_name[0];
      } else {
        throw IOException("PDB format only support single-letter chain names");
      }
    }    
  }
  line(22, 4)=fmt::LPaddedInt(res.GetNumber().GetNum());
  if (ins_code!=0) {
    line[26]=ins_code;
  }
  
  // deal with alternative atom locations
  if (names.empty()) {
    line(30, 8)=fmt::LPaddedFloat(p[0],  3);
    line(38, 8)=fmt::LPaddedFloat(p[1],  3);
    line(46, 8)=fmt::LPaddedFloat(p[2],  3);
    
    if (is_pqr) {
      line(54, 6)=fmt::LPaddedFloat(atom.GetCharge(), 2);
      line(60, 6)=fmt::LPaddedFloat(atom.GetRadius(), 2);
    } else {
      line(54, 6)=fmt::LPaddedFloat(atom.GetOccupancy(), 2);
      Real bfac=atom.GetBFactor();
      if (bfac>999.99) {
        line(60, 6)=fmt::LPaddedFloat(999.99, 2);
      } else {
        line(60, 6)=fmt::LPaddedFloat(bfac, 2);
      }
    }
    if (charmm_style) {
      line(72, 4)=fmt::RPadded(chain_name);
    }
    line(76, 2)=fmt::LPadded(atom.GetElement());
    ostr << line;
  } else {
    for (std::vector<String>::const_iterator
         i=names.begin(), e=names.end(); i!=e; ++i) {
      p=atom.GetAltPos(*i);
      line(30, 50).Clear();

      if (i->size()>1) {
        throw IOException("Alternative atom indicator '"+atom.GetQualifiedName()+
                          "("+*i+")' too long for PDB output. At most 1 "
                          "character is allowed");
      }
      line[16]=(*i)[0];
      line(30, 8)=fmt::LPaddedFloat(p[0],  3);
      line(38, 8)=fmt::LPaddedFloat(p[1],  3);
      line(46, 8)=fmt::LPaddedFloat(p[2],  3);

      if (is_pqr) {
       line(54, 6)=fmt::LPaddedFloat(atom.GetCharge(), 2);
       line(60, 6)=fmt::LPaddedFloat(atom.GetRadius(), 2);
      } else {
       line(54, 6)=fmt::LPaddedFloat(atom.GetOccupancy(), 2);
       Real bfac=atom.GetBFactor();
       if (bfac>999.99) {
         line(60, 6)=fmt::LPaddedFloat(999.99, 2);
       } else {
         line(60, 6)=fmt::LPaddedFloat(bfac, 2);
       }
      }
      if (charmm_style) {
        line(72, 4)=fmt::RPadded(chain_name);
      }
      line(76, 2)=fmt::LPadded(atom.GetElement());
      ostr << line;
    }
  }

  line.Clear();
}

void write_conect(std::ostream& ostr, int atom_index,
                  std::list<int>& partner_indices)
{
  int counter=0;
  partner_indices.sort();
  for (std::list<int>::const_iterator i=partner_indices.begin();
       i!=partner_indices.end(); ++i, counter++) {
    if (counter>3) {
      ostr << std::endl;
      counter=0;
    }
    if (counter==0) ostr << "CONECT" << format("%5i") % atom_index;
    ostr << format("%5i") % (*i);
  }
  ostr << std::endl;
}

class PDBWriterImpl : public mol::EntityVisitor {
public:
  PDBWriterImpl(std::ostream& ostream, FormattedLine& line, 
                std::map<long,int>& atom_indices, bool charmm_style)
    : ostr_(ostream), counter_(0), is_pqr_(false),
      atom_indices_(atom_indices), line_(line), peptide_(false),
      charmm_style_(charmm_style) {
  }
private:
public:
  virtual bool VisitAtom(const mol::AtomHandle& atom) {
    counter_++;
    write_atom(ostr_, line_, atom, counter_, is_pqr_, charmm_style_);
    if (atom.IsHetAtom()) {
      atom_indices_[atom.GetHashCode()]=counter_;
    }
    return true;
  }
  
  virtual bool VisitResidue(const mol::ResidueHandle& res)
  {
    if (res.IsPeptideLinking()) {
      peptide_=true;
    } else {
      if (peptide_) {
        this->WriteTer(prev_);
      }
      peptide_=false;
    }
    prev_=res;
    return true;
  }
  
  virtual bool VisitChain(const mol::ChainHandle& chain)
  {
    if (peptide_) {
      this->WriteTer(prev_);
    }
    return true;
  }
  virtual void OnExit()
  {
    if (peptide_) {
      this->WriteTer(prev_);
    }
  }
  void WriteTer(mol::ResidueHandle res)
  {
    counter_++;
    line_(0, 6)=StringRef("TER   ", 6);
    line_( 6, 5)=fmt::LPaddedInt(counter_);
    line_(17, 3)=fmt::LPadded(res.GetKey());
    line_[21]=res.GetChain().GetName()[0];
    line_(22, 4)=fmt::LPaddedInt(res.GetNumber().GetNum());
    char ins_code=res.GetNumber().GetInsCode();
    if (ins_code!=0) {
      line_[26]=ins_code;
    }    
    ostr_ << line_;
    line_.Clear();
  }
  
  void SetIsPQR(bool t) {
    is_pqr_=t;
  }
private:
  std::ostream&       ostr_;
  int                 counter_;
  bool                is_pqr_;
  std::map<long,int>& atom_indices_;
  FormattedLine&      line_;
  mol::ResidueHandle  prev_;
  bool                peptide_;
  bool                charmm_style_;
};

class PDBConectWriterImpl : public mol::EntityVisitor {
public:
  PDBConectWriterImpl(std::ostream& ostream, std::map<long,int>& atom_indices)
    : ostr_(ostream), atom_indices_(atom_indices) {
  }
private:
public:
  virtual bool VisitAtom(const mol::AtomHandle& atom) {
    if (atom.IsHetAtom()) {
      bool has_partner=false;
      int atom_index=atom_indices_[atom.GetHashCode()];
      mol::AtomHandleList partners=atom.GetBondPartners();
      std::list<int> partner_indices;
      for (mol::AtomHandleList::const_iterator i=partners.begin();
           i!=partners.end(); ++i) {
        int pind=atom_indices_[i->GetHashCode()];
        if (pind!=0) {
          partner_indices.push_back(pind);
          has_partner=true;
        }
      }
      if (has_partner) {
        write_conect(ostr_, atom_index, partner_indices);
      }
    }
    return true;
  }
private:
  std::ostream&       ostr_;
  std::map<long,int>& atom_indices_;
};

struct ForcePOSIX {
  std::locale old_locale;
  ForcePOSIX(){
    old_locale=std::locale();
    setlocale(LC_NUMERIC, "POSIX");
  }
  ~ForcePOSIX(){
    setlocale(LC_NUMERIC, old_locale.name().c_str());
  }
};

}

PDBWriter::PDBWriter(std::ostream& stream, bool charmm_style):
  outfile_(), outstream_(stream), mol_count_(0), line_(80), 
  charmm_style_(charmm_style)
{
  
}

PDBWriter::PDBWriter(const boost::filesystem::path& filename, bool charmm_style):
  outfile_(filename.file_string().c_str()), outstream_(outfile_), 
  mol_count_(0), line_(80), charmm_style_(charmm_style)
{}

PDBWriter::PDBWriter(const String& filename, bool charmm_style):
  outfile_(filename.c_str()), outstream_(outfile_), mol_count_(0), line_(80),
  charmm_style_(charmm_style)
{}

void PDBWriter::WriteModelLeader()
{
  ++mol_count_;
  if (PDB::Flags() & PDB::WRITE_MULTIPLE_MODELS) {
    outstream_ << "MODEL     " << mol_count_ << std::endl;
  } else if (mol_count_>1) {
    throw IOException("Please enable the PDB::WRITE_MULTIPLE_MODELS flag to "
                      "write multiple models");
  }
}

void PDBWriter::WriteModelTrailer()
{
  if (PDB::Flags() & PDB::WRITE_MULTIPLE_MODELS) {
    outstream_ << "ENDMDL" << std::endl;
  }
}

template <typename H>
void PDBWriter::WriteModel(H ent)
{
  ForcePOSIX posix = ForcePOSIX();
  this->WriteModelLeader();
  PDBWriterImpl writer(outstream_,line_, atom_indices_, charmm_style_);
  if (PDB::Flags() & PDB::PQR_FORMAT) {
    writer.SetIsPQR(true);
  }
  ent.Apply(writer);
  PDBConectWriterImpl con_writer(outstream_,atom_indices_);
  ent.Apply(con_writer);
  this->WriteModelTrailer();
}

void PDBWriter::Write(const mol::EntityView& ent)
{
  this->WriteModel(ent);
}

void PDBWriter::Write(const mol::EntityHandle& ent)
{
  this->WriteModel(ent);
}

void PDBWriter::Write(const mol::AtomHandleList& atoms)
{
  ForcePOSIX posix = ForcePOSIX();
  this->WriteModelLeader();
  int counter=1;
  mol::ChainHandle last_chain;
  for (mol::AtomHandleList::const_iterator i=atoms.begin(),
       e=atoms.end(); i!=e; ++i, ++counter) {
    write_atom(outstream_, line_, *i, counter, 
               (PDB::Flags() & PDB::PQR_FORMAT) != 0, charmm_style_);
  }
  this->WriteModelTrailer();
}

  
PDBWriter::~PDBWriter()
{
  outstream_ << "END   ";
}

}}