def test_franck_condon(testdir):
    # FCOverlap

    fco = FranckCondonOverlap()
    fcr = FranckCondonRecursive()

    # check factorial
    assert (fco.factorial(8) == factorial(8))
    # the second test is useful according to the implementation
    assert (fco.factorial(5) == factorial(5))
    assert (fco.factorial.inv(5) == 1. / factorial(5))

    # check T=0 and n=0 equality
    S = np.array([1, 2.1, 34])
    m = 5
    assert (((fco.directT0(m, S) - fco.direct(0, m, S)) / fco.directT0(m, S) <
             1e-15).all())

    # check symmetry
    S = 2
    n = 3
    assert (fco.direct(n, m, S) == fco.direct(m, n, S))

    # ---------------------------
    # specials
    S = np.array([0, 1.5])
    delta = np.sqrt(2 * S)
    for m in [2, 7]:
        equal(
            fco.direct0mm1(m, S)**2,
            fco.direct(1, m, S) * fco.direct(m, 0, S), 1.e-17)
        equal(fco.direct0mm1(m, S), fcr.ov0mm1(m, delta), 1.e-15)
        equal(fcr.ov0mm1(m, delta),
              fcr.ov0m(m, delta) * fcr.ov1m(m, delta), 1.e-15)
        equal(fcr.ov0mm1(m, -delta), fcr.direct0mm1(m, -delta), 1.e-15)
        equal(fcr.ov0mm1(m, delta), -fcr.direct0mm1(m, -delta), 1.e-15)

        equal(
            fco.direct0mm2(m, S)**2,
            fco.direct(2, m, S) * fco.direct(m, 0, S), 1.e-17)
        equal(fco.direct0mm2(m, S), fcr.ov0mm2(m, delta), 1.e-15)
        equal(fcr.ov0mm2(m, delta),
              fcr.ov0m(m, delta) * fcr.ov2m(m, delta), 1.e-15)
        equal(fco.direct0mm2(m, S), fcr.direct0mm2(m, delta), 1.e-15)

        equal(fcr.direct0mm3(m, delta),
              fcr.ov0m(m, delta) * fcr.ov3m(m, delta), 1.e-15)

        equal(fcr.ov1mm2(m, delta),
              fcr.ov1m(m, delta) * fcr.ov2m(m, delta), 1.e-15)
        equal(fcr.direct1mm2(m, delta), fcr.ov1mm2(m, delta), 1.e-15)
Esempio n. 2
0
def test_franck_condon():
    import sys
    import numpy as np

    from ase.vibrations.franck_condon import FranckCondonOverlap, FranckCondonRecursive
    from math import factorial


    def equal(x, y, tolerance=0, fail=True, msg=''):
        """Compare x and y."""

        if not np.isfinite(x - y).any() or (np.abs(x - y) > tolerance).any():
            msg = (msg + '%s != %s (error: |%s| > %.9g)' %
                   (x, y, x - y, tolerance))
            if fail:
                raise AssertionError(msg)
            else:
                sys.stderr.write('WARNING: %s\n' % msg)

    # FCOverlap

    fco = FranckCondonOverlap()
    fcr = FranckCondonRecursive()

    # check factorial
    assert(fco.factorial(8) == factorial(8))
    # the second test is useful according to the implementation
    assert(fco.factorial(5) == factorial(5))
    assert(fco.factorial.inv(5) == 1. / factorial(5))

    # check T=0 and n=0 equality
    S = np.array([1, 2.1, 34])
    m = 5
    assert(((fco.directT0(m, S) - fco.direct(0, m, S)) / fco.directT0(m, S) <
            1e-15).all())

    # check symmetry
    S = 2
    n = 3
    assert(fco.direct(n, m, S) == fco.direct(m, n, S))

    # ---------------------------
    # specials
    S = np.array([0, 1.5])
    delta = np.sqrt(2 * S)
    for m in [2, 7]:
        equal(fco.direct0mm1(m, S)**2,
              fco.direct(1, m, S) * fco.direct(m, 0, S), 1.e-17)
        equal(fco.direct0mm1(m, S), fcr.ov0mm1(m, delta), 1.e-15)
        equal(fcr.ov0mm1(m, delta),
              fcr.ov0m(m, delta) * fcr.ov1m(m, delta), 1.e-15)
        equal(fcr.ov0mm1(m, -delta), fcr.direct0mm1(m, -delta), 1.e-15)
        equal(fcr.ov0mm1(m, delta), - fcr.direct0mm1(m, -delta), 1.e-15)

        equal(fco.direct0mm2(m, S)**2,
              fco.direct(2, m, S) * fco.direct(m, 0, S), 1.e-17)
        equal(fco.direct0mm2(m, S), fcr.ov0mm2(m, delta), 1.e-15)
        equal(fcr.ov0mm2(m, delta),
              fcr.ov0m(m, delta) * fcr.ov2m(m, delta), 1.e-15)
        equal(fco.direct0mm2(m, S), fcr.direct0mm2(m, delta), 1.e-15)

        equal(fcr.direct0mm3(m, delta),
              fcr.ov0m(m, delta) * fcr.ov3m(m, delta), 1.e-15)

        equal(fcr.ov1mm2(m, delta),
              fcr.ov1m(m, delta) * fcr.ov2m(m, delta), 1.e-15)
        equal(fcr.direct1mm2(m, delta), fcr.ov1mm2(m, delta), 1.e-15)
Esempio n. 3
0
class Albrecht(ResonantRaman):
    def __init__(self, *args, **kwargs):
        """
        Parameters
        ----------
        all from ResonantRaman.__init__
        combinations: int
            Combinations to consider for multiple excitations.
            Default is 1, possible 2
        skip: int
            Number of first transitions to exclude. Default 0,
            recommended: 5 for linear molecules, 6 for other molecules
        nm: int
            Number of intermediate m levels to consider, default 20
        """
        self.combinations = kwargs.pop('combinations', 1)
        self.skip = kwargs.pop('skip', 0)
        self.nm = kwargs.pop('nm', 20)
        approximation = kwargs.pop('approximation', 'Albrecht')

        ResonantRaman.__init__(self, *args, **kwargs)

        self.set_approximation(approximation)

    def set_approximation(self, value):
        approx = value.lower()
        if approx in ['albrecht', 'albrecht b', 'albrecht c', 'albrecht bc']:
            if not self.overlap:
                raise ValueError('Overlaps are needed')
        elif not approx == 'albrecht a':
            raise ValueError('Please use "Albrecht" or "Albrecht A/B/C/BC"')
        self._approx = value

    def calculate_energies_and_modes(self):
        if hasattr(self, 'im_r'):
            return

        ResonantRaman.calculate_energies_and_modes(self)

        # single transitions and their occupation
        om_Q = self.om_Q[self.skip:]
        om_v = om_Q
        ndof = len(om_Q)
        n_vQ = np.eye(ndof, dtype=int)

        l_Q = range(ndof)
        ind_v = list(combinations_with_replacement(l_Q, 1))

        if self.combinations > 1:
            if not self.combinations == 2:
                raise NotImplementedError

            for c in range(2, self.combinations + 1):
                ind_v += list(combinations_with_replacement(l_Q, c))

            nv = len(ind_v)
            n_vQ = np.zeros((nv, ndof), dtype=int)
            om_v = np.zeros((nv), dtype=float)
            for j, wt in enumerate(ind_v):
                for i in wt:
                    n_vQ[j, i] += 1
            om_v = n_vQ.dot(om_Q)

        self.ind_v = ind_v
        self.om_v = om_v
        self.n_vQ = n_vQ  # how many of each
        self.d_vQ = np.where(n_vQ > 0, 1, 0)  # do we have them ?

    def get_energies(self):
        self.calculate_energies_and_modes()
        return self.om_v

    def _collect_r(self, arr_ro, oshape, dtype):
        """Collect an array that is distributed."""
        if len(self.myr) == self.ndof:  # serial
            return arr_ro
        data_ro = np.zeros([self.ndof] + oshape, dtype)
        if len(arr_ro):
            data_ro[self.slize] = arr_ro
        self.comm.sum(data_ro)
        return data_ro

    def Huang_Rhys_factors(self, forces_r):
        """Evaluate Huang-Rhys factors derived from forces."""
        return 0.5 * self.unitless_displacements(forces_r)**2

    def unitless_displacements(self, forces_r, mineigv=1e-12):
        """Evaluate unitless displacements from forces

        Parameters
        ----------
        forces_r: array
          Forces in cartesian coordinates
        mineigv: float
          Minimal Eigenvalue to consider in matrix inversion to handle
          numerical noise. Default 1e-12

        Returns
        -------
        Unitless displacements in Eigenmode coordinates
        """
        assert (len(forces_r.flat) == self.ndof)

        if not hasattr(self, 'Dm1_q'):
            self.eigv_q, self.eigw_rq = np.linalg.eigh(self.im_r[:, None] *
                                                       self.H * self.im_r)
            # there might be zero or nearly zero eigenvalues
            self.Dm1_q = np.divide(1,
                                   self.eigv_q,
                                   out=np.zeros_like(self.eigv_q),
                                   where=np.abs(self.eigv_q) > mineigv)
        X_r = self.eigw_rq @ np.diag(
            self.Dm1_q) @ self.eigw_rq.T @ (forces_r.flat * self.im_r)

        d_Q = np.dot(self.modes_Qq, X_r)
        s = 1.e-20 / u.kg / u.C / u._hbar**2
        d_Q *= np.sqrt(s * self.om_Q)

        return d_Q

    def omegaLS(self, omega, gamma):
        omL = omega + 1j * gamma
        omS_Q = omL - self.om_Q
        return omL, omS_Q

    def init_parallel_excitations(self):
        """Init for paralellization over excitations."""
        n_p = len(self.ex0E_p)

        # collect excited state forces
        exF_pr = self._collect_r(self.exF_rp, [n_p], self.ex0E_p.dtype).T

        # select your work load
        myn = -(-n_p // self.comm.size)  # ceil divide
        rank = self.comm.rank
        s = slice(myn * rank, myn * (rank + 1))
        return n_p, range(n_p)[s], exF_pr

    def meA(self, omega, gamma=0.1):
        """Evaluate Albrecht A term.

        Returns
        -------
        Full Albrecht A matrix element. Unit: e^2 Angstrom^2 / eV
        """
        self.read()

        if not hasattr(self, 'fcr'):
            self.fcr = FranckCondonRecursive()

        omL = omega + 1j * gamma
        omS_Q = omL - self.om_Q

        n_p, myp, exF_pr = self.init_parallel_excitations()
        exF_pr = np.where(np.abs(exF_pr) > 1e-2, exF_pr, 0)

        m_Qcc = np.zeros((self.ndof, 3, 3), dtype=complex)
        for p in myp:
            energy = self.ex0E_p[p]
            d_Q = self.unitless_displacements(exF_pr[p])
            energy_Q = energy - self.om_Q * d_Q**2 / 2.
            me_cc = np.outer(self.ex0m_pc[p], self.ex0m_pc[p].conj())

            wm_Q = np.zeros((self.ndof), dtype=complex)
            wp_Q = np.zeros((self.ndof), dtype=complex)
            for m in range(self.nm):
                fco_Q = self.fcr.direct0mm1(m, d_Q)
                e_Q = energy_Q + m * self.om_Q
                wm_Q += fco_Q / (e_Q - omL)
                wp_Q += fco_Q / (e_Q + omS_Q)
            m_Qcc += np.einsum('a,bc->abc', wm_Q, me_cc)
            m_Qcc += np.einsum('a,bc->abc', wp_Q, me_cc.conj())
        self.comm.sum(m_Qcc)

        return m_Qcc  # e^2 Angstrom^2 / eV

    def meAmult(self, omega, gamma=0.1):
        """Evaluate Albrecht A term.

        Returns
        -------
        Full Albrecht A matrix element. Unit: e^2 Angstrom^2 / eV
        """
        self.read()

        if not hasattr(self, 'fcr'):
            self.fcr = FranckCondonRecursive()

        omL = omega + 1j * gamma
        omS_v = omL - self.om_v
        nv = len(self.om_v)
        om_Q = self.om_Q[self.skip:]
        nQ = len(om_Q)

        # n_v:
        #     how many FC factors are involved
        # nvib_ov:
        #     delta functions to switch contributions depending on order o
        # ind_ov:
        #     Q indicees
        # n_ov:
        #     # of vibrational excitations
        n_v = self.d_vQ.sum(axis=1)  # multiplicity

        nvib_ov = np.empty((self.combinations, nv), dtype=int)
        om_ov = np.zeros((self.combinations, nv), dtype=float)
        n_ov = np.zeros((self.combinations, nv), dtype=int)
        d_ovQ = np.zeros((self.combinations, nv, nQ), dtype=int)
        for o in range(self.combinations):
            nvib_ov[o] = np.array(n_v == (o + 1))
            for v in range(nv):
                try:
                    om_ov[o, v] = om_Q[self.ind_v[v][o]]
                    d_ovQ[o, v, self.ind_v[v][o]] = 1
                except IndexError:
                    pass
        # XXXX change ????
        n_ov[0] = self.n_vQ.max(axis=1)
        n_ov[1] = nvib_ov[1]

        n_p, myp, exF_pr = self.init_parallel_excitations()

        m_vcc = np.zeros((nv, 3, 3), dtype=complex)
        for p in myp:
            energy = self.ex0E_p[p]
            d_Q = self.unitless_displacements(exF_pr[p])[self.skip:]
            S_Q = d_Q**2 / 2.
            energy_v = energy - self.d_vQ.dot(om_Q * S_Q)
            me_cc = np.outer(self.ex0m_pc[p], self.ex0m_pc[p].conj())

            fco1_mQ = np.empty((self.nm, nQ), dtype=float)
            fco2_mQ = np.empty((self.nm, nQ), dtype=float)
            for m in range(self.nm):
                fco1_mQ[m] = self.fcr.direct0mm1(m, d_Q)
                fco2_mQ[m] = self.fcr.direct0mm2(m, d_Q)

            wm_v = np.zeros((nv), dtype=complex)
            wp_v = np.zeros((nv), dtype=complex)
            for m in range(self.nm):
                fco1_v = np.where(n_ov[0] == 2, d_ovQ[0].dot(fco2_mQ[m]),
                                  d_ovQ[0].dot(fco1_mQ[m]))

                em_v = energy_v + m * om_ov[0]
                # multiples of same kind
                fco_v = nvib_ov[0] * fco1_v
                wm_v += fco_v / (em_v - omL)
                wp_v += fco_v / (em_v + omS_v)
                if nvib_ov[1].any():
                    # multiples of mixed type
                    for n in range(self.nm):
                        fco2_v = d_ovQ[1].dot(fco1_mQ[n])
                        e_v = em_v + n * om_ov[1]
                        fco_v = nvib_ov[1] * fco1_v * fco2_v
                        wm_v += fco_v / (e_v - omL)
                        wp_v += fco_v / (e_v + omS_v)

            m_vcc += np.einsum('a,bc->abc', wm_v, me_cc)
            m_vcc += np.einsum('a,bc->abc', wp_v, me_cc.conj())
        self.comm.sum(m_vcc)

        return m_vcc  # e^2 Angstrom^2 / eV

    def meBC(self, omega, gamma=0.1, term='BC'):
        """Evaluate Albrecht BC term.

        Returns
        -------
        Full Albrecht BC matrix element.
        Unit: e^2 Angstrom / eV / sqrt(amu)
        """
        self.read()

        if not hasattr(self, 'fco'):
            self.fco = FranckCondonOverlap()

        omL = omega + 1j * gamma
        omS_Q = omL - self.om_Q

        # excited state forces
        n_p, myp, exF_pr = self.init_parallel_excitations()
        # derivatives after normal coordinates
        exdmdr_rpc = self._collect_r(self.exdmdr_rpc, [n_p, 3],
                                     self.ex0m_pc.dtype)
        dmdq_qpc = (exdmdr_rpc.T * self.im_r).T  # unit e / sqrt(amu)
        dmdQ_Qpc = np.dot(dmdq_qpc.T, self.modes_Qq.T).T  # unit e / sqrt(amu)

        me_Qcc = np.zeros((self.ndof, 3, 3), dtype=complex)
        for p in myp:
            energy = self.ex0E_p[p]
            S_Q = self.Huang_Rhys_factors(exF_pr[p])
            # relaxed excited state energy
            # # n_vQ = np.where(self.n_vQ > 0, 1, 0)
            # # energy_v = energy - n_vQ.dot(self.om_Q * S_Q)
            energy_Q = energy - self.om_Q * S_Q

            # # me_cc = np.outer(self.ex0m_pc[p], self.ex0m_pc[p].conj())
            m_c = self.ex0m_pc[p]  # e Angstrom
            dmdQ_Qc = dmdQ_Qpc[:, p]  # e / sqrt(amu)

            wBLS_Q = np.zeros((self.ndof), dtype=complex)
            wBSL_Q = np.zeros((self.ndof), dtype=complex)
            wCLS_Q = np.zeros((self.ndof), dtype=complex)
            wCSL_Q = np.zeros((self.ndof), dtype=complex)
            for m in range(self.nm):
                f0mmQ1_Q = (self.fco.directT0(m, S_Q) +
                            np.sqrt(2) * self.fco.direct0mm2(m, S_Q))
                f0Qmm1_Q = self.fco.direct(1, m, S_Q)

                em_Q = energy_Q + m * self.om_Q
                wBLS_Q += f0mmQ1_Q / (em_Q - omL)
                wBSL_Q += f0Qmm1_Q / (em_Q - omL)
                wCLS_Q += f0mmQ1_Q / (em_Q + omS_Q)
                wCSL_Q += f0Qmm1_Q / (em_Q + omS_Q)

            # unit e^2 Angstrom / sqrt(amu)
            mdmdQ_Qcc = np.einsum('a,bc->bac', m_c, dmdQ_Qc.conj())
            dmdQm_Qcc = np.einsum('ab,c->abc', dmdQ_Qc, m_c.conj())
            if 'B' in term:
                me_Qcc += np.multiply(wBLS_Q, mdmdQ_Qcc.T).T
                me_Qcc += np.multiply(wBSL_Q, dmdQm_Qcc.T).T
            if 'C' in term:
                me_Qcc += np.multiply(wCLS_Q, mdmdQ_Qcc.T).T
                me_Qcc += np.multiply(wCSL_Q, dmdQm_Qcc.T).T

        self.comm.sum(me_Qcc)
        return me_Qcc  # unit e^2 Angstrom / eV / sqrt(amu)

    def electronic_me_Qcc(self, omega, gamma):
        self.calculate_energies_and_modes()

        approx = self.approximation.lower()
        assert (self.combinations == 1)
        Vel_Qcc = np.zeros((len(self.om_Q), 3, 3), dtype=complex)
        if approx == 'albrecht a' or approx == 'albrecht':
            Vel_Qcc += self.meA(omega, gamma)  # e^2 Angstrom^2 / eV
            # divide through pre-factor
            with np.errstate(divide='ignore'):
                Vel_Qcc *= np.where(self.vib01_Q > 0, 1. / self.vib01_Q,
                                    0)[:, None, None]
            # -> e^2 Angstrom / eV / sqrt(amu)
        if approx == 'albrecht bc' or approx == 'albrecht':
            Vel_Qcc += self.meBC(omega, gamma)  # e^2 Angstrom / eV / sqrt(amu)
        if approx == 'albrecht b':
            Vel_Qcc += self.meBC(omega, gamma, term='B')
        if approx == 'albrecht c':
            Vel_Qcc = self.meBC(omega, gamma, term='C')

        Vel_Qcc *= u.Hartree * u.Bohr  # e^2 Angstrom^2 / eV -> Angstrom^3

        return Vel_Qcc  # Angstrom^2 / sqrt(amu)

    def me_Qcc(self, omega, gamma):
        """Full matrix element"""
        self.read()
        approx = self.approximation.lower()
        nv = len(self.om_v)
        V_vcc = np.zeros((nv, 3, 3), dtype=complex)
        if approx == 'albrecht a' or approx == 'albrecht':
            if self.combinations == 1:
                # e^2 Angstrom^2 / eV
                V_vcc += self.meA(omega, gamma)[self.skip:]
            else:
                V_vcc += self.meAmult(omega, gamma)
        if approx == 'albrecht bc' or approx == 'albrecht':
            if self.combinations == 1:
                vel_vcc = self.meBC(omega, gamma)
                V_vcc += vel_vcc * self.vib01_Q[:, None, None]
            else:
                vel_vcc = self.meBCmult(omega, gamma)
                V_vcc = 0
        elif approx == 'albrecht b':
            assert (self.combinations == 1)
            vel_vcc = self.meBC(omega, gamma, term='B')
            V_vcc = vel_vcc * self.vib01_Q[:, None, None]
        if approx == 'albrecht c':
            assert (self.combinations == 1)
            vel_vcc = self.meBC(omega, gamma, term='C')
            V_vcc = vel_vcc * self.vib01_Q[:, None, None]

        return V_vcc  # e^2 Angstrom^2 / eV

    def summary(self,
                omega=0,
                gamma=0,
                method='standard',
                direction='central',
                log=sys.stdout):
        """Print summary for given omega [eV]"""
        if self.combinations > 1:
            return self.extended_summary()

        om_v = self.get_energies()
        intensities = self.get_absolute_intensities(omega, gamma)[self.skip:]

        if isinstance(log, str):
            log = paropen(log, 'a')

        parprint('-------------------------------------', file=log)
        parprint(' excitation at ' + str(omega) + ' eV', file=log)
        parprint(' gamma ' + str(gamma) + ' eV', file=log)
        parprint(' approximation:', self.approximation, file=log)
        parprint(' Mode    Frequency        Intensity', file=log)
        parprint('  #    meV     cm^-1      [A^4/amu]', file=log)
        parprint('-------------------------------------', file=log)
        for n, e in enumerate(om_v):
            if e.imag != 0:
                c = 'i'
                e = e.imag
            else:
                c = ' '
                e = e.real
            parprint('%3d %6.1f   %7.1f%s  %9.1f' %
                     (n, 1000 * e, e / u.invcm, c, intensities[n]),
                     file=log)
        parprint('-------------------------------------', file=log)
        parprint('Zero-point energy: %.3f eV' %
                 self.vibrations.get_zero_point_energy(),
                 file=log)

    def extended_summary(self,
                         omega=0,
                         gamma=0,
                         method='standard',
                         direction='central',
                         log=sys.stdout):
        """Print summary for given omega [eV]"""
        self.read(method, direction)
        om_v = self.get_energies()
        intens_v = self.intensity(omega, gamma)

        if isinstance(log, str):
            log = paropen(log, 'a')

        parprint('-------------------------------------', file=log)
        parprint(' excitation at ' + str(omega) + ' eV', file=log)
        parprint(' gamma ' + str(gamma) + ' eV', file=log)
        parprint(' approximation:', self.approximation, file=log)
        parprint(' observation:', self.observation, file=log)
        parprint(' Mode    Frequency        Intensity', file=log)
        parprint('  #    meV     cm^-1      [e^4A^4/eV^2]', file=log)
        parprint('-------------------------------------', file=log)
        for v, e in enumerate(om_v):
            parprint(self.ind_v[v],
                     '{0:6.1f}   {1:7.1f} {2:9.1f}'.format(
                         1000 * e, e / u.invcm, 1e9 * intens_v[v]),
                     file=log)
        parprint('-------------------------------------', file=log)
        parprint('Zero-point energy: %.3f eV' %
                 self.vibrations.get_zero_point_energy(),
                 file=log)
Esempio n. 4
0
assert (fco.direct(n, m, S) == fco.direct(m, n, S))

# ---------------------------
# specials
S = np.array([0, 1.5])
delta = np.sqrt(2 * S)
for m in [2, 7]:
    equal(
        fco.direct0mm1(m, S)**2,
        fco.direct(1, m, S) * fco.direct(m, 0, S), 1.e-17)
    equal(fco.direct0mm1(m, S), fcr.ov0mm1(m, delta), 1.e-15)
    equal(fcr.ov0mm1(m, delta),
          fcr.ov0m(m, delta) * fcr.ov1m(m, delta), 1.e-15)
    equal(fcr.ov0mm1(m, -delta), fcr.direct0mm1(m, -delta), 1.e-15)
    equal(fcr.ov0mm1(m, delta), -fcr.direct0mm1(m, -delta), 1.e-15)

    equal(
        fco.direct0mm2(m, S)**2,
        fco.direct(2, m, S) * fco.direct(m, 0, S), 1.e-17)
    equal(fco.direct0mm2(m, S), fcr.ov0mm2(m, delta), 1.e-15)
    equal(fcr.ov0mm2(m, delta),
          fcr.ov0m(m, delta) * fcr.ov2m(m, delta), 1.e-15)
    equal(fco.direct0mm2(m, S), fcr.direct0mm2(m, delta), 1.e-15)

    equal(fcr.direct0mm3(m, delta),
          fcr.ov0m(m, delta) * fcr.ov3m(m, delta), 1.e-15)

    equal(fcr.ov1mm2(m, delta),
          fcr.ov1m(m, delta) * fcr.ov2m(m, delta), 1.e-15)
    equal(fcr.direct1mm2(m, delta), fcr.ov1mm2(m, delta), 1.e-15)
Esempio n. 5
0
class ResonantRaman(Vibrations):
    """Class for calculating vibrational modes and
    resonant Raman intensities using finite difference.

    atoms:
        Atoms object
    Excitations:
        Class to calculate the excitations. The class object is
        initialized as::

            Excitations(atoms.get_calculator())

        or by reading form a file as::

            Excitations('filename', **exkwargs)

        The file is written by calling the method
        Excitations.write('filename').

        Excitations should work like a list of ex obejects, where:
            ex.get_dipole_me(form='v'):
                gives the dipole matrix element in |e| * Angstrom
            ex.energy:
                is the transition energy in Hartrees
    """
    def __init__(
        self,
        atoms,
        Excitations,
        indices=None,
        gsname='rraman',  # name for ground state calculations
        exname=None,  # name for excited state calculations
        delta=0.01,
        nfree=2,
        directions=None,
        approximation='Profeta',
        observation={'geometry': '-Z(XX)Z'},
        exkwargs={},  # kwargs to be passed to Excitations
        exext='.ex.gz',  # extension for Excitation names
        txt='-',
        verbose=False,
    ):
        assert (nfree == 2)
        Vibrations.__init__(self, atoms, indices, gsname, delta, nfree)
        self.name = gsname + '-d%.3f' % delta
        if exname is None:
            exname = gsname
        self.exname = exname + '-d%.3f' % delta
        self.exext = exext

        if directions is None:
            self.directions = np.array([0, 1, 2])
        else:
            self.directions = np.array(directions)

        self.approximation = approximation
        self.observation = observation
        self.exobj = Excitations
        self.exkwargs = exkwargs

        self.timer = Timer()
        self.txt = convert_string_to_fd(txt)

        self.verbose = verbose

    @staticmethod
    def m2(z):
        return (z * z.conj()).real

    def log(self, message, pre='# ', end='\n'):
        if self.verbose:
            self.txt.write(pre + message + end)
            self.txt.flush()

    def calculate(self, filename, fd):
        """Call ground and excited state calculation"""
        self.timer.start('Ground state')
        forces = self.atoms.get_forces()
        if rank == 0:
            pickle.dump(forces, fd, protocol=2)
            fd.close()
        self.timer.stop('Ground state')

        self.timer.start('Excitations')
        basename, _ = os.path.splitext(filename)
        excitations = self.exobj(self.atoms.get_calculator(), **self.exkwargs)
        excitations.write(basename + self.exext)
        self.timer.stop('Excitations')

    def read_excitations(self):
        self.timer.start('read excitations')
        self.timer.start('really read')
        self.log('reading ' + self.exname + '.eq' + self.exext)
        ex0_object = self.exobj(self.exname + '.eq' + self.exext,
                                **self.exkwargs)
        self.timer.stop('really read')
        self.timer.start('index')
        matching = frozenset(ex0_object)
        self.timer.stop('index')

        def append(lst, exname, matching):
            self.timer.start('really read')
            self.log('reading ' + exname, end=' ')
            exo = self.exobj(exname, **self.exkwargs)
            lst.append(exo)
            self.timer.stop('really read')
            self.timer.start('index')
            matching = matching.intersection(exo)
            self.log('len={0}, matching={1}'.format(len(exo), len(matching)),
                     pre='')
            self.timer.stop('index')
            return matching

        exm_object_list = []
        exp_object_list = []
        for a in self.indices:
            for i in 'xyz':
                name = '%s.%d%s' % (self.exname, a, i)
                matching = append(exm_object_list, name + '-' + self.exext,
                                  matching)
                matching = append(exp_object_list, name + '+' + self.exext,
                                  matching)
        self.ndof = 3 * len(self.indices)
        self.nex = len(matching)
        self.timer.stop('read excitations')

        self.timer.start('select')

        def select(exl, matching):
            mlst = [ex for ex in exl if ex in matching]
            assert (len(mlst) == len(matching))
            return mlst

        ex0 = select(ex0_object, matching)
        exm = []
        exp = []
        r = 0
        for a in self.indices:
            for i in 'xyz':
                exm.append(select(exm_object_list[r], matching))
                exp.append(select(exp_object_list[r], matching))
                r += 1
        self.timer.stop('select')

        self.timer.start('me and energy')

        eu = units.Hartree
        self.ex0E_p = np.array([ex.energy * eu for ex in ex0])
        self.ex0m_pc = np.array([ex.get_dipole_me(form='v') for ex in ex0])
        exmE_rp = []
        expE_rp = []
        exF_rp = []
        exmm_rpc = []
        expm_rpc = []
        r = 0
        for a in self.indices:
            for i in 'xyz':
                exmE_rp.append([em.energy for em in exm[r]])
                expE_rp.append([ep.energy for ep in exp[r]])
                exF_rp.append([(ep.energy - em.energy)
                               for ep, em in zip(exp[r], exm[r])])
                exmm_rpc.append([ex.get_dipole_me(form='v') for ex in exm[r]])
                expm_rpc.append([ex.get_dipole_me(form='v') for ex in exp[r]])
                r += 1
        self.exmE_rp = np.array(exmE_rp) * eu
        self.expE_rp = np.array(expE_rp) * eu
        self.exF_rp = np.array(exF_rp) * eu / 2 / self.delta
        self.exmm_rpc = np.array(exmm_rpc)
        self.expm_rpc = np.array(expm_rpc)

        self.timer.stop('me and energy')

    def read(self, method='standard', direction='central'):
        """Read data from a pre-performed calculation."""
        if not hasattr(self, 'modes'):
            self.timer.start('read vibrations')
            Vibrations.read(self, method, direction)
            # we now have:
            # self.H     : Hessian matrix
            # self.im    : 1./sqrt(masses)
            # self.modes : Eigenmodes of the mass weighted H
            self.om_r = self.hnu.real  # energies in eV
            self.timer.stop('read vibrations')
        if not hasattr(self, 'ex0E_p'):
            self.read_excitations()

    def get_Huang_Rhys_factors(self, forces_r):
        """Evaluate Huang-Rhys factors derived from forces."""
        self.timer.start('Huang-Rhys')
        assert (len(forces_r.flat) == self.ndof)

        # solve the matrix equation for the equilibrium displacements
        # XXX why are the forces mass weighted ???
        X_r = np.linalg.solve(self.im[:, None] * self.H * self.im,
                              forces_r.flat * self.im)
        d_r = np.dot(self.modes, X_r)

        # Huang-Rhys factors S
        s = 1.e-20 / units.kg / units.C / units._hbar**2  # SI units
        self.timer.stop('Huang-Rhys')
        return s * d_r**2 * self.om_r / 2.

    def get_matrix_element_AlbrechtA(self, omega, gamma=0.1, ml=range(16)):
        """Evaluate Albrecht A term.

        Unit: |e|^2Angstrom^2/eV
        """
        self.read()

        self.timer.start('AlbrechtA')

        if not hasattr(self, 'fco'):
            self.fco = FranckCondonOverlap()

        # excited state forces
        F_pr = self.exF_rp.T

        m_rcc = np.zeros((self.ndof, 3, 3), dtype=complex)
        for p, energy in enumerate(self.ex0E_p):
            S_r = self.get_Huang_Rhys_factors(F_pr[p])
            me_cc = np.outer(self.ex0m_pc[p], self.ex0m_pc[p].conj())

            for m in ml:
                self.timer.start('0mm1')
                fco_r = self.fco.direct0mm1(m, S_r)
                self.timer.stop('0mm1')
                self.timer.start('einsum')
                m_rcc += np.einsum(
                    'a,bc->abc',
                    fco_r / (energy + m * self.om_r - omega - 1j * gamma),
                    me_cc)
                m_rcc += np.einsum(
                    'a,bc->abc', fco_r /
                    (energy + (m - 1) * self.om_r + omega + 1j * gamma), me_cc)
                self.timer.stop('einsum')

        self.timer.stop('AlbrechtA')
        return m_rcc

    def get_matrix_element_AlbrechtBC(self,
                                      omega,
                                      gamma=0.1,
                                      ml=[1],
                                      term='BC'):
        """Evaluate Albrecht B and/or C term(s)."""
        self.read()

        self.timer.start('AlbrechtBC')

        if not hasattr(self, 'fco'):
            self.fco = FranckCondonOverlap()

        # excited state forces
        F_pr = self.exF_rp.T

        m_rcc = np.zeros((self.ndof, 3, 3), dtype=complex)
        for p, energy in enumerate(self.ex0E_p):
            S_r = self.get_Huang_Rhys_factors(F_pr[p])

            for m in ml:
                self.timer.start('Franck-Condon overlaps')
                fc1mm1_r = self.fco.direct(1, m, S_r)
                fc0mm02_r = self.fco.direct(0, m, S_r)
                fc0mm02_r += np.sqrt(2) * self.fco.direct0mm2(m, S_r)
                # XXXXX
                fc1mm1_r[-1] = 1
                fc0mm02_r[-1] = 1
                print(m, fc1mm1_r[-1], fc0mm02_r[-1])
                self.timer.stop('Franck-Condon overlaps')

                self.timer.start('me dervivatives')
                dm_rc = []
                r = 0
                for a in self.indices:
                    for i in 'xyz':
                        dm_rc.append(
                            (self.expm_rpc[r, p] - self.exmm_rpc[r, p]) *
                            self.im[r])
                        print('pm=', self.expm_rpc[r, p], self.exmm_rpc[r, p])
                        r += 1
                dm_rc = np.array(dm_rc) / (2 * self.delta)
                self.timer.stop('me dervivatives')

                self.timer.start('map to modes')
                # print('dm_rc[2], dm_rc[5]', dm_rc[2], dm_rc[5])
                print('dm_rc=', dm_rc)
                dm_rc = np.dot(dm_rc.T, self.modes.T).T
                print('dm_rc[-1][2]', dm_rc[-1][2])
                self.timer.stop('map to modes')

                self.timer.start('multiply')
                # me_cc = np.outer(self.ex0m_pc[p], self.ex0m_pc[p].conj())
                for r in range(self.ndof):
                    if 'B' in term:
                        # XXXX
                        denom = (1. / (energy + m * 0 * self.om_r[r] - omega -
                                       1j * gamma))
                        # ok print('denom=', denom)
                        m_rcc[r] += (
                            np.outer(dm_rc[r], self.ex0m_pc[p].conj()) *
                            fc1mm1_r[r] * denom)
                        if r == 5:
                            print('m_rcc[r]=', m_rcc[r][2, 2])
                        m_rcc[r] += (
                            np.outer(self.ex0m_pc[p], dm_rc[r].conj()) *
                            fc0mm02_r[r] * denom)
                    if 'C' in term:
                        denom = (1. /
                                 (energy +
                                  (m - 1) * self.om_r[r] + omega + 1j * gamma))
                        m_rcc[r] += (
                            np.outer(self.ex0m_pc[p], dm_rc[r].conj()) *
                            fc1mm1_r[r] * denom)
                        m_rcc[r] += (
                            np.outer(dm_rc[r], self.ex0m_pc[p].conj()) *
                            fc0mm02_r[r] * denom)
                self.timer.stop('multiply')
        print('m_rcc[-1]=', m_rcc[-1][2, 2])

        self.timer.start('pre_r')
        with np.errstate(divide='ignore'):
            pre_r = np.where(self.om_r > 0,
                             np.sqrt(units._hbar**2 / 2. / self.om_r), 0)
            # print('BC: pre_r=', pre_r)
        for r, p in enumerate(pre_r):
            m_rcc[r] *= p
        self.timer.stop('pre_r')
        self.timer.stop('AlbrechtBC')
        return m_rcc

    def get_matrix_element_Profeta(self,
                                   omega,
                                   gamma=0.1,
                                   energy_derivative=False):
        """Evaluate Albrecht B+C term in Profeta and Mauri approximation"""
        self.read()

        self.timer.start('amplitudes')

        self.timer.start('init')
        V_rcc = np.zeros((self.ndof, 3, 3), dtype=complex)
        pre = 1. / (2 * self.delta)
        self.timer.stop('init')

        def kappa(me_pc, e_p, omega, gamma, form='v'):
            """Kappa tensor after Profeta and Mauri
            PRB 63 (2001) 245415"""
            me_ccp = np.empty((3, 3, len(e_p)), dtype=complex)
            for p, me_c in enumerate(me_pc):
                me_ccp[:, :, p] = np.outer(me_pc[p], me_pc[p].conj())
                # print('kappa: me_ccp=', me_ccp[2,2,0])
                # ok print('kappa: den=', 1./(e_p - omega - 1j * gamma))
            kappa_ccp = (me_ccp / (e_p - omega - 1j * gamma) + me_ccp.conj() /
                         (e_p + omega + 1j * gamma))
            return kappa_ccp.sum(2)

        self.timer.start('kappa')
        r = 0
        for a in self.indices:
            for i in 'xyz':
                if not energy_derivative < 0:
                    V_rcc[r] = pre * self.im[r] * (
                        kappa(self.expm_rpc[r], self.ex0E_p, omega, gamma) -
                        kappa(self.exmm_rpc[r], self.ex0E_p, omega, gamma))
                if energy_derivative:
                    V_rcc[r] += pre * self.im[r] * (
                        kappa(self.ex0m_pc, self.expE_rp[r], omega, gamma) -
                        kappa(self.ex0m_pc, self.exmE_rp[r], omega, gamma))
                r += 1
        self.timer.stop('kappa')
        # print('V_rcc[2], V_rcc[5]=', V_rcc[2,2,2], V_rcc[5,2,2])

        self.timer.stop('amplitudes')

        # map to modes
        self.timer.start('pre_r')
        with np.errstate(divide='ignore'):
            pre_r = np.where(self.om_r > 0,
                             np.sqrt(units._hbar**2 / 2. / self.om_r), 0)
        V_rcc = np.dot(V_rcc.T, self.modes.T).T
        # looks ok        print('self.modes.T[-1]',self.modes.T)
        # looks ok       print('V_rcc[-1]=', V_rcc[-1][2,2])
        # ok       print('Profeta: pre_r=', pre_r)
        for r, p in enumerate(pre_r):
            V_rcc[r] *= p
        self.timer.stop('pre_r')
        return V_rcc

    def get_matrix_element(self, omega, gamma):
        self.read()
        V_rcc = np.zeros((self.ndof, 3, 3), dtype=complex)
        if self.approximation.lower() == 'profeta':
            V_rcc += self.get_matrix_element_Profeta(omega, gamma)
        elif self.approximation.lower() == 'placzek':
            V_rcc += self.get_matrix_element_Profeta(omega, gamma, True)
        elif self.approximation.lower() == 'p-p':
            V_rcc += self.get_matrix_element_Profeta(omega, gamma, -1)
        elif self.approximation.lower() == 'albrecht a':
            V_rcc += self.get_matrix_element_AlbrechtA(omega, gamma)
        elif self.approximation.lower() == 'albrecht b':
            raise NotImplementedError('not working')
            V_rcc += self.get_matrix_element_AlbrechtBC(omega, gamma, term='B')
        elif self.approximation.lower() == 'albrecht c':
            raise NotImplementedError('not working')
            V_rcc += self.get_matrix_element_AlbrechtBC(omega, gamma, term='C')
        elif self.approximation.lower() == 'albrecht bc':
            raise NotImplementedError('not working')
            V_rcc += self.get_matrix_element_AlbrechtBC(omega, gamma)
        elif self.approximation.lower() == 'albrecht':
            raise NotImplementedError('not working')
            V_rcc += self.get_matrix_element_AlbrechtA(omega, gamma)
            V_rcc += self.get_matrix_element_AlbrechtBC(omega, gamma)
        elif self.approximation.lower() == 'albrecht+profeta':
            V_rcc += self.get_matrix_element_AlbrechtA(omega, gamma)
            V_rcc += self.get_matrix_element_Profeta(omega, gamma)
        else:
            raise NotImplementedError(
                'Approximation {0} not implemented. '.format(
                    self.approximation) +
                'Please use "Profeta", "Albrecht A/B/C/BC", ' +
                'or "Albrecht".')

        return V_rcc

    def get_intensities(self, omega, gamma=0.1):
        m2 = ResonantRaman.m2
        alpha_rcc = self.get_matrix_element(omega, gamma)
        if not self.observation:  # XXXX remove
            """Simple sum, maybe too simple"""
            return m2(alpha_rcc).sum(axis=1).sum(axis=1)
        # XXX enable when appropraiate
        #        if self.observation['orientation'].lower() != 'random':
        #            raise NotImplementedError('not yet')

        # random orientation of the molecular frame
        # Woodward & Long,
        # Guthmuller, J. J. Chem. Phys. 2016, 144 (6), 64106
        m2 = ResonantRaman.m2
        alpha2_r = m2(alpha_rcc[:, 0, 0] + alpha_rcc[:, 1, 1] +
                      alpha_rcc[:, 2, 2]) / 9.
        delta2_r = 3 / 4. * (m2(alpha_rcc[:, 0, 1] - alpha_rcc[:, 1, 0]) +
                             m2(alpha_rcc[:, 0, 2] - alpha_rcc[:, 2, 0]) +
                             m2(alpha_rcc[:, 1, 2] - alpha_rcc[:, 2, 1]))
        gamma2_r = (3 / 4. * (m2(alpha_rcc[:, 0, 1] + alpha_rcc[:, 1, 0]) +
                              m2(alpha_rcc[:, 0, 2] + alpha_rcc[:, 2, 0]) +
                              m2(alpha_rcc[:, 1, 2] + alpha_rcc[:, 2, 1])) +
                    (m2(alpha_rcc[:, 0, 0] - alpha_rcc[:, 1, 1]) +
                     m2(alpha_rcc[:, 0, 0] - alpha_rcc[:, 2, 2]) +
                     m2(alpha_rcc[:, 1, 1] - alpha_rcc[:, 2, 2])) / 2)

        if self.observation['geometry'] == '-Z(XX)Z':  # Porto's notation
            return (45 * alpha2_r + 5 * delta2_r + 4 * gamma2_r) / 45.
        elif self.observation['geometry'] == '-Z(XY)Z':  # Porto's notation
            return gamma2_r / 15.
        elif self.observation['scattered'] == 'Z':
            # scattered light in direction of incoming light
            return (45 * alpha2_r + 5 * delta2_r + 7 * gamma2_r) / 45.
        elif self.observation['scattered'] == 'parallel':
            # scattered light perendicular and
            # polarization in plane
            return 6 * gamma2_r / 45.
        elif self.observation['scattered'] == 'perpendicular':
            # scattered light perendicular and
            # polarization out of plane
            return (45 * alpha2_r + 5 * delta2_r + 7 * gamma2_r) / 45.
        else:
            raise NotImplementedError

    def get_cross_sections(self, omega, gamma=0.1):
        I_r = self.get_intensities(omega, gamma)
        pre = 1. / 16 / np.pi**2 / units.eps0**2 / units.c**4
        # frequency of scattered light
        omS_r = omega - self.hnu
        return pre * omega * omS_r**3 * I_r

    def get_spectrum(self,
                     omega,
                     gamma=0.1,
                     start=200.0,
                     end=4000.0,
                     npts=None,
                     width=4.0,
                     type='Gaussian',
                     method='standard',
                     direction='central',
                     intensity_unit='????',
                     normalize=False):
        """Get resonant Raman spectrum.

        The method returns wavenumbers in cm^-1 with corresponding
        Raman cross section.
        Start and end point, and width of the Gaussian/Lorentzian should
        be given in cm^-1.
        """

        self.type = type.lower()
        assert self.type in ['gaussian', 'lorentzian']

        if not npts:
            npts = int((end - start) / width * 10 + 1)
        frequencies = self.get_frequencies(method, direction).real
        intensities = self.get_cross_sections(omega, gamma)
        prefactor = 1
        if type == 'lorentzian':
            intensities = intensities * width * np.pi / 2.
            if normalize:
                prefactor = 2. / width / np.pi
        else:
            sigma = width / 2. / np.sqrt(2. * np.log(2.))
            if normalize:
                prefactor = 1. / sigma / np.sqrt(2 * np.pi)
        # Make array with spectrum data
        spectrum = np.empty(npts)
        energies = np.linspace(start, end, npts)
        for i, energy in enumerate(energies):
            energies[i] = energy
            if type == 'lorentzian':
                spectrum[i] = (intensities * 0.5 * width / np.pi / (
                    (frequencies - energy)**2 + 0.25 * width**2)).sum()
            else:
                spectrum[i] = (
                    intensities *
                    np.exp(-(frequencies - energy)**2 / 2. / sigma**2)).sum()
        return [energies, prefactor * spectrum]

    def write_spectrum(self,
                       omega,
                       gamma,
                       out='resonant-raman-spectra.dat',
                       start=200,
                       end=4000,
                       npts=None,
                       width=10,
                       type='Gaussian',
                       method='standard',
                       direction='central'):
        """Write out spectrum to file.

        First column is the wavenumber in cm^-1, the second column the
        absolute infrared intensities, and
        the third column the absorbance scaled so that data runs
        from 1 to 0. Start and end
        point, and width of the Gaussian/Lorentzian should be given
        in cm^-1."""
        energies, spectrum = self.get_spectrum(omega, gamma, start, end, npts,
                                               width, type, method, direction)

        # Write out spectrum in file. First column is absolute intensities.
        outdata = np.empty([len(energies), 3])
        outdata.T[0] = energies
        outdata.T[1] = spectrum
        fd = open(out, 'w')
        fd.write('# Resonant Raman spectrum\n')
        fd.write('# omega={0:g} eV, gamma={1:g} eV\n'.format(omega, gamma))
        fd.write('# %s folded, width=%g cm^-1\n' % (type.title(), width))
        fd.write('# [cm^-1]  [a.u.]\n')

        for row in outdata:
            fd.write('%.3f  %15.5g\n' % (row[0], row[1]))
        fd.close()

    def summary(self,
                omega,
                gamma=0.1,
                method='standard',
                direction='central',
                log=sys.stdout):
        """Print summary for given omega [eV]"""
        hnu = self.get_energies(method, direction)
        s = 0.01 * units._e / units._c / units._hplanck
        intensities = self.get_intensities(omega, gamma)

        if isinstance(log, basestring):
            log = paropen(log, 'a')

        parprint('-------------------------------------', file=log)
        parprint(' excitation at ' + str(omega) + ' eV', file=log)
        parprint(' gamma ' + str(gamma) + ' eV', file=log)
        parprint(' approximation:', self.approximation, file=log)
        parprint(' observation:', self.observation, '\n', file=log)
        parprint(' Mode    Frequency        Intensity', file=log)
        parprint('  #    meV     cm^-1      [e^4A^4/eV^2]', file=log)
        parprint('-------------------------------------', file=log)
        for n, e in enumerate(hnu):
            if e.imag != 0:
                c = 'i'
                e = e.imag
            else:
                c = ' '
                e = e.real
            parprint('%3d %6.1f%s  %7.1f%s  %9.3g' %
                     (n, 1000 * e, c, s * e, c, intensities[n]),
                     file=log)
        parprint('-------------------------------------', file=log)
        parprint('Zero-point energy: %.3f eV' % self.get_zero_point_energy(),
                 file=log)

    def __del__(self):
        self.timer.write(self.txt)
Esempio n. 6
0
class ResonantRaman(Vibrations):
    """Class for calculating vibrational modes and
    resonant Raman intensities using finite difference.

    atoms:
        Atoms object
    Excitations:
        Class to calculate the excitations. The class object is
        initialized as::

            Excitations(atoms.get_calculator())

        or by reading form a file as::

            Excitations('filename', **exkwargs)

        The file is written by calling the method
        Excitations.write('filename').

        Excitations should work like a list of ex obejects, where:
            ex.get_dipole_me(form='v'):
                gives the dipole matrix element in |e| * Angstrom
            ex.energy:
                is the transition energy in Hartrees
    """
    def __init__(self, atoms, Excitations,
                 indices=None,
                 gsname='rraman',  # name for ground state calculations
                 exname=None,      # name for excited state calculations
                 delta=0.01,
                 nfree=2,
                 directions=None,
                 approximation='Profeta',
                 observation={'geometry': '-Z(XX)Z'},
                 exkwargs={},      # kwargs to be passed to Excitations
                 exext='.ex.gz',   # extension for Excitation names
                 txt='-',
                 verbose=False,):
        assert(nfree == 2)
        Vibrations.__init__(self, atoms, indices, gsname, delta, nfree)
        self.name = gsname + '-d%.3f' % delta
        if exname is None:
            exname = gsname
        self.exname = exname + '-d%.3f' % delta
        self.exext = exext

        if directions is None:
            self.directions = np.array([0, 1, 2])
        else:
            self.directions = np.array(directions)

        self.approximation = approximation
        self.observation = observation
        self.exobj = Excitations
        self.exkwargs = exkwargs

        self.timer = Timer()
        self.txt = convert_string_to_fd(txt)

        self.verbose = verbose

    @staticmethod
    def m2(z):
        return (z * z.conj()).real

    def log(self, message, pre='# ', end='\n'):
        if self.verbose:
            self.txt.write(pre + message + end)
            self.txt.flush()

    def calculate(self, filename, fd):
        """Call ground and excited state calculation"""
        self.timer.start('Ground state')
        forces = self.atoms.get_forces()
        if rank == 0:
            pickle.dump(forces, fd, protocol=2)
            fd.close()
        self.timer.stop('Ground state')

        self.timer.start('Excitations')
        basename, _ = os.path.splitext(filename)
        excitations = self.exobj(
            self.atoms.get_calculator(), **self.exkwargs)
        excitations.write(basename + self.exext)
        self.timer.stop('Excitations')

    def read_excitations(self):
        self.timer.start('read excitations')
        self.timer.start('really read')
        self.log('reading ' + self.exname + '.eq' + self.exext)
        ex0_object = self.exobj(self.exname + '.eq' + self.exext,
                                **self.exkwargs)
        self.timer.stop('really read')
        self.timer.start('index')
        matching = frozenset(ex0_object)
        self.timer.stop('index')

        def append(lst, exname, matching):
            self.timer.start('really read')
            self.log('reading ' + exname, end=' ')
            exo = self.exobj(exname, **self.exkwargs)
            lst.append(exo)
            self.timer.stop('really read')
            self.timer.start('index')
            matching = matching.intersection(exo)
            self.log('len={0}, matching={1}'.format(len(exo),
                                                    len(matching)), pre='')
            self.timer.stop('index')
            return matching

        exm_object_list = []
        exp_object_list = []
        for a in self.indices:
            for i in 'xyz':
                name = '%s.%d%s' % (self.exname, a, i)
                matching = append(exm_object_list,
                                  name + '-' + self.exext, matching)
                matching = append(exp_object_list,
                                  name + '+' + self.exext, matching)
        self.ndof = 3 * len(self.indices)
        self.nex = len(matching)
        self.timer.stop('read excitations')

        self.timer.start('select')

        def select(exl, matching):
            mlst = [ex for ex in exl if ex in matching]
            assert(len(mlst) == len(matching))
            return mlst
        ex0 = select(ex0_object, matching)
        exm = []
        exp = []
        r = 0
        for a in self.indices:
            for i in 'xyz':
                exm.append(select(exm_object_list[r], matching))
                exp.append(select(exp_object_list[r], matching))
                r += 1
        self.timer.stop('select')

        self.timer.start('me and energy')

        eu = units.Hartree
        self.ex0E_p = np.array([ex.energy * eu for ex in ex0])
        self.ex0m_pc = np.array(
            [ex.get_dipole_me(form='v') for ex in ex0])
        exmE_rp = []
        expE_rp = []
        exF_rp = []
        exmm_rpc = []
        expm_rpc = []
        r = 0
        for a in self.indices:
            for i in 'xyz':
                exmE_rp.append([em.energy for em in exm[r]])
                expE_rp.append([ep.energy for ep in exp[r]])
                exF_rp.append(
                    [(ep.energy - em.energy)
                     for ep, em in zip(exp[r], exm[r])])
                exmm_rpc.append(
                    [ex.get_dipole_me(form='v') for ex in exm[r]])
                expm_rpc.append(
                    [ex.get_dipole_me(form='v') for ex in exp[r]])
                r += 1
        self.exmE_rp = np.array(exmE_rp) * eu
        self.expE_rp = np.array(expE_rp) * eu
        self.exF_rp = np.array(exF_rp) * eu / 2 / self.delta
        self.exmm_rpc = np.array(exmm_rpc)
        self.expm_rpc = np.array(expm_rpc)

        self.timer.stop('me and energy')

    def read(self, method='standard', direction='central'):
        """Read data from a pre-performed calculation."""
        if not hasattr(self, 'modes'):
            self.timer.start('read vibrations')
            Vibrations.read(self, method, direction)
            # we now have:
            # self.H     : Hessian matrix
            # self.im    : 1./sqrt(masses)
            # self.modes : Eigenmodes of the mass weighted H
            self.om_r = self.hnu.real    # energies in eV
            self.timer.stop('read vibrations')
        if not hasattr(self, 'ex0E_p'):
            self.read_excitations()

    def get_Huang_Rhys_factors(self, forces_r):
        """Evaluate Huang-Rhys factors derived from forces."""
        self.timer.start('Huang-Rhys')
        assert(len(forces_r.flat) == self.ndof)

        # solve the matrix equation for the equilibrium displacements
        # XXX why are the forces mass weighted ???
        X_r = np.linalg.solve(self.im[:, None] * self.H * self.im,
                              forces_r.flat * self.im)
        d_r = np.dot(self.modes, X_r)

        # Huang-Rhys factors S
        s = 1.e-20 / units.kg / units.C / units._hbar**2  # SI units
        self.timer.stop('Huang-Rhys')
        return s * d_r**2 * self.om_r / 2.

    def get_matrix_element_AlbrechtA(self, omega, gamma=0.1, ml=range(16)):
        """Evaluate Albrecht A term.

        Unit: |e|^2Angstrom^2/eV
        """
        self.read()

        self.timer.start('AlbrechtA')

        if not hasattr(self, 'fco'):
            self.fco = FranckCondonOverlap()

        # excited state forces
        F_pr = self.exF_rp.T

        m_rcc = np.zeros((self.ndof, 3, 3), dtype=complex)
        for p, energy in enumerate(self.ex0E_p):
            S_r = self.get_Huang_Rhys_factors(F_pr[p])
            me_cc = np.outer(self.ex0m_pc[p], self.ex0m_pc[p].conj())

            for m in ml:
                self.timer.start('0mm1')
                fco_r = self.fco.direct0mm1(m, S_r)
                self.timer.stop('0mm1')
                self.timer.start('einsum')
                m_rcc += np.einsum('a,bc->abc',
                                   fco_r / (energy + m * self.om_r - omega -
                                            1j * gamma),
                                   me_cc)
                m_rcc += np.einsum('a,bc->abc',
                                   fco_r / (energy + (m - 1) * self.om_r +
                                            omega + 1j * gamma),
                                   me_cc)
                self.timer.stop('einsum')

        self.timer.stop('AlbrechtA')
        return m_rcc

    def get_matrix_element_AlbrechtBC(self, omega, gamma=0.1, ml=[1],
                                      term='BC'):
        """Evaluate Albrecht B and/or C term(s)."""
        self.read()

        self.timer.start('AlbrechtBC')

        if not hasattr(self, 'fco'):
            self.fco = FranckCondonOverlap()

        # excited state forces
        F_pr = self.exF_rp.T

        m_rcc = np.zeros((self.ndof, 3, 3), dtype=complex)
        for p, energy in enumerate(self.ex0E_p):
            S_r = self.get_Huang_Rhys_factors(F_pr[p])

            for m in ml:
                self.timer.start('Franck-Condon overlaps')
                fc1mm1_r = self.fco.direct(1, m, S_r)
                fc0mm02_r = self.fco.direct(0, m, S_r)
                fc0mm02_r += np.sqrt(2) * self.fco.direct0mm2(m, S_r)
                # XXXXX
                fc1mm1_r[-1] = 1
                fc0mm02_r[-1] = 1
                print(m, fc1mm1_r[-1], fc0mm02_r[-1])
                self.timer.stop('Franck-Condon overlaps')

                self.timer.start('me dervivatives')
                dm_rc = []
                r = 0
                for a in self.indices:
                    for i in 'xyz':
                        dm_rc.append(
                            (self.expm_rpc[r, p] - self.exmm_rpc[r, p]) *
                            self.im[r])
                        print('pm=', self.expm_rpc[r, p], self.exmm_rpc[r, p])
                        r += 1
                dm_rc = np.array(dm_rc) / (2 * self.delta)
                self.timer.stop('me dervivatives')

                self.timer.start('map to modes')
                # print('dm_rc[2], dm_rc[5]', dm_rc[2], dm_rc[5])
                print('dm_rc=', dm_rc)
                dm_rc = np.dot(dm_rc.T, self.modes.T).T
                print('dm_rc[-1][2]', dm_rc[-1][2])
                self.timer.stop('map to modes')

                self.timer.start('multiply')
                # me_cc = np.outer(self.ex0m_pc[p], self.ex0m_pc[p].conj())
                for r in range(self.ndof):
                    if 'B' in term:
                        # XXXX
                        denom = (1. /
                                 (energy + m * 0 * self.om_r[r] -
                                  omega - 1j * gamma))
                        # ok print('denom=', denom)
                        m_rcc[r] += (np.outer(dm_rc[r],
                                              self.ex0m_pc[p].conj()) *
                                     fc1mm1_r[r] * denom)
                        if r == 5:
                            print('m_rcc[r]=', m_rcc[r][2, 2])
                        m_rcc[r] += (np.outer(self.ex0m_pc[p],
                                              dm_rc[r].conj()) *
                                     fc0mm02_r[r] * denom)
                    if 'C' in term:
                        denom = (1. /
                                 (energy + (m - 1) * self.om_r[r] +
                                  omega + 1j * gamma))
                        m_rcc[r] += (np.outer(self.ex0m_pc[p],
                                              dm_rc[r].conj()) *
                                     fc1mm1_r[r] * denom)
                        m_rcc[r] += (np.outer(dm_rc[r],
                                              self.ex0m_pc[p].conj()) *
                                     fc0mm02_r[r] * denom)
                self.timer.stop('multiply')
        print('m_rcc[-1]=', m_rcc[-1][2, 2])

        self.timer.start('pre_r')
        with np.errstate(divide='ignore'):
            pre_r = np.where(self.om_r > 0,
                             np.sqrt(units._hbar**2 / 2. / self.om_r), 0)
            # print('BC: pre_r=', pre_r)
        for r, p in enumerate(pre_r):
            m_rcc[r] *= p
        self.timer.stop('pre_r')
        self.timer.stop('AlbrechtBC')
        return m_rcc

    def get_matrix_element_Profeta(self, omega, gamma=0.1,
                                   energy_derivative=False):
        """Evaluate Albrecht B+C term in Profeta and Mauri approximation"""
        self.read()

        self.timer.start('amplitudes')

        self.timer.start('init')
        V_rcc = np.zeros((self.ndof, 3, 3), dtype=complex)
        pre = 1. / (2 * self.delta)
        self.timer.stop('init')

        def kappa(me_pc, e_p, omega, gamma, form='v'):
            """Kappa tensor after Profeta and Mauri
            PRB 63 (2001) 245415"""
            me_ccp = np.empty((3, 3, len(e_p)), dtype=complex)
            for p, me_c in enumerate(me_pc):
                me_ccp[:, :, p] = np.outer(me_pc[p], me_pc[p].conj())
                # print('kappa: me_ccp=', me_ccp[2,2,0])
                # ok print('kappa: den=', 1./(e_p - omega - 1j * gamma))
            kappa_ccp = (me_ccp / (e_p - omega - 1j * gamma) +
                         me_ccp.conj() / (e_p + omega + 1j * gamma))
            return kappa_ccp.sum(2)

        self.timer.start('kappa')
        r = 0
        for a in self.indices:
            for i in 'xyz':
                if not energy_derivative < 0:
                    V_rcc[r] = pre * self.im[r] * (
                        kappa(self.expm_rpc[r], self.ex0E_p, omega, gamma) -
                        kappa(self.exmm_rpc[r], self.ex0E_p, omega, gamma))
                if energy_derivative:
                    V_rcc[r] += pre * self.im[r] * (
                        kappa(self.ex0m_pc, self.expE_rp[r], omega, gamma) -
                        kappa(self.ex0m_pc, self.exmE_rp[r], omega, gamma))
                r += 1
        self.timer.stop('kappa')
        # print('V_rcc[2], V_rcc[5]=', V_rcc[2,2,2], V_rcc[5,2,2])

        self.timer.stop('amplitudes')

        # map to modes
        self.timer.start('pre_r')
        with np.errstate(divide='ignore'):
            pre_r = np.where(self.om_r > 0,
                             np.sqrt(units._hbar**2 / 2. / self.om_r), 0)
        V_rcc = np.dot(V_rcc.T, self.modes.T).T
        # looks ok        print('self.modes.T[-1]',self.modes.T)
        # looks ok       print('V_rcc[-1]=', V_rcc[-1][2,2])
        # ok       print('Profeta: pre_r=', pre_r)
        for r, p in enumerate(pre_r):
            V_rcc[r] *= p
        self.timer.stop('pre_r')
        return V_rcc

    def get_matrix_element(self, omega, gamma):
        self.read()
        V_rcc = np.zeros((self.ndof, 3, 3), dtype=complex)
        if self.approximation.lower() == 'profeta':
            V_rcc += self.get_matrix_element_Profeta(omega, gamma)
        elif self.approximation.lower() == 'placzek':
            V_rcc += self.get_matrix_element_Profeta(omega, gamma, True)
        elif self.approximation.lower() == 'p-p':
            V_rcc += self.get_matrix_element_Profeta(omega, gamma, -1)
        elif self.approximation.lower() == 'albrecht a':
            V_rcc += self.get_matrix_element_AlbrechtA(omega, gamma)
        elif self.approximation.lower() == 'albrecht b':
            raise NotImplementedError('not working')
            V_rcc += self.get_matrix_element_AlbrechtBC(omega, gamma, term='B')
        elif self.approximation.lower() == 'albrecht c':
            raise NotImplementedError('not working')
            V_rcc += self.get_matrix_element_AlbrechtBC(omega, gamma, term='C')
        elif self.approximation.lower() == 'albrecht bc':
            raise NotImplementedError('not working')
            V_rcc += self.get_matrix_element_AlbrechtBC(omega, gamma)
        elif self.approximation.lower() == 'albrecht':
            raise NotImplementedError('not working')
            V_rcc += self.get_matrix_element_AlbrechtA(omega, gamma)
            V_rcc += self.get_matrix_element_AlbrechtBC(omega, gamma)
        elif self.approximation.lower() == 'albrecht+profeta':
            V_rcc += self.get_matrix_element_AlbrechtA(omega, gamma)
            V_rcc += self.get_matrix_element_Profeta(omega, gamma)
        else:
            raise NotImplementedError(
                'Approximation {0} not implemented. '.format(
                    self.approximation) +
                'Please use "Profeta", "Albrecht A/B/C/BC", ' +
                'or "Albrecht".')

        return V_rcc

    def get_intensities(self, omega, gamma=0.1):
        m2 = ResonantRaman.m2
        alpha_rcc = self.get_matrix_element(omega, gamma)
        if not self.observation:  # XXXX remove
            """Simple sum, maybe too simple"""
            return m2(alpha_rcc).sum(axis=1).sum(axis=1)
        # XXX enable when appropraiate
        #        if self.observation['orientation'].lower() != 'random':
        #            raise NotImplementedError('not yet')

        # random orientation of the molecular frame
        # Woodward & Long,
        # Guthmuller, J. J. Chem. Phys. 2016, 144 (6), 64106
        m2 = ResonantRaman.m2
        alpha2_r = m2(alpha_rcc[:, 0, 0] + alpha_rcc[:, 1, 1] +
                      alpha_rcc[:, 2, 2]) / 9.
        delta2_r = 3 / 4. * (
            m2(alpha_rcc[:, 0, 1] - alpha_rcc[:, 1, 0]) +
            m2(alpha_rcc[:, 0, 2] - alpha_rcc[:, 2, 0]) +
            m2(alpha_rcc[:, 1, 2] - alpha_rcc[:, 2, 1]))
        gamma2_r = (3 / 4. * (m2(alpha_rcc[:, 0, 1] + alpha_rcc[:, 1, 0]) +
                              m2(alpha_rcc[:, 0, 2] + alpha_rcc[:, 2, 0]) +
                              m2(alpha_rcc[:, 1, 2] + alpha_rcc[:, 2, 1])) +
                    (m2(alpha_rcc[:, 0, 0] - alpha_rcc[:, 1, 1]) +
                     m2(alpha_rcc[:, 0, 0] - alpha_rcc[:, 2, 2]) +
                     m2(alpha_rcc[:, 1, 1] - alpha_rcc[:, 2, 2])) / 2)

        if self.observation['geometry'] == '-Z(XX)Z':  # Porto's notation
            return (45 * alpha2_r + 5 * delta2_r + 4 * gamma2_r) / 45.
        elif self.observation['geometry'] == '-Z(XY)Z':  # Porto's notation
            return gamma2_r / 15.
        elif self.observation['scattered'] == 'Z':
            # scattered light in direction of incoming light
            return (45 * alpha2_r + 5 * delta2_r + 7 * gamma2_r) / 45.
        elif self.observation['scattered'] == 'parallel':
            # scattered light perendicular and
            # polarization in plane
            return 6 * gamma2_r / 45.
        elif self.observation['scattered'] == 'perpendicular':
            # scattered light perendicular and
            # polarization out of plane
            return (45 * alpha2_r + 5 * delta2_r + 7 * gamma2_r) / 45.
        else:
            raise NotImplementedError

    def get_cross_sections(self, omega, gamma=0.1):
        I_r = self.get_intensities(omega, gamma)
        pre = 1. / 16 / np.pi**2 / units.eps0**2 / units.c**4
        # frequency of scattered light
        omS_r = omega - self.hnu
        return pre * omega * omS_r**3 * I_r

    def get_spectrum(self, omega, gamma=0.1,
                     start=200.0, end=4000.0, npts=None, width=4.0,
                     type='Gaussian', method='standard', direction='central',
                     intensity_unit='????', normalize=False):
        """Get resonant Raman spectrum.

        The method returns wavenumbers in cm^-1 with corresponding
        Raman cross section.
        Start and end point, and width of the Gaussian/Lorentzian should
        be given in cm^-1.
        """

        self.type = type.lower()
        assert self.type in ['gaussian', 'lorentzian']

        if not npts:
            npts = int((end - start) / width * 10 + 1)
        frequencies = self.get_frequencies(method, direction).real
        intensities = self.get_cross_sections(omega, gamma)
        prefactor = 1
        if type == 'lorentzian':
            intensities = intensities * width * np.pi / 2.
            if normalize:
                prefactor = 2. / width / np.pi
        else:
            sigma = width / 2. / np.sqrt(2. * np.log(2.))
            if normalize:
                prefactor = 1. / sigma / np.sqrt(2 * np.pi)
        # Make array with spectrum data
        spectrum = np.empty(npts)
        energies = np.linspace(start, end, npts)
        for i, energy in enumerate(energies):
            energies[i] = energy
            if type == 'lorentzian':
                spectrum[i] = (intensities * 0.5 * width / np.pi /
                               ((frequencies - energy)**2 +
                                0.25 * width**2)).sum()
            else:
                spectrum[i] = (intensities *
                               np.exp(-(frequencies - energy)**2 /
                                      2. / sigma**2)).sum()
        return [energies, prefactor * spectrum]

    def write_spectrum(self, omega, gamma,
                       out='resonant-raman-spectra.dat',
                       start=200, end=4000,
                       npts=None, width=10,
                       type='Gaussian', method='standard',
                       direction='central'):
        """Write out spectrum to file.

        First column is the wavenumber in cm^-1, the second column the
        absolute infrared intensities, and
        the third column the absorbance scaled so that data runs
        from 1 to 0. Start and end
        point, and width of the Gaussian/Lorentzian should be given
        in cm^-1."""
        energies, spectrum = self.get_spectrum(omega, gamma,
                                               start, end, npts, width,
                                               type, method, direction)

        # Write out spectrum in file. First column is absolute intensities.
        outdata = np.empty([len(energies), 3])
        outdata.T[0] = energies
        outdata.T[1] = spectrum
        fd = open(out, 'w')
        fd.write('# Resonant Raman spectrum\n')
        fd.write('# omega={0:g} eV, gamma={1:g} eV\n'.format(omega, gamma))
        fd.write('# %s folded, width=%g cm^-1\n' % (type.title(), width))
        fd.write('# [cm^-1]  [a.u.]\n')

        for row in outdata:
            fd.write('%.3f  %15.5g\n' %
                     (row[0], row[1]))
        fd.close()

    def summary(self, omega, gamma=0.1,
                method='standard', direction='central',
                log=sys.stdout):
        """Print summary for given omega [eV]"""
        hnu = self.get_energies(method, direction)
        s = 0.01 * units._e / units._c / units._hplanck
        intensities = self.get_intensities(omega, gamma)

        if isinstance(log, basestring):
            log = paropen(log, 'a')

        parprint('-------------------------------------', file=log)
        parprint(' excitation at ' + str(omega) + ' eV', file=log)
        parprint(' gamma ' + str(gamma) + ' eV', file=log)
        parprint(' approximation:', self.approximation, file=log)
        parprint(' observation:', self.observation, '\n', file=log)
        parprint(' Mode    Frequency        Intensity', file=log)
        parprint('  #    meV     cm^-1      [e^4A^4/eV^2]', file=log)
        parprint('-------------------------------------', file=log)
        for n, e in enumerate(hnu):
            if e.imag != 0:
                c = 'i'
                e = e.imag
            else:
                c = ' '
                e = e.real
            parprint('%3d %6.1f%s  %7.1f%s  %9.3g' %
                     (n, 1000 * e, c, s * e, c, intensities[n]),
                     file=log)
        parprint('-------------------------------------', file=log)
        parprint('Zero-point energy: %.3f eV' % self.get_zero_point_energy(),
                 file=log)

    def __del__(self):
        self.timer.write(self.txt)
Esempio n. 7
0
# FCOverlap

fco = FranckCondonOverlap()

# check factorial
assert (fco.factorial(8) == factorial(8))
# the second test is useful according to the implementation
assert (fco.factorial(5) == factorial(5))

# check T=0 and n=0 equality
S = np.array([1, 2.1, 34])
m = 5
assert (((fco.directT0(m, S) - fco.direct(0, m, S)) / fco.directT0(m, S) <
         1e-15).all())

# check symmetry
S = 2
n = 3
assert (fco.direct(n, m, S) == fco.direct(m, n, S))

# ---------------------------
# specials
S = 1.5
for m in [2, 7]:
    equal(
        fco.direct0mm1(m, S)**2,
        fco.direct(1, m, S) * fco.direct(m, 0, S), 1.e-17)
    equal(
        fco.direct0mm2(m, S)**2,
        fco.direct(2, m, S) * fco.direct(m, 0, S), 1.e-17)
Esempio n. 8
0
            sys.stderr.write('WARNING: %s\n' % msg)

# FCOverlap

fco = FranckCondonOverlap()

# check factorial
assert(fco.factorial(8) == factorial(8))
# the second test is useful according to the implementation
assert(fco.factorial(5) == factorial(5))

# check T=0 and n=0 equality
S = np.array([1, 2.1, 34])
m = 5
assert(((fco.directT0(m, S) - fco.direct(0, m, S)) / fco.directT0(m, S) <
        1e-15).all())

# check symmetry
S = 2
n = 3
assert(fco.direct(n, m, S) == fco.direct(m, n, S))

# ---------------------------
# specials
S = 1.5
for m in [2, 7]:
    equal(fco.direct0mm1(m, S)**2,
          fco.direct(1, m, S) * fco.direct(m, 0, S), 1.e-17)
    equal(fco.direct0mm2(m, S)**2,
          fco.direct(2, m, S) * fco.direct(m, 0, S), 1.e-17)