def test_general_array_atom_quotient(): sym = sym_quotient_translational_00_xyz() callbacks = symmetry.GeneralArrayCallbacks(['atom']) disp_atoms, disp_carts, disp_values = zip(*[ DispData(0, [ 0.1, 0, 0], data=np.array([ 1., 2., 3.])), DispData(0, [-0.1, 0, 0], data=np.array([-1., -2., -3.])), ]) derivs = symmetry.expand_derivs_by_symmetry( callbacks=callbacks, disp_atoms=disp_atoms, disp_carts=disp_carts, disp_values=disp_values, oper_cart_rots=sym.oper_cart_rots, oper_perms=sym.oper_perms, quotient_perms=sym.quotient_perms, ) derivs = np.array(derivs.tolist()) assert np.allclose(derivs, np.array([[ [10, 20, 30], # derivative w.r.t. atom 0 x [10, 20, 30], # derivative w.r.t. atom 0 y [10, 20, 30], # derivative w.r.t. atom 0 z ], [ [30, 10, 20], # derivative w.r.t. atom 1 x [30, 10, 20], # derivative w.r.t. atom 1 y [30, 10, 20], # derivative w.r.t. atom 1 z ], [ [20, 30, 10], # derivative w.r.t. atom 2 x [20, 30, 10], # derivative w.r.t. atom 2 y [20, 30, 10], # derivative w.r.t. atom 2 z ]]))
def test_pure_translation(): for (description, sym) in [ ('SEPARATE TRANSLATIONS', sym_quotient_translational_010101()), ('EMBEDDED TRANSLATIONS', sym_embedded_translational_010101()), ]: print(f'TRYING {description}') callbacks = symmetry.GeneralArrayCallbacks([]) disp_atoms, disp_carts, disp_values = zip(*[ DispData(0, [ 0.1, 0, 0], data=np.array( 1.)), DispData(0, [-0.1, 0, 0], data=np.array(-1.)), DispData(0, [ 0, 0.1, 0], data=np.array( 2.)), DispData(0, [ 0, -0.1, 0], data=np.array(-2.)), DispData(0, [ 0, 0, 0.1], data=np.array( 3.)), DispData(0, [ 0, 0, -0.1], data=np.array(-3.)), DispData(1, [ 0.1, 0, 0], data=np.array( 4.)), DispData(1, [-0.1, 0, 0], data=np.array(-4.)), DispData(1, [ 0, 0.1, 0], data=np.array( 5.)), DispData(1, [ 0, -0.1, 0], data=np.array(-5.)), DispData(1, [ 0, 0, 0.1], data=np.array( 6.)), DispData(1, [ 0, 0, -0.1], data=np.array(-6.)), ]) derivs = symmetry.expand_derivs_by_symmetry( callbacks=callbacks, disp_atoms=disp_atoms, disp_carts=disp_carts, disp_values=disp_values, oper_cart_rots=sym.oper_cart_rots, oper_perms=sym.oper_perms, quotient_perms=sym.quotient_perms, ) derivs = np.array(derivs.tolist()) assert np.allclose(derivs, np.array([ [10, 20, 30], # gradient w.r.t. atom 0 [40, 50, 60], # gradient w.r.t. atom 1 [10, 20, 30], # gradient w.r.t. atom 2 [40, 50, 60], # gradient w.r.t. atom 3 [10, 20, 30], # gradient w.r.t. atom 4 [40, 50, 60], # gradient w.r.t. atom 5 ]))
def test_general_array_atom(): sym = sym_xyz_3_atom() callbacks = symmetry.GeneralArrayCallbacks(['atom']) disp_atoms, disp_carts, disp_values = zip(*[ # because the operator is no longer in the site symmetry, we need more disps DispData(0, [ 0.1, 0, 0], data=np.array([ 2., 3., 4.])), DispData(0, [-0.1, 0, 0], data=np.array([-2., -3., -4.])), DispData(0, [ 0, 0.1, 0], data=np.array([ 5., 6., 7.])), DispData(0, [ 0, -0.1, 0], data=np.array([-5., -6., -7.])), DispData(0, [ 0, 0, 0.1], data=np.array([ 8., 9., 10.])), DispData(0, [ 0, 0, -0.1], data=np.array([-8., -9., -10.])), ]) print(sym.oper_perms) derivs = symmetry.expand_derivs_by_symmetry( callbacks=callbacks, disp_atoms=disp_atoms, disp_carts=disp_carts, disp_values=disp_values, oper_cart_rots=sym.oper_cart_rots, oper_perms=sym.oper_perms, quotient_perms=sym.quotient_perms, ) derivs = np.array(derivs.tolist()) assert np.allclose(derivs, np.array([[ [20, 30, 40], # deriv w.r.t. atom 0 x [50, 60, 70], # deriv w.r.t. atom 0 y [80, 90, 100], # deriv w.r.t. atom 0 z ], [ [100, 80, 90], # deriv w.r.t. atom 1 x [ 40, 20, 30], # deriv w.r.t. atom 1 y [ 70, 50, 60], # deriv w.r.t. atom 1 z ], [ [60, 70, 50], # deriv w.r.t. atom 2 x [90, 100, 80], # deriv w.r.t. atom 2 y [30, 40, 20], # deriv w.r.t. atom 2 z ]]))
def test_gpaw_grid_C4_z(): N_c = (5, 5, 3) # grid dimensions nspins = 1 cart_rot_generator = np.array([[0, -1, 0], [1, 0, 0], [0, 0, 1]]) perm_generator = np.array([3, 0, 1, 2]) op_scc_generator = np.array([[0, 1, 0], [-1, 0, 0], [0, 0, 1]]) # gpaw's op_scc is transposed (operates on row vectors) oper_cart_rots = test_utils.cyclic_group(cart_rot_generator, compose_rot, make_rot_hashable) oper_perms = test_utils.cyclic_group(perm_generator, compose_perm, make_perm_hashable) op_scc = test_utils.cyclic_group(op_scc_generator, compose_rot_T, make_rot_hashable) ft_sc = np.zeros((4, 3)) quotient_perms = None supercell = (1, 1, 1) callbacks = symmetry.GpawLcaoVTCallbacks__from_parts(nspins=nspins, N_c=N_c, op_scc=op_scc, ft_sc=ft_sc, supercell=supercell, pbc_c=True) def data_pointed(index_tuple, value): """ Creates Vt data with a single nonzero value. """ array = np.zeros((nspins,) + N_c) array[(0,) + index_tuple] = value return array data_zero = np.zeros((nspins,) + N_c) disp_atoms, disp_carts, disp_values = zip(*[ DispData(0, [ 0.1, 0, 0], data=data_pointed((2, 0, 2), 1.0)), DispData(0, [-0.1, 0, 0], data=data_pointed((2, 0, 2), -1.0)), DispData(0, [ 0, 0.1, 0], data=data_zero), DispData(0, [ 0, -0.1, 0], data=data_zero), DispData(0, [ 0, 0, 0.1], data=data_zero), DispData(0, [ 0, 0, -0.1], data=data_zero), ]) derivs = symmetry.expand_derivs_by_symmetry( callbacks=callbacks, disp_atoms=disp_atoms, disp_carts=disp_carts, disp_values=disp_values, oper_cart_rots=oper_cart_rots, oper_perms=oper_perms, quotient_perms=quotient_perms, ) derivs = np.array(derivs.tolist()) expected = np.array([[ data_pointed((2, 0, 2), 10.0), # derivative w.r.t. atom 0 x data_zero, # derivative w.r.t. atom 0 y data_zero, # derivative w.r.t. atom 0 z ], [ data_zero, # derivative w.r.t. atom 1 x data_pointed((0, 2, 2), 10.0), # derivative w.r.t. atom 1 y data_zero, # derivative w.r.t. atom 1 z ], [ data_pointed(((-2)%5, 0, 2), -10.0), # derivative w.r.t. atom 2 x data_zero, # derivative w.r.t. atom 2 y data_zero, # derivative w.r.t. atom 2 z ], [ data_zero, # derivative w.r.t. atom 3 x data_pointed((0, (-2)%5, 2), -10.0), # derivative w.r.t. atom 3 y data_zero, # derivative w.r.t. atom 3 z ]]) print('actual', list(zip(*np.where(derivs)))) print('expected', list(zip(*np.where(expected)))) np.testing.assert_allclose(derivs, expected)
def test_general_array_vec(): sym = sym_xyz_1_atom() callbacks = symmetry.GeneralArrayCallbacks(['cart']) disp_atoms, disp_carts, disp_values = zip(*[ DispData(0, [ 0.1, 0, 0], data=np.array([ 1., 0, 0])), DispData(0, [-0.1, 0, 0], data=np.array([-1., 0, 0])), ]) derivs = symmetry.expand_derivs_by_symmetry( callbacks=callbacks, disp_atoms=disp_atoms, disp_carts=disp_carts, disp_values=disp_values, oper_cart_rots=sym.oper_cart_rots, oper_perms=sym.oper_perms, quotient_perms=sym.quotient_perms, ) derivs = np.array(derivs.tolist()) assert np.allclose(derivs, np.array([[ [10, 0, 0], [0, 10, 0], [0, 0, 10], ]]))
def do_elph_symmetry( data_subdir: str, params_fd: dict, supercell, all_displacements: tp.Iterable[AseDisplacement], symmetry_type: tp.Optional[str], ): atoms = Cluster(ase.build.bulk('C')) # a supercell exactly like ElectronPhononCoupling makes supercell_atoms = atoms * supercell quotient_perms = list( interop.ase_repeat_translational_symmetry_perms(len(atoms), supercell)) # Make sure the grid matches our calculations (we repeated the grid of the groundstate) params_fd = copy.deepcopy(params_fd) params_fd['gpts'] = GPAW('gs.gpw').wfs.gd.N_c * list(supercell) if 'h' in params_fd: del params_fd['h'] wfs_with_sym = get_wfs_with_sym(params_fd=params_fd, supercell_atoms=supercell_atoms, symmetry_type=symmetry_type) calc_fd = GPAW(**params_fd) # GPAW displaces the center cell for some reason instead of the first cell elph = ElectronPhononCoupling(atoms, calc=calc_fd, supercell=supercell, calculate_forces=True) displaced_cell_index = elph.offset del elph # just showing that we don't use these anymore del calc_fd get_displaced_index = lambda prim_atom: displaced_cell_index * len( atoms) + prim_atom all_displacements = list(all_displacements) disp_atoms = [get_displaced_index(disp.atom) for disp in all_displacements] disp_carts = [ disp.cart_displacement(DISPLACEMENT_DIST) for disp in all_displacements ] disp_values = [ read_elph_input(data_subdir, disp) for disp in all_displacements ] full_Vt = np.empty((len(supercell_atoms), 3) + disp_values[0][0].shape) full_dH = np.empty((len(supercell_atoms), 3), dtype=object) full_forces = np.empty((len(supercell_atoms), 3) + disp_values[0][2].shape) lattice = supercell_atoms.get_cell()[...] oper_cart_rots = interop.gpaw_op_scc_to_cart_rots( wfs_with_sym.kd.symmetry.op_scc, lattice) if world.rank == 0: full_values = symmetry.expand_derivs_by_symmetry( disp_atoms, # disp -> atom disp_carts, # disp -> 3-vec disp_values, # disp -> T (displaced value, optionally minus equilibrium value) elph_callbacks(wfs_with_sym, supercell), # how to work with T oper_cart_rots, # oper -> 3x3 oper_perms=wfs_with_sym.kd.symmetry.a_sa, # oper -> atom' -> atom quotient_perms=quotient_perms, ) for a in range(len(full_values)): for c in range(3): full_Vt[a][c] = full_values[a][c][0] full_dH[a][c] = full_values[a][c][1] full_forces[a][c] = full_values[a][c][2] else: # FIXME # the symmetry part is meant to be done in serial but we should return back to # our original parallel state after it... pass return full_Vt, full_dH, full_forces