예제 #1
0
def powdersim(crystal, q, fwhm_g=0.03, fwhm_l=0.06, **kwargs):
    """
    Simulates polycrystalline diffraction pattern.

    Parameters
    ----------
    crystal : `skued.structure.Crystal`
        Crystal from which to diffract.
    q : `~numpy.ndarray`, shape (N,)
        Range of scattering vector norm over which to compute the diffraction pattern [1/Angs].
    fwhm_g, fwhm_l : float, optional
        Full-width at half-max of the Gaussian and Lorentzian parts of the Voigt profile.
        See `skued.pseudo_voigt` for more details.

    Returns
    -------
    pattern : `~numpy.ndarray`, shape (N,)
        Diffraction pattern
    """
    refls = np.vstack(tuple(crystal.bounded_reflections(q.max())))
    h, k, l = np.hsplit(refls, 3)
    Gx, Gy, Gz = change_basis_mesh(
        h, k, l, basis1=crystal.reciprocal_vectors, basis2=np.eye(3)
    )
    qs = np.sqrt(Gx ** 2 + Gy ** 2 + Gz ** 2)
    intensities = np.absolute(structure_factor(crystal, h, k, l)) ** 2

    pattern = np.zeros_like(q)
    for qi, i in zip(qs, intensities):
        pattern += i * pseudo_voigt(q, qi, fwhm_g, fwhm_l)

    return pattern
예제 #2
0
def potential_synthesis(reflections, intensities, crystal, mesh):
    """
    Synthesize the electrostatic potential from a list of experimental 
    reflections and associated diffracted intensities. Diffraction phases are 
    taken from a known structure
    
    Parameters
    ----------
    reflections : iterable of tuples
        Iterable of Miller indices as tuples (e.g. `[(0,1,0), (0, -1, 2)]`)
    intensities : Iterable of floats
        Experimental diffracted intensity for corresponding reflections.
    crystal : crystals.Crystal
        Crystal that gave rise to the diffracted intensities.
    mesh : 3-tuple ndarrays, ndim 2 or ndim 3
        Real-space mesh over which to calculate the scattering map.
        Format should be similar to the output of numpy.meshgrid. 

    Returns
    -------
    out : ndarray, ndim 2 or ndim 3
        Electrostatic potential computed over the mesh.
    """
    assert len(intensities) == len(reflections)

    intensities = np.array(intensities)
    if np.any(intensities < 0):
        raise ValueError("Diffracted intensity cannot physically be negative.")

    # We want to support 2D and 3D meshes, therefore
    # expand mesh until 4D (last dim is for loop over reflections)
    # Extra dimensions will be squeezed out later
    xx, yy, zz = mesh
    while xx.ndim < 4:
        xx, yy, zz = (
            np.expand_dims(xx, xx.ndim),
            np.expand_dims(yy, yy.ndim),
            np.expand_dims(zz, zz.ndim),
        )

    # Reconstruct the structure factors from experimental data
    # We need to compute the theoretical phases from the crystal structure
    # To do this, we need to change 'reflections' into three iterables:
    # h, k, and l arrays
    hs, ks, ls = np.hsplit(np.array(reflections), 3)
    theoretical_SF = structure_factor(crystal, hs, ks, ls)
    phases = np.angle(theoretical_SF)
    experimental_SF = np.sqrt(intensities) * np.exp(1j * phases)
    experimental_SF = experimental_SF.reshape((1, 1, 1, -1))

    qx, qy, qz = change_basis_mesh(hs, ks, ls, basis1=crystal.reciprocal_vectors, basis2=np.eye(3))
    qx, qy, qz = (
        qx.reshape((1, 1, 1, -1)),
        qy.reshape((1, 1, 1, -1)),
        qz.reshape((1, 1, 1, -1)),
    )
    p = np.sum(experimental_SF * np.cos(xx * qx + yy * qy + zz * qz), axis=3)
    return np.squeeze(np.real(p))
예제 #3
0
def structure_factor(crystal, h, k, l, normalized=False):
    """
    Computation of the static structure factor for electron diffraction. 
    
    Parameters
    ----------
    crystal : Crystal
        Crystal instance
    h, k, l : array_likes or floats
        Miller indices. Can be given in a few different formats:
        
        * floats : returns structure factor computed for a single scattering vector
            
        * 3 coordinate ndarrays, shapes (L,M,N) : returns structure factor computed over all coordinate space
    
    normalized : bool, optional
        If True, the normalized structure factor :math`E` is returned. This is the statis structure
        factor normalized by the sum of form factors squared.
    
    Returns
    -------
    sf : ndarray, dtype complex
        Output is the same shape as input G[0]. Takes into account
        the Debye-Waller effect.
    """
    # Distribute input
    # This works whether G is a list of 3 numbers, a ndarray shape(3,) or
    # a list of meshgrid arrays.
    h, k, l = np.atleast_1d(h, k, l)
    Gx, Gy, Gz = change_basis_mesh(h,
                                   k,
                                   l,
                                   basis1=crystal.reciprocal_vectors,
                                   basis2=np.eye(3))
    nG = np.sqrt(Gx**2 + Gy**2 + Gz**2)

    # Separating the structure factor into sine and cosine parts avoids adding
    # complex arrays together. About 3x speedup vs. using complex exponentials
    SFsin, SFcos = (
        np.zeros(shape=nG.shape, dtype=np.float),
        np.zeros(shape=nG.shape, dtype=np.float),
    )

    # Pre-allocation of form factors gives huge speedups
    atomff_dict = dict()
    for atom in crystal:  # TODO: implement in parallel?

        if atom.element not in atomff_dict:
            atomff_dict[atom.element] = affe(atom, nG)

        x, y, z = atom.coords_cartesian
        arg = x * Gx + y * Gy + z * Gz
        # TODO: debye waller factor based on displacement
        atomff = atomff_dict[atom.element]
        SFsin += atomff * np.sin(arg)
        SFcos += atomff * np.cos(arg)

    SF = SFcos + 1j * SFsin

    if normalized:
        SF /= np.sqrt(sum(atomff_dict[atom.element]**2 for atom in crystal))

    return SF
예제 #4
0
def potential_map(q, I, crystal, mesh):
    """ 
    Compute the electrostatic potential from powder diffraction data.
    
    Parameters
    ----------
    q : ndarray, shape (N,)
        Scattering vector norm (:math:`Å^{-1}`).
    I : ndarray, shape (N,)
        Experimental diffracted intensity.
    crystal : crystals.Crystal
        Crystal that gave rise to diffraction pattern `I`.
    mesh : 3-tuple ndarrays, ndim 2 or ndim 3
        Real-space mesh over which to calculate the scattering map.
        Format should be similar to the output of numpy.meshgrid. 

    Returns
    -------
    out : ndarray, ndim 2 or ndim 3
        Electrostatic potential computed over the mesh.

    Raises
    ------
    ValueError: if intensity data is not strictly positive.

    Notes
    -----
    To compute the scattering map from a difference of intensities, note that scattering 
    maps are linear in *structure factor* norm. Thus, to get the map of difference data :code:`I1 - I2`:

    .. math::

        I = (\sqrt{I_1} - \sqrt{I_2})^2
    
    References
    ----------
    .. [#] Otto et al., How optical excitation controls the structure and properties of vanadium dioxide.
           PNAS, vol. 116 issue 2, pp. 450-455 (2018). :DOI:`10.1073/pnas.1808414115`
    """
    if np.any(I < 0):
        raise ValueError("Diffracted intensity cannot physically be negative.")

    # We want to support 2D and 3D meshes, therefore
    # expand mesh until 4D (last dim is for loop over reflections)
    # Extra dimensions will be squeezed out later
    xx, yy, zz = mesh
    while xx.ndim < 4:
        xx, yy, zz = (
            np.expand_dims(xx, xx.ndim),
            np.expand_dims(yy, yy.ndim),
            np.expand_dims(zz, zz.ndim),
        )

    # Prepare reflections
    # G is reshaped so that it is perpendicular to xx, yy, zz to enables broadcasting
    reflections = np.vstack(tuple(crystal.bounded_reflections(q.max())))
    hs, ks, ls = np.hsplit(reflections, 3)
    SF = structure_factor(crystal, hs, ks, ls)

    # Extract structure factor with correction factors
    # Diffracted intensities add up linearly (NOT structure factors)
    qx, qy, qz = change_basis_mesh(hs, ks, ls, basis1=crystal.reciprocal_vectors, basis2=np.eye(3))
    qx, qy, qz = (
        qx.reshape((1, 1, 1, -1)),
        qy.reshape((1, 1, 1, -1)),
        qz.reshape((1, 1, 1, -1)),
    )
    SF = SF.reshape((1, 1, 1, -1))
    q_theo = np.squeeze(np.sqrt(qx ** 2 + qy ** 2 + qz ** 2))
    theo_I = powdersim(crystal, q_theo)
    peak_mult_corr = np.abs(SF) ** 2 / theo_I
    exp_SF = np.sqrt(np.interp(q_theo, q, I)) * peak_mult_corr

    # Squeeze out extra dimensions (e.g. if mesh was 2D)
    potential_map = np.sum(
        exp_SF
        * np.real(np.exp(1j * np.angle(SF)))
        * np.cos(xx * qx + yy * qy + zz * qz),
        axis=3,
    )
    return np.squeeze(potential_map)