Statistiques
| Révision :

root / ase / infrared.py @ 11

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')