Coverage for gpaw/lcaotddft/dipolemomentwriter.py: 83%

162 statements  

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

1import re 

2 

3import numpy as np 

4 

5from ase.utils import IOContext 

6 

7from gpaw.lcaotddft.magneticmomentwriter import parse_header 

8 

9from gpaw.lcaotddft.observer import TDDFTObserver 

10 

11 

12def convert_repr(r): 

13 # Integer 

14 try: 

15 return int(r) 

16 except ValueError: 

17 pass 

18 # Boolean 

19 b = {repr(False): False, repr(True): True}.get(r, None) 

20 if b is not None: 

21 return b 

22 # String 

23 s = r[1:-1] 

24 if repr(s) == r: 

25 return s 

26 raise RuntimeError('Unknown value: %s' % r) 

27 

28 

29class DipoleMomentWriter(TDDFTObserver): 

30 """Observer for writing time-dependent dipole moment data. 

31 

32 The data is written in atomic units. 

33 

34 The observer attaches to the TDDFT calculator during creation. 

35 

36 Parameters 

37 ---------- 

38 paw 

39 TDDFT calculator 

40 filename 

41 File for writing dipole moment data 

42 center 

43 If true, dipole moment is evaluated with the center of cell 

44 as the origin 

45 density 

46 Density type used for evaluating dipole moment. 

47 Use the default value for production calculations; 

48 others are for testing purposes. 

49 Possible values: 

50 ``'comp'``: ``rhot_g``, 

51 ``'pseudo'``: ``nt_sg``, 

52 ``'pseudocoarse'``: ``nt_sG``. 

53 force_new_file 

54 If true, new dipole moment file is created (erasing any existing one) 

55 even when restarting time propagation. 

56 interval 

57 Update interval. Value of 1 corresponds to evaluating and 

58 writing data after every propagation step. 

59 """ 

60 version = 1 

61 

62 def __init__(self, paw, filename: str, *, 

63 center: bool = False, 

64 density: str = 'comp', 

65 force_new_file: bool = False, 

66 interval: int = 1): 

67 TDDFTObserver.__init__(self, paw, interval) 

68 self.ioctx = IOContext() 

69 if paw.niter == 0 or force_new_file: 

70 # Initialize 

71 self.do_center = center 

72 self.density_type = density 

73 self.fd = self.ioctx.openfile(filename, comm=paw.world, mode='w') 

74 self._write_header(paw) 

75 else: 

76 # Read and continue 

77 self.read_header(filename) 

78 self.fd = self.ioctx.openfile(filename, comm=paw.world, mode='a') 

79 

80 def _write(self, line): 

81 self.fd.write(line) 

82 self.fd.flush() 

83 

84 def _write_header(self, paw): 

85 line = f'# {self.__class__.__name__}[version={self.version}]' 

86 line += ('(center=%s, density=%s)\n' % 

87 (repr(self.do_center), repr(self.density_type))) 

88 line += ('# %15s %15s %22s %22s %22s\n' % 

89 ('time', 'norm', 'dmx', 'dmy', 'dmz')) 

90 self._write(line) 

91 

92 def read_header(self, filename): 

93 with open(filename) as f: 

94 line = f.readline() 

95 m_i = re.split("[^a-zA-Z0-9_=']+", line[2:]) 

96 assert m_i.pop(0) == self.__class__.__name__ 

97 for m in m_i: 

98 if '=' not in m: 

99 continue 

100 k, v = m.split('=') 

101 v = convert_repr(v) 

102 if k == 'version': 

103 assert v == self.version 

104 continue 

105 # Translate key 

106 k = {'center': 'do_center', 'density': 'density_type'}[k] 

107 setattr(self, k, v) 

108 

109 def _write_init(self, paw): 

110 time = paw.time 

111 line = '# Start; Time = %.8lf\n' % time 

112 self._write(line) 

113 

114 def _write_kick(self, paw): 

115 time = paw.time 

116 kick = paw.kick_strength 

117 gauge = paw.kick_gauge 

118 line = '# Kick = [%22.12le, %22.12le, %22.12le]; ' % tuple(kick) 

119 line += 'Gauge = %s; ' % gauge 

120 line += 'Time = %.8lf\n' % time 

121 self._write(line) 

122 

123 def calculate_dipole_moment(self, gd, rho_g, center=True): 

124 if center: 

125 center_v = 0.5 * gd.cell_cv.sum(0) 

126 else: 

127 center_v = np.zeros(3, dtype=float) 

128 r_vg = gd.get_grid_point_coordinates() 

129 dm_v = np.zeros(3, dtype=float) 

130 for v in range(3): 

131 dm_v[v] = - gd.integrate((r_vg[v] - center_v[v]) * rho_g) 

132 return dm_v 

133 

134 def _write_dm(self, paw): 

135 time = paw.time 

136 density = paw.density 

137 if self.density_type == 'comp': 

138 rho_g = density.rhot_g 

139 gd = density.finegd 

140 elif self.density_type == 'pseudo': 

141 rho_g = density.nt_sg.sum(axis=0) 

142 gd = density.finegd 

143 elif self.density_type == 'pseudocoarse': 

144 rho_g = density.nt_sG.sum(axis=0) 

145 gd = density.gd 

146 else: 

147 raise RuntimeError('Unknown density type: %s' % self.density_type) 

148 

149 norm = gd.integrate(rho_g) 

150 # dm = self.calculate_dipole_moment(gd, rho_g, center=self.do_center) 

151 dm = gd.calculate_dipole_moment(rho_g, center=self.do_center) 

152 if paw.hamiltonian.poisson.get_description() == 'FDTD+TDDFT': # XXX 

153 dm += paw.hamiltonian.poisson.get_classical_dipole_moment() 

154 line = ('%20.8lf %20.8le %22.12le %22.12le %22.12le\n' % 

155 (time, norm, dm[0], dm[1], dm[2])) 

156 self._write(line) 

157 

158 def _update(self, paw): 

159 if paw.action == 'init': 

160 self._write_init(paw) 

161 elif paw.action == 'kick': 

162 self._write_kick(paw) 

163 self._write_dm(paw) 

164 

165 def __del__(self): 

166 self.ioctx.close() 

167 

168 

169# TODO: Create a common base class for VelocityGaugeWriter 

170# and DipoleMomentWriter. 

171class VelocityGaugeWriter(TDDFTObserver): 

172 """Observer for writing time-dependent velocity-kick density 

173 for test purposes. 

174 

175 The data is written in atomic units. 

176 

177 The observer attaches to the TDDFT calculator during creation. 

178 

179 Parameters 

180 ---------- 

181 paw 

182 TDDFT calculator 

183 filename 

184 File for writing density data 

185 """ 

186 version = 5 

187 

188 def __init__(self, paw, filename: str, interval: int = 1): 

189 super().__init__(paw, interval) 

190 self.ioctx = IOContext() 

191 mode = paw.wfs.mode 

192 assert mode in ['lcao'] 

193 

194 if paw.niter == 0: 

195 self.fd = self.ioctx.openfile(filename, comm=paw.world, mode='w') 

196 else: 

197 self.fd = self.ioctx.openfile(filename, comm=paw.world, mode='a') 

198 

199 def _write(self, line): 

200 self.fd.write(line) 

201 self.fd.flush() 

202 

203 def _write_header(self, paw, kwargs): 

204 line = f'# {self.__class__.__name__}[version={self.version}]\n' 

205 line += ('# %15s %15s %22s %22s %22s\n' % 

206 ('time', 'norm', 'rhoVMM_x', 'rhoVMM_y', 'rhoVMM_z')) 

207 self._write(line) 

208 

209 def _read_header(self, filename): 

210 with open(filename, encoding='utf-8') as fd: 

211 line = fd.readline() 

212 try: 

213 name, version, kwargs = parse_header(line[2:]) 

214 except ValueError as e: 

215 raise ValueError(f'File {filename} cannot be parsed: {e}') 

216 if name != self.__class__.__name__: 

217 raise ValueError(f'File {filename} is not ' 

218 f'for {self.__class__.__name__}') 

219 if version != self.version: 

220 raise ValueError(f'File {filename} is not ' 

221 f'of version {self.version}') 

222 return kwargs 

223 

224 def _write_init(self, paw): 

225 time = paw.time 

226 line = '# Start; Time = %.8lf\n' % time 

227 self._write(line) 

228 

229 def _write_kick(self, paw): 

230 time = paw.time 

231 kick = paw.kick_strength 

232 gauge = paw.kick_gauge 

233 line = '# Kick = [%22.12le, %22.12le, %22.12le]; ' % tuple(kick) 

234 line += 'Gauge = %s; ' % gauge 

235 line += 'Time = %.8lf\n' % time 

236 self._write(line) 

237 

238 def _calculate_v(self, paw): 

239 C_nM = paw.wfs.kpt_u[0].C_nM 

240 f_n = paw.wfs.kpt_u[0].f_n 

241 rho_MM = C_nM.T.conj() @ (f_n[:, None] * C_nM) 

242 Vkick_vmm = paw.wfs.kpt_u[0].Vkick_vmm 

243 return [np.sum((rho_MM * Vkick_mm.conj()).ravel()) 

244 for Vkick_mm in Vkick_vmm] 

245 

246 def _write_v(self, paw): 

247 time = paw.time 

248 v_v = [v.real for v in self._calculate_v(paw)] 

249 line = ('%20.8lf %22.12le %22.12le %22.12le %22.12le\n' 

250 % (time, 0.0, v_v[0], v_v[1], v_v[2])) 

251 self._write(line) 

252 

253 def _update(self, paw): # This does not seem to work in here 

254 if paw.action == 'init': 

255 paw.propagator.calculate_velocity_operator_matrix() 

256 self._write_header(paw, {}) 

257 self._write_init(paw) 

258 elif paw.action == 'kick': 

259 self._write_kick(paw) 

260 self._write_v(paw) 

261 

262 def __del__(self): 

263 self.ioctx.close()