Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1"""Resonant Raman intensities""" 

2 

3import sys 

4 

5import numpy as np 

6 

7import ase.units as u 

8from ase.parallel import world, paropen, parprint 

9from ase.vibrations import Vibrations 

10from ase.vibrations.raman import Raman, RamanCalculatorBase 

11 

12 

13class ResonantRamanCalculator(RamanCalculatorBase, Vibrations): 

14 """Base class for resonant Raman calculators using finite differences. 

15 """ 

16 def __init__(self, atoms, ExcitationsCalculator, *args, 

17 exkwargs=None, exext='.ex.gz', overlap=False, 

18 **kwargs): 

19 """ 

20 Parameters 

21 ---------- 

22 atoms: Atoms 

23 The Atoms object 

24 ExcitationsCalculator: object 

25 Calculator for excited states 

26 exkwargs: dict 

27 Arguments given to the ExcitationsCalculator object 

28 exext: string 

29 Extension for filenames of Excitation lists (results of 

30 the ExcitationsCalculator). 

31 overlap : function or False 

32 Function to calculate overlaps between excitation at 

33 equilibrium and at a displaced position. Calculators are 

34 given as first and second argument, respectively. 

35 

36 Example 

37 ------- 

38 

39 >>> from ase.calculators.h2morse import (H2Morse, 

40 ... H2MorseExcitedStatesCalculator) 

41 >>> from ase.vibrations.resonant_raman import ResonantRamanCalculator 

42 >>> 

43 >>> atoms = H2Morse() 

44 >>> rmc = ResonantRamanCalculator(atoms, H2MorseExcitedStatesCalculator) 

45 >>> rmc.run() 

46 

47 This produces all necessary data for further analysis. 

48 """ 

49 self.exobj = ExcitationsCalculator 

50 if exkwargs is None: 

51 exkwargs = {} 

52 self.exkwargs = exkwargs 

53 self.overlap = overlap 

54 

55 super().__init__(atoms, *args, exext=exext, **kwargs) 

56 

57 def _new_exobj(self): 

58 # XXXX I have to duplicate this because there are two objects 

59 # which have exkwargs, why are they not unified? 

60 return self.exobj(**self.exkwargs) 

61 

62 def calculate(self, atoms, disp): 

63 """Call ground and excited state calculation""" 

64 assert atoms == self.atoms # XXX action required 

65 forces = self.atoms.get_forces() 

66 

67 if self.overlap: 

68 """Overlap is determined as 

69 

70 ov_ij = int dr displaced*_i(r) eqilibrium_j(r) 

71 """ 

72 ov_nn = self.overlap(self.atoms.calc, 

73 self.eq_calculator) 

74 if world.rank == 0: 

75 disp.save_ov_nn(ov_nn) 

76 

77 disp.calculate_and_save_exlist(atoms) 

78 return {'forces': forces} 

79 

80 def run(self): 

81 if self.overlap: 

82 # XXXX stupid way to make a copy 

83 self.atoms.get_potential_energy() 

84 self.eq_calculator = self.atoms.calc 

85 fname = 'tmp.gpw' 

86 self.eq_calculator.write(fname, 'all') 

87 self.eq_calculator = self.eq_calculator.__class__(restart=fname) 

88 try: 

89 # XXX GPAW specific 

90 self.eq_calculator.converge_wave_functions() 

91 except AttributeError: 

92 pass 

93 Vibrations.run(self) 

94 

95 

96class ResonantRaman(Raman): 

97 """Base Class for resonant Raman intensities using finite differences. 

98 """ 

99 def __init__(self, atoms, Excitations, *args, 

100 observation=None, 

101 form='v', # form of the dipole operator 

102 exkwargs=None, # kwargs to be passed to Excitations 

103 exext='.ex.gz', # extension for Excitation names 

104 overlap=False, 

105 minoverlap=0.02, 

106 minrep=0.8, 

107 comm=world, 

108 **kwargs): 

109 """ 

110 Parameters 

111 ---------- 

112 atoms: ase Atoms object 

113 Excitations: class 

114 Type of the excitation list object. The class object is 

115 initialized as:: 

116 

117 Excitations(atoms.calc) 

118 

119 or by reading form a file as:: 

120 

121 Excitations('filename', **exkwargs) 

122 

123 The file is written by calling the method 

124 Excitations.write('filename'). 

125 

126 Excitations should work like a list of ex obejects, where: 

127 ex.get_dipole_me(form='v'): 

128 gives the velocity form dipole matrix element in 

129 units |e| * Angstrom 

130 ex.energy: 

131 is the transition energy in Hartrees 

132 approximation: string 

133 Level of approximation used. 

134 observation: dict 

135 Polarization settings 

136 form: string 

137 Form of the dipole operator, 'v' for velocity form (default) 

138 and 'r' for length form. 

139 overlap: bool or function 

140 Use wavefunction overlaps. 

141 minoverlap: float ord dict 

142 Minimal absolute overlap to consider. Defaults to 0.02 to avoid 

143 numerical garbage. 

144 minrep: float 

145 Minimal representation to consider derivative, defaults to 0.8 

146 """ 

147 

148 if observation is None: 

149 observation = {'geometry': '-Z(XX)Z'} 

150 

151 kwargs['exext'] = exext 

152 Raman.__init__(self, atoms, *args, **kwargs) 

153 assert(self.vibrations.nfree == 2) 

154 

155 self.exobj = Excitations 

156 if exkwargs is None: 

157 exkwargs = {} 

158 self.exkwargs = exkwargs 

159 self.observation = observation 

160 self.dipole_form = form 

161 

162 self.overlap = overlap 

163 if not isinstance(minoverlap, dict): 

164 # assume it's a number 

165 self.minoverlap = {'orbitals': minoverlap, 

166 'excitations': minoverlap} 

167 else: 

168 self.minoverlap = minoverlap 

169 self.minrep = minrep 

170 

171 def read_exobj(self, filename): 

172 return self.exobj.read(filename, **self.exkwargs) 

173 

174 def get_absolute_intensities(self, omega, gamma=0.1, delta=0, **kwargs): 

175 """Absolute Raman intensity or Raman scattering factor 

176 

177 Parameter 

178 --------- 

179 omega: float 

180 incoming laser energy, unit eV 

181 gamma: float 

182 width (imaginary energy), unit eV 

183 delta: float 

184 pre-factor for asymmetric anisotropy, default 0 

185 

186 References 

187 ---------- 

188 Porezag and Pederson, PRB 54 (1996) 7830-7836 (delta=0) 

189 Baiardi and Barone, JCTC 11 (2015) 3267-3280 (delta=5) 

190 

191 Returns 

192 ------- 

193 raman intensity, unit Ang**4/amu 

194 """ 

195 alpha2_r, gamma2_r, delta2_r = self._invariants( 

196 self.electronic_me_Qcc(omega, gamma, **kwargs)) 

197 return 45 * alpha2_r + delta * delta2_r + 7 * gamma2_r 

198 

199 @property 

200 def approximation(self): 

201 return self._approx 

202 

203 @approximation.setter 

204 def approximation(self, value): 

205 self.set_approximation(value) 

206 

207 def read_excitations(self): 

208 """Read all finite difference excitations and select matching.""" 

209 if self.overlap: 

210 return self.read_excitations_overlap() 

211 

212 disp = self._eq_disp() 

213 ex0_object = disp.read_exobj() 

214 eu = ex0_object.energy_to_eV_scale 

215 matching = frozenset(ex0_object) 

216 

217 def append(lst, disp, matching): 

218 exo = disp.read_exobj() 

219 lst.append(exo) 

220 matching = matching.intersection(exo) 

221 return matching 

222 

223 exm_object_list = [] 

224 exp_object_list = [] 

225 for a, i in zip(self.myindices, self.myxyz): 

226 mdisp = self._disp(a, i, -1) 

227 pdisp = self._disp(a, i, 1) 

228 matching = append(exm_object_list, 

229 mdisp, matching) 

230 matching = append(exp_object_list, 

231 pdisp, matching) 

232 

233 def select(exl, matching): 

234 mlst = [ex for ex in exl if ex in matching] 

235 assert(len(mlst) == len(matching)) 

236 return mlst 

237 

238 ex0 = select(ex0_object, matching) 

239 exm = [] 

240 exp = [] 

241 r = 0 

242 for a, i in zip(self.myindices, self.myxyz): 

243 exm.append(select(exm_object_list[r], matching)) 

244 exp.append(select(exp_object_list[r], matching)) 

245 r += 1 

246 

247 self.ex0E_p = np.array([ex.energy * eu for ex in ex0]) 

248 self.ex0m_pc = (np.array( 

249 [ex.get_dipole_me(form=self.dipole_form) for ex in ex0]) * 

250 u.Bohr) 

251 exmE_rp = [] 

252 expE_rp = [] 

253 exF_rp = [] 

254 exmm_rpc = [] 

255 expm_rpc = [] 

256 r = 0 

257 for a, i in zip(self.myindices, self.myxyz): 

258 exmE_rp.append([em.energy for em in exm[r]]) 

259 expE_rp.append([ep.energy for ep in exp[r]]) 

260 exF_rp.append( 

261 [(em.energy - ep.energy) 

262 for ep, em in zip(exp[r], exm[r])]) 

263 exmm_rpc.append( 

264 [ex.get_dipole_me(form=self.dipole_form) 

265 for ex in exm[r]]) 

266 expm_rpc.append( 

267 [ex.get_dipole_me(form=self.dipole_form) 

268 for ex in exp[r]]) 

269 r += 1 

270 # indicees: r=coordinate, p=excitation 

271 # energies in eV 

272 self.exmE_rp = np.array(exmE_rp) * eu 

273 self.expE_rp = np.array(expE_rp) * eu 

274 # forces in eV / Angstrom 

275 self.exF_rp = np.array(exF_rp) * eu / 2 / self.delta 

276 # matrix elements in e * Angstrom 

277 self.exmm_rpc = np.array(exmm_rpc) * u.Bohr 

278 self.expm_rpc = np.array(expm_rpc) * u.Bohr 

279 

280 def read_excitations_overlap(self): 

281 """Read all finite difference excitations and wf overlaps. 

282 

283 We assume that the wave function overlaps are determined as 

284 

285 ov_ij = int dr displaced*_i(r) eqilibrium_j(r) 

286 """ 

287 ex0 = self._eq_disp().read_exobj() 

288 eu = ex0.energy_to_eV_scale 

289 rep0_p = np.ones((len(ex0)), dtype=float) 

290 

291 def load(disp, rep0_p): 

292 ex_p = disp.read_exobj() 

293 ov_nn = disp.load_ov_nn() 

294 # remove numerical garbage 

295 ov_nn = np.where(np.abs(ov_nn) > self.minoverlap['orbitals'], 

296 ov_nn, 0) 

297 ov_pp = ex_p.overlap(ov_nn, ex0) 

298 ov_pp = np.where(np.abs(ov_pp) > self.minoverlap['excitations'], 

299 ov_pp, 0) 

300 rep0_p *= (ov_pp.real**2 + ov_pp.imag**2).sum(axis=0) 

301 return ex_p, ov_pp 

302 

303 def rotate(ex_p, ov_pp): 

304 e_p = np.array([ex.energy for ex in ex_p]) 

305 m_pc = np.array( 

306 [ex.get_dipole_me(form=self.dipole_form) for ex in ex_p]) 

307 r_pp = ov_pp.T 

308 return ((r_pp.real**2 + r_pp.imag**2).dot(e_p), 

309 r_pp.dot(m_pc)) 

310 

311 exmE_rp = [] 

312 expE_rp = [] 

313 exF_rp = [] 

314 exmm_rpc = [] 

315 expm_rpc = [] 

316 exdmdr_rpc = [] 

317 for a, i in zip(self.myindices, self.myxyz): 

318 mdisp = self._disp(a, i, -1) 

319 pdisp = self._disp(a, i, 1) 

320 ex, ov = load(mdisp, rep0_p) 

321 exmE_p, exmm_pc = rotate(ex, ov) 

322 ex, ov = load(pdisp, rep0_p) 

323 expE_p, expm_pc = rotate(ex, ov) 

324 exmE_rp.append(exmE_p) 

325 expE_rp.append(expE_p) 

326 exF_rp.append(exmE_p - expE_p) 

327 exmm_rpc.append(exmm_pc) 

328 expm_rpc.append(expm_pc) 

329 exdmdr_rpc.append(expm_pc - exmm_pc) 

330 

331 # select only excitations that are sufficiently represented 

332 self.comm.product(rep0_p) 

333 select = np.where(rep0_p > self.minrep)[0] 

334 

335 self.ex0E_p = np.array([ex.energy * eu for ex in ex0])[select] 

336 self.ex0m_pc = (np.array( 

337 [ex.get_dipole_me(form=self.dipole_form) 

338 for ex in ex0])[select] * u.Bohr) 

339 

340 if len(self.myr): 

341 # indicees: r=coordinate, p=excitation 

342 # energies in eV 

343 self.exmE_rp = np.array(exmE_rp)[:, select] * eu 

344 self.expE_rp = np.array(expE_rp)[:, select] * eu 

345 # forces in eV / Angstrom 

346 self.exF_rp = np.array(exF_rp)[:, select] * eu / 2 / self.delta 

347 # matrix elements in e * Angstrom 

348 self.exmm_rpc = np.array(exmm_rpc)[:, select, :] * u.Bohr 

349 self.expm_rpc = np.array(expm_rpc)[:, select, :] * u.Bohr 

350 # matrix element derivatives in e 

351 self.exdmdr_rpc = (np.array(exdmdr_rpc)[:, select, :] * 

352 u.Bohr / 2 / self.delta) 

353 else: 

354 # did not read 

355 self.exmE_rp = self.expE_rp = self.exF_rp = np.empty((0)) 

356 self.exmm_rpc = self.expm_rpc = self.exdmdr_rpc = np.empty((0)) 

357 

358 def read(self, *args, **kwargs): 

359 """Read data from a pre-performed calculation.""" 

360 self.vibrations.read(*args, **kwargs) 

361 self.init_parallel_read() 

362 if not hasattr(self, 'ex0E_p'): 

363 if self.overlap: 

364 self.read_excitations_overlap() 

365 else: 

366 self.read_excitations() 

367 

368 self._already_read = True 

369 

370 def get_cross_sections(self, omega, gamma): 

371 """Returns Raman cross sections for each vibration.""" 

372 I_v = self.intensity(omega, gamma) 

373 pre = 1. / 16 / np.pi**2 / u._eps0**2 / u._c**4 

374 # frequency of scattered light 

375 omS_v = omega - self.om_Q 

376 return pre * omega * omS_v**3 * I_v 

377 

378 def get_spectrum(self, omega, gamma=0.1, 

379 start=None, end=None, npts=None, width=20, 

380 type='Gaussian', 

381 intensity_unit='????', normalize=False): 

382 """Get resonant Raman spectrum. 

383 

384 The method returns wavenumbers in cm^-1 with corresponding 

385 Raman cross section. 

386 Start and end point, and width of the Gaussian/Lorentzian should 

387 be given in cm^-1. 

388 """ 

389 

390 self.type = type.lower() 

391 assert self.type in ['gaussian', 'lorentzian'] 

392 

393 frequencies = self.get_energies().real / u.invcm 

394 intensities = self.get_cross_sections(omega, gamma) 

395 if width is None: 

396 return [frequencies, intensities] 

397 

398 if start is None: 

399 start = min(self.om_Q) / u.invcm - 3 * width 

400 if end is None: 

401 end = max(self.om_Q) / u.invcm + 3 * width 

402 

403 if not npts: 

404 npts = int((end - start) / width * 10 + 1) 

405 

406 prefactor = 1 

407 if self.type == 'lorentzian': 

408 intensities = intensities * width * np.pi / 2. 

409 if normalize: 

410 prefactor = 2. / width / np.pi 

411 else: 

412 sigma = width / 2. / np.sqrt(2. * np.log(2.)) 

413 if normalize: 

414 prefactor = 1. / sigma / np.sqrt(2 * np.pi) 

415 # Make array with spectrum data 

416 spectrum = np.empty(npts) 

417 energies = np.linspace(start, end, npts) 

418 for i, energy in enumerate(energies): 

419 energies[i] = energy 

420 if self.type == 'lorentzian': 

421 spectrum[i] = (intensities * 0.5 * width / np.pi / 

422 ((frequencies - energy)**2 + 

423 0.25 * width**2)).sum() 

424 else: 

425 spectrum[i] = (intensities * 

426 np.exp(-(frequencies - energy)**2 / 

427 2. / sigma**2)).sum() 

428 return [energies, prefactor * spectrum] 

429 

430 def write_spectrum(self, omega, gamma, 

431 out='resonant-raman-spectra.dat', 

432 start=200, end=4000, 

433 npts=None, width=10, 

434 type='Gaussian'): 

435 """Write out spectrum to file. 

436 

437 Start and end 

438 point, and width of the Gaussian/Lorentzian should be given 

439 in cm^-1.""" 

440 energies, spectrum = self.get_spectrum(omega, gamma, 

441 start, end, npts, width, 

442 type) 

443 

444 # Write out spectrum in file. First column is absolute intensities. 

445 outdata = np.empty([len(energies), 3]) 

446 outdata.T[0] = energies 

447 outdata.T[1] = spectrum 

448 

449 with paropen(out, 'w') as fd: 

450 fd.write('# Resonant Raman spectrum\n') 

451 if hasattr(self, '_approx'): 

452 fd.write('# approximation: {0}\n'.format(self._approx)) 

453 for key in self.observation: 

454 fd.write('# {0}: {1}\n'.format(key, self.observation[key])) 

455 fd.write('# omega={0:g} eV, gamma={1:g} eV\n' 

456 .format(omega, gamma)) 

457 if width is not None: 

458 fd.write('# %s folded, width=%g cm^-1\n' 

459 % (type.title(), width)) 

460 fd.write('# [cm^-1] [a.u.]\n') 

461 

462 for row in outdata: 

463 fd.write('%.3f %15.5g\n' % 

464 (row[0], row[1])) 

465 

466 def summary(self, omega, gamma=0.1, 

467 method='standard', direction='central', 

468 log=sys.stdout): 

469 """Print summary for given omega [eV]""" 

470 self.read(method, direction) 

471 hnu = self.get_energies() 

472 intensities = self.get_absolute_intensities(omega, gamma) 

473 te = int(np.log10(intensities.max())) - 2 

474 scale = 10**(-te) 

475 if not te: 

476 ts = '' 

477 elif te > -2 and te < 3: 

478 ts = str(10**te) 

479 else: 

480 ts = '10^{0}'.format(te) 

481 

482 if isinstance(log, str): 

483 log = paropen(log, 'a') 

484 

485 parprint('-------------------------------------', file=log) 

486 parprint(' excitation at ' + str(omega) + ' eV', file=log) 

487 parprint(' gamma ' + str(gamma) + ' eV', file=log) 

488 parprint(' method:', self.vibrations.method, file=log) 

489 parprint(' approximation:', self.approximation, file=log) 

490 parprint(' Mode Frequency Intensity', file=log) 

491 parprint(' # meV cm^-1 [{0}A^4/amu]'.format(ts), file=log) 

492 parprint('-------------------------------------', file=log) 

493 for n, e in enumerate(hnu): 

494 if e.imag != 0: 

495 c = 'i' 

496 e = e.imag 

497 else: 

498 c = ' ' 

499 e = e.real 

500 parprint('%3d %6.1f%s %7.1f%s %9.2f' % 

501 (n, 1000 * e, c, e / u.invcm, c, intensities[n] * scale), 

502 file=log) 

503 parprint('-------------------------------------', file=log) 

504 parprint('Zero-point energy: %.3f eV' % 

505 self.vibrations.get_zero_point_energy(), 

506 file=log) 

507 

508 

509class LrResonantRaman(ResonantRaman): 

510 """Resonant Raman for linear response 

511 

512 Quick and dirty approach to enable loading of LrTDDFT calculations 

513 """ 

514 

515 def read_excitations(self): 

516 eq_disp = self._eq_disp() 

517 ex0_object = eq_disp.read_exobj() 

518 eu = ex0_object.energy_to_eV_scale 

519 matching = frozenset(ex0_object.kss) 

520 

521 def append(lst, disp, matching): 

522 exo = disp.read_exobj() 

523 lst.append(exo) 

524 matching = matching.intersection(exo.kss) 

525 return matching 

526 

527 exm_object_list = [] 

528 exp_object_list = [] 

529 for a in self.indices: 

530 for i in 'xyz': 

531 disp1 = self._disp(a, i, -1) 

532 disp2 = self._disp(a, i, 1) 

533 

534 matching = append(exm_object_list, 

535 disp1, 

536 matching) 

537 matching = append(exp_object_list, 

538 disp2, 

539 matching) 

540 

541 def select(exl, matching): 

542 exl.diagonalize(**self.exkwargs) 

543 mlist = list(exl) 

544# mlst = [ex for ex in exl if ex in matching] 

545# assert(len(mlst) == len(matching)) 

546 return mlist 

547 ex0 = select(ex0_object, matching) 

548 exm = [] 

549 exp = [] 

550 r = 0 

551 for a in self.indices: 

552 for i in 'xyz': 

553 exm.append(select(exm_object_list[r], matching)) 

554 exp.append(select(exp_object_list[r], matching)) 

555 r += 1 

556 

557 self.ex0E_p = np.array([ex.energy * eu for ex in ex0]) 

558# self.exmE_p = np.array([ex.energy * eu for ex in exm]) 

559# self.expE_p = np.array([ex.energy * eu for ex in exp]) 

560 self.ex0m_pc = (np.array( 

561 [ex.get_dipole_me(form=self.dipole_form) for ex in ex0]) * 

562 u.Bohr) 

563 self.exF_rp = [] 

564 exmE_rp = [] 

565 expE_rp = [] 

566 exmm_rpc = [] 

567 expm_rpc = [] 

568 r = 0 

569 for a in self.indices: 

570 for i in 'xyz': 

571 exmE_rp.append([em.energy for em in exm[r]]) 

572 expE_rp.append([ep.energy for ep in exp[r]]) 

573 self.exF_rp.append( 

574 [(em.energy - ep.energy) 

575 for ep, em in zip(exp[r], exm[r])]) 

576 exmm_rpc.append( 

577 [ex.get_dipole_me(form=self.dipole_form) for ex in exm[r]]) 

578 expm_rpc.append( 

579 [ex.get_dipole_me(form=self.dipole_form) for ex in exp[r]]) 

580 r += 1 

581 self.exmE_rp = np.array(exmE_rp) * eu 

582 self.expE_rp = np.array(expE_rp) * eu 

583 self.exF_rp = np.array(self.exF_rp) * eu / 2 / self.delta 

584 self.exmm_rpc = np.array(exmm_rpc) * u.Bohr 

585 self.expm_rpc = np.array(expm_rpc) * u.Bohr