コード例 #1
0
def get_core_angle(core: Molecule) -> Tuple[float, float]:
    """Return the mean."""
    # Find all nearest anchor neighbours
    anchors = np.array([at.coords for at in core.properties.dummies])
    dist = cdist(anchors, anchors)
    np.fill_diagonal(dist, 10000)
    idx = np.argmin(dist, axis=0)

    # Construct (and normalize) vectors from the center of mass to the anchor atoms
    center = np.array(core.get_center_of_mass())
    vec1 = anchors - center
    vec2 = anchors[idx] - center
    vec1 /= np.linalg.norm(vec1, axis=1)[..., None]
    vec2 /= np.linalg.norm(vec2, axis=1)[..., None]

    # Calculate (and average) all the anchor-center-anchor angles
    r_ref = np.linalg.norm(anchors - anchors[idx], axis=1)
    dot = np.einsum('ij,ij->i', vec1, vec2)
    return np.arccos(dot).mean(), r_ref.mean()  # Theta and d
コード例 #2
0
ファイル: ligand_attach.py プロジェクト: pk-organics/CAT
def ligand_to_qd(core: Molecule, ligand: Molecule, path: str,
                 allignment: str = 'sphere',
                 idx_subset: Optional[Iterable[int]] = None) -> Molecule:
    """Function that handles quantum dot (qd, *i.e.* core + all ligands) operations.

    Combine the core and ligands and assign properties to the quantom dot.

    Parameters
    ----------
    core : |plams.Molecule|_
        A core molecule.

    ligand : |plams.Molecule|_
        A ligand molecule.

    allignment : :class:`str`
        How the core vector(s) should be defined.
        Accepted values are ``"sphere"`` and ``"surface"``:

        * ``"sphere"``: Vectors from the core anchor atoms to the center of the core.
        * ``"surface"``: Vectors perpendicular to the surface of the core.

        Note that for a perfect sphere both approaches are equivalent.

    idx_subset : :class:`Iterable<collections.anc.Iterable>` [:class:`int`], optional
        An iterable with the (0-based) indices defining a subset of atoms in **core**.
        Only relevant in the construction of the convex hull when ``allignment=surface``.

    Returns
    -------
    |plams.Molecule|_
        A quantum dot consisting of a core molecule and *n* ligands

    """
    def get_name() -> str:
        core_name = core.properties.name
        anchor = str(qd[-1].properties.pdb_info.ResidueNumber - 1)
        lig_name = ligand.properties.name
        return f'{core_name}__{anchor}_{lig_name}'

    idx_subset_ = idx_subset if idx_subset is not None else ...

    # Define vectors and indices used for rotation and translation the ligands
    vec1 = np.array([-1, 0, 0], dtype=float)  # All ligands are already alligned along the X-axis
    idx = ligand.get_index(ligand.properties.dummies) - 1
    ligand.properties.dummies.properties.anchor = True

    # Attach the rotated ligands to the core, returning the resulting strucutre (PLAMS Molecule).
    if allignment == 'sphere':
        vec2 = np.array(core.get_center_of_mass()) - sanitize_dim_2(core.properties.dummies)
        vec2 /= np.linalg.norm(vec2, axis=1)[..., None]
    elif allignment == 'surface':
        if isinstance(core.properties.dummies, np.ndarray):
            anchor = core.properties.dummies
        else:
            anchor = core.as_array(core.properties.dummies)
        vec2 = -get_surface_vec(np.array(core)[idx_subset_], anchor)
    else:
        raise ValueError(repr(allignment))

    lig_array = rot_mol(ligand, vec1, vec2, atoms_other=core.properties.dummies, core=core, idx=idx)
    qd = core.copy()
    array_to_qd(ligand, lig_array, mol_out=qd)
    qd.round_coords()

    # Set properties
    qd.properties = Settings({
        'indices': [i for i, at in enumerate(qd, 1) if
                    at.properties.pdb_info.ResidueName == 'COR' or at.properties.anchor],
        'path': path,
        'name': get_name(),
        'job_path': [],
        'prm': ligand.properties.get('prm')
    })

    # Print and return
    _evaluate_distance(qd, qd.properties.name)
    return qd
コード例 #3
0
def _construct_xyn(
    ion: str | int | Molecule,
    lig_count: int,
    lig: Molecule,
    lig_at: Atom,
    lig_idx: int,
) -> Tuple[Molecule, Atom]:
    """Construct the :math:`XYn` molecule for :func:`get_xyn`.

    Parameters
    ----------
    ion : |str|_, |int|_ or |plams.Molecule|_
        An ion (:math:`X`), be it mono- (*e.g.* atomic number or symbol) or poly-atomic.

    lig_count : int
        The number of to-be attached ligands per ion.

    lig : |plams.Molecule|_
        A single ligand molecule.

    lig_at : |plams.Atom|_
        The ligand anchor atom.

    lig_idx : int
        The (1-based) index of **lig_at**.

    Returns
    -------
    |plams.Molecule|_ and |plams.Atom|_
        A :math:`XY_{n}` molecule and the the charged atom from :math:`X`.

    """
    # Create a list of n ligands, n anchor atoms, n desired ion-anchor distances and n angles
    lig_gen = (lig.copy() for _ in range(lig_count))
    angle_ar = np.arange(0, 2 * np.pi, 2 * np.pi / lig_count)

    # Prepare vectors for translations and rotations
    vec1 = lig_at.vector_to(np.zeros(3))
    _vec = lig_at.vector_to(lig.get_center_of_mass())
    vec2 = get_perpendicular_vec(_vec)

    # Update the XYn molecule with ligands
    XYn, X = _parse_ion(ion)
    iterator = enumerate(zip(angle_ar, lig_gen), 2)
    for i, (angle, mol) in iterator:
        # Prepare for translations and rotations
        anchor = mol[lig_idx]
        rotmat = axis_rotation_matrix(vec2, angle)
        dist = anchor.radius + X.radius

        # Translate and rotate the ligand
        mol.translate(vec1)
        mol.rotate(rotmat)
        vec3 = anchor.vector_to(mol.get_center_of_mass())
        vec3 /= np.linalg.norm(vec3) / dist
        mol.translate(vec3)

        # Set pdb attributes
        for at in mol:
            at.properties.pdb_info.ResidueNumber = i
            at.properties.pdb_info.ResidueName = 'LIG'

        # Combine the translated and rotated ligand with XYn
        XYn.add_molecule(mol)
        XYn.add_bond(X, anchor)

    return XYn, X