def atom_bond_set_to_mol(frag, oemol, adjust_hcount=False, RGroup=True): from openeye import oechem import warnings fragatompred = oechem.OEIsAtomMember(frag.GetAtoms()) fragbondpred = oechem.OEIsBondMember(frag.GetBonds()) fragment_oemol = oechem.OEMol() adjustHCount = adjust_hcount oechem.OESubsetMol(fragment_oemol, oemol, fragatompred, fragbondpred, adjustHCount, RGroup) oechem.OEAddExplicitHydrogens(fragment_oemol) # sanity check that all atoms are bonded for atom in fragment_oemol.GetAtoms(): if not list(atom.GetBonds()): warnings.warn( "Yikes!!! An atom that is not bonded to any other atom in the fragment. " "You probably ran into a bug. Please report the input molecule to the issue tracker" ) # Perceive stereo and check that defined stereo did not change oechem.OEPerceiveChiral(fragment_oemol) oechem.OE3DToAtomStereo(fragment_oemol) oechem.OE3DToBondStereo(fragment_oemol) return fragment_oemol
def file_to_oemols(filename): """Create OEMol from file. If more than one mol in file, return list of OEMols. Parameters ---------- filename: str absolute path to oeb file Returns ------- mollist: list list of OEMol for multiple molecules. OEMol if file only has one molecule. """ from openeye import oechem if not os.path.exists(filename): raise Exception("File {} not found".format(filename)) ifs = oechem.oemolistream(filename) mollist = [] molecule = oechem.OEMol() while oechem.OEReadMolecule(ifs, molecule): molecule_copy = oechem.OEMol(molecule) oechem.OEPerceiveChiral(molecule_copy) oechem.OE3DToAtomStereo(molecule_copy) oechem.OE3DToBondStereo(molecule_copy) mollist.append(molecule_copy) ifs.close() if len(mollist) is 1: mollist = mollist[0] return mollist
def mol_from_json(symbols, connectivity, geometry, permute_xyz=False): """ Generate OEMol from QCSchema molecule specs Parameters ---------- inp_molecule: dict Must have symbols and connectivity and/or geometry Note: If geometry is given, the molecule will have a tag indicating that the goemetry came from QCSchema. This will ensure that the order of the atoms and configuration is not change for generation of mapped SMILES and isomeric SMILES. Returns ------- molecule: OEMol """ molecule = oechem.OEMol() for s in symbols: molecule.NewAtom(_symbols[s]) # Add connectivity for bond in connectivity: a1 = molecule.GetAtom(oechem.OEHasAtomIdx(bond[0])) a2 = molecule.GetAtom(oechem.OEHasAtomIdx(bond[1])) bond_order = bond[-1] if not isinstance(bond_order, int) and not bond_order.is_integer(): raise ValueError( "bond order must be a whole number. A bond order of 1.5 is not allowed" ) molecule.NewBond(a1, a2, int(bond_order)) # Add geometry if molecule.NumAtoms() != geometry.shape[0] / 3: raise ValueError( "Number of atoms in molecule does not match length of position array" ) molecule.SetCoords(oechem.OEFloatArray(geometry)) molecule.SetDimension(3) if not permute_xyz: # Add tag that the geometry is from JSON and shouldn't be changed. geom_tag = oechem.OEGetTag("json_geometry") molecule.SetData(geom_tag, True) oechem.OEDetermineConnectivity(molecule) oechem.OEFindRingAtomsAndBonds(molecule) # No need to perceive Bond Order because the information was added from the connectivity table. Apparently, this # function does many different perceptions under the hood and it can add implicit hydrogens to divalent N that should be negatively charged. #oechem.OEPerceiveBondOrders(molecule) # This seems to add hydrogens that are not in the json #oechem.OEAssignImplicitHydrogens(molecule) oechem.OEAssignFormalCharges(molecule) oechem.OEAssignAromaticFlags(molecule) oechem.OEPerceiveChiral(molecule) oechem.OE3DToAtomStereo(molecule) oechem.OE3DToBondStereo(molecule) return molecule
def canonical_order_molecule_inplace(mol_list): """rearrange atoms in molecules to canonical ordering based on example from Chaya """ if not mol_list: return toolkit = cmiles.utils._set_toolkit(mol_list[0]) for oe_mol in mol_list: toolkit.canonical_order_atoms(oe_mol) # make sure stereochemistry is defined if not cmiles.utils.has_stereo_defined(oe_mol): oechem.OEPerceiveChiral(oe_mol) oechem.OE3DToAtomStereo(oe_mol) oechem.OE3DToBondStereo(oe_mol)
def mol_from_json(symbols, connectivity, geometry, permute_xyz=False): """ Generate OEMol from QCSchema molecule specs Parameters ---------- inp_molecule: dict Must have symbols and connectivity and/or geometry Note: If geometry is given, the molecule will have a tag indicating that the goemetry came from QCSchema. This will ensure that the order of the atoms and configuration is not change for generation of mapped SMILES and isomeric SMILES. Returns ------- molecule: OEMol """ molecule = oechem.OEMol() for s in symbols: molecule.NewAtom(_symbols[s]) # Add connectivity for bond in connectivity: a1 = molecule.GetAtom(oechem.OEHasAtomIdx(bond[0])) a2 = molecule.GetAtom(oechem.OEHasAtomIdx(bond[1])) molecule.NewBond(a1, a2, bond[-1]) # Add geometry if molecule.NumAtoms() != geometry.shape[0] / 3: raise ValueError( "Number of atoms in molecule does not match length of position array" ) molecule.SetCoords(oechem.OEFloatArray(geometry)) molecule.SetDimension(3) if not permute_xyz: # Add tag that the geometry is from JSON and shouldn't be changed. geom_tag = oechem.OEGetTag("json_geometry") molecule.SetData(geom_tag, True) oechem.OEDetermineConnectivity(molecule) oechem.OEFindRingAtomsAndBonds(molecule) oechem.OEPerceiveBondOrders(molecule) # This seems to add hydrogens that are not in the json #oechem.OEAssignImplicitHydrogens(molecule) oechem.OEAssignFormalCharges(molecule) oechem.OEAssignAromaticFlags(molecule) oechem.OEPerceiveChiral(molecule) oechem.OE3DToAtomStereo(molecule) oechem.OE3DToBondStereo(molecule) return molecule
def PrepareDepiction(mol, clearcoords=False, suppressH=True): oechem.OESetDimensionFromCoords(mol) oechem.OEPerceiveChiral(mol) if mol.GetDimension() != 2 or clearcoords: if mol.GetDimension() == 3: oechem.OE3DToBondStereo(mol) oechem.OE3DToAtomStereo(mol) if suppressH: oechem.OESuppressHydrogens(mol) oechem.OEAddDepictionHydrogens(mol) oechem.OEDepictCoordinates(mol) oechem.OEMDLPerceiveBondStereo(mol) mol.SetDimension(2) return True
def main(infile): # open multi-molecule, multi-conformer file ifs = oechem.oemolistream() ifs.SetConfTest(oechem.OEAbsCanonicalConfTest()) if not ifs.open(infile): raise FileNotFoundError(f"Unable to open {infile} for reading") mols = ifs.GetOEMols() for i, mol in enumerate(mols): # perceive stereochemistry for mol oechem.OEPerceiveChiral(mol) oechem.OEAssignAromaticFlags(mol, oechem.OEAroModel_MDL) # assign charges to copy of mol # note that chg_mol does NOT have conformers try: chg_mol = charge_mol(mol) except RuntimeError: # perceive stereochem #find_unspecified_stereochem(mol) oechem.OE3DToInternalStereo(mol) # reset perceived and call OE3DToBondStereo, since it may be missed # by OE3DToInternalStereo if it thinks mol is flat mol.ResetPerceived() oechem.OE3DToBondStereo(mol) try: chg_mol = charge_mol(mol) print(f'fixed stereo: {mol.GetTitle()}') except RuntimeError: find_unspecified_stereochem(mol) title = mol.GetTitle() smilabel = oechem.OEGetSDData(mol, "SMILES QCArchive") print(' >>> Charge assignment failed due to unspecified ' f'stereochemistry {title} {smilabel}') continue
def main(infile, outfile, ffxml, minimizer): # open multi-molecule, multi-conformer file ifs = oechem.oemolistream() ifs.SetConfTest(oechem.OEAbsCanonicalConfTest()) if not ifs.open(infile): raise FileNotFoundError(f"Unable to open {infile} for reading") mols = ifs.GetOEMols() # open an outstream file ofs = oechem.oemolostream() if os.path.exists(outfile): raise FileExistsError("Output file {} already exists in {}".format( outfile, os.getcwd())) if not ofs.open(outfile): oechem.OEThrow.Fatal("Unable to open %s for writing" % outfile) # minimize with openforcefield ffxml file for i, mol in enumerate(mols): # perceive stereochemistry for mol oechem.OEPerceiveChiral(mol) oechem.OEAssignAromaticFlags(mol, oechem.OEAroModel_MDL) # assign charges to copy of mol # note that chg_mol does NOT have conformers try: chg_mol = charge_mol(mol) except RuntimeError: # perceive stereochem #find_unspecified_stereochem(mol) oechem.OE3DToInternalStereo(mol) # reset perceived and call OE3DToBondStereo, since it may be missed # by OE3DToInternalStereo if it thinks mol is flat mol.ResetPerceived() oechem.OE3DToBondStereo(mol) try: chg_mol = charge_mol(mol) print(f'fixed stereo: {mol.GetTitle()}') except RuntimeError: title = mol.GetTitle() smilabel = oechem.OEGetSDData(mol, "SMILES QCArchive") print(' >>> Charge assignment failed due to unspecified ' f'stereochemistry {title} {smilabel}') continue for j, conf in enumerate(mol.GetConfs()): # perceive sterochemistry for conf coordinates oechem.OE3DToInternalStereo(conf) # assign charges to the conf itself chg_conf = charge_conf(chg_mol, conf) if minimizer == 'ffxml': # minimize with parsley (charges set by ff not used from conf) min_ffxml(chg_conf, ofs, ffxml) if minimizer == 'mmff94': # minimize with mmff94 min_mmff94x(chg_conf, ofs, mmff94s=False) if minimizer == 'mmff94s': # minimize with mmff94S min_mmff94x(chg_conf, ofs, mmff94s=True) if minimizer == 'gaff': # minimize with gaff min_gaffx(chg_conf, ofs, gaff2=False) if minimizer == 'gaff2': # minimize with gaff2 min_gaffx(chg_conf, ofs, gaff2=True) ifs.close() ofs.close()
def has_stereo_defined(molecule): """ Check if any stereochemistry in undefined. Parameters ---------- molecule: OEMol Returns ------- bool: True if all stereo chemistry is defined. If any stereochemsitry is undefined, raise and exception. """ # perceive stereochemistry oechem.OEPerceiveChiral(molecule) oechem.OE3DToAtomStereo(molecule) oechem.OE3DToBondStereo(molecule) unspec_chiral = False unspec_db = False problematic_atoms = list() problematic_bonds = list() for atom in molecule.GetAtoms(): if atom.IsChiral() and not atom.HasStereoSpecified( oechem.OEAtomStereo_Tetrahedral): # Check if handness is specified v = [] for nbr in atom.GetAtoms(): v.append(nbr) stereo = atom.GetStereo(v, oechem.OEAtomStereo_Tetrahedral) if stereo == oechem.OEAtomStereo_Undefined: unspec_chiral = True problematic_atoms.append( (atom.GetIdx(), oechem.OEGetAtomicSymbol(atom.GetAtomicNum()))) for bond in molecule.GetBonds(): if bond.IsChiral() and not bond.HasStereoSpecified( oechem.OEBondStereo_CisTrans): v = [] for neigh in bond.GetBgn().GetAtoms(): if neigh != bond.GetEnd(): v.append(neigh) break for neigh in bond.GetEnd().GetAtoms(): if neigh != bond.GetBgn(): v.append(neigh) break stereo = bond.GetStereo(v, oechem.OEBondStereo_CisTrans) if stereo == oechem.OEBondStereo_Undefined: unspec_db = True a1 = bond.GetBgn() a2 = bond.GetEnd() a1_idx = a1.GetIdx() a2_idx = a2.GetIdx() a1_s = oechem.OEGetAtomicSymbol(a1.GetAtomicNum()) a2_s = oechem.OEGetAtomicSymbol(a2.GetAtomicNum()) bond_order = bond.GetOrder() problematic_bonds.append( (a1_idx, a1_s, a2_idx, a2_s, bond_order)) if unspec_chiral or unspec_db: warnings.warn( "Stereochemistry is unspecified. Problematic atoms {}, problematic bonds {}, SMILES: {}" .format(problematic_atoms, problematic_bonds, oechem.OEMolToSmiles(molecule))) return False else: return True
def _extract_oe_fragment( molecule: Molecule, atom_indices: Set[int], bond_indices: Set[Tuple[int, int]] ) -> Molecule: from openeye import oechem oe_molecule = molecule.to_openeye() # Restore the map indices as to_openeye does not automatically add them. for atom_index, map_index in molecule.properties["atom_map"].items(): oe_atom = oe_molecule.GetAtom(oechem.OEHasAtomIdx(atom_index)) oe_atom.SetMapIdx(map_index) # Include any Hs bonded to the included atom set so we can retain their map # indices. for map_index in {*atom_indices}: oe_atom = oe_molecule.GetAtom(oechem.OEHasMapIdx(map_index)) for neighbour in oe_atom.GetAtoms(): if ( neighbour.GetAtomicNum() != 1 or neighbour.GetMapIdx() < 1 or neighbour.GetMapIdx() in atom_indices ): continue atom_indices.add(neighbour.GetMapIdx()) bond_indices.add((map_index, neighbour.GetMapIdx())) atom_bond_set = oechem.OEAtomBondSet() for map_index in atom_indices: atom = oe_molecule.GetAtom(oechem.OEHasMapIdx(map_index)) atom_bond_set.AddAtom(atom) for map_index_1, map_index_2 in bond_indices: atom_1 = oe_molecule.GetAtom(oechem.OEHasMapIdx(map_index_1)) atom_2 = oe_molecule.GetAtom(oechem.OEHasMapIdx(map_index_2)) bond = oe_molecule.GetBond(atom_1, atom_2) if not bond: raise ValueError(f"{(map_index_1, map_index_2)} is a disconnected bond") atom_bond_set.AddBond(bond) atom_predicate = oechem.OEIsAtomMember(atom_bond_set.GetAtoms()) bond_predicate = oechem.OEIsBondMember(atom_bond_set.GetBonds()) fragment = oechem.OEMol() oechem.OESubsetMol(fragment, oe_molecule, atom_predicate, bond_predicate, True) oechem.OEAddExplicitHydrogens(fragment) oechem.OEPerceiveChiral(fragment) # Always restore map? # if restore_maps: # In some cases (symmetric molecules) this changes the atom map so skip it # restore_atom_map(fragment) # atom map should be restored for combinatorial fragmentation # Perceive stereo and check that defined stereo did not change oechem.OEPerceiveChiral(fragment) oechem.OE3DToAtomStereo(fragment) oechem.OE3DToBondStereo(fragment) return Molecule.from_openeye(fragment, allow_undefined_stereo=True)