root / ase / infrared.py @ 16
Historique | Voir | Annoter | Télécharger (11,78 ko)
1 | 1 | tkerber | |
---|---|---|---|
2 | 1 | tkerber | # -*- coding: utf-8 -*-
|
3 | 1 | tkerber | |
4 | 1 | tkerber | """Infrared intensities"""
|
5 | 1 | tkerber | |
6 | 1 | tkerber | import pickle |
7 | 1 | tkerber | from math import sin, pi, sqrt, exp, log |
8 | 1 | tkerber | from os import remove |
9 | 1 | tkerber | from os.path import isfile |
10 | 1 | tkerber | |
11 | 1 | tkerber | import numpy as np |
12 | 1 | tkerber | |
13 | 1 | tkerber | import ase.units as units |
14 | 1 | tkerber | from ase.io.trajectory import PickleTrajectory |
15 | 1 | tkerber | from ase.parallel import rank, barrier, parprint |
16 | 1 | tkerber | from ase.vibrations import Vibrations |
17 | 1 | tkerber | |
18 | 1 | tkerber | |
19 | 1 | tkerber | class InfraRed(Vibrations): |
20 | 1 | tkerber | """Class for calculating vibrational modes and infrared intensities
|
21 | 1 | tkerber | using finite difference.
|
22 | 1 | tkerber |
|
23 | 1 | tkerber | The vibrational modes are calculated from a finite difference
|
24 | 1 | tkerber | approximation of the Dynamical matrix and the IR intensities from
|
25 | 1 | tkerber | a finite difference approximation of the gradient of the dipole
|
26 | 1 | tkerber | moment. The method is described in:
|
27 | 1 | tkerber |
|
28 | 1 | tkerber | D. Porezag, M. R. Peterson:
|
29 | 1 | tkerber | "Infrared intensities and Raman-scattering activities within
|
30 | 1 | tkerber | density-functional theory",
|
31 | 1 | tkerber | Phys. Rev. B 54, 7830 (1996)
|
32 | 1 | tkerber |
|
33 | 1 | tkerber | The calculator object (calc) linked to the Atoms object (atoms) must
|
34 | 1 | tkerber | have the attribute:
|
35 | 1 | tkerber |
|
36 | 1 | tkerber | >>> calc.get_dipole_moment(atoms)
|
37 | 1 | tkerber |
|
38 | 1 | tkerber | In addition to the methods included in the ``Vibrations`` class
|
39 | 1 | tkerber | the ``InfraRed`` class introduces two new methods;
|
40 | 1 | tkerber | *get_spectrum()* and *write_spectra()*. The *summary()*, *get_energies()*,
|
41 | 1 | tkerber | *get_frequencies()*, *get_spectrum()* and *write_spectra()*
|
42 | 1 | tkerber | methods all take an optional *method* keyword. Use
|
43 | 1 | tkerber | method='Frederiksen' to use the method described in:
|
44 | 1 | tkerber |
|
45 | 1 | tkerber | T. Frederiksen, M. Paulsson, M. Brandbyge, A. P. Jauho:
|
46 | 1 | tkerber | "Inelastic transport theory from first-principles: methodology
|
47 | 1 | tkerber | and applications for nanoscale devices",
|
48 | 1 | tkerber | Phys. Rev. B 75, 205413 (2007)
|
49 | 1 | tkerber |
|
50 | 1 | tkerber | atoms: Atoms object
|
51 | 1 | tkerber | The atoms to work on.
|
52 | 1 | tkerber | indices: list of int
|
53 | 1 | tkerber | List of indices of atoms to vibrate. Default behavior is
|
54 | 1 | tkerber | to vibrate all atoms.
|
55 | 1 | tkerber | name: str
|
56 | 1 | tkerber | Name to use for files.
|
57 | 1 | tkerber | delta: float
|
58 | 1 | tkerber | Magnitude of displacements.
|
59 | 1 | tkerber | nfree: int
|
60 | 1 | tkerber | Number of displacements per degree of freedom, 2 or 4 are
|
61 | 1 | tkerber | supported. Default is 2 which will displace each atom +delta
|
62 | 1 | tkerber | and -delta in each cartesian direction.
|
63 | 1 | tkerber | directions: list of int
|
64 | 1 | tkerber | Cartesian coordinates to calculate the gradient of the dipole moment in.
|
65 | 1 | tkerber | For example directions = 2 only dipole moment in the z-direction will
|
66 | 1 | tkerber | be considered, whereas for directions = [0, 1] only the dipole
|
67 | 1 | tkerber | moment in the xy-plane will be considered. Default behavior is to
|
68 | 1 | tkerber | use the dipole moment in all directions.
|
69 | 1 | tkerber |
|
70 | 1 | tkerber | Example:
|
71 | 1 | tkerber |
|
72 | 1 | tkerber | >>> from ase.io import read
|
73 | 1 | tkerber | >>> from ase.calculators.vasp import Vasp
|
74 | 1 | tkerber | >>> from ase.infrared import InfraRed
|
75 | 1 | tkerber | >>> water = read('water.traj') # read pre-relaxed structure of water molecule
|
76 | 1 | tkerber | >>> calc = Vasp(prec='Accurate',
|
77 | 1 | tkerber | ... ediff=1E-8,
|
78 | 1 | tkerber | ... isym=0,
|
79 | 1 | tkerber | ... idipol=4, # calculate the total dipole moment
|
80 | 1 | tkerber | ... dipol=water.get_center_of_mass(scaled=True),
|
81 | 1 | tkerber | ... ldipol=True)
|
82 | 1 | tkerber | >>> water.set_calculator(calc)
|
83 | 1 | tkerber | >>> ir = InfraRed(water)
|
84 | 1 | tkerber | >>> ir.run()
|
85 | 1 | tkerber | >>> ir.summary()
|
86 | 1 | tkerber | -------------------------------------
|
87 | 1 | tkerber | Mode Frequency Intensity
|
88 | 1 | tkerber | # meV cm^-1 (D/Å)^2 amu^-1
|
89 | 1 | tkerber | -------------------------------------
|
90 | 1 | tkerber | 0 16.9i 136.2i 1.6108
|
91 | 1 | tkerber | 1 10.5i 84.9i 2.1682
|
92 | 1 | tkerber | 2 5.1i 41.1i 1.7327
|
93 | 1 | tkerber | 3 0.3i 2.2i 0.0080
|
94 | 1 | tkerber | 4 2.4 19.0 0.1186
|
95 | 1 | tkerber | 5 15.3 123.5 1.4956
|
96 | 1 | tkerber | 6 195.5 1576.7 1.6437
|
97 | 1 | tkerber | 7 458.9 3701.3 0.0284
|
98 | 1 | tkerber | 8 473.0 3814.6 1.1812
|
99 | 1 | tkerber | -------------------------------------
|
100 | 1 | tkerber | Zero-point energy: 0.573 eV
|
101 | 1 | tkerber | Static dipole moment: 1.833 D
|
102 | 1 | tkerber | Maximum force on atom in `eqiulibrium`: 0.0026 eV/Å
|
103 | 1 | tkerber |
|
104 | 1 | tkerber |
|
105 | 1 | tkerber |
|
106 | 1 | tkerber | This interface now also works for calculator 'siesta',
|
107 | 1 | tkerber | (added get_dipole_moment for siesta).
|
108 | 1 | tkerber |
|
109 | 1 | tkerber | Example:
|
110 | 1 | tkerber |
|
111 | 1 | tkerber | >>> #!/usr/bin/env python
|
112 | 1 | tkerber |
|
113 | 1 | tkerber | >>> from ase.io import read
|
114 | 1 | tkerber | >>> from ase.calculators.siesta import Siesta
|
115 | 1 | tkerber | >>> from ase.infrared import InfraRed
|
116 | 1 | tkerber |
|
117 | 1 | tkerber | >>> bud = read('bud1.xyz')
|
118 | 1 | tkerber |
|
119 | 1 | tkerber | >>> calc = Siesta(label='bud',
|
120 | 1 | tkerber | ... meshcutoff=250 * Ry,
|
121 | 1 | tkerber | ... basis='DZP',
|
122 | 1 | tkerber | ... kpts=[1, 1, 1])
|
123 | 1 | tkerber |
|
124 | 1 | tkerber | >>> calc.set_fdf('DM.MixingWeight', 0.08)
|
125 | 1 | tkerber | >>> calc.set_fdf('DM.NumberPulay', 3)
|
126 | 1 | tkerber | >>> calc.set_fdf('DM.NumberKick', 20)
|
127 | 1 | tkerber | >>> calc.set_fdf('DM.KickMixingWeight', 0.15)
|
128 | 1 | tkerber | >>> calc.set_fdf('SolutionMethod', 'Diagon')
|
129 | 1 | tkerber | >>> calc.set_fdf('MaxSCFIterations', 500)
|
130 | 1 | tkerber | >>> calc.set_fdf('PAO.BasisType', 'split')
|
131 | 1 | tkerber | >>> #50 meV = 0.003674931 * Ry
|
132 | 1 | tkerber | >>> calc.set_fdf('PAO.EnergyShift', 0.003674931 * Ry )
|
133 | 1 | tkerber | >>> calc.set_fdf('LatticeConstant', 1.000000 * Ang)
|
134 | 1 | tkerber | >>> calc.set_fdf('WriteCoorXmol', 'T')
|
135 | 1 | tkerber |
|
136 | 1 | tkerber | >>> bud.set_calculator(calc)
|
137 | 1 | tkerber |
|
138 | 1 | tkerber | >>> ir = InfraRed(bud)
|
139 | 1 | tkerber | >>> ir.run()
|
140 | 1 | tkerber | >>> ir.summary()
|
141 | 1 | tkerber |
|
142 | 1 | tkerber |
|
143 | 1 | tkerber |
|
144 | 1 | tkerber |
|
145 | 1 | tkerber | """
|
146 | 1 | tkerber | def __init__(self, atoms, indices=None, name='ir', delta=0.01, nfree=2, directions=None): |
147 | 1 | tkerber | assert nfree in [2, 4] |
148 | 1 | tkerber | self.atoms = atoms
|
149 | 1 | tkerber | if atoms.constraints:
|
150 | 1 | tkerber | print "WARNING! \n Your Atoms object is constrained. Some forces may be unintended set to zero. \n" |
151 | 1 | tkerber | self.calc = atoms.get_calculator()
|
152 | 1 | tkerber | if indices is None: |
153 | 1 | tkerber | indices = range(len(atoms)) |
154 | 1 | tkerber | self.indices = np.asarray(indices)
|
155 | 1 | tkerber | self.nfree = nfree
|
156 | 1 | tkerber | self.name = name+'-d%.3f' % delta |
157 | 1 | tkerber | self.delta = delta
|
158 | 1 | tkerber | self.H = None |
159 | 1 | tkerber | if directions is None: |
160 | 1 | tkerber | self.directions = np.asarray([0, 1, 2]) |
161 | 1 | tkerber | else:
|
162 | 1 | tkerber | self.directions = np.asarray(directions)
|
163 | 1 | tkerber | self.ir = True |
164 | 1 | tkerber | |
165 | 1 | tkerber | def read(self, method='standard', direction='central'): |
166 | 1 | tkerber | self.method = method.lower()
|
167 | 1 | tkerber | self.direction = direction.lower()
|
168 | 1 | tkerber | assert self.method in ['standard', 'frederiksen'] |
169 | 1 | tkerber | if direction != 'central': |
170 | 1 | tkerber | raise NotImplementedError('Only central difference is implemented at the moment.') |
171 | 1 | tkerber | |
172 | 1 | tkerber | # Get "static" dipole moment and forces
|
173 | 1 | tkerber | name = '%s.eq.pckl' % self.name |
174 | 1 | tkerber | [forces_zero, dipole_zero] = pickle.load(open(name))
|
175 | 1 | tkerber | self.dipole_zero = (sum(dipole_zero**2)**0.5)*units.Debye |
176 | 1 | tkerber | self.force_zero = max([sum((forces_zero[j])**2)**0.5 for j in self.indices]) |
177 | 1 | tkerber | |
178 | 1 | tkerber | ndof = 3 * len(self.indices) |
179 | 1 | tkerber | H = np.empty((ndof, ndof)) |
180 | 1 | tkerber | dpdx = np.empty((ndof, 3))
|
181 | 1 | tkerber | r = 0
|
182 | 1 | tkerber | for a in self.indices: |
183 | 1 | tkerber | for i in 'xyz': |
184 | 1 | tkerber | name = '%s.%d%s' % (self.name, a, i) |
185 | 1 | tkerber | [fminus, dminus] = pickle.load(open(name + '-.pckl')) |
186 | 1 | tkerber | [fplus, dplus] = pickle.load(open(name + '+.pckl')) |
187 | 1 | tkerber | if self.nfree == 4: |
188 | 1 | tkerber | [fminusminus, dminusminus] = pickle.load(open(name + '--.pckl')) |
189 | 1 | tkerber | [fplusplus, dplusplus] = pickle.load(open(name + '++.pckl')) |
190 | 1 | tkerber | if self.method == 'frederiksen': |
191 | 1 | tkerber | fminus[a] += -fminus.sum(0)
|
192 | 1 | tkerber | fplus[a] += -fplus.sum(0)
|
193 | 1 | tkerber | if self.nfree == 4: |
194 | 1 | tkerber | fminusminus[a] += -fminus.sum(0)
|
195 | 1 | tkerber | fplusplus[a] += -fplus.sum(0)
|
196 | 1 | tkerber | if self.nfree == 2: |
197 | 1 | tkerber | H[r] = (fminus - fplus)[self.indices].ravel() / 2.0 |
198 | 1 | tkerber | dpdx[r] = (dminus - dplus) |
199 | 1 | tkerber | if self.nfree == 4: |
200 | 1 | tkerber | H[r] = (-fminusminus+8*fminus-8*fplus+fplusplus)[self.indices].ravel() / 12.0 |
201 | 1 | tkerber | dpdx[r] = (-dplusplus + 8*dplus - 8*dminus +dminusminus) / 6.0 |
202 | 1 | tkerber | H[r] /= 2 * self.delta |
203 | 1 | tkerber | dpdx[r] /= 2 * self.delta |
204 | 1 | tkerber | for n in range(3): |
205 | 1 | tkerber | if n not in self.directions: |
206 | 1 | tkerber | dpdx[r][n] = 0
|
207 | 1 | tkerber | dpdx[r][n] = 0
|
208 | 1 | tkerber | r += 1
|
209 | 1 | tkerber | # Calculate eigenfrequencies and eigenvectors
|
210 | 1 | tkerber | m = self.atoms.get_masses()
|
211 | 1 | tkerber | H += H.copy().T |
212 | 1 | tkerber | self.H = H
|
213 | 1 | tkerber | m = self.atoms.get_masses()
|
214 | 1 | tkerber | self.im = np.repeat(m[self.indices]**-0.5, 3) |
215 | 1 | tkerber | omega2, modes = np.linalg.eigh(self.im[:, None] * H * self.im) |
216 | 1 | tkerber | self.modes = modes.T.copy()
|
217 | 1 | tkerber | |
218 | 1 | tkerber | # Calculate intensities
|
219 | 1 | tkerber | dpdq = np.array([dpdx[j]/sqrt(m[self.indices[j/3]]*units._amu/units._me) for j in range(ndof)]) |
220 | 1 | tkerber | dpdQ = np.dot(dpdq.T, modes) |
221 | 1 | tkerber | dpdQ = dpdQ.T |
222 | 1 | tkerber | intensities = np.array([sum(dpdQ[j]**2) for j in range(ndof)]) |
223 | 1 | tkerber | # Conversion factor:
|
224 | 1 | tkerber | s = units._hbar * 1e10 / sqrt(units._e * units._amu)
|
225 | 1 | tkerber | self.hnu = s * omega2.astype(complex)**0.5 |
226 | 1 | tkerber | # Conversion factor from atomic units to (D/Angstrom)^2/amu.
|
227 | 1 | tkerber | conv = units.Debye**2*units._amu/units._me
|
228 | 1 | tkerber | self.intensities = intensities*conv
|
229 | 1 | tkerber | |
230 | 1 | tkerber | def summary(self, method='standard', direction='central'): |
231 | 1 | tkerber | hnu = self.get_energies(method, direction)
|
232 | 1 | tkerber | s = 0.01 * units._e / units._c / units._hplanck
|
233 | 1 | tkerber | parprint('-------------------------------------')
|
234 | 1 | tkerber | parprint(' Mode Frequency Intensity')
|
235 | 1 | tkerber | parprint(' # meV cm^-1 (D/Å)^2 amu^-1')
|
236 | 1 | tkerber | parprint('-------------------------------------')
|
237 | 1 | tkerber | for n, e in enumerate(hnu): |
238 | 1 | tkerber | if e.imag != 0: |
239 | 1 | tkerber | c = 'i'
|
240 | 1 | tkerber | e = e.imag |
241 | 1 | tkerber | else:
|
242 | 1 | tkerber | c = ' '
|
243 | 1 | tkerber | parprint('%3d %6.1f%s %7.1f%s %9.4f' %
|
244 | 1 | tkerber | (n, 1000 * e, c, s * e, c, self.intensities[n])) |
245 | 1 | tkerber | parprint('-------------------------------------')
|
246 | 1 | tkerber | parprint('Zero-point energy: %.3f eV' % self.get_zero_point_energy()) |
247 | 1 | tkerber | parprint('Static dipole moment: %.3f D' % self.dipole_zero) |
248 | 1 | tkerber | parprint('Maximum force on atom in `eqiulibrium`: %.4f eV/Å' %
|
249 | 1 | tkerber | self.force_zero)
|
250 | 1 | tkerber | parprint() |
251 | 1 | tkerber | |
252 | 1 | tkerber | def get_spectrum(self, start=800, end=4000, npts=None, width=4, type='Gaussian', method='standard', direction='central'): |
253 | 1 | tkerber | """Get infrared spectrum.
|
254 | 1 | tkerber |
|
255 | 1 | tkerber | The method returns wavenumbers in cm^-1 with corresonding absolute infrared intensity.
|
256 | 1 | tkerber | Start and end point, and width of the Gaussian/Lorentzian should be given in cm^-1."""
|
257 | 1 | tkerber | |
258 | 1 | tkerber | self.type = type.lower() |
259 | 1 | tkerber | assert self.type in ['gaussian', 'lorentzian'] |
260 | 1 | tkerber | if not npts: |
261 | 1 | tkerber | npts = (end-start)/width*10+1 |
262 | 1 | tkerber | frequencies = self.get_frequencies(method, direction).real
|
263 | 1 | tkerber | intensities=self.intensities
|
264 | 1 | tkerber | if type == 'lorentzian': |
265 | 1 | tkerber | intensities = intensities*width*pi/2.
|
266 | 1 | tkerber | else:
|
267 | 1 | tkerber | sigma = width/2./sqrt(2.*log(2.)) |
268 | 1 | tkerber | #Make array with spectrum data
|
269 | 1 | tkerber | spectrum = np.empty(npts,np.float) |
270 | 1 | tkerber | energies = np.empty(npts,np.float) |
271 | 1 | tkerber | ediff = (end-start)/float(npts-1) |
272 | 1 | tkerber | energies = np.arange(start, end+ediff/2, ediff)
|
273 | 1 | tkerber | for i, energy in enumerate(energies): |
274 | 1 | tkerber | energies[i] = energy |
275 | 1 | tkerber | if type == 'lorentzian': |
276 | 1 | tkerber | spectrum[i] = (intensities*0.5*width/pi/((frequencies-energy)**2+0.25*width**2)).sum() |
277 | 1 | tkerber | else:
|
278 | 1 | tkerber | spectrum[i] = (intensities*np.exp(-(frequencies - energy)**2/2./sigma**2)).sum() |
279 | 1 | tkerber | return [energies, spectrum]
|
280 | 1 | tkerber | |
281 | 1 | tkerber | def write_spectra(self, out='ir-spectra.dat', start=800, end=4000, npts=None, width=10, type='Gaussian', method='standard', direction='central'): |
282 | 1 | tkerber | """Write out infrared spectrum to file.
|
283 | 1 | tkerber |
|
284 | 1 | tkerber | First column is the wavenumber in cm^-1, the second column the absolute infrared intensities, and
|
285 | 1 | tkerber | the third column the absorbance scaled so that data runs from 1 to 0. Start and end
|
286 | 1 | tkerber | point, and width of the Gaussian/Lorentzian should be given in cm^-1."""
|
287 | 1 | tkerber | energies, spectrum = self.get_spectrum(start, end, npts, width, type, method, direction) |
288 | 1 | tkerber | |
289 | 1 | tkerber | #Write out spectrum in file. First column is absolute intensities.
|
290 | 1 | tkerber | #Second column is absorbance scaled so that data runs from 1 to 0
|
291 | 1 | tkerber | spectrum2 = 1. - spectrum/spectrum.max()
|
292 | 1 | tkerber | outdata = np.empty([len(energies), 3]) |
293 | 1 | tkerber | outdata.T[0] = energies
|
294 | 1 | tkerber | outdata.T[1] = spectrum
|
295 | 1 | tkerber | outdata.T[2] = spectrum2
|
296 | 1 | tkerber | fd = open(out, 'w') |
297 | 1 | tkerber | for row in outdata: |
298 | 1 | tkerber | fd.write('%.3f %15.5e %15.5e \n' % (row[0], row[1], row[2]) ) |
299 | 1 | tkerber | fd.close() |
300 | 1 | tkerber | #np.savetxt(out, outdata, fmt='%.3f %15.5e %15.5e') |