From 659a5557b3e11c1234618e224f61cf9fd3bd2a5c Mon Sep 17 00:00:00 2001 From: marco <marco@5a81b35b-ba03-0410-adc8-b2c5c5119f08> Date: Mon, 10 May 2010 05:38:57 +0000 Subject: [PATCH] fix several problems in PDBWriter Residues with names shorter than 3 letters are left-padded, we throw an error if we encounter chain, residue and atom names that are too long (longer than 1, 3 and 4 characters respectively) and we try to put the atom name into the correct column. Oh, and we insert TER records at the correct positions. All in all the code is much cleaner and faster as it does not longer rely on boost.format (yes, io performance is sometimes critical). git-svn-id: https://dng.biozentrum.unibas.ch/svn/openstructure/trunk@2215 5a81b35b-ba03-0410-adc8-b2c5c5119f08 --- .gitignore | 1 + modules/io/src/CMakeLists.txt | 1 + modules/io/src/formatted_line.hh | 218 ++++++++++++++++++++ modules/io/src/mol/pdb_writer.cc | 227 ++++++++++++++------- modules/io/src/mol/pdb_writer.hh | 5 +- modules/io/tests/test_io_pdb.cc | 157 ++++++++++++++ modules/io/tests/testfiles/pdb/alt-loc.pdb | 3 + modules/io/tests/testfiles/pdb/ter.pdb | 15 ++ 8 files changed, 553 insertions(+), 74 deletions(-) create mode 100644 modules/io/src/formatted_line.hh create mode 100644 modules/io/tests/testfiles/pdb/alt-loc.pdb create mode 100644 modules/io/tests/testfiles/pdb/ter.pdb diff --git a/.gitignore b/.gitignore index 71a47aca6..be2ace828 100644 --- a/.gitignore +++ b/.gitignore @@ -19,5 +19,6 @@ html scratch pov_*test.pov pov_*test.inc +*-out.pdb CMakeLists.txt.user OpenStructure.cbp diff --git a/modules/io/src/CMakeLists.txt b/modules/io/src/CMakeLists.txt index e77cfda20..f61c471d3 100644 --- a/modules/io/src/CMakeLists.txt +++ b/modules/io/src/CMakeLists.txt @@ -13,6 +13,7 @@ io_utils.hh io_exception.hh convert.hh converting_streams.hh +formatted_line.hh ) set(OST_IO_SOURCES diff --git a/modules/io/src/formatted_line.hh b/modules/io/src/formatted_line.hh new file mode 100644 index 000000000..f42bcac73 --- /dev/null +++ b/modules/io/src/formatted_line.hh @@ -0,0 +1,218 @@ +#ifndef OST_IO_FORMATTED_LINE_HH +#define OST_IO_FORMATTED_LINE_HH + +#include <ost/string_ref.hh> + +/* + Author: Marco Biasini + */ + +namespace ost { namespace io { + +namespace fmt { + +struct LPadded : public StringRef { + LPadded(const char* s, size_t l): + StringRef(s, l) + { } + + explicit LPadded(const String& s): StringRef(s.data(), s.size()) + { } + + explicit LPadded(const char* s): StringRef(s, strlen(s)) + { } +}; + +struct RPadded : public StringRef { + RPadded(const char* s, size_t l): + StringRef(s, l) + { } + + explicit RPadded(const String& s): StringRef(s.data(), s.size()) + { } + + explicit RPadded(const char* s): StringRef(s, strlen(s)) + { } +}; + + +struct LPaddedInt { + LPaddedInt(int val) { + size_t curr=0; + bool minus=val<0; + val=std::abs(val); + if (minus) { + data[curr]='-'; + ++curr; + } + do { + int m=val%10; + data[curr]='0'+m; + ++curr; + val/=10; + } while(val); + // swap + for (size_t i=0, e=(curr-int(minus))/2; i<e; ++i) { + std::swap(data[int(minus)+i], data[curr-i-1]); + } + data[curr]='\0'; + len=curr; + } + char data[20]; + size_t len; +}; + +// BIG FAT WARNING: Before using this class to output floats with lots of +// digits, make sure you indeed get the result you want. The function has +// not been tested enough for numerical stability. +struct LPaddedFloat { + LPaddedFloat(Real val, int prec) + { + double rounded_val=round(val*pow(10, prec))*pow(0.1, prec); + size_t curr=0; + bool minus=rounded_val<0; + rounded_val=std::abs(rounded_val); + int int_val=int(rounded_val); + if (minus) { + data[curr]='-'; + ++curr; + } + do { + int m=int_val%10; + data[curr]='0'+m; + ++curr; + int_val/=10; + } while(int_val); + // swap + for (size_t i=0, e=(curr-int(minus))/2; i<e; ++i) { + std::swap(data[int(minus)+i], data[curr-i-1]); + } + data[curr]='\0'; + len=curr; + if (prec==0) { + return; + } + data[curr]='.'; + curr++; + rounded_val-=int(rounded_val); + while(prec>0) { + rounded_val*=10; + int m=int(rounded_val); + rounded_val-=int(rounded_val); + data[curr]='0'+m; + curr++; + --prec; + } + data[curr]='\0'; + len=curr; + } + char data[20]; + size_t len; +}; + + + +} + +class LineSlice { +public: + LineSlice(char* data, size_t l): data_(data), len_(l) + { + } + + LineSlice& operator=(const StringRef& str) + { + assert(str.length()==len_); + memcpy(data_, str.data(), str.size()); + return *this; + } + + LineSlice& operator=(const fmt::LPadded& str) + { + assert(str.size()<=len_); + memcpy(data_+len_-str.size(), str.data(), str.size()); + return *this; + } + + LineSlice& operator=(const fmt::RPadded& str) + { + assert(str.size()==len_); + memcpy(data_, str.data(), str.size()); + return *this; + } + + LineSlice& operator=(const fmt::LPaddedInt& i) + { + assert(i.len<=len_); + memcpy(data_+len_-i.len, i.data, i.len); + return *this; + } + + LineSlice& operator=(const fmt::LPaddedFloat& f) + { + assert(f.len<=len_); + memcpy(data_+len_-f.len, f.data, f.len); + return *this; + } + void Clear() + { + memset(data_, ' ', len_); + } +private: + char* data_; + size_t len_; +}; + +class FormattedLine { +public: + FormattedLine(size_t width): + data_(new char[width]), len_(width) + { + this->Clear(); + } + + void Clear() + { + memset(data_, ' ', len_); + } + ~FormattedLine() { delete[] data_; } + + LineSlice operator()(int start, int len) + { + assert(start>=0 && start+len<len_); + return LineSlice(data_+start, len); + } + + const char* Data() const { return data_; } + + size_t GetWidth() const { return len_; } + + char operator[](size_t index) const + { + assert(index<len_); + return data_[index]; + } + + char& operator[](size_t index) + { + assert(index<_len_); + return data_[index]; + } +private: + FormattedLine& operator=(const FormattedLine& rhs); + FormattedLine(const FormattedLine& rhs); + char* data_; + size_t len_; +}; + +inline std::ostream& operator<<(std::ostream& stream, const FormattedLine& line) +{ + stream.write(line.Data(), line.GetWidth()); + stream << std::endl; + return stream; +} + + +}} + +#endif diff --git a/modules/io/src/mol/pdb_writer.cc b/modules/io/src/mol/pdb_writer.cc index 775bec870..f46f70d04 100644 --- a/modules/io/src/mol/pdb_writer.cc +++ b/modules/io/src/mol/pdb_writer.cc @@ -22,6 +22,7 @@ #include <boost/format.hpp> #include <string.h> #include <ost/io/io_exception.hh> + #include "pdb_writer.hh" using boost::format; @@ -31,63 +32,119 @@ namespace ost { namespace io { namespace { -void write_atom(std::ostream& ostr, const mol::AtomHandle& atom, int atomnum, +// 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 (is_hetatm==false) { + return false; + } + + if (isnumber(atom_name[0]) || atom_name=="UNK" || + atom_name.length()==4) { + 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) { mol::ResidueHandle res=atom.GetResidue(); char ins_code=res.GetNumber().GetInsCode(); - String record_name=atom.GetAtomProps().is_hetatm ? "HETATM" : "ATOM "; - String aname_str=atom.GetName(); - if(aname_str.length()<4) { - aname_str=" "+aname_str; - } + 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 (res.GetKey().size()>3) { + throw IOException("Residue name '"+res.GetQualifiedName()+ + "' 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 (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()) { - geom::Vec3 p=atom.GetPos(); - String ins_str=(ins_code==0 ? " " : String(1, ins_code)); - ostr << record_name << format("%5d") % atomnum - << format(" %-4s") % aname_str - << " " - << format("%.3s") % res.GetKey() - << format(" %.1s") % res.GetChain().GetName() - << format("%4d%s") % res.GetNumber().GetNum() % ins_str - << " " - << format("%8.3f%8.3f%8.3f") % p[0] % p[1] % p[2]; - if(is_pqr) { - ostr << format("%6.2f") % atom.GetAtomProps().charge - << format("%6.2f") % atom.GetAtomProps().radius; + 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 { - ostr << format("%6.2f") % atom.GetAtomProps().occupancy - << format("%6.2f") % atom.GetAtomProps().b_factor; + line(54, 6)=fmt::LPaddedFloat(atom.GetOccupancy(), 2); + line(60, 6)=fmt::LPaddedFloat(atom.GetBFactor(), 2); } - ostr << format("%10s%2s") % "" % atom.GetAtomProps().element - << std::endl - ; + + 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) { - geom::Vec3 p=atom.GetAltPos(*i); - String ins_str=(ins_code==0 ? " " : String(1, ins_code)); - ostr << record_name << format("%5d") % atomnum - << format(" %-4s") % aname_str - << *i - << format("%.3s") % res.GetKey() - << format(" %.1s") % res.GetChain().GetName() - << format("%4d%s") % res.GetNumber().GetNum() % ins_str - << " " - << format("%8.3f%8.3f%8.3f") % p[0] % p[1] % p[2]; - if(is_pqr) { - ostr << format("%6.2f") % atom.GetAtomProps().charge - << format("%6.2f") % atom.GetAtomProps().radius; - } else { - ostr << format("%6.2f") % atom.GetAtomProps().occupancy - << format("%6.2f") % atom.GetAtomProps().b_factor; - } - ostr << format("%10s%2s") % "" % atom.GetAtomProps().element - << std::endl - ; + 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); + line(60, 6)=fmt::LPaddedFloat(atom.GetBFactor(), 2); + } + + line(76, 2)=fmt::LPadded(atom.GetElement()); + ostr << line; } } + + line.Clear(); } void write_conect(std::ostream& ostr, int atom_index, @@ -109,35 +166,63 @@ void write_conect(std::ostream& ostr, int atom_index, class PDBWriterImpl : public mol::EntityVisitor { public: - PDBWriterImpl(std::ostream& ostream, std::map<long,int>& atom_indices) - : ostr_(ostream), counter_(0), is_pqr_(false), last_chain_(), - atom_indices_(atom_indices) { + PDBWriterImpl(std::ostream& ostream, FormattedLine& line, + std::map<long,int>& atom_indices) + : ostr_(ostream), counter_(0), is_pqr_(false), + atom_indices_(atom_indices), line_(line), peptide_(false) { } private: public: virtual bool VisitAtom(const mol::AtomHandle& atom) { counter_++; - if (last_chain_!=atom.GetResidue().GetChain()) { - if (last_chain_.IsValid()) { - ostr_ << "TER" << std::endl; - } - last_chain_=atom.GetResidue().GetChain(); - } - write_atom(ostr_, atom, counter_, is_pqr_); + write_atom(ostr_, line_, atom, counter_, is_pqr_); if (atom.GetAtomProps().is_hetatm) { 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; + } + + 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_; - mol::ChainHandle last_chain_; + std::ostream& ostr_; + int counter_; + bool is_pqr_; std::map<long,int>& atom_indices_; + FormattedLine& line_; + mol::ResidueHandle prev_; + bool peptide_; }; class PDBConectWriterImpl : public mol::EntityVisitor { @@ -175,15 +260,18 @@ private: } PDBWriter::PDBWriter(std::ostream& stream): - outfile_(), outstream_(stream) -{} + outfile_(), outstream_(stream), mol_count_(0), line_(80) +{ + +} PDBWriter::PDBWriter(const boost::filesystem::path& filename): - outfile_(filename.file_string().c_str()), outstream_(outfile_), mol_count_(0) + outfile_(filename.file_string().c_str()), outstream_(outfile_), + mol_count_(0), line_(80) {} PDBWriter::PDBWriter(const String& filename): - outfile_(filename.c_str()), outstream_(outfile_), mol_count_(0) + outfile_(filename.c_str()), outstream_(outfile_), mol_count_(0), line_(80) {} void PDBWriter::WriteModelLeader() @@ -208,7 +296,7 @@ template <typename H> void PDBWriter::WriteModel(H ent) { this->WriteModelLeader(); - PDBWriterImpl writer(outstream_,atom_indices_); + PDBWriterImpl writer(outstream_,line_, atom_indices_); if (PDB::Flags() & PDB::PQR_FORMAT) { writer.SetIsPQR(true); } @@ -235,14 +323,7 @@ void PDBWriter::Write(const mol::AtomHandleList& atoms) mol::ChainHandle last_chain; for (mol::AtomHandleList::const_iterator i=atoms.begin(), e=atoms.end(); i!=e; ++i, ++counter) { - - if (last_chain!=(*i).GetResidue().GetChain()) { - if (last_chain.IsValid()) { - outstream_ << "TER" << std::endl; - } - last_chain=(*i).GetResidue().GetChain(); - } - write_atom(outstream_, *i, counter, PDB::Flags() & PDB::PQR_FORMAT); + write_atom(outstream_, line_, *i, counter, PDB::Flags() & PDB::PQR_FORMAT); } this->WriteModelTrailer(); } diff --git a/modules/io/src/mol/pdb_writer.hh b/modules/io/src/mol/pdb_writer.hh index 68a9332a9..40ed1cbff 100644 --- a/modules/io/src/mol/pdb_writer.hh +++ b/modules/io/src/mol/pdb_writer.hh @@ -28,9 +28,11 @@ #include <boost/filesystem/fstream.hpp> #include <boost/iostreams/filtering_stream.hpp> +#include <ost/mol/mol.hh> + #include <ost/io/module_config.hh> +#include <ost/io/formatted_line.hh> -#include <ost/mol/mol.hh> #include "pdb_io.hh" @@ -58,6 +60,7 @@ private: std::ostream& outstream_; int mol_count_; std::map<long, int> atom_indices_; + FormattedLine line_; }; }} diff --git a/modules/io/tests/test_io_pdb.cc b/modules/io/tests/test_io_pdb.cc index 811c459a8..053705f01 100644 --- a/modules/io/tests/test_io_pdb.cc +++ b/modules/io/tests/test_io_pdb.cc @@ -17,8 +17,10 @@ // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA //------------------------------------------------------------------------------ #include <ost/mol/mol.hh> +#include <ost/conop/conop.hh> #include <ost/io/mol/entity_io_pdb_handler.hh> #include <ost/io/pdb_reader.hh> +#include <ost/io/pdb_writer.hh> #include <ost/io/io_exception.hh> #define BOOST_TEST_DYN_LINK #include <boost/test/unit_test.hpp> @@ -27,6 +29,36 @@ using boost::unit_test_framework::test_suite; using namespace ost; using namespace ost::io; + +bool compare_files(const String& test, const String& gold_standard) +{ + std::ifstream test_stream(test.c_str()); + std::ifstream gold_stream(gold_standard.c_str()); + String test_line, gold_line; + while (true) { + bool test_end=std::getline(test_stream, test_line); + bool gold_end=std::getline(gold_stream, gold_line); + if (!(test_end || gold_end)) { + return true; + } + if (!test_end) { + std::cerr << gold_standard << " contains additional line(s):" + << std::endl << gold_line; + return false; + } + if (!gold_end) { + std::cerr << test << " contains additional line(s):" + << std::endl << test_line; + return false; + } + if (gold_line!=test_line) { + std::cerr << "line mismatch:" << std::endl << "test: " << test_line + << std::endl << "gold: " << gold_line; + return false; + } + } + return true; +} BOOST_AUTO_TEST_SUITE( io ) @@ -186,7 +218,54 @@ BOOST_AUTO_TEST_CASE(no_endmdl_record) PDBReader reader(fname); mol::EntityHandle ent=mol::CreateEntity(); BOOST_CHECK_THROW(reader.Import(ent), IOException); +} + +BOOST_AUTO_TEST_CASE(write_atom) +{ + std::stringstream out; + PDBWriter writer(out); + + mol::EntityHandle ent=mol::CreateEntity(); + mol::XCSEditor edi=ent.RequestXCSEditor(); + mol::ChainHandle ch=edi.InsertChain("A"); + mol::ResidueHandle r=edi.AppendResidue(ch, "GLY"); + mol::AtomProp c_prop; + c_prop.element="C"; + c_prop.occupancy=1.0; + c_prop.b_factor=128.0; + mol::AtomHandle a=edi.InsertAtom(r, "CA", geom::Vec3(32.0, -128.0, -2.5), + c_prop); + writer.Write(ent); + String s=out.str(); + BOOST_CHECK_EQUAL(s.substr(0, 54), + "ATOM 1 CA GLY A 1 32.000-128.000 -2.500"); + BOOST_CHECK_EQUAL(s.substr(54, 26), + " 1.00128.00 C "); +} +BOOST_AUTO_TEST_CASE(write_hetatom) +{ + std::stringstream out; + PDBWriter writer(out); + + mol::EntityHandle ent=mol::CreateEntity(); + mol::XCSEditor edi=ent.RequestXCSEditor(); + mol::ChainHandle ch=edi.InsertChain("A"); + mol::ResidueHandle r=edi.AppendResidue(ch, "CA"); + mol::AtomProp c_prop; + c_prop.element="CA"; + c_prop.is_hetatm=true; + c_prop.mass=40.01; + c_prop.occupancy=1.0; + c_prop.b_factor=40.75; + mol::AtomHandle a=edi.InsertAtom(r, "CA", geom::Vec3(32.0, -128.0, -2.5), + c_prop); + writer.Write(ent); + String s=out.str(); + BOOST_CHECK_EQUAL(s.substr(0, 54), + "HETATM 1 CA CA A 1 32.000-128.000 -2.500"); + BOOST_CHECK_EQUAL(s.substr(54, 26), + " 1.00 40.75 CA "); } BOOST_AUTO_TEST_CASE(no_endmdl_record_fault_tolerant) @@ -207,4 +286,82 @@ BOOST_AUTO_TEST_CASE(no_endmdl_record_fault_tolerant) PDB::PopFlags(); } +BOOST_AUTO_TEST_CASE(alt_loc_import_export) +{ + String fname("testfiles/pdb/alt-loc.pdb"); + // this scope is required to force the writer stream to be closed before + // opening the file again in compare_files. Avoids a race condition. + { + PDBReader reader(fname); + PDBWriter writer(String("testfiles/pdb/alt-loc-out.pdb")); + + mol::EntityHandle ent=mol::CreateEntity(); + reader.Import(ent); + writer.Write(ent); + } + BOOST_CHECK(compare_files("testfiles/pdb/alt-loc.pdb", + "testfiles/pdb/alt-loc-out.pdb")); +} + +BOOST_AUTO_TEST_CASE(write_ter) +{ + String fname("testfiles/pdb/ter.pdb"); + // this scope is required to force the writer stream to be closed before + // opening the file again in compare_files. Avoids a race condition. + { + PDBReader reader(fname); + PDBWriter writer(String("testfiles/pdb/ter-out.pdb")); + + mol::EntityHandle ent=mol::CreateEntity(); + reader.Import(ent); + // we use conopology to mark amino acids as peptide-linking. this is require + // for proper TER output + conop::Conopology& conop_inst=conop::Conopology::Instance(); + conop_inst.ConnectAll(conop_inst.GetBuilder(), ent); + writer.Write(ent); + } + BOOST_CHECK(compare_files("testfiles/pdb/ter.pdb", + "testfiles/pdb/ter-out.pdb")); +} + + +BOOST_AUTO_TEST_CASE(res_name_too_long) +{ + std::stringstream out; + PDBWriter writer(out); + + mol::EntityHandle ent=mol::CreateEntity(); + mol::XCSEditor edi=ent.RequestXCSEditor(); + mol::ChainHandle ch=edi.InsertChain("A"); + mol::ResidueHandle r=edi.AppendResidue(ch, "CALCIUM"); + mol::AtomHandle a=edi.InsertAtom(r, "CA", geom::Vec3(32.0, -128.0, -2.5)); + BOOST_CHECK_THROW(writer.Write(ent), IOException); +} + +BOOST_AUTO_TEST_CASE(chain_name_too_long) +{ + std::stringstream out; + PDBWriter writer(out); + + mol::EntityHandle ent=mol::CreateEntity(); + mol::XCSEditor edi=ent.RequestXCSEditor(); + mol::ChainHandle ch=edi.InsertChain("AB"); + mol::ResidueHandle r=edi.AppendResidue(ch, "CA"); + mol::AtomHandle a=edi.InsertAtom(r, "CA", geom::Vec3(32.0, -128.0, -2.5)); + BOOST_CHECK_THROW(writer.Write(ent), IOException); +} + +BOOST_AUTO_TEST_CASE(atom_name_too_long) +{ + std::stringstream out; + PDBWriter writer(out); + + mol::EntityHandle ent=mol::CreateEntity(); + mol::XCSEditor edi=ent.RequestXCSEditor(); + mol::ChainHandle ch=edi.InsertChain("A"); + mol::ResidueHandle r=edi.AppendResidue(ch, "CA"); + mol::AtomHandle a=edi.InsertAtom(r, "CALCIUM", geom::Vec3(32.0, -128.0, -2.5)); + BOOST_CHECK_THROW(writer.Write(ent), IOException); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/modules/io/tests/testfiles/pdb/alt-loc.pdb b/modules/io/tests/testfiles/pdb/alt-loc.pdb new file mode 100644 index 000000000..b08f83459 --- /dev/null +++ b/modules/io/tests/testfiles/pdb/alt-loc.pdb @@ -0,0 +1,3 @@ +ATOM 1 N AMET A 1 16.000 64.000 0.000 0.50 1.00 N +ATOM 1 N BMET A 1 8.000-128.000 0.000 0.50 1.00 N +END \ No newline at end of file diff --git a/modules/io/tests/testfiles/pdb/ter.pdb b/modules/io/tests/testfiles/pdb/ter.pdb new file mode 100644 index 000000000..fb85bb403 --- /dev/null +++ b/modules/io/tests/testfiles/pdb/ter.pdb @@ -0,0 +1,15 @@ +ATOM 1 N THR A 161 29.926 -12.642 -13.047 1.00 25.35 N +ATOM 2 CA THR A 161 29.978 -12.367 -11.602 1.00 25.36 C +ATOM 3 C THR A 161 29.215 -13.430 -10.777 1.00 27.67 C +ATOM 4 O THR A 161 29.065 -13.298 -9.552 1.00 26.09 O +ATOM 5 CB THR A 161 29.363 -11.029 -11.309 1.00 24.99 C +ATOM 6 OG1 THR A 161 28.023 -11.041 -11.744 1.00 22.56 O +ATOM 7 CG2 THR A 161 29.998 -9.872 -12.148 1.00 23.85 C +ATOM 8 N ALA A 162 28.657 -14.427 -11.463 1.00 28.77 N +ATOM 9 CA ALA A 162 28.111 -15.569 -10.810 1.00 30.99 C +ATOM 10 C ALA A 162 29.301 -16.453 -10.436 1.00 33.52 C +ATOM 11 O ALA A 162 29.230 -17.243 -9.498 1.00 36.04 O +ATOM 12 CB ALA A 162 27.155 -16.295 -11.713 1.00 30.93 C +TER 13 ALA A 162 +HETATM 14 O HOH A 164 25.516 0.940 27.392 1.00 38.40 O +END \ No newline at end of file -- GitLab