Пример #1
0
def neighbour_list(quantities, a, cutoff):
    """
    Compute a neighbour list for an atomic configuration.

    Parameters
    ----------
    quantities : str
        Quantities to compute by the neighbor list algorithm. Each character
        in this string defines a quantity. They are returned in a tuple of 
        the same order. Possible quantities are
            'i' : first atom index
            'j' : second atom index
            'd' : absolute distance
            'D' : distance vector
            'S' : shift vector (number of cell boundaries crossed by the bond
                  between atom i and j)
    a : ase.Atoms
        Atomic configuration.
    cutoff : float
        Cutoff for neighbour search.

    Returns
    -------
    i, j, ... : array
        Tuple with arrays for each quantity specified above.
    """

    return _matscipy.neighbour_list(quantities, a.cell,
                                    np.linalg.inv(a.cell.T), a.pbc,
                                    a.positions, cutoff)
Пример #2
0
def neighbour_list(quantities, a, cutoff):
    """
    Compute a neighbour list for an atomic configuration.

    Parameters
    ----------
    quantities : str
        Quantities to compute by the neighbor list algorithm. Each character
        in this string defines a quantity. They are returned in a tuple of 
        the same order. Possible quantities are
            'i' : first atom index
            'j' : second atom index
            'd' : absolute distance
            'D' : distance vector
            'S' : shift vector (number of cell boundaries crossed by the bond
                  between atom i and j)
    a : ase.Atoms
        Atomic configuration.
    cutoff : float
        Cutoff for neighbour search.

    Returns
    -------
    i, j, ... : array
        Tuple with arrays for each quantity specified above.
    """

    return _matscipy.neighbour_list(quantities, a.cell,
                                    np.linalg.inv(a.cell.T), a.pbc,
                                    a.positions, cutoff)
Пример #3
0
def neighbour_list(quantities, a, cutoff):
    """
    Compute a neighbor list for an atomic configuration. Atoms outside periodic
    boundaries are mapped into the box. Atoms outside nonperiodic boundaries
    are included in the neighbor list but complexity of neighbor list search
    for those can become n^2.

    The neighbor list is sorted by first atom index 'i', but not by second 
    atom index 'j'.

    Parameters
    ----------
    quantities : str
        Quantities to compute by the neighbor list algorithm. Each character
        in this string defines a quantity. They are returned in a tuple of
        the same order. Possible quantities are
            'i' : first atom index
            'j' : second atom index
            'd' : absolute distance
            'D' : distance vector
            'S' : shift vector (number of cell boundaries crossed by the bond
                  between atom i and j). With the shift vector S, the
                  distances D between atoms can be computed from:
                  D = a.positions[j]-a.positions[i]+S.dot(a.cell)
    a : ase.Atoms
        Atomic configuration.
    cutoff : float or dict
        Cutoff for neighbor search. It can be
            - A single float: This is a global cutoff for all elements.
            - A dictionary: This specifies cutoff values for element
              pairs. Specification accepts element numbers of symbols.
              Example: {(1, 6): 1.1, (1, 1): 1.0, ('C', 'C'): 1.85}
            - A list/array with a per atom value: This specifies the radius of
              an atomic sphere for each atoms. If spheres overlap, atoms are
              within each others neighborhood.

    Returns
    -------
    i, j, ... : array
        Tuple with arrays for each quantity specified above. Indices in `i`
        are returned in ascending order 0..len(a), but the order of (i,j)
        pairs is not guaranteed.

    Examples
    --------
    Examples assume Atoms object *a* and numpy imported as *np*.
    1. Coordination counting:
        i = neighbor_list('i', a, 1.85)
        coord = np.bincount(i)

    2. Coordination counting with different cutoffs for each pair of species
        i = neighbor_list('i', a,
                           {('H', 'H'): 1.1, ('C', 'H'): 1.3, ('C', 'C'): 1.85})
        coord = np.bincount(i)

    3. Pair distribution function:
        d = neighbor_list('d', a, 10.00)
        h, bin_edges = np.histogram(d, bins=100)
        pdf = h/(4*np.pi/3*(bin_edges[1:]**3 - bin_edges[:-1]**3)) * a.get_volume()/len(a)

    4. Pair potential:
        i, j, d, D = neighbor_list('ijdD', a, 5.0)
        energy = (-C/d**6).sum()
        pair_forces = (6*C/d**5  * (D/d).T).T
        forces_x = np.bincount(j, weights=pair_forces[:, 0], minlength=len(a)) - \
                   np.bincount(i, weights=pair_forces[:, 0], minlength=len(a))
        forces_y = np.bincount(j, weights=pair_forces[:, 1], minlength=len(a)) - \
                   np.bincount(i, weights=pair_forces[:, 1], minlength=len(a))
        forces_z = np.bincount(j, weights=pair_forces[:, 2], minlength=len(a)) - \
                   np.bincount(i, weights=pair_forces[:, 2], minlength=len(a))

    5. Dynamical matrix for a pair potential stored in a block sparse format:
        from scipy.sparse import bsr_matrix
        i, j, dr, abs_dr = neighbor_list('ijDd', atoms)
        energy = (dr.T / abs_dr).T
        dynmat = -(dde * (energy.reshape(-1, 3, 1) * energy.reshape(-1, 1, 3)).T).T \
                 -(de / abs_dr * (np.eye(3, dtype=energy.dtype) - \
                   (energy.reshape(-1, 3, 1) * energy.reshape(-1, 1, 3))).T).T
        dynmat_bsr = bsr_matrix((dynmat, j, first_i), shape=(3*len(a), 3*len(a)))

        dynmat_diag = np.empty((len(a), 3, 3))
        for x in range(3):
            for y in range(3):
                dynmat_diag[:, x, y] = -np.bincount(i, weights=dynmat[:, x, y])

        dynmat_bsr += bsr_matrix((dynmat_diag, np.arange(len(a)),
                                  np.arange(len(a) + 1)),
                                 shape=(3 * len(a), 3 * len(a)))


  i_n, j_n, dr_nc, abs_dr_n = neighbour_list('ijDd', atoms, dict)
e_nc = (dr_nc.T/abs_dr_n).T
    D_ncc = -(dde_n * (e_nc.reshape(-1,3,1) * e_nc.reshape(-1,1,3)).T).T
    D_ncc += -(de_n/abs_dr_n * (np.eye(3, dtype=e_nc.dtype) - (e_nc.reshape(-1,3,1) * e_nc.reshape(-1,1,3))).T).T

    D = bsr_matrix((D_ncc, j_n, first_i), shape=(3*nat,3*nat))

    Ddiag_icc = np.empty((nat,3,3))
    for x in range(3):
        for y in range(3):
            Ddiag_icc[:,x,y] = -np.bincount(i_n, weights = D_ncc[:,x,y])

    D += bsr_matrix((Ddiag_icc,np.arange(nat),np.arange(nat+1)), shape=(3*nat,3*nat))

    return D
    """

    if isinstance(cutoff, dict):
        maxel = np.max(a.numbers)
        _cutoff = np.zeros([maxel+1, maxel+1], dtype=float)
        for (el1, el2), c in cutoff.items():
            try:
                el1 = atomic_numbers[el1]
            except:
                pass
            try:
                el2 = atomic_numbers[el2]
            except:
                pass
            if el1 < maxel+1 and el2 < maxel+1:
                _cutoff[el1, el2] = c
                _cutoff[el2, el1] = c
    else:
        _cutoff = cutoff

    return _matscipy.neighbour_list(quantities, a.cell,
                                    np.linalg.inv(a.cell.T), a.pbc,
                                    a.positions, _cutoff,
                                    a.numbers.astype(np.int32))
Пример #4
0
def neighbour_list(quantities, a, cutoff):
    """
    Compute a neighbour list for an atomic configuration.

    Parameters
    ----------
    quantities : str
        Quantities to compute by the neighbor list algorithm. Each character
        in this string defines a quantity. They are returned in a tuple of
        the same order. Possible quantities are
            'i' : first atom index
            'j' : second atom index
            'd' : absolute distance
            'D' : distance vector
            'S' : shift vector (number of cell boundaries crossed by the bond
                  between atom i and j). With the shift vector S, the
                  distances d between can be computed as:
                  D = a.positions[j]-a.positions[i]+S.dot(a.cell)
    a : ase.Atoms
        Atomic configuration.
    cutoff : float or dict
        Cutoff for neighbour search. If single float is given, a global cutoff
        is used for all elements. A dictionary specifies cutoff for element
        pairs. Specification accepts element numbers of symbols.
        Example: {(1, 6): 1.1, (1, 1): 1.0, ('C', 'C'): 1.85}

    Returns
    -------
    i, j, ... : array
        Tuple with arrays for each quantity specified above. Indices in `i`
        are returned in ascending order 0..len(a), but the order of (i,j)
        pairs is not guaranteed.

    Examples
    --------
    Examples assume Atoms object *a* and numpy imported as *np*.
    1. Coordination counting:
        i = neighbour_list('i', a, 1.85)
        coord = np.bincount(i)

    2. Coordination counting with different cutoffs for each pair of species
        i = neighbour_list('i', a,
                           {('H', 'H'): 1.1, ('C', 'H'): 1.3, ('C', 'C'): 1.85})
        coord = np.bincount(i)

    3. Pair distribution function:
        d = neighbour_list('d', a, 10.00)
        h, bin_edges = np.histogram(d, bins=100)
        pdf = h/(4*np.pi/3*(bin_edges[1:]**3 - bin_edges[:-1]**3)) * a.get_volume()/len(a)

    4. Pair potential:
        i, j, d, D = neighbour_list('ijdD', a, 5.0)
        energy = (-C/d**6).sum()
        pair_forces = (6*C/d**5  * (D/d).T).T
        forces_x = np.bincount(j, weights=pair_forces[:, 0], minlength=len(a)) - \
                   np.bincount(i, weights=pair_forces[:, 0], minlength=len(a))
        forces_y = np.bincount(j, weights=pair_forces[:, 1], minlength=len(a)) - \
                   np.bincount(i, weights=pair_forces[:, 1], minlength=len(a))
        forces_z = np.bincount(j, weights=pair_forces[:, 2], minlength=len(a)) - \
                   np.bincount(i, weights=pair_forces[:, 2], minlength=len(a))
    """

    if isinstance(cutoff, dict):
        maxel = np.max(a.numbers)
        _cutoff = np.zeros([maxel+1, maxel+1], dtype=float)
        for (el1, el2), c in cutoff.items():
            try:
                el1 = atomic_numbers[el1]
            except:
                pass
            try:
                el2 = atomic_numbers[el2]
            except:
                pass
            if el1 < maxel+1 and el2 < maxel+1:
                _cutoff[el1, el2] = c
                _cutoff[el2, el1] = c
    else:
        _cutoff = cutoff

    return _matscipy.neighbour_list(quantities, a.cell,
                                    np.linalg.inv(a.cell.T), a.pbc,
                                    a.positions, _cutoff,
                                    a.numbers.astype(np.int32))
Пример #5
0
def neighbour_list(quantities,
                   atoms=None,
                   cutoff=None,
                   positions=None,
                   cell=None,
                   pbc=None,
                   numbers=None,
                   cell_origin=None):
    """
    Compute a neighbor list for an atomic configuration. Atoms outside periodic
    boundaries are mapped into the box. Atoms outside nonperiodic boundaries
    are included in the neighbor list but the complexity of neighbor list search
    for those can become n^2.

    The neighbor list is sorted by first atom index 'i', but not by second 
    atom index 'j'.

    The neighbour list accepts either an ASE Atoms object or positions and cell
    vectors individually.

    Parameters
    ----------
    quantities : str
        Quantities to compute by the neighbor list algorithm. Each character
        in this string defines a quantity. They are returned in a tuple of
        the same order. Possible quantities are
            'i' : first atom index
            'j' : second atom index
            'd' : absolute distance
            'D' : distance vector
            'S' : shift vector (number of cell boundaries crossed by the bond
                  between atom i and j). With the shift vector S, the
                  distances D between atoms can be computed from:
                  D = a.positions[j]-a.positions[i]+S.dot(a.cell)
    atoms : ase.Atoms
        Atomic configuration. (Default: None)
    cutoff : float or dict
        Cutoff for neighbor search. It can be
            - A single float: This is a global cutoff for all elements.
            - A dictionary: This specifies cutoff values for element
              pairs. Specification accepts element numbers of symbols.
              Example: {(1, 6): 1.1, (1, 1): 1.0, ('C', 'C'): 1.85}
            - A list/array with a per atom value: This specifies the radius of
              an atomic sphere for each atoms. If spheres overlap, atoms are
              within each others neighborhood.
    positions : array_like
        Atomic positions. (Default: None)
    cell : array_like
        Cell vectors as a 3x3 matrix. (Default: Shrink wrapped cell)
    pbc : array_like
        3-vector containing periodic boundary conditions in all three
        directions. (Default: Nonperiodic box)
    numbers : array_like
        Array containing the atomic numbers.

    Returns
    -------
    i, j, ... : array
        Tuple with arrays for each quantity specified above. Indices in `i`
        are returned in ascending order 0..len(a), but the order of (i,j)
        pairs is not guaranteed.

    Examples
    --------
    Examples assume Atoms object *a* and numpy imported as *np*.
    1. Coordination counting:
        i = neighbor_list('i', a, 1.85)
        coord = np.bincount(i)

    2. Coordination counting with different cutoffs for each pair of species
        i = neighbor_list('i', a,
                           {('H', 'H'): 1.1, ('C', 'H'): 1.3, ('C', 'C'): 1.85})
        coord = np.bincount(i)

    3. Pair distribution function:
        d = neighbor_list('d', a, 10.00)
        h, bin_edges = np.histogram(d, bins=100)
        pdf = h/(4*np.pi/3*(bin_edges[1:]**3 - bin_edges[:-1]**3)) * a.get_volume()/len(a)

    4. Pair potential:
        i, j, d, D = neighbor_list('ijdD', a, 5.0)
        energy = (-C/d**6).sum()
        pair_forces = (6*C/d**5  * (D/d).T).T
        forces_x = np.bincount(j, weights=pair_forces[:, 0], minlength=len(a)) - \
                   np.bincount(i, weights=pair_forces[:, 0], minlength=len(a))
        forces_y = np.bincount(j, weights=pair_forces[:, 1], minlength=len(a)) - \
                   np.bincount(i, weights=pair_forces[:, 1], minlength=len(a))
        forces_z = np.bincount(j, weights=pair_forces[:, 2], minlength=len(a)) - \
                   np.bincount(i, weights=pair_forces[:, 2], minlength=len(a))

    5. Dynamical matrix for a pair potential stored in a block sparse format:
        from scipy.sparse import bsr_matrix
        i, j, dr, abs_dr = neighbor_list('ijDd', atoms)
        energy = (dr.T / abs_dr).T
        dynmat = -(dde * (energy.reshape(-1, 3, 1) * energy.reshape(-1, 1, 3)).T).T \
                 -(de / abs_dr * (np.eye(3, dtype=energy.dtype) - \
                   (energy.reshape(-1, 3, 1) * energy.reshape(-1, 1, 3))).T).T
        dynmat_bsr = bsr_matrix((dynmat, j, first_i), shape=(3*len(a), 3*len(a)))

        dynmat_diag = np.empty((len(a), 3, 3))
        for x in range(3):
            for y in range(3):
                dynmat_diag[:, x, y] = -np.bincount(i, weights=dynmat[:, x, y])

        dynmat_bsr += bsr_matrix((dynmat_diag, np.arange(len(a)),
                                  np.arange(len(a) + 1)),
                                 shape=(3 * len(a), 3 * len(a)))


  i_n, j_n, dr_nc, abs_dr_n = neighbour_list('ijDd', atoms, dict)
e_nc = (dr_nc.T/abs_dr_n).T
    D_ncc = -(dde_n * (e_nc.reshape(-1,3,1) * e_nc.reshape(-1,1,3)).T).T
    D_ncc += -(de_n/abs_dr_n * (np.eye(3, dtype=e_nc.dtype) - (e_nc.reshape(-1,3,1) * e_nc.reshape(-1,1,3))).T).T

    D = bsr_matrix((D_ncc, j_n, first_i), shape=(3*nat,3*nat))

    Ddiag_icc = np.empty((nat,3,3))
    for x in range(3):
        for y in range(3):
            Ddiag_icc[:,x,y] = -np.bincount(i_n, weights = D_ncc[:,x,y])

    D += bsr_matrix((Ddiag_icc,np.arange(nat),np.arange(nat+1)), shape=(3*nat,3*nat))

    return D
    """

    if cutoff is None:
        raise ValueError('Please provide a value for the cutoff radius.')

    if atoms is None:
        if positions is None:
            raise ValueError('You provided neither an ASE Atoms object nor '
                             'a positions array.')
        if cell is None:
            # Shrink wrapped cell
            rmin = np.min(positions, axis=0)
            rmax = np.max(positions, axis=0)
            cell_origin = rmin
            cell = np.diag(rmax - rmin)
        if cell_origin is None:
            cell_origin = np.zeros(3)
        if pbc is None:
            pbc = np.zeros(3, dtype=bool)
        if numbers is None:
            numbers = np.ones(len(positions), dtype=np.int32)
    else:
        if positions is not None:
            raise ValueError(
                'You cannot provide an ASE Atoms object and '
                'individual position atomic positions at the same '
                'time.')
        positions = atoms.positions
        if cell_origin is not None:
            raise ValueError('You cannot provide an ASE Atoms object and '
                             'a cell origin at the same time.')
        cell_origin = np.zeros(3)
        if cell is not None:
            raise ValueError('You cannot provide an ASE Atoms object and '
                             'cell vectors at the same time.')
        cell = atoms.cell
        if pbc is not None:
            raise ValueError('You cannot provide an ASE Atoms object and '
                             'separate periodicity information at the same '
                             'time.')
        pbc = atoms.pbc
        if numbers is not None:
            raise ValueError('You cannot provide an ASE Atoms object and '
                             'separate atomic numbers at the same time.')
        numbers = atoms.numbers.astype(np.int32)

    if isinstance(cutoff, dict):
        maxel = np.max(numbers)
        _cutoff = np.zeros([maxel + 1, maxel + 1], dtype=float)
        for (el1, el2), c in cutoff.items():
            try:
                el1 = atomic_numbers[el1]
            except:
                pass
            try:
                el2 = atomic_numbers[el2]
            except:
                pass
            if el1 < maxel + 1 and el2 < maxel + 1:
                _cutoff[el1, el2] = c
                _cutoff[el2, el1] = c
    else:
        _cutoff = cutoff

    return _matscipy.neighbour_list(quantities, cell_origin, cell,
                                    np.linalg.inv(cell.T), pbc, positions,
                                    _cutoff, numbers)
Пример #6
0
def neighbour_list(quantities, a, cutoff):
    """
    Compute a neighbor list for an atomic configuration. Atoms outside periodic
    boundaries are mapped into the box. Atoms outside nonperiodic boundaries
    are included in the neighbor list but complexity of neighbor list search
    for those can become n^2.

    The neighbor list is sorted by first atom index 'i', but not by second 
    atom index 'j'.

    Parameters
    ----------
    quantities : str
        Quantities to compute by the neighbor list algorithm. Each character
        in this string defines a quantity. They are returned in a tuple of
        the same order. Possible quantities are
            'i' : first atom index
            'j' : second atom index
            'd' : absolute distance
            'D' : distance vector
            'S' : shift vector (number of cell boundaries crossed by the bond
                  between atom i and j). With the shift vector S, the
                  distances D between atoms can be computed from:
                  D = a.positions[j]-a.positions[i]+S.dot(a.cell)
    a : ase.Atoms
        Atomic configuration.
    cutoff : float or dict
        Cutoff for neighbor search. It can be
            - A single float: This is a global cutoff for all elements.
            - A dictionary: This specifies cutoff values for element
              pairs. Specification accepts element numbers of symbols.
              Example: {(1, 6): 1.1, (1, 1): 1.0, ('C', 'C'): 1.85}
            - A list/array with a per atom value: This specifies the radius of
              an atomic sphere for each atoms. If spheres overlap, atoms are
              within each others neighborhood.

    Returns
    -------
    i, j, ... : array
        Tuple with arrays for each quantity specified above. Indices in `i`
        are returned in ascending order 0..len(a), but the order of (i,j)
        pairs is not guaranteed.

    Examples
    --------
    Examples assume Atoms object *a* and numpy imported as *np*.
    1. Coordination counting:
        i = neighbor_list('i', a, 1.85)
        coord = np.bincount(i)

    2. Coordination counting with different cutoffs for each pair of species
        i = neighbor_list('i', a,
                           {('H', 'H'): 1.1, ('C', 'H'): 1.3, ('C', 'C'): 1.85})
        coord = np.bincount(i)

    3. Pair distribution function:
        d = neighbor_list('d', a, 10.00)
        h, bin_edges = np.histogram(d, bins=100)
        pdf = h/(4*np.pi/3*(bin_edges[1:]**3 - bin_edges[:-1]**3)) * a.get_volume()/len(a)

    4. Pair potential:
        i, j, d, D = neighbor_list('ijdD', a, 5.0)
        energy = (-C/d**6).sum()
        pair_forces = (6*C/d**5  * (D/d).T).T
        forces_x = np.bincount(j, weights=pair_forces[:, 0], minlength=len(a)) - \
                   np.bincount(i, weights=pair_forces[:, 0], minlength=len(a))
        forces_y = np.bincount(j, weights=pair_forces[:, 1], minlength=len(a)) - \
                   np.bincount(i, weights=pair_forces[:, 1], minlength=len(a))
        forces_z = np.bincount(j, weights=pair_forces[:, 2], minlength=len(a)) - \
                   np.bincount(i, weights=pair_forces[:, 2], minlength=len(a))

    5. Dynamical matrix for a pair potential stored in a block sparse format:
        from scipy.sparse import bsr_matrix
        i, j, dr, abs_dr = neighbor_list('ijDd', atoms)
        energy = (dr.T / abs_dr).T
        dynmat = -(dde * (energy.reshape(-1, 3, 1) * energy.reshape(-1, 1, 3)).T).T \
                 -(de / abs_dr * (np.eye(3, dtype=energy.dtype) - \
                   (energy.reshape(-1, 3, 1) * energy.reshape(-1, 1, 3))).T).T
        dynmat_bsr = bsr_matrix((dynmat, j, first_i), shape=(3*len(a), 3*len(a)))

        dynmat_diag = np.empty((len(a), 3, 3))
        for x in range(3):
            for y in range(3):
                dynmat_diag[:, x, y] = -np.bincount(i, weights=dynmat[:, x, y])

        dynmat_bsr += bsr_matrix((dynmat_diag, np.arange(len(a)),
                                  np.arange(len(a) + 1)),
                                 shape=(3 * len(a), 3 * len(a)))

    """

    if isinstance(cutoff, dict):
        maxel = np.max(a.numbers)
        _cutoff = np.zeros([maxel+1, maxel+1], dtype=float)
        for (el1, el2), c in cutoff.items():
            try:
                el1 = atomic_numbers[el1]
            except:
                pass
            try:
                el2 = atomic_numbers[el2]
            except:
                pass
            if el1 < maxel+1 and el2 < maxel+1:
                _cutoff[el1, el2] = c
                _cutoff[el2, el1] = c
    else:
        _cutoff = cutoff

    return _matscipy.neighbour_list(quantities, a.cell,
                                    np.linalg.inv(a.cell.T), a.pbc,
                                    a.positions, _cutoff,
                                    a.numbers.astype(np.int32))
Пример #7
0
def neigh_list_based_target_function(x, r=1.0, constraints=None, Dij=None):
    """Target function. Penalize dense packing for coordinates ||xi-xj||<ri+rj.

    Parameters
    ----------
    x: (N,dim) ndarray
        particle coordinates
    r: float or (N,) ndarray, optional (default=1.0)
        steric radii of particles
    constraints: callable, returns (float, (N,dim) ndarray)
        constraint function value and gradien
    Dij: (N, N) ndarray, optional (default=None)
        pairwise minimum allowed distance matrix, overrides r

    Returns
    -------
    (float, (N,dim) ndarray): target function value and gradient

        f: float, target (or penalty) function, value evaluates to
              sum_i sum_{j!=i} max(0,(r_i+r_j)^2-||xi-xj||^2)^2
            without double-counting pairs, where r_i and r_j are
            the steric radii of coordinate points i and j
        df: (N,dim) ndarray of float, the gradient, evaluates to
              4*si sj ((r_i'+r_j)^2-||xi-xj||^2)^2)*(xik'-xjk')*(kdi'j-kdi'i)
            for entry (i',k'), where i subscribes the coordinate point and k
            the spatial dimension. kd is Kronecker delta, si is sum over i
    """
    logger = logging.getLogger(__name__)
    #
    # function:
    #
    # f(x) = sum_i sum_{j!=i} max(0,(r_i+r_j)^2-||xi-xj||^2)^2
    #
    # gradient:
    #
    # dfdxi'k'=4*si sj ((r_i'+r_j)^2-||xi-xj||^2)^2)*(xik'-xjk')*(kdi'j-kdi'i)
    #
    # for all pairs i'j within ri'+rj cutoff
    # where k is the spatial direction x, y or z.

    assert x.ndim == 2, "2d array expected for x"
    n = x.shape[0]

    if not Dij:
        if not isinstance(r, np.ndarray) or r.shape != (n, ):
            r = r * np.ones(n)
        assert r.shape == (n, )

        # compute minimum allowed pairwise distances Dij (NxN matrix)
        # r(Nx1) kron ones(1xN) = Ri(NxN)
        Ri = np.kron(r, np.ones((n, 1)))
        Rj = Ri.T
        Dij = Ri + Rj

    # TODO: allow for periodic boundaries, for now use shrink wrapped box
    box = np.array([x.min(axis=0), x.max(axis=0)])
    cell_origin = box[0, :]
    cell = np.diag(box[1, :] - box[0, :])

    # get all pairs within their allowed minimum distance
    # parameters are
    # _matscipy.neighbour_list(quantities, cell_origin, cell,
    #                          np.linalg.inv(cell.T), pbc, positions,
    #                          cutoff, numbers)
    # If thhe parameter 'cutoff' is a per-atom value, then it must be a
    # diamater, not a radius (as wrongly stated within the function's
    # docstring)
    i, j, dxijnorm, dxijvec = _matscipy.neighbour_list(
        'ijdD', cell_origin, cell, np.linalg.inv(cell.T), [0, 0, 0], x,
        2.0 * Ri, np.ones(len(x), dtype=np.int32))
    # i, j are coordinate point indices, dxijnorm is pairwise distance,
    #   dxvijvec is distance vector

    # nl contains redundnancies, i.e. ij AND ji
    # pairs = list(zip(i,j))
    logger.debug(
        "Number of pairs within minimum allowed distance: {:d}".format(len(i)))

    # get minimum allowed pairwise distance for all pairs within this distance
    dij = Dij[i, j]

    # (r_i'+r_j)^2
    dsq = np.square(dij)

    # ||xi-xj||^2
    dxnormsq = np.square(dxijnorm)

    # (r_i+r_j)^2-||xi-xj||^2
    sqdiff = dsq - dxnormsq

    # ((r_i+r_j)^2-||xi-xj||^2)^2
    penaltysq = np.square(sqdiff)

    # correct for double counting due to redundancies
    f = 0.5 * np.sum(penaltysq)

    # 4*si sj ((r_i'+r_j)^2-||xi-xj||^2)^2)*(xik'-xjk')*(kdi'j-kdi'i)
    # (N x 1) column vector (a1 ... aN) * (N x dim) matrix ((d11,))
    # Other than the formula above implicates, _matscipy.neighbour_list
    # returns the distance vector dxijvec = xj-xi (pointing from i to j),
    # thus positive sign in gradient below:
    gradij = 4 * np.atleast_2d(
        sqdiff).T * dxijvec  # let's hope for proper broadcasting
    grad = np.zeros(x.shape)
    grad[i] += gradij
    # Since neighbour list always includes ij and ji, we only have to treat i,
    # not j. The following line is obsolete (otherwise we would introduce
    # double-counting again):
    # grad[j] -= gradij

    if constraints:
        logger.debug("Unconstrained penalty:       {:10.5e}.".format(f))
        logger.debug(
            DeferredMessage("Unconstrained gradient norm: {:10.5e}.",
                            np.linalg.norm, grad))
        g, g_grad = constraints(x)
        f += g
        grad += g_grad

    grad_1d = grad.reshape(np.product(grad.shape))

    logger.debug("Total penalty:               {:10.5e}.".format(f))
    logger.debug(
        DeferredMessage("Gradient norm:               {:10.5e}.",
                        np.linalg.norm, grad_1d))

    return f, grad_1d
Пример #8
0
def neighbour_list(quantities, a, cutoff, *args):
    """
    Compute a neighbour list for an atomic configuration.

    Parameters
    ----------
    quantities : str
        Quantities to compute by the neighbor list algorithm. Each character
        in this string defines a quantity. They are returned in a tuple of
        the same order. Possible quantities are
            'i' : first atom index
            'j' : second atom index
            'd' : absolute distance
            'D' : distance vector
            'S' : shift vector (number of cell boundaries crossed by the bond
                  between atom i and j). With the shift vector S, the
                  distances d between can be computed as:
                  D = a.positions[j]-a.positions[i]+S.dot(a.cell)
    a : ase.Atoms
        Atomic configuration.
    cutoff : float or array_like
        Cutoff for neighbour search. If square array is given, then different
        cutoffs will be used for individual bonds. Square array contains
        pair-wise cutoffs for a given species, given by the *numbers* parameter.
    numbers : array_like, optional
        Atomic numbers or similar identifiers for elements. Used for cutoff
        lookup.

    Returns
    -------
    i, j, ... : array
        Tuple with arrays for each quantity specified above. Indices in `i`
        are returned in ascending order 0..len(a), but the order of (i,j)
        pairs is not guaranteed.

    Examples
    --------
    Examples assume Atoms object *a* and numpy imported as *np*.
    1. Coordination counting:
        i = neighbour_list('i', a, 1.85)
        coord = np.bincount(i)

    2. Coordination counting with different cutoffs for each pair of species
       (Assumes that species are Carbon=6 and Hydrogen=1)
        i = neighbour_list('i', a, [[1.1, 1.3], [1.3, 1.85]], (a.numbers-1)//5)
        coord = np.bincount(i)

    3. Pair distribution function:
        d = neighbour_list('d', a, 10.00)
        h, bin_edges = np.histogram(d, bins=100)
        pdf = h/(4*np.pi/3*(bin_edges[1:]**3 - bin_edges[:-1]**3)) * a.get_volume()/len(a)

    4. Pair potential:
        i, j, d, D = neighbour_list('ijdD', a, 5.0)
        energy = (-C/d**6).sum()
        pair_forces = (6*C/d**5  * (D/d).T).T
        forces_x = np.bincount(j, weights=pair_forces[:, 0], minlength=len(a)) - \
                   np.bincount(i, weights=pair_forces[:, 0], minlength=len(a))
        forces_y = np.bincount(j, weights=pair_forces[:, 1], minlength=len(a)) - \
                   np.bincount(i, weights=pair_forces[:, 1], minlength=len(a))
        forces_z = np.bincount(j, weights=pair_forces[:, 2], minlength=len(a)) - \
                   np.bincount(i, weights=pair_forces[:, 2], minlength=len(a))
    """

    return _matscipy.neighbour_list(quantities, a.cell,
                                    np.linalg.inv(a.cell.T), a.pbc,
                                    a.positions, cutoff, *args)