Coverage for gpaw/elph/raman_data.py: 18%

78 statements  

« prev     ^ index     » next       coverage.py v7.7.1, created at 2025-07-14 00:18 +0000

1""" 

2Calculates Raman matrices from Raman tensor 

3""" 

4import numpy as np 

5from typing import Tuple 

6 

7from ase.units import invcm 

8from ase.utils.filecache import MultiFileJSONCache 

9from gpaw.typing import ArrayND 

10 

11 

12def gaussian(w, sigma): 

13 g = 1.0 / (np.sqrt(2.0 * np.pi) * sigma) * np.exp(-(w**2) / (2 * sigma**2)) 

14 return g 

15 

16 

17class RamanData: 

18 def __init__(self, raman_name="Rlab", gridspacing=1.0) -> None: 

19 """Raman Spectroscopy data. 

20 

21 Parameters 

22 ---------- 

23 raman_name: str 

24 Name of Rlab file cache. Default 'Rlab' 

25 gridspacing: float 

26 grid spacing in cm^-1, default 1 rcm 

27 """ 

28 # JSON cache 

29 self.raman_cache = MultiFileJSONCache(raman_name) 

30 self.wph_w = self.raman_cache["phonon_frequencies"] # eV 

31 self.wrl_w = self.raman_cache["frequency_grid"] # eV 

32 

33 gridmax = self.wph_w[-1] + 6.3e-3 # highest freq + 50rcm 

34 self.gridev_w = np.linspace( 

35 0.0, 

36 gridmax, 

37 num=int(gridmax / (gridspacing * invcm) + 1), 

38 ) # eV 

39 

40 def calculate_raman_intensity(self, d_i, d_o, T=300, sigma=5.0): 

41 """ 

42 Calculates Raman intensities from Raman tensor. 

43 

44 Returns bare `|R|^2` and and Bose occupation weighted values 

45 

46 Parameters 

47 ---------- 

48 d_i: int 

49 Incoming polarization 

50 d_o: int 

51 Outgoing polarization 

52 T: float 

53 Temperature in Kelvin 

54 sigma: float 

55 Gaussian line width in rcm, default 5rcm 

56 """ 

57 

58 KtoeV = 8.617278e-5 

59 sigmaev = sigma * invcm # 1eV = 8.065544E3 rcm 

60 

61 # grid for spectrum with extra space and sigma/5 spacing 

62 if self.gridev_w is None: 

63 self.gridev_w = np.linspace( 

64 0.0, 

65 self.wph_w[-1] * 1.1, 

66 num=int(self.wph_w[-1] / (sigmaev / 5) + 100), 

67 ) 

68 

69 int_bare = np.zeros_like(self.gridev_w, dtype=float) 

70 int_occ = np.zeros_like(self.gridev_w, dtype=float) 

71 

72 # Note: Resonant Raman does not have a frequency grid 

73 R_lw = self.raman_cache[f"{'xyz'[d_i]}{'xyz'[d_o]}"] 

74 

75 for l in range(len(self.wph_w)): 

76 occ = 1.0 / (np.exp(self.wph_w[l] / (KtoeV * T)) - 1.0) + 1.0 

77 delta_w = gaussian((self.gridev_w - self.wph_w[l]), sigmaev) 

78 R2_w = np.abs(R_lw[l])**2 

79 R2d_w = R2_w * delta_w 

80 int_bare += R2d_w 

81 int_occ += occ / self.wph_w[l] * R2d_w 

82 

83 return int_bare, int_occ 

84 

85 def calculate_raman_spectrum(self, 

86 entries, T=300, sigma=5.0 

87 ) -> Tuple[ArrayND, ArrayND]: 

88 """ 

89 Calculates Raman intensities from Raman tensor. 

90 

91 Returns Raman shift in eV, bare `|R|^2` 

92 and Bose occupation weighted `|R|^2` values 

93 

94 Parameters 

95 ---------- 

96 entries: str, list 

97 Sting or list of strings with desired polarisaitons 

98 For example: ["xx", "yy", "xy", "yx"] 

99 T: float 

100 Temperature in Kelvin 

101 sigma: float 

102 Gaussian line width in rcm, default 5rcm 

103 """ 

104 if isinstance(entries, str): 

105 entries = [ 

106 entries, 

107 ] 

108 

109 spectrum_w = np.zeros_like(self.gridev_w) 

110 

111 polnum = {"x": 0, "y": 1, "z": 2} 

112 for entry in entries: 

113 d_i = polnum[entry[0]] 

114 d_o = polnum[entry[1]] 

115 (int_bare, _) = self.calculate_raman_intensity(d_i, d_o, T, sigma) 

116 spectrum_w += int_bare 

117 

118 return self.gridev_w, spectrum_w 

119 

120 @classmethod 

121 def plot_raman( 

122 cls, 

123 figname, 

124 grid_w, 

125 spectra_nw, 

126 labels_n=None, 

127 relative=False, 

128 ): 

129 """Plots a given Raman spectrum. 

130 

131 Parameters 

132 ---------- 

133 figname: str 

134 Filename for figure. 

135 grid_w: 

136 Frequency grid of spectrum in eV 

137 spectra_nw: np.ndarray 

138 Raman spectra to plot 

139 labels_n: list 

140 Labels for the legend 

141 relative: bool 

142 If true, normalize each spectrum to 1 

143 Default is False 

144 """ 

145 

146 from scipy import signal 

147 import matplotlib.pyplot as plt 

148 

149 if not isinstance(spectra_nw, np.ndarray): 

150 spectra_nw = np.asarray(spectra_nw) 

151 

152 ylabel = "Intensity (arb. units)" 

153 if relative: 

154 ylabel = "I/I_max" 

155 spectra_nw /= np.max(spectra_nw, axis=1)[:, np.newaxis] 

156 else: 

157 spectra_nw /= np.max(spectra_nw) 

158 

159 nspec = spectra_nw.shape[0] 

160 if labels_n is None: 

161 labels_n = nspec * [None] 

162 

163 grid_w = cls.eVtorcm(grid_w) 

164 

165 for i in range(nspec): 

166 peaks = signal.find_peaks(spectra_nw[i])[0] 

167 locations = np.take(grid_w, peaks) 

168 intensities = np.take(spectra_nw[i], peaks) 

169 

170 plt.plot(grid_w, spectra_nw[i], label=labels_n[i]) 

171 

172 for j, loc in enumerate(locations): 

173 if intensities[j] / np.max(intensities) > 0.05: 

174 plt.axvline(x=loc, color="grey", linestyle="--") 

175 

176 plt.minorticks_on() 

177 if labels_n[0] is not None: 

178 plt.legend() 

179 plt.title("Raman intensity") 

180 plt.xlabel("Raman shift (cm$^{-1}$)") 

181 plt.ylabel(ylabel) 

182 if relative: 

183 plt.yticks([]) 

184 

185 plt.savefig(figname, dpi=300) 

186 plt.clf() 

187 

188 @classmethod 

189 def eVtorcm(cls, energy): 

190 return energy / invcm