11import pathlib
22import numpy
33
4+ from numpy .typing import NDArray
5+
46import phonopy
57from phonopy .interface import calculator as phonopy_calculator
68
7- from typing import Optional , List , Tuple
9+ from typing import Optional , List , Tuple , Union
810
911from phonopy_vibspec import logger
12+ from phonopy .units import VaspToCm
1013from phonopy_vibspec .spectra import RamanSpectrum , InfraredSpectrum
1114from phonopy_vibspec .vesta import VestaVector , make_vesta_file
1215
13- THZ_TO_INV_CM = 33.35641
14-
1516# [(value, coef), ...]
1617# see https://en.wikipedia.org/wiki/Finite_difference_coefficient
1718TWO_POINTS_STENCIL = [(- 1 , - .5 ), (1 , .5 )] # two-points, centered
2122
2223
2324class PhononsAnalyzer :
24- """Use Phonopy to extract phonon frequencies and eigenmodes, as well as irreps
25+ """Use Phonopy to extract phonon frequencies and eigenmodes, as well as irreps, at a given q-point
26+ (default is Gamma).
2527 """
2628
2729 DC_GEOMETRY_TEMPLATE = 'dielec_mode{:04d}_step{:02d}.vasp'
2830 VESTA_MODE_TEMPLATE = 'mode{:04d}.vesta'
2931
30- def __init__ (self , phonon : phonopy .Phonopy ):
31- self .phonotopy = phonon
32+ def __init__ (self , phonon : phonopy .Phonopy , q : Union [NDArray , Tuple [float , float , float ]] = (.0 , .0 , .0 )):
33+ self .phonopy = phonon
34+ self .q = q
3235 self .structure = phonon .primitive
3336
3437 # get eigenvalues and eigenvectors at gamma point
3538 # See https://github.com/phonopy/phonopy/issues/308#issuecomment-1769736200
3639 l_logger .info ('Symmetrize force constant' )
37- self .phonotopy .symmetrize_force_constants ()
38-
39- l_logger .info ('Run mesh' )
40- self .phonotopy .run_mesh ([1 , 1 , 1 ], with_eigenvectors = True )
40+ self .phonopy .symmetrize_force_constants ()
4141
42- mesh_dict = phonon .get_mesh_dict ()
42+ l_logger .info ('Fetch dynamical matrix at q=({})' .format (', ' .join ('{:.3f}' .format (x ) for x in q )))
43+ self .phonopy .dynamical_matrix .run (self .q )
44+ dm = self .phonopy .dynamical_matrix .dynamical_matrix
45+ eigv , eigf = numpy .linalg .eigh (dm )
4346
4447 self .N = self .structure .get_number_of_atoms ()
4548 l_logger .info ('Analyze {} modes (including acoustic)' .format (3 * self .N ))
46- self .frequencies = mesh_dict [ 'frequencies' ][ 0 ] * THZ_TO_INV_CM # in [cm⁻¹]
49+ self .frequencies = numpy . sqrt ( numpy . abs ( eigv . real )) * numpy . sign ( eigv . real ) * VaspToCm # in [cm⁻¹]
4750
48- l_logger .info ('The 5 first modes are {}' .format (
51+ l_logger .info ('The 5 first modes are at (in cm⁻¹) {}' .format (
4952 ', ' .join ('{:.3f}' .format (x ) for x in self .frequencies [:5 ]))
5053 )
5154
52- self .eigenvectors = mesh_dict [ 'eigenvectors' ][ 0 ] .real .T # in [Å sqrt(AMU)]
55+ self .eigenvectors = eigf .real .T # in [Å sqrt(AMU)]
5356
5457 # compute displacements with Eq. 4 of 10.1039/C7CP01680H
5558 sqrt_masses = numpy .repeat (numpy .sqrt (self .structure .masses ), 3 )
@@ -59,7 +62,7 @@ def __init__(self, phonon: phonopy.Phonopy):
5962 self .irrep_labels = ['A' ] * (self .N * 3 )
6063
6164 try :
62- self .phonotopy .set_irreps ([ 0 , 0 , 0 ] )
65+ self .phonopy .set_irreps (q )
6366 self .irreps = phonon .get_irreps ()
6467
6568 # TODO: that's internal API, so subject to change!
@@ -74,7 +77,8 @@ def from_phonopy(
7477 cls ,
7578 phonopy_yaml : str = 'phonopy_disp.yaml' ,
7679 force_constants_filename : str = 'force_constants.hdf5' ,
77- born_filename : Optional [str ] = None
80+ born_filename : Optional [str ] = None ,
81+ q : Union [NDArray , Tuple [float , float , float ]] = (.0 , .0 , .0 )
7882 ) -> 'PhononsAnalyzer' :
7983 """
8084 Use the Python interface of Phonopy, see https://phonopy.github.io/phonopy/phonopy-module.html.
@@ -86,7 +90,7 @@ def from_phonopy(
8690 phonopy_yaml = phonopy_yaml ,
8791 force_constants_filename = force_constants_filename ,
8892 born_filename = born_filename ,
89- ))
93+ ), q = q )
9094
9195 def infrared_spectrum (self , modes : Optional [List [int ]] = None ) -> InfraredSpectrum :
9296 """
@@ -96,11 +100,11 @@ def infrared_spectrum(self, modes: Optional[List[int]] = None) -> InfraredSpectr
96100
97101 l_logger .info ('Create IR spectrum object' )
98102
99- born_tensor = self .phonotopy .nac_params ['born' ]
103+ born_tensor = self .phonopy .nac_params ['born' ]
100104
101105 # select modes if any
102106 if modes is None :
103- modes = list (range (3 , 3 * self .N ))
107+ modes = list (range (3 if numpy . allclose ( self . q , [ .0 , .0 , .0 ]) else 0 , 3 * self .N ))
104108 else :
105109 for mode in modes :
106110 if mode < 0 or mode >= 3 * self .N :
@@ -139,7 +143,11 @@ def prepare_raman(
139143
140144 # select modes if any
141145 if modes is None :
142- modes = list (range (3 , 3 * self .N ))
146+ modes = list (range (3 if numpy .allclose (self .q , [.0 , .0 , .0 ]) else 0 , 3 * self .N ))
147+ else :
148+ for mode in modes :
149+ if mode < 0 or mode >= 3 * self .N :
150+ raise IndexError (mode )
143151
144152 frequencies = [self .frequencies [m ] for m in modes ]
145153 irrep_labels = [self .irrep_labels [m ] for m in modes ]
0 commit comments