diff --git a/.gitignore b/.gitignore index 71a47aca6f0cb322c46981610abe785b73597f04..be2ace82869f093288d4893aa88f98d3859a3e74 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 e77cfda20a98d8b856113f12c0b50deb470a2e92..f61c471d31a52f363fb17d43917719b927ebe06c 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 0000000000000000000000000000000000000000..f42bcac73aaec7166026bb36b530087fe0258ec8 --- /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 775bec870c7f4fc2ef02e4a8087472060803a37c..f46f70d040aa473edf037d443835edd24450096e 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 68a9332a95587b79afe16caea37ca72414de52ec..40ed1cbffa5cf8094f8a5ab04cd366500414a445 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 811c459a891d1f08e8400242ef896979a3829fbb..053705f01f820d376dff3da00209fe2d7f5d5a25 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 0000000000000000000000000000000000000000..b08f834596a2ff3d094fe2964479beccdbf168c0 --- /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 0000000000000000000000000000000000000000..fb85bb40350acded72722a6598b4b965409ec00f --- /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