Exemple #1
0
    def bind(self, frame):
        if not self.ui.get_widget('/MenuBar/ViewMenu/ShowBonds'
                                  ).get_active():
            self.bonds = np.empty((0, 5), int)
            return
        
        from ase.atoms import Atoms
        from ase.neighborlist import NeighborList
        nl = NeighborList(self.images.r * 1.5, skin=0, self_interaction=False)
        nl.update(Atoms(positions=self.images.P[frame],
                        cell=(self.images.repeat[:, np.newaxis] *
                              self.images.A[frame]),
                        pbc=self.images.pbc))
        nb = nl.nneighbors + nl.npbcneighbors
        self.bonds = np.empty((nb, 5), int)
        self.coordination = np.zeros((self.images.natoms), dtype=int)
        if nb == 0:
            return
        
        n1 = 0
        for a in range(self.images.natoms):
            indices, offsets = nl.get_neighbors(a)
            self.coordination[a] += len(indices)
            for a2 in indices:
                self.coordination[a2] += 1
            n2 = n1 + len(indices)
            self.bonds[n1:n2, 0] = a
            self.bonds[n1:n2, 1] = indices
            self.bonds[n1:n2, 2:] = offsets
            n1 = n2

        i = self.bonds[:n2, 2:].any(1)
        self.bonds[n2:, 0] = self.bonds[i, 1]
        self.bonds[n2:, 1] = self.bonds[i, 0]
        self.bonds[n2:, 2:] = -self.bonds[i, 2:]
Exemple #2
0
def candidates_combos(atoms, edge=None, pore_size=None):
    """Return candidate pore indices combinations."""
    from itertools import combinations

    cans = []
    indices = [a.index for a in atoms if a.index not in edge]

    nblist = NeighborList([graphene_cutoff for i in range(len(atoms))],
                           bothways=True,
                           self_interaction=False)
    nblist.update(atoms)

    def constraint_check(pores):
        for pore in pores:
            remains = [a.index for a in atoms if a.index not in pore]
            if is_connected(nblist, remains) and is_connected(nblist, pore):
                cans.append(pore)

    if pore_size is not None:
        pores = combinations(indices, pore_size)
        print(sum([1 for p in pores]))
        pores = combinations(indices, pore_size)
        constraint_check(pores)
    else:
        for i in range(1, len(indices)):
            pores = combinations(indices, i)
            constraint_check(pores)

    return cans
Exemple #3
0
def get_bondpairs(atoms, radius=1.1):
    """Get all pairs of bonding atoms

    Return all pairs of atoms which are closer than radius times the
    sum of their respective covalent radii.  The pairs are returned as
    tuples::

      (a, b, (i1, i2, i3))

    so that atoms a bonds to atom b displaced by the vector::

        _     _     _
      i c + i c + i c ,
       1 1   2 2   3 3

    where c1, c2 and c3 are the unit cell vectors and i1, i2, i3 are
    integers."""

    from ase.data import covalent_radii
    from ase.neighborlist import NeighborList
    cutoffs = radius * covalent_radii[atoms.numbers]
    nl = NeighborList(cutoffs=cutoffs, self_interaction=False)
    nl.update(atoms)
    bondpairs = []
    for a in range(len(atoms)):
        indices, offsets = nl.get_neighbors(a)
        bondpairs.extend([(a, a2, offset)
                          for a2, offset in zip(indices, offsets)])
    return bondpairs
Exemple #4
0
def is_connected(reference, indices, cutoff=graphene_cutoff):
    """Return True if indices in atoms are connected.

    Args:
        reference (Atoms or NeighborList): Structure containing atoms for test. The NeighborList must be constructed with bothways=True.
        indices (List[int]): Indices of the possibly connected atoms.
        cutoff (int): Radius defining neighbors in a NeighborList. Only relevent when reference is of the Atoms type.
    """
    if isinstance(reference, Atoms):
        nblist = NeighborList([cutoff for i in range(len(reference))],
                              bothways=True,
                              self_interaction=False)
        nblist.update(reference)
    else:
        nblist = reference

    if len(indices) == 0:
        return True

    connected = [indices[0]]
    for c in connected:
        neighbs = nblist.get_neighbors(c)[0]
        for n in neighbs:
            if n in indices and n not in connected:
                connected.append(n)

    return set(indices) == set(connected)
Exemple #5
0
def randomize_biatom_13(atoms, type_a, type_b, ratio):
    """ replace randomly by clusters of 13 atoms
     to acheive target conc """
    n_A = 0
    n_B = 0
    for atom in atoms:
        if atom.symbol == type_a:
            n_A += 1
        elif atom.symbol == type_b:
            n_B += 1
        else:
            raise Exception('Extra chemical element %s!'%atom.symbol)
    #print n_A, n_B
    N = len(atoms)
    nl = NeighborList([1.5]*N, self_interaction=False, bothways=True)  # 2*1.5=3 Angstr. radius
    nl.build(atoms)
    #print "conc",  n_A *1.0 / N
    r = random.Random()
    while n_A < ratio*N:  # add A atoms randomly
        index = r.randint(0, N-1)
        if (atoms[index].symbol != type_a):
            #print "changing atom #"+str(index)+" to "+type_a
            #if (r.randint(0, 1000) < 500):
            atoms[index].symbol = type_a
            n_A += 1
            indeces, offsets = nl.get_neighbors(index)
            for ia in indeces :
                if (atoms[ia].symbol != type_a)&(n_A < ratio*N):
                    atoms[ia].symbol = type_a
                    n_A += 1
    return atoms
Exemple #6
0
def get_bonds(atoms, covalent_radii):
    from ase.neighborlist import NeighborList
    nl = NeighborList(covalent_radii * 1.5,
                      skin=0, self_interaction=False)
    nl.update(atoms)
    nbonds = nl.nneighbors + nl.npbcneighbors

    bonds = np.empty((nbonds, 5), int)
    if nbonds == 0:
        return bonds

    n1 = 0
    for a in range(len(atoms)):
        indices, offsets = nl.get_neighbors(a)
        n2 = n1 + len(indices)
        bonds[n1:n2, 0] = a
        bonds[n1:n2, 1] = indices
        bonds[n1:n2, 2:] = offsets
        n1 = n2

    i = bonds[:n2, 2:].any(1)
    pbcbonds = bonds[:n2][i]
    bonds[n2:, 0] = pbcbonds[:, 1]
    bonds[n2:, 1] = pbcbonds[:, 0]
    bonds[n2:, 2:] = -pbcbonds[:, 2:]
    return bonds
Exemple #7
0
def exafs_first_shell(S02, energy_shift, absorber,
    ignore_elements, edge, neighbor_cutoff, trajectory):
    feff_options = {
            'RMAX':str(neighbor_cutoff),
            'HOLE':'%i %.4f' % (feff_edge_number(edge), S02),
            'CORRECTIONS':'%.4f %.4f' % (energy_shift, 0.0),
    }

    #get the bulk reference state
    path = exafs_reference_path(absorber, feff_options)

    k = None
    chi_total = None

    counter = -1
    interactions = 0
    nl = None

    for step, atoms in enumerate(trajectory):
        if COMM_WORLD.rank == 0:
            time_stamp = strftime("%F %T")
            print '[%s] step %i/%i' % (time_stamp, step+1, len(trajectory))
        atoms = atoms.copy()
        if ignore_elements:
            ignore_indicies = [atom.index for atom in atoms
                               if atom.symbol in ignore_elements]
            del atoms[ignore_indicies]
        if nl is None:
            nl = NeighborList(len(atoms)*[neighbor_cutoff], skin=0.3,
                    self_interaction=False)
        nl.update(atoms)

        for i in xrange(len(atoms)):
            if atoms[i].symbol != absorber:
                continue
            indicies, offsets = nl.get_neighbors(i)
            for j, offset in zip(indicies, offsets):
                counter += 1
                if counter % COMM_WORLD.size != COMM_WORLD.rank:
                    continue

                r = atoms.get_distance(i,j,True)
                if r >= neighbor_cutoff: continue
                interactions += 1
                k, chi = chi_path(path, r, 0.0, energy_shift, S02, 1)

                if chi_total is not None:
                    chi_total += chi
                else:
                    chi_total = chi
    chi_total = COMM_WORLD.allreduce(chi_total)
    chi_total /= atoms.get_chemical_symbols().count(absorber)
    chi_total /= len(trajectory)
    chi_total *= 2
    return k, chi_total
Exemple #8
0
    def update(self, atoms):
        # check all the elements are available in the potential
        self.Nelements = len(self.elements)
        elements = np.unique(atoms.get_chemical_symbols())
        unavailable = np.logical_not(
            np.array([item in self.elements for item in elements]))

        if np.any(unavailable):
            raise RuntimeError('These elements are not in the potential: %s' %
                               elements[unavailable])

        # cutoffs need to be a vector for NeighborList
        cutoffs = self.cutoff * np.ones(len(atoms))

        # convert the elements to an index of the position
        # in the eam format
        self.index = np.array([self.elements.index(el)
                               for el in atoms.get_chemical_symbols()])
        self.pbc = atoms.get_pbc()

        # since we need the contribution of all neighbors to the
        # local electron density we cannot just calculate and use
        # one way neighbors
        self.neighbors = NeighborList(cutoffs,
                                      skin=self.parameters.skin,
                                      self_interaction=False,
                                      bothways=True)
        self.neighbors.update(atoms)
Exemple #9
0
    def initialize(self, atoms):
        self.par = {}
        self.rc = 0.0
        self.numbers = atoms.get_atomic_numbers()
        if self.parameters.asap_cutoff:
            relevant_pars = {}
            for symb, p in parameters.items():
                if atomic_numbers[symb] in self.numbers:
                    relevant_pars[symb] = p
        else:
            relevant_pars = parameters
        maxseq = max(par[1] for par in relevant_pars.values()) * Bohr
        rc = self.rc = beta * maxseq * 0.5 * (sqrt(3) + sqrt(4))
        rr = rc * 2 * sqrt(4) / (sqrt(3) + sqrt(4))
        self.acut = np.log(9999.0) / (rr - rc)
        if self.parameters.asap_cutoff:
            self.rc_list = self.rc * 1.045
        else:
            self.rc_list = self.rc + 0.5
        for Z in self.numbers:
            if Z not in self.par:
                p = parameters[chemical_symbols[Z]]
                s0 = p[1] * Bohr
                eta2 = p[3] / Bohr
                kappa = p[4] / Bohr
                x = eta2 * beta * s0
                gamma1 = 0.0
                gamma2 = 0.0
                for i, n in enumerate([12, 6, 24]):
                    r = s0 * beta * sqrt(i + 1)
                    x = n / (12 * (1.0 + exp(self.acut * (r - rc))))
                    gamma1 += x * exp(-eta2 * (r - beta * s0))
                    gamma2 += x * exp(-kappa / beta * (r - beta * s0))

                self.par[Z] = {'E0': p[0],
                               's0': s0,
                               'V0': p[2],
                               'eta2': eta2,
                               'kappa': kappa,
                               'lambda': p[5] / Bohr,
                               'n0': p[6] / Bohr**3,
                               'rc': rc,
                               'gamma1': gamma1,
                               'gamma2': gamma2}

        self.ksi = {}
        for s1, p1 in self.par.items():
            self.ksi[s1] = {}
            for s2, p2 in self.par.items():
                self.ksi[s1][s2] = p2['n0'] / p1['n0']

        self.forces = np.empty((len(atoms), 3))
        self.sigma1 = np.empty(len(atoms))
        self.deds = np.empty(len(atoms))

        self.nl = NeighborList([0.5 * self.rc_list] * len(atoms),
                               self_interaction=False)
Exemple #10
0
def hop_shuffle(atoms, A, B, count=10, R=3.0):
    """
    Shuffle atoms in given structure
    by swapping atom types within first coordination shell

    Parameters
    ----------
    atoms: ase.Atoms
        ase Atoms object, containing atomic cluster.
    A, B: string
        symbols of atoms to swap
    count: integer
        number of shuffles
    R: float
        radius of coordination shell, were atoms will be swapped

    Returns
    -------
        Function returns ASE atoms object whith
        shuffled atoms
    """
    n_atoms = len(atoms)
    nswaps = 0
    neiblist = NeighborList( [ R ] * n_atoms,
                             self_interaction=False,
                             bothways=True )
    neiblist.build( atoms )
    rnd = random.Random()
    while nswaps < count:
        i = rnd.randint(0, n_atoms-1)
        indeces, offsets = neiblist.get_neighbors( i )
        if (atoms[i].symbol == B):
            candidates = []
            for ii in indeces:
                if atoms[ii].symbol == A:
                    candidates.append( ii )
            if len(candidates) > 0:
                j = random.choice(candidates)
                atoms[i].symbol = A
                atoms[j].symbol = B
                nswaps += 1
                neiblist.build( atoms )
        elif (atoms[i].symbol == B):
            candidates = []
            for ii in indeces:
                if atoms[ii].symbol == A:
                    candidates.append( ii )
            if len(candidates) > 0:
                j = random.choice(candidates)
                atoms[i].symbol = B
                atoms[j].symbol = A
                nswaps += 1
                neiblist.build( atoms )
    return atoms
Exemple #11
0
    def calculate(self, atoms=None,
                  properties=['energy'],
                  system_changes=all_changes):
        Calculator.calculate(self, atoms, properties, system_changes)

        natoms = len(self.atoms)

        sigma = self.parameters.sigma
        epsilon = self.parameters.epsilon
        rc = self.parameters.rc
        if rc is None:
            rc = 3 * sigma

        if 'numbers' in system_changes:
            self.nl = NeighborList([rc / 2] * natoms, self_interaction=False)

        self.nl.update(self.atoms)

        positions = self.atoms.positions
        cell = self.atoms.cell

        e0 = 4 * epsilon * ((sigma / rc)**12 - (sigma / rc)**6)

        energy = 0.0
        forces = np.zeros((natoms, 3))
        stress = np.zeros((3, 3))

        for a1 in range(natoms):
            neighbors, offsets = self.nl.get_neighbors(a1)
            cells = np.dot(offsets, cell)
            d = positions[neighbors] + cells - positions[a1]
            r2 = (d**2).sum(1)
            c6 = (sigma**2 / r2)**3
            c6[r2 > rc**2] = 0.0
            energy -= e0 * (c6 != 0.0).sum()
            c12 = c6**2
            energy += 4 * epsilon * (c12 - c6).sum()
            f = (24 * epsilon * (2 * c12 - c6) / r2)[:, np.newaxis] * d
            forces[a1] -= f.sum(axis=0)
            for a2, f2 in zip(neighbors, f):
                forces[a2] += f2
            stress += np.dot(f.T, d)

        if 'stress' in properties:
            if self.atoms.number_of_lattice_vectors == 3:
                stress += stress.T.copy()
                stress *= -0.5 / self.atoms.get_volume()
                self.results['stress'] = stress.flat[[0, 4, 8, 5, 2, 1]]
            else:
                raise PropertyNotImplementedError

        self.results['energy'] = energy
        self.results['free_energy'] = energy
        self.results['forces'] = forces
Exemple #12
0
class TwoCenterHoppingIntegrals:
    def __init__(self, bopatoms: BOPAtoms, cutoffs: list, **kwargs):
        self.bopatoms = bopatoms
        self.nl = NeighborList(cutoffs=cutoffs, bothways=True, self_interaction=False, **kwargs)
        self.nl.update(bopatoms)

    def update_hops(self):
        raise NotImplemented

    def get_single_hop_global(self, atomindex: int, jneigh: int):
        hop = self.get_single_hop_local(atomindex, jneigh)
        rot = self.get_rotation(atomindex, jneigh)
        return hop


    def get_single_hop_local(self, atomindex: int, jneigh: int):
        bopatom_i = self.bopatoms[atomindex]
        (bopatom_neighbors_i, scaled_pos_list) = self.nl.get_neighbors(atomindex)
        bopatom_j_neigh_i = self.bopatoms[bopatom_neighbors_i[jneigh]]
        pos_list = np.dot(scaled_pos_list, self.bopatoms.get_cell())
        rel_pos_ij = pos_list[jneigh]
        r_ij = np.linalg.norm(rel_pos_ij)

        ss_sigma = 0
        sp_sigma = 0
        sd_sigma = 0
        pp_sigma = 0
        pp_pi = 0
        pd_sigma = 0
        pd_pi = 0
        dd_sigma = 0
        dd_pi = 0
        dd_delta = 0
        slater_koster_matrix = np.zeros((9, 9))
        # initialize bond integrals
        if bopatom_i.number_valence_orbitals == 5 and bopatom_j_neigh_i.number_valence_orbitals == 5:
            dd_sigma = np.exp(-r_ij * 1)
            dd_pi = np.exp(-r_ij * 2)
            dd_delta = np.exp(-r_ij * 3)
            slater_koster_matrix[4, 4] = dd_delta
            slater_koster_matrix[5, 5] = dd_pi
            slater_koster_matrix[6, 6] = dd_pi
            slater_koster_matrix[7, 7] = dd_delta
            slater_koster_matrix[8, 8] = dd_sigma
        return slater_koster_matrix[4:, 4:]


    def get_relative_position(self, index: int, jneigh: int):
        '''
        :param index: atom index
        :param jneigh: indexing neighboring atoms of atom index
        :return:
        '''
        # if index > len(self.bopatoms):
        #     raise IndexError
        # if jneigh > self.nl.nneighbors - 1:
        #     raise IndexError
        (index_list, relative_position_list) = self.nl.get_neighbors(index)
        return relative_position_list[jneigh]

    def get_rotation(self, atomindex: int, jneigh:int, z_axis_global: np.array=np.array([0, 0, 1])) -> Rotation:
        rel_pos = self.get_relative_position(atomindex, jneigh)
        v = np.cross(rel_pos, z_axis_global)
        cosine = np.dot(rel_pos, z_axis_global)
        if cosine != -1:
            v_cross = [[0    , -v[2], v[1] ],
                       [v[2] , 0    , -v[0]],
                       [-v[1], v[0] , 0]]
            rotation_matrix = np.eye(3) + v_cross + np.dot(v_cross, v_cross) / (1 + cosine)
        else:
            rotation_matrix = np.diag([1, 1, -1])
        return Rotation.from_dcm(rotation_matrix)

    def get_dbond_rotation_matrix(self, theta: float, phi: float):
        '''
        assumes bond to be ordered in ddsigma, ddpi, ddpi, dddelta
        :param theta:
        :param phi:
        :return:
        '''
        from numpy import sqrt, cos, sin
        rot = np.zeros((5,5))
        rot[4, 3] = -2 * sin(phi) * cos(phi) * cos(theta)
        rot[3, 3] = cos(phi)**2 - sin(phi)**2
        rot[2, 3] = -2 * sin(phi) * cos(phi) * sin(theta)
        rot[1, 3] = (cos(phi)**2 - sin(phi)**2) * sin(theta) * cos(theta)
        rot[0, 3] = (cos(phi)**2 - sin(phi)**2) * sqrt(3/4.) * sin(theta)**2

        rot[4, 1] = sin(phi) * sin(theta)
        rot[3, 1] = -cos(phi) * sin(theta) * cos(theta)
        rot[2, 1] = -sin(phi) * cos(theta)
        rot[1, 1] = cos(phi) * (cos(phi)**2 - sin(phi)**2)
        rot[0, 1] = sqrt(3) * cos(phi) * sin(theta) * cos(theta)

        rot[4, 0] = 0
        rot[3, 0] = sqrt(3/4.) * sin(theta)**2
        rot[2, 0] = 0
        rot[1, 0] = -sqrt(3) * sin(theta) * cos(theta)
        rot[0, 0] = cos(theta)**2 - 0.5 * sin(theta)**2

        rot[4, 2] = -cos(phi) * sin(theta)
        rot[3, 2] = -sin(phi) * sin(theta) * cos(theta)
        rot[2, 2] = cos(phi) * cos(theta)
        rot[1, 2] = sin(phi) * (cos(theta)**2 - sin(theta)**2)
        rot[0, 2] = sqrt(3) * sin(phi) * sin(theta) * cos(theta)

        rot[4, 4] = (cos(phi)**2 - sin(phi)**2) * cos(theta)
        rot[3, 4] = sin(phi) * cos(phi) * (cos(theta)**2 + 1)
        rot[2, 4] = (cos(phi)**2 - sin(phi)**2) * sin(theta)
        rot[1, 4] = 2 * sin(phi) * cos(phi) * sin(theta) * cos(theta)
        rot[0, 4] = sqrt(3) * sin(phi) * cos(phi) * sin(theta)**2

        return rot
Exemple #13
0
    def _take_fingerprints(self, atoms, individual=False):
        """ Returns a [fingerprints,typedic] list, where fingerprints
        is a dictionary with the fingerprints, and typedic is a
        dictionary with the list of atom indices for each element
        (or "type") in the atoms object.
        The keys in the fingerprints dictionary are the (A,B) tuples,
        which are the different element-element combinations in the
        atoms object (A and B are the atomic numbers).
        When A != B, the (A,B) tuple is sorted (A < B).

        If individual=True, a dict is returned, where each atom index
        has an {atomic_number:fingerprint} dict as value.
        If individual=False, the fingerprints from atoms of the same
        atomic number are added together."""

        pos = atoms.get_positions()
        num = atoms.get_atomic_numbers()
        cell = atoms.get_cell()

        unique_types = np.unique(num)
        posdic = {}
        typedic = {}
        for t in unique_types:
            tlist = [i for i, atom in enumerate(atoms) if atom.number == t]
            typedic[t] = tlist
            posdic[t] = pos[tlist]

        # determining the volume normalization and other parameters
        volume, pmin, pmax, qmin, qmax = self._get_volume(atoms)

        # functions for calculating the surface area
        non_pbc_dirs = [i for i in range(3) if not self.pbc[i]]

        def surface_area_0d(r):
            return 4 * np.pi * (r**2)

        def surface_area_1d(r, pos):
            q0 = pos[non_pbc_dirs[1]]
            phi1 = np.lib.scimath.arccos((qmax - q0) / r).real
            phi2 = np.pi - np.lib.scimath.arccos((qmin - q0) / r).real
            factor = 1 - (phi1 + phi2) / np.pi
            return surface_area_2d(r, pos) * factor

        def surface_area_2d(r, pos):
            p0 = pos[non_pbc_dirs[0]]
            area = np.minimum(pmax - p0, r) + np.minimum(p0 - pmin, r)
            area *= 2 * np.pi * r
            return area

        def surface_area_3d(r):
            return 4 * np.pi * (r**2)

        # build neighborlist
        # this is computationally the most intensive part
        a = atoms.copy()
        a.set_pbc(self.pbc)
        nl = NeighborList([self.rcut / 2.] * len(a),
                          skin=0.,
                          self_interaction=False,
                          bothways=True)
        nl.update(a)

        # parameters for the binning:
        m = int(np.ceil(self.nsigma * self.sigma / self.binwidth))
        x = 0.25 * np.sqrt(2) * self.binwidth * (2 * m + 1) * 1. / self.sigma
        smearing_norm = erf(x)
        nbins = int(np.ceil(self.rcut * 1. / self.binwidth))
        bindist = self.binwidth * np.arange(1, nbins + 1)

        def take_individual_rdf(index, unique_type):
            # Computes the radial distribution function of atoms
            # of type unique_type around the atom with index "index".
            rdf = np.zeros(nbins)

            if self.dimensions == 3:
                weights = 1. / surface_area_3d(bindist)
            elif self.dimensions == 2:
                weights = 1. / surface_area_2d(bindist, pos[index])
            elif self.dimensions == 1:
                weights = 1. / surface_area_1d(bindist, pos[index])
            elif self.dimensions == 0:
                weights = 1. / surface_area_0d(bindist)
            weights /= self.binwidth

            indices, offsets = nl.get_neighbors(index)
            valid = np.where(num[indices] == unique_type)
            p = pos[indices[valid]] + np.dot(offsets[valid], cell)
            r = cdist(p, [pos[index]])
            bins = np.floor(r / self.binwidth)

            for i in range(-m, m + 1):
                newbins = bins + i
                valid = np.where((newbins >= 0) & (newbins < nbins))
                valid_bins = newbins[valid].astype(int)
                values = weights[valid_bins]

                c = 0.25 * np.sqrt(2) * self.binwidth * 1. / self.sigma
                values *= 0.5 * erf(c * (2 * i + 1)) - \
                    0.5 * erf(c * (2 * i - 1))
                values /= smearing_norm

                for j, valid_bin in enumerate(valid_bins):
                    rdf[valid_bin] += values[j]

            rdf /= len(typedic[unique_type]) * 1. / volume
            return rdf

        fingerprints = {}
        if individual:
            for i in range(len(atoms)):
                fingerprints[i] = {}
                for unique_type in unique_types:
                    fingerprint = take_individual_rdf(i, unique_type)
                    if self.dimensions > 0:
                        fingerprint -= 1
                    fingerprints[i][unique_type] = fingerprint
        else:
            for t1, t2 in combinations_with_replacement(unique_types, r=2):
                key = (t1, t2)
                fingerprint = np.zeros(nbins)
                for i in typedic[t1]:
                    fingerprint += take_individual_rdf(i, t2)
                fingerprint /= len(typedic[t1])
                if self.dimensions > 0:
                    fingerprint -= 1
                fingerprints[key] = fingerprint

        return [fingerprints, typedic]
Exemple #14
0
class LennardJones(Calculator):
    """Lennard Jones potential calculator

    see https://en.wikipedia.org/wiki/Lennard-Jones_potential

    The fundamental definition of this potential is a pairwise energy:

    ``u_ij = 4 epsilon ( sigma^12/r_ij^12 - sigma^6/r_ij^6 )``

    For convenience, we'll use d_ij to refer to "distance vector" and
    ``r_ij`` to refer to "scalar distance". So, with position vectors `r_i`:

    ``r_ij = | r_j - r_i | = | d_ij |``

    The derivative of u_ij is:

    ::

        d u_ij / d r_ij
        = (-24 epsilon / r_ij) ( sigma^12/r_ij^12 - sigma^6/r_ij^6 )

    We can define a pairwise force

    ``f_ij = d u_ij / d d_ij = d u_ij / d r_ij * d_ij / r_ij``

    The terms in front of d_ij are often combined into a "general derivative"

    ``du_ij = (d u_ij / d d_ij) / r_ij``

    The force on an atom is:

    ``f_i = sum_(j != i) f_ij``

    There is some freedom of choice in assigning atomic energies, i.e.
    choosing a way to partition the total energy into atomic contributions.

    We choose a symmetric approach:

    ``u_i = 1/2 sum_(j != i) u_ij``

    The total energy of a system of atoms is then:

    ``u = sum_i u_i = 1/2 sum_(i, j != i) u_ij``

    The stress can be written as ( `(x)` denoting outer product):

    ``sigma = 1/2 sum_(i, j != i) f_ij (x) d_ij = sum_i sigma_i ,``
    with atomic contributions

    ``sigma_i  = 1/2 sum_(j != i) f_ij (x) d_ij``

    Implementation note:

    For computational efficiency, we minimise the number of
    pairwise evaluations, so we iterate once over all the atoms,
    and use NeighbourList with bothways=False. In terms of the
    equations, we therefore effectively restrict the sum over `i != j`
    to `j > i`, and need to manually re-add the "missing" `j < i` contributions.

    Another consideration is the cutoff. We have to ensure that the potential
    goes to zero smoothly as an atom moves across the cutoff threshold,
    otherwise the potential is not continuous. In cases where the cutoff is
    so large that u_ij is very small at the cutoff this is automatically
    ensured, but in general, `u_ij(rc) != 0`.

    In order to catch this case, this implementation shifts the total energy

    ``u'_ij = u_ij - u_ij(rc)``

    which ensures that it is precisely zero at the cutoff.
    However, this means that the energy effectively depends
    on the cutoff, which might lead to unexpected results!

    """

    implemented_properties = [
        'energy', 'atomic_energies', 'forces', 'free_energy'
    ]
    implemented_properties += ['stress', 'stresses']  # bulk properties
    default_parameters = {'epsilon': 1.0, 'sigma': 1.0, 'rc': None}
    nolabel = True

    def __init__(self, **kwargs):
        """
        Parameters
        ----------
        sigma: float
          The potential minimum is at  2**(1/6) * sigma, default 1.0
        epsilon: float
          The potential depth, default 1.0
        rc: float, None
          Cut-off for the NeighborList is set to 3 * sigma if None.
          The energy is upshifted to be continuous at rc.
          Default None
        """

        Calculator.__init__(self, **kwargs)

        if self.parameters.rc is None:
            self.parameters.rc = 3 * self.parameters.sigma

        self.nl = None

    def calculate(
        self,
        atoms=None,
        properties=None,
        system_changes=all_changes,
    ):
        if properties is None:
            properties = self.implemented_properties

        Calculator.calculate(self, atoms, properties, system_changes)

        natoms = len(self.atoms)

        sigma = self.parameters.sigma
        epsilon = self.parameters.epsilon
        rc = self.parameters.rc

        if self.nl is None or 'numbers' in system_changes:
            self.nl = NeighborList([rc / 2] * natoms, self_interaction=False)

        self.nl.update(self.atoms)

        positions = self.atoms.positions
        cell = self.atoms.cell

        # potential value at rc
        e0 = 4 * epsilon * ((sigma / rc)**12 - (sigma / rc)**6)

        atomic_energies = np.zeros(natoms)
        forces = np.zeros((natoms, 3))
        stresses = np.zeros((natoms, 3, 3))

        for ii in range(natoms):
            neighbors, offsets = self.nl.get_neighbors(ii)
            cells = np.dot(offsets, cell)

            # pointing *towards* neighbours
            distance_vectors = positions[neighbors] + cells - positions[ii]

            r2 = (distance_vectors**2).sum(1)
            c6 = (sigma**2 / r2)**3
            c6[r2 > rc**2] = 0.0
            c12 = c6**2

            pairwise_energies = 4 * epsilon * (c12 - c6) - e0 * (c6 != 0.0)
            atomic_energies[ii] += 0.5 * pairwise_energies.sum(
            )  # atomic energies

            pairwise_forces = (-24 * epsilon * (2 * c12 - c6) /
                               r2)[:, np.newaxis] * distance_vectors

            forces[ii] += pairwise_forces.sum(axis=0)
            stresses[ii] += 0.5 * np.dot(
                pairwise_forces.T,
                distance_vectors)  # equivalent to outer product

            # add j < i contributions
            for jj, atom_j in enumerate(neighbors):
                atomic_energies[atom_j] += 0.5 * pairwise_energies[jj]
                forces[atom_j] += -pairwise_forces[jj]  # f_ji = - f_ij
                stresses[atom_j] += 0.5 * np.outer(pairwise_forces[jj],
                                                   distance_vectors[jj])

        # no lattice, no stress
        if self.atoms.number_of_lattice_vectors == 3:
            stresses = full_3x3_to_voigt_6_stress(stresses)
            self.results['stress'] = (stresses.sum(axis=0) /
                                      self.atoms.get_volume())
            self.results['stresses'] = stresses / self.atoms.get_volume()

        energy = atomic_energies.sum()
        self.results['energy'] = energy
        self.results['atomic_energies'] = atomic_energies

        self.results['free_energy'] = energy

        self.results['forces'] = forces
Exemple #15
0
    def initialize(self, atoms):
        self.par = {}
        self.rc = 0.0
        self.numbers = atoms.get_atomic_numbers()
        if self.parameters.asap_cutoff:
            relevant_pars = {}
            for symb, p in parameters.items():
                if atomic_numbers[symb] in self.numbers:
                    relevant_pars[symb] = p
        else:
            relevant_pars = parameters
        maxseq = max(par[1] for par in relevant_pars.values()) * Bohr
        rc = self.rc = beta * maxseq * 0.5 * (sqrt(3) + sqrt(4))
        rr = rc * 2 * sqrt(4) / (sqrt(3) + sqrt(4))
        self.acut = np.log(9999.0) / (rr - rc)
        if self.parameters.asap_cutoff:
            self.rc_list = self.rc * 1.045
        else:
            self.rc_list = self.rc + 0.5
        for Z in self.numbers:
            if Z not in self.par:
                sym = chemical_symbols[Z]
                if sym not in parameters:
                    raise NotImplementedError(
                        'No EMT-potential for {0}'.format(sym))
                p = parameters[sym]
                s0 = p[1] * Bohr
                eta2 = p[3] / Bohr
                kappa = p[4] / Bohr
                x = eta2 * beta * s0
                gamma1 = 0.0
                gamma2 = 0.0
                for i, n in enumerate([12, 6, 24]):
                    r = s0 * beta * sqrt(i + 1)
                    x = n / (12 * (1.0 + exp(self.acut * (r - rc))))
                    gamma1 += x * exp(-eta2 * (r - beta * s0))
                    gamma2 += x * exp(-kappa / beta * (r - beta * s0))

                self.par[Z] = {
                    'E0': p[0],
                    's0': s0,
                    'V0': p[2],
                    'eta2': eta2,
                    'kappa': kappa,
                    'lambda': p[5] / Bohr,
                    'n0': p[6] / Bohr**3,
                    'rc': rc,
                    'gamma1': gamma1,
                    'gamma2': gamma2
                }

        self.ksi = {}
        for s1, p1 in self.par.items():
            self.ksi[s1] = {}
            for s2, p2 in self.par.items():
                self.ksi[s1][s2] = p2['n0'] / p1['n0']

        self.forces = np.empty((len(atoms), 3))
        self.stress = np.empty((3, 3))
        self.sigma1 = np.empty(len(atoms))
        self.deds = np.empty(len(atoms))

        self.nl = NeighborList([0.5 * self.rc_list] * len(atoms),
                               self_interaction=False)
Exemple #16
0
def insertHbyList(ase_struct,
                  pmd_top,
                  implicitHbondingPartners,
                  bond_length=1.0,
                  debug=False):
    # make copies of passed structures as not to alter originals:
    new_pmd_top = pmd_top.copy(pmd.Structure)
    new_ase_struct = ase_struct.copy()

    # names stores the String IDs of all atoms as to facilitate later ASE ID -> Atom name mapping
    names = [a.name for a in pmd_top.atoms]
    residues = [a.residue.name for a in pmd_top.atoms]

    # make copied atoms accessible by unchangable indices (standard list)
    originalAtoms = [a for a in new_pmd_top.atoms]

    implicitHbondingPartnersIdxHnoTuples = [
        (a.idx, implicitHbondingPartners[k]) for a in pmd_top.atoms
        for k in implicitHbondingPartners.keys() if a.name == k
    ]
    implicitHbondingPartnersIdxHnoDict = dict(
        implicitHbondingPartnersIdxHnoTuples)

    # build numbered neighbour list "manually"
    #i: list of atoms e.g. [0,0,0,1,1,2,2,...]
    i = np.array([b.atom1.idx for b in new_pmd_top.bonds])
    #j: list of bonding partners corresponding to i [1,2,3,...] ==> 0 has bondpartners 1,2 and 3; etc.
    j = np.array([b.atom2.idx for b in new_pmd_top.bonds])
    r = new_ase_struct.positions

    for k, Hno in implicitHbondingPartnersIdxHnoDict.items(
    ):  # for all atoms to append hydrogen to
        logging.info('Adding {} H-atoms to {} (#{})...'.format(
            Hno, originalAtoms[k].name, k))
        for h in range(0, Hno):
            r = new_ase_struct.positions
            bondingPartners = j[i == k]
            logging.info('bondingPartners {}'.format(bondingPartners))
            partnerStr = ''
            for p in bondingPartners:
                if partnerStr == '':
                    partnerStr = originalAtoms[p].name
                else:
                    partnerStr += ', ' + originalAtoms[p].name
            logging.info('Atom {} already has bonding partners {}'.format(
                originalAtoms[k].name, partnerStr))
            dr = (r[j[i == k]] - r[k]).mean(axis=0)
            # my understanding: dr is vector
            # from atom k's position towards the geometrical center of mass
            # it forms with its defined neighbours
            # r0 is a vector offset into the opposit direction:
            dr = dr / np.linalg.norm(dr)  #normalized vector in direction dr

            #calculate an orthogonal vector 'dr_ortho' on dr
            #and push the H atoms in dr+dr_ortho and dr-dr_ortho
            #if one has to add more than two H atoms introduce dr_ortho_2 = dr x dr_ortho
            dr_ortho = np.cross(dr, np.array([1, 0, 0]))
            if np.linalg.norm(
                    dr_ortho
            ) < 0.1:  #if dr and (1,0,0) have almost the same direction
                dr_ortho = np.cross(dr, np.array([0, 1, 0]))
            # (1-2*h) = {1,-1} for h={0,1}
            h_pos_vec = (dr + (1 - 2 * h) * dr_ortho
                         ) / np.linalg.norm(dr + (1 - 2 * h) * dr_ortho)
            r0 = r[k] - bond_length * h_pos_vec

            new_ase_struct += ase.Atom('H', r0)  # add atom in ase structure
            n_atoms = len(new_ase_struct)

            #introduce a corrector step for a added atom which is too close to others
            #do as many corrector stepps until all atoms are more than 1\AA appart
            c_step = 0
            while True:
                nl = NeighborList(cutoffs=[.5] * len(new_ase_struct),
                                  skin=0.09,
                                  self_interaction=False,
                                  bothways=True)
                nl.update(new_ase_struct)
                indices, offsets = nl.get_neighbors(-1)
                indices = np.delete(indices, np.where(indices == k))
                if len(indices) == 0:
                    break

                elif c_step > 15:
                    logging.info(
                        'programm needs more than 15 corrector steps for H atom {} at atom {}'
                        .format(n_atoms, k))
                    sys.exit(15)
                    break

                logging.info('too close atoms {}'.format(indices))
                c_step += 1
                logging.info('correcter step {} for H {} at atom {}'.format(
                    c_step, n_atoms - 1, k))
                # if indices not empty -> the atom(-1)=r_H is to close together
                # with atom a_close=indices[0], it is a H-atom belonging to atom 'k'=r_k .
                #correctorstep: corr_step = (r_H-a_close)/|(r_H-a_close)|
                #corrected_pos: corr_pos = ((r_H-r_k) + corr_step)/|((r_H-r_k) + corr_step)|
                #new H position: new_r_H = r_k + corr_pos
                r_H, r_k, a_close = np.take(new_ase_struct.get_positions(),
                                            [-1, k, indices[0]],
                                            axis=0)
                #print('r_H, r_k, a_close', r_H, r_k, a_close)
                corr_step = (r_H - a_close) / np.linalg.norm(
                    (r_H - a_close)
                )  #maybe introduce here a skaling Faktor s=0.3 or somthing like that to make tiny corrections and don't overshoot.
                corr_pos = ((r_H - r_k) + corr_step) / np.linalg.norm(
                    (r_H - r_k) + corr_step)
                new_r_H = r_k + bond_length * corr_pos
                #correct the H position to new_r_H in new_ase_struct
                trans = np.zeros([n_atoms, 3])
                trans[-1] = new_r_H - r_H
                new_ase_struct.translate(trans)

            #view(new_ase_struct)
            #sys.exit()

            i = np.append(i, k)  # manually update numbered neighbour lists
            j = np.append(j, len(new_ase_struct) - 1)

            # update pmd topology
            bondingPartner = originalAtoms[
                k]  # here we need the original numbering,
            # as ParmEd alters indices when adding atoms to the structure
            nameH = '{}{}'.format(
                h + 1, bondingPartner.name)  # atom needs a unique name
            logging.info('Adding H-atom {} at position [ {}, {}, {} ]'.format(
                nameH, r0[0], r0[1], r0[2]))
            new_H = pmd.Atom(name=nameH, type='H', atomic_number=1)
            new_H.xx = r0[
                0]  # ParmEd documentation not very helpful, did not find any more compact assignment
            new_H.xy = r0[1]
            new_H.xz = r0[2]
            # do not understand ParmEd that well, apparently we need the Bond object in order to update topology
            new_Bond = pmd.Bond(bondingPartner, new_H)
            new_H.bond_to(
                bondingPartner)  # not sure, whether this is necessary
            new_pmd_top.bonds.append(new_Bond)
            new_pmd_top.add_atom_to_residue(new_H, bondingPartner.residue)
            originalAtoms.append(
                new_H)  # add atom to the bottom of "index-stiff" list

            names.append(nameH)  # append name of H-atom
            residues.append(bondingPartner.residue.name
                            )  # H is in same residue as bonding partner
    return new_ase_struct, new_pmd_top, names, residues
Exemple #17
0
class OPLSff:
    def __init__(self, fileobj=None, warnings=0):
        self.warnings = warnings
        self.data = {}
        if fileobj is not None:
            self.read(fileobj)

    def read(self, fileobj, comments='#'):
        if isinstance(fileobj, str):
            fileobj = open(fileobj)

        def read_block(name, symlen, nvalues):
            """Read a data block.

            name: name of the block to store in self.data
            symlen: length of the symbol
            nvalues: number of values expected
            """

            if name not in self.data:
                self.data[name] = {}
            data = self.data[name]

            def add_line():
                line = fileobj.readline().strip()
                if not len(line):  # end of the block
                    return False
                line = line.split('#')[0]  # get rid of comments
                if len(line) > symlen:
                    symbol = line[:symlen]
                    words = line[symlen:].split()
                    if len(words) >= nvalues:
                        if nvalues == 1:
                            data[symbol] = float(words[0])
                        else:
                            data[symbol] = [float(word)
                                            for word in words[:nvalues]]
                return True

            while add_line():
                pass

        read_block('one', 2, 3)
        read_block('bonds', 5, 2)
        read_block('angles', 8, 2)
        read_block('dihedrals', 11, 4)
        read_block('cutoffs', 5, 1)

        self.bonds = BondData(self.data['bonds'])
        self.angles = AnglesData(self.data['angles'])
        self.dihedrals = DihedralsData(self.data['dihedrals'])
        self.cutoffs = CutoffList(self.data['cutoffs'])

    def write_lammps(self, atoms, prefix='lammps'):
        """Write input for a LAMMPS calculation."""
        self.prefix = prefix

        if hasattr(atoms, 'connectivities'):
            connectivities = atoms.connectivities
        else:
            btypes, blist = self.get_bonds(atoms)
            atypes, alist = self.get_angles()
            dtypes, dlist = self.get_dihedrals(alist, atypes)
            connectivities = {
                'bonds': blist,
                'bond types': btypes,
                'angles': alist,
                'angle types': atypes,
                'dihedrals': dlist,
                'dihedral types': dtypes,
                }
            self.write_lammps_definitions(atoms, btypes, atypes, dtypes)
            self.write_lammps_in()

        self.write_lammps_atoms(atoms, connectivities)

    def write_lammps_in(self):
        fileobj = self.prefix + '_in'
        if isinstance(fileobj, str):
            fileobj = open(fileobj, 'w')
        fileobj.write("""# LAMMPS relaxation (written by ASE)

units           metal
atom_style      full
boundary        p p p
#boundary       p p f

""")
        fileobj.write('read_data ' + self.prefix + '_atoms\n')
        fileobj.write('include  ' + self.prefix + '_opls\n')
        fileobj.write("""
kspace_style    pppm 1e-5
#kspace_modify  slab 3.0

neighbor        1.0 bin
neigh_modify    delay 0 every 1 check yes

thermo          1000
thermo_style    custom step temp press cpu pxx pyy pzz pxy pxz pyz ke pe etotal vol lx ly lz atoms

dump            1 all xyz 1000 dump_relax.xyz
dump_modify     1 sort id

restart         100000 test_relax

min_style       fire
minimize        1.0e-14 1.0e-5 100000 100000
""")
        fileobj.close()

    def write_lammps_atoms(self, atoms, connectivities):
        """Write atoms input for LAMMPS"""

        fname = self.prefix + '_atoms'
        fileobj = open(fname, 'w')

        # header
        fileobj.write(fileobj.name + ' (by ' + str(self.__class__) + ')\n\n')
        fileobj.write(str(len(atoms)) + ' atoms\n')
        fileobj.write(str(len(atoms.types)) + ' atom types\n')
        blist = connectivities['bonds']
        if len(blist):
            btypes = connectivities['bond types']
            fileobj.write(str(len(blist)) + ' bonds\n')
            fileobj.write(str(len(btypes)) + ' bond types\n')
        alist = connectivities['angles']
        if len(alist):
            atypes = connectivities['angle types']
            fileobj.write(str(len(alist)) + ' angles\n')
            fileobj.write(str(len(atypes)) + ' angle types\n')
        dlist = connectivities['dihedrals']
        if len(dlist):
            dtypes = connectivities['dihedral types']
            fileobj.write(str(len(dlist)) + ' dihedrals\n')
            fileobj.write(str(len(dtypes)) + ' dihedral types\n')

        # cell
        p = Prism(atoms.get_cell())
        xhi, yhi, zhi, xy, xz, yz = p.get_lammps_prism_str()
        fileobj.write('\n0.0 %s  xlo xhi\n' % xhi)
        fileobj.write('0.0 %s  ylo yhi\n' % yhi)
        fileobj.write('0.0 %s  zlo zhi\n' % zhi)

        # atoms
        fileobj.write('\nAtoms\n\n')
        tag = atoms.get_tags()
        if atoms.has('molid'):
            molid = atoms.get_array('molid')
        else:
            molid = [1] * len(atoms)
        for i, r in enumerate(map(p.pos_to_lammps_str,
                                  atoms.get_positions())):
            q = self.data['one'][atoms.types[tag[i]]][2]
            fileobj.write('%6d %3d %3d %s %s %s %s' % ((i + 1, molid[i],
                                                        tag[i] + 1,
                                                        q)
                                                       + tuple(r)))
            fileobj.write(' # ' + atoms.types[tag[i]] + '\n')

        # velocities
        velocities = atoms.get_velocities()
        if velocities is not None:
            fileobj.write('\nVelocities\n\n')
            for i, v in enumerate(velocities):
                fileobj.write('%6d %g %g %g\n' %
                              (i + 1, v[0], v[1], v[2]))

        # masses
        fileobj.write('\nMasses\n\n')
        for i, typ in enumerate(atoms.types):
            cs = atoms.split_symbol(typ)[0]
            fileobj.write('%6d %g # %s -> %s\n' %
                          (i + 1,
                           atomic_masses[chemical_symbols.index(cs)],
                           typ, cs))

        # bonds
        if len(blist):
            fileobj.write('\nBonds\n\n')
            for ib, bvals in enumerate(blist):
                fileobj.write('%8d %6d %6d %6d ' %
                              (ib + 1, bvals[0] + 1, bvals[1] + 1,
                               bvals[2] + 1))
                try:
                    fileobj.write('# ' + btypes[bvals[0]])
                except:
                    pass
                fileobj.write('\n')

        # angles
        if len(alist):
            fileobj.write('\nAngles\n\n')
            for ia, avals in enumerate(alist):
                fileobj.write('%8d %6d %6d %6d %6d ' %
                              (ia + 1, avals[0] + 1,
                               avals[1] + 1, avals[2] + 1, avals[3] + 1))
                try:
                    fileobj.write('# ' + atypes[avals[0]])
                except:
                    pass
                fileobj.write('\n')

        # dihedrals
        if len(dlist):
            fileobj.write('\nDihedrals\n\n')
            for i, dvals in enumerate(dlist):
                fileobj.write('%8d %6d %6d %6d %6d %6d ' %
                              (i + 1, dvals[0] + 1,
                               dvals[1] + 1, dvals[2] + 1,
                               dvals[3] + 1, dvals[4] + 1))
                try:
                    fileobj.write('# ' + dtypes[dvals[0]])
                except:
                    pass
                fileobj.write('\n')

    def update_neighbor_list(self, atoms):
        cut = 0.5 * max(self.data['cutoffs'].values())
        self.nl = NeighborList([cut] * len(atoms), skin=0,
                               bothways=True, self_interaction=False)
        self.nl.update(atoms)
        self.atoms = atoms

    def get_bonds(self, atoms):
        """Find bonds and return them and their types"""
        cutoffs = CutoffList(self.data['cutoffs'])
        self.update_neighbor_list(atoms)

        types = atoms.get_types()
        tags = atoms.get_tags()
        cell = atoms.get_cell()
        bond_list = []
        bond_types = []
        for i, atom in enumerate(atoms):
            iname = types[tags[i]]
            indices, offsets = self.nl.get_neighbors(i)
            for j, offset in zip(indices, offsets):
                if j <= i:
                    continue  # do not double count
                jname = types[tags[j]]
                cut = cutoffs.value(iname, jname)
                if cut is None:
                    if self.warnings > 1:
                        print('Warning: cutoff %s-%s not found'
                              % (iname, jname))
                    continue  # don't have it
                dist = np.linalg.norm(atom.position - atoms[j].position
                                      - np.dot(offset, cell))
                if dist > cut:
                    continue  # too far away
                name, val = self.bonds.name_value(iname, jname)
                if name is None:
                    if self.warnings:
                        print('Warning: potential %s-%s not found'
                              % (iname, jname))
                    continue  # don't have it
                if name not in bond_types:
                    bond_types.append(name)
                bond_list.append([bond_types.index(name), i, j])
        return bond_types, bond_list

    def get_angles(self, atoms=None):
        cutoffs = CutoffList(self.data['cutoffs'])
        if atoms is not None:
            self.update_neighbor_list(atoms)
        else:
            atoms = self.atoms

        types = atoms.get_types()
        tags = atoms.get_tags()
        cell = atoms.get_cell()
        ang_list = []
        ang_types = []

        # center atom *-i-*
        for i, atom in enumerate(atoms):
            iname = types[tags[i]]
            indicesi, offsetsi = self.nl.get_neighbors(i)

            # search for first neighbor j-i-*
            for j, offsetj in zip(indicesi, offsetsi):
                jname = types[tags[j]]
                cut = cutoffs.value(iname, jname)
                if cut is None:
                    continue  # don't have it
                dist = np.linalg.norm(atom.position - atoms[j].position
                                      - np.dot(offsetj, cell))
                if dist > cut:
                    continue  # too far away

                # search for second neighbor j-i-k
                for k, offsetk in zip(indicesi, offsetsi):
                    if k <= j:
                        continue  # avoid double count
                    kname = types[tags[k]]
                    cut = cutoffs.value(iname, kname)
                    if cut is None:
                        continue  # don't have it
                    dist = np.linalg.norm(atom.position -
                                          np.dot(offsetk, cell) -
                                          atoms[k].position)
                    if dist > cut:
                        continue  # too far away
                    name, val = self.angles.name_value(jname, iname,
                                                       kname)
                    if name is None:
                        if self.warnings > 1:
                            print('Warning: angles %s-%s-%s not found'
                                  % (jname, iname, kname))
                        continue  # don't have it
                    if name not in ang_types:
                        ang_types.append(name)
                    ang_list.append([ang_types.index(name), j, i, k])

        return ang_types, ang_list

    def get_dihedrals(self, ang_types, ang_list):
        'Dihedrals derived from angles.'

        cutoffs = CutoffList(self.data['cutoffs'])

        atoms = self.atoms
        types = atoms.get_types()
        tags = atoms.get_tags()
        cell = atoms.get_cell()

        dih_list = []
        dih_types = []

        def append(name, i, j, k, l):
            if name not in dih_types:
                dih_types.append(name)
            index = dih_types.index(name)
            if (([index, i, j, k, l] not in dih_list) and
                ([index, l, k, j, i] not in dih_list)):
                dih_list.append([index, i, j, k, l])

        for angle in ang_types:
            l, i, j, k = angle
            iname = types[tags[i]]
            jname = types[tags[j]]
            kname = types[tags[k]]

            # search for l-i-j-k
            indicesi, offsetsi = self.nl.get_neighbors(i)
            for l, offsetl in zip(indicesi, offsetsi):
                if l == j:
                    continue # avoid double count
                lname = types[tags[l]]
                cut = cutoffs.value(iname, lname)
                if cut is None:
                    continue # don't have it
                dist = np.linalg.norm(atoms[i].position - atoms[l].position
                                      - np.dot(offsetl, cell))
                if dist > cut:
                    continue # too far away
                name, val = self.dihedrals.name_value(lname, iname,
                                                      jname, kname)
                if name is None:
                    continue # don't have it
                append(name, l, i, j, k)

            # search for i-j-k-l
            indicesk, offsetsk = self.nl.get_neighbors(k)
            for l, offsetl in zip(indicesk, offsetsk):
                if l == j:
                    continue # avoid double count
                lname = types[tags[l]]
                cut = cutoffs.value(kname, lname)
                if cut is None:
                    continue # don't have it
                dist = np.linalg.norm(atoms[k].position - atoms[l].position
                                      - np.dot(offsetl, cell))
                if dist > cut:
                    continue # too far away
                name, val = self.dihedrals.name_value(iname, jname,
                                                      kname, lname)
                if name is None:
                    continue # don't have it
                append(name, i, j, k, l)

        return dih_types, dih_list

    def write_lammps_definitions(self, atoms, btypes, atypes, dtypes):
        """Write force field definitions for LAMMPS."""

        fileobj = self.prefix + '_opls'
        if isinstance(fileobj, str):
            fileobj = open(fileobj, 'w')

        fileobj.write('# OPLS potential\n')
        fileobj.write('# write_lammps' +
                      str(time.asctime(
                    time.localtime(time.time()))))

        # bonds
        if len(btypes):
            fileobj.write('\n# bonds\n')
            fileobj.write('bond_style      harmonic\n')
            for ib, btype in enumerate(btypes):
                fileobj.write('bond_coeff %6d' % (ib + 1))
                for value in self.bonds.nvh[btype]:
                    fileobj.write(' ' + str(value))
                fileobj.write(' # ' + btype + '\n')

        # angles
        if len(atypes):
            fileobj.write('\n# angles\n')
            fileobj.write('angle_style      harmonic\n')
            for ia, atype in enumerate(atypes):
                fileobj.write('angle_coeff %6d' % (ia + 1))
                for value in self.angles.nvh[atype]:
                    fileobj.write(' ' + str(value))
                fileobj.write(' # ' + atype + '\n')

        # dihedrals
        if len(dtypes):
            fileobj.write('\n# dihedrals\n')
            fileobj.write('dihedral_style      opls\n')
            for i, dtype in enumerate(dtypes):
                fileobj.write('dihedral_coeff %6d' % (i + 1))
                for value in self.dihedrals.nvh[dtype]:
                    fileobj.write(' ' + str(value))
                fileobj.write(' # ' + dtype + '\n')

        # Lennard Jones settings
        fileobj.write('\n# L-J parameters\n')
        fileobj.write('pair_style lj/cut/coul/long 10.0 7.4' +
                      ' # consider changing these parameters\n')
        fileobj.write('special_bonds lj/coul 0.0 0.0 0.5\n')
        data = self.data['one']
        for ia, atype in enumerate(atoms.types):
            if len(atype) < 2:
                atype = atype + ' '
            fileobj.write('pair_coeff ' + str(ia + 1) + ' ' + str(ia + 1))
            for value in data[atype][:2]:
                fileobj.write(' ' + str(value))
            fileobj.write(' # ' + atype + '\n')
        fileobj.write('pair_modify shift yes mix geometric\n')

        # Charges
        fileobj.write('\n# charges\n')
        for ia, atype in enumerate(atoms.types):
            if len(atype) < 2:
                atype = atype + ' '
            fileobj.write('set type ' + str(ia + 1))
            fileobj.write(' charge ' + str(data[atype][2]))
            fileobj.write(' # ' + atype + '\n')
Exemple #18
0
class EMT(Calculator):
    """Python implementation of the Effective Medium Potential.

    Supports the following standard EMT metals:
    Al, Cu, Ag, Au, Ni, Pd and Pt.

    In addition, the following elements are supported.
    They are NOT well described by EMT, and the parameters
    are not for any serious use:
    H, C, N, O

    The potential takes a single argument, ``asap_cutoff``
    (default: False).  If set to True, the cutoff mimics
    how Asap does it; most importantly the global cutoff
    is chosen from the largest atom present in the simulation,
    if False it is chosen from the largest atom in the parameter
    table.  True gives the behaviour of the Asap code and
    older EMT implementations, although the results are not
    bitwise identical.
    """
    implemented_properties = ['energy', 'forces']

    nolabel = True

    default_parameters = {'asap_cutoff': False}

    def __init__(self, **kwargs):
        Calculator.__init__(self, **kwargs)

    def initialize(self, atoms):
        self.par = {}
        self.rc = 0.0
        self.numbers = atoms.get_atomic_numbers()
        if self.parameters.asap_cutoff:
            relevant_pars = {}
            for symb, p in parameters.items():
                if atomic_numbers[symb] in self.numbers:
                    relevant_pars[symb] = p
        else:
            relevant_pars = parameters
        maxseq = max(par[1] for par in relevant_pars.values()) * Bohr
        rc = self.rc = beta * maxseq * 0.5 * (sqrt(3) + sqrt(4))
        rr = rc * 2 * sqrt(4) / (sqrt(3) + sqrt(4))
        self.acut = np.log(9999.0) / (rr - rc)
        if self.parameters.asap_cutoff:
            self.rc_list = self.rc * 1.045
        else:
            self.rc_list = self.rc + 0.5
        for Z in self.numbers:
            if Z not in self.par:
                sym = chemical_symbols[Z]
                if sym not in parameters:
                    raise NotImplementedError('No EMT-potential for {0}'
                                              .format(sym))
                p = parameters[sym]
                s0 = p[1] * Bohr
                eta2 = p[3] / Bohr
                kappa = p[4] / Bohr
                x = eta2 * beta * s0
                gamma1 = 0.0
                gamma2 = 0.0
                for i, n in enumerate([12, 6, 24]):
                    r = s0 * beta * sqrt(i + 1)
                    x = n / (12 * (1.0 + exp(self.acut * (r - rc))))
                    gamma1 += x * exp(-eta2 * (r - beta * s0))
                    gamma2 += x * exp(-kappa / beta * (r - beta * s0))

                self.par[Z] = {'E0': p[0],
                               's0': s0,
                               'V0': p[2],
                               'eta2': eta2,
                               'kappa': kappa,
                               'lambda': p[5] / Bohr,
                               'n0': p[6] / Bohr**3,
                               'rc': rc,
                               'gamma1': gamma1,
                               'gamma2': gamma2}

        self.ksi = {}
        for s1, p1 in self.par.items():
            self.ksi[s1] = {}
            for s2, p2 in self.par.items():
                self.ksi[s1][s2] = p2['n0'] / p1['n0']

        self.forces = np.empty((len(atoms), 3))
        self.sigma1 = np.empty(len(atoms))
        self.deds = np.empty(len(atoms))

        self.nl = NeighborList([0.5 * self.rc_list] * len(atoms),
                               self_interaction=False)

    def calculate(self, atoms=None, properties=['energy'],
                  system_changes=all_changes):
        Calculator.calculate(self, atoms, properties, system_changes)

        if 'numbers' in system_changes:
            self.initialize(self.atoms)

        positions = self.atoms.positions
        numbers = self.atoms.numbers
        cell = self.atoms.cell

        self.nl.update(self.atoms)

        self.energy = 0.0
        self.sigma1[:] = 0.0
        self.forces[:] = 0.0

        natoms = len(self.atoms)

        for a1 in range(natoms):
            Z1 = numbers[a1]
            p1 = self.par[Z1]
            ksi = self.ksi[Z1]
            neighbors, offsets = self.nl.get_neighbors(a1)
            offsets = np.dot(offsets, cell)
            for a2, offset in zip(neighbors, offsets):
                d = positions[a2] + offset - positions[a1]
                r = sqrt(np.dot(d, d))
                if r < self.rc_list:
                    Z2 = numbers[a2]
                    p2 = self.par[Z2]
                    self.interact1(a1, a2, d, r, p1, p2, ksi[Z2])

        for a in range(natoms):
            Z = numbers[a]
            p = self.par[Z]
            try:
                ds = -log(self.sigma1[a] / 12) / (beta * p['eta2'])
            except (OverflowError, ValueError):
                self.deds[a] = 0.0
                self.energy -= p['E0']
                continue
            x = p['lambda'] * ds
            y = exp(-x)
            z = 6 * p['V0'] * exp(-p['kappa'] * ds)
            self.deds[a] = ((x * y * p['E0'] * p['lambda'] + p['kappa'] * z) /
                            (self.sigma1[a] * beta * p['eta2']))
            self.energy += p['E0'] * ((1 + x) * y - 1) + z

        for a1 in range(natoms):
            Z1 = numbers[a1]
            p1 = self.par[Z1]
            ksi = self.ksi[Z1]
            neighbors, offsets = self.nl.get_neighbors(a1)
            offsets = np.dot(offsets, cell)
            for a2, offset in zip(neighbors, offsets):
                d = positions[a2] + offset - positions[a1]
                r = sqrt(np.dot(d, d))
                if r < self.rc_list:
                    Z2 = numbers[a2]
                    p2 = self.par[Z2]
                    self.interact2(a1, a2, d, r, p1, p2, ksi[Z2])

        self.results['energy'] = self.energy
        self.results['free_energy'] = self.energy
        self.results['forces'] = self.forces

    def interact1(self, a1, a2, d, r, p1, p2, ksi):
        x = exp(self.acut * (r - self.rc))
        theta = 1.0 / (1.0 + x)
        y1 = (0.5 * p1['V0'] * exp(-p2['kappa'] * (r / beta - p2['s0'])) *
              ksi / p1['gamma2'] * theta)
        y2 = (0.5 * p2['V0'] * exp(-p1['kappa'] * (r / beta - p1['s0'])) /
              ksi / p2['gamma2'] * theta)
        self.energy -= y1 + y2
        f = ((y1 * p2['kappa'] + y2 * p1['kappa']) / beta +
             (y1 + y2) * self.acut * theta * x) * d / r
        self.forces[a1] += f
        self.forces[a2] -= f
        self.sigma1[a1] += (exp(-p2['eta2'] * (r - beta * p2['s0'])) *
                            ksi * theta / p1['gamma1'])
        self.sigma1[a2] += (exp(-p1['eta2'] * (r - beta * p1['s0'])) /
                            ksi * theta / p2['gamma1'])

    def interact2(self, a1, a2, d, r, p1, p2, ksi):
        x = exp(self.acut * (r - self.rc))
        theta = 1.0 / (1.0 + x)
        y1 = (exp(-p2['eta2'] * (r - beta * p2['s0'])) *
              ksi / p1['gamma1'] * theta * self.deds[a1])
        y2 = (exp(-p1['eta2'] * (r - beta * p1['s0'])) /
              ksi / p2['gamma1'] * theta * self.deds[a2])
        f = ((y1 * p2['eta2'] + y2 * p1['eta2']) +
             (y1 + y2) * self.acut * theta * x) * d / r
        self.forces[a1] -= f
        self.forces[a2] += f
Exemple #19
0
class TestGeometry(unittest.TestCase):
    """Container for tests to the geometry module."""
    def __init__(self, *args, **kwargs):
        super(TestGeometry, self).__init__(*args, **kwargs)

    def shortDescription(self):
        """Silences unittest from printing the docstrings in test cases."""
        return None

    def setUp(self):
        """Sets up some basic stuff which can be useful in the tests."""
        cutoff = 3.0
        self.structure = bulk('Al')
        self.neighborlist = NeighborList(len(self.structure) * [cutoff / 2],
                                         skin=1e-8,
                                         bothways=True,
                                         self_interaction=False)
        self.neighborlist.update(self.structure)

    def test_get_scaled_positions(self):
        """ Tests the test_get_scaled_positions method. """
        positions = np.array([[6.5, 5.1, 3.0], [-0.1, 1.3, 4.5], [0, 0, 0],
                              [15, 7, 4.5]])
        cell = np.array([[4.0, 1.0, 0.1], [-0.4, 6.7, 0], [-4, 2, 16]])

        retval = get_scaled_positions(positions, cell, wrap=False)
        targetval = np.array([[1.84431247, 0.43339424, 0.17597305],
                              [0.26176336, 0.07149383, 0.27961398], [0, 0, 0],
                              [4.04248515, 0.36500685, 0.25598447]])
        np.testing.assert_almost_equal(retval, targetval)

        retval = get_scaled_positions(positions, cell, wrap=True)
        targetval = np.array([[0.84431247, 0.43339424, 0.17597305],
                              [0.26176336, 0.07149383, 0.27961398], [0, 0, 0],
                              [0.04248515, 0.36500685, 0.25598447]])
        np.testing.assert_almost_equal(retval, targetval)

        retval = get_scaled_positions(positions,
                                      cell,
                                      wrap=True,
                                      pbc=3 * [False])
        targetval = np.array([[1.84431247, 0.43339424, 0.17597305],
                              [0.26176336, 0.07149383, 0.27961398], [0, 0, 0],
                              [4.04248515, 0.36500685, 0.25598447]])
        np.testing.assert_almost_equal(retval, targetval)

        retval = get_scaled_positions(positions,
                                      cell,
                                      wrap=True,
                                      pbc=[True, True, False])
        targetval = np.array([[0.84431247, 0.43339424, 0.17597305],
                              [0.26176336, 0.07149383, 0.27961398], [0, 0, 0],
                              [0.04248515, 0.36500685, 0.25598447]])
        np.testing.assert_almost_equal(retval, targetval)

    def test_get_primitive_structure(self) -> None:
        """ Tests the get_primitive_structure method. """
        def compare_structures(s1: Atoms, s2: Atoms) -> bool:
            if len(s1) != len(s2):
                return False
            if not np.all(np.isclose(s1.cell, s2.cell)):
                return False
            if not np.all(
                    np.isclose(s1.get_scaled_positions(),
                               s2.get_scaled_positions())):
                return False
            if not np.all(
                    s1.get_chemical_symbols() == s2.get_chemical_symbols()):
                return False
            return True

        structure = bulk('Al', crystalstructure='fcc', a=4).repeat(2)
        retval = get_primitive_structure(structure,
                                         no_idealize=True,
                                         to_primitive=True)
        targetval = Atoms('Al',
                          cell=[[0.0, 2.0, 2.0], [2.0, 0.0, 2.0],
                                [2.0, 2.0, 0.0]],
                          pbc=True,
                          positions=[[0, 0, 0]])
        self.assertTrue(compare_structures(retval, targetval))

        structure = bulk('Al', crystalstructure='fcc', a=4).repeat(2)
        noise_level = 1e-3
        structure[0].position += noise_level * np.array([1, -5, 3])
        structure[3].position += noise_level * np.array([-2, 3, -1])
        retval = get_primitive_structure(structure,
                                         no_idealize=True,
                                         to_primitive=True)
        targetval = Atoms(8 * 'Al',
                          cell=[[-4.0, 0.0, -4.0], [-4.0, 4.0, 0.0],
                                [0.0, 4.0, -4.0]],
                          pbc=True,
                          positions=[[-3.999e+00, 7.995e+00, -3.997e+00],
                                     [-2.000e+00, 2.000e+00, -4.000e+00],
                                     [-2.000e+00, 0.000e+00, -2.000e+00],
                                     [-2.000e-03, 2.003e+00, -2.001e+00],
                                     [-4.000e+00, 2.000e+00, -2.000e+00],
                                     [-2.000e+00, 4.000e+00, -2.000e+00],
                                     [-2.000e+00, 2.000e+00, 0.000e+00],
                                     [-4.000e+00, 4.000e+00, -4.000e+00]])
        self.assertTrue(compare_structures(retval, targetval))

        structure = bulk('Al', crystalstructure='fcc', a=4).repeat(2)
        noise_level = 1e-7
        structure[0].position += noise_level * np.array([1, -5, 3])
        structure[3].position += noise_level * np.array([-2, 3, -1])
        retval = get_primitive_structure(structure,
                                         no_idealize=True,
                                         to_primitive=True)
        targetval = Atoms(
            'Al',
            cell=[[0.0, 2.0, 2.0], [2.0, 0.0, 2.0], [2.0, 2.0, 0.0]],
            pbc=True,
            positions=[[-1.24999998e-08, -2.49999997e-08, 2.50000001e-08]])
        self.assertTrue(compare_structures(retval, targetval))

        structure = bulk('SiC', crystalstructure='zincblende', a=4).repeat(
            (2, 2, 1))
        structure.pbc = [True, True, False]
        retval = get_primitive_structure(structure,
                                         no_idealize=True,
                                         to_primitive=True)
        targetval = Atoms('SiC',
                          cell=[[0.0, 2.0, 2.0], [2.0, 0.0, 2.0],
                                [2.0, 2.0, 0.0]],
                          pbc=[True, True, False],
                          positions=[[0, 0, 0], [1, 1, 1]])
        self.assertTrue(compare_structures(retval, targetval))

        structure = bulk('SiC', crystalstructure='zincblende', a=4).repeat(
            (2, 2, 1))
        structure.pbc = [True, True, False]
        retval = get_primitive_structure(structure,
                                         no_idealize=True,
                                         to_primitive=False)
        targetval = Atoms(4 * 'SiC',
                          cell=[4, 4, 4],
                          pbc=[True, True, False],
                          positions=[[0, 0, 0], [1, 1, 1], [0, 2,
                                                            2], [1, 3, 3],
                                     [2, 0, 2], [3, 1, 3], [2, 2, 0],
                                     [3, 3, 1]])
        self.assertTrue(compare_structures(retval, targetval))

        structure = bulk('SiC', crystalstructure='zincblende', a=4)
        structure.cell = [[0.0, 2.0, 2.0], [2.0, 0.0, 2.0],
                          [2.0, 2.0, 0.00001]]
        retval = get_primitive_structure(structure,
                                         no_idealize=False,
                                         to_primitive=True,
                                         symprec=1e-3)
        targetval = Atoms('SiC',
                          cell=[[0.0, 1.9999983333374998, 1.9999983333374998],
                                [1.9999983333374998, 0.0, 1.9999983333374998],
                                [1.9999983333374998, 1.9999983333374998, 0.0]],
                          pbc=[True, True, False],
                          positions=[[0, 0, 0],
                                     [0.99999917, 0.99999917, 0.99999917]])
        self.assertTrue(compare_structures(retval, targetval))

        # Catch failure from spglib due to large symprec
        symprec = 0.3
        prim = bulk('Au', a=1.0, cubic=True)
        prim[0].position += [0, 0, 0.01]
        prim[1].position += [-0.01, 0, 0.01]
        prim[2].position += [0, 0.01, 0.01]
        prim[3].position += [0, 0, -0.01]
        prim.cell[0][0] += 0.002
        with self.assertRaises(ValueError) as cm:
            get_primitive_structure(prim, symprec=symprec)
        self.assertIn('spglib failed to find the primitive cell',
                      str(cm.exception))

    def test_fractional_to_cartesian(self):
        """Tests the geometry function fractional_to_cartesian."""

        # reference data
        structure = bulk('Al')
        frac_pos = np.array([[0.0, 0.0, -0.0], [0.0, 0.0, 1.0],
                             [0.0, 1.0, -1.0], [0.0, 1.0, -0.0],
                             [1.0, -1.0, -0.0], [1.0, 0.0, -1.0],
                             [1.0, 0.0, -0.0], [0.0, 0.0, -1.0],
                             [0.0, -1.0, 1.0], [0.0, -1.0, -0.0],
                             [-1.0, 1.0, -0.0], [-1.0, 0.0, 1.0],
                             [-1.0, 0.0, -0.0]])

        cart_pos_target = [[0., 0., 0.], [2.025, 2.025, 0.],
                           [0., -2.025, 2.025], [2.025, 0., 2.025],
                           [-2.025, 2.025, 0.], [-2.025, 0., 2.025],
                           [0., 2.025, 2.025], [-2.025, -2.025, 0.],
                           [0., 2.025, -2.025], [-2.025, 0., -2.025],
                           [2.025, -2.025, 0.], [2.025, 0., -2.025],
                           [0., -2.025, -2.025]]

        # Transform to cartesian
        cart_pos_predicted = []
        for fractional in frac_pos:
            cart_pos_predicted.append(
                fractional_to_cartesian(structure, fractional))

        # Test if predicted cartesian positions are equal to target
        for target, predicted in zip(cart_pos_target, cart_pos_predicted):
            np.testing.assert_almost_equal(target, predicted)

    def test_get_permutation(self):
        """Tests the get_permutation function."""
        value = ['a', 'b', 'c']
        target = ['a', 'b', 'c']
        permutation = [0, 1, 2]
        self.assertEqual(target, get_permutation(value, permutation))

        value = ['a', 'b', 'c']
        target = ['a', 'c', 'b']
        permutation = [0, 2, 1]
        self.assertEqual(target, get_permutation(value, permutation))

        value = [0, 3, 'c']
        target = [3, 'c', 0]
        permutation = [1, 2, 0]
        self.assertEqual(target, get_permutation(value, permutation))

        # Error on permutation list too short
        with self.assertRaises(Exception):
            get_permutation(value, [0, 1])

        # Error on permutation list not unique values
        with self.assertRaises(Exception):
            get_permutation(value, [0, 1, 1])

        # Error on permutation list going out of range
        with self.assertRaises(IndexError):
            get_permutation(value, [0, 1, 3])

    def test_ase_atoms_to_spglib_cell(self):
        """
        Tests that function returns the right tuple from the provided ASE
        Atoms object.
        """
        structure = bulk('Al').repeat(3)
        structure[1].symbol = 'Ag'

        cell, positions, species \
            = ase_atoms_to_spglib_cell(self.structure)

        self.assertTrue((cell == self.structure.get_cell()).all())
        self.assertTrue(
            (positions == self.structure.get_scaled_positions()).all())
        self.assertTrue((species == self.structure.get_atomic_numbers()).all())

    def test_chemical_symbols_to_numbers(self):
        """Tests chemical_symbols_to_numbers method."""

        symbols = ['Al', 'H', 'He']
        expected_numbers = [13, 1, 2]
        retval = chemical_symbols_to_numbers(symbols)
        self.assertEqual(expected_numbers, retval)

    def test_atomic_number_to_chemical_symbol(self):
        """Tests chemical_symbols_to_numbers method."""

        numbers = [13, 1, 2]
        expected_symbols = ['Al', 'H', 'He']
        retval = atomic_number_to_chemical_symbol(numbers)
        self.assertEqual(expected_symbols, retval)

    def test_get_wyckoff_sites(self):
        """Tests get_wyckoff_sites method."""

        # structures and reference data to test
        structures, targetvals = [], []
        structures.append(bulk('Po', crystalstructure='sc', a=4))
        targetvals.append(['1a'])
        structures.append(bulk('W', crystalstructure='bcc', a=4))
        targetvals.append(['2a'])
        structures.append(bulk('Al', crystalstructure='fcc', a=4))
        targetvals.append(['4a'])
        structures.append(bulk('Ti', crystalstructure='hcp', a=4, c=6))
        targetvals.append(2 * ['2d'])
        structures.append(bulk('SiC', crystalstructure='zincblende', a=4))
        targetvals.append(['4a', '4d'])
        structures.append(bulk('NaCl', crystalstructure='rocksalt', a=4))
        targetvals.append(['4a', '4b'])
        structures.append(bulk('ZnO', crystalstructure='wurtzite', a=4, c=5))
        targetvals.append(4 * ['2b'])

        structures.append(bulk('Al', crystalstructure='fcc', a=4, cubic=True))
        targetvals.append(4 * ['4a'])

        structures.append(bulk('Al').repeat(2))
        targetvals.append(8 * ['4a'])
        structures.append(bulk('Ti').repeat((3, 2, 1)))
        targetvals.append(12 * ['2d'])

        structure = bulk('Al').repeat(2)
        structure[0].position += [0, 0, 0.1]
        structures.append(structure)
        targetvals.append(['2a', '4b', '8c', '8c', '8c', '8c', '4b', '2a'])

        for structure, targetval in zip(structures, targetvals):
            retval = get_wyckoff_sites(structure)
            self.assertEqual(targetval, retval)

        structure = bulk('GaAs', crystalstructure='zincblende',
                         a=3.0).repeat(2)
        structure.set_chemical_symbols([
            'Ga', 'As', 'Al', 'As', 'Ga', 'As', 'Al', 'As', 'Ga', 'As', 'Ga',
            'As', 'Al', 'As', 'Ga', 'As'
        ])

        retval = get_wyckoff_sites(structure)
        targetval = [
            '8g', '8i', '4e', '8i', '8g', '8i', '2c', '8i', '2d', '8i', '8g',
            '8i', '4e', '8i', '8g', '8i'
        ]
        self.assertEqual(targetval, retval)

        retval = get_wyckoff_sites(structure,
                                   map_occupations=[['Ga', 'Al'], ['As']])
        targetval = 8 * ['4a', '4c']
        self.assertEqual(targetval, retval)

        retval = get_wyckoff_sites(structure, map_occupations=[])
        targetval = len(structure) * ['8a']
        self.assertEqual(targetval, retval)
Exemple #20
0
    def calculate(self,
                  atoms=None,
                  properties=["energy"],
                  system_changes=all_changes):
        Calculator.calculate(self, atoms, properties, system_changes)

        image = atoms
        params_dict = self.params
        chemical_symbols = np.array(image.get_chemical_symbols())
        params = []
        for element in chemical_symbols:
            re = params_dict[element]["re"]
            D = params_dict[element]["De"]
            # sig calculated from pubs.acs.org/doi/pdf/10.1021/acs.jpca.7b11252
            sig = re - np.log(2) / params_dict[element]["a"]
            params.append(np.array([[re, D, sig]]))
        params = np.vstack(np.array(params))
        n = NeighborList(
            cutoffs=[self.cutoff / 2.0] * len(image),
            self_interaction=False,
            primitive=NewPrimitiveNeighborList,
        )
        n.update(image)
        image_neighbors = [
            n.get_neighbors(index) for index in range(len(image))
        ]

        natoms = len(image)

        positions = image.positions
        cell = image.cell

        energy = 0.0
        forces = np.zeros((natoms, 3))

        for a1 in range(natoms):
            re_1 = params[a1][0]
            D_1 = np.abs(params[a1][1])
            sig_1 = params[a1][2]
            neighbors, offsets = image_neighbors[a1]
            cells = np.dot(offsets, cell)
            d = positions[neighbors] + cells - positions[a1]
            re_n = params[neighbors][:, 0]
            D_n = params[neighbors][:, 1]
            sig_n = params[neighbors][:, 2]
            if self.combo == "mean":
                D = np.sqrt(D_1 * D_n)
                sig = (sig_1 + sig_n) / 2
                re = (re_1 + re_n) / 2
            elif self.combo == "yang":
                D = (2 * D_1 * D_n) / (D_1 + D_n)
                sig = (sig_1 * sig_n) * (sig_1 + sig_n) / (sig_1**2 + sig_n**2)
                re = (re_1 * re_n) * (re_1 + re_n) / (re_1**2 + re_n**2)
            r = np.sqrt((d**2).sum(1))
            r_star = r / sig
            re_star = re / sig
            C = np.log(2) / (re_star - 1)
            atom_energy = D * (np.exp(-2 * C * (r_star - re_star)) -
                               2 * np.exp(-C * (r_star - re_star)))
            energy += atom_energy.sum()
            f = ((2 * D * C / sig) * (1 / r) *
                 (np.exp(-2 * C * (r_star - re_star)) -
                  np.exp(-C * (r_star - re_star))))[:, np.newaxis] * d
            forces[a1] -= f.sum(axis=0)
            for a2, f2 in zip(neighbors, f):
                forces[a2] += f2

        self.results["energy"] = energy
        self.results["forces"] = forces
Exemple #21
0
class TorchAtoms(Atoms):
    def __init__(self,
                 ase_atoms=None,
                 energy=None,
                 forces=None,
                 cutoff=None,
                 descriptors=[],
                 group=None,
                 ranks=None,
                 **kwargs):
        super().__init__(**kwargs)

        if ase_atoms:
            self.__dict__ = ase_atoms.__dict__

        # ------------------------------- ----------
        if type(ranks) == Distributer:
            self.ranks = None
            ranks(self)
        else:
            self.ranks = ranks
        if group is not None:
            self.attach_process_group(group)
        else:
            self.is_distributed = False
        self.cutoff = cutoff
        self.descriptors = descriptors
        self.changes = AtomsChanges(self)
        if cutoff is not None:
            self.build_nl(cutoff)
            self.update(forced=True)
        # ------------------------------------------

        try:
            self.target_energy = as_tensor(energy)
            self.target_forces = as_tensor(forces)
        except RuntimeError:
            if ase_atoms is not None and ase_atoms.get_calculator(
            ) is not None:
                if 'energy' in ase_atoms.calc.results:
                    self.target_energy = as_tensor(
                        ase_atoms.get_potential_energy())
                if 'forces' in ase_atoms.calc.results:
                    self.target_forces = as_tensor(ase_atoms.get_forces())

    def set_targets(self):
        self.target_energy = as_tensor(self.get_potential_energy())
        self.target_forces = as_tensor(self.get_forces())

    def attach_process_group(self, group):
        self.process_group = group
        self.is_distributed = True
        self.index_distribute()

    def index_distribute(self, randomize=True):
        if self.is_distributed:
            rank = torch.distributed.get_rank(group=self.process_group)
            if self.ranks:
                self.indices = []
                for i, j in enumerate(self.ranks):
                    if j == rank:
                        self.indices.append(i)
            else:
                workers = torch.distributed.get_world_size(
                    group=self.process_group)
                indices = balance_work(self.natoms, workers)
                if randomize:
                    # reproducibility issue: rnd sequence becomes workers dependent
                    # w = np.random.permutation(workers)
                    w = (np.arange(workers) +
                         np.random.randint(1024)) % workers
                    j = np.random.permutation(self.natoms)
                    self.indices = j[range(*indices[w[rank]])].tolist()
                else:
                    self.indices = range(*indices[rank])
        else:
            self.indices = range(self.natoms)

    def detach_process_group(self):
        del self.process_group
        self.is_distributed = False
        self.index_distribute()

    def build_nl(self, rc):
        self.nl = NeighborList(self.natoms * [rc / 2],
                               skin=0.0,
                               self_interaction=False,
                               bothways=True)
        self.cutoff = rc
        self.xyz = torch.from_numpy(self.positions)
        try:
            self.lll = torch.from_numpy(self.cell)
        except TypeError:
            self.lll = torch.from_numpy(self.cell.array)
        # distributed setup
        self.index_distribute()

    def local(self, a, stage=True, dont_save_grads=True, detach=False):
        n, off = self.nl.get_neighbors(a)
        cells = (from_numpy(off[..., None].astype(np.float)) *
                 self.lll).sum(dim=1)
        r = self.xyz[n] - self.xyz[a] + cells
        if detach:
            r = r.detach()
        loc = Local(a,
                    n,
                    self.numbers[a],
                    self.numbers[n],
                    r,
                    off,
                    self.descriptors if stage else [],
                    dont_save_grads=dont_save_grads)
        loc.natoms = self.natoms
        return loc

    def update(self,
               cutoff=None,
               descriptors=None,
               forced=False,
               build_locals=True,
               stage=True,
               posgrad=False,
               cellgrad=False,
               dont_save_grads=False):
        if cutoff or self.changes.numbers:
            self.build_nl(cutoff if cutoff else self.cutoff)
            forced = True
        if descriptors:
            self.descriptors = descriptors
            forced = True
        if forced or self.changes.atoms:
            self.nl.update(self)
            self.xyz.requires_grad = posgrad
            self.lll.requires_grad = cellgrad
            self.loc = [
                self.local(a, stage=stage, dont_save_grads=dont_save_grads)
                for a in self.indices
            ] if build_locals else None
            self.changes.update_references()

    def stage(self, descriptors=None, dont_save_grads=True):
        descs = iterable(descriptors) if descriptors else self.descriptors
        for loc in self.loc:
            loc.stage(descs, dont_save_grads=dont_save_grads)

    def set_descriptors(self, descriptors, stage=True, dont_save_grads=True):
        self.descriptors = [d for d in iterable(descriptors)]
        if stage:
            self.stage(dont_save_grads=dont_save_grads)

    def add_descriptors(self, descriptors, stage=True, dont_save_grads=True):
        self.descriptors = [d for d in self.descriptors] + \
            [d for d in iterable(descriptors)]
        names = [d.name for d in self.descriptors]
        if len(set(names)) != len(self.descriptors):
            raise RuntimeError(
                f'two or more descriptors have the same names: {names}')
        if stage:
            self.stage(iterable(descriptors), dont_save_grads=dont_save_grads)

    @property
    def natoms(self):
        return self.get_global_number_of_atoms()

    @property
    def tnumbers(self):
        return torch.from_numpy(self.numbers)

    @property
    def numbers_set(self):
        return np.unique(self.numbers).tolist()

    @property
    def tpbc(self):
        return torch.from_numpy(self.pbc)

    def includes_species(self, species):
        return any([a in iterable(species) for a in self.numbers_set])

    def cat(self, attr):
        """__getattr__ -> renamed to cat"""
        try:
            return torch.cat([env.__dict__[attr] for env in self.loc])
        except KeyError:
            raise AttributeError()

    def __getitem__(self, k):
        """This is a overloads the behavior of ase.Atoms."""
        return self.loc[k]

    def __iter__(self):
        """This is a overloads the behavior of ase.Atoms."""
        for env in self.loc:
            yield env

    def first_of_each_atom_type(atoms):
        indices = []
        unique = []
        for i, a in enumerate(atoms.numbers):
            if a not in unique:
                unique += [a]
                indices += [i]
        return indices

    def __eq__(self, other):  # Note: descriptors are excluded
        if other.__class__ == Local:
            return False
        elif self.natoms != other.natoms:
            return False
        elif (self.pbc != other.pbc).any():
            return False
        elif not self.lll.allclose(other.lll):
            return False
        elif (self.numbers != other.numbers).any():
            return False
        elif not self.xyz.allclose(other.xyz):
            return False
        else:
            return True

    def copy(self, update=True, group=True):
        new = TorchAtoms(positions=self.positions.copy(),
                         cell=self.cell.copy(),
                         numbers=self.numbers.copy(),
                         pbc=self.pbc.copy(),
                         ranks=self.ranks)
        if group and self.is_distributed:
            new.attach_process_group(self.process_group)
            assert new.indices == self.indices  # TODO: ignore?
        if update:
            new.update(cutoff=self.cutoff, descriptors=self.descriptors)
        vel = self.get_velocities()
        if vel is not None:
            new.set_velocities(vel.copy())
        return new

    def set_cell(self, *args, **kwargs):
        super().set_cell(*args, **kwargs)
        try:
            self.lll = torch.from_numpy(self.cell)
        except TypeError:
            self.lll = torch.from_numpy(self.cell.array)

    def set_positions(self, *args, **kwargs):
        super().set_positions(*args, **kwargs)
        self.xyz = torch.from_numpy(self.positions)

    def as_ase(self):
        atoms = Atoms(positions=self.positions,
                      cell=self.cell,
                      pbc=self.pbc,
                      numbers=self.numbers)
        atoms.calc = self.calc  # DONE: e, f
        if atoms.calc is not None:
            atoms.calc.atoms = atoms
        vel = self.get_velocities()
        if vel is not None:
            atoms.set_velocities(vel)
        return atoms

    def as_local(self):
        """ As the inverse of Local.as_atoms """
        # positions[0] should to be [0, 0, 0]
        r = torch.as_tensor(self.positions[1:])
        #a, b = np.broadcast_arrays(self.numbers[0], self.numbers[1:])
        a, b = self.numbers[0], self.numbers[1:]
        _i = np.arange(self.natoms)
        i, j = np.broadcast_arrays(_i[0], _i[1:])
        loc = Local(i, j, a, b, r)
        if 'target_energy' in self.__dict__:
            loc.target_energy = self.target_energy
        return loc

    def shake(self, beta=0.05, update=True):
        trans = np.random.laplace(0., beta, size=self.positions.shape)
        self.translate(trans)
        if update:
            self.update()

    def single_point(self):
        results = {}
        for q in ['energy', 'forces', 'stress', 'xx']:
            try:
                results[q] = self.calc.results[q]
            except KeyError:
                pass
        self.set_calculator(SinglePointCalculator(self, **results))

    def detached(self, set_targets=True):
        results = {}
        for q in ['energy', 'forces', 'stress', 'xx']:
            try:
                results[q] = self.calc.results[q]
            except KeyError:
                pass
        new = self.copy()
        new.set_calculator(SinglePointCalculator(new, **results))
        if set_targets:
            new.set_targets()
        return new

    def pickle_locals(self, folder='atoms'):
        mkdir_p(folder)
        for loc in self.loc:
            f = os.path.join(folder, f'loc_{loc.index}.pckl')
            torch.save(loc, f)

    def pickles(self, folder='atoms'):
        return [
            torch.load(os.path.join(folder, f'loc_{i}.pckl'))
            for i in range(self.natoms)
        ]

    def gathered(self, folder='atoms'):
        if self.is_distributed and len(self.loc) < self.natoms:
            self.pickle_locals(folder=folder)
            dist.barrier(self.process_group)
            loc = self.pickles()
        else:
            loc = self.loc
        return loc

    def gather_(self, folder='atoms'):
        self.loc = self.gathered(folder=folder)
        self.detach_process_group()

    def distribute_(self, group):
        self.attach_process_group(group)
        self.loc = [self.loc[i] for i in self.indices]

    def counts(self, total=True):
        c = Counter()
        if total:
            for number in self.numbers.tolist():
                c[number] += 1
        else:
            for loc in self.loc:
                c[loc.number] += 1
        return c
Exemple #22
0
def surface_coordination(atoms, cutoff=None, verbose=True):
    '''
    This function allows to extract the following data from the
    supplied Atoms object in the form of a dictionary:
    Per atom:
    - number of neighbouring atoms of specific chemical symbol

    Per atomic layer (based on tag):
    - average coordination number for all M-M combinations of all chemical species,
        e.g. for CuAu alloy slab an average number of Cu-Cu, Cu-Au, Au-Cu and Au-Au
            bonds with surrounding atoms i.e. Cu-Cu_neighbors_per_layer etc.
    - concentration of atoms per layer


    Parameters:
        atoms: Atoms object
            Surface slab containing tagged atomic layers
            e.g. using carmm.build.neb.symmetry.sort_z
        cutoff: list of floats or None
             Bond length cutoff distance in Angstrom can be set for each atom individually
             The list must contain exactly len(atoms) floats. If None, natural_cutoffs
             are used by default.
        verbose: boolean
            If True, analysed data that is contained in the dictionary will be printed
            as a table
            TODO: make table neater
    Returns:
        dict_CN, dict_surf_CN
            TODO: proper description of dictionary structure, writing csv files'''
    from ase.neighborlist import natural_cutoffs, NeighborList
    import numpy as np
    from collections import Counter
    from itertools import product

    dict_CN = {}
    if not cutoff:
        # Choose a cutoff to determine max bond distance
        cutoff = natural_cutoffs(atoms)
        if verbose:
            print("Default bond cutoffs selected:", set([(atoms[i].symbol, cutoff[i]) for i in range(len(atoms))]))

    # Create a symbols set based on all species present in the atoms
    symbols_set = sorted(list(set(atoms.symbols)))

    for l in set(atoms.get_tags()):
        index= [i.index for i in atoms if i.tag == l]
        for i in index:
            nl = NeighborList(cutoff, self_interaction=False, bothways=True)
            nl.update(atoms)
            indices, offsets = nl.get_neighbors(i)
            # avoid duplicate atomic indices
            indices_no_self = np.array(list(set([j for j in indices])))

            cn_numbers = Counter(atoms[indices_no_self].symbols)

            # create a dictionary of relevant values for cn calculations
            dict_CN[i] = {"symbol":atoms[i].symbol, 'index':i, "layer":l}
            for k in symbols_set:
                dict_CN[i].update({k+"_neighbors":cn_numbers[k]})

    # check all combinations of atomic symbols
    pr = product(symbols_set, repeat=2)

    dict_surf_CN= {}
    for layer in set(atoms.get_tags()):
        dict_surf_CN[layer] = {}
        dict_surf_CN[layer].update({"layer":layer})

        # extract data for all combinations of neighbors and put at the end of the dictionary
        for p in product(symbols_set, repeat=2):
            M_M_cn = [dict_CN[x][p[0] + "_neighbors"] for x in dict_CN if\
                 dict_CN[x]["layer"] == layer and dict_CN[x]['symbol'] == p[1]]
            if not M_M_cn == []:
                M_M_avg_cn = np.average(M_M_cn)
            else:
                M_M_avg_cn = "N/A"

            dict_surf_CN[layer].update({p[0] + "_neighboring_w_" + p[1]: M_M_avg_cn})

        # Check concentrations of atoms per layer
        for symbol in symbols_set:
            atoms_per_layer = len([x for x in dict_CN if dict_CN[x]["layer"] == layer])
            if atoms_per_layer > 0:
                symbol_count_per_layer = len([symbol for x in dict_CN if dict_CN[x]["layer"] == layer and dict_CN[x]['symbol'] == symbol])
                symbol_concentration_per_layer = symbol_count_per_layer / atoms_per_layer
            else:
                symbol_concentration_per_layer = 0
            dict_surf_CN[layer].update({symbol+"_concentration_per_layer":symbol_concentration_per_layer})

    # Preparation for verbose
    cn_layer_list_dict = [dict_surf_CN[i] for i in range(len(dict_surf_CN))]
    cn_layer_dict_keys = [dict_surf_CN[i].keys() for i in range(len(dict_surf_CN))][0]

    # Optional text output
    if verbose:
        print([key for key in cn_layer_dict_keys])
        for i in range(len(dict_surf_CN)):
            print([cn_layer_list_dict[i][key] for key in cn_layer_dict_keys])


    return dict_CN, dict_surf_CN
Exemple #23
0
class EAM(Calculator):
    r"""

    EAM Interface Documentation

Introduction
============

The Embedded Atom Method (EAM) [1]_ is a classical potential which is
good for modelling metals, particularly fcc materials. Because it is
an equiaxial potential the EAM does not model directional bonds
well. However, the Angular Dependent Potential (ADP) [2]_ which is an
extended version of EAM is able to model directional bonds and is also
included in the EAM calculator.

Generally all that is required to use this calculator is to supply a
potential file or as a set of functions that describe the potential.
The files containing the potentials for this calculator are not
included but many suitable potentials can be downloaded from The
Interatomic Potentials Repository Project at
https://www.ctcms.nist.gov/potentials/

Theory
======

A single element EAM potential is defined by three functions: the
embedded energy, electron density and the pair potential.  A two
element alloy contains the individual three functions for each element
plus cross pair interactions.  The ADP potential has two additional
sets of data to define the dipole and quadrupole directional terms for
each alloy and their cross interactions.

The total energy `E_{\rm tot}` of an arbitrary arrangement of atoms is
given by the EAM potential as

.. math::
   E_\text{tot} = \sum_i F(\bar\rho_i) + \frac{1}{2}\sum_{i\ne j} \phi(r_{ij})

and

.. math::
   \bar\rho_i = \sum_j \rho(r_{ij})

where `F` is an embedding function, namely the energy to embed an atom `i` in
the combined electron density `\bar\rho_i` which is contributed from
each of its neighbouring atoms `j` by an amount `\rho(r_{ij})`,
`\phi(r_{ij})` is the pair potential function representing the energy
in bond `ij` which is due to the short-range electro-static
interaction between atoms, and `r_{ij}` is the distance between an
atom and its neighbour for that bond.

The ADP potential is defined as

.. math::
   E_\text{tot} = \sum_i F(\bar\rho_i) + \frac{1}{2}\sum_{i\ne j} \phi(r_{ij})
   + \frac{1}{2} \sum_{i,\alpha} (\mu_i^\alpha)^2
   + \frac{1}{2} \sum_{i,\alpha,\beta} (\lambda_i^{\alpha\beta})^2
   - \frac{1}{6} \sum_i \nu_i^2

where `\mu_i^\alpha` is the dipole vector, `\lambda_i^{\alpha\beta}`
is the quadrupole tensor and `\nu_i` is the trace of
`\lambda_i^{\alpha\beta}`.

The fs potential is defined as

.. math::
   E_i = F_\alpha (\sum_{j\neq i} \rho_{\alpha \beta}(r_{ij}))
   + \frac{1}{2}\sum_{j\neq i}\phi_{\alpha \beta}(r_{ij})

where `\alpha` and `\beta` are element types of atoms. This form is similar to
original EAM formula above, except that `\rho` and `\phi` are determined
by element types.

Running the Calculator
======================

EAM calculates the cohesive atom energy and forces. Internally the
potential functions are defined by splines which may be directly
supplied or created by reading the spline points from a data file from
which a spline function is created.  The LAMMPS compatible ``.alloy``, ``.fs``
and ``.adp`` formats are supported. The LAMMPS ``.eam`` format is
slightly different from the ``.alloy`` format and is currently not
supported.

For example::

    from ase.calculators.eam import EAM

    mishin = EAM(potential='Al99.eam.alloy')
    mishin.write_potential('new.eam.alloy')
    mishin.plot()

    slab.set_calculator(mishin)
    slab.get_potential_energy()
    slab.get_forces()

The breakdown of energy contribution from the indvidual components are
stored in the calculator instance ``.results['energy_components']``

Arguments
=========

=========================  ====================================================
Keyword                    Description
=========================  ====================================================
``potential``              file of potential in ``.eam``, ``.alloy``, ``.adp`` or ``.fs``
                           format or file object (This is generally all you need to supply).
                           In case of file object the ``form`` argument is required

``elements[N]``            array of N element abbreviations

``embedded_energy[N]``     arrays of embedded energy functions

``electron_density[N]``    arrays of electron density functions

``phi[N,N]``               arrays of pair potential functions

``d_embedded_energy[N]``   arrays of derivative embedded energy functions

``d_electron_density[N]``  arrays of derivative electron density functions

``d_phi[N,N]``             arrays of derivative pair potentials functions

``d[N,N], q[N,N]``         ADP dipole and quadrupole function

``d_d[N,N], d_q[N,N]``     ADP dipole and quadrupole derivative functions

``skin``                   skin distance passed to NeighborList(). If no atom
                           has moved more than the skin-distance since the last
                           call to the ``update()`` method then the neighbor
                           list can be reused. Defaults to 1.0.

``form``                   the form of the potential ``eam``, ``alloy``, ``adp`` or
                           ``fs``. This will be determined from the file suffix
                           or must be set if using equations or file object

=========================  ====================================================


Additional parameters for writing potential files
=================================================

The following parameters are only required for writing a potential in
``.alloy``, ``.adp`` or ``fs`` format file.

=========================  ====================================================
Keyword                    Description
=========================  ====================================================
``header``                 Three line text header. Default is standard message.

``Z[N]``                   Array of atomic number of each element

``mass[N]``                Atomic mass of each element

``a[N]``                   Array of lattice parameters for each element

``lattice[N]``             Lattice type

``nrho``                   No. of rho samples along embedded energy curve

``drho``                   Increment for sampling density

``nr``                     No. of radial points along density and pair
                           potential curves

``dr``                     Increment for sampling radius

=========================  ====================================================

Special features
================

``.plot()``
  Plots the individual functions. This may be called from multiple EAM
  potentials to compare the shape of the individual curves. This
  function requires the installation of the Matplotlib libraries.

Notes/Issues
=============

* Although currently not fast, this calculator can be good for trying
  small calculations or for creating new potentials by matching baseline
  data such as from DFT results. The format for these potentials is
  compatible with LAMMPS_ and so can be used either directly by LAMMPS or
  with the ASE LAMMPS calculator interface.

* Supported formats are the LAMMPS_ ``.alloy`` and ``.adp``. The
  ``.eam`` format is currently not supported. The form of the
  potential will be determined from the file suffix.

* Any supplied values will override values read from the file.

* The derivative functions, if supplied, are only used to calculate
  forces.

* There is a bug in early versions of scipy that will cause eam.py to
  crash when trying to evaluate splines of a potential with one
  neighbor such as caused by evaluating a dimer.

.. _LAMMPS: http://lammps.sandia.gov/

.. [1] M.S. Daw and M.I. Baskes, Phys. Rev. Letters 50 (1983)
       1285.

.. [2] Y. Mishin, M.J. Mehl, and D.A. Papaconstantopoulos,
       Acta Materialia 53 2005 4029--4041.


End EAM Interface Documentation
    """

    implemented_properties = ['energy', 'forces']

    default_parameters = dict(skin=1.0,
                              potential=None,
                              header=[
                                  b'EAM/ADP potential file\n',
                                  b'Generated from eam.py\n', b'blank\n'
                              ])

    def __init__(self,
                 restart=None,
                 ignore_bad_restart_file=False,
                 label=os.curdir,
                 atoms=None,
                 form=None,
                 **kwargs):

        self.form = form

        if 'potential' in kwargs:
            self.read_potential(kwargs['potential'])

        Calculator.__init__(self, restart, ignore_bad_restart_file, label,
                            atoms, **kwargs)

        valid_args = (
            'potential',
            'elements',
            'header',
            'drho',
            'dr',
            'cutoff',
            'atomic_number',
            'mass',
            'a',
            'lattice',
            'embedded_energy',
            'electron_density',
            'phi',
            # derivatives
            'd_embedded_energy',
            'd_electron_density',
            'd_phi',
            'd',
            'q',
            'd_d',
            'd_q',  # adp terms
            'skin',
            'Z',
            'nr',
            'nrho',
            'mass')

        # set any additional keyword arguments
        for arg, val in self.parameters.items():
            if arg in valid_args:
                setattr(self, arg, val)
            else:
                raise RuntimeError('unknown keyword arg "%s" : not in %s' %
                                   (arg, valid_args))

    def set_form(self, fileobj):
        """set the form variable based on the file name suffix"""
        extension = os.path.splitext(fileobj)[1]

        if extension == '.eam':
            self.form = 'eam'
        elif extension == '.alloy':
            self.form = 'alloy'
        elif extension == '.adp':
            self.form = 'adp'
        elif extension == '.fs':
            self.form = 'fs'
        else:
            raise RuntimeError('unknown file extension type: %s' % extension)

    def read_potential(self, fileobj):
        """Reads a LAMMPS EAM file in alloy or adp format
        and creates the interpolation functions from the data
        """

        if isinstance(fileobj, basestring):
            f = open(fileobj)
            if self.form is None:
                self.set_form(fileobj)
        else:
            f = fileobj

        def lines_to_list(lines):
            """Make the data one long line so as not to care how its formatted
            """
            data = []
            for line in lines:
                data.extend(line.split())
            return data

        lines = f.readlines()
        if self.form == 'eam':  # single element eam file (aka funcfl)
            self.header = lines[:1]

            data = lines_to_list(lines[1:])

            # eam form is just like an alloy form for one element

            self.Nelements = 1
            self.Z = np.array([data[0]], dtype=int)
            self.mass = np.array([data[1]])
            self.a = np.array([data[2]])
            self.lattice = [data[3]]

            self.nrho = int(data[4])
            self.drho = float(data[5])
            self.nr = int(data[6])
            self.dr = float(data[7])
            self.cutoff = float(data[8])

            n = 9 + self.nrho
            self.embedded_data = np.array([np.float_(data[9:n])])

            self.rphi_data = np.zeros(
                [self.Nelements, self.Nelements, self.nr])

            effective_charge = np.float_(data[n:n + self.nr])
            # convert effective charges to rphi according to
            # http://lammps.sandia.gov/doc/pair_eam.html
            self.rphi_data[0, 0] = Bohr * Hartree * (effective_charge**2)

            self.density_data = np.array(
                [np.float_(data[n + self.nr:n + 2 * self.nr])])

        elif self.form in ['alloy', 'adq']:
            self.header = lines[:3]
            i = 3

            data = lines_to_list(lines[i:])

            self.Nelements = int(data[0])
            d = 1
            self.elements = data[d:d + self.Nelements]
            d += self.Nelements

            self.nrho = int(data[d])
            self.drho = float(data[d + 1])
            self.nr = int(data[d + 2])
            self.dr = float(data[d + 3])
            self.cutoff = float(data[d + 4])

            self.embedded_data = np.zeros([self.Nelements, self.nrho])
            self.density_data = np.zeros([self.Nelements, self.nr])
            self.Z = np.zeros([self.Nelements], dtype=int)
            self.mass = np.zeros([self.Nelements])
            self.a = np.zeros([self.Nelements])
            self.lattice = []
            d += 5

            # reads in the part of the eam file for each element
            for elem in range(self.Nelements):
                self.Z[elem] = int(data[d])
                self.mass[elem] = float(data[d + 1])
                self.a[elem] = float(data[d + 2])
                self.lattice.append(data[d + 3])
                d += 4

                self.embedded_data[elem] = np.float_(data[d:(d + self.nrho)])
                d += self.nrho
                self.density_data[elem] = np.float_(data[d:(d + self.nr)])
                d += self.nr

            # reads in the r*phi data for each interaction between elements
            self.rphi_data = np.zeros(
                [self.Nelements, self.Nelements, self.nr])

            for i in range(self.Nelements):
                for j in range(i + 1):
                    self.rphi_data[j, i] = np.float_(data[d:(d + self.nr)])
                    d += self.nr

        elif self.form == 'fs':
            self.header = lines[:3]
            i = 3

            data = lines_to_list(lines[i:])

            self.Nelements = int(data[0])
            d = 1
            self.elements = data[d:d + self.Nelements]
            d += self.Nelements

            self.nrho = int(data[d])
            self.drho = float(data[d + 1])
            self.nr = int(data[d + 2])
            self.dr = float(data[d + 3])
            self.cutoff = float(data[d + 4])

            self.embedded_data = np.zeros([self.Nelements, self.nrho])
            self.density_data = np.zeros(
                [self.Nelements, self.Nelements, self.nr])
            self.Z = np.zeros([self.Nelements], dtype=int)
            self.mass = np.zeros([self.Nelements])
            self.a = np.zeros([self.Nelements])
            self.lattice = []
            d += 5

            # reads in the part of the eam file for each element
            for elem in range(self.Nelements):
                self.Z[elem] = int(data[d])
                self.mass[elem] = float(data[d + 1])
                self.a[elem] = float(data[d + 2])
                self.lattice.append(data[d + 3])
                d += 4

                self.embedded_data[elem] = np.float_(data[d:(d + self.nrho)])
                d += self.nrho
                self.density_data[elem, :, :] = np.float_(
                    data[d:(d + self.nr * self.Nelements)]).reshape(
                        [self.Nelements, self.nr])
                d += self.nr * self.Nelements

            # reads in the r*phi data for each interaction between elements
            self.rphi_data = np.zeros(
                [self.Nelements, self.Nelements, self.nr])

            for i in range(self.Nelements):
                for j in range(i + 1):
                    self.rphi_data[j, i] = np.float_(data[d:(d + self.nr)])
                    d += self.nr

        self.r = np.arange(0, self.nr) * self.dr
        self.rho = np.arange(0, self.nrho) * self.drho

        # choose the set_splines method according to the type
        if self.form == 'fs':
            self.set_fs_splines()
        else:
            self.set_splines()

        if (self.form == 'adp'):
            self.read_adp_data(data, d)
            self.set_adp_splines()

    def set_splines(self):
        # this section turns the file data into three functions (and
        # derivative functions) that define the potential
        self.embedded_energy = np.empty(self.Nelements, object)
        self.electron_density = np.empty(self.Nelements, object)
        self.d_embedded_energy = np.empty(self.Nelements, object)
        self.d_electron_density = np.empty(self.Nelements, object)

        for i in range(self.Nelements):
            self.embedded_energy[i] = spline(self.rho,
                                             self.embedded_data[i],
                                             k=3)
            self.electron_density[i] = spline(self.r,
                                              self.density_data[i],
                                              k=3)
            self.d_embedded_energy[i] = self.deriv(self.embedded_energy[i])
            self.d_electron_density[i] = self.deriv(self.electron_density[i])

        self.phi = np.empty([self.Nelements, self.Nelements], object)
        self.d_phi = np.empty([self.Nelements, self.Nelements], object)

        # ignore the first point of the phi data because it is forced
        # to go through zero due to the r*phi format in alloy and adp
        for i in range(self.Nelements):
            for j in range(i, self.Nelements):
                self.phi[i, j] = spline(self.r[1:],
                                        self.rphi_data[i, j][1:] / self.r[1:],
                                        k=3)

                self.d_phi[i, j] = self.deriv(self.phi[i, j])

                if j != i:
                    self.phi[j, i] = self.phi[i, j]
                    self.d_phi[j, i] = self.d_phi[i, j]

    def set_fs_splines(self):
        self.embedded_energy = np.empty(self.Nelements, object)
        self.electron_density = np.empty([self.Nelements, self.Nelements],
                                         object)
        self.d_embedded_energy = np.empty(self.Nelements, object)
        self.d_electron_density = np.empty([self.Nelements, self.Nelements],
                                           object)

        for i in range(self.Nelements):
            self.embedded_energy[i] = spline(self.rho,
                                             self.embedded_data[i],
                                             k=3)
            self.d_embedded_energy[i] = self.deriv(self.embedded_energy[i])
            for j in range(self.Nelements):
                self.electron_density[i, j] = spline(self.r,
                                                     self.density_data[i, j],
                                                     k=3)
                self.d_electron_density[i, j] = self.deriv(
                    self.electron_density[i, j])

        self.phi = np.empty([self.Nelements, self.Nelements], object)
        self.d_phi = np.empty([self.Nelements, self.Nelements], object)

        for i in range(self.Nelements):
            for j in range(i, self.Nelements):
                self.phi[i, j] = spline(self.r[1:],
                                        self.rphi_data[i, j][1:] / self.r[1:],
                                        k=3)

                self.d_phi[i, j] = self.deriv(self.phi[i, j])

                if j != i:
                    self.phi[j, i] = self.phi[i, j]
                    self.d_phi[j, i] = self.d_phi[i, j]

    def set_adp_splines(self):
        self.d = np.empty([self.Nelements, self.Nelements], object)
        self.d_d = np.empty([self.Nelements, self.Nelements], object)
        self.q = np.empty([self.Nelements, self.Nelements], object)
        self.d_q = np.empty([self.Nelements, self.Nelements], object)

        for i in range(self.Nelements):
            for j in range(i, self.Nelements):
                self.d[i, j] = spline(self.r[1:], self.d_data[i, j][1:], k=3)
                self.d_d[i, j] = self.deriv(self.d[i, j])
                self.q[i, j] = spline(self.r[1:], self.q_data[i, j][1:], k=3)
                self.d_q[i, j] = self.deriv(self.q[i, j])

                # make symmetrical
                if j != i:
                    self.d[j, i] = self.d[i, j]
                    self.d_d[j, i] = self.d_d[i, j]
                    self.q[j, i] = self.q[i, j]
                    self.d_q[j, i] = self.d_q[i, j]

    def read_adp_data(self, data, d):
        """read in the extra adp data from the potential file"""

        self.d_data = np.zeros([self.Nelements, self.Nelements, self.nr])
        # should be non symmetrical combinations of 2
        for i in range(self.Nelements):
            for j in range(i + 1):
                self.d_data[j, i] = data[d:d + self.nr]
                d += self.nr

        self.q_data = np.zeros([self.Nelements, self.Nelements, self.nr])
        # should be non symmetrical combinations of 2
        for i in range(self.Nelements):
            for j in range(i + 1):
                self.q_data[j, i] = data[d:d + self.nr]
                d += self.nr

    def write_potential(self, filename, nc=1, numformat='%.8e'):
        """Writes out the potential in the format given by the form
        variable to 'filename' with a data format that is nc columns
        wide.  Note: array lengths need to be an exact multiple of nc
        """

        f = open(filename, 'wb')

        assert self.nr % nc == 0
        assert self.nrho % nc == 0

        for line in self.header:
            f.write(line)

        f.write('{0} '.format(self.Nelements).encode())
        f.write(' '.join(self.elements).encode() + b'\n')

        f.write(
            ('%d %f %d %f %f \n' %
             (self.nrho, self.drho, self.nr, self.dr, self.cutoff)).encode())

        # start of each section for each element
        #        rs = np.linspace(0, self.nr * self.dr, self.nr)
        #        rhos = np.linspace(0, self.nrho * self.drho, self.nrho)

        rs = np.arange(0, self.nr) * self.dr
        rhos = np.arange(0, self.nrho) * self.drho

        for i in range(self.Nelements):
            f.write(('%d %f %f %s\n' % (self.Z[i], self.mass[i], self.a[i],
                                        str(self.lattice[i]))).encode())
            np.savetxt(f,
                       self.embedded_energy[i](rhos).reshape(
                           self.nrho // nc, nc),
                       fmt=nc * [numformat])
            if self.form == 'fs':
                for j in range(self.Nelements):
                    np.savetxt(f,
                               self.electron_density[i, j](rs).reshape(
                                   self.nr // nc, nc),
                               fmt=nc * [numformat])
            else:
                np.savetxt(f,
                           self.electron_density[i](rs).reshape(
                               self.nr // nc, nc),
                           fmt=nc * [numformat])

        # write out the pair potentials in Lammps DYNAMO setfl format
        # as r*phi for alloy format
        for i in range(self.Nelements):
            for j in range(i, self.Nelements):
                np.savetxt(f, (rs * self.phi[i, j](rs)).reshape(
                    self.nr // nc, nc),
                           fmt=nc * [numformat])

        if self.form == 'adp':
            # these are the u(r) or dipole values
            for i in range(self.Nelements):
                for j in range(i + 1):
                    np.savetxt(f, self.d_data[i, j])

            # these are the w(r) or quadrupole values
            for i in range(self.Nelements):
                for j in range(i + 1):
                    np.savetxt(f, self.q_data[i, j])

        f.close()

    def update(self, atoms):
        # check all the elements are available in the potential
        self.Nelements = len(self.elements)
        elements = np.unique(atoms.get_chemical_symbols())
        unavailable = np.logical_not(
            np.array([item in self.elements for item in elements]))

        if np.any(unavailable):
            raise RuntimeError('These elements are not in the potential: %s' %
                               elements[unavailable])

        # cutoffs need to be a vector for NeighborList
        cutoffs = self.cutoff * np.ones(len(atoms))

        # convert the elements to an index of the position
        # in the eam format
        self.index = np.array(
            [self.elements.index(el) for el in atoms.get_chemical_symbols()])
        self.pbc = atoms.get_pbc()

        # since we need the contribution of all neighbors to the
        # local electron density we cannot just calculate and use
        # one way neighbors
        self.neighbors = NeighborList(cutoffs,
                                      skin=self.parameters.skin,
                                      self_interaction=False,
                                      bothways=True)
        self.neighbors.update(atoms)

    def calculate(self,
                  atoms=None,
                  properties=['energy'],
                  system_changes=all_changes):
        """EAM Calculator

        atoms: Atoms object
            Contains positions, unit-cell, ...
        properties: list of str
            List of what needs to be calculated.  Can be any combination
            of 'energy', 'forces'
        system_changes: list of str
            List of what has changed since last calculation.  Can be
            any combination of these five: 'positions', 'numbers', 'cell',
            'pbc', 'initial_charges' and 'initial_magmoms'.
            """

        Calculator.calculate(self, atoms, properties, system_changes)

        # we shouldn't really recalc if charges or magmos change
        if len(system_changes) > 0:  # something wrong with this way
            self.update(self.atoms)
            self.calculate_energy(self.atoms)

            if 'forces' in properties:
                self.calculate_forces(self.atoms)

        # check we have all the properties requested
        for property in properties:
            if property not in self.results:
                if property == 'energy':
                    self.calculate_energy(self.atoms)

                if property == 'forces':
                    self.calculate_forces(self.atoms)

        # we need to remember the previous state of parameters
#        if 'potential' in parameter_changes and potential != None:
#                self.read_potential(potential)

    def calculate_energy(self, atoms):
        """Calculate the energy
        the energy is made up of the ionic or pair interaction and
        the embedding energy of each atom into the electron cloud
        generated by its neighbors
        """

        pair_energy = 0.0
        embedding_energy = 0.0
        mu_energy = 0.0
        lam_energy = 0.0
        trace_energy = 0.0

        self.total_density = np.zeros(len(atoms))
        if (self.form == 'adp'):
            self.mu = np.zeros([len(atoms), 3])
            self.lam = np.zeros([len(atoms), 3, 3])

        for i in range(len(atoms)):  # this is the atom to be embedded
            neighbors, offsets = self.neighbors.get_neighbors(i)
            offset = np.dot(offsets, atoms.get_cell())

            rvec = (atoms.positions[neighbors] + offset - atoms.positions[i])

            # calculate the distance to the nearest neighbors
            r = np.sqrt(np.sum(np.square(rvec), axis=1))  # fast
            #            r = np.apply_along_axis(np.linalg.norm, 1, rvec)  # sloow

            nearest = np.arange(len(r))[r <= self.cutoff]
            for j_index in range(self.Nelements):
                use = self.index[neighbors[nearest]] == j_index
                if not use.any():
                    continue
                pair_energy += np.sum(self.phi[self.index[i], j_index](
                    r[nearest][use])) / 2.

                if self.form == 'fs':
                    density = np.sum(
                        self.electron_density[j_index,
                                              self.index[i]](r[nearest][use]))
                else:
                    density = np.sum(self.electron_density[j_index](
                        r[nearest][use]))
                self.total_density[i] += density

                if self.form == 'adp':
                    self.mu[i] += self.adp_dipole(
                        r[nearest][use], rvec[nearest][use],
                        self.d[self.index[i], j_index])

                    self.lam[i] += self.adp_quadrupole(
                        r[nearest][use], rvec[nearest][use],
                        self.q[self.index[i], j_index])

            # add in the electron embedding energy
            embedding_energy += self.embedded_energy[self.index[i]](
                self.total_density[i])

        components = dict(pair=pair_energy, embedding=embedding_energy)

        if self.form == 'adp':
            mu_energy += np.sum(self.mu**2) / 2.
            lam_energy += np.sum(self.lam**2) / 2.

            for i in range(len(atoms)):  # this is the atom to be embedded
                trace_energy -= np.sum(self.lam[i].trace()**2) / 6.

            adp_result = dict(adp_mu=mu_energy,
                              adp_lam=lam_energy,
                              adp_trace=trace_energy)
            components.update(adp_result)

        self.positions = atoms.positions.copy()
        self.cell = atoms.get_cell().copy()

        energy = 0.0
        for i in components.keys():
            energy += components[i]

        self.energy_free = energy
        self.energy_zero = energy

        self.results['energy_components'] = components
        self.results['energy'] = energy

    def calculate_forces(self, atoms):
        # calculate the forces based on derivatives of the three EAM functions

        self.update(atoms)
        self.results['forces'] = np.zeros((len(atoms), 3))

        for i in range(len(atoms)):  # this is the atom to be embedded
            neighbors, offsets = self.neighbors.get_neighbors(i)
            offset = np.dot(offsets, atoms.get_cell())
            # create a vector of relative positions of neighbors
            rvec = atoms.positions[neighbors] + offset - atoms.positions[i]
            r = np.sqrt(np.sum(np.square(rvec), axis=1))
            nearest = np.arange(len(r))[r < self.cutoff]

            d_embedded_energy_i = self.d_embedded_energy[self.index[i]](
                self.total_density[i])
            urvec = rvec.copy()  # unit directional vector

            for j in np.arange(len(neighbors)):
                urvec[j] = urvec[j] / r[j]

            for j_index in range(self.Nelements):
                use = self.index[neighbors[nearest]] == j_index
                if not use.any():
                    continue
                rnuse = r[nearest][use]
                density_j = self.total_density[neighbors[nearest][use]]
                if self.form == 'fs':
                    scale = (self.d_phi[self.index[i], j_index](rnuse) +
                             (d_embedded_energy_i *
                              self.d_electron_density[j_index, self.index[i]]
                              (rnuse)) +
                             (self.d_embedded_energy[j_index](density_j) *
                              self.d_electron_density[self.index[i], j_index]
                              (rnuse)))
                else:
                    scale = (self.d_phi[self.index[i], j_index](rnuse) +
                             (d_embedded_energy_i *
                              self.d_electron_density[j_index](rnuse)) +
                             (self.d_embedded_energy[j_index](density_j) *
                              self.d_electron_density[self.index[i]](rnuse)))

                self.results['forces'][i] += np.dot(scale, urvec[nearest][use])

                if (self.form == 'adp'):
                    adp_forces = self.angular_forces(
                        self.mu[i], self.mu[neighbors[nearest][use]],
                        self.lam[i], self.lam[neighbors[nearest][use]], rnuse,
                        rvec[nearest][use], self.index[i], j_index)

                    self.results['forces'][i] += adp_forces

    def angular_forces(self, mu_i, mu, lam_i, lam, r, rvec, form1, form2):
        # calculate the extra components for the adp forces
        # rvec are the relative positions to atom i
        psi = np.zeros(mu.shape)
        for gamma in range(3):
            term1 = (mu_i[gamma] - mu[:, gamma]) * self.d[form1][form2](r)

            term2 = np.sum(
                (mu_i - mu) * self.d_d[form1][form2](r)[:, np.newaxis] *
                (rvec * rvec[:, gamma][:, np.newaxis] / r[:, np.newaxis]),
                axis=1)

            term3 = 2 * np.sum((lam_i[:, gamma] + lam[:, :, gamma]) * rvec *
                               self.q[form1][form2](r)[:, np.newaxis],
                               axis=1)
            term4 = 0.0
            for alpha in range(3):
                for beta in range(3):
                    rs = rvec[:, alpha] * rvec[:, beta] * rvec[:, gamma]
                    term4 += ((lam_i[alpha, beta] + lam[:, alpha, beta]) *
                              self.d_q[form1][form2](r) * rs) / r

            term5 = ((lam_i.trace() + lam.trace(axis1=1, axis2=2)) *
                     (self.d_q[form1][form2](r) * r + 2 * self.q[form1][form2]
                      (r)) * rvec[:, gamma]) / 3.

            # the minus for term5 is a correction on the adp
            # formulation given in the 2005 Mishin Paper and is posted
            # on the NIST website with the AlH potential
            psi[:, gamma] = term1 + term2 + term3 + term4 - term5

        return np.sum(psi, axis=0)

    def adp_dipole(self, r, rvec, d):
        # calculate the dipole contribution
        mu = np.sum((rvec * d(r)[:, np.newaxis]), axis=0)

        return mu  # sign to agree with lammps

    def adp_quadrupole(self, r, rvec, q):
        # slow way of calculating the quadrupole contribution
        r = np.sqrt(np.sum(rvec**2, axis=1))

        lam = np.zeros([rvec.shape[0], 3, 3])
        qr = q(r)
        for alpha in range(3):
            for beta in range(3):
                lam[:, alpha, beta] += qr * rvec[:, alpha] * rvec[:, beta]

        return np.sum(lam, axis=0)

    def deriv(self, spline):
        """Wrapper for extracting the derivative from a spline"""
        def d_spline(aspline):
            return spline(aspline, 1)

        return d_spline

    def plot(self, name=''):
        """Plot the individual curves"""

        import matplotlib.pyplot as plt

        if self.form == 'eam' or self.form == 'alloy' or self.form == 'fs':
            nrow = 2
        elif self.form == 'adp':
            nrow = 3
        else:
            raise RuntimeError('Unknown form of potential: %s' % self.form)

        if hasattr(self, 'r'):
            r = self.r
        else:
            r = np.linspace(0, self.cutoff, 50)

        if hasattr(self, 'rho'):
            rho = self.rho
        else:
            rho = np.linspace(0, 10.0, 50)

        plt.subplot(nrow, 2, 1)
        self.elem_subplot(rho, self.embedded_energy, r'$\rho$',
                          r'Embedding Energy $F(\bar\rho)$', name, plt)

        plt.subplot(nrow, 2, 2)
        if self.form == 'fs':
            self.multielem_subplot(r,
                                   self.electron_density,
                                   r'$r$',
                                   r'Electron Density $\rho(r)$',
                                   name,
                                   plt,
                                   half=False)
        else:
            self.elem_subplot(r, self.electron_density, r'$r$',
                              r'Electron Density $\rho(r)$', name, plt)

        plt.subplot(nrow, 2, 3)
        self.multielem_subplot(r, self.phi, r'$r$',
                               r'Pair Potential $\phi(r)$', name, plt)
        plt.ylim(-1.0, 1.0)  # need reasonable values

        if self.form == 'adp':
            plt.subplot(nrow, 2, 5)
            self.multielem_subplot(r, self.d, r'$r$', r'Dipole Energy', name,
                                   plt)

            plt.subplot(nrow, 2, 6)
            self.multielem_subplot(r, self.q, r'$r$', r'Quadrupole Energy',
                                   name, plt)

        plt.plot()

    def elem_subplot(self, curvex, curvey, xlabel, ylabel, name, plt):
        plt.xlabel(xlabel)
        plt.ylabel(ylabel)
        for i in np.arange(self.Nelements):
            label = name + ' ' + self.elements[i]
            plt.plot(curvex, curvey[i](curvex), label=label)
        plt.legend()

    def multielem_subplot(self,
                          curvex,
                          curvey,
                          xlabel,
                          ylabel,
                          name,
                          plt,
                          half=True):
        plt.xlabel(xlabel)
        plt.ylabel(ylabel)
        for i in np.arange(self.Nelements):
            for j in np.arange((i + 1) if half else self.Nelements):
                label = name + ' ' + self.elements[i] + '-' + self.elements[j]
                plt.plot(curvex, curvey[i, j](curvex), label=label)
        plt.legend()
Exemple #24
0
    for i in glob('*/'):
        if i == '__pycache__/':
            continue
        dire = i
        atoms = read(i + "/" + "POSCAR")
        ################# These can be added in as arg parsers ##################
        ads = read("CO.POSCAR")
        surface_atoms = ["Pt"]
        radii_multiplier = 1.1
        skin_arg = 0.25
        no_adsorb = ['']
        min_ads_dist = 2.4

        nl = NeighborList(natural_cutoffs(atoms, radii_multiplier),
                          self_interaction=False,
                          bothways=True,
                          skin=skin_arg)
        nl.update(atoms)

        adsorbate_atoms = [
            index for index, atom in enumerate(atoms)
            if atom.symbol not in surface_atoms
        ]

        normals, mask = generate_normals(atoms,
                                         adsorbate_atoms=adsorbate_atoms,
                                         normalize_final=True)
        ### make sure to manually set the normals for 2-D materials, all atoms should have a normal pointing up, as all atoms are surface atoms
        #normals, mask = np.ones((len(atoms), 3)) * (0, 0, 1), list(range(len(atoms)))

        constrained = constrained_indices(atoms)
Exemple #25
0
class SHPP(Calculator):
    """ Soft+Harmonic Pair Potential -- a combination
    of LAMMPS-pair_style 'soft' pair potential
    plus restorative forces.

    The total energy is given by:
        E_tot(R) = E_harm(R) + E_soft(R)
        E_harm(R) = Sum_i 0.5*k*|R_i-R_i,start|^2
        E_soft(R) = Sum_i<j A*(1+cos(|R_i-R_j|*pi/d_min))

    atoms: an Atoms object
    blmin: dictionary with the minimal interatomic distance
           for each (sorted) pair of atomic numbers
    k: spring constant for the harmonic potential in eV/Angstrom^2
    A: prefactor for the 'soft' potential in eV
    """
    implemented_properties = ['energy', 'forces']

    def __init__(self, atoms, blmin, k=0.5, A=10.):
        Calculator.__init__(self)
        self.positions = atoms.get_positions()
        self.blmin = blmin
        self.k = k
        self.A = A
        self.N = len(atoms)
        rcut = max(self.blmin.values())
        self.nl = NeighborList([rcut / 2.] * self.N,
                               skin=1.,
                               bothways=True,
                               self_interaction=False)

    def calculate(self, atoms, properties, system_changes):
        Calculator.calculate(self, atoms, properties, system_changes)
        energy, forces = 0, np.zeros((self.N, 3))
        e, f = self._calculate_harm(atoms)
        energy += e
        forces += f
        e, f = self._calculate_soft(atoms)
        energy += e
        forces += f
        self.results = {'energy': energy, 'forces': forces}

    def _calculate_harm(self, atoms):
        cell = atoms.get_cell()
        pbc = atoms.get_pbc()
        vectors = atoms.get_positions() - self.positions
        e = 0.
        f = np.zeros((self.N, 3))
        for i, v in enumerate(vectors):
            v, d = find_mic([v], cell, pbc)
            e += 0.5 * self.k * (d**2)
            f[i] = -self.k * v
        return e, f

    def _calculate_soft(self, atoms):
        cell = atoms.get_cell()
        num = atoms.get_atomic_numbers()
        pos = atoms.get_positions()
        self.nl.update(atoms)
        e = 0
        f = np.zeros((self.N, 3))
        for i in range(self.N):
            indices, offsets = self.nl.get_neighbors(i)
            p = pos[indices] + np.dot(offsets, cell)
            r = cdist(p, [pos[i]])
            v = p - pos[i]
            for j, index in enumerate(indices):
                bl = self.blmin[tuple(sorted([num[i], num[index]]))]
                d = r[j][0]
                if d < bl:
                    e += self.A * (1 + np.cos(d * np.pi / bl))
                    fj = self.A * np.pi * np.sin(np.pi * d / bl)
                    fj /= (d * bl) * v[j]
                    f[index] += fj
        return e, f
def load_oqmd_data(num_dist_basis, dtype=float, cutoff=2.0, filter_query=None):
    """ Returns tuple (Z, D, y, num_species)
    where
        Z is a matrix, each row in Z corresponds to a molecule and the elements
        are atomic numbers
        D is a 4D tensor giving the Gaussian feature expansion of distances
            between atoms
        y is the energy of each molecule in kcal/mol
        num_species is the integer number of different atomic species in Z """
    import ase.db
    con = ase.db.connect("oqmd_all_entries.db")
    sel = filter_query

    # Create sorted list of species
    all_species = sorted(
        list(
            reduce(lambda x, y: x.union(set(y.numbers)), con.select(sel),
                   set())))
    # Insert dummy species 0
    all_species.insert(0, 0)
    # Create mapping from species to species index
    species_map = dict([(x, i) for i, x in enumerate(all_species)])

    # Find max number of atoms
    max_atoms = 0
    num_molecules = 0
    for row in con.select(sel):
        num_molecules += 1
        atoms = row.toatoms()
        max_atoms = max(max_atoms, len(atoms))

    # Gaussian basis function parameters
    mu_min = -1
    mu_max = 2 * cutoff + 1
    step = (mu_max - mu_min) / num_dist_basis
    sigma = step
    print "Number of molecules %d, max atoms %d" % (num_molecules, max_atoms)
    k = np.arange(num_dist_basis)
    D = np.zeros((num_molecules, max_atoms, max_atoms, num_dist_basis),
                 dtype=dtype)
    Z = np.zeros((num_molecules, max_atoms), dtype=np.int8)
    y = np.zeros(num_molecules, dtype=dtype)

    for i_mol, row in enumerate(con.select(sel)):
        print "feature expansion %10d/%10d\r" % (i_mol + 1, num_molecules),
        atoms = row.toatoms()
        y[i_mol] = row.total_energy
        Z[i_mol, 0:len(row.numbers)] = map(lambda x: species_map[x],
                                           row.numbers)
        assert np.all(atoms.get_atomic_numbers() == row.numbers)
        neighborhood = NeighborList([cutoff] * len(atoms),
                                    self_interaction=False,
                                    bothways=False)
        neighborhood.build(atoms)
        for ii in range(len(atoms)):
            neighbor_indices, offset = neighborhood.get_neighbors(ii)
            for jj, offs in zip(neighbor_indices, offset):
                ii_pos = atoms.positions[ii]
                jj_pos = atoms.positions[jj] + np.dot(offs, atoms.get_cell())
                dist = np.linalg.norm(ii_pos - jj_pos)
                d_expand = np.exp(-((dist - (mu_min + k * step))**2.0) /
                                  (2.0 * sigma**2.0))
                D[i_mol, ii, jj, :] += d_expand
                if jj != ii:
                    D[i_mol, jj, ii, :] += d_expand
    return Z, D, y, len(all_species)
def generate_site_type(atoms, surface_mask, normals, coordination, unallowed_elements=[]):
    cutoffs = natural_cutoffs(atoms)

    nl = NeighborList(cutoffs, self_interaction=False, bothways=True)
    nl.update(atoms)

    surface_mask = [index for index in surface_mask if atoms[index].symbol not in unallowed_elements]

    possible = list(combinations(set(surface_mask), coordination))
    valid = []
    sites = []

    for cycle in possible:
       for start, end in combinations(cycle, 2):
           if end not in nl.get_neighbors(start)[0]:
               break
       else: # All were valid
            valid.append(list(cycle))

    #print(valid)
    for cycle in valid:
        tracked = np.array(atoms[cycle[0]].position, dtype=float)
        known = np.zeros(shape=(coordination, 3), dtype=float)
        known[0] = tracked
        for index, (start, end) in enumerate(zip(cycle[:-1], cycle[1:])):
            for neighbor, offset in zip(*nl.get_neighbors(start)):
                if neighbor == end:
                    tracked += offset_position(atoms, neighbor, offset) - atoms[start].position
                    known[index + 1] = tracked

        average = np.average(known, axis=0)

        normal = np.zeros(3)

        #for index in cycle:
            #neighbors = len(nl.get_neighbors(index)[0])
            #normal += normals[index] * (1/neighbors)
        #normal = normalize(normal)
        #for index in cycle:
            #print(cycle)
        if len(cycle) == 1:
            for index in cycle:
                neighbors = len(nl.get_neighbors(index)[0])
                normal += normals[index] * (1/neighbors)
            normal = normalize(normal)

        if len(cycle) >1:
            neighbors = len(nl.get_neighbors(index)[0])
            cycle_orig = cycle
            #print(cycle)
            normal = generate_normals_new(atoms,cycle_orig,nl,surface_mask)
            #print(cycle,normal)
        for index in cycle:
            neighbors = len(nl.get_neighbors(index)[0])
            normal += normals[index] * (1/neighbors)
        normal = normalize(normal)

        if coordination ==2:
            average[2] = average[2] - 0.5
        if coordination == 3:
            average[2] = average[2] -0.7
            #print(average)
            #print(average[2])
        site_ads =Site(cycle=cycle, position=average, normal=normal)
        sites.append(site_ads)
        
    return sites
Exemple #28
0
def CoreShellCN(atoms, type_a, type_b, ratio, R_min = 1.5, CN_max=12, n_depth=-1):
    r"""This routine generates cluster with ideal core-shell architecture,
    so that atoms of type_a are placed on the surface
    and atoms of type_b are forming the core of nanoparticle.
    The 'surface' of nanoparticle is defined as atoms
    with unfinished first coordination shell.
      Used algorithm without need for explicit knowledge of
    far coordination shells parameters, as it was required in CoreShellFCC(..)

    Parameters
    ----------
    atoms: ase.Atoms
        ase Atoms object, containing atomic cluster.
    type_a: string
        Symbol of chemical element to be placed on the shell.
    type_b: string
        Symbol of chemical element to be placed in the core.
    ratio: float
        Guards the number of shell atoms, type_a:type_b = ratio:(1-ratio)
    R_min: float
        Typical bond length. Neighboring atoms within this value are counted
        as coordination numbers.
        Default is 3.0.
    CN_max: float
        Maximum possible coordination number (bulk coordination number).
        Default is 12.
    n_depth: int
        Number of layers of the shell formed by atoms ratio.
        Default value -1 is ignored and n_depth is calculated according
        ratio. If n_depth is set then value of ratio is ignored.

    Returns
    -------
        Function returns ASE atoms object which
        contains bimetallic core-shell cluster

    Example
    --------
    >>> atoms = FaceCenteredCubic('Ag',
      [(1, 0, 0), (1, 1, 0), (1, 1, 1)], [7,8,7], 4.09)
    >>> atoms = CoreShellCN(atoms, 'Pt', 'Ag', 0.5)
    >>> view(atoms)
    """
    # 0 < ratio < 1
    target_x = ratio
    if n_depth != -1:
        target_x = 1

    n_atoms = len(atoms)
    n_a = (np.array(atoms.get_chemical_symbols()) == type_a).sum()
    #n_b = (atoms.get_chemical_symbols() == type_b).sum()
    #print n_a

    n_shell = 0  # depth of the shell
    while (n_a < n_atoms * target_x):
        n_shell += 1
        print ("shell: ", n_shell)

        if (n_depth != -1)and(n_shell > n_depth):
            break

        neiblist = NeighborList( [ R_min ] * n_atoms,
          self_interaction=False, bothways=True )
        neiblist.build( atoms )

        for i in xrange( n_atoms ):
            indeces, offsets = neiblist.get_neighbors(i)
            if (atoms[i].symbol == type_b):
                CN_temp = 0
                for ii in indeces:
                    if atoms[ii].symbol == type_b:
                        CN_temp += 1
                #print "CN_temp: ", CN_temp
                if (CN_temp < CN_max):
                    # coord shell is not full, swap type to type_a!
                    atoms[i].tag = n_shell # not swap yet, but mark

        # swap atom types now. Stop if target ratio achieved
        for atom in atoms:
            if (atom.tag > 0)&(atom.symbol == type_b):
                if n_a < n_atoms * target_x:
                    atom.symbol = type_a
                    n_a += 1
                    #print "n_A: ", n_a
    # end while

    # check number of atoms
    checkn_a = 0
    for element in atoms.get_chemical_symbols():
        if element == type_a:
            checkn_a += 1
    #print "Should be equal: ", n_a, checkn_a
    assert n_a == checkn_a
    return atoms
Exemple #29
0
def test_nl(atoms, cutoff):
    cutoffs = [cutoff/2]*len(atoms)
    nl = NeighborList(cutoffs, skin=0, bothways=True, self_interaction=False)
    nl.update(atoms)
    for i in range(len(atoms)):
        (x, _) = nl.get_neighbors(i)
Exemple #30
0
class EMT(Calculator):
    """Python implementation of the Effective Medium Potential.

    Supports the following standard EMT metals:
    Al, Cu, Ag, Au, Ni, Pd and Pt.

    In addition, the following elements are supported.
    They are NOT well described by EMT, and the parameters
    are not for any serious use:
    H, C, N, O

    The potential takes a single argument, ``asap_cutoff``
    (default: False).  If set to True, the cutoff mimics
    how Asap does it; most importantly the global cutoff
    is chosen from the largest atom present in the simulation,
    if False it is chosen from the largest atom in the parameter
    table.  True gives the behaviour of the Asap code and
    older EMT implementations, although the results are not
    bitwise identical.
    """
    implemented_properties = [
        'energy', 'forces', 'stress', 'magmom', 'magmoms'
    ]

    nolabel = True

    default_parameters = {'asap_cutoff': False}

    def __init__(self, **kwargs):
        Calculator.__init__(self, **kwargs)

    def initialize(self, atoms):
        self.par = {}
        self.rc = 0.0
        self.numbers = atoms.get_atomic_numbers()
        if self.parameters.asap_cutoff:
            relevant_pars = {}
            for symb, p in parameters.items():
                if atomic_numbers[symb] in self.numbers:
                    relevant_pars[symb] = p
        else:
            relevant_pars = parameters
        maxseq = max(par[1] for par in relevant_pars.values()) * Bohr
        rc = self.rc = beta * maxseq * 0.5 * (sqrt(3) + sqrt(4))
        rr = rc * 2 * sqrt(4) / (sqrt(3) + sqrt(4))
        self.acut = np.log(9999.0) / (rr - rc)
        if self.parameters.asap_cutoff:
            self.rc_list = self.rc * 1.045
        else:
            self.rc_list = self.rc + 0.5
        for Z in self.numbers:
            if Z not in self.par:
                sym = chemical_symbols[Z]
                if sym not in parameters:
                    raise NotImplementedError(
                        'No EMT-potential for {0}'.format(sym))
                p = parameters[sym]
                s0 = p[1] * Bohr
                eta2 = p[3] / Bohr
                kappa = p[4] / Bohr
                x = eta2 * beta * s0
                gamma1 = 0.0
                gamma2 = 0.0
                for i, n in enumerate([12, 6, 24]):
                    r = s0 * beta * sqrt(i + 1)
                    x = n / (12 * (1.0 + exp(self.acut * (r - rc))))
                    gamma1 += x * exp(-eta2 * (r - beta * s0))
                    gamma2 += x * exp(-kappa / beta * (r - beta * s0))

                self.par[Z] = {
                    'E0': p[0],
                    's0': s0,
                    'V0': p[2],
                    'eta2': eta2,
                    'kappa': kappa,
                    'lambda': p[5] / Bohr,
                    'n0': p[6] / Bohr**3,
                    'rc': rc,
                    'gamma1': gamma1,
                    'gamma2': gamma2
                }

        self.ksi = {}
        for s1, p1 in self.par.items():
            self.ksi[s1] = {}
            for s2, p2 in self.par.items():
                self.ksi[s1][s2] = p2['n0'] / p1['n0']

        self.forces = np.empty((len(atoms), 3))
        self.stress = np.empty((3, 3))
        self.sigma1 = np.empty(len(atoms))
        self.deds = np.empty(len(atoms))

        self.nl = NeighborList([0.5 * self.rc_list] * len(atoms),
                               self_interaction=False)

    def calculate(self,
                  atoms=None,
                  properties=['energy'],
                  system_changes=all_changes):
        Calculator.calculate(self, atoms, properties, system_changes)

        if 'numbers' in system_changes:
            self.initialize(self.atoms)

        positions = self.atoms.positions
        numbers = self.atoms.numbers
        cell = self.atoms.cell

        self.nl.update(self.atoms)

        self.energy = 0.0
        self.sigma1[:] = 0.0
        self.forces[:] = 0.0
        self.stress[:] = 0.0

        natoms = len(self.atoms)

        for a1 in range(natoms):
            Z1 = numbers[a1]
            p1 = self.par[Z1]
            ksi = self.ksi[Z1]
            neighbors, offsets = self.nl.get_neighbors(a1)
            offsets = np.dot(offsets, cell)
            for a2, offset in zip(neighbors, offsets):
                d = positions[a2] + offset - positions[a1]
                r = sqrt(np.dot(d, d))
                if r < self.rc_list:
                    Z2 = numbers[a2]
                    p2 = self.par[Z2]
                    self.interact1(a1, a2, d, r, p1, p2, ksi[Z2])

        for a in range(natoms):
            Z = numbers[a]
            p = self.par[Z]
            try:
                ds = -log(self.sigma1[a] / 12) / (beta * p['eta2'])
            except (OverflowError, ValueError):
                self.deds[a] = 0.0
                self.energy -= p['E0']
                continue
            x = p['lambda'] * ds
            y = exp(-x)
            z = 6 * p['V0'] * exp(-p['kappa'] * ds)
            self.deds[a] = ((x * y * p['E0'] * p['lambda'] + p['kappa'] * z) /
                            (self.sigma1[a] * beta * p['eta2']))
            self.energy += p['E0'] * ((1 + x) * y - 1) + z

        for a1 in range(natoms):
            Z1 = numbers[a1]
            p1 = self.par[Z1]
            ksi = self.ksi[Z1]
            neighbors, offsets = self.nl.get_neighbors(a1)
            offsets = np.dot(offsets, cell)
            for a2, offset in zip(neighbors, offsets):
                d = positions[a2] + offset - positions[a1]
                r = sqrt(np.dot(d, d))
                if r < self.rc_list:
                    Z2 = numbers[a2]
                    p2 = self.par[Z2]
                    self.interact2(a1, a2, d, r, p1, p2, ksi[Z2])

        self.results['energy'] = self.energy
        self.results['free_energy'] = self.energy
        self.results['forces'] = self.forces

        if 'stress' in properties:
            if self.atoms.number_of_lattice_vectors == 3:
                self.stress += self.stress.T.copy()
                self.stress *= -0.5 / self.atoms.get_volume()
                self.results['stress'] = self.stress.flat[[0, 4, 8, 5, 2, 1]]
            else:
                raise PropertyNotImplementedError

    def interact1(self, a1, a2, d, r, p1, p2, ksi):
        x = exp(self.acut * (r - self.rc))
        theta = 1.0 / (1.0 + x)
        y1 = (0.5 * p1['V0'] * exp(-p2['kappa'] * (r / beta - p2['s0'])) *
              ksi / p1['gamma2'] * theta)
        y2 = (0.5 * p2['V0'] * exp(-p1['kappa'] * (r / beta - p1['s0'])) /
              ksi / p2['gamma2'] * theta)
        self.energy -= y1 + y2
        f = ((y1 * p2['kappa'] + y2 * p1['kappa']) / beta +
             (y1 + y2) * self.acut * theta * x) * d / r
        self.forces[a1] += f
        self.forces[a2] -= f
        self.stress -= np.outer(f, d)
        self.sigma1[a1] += (exp(-p2['eta2'] * (r - beta * p2['s0'])) * ksi *
                            theta / p1['gamma1'])
        self.sigma1[a2] += (exp(-p1['eta2'] * (r - beta * p1['s0'])) / ksi *
                            theta / p2['gamma1'])

    def interact2(self, a1, a2, d, r, p1, p2, ksi):
        x = exp(self.acut * (r - self.rc))
        theta = 1.0 / (1.0 + x)
        y1 = (exp(-p2['eta2'] * (r - beta * p2['s0'])) * ksi / p1['gamma1'] *
              theta * self.deds[a1])
        y2 = (exp(-p1['eta2'] * (r - beta * p1['s0'])) / ksi / p2['gamma1'] *
              theta * self.deds[a2])
        f = ((y1 * p2['eta2'] + y2 * p1['eta2']) +
             (y1 + y2) * self.acut * theta * x) * d / r
        self.forces[a1] -= f
        self.forces[a2] += f
        self.stress += np.outer(f, d)
Exemple #31
0
    d = 0.0
    for a in range(len(atoms)):
        i, offsets = nl.get_neighbors(a)
        for j in i:
            c[j] += 1
        c[a] += len(i)
        d += (((R[i] + np.dot(offsets, cell) - R[a])**2).sum(1)**0.5).sum()
    return d, c

for sorted in [False, True]:
    for p1 in range(2):
        for p2 in range(2):
            for p3 in range(2):
                # print(p1, p2, p3)
                atoms.set_pbc((p1, p2, p3))
                nl = NeighborList(atoms.numbers * 0.2 + 0.5,
                                  skin=0.0, sorted=sorted)
                nl.update(atoms)
                d, c = count(nl, atoms)
                atoms2 = atoms.repeat((p1 + 1, p2 + 1, p3 + 1))
                nl2 = NeighborList(atoms2.numbers * 0.2 + 0.5,
                                   skin=0.0, sorted=sorted)
                nl2.update(atoms2)
                d2, c2 = count(nl2, atoms2)
                c2.shape = (-1, 10)
                dd = d * (p1 + 1) * (p2 + 1) * (p3 + 1) - d2
                # print(dd)
                # print(c2 - c)
                assert abs(dd) < 1e-10
                assert not (c2 - c).any()

h2 = Atoms('H2', positions=[(0, 0, 0), (0, 0, 1)])
Exemple #32
0
    def calculate(
        self,
        atoms=None,
        properties=None,
        system_changes=all_changes,
    ):
        if properties is None:
            properties = self.implemented_properties

        Calculator.calculate(self, atoms, properties, system_changes)

        natoms = len(self.atoms)

        sigma = self.parameters.sigma
        epsilon = self.parameters.epsilon
        rc = self.parameters.rc

        if self.nl is None or 'numbers' in system_changes:
            self.nl = NeighborList([rc / 2] * natoms, self_interaction=False)

        self.nl.update(self.atoms)

        positions = self.atoms.positions
        cell = self.atoms.cell

        # potential value at rc
        e0 = 4 * epsilon * ((sigma / rc)**12 - (sigma / rc)**6)

        atomic_energies = np.zeros(natoms)
        forces = np.zeros((natoms, 3))
        stresses = np.zeros((natoms, 3, 3))

        for ii in range(natoms):
            neighbors, offsets = self.nl.get_neighbors(ii)
            cells = np.dot(offsets, cell)

            # pointing *towards* neighbours
            distance_vectors = positions[neighbors] + cells - positions[ii]

            r2 = (distance_vectors**2).sum(1)
            c6 = (sigma**2 / r2)**3
            c6[r2 > rc**2] = 0.0
            c12 = c6**2

            pairwise_energies = 4 * epsilon * (c12 - c6) - e0 * (c6 != 0.0)
            atomic_energies[ii] += 0.5 * pairwise_energies.sum(
            )  # atomic energies

            pairwise_forces = (-24 * epsilon * (2 * c12 - c6) /
                               r2)[:, np.newaxis] * distance_vectors

            forces[ii] += pairwise_forces.sum(axis=0)
            stresses[ii] += 0.5 * np.dot(
                pairwise_forces.T,
                distance_vectors)  # equivalent to outer product

            # add j < i contributions
            for jj, atom_j in enumerate(neighbors):
                atomic_energies[atom_j] += 0.5 * pairwise_energies[jj]
                forces[atom_j] += -pairwise_forces[jj]  # f_ji = - f_ij
                stresses[atom_j] += 0.5 * np.outer(pairwise_forces[jj],
                                                   distance_vectors[jj])

        # no lattice, no stress
        if self.atoms.number_of_lattice_vectors == 3:
            stresses = full_3x3_to_voigt_6_stress(stresses)
            self.results['stress'] = (stresses.sum(axis=0) /
                                      self.atoms.get_volume())
            self.results['stresses'] = stresses / self.atoms.get_volume()

        energy = atomic_energies.sum()
        self.results['energy'] = energy
        self.results['atomic_energies'] = atomic_energies

        self.results['free_energy'] = energy

        self.results['forces'] = forces
Exemple #33
0
    def calculate(self, crystal, ids=None):
        """Calculate and return the EAD.
        
        Parameters
        ----------
        crystal: object
            ASE Structure object.
        ids: list
            A list of the centered atoms to be computed
            if None, all atoms will be considered
        
        Returns
        -------
        d: dict
            The user-defined EAD that represent the crystal.
            d = {'x': [N, d], 'dxdr': [N, layer, d, 3], 'rdxdr': [N, layer, d, 3, 3],
            'elements': list of elements}
        """
        self.crystal = crystal
        self.total_atoms = len(crystal)  # total atoms in the unit cell
        vol = crystal.get_volume()

        rc = [self.Rc / 2.] * self.total_atoms
        neighbors = NeighborList(rc, self_interaction=False, bothways=True, skin=0.)
        neighbors.update(crystal)

        unique_N = 0
        for i in range(self.total_atoms):
            indices, offsets = neighbors.get_neighbors(i)
            ith = 0
            if i not in indices:
                ith += 1
            unique_N += len(np.unique(indices)) + ith  # +1 is for i

        # Make numpy array here
        self.d = {'x': np.zeros([self.total_atoms, self.dsize]), 'elements': []}
        if self.derivative:
            self.d['dxdr'] = np.zeros([unique_N, self.dsize, 3])
            self.d['seq'] = np.zeros([unique_N, 2], dtype=int)
        if self.stress:
            self.d['rdxdr'] = np.zeros([unique_N, self.dsize, 3, 3])

        seq_count = 0

        if ids is None:
            ids = range(len(crystal))

        for i in ids:  # range(self.total_atoms):
            element = crystal.get_chemical_symbols()[i]
            indices, offsets = neighbors.get_neighbors(i)
            Z = []  # atomic numbers of neighbors

            assert len(indices) > 0, \
                f"There's no neighbor for this structure at Rc = {self.Rc} A."

            Ri = crystal.get_positions()[i]
            total_neighbors = len(indices)

            Rj = np.zeros([total_neighbors, 3])
            IDs = np.zeros(total_neighbors, dtype=int)

            count = 0
            for j, offset in zip(indices, offsets):
                Rj[count, :] = crystal.positions[j] + np.dot(offset, crystal.get_cell())
                IDs[count] = j
                Z.append(crystal[j].number)
                count += 1
            Z = np.array(Z)

            Rij = Rj - Ri
            Dij = np.sqrt(np.sum(Rij ** 2, axis=1))

            d = calculate_eamd(i, self.total_atoms, Rij, Dij, Z, IDs, self.Rc,
                               self.parameters, self.derivative, self.stress)

            self.d['x'][i] = d['x']
            if self.derivative:
                n_seq = len(d['seq'])
                self.d['dxdr'][seq_count:seq_count + n_seq] = d['dxdr']
                self.d['seq'][seq_count:seq_count + n_seq] = d['seq']
            if self.stress:
                self.d['rdxdr'][seq_count:seq_count + n_seq] = -d['rdxdr'] / vol

            if self.derivative:
                seq_count += n_seq

            self.d['elements'].append(element)

        return self.d
Exemple #34
0
def get_neighbours(atoms, r_cut, self_interaction=False):
    """Return a list of pairs of atoms within a given distance of each other.

    If matscipy can be imported, then this will directly call matscipy's
    neighbourlist function. Otherwise it will use ASE's NeighborList object.

    Args:
        atoms: ase.atoms object to calculate neighbours for
        r_cut: cutoff radius (float). Pairs of atoms are considered neighbours
            if they are within a distance r_cut of each other (note that this
            is double the parameter used in the ASE's neighborlist module)

    Returns: a tuple (i_list, j_list, d_list, fixed_atoms):
        i_list, j_list: i and j indices of each neighbour pair
        d_list: absolute distance between the corresponding pair
        fixed_atoms: indices of any fixed atoms
    """

    if isinstance(atoms, Filter):
        atoms = atoms.atoms

    if have_matscipy:
        i_list, j_list, d_list = neighbour_list('ijd', atoms, r_cut)
    else:

        radii = [r_cut / 2 for i in range(len(atoms))]
        nl = NeighborList(radii,
                          sorted=False,
                          self_interaction=False,
                          bothways=True)
        nl.update(atoms)
        i_list = []
        j_list = []
        d_list = []

        for i, atom in enumerate(atoms):
            posn_i = atom.position
            indices, offsets = nl.get_neighbors(i)
            assert len(indices) == len(offsets)
            for j, offset in zip(indices, offsets):
                # Offsets represent how far away an atom is from its pair in terms
                # of the repeating cell - for instance, an atom i might be in cell
                # (0, 0, 0) while the neighbouring atom j is in cell (0, 1, 1). To
                # get the true position we have to correct for the offset:
                posn_j = atoms.positions[j] + np.dot(offset, atoms.get_cell())
                distance = np.sqrt(((posn_j - posn_i)**2).sum())
                i_list.append(i)
                j_list.append(j)
                d_list.append(distance)

        i_list = np.array(i_list)
        j_list = np.array(j_list)
        d_list = np.array(d_list)

    # filter out self-interactions (across PBC)
    if not self_interaction:
        mask = i_list != j_list
        i_list = i_list[mask]
        j_list = j_list[mask]
        d_list = d_list[mask]

    # filter out bonds where 1st atom (i) in pair is fixed
    fixed_atoms = []
    for constraint in atoms.constraints:
        if isinstance(constraint, FixAtoms):
            fixed_atoms.extend(list(constraint.index))

    return i_list, j_list, d_list, fixed_atoms
Exemple #35
0
    def biatomic(self, A, B, R1=3.0, calc_energy=False):
        r"""This routine analyzes atomic structure
        by the calculation of coordination numbers
        in cluster with atoms of two types (A and B).

        Parameters
        ----------
        A: string
            atom type, like 'Ag', 'Pt', etc.
        B: string
            atom type, like 'Ag', 'Pt', etc.
        R1: float
            First coordination shell will icnlude all atoms
            with distance less then R1 [Angstrom].
            Default value is 3.
        calc_energy: bool
            Flag used for calculation of potential energy with EMT
            calculator.  The default value is False, so that
            energy is not calculated.

        Returns
        -------
        N: int
            number of atoms in cluster
        nA:
            number of atoms of type A
        R: float
            radius of the cluster
        CN_AA: float
            average number of atoms A around atom A
        CN_AB: float
            average number of atoms A around atom B
        CN_BB: float
            average number of atoms B around atom B
        CN_BA: float
            average number of atoms B around atom A
        etha: float
            parameter of local ordering, -1 < etha < 1.
            Returns 999 if concentration of one of the
            component is too low.
        E: float
            potential energy
        NAcore:
            number of A atoms in core
        NBcore:
            number of B atoms in core
        CNshellAA:
            average CN of A-A for surface atoms only
        CNshellAB:
            average CN of A-B for surface atoms only
        CNshellBB:
            average CN of B-B for surface atoms only
        CNshellBA:
            average CN of B-A for surface atoms only

        Notes
        -----
            The radius of the cluster is roughly determined as
            maximum the distance from the center to most distant atom
            in the cluster.

        Example
        --------
        >>> atoms = FaceCenteredCubic('Ag',
          [(1, 0, 0), (1, 1, 0), (1, 1, 1)], [7,8,7], 4.09)
        >>> atoms = CoreShellFCC(atoms, 'Pt', 'Ag', 0.6, 4.09)
        >>> [N, nA, R, CN_AA, CN_AB, CN_BB, CN_BA, etha] =
          biatomic(atoms, 'Pt', 'Ag')
        >>> print "Short range order parameter: ", etha
        """
        self.chems = [A, B] # for now used for report only

        N = len(self.atoms)
        nA = 0
        nB = 0
        for element in self.atoms.get_chemical_symbols():
            if element == A:
                nA += 1
            elif element == B:
                nB += 1
            else:
                raise Exception('Extra element ' + element)
        if (nA + nB != N):
            raise Exception('Number of A (' + str(nA) + ') ' +
              'and B (' + str(nB) + ') artoms mismatch!')
        nl = NeighborList([0.5 * R1] * N, self_interaction=False, bothways=True)
        nl.build(self.atoms)

        # initialize counters:
        CN_AA = 0    # averaged total coord. numbers
        CN_AB = 0
        CN_BB = 0
        CN_BA = 0
        NAcore = 0  # number of atoms in core region
        NBcore = 0
        CNshellAA = 0  # average coord. numbers for surface atoms
        CNshellAB = 0
        CNshellBB = 0
        CNshellBA = 0
        for iatom in xrange(0, N):
            #print "central atom index:", iatom, " kind: ", self.atoms[iatom].symbol
            indeces, offsets = nl.get_neighbors(iatom)
            if self.atoms[iatom].symbol == B:
                CN_BB_temp = 0
                CN_BA_temp = 0
                for ii in indeces:
                    #print "neighbor atom index:", ii, " kind: ", self.atoms[ii].symbol
                    if self.atoms[ii].symbol == B:
                        CN_BB_temp += 1
                    elif  self.atoms[ii].symbol == A:
                        CN_BA_temp += 1
                    else:
                        print("Warning: unknown atom type %s. It will not be counted!"%self.atoms[ii].symbol)
                CN_BB += CN_BB_temp
                CN_BA += CN_BA_temp
                if len(indeces) < 12:
                    # SHELL
                    CNshellBB += CN_BB_temp
                    CNshellBA += CN_BA_temp
                else:
                    # CORE
                    NBcore += 1
            elif self.atoms[iatom].symbol == A:
                CN_AA_temp = 0
                CN_AB_temp = 0
                for i in indeces:
                    #print "neighbor atom index:", i, " kind: ", self.atoms[i].symbol
                    if self.atoms[i].symbol == A:
                        CN_AA_temp += 1
                    elif self.atoms[i].symbol == B:
                        CN_AB_temp += 1
                    else:
                        print("Warning: unknown atom type %s. It will not be counted!"%self.atoms[i].symbol)
                CN_AA += CN_AA_temp
                CN_AB += CN_AB_temp
                if len(indeces) < 12:
                    # SHELL
                    CNshellAA += CN_AA_temp
                    CNshellAB += CN_AB_temp
                else:
                    # CORE
                    NAcore += 1
            else:
                #raise Exception("Un")
                print("Warning: unknown atom type %s. It will not be counted!"%self.atoms[iatom].symbol)
        # averaging:
        CN_AA = CN_AA * 1.0 / nA
        CN_AB = CN_AB * 1.0 / nA
        CN_BB = CN_BB * 1.0 / nB
        CN_BA = CN_BA * 1.0 / nB
        znam = (nA - NAcore)
        if znam > 0.0001:
            CNshellAA = CNshellAA * 1.0 / znam
            CNshellAB = CNshellAB * 1.0 / znam
        else:
            CNshellAA = 0
            CNshellAB = 0
        znam = (nB - NBcore)
        if znam > 0.0001:
            CNshellBB = CNshellBB * 1.0 / znam
            CNshellBA = CNshellBA * 1.0 / znam
        else:
            CNshellBB = 0
            CNshellBA = 0

        # calc concentrations:
        concB = nB * 1.0 / N
        znam = concB * (CN_AA + CN_AB)
        if znam < 0.0001:
            #print "WARNING! Too low B concentration: ",concB
            etha = 999
        else:
            etha = 1 - CN_AB / znam
        R = self.atoms.positions.max() / 2.0
        if calc_energy:
            #from asap3 import EMT
            from ase.calculators.emt import EMT
            self.atoms.set_calculator(EMT())
            E = self.atoms.get_potential_energy()
        else:
            E = -1
        #return N, nA, R, CN_AA, CN_AB, CN_BB, CN_BA, etha, E, NAcore, \
        #  NBcore, CNshellAA, CNshellAB, CNshellBB, CNshellBA
        self.N = N
        self.nA = nA
        self.R = R
        self.CN_AA = CN_AA  #TODO: use only arrays of CNs
        self.CN_AB = CN_AB
        self.CN_BB = CN_BB
        self.CN_BA = CN_BA
        self.CNs = np.array([ [CN_AA, CN_AB], [CN_BA, CN_BB] ])
        self.etha = etha
        self.E = E
        self.NAcore = NAcore
        self.NBcore = NBcore
        self.CNshellAA = CNshellAA
        self.CNshellAB = CNshellAB
        self.CNshellBB = CNshellBB
        self.CNshellBA = CNshellBA
def find_layers(  # pylint: disable=too-many-locals,too-many-statements,too-many-branches
        asecell,
        factor=1.1):
    """
    Obtains all subunits of a given structure by looking
    at the connectivity of the bonds.

    :param asecell: the bulk unit cell (in ase.Atoms format)
    :param factor: the skin factor
    :return: a tuple with a boolean indicating if the material is layered, a list of layers in the structure (ase format),
        a list of indices of the atoms in each layer, and a rotated bulk ASE cell (with stacking axis along z).
        MOREOVER, it 1) returns layers ordered by stacking index and 2) makes sure the layer is connected when 
        removing the PBC along the third (stacking) axis.
    """
    tol = 1.0e-6
    nl = NeighborList(
        factor * get_covalent_radii_array(asecell),
        bothways=True,
        self_interaction=False,
        skin=0.0,
    )
    nl.update(asecell)
    vector1, vector2, vector3 = asecell.cell
    is_layered = True
    layer_structures = []
    layer_indices = []
    visited = []
    aselayer = None
    final_layered_structures = None

    # Loop over atoms (idx: atom index)
    for idx in range(len(asecell)):  # pylint: disable=too-many-nested-blocks
        # Will contain the indices of the atoms in the "current" layer
        layer = []
        # Check if I already visited this atom
        if idx not in visited:
            # Update 'layer' and 'visited'
            check_neighbors(idx, nl, asecell, visited, layer)
            aselayer = asecell.copy()[layer]
            layer_nl = NeighborList(
                factor * get_covalent_radii_array(aselayer),
                bothways=True,
                self_interaction=False,
                skin=0.0,
            )
            layer_nl.update(aselayer)
            # We search for the periodic images of the first atom (idx=0)
            # that are connected to at least one atom of the connected layer
            neigh_vec = []
            for idx2 in range(len(aselayer)):
                _, offsets = layer_nl.get_neighbors(idx2)
                for offset in offsets:
                    if not all(offset == [0, 0, 0]):
                        neigh_vec.append(offset)
            # We define the dimensionality as the rank
            dim = np.linalg.matrix_rank(neigh_vec)
            if dim == 2:
                cell = asecell.cell
                vectors = list(np.dot(neigh_vec, cell))
                iv = shortest_vector_index(vectors)
                vector1 = vectors.pop(iv)
                iv = shortest_vector_index(vectors)
                vector2 = vectors.pop(iv)
                vector3 = np.cross(vector1, vector2)
                while np.linalg.norm(vector3) < tol:
                    iv = shortest_vector_index(vectors)
                    vector2 = vectors.pop(iv)
                    vector3 = np.cross(vector1, vector2)
                vector1, vector2 = gauss_reduce(vector1, vector2)
                vector3 = np.cross(vector1, vector2)
                aselayer = _update_and_rotate_cell(
                    aselayer, [vector1, vector2, vector3],
                    [list(range(len(aselayer)))])
                disconnected = []
                for i in range(-3, 4):
                    for j in range(-3, 4):
                        for k in range(-3, 4):
                            vector = i * cell[0] + j * cell[1] + k * cell[2]
                            if np.dot(vector3, vector) > tol:
                                disconnected.append(vector)
                iv = shortest_vector_index(disconnected)
                vector3 = disconnected[iv]
                layer_structures.append(aselayer)
                layer_indices.append(layer)
            else:
                is_layered = False
    if is_layered:
        newcell = [vector1, vector2, vector3]
        if abs(np.linalg.det(newcell) / np.linalg.det(cell) - 1.0) > 1e-3:
            raise ValueError(
                "An error occurred. The new cell after rotation has a different volume than the original cell"
            )
        rotated_asecell = _update_and_rotate_cell(asecell, newcell,
                                                  layer_indices)
        # Re-order layers according to their projection
        # on the stacking direction
        vert_direction = np.cross(rotated_asecell.cell[0],
                                  rotated_asecell.cell[1])
        vert_direction /= np.linalg.norm(vert_direction)
        stack_proj = [
            np.dot(layer.positions, vert_direction).mean()
            for layer in [rotated_asecell[il] for il in layer_indices]
        ]
        stack_order = np.argsort(stack_proj)
        # order layers with increasing coordinate along the stacking direction
        layer_indices = [layer_indices[il] for il in stack_order]

        # Move the atoms along the third lattice vector so that
        # the first layer has zero projection along the vertical direction
        trans_vector = -(stack_proj[stack_order[0]] / np.dot(
            vert_direction, rotated_asecell.cell[2]) * rotated_asecell.cell[2])
        rotated_asecell.translate(trans_vector)

        # I don't return the 'layer_structures' because there the atoms are moved
        # from their positions and the z axis lenght might not be appropriate
        final_layered_structures = [
            rotated_asecell[this_layer_indices]
            for this_layer_indices in layer_indices
        ]
    else:
        rotated_asecell = None

    if not is_layered:
        aselayer = None
    return is_layered, final_layered_structures, layer_indices, rotated_asecell
Exemple #37
0
    def monoatomic(self, R1=3, calc_energy=False):
        r"""This routine analyzes atomic structure
        by the calculation of coordination numbers
        in cluster with only one type of atom.

        Parameters
        ----------
        R1: float
            First coordination shell will icnlude all atoms
            with distance less then R1 [Angstrom].
            Default value is 3.
        calc_energy: bool
            Flag used for calculation of potential energy with EMT
            calculator.  The default value is False, so that
            energy is not calculated.

        Returns
        -------
        N: int
            number of atoms in cluster
        R: float
            radius of the cluster
        CN: float
            average coord number
        E: float
            potential energy, -1 if calc_energy is False
        Ncore:
            number of atoms in core region (number of atoms with
            all 12 neighbors)
        CNshell:
            average coordination number for surface atoms only

        Notes
        -----
            The radius of the cluster is roughly determined as
            maximum the distance from the center to most distant atom
            in the cluster.

        Example
        --------
        >>> atoms = FaceCenteredCubic('Ag',
          [(1, 0, 0), (1, 1, 0), (1, 1, 1)], [7,8,7], 4.09)
        >>> qsar = QSAR(atoms)
        >>> qsar.monoatomic(R1=3.0)
        >>> print "average CN is ", qsar.CN
        """
        self.chems = ['*'] # any element. For now used for report only

        N = len(self.atoms)
        nl = NeighborList( [0.5 * R1] * N, self_interaction=False, bothways=True )
        nl.build(self.atoms)
        CN = 0
        Ncore = 0
        Nshell = 0
        CNshell = 0  # average CN of surface atoms
        for i in xrange(0, N):
            indeces, offsets = nl.get_neighbors(i)
            CN += len(indeces)
            if len(indeces) < 12:
                Nshell += 1
                CNshell += len(indeces)
            else:
                Ncore += 1
        CN = CN * 1.0 / N
        CNshell = CNshell * 1.0 / Nshell
        #atoms.center()
        R = self.atoms.positions.max() / 2.0
        if calc_energy:
            #from asap3 import EMT
            from ase.calculators.emt import EMT
            atoms.set_calculator(EMT())
            E = atoms.get_potential_energy()
        else:
            E = -1
        #return N, R, CN, E, Ncore, CNshell
        self.N = N #TODO: use array property CNs
        self.R = R
        self.CN = CN
        self.CNs = np.array([[CN]])
        self.E = E
        self.Ncore = Ncore
        self.CNshell = CNshell
Exemple #38
0
 def __init__(self, bopatoms: BOPAtoms, cutoffs: list, **kwargs):
     self.bopatoms = bopatoms
     self.nl = NeighborList(cutoffs=cutoffs, bothways=True, self_interaction=False, **kwargs)
     self.nl.update(bopatoms)
Exemple #39
0
class EAM(Calculator):
    r"""

    EAM Interface Documentation

Introduction
============

The Embedded Atom Method (EAM) [1]_ is a classical potential which is
good for modelling metals, particularly fcc materials. Because it is
an equiaxial potential the EAM does not model directional bonds
well. However, the Angular Dependent Potential (ADP) [2]_ which is an
extended version of EAM is able to model directional bonds and is also
included in the EAM calculator.

Generally all that is required to use this calculator is to supply a
potential file or as a set of functions that describe the potential.
The files containing the potentials for this calculator are not
included but many suitable potentials can be downloaded from The
Interatomic Potentials Repository Project at
http://www.ctcms.nist.gov/potentials/

Theory
======

A single element EAM potential is defined by three functions: the
embedded energy, electron density and the pair potential.  A two
element alloy contains the individual three functions for each element
plus cross pair interactions.  The ADP potential has two additional
sets of data to define the dipole and quadrupole directional terms for
each alloy and their cross interactions.

The total energy `E_{\rm tot}` of an arbitrary arrangement of atoms is
given by the EAM potential as

.. math::
   E_\text{tot} = \sum_i F(\bar\rho_i) + \frac{1}{2}\sum_{i\ne j} \phi(r_{ij})

and

.. math::
   \bar\rho_i = \sum_j \rho(r_{ij})

where `F` is an embedding function, namely the energy to embed an atom `i` in
the combined electron density `\bar\rho_i` which is contributed from
each of its neighbouring atoms `j` by an amount `\rho(r_{ij})`,
`\phi(r_{ij})` is the pair potential function representing the energy
in bond `ij` which is due to the short-range electro-static
interaction between atoms, and `r_{ij}` is the distance between an
atom and its neighbour for that bond.

The ADP potential is defined as

.. math::
   E_\text{tot} = \sum_i F(\bar\rho_i) + \frac{1}{2}\sum_{i\ne j} \phi(r_{ij})
   + \frac{1}{2} \sum_{i,\alpha} (\mu_i^\alpha)^2
   + \frac{1}{2} \sum_{i,\alpha,\beta} (\lambda_i^{\alpha\beta})^2
   - \frac{1}{6} \sum_i \nu_i^2

where `\mu_i^\alpha` is the dipole vector, `\lambda_i^{\alpha\beta}`
is the quadrupole tensor and `\nu_i` is the trace of
`\lambda_i^{\alpha\beta}`.

Running the Calculator
======================

EAM calculates the cohesive atom energy and forces. Internally the
potential functions are defined by splines which may be directly
supplied or created by reading the spline points from a data file from
which a spline function is created.  The LAMMPS compatible ``.alloy``
and ``.adp`` formats are supported. The LAMMPS ``.eam`` format is
slightly different from the ``.alloy`` format and is currently not
supported.

For example::

    from ase.calculators.eam import EAM

    mishin = EAM(potential='Al99.eam.alloy')
    mishin.write_potential('new.eam.alloy')
    mishin.plot()

    slab.set_calculator(mishin)
    slab.get_potential_energy()
    slab.get_forces()

The breakdown of energy contribution from the indvidual components are
stored in the calculator instance ``.results['energy_components']``

Arguments
=========

=========================  ====================================================
Keyword                    Description
=========================  ====================================================
``potential``              file of potential in ``.alloy`` or ``.adp`` format
                           (This is generally all you need to supply)

``elements[N]``            array of N element abbreviations

``embedded_energy[N]``     arrays of embedded energy functions

``electron_density[N]``    arrays of electron density functions

``phi[N,N]``               arrays of pair potential functions

``d_embedded_energy[N]``   arrays of derivative embedded energy functions

``d_electron_density[N]``  arrays of derivative electron density functions

``d_phi[N,N]``             arrays of derivative pair potentials functions

``d[N,N], q[N,N]``         ADP dipole and quadrupole function

``d_d[N,N], d_q[N,N]``     ADP dipole and quadrupole derivative functions

``skin``                   skin distance passed to NeighborList(). If no atom
                           has moved more than the skin-distance since the last
                           call to the ``update()`` method then the neighbor
                           list can be reused. Defaults to 1.0.

``form``                   the form of the potential ``alloy`` or ``adp``. This
                           will be determined from the file suffix or must be
                           set if using equations

=========================  ====================================================


Additional parameters for writing potential files
=================================================

The following parameters are only required for writing a potential in
``.alloy`` or ``.adp`` format file.

=========================  ====================================================
Keyword                    Description
=========================  ====================================================
``header``                 Three line text header. Default is standard message.

``Z[N]``                   Array of atomic number of each element

``mass[N]``                Atomic mass of each element

``a[N]``                   Array of lattice parameters for each element

``lattice[N]``             Lattice type

``nrho``                   No. of rho samples along embedded energy curve

``drho``                   Increment for sampling density

``nr``                     No. of radial points along density and pair
                           potential curves

``dr``                     Increment for sampling radius

=========================  ====================================================

Special features
================

``.plot()``
  Plots the individual functions. This may be called from multiple EAM
  potentials to compare the shape of the individual curves. This
  function requires the installation of the Matplotlib libraries.

Notes/Issues
=============

* Although currently not fast, this calculator can be good for trying
  small calculations or for creating new potentials by matching baseline
  data such as from DFT results. The format for these potentials is
  compatible with LAMMPS_ and so can be used either directly by LAMMPS or
  with the ASE LAMMPS calculator interface.

* Supported formats are the LAMMPS_ ``.alloy`` and ``.adp``. The
  ``.eam`` format is currently not supported. The form of the
  potential will be determined from the file suffix.

* Any supplied values will override values read from the file.

* The derivative functions, if supplied, are only used to calculate
  forces.

* There is a bug in early versions of scipy that will cause eam.py to
  crash when trying to evaluate splines of a potential with one
  neighbor such as caused by evaluating a dimer.

.. _LAMMPS: http://lammps.sandia.gov/

.. [1] M.S. Daw and M.I. Baskes, Phys. Rev. Letters 50 (1983)
       1285.

.. [2] Y. Mishin, M.J. Mehl, and D.A. Papaconstantopoulos,
       Acta Materialia 53 2005 4029--4041.


End EAM Interface Documentation
    """

    implemented_properties = ['energy', 'forces']

    default_parameters = dict(
        skin=1.0,
        potential=None,
        header=[b'EAM/ADP potential file\n',
                b'Generated from eam.py\n',
                b'blank\n'])

    def __init__(self, restart=None, ignore_bad_restart_file=False,
                 label=os.curdir, atoms=None, **kwargs):

        if 'potential' in kwargs:
            self.read_potential(kwargs['potential'])

        Calculator.__init__(self, restart, ignore_bad_restart_file,
                            label, atoms, **kwargs)

        valid_args = ('potential', 'elements', 'header', 'drho', 'dr',
                      'cutoff', 'atomic_number', 'mass', 'a', 'lattice',
                      'embedded_energy', 'electron_density', 'phi',
                      # derivatives
                      'd_embedded_energy', 'd_electron_density', 'd_phi',
                      'd', 'q', 'd_d', 'd_q',  # adp terms
                      'skin', 'form', 'Z', 'nr', 'nrho', 'mass')

        # set any additional keyword arguments
        for arg, val in self.parameters.items():
            if arg in valid_args:
                setattr(self, arg, val)
            else:
                raise RuntimeError('unknown keyword arg "%s" : not in %s'
                                   % (arg, valid_args))

    def set_form(self, fileobj):
        """set the form variable based on the file name suffix"""
        extension = os.path.splitext(fileobj)[1]

        if extension == '.eam':
            self.form = 'eam'
        elif extension == '.alloy':
            self.form = 'alloy'
        elif extension == '.adp':
            self.form = 'adp'
        else:
            raise RuntimeError('unknown file extension type: %s' % extension)

    def read_potential(self, fileobj):
        """Reads a LAMMPS EAM file in alloy or adp format
        and creates the interpolation functions from the data
        """

        if isinstance(fileobj, basestring):
            f = open(fileobj)
            self.set_form(fileobj)
        else:
            f = fileobj
            
        def lines_to_list(lines):
            """Make the data one long line so as not to care how its formatted
            """
            data = []
            for line in lines:
                data.extend(line.split())
            return data

        lines = f.readlines()
        if self.form == 'eam':        # single element eam file (aka funcfl)
                self.header = lines[:1]
                
                data = lines_to_list(lines[1:])
                
                # eam form is just like an alloy form for one element
                
                self.Nelements = 1
                self.Z = np.array([data[0]], dtype=int)
                self.mass = np.array([data[1]])
                self.a = np.array([data[2]])
                self.lattice = [data[3]]
                 
                self.nrho = int(data[4])
                self.drho = float(data[5])
                self.nr = int(data[6])
                self.dr = float(data[7])
                self.cutoff = float(data[8])
                
                n = 9 + self.nrho
                self.embedded_data = np.array([np.float_(data[9:n])])
                
                self.rphi_data = np.zeros([self.Nelements, self.Nelements,
                                           self.nr])
                                
                effective_charge = np.float_(data[n:n + self.nr])
                # convert effective charges to rphi according to
                # http://lammps.sandia.gov/doc/pair_eam.html
                self.rphi_data[0, 0] = Bohr * Hartree * (effective_charge**2)
                
                self.density_data = np.array(
                    [np.float_(data[n + self.nr:n + 2 * self.nr])])
                
        else:
                self.header = lines[:3]
                i = 3

                data = lines_to_list(lines[i:])

                self.Nelements = int(data[0])
                d = 1
                self.elements = data[d:d + self.Nelements]
                d += self.Nelements

                self.nrho = int(data[d])
                self.drho = float(data[d + 1])
                self.nr = int(data[d + 2])
                self.dr = float(data[d + 3])
                self.cutoff = float(data[d + 4])

                self.embedded_data = np.zeros([self.Nelements, self.nrho])
                self.density_data = np.zeros([self.Nelements, self.nr])
                self.Z = np.zeros([self.Nelements], dtype=int)
                self.mass = np.zeros([self.Nelements])
                self.a = np.zeros([self.Nelements])
                self.lattice = []
                d += 5

                # reads in the part of the eam file for each element
                for elem in range(self.Nelements):
                    self.Z[elem] = int(data[d])
                    self.mass[elem] = float(data[d + 1])
                    self.a[elem] = float(data[d + 2])
                    self.lattice.append(data[d + 3])
                    d += 4

                    self.embedded_data[elem] = np.float_(
                        data[d:(d + self.nrho)])
                    d += self.nrho
                    self.density_data[elem] = np.float_(data[d:(d + self.nr)])
                    d += self.nr

                # reads in the r*phi data for each interaction between elements
                self.rphi_data = np.zeros([self.Nelements, self.Nelements,
                                           self.nr])

                for i in range(self.Nelements):
                    for j in range(i + 1):
                        self.rphi_data[j, i] = np.float_(data[d:(d + self.nr)])
                        d += self.nr

        self.r = np.arange(0, self.nr) * self.dr
        self.rho = np.arange(0, self.nrho) * self.drho

        self.set_splines()

        if (self.form == 'adp'):
            self.read_adp_data(data, d)
            self.set_adp_splines()

    def set_splines(self):
        # this section turns the file data into three functions (and
        # derivative functions) that define the potential
        self.embedded_energy = np.empty(self.Nelements, object)
        self.electron_density = np.empty(self.Nelements, object)
        self.d_embedded_energy = np.empty(self.Nelements, object)
        self.d_electron_density = np.empty(self.Nelements, object)

        for i in range(self.Nelements):
            self.embedded_energy[i] = spline(self.rho,
                                             self.embedded_data[i], k=3)
            self.electron_density[i] = spline(self.r,
                                              self.density_data[i], k=3)
            self.d_embedded_energy[i] = self.deriv(self.embedded_energy[i])
            self.d_electron_density[i] = self.deriv(self.electron_density[i])

        self.phi = np.empty([self.Nelements, self.Nelements], object)
        self.d_phi = np.empty([self.Nelements, self.Nelements], object)

        # ignore the first point of the phi data because it is forced
        # to go through zero due to the r*phi format in alloy and adp
        for i in range(self.Nelements):
            for j in range(i, self.Nelements):
                self.phi[i, j] = spline(
                    self.r[1:],
                    self.rphi_data[i, j][1:] / self.r[1:], k=3)

                self.d_phi[i, j] = self.deriv(self.phi[i, j])

                if j != i:
                    self.phi[j, i] = self.phi[i, j]
                    self.d_phi[j, i] = self.d_phi[i, j]

    def set_adp_splines(self):
        self.d = np.empty([self.Nelements, self.Nelements], object)
        self.d_d = np.empty([self.Nelements, self.Nelements], object)
        self.q = np.empty([self.Nelements, self.Nelements], object)
        self.d_q = np.empty([self.Nelements, self.Nelements], object)

        for i in range(self.Nelements):
            for j in range(i, self.Nelements):
                self.d[i, j] = spline(self.r[1:], self.d_data[i, j][1:], k=3)
                self.d_d[i, j] = self.deriv(self.d[i, j])
                self.q[i, j] = spline(self.r[1:], self.q_data[i, j][1:], k=3)
                self.d_q[i, j] = self.deriv(self.q[i, j])

                # make symmetrical
                if j != i:
                    self.d[j, i] = self.d[i, j]
                    self.d_d[j, i] = self.d_d[i, j]
                    self.q[j, i] = self.q[i, j]
                    self.d_q[j, i] = self.d_q[i, j]

    def read_adp_data(self, data, d):
        """read in the extra adp data from the potential file"""

        self.d_data = np.zeros([self.Nelements, self.Nelements, self.nr])
        # should be non symmetrical combinations of 2
        for i in range(self.Nelements):
            for j in range(i + 1):
                self.d_data[j, i] = data[d:d + self.nr]
                d += self.nr

        self.q_data = np.zeros([self.Nelements, self.Nelements, self.nr])
        # should be non symmetrical combinations of 2
        for i in range(self.Nelements):
            for j in range(i + 1):
                self.q_data[j, i] = data[d:d + self.nr]
                d += self.nr

    def write_potential(self, filename, nc=1, numformat='%.8e'):
        """Writes out the potential in the format given by the form
        variable to 'filename' with a data format that is nc columns
        wide.  Note: array lengths need to be an exact multiple of nc
        """

        f = open(filename, 'wb')

        assert self.nr % nc == 0
        assert self.nrho % nc == 0

        for line in self.header:
            f.write(line)

        f.write('{0} '.format(self.Nelements).encode())
        f.write(' '.join(self.elements).encode() + b'\n')

        f.write(('%d %f %d %f %f \n' %
                 (self.nrho, self.drho, self.nr,
                  self.dr, self.cutoff)).encode())

        # start of each section for each element
#        rs = np.linspace(0, self.nr * self.dr, self.nr)
#        rhos = np.linspace(0, self.nrho * self.drho, self.nrho)

        rs = np.arange(0, self.nr) * self.dr
        rhos = np.arange(0, self.nrho) * self.drho

        for i in range(self.Nelements):
            f.write(('%d %f %f %s\n' %
                     (self.Z[i], self.mass[i],
                      self.a[i], str(self.lattice[i]))).encode())
            np.savetxt(f,
                       self.embedded_energy[i](rhos).reshape(self.nrho // nc,
                                                             nc),
                       fmt=nc * [numformat])
            np.savetxt(f,
                       self.electron_density[i](rs).reshape(self.nr // nc,
                                                            nc),
                       fmt=nc * [numformat])

        # write out the pair potentials in Lammps DYNAMO setfl format
        # as r*phi for alloy format
        for i in range(self.Nelements):
            for j in range(i, self.Nelements):
                np.savetxt(f,
                           (rs * self.phi[i, j](rs)).reshape(self.nr // nc,
                                                             nc),
                           fmt=nc * [numformat])

        if self.form == 'adp':
            # these are the u(r) or dipole values
            for i in range(self.Nelements):
                for j in range(i + 1):
                    np.savetxt(f, self.d_data[i, j])

            # these are the w(r) or quadrupole values
            for i in range(self.Nelements):
                for j in range(i + 1):
                    np.savetxt(f, self.q_data[i, j])

        f.close()

    def update(self, atoms):
        # check all the elements are available in the potential
        self.Nelements = len(self.elements)
        elements = np.unique(atoms.get_chemical_symbols())
        unavailable = np.logical_not(
            np.array([item in self.elements for item in elements]))

        if np.any(unavailable):
            raise RuntimeError('These elements are not in the potential: %s' %
                               elements[unavailable])

        # cutoffs need to be a vector for NeighborList
        cutoffs = self.cutoff * np.ones(len(atoms))

        # convert the elements to an index of the position
        # in the eam format
        self.index = np.array([self.elements.index(el)
                               for el in atoms.get_chemical_symbols()])
        self.pbc = atoms.get_pbc()

        # since we need the contribution of all neighbors to the
        # local electron density we cannot just calculate and use
        # one way neighbors
        self.neighbors = NeighborList(cutoffs,
                                      skin=self.parameters.skin,
                                      self_interaction=False,
                                      bothways=True)
        self.neighbors.update(atoms)

    def calculate(self, atoms=None, properties=['energy'],
                  system_changes=all_changes):
        """EAM Calculator

        atoms: Atoms object
            Contains positions, unit-cell, ...
        properties: list of str
            List of what needs to be calculated.  Can be any combination
            of 'energy', 'forces'
        system_changes: list of str
            List of what has changed since last calculation.  Can be
            any combination of these five: 'positions', 'numbers', 'cell',
            'pbc', 'initial_charges' and 'initial_magmoms'.
            """

        Calculator.calculate(self, atoms, properties, system_changes)

        # we shouldn't really recalc if charges or magmos change
        if len(system_changes) > 0:  # something wrong with this way
            self.update(self.atoms)
            self.calculate_energy(self.atoms)

            if 'forces' in properties:
                self.calculate_forces(self.atoms)

        # check we have all the properties requested
        for property in properties:
            if property not in self.results:
                if property is 'energy':
                    self.calculate_energy(self.atoms)

                if property is 'forces':
                    self.calculate_forces(self.atoms)

        # we need to remember the previous state of parameters
#        if 'potential' in parameter_changes and potential != None:
#                self.read_potential(potential)

    def calculate_energy(self, atoms):
        """Calculate the energy
        the energy is made up of the ionic or pair interaction and
        the embedding energy of each atom into the electron cloud
        generated by its neighbors
        """

        pair_energy = 0.0
        embedding_energy = 0.0
        mu_energy = 0.0
        lam_energy = 0.0
        trace_energy = 0.0

        self.total_density = np.zeros(len(atoms))
        if (self.form == 'adp'):
            self.mu = np.zeros([len(atoms), 3])
            self.lam = np.zeros([len(atoms), 3, 3])

        for i in range(len(atoms)):  # this is the atom to be embedded
            neighbors, offsets = self.neighbors.get_neighbors(i)
            offset = np.dot(offsets, atoms.get_cell())

            rvec = (atoms.positions[neighbors] + offset -
                    atoms.positions[i])

            # calculate the distance to the nearest neighbors
            r = np.sqrt(np.sum(np.square(rvec), axis=1))  # fast
#            r = np.apply_along_axis(np.linalg.norm, 1, rvec)  # sloow

            nearest = np.arange(len(r))[r <= self.cutoff]
            for j_index in range(self.Nelements):
                use = self.index[neighbors[nearest]] == j_index
                if not use.any():
                    continue
                pair_energy += np.sum(self.phi[self.index[i], j_index](
                    r[nearest][use])) / 2.

                density = np.sum(
                    self.electron_density[j_index](r[nearest][use]))
                self.total_density[i] += density

                if self.form == 'adp':
                    self.mu[i] += self.adp_dipole(
                        r[nearest][use],
                        rvec[nearest][use],
                        self.d[self.index[i], j_index])

                    self.lam[i] += self.adp_quadrupole(
                        r[nearest][use],
                        rvec[nearest][use],
                        self.q[self.index[i], j_index])

            # add in the electron embedding energy
            embedding_energy += self.embedded_energy[self.index[i]](
                self.total_density[i])

        components = dict(pair=pair_energy, embedding=embedding_energy)

        if self.form == 'adp':
            mu_energy += np.sum(self.mu ** 2) / 2.
            lam_energy += np.sum(self.lam ** 2) / 2.

            for i in range(len(atoms)):  # this is the atom to be embedded
                trace_energy -= np.sum(self.lam[i].trace() ** 2) / 6.

            adp_result = dict(adp_mu=mu_energy,
                              adp_lam=lam_energy,
                              adp_trace=trace_energy)
            components.update(adp_result)

        self.positions = atoms.positions.copy()
        self.cell = atoms.get_cell().copy()

        energy = 0.0
        for i in components.keys():
            energy += components[i]

        self.energy_free = energy
        self.energy_zero = energy

        self.results['energy_components'] = components
        self.results['energy'] = energy

    def calculate_forces(self, atoms):
        # calculate the forces based on derivatives of the three EAM functions

        self.update(atoms)
        self.results['forces'] = np.zeros((len(atoms), 3))

        for i in range(len(atoms)):  # this is the atom to be embedded
            neighbors, offsets = self.neighbors.get_neighbors(i)
            offset = np.dot(offsets, atoms.get_cell())
            # create a vector of relative positions of neighbors
            rvec = atoms.positions[neighbors] + offset - atoms.positions[i]
            r = np.sqrt(np.sum(np.square(rvec), axis=1))
            nearest = np.arange(len(r))[r < self.cutoff]

            d_embedded_energy_i = self.d_embedded_energy[
                self.index[i]](self.total_density[i])
            urvec = rvec.copy()  # unit directional vector

            for j in np.arange(len(neighbors)):
                urvec[j] = urvec[j] / r[j]

            for j_index in range(self.Nelements):
                use = self.index[neighbors[nearest]] == j_index
                if not use.any():
                    continue
                rnuse = r[nearest][use]
                density_j = self.total_density[neighbors[nearest][use]]
                scale = (self.d_phi[self.index[i], j_index](rnuse) +
                         (d_embedded_energy_i *
                          self.d_electron_density[j_index](rnuse)) +
                         (self.d_embedded_energy[j_index](density_j) *
                          self.d_electron_density[self.index[i]](rnuse)))

                self.results['forces'][i] += np.dot(scale, urvec[nearest][use])

                if (self.form == 'adp'):
                    adp_forces = self.angular_forces(
                        self.mu[i],
                        self.mu[neighbors[nearest][use]],
                        self.lam[i],
                        self.lam[neighbors[nearest][use]],
                        rnuse,
                        rvec[nearest][use],
                        self.index[i],
                        j_index)

                    self.results['forces'][i] += adp_forces

    def angular_forces(self, mu_i, mu, lam_i, lam, r, rvec, form1, form2):
        # calculate the extra components for the adp forces
        # rvec are the relative positions to atom i
        psi = np.zeros(mu.shape)
        for gamma in range(3):
            term1 = (mu_i[gamma] - mu[:, gamma]) * self.d[form1][form2](r)

            term2 = np.sum((mu_i - mu) *
                           self.d_d[form1][form2](r)[:, np.newaxis] *
                           (rvec * rvec[:, gamma][:, np.newaxis] /
                            r[:, np.newaxis]), axis=1)

            term3 = 2 * np.sum((lam_i[:, gamma] + lam[:, :, gamma]) *
                               rvec * self.q[form1][form2](r)[:, np.newaxis],
                               axis=1)
            term4 = 0.0
            for alpha in range(3):
                for beta in range(3):
                    rs = rvec[:, alpha] * rvec[:, beta] * rvec[:, gamma]
                    term4 += ((lam_i[alpha, beta] + lam[:, alpha, beta]) *
                              self.d_q[form1][form2](r) * rs) / r

            term5 = ((lam_i.trace() + lam.trace(axis1=1, axis2=2)) *
                     (self.d_q[form1][form2](r) * r +
                      2 * self.q[form1][form2](r)) * rvec[:, gamma]) / 3.

            # the minus for term5 is a correction on the adp
            # formulation given in the 2005 Mishin Paper and is posted
            # on the NIST website with the AlH potential
            psi[:, gamma] = term1 + term2 + term3 + term4 - term5

        return np.sum(psi, axis=0)

    def adp_dipole(self, r, rvec, d):
        # calculate the dipole contribution
        mu = np.sum((rvec * d(r)[:, np.newaxis]), axis=0)

        return mu  # sign to agree with lammps

    def adp_quadrupole(self, r, rvec, q):
        # slow way of calculating the quadrupole contribution
        r = np.sqrt(np.sum(rvec ** 2, axis=1))

        lam = np.zeros([rvec.shape[0], 3, 3])
        qr = q(r)
        for alpha in range(3):
            for beta in range(3):
                lam[:, alpha, beta] += qr * rvec[:, alpha] * rvec[:, beta]

        return np.sum(lam, axis=0)

    def deriv(self, spline):
        """Wrapper for extracting the derivative from a spline"""
        def d_spline(aspline):
            return spline(aspline, 1)

        return d_spline

    def plot(self, name=''):
        """Plot the individual curves"""

        try:
            import matplotlib.pyplot as plt

        except ImportError:
            raise NotAvailable('This needs matplotlib module.')

        if self.form == 'eam' or self.form == 'alloy':
            nrow = 2
        elif self.form == 'adp':
            nrow = 3
        else:
            raise RuntimeError('Unknown form of potential: %s' % self.form)

        if hasattr(self, 'r'):
            r = self.r
        else:
            r = np.linspace(0, self.cutoff, 50)

        if hasattr(self, 'rho'):
            rho = self.rho
        else:
            rho = np.linspace(0, 10.0, 50)

        plt.subplot(nrow, 2, 1)
        self.elem_subplot(rho, self.embedded_energy,
                          r'$\rho$', r'Embedding Energy $F(\bar\rho)$',
                          name, plt)

        plt.subplot(nrow, 2, 2)
        self.elem_subplot(r, self.electron_density,
                          r'$r$', r'Electron Density $\rho(r)$', name, plt)

        plt.subplot(nrow, 2, 3)
        self.multielem_subplot(r, self.phi,
                               r'$r$', r'Pair Potential $\phi(r)$', name, plt)
        plt.ylim(-1.0, 1.0)  # need reasonable values

        if self.form == 'adp':
            plt.subplot(nrow, 2, 5)
            self.multielem_subplot(r, self.d,
                                   r'$r$', r'Dipole Energy', name, plt)

            plt.subplot(nrow, 2, 6)
            self.multielem_subplot(r, self.q,
                                   r'$r$', r'Quadrupole Energy', name, plt)

        plt.plot()

    def elem_subplot(self, curvex, curvey, xlabel, ylabel, name, plt):
        plt.xlabel(xlabel)
        plt.ylabel(ylabel)
        for i in np.arange(self.Nelements):
            label = name + ' ' + self.elements[i]
            plt.plot(curvex, curvey[i](curvex), label=label)
        plt.legend()

    def multielem_subplot(self, curvex, curvey, xlabel, ylabel, name, plt):
        plt.xlabel(xlabel)
        plt.ylabel(ylabel)
        for i in np.arange(self.Nelements):
            for j in np.arange(i + 1):
                label = name + ' ' + self.elements[i] + '-' + self.elements[j]
                plt.plot(curvex, curvey[i, j](curvex), label=label)
        plt.legend()
Exemple #40
0
    def calculate(self, crystal, system=None):
        """The symmetry functions are obtained through this `calculate` method.
        
        Parameters
        ----------
        crystal: object
            ASE Structure object.
        system: list
            A list of the crystal structures system. 
            All elements in the list have to be integer. For example, the
            system of crystal structure is NaCl system. Then, system should be
            pass as [11, 17]
            
        Returns
        -------
        all_G: dict
            The user-defined symmetry functions that represent the crystal.
            
            Currently, there are 3 types of symmetry functions implemented. 
            Here are the order of the descriptors are printed out based on
            their symmetry parameters:
                - G2: ["element", "Rs", "eta"]
                - G4: ["pair_elements", "eta", "lambda", "zeta"]
                - G5: ["pair_elements", "eta", "lambda", "zeta"]
        """
        self.crystal = crystal
        atomic_numbers = np.array(crystal.get_atomic_numbers())
        vol = crystal.get_volume()

        if system is None:
            type_set1 = create_type_set(atomic_numbers, 1)  # l
            type_set2 = create_type_set(atomic_numbers, 2)  # l
        else:
            system = np.array(system, dtype=int)
            type_set1 = create_type_set(system, 1)  # l
            type_set2 = create_type_set(system, 2)  # l

        # Initialize dict for the symmetry functions
        self.all_G = {'x': [], 'elements': []}
        if self.derivative:
            self.all_G['dxdr'] = None
            self.all_G['seq'] = None
        if self.stress:
            self.all_G['rdxdr'] = None

        # Obtain neighbors info.
        rc = [self.Rc / 2] * len(self.crystal)
        neighbors = NeighborList(rc,
                                 self_interaction=False,
                                 bothways=True,
                                 skin=0.0)
        neighbors.update(crystal)

        for i in range(len(crystal)):
            element = crystal.get_chemical_symbols()[i]
            indices, offsets = neighbors.get_neighbors(i)

            assert len(indices)>0, \
            f"There's no neighbor for this structure at Rc = {self.Rc} A."

            Ri = crystal.get_positions()[i]
            total_neighbors = len(
                indices)  # total number of neighbors of atom i

            Rj = np.zeros([total_neighbors, 3])
            Dij = np.zeros(total_neighbors)
            IDs = np.zeros(total_neighbors, dtype=int)
            jks = np.array(list(combinations(range(total_neighbors), 2)))

            count = 0
            for j, offset in zip(indices, offsets):
                Rj[count, :] = crystal.positions[j] + np.dot(
                    offset, crystal.get_cell())
                Dij[count] = np.sqrt(sum((Rj[count, :] - Ri)**2))
                IDs[count] = j
                count += 1

            Rij = Rj - Ri

            Gi = []
            GiPrime = None
            GiPrime_seq = None

            if self.G2_parameters is not None:
                G2i = calculate_G2(Dij, IDs, atomic_numbers, type_set1,
                                   self.Rc, self.G2_parameters, self._type)
                Gi = np.append(Gi, G2i)

                if self.derivative:
                    seq, G2iP, rG2iP = calculate_G2Prime(
                        Rij, Ri, i, IDs, atomic_numbers, type_set1, self.Rc,
                        self.G2_parameters, self._type)
                    if GiPrime is None:
                        GiPrime = G2iP  #[N1, l, 3]
                        GiPrime_seq = seq
                        if self.stress:
                            rGiPrime = rG2iP
                    else:
                        GiPrime_seq = np.append(GiPrime_seq, seq, axis=0)
                        GiPrime = np.append(GiPrime, G2iP, axis=1)
                        if self.stress:
                            rGiPrime = np.append(rGiPrime, rG2iP, axis=1)

            if self.G4_parameters is not None:
                G4i = calculate_G4(Rij, IDs, jks, atomic_numbers, type_set2,
                                   self.Rc, self.G4_parameters, self._type)
                Gi = np.append(Gi, G4i)

                if self.derivative:
                    seq, G4iP, rG4iP = calculate_G4Prime(
                        Rij, Ri, i, IDs, jks, atomic_numbers, type_set2,
                        self.Rc, self.G4_parameters, self._type)
                    if GiPrime is None:
                        GiPrime = G4iP
                        GiPrime_seq = seq
                        if self.stress:
                            rGiPrime = rG4iP
                    else:
                        #GiPrime_seq = np.append(GiPrime_seq, seq, axis=0)
                        GiPrime = np.append(GiPrime, G4iP, axis=1)
                        if self.stress:
                            rGiPrime = np.append(rGiPrime, rG4iP, axis=1)
                #print(seq.shape, G4iP.shape, rG4iP.shape)
                #print(GiPrime_seq.shape, GiPrime.shape, rGiPrime.shape)

            if self.G5_parameters is not None:
                G5i = calculate_G5(Rij, IDs, jks, atomic_numbers, type_set2,
                                   self.Rc, self.G5_parameters, self._type)
                Gi = np.append(Gi, G5i)

                if self.derivative:
                    seq, G5iP, rG5iP = calculate_G5Prime(
                        Rij, Ri, i, IDs, jks, atomic_numbers, type_set2,
                        self.Rc, self.G5_parameters, self._type)
                    if GiPrime is None:
                        GiPrime = G5iP
                        GiPrime_seq = seq
                        if self.stress:
                            rGiPrime = rG5iP
                    else:
                        GiPrime = np.append(GiPrime, G5iP, axis=1)
                        if self.stress:
                            rGiPrime = np.append(rGiPrime, rG5iP, axis=1)

            self.all_G['x'].append(Gi)
            self.all_G['elements'].append(element)
            if self.derivative:
                if self.all_G['seq'] is None:
                    self.all_G['seq'] = GiPrime_seq
                    self.all_G['dxdr'] = GiPrime
                    if self.stress:
                        self.all_G['rdxdr'] = rGiPrime
                else:
                    self.all_G['seq'] = np.append(self.all_G['seq'],
                                                  GiPrime_seq,
                                                  axis=0)
                    self.all_G['dxdr'] = np.append(self.all_G['dxdr'],
                                                   GiPrime,
                                                   axis=0)
                    if self.stress:
                        self.all_G['rdxdr'] = np.append(self.all_G['rdxdr'],
                                                        rGiPrime,
                                                        axis=0)

        self.all_G['x'] = np.asarray(self.all_G['x'])
        if self.derivative:
            self.all_G['dxdr'] = np.asarray(self.all_G['dxdr'])
            self.all_G['seq'] = np.asarray(self.all_G['seq'])
            if self.stress:
                self.all_G['rdxdr'] = -np.asarray(self.all_G['rdxdr']) / vol
            else:
                self.all_G['rdxdr'] = None
        return self.all_G
Exemple #41
0
    def calculate(self, atoms=None,
                  properties=['energy'],
                  system_changes=all_changes):
        Calculator.calculate(self, atoms, properties, system_changes)

        natoms = len(self.atoms)

        sigma = self.parameters.sigma
        epsilon = self.parameters.epsilon
        tolerAngs = self.parameters.tolerAngs
        tolerMult = self.parameters.tolerMult        
        cutoff = self.parameters.cutoff
        if cutoff is None:
            cutoff = 0.9

        if 'numbers' in system_changes:
            self.nl = NeighborList([cutoff / 2] * natoms,\
                      self_interaction=False)

        self.nl.update(self.atoms)

        positions = self.atoms.positions
        cell = self.atoms.cell

        e0 = 4 * epsilon * ((sigma / cutoff)**12 - (sigma / cutoff)**6)

        energy = 0.0
        forces = np.zeros((natoms, 3))
        stress = np.zeros((3, 3))

        def getLJparam(elem1, elem2):
            sigma_tmp = covalRadii[elem1] + covalRadii[elem1]
            return sigma_tmp


        for a1 in range(natoms):
            neighbors, offsets = self.nl.get_neighbors(a1)
            cells = np.dot(offsets, cell)
            d = positions[neighbors] + cells - positions[a1]
            r2 = (d**2).sum(1)
            covalBl = covalRadii[self.atoms.get_atomic_numbers()[a1]]+\
                covalRadii[self.atoms.get_atomic_numbers()[neighbors]]
            c6 = (sigma**2 / r2)**3
            c6[covalBl - np.sqrt(r2) < tolerAngs] = 0.0
            c6[np.sqrt(r2) / covalBl > 1 - tolerMult] = 0.0
#            c6[r2 > radius**2] = 0.0
            energy -= e0 * (c6 != 0.0).sum()
            c12 = c6**2
            energy += 4 * epsilon * (c12 - c6).sum()
            f = (24 * epsilon * (2 * c12 - c6) / r2)[:, np.newaxis] * d
            forces[a1] -= f.sum(axis=0)
            for a2, f2 in zip(neighbors, f):
                forces[a2] += f2
            stress += np.dot(f.T, d)

        if 'stress' in properties:
            if self.atoms.number_of_lattice_vectors == 3:
                stress += stress.T.copy()
                stress *= -0.5 / self.atoms.get_volume()
                self.results['stress'] = stress.flat[[0, 4, 8, 5, 2, 1]]
            else:
                raise PropertyNotImplementedError

        self.results['energy'] = energy
        self.results['free_energy'] = energy
        self.results['forces'] = forces
Exemple #42
0
class OPLSff:
    def __init__(self, fileobj=None, warnings=0):
        self.warnings = warnings
        self.data = {}
        if fileobj is not None:
            self.read(fileobj)

    def read(self, fileobj, comments='#'):
        if isinstance(fileobj, str):
            fileobj = open(fileobj)

        def read_block(name, symlen, nvalues):
            """Read a data block.

            name: name of the block to store in self.data
            symlen: length of the symbol
            nvalues: number of values expected
            """

            if name not in self.data:
                self.data[name] = {}
            data = self.data[name]

            def add_line():
                line = fileobj.readline().strip()
                if not len(line):  # end of the block
                    return False
                line = line.split('#')[0]  # get rid of comments
                if len(line) > symlen:
                    symbol = line[:symlen]
                    words = line[symlen:].split()
                    if len(words) >= nvalues:
                        if nvalues == 1:
                            data[symbol] = float(words[0])
                        else:
                            data[symbol] = [
                                float(word) for word in words[:nvalues]
                            ]
                return True

            while add_line():
                pass

        read_block('one', 2, 3)
        read_block('bonds', 5, 2)
        read_block('angles', 8, 2)
        read_block('dihedrals', 11, 4)
        read_block('cutoffs', 5, 1)

        self.bonds = BondData(self.data['bonds'])
        self.angles = AnglesData(self.data['angles'])
        self.dihedrals = DihedralsData(self.data['dihedrals'])
        self.cutoffs = CutoffList(self.data['cutoffs'])

    def write_lammps(self, atoms, prefix='lammps'):
        """Write input for a LAMMPS calculation."""
        self.prefix = prefix

        if hasattr(atoms, 'connectivities'):
            connectivities = atoms.connectivities
        else:
            btypes, blist = self.get_bonds(atoms)
            atypes, alist = self.get_angles()
            dtypes, dlist = self.get_dihedrals(alist, atypes)
            connectivities = {
                'bonds': blist,
                'bond types': btypes,
                'angles': alist,
                'angle types': atypes,
                'dihedrals': dlist,
                'dihedral types': dtypes
            }

            self.write_lammps_definitions(atoms, btypes, atypes, dtypes)
            self.write_lammps_in()

        self.write_lammps_atoms(atoms, connectivities)

    def write_lammps_in(self):
        fileobj = self.prefix + '_in'
        if isinstance(fileobj, str):
            fileobj = open(fileobj, 'w')
        fileobj.write("""# LAMMPS relaxation (written by ASE)

units           metal
atom_style      full
boundary        p p p
#boundary       p p f

""")
        fileobj.write('read_data ' + self.prefix + '_atoms\n')
        fileobj.write('include  ' + self.prefix + '_opls\n')
        fileobj.write("""
kspace_style    pppm 1e-5
#kspace_modify  slab 3.0

neighbor        1.0 bin
neigh_modify    delay 0 every 1 check yes

thermo          1000
thermo_style    custom step temp press cpu pxx pyy pzz pxy pxz pyz ke pe etotal vol lx ly lz atoms

dump            1 all xyz 1000 dump_relax.xyz
dump_modify     1 sort id

restart         100000 test_relax

min_style       fire
minimize        1.0e-14 1.0e-5 100000 100000
""")
        fileobj.close()

    def write_lammps_atoms(self, atoms, connectivities):
        """Write atoms input for LAMMPS"""

        fname = self.prefix + '_atoms'
        fileobj = open(fname, 'w')

        # header
        fileobj.write(fileobj.name + ' (by ' + str(self.__class__) + ')\n\n')
        fileobj.write(str(len(atoms)) + ' atoms\n')
        fileobj.write(str(len(atoms.types)) + ' atom types\n')
        blist = connectivities['bonds']
        if len(blist):
            btypes = connectivities['bond types']
            fileobj.write(str(len(blist)) + ' bonds\n')
            fileobj.write(str(len(btypes)) + ' bond types\n')
        alist = connectivities['angles']
        if len(alist):
            atypes = connectivities['angle types']
            fileobj.write(str(len(alist)) + ' angles\n')
            fileobj.write(str(len(atypes)) + ' angle types\n')
        dlist = connectivities['dihedrals']
        if len(dlist):
            dtypes = connectivities['dihedral types']
            fileobj.write(str(len(dlist)) + ' dihedrals\n')
            fileobj.write(str(len(dtypes)) + ' dihedral types\n')

        # cell
        p = Prism(atoms.get_cell())
        xhi, yhi, zhi, xy, xz, yz = p.get_lammps_prism_str()
        fileobj.write('\n0.0 %s  xlo xhi\n' % xhi)
        fileobj.write('0.0 %s  ylo yhi\n' % yhi)
        fileobj.write('0.0 %s  zlo zhi\n' % zhi)

        # atoms
        fileobj.write('\nAtoms\n\n')
        tag = atoms.get_tags()
        if atoms.has('molid'):
            molid = atoms.get_array('molid')
        else:
            molid = [1] * len(atoms)
        for i, r in enumerate(p.positions_to_lammps_strs(
                atoms.get_positions())):
            atype = atoms.types[tag[i]]
            if len(atype) < 2:
                atype = atype + ' '
            q = self.data['one'][atype][2]
            fileobj.write('%6d %3d %3d %s %s %s %s' %
                          ((i + 1, molid[i], tag[i] + 1, q) + tuple(r)))
            fileobj.write(' # ' + atoms.types[tag[i]] + '\n')

        # velocities
        velocities = atoms.get_velocities()
        if velocities is not None:
            fileobj.write('\nVelocities\n\n')
            for i, v in enumerate(velocities):
                fileobj.write('%6d %g %g %g\n' % (i + 1, v[0], v[1], v[2]))

        # masses
        fileobj.write('\nMasses\n\n')
        for i, typ in enumerate(atoms.types):
            cs = atoms.split_symbol(typ)[0]
            fileobj.write(
                '%6d %g # %s -> %s\n' %
                (i + 1, atomic_masses[chemical_symbols.index(cs)], typ, cs))

        # bonds
        if len(blist):
            fileobj.write('\nBonds\n\n')
            for ib, bvals in enumerate(blist):
                fileobj.write(
                    '%8d %6d %6d %6d ' %
                    (ib + 1, bvals[0] + 1, bvals[1] + 1, bvals[2] + 1))
                try:
                    fileobj.write('# ' + btypes[bvals[0]])
                except:
                    pass
                fileobj.write('\n')

        # angles
        if len(alist):
            fileobj.write('\nAngles\n\n')
            for ia, avals in enumerate(alist):
                fileobj.write('%8d %6d %6d %6d %6d ' %
                              (ia + 1, avals[0] + 1, avals[1] + 1,
                               avals[2] + 1, avals[3] + 1))
                try:
                    fileobj.write('# ' + atypes[avals[0]])
                except:
                    pass
                fileobj.write('\n')

        # dihedrals
        if len(dlist):
            fileobj.write('\nDihedrals\n\n')
            for i, dvals in enumerate(dlist):
                fileobj.write('%8d %6d %6d %6d %6d %6d ' %
                              (i + 1, dvals[0] + 1, dvals[1] + 1, dvals[2] + 1,
                               dvals[3] + 1, dvals[4] + 1))
                try:
                    fileobj.write('# ' + dtypes[dvals[0]])
                except:
                    pass
                fileobj.write('\n')

    def update_neighbor_list(self, atoms):
        cut = 0.5 * max(self.data['cutoffs'].values())
        self.nl = NeighborList([cut] * len(atoms),
                               skin=0,
                               bothways=True,
                               self_interaction=False)
        self.nl.update(atoms)
        self.atoms = atoms

    def get_bonds(self, atoms):
        """Find bonds and return them and their types"""
        cutoffs = CutoffList(self.data['cutoffs'])
        self.update_neighbor_list(atoms)

        types = atoms.get_types()
        tags = atoms.get_tags()
        cell = atoms.get_cell()
        bond_list = []
        bond_types = []
        for i, atom in enumerate(atoms):
            iname = types[tags[i]]
            indices, offsets = self.nl.get_neighbors(i)
            for j, offset in zip(indices, offsets):
                if j <= i:
                    continue  # do not double count
                jname = types[tags[j]]
                cut = cutoffs.value(iname, jname)
                if cut is None:
                    if self.warnings > 1:
                        print('Warning: cutoff %s-%s not found' %
                              (iname, jname))
                    continue  # don't have it
                dist = np.linalg.norm(atom.position - atoms[j].position -
                                      np.dot(offset, cell))
                if dist > cut:
                    continue  # too far away
                name, val = self.bonds.name_value(iname, jname)
                if name is None:
                    if self.warnings:
                        print('Warning: potential %s-%s not found' %
                              (iname, jname))
                    continue  # don't have it
                if name not in bond_types:
                    bond_types.append(name)
                bond_list.append([bond_types.index(name), i, j])
        return bond_types, bond_list

    def get_angles(self, atoms=None):
        cutoffs = CutoffList(self.data['cutoffs'])
        if atoms is not None:
            self.update_neighbor_list(atoms)
        else:
            atoms = self.atoms

        types = atoms.get_types()
        tags = atoms.get_tags()
        cell = atoms.get_cell()
        ang_list = []
        ang_types = []

        # center atom *-i-*
        for i, atom in enumerate(atoms):
            iname = types[tags[i]]
            indicesi, offsetsi = self.nl.get_neighbors(i)

            # search for first neighbor j-i-*
            for j, offsetj in zip(indicesi, offsetsi):
                jname = types[tags[j]]
                cut = cutoffs.value(iname, jname)
                if cut is None:
                    continue  # don't have it
                dist = np.linalg.norm(atom.position - atoms[j].position -
                                      np.dot(offsetj, cell))
                if dist > cut:
                    continue  # too far away

                # search for second neighbor j-i-k
                for k, offsetk in zip(indicesi, offsetsi):
                    if k <= j:
                        continue  # avoid double count
                    kname = types[tags[k]]
                    cut = cutoffs.value(iname, kname)
                    if cut is None:
                        continue  # don't have it
                    dist = np.linalg.norm(atom.position -
                                          np.dot(offsetk, cell) -
                                          atoms[k].position)
                    if dist > cut:
                        continue  # too far away
                    name, val = self.angles.name_value(jname, iname, kname)
                    if name is None:
                        if self.warnings > 1:
                            print('Warning: angles %s-%s-%s not found' %
                                  (jname, iname, kname))
                        continue  # don't have it
                    if name not in ang_types:
                        ang_types.append(name)
                    ang_list.append([ang_types.index(name), j, i, k])

        return ang_types, ang_list

    def get_dihedrals(self, ang_types, ang_list):
        'Dihedrals derived from angles.'

        cutoffs = CutoffList(self.data['cutoffs'])

        atoms = self.atoms
        types = atoms.get_types()
        tags = atoms.get_tags()
        cell = atoms.get_cell()

        dih_list = []
        dih_types = []

        def append(name, i, j, k, l):
            if name not in dih_types:
                dih_types.append(name)
            index = dih_types.index(name)
            if (([index, i, j, k, l] not in dih_list)
                    and ([index, l, k, j, i] not in dih_list)):
                dih_list.append([index, i, j, k, l])

        for angle in ang_types:
            l, i, j, k = angle
            iname = types[tags[i]]
            jname = types[tags[j]]
            kname = types[tags[k]]

            # search for l-i-j-k
            indicesi, offsetsi = self.nl.get_neighbors(i)
            for l, offsetl in zip(indicesi, offsetsi):
                if l == j:
                    continue  # avoid double count
                lname = types[tags[l]]
                cut = cutoffs.value(iname, lname)
                if cut is None:
                    continue  # don't have it
                dist = np.linalg.norm(atoms[i].position - atoms[l].position -
                                      np.dot(offsetl, cell))
                if dist > cut:
                    continue  # too far away
                name, val = self.dihedrals.name_value(lname, iname, jname,
                                                      kname)
                if name is None:
                    continue  # don't have it
                append(name, l, i, j, k)

            # search for i-j-k-l
            indicesk, offsetsk = self.nl.get_neighbors(k)
            for l, offsetl in zip(indicesk, offsetsk):
                if l == j:
                    continue  # avoid double count
                lname = types[tags[l]]
                cut = cutoffs.value(kname, lname)
                if cut is None:
                    continue  # don't have it
                dist = np.linalg.norm(atoms[k].position - atoms[l].position -
                                      np.dot(offsetl, cell))
                if dist > cut:
                    continue  # too far away
                name, val = self.dihedrals.name_value(iname, jname, kname,
                                                      lname)
                if name is None:
                    continue  # don't have it
                append(name, i, j, k, l)

        return dih_types, dih_list

    def write_lammps_definitions(self, atoms, btypes, atypes, dtypes):
        """Write force field definitions for LAMMPS."""

        fileobj = self.prefix + '_opls'
        if isinstance(fileobj, str):
            fileobj = open(fileobj, 'w')

        fileobj.write('# OPLS potential\n')
        fileobj.write('# write_lammps' +
                      str(time.asctime(time.localtime(time.time()))))

        # bonds
        if len(btypes):
            fileobj.write('\n# bonds\n')
            fileobj.write('bond_style      harmonic\n')
            for ib, btype in enumerate(btypes):
                fileobj.write('bond_coeff %6d' % (ib + 1))
                for value in self.bonds.nvh[btype]:
                    fileobj.write(' ' + str(value))
                fileobj.write(' # ' + btype + '\n')

        # angles
        if len(atypes):
            fileobj.write('\n# angles\n')
            fileobj.write('angle_style      harmonic\n')
            for ia, atype in enumerate(atypes):
                fileobj.write('angle_coeff %6d' % (ia + 1))
                for value in self.angles.nvh[atype]:
                    fileobj.write(' ' + str(value))
                fileobj.write(' # ' + atype + '\n')

        # dihedrals
        if len(dtypes):
            fileobj.write('\n# dihedrals\n')
            fileobj.write('dihedral_style      opls\n')
            for i, dtype in enumerate(dtypes):
                fileobj.write('dihedral_coeff %6d' % (i + 1))
                for value in self.dihedrals.nvh[dtype]:
                    fileobj.write(' ' + str(value))
                fileobj.write(' # ' + dtype + '\n')

        # Lennard Jones settings
        fileobj.write('\n# L-J parameters\n')
        fileobj.write('pair_style lj/cut/coul/long 10.0 7.4' +
                      ' # consider changing these parameters\n')
        fileobj.write('special_bonds lj/coul 0.0 0.0 0.5\n')
        data = self.data['one']
        for ia, atype in enumerate(atoms.types):
            if len(atype) < 2:
                atype = atype + ' '
            fileobj.write('pair_coeff ' + str(ia + 1) + ' ' + str(ia + 1))
            for value in data[atype][:2]:
                fileobj.write(' ' + str(value))
            fileobj.write(' # ' + atype + '\n')
        fileobj.write('pair_modify shift yes mix geometric\n')

        # Charges
        fileobj.write('\n# charges\n')
        for ia, atype in enumerate(atoms.types):
            if len(atype) < 2:
                atype = atype + ' '
            fileobj.write('set type ' + str(ia + 1))
            fileobj.write(' charge ' + str(data[atype][2]))
            fileobj.write(' # ' + atype + '\n')
Exemple #43
0
def get_bond_matrix(sbu):
    """Guesses the bond order in neighbourlist based on covalent radii
    the radii for BO > 1 are extrapolated by removing 0.1 Angstroms by order
    see Beatriz Cordero, Veronica Gomez, Ana E. Platero-Prats, Marc Reves, Jorge Echeverria,
    Eduard Cremades, Flavia Barragan and Santiago Alvarez (2008).
    "Covalent radii revisited".
    Dalton Trans. (21): 2832-2838
    http://dx.doi.org/10.1039/b801115j
    """
    # first guess
    bonds = numpy.zeros((len(sbu), len(sbu)))
    symbols = numpy.array(sbu.get_chemical_symbols())
    numbers = numpy.array(sbu.get_atomic_numbers())
    positions = numpy.array(sbu.get_positions())
    BO1 = numpy.array([covalent_radii[n] if n > 0 else 0.35 for n in numbers])
    BO2 = BO1 - 0.15
    BO3 = BO2 - 0.15
    nl1 = NeighborList(cutoffs=BO1,
                       bothways=True,
                       self_interaction=False,
                       skin=0.1)
    nl2 = NeighborList(cutoffs=BO2,
                       bothways=True,
                       self_interaction=False,
                       skin=0.1)
    nl3 = NeighborList(cutoffs=BO3,
                       bothways=True,
                       self_interaction=False,
                       skin=0.1)
    nl1.update(sbu)
    nl2.update(sbu)
    nl3.update(sbu)
    for atom in sbu:
        i1, _ = nl1.get_neighbors(atom.index)
        i2, _ = nl2.get_neighbors(atom.index)
        i3, _ = nl3.get_neighbors(atom.index)
        bonds[atom.index, i1] = 1.0
        bonds[atom.index, i2] = 2.0
        bonds[atom.index, i3] = 3.0
    # cleanup with particular cases
    # identify particular atoms
    hydrogens = numpy.where(symbols == "H")[0]
    metals = numpy.where(is_metal(symbols))[0]
    alkali = numpy.where(is_alkali(symbols))[0]
    # the rest is dubbed "organic"
    organic = numpy.ones(bonds.shape)
    organic[hydrogens, :] = False
    organic[metals, :] = False
    organic[alkali, :] = False
    organic[:, hydrogens] = False
    organic[:, metals] = False
    organic[:, alkali] = False
    organic = numpy.where(organic)[0]
    # Hydrogen has BO of 1
    bonds_h = bonds[hydrogens]
    bonds_h[bonds_h > 1.0] = 1.0
    bonds[hydrogens, :] = bonds_h
    bonds[:, hydrogens] = bonds_h.T
    #Metal-Metal bonds: if no special case, nominal bond
    ix = numpy.ix_(metals, metals)
    bix = bonds[ix]
    bix[numpy.nonzero(bix)] = 0.25
    bonds[ix] = bix
    # no H-Metal bonds
    ix = numpy.ix_(metals, hydrogens)
    bonds[ix] = 0.0
    ix = numpy.ix_(hydrogens, metals)
    bonds[ix] = 0.0
    # no alkali-alkali bonds
    ix = numpy.ix_(alkali, alkali)
    bonds[ix] = 0.0
    # no alkali-metal bonds
    ix = numpy.ix_(metals, alkali)
    bonds[ix] = 0.0
    ix = numpy.ix_(alkali, metals)
    bonds[ix] = 0.0
    # metal-organic is coordination bond
    ix = numpy.ix_(metals, organic)
    bix = bonds[ix]
    bix[numpy.nonzero(bix)] = 0.5
    bonds[ix] = bix
    ix = numpy.ix_(organic, metals)
    bix = bonds[ix]
    bix[numpy.nonzero(bix)] = 0.5
    bonds[ix] = bix
    # aromaticity and rings
    rings = []
    # first, use the compressed sparse graph object
    # we only care about organic bonds and not hydrogens
    graph_bonds = numpy.array(bonds > 0.99, dtype=float)
    graph_bonds[hydrogens, :] = 0.0
    graph_bonds[:, hydrogens] = 0.0
    graph = csgraph.csgraph_from_dense(graph_bonds)
    for sg in graph.indices:
        subgraph = graph[sg]
        for i, j in combinations(subgraph.indices, 2):
            t0 = csgraph.breadth_first_tree(graph, i_start=i, directed=False)
            t1 = csgraph.breadth_first_tree(graph, i_start=j, directed=False)
            t0i = t0.indices
            t1i = t1.indices
            ring = sorted(set(list(t0i) + list(t1i) + [i, j, sg]))
            # some conditions
            seen = (ring in rings)
            isring = (sorted(t0i[1:]) == sorted(t1i[1:]))
            bigenough = (len(ring) >= 5)
            smallenough = (len(ring) <= 10)
            if isring and not seen and bigenough and smallenough:
                rings.append(ring)
    # we now have a list of all the shortest rings within
    # the molecular graph. If planar, the ring might be aromatic
    aromatic_epsilon = 0.1
    aromatic = []
    for ring in rings:
        homocycle = (symbols[ring] == "C").all()
        heterocycle = numpy.in1d(symbols[ring],
                                 numpy.array(["C", "S", "N", "O"])).all()
        if (homocycle and (len(ring) % 2) == 0) or heterocycle:
            ring_positions = positions[ring]
            # small function for coplanarity
            coplanar = all([
                numpy.linalg.det(numpy.array(x[:3]) - x[3]) < aromatic_epsilon
                for x in combinations(ring_positions, 4)
            ])
            if coplanar:
                aromatic.append(ring)
    # aromatic bond fixing
    aromatic = numpy.array(aromatic).ravel()
    ix = numpy.ix_(aromatic, aromatic)
    bix = bonds[ix]
    bix[numpy.nonzero(bix)] = 1.5
    bonds[ix] = bix
    # hydrogen bonds
    # TODO
    return bonds
Exemple #44
0
def do_one_vacancy(bulk_supercell,
                   calculator,
                   relax_radial=0.0,
                   relax_symm_break=0.0,
                   nn_cutoff=0.0,
                   tol=1.0e-2):
    bulk_supercell_pe = bulk_supercell.get_potential_energy()
    vac_i = 0

    # do unrelaxed (without perturbations)
    vac = bulk_supercell.copy()
    del vac[vac_i]

    label = "ind_%d_Z_%d" % (vac_i, bulk_supercell.get_atomic_numbers()[vac_i])
    unrelaxed_filename = "-%s-unrelaxed.xyz" % label
    ase.io.write(os.path.join("..", unrelaxed_filename), vac, format='extxyz')
    #evaluate(vac)
    vac.set_calculator(calculator)
    unrelaxed_vac_pe = vac.get_potential_energy()

    # recreate with perturbations for relaxation
    vac = bulk_supercell.copy()

    if relax_radial != 0.0 or relax_symm_break != 0.0:
        nl = NeighborList([nn_cutoff / 2.0] * len(bulk_supercell),
                          self_interaction=False,
                          bothways=True)
        nl.update(bulk_supercell)
        indices, offsets = nl.get_neighbors(vac_i)
        offset_factor = relax_radial
        for i, offset in zip(indices, offsets):
            ri = vac.positions[vac_i] - (vac.positions[i] +
                                         np.dot(offset, vac.get_cell()))
            vac.positions[i] += offset_factor * ri
            offset_factor += relax_symm_break

    del vac[vac_i]
    vac_pos = vac.positions[vac_i]

    vac = relax_config(vac,
                       calculator,
                       relax_pos=True,
                       relax_cell=False,
                       tol=tol,
                       traj_file=None,
                       config_label=label,
                       from_base_model=True,
                       save_config=True)
    relaxed_filename = "-%s-relaxed.xyz" % label
    ase.io.write(os.path.join("..", relaxed_filename), vac, format='extxyz')

    # already has calculator from relax_configs
    vac_pe = vac.get_potential_energy()
    if len(set(bulk_supercell.get_atomic_numbers())) == 1:
        Ebulk = float(len(vac)) / float(
            len(bulk_supercell)) * bulk_supercell_pe
    else:
        Ebulk = bulk_supercell_pe
    Ef0 = unrelaxed_vac_pe - Ebulk
    Ef = vac_pe - Ebulk
    print("got vacancy", label, "cell energy", vac_pe, "n_atoms", len(vac))
    print("got bulk energy", Ebulk, " (scaled to (N-1)/N if single component)")
    return (label, unrelaxed_filename, Ef0, relaxed_filename, Ef,
            int(bulk_supercell.get_atomic_numbers()[vac_i]), vac_pos)
Exemple #45
0
    def build_neighbor_list(self):
        '''
        Builds a neighborlist for the calculation of bispectrum components for
        a given ASE atoms object given in the calculate method.
        '''
        if self._backend == 'ase':
            atoms = self._atoms
            # cutoffs for each atom
            cutoffs = [self.rcut / 2] * len(atoms)
            # instantiate neighborlist calculator
            nl = NeighborList(cutoffs, self_interaction=False, bothways=True, skin=0.0)
            # provide atoms object to neighborlist calculator
            nl.update(atoms)
            # instantiate memory for neighbor separation vectors, periodic indices, and atomic numbers
            center_atoms = np.zeros((len(atoms), 3), dtype=np.float64)
            neighbors = []
            neighbor_indices = []
            atomic_numbers = []

            max_len = 0
            for i in range(len(atoms)):
                # get center atom position
                center_atom = atoms.positions[i]
                center_atoms[i] = center_atom
                # get indices and cell offsets of each neighbor
                indices, offsets = nl.get_neighbors(i)
                # add an empty list to neighbors and atomic numbers for population
                neighbors.append([])
                atomic_numbers.append([])
                # the indices are already numpy arrays so just append as is
                neighbor_indices.append(indices)
                for j, offset in zip(indices, offsets):
                    # compute separation vector
                    pos = atoms.positions[j] + np.dot(offset, atoms.get_cell()) - center_atom
                    neighbors[i].append(pos)
                    atomic_numbers[i].append(atoms[j].number)

                if len(neighbors[i]) > max_len:
                    max_len = len(neighbors[i])

            # declare arrays to store the separation vectors, neighbor indices
            # atomic numbers of each neighbor, and the atomic numbers of each
            # site
            neighborlist = np.zeros((len(atoms), max_len, 3), dtype=np.float64)
            neighbor_inds = np.zeros((len(atoms), max_len), dtype=np.int64)
            atm_nums = np.zeros((len(atoms), max_len), dtype=np.int64)
            site_atomic_numbers = np.array(list(atoms.numbers), dtype=np.int64)

            # populate the arrays with list elements
            for i in range(len(atoms)):
                neighborlist[i, :len(neighbors[i]), :] = neighbors[i]
                neighbor_inds[i, :len(neighbors[i])] = neighbor_indices[i]
                atm_nums[i, :len(neighbors[i])] = atomic_numbers[i]


        elif self._backend == 'pymatgen':
            from pymatgen.io.ase import AseAtomsAdaptor
            struc = AseAtomsAdaptor.get_structure(self._atoms)
            neighbors = struc.get_all_neighbors(self._rcut, include_index=True)

            max_len = 0
            for i, neighlist in enumerate(neighbors):
                if len(neighlist) > max_len:
                    max_len = len(neighlist)

            center_atoms = np.zeros((len(struc), 3), dtype=np.float64)
            neighborlist = np.zeros((len(struc), max_len, 3), dtype=np.float64)
            neighbor_inds = np.zeros((len(struc), max_len), dtype=np.int64)
            atm_nums = np.zeros((len(struc), max_len), dtype=np.int64)
            site_atomic_numbers = np.zeros(len(struc), dtype=np.int64)

            for i, site in enumerate(struc):
                neighlist = neighbors[i]
                site_atomic_numbers[i] = site.specie.number
                center_atoms[i] = site.coords
                for j, neighbor in enumerate(neighlist):
                    neighborlist[i, j, :] = neighbor[0].coords - site.coords
                    neighbor_inds[i, j] = neighbor[2]
                    atm_nums[i, j] = neighbor[0].specie.number

        else:
            raise NotImplementedError('Specified backend not supported')

        # assign these arrays to attributes
        self.center_atoms = center_atoms
        self.neighborlist = neighborlist
        self.neighbor_indices = neighbor_inds
        self.atomic_numbers = atm_nums
        self.site_atomic_numbers = site_atomic_numbers

        return
Exemple #46
0
 def update_neighbor_list(self, atoms):
     cut = 0.5 * max(self.data['cutoffs'].values())
     self.nl = NeighborList([cut] * len(atoms), skin=0,
                            bothways=True, self_interaction=False)
     self.nl.update(atoms)
     self.atoms = atoms
Exemple #47
0
from ase.io import read, write
from ase.neighborlist import NeighborList
from ase.neighborlist import natural_cutoffs

nt = [0]*32
atoms = read('run-01.pdb')
nl = NeighborList(natural_cutoffs(atoms, 1.35))
nl.update(atoms)

o = open('nl.dat', 'w')
for i in range(len(atoms)):
    indices, offsets = nl.get_neighbors(i)
    nt[len(indices)] += 1
    o.write('%d\n'%len(indices))

Exemple #48
0
def get_all_interstitials(ats, unique_atoms):
    """Function to return list of all interstitial sites using Voronoi.py

    Args:
      ats(:ase:class:`Atoms`):Atoms object of structure.
      positions(numpy array): Lattice sites to compute interstitials for.

    Returns:
      list of list, with inner list containing ['Interstitial Type', [x,y,z]]
      Interstitial type can be of type 'B', 'C', 'N' corresponding to;
        'B' Voronoi vertices.
        'C' face centers.
        'N' Edge centers.
    """

    ints_list = []
    #generate neighbours list.
    for site_num in unique_atoms:
        nl = NeighborList([2.0] * len(ats),
                          bothways=False,
                          self_interaction=True)
        nl.update(ats)
        site = np.array([
            ats[site_num].position[0], ats[site_num].position[1],
            ats[site_num].position[2]
        ])
        points = []
        indices, offsets = nl.get_neighbors(site_num)
        print 'site_num:', site_num
        for i, offset in zip(indices, offsets):
            if offset[2] == 0.0:
                print 'neighb:', i, ats.positions[i], offset, np.dot(
                    offset, ats.get_cell())
                points.append(ats.positions[i] +
                              np.dot(offset, ats.get_cell()))
        ### converting list to numpy array
        print "neighbors", len(points)
        points = np.asarray(points)

        voronoi = Voronoi()

        ### using tess object cntr to compute voronoi
        cntr = voronoi.compute_voronoi(points)

        ### Voronoi vertices
        ### the first position in points is the site, therefore '0'
        v = voronoi.get_vertices(0, cntr)

        for i in range(len(v)):
            ints_list.append(['B', v[i].tolist()])

        ### Voronoi face centers
        f = voronoi.get_facecentroid(0, cntr)

        for j in range(len(f)):
            ints_list.append(['C', f[j].tolist()])

        ### Voronoi edge centers
        e = voronoi.get_edgecenter(0, cntr)

        for k in range(len(e)):
            ints_list.append(['N', e[k].tolist()])

    ### return list of list ['Atom type', [x,y,z]]
    return ints_list
Exemple #49
0
def CoreShellFCC(atoms, type_a, type_b, ratio, a_cell, n_depth=-1):
    r"""This routine generates cluster with ideal core-shell architecture,
    so that atoms of type_a are placed on the surface
    and atoms of type_b are forming the core of nanoparticle.
    The 'surface' of nanoparticle is defined as atoms
    with unfinished coordination shell.

    Parameters
    ----------
    atoms: ase.Atoms
        ase Atoms object, containing atomic cluster.
    type_a: string
        Symbol of chemical element to be placed on the shell.
    type_b: string
        Symbol of chemical element to be placed in the core.
    ratio: float
        Guards the number of shell atoms, type_a:type_b = ratio:(1-ratio)
    a_cell: float
        Parameter of FCC cell, in Angstrom.
        Required for calculation of neighbor distances in for infinite
        crystal.
    n_depth: int
        Number of layers of the shell formed by atoms ratio.
        Default value -1 is ignored and n_depth is calculated according
        ratio. If n_depth is set then value of ratio is ignored.

    Returns
    -------
        Function returns ASE atoms object which
        contains bimetallic core-shell cluster

    Notes
    -----
        The criterion of the atom beeing on the surface is incompletnes
        of it's coordination shell. For the most outer atoms the first
        coordination shell will be incomplete (coordination number
        is less then 12 for FCC), for the second layer --
        second coordination shell( CN1 + CN2 < 12 + 6) and so on.
        In this algorithm each layer is tagged by the number
        ('depth'), take care if used with other routines
        dealing with tags (add_adsorbate etc).

        First, atoms with unfinished first shell are replaced
        by atoms type_a, then second, and so on.
        The last depth surface layer is replaced by random
        to maintain given ratio value.

    Example
    --------
    >>> atoms = FaceCenteredCubic('Ag',
      [(1, 0, 0), (1, 1, 0), (1, 1, 1)], [7,8,7], 4.09)
    >>> atoms = CoreShellFCC(atoms, 'Pt', 'Ag', 0.6, 4.09)
    >>> view(atoms)
    """
    # 0 < ratio < 1
    target_x = ratio
    if n_depth != -1:
        target_x = 1  # needed to label all needed layeres

    def fill_by_tag(atoms, chems, tag):
        """Replaces all atoms within selected layer"""
        for i in xrange(0, len(atoms)):
            if atoms[i].tag == tag:
                chems[i] = type_a
        return
    # coord numbers for FCC:
    coord_nums = [1, 12, 6, 24, 12, 24, 8, 48, 6, 36, 24, 24, 24, 72, 48,
    12, 48, 30, 72, 24]
    # coordination radii obtained from this array as R = sqrt(coord_radii)*a/2
    coord_radii = [0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 30,
    32, 34, 36, 38, 40]

    ## generate FCC cluster ##
    #atoms = FaceCenteredCubic(type_b, surfaces, layers, a_cell)
    n_atoms = len(atoms)
    ## tag layers ##
    positions = [0]  # number of positions in layer
    n_tag = 0    # number of tags to check if there is enought layers
    n_shell = 0  # depth of the shell
    while (n_tag < n_atoms * target_x):
        n_shell += 1
        if (n_depth != -1)and(n_shell > n_depth):
            break
        neiblist = NeighborList(
          [
            a_cell / 2.0 * sqrt(coord_radii[n_shell]) / 2.0 + 0.0001
          ] * n_atoms,
          self_interaction=False, bothways=True
        )
        neiblist.build(atoms)
        for i in xrange(0, n_atoms):
            indeces, offsets = neiblist.get_neighbors(i)
            if (atoms[i].tag == 0):
                if (len(indeces) < sum(coord_nums[1:n_shell + 1])):
                    # coord shell is not full -> atom is on surface!
                    atoms[i].tag = n_shell
                    n_tag += 1
        # save the count of positions at each layer:
        positions.append(n_tag - sum(positions[0:n_shell]))
    ## populate layers ##
    chems = atoms.get_chemical_symbols()
    n_type_a = 0  # number of changes B -> A
    if (n_tag < n_atoms * target_x)and(n_depth == -1):
        # raise exception?
        return None
    else:
        n_filled = n_shell - 1  # number of totally filled layers
        ilayer = 1
        while (ilayer < n_filled + 1):
            fill_by_tag(atoms, chems, ilayer)
            n_type_a += positions[ilayer]
            ilayer += 1
        while (n_type_a < n_atoms * target_x)and(n_depth == -1):
            i = random.randint(0, n_atoms - 1)
            if (atoms[i].tag == n_shell):
                if (chems[i] == type_b):
                    chems[i] = type_a
                    n_type_a += 1
    atoms.set_chemical_symbols(chems)
    ## check number of atoms ##
    checkn_a = 0
    for element in chems:
        if element == type_a:
            checkn_a += 1
    assert n_type_a == checkn_a
    return atoms
class RepulsivePotential(Calculator):
    """ ASE Calculator representing the repulsive potentials in a DFTB
    parameter set. Arguments:

    atoms: an ASE atoms object to which the calculator will be attached

    skfdict: dict with the (paths to the) SKF files containing the
             (exponential+spline-based) repulsive potentials, one
             for every (alphabetically sorted) element pair, e.g.:
             {'O-O':'O-O.skf', 'H-O':'H-O.skf', 'H-H':'H-H.skf'}.

             If equal to None, all files are assumed to reside in
             the $DFTB_PREFIX folder and formatted as *-*.skf
             as in the example above.
    """
    implemented_properties = ['energy', 'forces']

    def __init__(self, atoms, skfdict=None):
        Calculator.__init__(self)

        elements = np.unique(atoms.get_chemical_symbols())
        self.pairs = get_skf_prefixes(elements, redundant=False)
        rcut = 0.
        self.func = {}

        for p in self.pairs:
            if skfdict is None:
                f = os.environ['DFTB_PREFIX'] + '/%s.skf' % p
            else:
                assert p in skfdict, 'No SKF file specified for %s' % p
                f = skfdict[p]

            assert os.path.exists(f), 'SKF file %s does not exist' % f
            self.func[p] = read_spline_from_skf(f)
            rcut = max([rcut, self.func[p].rcut])

        self.nl = NeighborList([rcut * Bohr / 2.] * len(atoms),
                               skin=1.,
                               bothways=False,
                               self_interaction=False)

    def calculate(self, atoms, properties, system_changes):
        Calculator.calculate(self, atoms, properties, system_changes)

        N = len(atoms)
        energy, forces = 0, np.zeros((N, 3))
        cell = atoms.get_cell()
        sym = atoms.get_chemical_symbols()
        pos = atoms.get_positions()
        self.nl.update(atoms)

        for i in range(N):
            indices, offsets = self.nl.get_neighbors(i)
            p = pos[indices] + np.dot(offsets, cell)
            r = cdist(p, [pos[i]])
            v = pos[i] - p

            for j, index in enumerate(indices):
                p = '-'.join(sorted([sym[i], sym[index]]))
                d = r[j][0]
                energy += self.func[p](d, der=0)
                f = self.func[p](d, der=1) * v[j] / d
                forces[index] += f
                forces[i] -= f

        self.results = {'energy': energy, 'forces': forces}
Exemple #51
0
def get_neighbours(atoms, r_cut, self_interaction=False):
    """Return a list of pairs of atoms within a given distance of each other.

    If matscipy can be imported, then this will directly call matscipy's
    neighbourlist function. Otherwise it will use ASE's NeighborList object.

    Args:
        atoms: ase.atoms object to calculate neighbours for
        r_cut: cutoff radius (float). Pairs of atoms are considered neighbours
            if they are within a distance r_cut of each other (note that this
            is double the parameter used in the ASE's neighborlist module)

    Returns: a tuple (i_list, j_list, d_list, fixed_atoms):
        i_list, j_list: i and j indices of each neighbour pair
        d_list: absolute distance between the corresponding pair
        fixed_atoms: indices of any fixed atoms
    """

    if isinstance(atoms, Filter):
        atoms = atoms.atoms

    if have_matscipy:
        i_list, j_list, d_list = neighbour_list('ijd', atoms, r_cut)
    else:

        radii = [r_cut / 2 for i in range(len(atoms))]
        nl = NeighborList(radii,
                          sorted=False,
                          self_interaction=False,
                          bothways=True)
        nl.update(atoms)
        i_list = []
        j_list = []
        d_list = []

        for i, atom in enumerate(atoms):
            posn_i = atom.position
            indices, offsets = nl.get_neighbors(i)
            assert len(indices) == len(offsets)
            for j, offset in zip(indices, offsets):
                # Offsets represent how far away an atom is from its pair in terms
                # of the repeating cell - for instance, an atom i might be in cell
                # (0, 0, 0) while the neighbouring atom j is in cell (0, 1, 1). To
                # get the true position we have to correct for the offset:
                posn_j = atoms.positions[j] + np.dot(offset, atoms.get_cell())
                distance = np.sqrt(((posn_j - posn_i)**2).sum())
                i_list.append(i)
                j_list.append(j)
                d_list.append(distance)

        i_list = np.array(i_list)
        j_list = np.array(j_list)
        d_list = np.array(d_list)

    # filter out self-interactions (across PBC)
    if not self_interaction:
        mask = i_list != j_list
        i_list = i_list[mask]
        j_list = j_list[mask]
        d_list = d_list[mask]

    # filter out bonds where 1st atom (i) in pair is fixed
    fixed_atoms = []
    for constraint in atoms.constraints:
        if isinstance(constraint, FixAtoms):
            fixed_atoms.extend(list(constraint.index))
        else:
            raise TypeError(
                'only FixAtoms constraints are supported by Precon class')

    return i_list, j_list, d_list, fixed_atoms