예제 #1
0
def find_ipso_hydrogen(a_i, cell, symbol):
    """Find closest hydrogen to atom at index a_i in cell

    | Args:
    |   a_i (int): Index of atom in cell position array
    |   cell (Atoms object): ASE atoms object for molecule
    |   symbol (str): Symbol used to represent relevant atom in cell
    |
    | Returns:
    |   ipso_i (int): Index of closest hydrogen in cell position array
    """
    if cell.has('castep_custom_species'):
        chems = cell.get_array('castep_custom_species')
    else:
        chems = np.array(cell.get_chemical_symbols())
    pos = cell.get_positions()
    iH = np.where(['H' in c and c != symbol for c in chems])[0]
    posH = pos[iH]
    distH = np.linalg.norm(minimum_periodic(posH - pos[a_i],
                                            cell.get_cell())[0],
                           axis=-1)
    #Which one is the closest?
    ipso_i = iH[np.argmin(distH)]

    return ipso_i
예제 #2
0
    def extract(s, size, return_pairs):
        # Get the interatomic pair distances
        v = s.get_positions()
        v = v[:, None, :] - v[None, :, :]
        pair_inds = np.triu_indices(v.shape[0], k=1)
        v = v[pair_inds]
        # Reduce them
        v, _ = minimum_periodic(v, s.get_cell())
        # And now compile the list
        link_list = np.linalg.norm(v, axis=-1)
        sort_i = np.argsort(link_list)
        link_list = link_list[sort_i]
        if size > 0:
            if link_list.shape[0] >= size:
                link_list = link_list[:size]
            else:
                link_list = np.pad(link_list, (0, size - link_list.shape[0]),
                                   mode=str('constant'),
                                   constant_values=np.inf)

        if not return_pairs:
            return link_list
        else:
            pairs = zip(pair_inds[0][sort_i], pair_inds[1][sort_i])
            return link_list, pairs
예제 #3
0
    def extract(s, force_recalc, size):

        if Molecules.default_name not in s.info or force_recalc:
            Molecules.get(s)

        mol_com = MoleculeCOM.get(s)

        # Safety check
        if len(mol_com) < 2:
            return [np.inf] * size

        # Now make the linkage
        v = np.array(mol_com)
        v = v[:, None, :] - v[None, :, :]
        v = v[np.triu_indices(v.shape[0], k=1)]
        # Reduce them
        v, _ = minimum_periodic(v, s.get_cell())
        # And now compile the list
        link_list = np.linalg.norm(v, axis=-1)
        link_list.sort()

        if size > 0:
            if link_list.shape[0] >= size:
                link_list = link_list[:size]
            else:
                link_list = np.pad(link_list, (0, size - link_list.shape[0]),
                                   mode=str('constant'),
                                   constant_values=np.inf)

        return link_list
예제 #4
0
def euclideanDistance(s, i, j, periodic=True):
    """
    Return the distance between two atoms, in Angstroms.

    | Parameters:
    |   s (ase.Atoms): the structure on which to compute the distance
    |   i (int): index of the first atom
    |   j (int): index of the second atom
    |   periodic (bool): whether to account for periodic boundaries when 
    |                    computing the distance; default is True

    | Returns:
    |   r (float): computed distance
    """

    if i == j:
        return 0.0

    cell = s.get_cell()
    pos = s.get_positions()

    r = pos[j] - pos[i]

    if periodic:
        r, _ = minimum_periodic(r[None, :], cell)

    return np.linalg.norm(r)
예제 #5
0
    def extract(s, selection, center, quaternion, scaled, periodic):

        center = np.array(center)
        if center.shape != (3, ):
            raise ValueError('Invalid center passed to Rotate.')

        if quaternion is None:
            quaternion = Quaternion()

        sT = s.copy()

        if not scaled:
            pos = sT.get_positions()
        else:
            pos = sT.get_scaled_positions()

        pos -= center
        if periodic:
            ppos, _ = minimum_periodic(pos[selection.indices], s.get_cell())
            pos[selection.indices] = ppos
        pos[selection.indices] = quaternion \
            .rotate(pos[selection.indices].T).T
        pos += center

        if not scaled:
            sT.set_positions(pos)
        else:
            sT.set_scaled_positions(pos)

        return sT
예제 #6
0
    def extract(s, l_channels, center_atoms, environment_atoms, cutoff_radius,
                cutoff_width, compute_W):

        # Check that l_channels are valid
        l_channels = np.array(l_channels)
        if ((l_channels < 1) | ((l_channels % 1) != 0)).any():
            raise ValueError('Invalid angular momentum channels')

        # Turn center_atoms and environment_atoms into AtomSelections
        if not isinstance(center_atoms, AtomSelection):
            if center_atoms is None:
                center_atoms = range(len(s))
            elif not isinstance(center_atoms, list):
                center_atoms = [center_atoms]
            center_atoms = AtomSelection(s, center_atoms)

        if not isinstance(environment_atoms, AtomSelection):
            if environment_atoms is None:
                environment_atoms = range(len(s))
            elif not isinstance(environment_atoms, list):
                environment_atoms = [environment_atoms]
            environment_atoms = AtomSelection(s, environment_atoms)

        xyz = s.get_positions()

        # Now to build the total list of vectors and weights
        all_vecs = np.zeros((0, 3))
        all_weights = np.zeros((0, ))

        for ci in center_atoms.indices:
            # What would the environment be?
            env_inds = list(set(environment_atoms.indices) - set([ci]))
            # So what are the 'bonds'?
            bonds = minimum_periodic(xyz[env_inds] - xyz[ci], s.get_cell())[0]
            bnorms = np.linalg.norm(bonds, axis=1)

            # Compute sigmoid weights
            bweights = 0.5 * (((cutoff_radius - bnorms) / cutoff_width) / ((
                (cutoff_radius - bnorms) / cutoff_width)**2 + 1)**0.5 + 1)
            bweights /= np.sum(bweights)  # Norm to 1

            all_vecs = np.concatenate((all_vecs, bonds))
            all_weights = np.concatenate((all_weights, bweights))

        # Norm to 1 all weights:
        all_weights /= len(center_atoms.indices)

        # Now compute the order parameters!
        stp = _steinhardt_pars(all_vecs,
                               l_channels,
                               weights=all_weights,
                               compute_W=compute_W)

        if compute_W:
            return {'Q': stp[0], 'W': stp[1]}
        else:
            return {'Q': stp}
예제 #7
0
    def extract(s, sel_i, sel_j, isotopes, isotope_list, self_coupling,
                block_size):

        # Selections
        if sel_i is None:
            sel_i = AtomSelection.all(s)
        elif not isinstance(sel_i, AtomSelection):
            sel_i = AtomSelection(s, sel_i)

        if sel_j is None:
            sel_j = sel_i
        elif not isinstance(sel_j, AtomSelection):
            sel_j = AtomSelection(s, sel_j)

        # Find gammas
        elems = s.get_chemical_symbols()

        gammas = _get_isotope_data(elems, 'gamma', isotopes, isotope_list)

        # Viable pairs
        pairs = [(i, j) for i in sel_i.indices for j in sel_j.indices]
        if not self_coupling:
            pairs = [p for p in pairs if p[0] != p[1]]

        pairs = np.array(pairs).T
        # Need to sort them and remove any duplicates, also take i < j as
        # convention
        pairs = np.array(
            list(zip(*set([tuple(x) for x in np.sort(pairs, axis=0).T]))))

        pos = s.get_positions()

        # Split this in blocks to make sure we don't clog the memory

        d_ij = np.zeros((0, ))
        v_ij = np.zeros((0, 3))

        npairs = pairs.shape[1]

        for b_i in range(0, npairs, block_size):
            block = pairs.T[b_i:b_i + block_size]
            r_ij = pos[block[:, 1]] - pos[block[:, 0]]
            # Reduce to NN
            r_ij, _ = minimum_periodic(r_ij, s.get_cell(), exclude_self=True)
            # Distance
            R_ij = np.linalg.norm(r_ij, axis=1)
            # Versors
            v_ij = np.concatenate([v_ij, r_ij / R_ij[:, None]], axis=0)
            # Couplings
            d_ij = np.concatenate([
                d_ij,
                _dip_constant(R_ij * 1e-10, gammas[block[:, 0]],
                              gammas[block[:, 1]])
            ])

        return {tuple(ij): [d_ij[l], v_ij[l]] for l, ij in enumerate(pairs.T)}
예제 #8
0
def linspaceGen(struct_0, struct_1, steps=10, periodic=False):
    """Generator function to create multiple structures with positions
    interpolated linearly between two extremes.

    | Args:
    |   struct_0 (ase.Atoms): the starting structure
    |   struct_1 (ase.Atoms): the final structure. The atoms should be in the
    |                         same order as the ones in struct_0
    |   steps (Optional[int]): number of interpolated steps to produce
    |                          (extremes included). Default is 10
    |   periodic (Optional[bool]): if True the interpolation will take into
    |                              account periodic boundaries and interpolate
    |                              between positions in struct_0 and the
    |                              closest periodic copy of positions in
    |                              struct_1. By default set to False

    | Returns:
    |   linspaceGenerator (generator): an iterator object that yields
    |                                  structures created by linear
    |                                  interpolation.

    """

    # First, a compatibility check
    chem0 = struct_0.get_chemical_symbols()
    chem1 = struct_1.get_chemical_symbols()

    if chem0 != chem1:
        raise RuntimeError('The two structures passed to linspaceGen do not '
                           'have the same chemical composition')

    pos0 = struct_0.get_positions()
    pos1 = struct_1.get_positions()

    rootname = struct_0.info['name'] if 'name' in struct_0.info else 'linspace'

    # Adjust pos1 to be periodic if asked to
    if periodic:
        dpos = pos1 - pos0
        dpos = utils.minimum_periodic(dpos, struct_0.get_cell())[0]
        pos1 = pos0 + dpos

    for i, t in enumerate(np.linspace(0, 1, steps)):

        pos = pos0 * (1 - t) + pos1 * t
        struct = struct_0.copy()
        struct.set_positions(pos)
        struct.info['name'] = '{0}_{1}'.format(rootname, i)

        yield struct
예제 #9
0
    def test_defect(self):

        si2 = bulk('Si')

        poisson_r = 0.5

        dGen = defectGen(si2, 'H', poisson_r)
        dColl = AtomsCollection(dGen)

        dPos = dColl.all.get_positions()[:, 0]

        holds = True
        for i, p1 in enumerate(dPos[:-1]):
            vecs, _ = minimum_periodic(dPos[i + 1:] - p1, si2.get_cell())
            p_holds = (np.linalg.norm(vecs, axis=1) >= poisson_r).all()
            holds = holds and p_holds

        self.assertTrue(holds)
예제 #10
0
    def extract(s, sel_i, sel_j, isotopes, isotope_list, self_coupling):

        # Selections
        if sel_i is None:
            sel_i = AtomSelection.all(s)
        elif not isinstance(sel_i, AtomSelection):
            sel_i = AtomSelection(s, sel_i)

        if sel_j is None:
            sel_j = sel_i
        elif not isinstance(sel_j, AtomSelection):
            sel_j = AtomSelection(s, sel_j)

        # Find gammas
        elems = s.get_chemical_symbols()
        _nmr_data = _get_nmr_data()

        gammas = _get_isotope_data(elems, 'gamma', isotopes, isotope_list)

        # Viable pairs
        pairs = [(i, j) for i in sel_i.indices
                 for j in sel_j.indices]
        if not self_coupling:
            pairs = [p for p in pairs if p[0] != p[1]]

        pairs = np.array(pairs).T
        # Need to sort them and remove any duplicates, also take i < j as
        # convention
        pairs = np.array(zip(*set([tuple(x)
                                   for x in np.sort(pairs, axis=0).T])))

        pos = s.get_positions()

        r_ij = pos[pairs[1]] - pos[pairs[0]]
        # Reduce to NN
        r_ij, _ = minimum_periodic(r_ij, s.get_cell(), exclude_self=True)
        # Distance
        R_ij = np.linalg.norm(r_ij, axis=1)
        # Versors
        v_ij = r_ij/R_ij[:, None]
        # Couplings
        d_ij = _dip_constant(R_ij*1e-10, gammas[pairs[0]], gammas[pairs[1]])

        return {tuple(ij): [d_ij[l], v_ij[l]] for l, ij in enumerate(pairs.T)}
def compute_hfine_mullpop(
    atoms,
    populations,
    self_i=0,
    cut_r=10,
    lorentz=True,
    fermi=True,
    fermi_neigh=False,
):
    """Compute a hyperfine tensor for a given atomic system from the Mulliken
    electronic populations, with additional empyrical corrections.

    | Args:
    |   atoms (ase.Atoms):   Atoms object to work with
    |   populations (np.ndarray):  electronic orbital-resolved Mulliken
    |                              populations, as returned for example by
    |                              parse_spinpol_dftb. Spins here ought to
    |                              be in Bohr magnetons (so for example
    |                              one electron up would pass as 0.5)
    |   self_i (int):        index of point at which to compute the tensor.
    |                        Local spin density will give rise to a Fermi
    |                        contact term
    |   species (str or [str]): symbol or list of symbols identifying the
    |                           species generating the magnetic field.
    |                           Determines the magnetic moments
    |   cut_r (float):       cutoff radius for dipolar component calculation
    |   lorentz (bool):      if True, include a Lorentz term (average bulk
    |                        magnetization). Default is True
    |   fermi (bool):        if True, include a Fermi contact term
    |                        (magnetization at site i). Default is True
    |   fermi_neigh (bool):  if True, include an empyrical neighbour
    |                        correction for the Fermi contact term.
    |                        Default is False

    | Returns:
    |   HT (np.ndarray):    hyperfine tensor at point i
    """

    pbc = atoms.get_pbc()
    # Only works if either all, or none
    if pbc.any() and not pbc.all():
        raise ValueError("Partially periodic systems not implemented")
    pbc = pbc.all()
    if pbc:
        cell = atoms.get_cell()
    else:
        cell = None
    pos = atoms.get_positions()

    # First correction: compile the total spins
    totspins = np.array([sp["spin"] for sp in populations])
    magmoms = totspins

    # Fermi contact term density
    fermi_mm = 0
    if fermi:
        a0 = cnst.physical_constants["Bohr radius"][0]
        Z = atoms.get_atomic_numbers()[self_i]
        for (n, l, m), p in populations[self_i]["spin_orbital"].items():
            if l > 0:
                continue
            fermi_mm += 2 / np.pi * (Z / (n * a0 * 1e10) ** 3.0) * p
        # If required, add the neighbour effect
        if fermi_neigh:
            dr = pos - pos[self_i]
            if pbc:
                dr, _ = minimum_periodic(dr, cell)
            drnorm = np.linalg.norm(dr, axis=-1)

            # This is totally empirical! Works with C6H6Mu for now
            expcorr = np.exp(-drnorm * 1e-10 / (1.55 * a0)) * totspins
            expcorr[self_i] = 0.0

            fermi_mm += np.sum(expcorr)

    return compute_hfine_tensor(
        pos,
        magmoms,
        cell,
        self_i=self_i,
        cut_r=cut_r,
        lorentz=lorentz,
        fermi_mm=fermi_mm,
    )
예제 #12
0
    def extract(s, vdw_set, vdw_scale, default_vdw, hbond_elems, max_length,
                max_angle, save_info):
        def elem_inds(s, el):
            return [
                i for i, cs in enumerate(s.get_chemical_symbols()) if cs == el
            ]

        def bname(A, B):
            return '{0}H..{1}'.format(A, B)

        # Define types
        hbonds = {}
        for elA in hbond_elems:
            for elB in hbond_elems:
                hbonds[bname(elA, elB)] = []

        # First, grab the hydrogen atoms
        h_atoms = elem_inds(s, 'H')
        if len(h_atoms) == 0:
            # Nothing to do
            if save_info:
                s.info[HydrogenBonds.default_name] = hbonds
            return hbonds

        bond_atoms = []
        for el_b in hbond_elems:
            bond_atoms += elem_inds(s, el_b)

        if len(bond_atoms) < 2:
            if save_info:
                s.info[HydrogenBonds.default_name] = hbonds
            return hbonds

        # Now to pick the positions
        h_atoms_pos = s.get_positions()[h_atoms]
        bond_atoms_pos = s.get_positions()[bond_atoms]
        # Van der Waals radii length of H-atom bonds
        bonds_vdw = _vdw_radii[vdw_set][s.get_atomic_numbers()[bond_atoms]]
        bonds_vdw = np.where(np.isnan(bonds_vdw), default_vdw, bonds_vdw)
        bonds_vdw = (bonds_vdw + _vdw_radii[vdw_set][1]) / 2.0
        bonds_vdw *= vdw_scale

        # Now find the shortest and second shortest bonds for each H
        h_links = (h_atoms_pos[:, None, :] - bond_atoms_pos[None, :, :])
        shape = h_links.shape
        h_links, h_cells = minimum_periodic(h_links.reshape((-1, 3)),
                                            s.get_cell())
        h_links = h_links.reshape(shape)
        h_cells = h_cells.reshape(shape)
        h_links_norm = np.linalg.norm(h_links, axis=-1)

        # Now for each hydrogen: first and second closest
        h_closest = np.argsort(h_links_norm, axis=-1)[:, :2]

        # Which ones DO actually form bonds?
        rngh = range(len(h_atoms))
        # Condition one: closest atom, A, is bonded
        h_bonded = h_links_norm[rngh,
                                h_closest[:, 0]] <= bonds_vdw[h_closest[:, 0]]
        # Condition two: furthest atom, B, is NOT bonded...
        h_bonded = np.logical_and(
            h_bonded,
            h_links_norm[rngh, h_closest[:, 1]] > bonds_vdw[h_closest[:, 1]])
        # Condition three: ...but still closer to A than max_length
        links_ab = h_links[rngh, h_closest[:, 0]] - \
            h_links[rngh, h_closest[:, 1]]
        links_ab_norm = np.linalg.norm(links_ab, axis=-1)
        h_bonded = np.logical_and(h_bonded, links_ab_norm <= max_length)
        # Condition four: finally, the angle between AH and AB in A-H..B
        # must be smaller than max_angle
        angles_abah = np.sum(links_ab * h_links[rngh, h_closest[:, 0]],
                             axis=-1)
        angles_abah /= links_ab_norm * h_links_norm[rngh, h_closest[:, 0]]
        angles_abah = np.arccos(angles_abah) * 180.0 / np.pi
        h_bonded = np.logical_and(h_bonded, angles_abah <= max_angle)

        # The survivors are actual h bonds!
        # Now on to defining them
        h_bonded = np.where(h_bonded)[0]
        if len(h_bonded) == 0:
            if save_info:
                s.info[HydrogenBonds.default_name] = hbonds
            return hbonds

        # Moving to structure indices

        for h in h_bonded:

            ai, bi = h_closest[h]
            elA = s.get_chemical_symbols()[bond_atoms[ai]]
            elB = s.get_chemical_symbols()[bond_atoms[bi]]

            bond = {
                'H': h_atoms[h],
                'A': (bond_atoms[ai], h_cells[h, ai]),
                'B': (bond_atoms[bi], h_cells[h, bi]),
                'length': links_ab_norm[h],
                'angle': angles_abah[h]
            }

            btype = bname(elA, elB)
            hbonds[btype].append(bond)

        if save_info:
            s.info[HydrogenBonds.default_name] = hbonds

        return hbonds
예제 #13
0
    def extract(s, vdw_set, vdw_scale, default_vdw, save_info):

        # Sanity check
        if len(s) < 2:
            # WTF?
            print('WARNING: impossible to calculate molecules on single-atom '
                  'system')
            return None

        # Get the bonds
        bond_calc = Bonds(vdw_set=vdw_set,
                          vdw_scale=vdw_scale,
                          default_vdw=default_vdw)
        bonds = bond_calc(s)

        # First, we need the biggest Van der Waals radius
        # So that we know how big the supercell needs to be
        vdw_vals = _vdw_radii[vdw_set][s.get_atomic_numbers()]
        vdw_vals = np.where(np.isnan(vdw_vals), default_vdw, vdw_vals)
        vdw_vals *= vdw_scale
        vdw_max = max(vdw_vals)

        # Get the interatomic pair distances
        atomn = s.get_number_of_atoms()
        triui = np.triu_indices(atomn, k=1)
        v = s.get_positions()
        v = (v[:, None, :] - v[None, :, :])[triui]
        # Reduce them
        v, v_cells = minimum_periodic(v, s.get_cell())
        v = np.linalg.norm(v, axis=-1)

        # Now distance and VdW matrices
        vdw_M = ((vdw_vals[None, :] + vdw_vals[:, None]) / 2.0)[triui]
        link_M = v <= vdw_M

        mol_sets = []
        unsorted_atoms = list(range(atomn))

        def get_linked(i):
            i_bonds = filter(lambda b: i in b[:2], bonds)
            links = map(lambda b: (b[1], b[2])
                        if b[0] == i else (b[0], -b[2]), i_bonds)
            return links

        while len(unsorted_atoms) > 0:
            mol_queue = [(unsorted_atoms.pop(0), np.zeros(3))]
            current_mol = []
            current_mol_cells = []
            current_mol_bonds = []
            while len(mol_queue) > 0:
                a1, cell1 = mol_queue.pop(0)
                current_mol.append(a1)
                current_mol_cells.append(cell1)
                current_mol_bonds.append([])
                # Find linked atoms
                links = get_linked(a1)
                for l, cl in links:
                    if l in unsorted_atoms:
                        mol_queue.append((l, cell1 + cl))
                        unsorted_atoms.remove(l)
                    current_mol_bonds[-1].append(l)

            mol_sets.append(
                (current_mol, current_mol_cells, current_mol_bonds))

        mols = []
        for m_i, m_cells, m_bonds in mol_sets:
            mols.append(AtomSelection(s, m_i))
            mols[-1].set_array('cell_indices', m_cells)
            mols[-1].set_array('bonds', m_bonds)

        if save_info:
            s.info[Molecules.default_name] = mols

        return mols