Coverage for gpaw/test/lcaotddft/test_circular_dichroism.py: 100%

131 statements  

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

1import numpy as np 

2import pytest 

3 

4from ase import Atoms 

5 

6from gpaw import GPAW 

7from gpaw.mpi import world, serial_comm 

8from gpaw.lcaotddft import LCAOTDDFT 

9from gpaw.lcaotddft.densitymatrix import DensityMatrix 

10from gpaw.lcaotddft.magneticmomentwriter import MagneticMomentWriter 

11from gpaw.lcaotddft.magneticmomentwriter import parse_header 

12from gpaw.tddft.spectrum import rotatory_strength_spectrum 

13from gpaw.tddft.units import as_to_au, eV_to_au, au_to_eV, rot_au_to_cgs 

14 

15from gpaw.test import only_on_master 

16from . import parallel_options, check_txt_data, copy_and_cut_file 

17 

18pytestmark = pytest.mark.usefixtures('module_tmp_path') 

19 

20parallel_i = parallel_options() 

21 

22 

23# Parameterize over spin polarized and unpolarized calculations 

24@pytest.fixture(scope='module', params=[True, False]) 

25@only_on_master(world) 

26def initialize_system(request): 

27 comm = serial_comm 

28 

29 atoms = Atoms('LiNaNaNa', 

30 positions=[[0.0, 0.0, 0.0], 

31 [2.0, 1.0, 0.0], 

32 [4.0, 0.0, 1.0], 

33 [6.0, -1.0, 0.0]]) 

34 atoms.center(vacuum=4.0) 

35 

36 if request.param: 

37 atoms.set_initial_magnetic_moments([0.01] * len(atoms)) 

38 

39 calc = GPAW(nbands=2, 

40 h=0.4, 

41 setups={'Na': '1'}, 

42 basis='sz(dzp)', 

43 mode='lcao', 

44 convergence={'density': 1e-12}, 

45 communicator=comm, 

46 symmetry={'point_group': False}, 

47 txt='gs.out') 

48 atoms.calc = calc 

49 atoms.get_potential_energy() 

50 calc.write('gs.gpw', mode='all') 

51 

52 for gauge in ['length', 'velocity']: 

53 if gauge == 'velocity': 

54 add = '_' + gauge 

55 else: 

56 add = '' 

57 td_calc = LCAOTDDFT('gs.gpw', communicator=comm, 

58 txt='td' + add + '.out') 

59 dmat = DensityMatrix(td_calc) 

60 MagneticMomentWriter(td_calc, 'mm' + add + '.dat', dmat=dmat) 

61 MagneticMomentWriter(td_calc, 'mm_grid' + add + '.dat', 

62 calculate_on_grid=True) 

63 MagneticMomentWriter(td_calc, 'mm_origin' + add + '.dat', 

64 origin='zero', origin_shift=[1.0, 2.0, 3.0]) 

65 td_calc.absorption_kick([1e-5, 0., 0.], gauge=gauge) 

66 td_calc.propagate(100, 3) 

67 td_calc.write('td' + add + '.gpw', mode='all') 

68 td_calc.propagate(100, 2) 

69 

70 

71@pytest.mark.rttddft 

72def test_magnetic_moment_velocity_gauge(initialize_system, module_tmp_path, 

73 in_tmp_dir): 

74 with open('mm_ref.dat', 'w', encoding='utf-8') as f: 

75 f.write(''' 

76# MagneticMomentWriter[version=5](**{"origin": "COM", "origin_shift": [0.0, 0.0, 0.0], "calculate_on_grid": false, "only_pseudo": false}) 

77# origin_v = [7.634300, 5.000000, 4.302858] Å 

78# time mmx mmy mmz 

79# Start; Time = 0.00000000 

80 0.00000000 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 

81# Kick = [ 1.000000000000e-05, 0.000000000000e+00, 0.000000000000e+00]; Time = 0.00000000 

82 0.00000000 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 

83 4.13413733 -3.063974663768e-07 -3.574171353385e-07 1.245812807572e-06 

84 8.26827467 -1.197667430552e-06 -1.414585723558e-06 4.779431710530e-06 

85 12.40241200 -2.606870392812e-06 -3.132296132758e-06 1.010969895228e-05 

86 16.53654934 -4.424269197168e-06 -5.428714567811e-06 1.652651689986e-05 

87 20.67068667 -6.501209589454e-06 -8.162871785725e-06 2.323149911180e-05 

88'''.strip()) # noqa: E501 

89 check_txt_data(module_tmp_path / 'mm_velocity.dat', 

90 'mm_ref.dat', atol=2e-14) 

91 

92 with open('mm_grid_ref.dat', 'w', encoding='utf-8') as f: 

93 f.write(''' 

94# MagneticMomentWriter[version=5](**{"origin": "COM", "origin_shift": [0.0, 0.0, 0.0], "calculate_on_grid": true, "only_pseudo": false}) 

95# origin_v = [7.634300, 5.000000, 4.302858] Å 

96# time mmx mmy mmz 

97# Start; Time = 0.00000000 

98 0.00000000 -0.000000000000e+00 -0.000000000000e+00 -0.000000000000e+00 

99# Kick = [ 1.000000000000e-05, 0.000000000000e+00, 0.000000000000e+00]; Time = 0.00000000 

100 0.00000000 -0.000000000000e+00 -0.000000000000e+00 -0.000000000000e+00 

101 4.13413733 -3.063224380042e-07 -3.573133338861e-07 1.245412048950e-06 

102 8.26827467 -1.197361494602e-06 -1.414167218866e-06 4.777891626962e-06 

103 12.40241200 -2.606162974554e-06 -3.131346591800e-06 1.010643346002e-05 

104 16.53654934 -4.422978770321e-06 -5.427020998550e-06 1.652116134041e-05 

105 20.67068667 -6.499162446085e-06 -8.160246795614e-06 2.322393973892e-05 

106'''.strip()) # noqa: E501 

107 

108 check_txt_data(module_tmp_path / 'mm_grid_velocity.dat', 

109 'mm_grid_ref.dat', atol=2e-14) 

110 

111 with open('mm_origin_ref.dat', 'w', encoding='utf-8') as f: 

112 f.write(''' 

113# MagneticMomentWriter[version=5](**{"origin": "zero", "origin_shift": [1.0, 2.0, 3.0], "calculate_on_grid": false, "only_pseudo": false}) 

114# origin_v = [1.000000, 2.000000, 3.000000] Å 

115# time mmx mmy mmz 

116# Start; Time = 0.00000000 

117 0.00000000 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 

118# Kick = [ 1.000000000000e-05, 0.000000000000e+00, 0.000000000000e+00]; Time = 0.00000000 

119 0.00000000 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 

120 4.13413733 3.055448269266e-07 -2.985624975165e-06 4.181522917320e-06 

121 8.26827467 1.055413708309e-06 -1.144251258824e-05 1.639709003121e-05 

122 12.40241200 1.830150711621e-06 -2.413141874059e-05 3.586908883647e-05 

123 16.53654934 2.098630938582e-06 -3.928697753788e-05 6.127421238813e-05 

124 20.67068667 1.409938523718e-06 -5.497359580260e-05 9.073479845925e-05 

125'''.strip()) # noqa: E501 

126 

127 check_txt_data(module_tmp_path / 'mm_origin_velocity.dat', 

128 'mm_origin_ref.dat', atol=1e-13) 

129 

130 

131@pytest.mark.rttddft 

132def test_magnetic_moment_values(initialize_system, module_tmp_path, 

133 in_tmp_dir): 

134 with open('mm_ref.dat', 'w', encoding='utf-8') as f: 

135 f.write(''' 

136# MagneticMomentWriter[version=4](origin='COM') 

137# origin_v = [7.634300, 5.000000, 4.302858] Å 

138# time mmx mmy mmz 

139 0.00000000 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 

140# Kick = [ 1.000000000000e-05, 0.000000000000e+00, 0.000000000000e+00]; Time = 0.00000000 

141 0.00000000 8.192189793082e-06 1.038446327373e-05 -2.730498071751e-05 

142 4.13413733 7.838837723234e-06 1.000765310013e-05 -2.573300722038e-05 

143 8.26827467 6.809084660174e-06 8.879683492897e-06 -2.128890950807e-05 

144 12.40241200 5.175350632237e-06 7.009694921954e-06 -1.462938416394e-05 

145 16.53654934 3.058296873929e-06 4.443905967036e-06 -6.697375210691e-06 

146 20.67068667 6.247451722277e-07 1.298788405738e-06 1.460017881082e-06 

147'''.strip()) # noqa: E501 

148 

149 check_txt_data(module_tmp_path / 'mm.dat', 'mm_ref.dat', atol=1e-13) 

150 

151 

152@pytest.mark.rttddft 

153def test_magnetic_moment_grid_evaluation(initialize_system, module_tmp_path): 

154 dpath = module_tmp_path 

155 check_txt_data(dpath / 'mm.dat', dpath / 'mm_grid.dat', atol=2e-8) 

156 

157 

158@pytest.mark.rttddft 

159@pytest.mark.parametrize('parallel', parallel_i) 

160@pytest.mark.parametrize('gauge', ['velocity', 'length']) 

161def test_magnetic_moment_parallel(initialize_system, module_tmp_path, parallel, 

162 in_tmp_dir, gauge): 

163 

164 td_calc = LCAOTDDFT(module_tmp_path / 'gs.gpw', 

165 parallel=parallel, 

166 txt='td.out') 

167 print(parallel) 

168 add = '_velocity' if gauge == 'velocity' else '' 

169 MagneticMomentWriter(td_calc, f'mm{add}.dat') 

170 MagneticMomentWriter(td_calc, f'mm_grid{add}.dat', calculate_on_grid=True) 

171 MagneticMomentWriter(td_calc, f'mm_origin{add}.dat', 

172 origin='zero', origin_shift=[1.0, 2.0, 3.0]) 

173 td_calc.absorption_kick([1e-5, 0., 0.], gauge=gauge) 

174 td_calc.propagate(100, 5) 

175 

176 for fname in [f'mm{add}.dat', f'mm_grid{add}.dat', f'mm_origin{add}.dat']: 

177 check_txt_data(module_tmp_path / fname, fname, atol=7e-14) 

178 

179 

180@pytest.mark.rttddft 

181@pytest.mark.parametrize('parallel', parallel_i) 

182def test_magnetic_moment_restart(initialize_system, module_tmp_path, parallel, 

183 in_tmp_dir): 

184 td_calc = LCAOTDDFT(module_tmp_path / 'td.gpw', 

185 parallel=parallel, 

186 txt='td.out') 

187 for fname in ['mm.dat', 'mm_grid.dat', 'mm_origin.dat']: 

188 if world.rank == 0: 

189 copy_and_cut_file(module_tmp_path / fname, fname, cut_lines=3) 

190 world.barrier() 

191 MagneticMomentWriter(td_calc, fname) 

192 td_calc.propagate(100, 2) 

193 

194 for fname in ['mm.dat', 'mm_grid.dat', 'mm_origin.dat']: 

195 check_txt_data(module_tmp_path / fname, fname, atol=7e-14) 

196 

197 

198@only_on_master(world) 

199def test_spectrum(in_tmp_dir, rng): 

200 from gpaw.utilities.folder import Folder 

201 

202 # Parameters for test data 

203 kick_strength = 1e-5 

204 frequency_v = np.array([1.0, 2.0, 3.0]) * eV_to_au 

205 strength_v = np.array([1.0, 2.0, 3.0]) 

206 

207 # Create dummy magnetic moment files 

208 for v, kick in enumerate('xyz'): 

209 kick_v = [0.0, 0.0, 0.0] 

210 kick_v[v] = kick_strength 

211 time_t = np.arange(0, 31e3, 10.0) * as_to_au 

212 data_tv = np.zeros((len(time_t), 4)) 

213 data_tv[:, 0] = time_t 

214 # Fill unused columns with random values 

215 data_tv[:, 1:] = rng.random((len(time_t), 3)) 

216 # Diagonal column has the data used for spectrum 

217 data_tv[:, v + 1] = (kick_strength * strength_v[v] 

218 * np.cos(frequency_v[v] * time_t)) 

219 with open(f'mm-{kick}.dat', 'w', encoding='utf-8') as f: 

220 f.write(f''' 

221# MagneticMomentWriter[version=4](origin='COM') 

222# time mmx mmy mmz 

223 0.00000000 0.000000000000e+00 0.000000000000e+00 0.000000000000e+00 

224# Kick = {kick_v}; Time = 0.00000000 

225''') # noqa: E501 

226 np.savetxt(f, data_tv) 

227 

228 # Calculate spectrum 

229 folding_kwargs = dict(folding='Gauss', width=0.2) 

230 rotatory_strength_spectrum(['mm-x.dat', 'mm-y.dat', 'mm-z.dat'], 

231 'spec.dat', 

232 **folding_kwargs, 

233 e_min=0.0, e_max=5.0, delta_e=0.01) 

234 

235 # Reference spectrum 

236 energy_e = np.arange(0, 5.0 + 1e-8, 0.01) 

237 f = Folder(**folding_kwargs).fold_values 

238 spec_e = (f(frequency_v * au_to_eV, strength_v, energy_e)[1] 

239 + f(-frequency_v * au_to_eV, strength_v, energy_e)[1]) 

240 spec_e *= 0.5 

241 spec_e *= rot_au_to_cgs * 1e40 

242 

243 # Compare spectrum 

244 data_ei = np.loadtxt('spec.dat') 

245 assert np.allclose(data_ei[:, 0], energy_e) 

246 assert np.allclose(data_ei[:, 1], spec_e) 

247 

248 # Test failure 

249 with pytest.raises(RuntimeError): 

250 rotatory_strength_spectrum(['mm-x.dat', 'mm-x.dat', 'mm-z.dat'], 

251 'spec.dat') 

252 with pytest.raises(RuntimeError): 

253 rotatory_strength_spectrum(['mm-y.dat', 'mm-x.dat', 'mm-z.dat'], 

254 'spec.dat') 

255 with pytest.raises(RuntimeError): 

256 rotatory_strength_spectrum(['mm-x.dat', 'mm-y.dat', 'mm-x.dat'], 

257 'spec.dat') 

258 

259 

260def test_parse_header(): 

261 line = 'SomeWriter[version=42](**{"str": "value", "vector": [1.2, 3.4], "boolean": true})' # noqa: E501 

262 name, version, kwargs = parse_header(line) 

263 assert name == 'SomeWriter' 

264 assert version == 42 

265 assert kwargs['str'] == 'value' 

266 assert kwargs['vector'] == [1.2, 3.4] 

267 assert kwargs['boolean'] 

268 

269 # Test failure 

270 with pytest.raises(ValueError): 

271 parse_header('wrong line') 

272 with pytest.raises(ValueError): 

273 parse_header('A[version=1](**{wrong json})')