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 

4from pathlib import Path 

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 

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

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

19 **kwargs): 

20 """ 

21 Parameters 

22 ---------- 

23 atoms: Atoms 

24 The Atoms object 

25 ExcitationsCalculator: object 

26 Calculator for excited states 

27 exkwargs: dict 

28 Arguments given to the ExcitationsCalculator object 

29 exext: string 

30 Extension for filenames of Excitation lists (results of 

31 the ExcitationsCalculator). 

32 overlap : function or False 

33 Function to calculate overlaps between excitation at 

34 equilibrium and at a displaced position. Calculators are 

35 given as first and second argument, respectively. 

36 

37 Example 

38 ------- 

39 

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

41 ... H2MorseExcitedStatesCalculator) 

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

43 >>> 

44 >>> atoms = H2Morse() 

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

46 >>> rmc.run() 

47 

48 This produces all necessary data for further analysis. 

49 """ 

50 self.exobj = ExcitationsCalculator 

51 if exkwargs is None: 

52 exkwargs = {} 

53 self.exkwargs = exkwargs 

54 self.overlap = overlap 

55 

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

57 

58 def _new_exobj(self): 

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

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

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

62 

63 def calculate(self, atoms, disp): 

64 """Call ground and excited state calculation""" 

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

66 forces = self.atoms.get_forces() 

67 

68 if self.overlap: 

69 """Overlap is determined as 

70 

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

72 """ 

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

74 self.eq_calculator) 

75 if world.rank == 0: 

76 disp.save_ov_nn(ov_nn) 

77 

78 disp.calculate_and_save_exlist(atoms) 

79 return {'forces': forces} 

80 

81 def run(self): 

82 if self.overlap: 

83 # XXXX stupid way to make a copy 

84 self.atoms.get_potential_energy() 

85 self.eq_calculator = self.atoms.calc 

86 Path(self.name).mkdir(parents=True, exist_ok=True) 

87 fname = Path(self.name) / 'tmp.gpw' 

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

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

90 try: 

91 # XXX GPAW specific 

92 self.eq_calculator.converge_wave_functions() 

93 except AttributeError: 

94 pass 

95 Vibrations.run(self) 

96 

97 

98class ResonantRaman(Raman): 

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

100 """ 

101 

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

103 observation=None, 

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

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

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

107 overlap=False, 

108 minoverlap=0.02, 

109 minrep=0.8, 

110 comm=world, 

111 **kwargs): 

112 """ 

113 Parameters 

114 ---------- 

115 atoms: ase Atoms object 

116 Excitations: class 

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

118 initialized as:: 

119 

120 Excitations(atoms.calc) 

121 

122 or by reading form a file as:: 

123 

124 Excitations('filename', **exkwargs) 

125 

126 The file is written by calling the method 

127 Excitations.write('filename'). 

128 

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

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

131 gives the velocity form dipole matrix element in 

132 units |e| * Angstrom 

133 ex.energy: 

134 is the transition energy in Hartrees 

135 approximation: string 

136 Level of approximation used. 

137 observation: dict 

138 Polarization settings 

139 form: string 

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

141 and 'r' for length form. 

142 overlap: bool or function 

143 Use wavefunction overlaps. 

144 minoverlap: float ord dict 

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

146 numerical garbage. 

147 minrep: float 

148 Minimal representation to consider derivative, defaults to 0.8 

149 """ 

150 

151 if observation is None: 

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

153 

154 kwargs['exext'] = exext 

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

156 assert self.vibrations.nfree == 2 

157 

158 self.exobj = Excitations 

159 if exkwargs is None: 

160 exkwargs = {} 

161 self.exkwargs = exkwargs 

162 self.observation = observation 

163 self.dipole_form = form 

164 

165 self.overlap = overlap 

166 if not isinstance(minoverlap, dict): 

167 # assume it's a number 

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

169 'excitations': minoverlap} 

170 else: 

171 self.minoverlap = minoverlap 

172 self.minrep = minrep 

173 

174 def read_exobj(self, filename): 

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

176 

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

178 """Absolute Raman intensity or Raman scattering factor 

179 

180 Parameter 

181 --------- 

182 omega: float 

183 incoming laser energy, unit eV 

184 gamma: float 

185 width (imaginary energy), unit eV 

186 delta: float 

187 pre-factor for asymmetric anisotropy, default 0 

188 

189 References 

190 ---------- 

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

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

193 

194 Returns 

195 ------- 

196 raman intensity, unit Ang**4/amu 

197 """ 

198 alpha2_r, gamma2_r, delta2_r = self._invariants( 

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

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

201 

202 @property 

203 def approximation(self): 

204 return self._approx 

205 

206 @approximation.setter 

207 def approximation(self, value): 

208 self.set_approximation(value) 

209 

210 def read_excitations(self): 

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

212 if self.overlap: 

213 return self.read_excitations_overlap() 

214 

215 disp = self._eq_disp() 

216 ex0_object = disp.read_exobj() 

217 eu = ex0_object.energy_to_eV_scale 

218 matching = frozenset(ex0_object) 

219 

220 def append(lst, disp, matching): 

221 exo = disp.read_exobj() 

222 lst.append(exo) 

223 matching = matching.intersection(exo) 

224 return matching 

225 

226 exm_object_list = [] 

227 exp_object_list = [] 

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

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

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

231 matching = append(exm_object_list, 

232 mdisp, matching) 

233 matching = append(exp_object_list, 

234 pdisp, matching) 

235 

236 def select(exl, matching): 

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

238 assert len(mlst) == len(matching) 

239 return mlst 

240 

241 ex0 = select(ex0_object, matching) 

242 exm = [] 

243 exp = [] 

244 r = 0 

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

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

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

248 r += 1 

249 

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

251 self.ex0m_pc = (np.array( 

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

253 u.Bohr) 

254 exmE_rp = [] 

255 expE_rp = [] 

256 exF_rp = [] 

257 exmm_rpc = [] 

258 expm_rpc = [] 

259 r = 0 

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

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

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

263 exF_rp.append( 

264 [(em.energy - ep.energy) 

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

266 exmm_rpc.append( 

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

268 for ex in exm[r]]) 

269 expm_rpc.append( 

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

271 for ex in exp[r]]) 

272 r += 1 

273 # indicees: r=coordinate, p=excitation 

274 # energies in eV 

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

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

277 # forces in eV / Angstrom 

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

279 # matrix elements in e * Angstrom 

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

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

282 

283 def read_excitations_overlap(self): 

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

285 

286 We assume that the wave function overlaps are determined as 

287 

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

289 """ 

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

291 eu = ex0.energy_to_eV_scale 

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

293 

294 def load(disp, rep0_p): 

295 ex_p = disp.read_exobj() 

296 ov_nn = disp.load_ov_nn() 

297 # remove numerical garbage 

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

299 ov_nn, 0) 

300 ov_pp = ex_p.overlap(ov_nn, ex0) 

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

302 ov_pp, 0) 

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

304 return ex_p, ov_pp 

305 

306 def rotate(ex_p, ov_pp): 

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

308 m_pc = np.array( 

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

310 r_pp = ov_pp.T 

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

312 r_pp.dot(m_pc)) 

313 

314 exmE_rp = [] 

315 expE_rp = [] 

316 exF_rp = [] 

317 exmm_rpc = [] 

318 expm_rpc = [] 

319 exdmdr_rpc = [] 

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

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

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

323 ex, ov = load(mdisp, rep0_p) 

324 exmE_p, exmm_pc = rotate(ex, ov) 

325 ex, ov = load(pdisp, rep0_p) 

326 expE_p, expm_pc = rotate(ex, ov) 

327 exmE_rp.append(exmE_p) 

328 expE_rp.append(expE_p) 

329 exF_rp.append(exmE_p - expE_p) 

330 exmm_rpc.append(exmm_pc) 

331 expm_rpc.append(expm_pc) 

332 exdmdr_rpc.append(expm_pc - exmm_pc) 

333 

334 # select only excitations that are sufficiently represented 

335 self.comm.product(rep0_p) 

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

337 

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

339 self.ex0m_pc = (np.array( 

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

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

342 

343 if len(self.myr): 

344 # indicees: r=coordinate, p=excitation 

345 # energies in eV 

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

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

348 # forces in eV / Angstrom 

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

350 # matrix elements in e * Angstrom 

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

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

353 # matrix element derivatives in e 

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

355 u.Bohr / 2 / self.delta) 

356 else: 

357 # did not read 

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

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

360 

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

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

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

364 self.init_parallel_read() 

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

366 if self.overlap: 

367 self.read_excitations_overlap() 

368 else: 

369 self.read_excitations() 

370 

371 self._already_read = True 

372 

373 def get_cross_sections(self, omega, gamma): 

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

375 I_v = self.intensity(omega, gamma) 

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

377 # frequency of scattered light 

378 omS_v = omega - self.om_Q 

379 return pre * omega * omS_v**3 * I_v 

380 

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

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

383 type='Gaussian', 

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

385 """Get resonant Raman spectrum. 

386 

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

388 Raman cross section. 

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

390 be given in cm^-1. 

391 """ 

392 

393 self.type = type.lower() 

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

395 

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

397 intensities = self.get_cross_sections(omega, gamma) 

398 if width is None: 

399 return [frequencies, intensities] 

400 

401 if start is None: 

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

403 if end is None: 

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

405 

406 if not npts: 

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

408 

409 prefactor = 1 

410 if self.type == 'lorentzian': 

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

412 if normalize: 

413 prefactor = 2. / width / np.pi 

414 else: 

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

416 if normalize: 

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

418 # Make array with spectrum data 

419 spectrum = np.empty(npts) 

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

421 for i, energy in enumerate(energies): 

422 energies[i] = energy 

423 if self.type == 'lorentzian': 

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

425 ((frequencies - energy)**2 + 

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

427 else: 

428 spectrum[i] = (intensities * 

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

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

431 return [energies, prefactor * spectrum] 

432 

433 def write_spectrum(self, omega, gamma, 

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

435 start=200, end=4000, 

436 npts=None, width=10, 

437 type='Gaussian'): 

438 """Write out spectrum to file. 

439 

440 Start and end 

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

442 in cm^-1.""" 

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

444 start, end, npts, width, 

445 type) 

446 

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

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

449 outdata.T[0] = energies 

450 outdata.T[1] = spectrum 

451 

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

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

454 if hasattr(self, '_approx'): 

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

456 for key in self.observation: 

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

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

459 .format(omega, gamma)) 

460 if width is not None: 

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

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

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

464 

465 for row in outdata: 

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

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

468 

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

470 method='standard', direction='central', 

471 log=sys.stdout): 

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

473 self.read(method, direction) 

474 hnu = self.get_energies() 

475 intensities = self.get_absolute_intensities(omega, gamma) 

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

477 scale = 10**(-te) 

478 if not te: 

479 ts = '' 

480 elif te > -2 and te < 3: 

481 ts = str(10**te) 

482 else: 

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

484 

485 if isinstance(log, str): 

486 log = paropen(log, 'a') 

487 

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

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

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

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

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

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

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

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

496 for n, e in enumerate(hnu): 

497 if e.imag != 0: 

498 c = 'i' 

499 e = e.imag 

500 else: 

501 c = ' ' 

502 e = e.real 

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

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

505 file=log) 

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

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

508 self.vibrations.get_zero_point_energy(), 

509 file=log) 

510 

511 

512class LrResonantRaman(ResonantRaman): 

513 """Resonant Raman for linear response 

514 

515 Quick and dirty approach to enable loading of LrTDDFT calculations 

516 """ 

517 

518 def read_excitations(self): 

519 eq_disp = self._eq_disp() 

520 ex0_object = eq_disp.read_exobj() 

521 eu = ex0_object.energy_to_eV_scale 

522 matching = frozenset(ex0_object.kss) 

523 

524 def append(lst, disp, matching): 

525 exo = disp.read_exobj() 

526 lst.append(exo) 

527 matching = matching.intersection(exo.kss) 

528 return matching 

529 

530 exm_object_list = [] 

531 exp_object_list = [] 

532 for a in self.indices: 

533 for i in 'xyz': 

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

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

536 

537 matching = append(exm_object_list, 

538 disp1, 

539 matching) 

540 matching = append(exp_object_list, 

541 disp2, 

542 matching) 

543 

544 def select(exl, matching): 

545 exl.diagonalize(**self.exkwargs) 

546 mlist = list(exl) 

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

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

549 return mlist 

550 ex0 = select(ex0_object, matching) 

551 exm = [] 

552 exp = [] 

553 r = 0 

554 for a in self.indices: 

555 for i in 'xyz': 

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

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

558 r += 1 

559 

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

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

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

563 self.ex0m_pc = (np.array( 

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

565 u.Bohr) 

566 self.exF_rp = [] 

567 exmE_rp = [] 

568 expE_rp = [] 

569 exmm_rpc = [] 

570 expm_rpc = [] 

571 r = 0 

572 for a in self.indices: 

573 for i in 'xyz': 

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

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

576 self.exF_rp.append( 

577 [(em.energy - ep.energy) 

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

579 exmm_rpc.append( 

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

581 expm_rpc.append( 

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

583 r += 1 

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

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

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

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

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