diff --git a/modules/mol/alg/pymod/CMakeLists.txt b/modules/mol/alg/pymod/CMakeLists.txt index 979503197c3532b301a1e3abf1b8c3f5a93113ff..146774722d330305601088fd7fa18735b821c001 100644 --- a/modules/mol/alg/pymod/CMakeLists.txt +++ b/modules/mol/alg/pymod/CMakeLists.txt @@ -11,6 +11,7 @@ set(OST_MOL_ALG_PYMOD_SOURCES export_molck.cc export_membrane.cc export_entity_to_density.cc + export_biounit.cc ) set(OST_MOL_ALG_PYMOD_MODULES diff --git a/modules/mol/alg/pymod/export_biounit.cc b/modules/mol/alg/pymod/export_biounit.cc new file mode 100644 index 0000000000000000000000000000000000000000..1b1e6b354b66d8f0b3e0673086bb1d94fe8a3f52 --- /dev/null +++ b/modules/mol/alg/pymod/export_biounit.cc @@ -0,0 +1,86 @@ +//------------------------------------------------------------------------------ +// This file is part of the OpenStructure project <www.openstructure.org> +// +// Copyright (C) 2008-2023 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 +//------------------------------------------------------------------------------ + +#include <boost/python.hpp> +using namespace boost::python; + +#include <ost/mol/alg/biounit.hh> + +namespace{ + + PyObject* wrap_to_bytes(ost::mol::alg::BUInfo& bu) { + String str = bu.ToString(); + return PyBytes_FromStringAndSize(str.c_str(), str.size()); + } + + ost::mol::alg::BUInfo wrap_from_bytes(boost::python::object obj) { + String str(PyBytes_AsString(obj.ptr()), PyBytes_Size(obj.ptr())); + return ost::mol::alg::BUInfo::FromString(str); + } + + list wrap_get_au_chains(const ost::mol::alg::BUInfo& info) { + list return_list; + const std::vector<std::vector<String> >& au_chains = info.GetAUChains(); + for(uint i = 0; i < au_chains.size(); ++i) { + list tmp; + for(uint j = 0; j < au_chains[i].size(); ++j) { + tmp.append(au_chains[i][j]); + } + return_list.append(tmp); + } + return return_list; + } + + list wrap_get_transformations(const ost::mol::alg::BUInfo& info) { + list return_list; + const std::vector<std::vector<geom::Mat4> >& tfs = info.GetTransformations(); + for(uint i = 0; i < tfs.size(); ++i) { + list tmp; + for(uint j = 0; j < tfs[i].size(); ++j) { + tmp.append(tfs[i][j]); + } + return_list.append(tmp); + } + return return_list; + } + + ost::mol::EntityHandle wrap_CreateBU_one(const ost::mol::EntityHandle& asu, + const ost::io::MMCifInfoBioUnit& bu) { + return ost::mol::alg::CreateBU(asu, bu); + } + + ost::mol::EntityHandle wrap_CreateBU_two(const ost::mol::EntityHandle& asu, + const ost::mol::alg::BUInfo& bu) { + return ost::mol::alg::CreateBU(asu, bu); + } + +} // anon ns + +void export_biounit() { + + class_<ost::mol::alg::BUInfo>("BUInfo", init<const ost::io::MMCifInfoBioUnit&>()) + .def("FromBytes", &wrap_from_bytes).staticmethod("FromBytes") + .def("ToBytes", &wrap_to_bytes) + .def("GetAUChains", &wrap_get_au_chains) + .def("GetTransformations", &wrap_get_transformations) + ; + + def("CreateBU", &wrap_CreateBU_one); + def("CreateBU", &wrap_CreateBU_two); +} diff --git a/modules/mol/alg/pymod/wrap_mol_alg.cc b/modules/mol/alg/pymod/wrap_mol_alg.cc index e1b80398615764e98b67b912ae0e75758b24c136..295bea2bb84a0a3beb7784e26a9ae4efd81a9089 100644 --- a/modules/mol/alg/pymod/wrap_mol_alg.cc +++ b/modules/mol/alg/pymod/wrap_mol_alg.cc @@ -50,6 +50,7 @@ void export_sec_struct(); void export_sec_struct_segments(); void export_find_membrane(); void export_entity_to_density(); +void export_biounit(); namespace { @@ -321,6 +322,7 @@ BOOST_PYTHON_MODULE(_ost_mol_alg) export_sec_struct_segments(); export_find_membrane(); export_entity_to_density(); + export_biounit(); def("LocalDistDiffTest", lddt_a, (arg("sequence_separation")=0,arg("local_lddt_property_string")="")); def("LocalDistDiffTest", lddt_c, (arg("local_lddt_property_string")="")); diff --git a/modules/mol/alg/src/CMakeLists.txt b/modules/mol/alg/src/CMakeLists.txt index c90616ba298908dcc12d798291ba0a1770306328..49f58f50e27c0f7eeb29feafac4d4523b5cfb753 100644 --- a/modules/mol/alg/src/CMakeLists.txt +++ b/modules/mol/alg/src/CMakeLists.txt @@ -24,6 +24,7 @@ set(OST_MOL_ALG_HEADERS molck.hh find_membrane.hh entity_to_density.hh + biounit.hh ) set(OST_MOL_ALG_SOURCES @@ -51,6 +52,7 @@ set(OST_MOL_ALG_SOURCES molck.cc find_membrane.cc entity_to_density.cc + biounit.cc ) set(MOL_ALG_DEPS ost_mol ost_seq ost_img ost_img_alg ost_seq_alg ost_conop) diff --git a/modules/mol/alg/src/biounit.cc b/modules/mol/alg/src/biounit.cc new file mode 100644 index 0000000000000000000000000000000000000000..2ff12d7dd7950e63452ab8599290b338902e2b87 --- /dev/null +++ b/modules/mol/alg/src/biounit.cc @@ -0,0 +1,321 @@ +//------------------------------------------------------------------------------ +// This file is part of the OpenStructure project <www.openstructure.org> +// +// Copyright (C) 2008-2023 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 +//------------------------------------------------------------------------------ + +#include <ost/mol/alg/biounit.hh> +#include <ost/mol/entity_view.hh> + +namespace{ + +// dump and load vectors with various types of integers +template<typename T> +void LoadIntVec(std::istream& stream, std::vector<T>& vec) { + uint32_t size; + stream.read(reinterpret_cast<char*>(&size), sizeof(uint32_t)); + vec.resize(size); + stream.read(reinterpret_cast<char*>(&vec[0]), size*sizeof(T)); +} + +template<typename T> +void DumpIntVec(std::ostream& stream, const std::vector<T>& vec) { + uint32_t size = vec.size(); + stream.write(reinterpret_cast<char*>(&size), sizeof(uint32_t)); + stream.write(reinterpret_cast<const char*>(&vec[0]), size*sizeof(T)); +} + +// dump and load strings +void Load(std::istream& stream, String& str) { + uint32_t size; + stream.read(reinterpret_cast<char*>(&size), sizeof(uint32_t)); + str.resize(size); + stream.read(&str[0], size); +} + +void Dump(std::ostream& stream, const String& str) { + uint32_t size = str.size(); + stream.write(reinterpret_cast<char*>(&size), sizeof(uint32_t)); + stream.write(&str[0], size); +} + +// dump and load vectors with strings +void Load(std::istream& stream, std::vector<String>& vec) { + std::vector<uint8_t> string_sizes; + String str; + LoadIntVec(stream, string_sizes); + Load(stream, str); + vec.resize(string_sizes.size()); + int idx = 0; + for(uint i = 0; i < string_sizes.size(); ++i) { + vec[i] = str.substr(idx, string_sizes[i]); + idx += string_sizes[i]; + } +} + +void Dump(std::ostream& stream, const std::vector<String>& vec) { + String total_string; + std::vector<uint8_t> string_sizes; + for(auto it = vec.begin(); it != vec.end(); ++it) { + if(it->size() > std::numeric_limits<uint8_t>::max()) { + std::stringstream ss; + ss << "Max string size that can be encoded is "; + ss << std::numeric_limits<uint8_t>::max() << " cannot encode "<< *it; + } + string_sizes.push_back(it->size()); + total_string += *it; + } + DumpIntVec(stream, string_sizes); + Dump(stream, total_string); +} + +// dump and load vectors with Mat4 +void Load(std::istream& stream, std::vector<geom::Mat4>& vec) { + uint32_t size; + stream.read(reinterpret_cast<char*>(&size), sizeof(uint32_t)); + vec.resize(size); + for(uint i = 0; i < size; ++i) { + stream.read(reinterpret_cast<char*>(vec[i].Data()),16*sizeof(Real)); + } +} + +void Dump(std::ostream& stream, const std::vector<geom::Mat4>& vec) { + uint32_t size = vec.size(); + stream.write(reinterpret_cast<char*>(&size), sizeof(uint32_t)); + for(uint i = 0; i < size; ++i) { + stream.write(reinterpret_cast<const char*>(vec[i].Data()), 16*sizeof(Real)); + } +} + +} // anon ns + + +namespace ost{ namespace mol{ namespace alg{ + +BUInfo::BUInfo(const ost::io::MMCifInfoBioUnit& bu) { + + // translate MMCifInfoBioUnit objects into simpler data structures + au_chains = bu.GetChainList(); + + const std::vector<std::pair<int, int> >& bu_ch_intvl = bu.GetChainIntervalList(); + for(auto it = bu_ch_intvl.begin(); it != bu_ch_intvl.end(); ++it) { + chain_intvl.push_back(it->first); + chain_intvl.push_back(it->second); + } + + const std::vector<std::vector<ost::io::MMCifInfoTransOpPtr> >& bu_op_list = bu.GetOperations(); + for(auto i = bu_op_list.begin(); i != bu_op_list.end(); ++i) { + std::vector<geom::Mat4> mat_list; + for(auto j = i->begin(); j != i->end(); ++j) { + geom::Mat4 m; + m.PasteRotation((*j)->GetMatrix()); + m.PasteTranslation((*j)->GetVector()); + mat_list.push_back(m); + } + operations.push_back(mat_list); + } + + const std::vector<std::pair<int, int> >& bu_op_intvl = bu.GetOperationsIntervalList(); + for(auto it = bu_op_intvl.begin(); it != bu_op_intvl.end(); ++it) { + op_intvl.push_back(it->first); + op_intvl.push_back(it->second); + } +} + +void BUInfo::ToStream(std::ostream& stream) const { + Dump(stream, au_chains); + DumpIntVec(stream, chain_intvl); + uint32_t size = operations.size(); + stream.write(reinterpret_cast<char*>(&size), sizeof(uint32_t)); + for(auto it = operations.begin(); it != operations.end(); ++it) { + Dump(stream, *it); + } + DumpIntVec(stream, op_intvl); +} + +BUInfo BUInfo::FromStream(std::istream& stream) { + BUInfo info; + Load(stream, info.au_chains); + LoadIntVec(stream, info.chain_intvl); + uint32_t size = 0; + stream.read(reinterpret_cast<char*>(&size), sizeof(uint32_t)); + info.operations.resize(size); + for(uint i = 0; i < size; ++i) { + Load(stream, info.operations[i]); + } + LoadIntVec(stream, info.op_intvl); + return info; +} + +BUInfo BUInfo::FromString(const String& s) { + std::istringstream in_stream(s); + BUInfo info = BUInfo::FromStream(in_stream); + return info; +} + +String BUInfo::ToString() const { + std::ostringstream out_stream; + this->ToStream(out_stream); + return out_stream.str(); +} + +const std::vector<std::vector<String> >& BUInfo::GetAUChains() const { + if(au_chains_.empty()) { + this->_InitTransforms(); + } + return au_chains_; +} + +const std::vector<std::vector<geom::Mat4> >& BUInfo::GetTransformations() const { + if(transforms_.empty()) { + this->_InitTransforms(); + } + return transforms_; +} + +void BUInfo::_InitTransforms() const { + int n_intervals = chain_intvl.size() / 2; + for(int intvl_idx = 0; intvl_idx < n_intervals; ++intvl_idx) { + // extract relevant chain names from asu + //////////////////////////////////////// + std::vector<String> chain_names; + int chain_start = chain_intvl[2*intvl_idx]; + int chain_end = chain_intvl[2*intvl_idx+1]; + for(int ch_idx = chain_start; ch_idx < chain_end; ++ch_idx) { + chain_names.push_back(au_chains[ch_idx]); + } + au_chains_.push_back(chain_names); + // extract operations that will be applied to those chains + ////////////////////////////////////////////////////////// + std::vector<geom::Mat4> transforms; + int op_start = op_intvl[2*intvl_idx]; + int op_end = op_intvl[2*intvl_idx+1]; + if(op_end > op_start) { + for(auto it = operations[op_start].begin(); + it != operations[op_start].end(); ++it) { + transforms.push_back(*it); + } + ++op_start; + while(op_start < op_end) { + std::vector<geom::Mat4> tmp_transforms; + for(auto i = operations[op_start].begin(); + i != operations[op_start].end(); ++i) { + for(auto j = transforms.begin(); j != transforms.end(); ++j) { + tmp_transforms.push_back((*j)*(*i)); + } + } + transforms = tmp_transforms; + ++op_start; + } + } + transforms_.push_back(transforms); + } +} + +ost::mol::EntityHandle CreateBU(const ost::mol::EntityHandle& asu, + const ost::io::MMCifInfoBioUnit& bu) { + BUInfo bu_info(bu); + return CreateBU(asu, bu_info); +} + +ost::mol::EntityHandle CreateBU(const ost::mol::EntityHandle& asu, + const BUInfo& bu_info) { + ost::mol::EntityHandle ent = ost::mol::CreateEntity(); + ent.SetName(asu.GetName()); + ost::mol::XCSEditor ed = ent.EditXCS(mol::BUFFERED_EDIT); + + // For chain naming. First occurence: 1.<au_cname>, Second: 2.<au_cname> etc. + std::map<String, int> chain_counter; + + const std::vector<std::vector<String> >& au_chains = bu_info.GetAUChains(); + const std::vector<std::vector<geom::Mat4> >& transforms = + bu_info.GetTransformations(); + + for(uint chain_intvl = 0; chain_intvl < au_chains.size(); ++chain_intvl) { + if(au_chains[chain_intvl].empty()) continue; + // derive all bonds related to that chain_intvl + // potentially also interchain bonds + std::stringstream query_ss; + query_ss << "cname=" << au_chains[chain_intvl][0]; + for(uint i = 1; i < au_chains[chain_intvl].size(); ++i) { + query_ss << ',' << au_chains[chain_intvl][i]; + } + ost::mol::EntityView asu_view = asu.Select(query_ss.str()); + const ost::mol::BondHandleList& bond_list = asu_view.GetBondList(); + + // process all transformations + for(uint t_idx = 0; t_idx < transforms[chain_intvl].size(); ++t_idx) { + const geom::Mat4& m = transforms[chain_intvl][t_idx]; + // key: au_at.GetHashCode, value: bu_at + // required for bond buildup in the end + std::map<long, AtomHandle> atom_mapper; + for(uint c_idx = 0; c_idx < au_chains[chain_intvl].size(); ++c_idx) { + String au_cname = au_chains[chain_intvl][c_idx]; + if(chain_counter.find(au_cname) == chain_counter.end()) { + chain_counter[au_cname] = 1; + } + std::stringstream bu_cname_ss; + bu_cname_ss << chain_counter[au_cname] << '.' << au_cname; + chain_counter[au_cname] += 1; + ost::mol::ChainHandle asu_ch = asu.FindChain(au_cname); + if(!asu_ch.IsValid()) { + std::stringstream ss; + ss << "Cannot construct biounit with asu chain "<<au_cname; + ss << ". Specified interval only has: " <<au_chains[chain_intvl][0]; + for(uint i = 1; i < au_chains[chain_intvl].size(); ++i) { + ss << ',' << au_chains[chain_intvl][i]; + } + throw ost::Error(ss.str()); + } + ost::mol::ChainHandle bu_ch = ed.InsertChain(bu_cname_ss.str()); + ed.SetChainType(bu_ch, asu_ch.GetType()); + ost::mol::ResidueHandleList au_res_list = asu_ch.GetResidueList(); + for(auto res_it = au_res_list.begin(); + res_it != au_res_list.end(); ++res_it) { + ost::mol::ResidueHandle bu_res = ed.AppendResidue(bu_ch, + res_it->GetName(), res_it->GetNumber()); + bu_res.SetOneLetterCode(res_it->GetOneLetterCode()); + bu_res.SetSecStructure(res_it->GetSecStructure()); + bu_res.SetChemClass(res_it->GetChemClass()); + bu_res.SetChemType(res_it->GetChemType()); + bu_res.SetIsProtein(res_it->IsProtein()); + bu_res.SetIsLigand(res_it->IsLigand()); + ost::mol::AtomHandleList au_at_list = res_it->GetAtomList(); + for(auto at_it = au_at_list.begin(); at_it != au_at_list.end(); ++at_it) { + geom::Vec3 bu_at_pos = geom::Vec3(m*geom::Vec4(at_it->GetPos())); + ost::mol::AtomHandle bu_at = ed.InsertAtom(bu_res, at_it->GetName(), + bu_at_pos, + at_it->GetElement(), + at_it->GetOccupancy(), + at_it->GetBFactor(), + at_it->IsHetAtom()); + atom_mapper[at_it->GetHashCode()] = bu_at; + } + } + } + + // connect + for(auto it = bond_list.begin(); it != bond_list.end(); ++it) { + ed.Connect(atom_mapper[it->GetFirst().GetHashCode()], + atom_mapper[it->GetSecond().GetHashCode()]); + } + + } + } + return ent; +} + +}}} // ns diff --git a/modules/mol/alg/src/biounit.hh b/modules/mol/alg/src/biounit.hh new file mode 100644 index 0000000000000000000000000000000000000000..3af0483e19bb5fa5afe816d93ebdd8a919613861 --- /dev/null +++ b/modules/mol/alg/src/biounit.hh @@ -0,0 +1,66 @@ +//------------------------------------------------------------------------------ +// This file is part of the OpenStructure project <www.openstructure.org> +// +// Copyright (C) 2008-2023 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 +//------------------------------------------------------------------------------ +#ifndef OST_MOL_ALG_BIOUNIT_HH +#define OST_MOL_ALG_BIOUNIT_HH + +#include <ost/mol/entity_handle.hh> +#include <ost/io/mmcif_info.hh> + +namespace ost { namespace mol { namespace alg { + +struct BUInfo { + + BUInfo() { }; + + BUInfo(const ost::io::MMCifInfoBioUnit& bu); + + void ToStream(std::ostream& stream) const; + + static BUInfo FromStream(std::istream& stream); + + String ToString() const; + + static BUInfo FromString(const String& s); + + const std::vector<std::vector<String> >& GetAUChains() const; + + const std::vector<std::vector<geom::Mat4> >& GetTransformations() const; + + void _InitTransforms() const; + + std::vector<String> au_chains; + std::vector<int> chain_intvl; + std::vector<std::vector<geom::Mat4> > operations; + std::vector<int> op_intvl; + +private: + mutable std::vector<std::vector<String> > au_chains_; + mutable std::vector<std::vector<geom::Mat4> > transforms_; +}; + +ost::mol::EntityHandle CreateBU(const ost::mol::EntityHandle& asu, + const ost::io::MMCifInfoBioUnit& bu); + + +ost::mol::EntityHandle CreateBU(const ost::mol::EntityHandle& asu, + const BUInfo& bu_info); + +}}} // ns + +#endif // OST_MOL_ALG_BIOUNIT_HH diff --git a/modules/mol/alg/tests/CMakeLists.txt b/modules/mol/alg/tests/CMakeLists.txt index 4e3126dd88d49cecef7235c368ee1835d34b5e9c..268d09c8153ad1b2d5fe756981416e5373e4b0dd 100644 --- a/modules/mol/alg/tests/CMakeLists.txt +++ b/modules/mol/alg/tests/CMakeLists.txt @@ -12,6 +12,7 @@ set(OST_MOL_ALG_UNIT_TESTS test_qsscore.py test_stereochemistry.py test_contact_score.py + test_biounit.py ) if (COMPOUND_LIB) diff --git a/modules/mol/alg/tests/test_biounit.py b/modules/mol/alg/tests/test_biounit.py new file mode 100644 index 0000000000000000000000000000000000000000..a3dc77ae61df64dfc7f4d27ff8c96310c9bfd91c --- /dev/null +++ b/modules/mol/alg/tests/test_biounit.py @@ -0,0 +1,104 @@ +import unittest, os, sys +import ost +from ost import io, geom, mol +from ost.mol.alg import BUInfo + +class TestBioUnit(unittest.TestCase): + + def test_bu(self): + ent, seqres, info = io.LoadMMCIF("testfiles/1out.cif.gz", + seqres=True, + info=True) + + # Create BUInfo from MMCifInfoBioUnit + biounits = info.GetBioUnits() + self.assertEqual(len(biounits), 1) + bu_info = BUInfo(biounits[0]) + + # directly use the dump and load mechanism + # IF ANY UNIT TEST FAILS, DISABLE THE FOLLOWING TWO LINES + # TO EXCLUDE THE POSSIBILITY OF ISSUES IN BUINFO IO FUNCTIONALITY + bytes_str = bu_info.ToBytes() + bu_info = BUInfo.FromBytes(bytes_str) + + # check whether properties in BUInfo object are correctly set + asu_chains = bu_info.GetAUChains() + self.assertEqual(asu_chains, [["A", "B", "C", "D", "E", "F"]]) + transformations = bu_info.GetTransformations() + self.assertEqual(len(transformations), 1) + self.assertEqual(len(transformations[0]), 2) + self.assertEqual(transformations[0][0], geom.Mat4()) # identity + + # reconstruct biounit + bu = mol.alg.CreateBU(ent, bu_info) + self.assertEqual([ch.GetName() for ch in bu.chains], + ["1.A", "1.B", "1.C", "1.D", "1.E", "1.F", + "2.A", "2.B", "2.C", "2.D", "2.E", "2.F"]) + + # extract copies of original assymetric units from biounit + for ch in bu.chains: + ch.SetIntProp("asu_copy_idx", int(ch.GetName().split('.')[0])) + asu_copy_one = bu.Select("gcasu_copy_idx=1") + asu_copy_two = bu.Select("gcasu_copy_idx=2") + + # compare bonds - well, this tests whether bonds have been correctly + # transferred. BUT: we can be pretty sure that all atoms/residues/chains + # have been transferred too when explicitely checking the bonds. + asu_bonds = ent.GetBondList() + asu_bonds_desc = list() + for b in asu_bonds: + at_one = b.GetFirst() + at_one_cname = at_one.GetChain().GetName() + at_one_rname = at_one.GetResidue().GetName() + at_one_rnum = at_one.GetResidue().GetNumber().GetNum() + at_one_name = at_one.GetName() + at_one_desc = f"{at_one_cname}.{at_one_rname}.{at_one_rnum}.{at_one_name}" + at_two = b.GetSecond() + at_two_cname = at_two.GetChain().GetName() + at_two_rname = at_two.GetResidue().GetName() + at_two_rnum = at_two.GetResidue().GetNumber().GetNum() + at_two_name = at_two.GetName() + at_two_desc = f"{at_two_cname}.{at_two_rname}.{at_two_rnum}.{at_two_name}" + asu_bonds_desc.append((at_one_desc, at_two_desc)) + + asu_copy_one_bonds = asu_copy_one.GetBondList() + asu_copy_one_bonds_desc = list() + for b in asu_copy_one_bonds: + at_one = b.GetFirst() + at_one_cname = at_one.GetChain().GetName().split('.')[1] + at_one_rname = at_one.GetResidue().GetName() + at_one_rnum = at_one.GetResidue().GetNumber().GetNum() + at_one_name = at_one.GetName() + at_one_desc = f"{at_one_cname}.{at_one_rname}.{at_one_rnum}.{at_one_name}" + at_two = b.GetSecond() + at_two_cname = at_two.GetChain().GetName().split('.')[1] + at_two_rname = at_two.GetResidue().GetName() + at_two_rnum = at_two.GetResidue().GetNumber().GetNum() + at_two_name = at_two.GetName() + at_two_desc = f"{at_two_cname}.{at_two_rname}.{at_two_rnum}.{at_two_name}" + asu_copy_one_bonds_desc.append((at_one_desc, at_two_desc)) + + asu_copy_two_bonds = asu_copy_two.GetBondList() + asu_copy_two_bonds_desc = list() + for b in asu_copy_two_bonds: + at_one = b.GetFirst() + at_one_cname = at_one.GetChain().GetName().split('.')[1] + at_one_rname = at_one.GetResidue().GetName() + at_one_rnum = at_one.GetResidue().GetNumber().GetNum() + at_one_name = at_one.GetName() + at_one_desc = f"{at_one_cname}.{at_one_rname}.{at_one_rnum}.{at_one_name}" + at_two = b.GetSecond() + at_two_cname = at_two.GetChain().GetName().split('.')[1] + at_two_rname = at_two.GetResidue().GetName() + at_two_rnum = at_two.GetResidue().GetNumber().GetNum() + at_two_name = at_two.GetName() + at_two_desc = f"{at_two_cname}.{at_two_rname}.{at_two_rnum}.{at_two_name}" + asu_copy_two_bonds_desc.append((at_one_desc, at_two_desc)) + + self.assertEqual(asu_bonds_desc, asu_copy_one_bonds_desc) + self.assertEqual(asu_bonds_desc, asu_copy_two_bonds_desc) + + +if __name__ == "__main__": + from ost import testutils + testutils.RunTests() diff --git a/modules/mol/alg/tests/testfiles/1out.cif.gz b/modules/mol/alg/tests/testfiles/1out.cif.gz new file mode 100644 index 0000000000000000000000000000000000000000..753337af9ab9dc0eca11a4391f1e9e92f5148332 Binary files /dev/null and b/modules/mol/alg/tests/testfiles/1out.cif.gz differ