Coverage for dynasor / modes / project_modes.py: 20%
32 statements
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-16 12:31 +0000
« prev ^ index » next coverage.py v7.13.4, created at 2026-03-16 12:31 +0000
1from typing import Optional
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 dynasor.tools.structures import get_displacements_from_u
13def project_modes(
14 traj: Trajectory,
15 modes: NDArray[float],
16 ideal_supercell: Atoms,
17 check_mic: Optional[bool] = True,
18) -> tuple[NDArray[float], NDArray[float]]:
19 """Projects an atomic trajectory onto set of phonon modes.
21 Parameters
22 ----------
23 traj
24 Input trajectory.
25 modes
26 Modes to project on, as an array with shape ``(..., N, 3)`` where ``N`` is the
27 number of atoms in the supercell and the leading dimensions define the output shape.
28 ideal_supercell
29 Ideal supercell used to find atomic displacements. It should correspond to the ideal
30 structure. Be careful not to mess up the permutation.
31 check_mic
32 Whether to wrap the displacements or not, faster if no wrap.
34 Returns
35 -------
36 A tuple comprising `(Q,P)` where `Q` are the mode coordinates as a complex array
37 with dimension (length of traj, number of modes) and `P` are the mode momenta as a
38 complex array with dimension (length of traj, number of modes).
39 """
40 # logger
41 logger.info('Running mode projection')
43 modes = np.array(modes)
45 original_mode_shape = modes.shape
47 if modes.shape[-2] != traj.n_atoms:
48 raise ValueError('Second dim in modes must be same len as number of atoms in trajectory')
49 if traj.n_atoms != len(ideal_supercell):
50 raise ValueError('ideal_supercell must contain the same number of atoms as the trajectory.')
52 modes = modes.reshape((-1, modes.shape[-2], 3))
54 Q_traj, P_traj = [], []
55 for it, frame in enumerate(traj):
56 logger.debug(f'Reading frame {it}')
58 # Make positions into displacements
59 x = frame.get_positions_as_array(traj._atomic_indices)
60 u = x - ideal_supercell.positions
62 # Calculate Q
63 u = get_displacements_from_u(u, ideal_supercell.cell, check_mic=check_mic)
64 Q = np.einsum('mnx,nx->m', modes, u)
66 # Calculate P
67 if frame.velocities_by_type is not None:
68 v = frame.get_velocities_as_array(traj._atomic_indices)
69 P = np.einsum('mna,na->m', modes.conj(), v)
70 else:
71 P = np.zeros_as(Q)
73 Q_traj.append(Q)
74 P_traj.append(P)
76 Q_traj = Q_traj.reshape((len(Q_traj), *original_mode_shape[:-2]))
77 P_traj = P_traj.reshape((len(P_traj), *original_mode_shape[:-2]))
79 return np.array(Q_traj), np.array(P_traj)