Mix eta in Electrode package

This commit is contained in:
Ludwig Ahrens-Iwers
2023-11-13 14:41:13 +01:00
committed by Shern Tee
parent 857cc53923
commit a651697d2e
16 changed files with 303 additions and 78 deletions

View File

@ -0,0 +1,34 @@
LAMMPS data file via write_data, version 24 Dec 2020, timestep = 0
4 atoms
3 atom types
0 1 xlo xhi
0 1 ylo yhi
-10 10 zlo zhi
Masses
1 196.966553
2 196.966553
3 1.0
Pair Coeffs # lj/cut/coul/long
1 0 0
2 0 0
3 0 0
Atoms # full
1 1 1 0.00 0.00 0.00 -2.00 # bottom electrode
2 2 2 0.00 0.00 0.00 2.00 # top electrode
3 3 3 0.50 0.00 0.00 -1.00 # bottom electrolyte
4 3 3 -0.50 0.00 0.00 1.00 # top electrolyte
ETA
1 2.0
2 2.0
3 0
4 0

View File

@ -0,0 +1,34 @@
LAMMPS data file via write_data, version 24 Dec 2020, timestep = 0
4 atoms
3 atom types
0 1 xlo xhi
0 1 ylo yhi
-10 10 zlo zhi
Masses
1 196.966553
2 196.966553
3 1.0
Pair Coeffs # lj/cut/coul/long
1 0 0
2 0 0
3 0 0
Atoms # full
1 1 1 0.00 0.00 0.00 -2.00 # bottom electrode
2 2 2 0.00 0.00 0.00 2.00 # top electrode
3 3 3 0.50 0.00 0.00 -1.00 # bottom electrolyte
4 3 3 -0.50 0.00 0.00 1.00 # top electrolyte
ETA
1 0.5
2 3.0
3 0
4 0

View File

@ -1,7 +1,7 @@
#!/usr/env/python3
import sys
import os.path as op
import sys
def rel_error(out, ref):
@ -49,5 +49,5 @@ for label, ref, out in out_lines:
error = rel_error(out, ref)
lines.append(f"{label}: {out:.5f}, {error:.5f}\n")
with open("madelung.txt", 'a') as f:
with open("madelung.txt", "a") as f:
f.writelines(lines)

View File

@ -0,0 +1,17 @@
atom_style full
units real
boundary p p f
kspace_style ewald/electrode 1.0e-8
kspace_modify slab 8.0 # ew3dc
pair_style lj/cut/coul/long 12
fix feta all property/atom d_eta ghost yes
read_data data.eta fix feta NULL ETA
include "settings_eta.mod"
fix conp bot electrode/conp 0 2 couple top 1 symm on eta d_eta write_inv inv.csv write_vec vec.csv
run 0

View File

@ -0,0 +1,17 @@
atom_style full
units real
boundary p p f
kspace_style ewald/electrode 1.0e-8
kspace_modify slab 8.0 # ew3dc
pair_style lj/cut/coul/long 12
fix feta all property/atom d_eta ghost yes
read_data data.eta_mix fix feta NULL ETA
include "settings_eta.mod"
fix conp bot electrode/conp 0 2 couple top 1 symm on algo cg 1e-6 eta d_eta
run 0

View File

@ -0,0 +1,17 @@
atom_style full
units real
boundary p p f
kspace_style ewald/electrode 1.0e-8
kspace_modify slab 8.0 # ew3dc
pair_style lj/cut/coul/long 12
fix feta all property/atom d_eta ghost on
read_data data.eta_mix fix feta NULL ETA
include "settings_eta.mod"
fix conp bot electrode/conp 0 2 couple top 1 symm on eta d_eta write_inv inv.csv write_vec vec.csv
run 0

View File

@ -3,7 +3,6 @@
import numpy as np
from scipy.special import erf
ETA = 2
SQRT2 = np.sqrt(2)
COULOMB = 332.06371 # Coulomb constant in Lammps 'real' units
QE2F = 23.060549
@ -17,14 +16,14 @@ def lattice(length):
return np.array(np.meshgrid(x, y)).T.reshape(-1, 2)
def a_element(r):
def a_element(r, eta):
"""Coulomb contribution of two Gaussians"""
return erf(ETA / SQRT2 * r) / r
return erf(eta * r) / r
def b_element(r, q):
def b_element(r, q, eta):
"""Coulomb contribution of a Gaussian with a point charge"""
return q * erf(ETA * r) / r
return q * erf(eta * r) / r
a = 1 # nearest neighbor distance i.e. lattice constant / sqrt(2)
@ -36,59 +35,65 @@ v = np.array([-0.5, 0.5]) * (QE2F / COULOMB)
# distances to images within electrode and to opposite electrode
distances = a * np.linalg.norm(lattice(LENGTH), axis=1)
opposite_distances = np.sqrt(np.square(distances) + distance_plates ** 2)
opposite_distances = np.sqrt(np.square(distances) + distance_plates**2)
# self interaction and within original box
A_11 = np.sqrt(2 / np.pi) * ETA
A_12 = erf(ETA * distance_plates / SQRT2) / distance_plates
for name, eta_elec in [("", [2.0, 2.0]), ("_eta_mix", [0.5, 3.0])]:
eta_mix = np.prod(eta_elec) / np.sqrt(np.sum(np.square(eta_elec)))
# self interaction and within original box
A_11 = np.sqrt(2 / np.pi) * eta_elec[0]
A_22 = np.sqrt(2 / np.pi) * eta_elec[1]
A_12 = erf(eta_mix * distance_plates) / distance_plates
# interaction with periodic images
A_11 += 4 * np.sum(a_element(distances))
A_12 += 4 * np.sum(a_element(opposite_distances))
A = np.array([[A_11, A_12], [A_12, A_11]])
inv = np.linalg.inv(A)
e = np.array([1, 1])
inv -= np.matmul(inv, np.matmul(np.outer(e, e), inv)) / np.dot(e, np.dot(inv, e))
# interaction with periodic images
A_11 += 4 * np.sum(a_element(distances, eta_elec[0] / SQRT2))
A_22 += 4 * np.sum(a_element(distances, eta_elec[1] / SQRT2))
A_12 += 4 * np.sum(a_element(opposite_distances, eta_mix))
A = np.array([[A_11, A_12], [A_12, A_22]])
inv = np.linalg.inv(A)
e = np.array([1, 1])
inv -= np.matmul(inv, np.matmul(np.outer(e, e), inv)) / np.dot(e, np.dot(inv, e))
# electrode-electrolyte interaction
b = []
for x in x_elec:
bi = 0
for y, q in zip(x_elyt, q_elyt):
d = abs(y - x)
bi += b_element(d, q)
image_distances = np.sqrt(np.square(distances) + d ** 2)
bi += 4 * np.sum(b_element(image_distances, q))
b.append(bi)
b = np.array(b)
# electrode-electrolyte interaction
b = []
for x, eta in zip(x_elec, eta_elec):
bi = 0
for y, q in zip(x_elyt, q_elyt):
d = abs(y - x)
bi += b_element(d, q, eta)
image_distances = np.sqrt(np.square(distances) + d**2)
bi += 4 * np.sum(b_element(image_distances, q, eta))
b.append(bi)
b = np.array(b)
# electrolyte-electrolyte energy
elyt_11 = 4 * np.sum(1 / distances)
distance_elyt = x_elyt[1] - x_elyt[0]
elyt_12 = 1 / distance_elyt + 4 * np.sum(
1 / np.sqrt(np.square(distances) + distance_elyt ** 2)
)
elyt = np.array([[elyt_11, elyt_12], [elyt_12, elyt_11]])
energy_elyt = 0.5 * np.dot(q_elyt, np.dot(elyt, q_elyt))
# electrode charges and energy
q = np.dot(inv, v - b)
energy = COULOMB * (0.5 * np.dot(q, np.dot(A, q)) + np.dot(b, q) + energy_elyt)
print(
"length, energy / kcal/mol, q1 / e, q2 / e, inv11 / A, inv12 / A, b1 / e/A, b2 / e/A"
)
print(
", ".join(
[
str(LENGTH),
f"{energy:.8f}",
f"{q[0]:.10f}",
f"{q[1]:.10f}",
f"{inv[0, 0]:.10f}",
f"{inv[0, 1]:.10f}",
f"{b[0]:.8f}",
f"{b[1]:.8f}",
]
# electrolyte-electrolyte energy
elyt_11 = 4 * np.sum(1 / distances)
distance_elyt = x_elyt[1] - x_elyt[0]
elyt_12 = 1 / distance_elyt + 4 * np.sum(
1 / np.sqrt(np.square(distances) + distance_elyt**2)
)
)
elyt = np.array([[elyt_11, elyt_12], [elyt_12, elyt_11]])
energy_elyt = 0.5 * np.dot(q_elyt, np.dot(elyt, q_elyt))
# electrode charges and energy
q = np.dot(inv, v - b)
energy = COULOMB * (0.5 * np.dot(q, np.dot(A, q)) + np.dot(b, q) + energy_elyt)
with open(f"plate_cap{name}.csv", "w") as f:
f.write(
"length, energy / kcal/mol, q1 / e, q2 / e, inv11 / A, inv12 / A, b1 / e/A, b2 / e/A\n"
)
f.write(
", ".join(
[
str(LENGTH),
f"{energy:.8f}",
f"{q[0]:.10f}",
f"{q[1]:.10f}",
f"{inv[0, 0]:.10f}",
f"{inv[0, 1]:.10f}",
f"{b[0]:.8f}",
f"{b[1]:.8f}",
]
)
+ "\n"
)

View File

@ -0,0 +1,19 @@
# distribute electrode atoms among all processors:
if "$(extract_setting(world_size) % 2) == 0" then "processors * * 2"
group bot type 1
group top type 2
# get electrode charges
variable q atom q
compute qbot bot reduce sum v_q
compute qtop top reduce sum v_q
compute compute_pe all pe
variable vpe equal c_compute_pe
variable charge equal c_qtop
fix fxprint all print 1 "${vpe}, ${charge}" file "out.csv"
thermo_style custom step pe c_qbot c_qtop

View File

@ -7,17 +7,27 @@ if [ ! -f $lmpbin ]; then
fi
ref_out="plate_cap.csv"
if [ ! -f $ref_out ]; then
ref_mix_out="plate_cap_eta_mix.csv"
if [ ! -f $ref_out ] || [ ! -f $ref_mix_out ]; then
echo "Generating reference data"
python3 plate_cap.py > $ref_out
python3 plate_cap.py
fi
echo "Running Lammps inputs"
# w/o eta mixing
rm -rf madelung.txt && touch madelung.txt
for file in in.*; do
for file in in.eta in.ewald-ew3dc in.ewald-ew2d in.pppm-ew3dc in.cg; do
printf "\n$file\n" >> madelung.txt
rm -f out.csv inv.csv vec.csv
$lmpbin -i $file &> /dev/null
python3 eval.py $ref_out out.csv inv.csv vec.csv
done
# with eta mixing
for file in in.eta_mix in.eta_cg; do
printf "\n$file\n" >> madelung.txt
rm -f out.csv inv.csv vec.csv
$lmpbin -i $file &> /dev/null
python3 eval.py $ref_mix_out out.csv inv.csv vec.csv
done
cat madelung.txt