def sterimol(self, to_center=None, bisect_L=False, **kwargs): """ calculate ligand sterimol parameters for the ligand to_center - atom the ligand is coordinated to bisect_L - L axis will bisect (or analogous for higher denticity ligands) the L-M-L angle Default - center to centroid of key atoms **kwargs - arguments passed to Geometry.sterimol """ if to_center is not None: center = self.find(to_center) else: center = self.find( [BondedTo(atom) for atom in self.key_atoms], NotAny(self.atoms) ) if len(center) != 1: raise TypeError( "wrong number of center atoms specified;\n" "expected 1, got %i" % len(center) ) center = center[0] if bisect_L: L_axis = np.zeros(3) for atom in self.key_atoms: v = center.bond(atom) v /= np.linalg.norm(v) v /= len(self.key_atoms) L_axis += v else: L_axis = self.COM(self.key_atoms) - center.coords L_axis /= np.linalg.norm(L_axis) return super().sterimol(L_axis, center, self.atoms, **kwargs)
rotations.append(sub.conf_angle) mod_array = [] for i in range(0, len(rotations)): mod_array.append(1) for j in range(0, i): mod_array[i] *= conformers[j] prev_conf = 0 for conf in range(0, int(prod(conformers))): for i, sub in enumerate(substituents): rot = int(conf / mod_array[i]) % conformers[i] rot -= int(prev_conf / mod_array[i]) % conformers[i] angle = rotations[i] * rot if angle != 0: sub_atom = sub.find_exact(BondedTo(sub.end))[0] axis = sub_atom.bond(sub.end) center = sub.end.coords geom.rotate(axis, angle=angle, targets=sub.atoms, center=center) prev_conf = conf bad_subs = [] print_geom = geom if args.remove_clash: print_geom = Geometry([a.copy() for a in geom]) # print_geom.update_geometry(geom.coordinates.copy()) sub_list = [
def calc_cone(self, *args): self.settings.cone_option = self.cone_option.currentText() self.settings.radii = self.radii_option.currentText() self.settings.display_radii = self.display_radii.checkState( ) == Qt.Checked self.settings.display_cone = self.display_cone.checkState( ) == Qt.Checked if self.cone_option.currentText() == "Tolman (Unsymmetrical)": method = "tolman" else: method = self.cone_option.currentText() radii = self.radii_option.currentText() return_cones = self.display_cone.checkState() == Qt.Checked display_radii = self.display_radii.checkState() == Qt.Checked # self.table.setRowCount(0) for center_atom in selected_atoms(self.session): rescol = ResidueCollection(center_atom.structure) at_center = rescol.find_exact(AtomSpec(center_atom.atomspec))[0] if center_atom.structure in self.ligands: comp = Component( rescol.find([ AtomSpec(atom.atomspec) for atom in self.ligands[center_atom.structure] ]), to_center=rescol.find_exact(AtomSpec( center_atom.atomspec)), key_atoms=rescol.find(BondedTo(at_center)), ) else: comp = Component( rescol.find(NotAny(at_center)), to_center=rescol.find_exact(AtomSpec( center_atom.atomspec)), key_atoms=rescol.find(BondedTo(at_center)), ) cone_angle = comp.cone_angle( center=rescol.find(AtomSpec(center_atom.atomspec)), method=method, radii=radii, return_cones=return_cones, ) if return_cones: cone_angle, cones = cone_angle s = ".transparency 0.5\n" for cone in cones: apex, base, radius = cone s += ".cone %6.3f %6.3f %6.3f %6.3f %6.3f %6.3f %.3f open\n" % ( *apex, *base, radius) stream = BytesIO(bytes(s, "utf-8")) bild_obj, status = read_bild(self.session, stream, "Cone angle %s" % center_atom) self.session.models.add(bild_obj, parent=center_atom.structure) if display_radii: s = ".note radii\n" s += ".transparency 75\n" color = None for atom in comp.atoms: chix_atom = atom.chix_atom if radii.lower() == "umn": r = VDW_RADII[chix_atom.element.name] elif radii.lower() == "bondi": r = BONDI_RADII[chix_atom.element.name] if color is None or chix_atom.color != color: color = chix_atom.color rgb = [x / 255. for x in chix_atom.color] rgb.pop(-1) s += ".color %f %f %f\n" % tuple(rgb) s += ".sphere %f %f %f %f\n" % (*chix_atom.coord, r) stream = BytesIO(bytes(s, "utf-8")) bild_obj, status = read_bild(self.session, stream, "Cone angle radii") self.session.models.add(bild_obj, parent=center_atom.structure) row = self.table.rowCount() self.table.insertRow(row) name = QTableWidgetItem() name.setData(Qt.DisplayRole, center_atom.structure.name) self.table.setItem(row, 0, name) center = QTableWidgetItem() center.setData(Qt.DisplayRole, center_atom.atomspec) self.table.setItem(row, 1, center) ca = QTableWidgetItem() ca.setData(Qt.DisplayRole, "%.2f" % cone_angle) self.table.setItem(row, 2, ca) self.table.resizeColumnToContents(0) self.table.resizeColumnToContents(1) self.table.resizeColumnToContents(2)
def substitute(self, sub, target, attached_to=None, *args, minimize=False, use_greek=False, new_residue=False, new_name=None, **kwargs): """find the residue that target is on and substitute it for sub""" target = self.find_exact(target)[0] residue = self.find_residue(target) if len(residue) != 1: raise RuntimeError("multiple or no residues found containing %s" % str(target)) else: residue = residue[0] if attached_to: attached_to = self.find_exact(attached_to)[0] if not new_residue: # call substitute on residue so atoms are added to that residue if attached_to and attached_to not in residue.atoms: residue.atoms.append(attached_to) residue.substitute(sub, target, attached_to=attached_to, *args, minimize=False, **kwargs) residue.atoms.remove(attached_to) new_residue = True else: residue.substitute(sub, target, *args, attached_to=attached_to, minimize=False, **kwargs) if new_name: residue.name = new_name else: super().substitute(sub, target, attached_to=attached_to, *args, minimize=False, **kwargs) frag = self.remove_fragment(target, avoid=attached_to) for atom in frag: if atom is attached_to: continue if atom in residue: residue -= atom else: warn( "tried to remove an atom beyond the residues being considered: %s" % atom) resnum = len(self.residues) + 1 self.residues.append( Residue(sub.atoms, name=new_name, resnum=resnum, chain_id=target.chix_atom.residue.chain_id)) self._atom_update() # minimize on self so other residues can be taken into account if minimize: sub_start = sub.find_exact(BondedTo(sub.end))[0] self.minimize_torsion( sub.atoms, sub_start.bond(sub.end), sub.end, increment=10, ) if use_greek: from AaronTools.finders import BondsFrom, NotAny alphabet = [ "A", "B", "G", "D", "E", "Z", "H", "Q", "I", "K", "L", "M", "N", "X", "R", "T", "U", "F", "C", "Y", "W", ] start_atom = sub.end start = start_atom.chix_name[len(start_atom.element):] if not new_residue and any(letter == start for letter in alphabet): ndx = alphabet.index(start) + 1 else: ndx = 0 dist = 1 cur_atoms = sub.find(list(start_atom.connected), BondedTo(sub.end)) prev_atoms = [start_atom] stop = [start_atom] while ndx < len(alphabet): if not cur_atoms: break for i, atom in enumerate(cur_atoms): atom.chix_name = "%s%s" % (atom.element, alphabet[ndx]) if len([a for a in cur_atoms if a.element == atom.element ]) > 1: neighbors = sub.find(BondedTo(atom), prev_atoms, NotAny("H")) for neighbor in neighbors: if not hasattr(neighbor, "chix_name"): continue match = re.search("(\d+)", neighbor.chix_name) if match: i = int(match.group(1)) - 1 break atom.chix_name += "%i" % (i + 1) h_atoms = sub.find("H", BondedTo(atom)) for j, h_atom in enumerate(h_atoms): if len([ a for a in cur_atoms if a.element == atom.element ]) == 1 and len(h_atoms) > 1: h_atom.chix_name = "%s%s%i" % ("H", alphabet[ndx], j + 1) elif len(h_atoms) > 1: h_atom.chix_name = "%s%s%i%i" % ( "H", alphabet[ndx], i + 1, j + 1) elif (len([ a for a in cur_atoms if a.element == atom.element ]) > 1 and len( sub.find("H", [BondedTo(a) for a in cur_atoms])) > 1): h_atom.chix_name = "%s%s%i" % ("H", alphabet[ndx], i + 1) else: h_atom.chix_name = "%s%s" % ("H", alphabet[ndx]) prev_atoms = cur_atoms stop.extend(cur_atoms) cur_atoms = self.find( [BondedTo(atom) for atom in prev_atoms], NotAny(stop), NotAny("H"), ) for atom in cur_atoms[::-1]: if cur_atoms.count(atom) > 1: cur_atoms.remove(atom) ndx += 1 dist += 1 return sub
def sterimol(self, return_vector=False, radii="bondi", old_L=False, **kwargs): """ returns sterimol parameter values in a dictionary keys are B1, B2, B3, B4, B5, and L see Verloop, A. and Tipker, J. (1976), Use of linear free energy related and other parameters in the study of fungicidal selectivity. Pestic. Sci., 7: 379-390. (DOI: 10.1002/ps.2780070410) return_vector: bool/returns dict of tuple(vector start, vector end) instead radii: "bondi" - Bondi vdW radii "umn" - vdW radii from Mantina, Chamberlin, Valero, Cramer, and Truhlar old_L: bool - True: use original L (ideal bond length between first substituent atom and hydrogen + 0.40 angstrom False: use AaronTools definition AaronTools' definition of the L parameter is different than the original STERIMOL program. In STERIMOL, the van der Waals radii of the substituent is projected onto a plane parallel to the bond between the molecule and the substituent. The L parameter is 0.40 Å plus the distance from the first substituent atom to the outer van der Waals surface of the projection along the bond vector. This 0.40 Å is a correction for STERIMOL using a hydrogen to represent the molecule, when a carbon would be more likely. In AaronTools the substituent is projected the same, but L is calculated starting from the van der Waals radius of the first substituent atom instead. This means AaronTools will give the same L value even if the substituent is capped with something besides a hydrogen. When comparing AaronTools' L values with STERIMOL (using the same set of radii for the atoms), the values usually differ by < 0.1 Å. """ from AaronTools.finders import BondedTo CITATION = "doi:10.1002/ps.2780070410" self.LOG.citation(CITATION) if self.end is None: raise RuntimeError( "cannot calculate sterimol values for substituents without end" ) atom1 = self.find(BondedTo(self.end))[0] atom2 = self.end if isinstance(radii, dict): radii_dict = radii elif radii.lower() == "bondi": radii_dict = BONDI_RADII elif radii.lower() == "umn": radii_dict = VDW_RADII if old_L: from AaronTools.atoms import Atom, BondOrder bo = BondOrder key = bo.key(atom1, Atom(element="H")) dx = bo.bonds[key]["1.0"] + 0.4 def L_func(atom, start, radius, L_axis, dx=dx, atom1=atom1): test_v = start.bond(atom) test_L = (np.dot(test_v, L_axis) - start.dist(atom1) + dx + radius) start_x = atom1.coords - dx * L_axis L_vec = (start_x, start_x + test_L * L_axis) return test_L, L_vec else: r1 = radii_dict[atom1.element] def L_func(atom, start, radius, L_axis, atom1=atom1, r1=r1): test_v = start.bond(atom) test_L = (np.dot(test_v, L_axis) - start.dist(atom1) + r1 + radius) start_x = atom1.coords - r1 * L_axis L_vec = (start_x, start_x + test_L * L_axis) return test_L, L_vec L_axis = atom2.bond(atom1) L_axis /= np.linalg.norm(L_axis) return super().sterimol( L_axis, atom2, self.atoms, L_func=L_func, return_vector=return_vector, radii=radii, **kwargs, )
def from_string( cls, name, conf_num=None, conf_angle=None, form="smiles", debug=False, strict_use_rdkit=False, ): """ creates a substituent from a string name str identifier for substituent conf_num int number of conformers expected for hierarchical conformer generation conf_angle int angle between conformers form str type of identifier (smiles, iupac) """ # convert whatever format we"re given to smiles # then grab the structure from cactus site from AaronTools.finders import BondedTo accepted_forms = ["iupac", "smiles"] if form not in accepted_forms: raise NotImplementedError( "cannot create substituent given %s; use one of %s" % form, str(accepted_forms), ) rad = re.compile(r"\[\S+?\]") elements = re.compile(r"[A-Z][a-z]?") if form == "smiles": smiles = name elif form == "iupac": smiles = cls.iupac2smiles(name) if debug: print("radical smiles:", smiles, file=sys.stderr) # radical atom is the first atom in [] # charged atoms are also in [] my_rad = None radicals = rad.findall(smiles) if radicals: for rad in radicals: if "." in rad: my_rad = rad break elif "+" not in rad and "-" not in rad: my_rad = rad break if my_rad is None: if radicals: cls.LOG.warning( "radical atom may be ambiguous, be sure to check output: %s" % smiles) my_rad = radicals[0] else: raise RuntimeError( "could not determine radical site on %s; radical site is expected to be in []" % smiles) # construct a modified smiles string with (Cl) right after the radical center # keep track of the position of this added Cl # (use Cl instead of H b/c explicit H"s don"t always play nice with RDKit) pos1 = smiles.index(my_rad) pos2 = smiles.index(my_rad) + len(my_rad) previous_atoms = elements.findall(smiles[:pos1]) rad_pos = len(previous_atoms) if "+" not in my_rad and "-" not in my_rad: mod_smiles = (smiles[:pos1] + re.sub(r"H\d+", "", my_rad[1:-1]) + "(Cl)" + smiles[pos2:]) else: mod_smiles = (smiles[:pos1] + my_rad[:-1].rstrip("H") + "]" + "(Cl)" + smiles[pos2:]) mod_smiles = mod_smiles.replace(".", "") if debug: print("modified smiles:", mod_smiles, file=sys.stderr) print("radical position:", rad_pos, file=sys.stderr) # grab structure from cactus/RDKit geom = Geometry.from_string(mod_smiles, form="smiles", strict_use_rdkit=strict_use_rdkit) # the Cl we added is in the same position in the structure as in the smiles string rad = geom.atoms[rad_pos] added_Cl = [atom for atom in rad.connected if atom.element == "Cl"][0] # move the added H to the origin geom.coord_shift(-added_Cl.coords) # get the atom bonded to this H # also move the atom on H to the front of the atoms list to have the expected connectivity bonded_atom = geom.find(BondedTo(added_Cl))[0] geom.atoms = [bonded_atom ] + [atom for atom in geom.atoms if atom != bonded_atom] bonded_atom.connected.discard(added_Cl) # align the H-atom bond with the x-axis to have the expected orientation bond = deepcopy(bonded_atom.coords) bond /= np.linalg.norm(bond) x_axis = np.array([1.0, 0.0, 0.0]) rot_axis = np.cross(x_axis, bond) if abs(np.linalg.norm(rot_axis)) > np.finfo(float).eps: rot_axis /= np.linalg.norm(rot_axis) angle = np.arccos(np.dot(bond, x_axis)) geom.rotate(rot_axis, -angle) else: try: import rdkit except ImportError: # if the bonded_atom is already on the x axis, we will instead # rotate about the y axis by 180 degrees angle = np.pi geom.rotate(np.array([0.0, 1.0, 0.0]), -angle) out = cls( [atom for atom in geom.atoms if atom is not added_Cl], conf_num=conf_num, conf_angle=conf_angle, detect=False, ) out.refresh_connected() out.refresh_ranks() return out
def cone_angle( self, center=None, method="exact", return_cones=False, radii="umn" ): """ returns cone angle in degrees center - Atom() that this component is coordinating used as the apex of the cone method (str) can be: 'Tolman' - Tolman cone angle for asymmetric ligands See J. Am. Chem. Soc. 1974, 96, 1, 53–60 (DOI: 10.1021/ja00808a009) NOTE: this does not make assumptions about the geometry NOTE: only works with monodentate and bidentate ligands 'exact' - cone angle from Allen et. al. See Bilbrey, J.A., Kazez, A.H., Locklin, J. and Allen, W.D. (2013), Exact ligand cone angles. J. Comput. Chem., 34: 1189-1197. (DOI: 10.1002/jcc.23217) return_cones - return cone apex, center of base, and base radius list the sides of the cones will be 5 angstroms long for Tolman cone angles, multiple cones will be returned, one for each substituent coming off the coordinating atom radii: 'bondi' - Bondi vdW radii 'umn' - vdW radii from Mantina, Chamberlin, Valero, Cramer, and Truhlar dict() with elements as keys and radii as values """ if method.lower() == "tolman": CITATION = "doi:10.1021/ja00808a009" elif method.lower() == "exact": CITATION = "doi:10.1002/jcc.23217" self.LOG.citation(CITATION) key = self.find("key") center = self.find_exact(center)[0] L_axis = self.COM(key) - center.coords L_axis /= np.linalg.norm(L_axis) if isinstance(radii, dict): radii_dict = radii elif radii.lower() == "bondi": radii_dict = BONDI_RADII elif radii.lower() == "umn": radii_dict = VDW_RADII # list of cone data for printing bild file or w/e cones = [] if method.lower() == "tolman": total_angle = 0 if len(key) > 2: raise NotImplementedError( "Tolman cone angle not implemented for tridentate or more ligands" ) elif len(key) == 2: key1, key2 = key try: bridge_path = self.shortest_path(key1, key2) except LookupError: bridge_path = False for key_atom in key: L_axis = key_atom.coords - center.coords L_axis /= np.linalg.norm(L_axis) bonded_atoms = self.find(BondedTo(key_atom)) for bonded_atom in bonded_atoms: frag = self.get_fragment(bonded_atom, key_atom) use_bridge = False if any(k in frag for k in key): # fragment on bidentate ligands that connects to # the other coordinating atom k = self.find(frag, key)[0] # the bridge might be part of a ring (e.g. BPY) # to avoid double counting the bridge, check if the # first atom in the fragment is the first atom on the # path from one key atom to the other if frag[0] in bridge_path: use_bridge = True if use_bridge: # angle between one L-M bond and L-M-L bisecting vector tolman_angle = center.angle(k, key_atom) / 2 else: tolman_angle = None # for bidentate ligands with multiple bridges across, only use atoms that # are closer to the key atom we are looking at right now if len(key) == 2: if bridge_path: if key_atom is key1: other_key = key2 else: other_key = key1 frag = self.find( frag, CloserTo( key_atom, other_key, include_ties=True ), ) # some ligands like DuPhos have rings on the phosphorous atom # we only want ones that are closer to the the substituent end frag = self.find(frag, CloserTo(bonded_atom, key_atom)) # Geometry(frag).write(outfile="frag%s.xyz" % bonded_atom.name) for atom in frag: beta = np.arcsin( radii_dict[atom.element] / atom.dist(center) ) v = center.bond(atom) / center.dist(atom) c = np.linalg.norm(v - L_axis) test_angle = beta + np.arccos((c ** 2 - 2) / -2) if ( tolman_angle is None or test_angle > tolman_angle ): tolman_angle = test_angle scale = 5 * np.cos(tolman_angle) cones.append( ( center.coords + scale * L_axis, center.coords, scale * abs(np.tan(tolman_angle)), ) ) total_angle += 2 * tolman_angle / len(bonded_atoms) if return_cones: return np.rad2deg(total_angle), cones return np.rad2deg(total_angle) elif method.lower() == "exact": beta = np.zeros(len(self.atoms), dtype=float) test_one_atom_axis = None max_beta = None for i, atom in enumerate(self.atoms): beta[i] = np.arcsin( radii_dict[atom.element] / atom.dist(center) ) if max_beta is None or beta[i] > max_beta: max_beta = beta[i] test_one_atom_axis = center.bond(atom) # check to see if all other atoms are in the shadow of one atom # e.g. cyano, carbonyl overshadowed_list = [] for i, atom in enumerate(self.atoms): rhs = beta[i] if ( np.dot(center.bond(atom), test_one_atom_axis) / (center.dist(atom) * np.linalg.norm(test_one_atom_axis)) <= 1 ): rhs += np.arccos( np.dot(center.bond(atom), test_one_atom_axis) / ( center.dist(atom) * np.linalg.norm(test_one_atom_axis) ) ) lhs = max_beta if lhs >= rhs: # print(atom, "is overshadowed") overshadowed_list.append(atom) break # all atoms are in the cone - we're done if len(overshadowed_list) == len(self.atoms): scale = 5 * np.cos(max_beta) cones.append( ( center.coords + scale * test_one_atom_axis, center.coords, scale * abs( np.linalg.norm(test_one_atom_axis) * np.tan(max_beta) ), ) ) if return_cones: return np.rad2deg(2 * max_beta), cones return np.rad2deg(2 * max_beta) overshadowed_list = [] for i, atom1 in enumerate(self.atoms): for j, atom2 in enumerate(self.atoms[:i]): rhs = beta[i] if ( np.dot(center.bond(atom1), center.bond(atom2)) / (center.dist(atom1) * center.dist(atom2)) <= 1 ): rhs += np.arccos( np.dot(center.bond(atom1), center.bond(atom2)) / (center.dist(atom1) * center.dist(atom2)) ) lhs = beta[j] if lhs >= rhs: overshadowed_list.append(atom1) break # winow list to ones that aren't in the shadow of another atom_list = [ atom for atom in self.atoms if atom not in overshadowed_list ] # check pairs of atoms max_a = None aij = None bij = None cij = None for i, atom1 in enumerate(atom_list): ndx_i = self.atoms.index(atom1) for j, atom2 in enumerate(atom_list[:i]): ndx_j = self.atoms.index(atom2) beta_ij = np.arccos( np.dot(center.bond(atom1), center.bond(atom2)) / (atom1.dist(center) * atom2.dist(center)) ) test_alpha = (beta[ndx_i] + beta[ndx_j] + beta_ij) / 2 if max_a is None or test_alpha > max_a: max_a = test_alpha mi = center.bond(atom1) mi /= np.linalg.norm(mi) mj = center.bond(atom2) mj /= np.linalg.norm(mj) aij = np.sin( 0.5 * (beta_ij + beta[ndx_i] - beta[ndx_j]) ) / np.sin(beta_ij) bij = np.sin( 0.5 * (beta_ij - beta[ndx_i] + beta[ndx_j]) ) / np.sin(beta_ij) cij = 0 norm = ( aij * mi + bij * mj + cij * np.cross(mi, mj) / np.sin(bij) ) # r = 0.2 * np.tan(max_a) # print( # ".cone %.3f %.3f %.3f 0.0 0.0 0.0 %.3f open" % ( # 0.2 * norm[0], 0.2 * norm[1], 0.2 * norm[2], r # ) # ) overshadowed_list = [] rhs = max_a for atom in atom_list: ndx_i = self.atoms.index(atom) lhs = beta[ndx_i] + np.arccos( np.dot(center.bond(atom), norm) / center.dist(atom) ) # this should be >=, but there can be numerical issues if rhs > lhs or np.isclose(rhs, lhs): overshadowed_list.append(atom) # the cone fits all atoms, we're done if len(overshadowed_list) == len(atom_list): scale = 5 * np.cos(max_a) cones.append( ( center.coords + (scale * norm), center.coords, scale * abs(np.tan(max_a)), ) ) if return_cones: return np.rad2deg(2 * max_a), cones return np.rad2deg(2 * max_a) centroid = self.COM() c_vec = centroid - center.coords c_vec /= np.linalg.norm(c_vec) min_alpha = None c = 0 for i, atom1 in enumerate(atom_list): for j, atom2 in enumerate(atom_list[:i]): for k, atom3 in enumerate(atom_list[i + 1 :]): c += 1 ndx_i = self.atoms.index(atom1) ndx_j = self.atoms.index(atom2) ndx_k = self.atoms.index(atom3) # print(atom1.name, atom2.name, atom3.name) mi = center.bond(atom1) mi /= np.linalg.norm(center.dist(atom1)) mj = center.bond(atom2) mj /= np.linalg.norm(center.dist(atom2)) mk = center.bond(atom3) mk /= np.linalg.norm(center.dist(atom3)) gamma_ijk = np.dot(mi, np.cross(mj, mk)) # M = np.column_stack((mi, mj, mk)) # N = gamma_ijk * np.linalg.inv(M) N = np.column_stack( ( np.cross(mj, mk), np.cross(mk, mi), np.cross(mi, mj), ) ) u = np.array( [ np.cos(beta[ndx_i]), np.cos(beta[ndx_j]), np.cos(beta[ndx_k]), ] ) v = np.array( [ np.sin(beta[ndx_i]), np.sin(beta[ndx_j]), np.sin(beta[ndx_k]), ] ) P = np.dot(N.T, N) A = np.dot(u.T, np.dot(P, u)) B = np.dot(v.T, np.dot(P, v)) C = np.dot(u.T, np.dot(P, v)) D = gamma_ijk ** 2 # beta_ij = np.dot(center.bond(atom1), center.bond(atom2)) # beta_ij /= atom1.dist(center) * atom2.dist(center) # beta_ij = np.arccos(beta_ij) # beta_jk = np.dot(center.bond(atom2), center.bond(atom3)) # beta_jk /= atom2.dist(center) * atom3.dist(center) # beta_jk = np.arccos(beta_jk) # beta_ik = np.dot(center.bond(atom1), center.bond(atom3)) # beta_ik /= atom1.dist(center) * atom3.dist(center) # beta_ik = np.arccos(beta_ik) # # D = 1 - np.cos(beta_ij) ** 2 - np.cos(beta_jk) ** 2 - np.cos(beta_ik) ** 2 # D += 2 * np.cos(beta_ik) * np.cos(beta_jk) * np.cos(beta_ij) # this should be equal to the other D t1 = (A - B) ** 2 + 4 * C ** 2 t2 = 2 * (A - B) * (A + B - 2 * D) t3 = (A + B - 2 * D) ** 2 - 4 * C ** 2 w_lt = (-t2 - np.sqrt(t2 ** 2 - 4 * t1 * t3)) / ( 2 * t1 ) w_gt = (-t2 + np.sqrt(t2 ** 2 - 4 * t1 * t3)) / ( 2 * t1 ) alpha1 = np.arccos(w_lt) / 2 alpha2 = (2 * np.pi - np.arccos(w_lt)) / 2 alpha3 = np.arccos(w_gt) / 2 alpha4 = (2 * np.pi - np.arccos(w_gt)) / 2 for alpha in [alpha1, alpha2, alpha3, alpha4]: if alpha < max_a: continue if min_alpha is not None and alpha >= min_alpha: continue lhs = ( A * np.cos(alpha) ** 2 + B * np.sin(alpha) ** 2 ) lhs += 2 * C * np.sin(alpha) * np.cos(alpha) if not np.isclose(lhs, D): continue # print(lhs, D) p = np.dot( N, u * np.cos(alpha) + v * np.sin(alpha) ) norm = p / gamma_ijk for atom in atom_list: ndx = self.atoms.index(atom) rhs = beta[ndx] d = np.dot( center.bond(atom), norm ) / center.dist(atom) if abs(d) < 1: rhs += np.arccos(d) if not alpha >= rhs: break else: if min_alpha is None or alpha < min_alpha: # print("min_alpha set", alpha) min_alpha = alpha min_norm = norm # r = 2 * np.tan(min_alpha) # print( # ".cone %.3f %.3f %.3f 0.0 0.0 0.0 %.3f open" % ( # 2 * norm[0], 2 * norm[1], 2 * norm[2], r # ) # ) scale = 5 * np.cos(min_alpha) cones.append( ( center.coords + scale * min_norm, center.coords, scale * abs(np.tan(min_alpha)), ) ) if return_cones: return np.rad2deg(2 * min_alpha), cones return np.rad2deg(2 * min_alpha) else: raise NotImplementedError( "cone angle type is not implemented: %s" % method )
for atom in targ.connected: frag = geom.get_fragment(atom, targ) if sum([ int(targ in frag_atom.connected) for frag_atom in frag ]) == 2: qualifying_fragments.append(frag) if not qualifying_fragments: warn("cannot change chirality of atom %s\n" % targ.name + "must have at least two groups not in a ring") else: frag = qualifying_fragments[0] atom1, atom2 = geom.find(BondedTo(targ), frag) v1 = targ.bond(atom1) / targ.dist(atom1) v2 = targ.bond(atom2) / targ.dist(atom2) rv = v1 + v2 geom.rotate(rv, angle=pi, targets=frag, center=targ) if args.minimize: warn( "cannot minimize steric clashing for cyclic fragment on atom %s" % targ.name) else: while len(qualifying_fragments) > 2: