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 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 find_smirks_parameters(smiles_list, molecule_paths): """Finds the force field parameters which would be assigned to a list of molecules defined by the provided SMILES patterns. Parameters ---------- smiles_list: list of str The SMILES patterns of the target molecules molecule_paths: list of Path The list of molecules that correspond to the SMILES strings (to make it easier to see which molecules utilize which parameters) Returns ------- dict of str and list of str A dictionary with keys of SMIRKS patterns, and values of lists of SMILES patterns which would utilize those patterns, and the parameter ID in the force field. """ force_field = smirnoff.ForceField('smirnoff99Frosst-1.0.9.offxml') smiles_by_smirks = {} smiles_by_smirks["Bonds"] = {} smiles_by_smirks["Angles"] = {} smiles_by_smirks["ProperTorsions"] = {} smiles_by_smirks["vdW"] = {} smiles_by_smirks["ImproperTorsions"] = {} smiles_by_smirks["Electrostatics"] = {} # Populate the dictionary using the open force field toolkit. for index, smiles in enumerate(smiles_list): ifs = oechem.oemolistream() if not ifs.open(str(molecule_paths[index])): logging.error( f'Unable to open {molecule_paths[index]} for reading...') ifs.open(str(molecule_paths[index])) oe_mols = [] for mol in ifs.GetOEMols(): oe_mols.append(oechem.OEMol(mol)) oechem.OE3DToAtomStereo(oe_mols[0]) molecule = Molecule.from_openeye(oe_mols[0]) # molecule = Molecule.from_smiles(smiles, allow_undefined_stereo=True) topology = Topology.from_molecules([molecule]) molecule_force_list = force_field.label_molecules(topology) for molecule_index, molecule_forces in enumerate(molecule_force_list): print(f'Forces for molecule {molecule_index}') for force_name, force_dict in molecule_forces.items(): print(f"\n{force_name}:") for (atom_indices, parameter) in force_dict.items(): atomstr = '' for idx in atom_indices: atomstr += '%5s' % idx print("atoms: %s parameter_id: %s smirks %s" % ([ oe_mols[0].GetAtom(oechem.OEHasAtomIdx(i)).GetName() for i in atom_indices ], parameter.id, parameter.smirks)) # This is not catching _all_ the atoms that hit a certain parameter. # I think these need to be initialized in the outer loop. # Each parameter is getting a list of length 1. if parameter.id not in smiles_by_smirks[force_name]: smiles_by_smirks[force_name][parameter.id] = {} if "atom_indices" not in smiles_by_smirks[force_name]: smiles_by_smirks[force_name][ parameter.id]["atom_indices"] = [] if "atom_names" not in smiles_by_smirks[force_name]: smiles_by_smirks[force_name][ parameter.id]["atom_names"] = [] smiles_by_smirks[force_name][ parameter.id]["atom_indices"].append(atom_indices) smiles_by_smirks[force_name][ parameter.id]["atom_names"].append([ oe_mols[0].GetAtom( oechem.OEHasAtomIdx(i)).GetName() for i in atom_indices ]) smiles_by_smirks[force_name][ parameter.id]["smirks"] = parameter.smirks return smiles_by_smirks
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)