Coverage for gpaw/lcaotddft/__init__.py: 84%

126 statements  

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

1from __future__ import annotations 

2 

3from typing import Optional 

4 

5import numpy as np 

6from ase.units import Bohr, Hartree 

7 

8from gpaw import GPAW_NEW 

9from gpaw.calculator import GPAW 

10from gpaw.external import ConstantElectricField, ExternalPotential 

11from gpaw.lcaotddft.hamiltonian import TimeDependentHamiltonian 

12from gpaw.lcaotddft.logger import TDDFTLogger 

13from gpaw.lcaotddft.propagators import create_propagator 

14from gpaw.tddft.units import attosec_to_autime 

15from gpaw.typing import Any, Vector 

16 

17 

18def LCAOTDDFT(filename: str, **kwargs) -> Any: 

19 if GPAW_NEW: 

20 from gpaw.new.rttddft import RTTDDFT 

21 assert kwargs.get('propagator', None) in [None, 'ecn'], \ 

22 'Not implemented yet' 

23 assert kwargs.get('rremisison', None) in [None], 'Not implemented yet' 

24 assert kwargs.get('fxc', None) in [None], 'Not implemented yet' 

25 assert kwargs.get('scale', None) in [None], 'Not implemented yet' 

26 assert kwargs.get('parallel', None) in [None], 'Not implemented yet' 

27 assert kwargs.get('communicator', None) in [None], \ 

28 'Not implemented yet' 

29 new_tddft = RTTDDFT.from_dft_file(filename) 

30 return new_tddft 

31 return OldLCAOTDDFT(filename, **kwargs) 

32 

33 

34class OldLCAOTDDFT(GPAW): 

35 """Real-time time-propagation TDDFT calculator with LCAO basis. 

36 

37 Parameters 

38 ---------- 

39 filename 

40 File containing ground state or time-dependent state to propagate 

41 propagator 

42 Time propagator for the Kohn-Sham wavefunctions 

43 td_potential 

44 External time-dependent potential 

45 rremission 

46 Radiation-reaction potential for Self-consistent Light-Matter coupling 

47 fxc 

48 Exchange-correlation functional used for 

49 the dynamic part of Hamiltonian 

50 scale 

51 Experimental option (use carefully). 

52 Scaling factor for the dynamic part of Hamiltonian 

53 parallel 

54 Parallelization options 

55 communicator 

56 MPI communicator 

57 txt 

58 Text output 

59 """ 

60 def __init__(self, filename: str, *, 

61 propagator: dict | None = None, 

62 td_potential: dict | None = None, 

63 rremission=None, 

64 fxc: str | None = None, 

65 scale: float | None = None, 

66 parallel: dict | None = None, 

67 communicator=None, 

68 txt: str = '-'): 

69 """""" 

70 assert filename is not None 

71 self.time = 0.0 

72 self.niter = 0 

73 # TODO: deprecate kick keywords (and store them as td_potential) 

74 self.kick_strength = np.zeros(3) 

75 self.kick_ext: Optional[ExternalPotential] = None 

76 self.tddft_initialized = False 

77 self.action = '' 

78 tdh = TimeDependentHamiltonian(fxc=fxc, td_potential=td_potential, 

79 scale=scale, rremission=rremission) 

80 self.td_hamiltonian = tdh 

81 

82 self.propagator_set = propagator is not None 

83 self.propagator = create_propagator(propagator) 

84 GPAW.__init__(self, filename, parallel=parallel, 

85 communicator=communicator, txt=txt) 

86 if len(self.symmetry.op_scc) > 1: 

87 raise ValueError('Symmetries are not allowed for LCAOTDDFT. ' 

88 'Run the ground state calculation with ' 

89 'symmetry={"point_group": False}.') 

90 

91 self.set_positions() 

92 

93 def write(self, filename, mode=''): 

94 # This function is included here in order to generate 

95 # documentation for LCAOTDDFT.write() with autoclass in sphinx 

96 GPAW.write(self, filename, mode=mode) 

97 

98 def _write(self, writer, mode): 

99 GPAW._write(self, writer, mode) 

100 if self.tddft_initialized: 

101 w = writer.child('tddft') 

102 w.write(time=self.time, 

103 niter=self.niter, 

104 kick_strength=self.kick_strength, 

105 propagator=self.propagator.todict()) 

106 self.td_hamiltonian.write(w.child('td_hamiltonian')) 

107 

108 def read(self, filename): 

109 reader = GPAW.read(self, filename) 

110 if 'tddft' in reader: 

111 r = reader.tddft 

112 self.time = r.time 

113 self.niter = r.niter 

114 self.kick_strength = r.kick_strength 

115 if not self.propagator_set: 

116 self.propagator = create_propagator(r.propagator) 

117 else: 

118 self.log('Note! Propagator possibly changed!') 

119 self.td_hamiltonian.wfs = self.wfs 

120 self.td_hamiltonian.read(r.td_hamiltonian) 

121 

122 def tddft_init(self): 

123 if self.tddft_initialized: 

124 return 

125 

126 self.log('-----------------------------------') 

127 self.log('Initializing time-propagation TDDFT') 

128 self.log('-----------------------------------') 

129 self.log() 

130 

131 assert self.wfs.dtype == complex 

132 

133 self.timer.start('Initialize TDDFT') 

134 

135 # Initialize Hamiltonian 

136 self.td_hamiltonian.initialize(self) 

137 

138 # Initialize propagator 

139 self.propagator.initialize(self) 

140 

141 self.log('Propagator:') 

142 self.log(self.propagator.get_description()) 

143 self.log() 

144 

145 # Add logger 

146 TDDFTLogger(self) 

147 

148 # Call observers before propagation 

149 self.action = 'init' 

150 self.call_observers(self.niter) 

151 

152 self.tddft_initialized = True 

153 self.timer.stop('Initialize TDDFT') 

154 

155 def absorption_kick(self, kick_strength: Vector, 

156 gauge: str = 'length'): 

157 """Kick with a weak electric field. 

158 

159 Parameters 

160 ---------- 

161 kick_strength 

162 Strength of the kick in atomic units 

163 gauge 

164 Either 'length' or 'velocity' 

165 """ 

166 

167 assert gauge in {'length', 'velocity'} 

168 self.tddft_init() 

169 

170 self.timer.start('Kick') 

171 

172 self.kick_gauge = gauge 

173 self.kick_strength = np.array(kick_strength, dtype=float) 

174 magnitude = np.sqrt(np.sum(self.kick_strength**2)) 

175 direction = self.kick_strength / magnitude 

176 

177 self.log(f'---- Applying absorption kick in {gauge} gauge') 

178 self.log('---- Magnitude: %.8f Hartree/Bohr' % magnitude) 

179 self.log('---- Direction: %.4f %.4f %.4f' % tuple(direction)) 

180 

181 if gauge == 'length': 

182 # Create hamiltonian object for absorption kick 

183 cef = ConstantElectricField(magnitude * Hartree / Bohr, direction) 

184 

185 # Propagate kick 

186 self.propagator.kick(cef, self.time) 

187 else: 

188 self.propagator.velocity_gauge_kick(magnitude, 

189 direction, self.time) 

190 

191 # Call observers after kick 

192 self.action = 'kick' 

193 self.call_observers(self.niter) 

194 self.niter += 1 

195 self.timer.stop('Kick') 

196 

197 def kick(self, ext): 

198 """Kick with any external potential. 

199 

200 Parameters 

201 ---------- 

202 ext 

203 External potential 

204 """ 

205 self.tddft_init() 

206 

207 self.timer.start('Kick') 

208 

209 self.log('---- Applying kick') 

210 self.log('---- %s' % ext) 

211 

212 self.kick_ext = ext 

213 

214 # Propagate kick 

215 self.propagator.kick(ext, self.time) 

216 

217 # Call observers after kick 

218 self.action = 'kick' 

219 self.call_observers(self.niter) 

220 self.niter += 1 

221 self.timer.stop('Kick') 

222 

223 def propagate(self, time_step: float = 10.0, iterations: int = 2000): 

224 """Propagate the electronic system. 

225 

226 Parameters 

227 ---------- 

228 time_step 

229 Time step in attoseconds 

230 iterations 

231 Number of propagation steps 

232 """ 

233 self.tddft_init() 

234 

235 time_step *= attosec_to_autime 

236 self.maxiter = self.niter + iterations 

237 

238 self.log('---- About to do %d propagation steps' % iterations) 

239 

240 self.timer.start('Propagate') 

241 while self.niter < self.maxiter: 

242 # Propagate one step 

243 self.time = self.propagator.propagate(self.time, time_step) 

244 

245 # Call registered callback functions 

246 self.action = 'propagate' 

247 self.call_observers(self.niter) 

248 

249 self.niter += 1 

250 self.timer.stop('Propagate') 

251 

252 def replay(self, **kwargs): 

253 # TODO: Consider deprecating this function? 

254 self.propagator = create_propagator(**kwargs) 

255 self.tddft_init() 

256 self.propagator.control_paw(self)