From dc8b263676247f203962229f32913fb1eb467bd0 Mon Sep 17 00:00:00 2001 From: Amy He Date: Mon, 9 Dec 2024 23:03:01 -0800 Subject: [PATCH 1/4] remove from class Ring the unused attributes: graph and ring_corners --- meeko/molsetup.py | 23 ++--------------------- test/json_serialization_test.py | 5 ----- 2 files changed, 2 insertions(+), 26 deletions(-) diff --git a/meeko/molsetup.py b/meeko/molsetup.py index 6e50640..e2771e9 100644 --- a/meeko/molsetup.py +++ b/meeko/molsetup.py @@ -48,8 +48,6 @@ DEFAULT_BOND_ROTATABLE = False -DEFAULT_RING_CORNER_FLIP = False -DEFAULT_RING_GRAPH = [] DEFAULT_RING_IS_AROMATIC = False DEFAULT_RING_CLOSURE_BONDS_REMOVED = [] DEFAULT_RING_CLOSURE_PSEUDOS_BY_ATOM = defaultdict @@ -348,8 +346,6 @@ def from_json(obj: dict): @dataclass class Ring: ring_id: tuple - corner_flip: bool = DEFAULT_RING_CORNER_FLIP - graph: dict = field(default_factory=list) is_aromatic: bool = DEFAULT_RING_IS_AROMATIC @staticmethod @@ -374,16 +370,14 @@ def from_json(obj: dict): return obj # Check that all the keys we expect are in the object dictionary as a safety measure - expected_json_keys = {"ring_id", "corner_flip", "graph", "is_aromatic"} + expected_json_keys = {"ring_id", "is_aromatic"} if set(obj.keys()) != expected_json_keys: return obj # Constructs a Ring object from the provided keys. ring_id = string_to_tuple(obj["ring_id"], int) - corner_flip = obj["corner_flip"] - graph = obj["graph"] is_aromatic = obj["is_aromatic"] - output_ring = Ring(ring_id, corner_flip, graph, is_aromatic) + output_ring = Ring(ring_id, is_aromatic) return output_ring @@ -1524,8 +1518,6 @@ class RDKitMoleculeSetup(MoleculeSetup, MoleculeSetupExternalToolkit): a mapping from tuples of atom indices to dihedral labels atom_to_ring_id: dict() mapping of atom index to ring id of each atom belonging to the ring - ring_corners: dict() - unclear what this is a mapping of, but is used to store corner flexibility for the rings rmsd_symmetry_indices: tuple Tuples of the indices of the molecule's atoms that match a substructure query. needs info. @@ -1543,7 +1535,6 @@ def __init__(self, name: str = None, is_sidechain: bool = False): self.dihedral_partaking_atoms: dict = {} self.dihedral_labels: dict = {} self.atom_to_ring_id = {} - self.ring_corners = {} self.rmsd_symmetry_indices = () def copy(self): @@ -1976,11 +1967,6 @@ def perceive_rings(self, keep_chorded_rings: bool, keep_equivalent_rings: bool): ring_to_add = Ring(ring_atom_indices) if self._is_ring_aromatic(ring_atom_indices): ring_to_add.is_aromatic = True - for atom_idx in ring_atom_indices: - # TODO: add it to some sort of atom to ring id tracking thing -> add to atom data structure - ring_to_add.graph = self._recursive_graph_walk( - atom_idx, collected=[], exclude=list(ring_atom_indices) - ) self.rings[ring_atom_indices] = ring_to_add return @@ -2152,7 +2138,6 @@ def from_json(obj): "dihedral_partaking_atoms", "dihedral_labels", "atom_to_ring_id", - "ring_corners", "rmsd_symmetry_indices", } for key in expected_molsetup_keys: @@ -2179,7 +2164,6 @@ def from_json(obj): int(k): [string_to_tuple(t) for t in v] for k, v in obj["atom_to_ring_id"].items() } - rdkit_molsetup.ring_corners = obj["ring_corners"] rdkit_molsetup.rmsd_symmetry_indices = [ string_to_tuple(v) for v in obj["rmsd_symmetry_indices"] ] @@ -2280,8 +2264,6 @@ def default(self, obj): if isinstance(obj, Ring): return { "ring_id": tuple_to_string(obj.ring_id), - "corner_flip": obj.corner_flip, - "graph": obj.graph, "is_aromatic": obj.is_aromatic, } return json.JSONEncoder.default(self, obj) @@ -2387,7 +2369,6 @@ def default(self, obj): output_dict["dihedral_partaking_atoms"] = {tuple_to_string(k): v for k,v in obj.dihedral_partaking_atoms.items()} output_dict["dihedral_labels"] = {tuple_to_string(k): v for k,v in obj.dihedral_labels.items()} output_dict["atom_to_ring_id"] = obj.atom_to_ring_id - output_dict["ring_corners"] = obj.ring_corners output_dict["rmsd_symmetry_indices"] = obj.rmsd_symmetry_indices # If nothing is in the dict, then none of the possible object types for this encoder matched and we should # return the default JSON encoder. diff --git a/test/json_serialization_test.py b/test/json_serialization_test.py index 7101f31..589103c 100644 --- a/test/json_serialization_test.py +++ b/test/json_serialization_test.py @@ -525,11 +525,6 @@ def check_ring_equality(decoded_obj: Ring, starting_obj: Ring): """ assert isinstance(decoded_obj.ring_id, tuple) assert decoded_obj.ring_id == starting_obj.ring_id - assert isinstance(decoded_obj.corner_flip, bool) - assert decoded_obj.corner_flip == starting_obj.corner_flip - assert len(decoded_obj.graph) == len(starting_obj.graph) - for idx, val in enumerate(starting_obj.graph): - assert decoded_obj.graph[idx] == val assert isinstance(decoded_obj.is_aromatic, bool) assert decoded_obj.is_aromatic == starting_obj.is_aromatic return From b7ced6ce167f85165069986d87d689097b648144 Mon Sep 17 00:00:00 2001 From: Amy He Date: Tue, 10 Dec 2024 14:24:05 -0800 Subject: [PATCH 2/4] remove call to get_symmetries_for_rmsd from RDKitMoleculeSetup.from_mol() to reduce overhead --- meeko/molsetup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/meeko/molsetup.py b/meeko/molsetup.py index e2771e9..dbdeb84 100644 --- a/meeko/molsetup.py +++ b/meeko/molsetup.py @@ -1613,7 +1613,7 @@ def from_mol( molsetup.init_atom(compute_gasteiger_charges, read_charges_from_prop, coords) molsetup.init_bond() molsetup.perceive_rings(keep_chorded_rings, keep_equivalent_rings) - molsetup.rmsd_symmetry_indices = cls.get_symmetries_for_rmsd(mol) + # molsetup.rmsd_symmetry_indices = cls.get_symmetries_for_rmsd(mol) # to store sets of coordinates, e.g. docked poses, as dictionaries indexed by # the atom index, because not all atoms need to have new coordinates specified From 573a366ae117de4d57bfc7829d4f30e005fbc93f Mon Sep 17 00:00:00 2001 From: Amy He Date: Tue, 10 Dec 2024 14:44:27 -0800 Subject: [PATCH 3/4] remove is_aromatic from ring's attributes --- meeko/macrocycle.py | 4 +--- meeko/molsetup.py | 10 ++-------- test/json_serialization_test.py | 2 -- 3 files changed, 3 insertions(+), 13 deletions(-) diff --git a/meeko/macrocycle.py b/meeko/macrocycle.py index d3cd947..22700d1 100644 --- a/meeko/macrocycle.py +++ b/meeko/macrocycle.py @@ -83,9 +83,7 @@ def collect_rings(self, setup): setup.rings.keys() ): # ring_id are the atom indices in each ring size = len(ring_id) - if setup.rings[ring_id].is_aromatic: - rigid_rings.append(ring_id) - elif size < self._min_ring_size: + if size < self._min_ring_size: rigid_rings.append(ring_id) # do not add rings > _max_ring_size to rigid_rings # because bonds in rigid rings will not be breakable diff --git a/meeko/molsetup.py b/meeko/molsetup.py index dbdeb84..211f7ef 100644 --- a/meeko/molsetup.py +++ b/meeko/molsetup.py @@ -48,7 +48,6 @@ DEFAULT_BOND_ROTATABLE = False -DEFAULT_RING_IS_AROMATIC = False DEFAULT_RING_CLOSURE_BONDS_REMOVED = [] DEFAULT_RING_CLOSURE_PSEUDOS_BY_ATOM = defaultdict # endregion @@ -346,7 +345,6 @@ def from_json(obj: dict): @dataclass class Ring: ring_id: tuple - is_aromatic: bool = DEFAULT_RING_IS_AROMATIC @staticmethod def from_json(obj: dict): @@ -370,14 +368,13 @@ def from_json(obj: dict): return obj # Check that all the keys we expect are in the object dictionary as a safety measure - expected_json_keys = {"ring_id", "is_aromatic"} + expected_json_keys = {"ring_id"} if set(obj.keys()) != expected_json_keys: return obj # Constructs a Ring object from the provided keys. ring_id = string_to_tuple(obj["ring_id"], int) - is_aromatic = obj["is_aromatic"] - output_ring = Ring(ring_id, is_aromatic) + output_ring = Ring(ring_id) return output_ring @@ -1965,8 +1962,6 @@ def perceive_rings(self, keep_chorded_rings: bool, keep_equivalent_rings: bool): rings = hjk_ring_detection.scan(keep_chorded_rings, keep_equivalent_rings) for ring_atom_indices in rings: ring_to_add = Ring(ring_atom_indices) - if self._is_ring_aromatic(ring_atom_indices): - ring_to_add.is_aromatic = True self.rings[ring_atom_indices] = ring_to_add return @@ -2264,7 +2259,6 @@ def default(self, obj): if isinstance(obj, Ring): return { "ring_id": tuple_to_string(obj.ring_id), - "is_aromatic": obj.is_aromatic, } return json.JSONEncoder.default(self, obj) diff --git a/test/json_serialization_test.py b/test/json_serialization_test.py index 589103c..f13112d 100644 --- a/test/json_serialization_test.py +++ b/test/json_serialization_test.py @@ -525,8 +525,6 @@ def check_ring_equality(decoded_obj: Ring, starting_obj: Ring): """ assert isinstance(decoded_obj.ring_id, tuple) assert decoded_obj.ring_id == starting_obj.ring_id - assert isinstance(decoded_obj.is_aromatic, bool) - assert decoded_obj.is_aromatic == starting_obj.is_aromatic return From cd40ef138b0ad079c042e7f1586f23a4f45874d4 Mon Sep 17 00:00:00 2001 From: diogom Date: Tue, 10 Dec 2024 16:23:41 -0800 Subject: [PATCH 4/4] test aromatic rings unbreakable with macrocycle code --- test/macrocycle_test.py | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/test/macrocycle_test.py b/test/macrocycle_test.py index 9bae4b7..7463f4c 100644 --- a/test/macrocycle_test.py +++ b/test/macrocycle_test.py @@ -62,6 +62,30 @@ def test_external_ring_closure(): mindist = min(mindist, abs(x - xcoord)) assert(mindist < 1e-3) # loose matching +def test_aromatic_rings_unbroken(): + write_pdbqt = PDBQTWriterLegacy.write_string + mol = Chem.MolFromMolFile(filenames["macrocycle3"], removeHs=False) + mk_prep = MoleculePreparation(min_ring_size=5) + setups = mk_prep(mol) + assert len(setups) == 1 + setup = setups[0] + nr_rot = sum([b.rotatable for _, b in setup.bond_info.items()]) + assert nr_rot == 9 + pdbqt_string, is_ok, error_msg = write_pdbqt(setup) + assert pdbqt_string.count("ENDBRANCH") == 8 + # now we will check that a 6-member ring that is not aromatic does break + # when we set the min ring size to six or less + mol2 = Chem.MolFromMolFile(filenames["macrocycle5"], removeHs=False) + setups_broken = mk_prep(mol2) + mk_prep = MoleculePreparation(min_ring_size=7) + setups_unbroken = mk_prep(mol2) + assert len(setups_broken) == 1 + assert len(setups_unbroken) == 1 + pdbqt_broken, is_ok, err = write_pdbqt(setups_broken[0]) + pdbqt_unbroken, is_ok, err = write_pdbqt(setups_unbroken[0]) + assert pdbqt_broken.count("ENDBRANCH") > pdbqt_unbroken.count("ENDBRANCH") + + def run(molname): filename = filenames[molname] mol = Chem.MolFromMolFile(filename, removeHs=False) @@ -76,3 +100,4 @@ def run(molname): def test_all(): for molname in num_cycle_breaks: run(molname) +