def get_linear_angle(mol, ind): flag, catoms = is_linear_ligand(mol, ind) if flag: vec1 = np.array(mol.getAtomCoords(catoms[0])) - np.array(mol.getAtomCoords(ind)) vec2 = np.array(mol.getAtomCoords(catoms[1])) - np.array(mol.getAtomCoords(ind)) ang = vecangle(vec1, vec2) else: ang = 0 return flag, ang
def getconnections(core, catom, Midx, BL, ABXang): Ocoords = core.getAtom(catom).coords() Mcoords = core.getAtom(Midx).coords() backbcoords = alignPtoaxis(Ocoords, Ocoords, vecdiff(Ocoords, Mcoords), BL) am = atom3D('C', backbcoords) connPts = [] for iphi in range(1, 359, 10): for itheta in range(1, 179, 1): P = PointTranslateSph(Ocoords, backbcoords, [BL, iphi, itheta]) am.setcoords(P) ang = 180-vecangle(vecdiff(Ocoords, Mcoords), vecdiff(P, Ocoords)) if abs(ang - ABXang) < 1: connPts.append(P) return connPts
def is_linear_ligand(mol, ind): catoms = mol.getBondedAtomsSmart(ind) metal_ind = mol.findMetal()[0] flag = False if metal_ind in catoms and len(catoms) == 2: ind_next = find_the_other_ind(catoms[:], metal_ind) _catoms = mol.getBondedAtomsSmart(ind_next) if len(_catoms) == 1: flag = True elif len(_catoms) == 2: ind_next2 = find_the_other_ind(_catoms[:], ind) vec1 = np.array(mol.getAtomCoords(ind)) - np.array(mol.getAtomCoords(ind_next)) vec2 = np.array(mol.getAtomCoords(ind_next2)) - np.array(mol.getAtomCoords(ind_next)) ang = vecangle(vec1, vec2) if ang > 170: flag = True # print(flag, catoms) return flag, catoms
def GetBoundsMatrices(mol, natoms, catoms=[], shape=[], A=[]): """Generate distance bounds matrices. The basic idea is outlined in ref [1]. We first apply 1-2 (bond length) and 1-3 (bond angle) constraints, read from the FF-optimized initial conformer. Next, to bias the search towards coordinating conformers, approximate connection atom distance constraints based on topological distances are also included. Parameters ---------- mol : mol3D mol3D class instance of molecule. natoms : int Number of atoms in the molecule. catoms : list, optional List of ligand connection atoms. Default is Empty. shape : dict Dict containing angles. A : list List of lists making a distance 2 connectivity matrix. Returns ------- LB : np.array Lower bound matrix. UB : np.array Upper bound matrix. """ LB = np.zeros((natoms, natoms)) # lower bound UB = np.zeros((natoms, natoms)) # upper bound, both symmetric # Set constraints for all atoms excluding the dummy metal atom for i in range(natoms - 1): for j in range(natoms - 1): # 1-2 constraints: UB = LB = BL if mol.OBMol.GetBond(i + 1, j + 1) is not None: UB[i][j] = distance(mol.getAtomCoords(i), mol.getAtomCoords(j)) UB[j][i] = distance(mol.getAtomCoords(i), mol.getAtomCoords(j)) LB[i][j] = distance(mol.getAtomCoords(i), mol.getAtomCoords(j)) LB[j][i] = distance(mol.getAtomCoords(i), mol.getAtomCoords(j)) for i in range(natoms - 1): for j in range(natoms - 1): for k in range(natoms - 1): # 1-3 constraints: UB = LB = BL if mol.OBMol.GetBond( i + 1, j + 1) is not None and mol.OBMol.GetBond( j + 1, k + 1) is not None and j != k and i != k: AB = vecdiff(mol.getAtomCoords(j), mol.getAtomCoords(i)) BC = vecdiff(mol.getAtomCoords(k), mol.getAtomCoords(j)) UB[i][k] = CosRule(norm(AB), norm(BC), 180 - vecangle(AB, BC)) UB[k][i] = CosRule(norm(AB), norm(BC), 180 - vecangle(AB, BC)) LB[i][k] = CosRule(norm(AB), norm(BC), 180 - vecangle(AB, BC)) LB[k][i] = CosRule(norm(AB), norm(BC), 180 - vecangle(AB, BC)) # Set constraints for atoms bonded to the dummy metal atom # Currently assumes all M-L bonds are 2 Angstroms dummy_idx = natoms - 1 M_L_bond = 2 for catom in catoms: # Set 1-2 constraints UB[catom][dummy_idx] = M_L_bond UB[dummy_idx][catom] = M_L_bond LB[catom][dummy_idx] = M_L_bond LB[dummy_idx][catom] = M_L_bond if len(catoms) > 1: # Set 1-3 contraints for ligating atoms for i in range(len(catoms[:-1])): for j in range(i + 1, len(catoms)): angle = shape[str(i) + '-' + str(j)] lig_distance = CosRule(M_L_bond, M_L_bond, angle) UB[catoms[i]][catoms[j]] = lig_distance UB[catoms[j]][catoms[i]] = lig_distance LB[catoms[i]][catoms[j]] = lig_distance LB[catoms[j]][catoms[i]] = lig_distance expanded_vdwrad = vdwrad.copy() expanded_vdwrad[ 'Fe'] = 1.5 # Default vdw radius for the dummy metal is 1.5 for i in range(natoms): for j in range(i): # fill LBs with sums of vdW radii and UBs with arbitrary large cutoff if LB[i][j] == 0: LB[i][j] = expanded_vdwrad[mol.getAtom( i).sym] + expanded_vdwrad[mol.getAtom(j).sym] LB[j][i] = expanded_vdwrad[mol.getAtom( i).sym] + expanded_vdwrad[mol.getAtom(j).sym] UB[i][j] = 100 UB[j][i] = 100 return LB, UB
def oct_comp(file_in, angle_ref=oct_angle_ref, catoms_arr=None, debug=False): my_mol = create_mol_with_xyz(_file_in=file_in) num_coord_metal, catoms = get_num_coord_metal(file_in=file_in) # metal_ind = my_mol.findMetal()[0] metal_coord = my_mol.getAtomCoords(my_mol.findMetal()[0]) catom_coord = [] if not catoms_arr == None: catoms = catoms_arr num_coord_metal = len(catoms_arr) theta_arr, oct_dist = [], [] for atom in catoms: coord = my_mol.getAtomCoords(atom) catom_coord.append(coord) th_input_arr = [] for idx1, coord1 in enumerate(catom_coord): delr1 = (np.array(coord1) - np.array(metal_coord)).tolist() theta_tmp = [] for idx2, coord2 in enumerate(catom_coord): if idx2 != idx1: delr2 = (np.array(coord2) - np.array(metal_coord)).tolist() theta = vecangle(delr1, delr2) theta_tmp.append(theta) th_input_arr.append([catoms[idx1], theta_tmp]) th_output_arr, sum_del_angle, catoms_arr, max_del_sig_angle = loop_target_angle_arr( th_input_arr, angle_ref) if debug: print(('th:', th_output_arr)) print(('sum_del:', sum_del_angle)) print(('catoms_arr:', catoms_arr)) print( ('catoms_type:', [my_mol.getAtom(x).symbol() for x in catoms_arr])) for idx, ele in enumerate(th_output_arr): theta_arr.append([catoms_arr[idx], sum_del_angle[idx], ele]) # theta_arr.sort(key=sort_sec_ele) # theta_trunc_arr = theta_arr[0:6] theta_trunc_arr = theta_arr # print('truncated theta array:', theta_trunc_arr) theta_trunc_arr_T = list(map(list, list(zip(*theta_trunc_arr)))) oct_catoms = theta_trunc_arr_T[0] oct_angle_devi = theta_trunc_arr_T[1] oct_angle_all = theta_trunc_arr_T[2] if debug: print(('Summation of deviation angle for catoms:', oct_angle_devi)) print(('Angle for catoms:', oct_angle_all)) for atom in oct_catoms: coord = catom_coord[catoms.index(atom)] dist = distance(coord, metal_coord) oct_dist.append(dist) oct_dist.sort() # print('!!!oct_dist:', oct_dist) try: # For Oct dist_del_arr = np.array([ oct_dist[3] - oct_dist[0], oct_dist[4] - oct_dist[1], oct_dist[5] - oct_dist[2] ]) min_posi = np.argmin(dist_del_arr) if min_posi == 0: dist_eq, dist_ax = oct_dist[:4], oct_dist[4:] elif min_posi == 1: dist_eq, dist_ax = oct_dist[1:5], [oct_dist[0], oct_dist[5]] else: dist_eq, dist_ax = oct_dist[2:], oct_dist[:2] except IndexError: # For one empty site if (oct_dist[3] - oct_dist[0]) > (oct_dist[4] - oct_dist[1]): dist_ax, dist_eq = oct_dist[:1], oct_dist[1:] # ax dist is smaller else: dist_ax, dist_eq = oct_dist[4:], oct_dist[:4] # eq dist is smaller dist_del_all = oct_dist[-1] - oct_dist[0] if debug: print(('dist:', dist_eq, dist_ax)) dist_del_eq = max(dist_eq) - min(dist_eq) dist_del_ax = max(dist_ax) - min(dist_ax) dist_del_eq_ax = max(abs(max(dist_eq) - min(dist_ax)), abs(max(dist_ax) - min(dist_eq))) oct_dist_del = [dist_del_eq, dist_del_ax, dist_del_eq_ax, dist_del_all] if debug: print(('distance difference for catoms to metal (eq, ax, eq_ax):', oct_dist_del)) return oct_angle_devi, oct_dist_del, max_del_sig_angle, catoms_arr
def oct_comp(file_in, angle_ref=oct_angle_ref, catoms_arr=None, debug=False): my_mol = create_mol_with_xyz(_file_in=file_in) num_coord_metal, catoms = get_num_coord_metal(file_in=file_in) # metal_ind = my_mol.findMetal()[0] metal_coord = my_mol.getAtomCoords(my_mol.findMetal()[0]) catom_coord = [] if not catoms_arr == None: catoms = catoms_arr num_coord_metal = len(catoms_arr) theta_arr, oct_dist = [], [] for atom in catoms: coord = my_mol.getAtomCoords(atom) catom_coord.append(coord) th_input_arr = [] for idx1, coord1 in enumerate(catom_coord): delr1 = (np.array(coord1) - np.array(metal_coord)).tolist() theta_tmp = [] for idx2, coord2 in enumerate(catom_coord): if idx2 != idx1: delr2 = (np.array(coord2) - np.array(metal_coord)).tolist() theta = vecangle(delr1, delr2) theta_tmp.append(theta) th_input_arr.append([catoms[idx1], theta_tmp]) th_output_arr, sum_del_angle, catoms_arr, max_del_sig_angle = loop_target_angle_arr(th_input_arr, angle_ref) if debug: print('th:', th_output_arr) print('sum_del:', sum_del_angle) print('catoms_arr:', catoms_arr) print('catoms_type:', [my_mol.getAtom(x).symbol() for x in catoms_arr]) for idx, ele in enumerate(th_output_arr): theta_arr.append([catoms_arr[idx], sum_del_angle[idx], ele]) # theta_arr.sort(key=sort_sec_ele) # theta_trunc_arr = theta_arr[0:6] theta_trunc_arr = theta_arr # print('truncated theta array:', theta_trunc_arr) theta_trunc_arr_T = list(map(list, zip(*theta_trunc_arr))) oct_catoms = theta_trunc_arr_T[0] oct_angle_devi = theta_trunc_arr_T[1] oct_angle_all = theta_trunc_arr_T[2] if debug: print('Summation of deviation angle for catoms:', oct_angle_devi) print('Angle for catoms:', oct_angle_all) for atom in oct_catoms: coord = catom_coord[catoms.index(atom)] dist = distance(coord, metal_coord) oct_dist.append(dist) oct_dist.sort() # print('!!!oct_dist:', oct_dist) try: ### For Oct dist_del_arr = np.array([oct_dist[3] - oct_dist[0], oct_dist[4] - oct_dist[1], oct_dist[5] - oct_dist[2]]) min_posi = np.argmin(dist_del_arr) if min_posi == 0: dist_eq, dist_ax = oct_dist[:4], oct_dist[4:] elif min_posi == 1: dist_eq, dist_ax = oct_dist[1:5], [oct_dist[0], oct_dist[5]] else: dist_eq, dist_ax = oct_dist[2:], oct_dist[:2] except IndexError: ## For one empty site if (oct_dist[3] - oct_dist[0]) > (oct_dist[4] - oct_dist[1]): dist_ax, dist_eq = oct_dist[:1], oct_dist[1:] # ax dist is smaller else: dist_ax, dist_eq = oct_dist[4:], oct_dist[:4] # eq dist is smaller dist_del_all = oct_dist[-1] - oct_dist[0] if debug: print('dist:', dist_eq, dist_ax) dist_del_eq = max(dist_eq) - min(dist_eq) dist_del_ax = max(dist_ax) - min(dist_ax) dist_del_eq_ax = max(abs(max(dist_eq) - min(dist_ax)), abs(max(dist_ax) - min(dist_eq))) oct_dist_del = [dist_del_eq, dist_del_ax, dist_del_eq_ax, dist_del_all] if debug: print('distance difference for catoms to metal (eq, ax, eq_ax):', oct_dist_del) return oct_angle_devi, oct_dist_del, max_del_sig_angle, catoms_arr
def decorate_ligand(args, ligand_to_decorate, decoration, decoration_index): # structgen depends on decoration_manager, and decoration_manager depends on structgen.ffopt # Thus, this import needs to be placed here to avoid a circular dependence from molSimplify.Scripts.structgen import ffopt # INPUT # - args: placeholder for input arguments # - ligand_to_decorate: mol3D ligand # - decoration: list of smiles/decorations # - decoration_index: list of ligand atoms to replace # OUTPUT # - new_ligand: built ligand # - complex3D: list of all mol3D ligands and core # - emsg: error messages #if args.debug: # print 'decorating ligand' lig = ligand_to_decorate ## reorder to ensure highest atom index ## removed first sort_order = [ i[0] for i in sorted(enumerate(decoration_index), key=lambda x: x[1]) ] sort_order = sort_order[::-1] ## reverse decoration_index = [decoration_index[i] for i in sort_order] decoration = [decoration[i] for i in sort_order] if args.debug: print(('decoration_index is ' + str(decoration_index))) licores = getlicores() if not isinstance(lig, mol3D): lig, emsg = lig_load(lig, licores) else: lig.convert2OBMol() lig.charge = lig.OBMol.GetTotalCharge() lig.convert2mol3D() # convert to mol3D ## create new ligand merged_ligand = mol3D() merged_ligand.copymol3D(lig) for i, dec in enumerate(decoration): print(('** decoration number ' + str(i) + ' attaching ' + dec + ' at site ' + str(decoration_index[i]) + '**\n')) dec, emsg = lig_load(dec, licores) # dec.OBMol.AddHydrogens() dec.convert2mol3D() # convert to mol3D if args.debug: print(i) print(decoration_index) print((merged_ligand.getAtom(decoration_index[i]).symbol())) print((merged_ligand.getAtom(decoration_index[i]).coords())) merged_ligand.writexyz('basic.xyz') #dec.writexyz('dec' + str(i) + '.xyz') Hs = dec.getHsbyIndex(0) if len(Hs) > 0 and (not len(dec.cat)): dec.deleteatom(Hs[0]) dec.charge = dec.charge - 1 #dec.writexyz('dec_noH' + str(i) + '.xyz') if len(dec.cat) > 0: decind = dec.cat[0] else: decind = 0 dec.alignmol(dec.getAtom(decind), merged_ligand.getAtom(decoration_index[i])) r1 = dec.getAtom(decind).coords() r2 = dec.centermass() # center of mass rrot = r1 decb = mol3D() decb.copymol3D(dec) #################################### # center of mass of local environment (to avoid bad placement of bulky ligands) auxmol = mol3D() for at in dec.getBondedAtoms(decind): auxmol.addAtom(dec.getAtom(at)) if auxmol.natoms > 0: r2 = auxmol.centermass() # overwrite global with local centermass #################################### # rotate around axis and get both images theta, u = rotation_params(merged_ligand.centermass(), r1, r2) #print('u = ' + str(u) + ' theta = ' + str(theta)) dec = rotate_around_axis(dec, rrot, u, theta) if args.debug: dec.writexyz('dec_ARA' + str(i) + '.xyz') decb = rotate_around_axis(decb, rrot, u, theta - 180) if args.debug: decb.writexyz('dec_ARB' + str(i) + '.xyz') d1 = distance(dec.centermass(), merged_ligand.centermass()) d2 = distance(decb.centermass(), merged_ligand.centermass()) dec = dec if (d2 < d1) else decb # pick best one ##################################### # check for linear molecule auxm = mol3D() for at in dec.getBondedAtoms(decind): auxm.addAtom(dec.getAtom(at)) if auxm.natoms > 1: r0 = dec.getAtom(decind).coords() r1 = auxm.getAtom(0).coords() r2 = auxm.getAtom(1).coords() if checkcolinear(r1, r0, r2): theta, urot = rotation_params( r1, merged_ligand.getAtom(decoration_index[i]).coords(), r2) theta = vecangle( vecdiff( r0, merged_ligand.getAtom(decoration_index[i]).coords()), urot) dec = rotate_around_axis(dec, r0, urot, theta) ## get the default distance between atoms in question connection_neighbours = merged_ligand.getAtom( merged_ligand.getBondedAtomsnotH(decoration_index[i])[0]) new_atom = dec.getAtom(decind) target_distance = connection_neighbours.rad + new_atom.rad position_to_place = vecdiff(new_atom.coords(), connection_neighbours.coords()) old_dist = norm(position_to_place) missing = (target_distance - old_dist) / 2 dec.translate([missing * position_to_place[j] for j in [0, 1, 2]]) r1 = dec.getAtom(decind).coords() u = vecdiff(r1, merged_ligand.getAtom(decoration_index[i]).coords()) dtheta = 2 optmax = -9999 totiters = 0 decb = mol3D() decb.copymol3D(dec) # check for minimum distance between atoms and center of mass distance while totiters < 180: #print('totiters '+ str(totiters)) dec = rotate_around_axis(dec, r1, u, dtheta) d0 = dec.mindist( merged_ligand) # try to maximize minimum atoms distance d0cm = dec.distance( merged_ligand) # try to maximize center of mass distance iteropt = d0cm + d0 # optimization function if (iteropt > optmax): # if better conformation, keep decb = mol3D() decb.copymol3D(dec) optmax = iteropt #temp = mol3D() #temp.copymol3D(merged_ligand) #temp.combine(decb) #temp.writexyz('opt_iter_'+str(totiters)+'.xyz') #print('new max! ' + str(iteropt) ) totiters += 1 dec = decb if args.debug: dec.writexyz('dec_aligned' + str(i) + '.xyz') print(('natoms before delete ' + str(merged_ligand.natoms))) print(('obmol before delete at ' + str(decoration_index[i]) + ' is ' + str(merged_ligand.OBMol.NumAtoms()))) ## store connectivity for deleted H BO_mat = merged_ligand.populateBOMatrix() row_deleted = BO_mat[decoration_index[i]] bonds_to_add = [] # find where to put the new bonds ->>> Issue here. for j, els in enumerate(row_deleted): if els > 0: # if there is a bond with an atom number # before the deleted atom, all is fine # else, we subtract one as the row will be be removed if j < decoration_index[i]: bond_partner = j else: bond_partner = j - 1 if len(dec.cat) > 0: bonds_to_add.append( (bond_partner, (merged_ligand.natoms - 1) + dec.cat[0], els)) else: bonds_to_add.append( (bond_partner, merged_ligand.natoms - 1, els)) ## perfrom delete merged_ligand.deleteatom(decoration_index[i]) merged_ligand.convert2OBMol() if args.debug: merged_ligand.writexyz('merged del ' + str(i) + '.xyz') ## merge and bond merged_ligand.combine(dec, bond_to_add=bonds_to_add) merged_ligand.convert2OBMol() if args.debug: merged_ligand.writexyz('merged' + str(i) + '.xyz') merged_ligand.printxyz() print('************') merged_ligand.convert2OBMol() merged_ligand, emsg = ffopt('MMFF94', merged_ligand, [], 0, [], False, [], 100) BO_mat = merged_ligand.populateBOMatrix() if args.debug: merged_ligand.writexyz('merged_relaxed.xyz') print(BO_mat) return (merged_ligand)