Ejemplo n.º 1
0
def forster_coupling_extended(initial,
                              final,
                              ref_index=1,
                              transitions=(),
                              longitude=3,
                              n_divisions=300):
    """
    Compute Forster coupling in eV

    :param initial: initial states
    :param final: final states
    :param longitude: extension length of the dipole
    :param n_divisions: number of subdivisions. To use with longitude. Increase until convergence.
    :return: Forster coupling
    """

    d_transition = Transition(initial[0], final[1])
    a_transition = Transition(initial[1], final[0])

    d_orientation = initial[0].get_center().molecular_orientation()
    a_orientation = initial[1].get_center().molecular_orientation()

    r_vector = initial[0].get_coordinates_absolute(
    ) - final[0].get_coordinates_absolute()

    hash_string = generate_hash_2(
        inspect.currentframe().f_code.co_name, d_transition, a_transition,
        d_orientation, a_orientation, r_vector,
        [ref_index, tuple(transitions), longitude, n_divisions])

    if hash_string in coupling_data:
        return coupling_data[hash_string]

    mu_a = transitions[transitions.index(a_transition)].tdm
    mu_d = transitions[transitions.index(d_transition)].tdm

    mu_d = rotate_vector(mu_d, d_orientation) * ATOMIC_TO_ANGS_EL
    mu_a = rotate_vector(mu_a, a_orientation) * ATOMIC_TO_ANGS_EL

    # mu_d = donor.get_transition_moment(to_state=_GS_)            # transition dipole moment (donor) e*angs
    # mu_a = acceptor.get_transition_moment(to_state=donor.state)  # transition dipole moment (acceptor) e*angs

    #r_vector = intermolecular_vector(donor, acceptor, supercell, cell_increment)  # position vector between donor and acceptor

    coupling_data[hash_string] = forster.dipole_extended(
        r_vector,
        mu_a,
        mu_d,
        n=ref_index,
        longitude=longitude,
        n_divisions=n_divisions)

    return coupling_data[hash_string]
Ejemplo n.º 2
0
def forster_coupling_py(initial, final, ref_index=1, transitions=()):
    """
    Compute Forster coupling in eV

    :param initial: initial states
    :param final: final states
    :return: Forster coupling
    """

    d_transition = Transition(initial[0], final[1])
    a_transition = Transition(initial[1], final[0])

    d_orientation = initial[0].get_center().molecular_orientation()
    a_orientation = initial[1].get_center().molecular_orientation()

    # r_vector = initial[1].get_coordinates_absolute() - final[1].get_coordinates_absolute()
    r_vector = initial[0].get_coordinates_absolute(
    ) - final[0].get_coordinates_absolute()

    hash_string = generate_hash_2(inspect.currentframe().f_code.co_name,
                                  d_transition, a_transition, d_orientation,
                                  a_orientation, r_vector,
                                  [ref_index, tuple(transitions)])

    if hash_string in coupling_data:
        return coupling_data[hash_string]

    try:
        mu_a = transitions[transitions.index(a_transition)].tdm
        mu_d = transitions[transitions.index(d_transition)].tdm
    except ValueError:
        raise Exception('TDM for {} / {} not defined'.format(
            a_transition, d_transition))

    mu_d = rotate_vector(mu_d, d_orientation) * ATOMIC_TO_ANGS_EL
    mu_a = rotate_vector(mu_a, a_orientation) * ATOMIC_TO_ANGS_EL

    distance = np.linalg.norm(r_vector)

    k = orientation_factor(mu_d, mu_a,
                           r_vector)  # orientation factor between molecules

    k_e = 1.0 / (4.0 * np.pi * VAC_PERMITTIVITY)

    coupling_data[hash_string] = k_e * k * np.dot(
        mu_d, mu_a) / (ref_index**2 * distance**3)

    return coupling_data[hash_string]
Ejemplo n.º 3
0
    def get_vib_spectrum(self, target_state, origin_state):

        # elec_trans_ene = self.state_energies[transition[1]] - self.state_energies[transition[0]]
        elec_trans_ene = target_state.energy - origin_state.energy
        transition = Transition(target_state, origin_state, symmetric=False)

        temp = self.temperature  # temperature (K)

        if transition in self._transitions:
            reorg_ene = self._transitions[self._transitions.index(
                transition)].reorganization_energy
        #if transition in self.reorganization_energies:
        #    reorg_ene = np.sum(self.reorganization_energies[transition])
        else:
            raise Exception(
                'Reorganization energy for transition {} not defined'.format(
                    transition))

        sign = np.sign(elec_trans_ene)

        # print('T', temp, molecule.reorganization_energies[transition], elec_trans_ene, -sign)

        def vib_spectrum(e):
            return 1.0 / (np.sqrt(4.0 * np.pi * BOLTZMANN_CONSTANT * temp * reorg_ene)) * \
                   np.exp(-(elec_trans_ene - e * sign + reorg_ene) ** 2 / (4 * BOLTZMANN_CONSTANT * temp * reorg_ene))

        return vib_spectrum
Ejemplo n.º 4
0
def einstein_radiative_decay(initial,
                             final,
                             g1=1,
                             g2=1,
                             transitions=None,
                             transition_moment=None):
    """
    Einstein radiative decay

    :param molecule:
    :param g1: degeneracy of target state
    :param g2: degeneracy of origin state

    :return: decay rate constant
    """
    deexcitation_energy = initial[0].energy - final[0].energy

    mu = np.array(transitions[transitions.index(
        Transition(initial[0], final[0]))].tdm) * ATOMIC_TO_ANGS_EL

    # mu = np.array(transition_moment[Transition(initial[0], final[0])]) * ATOMIC_TO_ANGS_EL
    mu2 = np.dot(mu, mu)  # transition moment square norm.
    alpha = 1.0 / 137.036
    return float(g1) / g2 * alpha * 4 * deexcitation_energy**3 * mu2 / (
        3 * SPEED_OF_LIGHT**2 * HBAR_PLANCK**3)
Ejemplo n.º 5
0
def forster_coupling(initial, final, ref_index=1, transitions=()):
    """
    Compute Forster coupling in eV

    :param initial: initial states
    :param final: final states
    :return: Forster coupling
    """

    d_transition = Transition(initial[0], final[1])
    a_transition = Transition(initial[1], final[0])

    d_orientation = initial[0].get_center().molecular_orientation()
    a_orientation = initial[1].get_center().molecular_orientation()

    #r_vector = initial[1].get_coordinates_absolute() - final[1].get_coordinates_absolute()
    r_vector = initial[0].get_coordinates_absolute(
    ) - final[0].get_coordinates_absolute()

    hash_string = generate_hash_2(inspect.currentframe().f_code.co_name,
                                  d_transition, a_transition, d_orientation,
                                  a_orientation, r_vector,
                                  [ref_index, tuple(transitions)])

    if hash_string in coupling_data:
        return coupling_data[hash_string]

    try:
        mu_a = transitions[transitions.index(a_transition)].tdm
        mu_d = transitions[transitions.index(d_transition)].tdm
    except ValueError:
        raise Exception('TDM for {} / {} not defined'.format(
            a_transition, d_transition))

    mu_d = rotate_vector(mu_d, d_orientation) * ATOMIC_TO_ANGS_EL
    mu_a = rotate_vector(mu_a, a_orientation) * ATOMIC_TO_ANGS_EL

    coupling_data[hash_string] = forster.dipole(mu_d,
                                                mu_a,
                                                r_vector,
                                                n=ref_index)

    return coupling_data[hash_string]
Ejemplo n.º 6
0
    def get_vib_spectrum(self, target_state, origin_state):

        elec_trans_ene = target_state.energy - origin_state.energy
        transition = Transition(target_state, origin_state, symmetric=False)

        #elec_trans_ene = self.state_energies[transition[1]] - self.state_energies[transition[0]]

        temp = self.temperature  # temperature (K)
        ext_reorg_ene = self.external_reorganization_energies[transition]
        reorg_ene = np.array(self.reorganization_energies[transition])

        sign = np.sign(elec_trans_ene)

        angl_freqs = np.array(
            self.frequencies[transition]
        ) * 29.9792558 * 2 * np.pi  # cm-1 -> ns-1 , angular frequency

        freq_classic_limit = BOLTZMANN_CONSTANT * temp / HBAR_PLANCK  # ns^-1,  angular frequency

        indices_cl = np.where(angl_freqs < freq_classic_limit)[0]
        indices_qm = np.where(angl_freqs >= freq_classic_limit)[0]

        l_cl = np.sum(reorg_ene[indices_cl]) + ext_reorg_ene

        l_qm = reorg_ene[indices_qm]

        ang_freq_qm = angl_freqs[indices_qm]

        s = np.true_divide(l_qm, ang_freq_qm) / HBAR_PLANCK

        s_eff = np.sum(s)
        w_eff = np.sum(np.multiply(s,
                                   ang_freq_qm)) / s_eff  # angular frequency

        # print('l_qm', np.sum(l_qm))
        # print('s_eff', s_eff)
        # print('w_eff', w_eff)

        def vib_spectrum(e):
            e = np.array(e, dtype=float)
            fcwd_term = np.zeros_like(e)
            for m in range(10):
                fcwd_term += s_eff**m / np.math.factorial(m) * np.exp(
                    -s_eff) * np.exp(-(elec_trans_ene - e * sign + l_cl +
                                       m * HBAR_PLANCK * w_eff)**2 /
                                     (4 * BOLTZMANN_CONSTANT * temp * l_cl))

            return 1.0 / (np.sqrt(
                4 * np.pi * BOLTZMANN_CONSTANT * temp * l_cl)) * fcwd_term

        return vib_spectrum
Ejemplo n.º 7
0
    def get_vib_spectrum(self, target_state, origin_state):
        """
        :param donor: energy diference between states
        :param acceptor: deviation in energy units
        :return: Franck-Condon-weighted density of states in gaussian aproximation
        """

        elec_trans_ene = target_state.energy - origin_state.energy
        transition = Transition(target_state, origin_state, symmetric=False)

        #elec_trans_ene = self.state_energies[transition[1]] - self.state_energies[transition[0]]
        reorg_ene = np.sum(self.reorganization_energies[transition])

        deviation = self.deviations[transition]  # atomic units

        sign = np.sign(elec_trans_ene)

        def vib_spectrum(e):
            return np.exp(
                -(elec_trans_ene - e * sign + reorg_ene)**22 /
                (2 * deviation)**2) / (2 * np.sqrt(np.pi) * deviation)

        return vib_spectrum
Ejemplo n.º 8
0
    def test_kmc_algorithm(self):
        np.random.seed(
            0
        )  # set random seed in order for the examples to reproduce the exact references

        transitions = [
            Transition(
                s1,
                gs,
                tdm=[0.5, 0.2],  # a.u.
                reorganization_energy=0.08,  # eV
                symmetric=True)
        ]

        vibrational_model = MarcusModel(transitions=transitions,
                                        temperature=300)  # Kelvin

        # list of transfer functions by state
        self.system.process_scheme = [
            GoldenRule(initial_states=(s1, gs),
                       final_states=(gs, s1),
                       electronic_coupling_function=forster_coupling_extended,
                       description='Forster',
                       arguments={
                           'ref_index': 2,
                           'longitude': 2,
                           'n_divisions': 30,
                           'transitions': transitions
                       },
                       vibrations=vibrational_model),
            DecayRate(initial_state=s1,
                      final_state=gs,
                      decay_rate_function=einstein_radiative_decay,
                      arguments={'transitions': transitions},
                      description='singlet_radiative_decay')
        ]

        self.system.cutoff_radius = 10.0  # interaction cutoff radius in Angstrom

        # some system analyze functions
        system_test_info(self.system)

        trajectories = calculate_kmc(
            self.system,
            num_trajectories=10,  # number of trajectories that will be simulated
            max_steps=100,  # maximum number of steps for trajectory allowed
            silent=True)

        # Results analysis
        analysis = TrajectoryAnalysis(trajectories)

        print('diffusion coefficient: {:9.5f} Angs^2/ns'.format(
            analysis.diffusion_coefficient('s1')))
        print('lifetime:              {:9.5f} ns'.format(
            analysis.lifetime('s1')))
        print('diffusion length:      {:9.5f} Angs'.format(
            analysis.diffusion_length('s1')))
        print('diffusion tensor (Angs^2/ns)')
        print(analysis.diffusion_coeff_tensor('s1'))

        print('diffusion length square tensor (Angs)')
        print(analysis.diffusion_length_square_tensor('s1'))

        test = {
            'diffusion coefficient':
            np.around(analysis.diffusion_coefficient('s1'), decimals=4),
            'lifetime':
            np.around(analysis.lifetime('s1'), decimals=4),
            'diffusion length':
            np.around(analysis.diffusion_length('s1'), decimals=4),
            'diffusion tensor':
            np.around(analysis.diffusion_coeff_tensor('s1',
                                                      unit_cell=[[0.0, 0.5],
                                                                 [0.2, 0.0]]),
                      decimals=4).tolist(),
            'diffusion length tensor':
            np.around(analysis.diffusion_length_square_tensor(
                's1', unit_cell=[[0.0, 0.5], [0.2, 0.0]]),
                      decimals=6).tolist()
        }
        print(test)

        ref = {
            'diffusion coefficient': 1586.4162,
            'lifetime': 0.3621,
            'diffusion length': 47.9375,
            'diffusion tensor': [[2048.6421, 3.4329], [3.4329, 1124.1904]],
            'diffusion length tensor': [[3142.8, 42.0], [42.0, 1453.2]]
        }

        self.assertDictEqual(ref, test)
Ejemplo n.º 9
0
    def test_kmc_algorithm(self):
        np.random.seed(0)  # set random seed in order for the examples to reproduce the exact references

        transitions = [Transition(s1, gs,
                                  tdm=[0.3, 0.1],  # a.u.
                                  reorganization_energy=0.08,  # eV
                                  symmetric=True)]

        marcus = MarcusModel(transitions=transitions,
                             temperature=300)  # Kelvin

        # list of transfer functions by state
        self.system.process_scheme = [GoldenRule(initial_states=(s1, gs), final_states=(gs, s1),
                                                 electronic_coupling_function=forster_coupling_extended,
                                                 description='Forster',
                                                 arguments={'ref_index': 2, 'longitude': 2, 'n_divisions': 30,
                                                            'transitions': transitions},
                                                 vibrations=marcus
                                                 ),
                                      DecayRate(initial_state=s1, final_state=gs,
                                                decay_rate_function=einstein_radiative_decay,
                                                arguments={'transitions': transitions},
                                                description='singlet_radiative_decay')
                                      ]

        self.system.cutoff_radius = 10.0  # interaction cutoff radius in Angstrom

        # some system analyze functions
        system_test_info(self.system)

        trajectories = calculate_kmc(self.system,
                                     num_trajectories=10,  # number of trajectories that will be simulated
                                     max_steps=100,  # maximum number of steps for trajectory allowed
                                     silent=True)

        # Results analysis
        analysis = TrajectoryAnalysis(trajectories)

        print('diffusion coefficient: {:9.5f} Angs^2/ns'.format(analysis.diffusion_coefficient('s1')))
        print('lifetime:              {:9.5f} ns'.format(analysis.lifetime('s1')))
        print('diffusion length:      {:9.5f} Angs'.format(analysis.diffusion_length('s1')))
        print('diffusion tensor (Angs^2/ns)')
        print(analysis.diffusion_coeff_tensor('s1'))

        print('diffusion length square tensor (Angs)')
        print(analysis.diffusion_length_square_tensor('s1'))

        test = {'diffusion coefficient': np.around(analysis.diffusion_coefficient('s1'), decimals=4),
                'lifetime': np.around(analysis.lifetime('s1'), decimals=4),
                'diffusion length': np.around(analysis.diffusion_length('s1'), decimals=4),
                'diffusion tensor': np.around(analysis.diffusion_coeff_tensor('s1', unit_cell=[[5.0, 1.0],
                                                                                               [1.0, 5.0]]), decimals=4).tolist(),
                'diffusion length tensor': np.around(analysis.diffusion_length_square_tensor('s1', unit_cell=[[5.0, 1.0],
                                                                                                              [1.0, 5.0]]), decimals=4).tolist()
                }

        ref = {'diffusion coefficient': 120.7623,
               'lifetime': 2.1215,
               'diffusion length': 33.0968,
               'diffusion tensor': [[198.6035, -72.076],
                                    [-72.076, 98.3641]],
               'diffusion length tensor': [[1606.8, -728.0],
                                           [-728.0, 1144.0]]
               }


        self.maxDiff = None
        __import__('sys').modules['unittest.util']._MAX_LENGTH = 999999999
        self.assertDictEqual(ref, test)
Ejemplo n.º 10
0
    def get_vib_spectrum(self, target_state, origin_state):
        transition = Transition(target_state, origin_state, symmetric=False)

        # Temperature is not actually used. This is to keep common interface
        return self.empirical_function[transition]
Ejemplo n.º 11
0
    def test_kmc_algorithm(self):
        np.random.seed(
            0
        )  # set random seed in order for the examples to reproduce the exact references

        transitions = [Transition(s1, gs, symmetric=True)]

        # list of transfer functions by state
        self.system.process_scheme = [
            SimpleRate(initial_states=(s1, gs),
                       final_states=(gs, s1),
                       rate_constant=0.1),
            SimpleRate(initial_states=(s1, ),
                       final_states=(gs, ),
                       rate_constant=0.01)
        ]

        # some system analyze functions
        system_test_info(self.system)

        trajectories = calculate_kmc(
            self.system,
            num_trajectories=
            500,  # number of trajectories that will be simulated
            max_steps=1000,  # maximum number of steps for trajectory allowed
            silent=True)

        # Results analysis
        analysis = TrajectoryAnalysis(trajectories)

        print('diffusion coefficient: {:9.5f} Angs^2/ns'.format(
            analysis.diffusion_coefficient('s1')))
        print('lifetime:              {:9.5f} ns'.format(
            analysis.lifetime('s1')))
        print('diffusion length:      {:9.5f} Angs'.format(
            analysis.diffusion_length('s1')))
        print('diffusion tensor (Angs^2/ns)')
        print(analysis.diffusion_coeff_tensor('s1'))

        print('diffusion length square tensor (Angs)')
        print(analysis.diffusion_length_square_tensor('s1'))

        test = {
            'diffusion coefficient':
            np.around(analysis.diffusion_coefficient('s1'), decimals=4),
            'lifetime':
            np.around(analysis.lifetime('s1'), decimals=4),
            'diffusion length':
            np.around(analysis.diffusion_length('s1'), decimals=4),
            'diffusion tensor':
            np.around(analysis.diffusion_coeff_tensor('s1',
                                                      unit_cell=[[0.0, 0.5],
                                                                 [0.2, 0.0]]),
                      decimals=4).tolist(),
            'diffusion length tensor':
            np.around(analysis.diffusion_length_square_tensor(
                's1', unit_cell=[[0.0, 0.5], [0.2, 0.0]]),
                      decimals=6).tolist()
        }
        print(test)

        ref = {
            'diffusion coefficient': 2.4439,
            'lifetime': 92.7496,
            'diffusion length': 30.1679,
            'diffusion tensor': [[2.1338, 0.1308], [0.1308, 2.7539]],
            'diffusion length tensor': [[798.7, 25.7], [25.7, 1021.5]]
        }

        # This is just for visual comparison (not accounted in the test)
        decay = 0.01
        transfer = 0.1
        distance = 5

        print('analytical model')
        print('----------------')
        data = get_analytical_model(distance, analysis.n_dim, transfer, decay)
        print('results analytical:', data)

        self.assertDictEqual(ref, test)
Ejemplo n.º 12
0
from kimonet.system.vibrations import MarcusModel, LevichJortnerModel, EmpiricalModel
from kimonet.core.processes.transitions import Transition
from kimonet import calculate_kmc, calculate_kmc_parallel, calculate_kmc_parallel_py2
from kimonet.system.state import ground_state as gs
from kimonet.utils import old_distance_between_molecules, distance_vector_periodic
from kimonet.fileio import store_trajectory_list, load_trajectory_list
import numpy as np

# states list
s1 = State(label='s1', energy=2.9705, multiplicity=1, size=1)
tt = State(label='tt', energy=2.0, multiplicity=1, size=2, connected_distance=8)
t1 = State(label='t1', energy=1.5, multiplicity=3, size=1)


# transition moments
transition_moment = {Transition(s1, gs): [0.1, 0.0]}


def direction_transfer(initial, final, rate_constant=1):
    """
    Allows transfer along a single direction

    :param initial:
    :param final:
    :param rate_constant:
    :return:
    """

    r = initial[0].get_center().get_coordinates() - initial[1].get_center().get_coordinates()
    dot = np.dot(r, initial[0].get_center().get_orientation_vector())
Ejemplo n.º 13
0
from kimonet.analysis import plot_polar_plot
from kimonet import calculate_kmc, calculate_kmc_parallel, calculate_kmc_parallel_py2
from kimonet.system.state import State
from kimonet.system.state import ground_state as gs
from kimonet.core.processes.transitions import Transition

import numpy as np

# states list
s1 = State(label='s1', energy=20.0, multiplicity=1)

# transition moments
transitions = [
    Transition(
        s1,
        gs,
        tdm=[0.1, 0.0],  # a.u.
        reorganization_energy=0.08)
]  # eV

# define system as a crystal
molecule = Molecule()

#print(molecule, molecule.state, molecule.state.get_center())

molecule2 = Molecule(site_energy=2)

print(molecule2, molecule2.state, molecule2.state.get_center())
print(molecule, molecule.state, molecule.state.get_center())

system = crystal_system(
Ejemplo n.º 14
0
def forster_coupling_sine_py(initial,
                             final,
                             ref_index=1,
                             transitions=(),
                             longitude=0.0,
                             n_divisions=1):
    """

    :param initial: initial states
    :param final: final states
    :param ref_index:
    :param transitions:
    :param longitude: extension length of the dipole
    :param n_divisions: number of subdivisions. To use with longitude. Increase until convergence
    :return:
    """

    n_divisions += 2

    d_transition = Transition(initial[0], final[1])
    a_transition = Transition(initial[1], final[0])

    d_orientation = initial[0].get_center().molecular_orientation()
    a_orientation = initial[1].get_center().molecular_orientation()

    r_vector = initial[0].get_coordinates_absolute(
    ) - final[0].get_coordinates_absolute()

    hash_string = generate_hash_2(
        inspect.currentframe().f_code.co_name, d_transition, a_transition,
        d_orientation, a_orientation, r_vector,
        [ref_index, tuple(transitions), longitude, n_divisions])

    if hash_string in coupling_data:
        return coupling_data[hash_string]

    mu_a = transitions[transitions.index(a_transition)].tdm
    mu_d = transitions[transitions.index(d_transition)].tdm

    mu_d = rotate_vector(mu_d, d_orientation) * ATOMIC_TO_ANGS_EL
    mu_a = rotate_vector(mu_a, a_orientation) * ATOMIC_TO_ANGS_EL

    k_e = 1.0 / (4.0 * np.pi * VAC_PERMITTIVITY)

    forster_coupling = 0
    for i, x in enumerate(
            np.linspace(-1 + 1 / n_divisions, 1 - 1 / n_divisions,
                        n_divisions)):

        #test=[]
        for j, y in enumerate(
                np.linspace(-1 + 1 / n_divisions, 1 - 1 / n_divisions,
                            n_divisions)):

            if n_divisions - 1 > i > 0 and n_divisions - 1 > j > 0:
                #if True:
                n_d = n_divisions - 1
                mu_ai = np.sin(i * np.pi / n_d) / np.sum(
                    [np.sin(ik * np.pi / n_d) for ik in range(n_d)]) * mu_a
                mu_di = np.sin(j * np.pi / n_d) / np.sum(
                    [np.sin(jk * np.pi / n_d) for jk in range(n_d)]) * mu_d
                #print(x, y, np.sin(j*np.pi/n_d)/np.sum([np.sin(jk*np.pi/n_d) for jk in range(n_d)]))

                dr_a = mu_a / np.linalg.norm(mu_a) * longitude * x
                dr_d = mu_d / np.linalg.norm(mu_d) * longitude * y

                r_vectori = r_vector + dr_a - dr_d

                distance = np.linalg.norm(r_vectori)

                #if np.linalg.norm(mu_ai) != 0 and np.linalg.norm(mu_di) != 0:
                # print(i, j)
                k = orientation_factor(
                    mu_ai, mu_di,
                    r_vectori)  # orientation factor between molecules
                forster_coupling += k_e * k * np.dot(mu_ai, mu_di) / (distance
                                                                      **3)
                #test.append(np.linalg.norm(mu_di))

        #plt.plot(test, 'o-')
        #plt.show()
    coupling_data[
        hash_string] = forster_coupling  # memory update for new couplings

    return coupling_data[hash_string]
Ejemplo n.º 15
0
def forster_coupling_extended_py(initial,
                                 final,
                                 ref_index=1,
                                 transitions=(),
                                 longitude=3,
                                 n_divisions=300):
    """
    Compute Forster coupling in eV (pure python version)

    :param initial: initial states
    :param final: final states
    :param longitude: extension length of the dipole
    :param n_divisions: number of subdivisions. To use with longitude. Increase until convergence.
    :return: Forster coupling
    """

    d_transition = Transition(initial[0], final[1])
    a_transition = Transition(initial[1], final[0])

    d_orientation = initial[0].get_center().molecular_orientation()
    a_orientation = initial[1].get_center().molecular_orientation()

    r_vector = initial[0].get_coordinates_absolute(
    ) - final[0].get_coordinates_absolute()

    hash_string = generate_hash_2(
        inspect.currentframe().f_code.co_name, d_transition, a_transition,
        d_orientation, a_orientation, r_vector,
        [ref_index, tuple(transitions), longitude, n_divisions])

    if hash_string in coupling_data:
        return coupling_data[hash_string]

    mu_a = transitions[transitions.index(a_transition)].tdm
    mu_d = transitions[transitions.index(d_transition)].tdm

    mu_d = rotate_vector(mu_d, d_orientation) * ATOMIC_TO_ANGS_EL
    mu_a = rotate_vector(mu_a, a_orientation) * ATOMIC_TO_ANGS_EL

    # mu_d = donor.get_transition_moment(to_state=_GS_)            # transition dipole moment (donor) e*angs
    # mu_a = acceptor.get_transition_moment(to_state=donor.state)  # transition dipole moment (acceptor) e*angs

    #r_vector = intermolecular_vector(donor, acceptor, supercell, cell_increment)  # position vector between donor and acceptor

    mu_ai = mu_a / n_divisions
    mu_di = mu_d / n_divisions

    k_e = 1.0 / (4.0 * np.pi * VAC_PERMITTIVITY)

    forster_coupling = 0
    for x in np.linspace(-0.5 + 0.5 / n_divisions, 0.5 - 0.5 / n_divisions,
                         n_divisions):
        for y in np.linspace(-0.5 + 0.5 / n_divisions, 0.5 - 0.5 / n_divisions,
                             n_divisions):

            #print(x, y)
            dr_a = mu_a / np.linalg.norm(mu_a) * longitude * x
            dr_d = mu_d / np.linalg.norm(mu_d) * longitude * y
            r_vector_i = r_vector + dr_a + dr_d

            distance = np.linalg.norm(r_vector_i)

            k = orientation_factor(
                mu_ai, mu_di,
                r_vector_i)  # orientation factor between molecules

            forster_coupling += k_e * k * np.dot(
                mu_ai, mu_di) / (ref_index**2 * distance**3)

    coupling_data[
        hash_string] = forster_coupling  # memory update for new couplings

    return forster_coupling
Ejemplo n.º 16
0
    def test_kmc_algorithm_2(self):
        np.random.seed(
            0
        )  # set random seed in order for the examples to reproduce the exact references

        transitions = [
            Transition(s1,
                       gs,
                       tdm=[0.01],
                       reorganization_energy=0.07,
                       symmetric=True)
        ]

        # set additional system parameters
        self.system.process_scheme = [
            GoldenRule(initial_states=(s1, gs),
                       final_states=(gs, s1),
                       electronic_coupling_function=forster_coupling,
                       description='Forster coupling',
                       arguments={
                           'ref_index': 1,
                           'transitions': transitions
                       },
                       vibrations=MarcusModel(transitions=transitions)),
            DecayRate(initial_state=s1,
                      final_state=gs,
                      decay_rate_function=decay_rate,
                      description='custom decay rate')
        ]

        self.system.cutoff_radius = 10.0  # interaction cutoff radius in Angstrom

        # some system analyze functions
        system_test_info(self.system)

        trajectories = calculate_kmc(
            self.system,
            num_trajectories=10,  # number of trajectories that will be simulated
            max_steps=10000,  # maximum number of steps for trajectory allowed
            silent=True)

        # Results analysis
        analysis = TrajectoryAnalysis(trajectories)

        print('diffusion coefficient: {:9.5f} Angs^2/ns'.format(
            analysis.diffusion_coefficient('s1')))
        print('lifetime:              {:9.5f} ns'.format(
            analysis.lifetime('s1')))
        print('diffusion length:      {:9.5f} Angs'.format(
            analysis.diffusion_length('s1')))
        print('diffusion tensor (Angs^2/ns)')
        print(analysis.diffusion_coeff_tensor('s1'))

        print('diffusion length square tensor (Angs)')
        print(analysis.diffusion_length_square_tensor('s1'))

        test = {
            'diffusion coefficient':
            np.around(analysis.diffusion_coefficient('s1'), decimals=4),
            'lifetime':
            np.around(analysis.lifetime('s1'), decimals=4),
            'diffusion length':
            np.around(analysis.diffusion_length('s1'), decimals=4),
            'diffusion tensor':
            np.around(analysis.diffusion_coeff_tensor('s1'),
                      decimals=4).tolist(),
            'diffusion length tensor':
            np.around(np.sqrt(analysis.diffusion_length_square_tensor('s1')),
                      decimals=6).tolist()
        }

        print(test)
        ref = {
            'diffusion coefficient': 0.4265,
            'lifetime': 24.1692,
            'diffusion length': 5.4498,
            'diffusion tensor': [[0.4265]],
            'diffusion length tensor': [[5.449771]]
        }

        self.assertDictEqual(ref, test)