Source code for ase2sprkkr.asr.setup.symmetrize

"""Generate symmetrized atomic structure."""
from asr.core import command, option, ASRResult


[docs] def symmetrize_atoms(atoms, tolerance=None, angle_tolerance=None, return_dataset=False): import numpy as np from ase import Atoms from asr.utils.symmetry import atoms2symmetry symmetry = atoms2symmetry(atoms, tolerance=tolerance, angle_tolerance=angle_tolerance) dataset = symmetry.dataset cell_cv = atoms.get_cell() spos_ac = atoms.get_scaled_positions() numbers = atoms.get_atomic_numbers() uspos_sac = [] M_scc = [] origin_sc = [] point_c = np.zeros(3, float) U_scc = dataset['rotations'] t_sc = dataset['translations'] # t_sc -= np.rint(t_sc) for U_cc, t_c in zip(U_scc, t_sc): origin_sc.append(np.dot(U_cc, point_c) + t_c) symspos_ac = np.dot(spos_ac, U_cc.T) + t_c symcell_cv = np.dot(U_cc.T, cell_cv) # Cell metric M_cc = np.dot(symcell_cv, symcell_cv.T) M_scc.append(M_cc) inds = [] for i, s_c in enumerate(spos_ac): d_ac = s_c - symspos_ac dm_ac = np.abs(d_ac - np.round(d_ac)) ind = np.argwhere(np.all(dm_ac < tolerance, axis=1))[0][0] symspos_ac[ind] += np.round(d_ac[ind]) inds.append(ind) assert atoms.numbers[i] == atoms.numbers[ind] assert len(set(inds)) == len(atoms) uspos_ac = symspos_ac[inds] uspos_sac.append(uspos_ac) assert np.all(np.abs(spos_ac - uspos_ac) < tolerance) origin_sc = np.array(origin_sc) origin_sc -= np.rint(t_sc) origin_c = np.mean(origin_sc, axis=0) # Shift origin print('Origin shifted by', origin_c) spos_ac = np.mean(uspos_sac, axis=0) - origin_c M_cc = np.mean(M_scc, axis=0) from ase.geometry.cell import cellpar_to_cell dotprods = M_cc[[0, 1, 2, 1, 0, 0], [0, 1, 2, 2, 2, 1]] l_c = np.sqrt(dotprods[:3]) angles_c = np.arccos(dotprods[3:] / l_c[[1, 0, 0]] / l_c[[2, 2, 1]]) angles_c *= 180 / np.pi cp = np.concatenate([l_c, angles_c]) ab_normal = np.cross(atoms.cell[0], atoms.cell[1]) cell = cellpar_to_cell(cp, ab_normal=ab_normal, a_direction=atoms.cell[0]) idealized = Atoms(numbers=numbers, scaled_positions=spos_ac, cell=cell, pbc=True) newsymmetry = atoms2symmetry(idealized, tolerance=tolerance, angle_tolerance=angle_tolerance) newdataset = newsymmetry.dataset if return_dataset: return idealized, origin_c, dataset, newdataset return idealized, origin_c
@command('asr.setup.symmetrize') @option('--tolerance', type=float, help='Tolerance when evaluating symmetries') @option('--angle-tolerance', type=float, help='Tolerance one angles when evaluating symmetries') def main(tolerance: float = 1e-3, angle_tolerance: float = 0.1) -> ASRResult: """Symmetrize atomic structure. This function changes the atomic positions and the unit cell of an approximately symmetric structure into an exactly symmetric structure. In practice, the spacegroup of the structure located in 'original.json' is evaluated using a not-very-strict tolerance, which can be adjusted using the --tolerance and --angle-tolerance switches. Then the symmetries of the spacegroup are used to generate equivalent atomic structures and by taking an average of these atomic positions we generate an exactly symmetric atomic structure. Examples -------- Symmetrize an atomic structure using the default tolerances $ ase build -x diamond C original.json $ asr run setup.symmetrize """ import numpy as np from ase.io import read, write atoms = read('original.json') assert atoms.pbc.all(), \ ('Symmetrization has only been tested for 3D systems! ' 'To apply it to other systems you will have to test and update ' 'the code.') idealized = atoms.copy() spgs = [] # There is a chance that the space group changes when symmetrizing # structure. maxiter = 2 for i in range(maxiter): atol = angle_tolerance idealized, origin_c, dataset1, dataset2 = \ symmetrize_atoms(idealized, tolerance=tolerance, angle_tolerance=atol, return_dataset=True) spg1 = '{} ({})'.format(dataset1['international'], dataset1['number']) spg2 = '{} ({})'.format(dataset2['international'], dataset2['number']) if i == 0: spgs.extend([spg1, spg2]) else: spgs.append(spg2) if spg1 == spg2: break print(f'Spacegroup changed {spg1} -> {spg2}. Trying again.') else: msg = 'Reached maximum iteration! Went through ' + ' -> '.join(spgs) raise RuntimeError(msg) print(f'Idealizing structure into spacegroup {spg2}.') idealized.set_initial_magnetic_moments( atoms.get_initial_magnetic_moments()) write('unrelaxed.json', idealized) # Check that the cell was only slightly perturbed cp = atoms.cell.cellpar() idcp = idealized.cell.cellpar() deltacp = idcp - cp abc, abg = deltacp[:3], deltacp[3:] print('Cell Change: (Δa, Δb, Δc, Δα, Δβ, Δγ) = ' f'({abc[0]:.1e} Å, {abc[1]:.1e} Å, {abc[2]:.1e} Å, ' f'{abg[0]:.2e}°, {abg[1]:.2e}°, {abg[2]:.2e}°)') assert (np.abs(abc) < 10 * tolerance).all(), \ 'a, b and/or c changed too much! See output above.' assert (np.abs(abg[3:]) < 10 * angle_tolerance).all(), \ 'α, β and/or γ changed too much! See output above.' cell = idealized.get_cell() spos_ac = atoms.get_scaled_positions(wrap=False) idspos_ac = idealized.get_scaled_positions(wrap=False) + origin_c dpos_av = np.dot(idspos_ac - spos_ac, cell) dpos_a = np.sqrt(np.sum(dpos_av**2, 1)) with np.printoptions(precision=2, suppress=False): print(f'Change of positions:') msg = ' ' for symbol, dpos in zip(atoms.symbols, dpos_a): msg += f' {symbol}: {dpos:.1e} Å,' if len(msg) > 70: print(msg[:-1]) msg = ' ' print(msg[:-1]) assert (dpos_a < 10 * tolerance).all(), \ 'Some atoms moved too much! See output above.' if __name__ == '__main__': main.cli()