File: siesta_lrtddft.py

package info (click to toggle)
python-ase 3.21.1-2
  • links: PTS, VCS
  • area: main
  • in suites: bullseye
  • size: 13,936 kB
  • sloc: python: 122,428; xml: 946; makefile: 111; javascript: 47
file content (195 lines) | stat: -rw-r--r-- 6,483 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
import numpy as np
import ase.units as un

class SiestaLRTDDFT:
    """Interface for linear response TDDFT for Siesta via
    [PyNAO](https://mbarbry.website.fr.to/pynao/doc/html/)

    When using PyNAO please cite the papers indicated at in the PyNAO
    [documentation](https://mbarbry.website.fr.to/pynao/doc/html/references.html)
    """
    def __init__(self, initialize=False, **kw):
        """
        Parameters
        ----------
        initialize: bool
            To initialize the tddft calculations before calculating the polarizability
            Can be useful to calculate multiple frequency range without the need
            to recalculate the kernel
        kw: dictionary
            keywords for the tddft_iter function from PyNAO
        """

        try:
            from pynao import tddft_iter
        except ModuleNotFoundError as err:
            msg = "running lrtddft with Siesta calculator requires pynao package"
            raise ModuleNotFoundError(msg) from err

        self.initialize=initialize
        self.lrtddft_params = kw
        self.tddft = None

        # convert iter_broadening to Ha
        if "iter_broadening" in self.lrtddft_params:
            self.lrtddft_params["iter_broadening"] /= un.Ha

        if self.initialize:
            self.tddft = tddft_iter(**self.lrtddft_params)

    def get_ground_state(self, atoms, **kw):
        """
        Run siesta calculations in order to get ground state properties.
        Makes sure that the proper parameters are unsed in order to be able
        to run pynao afterward, i.e.,

            COOP.Write = True
            WriteDenchar = True
            XML.Write = True
        """
        from ase.calculators.siesta import Siesta

        if "fdf_arguments" not in kw.keys():
            kw["fdf_arguments"] = {"COOP.Write": True,
                                   "WriteDenchar": True,
                                   "XML.Write": True}
        else:
            for param in ["COOP.Write", "WriteDenchar", "XML.Write"]:
                kw["fdf_arguments"][param] = True

        siesta = Siesta(**kw)
        atoms.calc = siesta
        atoms.get_potential_energy()


    def get_polarizability(self, omega, Eext=np.array([1.0, 1.0, 1.0]), inter=True):
        """
        Calculate the polarizability of a molecule via linear response TDDFT
        calculation.

        Parameters
        ----------
        omega: float or array like
            frequency range for which the polarizability should be computed, in eV

        Returns
        -------
        polarizability: array like (complex)
            array of dimension (3, 3, nff) with nff the number of frequency,
            the first and second dimension are the matrix elements of the
            polarizability in atomic units::

                P_xx, P_xy, P_xz, Pyx, .......

        Example
        -------

        from ase.calculators.siesta.siesta_lrtddft import siestaLRTDDFT
        from ase.build import molecule
        import numpy as np
        import matplotlib.pyplot as plt

        # Define the systems
        CH4 = molecule('CH4')

        lr = siestaLRTDDFT(label="siesta", jcutoff=7, iter_broadening=0.15,
                            xc_code='LDA,PZ', tol_loc=1e-6, tol_biloc=1e-7)

        # run DFT calculation with Siesta
        lr.get_ground_state(CH4)

        # run TDDFT calculation with PyNAO
        freq=np.arange(0.0, 25.0, 0.05)
        pmat = lr.get_polarizability(freq) 
        """
        from pynao import tddft_iter

        if not self.initialize:
            self.tddft = tddft_iter(**self.lrtddft_params)

        if isinstance(omega, float):
            freq = np.array([omega])
        elif isinstance(omega, list):
            freq = np.array([omega])
        elif isinstance(omega, np.ndarray):
            freq = omega
        else:
            raise ValueError("omega soulf")

        freq_cmplx = freq/un.Ha + 1j * self.tddft.eps
        if inter:
            pmat = -self.tddft.comp_polariz_inter_Edir(freq_cmplx, Eext=Eext)
            self.dn = self.tddft.dn
        else:
            pmat = -self.tddft.comp_polariz_nonin_Edir(freq_cmplx, Eext=Eext)
            self.dn = self.tddft.dn0

        return pmat

class RamanCalculatorInterface(SiestaLRTDDFT):
    """Raman interface for Siesta calculator.
    When using the Raman calculator, please cite

    M. Walter and M. Moseler, Ab Initio Wavelength-Dependent Raman Spectra:
    Placzek Approximation and Beyond, J. Chem. Theory Comput. 2020, 16, 1, 576–586
    """
    def __init__(self, omega=0.0, **kw):
        """
        Parameters
        ----------
        omega: float
            frequency at which the Raman intensity should be computed, in eV

        kw: dictionary
            The parameter for the siesta_lrtddft object
        """

        self.omega = omega
        super().__init__(**kw)


    def __call__(self, *args, **kwargs):
        """Shorthand for calculate"""
        return self.calculate(*args, **kwargs)

    def calculate(self, atoms):
        """
        Calculate the polarizability for frequency omega

        Parameters
        ----------
        atoms: atoms class
            The atoms definition of the system. Not used but required by Raman
            calculator
        """
        pmat = self.get_polarizability(self.omega, Eext=np.array([1.0, 1.0, 1.0]))

        # Specific for raman calls, it expects just the tensor for a single
        # frequency and need only the real part

        # For static raman, imaginary part is zero??
        # Answer from Michael Walter: Yes, in the case of finite systems you may
        # choose the wavefunctions to be real valued. Then also the density
        # response function and hence the polarizability are real.

        # Convert from atomic units to e**2 Ang**2/eV
        return pmat[:, :, 0].real * (un.Bohr**2) / un.Ha
 
def pol2cross_sec(p, omg):
    """
    Convert the polarizability in au to cross section in nm**2

    Input parameters:
    -----------------
    p (np array): polarizability from mbpt_lcao calc
    omg (np.array): frequency range in eV

    Output parameters:
    ------------------
    sigma (np array): cross section in nm**2
    """

    c = 1 / un.alpha                      # speed of the light in au
    omg /= un.Ha                          # to convert from eV to Hartree
    sigma = 4 * np.pi * omg * p / (c)     # bohr**2
    return sigma * (0.1 * un.Bohr)**2     # nm**2