Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1from typing import Tuple
2import numpy as np
4from ase.units import Bohr, Ha
5from ase.data import covalent_radii
6from ase.neighborlist import NeighborList
9class LippincottStuttman:
10 # atomic polarizability values from:
11 # Lippincott and Stutman J. Phys. Chem. 68 (1964) 2926-2940
12 # DOI: 10.1021/j100792a033
13 # see also:
14 # Marinov and Zotov Phys. Rev. B 55 (1997) 2938-2944
15 # DOI: 10.1103/PhysRevB.55.2938
16 # unit: Angstrom^3
17 atomic_polarizability = {
18 'B': 1.358,
19 'C': 0.978,
20 'N': 0.743,
21 'O': 0.592,
22 'Al': 3.918,
23 'Si': 2.988,
24 }
25 # reduced electronegativity Table I
26 reduced_eletronegativity = {
27 'B': 0.538,
28 'C': 0.846,
29 'N': 0.927,
30 'O': 1.0,
31 'Al': 0.533,
32 'Si': 0.583,
33 }
35 def __call__(self, el1: str, el2: str,
36 length: float) -> Tuple[float, float]:
37 """Bond polarizability
39 Parameters
40 ----------
41 el1: element string
42 el2: element string
43 length: float
45 Returns
46 -------
47 alphal: float
48 Parallel component
49 alphap: float
50 Perpendicular component
51 """
52 alpha1 = self.atomic_polarizability[el1]
53 alpha2 = self.atomic_polarizability[el2]
54 ren1 = self.reduced_eletronegativity[el1]
55 ren2 = self.reduced_eletronegativity[el2]
57 sigma = 1.
58 if el1 != el2:
59 sigma = np.exp(- (ren1 - ren2)**2 / 4)
61 # parallel component
62 alphal = sigma * length**4 / (4**4 * alpha1 * alpha2)**(1. / 6)
63 # XXX consider fractional covalency ?
65 # prependicular component
66 alphap = ((ren1**2 * alpha1 + ren2**2 * alpha2)
67 / (ren1**2 + ren2**2))
68 # XXX consider fractional covalency ?
70 return alphal, alphap
73class Linearized:
74 def __init__(self):
75 self._data = {
76 # L. Wirtz, M. Lazzeri, F. Mauri, A. Rubio,
77 # Phys. Rev. B 2005, 71, 241402.
78 # R0 al al' ap ap'
79 'CC': (1.53, 1.69, 7.43, 0.71, 0.37),
80 'BN': (1.56, 1.58, 4.22, 0.42, 0.90),
81 }
83 def __call__(self, el1: str, el2: str,
84 length: float) -> Tuple[float, float]:
85 """Bond polarizability
87 Parameters
88 ----------
89 el1: element string
90 el2: element string
91 length: float
93 Returns
94 -------
95 alphal: float
96 Parallel component
97 alphap: float
98 Perpendicular component
99 """
100 if el1 > el2:
101 bond = el2 + el1
102 else:
103 bond = el1 + el2
104 assert bond in self._data
105 length0, al, ald, ap, apd = self._data[bond]
107 return al + ald * (length - length0), ap + apd * (length - length0)
110class BondPolarizability:
111 def __init__(self, model=LippincottStuttman()):
112 self.model = model
114 def __call__(self, *args, **kwargs):
115 """Shorthand for calculate"""
116 return self.calculate(*args, **kwargs)
118 def calculate(self, atoms, radiicut=1.5):
119 """Sum up the bond polarizability from all bonds
121 Parameters
122 ----------
123 atoms: Atoms object
124 radiicut: float
125 Bonds are counted up to
126 radiicut * (sum of covalent radii of the pairs)
127 Default: 1.5
129 Returns
130 -------
131 polarizability tensor with unit (e^2 Angstrom^2 / eV).
132 Multiply with Bohr * Ha to get (Angstrom^3)
133 """
134 radii = np.array([covalent_radii[z]
135 for z in atoms.numbers])
136 nl = NeighborList(radii * 1.5, skin=0,
137 self_interaction=False)
138 nl.update(atoms)
139 pos_ac = atoms.get_positions()
141 alpha = 0
142 for ia, atom in enumerate(atoms):
143 indices, offsets = nl.get_neighbors(ia)
144 pos_ac = atoms.get_positions() - atoms.get_positions()[ia]
146 for ib, offset in zip(indices, offsets):
147 weight = 1
148 if offset.any(): # this comes from a periodic image
149 weight = 0.5 # count half the bond only
151 dist_c = pos_ac[ib] + np.dot(offset, atoms.get_cell())
152 dist = np.linalg.norm(dist_c)
153 al, ap = self.model(atom.symbol, atoms[ib].symbol, dist)
155 eye3 = np.eye(3) / 3
156 alpha += weight * (al + 2 * ap) * eye3
157 alpha += weight * (al - ap) * (
158 np.outer(dist_c, dist_c) / dist**2 - eye3)
159 return alpha / Bohr / Ha