Figure 5.3:

Contour representation of the potential-energy surface.

Figure 5.3

Code for Figure 5.3

Text of the GNU GPL.

initdata.py


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
# Converted from initdata.m - returns parameter dict for LEPS potential
import numpy as np

def initdata():
    p = {}
    p['D1']  = np.array([129.3, 94.8, 129.3])
    p['B1']  = np.array([2.316, 2.047, 2.316])
    p['r10'] = np.array([0.931, 0.753, 0.931])
    p['D3']  = np.array([51.83, 34.56, 51.83])
    p['B3']  = np.array([2.229, 1.522, 2.229])
    p['r30'] = np.array([0.931, 0.753, 0.931])
    p['R_1'] = np.array([1.217, 0.443, 1.217])
    p['C']   = np.array([1174.9, 388.8, 1174.9])
    p['Si']  = np.array([3.090, 1.929, 3.090])
    p['A']   = np.array([1.315, 0.762, 1.315])
    return p


if __name__ == '__main__':
    pass

inittrace.py


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# Converted from inittrace.m - compute reaction path trace for LEPS potential
# [depends] Vau.py rder.py lder.py
import numpy as np
from scipy.integrate import solve_ivp
import os
from Vau import Vau
from rder import rder
from lder import lder

def inittrace(en1, en2, p):
    R1sar = 1.29781
    R2sar = 0.80363
    numr  = 30

    R1r = np.linspace(R1sar, en1, numr)
    sqeps = np.sqrt(np.finfo(float).eps)

    sol_r = solve_ivp(lambda r1, r2: np.atleast_1d(rder(r1, r2[0], p)),
                      [R1r[0], R1r[-1]], [R2sar],
                      method='BDF', t_eval=R1r, rtol=sqeps, atol=sqeps)
    R2r = sol_r.y[0]

    R1sal = 1.297
    R2sal = 0.80363
    numl  = 1000

    R2l = np.linspace(R2sal, en2, numl)
    sol_l = solve_ivp(lambda r2, r1: np.atleast_1d(lder(r2, r1[0], p)),
                      [R2l[0], R2l[-1]], [R1sal],
                      method='BDF', t_eval=R2l, rtol=sqeps, atol=sqeps)
    R1l = sol_l.y[0]

    # reverse the 'r' branch
    R1rn = R1r[::-1]
    R2rn = R2r[::-1]

    R1 = np.concatenate([R1rn, R1l])
    R2 = np.concatenate([R2rn, R2l])
    num = numl + numr

    s = np.zeros(num)
    V = np.zeros(num)

    V[0] = Vau(R1[0], R2[0], p)
    for i in range(1, num):
        s[i] = np.sqrt((R1[i]-R1[i-1])**2 + (R2[i]-R2[i-1])**2) + s[i-1]
        V[i] = Vau(R1[i], R2[i], p)

    d3trace = np.zeros(num * 3)
    for i in range(num):
        j = i * 3
        d3trace[j]   = R1[i]
        d3trace[j+1] = R2[i]
        d3trace[j+2] = V[i]

    return s, V, d3trace, num


if __name__ == '__main__':
    pass

Vau.py


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# Converted from Vau.m - London-Eyring-Polanyi-Sato (LEPS) potential
import numpy as np

def Vau(r1, r2, p):
    r3 = r1 + r2
    r = [r1, r2, r3]

    E1 = np.zeros(3)
    E3 = np.zeros(3)
    Q  = np.zeros(3)
    J  = np.zeros(3)

    for x in range(3):
        E1[x] = p['D1'][x] * (np.exp(-2*p['B1'][x]*(r[x] - p['r10'][x])) -
                               2*np.exp(-p['B1'][x]*(r[x] - p['r10'][x])))
        E3a = p['D3'][x] * (np.exp(-2*p['B3'][x]*(r[x] - p['r30'][x])) +
                             2*np.exp(-p['B3'][x]*(r[x] - p['r30'][x])))
        E3b = p['C'][x] * (r[x] + p['A'][x]) * np.exp(-p['Si'][x]*r[x])
        E3[x] = E3a if r[x] <= p['R_1'][x] else E3b
        Q[x] = (E1[x] + E3[x]) / 2
        J[x] = (E1[x] - E3[x]) / 2

    retval = (Q[0] + Q[1] + Q[2] -
              np.sqrt(0.5 * ((J[0]-J[1])**2 + (J[1]-J[2])**2 + (J[0]-J[2])**2)))
    return retval


if __name__ == '__main__':
    # Simple test - no file output for helper module
    pass

rder.py


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# Converted from rder.m - derivative dr2/dr1 along reaction path
# [depends] lder.py
import os
from lder import lder

def rder(r1, r2, p):
    return 1.0 / lder(r2, r1, p)


if __name__ == '__main__':
    pass

lder.py


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
# Converted from lder.m - derivative dr1/dr2 along reaction path (London-Eyring-Polanyi-Sato)
import numpy as np

def lder(r2, r1, p):
    r3 = r1 + r2
    r = [r1, r2, r3]

    E1  = np.zeros(3)
    E3  = np.zeros(3)
    Q   = np.zeros(3)
    J   = np.zeros(3)
    dE1 = np.zeros((3, 3))
    dE3 = np.zeros((3, 3))

    for x in range(3):
        E1[x] = p['D1'][x] * (np.exp(-2*p['B1'][x]*(r[x] - p['r10'][x])) -
                               2*np.exp(-p['B1'][x]*(r[x] - p['r10'][x])))
        E3a = p['D3'][x] * (np.exp(-2*p['B3'][x]*(r[x] - p['r30'][x])) +
                             2*np.exp(-p['B3'][x]*(r[x] - p['r30'][x])))
        E3b = p['C'][x] * (r[x] + p['A'][x]) * np.exp(-p['Si'][x]*r[x])
        E3[x] = E3a if r[x] <= p['R_1'][x] else E3b
        Q[x] = (E1[x] + E3[x]) / 2
        J[x] = (E1[x] - E3[x]) / 2

    # diagonal derivatives dE1[i,i] and dE3[i,i]
    for i in range(3):
        dE1[i, i] = -2*p['B1'][i]*p['D1'][i] * (
            np.exp(-2*p['B1'][i]*(r[i] - p['r10'][i])) -
            np.exp(-p['B1'][i]*(r[i] - p['r10'][i])))
        if r[i] <= p['R_1'][i]:
            dE3[i, i] = -2*p['B3'][i]*p['D3'][i] * (
                np.exp(-2*p['B3'][i]*(r[i] - p['r30'][i])) +
                np.exp(-p['B3'][i]*(r[i] - p['r30'][i])))
        else:
            dE3[i, i] = p['C'][i]*np.exp(-p['Si'][i]*r[i]) * (
                1 - p['Si'][i]*(r[i] + p['A'][i]))

    dE1[0, 1] = 0.; dE3[0, 1] = 0.
    dE1[1, 0] = 0.; dE3[1, 0] = 0.

    dE1[2, 0] = dE1[2, 2]; dE3[2, 0] = dE3[2, 2]
    dE1[2, 1] = dE1[2, 2]; dE3[2, 1] = dE3[2, 2]

    dE1[0, 2] = 1000.; dE3[0, 2] = 1000.
    dE1[1, 2] = 1000.; dE3[1, 2] = 1000.
    dE1[2, 2] = 1000.; dE3[2, 2] = 1000.

    dQ = (dE1 + dE3) / 2
    dJ = (dE1 - dE3) / 2

    sq = np.sqrt(0.5 * ((J[0]-J[1])**2 + (J[1]-J[2])**2 + (J[0]-J[2])**2))

    dV1 = (dQ[0, 0] + dQ[2, 0] - 0.5/sq * (
        J[0]*(2*dJ[0, 0] - dJ[2, 0]) +
        J[1]*(-dJ[0, 0] - dJ[2, 0]) +
        J[2]*(2*dJ[2, 0] - dJ[0, 0])))

    dV2 = (dQ[1, 1] + dQ[2, 1] - 0.5/sq * (
        J[0]*(-dJ[1, 1] - dJ[2, 1]) +
        J[1]*(2*dJ[1, 1] - dJ[2, 1]) +
        J[2]*(2*dJ[2, 1] - dJ[1, 1])))

    return dV1 / dV2


if __name__ == '__main__':
    pass

main.py


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
# Converted from hfc.m - contour plot data for LEPS potential surface
# [depends] initdata.py inittrace.py Vau.py
import numpy as np
from matplotlib import contour as mpl_contour
import os
from initdata import initdata
from inittrace import inittrace
from Vau import Vau

p = initdata()

an1, en1 = 0., 3.
an2, en2 = 0., 3.
n1, n2 = 301, 301

step1 = (en1 - an1) / (n1 - 1)
step2 = (en2 - an2) / (n2 - 1)

x_arr = np.linspace(an1, en1, n1)
y_arr = np.linspace(an2, en2, n2)

z = np.zeros((n2, n1))
for p1 in range(n1):
    r1 = p1 * step1 + an1
    for p2 in range(n2):
        r2 = p2 * step2 + an2
        z[p2, p1] = Vau(r1, r2, p)

levels = sorted([-120, -110, -100, -95, -92, -90.5, -80, -60, -40, -20,
                 200, 1000, 3000])

# Compute contours using matplotlib (same algorithm as Octave contourc)
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
fig, ax = plt.subplots()
cs = ax.contour(x_arr, y_arr, z, levels=levels)
plt.close(fig)

with open('hfc.dat', 'w') as fid:
    fid.write('# [contours]\n')
    for i, level in enumerate(cs.levels):
        for seg in cs.allsegs[i]:
            fid.write(f'# contour level {level}\n')
            for row in seg:
                fid.write(f'{row[0]:f} {row[1]:f}\n')
            fid.write('\n')

    s, V, d3trace, num = inittrace(en1, en2, p)
    fid.write('\n# [min]\n')
    for i in range(num):
        j = i * 3
        fid.write(f'{d3trace[j]:f} {d3trace[j+1]:f} {d3trace[j+2]:f}\n')