diff --git a/python/docs/nuri.core.rst b/python/docs/nuri.core.rst index eb92bc51..015efcda 100644 --- a/python/docs/nuri.core.rst +++ b/python/docs/nuri.core.rst @@ -18,6 +18,7 @@ nuri.core .. automethod:: num_atoms .. automethod:: bonds .. automethod:: bond + .. automethod:: has_bond .. automethod:: num_bonds .. automethod:: neighbor .. automethod:: mutator diff --git a/python/src/nuri/core/molecule.cpp b/python/src/nuri/core/molecule.cpp index 4aea6258..9db7433d 100644 --- a/python/src/nuri/core/molecule.cpp +++ b/python/src/nuri/core/molecule.cpp @@ -617,6 +617,34 @@ Get a bond of the molecule. ``src`` and ``dst`` are interchangeable. The returned bond is invalidated when a mutator context is exited. If the bond must be kept alive, copy the bond data first with :meth:`Bond.copy_data` method. +)doc") + .def( + "has_bond", + [](PyMol &self, int src, int dst) { + std::tie(src, dst) = check_bond_ends(*self, src, dst); + return self->find_bond(src, dst) != self->bond_end(); + }, + py::arg("src"), py::arg("dst"), R"doc( +Check if two atoms are connected by a bond. + +:param src: The source atom of the bond. +:param dst: The destination atom of the bond. +:returns: Whether the source and destination atoms are connected by a bond. +:raises IndexError: If the source or destination atom does not exist. +)doc") + .def( + "has_bond", + [](PyMol &self, PyAtom &src, PyAtom &dst) { + auto [sa, da] = check_bond_ends(*self, src, dst); + return self->find_bond(sa, da) != self->bond_end(); + }, + py::arg("src"), py::arg("dst"), R"doc( +Check if two atoms are connected by a bond. + +:param src: The source atom of the bond. +:param dst: The destination atom of the bond. +:returns: Whether the source and destination atoms are connected by a bond. +:raises ValueError: If any of the atoms does not belong to the molecule. )doc") .def( "num_bonds", [](PyMol &self) { return self->num_bonds(); }, R"doc( @@ -809,7 +837,7 @@ Remove a conformation from the molecule. Get the number of conformations of the molecule. )doc") .def( - "clear_confs", [](PyMol &self) { return self->confs().clear(); }, + "clear_confs", [](PyMol &self) { self->confs().clear(); }, R"doc( Remove all conformations from the molecule. )doc") diff --git a/python/src/nuri/core/substructure.cpp b/python/src/nuri/core/substructure.cpp index ef0ca2be..312bfc3a 100644 --- a/python/src/nuri/core/substructure.cpp +++ b/python/src/nuri/core/substructure.cpp @@ -430,6 +430,34 @@ Get a bond of the substructure. ``src`` and ``dst`` are interchangeable. The returned bond is invalidated when the parent molecule is modified, or if the substructure is modified. If the bond must be kept alive, copy the bond data first. +)doc"); + sub.def( + "has_bond", + [](P &self, PySubAtom

&src, PySubAtom

&dst) { + auto [sa, da] = check_subbond_ends(*self, src, dst); + return self->find_bond(sa, da) != self->bond_end(); + }, + py::arg("src"), py::arg("dst"), R"doc( +Check if two atoms are connected by a bond. + +:param src: The source sub-atom of the bond. +:param dst: The destination sub-atom of the bond. +:returns: Whether the source and destination atoms are connected by a bond. +:raises ValueError: If any of the sub-atoms does not belong to the substructure. +)doc"); + sub.def( + "has_bond", + [](P &self, PyAtom &src, PyAtom &dst) { + auto [sa, da] = check_bond_ends(*self.parent(), src, dst); + return self->find_bond(sa, da) != self->bond_end(); + }, + py::arg("src"), py::arg("dst"), R"doc( +Check if two atoms are connected by a bond. + +:param src: The source atom of the bond. +:param dst: The destination atom of the bond. +:returns: Whether the source and destination atoms are connected by a bond. +:raises ValueError: If any of the atoms does not belong to the molecule. )doc"); sub.def( "num_bonds", [](P &self) { return self->num_bonds(); }, diff --git a/python/test/core/molecule_test.py b/python/test/core/molecule_test.py index 81b9b908..279081b1 100644 --- a/python/test/core/molecule_test.py +++ b/python/test/core/molecule_test.py @@ -153,6 +153,8 @@ def test_add_bond(): assert {bond.src.id, bond.dst.id} == {0, 1} assert bond.src.atomic_number == 6 assert bond.dst.atomic_number == 6 + assert mol.has_bond(0, 1) + assert mol.has_bond(bond.src, bond.dst) i = -1 for i, bond in enumerate(mol.bonds(), 1): @@ -165,6 +167,9 @@ def test_add_bond(): a3 = mut.add_atom(6) a4 = mut.add_atom(6) + assert not mol.has_bond(a3.id, a4.id) + assert not mol.has_bond(a3, a4) + mut.add_bond(a3, a4, BondData(BondOrder.Triple)) mut.add_bond(1, 3, BondData(BondOrder.Aromatic)) diff --git a/python/test/core/substructure_test.py b/python/test/core/substructure_test.py index 7d80f7c3..2a63c483 100644 --- a/python/test/core/substructure_test.py +++ b/python/test/core/substructure_test.py @@ -211,14 +211,17 @@ def test_neighbors(molsub: Molecule): def test_find_bond(molsub: Molecule): sub = molsub.subs[0] + assert not sub.has_bond(sub[0], sub[2]) with pytest.raises(ValueError, match="no such bond"): sub.bond(sub[0], sub[2]) bond_01 = sub.bond(sub[0], sub[1]) bond_10 = sub.bond(sub[1], sub[0]) + assert sub.has_bond(sub[0], sub[1]) bond_01_atoms = sub.bond(sub[0].as_parent(), sub[1].as_parent()) bond_10_atoms = sub.bond(sub[1].as_parent(), sub[0].as_parent()) + assert sub.has_bond(sub[0].as_parent(), sub[1].as_parent()) assert bond_01.src.id == bond_10.src.id assert bond_01.dst.id == bond_10.dst.id