def optg_c(self, steps=60, ff="mmff94", optimizer='cg'): """ Opt geometry by ff with constrained torsions ============================================== defaut ff: mmff94, mmff94s seems to be problematic, sometimes it leads to weird geometries steps: ff steps for constraint opt """ m = self.m #copy.copy( self.m ) # Define constraints c = ob.OBFFConstraints() #c.AddDistanceConstraint(1, 8, 10) # Angstroms #c.AddAngleConstraint(1, 2, 3, 120.0) # Degrees # constrain torsion only torsions = self.get_all_torsions() for torsion in torsions: i, j, k, l = torsion ang = torsions[torsion] c.AddTorsionConstraint(i, j, k, l, ang) # Degrees # Setup the force field with the constraints obff = ob.OBForceField.FindForceField(ff) obff.Setup(m, c) obff.SetConstraints(c) obff.ConjugateGradients(steps) obff.GetCoordinates(m) self.m = m self.M = pb.Molecule(m) self.coords = get_coords(m)
def opt_mmff_FG(mol, numAtoms, outfile): # initialize force field and constraint classes MMFF = ob.OBForceField.FindType("MMFF94") constraints = ob.OBFFConstraints() conv = ob.OBConversion() # setup force field MMFF.Setup(mol) # add constraints [constraints.AddAtomConstraint(i) for i in range(1, numAtoms)] MMFF.SetConstraints(constraints) # run optization MMFF.ConjugateGradients(1000) # update coordinates MMFF.UpdateCoordinates(mol) # write to xyz file print outfile conv.WriteFile(mol, "test/" + outfile) # clear mol class mol.Clear()
def optimize(self, constraint=False, steps=100): """ Perform conjugate gradient optimization with the MMFF94 force field. Keyword arguments: constraint -- Apply harmonic constraint to dihedral angles. (default False) steps -- Max number of CG steps. (default 100) """ mol = self._molecule.OBMol restraints = self._get_dihedral_constraints() cnstr = openbabel.OBFFConstraints() if constraint: for r in restraints: a = mol.GetTorsion(r[0], r[1], r[2], r[3]) cnstr.AddTorsionConstraint(r[0], r[1], r[2], r[3], a) FF = openbabel.OBForceField.FindForceField("MMFF94") FF.Setup(mol, cnstr) FF.SetConstraints(cnstr) FF.ConjugateGradients(steps) FF.GetCoordinates(mol) return
def chain_ffopt(ff, mol, frozenats): ### FORCE FIELD OPTIMIZATION ## # INPUT # - ff: force field to use, available MMFF94, UFF< Ghemical, GAFF # - mol: mol3D to be ff optimized # - connected: indices of connection atoms to metal # - constopt: flag for constrained optimization # OUTPUT # - mol: force field optimized mol3D metals = range(21, 31) + range(39, 49) + range(72, 81) ### check requested force field ffav = 'mmff94, uff, ghemical, gaff, mmff94s' # force fields if ff.lower() not in ffav: print 'Requested force field not available. Defaulting to MMFF94' ff = 'mmff94' ### convert mol3D to OBMol via xyz file, because AFTER/END option have coordinates backup_mol = mol3D() backup_mol.copymol3D(mol) # print('bck ' + str(backup_mol.getAtom(0).coords())) # print('mol_ibf ' + str(mol.getAtom(0).coords())) mol.convert2OBMol() ### initialize constraints constr = openbabel.OBFFConstraints() ### openbabel indexing starts at 1 ### !!! # convert metals to carbons for FF indmtls = [] mtlsnums = [] for iiat, atom in enumerate(openbabel.OBMolAtomIter(OBMol)): if atom.atomicnum in metals: indmtls.append(iiat) mtlsnums.append(atom.GetAtomicNum()) atom.OBAtom.SetAtomicNum(19) for cat in frozenats: constr.AddAtomConstraint(cat + 1) # indexing babel ### set up forcefield forcefield = openbabel.OBForceField.FindForceField(ff) OBMol = mol.OBMol forcefield.Setup(obmol, constr) ## force field optimize structure forcefield.ConjugateGradients(2500) forcefield.GetCoordinates(OBMol) mol.OBMol = OBMol # reset atomic number to metal for i, iiat in enumerate(indmtls): mol.OBMol.GetAtomById(iiat).SetAtomicNum(mtlsnums[i]) mol.convert2mol3D() en = forcefield.Energy() # print(str(mol.OBmol.atoms[1].OBAtom.GetVector().GetZ())) # print(str(forcefield.Validate())) # print('mol_af ' + str(mol.getAtom(0).coords())) # print('ff delta = ' + str(backup_mol.rmsd(mol))) del forcefield, constr, OBMol return mol, en
def minimize(selection='tmp', forcefield='MMFF94', method='steepest descent', nsteps=2000, conv=1E-6, cutoff=False, cut_vdw=6.0, cut_elec=8.0, rigid_geometry=True): """ Use openbabel to minimize the energy of a molecule. """ pdb_string = cmd.get_pdbstr(selection) name = cmd.get_legal_name(selection) obconversion = ob.OBConversion() obconversion.SetInAndOutFormats('pdb', 'pdb') mol = ob.OBMol() obconversion.ReadString(mol, pdb_string) if rigid_geometry: constraints = ob.OBFFConstraints() for angle in ob.OBMolAngleIter(mol): b, a, c = [mol.GetAtom(x + 1) for x in angle] value = mol.GetAngle(a, b, c) b, a, c = [x + 1 for x in angle] constraints.AddAngleConstraint(a, b, c, value) for i in ob.OBMolBondIter(mol): a, b = (i.GetBeginAtomIdx(), i.GetEndAtomIdx()) value = i.GetLength() constraints.AddDistanceConstraint(a, b, value) ff = ob.OBForceField.FindForceField(forcefield) ff.Setup(mol, constraints) ff.SetConstraints(constraints) else: ff = ob.OBForceField.FindForceField(forcefield) ff.Setup(mol) if cutoff: ff.EnableCutOff(True) ff.SetVDWCutOff(cut_vdw) ff.SetElectrostaticCutOff(cut_elec) if method == 'conjugate gradients': ff.ConjugateGradients(nsteps, conv) else: ff.SteepestDescent(nsteps, conv) ff.GetCoordinates(mol) nrg = ff.Energy() pdb_string = obconversion.WriteString(mol) cmd.delete(name) if name == 'all': name = 'all_' cmd.delete(selection) cmd.read_pdbstr(pdb_string, selection) return nrg
def constrained_ff_optimization(xyz_filename, constraints): """ Do a constrained optimization with UFF to make sure that the ab initio MD won't explode Code modified from gist.github.com/andersx/7784817 """ # Standard openbabel molecule load conv = ob.OBConversion() conv.SetInAndOutFormats('xyz','xyz') mol = ob.OBMol() conv.ReadFile(mol,xyz_filename) # Define constraints ffconstraints = ob.OBFFConstraints() for idx1, idx2, distance in constraints: ffconstraints.AddDistanceConstraint(int(idx1)+1, int(idx2)+1, float(distance)) ## If only two constraints, then add additional angle constraint #if len(constraints) == 2: # idx1 = constraints[0][0] # idx2 = constraints[0][1] # idx3 = constraints[1][0] # idx4 = constraints[1][1] # #if idx1 == idx3: # # ffconstraints.AddAngleConstraint(int(idx2), int(idx1), int(idx4), 180.0) # # #ffconstraints.AddAngleConstraint(int(idx2), int(idx1), int(idx4)+1, 180.0) # #elif idx1 == idx4: # # ffconstraints.AddAngleConstraint(int(idx2), int(idx1), int(idx3), 180.0) # #elif idx2 == idx3: # # ffconstraints.AddAngleConstraint(int(idx1), int(idx2), int(idx4), 180.0) # #elif idx2 == idx4: # # ffconstraints.AddAngleConstraint(int(idx1), int(idx2), int(idx3), 180.0) # Setup the force field with the constraints forcefield = ob.OBForceField.FindForceField("UFF") forcefield.Setup(mol, ffconstraints) forcefield.SetConstraints(ffconstraints) # Do a 1000 steps conjugate gradient minimization # and save the coordinates to mol. forcefield.ConjugateGradients(500) forcefield.GetCoordinates(mol) return conv, mol
def set_dihedral(angles): """ Set the dihedral angles of the carbon chain """ global mol global forcefield constraints = ob.OBFFConstraints() # constraints.AddDistanceConstraint(1, 10, 3.4) # Angstroms # constraints.AddAngleConstraint(1, 2, 3, 120.0) # Degrees # constraints.AddTorsionConstraint(1, 2, 3, 4, 180.0) # Degrees # Find all carbons smarts = pb.Smarts("C") smarts = smarts.findall(mol) for i in xrange(len(angles)): angle = angles[i] # Get the next 4 carbons Cs = smarts[i:i+4] ai, = Cs[0] bi, = Cs[1] ci, = Cs[2] di, = Cs[3] a = mol.OBMol.GetAtom(ai) b = mol.OBMol.GetAtom(bi) c = mol.OBMol.GetAtom(ci) d = mol.OBMol.GetAtom(di) mol.OBMol.SetTorsion(a, b, c, d, angle/(180.0)*np.pi) # Radians anglep = mol.OBMol.GetTorsion(a, b, c, d) # Define constraint constraints.AddTorsionConstraint(ai, bi, ci, di, anglep) # Degrees # Setup the force field with the constraints forcefield = ob.OBForceField.FindForceField("GAFF") forcefield.Setup(mol.OBMol) forcefield.SetConstraints(constraints) forcefield.EnableCutOff(True) # VDW cutoff forcefield.SetElectrostaticCutOff(0) # Remove electrostatics # Use forcefield to reoptimize bondlengths+angles find_local_min()
def cell_ffopt(ff, mol, frozenats): ### FORCE FIELD OPTIMIZATION ## # INPUT # - ff: force field to use, available MMFF94, UFF< Ghemical, GAFF # - mol: mol3D to be ff optimized # OUTPUT # - mol: force field optimized mol3D metals = list(range(21, 31))+list(range(39, 49))+list(range(72, 81)) # check requested force field ffav = 'mmff94, uff, ghemical, gaff, mmff94s' # force fields if ff.lower() not in ffav: print('Requested force field not available. Defaulting to UFF') ff = 'UFF' # convert mol3D to OBMol via xyz file, because AFTER/END option have coordinates backup_mol = mol3D() backup_mol.copymol3D(mol) # print('bck ' + str(backup_mol.getAtom(0).coords())) # print('mol_ibf ' + str(mol.getAtom(0).coords())) mol.convert2OBMol() # initialize constraints constr = openbabel.OBFFConstraints() # openbabel indexing starts at 1 ### !!! # convert metals to carbons for FF indmtls = [] mtlsnums = [] for iiat, atom in enumerate(openbabel.OBMolAtomIter(mol.OBMol)): if atom.GetAtomicNum() in metals: indmtls.append(iiat) mtlsnums.append(atom.GetAtomicNum()) atom.SetAtomicNum(6) for cat in frozenats: constr.AddAtomConstraint(cat+1) # indexing babel # set up forcefield forcefield = openbabel.OBForceField.FindForceField(ff) forcefield.Setup(mol.OBMol, constr) # force field optimize structure forcefield.ConjugateGradients(2500) forcefield.GetCoordinates(mol.OBMol) # reset atomic number to metal for i, iiat in enumerate(indmtls): mol.OBMol.GetAtomById(iiat).SetAtomicNum(mtlsnums[i]) mol.convert2mol3D() en = forcefield.Energy() del forcefield, constr return mol, en
def gen_types_from_bonds(self): """ Pass the bonding information into openbabel to get the atomic types. """ # import these locally so we can run fapswitch without them import openbabel as ob import pybel # Construct the molecule from atoms and bonds obmol = ob.OBMol() obmol.BeginModify() for atom in self.atoms: new_atom = obmol.NewAtom() new_atom.SetAtomicNum(atom.atomic_number) for bond, bond_info in self.bonds.items(): # Remember openbabel indexes from 1 obmol.AddBond(bond[0] + 1, bond[1] + 1, OB_BOND_ORDERS[bond_info[1]]) obmol.EndModify() pybel_mol = pybel.Molecule(obmol) # need to tell the typing system to ignore all atoms in the setup # or it will silently crash with memory issues constraint = ob.OBFFConstraints() for at_idx in range(pybel_mol.OBMol.NumAtoms()): constraint.AddIgnore(at_idx) uff = ob.OBForceField_FindForceField('uff') uff.Setup(pybel_mol.OBMol, constraint) uff.GetAtomTypes(pybel_mol.OBMol) # Dative nitrogen bonds break aromaticity determination from resonant # structures, so make anything with an aromatic bond be aromatic for at_idx, atom, ob_atom in zip(count(), self.atoms, pybel_mol): uff_type = ob_atom.OBAtom.GetData("FFAtomType").GetValue() if atom.type in ['C', 'N', 'O', 'S']: for bond, bond_info in self.bonds.items(): if at_idx in bond and bond_info[1] == 1.5: uff_type = uff_type[0] + '_R' break atom.uff_type = uff_type
def SaveConf(X, mol, ffclean=True, catoms=[]): conf3D = mol3D() conf3D.copymol3D(mol) # set coordinates using OBMol to keep bonding info OBMol = conf3D.OBMol for i, atom in enumerate(openbabel.OBMolAtomIter(OBMol)): atom.SetVector(X[i, 0], X[i, 1], X[i, 2]) #First stage of cleaning takes place with the metal still present if ffclean: ff = openbabel.OBForceField.FindForceField('UFF') s = ff.Setup(OBMol) if not s: print('FF setup failed') for i in range(200): ff.SteepestDescent(10) ff.ConjugateGradients(10) ff.GetCoordinates(OBMol) last_atom_index = OBMol.NumAtoms( ) #Delete the dummy metal atom that we added earlier metal_atom = OBMol.GetAtom(last_atom_index) OBMol.DeleteAtom(metal_atom) #Second stage of cleaning removes the metal, but uses constraints on the bonding atoms to ensure a binding conformer is maintained #This stage is critical for getting planar aromatic ligands like porphyrin and correct. Not really sure why though... if ffclean: ff = openbabel.OBForceField.FindForceField('UFF') constr = openbabel.OBFFConstraints() for atom in catoms: constr.AddAtomConstraint(atom + 1) s = ff.Setup(OBMol, constr) if not s: print('FF setup failed') for i in range(200): ff.SteepestDescent(10) ff.ConjugateGradients(10) ff.GetCoordinates(OBMol) conf3D.OBMol = OBMol conf3D.convert2mol3D() return conf3D
def optg_c2(self, smartss, iass, ff='MMFF94', step=500): """ optg with constraints specified by `smartss vars ================ smartss -- ['[#1]', 'C(=O)[OH]'] iass -- [[0,], [1,2,3] ] """ # Define constraints c = ob.OBFFConstraints() iast = list(range(self.na)) iasf = [] for i, smarts_i in enumerate(smartss): #if 'H' in smarts_i: assert self.addh q = pb.Smarts(smarts_i) assert q.obsmarts.Match(self.m), '#ERROR: no match?' for match in q.obsmarts.GetMapList(): idxs = [self.m.GetAtom(ia).GetIdx() for ia in match] for j in iass[i]: iasf.append(idxs[j]) for ia in list(set(iast) ^ set(iasf)): c.AddAtomConstraint(idxs[j]) # Setup the force field with the constraints f = ob.OBForceField.FindForceField(ff) assert f.Setup( self.m, c ), '#ERROR: ForceFiled setup failure [contains non-mmff94 elements]?' f.SetConstraints(c) #if optimizer in ['cg','CG','ConjugateGradients']: f.ConjugateGradients(step) #elif optimizer in ['sd','SD','SteepestDescent']: # f.SteepestDescent(step) f.GetCoordinates(self.m) self.M = pb.Molecule(m) self.coords = get_coords(m)
def SaveConf(X, mol, ffclean=True, catoms=[]): conf3D = mol3D() conf3D.copymol3D(mol) # set coordinates using OBMol to keep bonding info OBMol = conf3D.OBMol for i, atom in enumerate(openbabel.OBMolAtomIter(OBMol)): atom.SetVector(X[i, 0], X[i, 1], X[i, 2]) if ffclean: ff = openbabel.OBForceField.FindForceField('mmff94') constr = openbabel.OBFFConstraints() # constrain connecting atoms for catom in catoms: constr.AddAtomConstraint(catom + 1) s = ff.Setup(OBMol, constr) if not s: print('FF setup failed') for i in range(200): ff.SteepestDescent(10) ff.ConjugateGradients(10) ff.GetCoordinates(OBMol) conf3D.OBMol = OBMol conf3D.convert2mol3D() return conf3D
def cons_opt(infile,outfile,idx): """ Only Mol2/UFF can Optimize Optimize with Pd1-P-P1 constraints :param infile: :param outfile: :param idx: :return: """ p1,p2,pd = idx conv = ob.OBConversion() conv.SetInAndOutFormats("mol2", "mol2") mol = ob.OBMol() conv.ReadFile(mol, infile) cons = ob.OBFFConstraints() pp = 3.2 ppd = 2.4 cons.AddDistanceConstraint(p1, p2, pp) cons.AddDistanceConstraint(p1, pd, ppd) cons.AddDistanceConstraint(p2, pd, ppd) # Set up FF ff = ob.OBForceField.FindForceField("UFF") ff.Setup(mol, cons) ff.SetConstraints(cons) ff.EnableCutOff(True) # Optimize ff.ConjugateGradients(10000) ff.GetCoordinates(mol) def ring_bond(ring): """ :param ring: OBRing class :return: list of lists [atom1,atom2] """ bonds = [] mol = ring.GetParent() for bond in ob.OBMolBondIter(mol): at1 = bond.GetBeginAtom().GetIndex() + 1 at2 = bond.GetEndAtom().GetIndex() + 1 if ring.IsMember(bond): if not bond.IsAromatic(): bonds.append(sorted([at1,at2])) return bonds def common_atom(bond,bonds): """ :param bond: list [atom1,atom2] :param bonds: list of list [atom1,atom2] :return: True if there is common atom in bonds """ result = False if len(bonds) == 0: return result for bond2 in bonds: for at1 in bond: for at2 in bond2: if at1 == at2: result = True return result return result # extract ring info # iterate over all bond # CHIRAL ATOMS chiral = [] for atom in ob.OBMolAtomIter(mol): if atom.IsChiral(): chiral.append(atom.GetIndex() + 1) rot_bond = [] rca4 = [] rca23 = [] for bond in ob.OBMolBondIter(mol): at1 = bond.GetBeginAtom().GetIndex() + 1 at2 = bond.GetEndAtom().GetIndex() + 1 # print(at1, at2) if bond.IsRotor() and not bond.IsAromatic(): rot = sorted([at1,at2]) # print(outfile,"ROT ",rot) rot_bond.append(rot) if bond.IsClosure(): rca0 = sorted([at1,at2]) rca = sorted([at1,at2]) # The Assumption is IsClosure picking up only one bond in the ring # and bond.IsRotor() does not provide any ring bonds. # to prevent rca23 sharing common atoms if len(rca23) != 0: if common_atom(rca,rca23): ringbonds = ring_bond(bond.FindSmallestRing()) for rbond in ringbonds: if common_atom(rbond,rca23): continue else: rca0 = rbond.copy() rca = rbond.copy() break else: ringbonds = ring_bond(bond.FindSmallestRing()) for rbond in ringbonds: if not (rbond[0] in rca or rbond[1] in rca): rca0 = rbond.copy() rca = rbond.copy() break rca23.append(rca0) #print(outfile,"RING OPENING BOND", rca0) ring = bond.FindSmallestRing() ring_rots = [] if not ring.IsAromatic(): for bond1 in ob.OBMolBondIter(mol): if ring.IsMember(bond1): b1 = bond1.GetBeginAtom().GetIndex() + 1 b2 = bond1.GetEndAtom().GetIndex() + 1 rot = sorted([b1,b2]) if rot != rca0: ring_rots.append(rot) #print("RING ROT:",ring_rots) for rrot in ring_rots: if rca0[0] in rrot: atom = rrot.copy() atom.remove(rca0[0]) #print(atom) rca.insert(0,atom[0]) #print("INSERT ",rca) elif rca0[1] in rrot: atom = rrot.copy() atom.remove(rca0[1]) #print(rca) rca.append(atom[0]) #print("APPEND ",rca) elif rca0 != rot: rot_bond.append(rrot) rca4.append(rca) # print(outfile,"CHIRAL",chiral) # print(outfile,"RCA4",rca4) # print(outfile,"RCA23",rca23) check = [] for rr in rca4: check.append(rr[1]) check.append(rr[2]) bond = sorted([rr[1],rr[2]]) if bond in rot_bond: rot_bond.remove(bond) if len(check) != len(set(check)): print("\t\tBAD",outfile) print("\t\t",rca4) # else: # print("\t\tBAD",outfile) # print(outfile,"ROT", rot_bond) conv.WriteFile(mol, outfile) return chiral, rca4, rot_bond
def add_pd(infile,outfile): conv = ob.OBConversion() conv.SetInAndOutFormats("mol2","mol2") mol = ob.OBMol() conv.ReadFile(mol, infile) nAtoms = mol.NumAtoms() p = [] for i in range(nAtoms): n = i + 1 at = mol.GetAtom(n) an = (at.GetAtomicNum()) if an == 15: p.append(n) elif an == 8: # Oxygen neis = [] for nei in ob.OBAtomAtomIter(at): neis.append(nei) # Oxygen has one neighbor lnei = len(neis) if lnei == 1: nei = neis[0] # neighbor is P (i.e. P=O) if nei.GetAtomicNum() == 15: return None if len(p) != 2: return None # optimize for P-P distance first p1, p2 = p cons = ob.OBFFConstraints() pp = 3.2 cons.AddDistanceConstraint(p1, p2, pp) # Set up FF ff = ob.OBForceField.FindForceField("UFF") ff.Setup(mol, cons) ff.SetConstraints(cons) ff.EnableCutOff(True) # Optimize ff.ConjugateGradients(10000) ff.GetCoordinates(mol) cont = True while cont: pho1 = mol.GetAtom(p1) pho2 = mol.GetAtom(p2) pp1 = pho1.GetDistance(pho2) err0 = abs(pp1-pp) if err0 < 0.015: cont = False else: print("\tNOT converged YET:",outfile, " diff:", err0) ff.ConjugateGradients(10000) ff.GetCoordinates(mol) p = [] pxyz = [] nxyz = [] # find out where two P are located for i in range(nAtoms): n = i + 1 at = mol.GetAtom(n) an = (at.GetAtomicNum()) if an == 15: p.append(n) pxyz.append([at.x(),at.y(),at.z()]) else: nxyz.append([at.x(),at.y(),at.z()]) nxyz = np.array(nxyz) # Add Pd and connect it to two Ps a = mol.NewAtom() a.SetAtomicNum(46) pxyz = np.array(pxyz) x,y,z = (pxyz[0] + pxyz[1])/2 pdxyz = np.array([x,y,z]) vec0 = None r0 = 100.0 for vec in nxyz: vec = vec - pdxyz r = np.linalg.norm(vec) if r < r0: r0 = r vec0 = vec x,y,z = pdxyz-10.0*vec0 a.SetVector(x,y,z) # AddBond(BeginIdx,EndIdx,bond order) pd = mol.NumAtoms() p1,p2 = p mol.AddBond(pd,p1,1) mol.AddBond(pd,p2,1) mol.NumAtoms() conv.WriteFile(mol, outfile) return [p1,p2,pd]
def generate_limited_rotamer_set( input_path, output_path, atoms_to_unfreeze, output_format='mol2', include_input_conformation_in_output=True, # Note: the Confab settings below (rmsd_cutoff, conf_cutoff, energy_cutoff, verbose) # are currently set to the default values from Confab. # You you might need to change these for your project. rmsd_cutoff=0.5, conf_cutoff=10000, energy_cutoff=25.0, confab_verbose=True): assert (output_path.endswith(output_format)) assert (os.path.isfile(input_path)) mol = read_molecules(input_path, single=True) # force field for open babel applied - mmff94 pff = ob.OBForceField_FindType("mmff94") assert (pff.Setup(mol)) # Make sure setup works OK count_aromatic_bonds(mol) print("Molecule " + mol.GetTitle()) print("Number of rotatable bonds = " + str(mol.NumRotors())) if len(atoms_to_unfreeze) > 0: print('%d atoms will be allowed to move; freezing others' % len(atoms_to_unfreeze)) constraints = ob.OBFFConstraints() for atom in ob.OBMolAtomIter(mol): atom_id = atom.GetIndex() + 1 if atom_id not in atoms_to_unfreeze: constraints.AddAtomConstraint(atom_id) pff.SetConstraints(constraints) # Run Confab conformer generation pff.DiverseConfGen(rmsd_cutoff, conf_cutoff, energy_cutoff, confab_verbose) pff.GetConformers(mol) if include_input_conformation_in_output: confs_to_write = mol.NumConformers() else: confs_to_write = mol.NumConformers() - 1 print("Generated %d conformers total (%d will be written)" % (mol.NumConformers(), confs_to_write)) obconversion = ob.OBConversion() obconversion.SetOutFormat(output_format) output_strings = [] for conf_num in range(confs_to_write): mol.SetConformer(conf_num) output_strings.append(obconversion.WriteString(mol)) print('Writing %d conformations to %s' % (len(output_strings), output_path)) with open(output_path, 'w') as f: for output_string in output_strings: f.write(output_string)
def gen_babel_uff_properties(self): """ Process a supercell with babel to calculate UFF atom types and bond orders. """ # import these locally so we can run fapswitch without them import openbabel as ob import pybel # Pass as free form fractional # GG's periodic should take care of bonds warning("Assuming periodic openbabel; not generating supercell") cell = self.cell as_fffract = [ 'generated fractionals\n', '%f %f %f %f %f %f\n' % cell.params ] for atom in self.atoms: atom_line = ("%s " % atom.type + "%f %f %f\n" % tuple(atom.cellfpos)) as_fffract.append(atom_line) pybel_string = ''.join(as_fffract) pybel_mol = pybel.readstring('fract', pybel_string) # need to tell the typing system to ignore all atoms in the setup # or it will silently crash with memory issues constraint = ob.OBFFConstraints() for at_idx in range(pybel_mol.OBMol.NumAtoms()): constraint.AddIgnore(at_idx) uff = ob.OBForceField_FindForceField('uff') uff.Setup(pybel_mol.OBMol, constraint) uff.GetAtomTypes(pybel_mol.OBMol) for atom, ob_atom in zip(self.atoms, pybel_mol): atom.uff_type = ob_atom.OBAtom.GetData("FFAtomType").GetValue() bonds = {} max_idx = self.natoms # look at all the bonds separately from the atoms for bond in ob.OBMolBondIter(pybel_mol.OBMol): # These rules are translated from ob/forcefielduff.cpp... start_idx = bond.GetBeginAtomIdx() end_idx = bond.GetEndAtomIdx() if start_idx > max_idx and end_idx > max_idx: continue if end_idx > max_idx: end_idx %= max_idx if start_idx > max_idx: start_idx %= max_idx start_atom = bond.GetBeginAtom() end_atom = bond.GetEndAtom() bond_order = bond.GetBondOrder() if bond.IsAromatic(): bond_order = 1.5 # e.g., in Cp rings, may not be "aromatic" by OB # but check for explicit hydrogen counts #(e.g., biphenyl inter-ring is not aromatic) #FIXME(tdaff): aromatic C from GetType is "Car" is this correct? if start_atom.GetType()[-1] == 'R' and end_atom.GetType( )[-1] == 'R' and start_atom.ExplicitHydrogenCount( ) == 1 and end_atom.ExplicitHydrogenCount() == 1: bond_order = 1.5 if bond.IsAmide(): bond_order = 1.41 # save the indicies as zero based bond_length = bond.GetLength() bond_id = tuple(sorted((start_idx - 1, end_idx - 1))) bonds[bond_id] = (bond_length, bond_order) self.bonds = bonds
def SaveConf(X, mol, ffclean=True, catoms=[]): """Further cleans up with OB FF and saves to a new mol3D object. Note that distance geometry tends to produce puckered aromatic rings because of the lack of explicit impropers, see Riniker et al. JCIM (2015) 55, 2562-74 for details. Hence, a FF optimization (with connection atoms constrained) is recommended to clean up the structure. Parameters ---------- x : np.array Array of coordinates. mol : mol3D mol3D class instance of original molecule. ffclean : bool, optional Flag for openbabel forcefield cleanup. Default is True. catoms : list, optional List of connection atoms used to generate FF constraints if specified. Default is empty. Returns ------- conf3D : mol3D mol3D class instance of conformer. """ conf3D = mol3D() conf3D.copymol3D(mol) # set coordinates using OBMol to keep bonding info OBMol = conf3D.OBMol for i, atom in enumerate(openbabel.OBMolAtomIter(OBMol)): atom.SetVector(X[i, 0], X[i, 1], X[i, 2]) #First stage of cleaning takes place with the metal still present if ffclean: ff = openbabel.OBForceField.FindForceField('UFF') s = ff.Setup(OBMol) if not s: print('FF setup failed') for i in range(200): ff.SteepestDescent(10) ff.ConjugateGradients(10) ff.GetCoordinates(OBMol) last_atom_index = OBMol.NumAtoms( ) #Delete the dummy metal atom that we added earlier metal_atom = OBMol.GetAtom(last_atom_index) OBMol.DeleteAtom(metal_atom) #Second stage of cleaning removes the metal, but uses constraints on the bonding atoms to ensure a binding conformer is maintained #This stage is critical for getting planar aromatic ligands like porphyrin and correct. Not really sure why though... if ffclean: ff = openbabel.OBForceField.FindForceField('UFF') constr = openbabel.OBFFConstraints() for atom in catoms: constr.AddAtomConstraint(atom + 1) s = ff.Setup(OBMol, constr) if not s: print('FF setup failed') for i in range(200): ff.SteepestDescent(10) ff.ConjugateGradients(10) ff.GetCoordinates(OBMol) conf3D.OBMol = OBMol conf3D.convert2mol3D() return conf3D
def confab_conformers(self, forcefield="mmff94", freeze_atoms=None, rmsd_cutoff=0.5, energy_cutoff=50.0, conf_cutoff=100000, verbose=False): """ Conformer generation based on Confab to generate all diverse low-energy conformers for molecules. This is different from rotor_conformer or gen3d_conformer as it aims to not simply to find a low energy conformation but to generate several different conformations. Args: forcefield (str): Default is mmff94. Options are 'gaff', 'ghemical', 'mmff94', 'mmff94s', and 'uff'. freeze_atoms ([int]): index of atoms to be freezed when performing conformer search, default is None. rmsd_cutoff (float): rmsd_cufoff, default is 0.5 Angstrom. energy_cutoff (float): energy_cutoff, default is 50.0 kcal/mol. conf_cutoff (float): max number of conformers to test, default is 1 million. verbose (bool): whether to display information on torsions found, default is False. Returns: (list): list of pymatgen Molecule objects for generated conformers. """ if self._obmol.GetDimension() != 3: self.make3d() else: self.add_hydrogen() ff = ob.OBForceField_FindType(forcefield) if ff == 0: print("Could not find forcefield {} in openbabel, the forcefield " "will be reset as default 'mmff94'".format(forcefield)) ff = ob.OBForceField_FindType("mmff94") if freeze_atoms: print('{} atoms will be freezed'.format(len(freeze_atoms))) constraints = ob.OBFFConstraints() for atom in ob.OBMolAtomIter(self._obmol): atom_id = atom.GetIndex() + 1 if id in freeze_atoms: constraints.AddAtomConstraint(atom_id) ff.SetConstraints(constraints) # Confab conformer generation ff.DiverseConfGen(rmsd_cutoff, conf_cutoff, energy_cutoff, verbose) ff.GetConformers(self._obmol) # Number of conformers generated by Confab conformer generation conformer_num = self._obmol.NumConformers() conformers = [] for i in range(conformer_num): self._obmol.SetConformer(i) conformer = copy.deepcopy( BabelMolAdaptor(self._obmol).pymatgen_mol) conformers.append(conformer) self._obmol.SetConformer(0) return conformers
def minimize_ob(selection='enabled', state=-1, ff='UFF', nsteps=500, conv=0.0001, cutoff=0, cut_vdw=6.0, cut_elec=8.0, name='', quiet=1, _self=cmd): ''' DESCRIPTION Emergy minimization with openbabel Supports fixed atoms (flag fix) ARGUMENTS selection = str: atom selection state = int: object state {default: -1} ff = GAFF|MMFF94s|MMFF94|UFF|Ghemical: force field {default: UFF} nsteps = int: number of steps {default: 500} ''' import openbabel as ob state = int(state) sele = _self.get_unused_name('_sele') _self.select(sele, selection, 0) try: ioformat = 'mol' molstr = _self.get_str(ioformat, sele, state) obconversion = ob.OBConversion() obconversion.SetInAndOutFormats(ioformat, ioformat) mol = ob.OBMol() obconversion.ReadString(mol, molstr) # add hydrogens orig_ids = [a.GetId() for a in ob.OBMolAtomIter(mol)] mol.AddHydrogens() added_ids = set(a.GetId() for a in ob.OBMolAtomIter(mol)).difference(orig_ids) consttrains = ob.OBFFConstraints() consttrains.Setup(mol) # atoms with "flag fix" fixed_indices = get_fixed_indices(sele, state, _self) for idx in fixed_indices: consttrains.AddAtomConstraint(idx + 1) # setup forcefield (one of: GAFF, MMFF94s, MMFF94, UFF, Ghemical) ff = ob.OBForceField.FindForceField(ff) ff.Setup(mol, consttrains) if int(cutoff): ff.EnableCutOff(True) ff.SetVDWCutOff(float(cut_vdw)) ff.SetElectrostaticCutOff(float(cut_elec)) # run minimization ff.SteepestDescent(int(nsteps) // 2, float(conv)) ff.ConjugateGradients(int(nsteps) // 2, float(conv)) ff.GetCoordinates(mol) # remove previously added hydrogens for hydro_id in added_ids: mol.DeleteAtom(mol.GetAtomById(hydro_id)) molstr = obconversion.WriteString(mol) load_or_update(molstr, name, sele, state, _self) if not int(quiet): print(' Energy: %8.2f %s' % (ff.Energy(), ff.GetUnit())) finally: _self.delete(sele)