Coverage for local_installation/dynasor/modes/project_modes.py: 20%
32 statements
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-15 07:34 +0000
« prev ^ index » next coverage.py v7.9.2, created at 2025-07-15 07:34 +0000
1from typing import Tuple
3import numpy as np
5from ase import Atoms
6from numpy.typing import NDArray
8from dynasor.logging_tools import logger
9from dynasor.trajectory import Trajectory
10from .tools import get_wrapped_displacements
13def project_modes(traj: Trajectory, modes: NDArray[float], ideal_supercell: Atoms, check_mic=True
14 ) -> Tuple[NDArray[float], NDArray[float]]:
15 """Projects an atomic trajectory onto set of phonon modes
17 Parameters
18 ----------
19 traj
20 Input trajectory
21 modes
22 Modes to project on (1, Nlambda, N, 3) array where Nm is the number of modes
23 and N is the number of atoms in the supercell
24 ideal_supercell
25 Used to find atomic displacements and should correspond to the ideal
26 structure. Be careful not to mess up the permutation
27 check_mic
28 Whether to wrap the displacements or not, faster if no wrap.
30 Returns
31 -------
32 Q
33 mode coordinates, complex ndarray (length of traj, number of modes)
34 P
35 mode momenta, complex ndarray (length of traj, number of modes)
36 """
37 # logger
38 logger.info('Running mode projection')
40 modes = np.array(modes)
42 original_mode_shape = modes.shape
44 if modes.shape[-2] != traj.n_atoms:
45 raise ValueError('Second dim in modes must be same len as number of atoms in trajectory')
46 if traj.n_atoms != len(ideal_supercell):
47 raise ValueError('ideal_supercell must contain the same number of atoms as the trajectory.')
49 modes = modes.reshape((-1, modes.shape[-2], 3))
51 Q_traj, P_traj = [], []
52 for it, frame in enumerate(traj):
53 logger.debug(f'Reading frame {it}')
55 # Make positions into displacements
56 x = frame.get_positions_as_array(traj._atomic_indices)
57 u = x - ideal_supercell.positions
59 # Calculate Q
60 u = get_wrapped_displacements(u, ideal_supercell.cell, check_mic=check_mic)
61 Q = np.einsum('mnx,nx->m', modes, u)
63 # Calculate P
64 if frame.velocities_by_type is not None:
65 v = frame.get_velocities_as_array(traj._atomic_indices)
66 P = np.einsum('mna,na->m', modes.conj(), v)
67 else:
68 P = np.zeros_as(Q)
70 Q_traj.append(Q)
71 P_traj.append(P)
73 Q_traj = Q_traj.reshape((len(Q_traj), *original_mode_shape[:-2]))
74 P_traj = P_traj.reshape((len(P_traj), *original_mode_shape[:-2]))
76 return np.array(Q_traj), np.array(P_traj)