diff --git a/modules/conop/src/CMakeLists.txt b/modules/conop/src/CMakeLists.txt index c2f1f4c6d0148c62461be5852bd33872ad39f903..b45b11272b79a090210c3a3d7134676d83fcbc05 100644 --- a/modules/conop/src/CMakeLists.txt +++ b/modules/conop/src/CMakeLists.txt @@ -50,7 +50,7 @@ if (COMPOUND_LIB) install(FILES ${TO} DESTINATION "share/openstructure") else() message(FATAL_ERROR "${COMPOUND_LIB} does not exist. \n" - "Pass -DCOMPOUND_LIB=NONE if you would like to build " + "Pass -DCOMPOUND_LIB=OFF if you would like to build " "OpenStructure without a compound library") endif() diff --git a/modules/geom/pymod/export_vec3.cc b/modules/geom/pymod/export_vec3.cc index 7691c59ac0090a17a0aa79394ac24aada3686eb8..445b8aef781d463d31b162af60d7811334f1f4fa 100644 --- a/modules/geom/pymod/export_vec3.cc +++ b/modules/geom/pymod/export_vec3.cc @@ -127,5 +127,7 @@ void export_Vec3() .def("GetGDTHA", &Vec3List::GetGDTHA, (arg("other"), arg("norm")=true)) .def("GetGDTTS", &Vec3List::GetGDTTS, (arg("other"), arg("norm")=true)) .def("GetGDT", &Vec3List::GetGDT, (arg("other"), arg("thresh"), arg("norm")=true)) + .def("GetMinDist", &Vec3List::GetMinDist, (arg("other"))) + .def("IsWithin", &Vec3List::IsWithin, (arg("other"), arg("dist"))) ; } diff --git a/modules/geom/src/vec3.cc b/modules/geom/src/vec3.cc index 0adbc1a65d2ab414234ca9769374fe066cf9bbd2..bb3a0617e6cf59a3226bc2da6a686a52078457ca 100644 --- a/modules/geom/src/vec3.cc +++ b/modules/geom/src/vec3.cc @@ -198,6 +198,28 @@ Real Vec3List::GetGDT(const Vec3List& other, Real thresh, bool norm) const return norm && !this->empty() ? static_cast<Real>(n)/(this->size()) : n; } +Real Vec3List::GetMinDist(const Vec3List& other) const { + Real min = std::numeric_limits<Real>::max(); + for(size_t i = 0; i < this->size(); ++i) { + for(size_t j = 0; j < other.size(); ++j) { + min = std::min(min, geom::Length2((*this)[i] - other[j])); + } + } + return std::sqrt(min); +} + +bool Vec3List::IsWithin(const Vec3List& other, Real dist) const { + Real squared_dist = dist*dist; + for(size_t i = 0; i < this->size(); ++i) { + for(size_t j = 0; j < other.size(); ++j) { + if(geom::Length2((*this)[i] - other[j]) <= squared_dist) { + return true; + } + } + } + return false; +} + std::pair<Line3, Real> Vec3List::FitCylinder(const Vec3& initial_direction) const { Vec3 center=this->GetCenter(); diff --git a/modules/geom/src/vec3.hh b/modules/geom/src/vec3.hh index 8302729061277c0bc5f9fa0b3d20caca36aaaa66..2c0e2bbb2c601f1238f7a5a7948ebfafaba41277 100644 --- a/modules/geom/src/vec3.hh +++ b/modules/geom/src/vec3.hh @@ -343,6 +343,8 @@ public: Real GetGDTHA(const Vec3List& other, bool norm=true) const; Real GetGDTTS(const Vec3List& other, bool norm=true) const; Real GetGDT(const Vec3List& other, Real thresh, bool norm=true) const; + Real GetMinDist(const Vec3List& other) const; + bool IsWithin(const Vec3List& other, Real dist) const; //This function fits a cylinder to the positions in Vec3List //It takes as argument an initial guess for the direction. diff --git a/modules/mol/alg/doc/molalg.rst b/modules/mol/alg/doc/molalg.rst index 06c4ef78276b5ce0e29eed88a0b781f95dcbf7fe..5548d1cd951f4e5d11a9f3adba543d0f587ee024 100644 --- a/modules/mol/alg/doc/molalg.rst +++ b/modules/mol/alg/doc/molalg.rst @@ -155,6 +155,14 @@ Local Distance Test scores (lDDT, DRMSD) .. currentmodule:: ost.mol.alg +:mod:`DockQ <ost.mol.alg.dockq>` -- DockQ implementation +-------------------------------------------------------------------------------- + +.. autofunction:: ost.mol.alg.dockq.DockQ + +.. currentmodule:: ost.mol.alg + + .. _steric-clashes: Steric Clashes diff --git a/modules/mol/alg/pymod/CMakeLists.txt b/modules/mol/alg/pymod/CMakeLists.txt index e50232556749336687c37a16804da393240066ce..9c65a6b87cf2b330b53868278135f62e473d036b 100644 --- a/modules/mol/alg/pymod/CMakeLists.txt +++ b/modules/mol/alg/pymod/CMakeLists.txt @@ -28,6 +28,7 @@ set(OST_MOL_ALG_PYMOD_MODULES chain_mapping.py stereochemistry.py ligand_scoring.py + dockq.py ) if (NOT ENABLE_STATIC) diff --git a/modules/mol/alg/pymod/dockq.py b/modules/mol/alg/pymod/dockq.py new file mode 100644 index 0000000000000000000000000000000000000000..c22ca78d21746de90900b2a1f539d931c8cc65a8 --- /dev/null +++ b/modules/mol/alg/pymod/dockq.py @@ -0,0 +1,234 @@ +from ost import geom +from ost import mol + +def _PreprocessStructures(mdl, ref, mdl_ch1, mdl_ch2, ref_ch1, ref_ch2, + ch1_aln = None, ch2_aln = None): + """ Preprocesses *mdl* and *ref* + + Returns two entity views with the exact same number of residues. I.e. the + residues correspond to a one-to-one mapping. Additionally, each residue gets + the int property "dockq_map" assigned, which corresponds to the residue + index in the respective chain of the processed structures. + """ + mdl_residues_1 = list() + mdl_residues_2 = list() + ref_residues_1 = list() + ref_residues_2 = list() + + if ch1_aln is None and ch2_aln is None: + # go by residue numbers + for mdl_r in mdl.Select(f"cname={mdl_ch1}").residues: + ref_r = ref.FindResidue(ref_ch1, mdl_r.GetNumber()) + if ref_r.IsValid(): + mdl_residues_1.append(mdl_r) + ref_residues_1.append(ref_r) + for mdl_r in mdl.Select(f"cname={mdl_ch2}").residues: + ref_r = ref.FindResidue(ref_ch2, mdl_r.GetNumber()) + if ref_r.IsValid(): + mdl_residues_2.append(mdl_r) + ref_residues_2.append(ref_r) + else: + raise NotImplementedError("No aln mapping implemented yet") + + new_mdl = mdl.handle.CreateEmptyView() + new_ref = ref.handle.CreateEmptyView() + for r in mdl_residues_1: + new_mdl.AddResidue(r.handle, mol.INCLUDE_ALL) + for r in mdl_residues_2: + new_mdl.AddResidue(r.handle, mol.INCLUDE_ALL) + for r in ref_residues_1: + new_ref.AddResidue(r.handle, mol.INCLUDE_ALL) + for r in ref_residues_2: + new_ref.AddResidue(r.handle, mol.INCLUDE_ALL) + + # set dockq_map property + ch = new_mdl.FindChain(mdl_ch1) + for r_idx, r in enumerate(ch.residues): + r.SetIntProp("dockq_map", r_idx) + ch = new_mdl.FindChain(mdl_ch2) + for r_idx, r in enumerate(ch.residues): + r.SetIntProp("dockq_map", r_idx) + ch = new_ref.FindChain(ref_ch1) + for r_idx, r in enumerate(ch.residues): + r.SetIntProp("dockq_map", r_idx) + ch = new_ref.FindChain(ref_ch2) + for r_idx, r in enumerate(ch.residues): + r.SetIntProp("dockq_map", r_idx) + + return (new_mdl, new_ref) + +def _GetContacts(ent, ch1, ch2, dist_thresh): + int1 = ent.Select(f"cname={ch1} and {dist_thresh} <> [cname={ch2}]") + int2 = ent.Select(f"cname={ch2} and {dist_thresh} <> [cname={ch1}]") + contacts = set() + int1_p = [geom.Vec3List([a.pos for a in r.atoms]) for r in int1.residues] + int2_p = [geom.Vec3List([a.pos for a in r.atoms]) for r in int2.residues] + for r1, p1 in zip(int1.residues, int1_p): + for r2, p2 in zip(int2.residues, int2_p): + if p1.IsWithin(p2, dist_thresh): + contacts.add((r1.GetIntProp("dockq_map"), r2.GetIntProp("dockq_map"))) + return contacts + +def _ContactScores(mdl, ref, mdl_ch1, mdl_ch2, ref_ch1, ref_ch2, dist_thresh=5.0): + ref_contacts = _GetContacts(ref, ref_ch1, ref_ch2, dist_thresh) + mdl_contacts = _GetContacts(mdl, mdl_ch1, mdl_ch2, dist_thresh) + + nnat = len(ref_contacts) + nmdl = len(mdl_contacts) + + fnat = len(ref_contacts.intersection(mdl_contacts)) + if nnat > 0: + fnat /= nnat + + fnonnat = len(mdl_contacts.difference(ref_contacts)) + if len(mdl_contacts) > 0: + fnonnat /= len(mdl_contacts) + + return (nnat, nmdl, fnat, fnonnat) + +def _RMSDScores(mdl, ref, mdl_ch1, mdl_ch2, ref_ch1, ref_ch2, dist_thresh=10.0): + + mdl_ch1_residues = mdl.FindChain(mdl_ch1).residues + mdl_ch2_residues = mdl.FindChain(mdl_ch2).residues + ref_ch1_residues = ref.FindChain(ref_ch1).residues + ref_ch2_residues = ref.FindChain(ref_ch2).residues + + # iRMSD + ####### + int1 = ref.Select(f"cname={ref_ch1} and {dist_thresh} <> [cname={ref_ch2}]") + int2 = ref.Select(f"cname={ref_ch2} and {dist_thresh} <> [cname={ref_ch1}]") + int1_indices = [r.GetIntProp("dockq_map") for r in int1.residues] + int2_indices = [r.GetIntProp("dockq_map") for r in int2.residues] + ref_pos = geom.Vec3List() + mdl_pos = geom.Vec3List() + atom_names = ['CA','C','N','O'] + for idx in int1_indices: + ref_r = ref_ch1_residues[idx] + mdl_r = mdl_ch1_residues[idx] + for aname in atom_names: + ref_a = ref_r.FindAtom(aname) + mdl_a = mdl_r.FindAtom(aname) + if ref_a.IsValid() and mdl_a.IsValid(): + ref_pos.append(ref_a.pos) + mdl_pos.append(mdl_a.pos) + + for idx in int2_indices: + ref_r = ref_ch2_residues[idx] + mdl_r = mdl_ch2_residues[idx] + for aname in atom_names: + ref_a = ref_r.FindAtom(aname) + mdl_a = mdl_r.FindAtom(aname) + if ref_a.IsValid() and mdl_a.IsValid(): + ref_pos.append(ref_a.pos) + mdl_pos.append(mdl_a.pos) + + if len(mdl_pos) >= 3: + sup_result = mol.alg.SuperposeSVD(mdl_pos, ref_pos) + irmsd = sup_result.rmsd + else: + irmsd = 0.0 + + # lRMSD + ####### + # receptor is by definition the larger chain + if len(ref_ch1_residues) > len(ref_ch2_residues): + ref_receptor_residues = ref_ch1_residues + ref_ligand_residues = ref_ch2_residues + mdl_receptor_residues = mdl_ch1_residues + mdl_ligand_residues = mdl_ch2_residues + else: + ref_receptor_residues = ref_ch2_residues + ref_ligand_residues = ref_ch1_residues + mdl_receptor_residues = mdl_ch2_residues + mdl_ligand_residues = mdl_ch1_residues + + ref_receptor_positions = geom.Vec3List() + mdl_receptor_positions = geom.Vec3List() + ref_ligand_positions = geom.Vec3List() + mdl_ligand_positions = geom.Vec3List() + + for ref_r, mdl_r in zip(ref_receptor_residues, mdl_receptor_residues): + for aname in atom_names: + ref_a = ref_r.FindAtom(aname) + mdl_a = mdl_r.FindAtom(aname) + if ref_a.IsValid() and mdl_a.IsValid(): + ref_receptor_positions.append(ref_a.pos) + mdl_receptor_positions.append(mdl_a.pos) + + for ref_r, mdl_r in zip(ref_ligand_residues, mdl_ligand_residues): + for aname in atom_names: + ref_a = ref_r.FindAtom(aname) + mdl_a = mdl_r.FindAtom(aname) + if ref_a.IsValid() and mdl_a.IsValid(): + ref_ligand_positions.append(ref_a.pos) + mdl_ligand_positions.append(mdl_a.pos) + + if len(mdl_receptor_positions) >= 3: + sup_result = mol.alg.SuperposeSVD(mdl_receptor_positions, + ref_receptor_positions) + mdl_ligand_positions.ApplyTransform(sup_result.transformation) + lrmsd = mdl_ligand_positions.GetRMSD(ref_ligand_positions) + else: + lrmsd = 0.0 + + return (irmsd, lrmsd) + +def _ScaleRMSD(rmsd, d): + return 1.0/(1+(rmsd/d)**2) + +def _DockQ(fnat, lrmsd, irmsd, d1, d2): + """ The final number chrunching as described in the DockQ manuscript + """ + return (fnat + _ScaleRMSD(lrmsd, d1) + _ScaleRMSD(irmsd, d2))/3 + +def DockQ(mdl, ref, mdl_ch1, mdl_ch2, ref_ch1, ref_ch2, + ch1_aln=None, ch2_aln=None): + """ Computes DockQ for specified interface + + DockQ is described in: Sankar Basu and Bjoern Wallner (2016), "DockQ: A + Quality Measure for Protein-Protein Docking Models", PLOS one + + Residues are mapped based on residue numbers by default. If you provide + *ch1_aln* and *ch2_aln* you can enforce an arbitrary mapping. + + :param mdl: Model structure + :type mdl: :class:`ost.mol.EntityView`/:class:`ost.mol.EntityHandle` + :param ref: Reference structure, i.e. native structure + :type ref: :class:`ost.mol.EntityView`/:class:`ost.mol.EntityHandle` + :param mdl_ch1: Specifies chain in model constituting first part of + interface + :type mdl_ch1: :class:`str` + :param mdl_ch2: Specifies chain in model constituting second part of + interface + :type mdl_ch2: :class:`str` + :param ref_ch1: ref equivalent of mdl_ch1 + :type ref_ch1: :class:`str` + :param ref_ch2: ref equivalent of mdl_ch2 + :type ref_ch2: :class:`str` + :param ch1_aln: Alignment with two sequences to map *ref_ch1* and *mdl_ch1*. + The first sequence must match the sequence in *ref_ch1* and + the second to *mdl_ch1*. + :type ch1_aln: :class:`ost.seq.AlignmentHandle` + :param ch2_aln: Alignment with two sequences to map *ref_ch2* and *mdl_ch2*. + The first sequence must match the sequence in *ref_ch2* and + the second to *mdl_ch2*. + :type ch2_aln: :class:`ost.seq.AlignmentHandle` + :returns: :class:`dict` with keys nnat, nmdl, fnat, fnonnat, irmsd, lrmsd, + DockQ which corresponds to the equivalent values in the original + DockQ implementation. + """ + mapped_model, mapped_ref = _PreprocessStructures(mdl, ref, mdl_ch1, mdl_ch2, + ref_ch1, ref_ch2, + ch1_aln = ch1_aln, + ch2_aln = ch2_aln) + nnat, nmdl, fnat, fnonnat = _ContactScores(mapped_model, mapped_ref, + mdl_ch1, mdl_ch2, ref_ch1, ref_ch2) + irmsd, lrmsd = _RMSDScores(mapped_model, mapped_ref, + mdl_ch1, mdl_ch2, ref_ch1, ref_ch2) + return {"nnat": nnat, + "nmdl": nmdl, + "fnat": fnat, + "fnonnat": fnonnat, + "irmsd": round(irmsd, 3), + "lrmsd": round(lrmsd, 3), + "DockQ": round(_DockQ(fnat, lrmsd, irmsd, 8.5, 1.5), 3)} diff --git a/modules/mol/alg/pymod/scoring.py b/modules/mol/alg/pymod/scoring.py index 359aa0e67b9dbe6f131d074610219a434e162f15..1cb97530eadd1f90f30a31b25df4405c798cf7b2 100644 --- a/modules/mol/alg/pymod/scoring.py +++ b/modules/mol/alg/pymod/scoring.py @@ -9,10 +9,10 @@ from ost.mol.alg import lddt from ost.mol.alg import qsscore from ost.mol.alg import chain_mapping from ost.mol.alg import stereochemistry +from ost.mol.alg import dockq from ost.mol.alg.lddt import lDDTScorer from ost.mol.alg.qsscore import QSScorer from ost.mol.alg import Molck, MolckSettings -from ost.bindings import dockq from ost.bindings import cadscore import numpy as np @@ -114,11 +114,6 @@ class Scorer: to optimize for QS-score. Everything above is treated with a heuristic. :type naive_chain_mapping_thresh: :class:`int` - :param dockq_exec: Explicit path to DockQ.py script from DockQ installation - from https://github.com/bjornwallner/DockQ. If not given, - DockQ.py must be in PATH if any of the DockQ related - attributes is requested. - :type dockq_exec: :class:`str` :param cad_score_exec: Explicit path to voronota-cadscore executable from voronota installation from https://github.com/kliment-olechnovic/voronota. If @@ -128,7 +123,7 @@ class Scorer: """ def __init__(self, model, target, resnum_alignments=False, molck_settings = None, naive_chain_mapping_thresh=12, - dockq_exec = None, cad_score_exec = None): + cad_score_exec = None): if isinstance(model, mol.EntityView): self._model = mol.CreateEntityFromView(model, False) @@ -183,7 +178,6 @@ class Scorer: Molck(self._target, conop.GetDefaultLib(), molck_settings) self.resnum_alignments = resnum_alignments self.naive_chain_mapping_thresh = naive_chain_mapping_thresh - self.dockq_exec = dockq_exec self.cad_score_exec = cad_score_exec # lazily evaluated attributes @@ -868,12 +862,6 @@ class Scorer: "that are consistent between target and model " "chains, i.e. only work if resnum_alignments " "is True at Scorer construction.") - try: - dockq_exec = settings.Locate("DockQ.py", - explicit_file_name=self.dockq_exec) - except Exception as e: - raise RuntimeError("DockQ.py must be in PATH for DockQ " - "scoring") from e flat_mapping = self.mapping.GetFlatMapping() # list of [trg_ch1, trg_ch2, mdl_ch1, mdl_ch2] @@ -896,38 +884,22 @@ class Scorer: if trg_ch1 in flat_mapping and trg_ch2 in flat_mapping: mdl_ch1 = flat_mapping[trg_ch1] mdl_ch2 = flat_mapping[trg_ch2] - try: - res = dockq.DockQ(dockq_exec, self.model, self.target, - mdl_ch1, mdl_ch2, trg_ch1, trg_ch2) - except Exception as e: - if "AssertionError: length of native is zero" in str(e): - # happens if target interface has no native contacts - continue - else: - raise - - if res.native_contacts > 0: + res = dockq.DockQ(self.model, self.target, mdl_ch1, mdl_ch2, + trg_ch1, trg_ch2) + if res["nnat"] > 0: self._dockq_interfaces.append((trg_ch1, trg_ch2, mdl_ch1, mdl_ch2)) - self._dockq_native_contacts.append(res.native_contacts) - self._dockq_scores.append(res.DockQ) + self._dockq_native_contacts.append(res["nnat"]) + self._dockq_scores.append(res["DockQ"]) else: # interface which is not covered by mdl... let's run DockQ with # trg as trg/mdl in order to get the native contacts out - try: - res = dockq.DockQ(dockq_exec, self.target, self.target, - trg_ch1, trg_ch2, trg_ch1, trg_ch2) - counts = res.native_contacts - if counts > 0: - self._dockq_nonmapped_interfaces.append((trg_ch1, - trg_ch2)) - self._dockq_nonmapped_interfaces_counts.append(counts) - except Exception as e: - if "AssertionError: length of native is zero" in str(e): - # happens if target interface has no native contacts - continue - else: - raise + res = dockq.DockQ(self.target, self.target, + trg_ch1, trg_ch2, trg_ch1, trg_ch2) + if res["nnat"] > 0: + self._dockq_nonmapped_interfaces.append((trg_ch1, + trg_ch2)) + self._dockq_nonmapped_interfaces_counts.append(res["nnat"]) # there are 4 types of combined scores # - simple average @@ -1268,27 +1240,12 @@ class Scorer: :type trg_patch_two: :class:`ost.mol.EntityView` :returns: DockQ score """ - if not self.resnum_alignments: - raise RuntimeError("DockQ computations rely on residue numbers " - "that are consistent between target and model " - "chains, i.e. only work if resnum_alignments " - "is True at Scorer construction.") - try: - dockq_exec = settings.Locate("DockQ.py", - explicit_file_name=self.dockq_exec) - except Exception as e: - raise RuntimeError("DockQ.py must be in PATH for DockQ " - "scoring") from e m = self._qs_ent_from_patches(mdl_patch_one, mdl_patch_two) t = self._qs_ent_from_patches(trg_patch_one, trg_patch_two) - try: - dockq_result = dockq.DockQ(dockq_exec, t, m, "A", "B", "A", "B") - except Exception as e: - if "AssertionError: length of native is zero" in str(e): - return 0.0 - else: - raise - return dockq_result.DockQ + dockq_result = dockq.DockQ(t, m, "A", "B", "A", "B") + if dockq_result["nnat"] > 0: + return dockq_result["DockQ"] + return 0.0 def _qs_ent_from_patches(self, patch_one, patch_two): """ Constructs Entity with two chains named "A" and "B"" diff --git a/modules/mol/base/doc/entity.rst b/modules/mol/base/doc/entity.rst index 5f56ccec19790ab02cd68b242e11b11782244a69..a0946b7f4bab7248565cac5dd8980dc1ab54b431 100644 --- a/modules/mol/base/doc/entity.rst +++ b/modules/mol/base/doc/entity.rst @@ -999,7 +999,7 @@ The Handle Classes See :attr:`bonds` - :rtype: :class:`BondHandleList` + :rtype: :class:`BondHandleList` (list of :class:`BondHandle`) .. method:: GetBondPartners() @@ -1097,7 +1097,106 @@ The Handle Classes See :attr:`valid` - + +.. class:: BondHandle + + Represents a chemical bond between two atoms (first and second). + + .. attribute:: first + .. attribute:: second + + Atoms involved in the bond. No assumptions about the order should be made. + With the internal coordinate system enabled, first and second may even be + swapped when rebuilding the internal connectivity tree. Also available as + :meth:`GetFirst` and :meth:`GetSecond`. + + :type: :class:`AtomHandle` + + .. attribute:: pos + + Midpoint between the two atoms (transformed coordinates). Also available as + :meth:`GetPos`. + + :type: :class:`~ost.geom.Vec3` + + .. attribute:: length + + Length of the bond. Also available as :meth:`GetLength`. + + :type: float + + .. attribute:: bond_order + + The bond order. Possible values: + + * ``1`` - single bond + * ``2`` - double bond + * ``3`` - triple bond + * ``4`` - aromatic bond + + Also available as :meth:`GetBondOrder`. + + :type: int + + .. attribute:: hash_code + + A unique identifier for this bond handle. Also available as :meth:`GetHashCode`. + + :type: int + + .. attribute:: valid + + Validity of handle. Also available as :meth:`IsValid`. + + :type: bool + + .. method:: GetFirst() + + See :attr:`first` + + .. method:: GetSecond() + + See :attr:`second` + + .. method:: GetPos() + + See :attr:`pos` + + .. method:: GetLength() + + See :attr:`length` + + .. method:: GetBondOrder() + + See :attr:`bond_order` + + .. method:: GetOther(other_atom) + + Get the other atom. Returns the one of the two atoms that does not match + the given one. + + :param other_atom: The other atom + :type other_atom: :class:`AtomHandle` + :rtype: :class:`AtomHandle` + + .. method:: SetBondOrder(order) + + Set the bond order. See :meth:`GetBondOrder`. + + :param order: The bond order + :type order: :class:`int` + + See :attr:`bond_order` + + .. method:: IsValid() + + See :attr:`valid` + + .. method:: GetHashCode() + + See :attr:`hash_code` + + The View Classes -------------------------------------------------------------------------------- @@ -1411,7 +1510,7 @@ The View Classes See :attr:`bonds` - :rtype: :class:`BondHandleList` + :rtype: :class:`BondHandleList` (list of :class:`BondHandle`) .. method:: GetHandle() diff --git a/modules/mol/base/pymod/export_bond.cc b/modules/mol/base/pymod/export_bond.cc index 5dc6e6503fdc9ce9038e595fd3a78384c1e37d56..a972ce175dde361423a6ecbc5b59403749c84d6a 100644 --- a/modules/mol/base/pymod/export_bond.cc +++ b/modules/mol/base/pymod/export_bond.cc @@ -44,17 +44,20 @@ void export_Bond() .add_property("bond_order", &BondHandle::GetBondOrder, &BondHandle::SetBondOrder) + .add_property("valid", &BondHandle::IsValid) .def("GetFirst", &BondHandle::GetFirst) .def("GetSecond",&BondHandle::GetSecond) - .def("GetOther",&BondHandle::GetOther) + .def("GetOther",&BondHandle::GetOther, arg("other_atom")) .def("GetLength",&BondHandle::GetLength) .def("GetBondOrder",&BondHandle::GetBondOrder) - .def("SetBondOrder",&BondHandle::SetBondOrder) + .def("SetBondOrder",&BondHandle::SetBondOrder, arg("order")) .def("IsValid", &BondHandle::IsValid) .def("GetHashCode", &BondHandle::GetHashCode) .def(self == self) .def(self != self) .def(self_ns::str(self)) + .def("__hash__", &BondHandle::GetHashCode) + .add_property("hash_code", &BondHandle::GetHashCode) ; generic_prop_def<BondHandle>(bond_handle); class_<BondHandleList>("BondHandleList", no_init) diff --git a/modules/mol/base/src/bond_handle.hh b/modules/mol/base/src/bond_handle.hh index 76acfff537e1a6cbf09eea762cfd69fd78209dc2..26a9df83c87e9213371ffb72f939bca7e49be1f2 100644 --- a/modules/mol/base/src/bond_handle.hh +++ b/modules/mol/base/src/bond_handle.hh @@ -76,7 +76,6 @@ public: /// \brief get other atom /// Returns one of the two atoms that does not match the given one. - /// In python also available as the property other AtomHandle GetOther(const AtomHandle& a) const; //@}