def to_rdkit_mol_without_metals(mol, metal_atoms, metal_bonds): """ Create :class:`rdkit.Mol` with metals replaced by H atoms. Parameters ---------- mol : :class:`.Molecule` The molecule to be optimized. metal_atoms : :class:`.list` of :class:`stk.Atom` List of metal atoms. metal_bonds : :class:`.list` of :class:`stk.Bond` List of bonds including metal atoms. Returns ------- edit_mol : :class:`rdkit.Mol` RDKit molecule with metal atoms replaced with H atoms. """ edit_mol = rdkit.EditableMol(rdkit.Mol()) for atom in mol.get_atoms(): if atom in metal_atoms: # In place of metals, add H's that will be constrained. # This allows the atom ids to not be changed. rdkit_atom = rdkit.Atom(1) rdkit_atom.SetFormalCharge(0) else: rdkit_atom = rdkit.Atom(atom.get_atomic_number()) rdkit_atom.SetFormalCharge(atom.get_charge()) edit_mol.AddAtom(rdkit_atom) for bond in mol.get_bonds(): if bond in metal_bonds: # Do not add bonds to metal atoms (replaced with H's). continue edit_mol.AddBond( beginAtomIdx=bond.get_atom1().get_id(), endAtomIdx=bond.get_atom2().get_id(), order=rdkit.BondType(bond.get_order()) ) edit_mol = edit_mol.GetMol() rdkit_conf = rdkit.Conformer(mol.get_num_atoms()) for atom_id, atom_coord in enumerate(mol.get_position_matrix()): rdkit_conf.SetAtomPosition(atom_id, atom_coord) edit_mol.GetAtomWithIdx(atom_id).SetNoImplicit(True) edit_mol.AddConformer(rdkit_conf) return edit_mol
def remake(mol): """ Remakes a molecule from scratch. Parameters ---------- mol : :class:`rdkit.Mol` The molecule to be remade. Returns ------- :class:`rdkit.Mol` The remade molecule. """ emol = rdkit.EditableMol(rdkit.Mol()) for atom in mol.GetAtoms(): new = rdkit.Atom(atom.GetAtomicNum()) new.SetFormalCharge(atom.GetFormalCharge()) emol.AddAtom(new) for bond in mol.GetBonds(): emol.AddBond(beginAtomIdx=bond.GetBeginAtomIdx(), endAtomIdx=bond.GetEndAtomIdx(), order=bond.GetBondType()) m = emol.GetMol() m.AddConformer(mol.GetConformer()) return m
def to_rdkit_mol(self): """ Return an :mod:`rdkit` representation. Returns ------- :class:`rdkit.Mol` The molecule in :mod:`rdkit` format. """ mol = rdkit.EditableMol(rdkit.Mol()) for atom in self._atoms: rdkit_atom = rdkit.Atom(atom.get_atomic_number()) rdkit_atom.SetFormalCharge(atom.get_charge()) mol.AddAtom(rdkit_atom) for bond in self._bonds: mol.AddBond( beginAtomIdx=bond.get_atom1().get_id(), endAtomIdx=bond.get_atom2().get_id(), order=(rdkit.BondType.DATIVE if bond.get_order() == 9 else rdkit.BondType(bond.get_order())), ) mol = mol.GetMol() rdkit_conf = rdkit.Conformer(len(self._atoms)) for atom_id, atom_coord in enumerate(self._position_matrix.T): rdkit_conf.SetAtomPosition(atom_id, atom_coord) mol.GetAtomWithIdx(atom_id).SetNoImplicit(True) mol.AddConformer(rdkit_conf) return mol
def remake_mol(mol): """ Creates a copy of `mol` from scratch. This fixes f****d up valences which occur otherwise. Paramters --------- mol : :class:`rdkit.Chem.rdchem.Mol` The molecule to be copied. Returns ------- class:`rdkit.Chem.rdchem.Mol` A copy of `mol`. """ emol = rdkit.EditableMol(rdkit.Mol()) for atom in mol.GetAtoms(): emol.AddAtom(rdkit.Atom(atom.GetAtomicNum())) for bond in mol.GetBonds(): emol.AddBond(bond.GetBeginAtomIdx(), bond.GetEndAtomIdx(), bond.GetBondType()) new_mol = emol.GetMol() new_mol.AddConformer(rdkit.Conformer(mol.GetConformer())) return new_mol
def to_rdkit_mol(self): """ Return an :mod:`rdkit` representation. Returns ------- :class:`rdkit.Mol` The molecule in :mod:`rdkit` format. """ mol = rdkit.EditableMol(rdkit.Mol()) for atom in self.atoms: rdkit_atom = rdkit.Atom(atom.atomic_number) rdkit_atom.SetFormalCharge(atom.charge) mol.AddAtom(rdkit_atom) for bond in self.bonds: mol.AddBond(beginAtomIdx=bond.atom1.id, endAtomIdx=bond.atom2.id, order=rdkit.BondType(bond.order)) mol = mol.GetMol() rdkit_conf = rdkit.Conformer(len(self.atoms)) for atom_id, atom_coord in enumerate(self._position_matrix.T): rdkit_conf.SetAtomPosition(atom_id, atom_coord) mol.AddConformer(rdkit_conf) return mol
def add_atom(childGenes, GeneSet, oldGene): geneSet = GeneSet.Atoms newGeneNumber = childGenes.RWMol.GetNumAtoms() newGene = random.sample(geneSet, 1)[0] childGenes.RWMol.AddAtom(Chem.Atom(newGene)) childGenes.RWMol.AddBond(newGeneNumber, oldGene, Chem.BondType.SINGLE) return childGenes
def get_rdkit_mol(molecule): rdkit_mol = rdkit.EditableMol(rdkit.Mol()) for atom in molecule.get_atoms(): rdkit_atom = rdkit.Atom(atom.get_atomic_number()) rdkit_atom.SetFormalCharge(atom.get_charge()) rdkit_mol.AddAtom(rdkit_atom) for bond in molecule.get_bonds(): rdkit_mol.AddBond( beginAtomIdx=bond.get_atom1().get_id(), endAtomIdx=bond.get_atom2().get_id(), order=(rdkit.BondType.DATIVE if bond.get_order() == 9 else rdkit.BondType(bond.get_order())), ) return rdkit_mol.GetMol()
def with_hydrogens(molecule): rdkit_molecule = rdkit.EditableMol(rdkit.Mol()) for atom in molecule.get_atoms(): rdkit_atom = rdkit.Atom(atom.get_atomic_number()) rdkit_atom.SetFormalCharge(atom.get_charge()) rdkit_molecule.AddAtom(rdkit_atom) for bond in molecule.get_bonds(): rdkit_molecule.AddBond( beginAtomIdx=bond.get_atom1_id(), endAtomIdx=bond.get_atom2_id(), order=rdkit.BondType(bond.get_order()), ) rdkit_molecule = rdkit_molecule.GetMol() rdkit.SanitizeMol(rdkit_molecule) return rdkit.AddHs(rdkit_molecule)
def remake(mol): """ Remakes a molecule from scratch. Parameters ---------- mol : :class:`rdkit.Mol` The molecule to be remade. Returns ------- :class:`rdkit.Mol` The remade molecule. """ emol = rdkit.EditableMol(rdkit.Mol()) for a in mol.GetAtoms(): new_atom = rdkit.Atom(a.GetAtomicNum()) # Set properties. for pname, pval in a.GetPropsAsDict(False, False).items(): if isinstance(pval, int): new_atom.SetIntProp(pname, pval) elif isinstance(pval, bool): new_atom.SetBoolProp(pname, pval) else: new_atom.SetProp(pname, pval) new_atom.SetFormalCharge(a.GetFormalCharge()) emol.AddAtom(new_atom) for bond in mol.GetBonds(): emol.AddBond(bond.GetBeginAtomIdx(), bond.GetEndAtomIdx(), bond.GetBondType()) m = emol.GetMol() m.AddConformer(rdkit.Conformer(mol.GetConformer())) for a in m.GetAtoms(): a.UpdatePropertyCache() rdkit.GetSSSR(m) return m
def vabene_to_smiles(molecule): editable = rdkit.EditableMol(rdkit.Mol()) for atom in molecule.get_atoms(): rdkit_atom = rdkit.Atom(atom.get_atomic_number()) rdkit_atom.SetFormalCharge(atom.get_charge()) editable.AddAtom(rdkit_atom) for bond in molecule.get_bonds(): editable.AddBond( beginAtomIdx=bond.get_atom1_id(), endAtomIdx=bond.get_atom2_id(), order=rdkit.BondType(bond.get_order()), ) rdkit_molecule = editable.GetMol() rdkit.SanitizeMol(rdkit_molecule) rdkit.Kekulize(rdkit_molecule) return rdkit.MolToSmiles(rdkit_molecule)
def mol_from_mol_file(mol_file): """ Creates a rdkit molecule from a ``.mol`` (V3000) file. Parameters ---------- mol_file : :class:`str` The full of the .mol file from which an rdkit molecule should be instantiated. Returns ------- :class:`rdkit.Mol` An rdkit instance of the molecule held in `mol2_file`. Raises ------ :class:`ChargedMolError` If an atom row has more than 8 coloumns it is usually because there is a 9th coloumn indicating atomic charge. Such molecules are not currently supported, so an error is raised. :class:`MolFileError` If the file is not a V3000 ``.mol`` file. """ e_mol = rdkit.EditableMol(rdkit.Mol()) conf = rdkit.Conformer() with open(mol_file, 'r') as f: take_atom = False take_bond = False v3000 = False for line in f: if 'V3000' in line: v3000 = True if 'M V30 BEGIN ATOM' in line: take_atom = True continue if 'M V30 END ATOM' in line: take_atom = False continue if 'M V30 BEGIN BOND' in line: take_bond = True continue if 'M V30 END BOND' in line: take_bond = False continue if take_atom: words = line.split() if len(words) > 8: raise ChargedMolError(mol_file, ('Atom row has more' ' than 8 coloumns. Likely ' 'due to a charged atom.')) _, _, _, atom_sym, *coords, _ = words coords = [float(x) for x in coords] atom_coord = Point3D(*coords) atom_id = e_mol.AddAtom(rdkit.Atom(atom_sym)) conf.SetAtomPosition(atom_id, atom_coord) continue if take_bond: *_, bond_id, bond_order, atom1, atom2 = line.split() e_mol.AddBond(int(atom1)-1, int(atom2)-1, bond_dict[bond_order]) continue if not v3000: raise MolFileError(mol_file, 'Not a V3000 .mol file.') mol = e_mol.GetMol() mol.AddConformer(conf) return mol
def init_from_vabene_molecule( cls, molecule: vabene.Molecule, functional_groups: typing.Iterable[typing.Union[ FunctionalGroup, FunctionalGroupFactory]] = (), placer_ids: typing.Optional[tuple[int, ...]] = None, position_matrix: typing.Optional[np.ndarray] = None, ) -> BuildingBlock: """ Initialize from a :mod:`vabene.Molecule`. Notes: The molecule is given 3D coordinates with :func:`rdkit.ETKDGv2()`. Parameters: molecule: The :class:`vabene.Molecule` from which to initialize. functional_groups: An :class:`iterable` of :class:`.FunctionalGroup` or :class:`.FunctionalGroupFactory` or both. :class:`.FunctionalGroup` instances are added to the building block and :class:`.FunctionalGroupFactory` instances are used to create :class:`.FunctionalGroup` instances the building block should hold. :class:`.FunctionalGroup` instances are used to identify which atoms are modified during :class:`.ConstructedMolecule` construction. placer_ids: The ids of *placer* atoms. These are the atoms which should be used for calculating the position of the building block. Depending on the values passed to `placer_ids`, and the functional groups in the building block, different *placer* ids will be used by the building block. #. `placer_ids` is passed to the initializer: the passed *placer* ids will be used by the building block. #. `placer_ids` is ``None`` and the building block has functional groups: The *placer* ids of the functional groups will be used as the *placer* ids of the building block. #. `placer_ids` is ``None`` and `functional_groups` is empty. All atoms of the molecule will be used for *placer* ids. position_matrix: The position matrix the building block should use. If ``None``, :func:`rdkit.ETKDGv2` will be used to calculate it. Returns: The building block. Raises: :class:`RuntimeError` If embedding the molecule fails. """ editable = rdkit.EditableMol(rdkit.Mol()) for atom in molecule.get_atoms(): rdkit_atom = rdkit.Atom(atom.get_atomic_number()) rdkit_atom.SetFormalCharge(atom.get_charge()) editable.AddAtom(rdkit_atom) for bond in molecule.get_bonds(): editable.AddBond( beginAtomIdx=bond.get_atom1_id(), endAtomIdx=bond.get_atom2_id(), order=rdkit.BondType(bond.get_order()), ) rdkit_molecule = editable.GetMol() rdkit.SanitizeMol(rdkit_molecule) rdkit_molecule = rdkit.AddHs(rdkit_molecule) if position_matrix is None: params = rdkit.ETKDGv2() random_seed = 4 params.randomSeed = random_seed if rdkit.EmbedMolecule(rdkit_molecule, params) == -1: raise RuntimeError( f'Embedding with seed value of {random_seed} ' 'failed.') else: # Make sure the position matrix always holds floats. position_matrix = np.array( position_matrix, dtype=np.float64, ) conformer = rdkit.Conformer(rdkit_molecule.GetNumAtoms()) for atom_id, position in enumerate(position_matrix): conformer.SetAtomPosition(atom_id, position) rdkit_molecule.AddConformer(conformer) rdkit.Kekulize(rdkit_molecule) return cls.init_from_rdkit_mol( molecule=rdkit_molecule, functional_groups=functional_groups, placer_ids=placer_ids, )
def split(self, molecule): """ Split a molecule into multiple molecules. Parameters ---------- molecule : :class:`stk.Molecule` Molecule to split. Returns ------- :class:`iterable` of :class:`stk.BuildingBlock` The resulting list of building blocks, with new functional groups assigned. """ new_building_blocks = [] rdkit_mol = molecule.to_rdkit_mol() rdkit.SanitizeMol(rdkit_mol) bond_pair_ids_to_delete = [] reactable_atom_ids = [] for atom_ids in rdkit_mol.GetSubstructMatches( query=rdkit.MolFromSmarts(self._breaker_smarts)): translated_bond_atom_ids = ( atom_ids[self._bond_deleter_ids[0]], atom_ids[self._bond_deleter_ids[1]], ) bond_pair_ids_to_delete.extend( tuple(sorted((i, j))) for i, j in combinations(translated_bond_atom_ids, 2)) reactable_atom_ids.extend(i for i in translated_bond_atom_ids) bond_ids_to_delete = [] for bond in rdkit_mol.GetBonds(): idxs = tuple(sorted( (bond.GetBeginAtomIdx(), bond.GetEndAtomIdx()))) if idxs in bond_pair_ids_to_delete: bond_ids_to_delete.append(bond.GetIdx()) # Delete bonds and atoms. fragments = rdkit.GetMolFrags(rdkit.FragmentOnBonds( mol=rdkit_mol, bondIndices=tuple(bond_ids_to_delete), addDummies=True, ), asMols=True) # Add replacers bonded to * atoms. for i in rdkit.MolFromSmarts(self._replacer_smarts).GetAtoms(): replacer_atom = rdkit.Atom(i.GetAtomicNum()) replacer_atom.SetFormalCharge(0) break for frag in fragments: editable = rdkit.EditableMol(frag) for atom in frag.GetAtoms(): # 0 if dummy atom. if atom.GetAtomicNum() == 0: editable.ReplaceAtom(atom.GetIdx(), replacer_atom) frag_rdkit = editable.GetMol() rdkit.Kekulize(frag_rdkit) new_building_blocks.append( stk.BuildingBlock.init_from_rdkit_mol( molecule=frag_rdkit, functional_groups=self._functional_groups)) return new_building_blocks
def amine3_with_amine3(mol, del_atoms, fg1, fg2): """ Crates bonds between functional groups. Parameters ---------- mol : :class:`rdkit.Chem.rdchem.Mol` A molecule being assembled. del : :class:`bool` Toggles if atoms with the ``'del'`` property are deleted. fg1 : :class:`int` The id of the first functional group which is to be joined, as given by the 'fg_id' property. fg2 : :class:`int` The id of the second functional group which is to be joined, as given by the 'fg_id' property. Returns ------- :class:`tuple` The first element is an :class:`rdkit.Chem.rdchem.Mol`. It is the molecule with bonds added between the functional groups. The second element is a :class:`int`. It is the number of bonds added. """ fgs = {fg1, fg2} atoms1, atoms2 = {}, {} deleters = [] for a in mol.GetAtoms(): if not a.HasProp('fg_id') or a.GetIntProp('fg_id') not in fgs: continue if a.HasProp('bonder') and a.GetIntProp('fg_id') == fg1: atoms1[a.GetSymbol()] = a.GetIdx() if a.HasProp('bonder') and a.GetIntProp('fg_id') == fg2: atoms2[a.GetSymbol()] = a.GetIdx() if a.HasProp('del'): deleters.append(a.GetIdx()) conf = mol.GetConformer() n1_pos = np.array([*conf.GetAtomPosition(atoms1['N'])]) n2_pos = np.array([*conf.GetAtomPosition(atoms2['N'])]) c1_pos = np.array([*conf.GetAtomPosition(atoms1['C'])]) c2_pos = np.array([*conf.GetAtomPosition(atoms2['C'])]) emol = rdkit.EditableMol(mol) n_joiner = emol.AddAtom(rdkit.Atom(6)) n_joiner_pos = (n1_pos + n2_pos) / 2 nh1 = emol.AddAtom(rdkit.Atom(1)) nh1_pos = n_joiner_pos + np.array([0, 0, 1]) nh2 = emol.AddAtom(rdkit.Atom(1)) nh2_pos = n_joiner_pos + np.array([0, 0, -1]) nc_joiner1 = emol.AddAtom(rdkit.Atom(6)) nc_joiner1_pos = (c1_pos + n2_pos) / 2 nc1h1 = emol.AddAtom(rdkit.Atom(1)) nc1h1_pos = nc_joiner1_pos + np.array([0, 0, 1]) nc1h2 = emol.AddAtom(rdkit.Atom(1)) nc1h2_pos = nc_joiner1_pos + np.array([0, 0, -1]) nc_joiner2 = emol.AddAtom(rdkit.Atom(6)) nc_joiner2_pos = (c2_pos + n1_pos) / 2 nc2h1 = emol.AddAtom(rdkit.Atom(1)) nc2h1_pos = nc_joiner2_pos + np.array([0, 0, 1]) nc2h2 = emol.AddAtom(rdkit.Atom(1)) nc2h2_pos = nc_joiner2_pos + np.array([0, 0, -1]) single = rdkit.rdchem.BondType.SINGLE emol.AddBond(atoms1['N'], n_joiner, single) emol.AddBond(atoms2['N'], n_joiner, single) emol.AddBond(n_joiner, nh1, single) emol.AddBond(n_joiner, nh2, single) emol.AddBond(atoms1['C'], nc_joiner1, single) emol.AddBond(atoms2['N'], nc_joiner1, single) emol.AddBond(nc_joiner1, nc1h1, single) emol.AddBond(nc_joiner1, nc1h2, single) emol.AddBond(atoms2['C'], nc_joiner2, single) emol.AddBond(atoms1['N'], nc_joiner2, single) emol.AddBond(nc_joiner2, nc2h1, single) emol.AddBond(nc_joiner2, nc2h2, single) mol = emol.GetMol() conf = mol.GetConformer() conf.SetAtomPosition(n_joiner, rdkit_geo.Point3D(*n_joiner_pos)) conf.SetAtomPosition(nh1, rdkit_geo.Point3D(*nh1_pos)) conf.SetAtomPosition(nh2, rdkit_geo.Point3D(*nh2_pos)) conf.SetAtomPosition(nc_joiner1, rdkit_geo.Point3D(*nc_joiner1_pos)) conf.SetAtomPosition(nc1h1, rdkit_geo.Point3D(*nc1h1_pos)) conf.SetAtomPosition(nc1h2, rdkit_geo.Point3D(*nc1h2_pos)) conf.SetAtomPosition(nc_joiner2, rdkit_geo.Point3D(*nc_joiner2_pos)) conf.SetAtomPosition(nc2h1, rdkit_geo.Point3D(*nc2h1_pos)) conf.SetAtomPosition(nc2h2, rdkit_geo.Point3D(*nc2h2_pos)) if del_atoms: emol = rdkit.EditableMol(mol) for a in reversed(deleters): emol.RemoveAtom(a) mol = emol.GetMol() return mol, 6
def ring_amine_with_ring_amine(self, fg1, fg2): """ Creates bonds between functional groups. Parameters ---------- fg1 : :class:`.FunctionalGroup` A functional group which undergoes the reaction. fg2 : :class:`.FunctionalGroup` A functional group which undergoes the reaction. Returns ------- None : :class:`NoneType` """ c1 = next(a for a in fg1.bonder_ids if self.mol.GetAtomWithIdx(a).GetAtomicNum() == 6) n1 = next(a for a in fg1.bonder_ids if self.mol.GetAtomWithIdx(a).GetAtomicNum() == 7) c2 = next(a for a in fg2.bonder_ids if self.mol.GetAtomWithIdx(a).GetAtomicNum() == 6) n2 = next(a for a in fg2.bonder_ids if self.mol.GetAtomWithIdx(a).GetAtomicNum() == 7) conf = self.mol.GetConformer() n1_pos = np.array([*conf.GetAtomPosition(n1)]) n2_pos = np.array([*conf.GetAtomPosition(n2)]) c1_pos = np.array([*conf.GetAtomPosition(c1)]) c2_pos = np.array([*conf.GetAtomPosition(c2)]) n_joiner = self.emol.AddAtom(rdkit.Atom(6)) n_joiner_pos = (n1_pos + n2_pos) / 2 self.new_atom_coords.append((n_joiner, n_joiner_pos)) nh1 = self.emol.AddAtom(rdkit.Atom(1)) nh1_pos = n_joiner_pos + np.array([0, 0, 1]) self.new_atom_coords.append((nh1, nh1_pos)) nh2 = self.emol.AddAtom(rdkit.Atom(1)) nh2_pos = n_joiner_pos + np.array([0, 0, -1]) self.new_atom_coords.append((nh2, nh2_pos)) nc_joiner1 = self.emol.AddAtom(rdkit.Atom(6)) nc_joiner1_pos = (c1_pos + n2_pos) / 2 self.new_atom_coords.append((nc_joiner1, nc_joiner1_pos)) nc1h1 = self.emol.AddAtom(rdkit.Atom(1)) nc1h1_pos = nc_joiner1_pos + np.array([0, 0, 1]) self.new_atom_coords.append((nc1h1, nc1h1_pos)) nc1h2 = self.emol.AddAtom(rdkit.Atom(1)) nc1h2_pos = nc_joiner1_pos + np.array([0, 0, -1]) self.new_atom_coords.append((nc1h2, nc1h2_pos)) nc_joiner2 = self.emol.AddAtom(rdkit.Atom(6)) nc_joiner2_pos = (c2_pos + n1_pos) / 2 self.new_atom_coords.append((nc_joiner2, nc_joiner2_pos)) nc2h1 = self.emol.AddAtom(rdkit.Atom(1)) nc2h1_pos = nc_joiner2_pos + np.array([0, 0, 1]) self.new_atom_coords.append((nc2h1, nc2h1_pos)) nc2h2 = self.emol.AddAtom(rdkit.Atom(1)) nc2h2_pos = nc_joiner2_pos + np.array([0, 0, -1]) self.new_atom_coords.append((nc2h2, nc2h2_pos)) single = rdkit.rdchem.BondType.SINGLE self.emol.AddBond(n1, n_joiner, single) self.emol.AddBond(n2, n_joiner, single) self.emol.AddBond(n_joiner, nh1, single) self.emol.AddBond(n_joiner, nh2, single) self.emol.AddBond(c1, nc_joiner1, single) self.emol.AddBond(n2, nc_joiner1, single) self.emol.AddBond(nc_joiner1, nc1h1, single) self.emol.AddBond(nc_joiner1, nc1h2, single) self.emol.AddBond(c2, nc_joiner2, single) self.emol.AddBond(n1, nc_joiner2, single) self.emol.AddBond(nc_joiner2, nc2h1, single) self.emol.AddBond(nc_joiner2, nc2h2, single) self.bonds_made += 6
def convert_ob_mol_to_rd_mol(ob_mol, struct=None): '''Convert OBMol to RDKit mol, fixing up issues''' ob_mol.DeleteHydrogens() n_atoms = ob_mol.NumAtoms() rd_mol = Chem.RWMol() rd_conf = Chem.Conformer(n_atoms) for ob_atom in ob.OBMolAtomIter(ob_mol): rd_atom = Chem.Atom(ob_atom.GetAtomicNum()) #TODO copy format charge if ob_atom.IsAromatic() and ob_atom.IsInRing( ) and ob_atom.MemberOfRingSize() <= 6: #don't commit to being aromatic unless rdkit will be okay with the ring status #(this can happen if the atoms aren't fit well enough) rd_atom.SetIsAromatic(True) i = rd_mol.AddAtom(rd_atom) ob_coords = ob_atom.GetVector() x = ob_coords.GetX() y = ob_coords.GetY() z = ob_coords.GetZ() rd_coords = Geometry.Point3D(x, y, z) rd_conf.SetAtomPosition(i, rd_coords) rd_mol.AddConformer(rd_conf) for ob_bond in ob.OBMolBondIter(ob_mol): i = ob_bond.GetBeginAtomIdx() - 1 j = ob_bond.GetEndAtomIdx() - 1 bond_order = ob_bond.GetBondOrder() if bond_order == 1: rd_mol.AddBond(i, j, Chem.BondType.SINGLE) elif bond_order == 2: rd_mol.AddBond(i, j, Chem.BondType.DOUBLE) elif bond_order == 3: rd_mol.AddBond(i, j, Chem.BondType.TRIPLE) else: raise Exception('unknown bond order {}'.format(bond_order)) if ob_bond.IsAromatic(): bond = rd_mol.GetBondBetweenAtoms(i, j) bond.SetIsAromatic(True) rd_mol = Chem.RemoveHs(rd_mol, sanitize=False) pt = Chem.GetPeriodicTable() #if double/triple bonds are connected to hypervalent atoms, decrement the order positions = rd_mol.GetConformer().GetPositions() nonsingles = [] for bond in rd_mol.GetBonds(): if bond.GetBondType() == Chem.BondType.DOUBLE or bond.GetBondType( ) == Chem.BondType.TRIPLE: i = bond.GetBeginAtomIdx() j = bond.GetEndAtomIdx() dist = np.linalg.norm(positions[i] - positions[j]) nonsingles.append((dist, bond)) nonsingles.sort(reverse=True, key=lambda t: t[0]) for (d, bond) in nonsingles: a1 = bond.GetBeginAtom() a2 = bond.GetEndAtom() if calc_valence(a1) > pt.GetDefaultValence(a1.GetAtomicNum()) or \ calc_valence(a2) > pt.GetDefaultValence(a2.GetAtomicNum()): btype = Chem.BondType.SINGLE if bond.GetBondType() == Chem.BondType.TRIPLE: btype = Chem.BondType.DOUBLE bond.SetBondType(btype) for atom in rd_mol.GetAtoms(): #set nitrogens with 4 neighbors to have a charge if atom.GetAtomicNum() == 7 and atom.GetDegree() == 4: atom.SetFormalCharge(1) rd_mol = Chem.AddHs(rd_mol, addCoords=True) positions = rd_mol.GetConformer().GetPositions() center = np.mean(positions[np.all(np.isfinite(positions), axis=1)], axis=0) for atom in rd_mol.GetAtoms(): i = atom.GetIdx() pos = positions[i] if not np.all(np.isfinite(pos)): #hydrogens on C fragment get set to nan (shouldn't, but they do) rd_mol.GetConformer().SetAtomPosition(i, center) try: Chem.SanitizeMol(rd_mol, Chem.SANITIZE_ALL ^ Chem.SANITIZE_KEKULIZE) except: # mtr22 - don't assume mols will pass this pass # dkoes - but we want to make failures as rare as possible and should debug them m = pybel.Molecule(ob_mol) i = np.random.randint(1000000) outname = 'bad%d.sdf' % i print("WRITING", outname) m.write('sdf', outname, overwrite=True) pickle.dump(struct, open('bad%d.pkl' % i, 'wb')) #but at some point stop trying to enforce our aromaticity - #openbabel and rdkit have different aromaticity models so they #won't always agree. Remove any aromatic bonds to non-aromatic atoms for bond in rd_mol.GetBonds(): a1 = bond.GetBeginAtom() a2 = bond.GetEndAtom() if bond.GetIsAromatic(): if not a1.GetIsAromatic() or not a2.GetIsAromatic(): bond.SetIsAromatic(False) elif a1.GetIsAromatic() and a2.GetIsAromatic(): bond.SetIsAromatic(True) return rd_mol
mol_atm_idxs_list = list() input_mol_frags = Chem.FragmentOnBonds(input_mol, bond_list, dummyLabels=dummy_labels) flist = list() for fragmol, fragmol_atm_idxs in zip( Chem.GetMolFrags(input_mol_frags, asMols=True), Chem.GetMolFrags(input_mol_frags, asMols=False)): flist.append(fragmol) atm_idx_list = list() rwmol = Chem.RWMol(fragmol) for atm, atm_idx in zip(fragmol.GetAtoms(), fragmol_atm_idxs): ### This is where the labels are stored label = atm.GetIsotope() if label == 1: rwmol.ReplaceAtom(atm.GetIdx(), Chem.Atom(8)) rwmol.AddAtom(Chem.Atom(6)) rwmol.AddBond(atm.GetIdx(), fragmol.GetNumAtoms(), order=Chem.rdchem.BondType.SINGLE) fragmol = rwmol.GetMol() elif label == 4: rwmol.ReplaceAtom(atm.GetIdx(), Chem.Atom(6)) else: atm_idx_list.append(atm_idx) fragmol = rwmol.GetMol() Chem.SanitizeMol(fragmol) fragmol.Compute2DCoords() mol_list.append(fragmol) mol_atm_idxs_list.append(atm_idx_list)
def extract_core_from_mol(mol, reactive_atoms): """ Marks and removes non-core atoms from the reactant molecules and returns the reactive part of the reactants or the isolated reaction core for the products. """ # Create an editable RDKit RWMol object and create a backup copy of it for later use. editable_mol = AllChem.RWMol(mol) default_editable_mol = deepcopy(editable_mol) # If the indices are not correct or an empty array, return the starting molecule. if len(reactive_atoms) == len(mol.GetAtoms()) or len(reactive_atoms) == 0: return editable_mol, default_editable_mol # Create the list of atoms that are not part of the core and sort them in DESC order to avoid removal conflicts. nr_atoms = sorted([ atom.GetIdx() for atom in mol.GetAtoms() if atom.GetIdx() not in reactive_atoms ], reverse=True) # First, try to remove molecule atoms that are marked as non-reactive. try: for rm_atom in nr_atoms: editable_mol.ReplaceAtom(rm_atom, AllChem.Atom("*")) # Save the state with the wildcard atoms for later use in fragmentation of reactants. basic_editable_mol = deepcopy(editable_mol) # Remove all bonds that were replaced with the wildcard atom '*'. remove_marked_bonds(editable_mol) # If this fails, usually due to sanitization errors, remove molecule atoms according to the expanded core.Some cores # are complete aromatic rings or fused rings which cannot be broken and converted to a RDKit Mol object. Expand the # core indices to include all rings or fused rings, and generate a list of atoms that are not part of this core. except: # Generate the expanded core. expanded_core = fetch_rest_of_ring_atoms(mol, reactive_atoms) expanded_synthon_atoms = sorted([ atom.GetIdx() for atom in mol.GetAtoms() if atom.GetIdx() not in expanded_core ], reverse=True) # Create a new copy of the molecule because the previous one may have been modified. editable_mol = deepcopy(default_editable_mol) # To prevent later sanitization errors due to incorrect valences, skip any isolated atoms connected to the core. skip_atoms = [] for bond in editable_mol.GetBonds(): if bond.GetBeginAtomIdx() in expanded_core and bond.GetEndAtomIdx() not in expanded_core and \ editable_mol.GetAtomWithIdx(bond.GetEndAtomIdx()).GetDegree() == 1: skip_atoms.append(bond.GetEndAtomIdx()) if bond.GetEndAtomIdx() in expanded_core and bond.GetBeginAtomIdx() not in expanded_core and \ editable_mol.GetAtomWithIdx(bond.GetBeginAtomIdx()).GetDegree() == 1: skip_atoms.append(bond.GetBeginAtomIdx()) # Replace all of the atoms with the wildcard atom symbol '*'. for atom in set(expanded_synthon_atoms).difference(set(skip_atoms)): editable_mol.ReplaceAtom(atom, AllChem.Atom("*")) # Save the state with the wildcard atoms for later use in fragmentation of reactants. basic_editable_mol = deepcopy(editable_mol) # Remove all bonds that were replaced with the wildcard atom '*'. remove_marked_bonds(editable_mol) # Return the editable RDKit RWMol object after the changes have been made. return editable_mol, basic_editable_mol
def mol_from_mae_file(mae_path): """ Creates a ``rdkit`` molecule from a ``.mae`` file. Parameters ---------- mol2_file : :class:`str` The full path of the ``.mae`` file from which an rdkit molecule should be instantiated. Returns ------- :class:`rdkit.Mol` An ``rdkit`` instance of the molecule held in `mae_file`. """ mol = rdkit.EditableMol(rdkit.Mol()) conf = rdkit.Conformer() with open(mae_path, 'r') as mae: content = re.split(r'[{}]', mae.read()) prev_block = deque([''], maxlen=1) for block in content: if 'm_atom[' in prev_block[0]: atom_block = block if 'm_bond[' in prev_block[0]: bond_block = block prev_block.append(block) labels, data_block, *_ = atom_block.split(':::') labels = [ label for label in labels.split('\n') if not label.isspace() and label != '' ] data_block = [ a.split() for a in data_block.split('\n') if not a.isspace() and a != '' ] for line in data_block: line = [word for word in line if word != '"'] if len(labels) != len(line): raise RuntimeError(('Number of labels does' ' not match number of columns' ' in .mae file.')) for label, data in zip(labels, line): if 'x_coord' in label: x = float(data) if 'y_coord' in label: y = float(data) if 'z_coord' in label: z = float(data) if 'atomic_number' in label: atom_num = int(data) atom_sym = periodic_table[atom_num] atom_coord = Point3D(x, y, z) atom_id = mol.AddAtom(rdkit.Atom(atom_sym)) conf.SetAtomPosition(atom_id, atom_coord) labels, data_block, *_ = bond_block.split(':::') labels = [ label for label in labels.split('\n') if not label.isspace() and label != '' ] data_block = [ a.split() for a in data_block.split('\n') if not a.isspace() and a != '' ] for line in data_block: if len(labels) != len(line): raise RuntimeError(('Number of labels does' ' not match number of ' 'columns in .mae file.')) for label, data in zip(labels, line): if 'from' in label: atom1 = int(data) - 1 if 'to' in label: atom2 = int(data) - 1 if 'order' in label: bond_order = str(int(data)) mol.AddBond(atom1, atom2, bond_dict[bond_order]) mol = mol.GetMol() mol.AddConformer(conf) return mol
def extract_synthons_from_product(product_mol, reactive_atoms): """ Marks and removes the reactive atoms from the product molecules and returns only synthon templates. """ # Create an editable RDKit RWMol object and create a backup copy of it for later use. editable_mol = AllChem.RWMol(product_mol) default_editable_mol = deepcopy(editable_mol) # If the indices are not correct or an empty array, return the starting molecule. if len(reactive_atoms) == len( product_mol.GetAtoms()) or len(reactive_atoms) == 0: return editable_mol # First check if all of the core bonds are aromatic. if all([ str(bond.GetBondType()) == "AROMATIC" for bond in editable_mol.GetBonds() if bond.GetBeginAtomIdx() in reactive_atoms and bond.GetEndAtomIdx() in reactive_atoms ]): # ------------------------------------------------ # Reactive atoms CONTAIN ONLY FULL AROMATIC RINGS. # ------------------------------------------------ if atoms_contain_complete_rings(editable_mol, reactive_atoms): # Try removing all aromatic atoms in the core ring, except atoms shared between fused rings. try: for bond in editable_mol.GetBonds(): if bond.GetBeginAtomIdx( ) in reactive_atoms and bond.GetEndAtomIdx( ) in reactive_atoms: if count_atom_ring_memberships( editable_mol, bond.GetBeginAtomIdx()) < 2: editable_mol.ReplaceAtom(bond.GetBeginAtomIdx(), AllChem.Atom("*")) if count_atom_ring_memberships( editable_mol, bond.GetEndAtomIdx()) < 2: editable_mol.ReplaceAtom(bond.GetEndAtomIdx(), AllChem.Atom("*")) # Remove all bonds that were replaced with the wildcard atom '*'. remove_marked_bonds(editable_mol) # If this fails, it's mostly due to kekulization issues. Remove all non-aromatic atoms attached to the ring. except: try: # Create a new copy of the molecule because the previous one may have been modified. editable_mol = deepcopy(default_editable_mol) for bond in editable_mol.GetBonds(): if str(bond.GetBondType()) != "AROMATIC": if bond.GetBeginAtomIdx( ) in reactive_atoms and bond.GetEndAtomIdx( ) not in reactive_atoms: editable_mol.ReplaceAtom( bond.GetBeginAtomIdx(), AllChem.Atom("*")) editable_mol.ReplaceAtom( bond.GetEndAtomIdx(), AllChem.Atom("*")) elif bond.GetEndAtomIdx() in reactive_atoms and \ bond.GetBeginAtomIdx() not in reactive_atoms: editable_mol.ReplaceAtom( bond.GetBeginAtomIdx(), AllChem.Atom("*")) editable_mol.ReplaceAtom( bond.GetEndAtomIdx(), AllChem.Atom("*")) # Remove all bonds that were replaced with the wildcard atom '*'. remove_marked_bonds(editable_mol) # If that also fails, completely remove all aromatic atoms from the ring. except: # Create a new copy of the molecule because the previous one may have been modified. editable_mol = deepcopy(default_editable_mol) for bond in editable_mol.GetBonds(): if str(bond.GetBondType()) == "AROMATIC": if bond.GetBeginAtomIdx( ) in reactive_atoms and bond.GetEndAtomIdx( ) in reactive_atoms: editable_mol.ReplaceAtom( bond.GetBeginAtomIdx(), AllChem.Atom("*")) editable_mol.ReplaceAtom( bond.GetEndAtomIdx(), AllChem.Atom("*")) # Remove all bonds that were replaced with the wildcard atom '*'. remove_marked_bonds(editable_mol) # ------------------------------------------------------------------ # Reactive atoms CONTAIN ALL AROMATIC BONDS, BUT NOT COMPLETE RINGS. # ------------------------------------------------------------------ else: # Try removing all of the atoms normally. try: for rm_atom in reactive_atoms: editable_mol.ReplaceAtom(rm_atom, AllChem.Atom("*")) # Remove all bonds that were replaced with the wildcard atom '*'. remove_marked_bonds(editable_mol) # If this fails, mark and remove only the nearest non-aromatic bond connected to the aromatic part. except: # Create a new copy of the molecule because the previous one may have been modified. editable_mol = deepcopy(default_editable_mol) for bond in editable_mol.GetBonds(): if bond.GetBeginAtomIdx( ) in reactive_atoms and bond.GetEndAtomIdx( ) in reactive_atoms: if str(bond.GetBondType()) != "AROMATIC": editable_mol.ReplaceAtom(bond.GetBeginAtomIdx(), AllChem.Atom("*")) editable_mol.ReplaceAtom(bond.GetEndAtomIdx(), AllChem.Atom("*")) # Remove all bonds that were replaced with the wildcard atom '*'. remove_marked_bonds(editable_mol) # ----------------------------------------- # Reactive atoms CONTAIN NO AROMATIC BONDS. # ----------------------------------------- elif not any([ str(bond.GetBondType()) == "AROMATIC" for bond in editable_mol.GetBonds() if bond.GetBeginAtomIdx() in reactive_atoms and bond.GetEndAtomIdx() in reactive_atoms ]): # Try removing all of the atoms normally. This should work for all cases. for rm_atom in reactive_atoms: editable_mol.ReplaceAtom(rm_atom, AllChem.Atom("*")) # Remove all bonds that were replaced with the wildcard atom '*'. remove_marked_bonds(editable_mol) # ------------------------------------------------------------- # Reactive atoms CONTAIN COMPLETE RINGS AND NON-AROMATIC BONDS. # ------------------------------------------------------------- else: if atoms_contain_complete_rings(editable_mol, reactive_atoms): # Try removing all aromatic atoms in the core ring, except atoms shared between fused rings. try: for bond in editable_mol.GetBonds(): if bond.GetBeginAtomIdx( ) in reactive_atoms and bond.GetEndAtomIdx( ) in reactive_atoms: if count_atom_ring_memberships( editable_mol, bond.GetBeginAtomIdx()) < 2: editable_mol.ReplaceAtom(bond.GetBeginAtomIdx(), AllChem.Atom("*")) if count_atom_ring_memberships( editable_mol, bond.GetEndAtomIdx()) < 2: editable_mol.ReplaceAtom(bond.GetEndAtomIdx(), AllChem.Atom("*")) # Remove all bonds that were replaced with the wildcard atom '*'. remove_marked_bonds(editable_mol) # If this fails, it's mostly due to kekulization issues. Remove all non-aromatic atoms attached to the ring. except: # Create a new copy of the molecule because the previous one may have been modified. editable_mol = deepcopy(default_editable_mol) for bond in editable_mol.GetBonds(): if bond.GetBeginAtomIdx( ) in reactive_atoms and bond.GetEndAtomIdx( ) in reactive_atoms: if str(bond.GetBondType()) != "AROMATIC": editable_mol.ReplaceAtom(bond.GetBeginAtomIdx(), AllChem.Atom("*")) editable_mol.ReplaceAtom(bond.GetEndAtomIdx(), AllChem.Atom("*")) # Sanitize the molecule at this point because some of these entries require it here. AllChem.SanitizeMol(editable_mol) # Remove all bonds that were replaced with the wildcard atom '*'. remove_marked_bonds(editable_mol) # ------------------------------------------------------------------------------ # Reactive atoms CONTAIN AROMATIC AND NON-AROMATIC BONDS, BUT NO COMPLETE RINGS. # ------------------------------------------------------------------------------ else: try: # Try removing all of the atoms normally. This should work for all cases. for rm_atom in reactive_atoms: editable_mol.ReplaceAtom(rm_atom, AllChem.Atom("*")) # Remove all bonds that were replaced with the wildcard atom '*'. remove_marked_bonds(editable_mol) # If this fails, remove only the non-aromatic bonds. except: # Create a new copy of the molecule because the previous one may have been modified. editable_mol = deepcopy(default_editable_mol) for bond in editable_mol.GetBonds(): if bond.GetBeginAtomIdx( ) in reactive_atoms and bond.GetEndAtomIdx( ) in reactive_atoms: if str(bond.GetBondType()) != "AROMATIC": editable_mol.ReplaceAtom(bond.GetBeginAtomIdx(), AllChem.Atom("*")) editable_mol.ReplaceAtom(bond.GetEndAtomIdx(), AllChem.Atom("*")) # Remove all bonds that were replaced with the wildcard atom '*'. # If this also fails because of wrongly marked AROMATIC, return default molecule. try: remove_marked_bonds(editable_mol) except: editable_mol = deepcopy(default_editable_mol) # Return the editable RDKit RWMol object after the changes have been made. return editable_mol
def extract_synthons_from_reactant(reactant_mol, reactive_atoms): """ Marks and removes marked reactive atoms from the reactant molecules and returns only the non-reactive part. """ # Create an editable RDKit RWMol object and create a backup copy of it for later use. editable_mol = AllChem.RWMol(reactant_mol) default_editable_mol = deepcopy(editable_mol) # If the indices are not correct or an empty array, return the starting molecule. if len(reactive_atoms) == len( reactant_mol.GetAtoms()) or len(reactive_atoms) == 0: return editable_mol, default_editable_mol # First, try to just remove all of the core atoms without any further modifications. try: for rm_atom in reactive_atoms: editable_mol.ReplaceAtom(rm_atom, AllChem.Atom("*")) # Save the state with the wildcard atoms for later use in fragmentation of reactants. basic_editable_mol = deepcopy(editable_mol) # Remove all bonds that were replaced with the wildcard atom '*'. remove_marked_bonds(editable_mol) # If that fails, it's most likely due to the incorrect decomposition of aromatic rings. except: # Create a new copy of the molecule because the previous one may have been modified. editable_mol = deepcopy(default_editable_mol) # Try removing all atoms in the present aromatic ring, except atoms shared between fused rings. try: for bond in editable_mol.GetBonds(): if bond.GetBeginAtomIdx( ) in reactive_atoms and bond.GetEndAtomIdx() in reactive_atoms: if count_atom_ring_memberships(editable_mol, bond.GetBeginAtomIdx()) < 2: editable_mol.ReplaceAtom(bond.GetBeginAtomIdx(), AllChem.Atom("*")) if count_atom_ring_memberships(editable_mol, bond.GetEndAtomIdx()) < 2: editable_mol.ReplaceAtom(bond.GetEndAtomIdx(), AllChem.Atom("*")) # Save the state with the wildcard atoms for later use in fragmentation of reactants. basic_editable_mol = deepcopy(editable_mol) # Remove all bonds that were replaced with the wildcard atom '*'. remove_marked_bonds(editable_mol) # If this also fails, only remove non-aromatic bond atoms attached to the ring if there are any. except: # Create a new copy of the molecule because the previous one may have been modified. editable_mol = deepcopy(default_editable_mol) for bond in editable_mol.GetBonds(): if str(bond.GetBondType()) != "AROMATIC": if bond.GetBeginAtomIdx( ) in reactive_atoms and bond.GetEndAtomIdx( ) not in reactive_atoms: editable_mol.ReplaceAtom(bond.GetBeginAtomIdx(), AllChem.Atom("*")) editable_mol.ReplaceAtom(bond.GetEndAtomIdx(), AllChem.Atom("*")) elif bond.GetEndAtomIdx( ) in reactive_atoms and bond.GetBeginAtomIdx( ) not in reactive_atoms: editable_mol.ReplaceAtom(bond.GetBeginAtomIdx(), AllChem.Atom("*")) editable_mol.ReplaceAtom(bond.GetEndAtomIdx(), AllChem.Atom("*")) # Save the state with the wildcard atoms for later use in fragmentation of reactants. basic_editable_mol = deepcopy(editable_mol) # Remove all bonds that were replaced with the wildcard atom '*'. remove_marked_bonds(editable_mol) # Return the editable RDKit RWMol object after the changes have been made. return editable_mol, basic_editable_mol
def init_from_vabene_molecule( cls, molecule, functional_groups=(), placer_ids=None, random_seed=None, position_matrix=None, ): """ Initialize from a :mod:`vabene.Molecule`. Notes ----- The molecule is given 3D coordinates with :func:`rdkit.ETKDGv2()`. Parameters ---------- molecule : :class:`vabene.Molecule` The molecule from which to initialize. functional_groups : :class:`iterable`, optional An :class:`iterable` of :class:`.FunctionalGroup` or :class:`.FunctionalGroupFactory` or both. :class:`.FunctionalGroup` instances are added to the building block and :class:`.FunctionalGroupFactory` instances are used to create :class:`.FunctionalGroup` instances the building block should hold. :class:`.FunctionalGroup` instances are used to identify which atoms are modified during :class:`.ConstructedMolecule` construction. placer_ids : :class:`tuple` of :class:`int`, optional The ids of *placer* atoms. These are the atoms which should be used for calculating the position of the building block. Depending on the values passed to `placer_ids`, and the functional groups in the building block, different *placer* ids will be used by the building block. #. `placer_ids` is passed to the initializer: the passed *placer* ids will be used by the building block. #. `placer_ids` is ``None`` and the building block has functional groups: The *placer* ids of the functional groups will be used as the *placer* ids of the building block. #. `placer_ids` is ``None`` and `functional_groups` is empty. All atoms of the molecule will be used for *placer* ids. random_seed : :class:`int`, optional Random seed passed to :func:`rdkit.ETKDGv2`. This argument is deprecated and will be removed in any version of :mod:`stk` released on, or after, 01/08/20. If you want to change the position matrix of the initialized building block, please use `position_matrix`. position_matrix : :class:`numpy.ndarray`, optional The position matrix the building block should use. If ``None``, :func:`rdkit.ETKDGv2` will be used to calculate it. Returns ------- :class:`.BuildingBlock` The building block. Raises ------ :class:`RuntimeError` If embedding the molecule fails. """ if random_seed is not None: warnings.warn( 'The init_from_vabene_molecule random seed parameter ' 'will be removed in any ' 'version of stk released on, or after, 01/08/20. ' 'If you want to change the position matrix of the ' 'initialized building block, please use the ' 'position_matrix parameter.', FutureWarning, ) else: random_seed = 4 editable = rdkit.EditableMol(rdkit.Mol()) for atom in molecule.get_atoms(): rdkit_atom = rdkit.Atom(atom.get_atomic_number()) rdkit_atom.SetFormalCharge(atom.get_charge()) editable.AddAtom(rdkit_atom) for bond in molecule.get_bonds(): editable.AddBond( beginAtomIdx=bond.get_atom1_id(), endAtomIdx=bond.get_atom2_id(), order=rdkit.BondType(bond.get_order()), ) rdkit_molecule = editable.GetMol() rdkit.SanitizeMol(rdkit_molecule) rdkit_molecule = rdkit.AddHs(rdkit_molecule) if position_matrix is None: params = rdkit.ETKDGv2() params.randomSeed = random_seed if rdkit.EmbedMolecule(rdkit_molecule, params) == -1: raise RuntimeError( f'Embedding with seed value of {random_seed} ' 'failed.' ) else: # Make sure the position matrix always holds floats. position_matrix = np.array( position_matrix, dtype=np.float64, ) conformer = rdkit.Conformer(rdkit_molecule.GetNumAtoms()) for atom_id, position in enumerate(position_matrix): conformer.SetAtomPosition(atom_id, position) rdkit_molecule.AddConformer(conformer) rdkit.Kekulize(rdkit_molecule) return cls.init_from_rdkit_mol( molecule=rdkit_molecule, functional_groups=functional_groups, placer_ids=placer_ids, )