Esempio n. 1
0
    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)
Esempio n. 2
0
        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 = [
Esempio n. 3
0
    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)
Esempio n. 4
0
    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
Esempio n. 5
0
    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,
        )
Esempio n. 6
0
    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
Esempio n. 7
0
    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
            )
Esempio n. 8
0
                        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: