def interpret_sel(s, sel): if sel is None: return AtomSelection.all(s) elif is_string(sel): return AtomSelection.from_element(s, sel) elif hasattr(sel, '__call__'): return sel(s)
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 test_arrays(self): a = Atoms('HCHC', positions=[[i] * 3 for i in range(4)], cell=[4] * 3, pbc=[True] * 3) s = AtomSelection.from_element(a, 'C') s.set_array('testarr', [1, 2]) self.assertTrue(all(s.subset(a).get_array('testarr') == [1, 2])) # Test that arrays are reordered a.set_array('testarr', np.array([1, 2, 3, 4])) s = AtomSelection(a, [2, 0]) a2 = s.subset(a) self.assertTrue((a2.get_array('testarr') == np.array([3, 1])).all()) # Cell indices test! s = AtomSelection(a, [0, 3]) s.set_array('cell_indices', [[0, 0, 0], [-1, 0, 0]]) a2 = s.subset(a, True) self.assertTrue(np.allclose(a2.get_positions()[-1], [-1, 3, 3]))
def test_operators(self): # Create an Atoms object a1 = Atoms('HHH') a2 = Atoms('CC') s1 = AtomSelection(a1, [0, 2]) s2 = AtomSelection(a1, [0, 1]) self.assertTrue(set((s1 + s2).indices) == set([0, 1, 2])) self.assertTrue(set((s1 - s2).indices) == set([2])) self.assertTrue(set((s1 * s2).indices) == set([0]))
def test_transformprops(self): from ase.quaternions import Quaternion from soprano.selection import AtomSelection from soprano.properties.transform import (Translate, Rotate, Mirror) a = Atoms('CH', positions=[[0, 0, 0], [0.5, 0, 0]]) sel = AtomSelection.from_element(a, 'C') transl = Translate(selection=sel, vector=[0.5, 0, 0]) rot = Rotate(selection=sel, center=[0.25, 0.0, 0.25], quaternion=Quaternion([np.cos(np.pi/4.0), 0, np.sin(np.pi/4.0), 0])) mirr = Mirror(selection=sel, plane=[1, 0, 0, -0.25]) aT = transl(a) aR = rot(a) aM = mirr(a) self.assertAlmostEqual(np.linalg.norm(aT.get_positions()[0]), np.linalg.norm(aT.get_positions()[1])) self.assertAlmostEqual(np.linalg.norm(aR.get_positions()[0]), np.linalg.norm(aR.get_positions()[1])) self.assertAlmostEqual(np.linalg.norm(aM.get_positions()[0]), np.linalg.norm(aM.get_positions()[1]))
def extract(s, sel_i, sel_j, tag, isotopes, isotope_list, self_coupling, force_recalc): # Compute the diagonalised eigenvectors if necessary iname_pairs = ISCDiagonal.default_name + '_' + tag + '_pairs' iname_evals = ISCDiagonal.default_name + '_' + tag + '_evals' iname_evecs = ISCDiagonal.default_name + '_' + tag + '_evecs' if iname_pairs not in s.info or force_recalc: iprop = ISCDiagonal(tag=tag) iprop(s) all_pairs = list(s.info[iname_pairs]) all_evals = s.info[iname_evals] all_evecs = s.info[iname_evecs] # 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) sel_pairs = [(i, j) for i in sel_i.indices for j in sel_j.indices] if not self_coupling: sel_pairs = [p for p in sel_pairs if p[0] != p[1]] jc_dict = {} for sp in sel_pairs: try: i = all_pairs.index(sp) except ValueError: continue evals = _J_constant(all_evals[i], gammas[sp[0]], gammas[sp[1]]) evecs = all_evecs[i] jc_dict[sp] = {'evals': evals, 'evecs': evecs} return jc_dict
def test_basic(self): # Create an Atoms object a = Atoms('HHH') # Try a valid selection s1 = AtomSelection(a, [0, 2]) # Try an invalid one self.assertRaises(ValueError, AtomSelection, a, [0, 3]) # Check validation self.assertTrue(s1.validate(a)) # Now make a subset a_s = s1.subset(a) self.assertTrue(len(a_s) == 2)
def test_arrays(self): a = Atoms('HCHC', positions=[[i]*3 for i in range(4)], cell=[4]*3, pbc=[True]*3) s = AtomSelection.from_element(a, 'C') s.set_array('testarr', [1, 2]) self.assertTrue(all(s.subset(a).get_array('testarr') == [1, 2]))
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 decorated_extrfunc(s, selection, **kwargs): # Perform basic checks on selection if selection is None: selection = AtomSelection.all(s) elif not selection.validate(s): raise ValueError('Selection passed to transform does not apply to' ' system.') return extrfunc(s, selection, **kwargs)
def test_selectors(self): # Multiple tests for various methods a = Atoms('HCHC', positions=[[i] * 3 for i in range(4)], cell=[4] * 3, pbc=[True] * 3) # Element test s1 = AtomSelection.from_element(a, 'C') self.assertTrue(set(s1.indices) == set([1, 3])) # Box test s1 = AtomSelection.from_box(a, [1.5] * 3, [4.5] * 3, periodic=True) s2 = AtomSelection.from_box(a, [1.5] * 3, [4.5] * 3, periodic=False) s3 = AtomSelection.from_box(a, [0.375] * 3, [1.125] * 3, periodic=True, scaled=True) self.assertTrue(set(s1.indices) == set([0, 2, 3])) self.assertTrue(set(s2.indices) == set([2, 3])) self.assertTrue(set(s3.indices) == set([0, 2, 3])) # Sphere test s1 = AtomSelection.from_sphere(a, [0.5] * 3, 3, periodic=True) s2 = AtomSelection.from_sphere(a, [0.5] * 3, 3, periodic=False) self.assertTrue(set(s1.indices) == set([0, 1, 2, 3])) self.assertTrue(set(s2.indices) == set([0, 1, 2]))
def test_iterate(self): a = Atoms('HCHC', positions=[[i] * 3 for i in range(4)], cell=[4] * 3, pbc=[True] * 3) sAll = AtomSelection.all(a) # Slicing? self.assertTrue((sAll[:2].indices == [0, 1]).all()) # Iterating? for i, s in enumerate(sAll): self.assertEqual(i, s.indices[0])
def substitutionGen(struct, subst, to_replace=None, n=1, accept=None): """Generator function to create multiple structures with a defect of a given element substituted in the existing cell. The defects will be put in place of the atoms passed in the to_replace selection. If none is passed, all atoms will be replaced in turn. Multiple defects can be included, in which case all permutations will be generated. It is also possible to reject some configurations based on the output of a filter function. | Args: | struct (ase.Atoms): the starting structure. All defects will be added | to it. | subst (str): element symbol of the defect to add. | to_replace (AtomSelection): if present, only atoms belonging to this | selection will be substituted. | n (int): number of defects to include in each structure. Default is 1. | accept (function): a function that determines whether a generated | structure should be accepted or rejected. Takes as | input the generated structure and a tuple of the | indices of the substituted atoms, and must return a | bool. If False, the structure will be rejected. | Returns: | defectGenerator (generator): an iterator object that yields | structures with all possible | substitutions. """ if to_replace is None: to_replace = AtomSelection.all(struct) defconfs = itertools.combinations(to_replace.indices, n) elems = np.array(struct.get_chemical_symbols()).astype('S2') print(subst) for dc in defconfs: dstruct = struct.copy() delems = elems.copy() delems[list(dc)] = subst dstruct.set_chemical_symbols(delems) if accept is not None: if not accept(dstruct, dc): continue yield dstruct
Besides allowing to manipulate information about multiple structures, Soprano provides tools to edit them as well. This is accomplished by combining selection of atoms and transformation operations that change their positions. As an example we will use again the ammonia molecule. Selections can be carried with multiple criteria. The basic ones are selection by element, selection of all atoms in a box, and selection of all atoms in a sphere. """ from soprano.selection import AtomSelection nh3coords = np.array([[2.5, 2.5, 2.5], [3.4373, 2.5, 2.1193], [2.0314, 3.3117, 2.1193], [2.0314, 1.6883, 2.1193]]) nh3l = Atoms('NHHH', nh3coords, cell=[5, 5, 5]) # The cell is just an empty box # Now instead of switching the coordinates by hand let's do this with selections. nh3Hsel = AtomSelection.from_element(nh3l, 'H') # All H atoms in nh3l # Selections can be manipulated in interesting ways. To begin with, we can create an Atoms object containing # only the selected atoms h3 = nh3Hsel.subset(nh3l) print "---- Selected atoms contained in nh3Hsel ----\n" print h3.get_chemical_symbols(), "\n\n" # Also, selections can be summed, subtracted, or multiplied (representing intersection) sel1 = AtomSelection(nh3l, [1]) # A custom generated selection sel2 = AtomSelection(nh3l, [0, 2]) # A custom generated selection print "---- Indices of selected atoms for various combinations ----\n" print "sel1:\t", sel1.indices
def additionGen(struct, add, to_addition=None, n=1, add_r=1.2, accept=None): """Generator function to create multiple structures with an atom of a given element added in the existing cell. The atoms will be attached to the atoms passed in the to_addition selection. If none is passed, all atoms will be additioned in turn. Multiple defects can be included, in which case all permutations will be generated. The algorithm will try adding the atom in the direction that seems most compatible with all the already existing bonds. If multiple directions satisfy the condition, they will all be tested. It is also possible to reject some configurations based on the output of a filter function. | Args: | struct (ase.Atoms): the starting structure. All atoms will be added | to it. | add (str): element symbol of the atom to add. | to_replace (AtomSelection): if present, only atoms belonging to this | selection will be substituted. | n (int): number of new atoms to include in each structure. Default | is 1. | add_r (float): distance, in Angstroms, at which to add the atoms. | Default is 1.2 Ang | accept (function): a function that determines whether a generated | structure should be accepted or rejected. Takes as | input the generated structure and a tuple of | the indices of the atoms to which the new atoms | were added, and must return a bool. The newly added | atoms will always be the last n of the structure. | If False, the structure will be rejected. | Returns: | defectGenerator (generator): an iterator object that yields | structures with all possible additions. """ if to_addition is None: to_addition = AtomSelection.all(struct) # Compute bonds bonds = Bonds.get(struct) cell = struct.get_cell() pos = struct.get_positions() # Separate bonds by atoms atom_bonds = [[] if i in to_addition.indices else None for i in range(len(struct))] for b in bonds: v = pos[b[1]] - pos[b[0]] + np.dot(b[2], cell) try: atom_bonds[b[0]].append(v.copy()) except AttributeError: pass try: atom_bonds[b[1]].append(-v.copy()) except AttributeError: pass # Compute possible attachment points for each atom attach_v = [None] * len(struct) for i, bset in enumerate(atom_bonds): if bset is None: continue if len(bset) == 0: rndv = np.random.random((1, 3)) - 0.5 rndv /= np.linalg.norm(rndv, axis=1)[:, None] attach_v[i] = rndv else: attach_v[i] = utils.rep_alg(bset) attach_v = np.array(attach_v) addconfs = itertools.combinations(to_addition.indices, n) for ac in addconfs: addpos = itertools.product(*attach_v[list(ac)]) for ap in addpos: astruct = struct.copy() astruct += Atoms(add * n, positions=pos[list(ac)] + np.array(ap) * add_r) if accept is not None: if not accept(astruct, ac): continue yield astruct
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
def dq_buildup(self, sel_i, sel_j=None, t_max=1e-3, t_steps=1000, R_cut=3, kdq=0.155, A=1, tau=np.inf): """ Return a dictionary of double quantum buildup curves for given pairs of atoms, built according to the theory given in: G. Pileio et al., "Analytical theory of gamma-encoded double-quantum recoupling sequences in solid-state nuclear magnetic resonance" Journal of Magnetic Resonance 186 (2007) 65-74 | Args: | sel_i (AtomSelection or [int]): Selection or list of indices of | atoms for which to compute the | curves. By default is None | (= all of them). | sel_i (AtomSelection or [int]): Selection or list of indices of | atoms for which to compute the | curves with sel_i. By default is | None (= same as sel_i). | t_max (float): maximum DQ buildup time, in seconds. Default | is 1e-3. | t_steps (int): number of DQ buildup time steps. Default is 1000. | R_cut (float): cutoff radius for which periodic copies to consider | in each pair, in Angstrom. Default is 3. | kdq (float): same as the k constant in eq. 35 of the reference. A | parameter depending on the specific sequence used. | Default is 0.155. | A (float): overall scaling factor for the curve. Default is 1. | tau (float): exponential decay factor for the curve. Default | is np.inf. | Returns: | curves (dict): a dictionary of all buildup curves indexed by pair, | plus the time axis in seconds as member 't'. """ tdq = np.linspace(0, t_max, t_steps) # Selections if sel_i is None: sel_i = AtomSelection.all(s) elif not isinstance(sel_i, AtomSelection): sel_i = AtomSelection(self._sample, sel_i) if sel_j is None: sel_j = sel_i elif not isinstance(sel_j, AtomSelection): sel_j = AtomSelection(self._sample, sel_j) # Find gammas elems = self._sample.get_chemical_symbols() gammas = _get_isotope_data(elems, 'gamma', {}, self._isos) # Need to sort them and remove any duplicates, also take i < j as # convention pairs = [ tuple(sorted((i, j))) for i in sorted(sel_i.indices) for j in sorted(sel_j.indices) ] scell_shape = minimum_supcell(R_cut, latt_cart=self._sample.get_cell()) nfg, ng = supcell_gridgen(self._sample.get_cell(), scell_shape) pos = self._sample.get_positions() curves = {'t': tdq} for ij in pairs: r = pos[ij[1]] - pos[ij[0]] all_r = r[None, :] + ng all_R = np.linalg.norm(all_r, axis=1) # Apply cutoff all_R = all_R[np.where((all_R <= R_cut) * (all_R > 0))] n = all_R.shape[0] bij = _dip_constant(all_R * 1e-10, gammas[ij[0]], gammas[ij[1]]) th = 1.5 * kdq * abs(bij[:, None]) * tdq[None, :] * 2 * np.pi x = (2 * th / np.pi)**0.5 Fs, Fc = fresnel(x * 2**0.5) x[:, 0] = np.inf bdup = 0.5-(1.0/(x*8**0.5)) * \ (Fc*np.cos(2*th) + Fs*np.sin(2*th)) bdup[:, 0] = 0 curves[ij] = A * np.sum(bdup, axis=0) * np.exp(-tdq / tau) return curves
def extract(s, vdw_set, vdw_scale, default_vdw, vdw_custom, save_info): N = len(s) # Sanity check if N < 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, vdw_custom=vdw_custom) bonds = bond_calc(s) mol_sets = [] unsorted_atoms = list(range(N)) 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) # This is necessary to guarantee shape consistency m_barr = np.empty((len(m_bonds), ), dtype=list) for i, m_b in enumerate(m_bonds): m_barr[i] = m_b mols[-1].set_array('bonds', m_barr) if save_info: s.info[Molecules.default_name] = mols return mols