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
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
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
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)
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
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}
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)}
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
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)
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, )
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
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