Example #1
0
def vhf_from_pvhf(trj, partial_dict, water=False):
    """
    Compute the total Van Hove function from partial Van Hove functions


    Parameters
    ----------
    trj : mdtrj.Trajectory
        trajectory on which partial vhf were calculated form
    partial_dict : dict
        dictionary containing partial vhf as a np.array.
        Key is a tuple of len 2 with 2 atom types

    Return
    -------
    total_grt : numpy.ndarray
        Total Van Hove Function generated from addition of partial Van Hove Functions
    """
    unique_atoms = get_unique_atoms(trj)
    all_atoms = [atom for atom in trj.topology.atoms]

    norm_coeff = 0
    dict_shape = list(partial_dict.values())[0][0].shape
    total_grt = np.zeros(dict_shape)

    for atom_pair in partial_dict.keys():
        # checks if key is a tuple
        if isinstance(atom_pair, tuple) == False:
            raise ValueError("Dictionary key not valid. Must be a tuple.")
        for atom in atom_pair:
            # checks if the atoms in tuple pair are atom types
            if type(atom) != type(unique_atoms[0]):
                raise ValueError(
                    "Dictionary key not valid. Must be type `MDTraj.Atom`.")
            # checks if atoms are in the trajectory
            if atom not in all_atoms:
                raise ValueError(
                    f"Dictionary key not valid, `Atom` {atom} not in MDTraj trajectory."
                )

        # checks if key has two atoms
        if len(atom_pair) != 2:
            raise ValueError(
                "Dictionary key not valid. Must only have 2 atoms per pair.")

        atom1 = atom_pair[0]
        atom2 = atom_pair[1]
        coeff = (get_form_factor(element_name=f"{atom1.element.symbol}",
                                 water=False) *
                 get_form_factor(element_name=f"{atom2.element.symbol}",
                                 water=False) *
                 len(trj.topology.select(f"name {atom1.name}")) /
                 (trj.n_atoms) *
                 len(trj.topology.select(f"name {atom2.name}")) /
                 (trj.n_atoms))

        normalized_pvhf = coeff * partial_dict[atom_pair]
        norm_coeff += coeff
        total_grt = np.add(total_grt, normalized_pvhf)

    total_grt /= norm_coeff

    return total_grt
Example #2
0
def compute_van_hove(
    trj,
    chunk_length,
    parallel=False,
    water=False,
    r_range=(0, 1.0),
    bin_width=0.005,
    n_bins=None,
    self_correlation=True,
    periodic=True,
    opt=True,
    partial=False,
):
    """Compute the  Van Hove function of a trajectory. Atom pairs
    referenced in partial Van Hove functions are in alphabetical
    order. If specific ordering of atom pairs are needed, user should
    use compute_partial_van_hove then vhf_from_pvhf to compute total
    Van Hove function.

    Parameters
    ----------
    trj : mdtraj.Trajectory
        trajectory on which to compute the Van Hove function
    chunk_length : int
        length of time between restarting averaging
    parallel : bool, default=True
        Use parallel implementation with `multiprocessing`
    water : bool
        use X-ray form factors for water that account for polarization
    r_range : array-like, shape=(2,), optional, default=(0.0, 1.0)
        Minimum and maximum radii.
    bin_width : float, optional, default=0.005
        Width of the bins in nanometers.
    n_bins : int, optional, default=None
        The number of bins. If specified, this will override the `bin_width`
         parameter.
    self_correlation : bool, default=True
        Whether or not to include the self-self correlations
    partial : bool, default = False
        Whether or not to return a dictionary including partial Van Hove function.

    Returns
    -------
    r : numpy.ndarray
        r positions generated by histogram binning
    g_r_t : numpy.ndarray
        Van Hove function at each time and position
    """

    n_physical_atoms = len([a for a in trj.top.atoms if a.element.mass > 0])
    unique_elements = list(
        set([a.element for a in trj.top.atoms if a.element.mass > 0]))

    if parallel:
        data = []
        for elem1, elem2 in it.combinations_with_replacement(
                unique_elements[::-1], 2):
            # Add a bool to check if self-correlations should be analyzed
            self_bool = self_correlation
            if elem1 != elem2:
                self_bool = False
                warnings.warn(
                    "Total VHF calculation: No self-correlations for {} and {}, setting `self_correlation` to `False`."
                    .format(elem1, elem2))

            data.append([
                trj,
                chunk_length,
                "element {}".format(elem1.symbol),
                "element {}".format(elem2.symbol),
                r_range,
                bin_width,
                n_bins,
                self_bool,
                periodic,
                opt,
            ])

        manager = multiprocessing.Manager()
        partial_dict = manager.dict()
        jobs = []
        version_info = sys.version_info
        for d in data:
            with multiprocessing.Pool(
                    processes=multiprocessing.cpu_count()) as pool:
                if version_info.major == 3 and version_info.minor <= 7:
                    p = pool.Process(target=worker, args=(partial_dict, d))
                elif version_info.major == 3 and version_info.minor >= 8:
                    ctx = multiprocessing.get_context()
                    p = pool.Process(ctx,
                                     target=worker,
                                     args=(partial_dict, d))
                jobs.append(p)
                p.start()

        for proc in jobs:
            proc.join()

        r = partial_dict["r"]
        del partial_dict["r"]

    else:
        partial_dict = dict()

        for elem1, elem2 in it.combinations_with_replacement(
                unique_elements[::-1], 2):
            # Add a bool to check if self-correlations should be analyzed
            self_bool = self_correlation
            if elem1 != elem2:
                self_bool = False
                warnings.warn(
                    "Total VHF calculation: No self-correlations for {} and {}, setting `self_correlation` to `False`."
                    .format(elem1, elem2))

            if elem1.symbol > elem2.symbol:
                temp = elem1
                elem1 = elem2
                elem2 = temp
            print("doing {0} and {1} ...".format(elem1, elem2))
            r, g_r_t_partial = compute_partial_van_hove(
                trj=trj,
                chunk_length=chunk_length,
                selection1="element {}".format(elem1.symbol),
                selection2="element {}".format(elem2.symbol),
                r_range=r_range,
                bin_width=bin_width,
                n_bins=n_bins,
                self_correlation=self_bool,
                periodic=periodic,
                opt=opt,
            )
            partial_dict[("element {}".format(elem1.symbol),
                          "element {}".format(elem2.symbol))] = g_r_t_partial

    if partial:
        return partial_dict

    norm = 0
    g_r_t = None

    for key, val in partial_dict.items():
        elem1, elem2 = key
        concentration1 = (trj.atom_slice(trj.top.select(elem1)).n_atoms /
                          n_physical_atoms)
        concentration2 = (trj.atom_slice(trj.top.select(elem2)).n_atoms /
                          n_physical_atoms)
        form_factor1 = get_form_factor(element_name=elem1.split()[1],
                                       water=water)
        form_factor2 = get_form_factor(element_name=elem2.split()[1],
                                       water=water)

        coeff = form_factor1 * concentration1 * form_factor2 * concentration2
        if g_r_t is None:
            g_r_t = np.zeros_like(val)
        g_r_t += val * coeff

        norm += coeff

    # Reshape g_r_t to better represent the discretization in both r and t
    g_r_t_final = np.empty(shape=(chunk_length, len(r)))
    for i in range(chunk_length):
        g_r_t_final[i, :] = np.mean(g_r_t[i::chunk_length], axis=0)

    g_r_t_final /= norm

    t = trj.time[:chunk_length]

    return r, t, g_r_t_final
Example #3
0
def compute_van_hove(trj,
                     chunk_length,
                     water=False,
                     r_range=(0, 1.0),
                     bin_width=0.005,
                     n_bins=None,
                     self_correlation=True,
                     periodic=True,
                     opt=True,
                     partial=False):
    """Compute the partial van Hove function of a trajectory

    Parameters
    ----------
    trj : mdtraj.Trajectory
        trajectory on which to compute the Van Hove function
    chunk_length : int
        length of time between restarting averaging
    water : bool
        use X-ray form factors for water that account for polarization
    r_range : array-like, shape=(2,), optional, default=(0.0, 1.0)
        Minimum and maximum radii.
    bin_width : float, optional, default=0.005
        Width of the bins in nanometers.
    n_bins : int, optional, default=None
        The number of bins. If specified, this will override the `bin_width`
         parameter.
    self_correlation : bool, default=True
        Whether or not to include the self-self correlations

    Returns
    -------
    r : numpy.ndarray
        r positions generated by histogram binning
    g_r_t : numpy.ndarray
        Van Hove function at each time and position
    """

    n_physical_atoms = len([a for a in trj.top.atoms if a.element.mass > 0])
    unique_elements = list(
        set([a.element for a in trj.top.atoms if a.element.mass > 0]))

    partial_dict = dict()

    for elem1, elem2 in it.combinations_with_replacement(
            unique_elements[::-1], 2):
        print('doing {0} and {1} ...'.format(elem1, elem2))
        r, g_r_t_partial = compute_partial_van_hove(
            trj=trj,
            chunk_length=chunk_length,
            selection1='element {}'.format(elem1.symbol),
            selection2='element {}'.format(elem2.symbol),
            r_range=r_range,
            bin_width=bin_width,
            n_bins=n_bins,
            self_correlation=self_correlation,
            periodic=periodic,
            opt=opt)
        partial_dict[(elem1, elem2)] = g_r_t_partial

    if partial:
        return partial_dict

    norm = 0
    g_r_t = None

    for key, val in partial_dict.items():
        elem1, elem2 = key
        concentration1 = trj.atom_slice(
            trj.top.select('element {}'.format(
                elem1.symbol))).n_atoms / n_physical_atoms
        concentration2 = trj.atom_slice(
            trj.top.select('element {}'.format(
                elem2.symbol))).n_atoms / n_physical_atoms
        form_factor1 = get_form_factor(element_name=elem1.symbol, water=water)
        form_factor2 = get_form_factor(element_name=elem2.symbol, water=water)

        coeff = form_factor1 * concentration1 * form_factor2 * concentration2

        if g_r_t is None:
            g_r_t = np.zeros_like(val)
        g_r_t += val * coeff

        norm += coeff

    # Reshape g_r_t to better represent the discretization in both r and t
    g_r_t_final = np.empty(shape=(chunk_length, len(r)))
    for i in range(chunk_length):
        g_r_t_final[i, :] = np.mean(g_r_t[i::chunk_length], axis=0)

    g_r_t_final /= norm

    t = trj.time[:chunk_length]

    return r, t, g_r_t_final
Example #4
0
def structure_factor(trj, Q_range=(0.5, 50), n_points=1000, framewise_rdf=False, weighting_factor='fz', isotopes={}, probe="neutron"):
    """Compute the structure factor through a fourier transform of
    the radial distribution function.

    The consdered trajectory must include valid elements.

    Atomic form factors are estimated by atomic number.

    The computed structure factor is only valid for certain values of Q. The
    lowest value of Q that can sufficiently be described by a box of
    characteristic length `L` is `2 * pi / (L / 2)`.

    Parameters
    ----------
    trj : mdtraj.Trajectory
        A trajectory for which the structure factor is to be computed.
    Q_range : list or np.ndarray, default=(0.5, 50)
        Minimum and maximum Values of the scattering vector, in `1/nm`, to be
        consdered.
    n_points : int, default=1000
    framewise_rdf : boolean, default=False
        If True, computes the rdf frame-by-frame. This can be useful for
        managing memory in large systems.
    weighting_factor : string, optional, default='fz'
         Weighting factor for calculating the structure-factor, default is Faber-Ziman.
        See https://openscholarship.wustl.edu/etd/1358/ and http://isaacs.sourceforge.net/manual/page26_mn.html for details.
    isotopes: dict, optional, default=None
        If the scattering experiment was run with specific isotopic compositions (i.e. an NDIS experiment), specify
        isotopic composition as follows:
            {
                element_1.symbol:
                    {
                        element_1.atomic_number_1: fraction,
                        element_1.atomic_number_2: fraction,
                        ...
                    },
                element_2.symbol:
                    {
                        ...
                    },
                ...
            }
        The sum over the fraction for each isotope for each element must be 1.0. An atomic number of -1 signifies
        no isotopic enrichment, at which point the average scattering length will be pulled.

    Returns
    -------
    Q : np.ndarray
        The values of the scattering vector, in `1/nm`, that was considered.
    S : np.ndarray
        The structure factor of the trajectory

    """
    if weighting_factor not in ['fz']:
        raise ValueError('Invalid weighting_factor `{}` is given.'
                         '  The only weighting_factor currently supported is `fz`.'.format(
                             weighting_factor))

    rho = np.mean(trj.n_atoms / trj.unitcell_volumes)
    L = np.min(trj.unitcell_lengths)

    top = trj.topology
    elements = set([a.element for a in top.atoms])

    compositions = dict()
    form_factors = dict()
    rdfs = dict()

    Q = np.logspace(np.log10(Q_range[0]),
                    np.log10(Q_range[1]),
                    num=n_points)
    S = np.zeros(shape=(len(Q)))

    for elem in elements:
        compositions[elem.symbol] = len(top.select('element {}'.format(elem.symbol)))/trj.n_atoms
        form_factors[elem.symbol] = get_form_factor(elem.atomic_number, isotopes.get(elem.atomic_number, {-1: 1.0}), probe=probe)

    for i, q in enumerate(Q):
        num = 0
        denom = 0

        for elem in elements:
            denom += compositions[elem.symbol] * form_factors[elem.symbol]

        for (elem1, elem2) in it.product(elements, repeat=2):
            e1 = elem1.symbol
            e2 = elem2.symbol

            f_a = form_factors[e1]
            f_b = form_factors[e2]

            x_a = compositions[e1]
            x_b = compositions[e2]
            
            try:
                g_r = rdfs['{0}{1}'.format(e1, e2)]
            except KeyError:
                pairs = top.select_pairs(selection1='element {}'.format(e1),
                                         selection2='element {}'.format(e2))
                if framewise_rdf:
                    r, g_r = rdf_by_frame(trj,
                                         pairs=pairs,
                                         r_range=(0, L / 2),
                                         bin_width=0.001)
                else:
                    r, g_r = md.compute_rdf(trj,
                                            pairs=pairs,
                                            r_range=(0, L / 2),
                                            bin_width=0.001)
                rdfs['{0}{1}'.format(e1, e2)] = g_r
            integral = simps(r ** 2 * (g_r - 1) * np.sin(q * r) / (q * r), r)

            if weighting_factor == 'fz':
                pre_factor = 4 * np.pi * rho
                # It's an unrestricted double sum, so non-identical pairs need to be counted twice
                if e1 != e2:
                    pre_factor *= 2.0
                partial_sq = (integral*pre_factor)
                num += (x_a*f_a*x_b*f_b) * (partial_sq)
        # Faber-Ziman comes out in units of barn/sr/atom. 100 is to convert between fm^2 and barn.
        if weighting_factor == 'fz':
            S[i] = num/100.0
        else:
            S[i] = (num/(denom**2))
    return Q, S
Example #5
0
def structure_factor(
    trj,
    Q_range=(0.5, 50),
    n_points=1000,
    framewise_rdf=False,
    weighting_factor="fz",
    form="atomic",
):
    """Compute the structure factor through a fourier transform of
    the radial distribution function.

    The consdered trajectory must include valid elements.

    The computed structure factor is only valid for certain values of Q. The
    lowest value of Q that can sufficiently be described by a box of
    characteristic length `L` is `2 * pi / (L / 2)`.

    Parameters
    ----------
    trj : mdtraj.Trajectory
        A trajectory for which the structure factor is to be computed.
    Q_range : list or np.ndarray, default=(0.5, 50)
        Minimum and maximum Values of the scattering vector, in `1/nm`, to be
        consdered.
    n_points : int, default=1000
    framewise_rdf : boolean, default=False
        If True, computes the rdf frame-by-frame. This can be useful for
        managing memory in large systems.
    weighting_factor : string, optional, default='fz'
        Weighting factor for calculating the structure-factor, default is Faber-Ziman.
        See https://openscholarship.wustl.edu/etd/1358/ and http://isaacs.sourceforge.net/manual/page26_mn.html for details.
    form : string, optional, default='atomic'
        Method for determining form factors. If default, form factors are estimated from
        atomic numbers.  If 'cromer-mann', form factors are determined from Cromer-Mann
        tables.

    Returns
    -------
    Q : np.ndarray
        The values of the scattering vector, in `1/nm`, that was considered.
    S : np.ndarray
        The structure factor of the trajectory

    """
    if weighting_factor not in ["fz", "al"]:
        raise ValueError(
            "Invalid weighting_factor `{}` is given."
            "  The only weighting_factor currently supported is `fz`, and `al`."
            .format(weighting_factor))

    rho = np.mean(trj.n_atoms / trj.unitcell_volumes)
    L = np.min(trj.unitcell_lengths)

    top = trj.topology
    elements = set([a.element for a in top.atoms])

    compositions = dict()
    rdfs = dict()

    Q = np.logspace(np.log10(Q_range[0]), np.log10(Q_range[1]), num=n_points)
    S = np.zeros(shape=(len(Q)))

    for elem in elements:
        compositions[elem.symbol] = (
            len(top.select("element {}".format(elem.symbol))) / trj.n_atoms)

    for i, q in enumerate(Q):
        num = 0
        denom = 0

        for elem in elements:
            denom += _get_normalize(method=weighting_factor,
                                    c=compositions[elem.symbol],
                                    f=get_form_factor(elem.symbol,
                                                      q=q / 10,
                                                      method=form))

        for (elem1, elem2) in it.product(elements, repeat=2):
            e1 = elem1.symbol
            e2 = elem2.symbol

            f_a = get_form_factor(e1, q=q / 10, method=form)
            f_b = get_form_factor(e2, q=q / 10, method=form)

            x_a = compositions[e1]
            x_b = compositions[e2]

            try:
                g_r = rdfs["{0}{1}".format(e1, e2)]
            except KeyError:
                pairs = top.select_pairs(
                    selection1="element {}".format(e1),
                    selection2="element {}".format(e2),
                )
                if framewise_rdf:
                    r, g_r = rdf_by_frame(trj,
                                          pairs=pairs,
                                          r_range=(0, L / 2),
                                          bin_width=0.001)
                else:
                    r, g_r = md.compute_rdf(trj,
                                            pairs=pairs,
                                            r_range=(0, L / 2),
                                            bin_width=0.001)
                rdfs["{0}{1}".format(e1, e2)] = g_r
            integral = simps(r**2 * (g_r - 1) * np.sin(q * r) / (q * r), r)

            coefficient = x_a * x_b * f_a * f_b
            pre_factor = 4 * np.pi * rho

            partial_sq = (integral * pre_factor)
            num += coefficient * (partial_sq)

        if weighting_factor == "fz":
            denom = denom**2

        S[i] = num / denom
    return Q, S
Example #6
0
def compute_van_hove(trj, chunk_length, water=False,
                     r_range=(0, 1.0), bin_width=0.005, n_bins=None,
                     periodic=True, opt=True):
    """Compute the partial van Hove function of a trajectory

    Parameters
    ----------
    trj : mdtraj.Trajectory
        trajectory on which to compute the Van Hove function
    chunk_length : int
        length of time between restarting averaging
    water : bool
        use X-ray form factors for water that account for polarization
    r_range : array-like, shape=(2,), optional, default=(0.0, 1.0)
        Minimum and maximum radii.
    bin_width : float, optional, default=0.005
        Width of the bins in nanometers.
    n_bins : int, optional, default=None
        The number of bins. If specified, this will override the `bin_width`
         parameter.

    Returns
    -------
    r : numpy.ndarray
        r positions generated by histogram binning
    g_r_t : numpy.ndarray
        Van Hove function at each time and position
    """

    unique_elements = list(set([a.element for a in trj.top.atoms]))

    norm = 0
    g_r_t = None

    for elem1, elem2 in it.combinations_with_replacement(unique_elements[::-1], 2):
        r, g_r_t_partial = compute_partial_van_hove(trj=trj,
                                                    chunk_length=chunk_length,
                                                    selection1='element {}'.format(elem1.symbol),
                                                    selection2='element {}'.format(elem2.symbol),
                                                    r_range=r_range,
                                                    bin_width=bin_width,
                                                    n_bins=n_bins,
                                                    periodic=periodic,
                                                    opt=opt)


        concentration1 = trj.atom_slice(trj.top.select('element {}'.format(elem1.symbol))).n_atoms / trj.n_atoms
        concentration2 = trj.atom_slice(trj.top.select('element {}'.format(elem2.symbol))).n_atoms / trj.n_atoms
        form_factor1 = get_form_factor(element_name=elem1.symbol, water=water)
        form_factor2 = get_form_factor(element_name=elem2.symbol, water=water)

        coeff = form_factor1 * concentration1 * form_factor2 * concentration2

        if g_r_t is None:
            g_r_t = np.zeros_like(g_r_t_partial)
        g_r_t += g_r_t_partial * coeff

        norm += coeff

    # Reshape g_r_t to better represent the discretization in both r and t
    g_r_t_final = np.empty(shape=(chunk_length, len(r)))
    for i in range(chunk_length):
        g_r_t_final[i, :] = np.mean(g_r_t[i::chunk_length], axis=0)

    g_r_t_final /= norm

    t = trj.time[:chunk_length]

    return r, t, g_r_t_final