This commit is contained in:
2026-01-08 19:47:32 +03:00
commit 4d7676a79e
89 changed files with 62260 additions and 0 deletions

View File

@@ -0,0 +1,528 @@
import numpy as np
import scipy.integrate as integrate
from scipy.optimize import minimize
import numbers
import typing
from tqdm import tqdm
from echem.core.useful_funcs import nearest_array_index, ClassMethods
E_F_SHE_VAC = -4.5 # Fermi Energy of Standard Hydrogen Electrode with respect to vacuum
class GM(ClassMethods):
"""This class calculates the final Fermi and Redox species distributions according
to the Gerischer-Marcus formalism.
Parameters:
-----------
DOS: np.ndarray, optional
The values of DOS in 1D numpy array. If not specified values will be taken from saved data.
E: np.ndarray, optional
The corresponding to the DOS energy mesh. If not specified values will be taken from saved data.
efermi: np.ndarray. optional
System Fermi level. If not specified values will be taken from saved data.
vacuum_lvl: np.ndarray, optional
System vacuum level. If not specified values will be taken from saved data.
"""
def __init__(self, path_to_data='Saved_data', DOS=None, E=None, efermi=None, vacuum_lvl=None):
# variables that might be defined through __init__ function
self.E = E
self.DOS = DOS
self.efermi = efermi
self.vacuum_lvl = vacuum_lvl
# variables that should be defined through set_params function
self.C_EDL = None
self.T = None
self.l = None
self.sheet_area = None
# variables that will be created during calculations
self.sigma_Q_arr = None
# variable that define numerical parameters of quantum charge calculation
self.__SIGMA_0 = 0.5
self.__SIGMA_ACCURACY = 1e-3
self.__SIGMA_RANGE = 4
if DOS is None:
try:
self.DOS = np.load(path_to_data + '/DOS.npy')
except OSError:
print('File DOS.npy does not exist')
if E is None:
try:
self.E = np.load(path_to_data + '/E.npy')
except OSError:
print('File E_DOS.npy does not exist')
if efermi is None:
try:
self.efermi = np.load(path_to_data + '/efermi.npy')
except OSError:
print('File efermi.npy does not exist')
if vacuum_lvl is None:
try:
self.vacuum_lvl = np.load(path_to_data + '/vacuum_lvl.npy')
except OSError:
print('File vacuum_lvl.npy does not exist')
def set_params(self, C_EDL, T, l, sheet_area):
"""Sets parameters of calculation
Parameters:
----------
C_EDL: float, str
float: Capacitance of electric double layer (microF/cm^2)
str: 'Q' calculating in the Quantum Capacitance Dominating limit (C_Q << C_EDL)
str: 'Cl' calculating in the Classical limit (C_Q >> C_EDL)
T: int, float
Temperature. It is used in computing Fermi function and distribution function of redox system states
l: float
Reorganization energy in eV
"""
self.C_EDL = C_EDL
self.T = T
self.l = l
self.sheet_area = sheet_area
def set_params_advance(self, SIGMA_0=0.5, ACCURACY_SIGMA=1e-3, SIGMA_RANGE=4):
"""
Sets numerical parameters that are used in quantum charge density calculations. Delete cashed
results of charge calculations.
Args:
SIGMA_0: float, optional
Initial guess for charge at equilibrium
ACCURACY_SIGMA: float, optional
Accuracy of charge calculation
SIGMA_RANGE: float, optional
It defines the minimum and maximum calculated charge
"""
self.__SIGMA_0 = SIGMA_0
self.__SIGMA_ACCURACY = ACCURACY_SIGMA
self.__SIGMA_RANGE = SIGMA_RANGE
self.sigma_Q_arr = None
@staticmethod
def fermi_func(E, T):
"""
Calculates Fermi-Dirac Distribution
Args:
E: Energies
T: Temperature in K
"""
k = 8.617e-5 # eV/K
return 1 / (1 + np.exp(E / (k * T)))
@staticmethod
def W_ox(E, T, l):
"""
Distribution of oxidized states
Args:
E (np.array): Energies
T (float): Temperature
l (float): Reorganization energy
"""
k = 8.617e-5 # eV/K
W_0 = (1 / np.sqrt(4 * k * T * l))
return W_0 * np.exp(- (E - l) ** 2 / (4 * k * T * l))
@staticmethod
def W_red(E, T, l):
"""
Distribution of reduced states
Args:
E (np.array): Energies
T (float): Temperature
l (float): Reorganization energy
"""
k = 8.617e-5 # eV/K
W_0 = (1 / np.sqrt(4 * k * T * l))
return W_0 * np.exp(- (E + l) ** 2 / (4 * k * T * l))
def compute_C_quantum(self, dE_Q_arr):
"""
Calculates differential quantum capacitance
Q = e * int{DOS(E) * [f(E) - f(E + deltaE)] dE}
C_Q = - dQ/d(deltaE) = - (e / (4*k*T)) * int{DOS(E) * sech^2[(E+deltaE)/(2*k*T)] dE}
Args:
dE_Q_arr (np.array, float): Energy shift at which C_Q is calculated
Returns:
Quantum capacitance in accordance with energy displacement(s)
TODO check constants
"""
self.check_existence('T')
self.check_existence('sheet_area')
k = 8.617e-5 # eV/K
elementary_charge = 1.6e-19 # C
k_1 = 1.38e-23 # J/K
const = (1e6 * elementary_charge ** 2) / (4 * k_1 * self.sheet_area) # micro F / cm^2
if isinstance(dE_Q_arr, typing.Iterable):
C_q_arr = np.zeros_like(dE_Q_arr)
for i, dE_Q in enumerate(dE_Q_arr):
E_2 = self.E - dE_Q # energy range for cosh function
cosh = np.cosh(E_2 / (2 * k * self.T))
integrand = (self.DOS / cosh) / cosh
C_q = (const / self.T) * integrate.simps(integrand, self.E)
C_q_arr[i] = C_q
return C_q_arr
def compute_C_total(self, E_diff_arr, add_info=False):
sigma_arr = np.zeros_like(E_diff_arr)
for i, E_diff in tqdm(enumerate(E_diff_arr), total=len(E_diff_arr)):
sigma_arr[i] = self.compute_sigma(E_diff, sigma_0=sigma_arr[i-1])
C_tot_arr = np.zeros_like(E_diff_arr)
C_Q_arr = np.zeros_like(E_diff_arr)
for i, (E_diff, sigma) in enumerate(zip(E_diff_arr, sigma_arr)):
ind = nearest_array_index(self.sigma_Q_arr, sigma)
E_step = self.__SIGMA_ACCURACY
E_start = - self.__SIGMA_RANGE
dE_Q = E_start + E_step * ind
C_Q = self.compute_C_quantum([dE_Q])
C_Q_arr[i] = C_Q[0]
C_tot = C_Q * self.C_EDL / (C_Q + self.C_EDL)
C_tot_arr[i] = C_tot
if add_info is False:
return C_tot_arr
else:
return C_tot_arr, C_Q_arr, sigma_arr
def compute_sigma_EDL(self, dE_EDL):
"""
Calculates charge corresponding to the potential drop of -dE_EDL/|e|.
Takes into account integral capacitance C_EDL
Args:
dE_EDL (float, np.array): Electron energy shift due to potential drop
Returns:
Charge or Sequence of charges
"""
self.check_existence('C_EDL')
return - self.C_EDL * dE_EDL
def compute_sigma_quantum(self, dE_Q_arr):
"""
Computes surface charge density induced by depletion or excess of electrons
Parameters:
----------
dE_Q_arr: np.ndarray, float
Shift in Fermi level due to quantum capacitance
Returns:
-------
sigmas: np.ndarray, float
Computed values (or one value) of surface charge densities
"""
self.check_existence('T')
self.check_existence('sheet_area')
elementary_charge = 1.6e-13 # micro coulomb
if isinstance(dE_Q_arr, typing.Iterable):
y_fermi = self.fermi_func(self.E, self.T)
sigmas = []
for dE_Q in dE_Q_arr:
E_2 = self.E - dE_Q # energy range for shifted Fermi_Dirac function
y_fermi_shifted = self.fermi_func(E_2, self.T)
integrand = self.DOS * (y_fermi - y_fermi_shifted)
sigma = (elementary_charge / self.sheet_area) * integrate.simps(integrand, self.E)
sigmas.append(sigma)
return sigmas
elif isinstance(dE_Q_arr, numbers.Real):
y_fermi = self.fermi_func(self.E, self.T)
E_2 = self.E - dE_Q_arr # energy range for shifted Fermi_Dirac function
y_fermi_shifted = self.fermi_func(E_2, self.T)
integrand = self.DOS * (y_fermi - y_fermi_shifted)
sigma = (elementary_charge / self.sheet_area) * integrate.simps(integrand, self.E)
return sigma
else:
raise TypeError(f'Invalid type of dE_Q_arr: {type(dE_Q_arr)}')
def compute_sigma(self, E_diff, sigma_0=None):
def error_E_diff(sigma, E_diff, sigma_Q_arr):
ind = nearest_array_index(sigma_Q_arr, sigma)
dE_Q = E_start + E_step * ind
dE_EDL = - sigma / self.C_EDL
dE_total = dE_Q + dE_EDL
return (dE_total - E_diff) ** 2
for var in ['T', 'l', 'C_EDL']:
self.check_existence(var)
E_step = self.__SIGMA_ACCURACY
E_start = - self.__SIGMA_RANGE
if sigma_0 is None:
sigma_0 = self.__SIGMA_0
# check if we've already calculated sigma_Q_arr in another run
if self.sigma_Q_arr is None:
E_range = np.arange(E_start, -E_start, E_step)
sigma_Q_arr = self.compute_sigma_quantum(E_range)
self.sigma_Q_arr = sigma_Q_arr
else:
sigma_Q_arr = self.sigma_Q_arr
result = minimize(error_E_diff, np.array([sigma_0]), args=(E_diff, sigma_Q_arr))
sigma = result.x[0]
return sigma
def compute_distributions(self, V_std, overpot=0, reverse=False, add_info=False):
"""Computes Fermi-Dirac and Redox species distributions according to Gerischer-Markus formalism
with Quantum Capacitance
Parameters:
----------
V_std: float
Standard potential (vs SHE) of a redox couple (Volts)
overpot: float, optional
Overpotential (Volts). It shifts the electrode Fermi energy to -|e|*overpot
reverse: bool, optional
If reverse is False the process of electron transfer from electrode to the oxidized state of the
redox species is considered and vice versa
add_info: bool, optional
If False the func returns Fermi-Dirac and Redox species distributions
If True additionally returns dE_Q (Fermi energy shift due to the quantum capacitance),
sigma (surface charge) and E_diff (the whole energy shift with respect to the original Fermi level)
Returns:
-------
y_fermi: np.array
Fermi-Dirac distribution
y_redox: np.array
Redox species distributions
dE_Q: np.array, optional (if add_info == True)
Total shift of the Fermi energy due to the Quantum Capacitance
sigma: np.array, optional (if add_info == True)
surface charge in microF/cm^2
E_F_redox: np.array, optional (if add_info == True)
The sum of two energy displacement of the electrode due to the difference in Fermi level of Redox couple
and the electrode and overpotential. It splits into dE_Q and dE_EDL
"""
E_F_redox = E_F_SHE_VAC - self.efermi - V_std + self.vacuum_lvl - overpot
sigma = self.compute_sigma(E_F_redox)
ind = nearest_array_index(self.sigma_Q_arr, sigma)
E_step = self.__SIGMA_ACCURACY
E_start = - self.__SIGMA_RANGE
dE_Q = E_start + E_step * ind
E_fermi = self.E - dE_Q
E_DOS_redox = self.E - dE_Q - overpot
if reverse:
y_fermi = 1 - self.fermi_func(E_fermi, self.T)
y_redox = self.W_red(E_DOS_redox, self.T, self.l)
else:
y_fermi = self.fermi_func(E_fermi, self.T)
y_redox = self.W_ox(E_DOS_redox, self.T, self.l)
if not add_info:
return y_fermi, y_redox
else:
return y_fermi, y_redox, dE_Q, sigma, E_F_redox
def compute_k_HET(self, V_std_pot_arr, overpot_arr, reverse=False, add_info=False):
"""Computes integral k_HET using Gerischer-Markus formalism with quantum capacitance
Parameters:
----------
V_std_pot_arr: float, np.ndarray
A range of varying a standard potential
overpot_arr: float, np.ndarray
A range of varying an overpotential
reverse: bool, optional
if reverse is False the process of electron transfer from electrode to the oxidized state of the
redox mediator is considered and vice versa
Returns:
-------
k_HET: np.array
Calculated heterogeneous electron transfer rate constant according to Gerischer-Marcus model with quantum
capacitance
dE_Q_arr: np.ndarray, optional (if add_info == True)
Total shift of the Fermi energy due to the Quantum Capacitance for all calculated redox potentials or
overpotentials
sigma_arr: np.ndarray, optional (if add_info == True)
surface charge in microF/cm^2 for all calculated redox potentials or overpotentials
E_F_redox_arr: np.ndarray, optional (if add_info == True)
The sum of two energy displacement of the electrode due to the difference in Fermi level of Redox couple
and the electrode and overpotential. It splits into dE_Q and dE_EDL. For all calculated redox potentials
or overpotentials
y_fermi_arr: 2D np.ndarray, optional (if add_info == True)
Fermi-Dirac distribution for all calculated redox potentials or overpotentials
y_redox_arr: 2D np.ndarray, optional (if add_info == True)
Redox species distributions for all calculated redox potentials or overpotentials
"""
if isinstance(self.C_EDL, numbers.Real):
if isinstance(V_std_pot_arr, typing.Iterable) and isinstance(overpot_arr, numbers.Real):
k_HET = np.zeros_like(V_std_pot_arr)
if not add_info:
for i, V_std in tqdm(enumerate(V_std_pot_arr), total=len(V_std_pot_arr)):
y_fermi, y_redox = self.compute_distributions(V_std, reverse=reverse, overpot=overpot_arr)
integrand = self.DOS * y_fermi * y_redox
k_HET[i] = integrate.simps(integrand, self.E) / self.sheet_area
return k_HET
else:
dE_Q_arr = np.zeros_like(V_std_pot_arr)
sigma_arr = np.zeros_like(V_std_pot_arr)
E_F_redox_arr = np.zeros_like(V_std_pot_arr)
y_fermi_arr = np.zeros((len(V_std_pot_arr), len(self.E)))
y_redox_arr = np.zeros((len(V_std_pot_arr), len(self.E)))
for i, V_std in tqdm(enumerate(V_std_pot_arr), total=len(V_std_pot_arr)):
y_fermi, y_redox, dE_Q, sigma, E_F_redox = self.compute_distributions(V_std, reverse=reverse,
overpot=overpot_arr,
add_info=add_info)
integrand = self.DOS * y_fermi * y_redox
k_HET[i] = integrate.simps(integrand, self.E) / self.sheet_area
dE_Q_arr[i] = dE_Q
sigma_arr[i] = sigma
E_F_redox_arr[i] = E_F_redox
y_fermi_arr[i] = y_fermi
y_redox_arr[i] = y_redox
return k_HET, dE_Q_arr, sigma_arr, E_F_redox_arr, y_fermi_arr, y_redox_arr
elif isinstance(overpot_arr, typing.Iterable) and isinstance(V_std_pot_arr, numbers.Real):
k_HET = np.zeros_like(overpot_arr)
if not add_info:
for i, overpot in tqdm(enumerate(overpot_arr), total=len(overpot_arr)):
y_fermi, y_redox = self.compute_distributions(V_std_pot_arr, reverse=reverse, overpot=overpot)
integrand = self.DOS * y_fermi * y_redox
k_HET[i] = integrate.simps(integrand, self.E) / self.sheet_area
return k_HET
else:
dE_Q_arr = np.zeros_like(overpot_arr)
sigma_arr = np.zeros_like(overpot_arr)
E_F_redox_arr = np.zeros_like(overpot_arr)
y_fermi_arr = np.zeros((len(overpot_arr), len(self.E)))
y_redox_arr = np.zeros((len(overpot_arr), len(self.E)))
for i, overpot in tqdm(enumerate(overpot_arr), total=len(overpot_arr)):
y_fermi, y_redox, dE_Q, sigma, E_F_redox = self.compute_distributions(V_std_pot_arr,
reverse=reverse,
overpot=overpot,
add_info=add_info)
integrand = self.DOS * y_fermi * y_redox
k_HET[i] = integrate.simps(integrand, self.E) / self.sheet_area
dE_Q_arr[i] = dE_Q
sigma_arr[i] = sigma
E_F_redox_arr[i] = E_F_redox
y_fermi_arr[i] = y_fermi
y_redox_arr[i] = y_redox
return k_HET, dE_Q_arr, sigma_arr, E_F_redox_arr, y_fermi_arr, y_redox_arr
else:
raise ValueError('One and only one type of V_std_pot_arr and overpot arr must be Sequence. The other \
must be a Real number')
elif self.C_EDL == 'Cl':
if isinstance(V_std_pot_arr, typing.Iterable) and isinstance(overpot_arr, numbers.Real):
E_fermi = self.E
E_DOS_redox = self.E - overpot_arr
if reverse:
y_fermi = 1 - self.fermi_func(E_fermi, self.T)
y_redox = self.W_red(E_DOS_redox, self.T, self.l)
else:
y_fermi = self.fermi_func(E_fermi, self.T)
y_redox = self.W_ox(E_DOS_redox, self.T, self.l)
integrand = self.DOS * y_fermi * y_redox
k_HET = np.ones_like(V_std_pot_arr) * integrate.simps(integrand, self.E)
return k_HET
elif isinstance(overpot_arr, typing.Sequence) and isinstance(V_std_pot_arr, numbers.Real):
k_HET = np.zeros_like(overpot_arr)
for i, overpot in tqdm(enumerate(overpot_arr), total=len(overpot_arr)):
E_fermi = self.E
E_DOS_redox = self.E - overpot
if reverse:
y_fermi = 1 - self.fermi_func(E_fermi, self.T)
y_redox = self.W_red(E_DOS_redox, self.T, self.l)
else:
y_fermi = self.fermi_func(E_fermi, self.T)
y_redox = self.W_ox(E_DOS_redox, self.T, self.l)
integrand = self.DOS * y_fermi * y_redox
k_HET[i] = integrate.simps(integrand, self.E)
return k_HET
else:
raise ValueError('One and only one type of V_std_pot_arr and overpot arr must be Sequence. The other \
must be Real number')
elif self.C_EDL == 'Q':
if isinstance(V_std_pot_arr, typing.Iterable) and isinstance(overpot_arr, numbers.Real):
k_HET = np.zeros_like(V_std_pot_arr)
for i, V_std in tqdm(enumerate(V_std_pot_arr), total=len(V_std_pot_arr)):
E_F_redox = E_F_SHE_VAC - self.efermi - V_std + self.vacuum_lvl
E_DOS_redox = self.E - E_F_redox
E_fermi = E_DOS_redox - overpot_arr
if reverse:
y_fermi = 1 - self.fermi_func(E_fermi, self.T)
y_redox = self.W_red(E_DOS_redox, self.T, self.l)
else:
y_fermi = self.fermi_func(E_fermi, self.T)
y_redox = self.W_ox(E_DOS_redox, self.T, self.l)
integrand = self.DOS * y_fermi * y_redox
k_HET[i] = integrate.simps(integrand, self.E)
return k_HET
elif isinstance(overpot_arr, typing.Iterable) and isinstance(V_std_pot_arr, numbers.Real):
k_HET = np.zeros_like(overpot_arr)
for i, overpot in tqdm(enumerate(overpot_arr), total=len(overpot_arr)):
E_F_redox = E_F_SHE_VAC - self.efermi - V_std_pot_arr + self.vacuum_lvl - overpot
E_fermi = self.E - E_F_redox
E_DOS_redox = self.E - E_F_redox - overpot
if reverse:
y_fermi = 1 - self.fermi_func(E_fermi, self.T)
y_redox = self.W_red(E_DOS_redox, self.T, self.l)
else:
y_fermi = self.fermi_func(E_fermi, self.T)
y_redox = self.W_ox(E_DOS_redox, self.T, self.l)
integrand = self.DOS * y_fermi * y_redox
k_HET[i] = integrate.simps(integrand, self.E)
return k_HET
else:
raise ValueError('One and only one type of V_std_pot_arr and overpot arr must be Sequence. The other \
must be Real number')

View File

@@ -0,0 +1,514 @@
import numpy as np
from . import tip_types
from .GerischerMarkus import GM
import matplotlib.pyplot as plt
from scipy.interpolate import griddata
from mpl_toolkits.axes_grid1 import make_axes_locatable
from pymatgen.io.vasp import Procar
from echem.core import constants
from echem.io_data import vasp
class kHET:
"""
This class calculates heterogeneous electron transfer rate constant with spatial resolution
"""
# TODO update tips types
AVAILABLE_TIPS_TYPES = ['oxygen', 'IrCl6', 'RuNH3_6', 'RuNH3_6_NNN_plane', 'RuNH3_6_perpendicular',
'oxygen_parallel_x', 'oxygen_parallel_y']
def __init__(self, working_folder=''):
if working_folder == '':
working_folder = '.'
# TODO think about better than get class objects for outcar and poscar info
self.outcar = vasp.Outcar.from_file(working_folder + '/OUTCAR')
self.poscar = vasp.Poscar.from_file(working_folder + '/POSCAR')
self.procar = Procar(working_folder + '/PROCAR')
self.working_folder = working_folder
self.path_to_data = working_folder + '/Saved_data'
self.wavecar = None
self.C_EDL = None
self.T = None
self.lambda_ = None
self.sheet_area = None
self.V_std = None
self.overpot = None
self.dE_Q = None
self.kb_array = None
self.E = None
def set_parameters(self, T, lambda_, overpot=0, V_std=None, C_EDL=None, dE_Q=None, linear_constant=26, threshold_value=1e-5, shifted_DOS='all'):
"""
:param C_EDL: float
Capacitance of electric double layer (microF/cm^2)
:param T: int, float
Temperature. It is used in computing Fermi function and distribution function of redox system states
:param lambda_: float
Reorganization energy in eV
:param V_std: float
Standart potential of the redox couple (Volts)
:param overpot: float
Overpotential (Volts). It shifts the electrode Fermi energy to -|e|*overpot
:return:
"""
#TODO check get_DOS parameters
self.E, DOS = self.outcar.get_DOS(zero_at_fermi=True, smearing='Gaussian', sigma=0.1, dE = 0.01)
self.T = T
self.lambda_ = lambda_
self.overpot = overpot
self.sheet_area = self._get_sheet_area() # Area of investigated surface(XY) in cm^2
self.linear_constant = linear_constant
if V_std == None or C_EDL == None:
if dE_Q == None:
raise ValueError ("Set either V_std and C_EDL or dE_Q parameter")
else:
self.dE_Q = dE_Q
else:
self.V_std = V_std
self.C_EDL = C_EDL
self.dE_Q = self._calculate_dE_Q()
if shifted_DOS == 'all':
self.are_frozen_states = False
self.kb_array = self._get_kb_array(threshold_value)
else:
self.are_frozen_states = True
self.kb_array = self._get_kb_array(threshold_value, shifted_DOS)
if self.kb_array == []:
print('Error! kb_array is empty. Try to decrease threshold_value')
def load_wavecar(self):
self.wavecar = vasp.Wavecar.from_file(self.working_folder + '/WAVECAR', self.kb_array)
def plot_distributions(self):
#TODO make it
pass
def _get_atom_localization(self, kpoint, band, target_atom_types):
for key in self.procar.data.keys():
procar_data = self.procar.data[key]
list_of_ions = []
atomnames = self.poscar.structure.species
orbital_names = self.procar.orbitals
for i, name in enumerate(atomnames):
if name in target_atom_types:
list_of_ions.append(i)
list_of_orbitals = [i for i in range(len(orbital_names))]
localization = 0
for ion in list_of_ions:
for orb in list_of_orbitals:
localization += procar_data[kpoint - 1][band - 1][ion][orb]
return localization
def _get_kb_array(self, threshold_value, shifted_DOS='all', threshold_for_localization_to_be_shifted=0.4):
if shifted_DOS=='all':
fermi_distribution = GM.fermi_func(self.E - self.dE_Q, self.T)
W_ox = GM.W_ox(self.E - self.dE_Q - self.overpot, self.T, self.lambda_)
E_satisfy_mask = W_ox * fermi_distribution > np.max(W_ox) * np.max(fermi_distribution) * threshold_value
list_of_E_indices = [i for i, E in enumerate(E_satisfy_mask) if E]
min_E_ind = min(list_of_E_indices)
max_E_ind = max(list_of_E_indices)
Erange = [self.E[min_E_ind], self.E[max_E_ind]]
kb_array = []
for band in range(1, self.outcar.nbands + 1):
for kpoint in range(1, self.outcar.nkpts + 1):
energy = self.outcar.eigenvalues[0][kpoint - 1][band - 1] - self.outcar.efermi
if energy >= Erange[0] and energy < Erange[1]:
kb_array.append([kpoint, band])
else:
fermi_distribution_shifted = GM.fermi_func(self.E - self.dE_Q, self.T)
W_ox_shifted = GM.W_ox(self.E - self.dE_Q - self.overpot, self.T, self.lambda_)
E_satisfy_mask_shifted = W_ox_shifted * fermi_distribution_shifted > np.max(W_ox_shifted) * np.max(
fermi_distribution_shifted) * threshold_value
list_of_E_indices_shifted = [i for i, E in enumerate(E_satisfy_mask_shifted) if E]
min_E_ind_shifted = min(list_of_E_indices_shifted)
max_E_ind_shifted = max(list_of_E_indices_shifted)
Erange_shifted = [self.E[min_E_ind_shifted], self.E[max_E_ind_shifted]]
fermi_distribution_frozen = GM.fermi_func(self.E - 0, self.T)
W_ox_frozen = GM.W_ox(self.E - 0 - self.overpot, self.T, self.lambda_)
E_satisfy_mask_frozen = W_ox_frozen * fermi_distribution_frozen > np.max(W_ox_frozen) * np.max(
fermi_distribution_frozen) * threshold_value
list_of_E_indices_frozen = [i for i, E in enumerate(E_satisfy_mask_frozen) if E]
min_E_ind_frozen = min(list_of_E_indices_frozen)
max_E_ind_frozen = max(list_of_E_indices_frozen)
Erange_frozen = [self.E[min_E_ind_frozen], self.E[max_E_ind_frozen]]
kb_array = []
self.frozen_mask = []
for band in range(1, self.outcar.nbands + 1):
for kpoint in range(1, self.outcar.nkpts + 1):
energy = self.outcar.eigenvalues[0][kpoint - 1][band - 1] - self.outcar.efermi
if energy >= Erange_frozen[0] and energy < Erange_frozen[1]:
if self._get_atom_localization(kpoint, band,
target_atom_types=shifted_DOS) < threshold_for_localization_to_be_shifted:
kb_array.append([kpoint, band])
self.frozen_mask.append(1)
if energy >= Erange_shifted[0] and energy < Erange_shifted[1]:
if self._get_atom_localization(kpoint, band,
target_atom_types=shifted_DOS) >= threshold_for_localization_to_be_shifted:
kb_array.append([kpoint, band])
self.frozen_mask.append(0)
return kb_array
def calculate_kHET_spatial(self, tip_type='s', z_pos=None, from_center=False, cutoff_in_Angstroms=5, all_z=False, dim='2D', shifted_separately=False):
xlen, ylen, zlen = np.shape(self.wavecar.wavefunctions[0])
if dim == '2D':
k_HET_ = np.zeros((xlen, ylen))
if z_pos == None:
raise ValueError('For dim=2D z_pos is obligatory parameter')
b1, b2, b3 = self.poscar.structure.lattice
bn1 = b1 / xlen
bn2 = b2 / ylen
bn3 = b3 / zlen
if b3[0] != 0.0 or b3[1] != 0.0:
print("WARNING! You z_vector is not perpendicular to XY plane, Check calculate_kHET_spatial_2D function")
if from_center:
z = int(zlen // 2 + z_pos // np.linalg.norm(bn3))
else:
z = int(z_pos // np.linalg.norm(bn3))
real_z_position = z_pos // np.linalg.norm(bn3) * np.linalg.norm(bn3)
print(f"Real z_pos of tip = {real_z_position}")
if any(tip_type == kw for kw in self.AVAILABLE_TIPS_TYPES):
cutoff = int(cutoff_in_Angstroms // np.linalg.norm(bn1))
if cutoff > xlen // 2 or cutoff > ylen // 2:
print("ERROR: Cutoff should be less than 1/2 of each dimension of the cell. "
"Try to reduce cutoff. Otherwise, result could be unpredictible")
acc_orbitals = self.generate_acceptor_orbitals(tip_type, (xlen, ylen, zlen), z_shift=z)
if all_z == True:
zmin = 0
zmax = zlen
elif z - cutoff >= 0 and z + cutoff <= zlen:
zmin = z - cutoff
zmax = z + cutoff
else:
print("Can't reduce integrating area in z dimention. "
"Will calculate overlapping for all z. You can ignore this message "
"if you don't care about efficiency")
zmin = 0
zmax = zlen
for i, kb in enumerate(self.wavecar.kb_array):
kpoint, band = kb[0], kb[1]
energy = self.outcar.eigenvalues[0][kpoint - 1][band - 1] - self.outcar.efermi
weight = self.outcar.weights[0][kpoint - 1]
f_fermi = GM.fermi_func(energy - self.dE_Q, self.T)
w_redox = GM.W_ox(energy - self.dE_Q - self.overpot, self.T, self.lambda_)
overlap_integrals_squared = self._get_overlap_integrals_squared(self.wavecar.wavefunctions[i], cutoff,
acc_orbitals, zmin, zmax)
# TODO check eq below
matrix_elements_squared = overlap_integrals_squared * self.linear_constant * np.linalg.norm(bn3) ** 3
k_HET_ += matrix_elements_squared * f_fermi * w_redox * weight
elif tip_type == 's':
for i, kb in enumerate(self.wavecar.kb_array):
kpoint, band = kb[0], kb[1]
energy = self.outcar.eigenvalues[0][kpoint - 1][band - 1] - self.outcar.efermi
weight = self.outcar.weights[0][kpoint - 1]
f_fermi = GM.fermi_func(energy - self.dE_Q, self.T)
w_redox = GM.W_ox(energy - self.dE_Q - self.overpot, self.T, self.lambda_)
matrix_elements_squared = np.abs(self.wavecar.wavefunctions[i][:, :, z]) ** 2
k_HET_ += matrix_elements_squared * f_fermi * w_redox * weight
elif tip_type == 'pz':
for i, kb in enumerate(self.wavecar.kb_array):
kpoint, band = kb[0], kb[1]
energy = self.outcar.eigenvalues[0][kpoint - 1][band - 1] - self.outcar.efermi
weight = self.outcar.weights[0][kpoint - 1]
f_fermi = GM.fermi_func(energy - self.dE_Q, self.T)
w_redox = GM.W_ox(energy - self.dE_Q - self.overpot, self.T, self.lambda_)
wf_grad_z = np.gradient(self.wavecar.wavefunctions[i], axis=2)
matrix_elements_squared = np.abs(wf_grad_z[:, :, z]) ** 2
k_HET_ += matrix_elements_squared * f_fermi * w_redox * weight
else:
print(f"Try another tip type, for now {tip_type} is unavailiable")
elif dim == '3D':
k_HET_ = np.zeros((xlen, ylen, zlen))
if tip_type == 's':
if self.are_frozen_states:
if shifted_separately:
k_HET_shifted = np.zeros((xlen, ylen, zlen))
for i, kb in enumerate(self.wavecar.kb_array):
kpoint, band = kb[0], kb[1]
energy = self.outcar.eigenvalues[0][kpoint - 1][band - 1] - self.outcar.efermi
weight = self.outcar.weights[kpoint - 1]
if self.frozen_mask[i] == 1:
f_fermi = GM.fermi_func(energy, self.T)
w_redox = GM.W_ox(energy - self.overpot, self.T, self.lambda_)
matrix_elements_squared = np.abs(self.wavecar.wavefunctions[i]) ** 2
k_HET_ += matrix_elements_squared * f_fermi * w_redox * weight
else:
f_fermi = GM.fermi_func(energy - self.dE_Q, self.T)
w_redox = GM.W_ox(energy - self.dE_Q - self.overpot, self.T, self.lambda_)
matrix_elements_squared = np.abs(self.wavecar.wavefunctions[i]) ** 2
k_HET_ += matrix_elements_squared * f_fermi * w_redox * weight
try:
k_HET_shifted += matrix_elements_squared * f_fermi * w_redox * weight
except:
pass
else:
for i, kb in enumerate(self.wavecar.kb_array):
kpoint, band = kb[0], kb[1]
energy = self.outcar.eigenvalues[0][kpoint - 1][band - 1] - self.outcar.efermi
weight = self.outcar.weights[0][kpoint - 1]
f_fermi = GM.fermi_func(energy - self.dE_Q, self.T)
w_redox = GM.W_ox(energy - self.dE_Q - self.overpot, self.T, self.lambda_)
matrix_elements_squared = np.abs(self.wavecar.wavefunctions[i]) ** 2
k_HET_ += matrix_elements_squared * f_fermi * w_redox * weight
elif tip_type == 'pz':
if self.are_frozen_states:
if shifted_separately:
k_HET_shifted = np.zeros((xlen, ylen, zlen))
for i, kb in enumerate(self.wavecar.kb_array):
kpoint, band = kb[0], kb[1]
energy = self.outcar.eigenvalues[0][kpoint - 1][band - 1] - self.outcar.efermi
weight = self.outcar.weights[0][kpoint - 1]
if self.frozen_mask[i] == 1:
f_fermi = GM.fermi_func(energy, self.T)
w_redox = GM.W_ox(energy - self.overpot, self.T, self.lambda_)
wf_grad_z = np.gradient(self.wavecar.wavefunctions[i], axis=2)
matrix_elements_squared = np.abs(wf_grad_z) ** 2
k_HET_ += matrix_elements_squared * f_fermi * w_redox * weight
else:
f_fermi = GM.fermi_func(energy - self.dE_Q, self.T)
w_redox = GM.W_ox(energy - self.dE_Q - self.overpot, self.T, self.lambda_)
wf_grad_z = np.gradient(self.wavecar.wavefunctions[i], axis=2)
matrix_elements_squared = np.abs(wf_grad_z) ** 2
k_HET_ += matrix_elements_squared * f_fermi * w_redox * weight
try:
k_HET_shifted += matrix_elements_squared * f_fermi * w_redox * weight
except:
pass
else:
for i, kb in enumerate(self.wavecar.kb_array):
kpoint, band = kb[0], kb[1]
energy = self.outcar.eigenvalues[0][kpoint - 1][band - 1] - self.outcar.efermi
weight = self.outcar.weights[0][kpoint - 1]
f_fermi = GM.fermi_func(energy - self.dE_Q, self.T)
w_redox = GM.W_ox(energy - self.dE_Q - self.overpot, self.T, self.lambda_)
wf_grad_z = np.gradient(self.wavecar.wavefunctions[i], axis=2)
matrix_elements_squared = np.abs(wf_grad_z) ** 2
k_HET_ += matrix_elements_squared * f_fermi * w_redox * weight
else:
raise ValueError("dim should be 3D or 2D")
# TODO: check THIS below
#k_HET_ *= 2 * np.pi / constants.PLANCK_CONSTANT
if shifted_separately:
return k_HET_, k_HET_shifted
else:
return k_HET_
def plot_2D(self, func, show=True, save=False, filename='fig.png', method='linear', logscale=None):
"""
Function for plotting 2D images
"""
xlen, ylen = np.shape(func)
b1, b2, b3 = self.poscar.structure.lattice
bn1 = b1 / xlen
bn2 = b2 / ylen
R = []
for j in range(ylen):
for i in range(xlen):
R.append(i * bn1 + j * bn2)
X = np.array([x[0] for x in R])
Y = np.array([x[1] for x in R])
Z = func.transpose().flatten()
xi = np.linspace(X.min(), X.max(), 1000)
yi = np.linspace(Y.min(), Y.max(), 1000)
zi = griddata((X, Y), Z, (xi[None, :], yi[:, None]), method=method)
if logscale == None:
plt.contourf(xi, yi, zi, 500, cmap=plt.cm.rainbow)
else:
zi = np.log10(zi)
min, max, step = logscale
levels = np.arange(min, max, step)
plt.contourf(xi, yi, zi, 500, cmap=plt.cm.rainbow, levels=levels)
ax = plt.gca()
ax.set_aspect('equal')
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="7%", pad=0.1)
cbar = plt.colorbar(cax=cax)
if show == True:
plt.show()
if save == True:
plt.savefig(filename, dpi=300)
plt.close()
def _calculate_dE_Q(self):
"""
This function calls GerischerMarcus module with GM class to calculate distribution of redox species
and Fermi-Dirac distribution according to Gerischer-Marcus formalism
:return:
"""
gm = GM(path_to_data=self.path_to_data)
gm.set_params(self.C_EDL, self.T, self.lambda_, self.sheet_area)
return gm.compute_distributions(self.V_std, overpot=self.overpot, add_info=True)[2]
def _get_sheet_area(self):
"""
Inner function to calculate sheet_area (XY plane) in cm^2
"""
b1, b2, b3 = self.poscar.structure.lattice
return np.linalg.norm(np.cross(b1, b2))*1e-16
def generate_acceptor_orbitals(self, orb_type, shape, z_shift=0, x_shift=0, y_shift=0):
"""
This is generator of acceptor orbitals using tip_types.py module
:return:
"""
bohr_radius = 0.529177 #TODO Check
b1, b2, b3 = self.poscar.structure.lattice
bn1 = b1 / shape[0]
bn2 = b2 / shape[1]
bn3 = b3 / shape[2]
basis = np.array([bn1, bn2, bn3])
transition_matrix = basis.transpose()
number_of_orbitals = len(tip_types.orbitals(orb_type))
acc_orbitals = []
for i in range(number_of_orbitals):
acc_orbitals.append(np.zeros(shape))
for i in range(shape[0]):
for j in range(shape[1]):
for k in range(shape[2]):
if i - x_shift >= shape[0] / 2:
x = i - shape[0] - x_shift
else:
x = i - x_shift
if j - y_shift >= shape[1] / 2:
y = j - shape[1] - y_shift
else:
y = j - y_shift
if k - z_shift >= shape[2] / 2:
z = k - shape[2] - z_shift
else:
z = k - z_shift
r = np.dot(transition_matrix, np.array([x, y, z])) / bohr_radius
for o, orbital in enumerate(acc_orbitals):
orbital[i][j][k] = tip_types.orbitals(orb_type)[o](r)
return acc_orbitals
def _get_overlap_integrals_squared(self, wf, cutoff, acc_orbitals, zmin, zmax):
xlen, ylen, zlen = np.shape(wf)
overlap_integrals_squared = np.zeros((xlen, ylen))
for i in range(xlen):
if i - cutoff < 0:
wf_rolled_x = np.roll(wf, cutoff, axis=0)
orb_rolled_x = []
for orbital in acc_orbitals:
orb_rolled_x.append(np.roll(orbital, i + cutoff, axis=0))
xmin = i
xmax = i + cutoff * 2
elif i - cutoff >= 0 and i + cutoff <= xlen:
wf_rolled_x = np.copy(wf)
orb_rolled_x = []
for orbital in acc_orbitals:
orb_rolled_x.append(np.roll(orbital, i, axis = 0))
xmin = i - cutoff
xmax = i + cutoff
elif i + cutoff > xlen:
wf_rolled_x = np.roll(wf, -cutoff, axis=0)
orb_rolled_x = []
for orbital in acc_orbitals:
orb_rolled_x.append(np.roll(orbital, i - cutoff, axis=0))
xmin = i - cutoff * 2
xmax = i
else:
print(f"ERROR: for i = {i} something with rolling arrays along x goes wrong")
for j in range(ylen):
if j - cutoff < 0:
wf_rolled = np.roll(wf_rolled_x, cutoff, axis=1)
orb_rolled = []
for orbital in orb_rolled_x:
orb_rolled.append(np.roll(orbital, j + cutoff, axis=1))
ymin = j
ymax = j + cutoff * 2
elif j - cutoff >= 0 and j + cutoff <= ylen:
wf_rolled = np.copy(wf_rolled_x)
orb_rolled = []
for orbital in orb_rolled_x:
orb_rolled.append(np.roll(orbital, j, axis=1))
ymin = j - cutoff
ymax = j + cutoff
elif j + cutoff > ylen:
wf_rolled = np.roll(wf_rolled_x, -cutoff, axis=1)
orb_rolled = []
for orbital in orb_rolled_x:
orb_rolled.append(np.roll(orbital, j - cutoff, axis=1))
ymin = j - cutoff * 2
ymax = j
else:
print(f"ERROR: for i = {i} something with rolling arrays along y goes wrong")
integral = []
for orbital in orb_rolled:
integral.append(np.linalg.norm(wf_rolled[xmin:xmax, ymin:ymax, zmin:zmax]*\
orbital[xmin:xmax, ymin:ymax, zmin:zmax]))
overlap_integrals_squared[i][j] = max(integral) ** 2
return overlap_integrals_squared
def save_as_cube(self, array, name, dir):
# TODO: rewrite
import os
if not os.path.exists(dir):
print(f"Directory {dir} does not exist. Creating directory")
os.mkdir(dir)
shape = np.shape(array)
with open(self.working_folder + '/POSCAR') as inf: #TODO get data from poscar class would be better
lines = inf.readlines()
natoms = sum(map(int, lines[6].strip().split()))
atomtypes = lines[5].strip().split()
numbers_of_atoms = list(map(int, lines[6].strip().split()))
type_of_i_atom = []
basis = []
for i in [2, 3, 4]:
vector = list(map(float, lines[i].strip().split()))
basis.append(vector)
basis = np.array(basis)
for i, number in enumerate(numbers_of_atoms):
for j in range(number):
type_of_i_atom.append(atomtypes[i])
with open(dir + '/' + name + '.cube', 'w') as ouf:
ouf.write(' This file is generated using stm.py module\n')
ouf.write(' Good luck\n')
ouf.write(' ' + str(natoms) + '\t0.000\t0.000\t0.000\n')
ouf.write(' ' + str(-shape[0]) + lines[2])
ouf.write(' ' + str(-shape[1]) + lines[3])
ouf.write(' ' + str(-shape[2]) + lines[4])
for i, line in enumerate(lines[8:natoms + 8]):
coordinate = np.array(list(map(float, line.strip().split())))
if lines[7].strip() == 'Direct':
coordinate = coordinate.dot(basis)
coordinate *= constants.BOHR_RADIUS
elif lines[7].strip() == 'Cartesian':
coordinate *= constants.BOHR_RADIUS
else:
print('WARNING!!! Cannot read POSCAR correctly')
atomtype = type_of_i_atom[i]
atomnumber = list(constants.ElemNum2Name.keys())[list(constants.ElemNum2Name.values()).index(atomtype)]
ouf.write(
' ' + str(atomnumber) + '\t0.00000\t' + str(coordinate[0]) + '\t' + str(coordinate[1]) + '\t' + str(
coordinate[2]) + '\n')
counter = 0
for i in range(shape[0]):
for j in range(shape[1]):
for k in range(shape[2]):
ouf.write(str('%.5E' % array[i][j][k]) + ' ')
counter += 1
if counter % 6 == 0:
ouf.write('\n ')
ouf.write('\n ')
print(f"File {name} saved")

View File

@@ -0,0 +1,12 @@
class Ldos():
"""
The aim of this class is to produce LDOS images (2d or 3d) in different energy ranges
"""
# TODO:
# 1) One should get all necessary data from WAVECAR (process WAVECAR with energy step dE and
# produce electron denstites
#
#

View File

@@ -0,0 +1,571 @@
from monty.re import regrep
from pymatgen.io.vasp.outputs import Locpot
from pymatgen.io.vasp.outputs import Procar
from electrochemistry.core.vaspwfc_p3 import vaspwfc
import numpy as np
import multiprocessing as mp
import os
import sys
import time
import matplotlib.pyplot as plt
from scipy.ndimage.filters import gaussian_filter1d
class Preprocessing():
"""
This class is for basic preprocessing VASP output files (WAVECAR, OUTCAR, LOCPOT)
Main goal for this class is to extract necessary date from VASP files and save data to .npy files:
efermi - Fermi level
eigenvalues - eigenvalues for each kpoint and band
ndands - number of bands
nkpts - number of kpoints
occupations - occupations for each kpoint and band
weights - weights for each kpoint
WF_ijke - "effective" wavefunction 3D grids for each small energy range from E to E+dE
"""
def __init__(self, working_folder='', print_out=False, dir_to_save=None):
"""
Initialization of variables
"""
self.print_out = print_out
self.working_folder = working_folder
if dir_to_save == None:
self.dir_to_save = working_folder+'/Saved_data'
else:
self.dir_to_save = dir_to_save
self.efermi = None
self.nkpts = None
self.nbands = None
self.weights = None
self.eigenvalues = None
self.occupations = None
self.vacuum_lvl = None
self.procar_data = None
self.ion_names = None
self.orbital_names = None
self.wavecar_path = working_folder+'/WAVECAR'
self.outcar_path = working_folder+'/OUTCAR'
self.poscar_path = working_folder+'/POSCAR'
self.procar_path = working_folder+'/PROCAR'
self.locpot_path = working_folder+'/LOCPOT'
def get_wavefunction(self, mesh_shape, energies, kb_array, weights, arr_real, arr_imag, arr_cd, done_number, n_grid, wavecar_path=None):
"""
This function is inner function for processes.
It extracts wavefunction from WAVECAR using vaspwfc library (see. https://github.com/QijingZheng/VaspBandUnfolding)
:param mesh_shape: shape of wavefucntion mesh (tuple of 3 int)
:param energies: list of energies for current process to be done (list of int)
:param kb_array: array of kpoints and bands which lay in current energy range (np.array of tuples (k,b) depends on energies)
:param weights: np.array of float64 containes weights of different k-points (np.array of float depends on energies)
:param arr_real: shared memory float64 array to put real part of wavefunction
:param arr_imag: shared memory float64 array to put imaginary part of wavefunction
:param done_number: shared memory int to controll number of done energy ranges
:param wavecar_path: path to WAVECAR file
:param n_grid: float parameter (multipicator) to increase mesh density (1.0 - standart grid, higher values e.g. 1.5, 2.0 - to inrease quality of grid)
:return:
"""
if wavecar_path == None:
wavecar_path = self.wavecar_path
t_p = time.time()
wavecar = vaspwfc(wavecar_path)
#print("Reading WAVECAR takes", time.time()-t_p, " sec")
#sys.stdout.flush()
for e in energies:
#t = time.time()
n = mesh_shape[0]*mesh_shape[1]*mesh_shape[2]
start = e * n
kb_array_for_e = kb_array[e]
WF_for_current_Energy_real = np.zeros(mesh_shape)
WF_for_current_Energy_imag = np.zeros(mesh_shape)
CD_for_current_Energy = np.zeros(mesh_shape)
for kb in kb_array_for_e:
kpoint = kb[0]
band = kb[1]
wf = wavecar.wfc_r(ikpt=kpoint, iband=band, ngrid=wavecar._ngrid * n_grid)
phi_real = np.real(wf)
phi_imag = np.imag(wf)
phi_squared = np.abs(wf)**2
#print ("Wavefunction done from process:", e, "; kpoint = ", kpoint, "; band = ", band)
#sys.stdout.flush()
WF_for_current_Energy_real += phi_real * np.sqrt(weights[kpoint - 1])
WF_for_current_Energy_imag += phi_imag * np.sqrt(weights[kpoint - 1])
CD_for_current_Energy += phi_squared * weights[kpoint - 1]
WF_for_current_Energy_1D_real = np.reshape(WF_for_current_Energy_real, n)
WF_for_current_Energy_1D_imag = np.reshape(WF_for_current_Energy_imag, n)
CD_for_current_Energy_1D = np.reshape(CD_for_current_Energy, n)
for ii in range(n):
arr_real[ii + start] = float(WF_for_current_Energy_1D_real[ii])
arr_imag[ii + start] = float(WF_for_current_Energy_1D_imag[ii])
arr_cd[ii + start] = float(CD_for_current_Energy_1D[ii])
#print ("Energy ", e, " finished, it takes: ", time.time()-t, " sec")
#sys.stdout.flush()
done_number.value += 1
if self.print_out == True:
print(done_number.value, " energies are done")
sys.stdout.flush()
def process_WAVECAR(self, Erange_from_fermi, dE, wavecar_path=None, dir_to_save=None, n_grid=1.0):
"""
This function is based on vaspwfc class https://github.com/QijingZheng/VaspBandUnfolding
This function process WAVECAR file obtained with VASP. It saves 4D np.array WF_ijke.npy
which containes spacially resolved _complex128 effective wavefunctions for each energy range [E, E+dE]
'Effective' means that we sum wavefunctions with close energies.
UPDATE: The function saves also dictionary WF_data.npy, which contains two keys: 'energy_range','dE'
:param Erange_from_fermi: Energy range for wich we extract wavefunction (tuple or list of float)
:param dE: Energy step (float)
:param wavecar_path: path to WAVECAR file (string)
:param dir_to_save: directory to save WF_ijke.npy file (string)
:param n_grid: float parameter (multipicator) to increase mesh density (1.0 - standart grid, higher values e.g. 1.5, 2.0 - to inrease quality of grid)
:return: nothing
"""
if wavecar_path == None:
wavecar_path = self.wavecar_path
if dir_to_save == None:
dir_to_save = self.dir_to_save
for var in ['efermi', 'nkpts', 'nbands', 'eigenvalues', 'weights']:
self.check_existance(var)
Erange = [round(Erange_from_fermi[0] + self.efermi, 5), round(Erange_from_fermi[1] + self.efermi, 5)]
energies_number = int((Erange[1] - Erange[0]) / dE)
kb_array = [[] for i in range(energies_number)]
wavecar = vaspwfc(wavecar_path)
wf = wavecar.wfc_r(ikpt=1, iband=1, ngrid=wavecar._ngrid * n_grid)
mesh_shape = np.shape(wf)
n = mesh_shape[0]*mesh_shape[1]*mesh_shape[2]
for band in range(1, self.nbands+1):
for kpoint in range(1, self.nkpts+1):
energy = self.eigenvalues[kpoint - 1][band - 1]
if energy >= Erange[0] and energy < Erange[1]:
e = int((energy - Erange[0]) / dE)
kb_array[e].append([kpoint, band])
t_arr = time.time()
arr_real = mp.Array('f', [0.0] * n * energies_number)
arr_imag = mp.Array('f', [0.0] * n * energies_number)
arr_cd = mp.Array('f', [0.0] * n * energies_number)
done_number = mp.Value('i', 0)
if self.print_out == True:
print("Array Created, it takes: ", time.time() - t_arr, " sec")
processes = []
if self.print_out == True:
print("Total Number Of Energies = ", energies_number)
numCPU = mp.cpu_count()
Energies_per_CPU = energies_number // numCPU + 1
for i in range(numCPU):
if (i + 1) * Energies_per_CPU > energies_number:
energies = np.arange(i * Energies_per_CPU, energies_number)
else:
energies = np.arange(i * Energies_per_CPU, (i + 1) * Energies_per_CPU)
p = mp.Process(target=self.get_wavefunction, args=(mesh_shape, energies, kb_array, self.weights, arr_real, arr_imag, arr_cd, done_number, n_grid, wavecar_path))
processes.append(p)
if self.print_out == True:
print(numCPU, " CPU Available")
for step in range(len(processes) // numCPU + 1):
for i in range(step * numCPU, (step + 1) * numCPU):
try:
processes[i].start()
except:
pass
for i in range(step * numCPU, (step + 1) * numCPU):
try:
processes[i].join()
except:
pass
if self.print_out == True:
print("All energies DONE! Start wavefunction reshaping")
arr = np.zeros(n * energies_number, dtype=np.complex_)
arr.real = arr_real
arr.imag = arr_imag
wave_functions = np.reshape(arr, (energies_number,) + mesh_shape)
wave_functions = np.moveaxis(wave_functions, 0, -1)
charge_densities = np.reshape(arr_cd, (energies_number,) + mesh_shape)
charge_densities = np.moveaxis(charge_densities, 0, -1)
WF_data = {'energy_range':Erange_from_fermi, 'dE':dE}
np.save(dir_to_save+'/WF_ijke', wave_functions)
np.save(dir_to_save+'/CD_ijke', charge_densities)
if self.print_out == True:
print(dir_to_save+'/WF_ijke.npy Saved')
print(dir_to_save+'/CD_ijke.npy Saved')
np.save(dir_to_save+'/WF_data', WF_data)
if self.print_out == True:
print(dir_to_save+'/WF_data.npy Saved')
def process_OUTCAR(self, outcar_path=None, dir_to_save=None, optimization=False):
"""
process OUTCAR file obtained from VASP
get following variables:
self.nkpts - number of k-points (int)
self.efermi - Fermi level (float)
self.nbands - number of bands (int)
self.eigenvalues - 2D np.array, eigenvalues[i][j] contains energy for i k-point and j band
self.occupations - 2D np.array, occupations[i][j] contains occupation for i k-point and j band
:param outcar_path: path to OUTCAR file
:return: nothing
"""
if outcar_path == None:
outcar_path = self.outcar_path
if dir_to_save == None:
dir_to_save = self.dir_to_save
patterns = {'nkpts': r'Found\s+(\d+)\s+irreducible\sk-points',
'weights':'Following reciprocal coordinates:',
'efermi': 'E-fermi\s:\s+([-.\d]+)',
'kpoints': r'k-point\s+(\d+)\s:\s+[-.\d]+\s+[-.\d]+\s+[-.\d]+\n'}
matches = regrep(outcar_path, patterns)
self.nkpts = int(matches['nkpts'][0][0][0])
if optimization:
self.efermi = []
efermi_data = np.array(matches['efermi'])[...,0]
number_of_ionic_steps = len(efermi_data)
for i in range(number_of_ionic_steps):
self.efermi.append(float(efermi_data[i][0]))
else:
self.efermi = float(matches['efermi'][0][0][0])
self.nbands = int(matches['kpoints'][1][1] - matches['kpoints'][0][1]-3)
self.eigenvalues = []
self.occupations = []
self.weights = []
with open(outcar_path) as file:
lines = file.readlines()
for i in range(self.nkpts):
self.weights.append(float(lines[matches['weights'][0][1]+i+2].split()[3]))
if optimization:
for step in range(number_of_ionic_steps):
self.eigenvalues.append([])
self.occupations.append([])
for kpoint in range(self.nkpts):
self.eigenvalues[step].append([])
self.occupations[step].append([])
startline = matches['kpoints'][kpoint+(step*self.nkpts)][1] + 2
for i in range(startline, startline + self.nbands):
self.eigenvalues[step][kpoint].append(float(lines[i].split()[1]))
self.occupations[step][kpoint].append(float(lines[i].split()[2]))
else:
for kpoint in range(self.nkpts):
self.eigenvalues.append([])
self.occupations.append([])
startline = matches['kpoints'][kpoint][1]+2
for i in range(startline, startline + self.nbands):
self.eigenvalues[kpoint].append(float(lines[i].split()[1]))
self.occupations[kpoint].append(float(lines[i].split()[2]))
self.eigenvalues = np.array(self.eigenvalues)
self.occupations = np.array(self.occupations)
self.weights = np.array(self.weights)
self.weights /= np.sum(self.weights)
for var in ['efermi', 'nkpts', 'nbands', 'weights', 'eigenvalues', 'occupations']:
self.save(var, dir_to_save)
def process_PROCAR(self, procar_path=None, poscar_path=None, dir_to_save=None):
"""
This function process PROCAR file obtained by VASP using pymatgen.io_data.vasp.outputs.Procar class
and saves ion_names, orbital_names and data array
data array contains projections in the following form: data[kpoint][band][ion_number][orbital_number]
All numberings start from 0
:param procar_path: path to PROCAR file
:param poscar_path: path to POSCAR file
:param dir_to_save: directory to save data
:return:
"""
if procar_path == None:
procar_path = self.procar_path
if poscar_path == None:
poscar_path = self.poscar_path
if dir_to_save == None:
dir_to_save = self.dir_to_save
procar=Procar(procar_path)
for key in procar.data.keys():
self.procar_data = procar.data[key]
self.orbital_names = procar.orbitals
self.ion_names = self._get_atom_types(poscar_path=poscar_path)
for var in ['procar_data', 'orbital_names', 'ion_names']:
self.save(var, dir_to_save)
def _get_atom_types(self, poscar_path=None):
"""
Inner function to obtain list of atom types from POSCAR
:param file_path: path to POSCAR file
:return: atom_types 1D array with atom_types. Index from 0
"""
if poscar_path == None:
poscar_path = self.poscar_path
with open(poscar_path) as inf:
lines = inf.readlines()
ion_types = lines[5].strip().split()
nions = map(int, lines[6].strip().split())
atom_types=[]
for i, number in enumerate(nions):
for j in range(number):
atom_types.append(ion_types[i])
return atom_types
def process_LOCPOT(self, locpot_path=None, dir_to_save=None):
"""
This function process LOCPOT file obtained by VASP
:param file_path: path to LOCPOT file
:param dir_to_save: path to directory to save vacuum_lvl
:return: nothing
"""
if locpot_path == None:
locpot_path = self.locpot_path
if dir_to_save == None:
dir_to_save = self.dir_to_save
locpot = Locpot.from_file(locpot_path)
avr = locpot.get_average_along_axis(2)
self.vacuum_lvl = np.max(avr)
self.save('vacuum_lvl', dir_to_save)
def save(self, variable='all', dir=None):
"""
This function saves variables in .npy files
:param variable: desired variable
:param dir: directory name to save file
:return: nothing
"""
if dir == None:
dir = self.dir_to_save
if variable == 'all':
for var in ['efermi', 'nkpts', 'nbands', 'weights', 'eigenvalues', 'occupations', 'vacuum_lvl']:
self.save(var)
else:
if not os.path.exists(dir):
if self.print_out == True:
print('Directory ', dir, ' does not exist. Creating directory')
os.mkdir(dir)
np.save(dir+'/'+variable+'.npy', getattr(self, variable))
if self.print_out == True:
print('Variable ', variable, ' saved to directory ', dir)
def load(self, variable='all', dir=None):
"""
This function loads variables from .npy files to class variables
:param variable: desired variable
:param dir: directory from which load files
:return: nothing
"""
if dir == None:
dir = self.dir_to_save
if variable == 'all':
for var in ['efermi', 'nkpts', 'nbands', 'weights', 'eigenvalues', 'occupations', 'vacuum_lvl']:
self.load(var)
else:
setattr(self, variable, np.load(dir+'/'+str(variable)+'.npy'))
if self.print_out == True:
print('Variable ', variable, ' loaded')
def check_existance(self, variable='all', dir=None):
"""
This function checks whether desires variable is not None and if necessary load it from file or process VASP data
:param variable: desired variable
:param dir: directory in which check .npy saved data
:return: nothing
"""
if dir == None:
dir = self.dir_to_save
if variable == 'all':
for var in ['efermi', 'nkpts', 'nbands', 'weights', 'eigenvalues', 'occupations', 'vacuum_lvl']:
self.check_existance(var)
else:
if getattr(self, variable) is None:
try:
if self.print_out == True:
print('Try load variable ',variable, ' from dir ', dir)
self.load(variable, dir)
except:
if variable == 'vacuum_lvl':
if self.print_out == True:
print('Loading ', variable, ' failed! Start processing LOCPOT')
self.process_LOCPOT()
elif variable == 'procar_data' or variable == 'orbital_names' \
or variable == 'ion_names':
if self.print_out == True:
print('Loading ', variable, ' failed! Start processing PROCAR and POSCAR')
self.process_PROCAR()
else:
if self.print_out == True:
print('Loading ', variable, ' failed! Start processing OUTCAR')
self.process_OUTCAR()
else:
if self.print_out == True:
print('Variable ', variable, 'exists')
def get_band_eigs(self, band, outcar_path='OUTCAR'):
"""
This function get eigenvalues for desired band
:param band: desired band
:param outcar_path: path to OUTCAR file
:return:
"""
if self.eigenvalues is None:
try:
if self.print_out == True:
print("Variable is not define. Try load from file")
self.load('eigenvalues')
except:
if self.print_out == True:
print("Loading failed. Start processing OUTCAR")
self.process_OUTCAR(outcar_path)
return self.eigenvalues[:,band]
else:
return self.eigenvalues[:,band]
def get_band_occ(self, band, outcar_path='OUTCAR'):
"""
This function get occupations for desired band
:param band: desired band
:param outcar_path: path to OUTCAR file
:return:
"""
if self.occupations is None:
try:
if self.print_out == True:
print("Variable is not define. Try load from file")
self.load('occupations')
except:
if self.print_out == True:
print("Loading failed. Start processing OUTCAR")
self.process_OUTCAR(outcar_path)
return self.occupations[:, band]
else:
return self.occupations[:, band]
def get_DOS(self, Erange_from_fermi, dE, optimization=False):
"""
This function calculates Density of States for desired energy range and energy step
:param Erange_from_fermi: Energy range
:param dE: energy step
:param dE_new : recalculated energy step to enshure integer number of steps
:return:
E_arr: float64 np.array with energies relative to Fermi level
DOS_arr: float64 np.array with Density of States
"""
for var in ['efermi', 'nkpts', 'nbands', 'eigenvalues', 'weights']:
self.check_existance(var)
if optimization:
DOS_arr = []
E_arr = []
for step in range(len(self.efermi)):
Erange = [Erange_from_fermi[0] + self.efermi[step], Erange_from_fermi[1] + self.efermi[step]]
energies_number = int((Erange[1] - Erange[0]) / dE)
dE_new = (Erange[1] - Erange[0]) / energies_number
DOS_arr.append(np.zeros(energies_number))
E_arr.append(np.arange(Erange_from_fermi[0], Erange_from_fermi[1], dE_new))
for k in range(self.nkpts):
for b in range(self.nbands):
energy = self.eigenvalues[step][k][b]
if energy > Erange[0] and energy < Erange[1]:
e = int((energy - Erange[0]) / dE_new)
DOS_arr[step][e] += self.weights[k] / dE_new
return E_arr, DOS_arr * 2
else:
Erange = [Erange_from_fermi[0] + self.efermi, Erange_from_fermi[1] + self.efermi]
energies_number = int((Erange[1] - Erange[0]) / dE)
dE_new = (Erange[1] - Erange[0])/energies_number
DOS_arr = np.zeros(energies_number)
E_arr = np.arange(Erange_from_fermi[0], Erange_from_fermi[1], dE_new)
for k in range(self.nkpts):
for b in range(self.nbands):
energy = self.eigenvalues[k][b]
if energy > Erange[0] and energy < Erange[1]:
e = int((energy - Erange[0]) / dE_new)
DOS_arr[e]+=self.weights[k] / dE_new
return E_arr, DOS_arr * 2, dE_new
def get_pdos(self, Erange_from_fermi, dE, ions='all', orbitals='all', dir_to_data=None):
if dir_to_data == None:
dir_to_data = self.dir_to_save
for var in ['procar_data' , 'orbital_names', 'ion_names', 'efermi', 'eigenvalues', 'weights']:
self.check_existance(var, dir_to_data)
Erange = [Erange_from_fermi[0] + self.efermi, Erange_from_fermi[1] + self.efermi]
energies_number = int((Erange[1] - Erange[0]) / dE)
dE_new = (Erange[1] - Erange[0]) / energies_number
DOS_arr = np.zeros(energies_number)
E_arr = np.arange(Erange_from_fermi[0], Erange_from_fermi[1], dE_new)
nkpts = np.shape(self.procar_data)[0]
nbands = np.shape(self.procar_data)[1]
for k in range(nkpts):
if self.print_out == True:
print('kpoint = ', k)
for b in range(nbands):
energy = self.eigenvalues[k][b]
if energy > Erange[0] and energy < Erange[1]:
e = int((energy - Erange[0]) / dE_new)
if ions == 'all':
list_of_ions = [i for i in range(len(self.ion_names))]
elif type(ions) == str:
list_of_ions = []
for i, name in enumerate(self.ion_names):
if name == ions:
list_of_ions.append(i)
elif type(ions) == list:
if type(ions[0]) == int:
list_of_ions = ions
elif type(ions[0]) == str:
list_of_ions = []
for ion_name in ions:
for i, name in enumerate(self.ion_names):
if name == ion_name:
list_of_ions.append(i)
if orbitals == 'all':
list_of_orbitals = [i for i in range(len(self.orbital_names))]
elif type(orbitals) == str:
list_of_orbitals = []
for i, name in enumerate(self.orbital_names):
if name == orbitals:
list_of_orbitals.append(i)
elif type(orbitals) == list:
if type(orbitals[0]) == int:
list_of_orbitals = ions
elif type(orbitals[0]) == str:
list_of_orbitals = []
for orb_name in orbitals:
for i, name in enumerate(self.orbital_names):
if name == orb_name:
list_of_orbitals.append(i)
weight = 0
for ion in list_of_ions:
for orb in list_of_orbitals:
weight+=self.procar_data[k][b][ion][orb]
DOS_arr[e] += weight / dE_new * self.weights[k]
return E_arr, DOS_arr * 2, dE_new
if __name__=='__main__':
t=time.time()
"""
outcar_path = 'OUTCAR'
wavecar_path = 'WAVECAR'
dir_to_save = "Saved_data"
p = Preprocessing(dir_to_save)
p.process_OUTCAR(outcar_path=outcar_path, dir_to_save=dir_to_save)
print('Processing OUTCAR takes: ', time.time()-t, 'sec')
t=time.time()
p.process_WAVECAR((-7.0, 4.0), 0.01, wavecar_path=wavecar_path, dir_to_save=dir_to_save)
print('Job done! Processing WAVECAR: ', time.time()-t, ' sec')
"""
dir_to_save = 'Saved_data'
p = Preprocessing(dir_to_save)
Erange = [-25.0, 5.0]
dE = 0.1
E_arr, DOS_arr, dE_new = p.get_DOS(Erange, dE)
plt.plot(E_arr, gaussian_filter1d(DOS_arr, sigma=2), label='total')
E_arr, PDOS_arr, dE_new = p.get_pdos(Erange, dE, ions='C', orbitals='s')
plt.plot(E_arr, gaussian_filter1d(PDOS_arr, sigma=2), label='C-s')
E_arr, PDOS_arr, dE_new = p.get_pdos(Erange, dE, ions='C', orbitals=['px', 'py', 'pz'])
plt.plot(E_arr, gaussian_filter1d(PDOS_arr, sigma=2), label='C-p')
E_arr, PDOS_arr, dE_new = p.get_pdos(Erange, dE, ions='H', orbitals='s')
plt.plot(E_arr, gaussian_filter1d(PDOS_arr, sigma=2), label='H-s')
E_arr, PDOS_arr, dE_new = p.get_pdos(Erange, dE, ions='O', orbitals='s')
plt.plot(E_arr, gaussian_filter1d(PDOS_arr, sigma=2), label='O-s')
E_arr, PDOS_arr, dE_new = p.get_pdos(Erange, dE, ions='O', orbitals=['px', 'py', 'pz'])
plt.plot(E_arr, gaussian_filter1d(PDOS_arr, sigma=2), label='O-p')
plt.xlabel('E, eV')
plt.ylabel('DOS, states/eV/cell')
plt.legend()
plt.savefig('pdos.png', dpi=300)
plt.show()
plt.close()

View File

@@ -0,0 +1,811 @@
import numpy as np
import os
import time
import math
import matplotlib.pyplot as plt
from scipy.interpolate import griddata
from mpl_toolkits.axes_grid1 import make_axes_locatable
from scipy.ndimage.filters import gaussian_filter1d
import tip_types
from echem.core import ElemNum2Name
plt.switch_backend('agg')
class STM:
"""
This class is for computing 2D and 3D STM images with different theory levels:
Tersoff-Hummann, Chen, analytical acceptor wavefucntions (oxygen)
Also this class can calculate 2D ECSTM images using GerisherMarcus module
In current version of program this class is no more needed. Most of functions are relocated to kHET_spatial.py
"""
PLANCK_CONSTANT = 4.135667662e-15 # Planck's constant in eV*s
BOLTZMANN_CONSTANT = 8.617333262145e-5 # Boltzmann's constant in eV/K
ELEM_CHARGE = 1.60217662e-19 # Elementary charge in Coulombs
BOHR_RADIUS = 1.88973
AVAILABLE_TIPS_TYPES = ['oxygen', 'IrCl6', 'RuNH3_6', 'RuNH3_6_NNN_plane', 'RuNH3_6_perpendicular', 'oxygen_parallel_x', 'oxygen_parallel_y']
def __init__(self, working_folder):
self.WF_ijke = None
self.CD_ijke = None
self.dE = None
self.energy_range = None
self.working_folder = working_folder
self.path_to_data = self.working_folder+'/Saved_data'
self.WF_data_path = self.path_to_data+'/WF_data.npy'
self.WF_ijke_path = self.path_to_data+'/WF_ijke.npy'
self.CD_ijke_path = self.path_to_data+'/CD_ijke.npy'
self.poscar_path = working_folder+'/POSCAR'
def save_as_vasp(self, array, name, dir):
if not os.path.exists(dir):
print(f"Directory {dir} does not exist. Creating directory")
os.mkdir(dir)
shape = np.shape(array)
with open(self.poscar_path) as inf:
lines = inf.readlines()
natoms = int(lines[6].strip())
with open(dir+'/'+ name + '.vasp', 'w') as ouf:
ouf.writelines(lines[:natoms+8])
ouf.write('\n ' + str(shape[0]) + ' ' + str(shape[1]) + ' ' + str(shape[2]) + '\n ')
counter = 0
for k in range(shape[2]):
for j in range(shape[1]):
for i in range(shape[0]):
ouf.write(str('%.8E' % array[i][j][k]) + ' ')
counter += 1
if counter % 10 == 0:
ouf.write('\n ')
print(f"File {name} saved")
def save_as_cube(self, array, name, dir):
if not os.path.exists(dir):
print(f"Directory {dir} does not exist. Creating directory")
os.mkdir(dir)
shape = np.shape(array)
with open(self.poscar_path) as inf:
lines = inf.readlines()
natoms = sum(map(int, lines[6].strip().split()))
atomtypes = lines[5].strip().split()
numbers_of_atoms = list(map(int, lines[6].strip().split()))
type_of_i_atom = []
basis = []
for i in [2,3,4]:
vector = list(map(float, lines[i].strip().split()))
basis.append(vector)
basis = np.array(basis)
for i, number in enumerate(numbers_of_atoms):
for j in range(number):
type_of_i_atom.append(atomtypes[i])
with open(dir+'/'+ name + '.cube', 'w') as ouf:
ouf.write(' This file is generated using stm.py module\n')
ouf.write(' Good luck\n')
ouf.write(' ' + str(natoms) + '\t0.000\t0.000\t0.000\n')
ouf.write(' ' + str(-shape[0]) + lines[2])
ouf.write(' ' + str(-shape[1]) + lines[3])
ouf.write(' ' + str(-shape[2]) + lines[4])
for i, line in enumerate(lines[8:natoms+8]):
coordinate = np.array(list(map(float, line.strip().split())))
if lines[7].strip() == 'Direct':
coordinate = coordinate.dot(basis)
coordinate *= self.BOHR_RADIUS
elif lines[7].strip() == 'Cartesian':
coordinate *= self.BOHR_RADIUS
else:
print ('WARNING!!! Cannot read POSCAR correctly')
atomtype = type_of_i_atom[i]
atomnumber = list(ElemNum2Name.keys())[list(ElemNum2Name.values()).index(atomtype)]
ouf.write(' ' + str(atomnumber) + '\t0.00000\t' + str(coordinate[0])+ '\t' + str(coordinate[1])+ '\t' + str(coordinate[2])+'\n')
counter = 0
for i in range(shape[0]):
for j in range(shape[1]):
for k in range(shape[2]):
ouf.write(str('%.5E' % array[i][j][k]) + ' ')
counter += 1
if counter % 6 == 0:
ouf.write('\n ')
ouf.write('\n ')
print(f"File {name} saved")
def load_data(self):
WF_data = np.load(self.WF_data_path, allow_pickle=True).item()
self.dE = WF_data['dE']
self.energy_range = WF_data['energy_range']
self.WF_ijke = np.load(self.WF_ijke_path)
self.CD_ijke = np.load(self.CD_ijke_path)
def set_ecstm_parameters(self, C_EDL, T, lambda_, V_std, overpot,
effective_freq, linear_constant, threshold_value):
"""
:param C_EDL: float
Capacitance of electric double layer (microF/cm^2)
:param T: int, float
Temperature. It is used in computing Fermi function and distribution function of redox system states
:param lambda_: float
Reorganization energy in eV
:param V_std: float
Standart potential of the redox couple (Volts)
:param overpot: float
Overpotential (Volts). It shifts the electrode Fermi energy to -|e|*overpot
:param effective_freq: float
Effective frequency of redox species motion
:param effective_freq: float
Linear constant of proportionality of Hif and Sif: Hif = linear_constant * Sif
:param threshold_value: float
Minimum value of y_redox and y_fermi to be considered in integral
:return:
"""
self.C_EDL = C_EDL
self.T = T
self.lambda_ = lambda_
self.sheet_area = self._get_sheet_area() # Area of investigated surface(XY) in cm^2
self.V_std = V_std
self.overpot = overpot
self.effective_freq = effective_freq
self.linear_constant = linear_constant
self.threshold_value = threshold_value
def load_data_for_ecstm(self):
"""
This inner function load necessary data for ECSTM calculations
:param outcar_path: str
path to OUTCAR vasp file
:param locpot_path: str
path to LOCPOT vasp file
:return:
"""
try:
self.E = np.load(self.path_to_data+'/E.npy')
self.DOS = np.load(self.path_to_data+'/DOS.npy')
except:
import preprocessing_v2 as preprocessing
p = preprocessing.Preprocessing(working_folder=self.working_folder)
p.process_OUTCAR()
self.E, self.DOS, dE_new = p.get_DOS(self.energy_range, self.dE)
if dE_new != self.dE:
print("WARNING! Something wrong with dE during DOS calculations")
np.save(self.path_to_data+'/DOS.npy', self.DOS)
np.save(self.path_to_data+'/E.npy', self.E)
try:
self.efermi = np.load(self.path_to_data+'/efermi.npy')
except:
print(f"ERROR! {self.path_to_data}/efermi.npy does not exist. Try to preprocess data")
try:
self.vacuum_lvl = np.load(self.path_to_data+'vacuum_lvl.npy')
except:
from pymatgen.io.vasp.outputs import Locpot
locpot = Locpot.from_file(self.working_folder+'/LOCPOT')
avr = locpot.get_average_along_axis(2)
self.vacuum_lvl = np.max(avr)
np.save(self.path_to_data+'/vacuum_lvl.npy', self.vacuum_lvl)
def _calculate_distributions(self):
"""
This function calls GerisherMarcus module with GM class to calculate distribution of redox species
and Fermi-Dirac distribution according to Gerisher-Marcformalismizm
:return:
"""
import GerischerMarkus as gm
gerisher_marcus_obj = gm.GM(path_to_data = self.path_to_data)
gerisher_marcus_obj.set_params(self.C_EDL, self.T, self.lambda_, self.sheet_area)
self.y_fermi, self.y_redox = gerisher_marcus_obj.compute_distributions(self.V_std, overpot=self.overpot)
return gerisher_marcus_obj
@staticmethod
def _nearest_array_indices(array, value):
i = 0
while value > array[i]:
i += 1
return i - 1, i
def plot_distributions(self, E_range=[-7, 4], dE=None, sigma=2, fill_area_lower_Fermi_lvl=True,
plot_Fermi_Dirac_distib=True, plot_redox_distrib=True):
a = self._calculate_distributions()
if dE == None:
dE = self.dE
if dE != self.dE or E_range[0] < self.energy_range[0] or E_range[1] > self.energy_range[1]:
import preprocessing_v2 as prep
p = prep.Preprocessing(working_folder = self.working_folder)
E, DOS, dE_new = p.get_DOS(E_range, dE)
else:
E, DOS = a.E, a.DOS
n_1, n_2 = self._nearest_array_indices(E, a.dE_Q_eq)
if sigma > 0 and sigma != None:
DOS = gaussian_filter1d(DOS, sigma)
plt.plot(E, DOS)
if fill_area_lower_Fermi_lvl == True:
plt.fill_between(E[:n_2], DOS[:n_2])
if plot_Fermi_Dirac_distib == True:
plt.plot(a.E, self.y_fermi * 30)
if plot_redox_distrib == True:
plt.plot(a.E, self.y_redox * 10)
plt.xlabel('E, eV')
plt.ylabel('DOS, states/eV/cell')
plt.xlim(E_range)
plt.savefig(f"distributions_{self.V_std}_{self.overpot}.png", dpi=300)
plt.close()
def _kT_in_eV(self, T):
return T * self.BOLTZMANN_CONSTANT
def _get_kappa(self, matrix_elements_squared):
lz_factor = 2 * matrix_elements_squared / self.PLANCK_CONSTANT / self.effective_freq * math.sqrt(
math.pi / self.lambda_ / self._kT_in_eV(self.T))
kappa = 1 - np.exp(-2 * math.pi * lz_factor)
return kappa
def _get_basis_vectors(self):
"""
This function processes POSCAR file to extract vectors of the simulation box
:param poscar_path:
:return:
"""
with open(self.poscar_path) as inf:
lines = inf.readlines()
b1 = np.array(list(map(float, lines[2].strip().split())))
b2 = np.array(list(map(float, lines[3].strip().split())))
b3 = np.array(list(map(float, lines[4].strip().split())))
return b1, b2, b3
def _get_sheet_area(self):
"""
Inner function to calculate sheet_area (XY plane) in cm^2
"""
b1, b2, b3 = self._get_basis_vectors()
return np.linalg.norm(np.cross(b1,b2))*1e-16
def _plot_contour_map(self, function, X, Y, dir, filename):
"""
Function for plotting 2D images
:param function: Z values
:param X: X values
:param Y: Y values
:param dir: directory in which contour map will be saved
:param filename: filename of image
:return:
"""
if not os.path.exists(dir):
print(f"Directory {dir} does not exist. Creating directory")
os.mkdir(dir)
t = time.time()
Z = function.transpose().flatten()
xi = np.linspace(X.min(), X.max(), 1000)
yi = np.linspace(Y.min(), Y.max(), 1000)
zi = griddata((X, Y), Z, (xi[None, :], yi[:, None]), method='cubic')
plt.contourf(xi, yi, zi, 500, cmap=plt.cm.rainbow)
ax = plt.gca()
ax.set_aspect('equal')
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="7%", pad=0.1)
plt.colorbar(cax=cax)
plt.savefig(dir+'/'+filename+'.png', dpi=300)
plt.close()
print (f"Plotting map takes {time.time()-t} sec")
def _get_overlap_integrals_squared(self, e, cutoff, acc_orbitals, zmin, zmax):
WF_ijk = self.WF_ijke[:, :, :, e]
xlen, ylen, zlen = np.shape(WF_ijk)
overlap_integrals_squared = np.zeros((xlen, ylen))
if np.allclose(WF_ijk, np.zeros((xlen, ylen, zlen))):
# If WF_ijk array for current energy is empty, code goes to the next energy
print(f"WF_ijk array for energy {e} is empty. Going to the next")
return overlap_integrals_squared
for i in range(xlen):
if i - cutoff < 0:
WF_ijk_rolled_x = np.roll(WF_ijk, cutoff, axis=0)
orb_rolled_x = []
for orbital in acc_orbitals:
orb_rolled_x.append(np.roll(orbital, i + cutoff, axis=0))
xmin = i
xmax = i + cutoff * 2
elif i - cutoff >= 0 and i + cutoff <= xlen:
WF_ijk_rolled_x = np.copy(WF_ijk)
orb_rolled_x = []
for orbital in acc_orbitals:
orb_rolled_x.append(np.roll(orbital, i, axis = 0))
xmin = i - cutoff
xmax = i + cutoff
elif i + cutoff > xlen:
WF_ijk_rolled_x = np.roll(WF_ijk, -cutoff, axis=0)
orb_rolled_x = []
for orbital in acc_orbitals:
orb_rolled_x.append(np.roll(orbital, i - cutoff, axis=0))
xmin = i - cutoff * 2
xmax = i
else:
print(f"ERROR: for i = {i} something with rolling arrays along x goes wrong")
for j in range(ylen):
if j - cutoff < 0:
WF_ijk_rolled = np.roll(WF_ijk_rolled_x, cutoff, axis=1)
orb_rolled = []
for orbital in orb_rolled_x:
orb_rolled.append(np.roll(orbital, j + cutoff, axis=1))
ymin = j
ymax = j + cutoff * 2
elif j - cutoff >= 0 and j + cutoff <= ylen:
WF_ijk_rolled = np.copy(WF_ijk_rolled_x)
orb_rolled = []
for orbital in orb_rolled_x:
orb_rolled.append(np.roll(orbital, j, axis=1))
ymin = j - cutoff
ymax = j + cutoff
elif j + cutoff > ylen:
WF_ijk_rolled = np.roll(WF_ijk_rolled_x, -cutoff, axis=1)
orb_rolled = []
for orbital in orb_rolled_x:
orb_rolled.append(np.roll(orbital, j - cutoff, axis=1))
ymin = j - cutoff * 2
ymax = j
else:
print(f"ERROR: for i = {i} something with rolling arrays along y goes wrong")
integral = []
for orbital in orb_rolled:
integral.append(np.linalg.norm(WF_ijk_rolled[xmin:xmax, ymin:ymax, zmin:zmax]*\
orbital[xmin:xmax, ymin:ymax, zmin:zmax]))
overlap_integrals_squared[i][j] = max(integral) ** 2
return overlap_integrals_squared
def generate_acceptor_orbitals(self, orb_type, shape, z_shift=0, x_shift=0, y_shift=0):
"""
This is generator of acceptor orbitals using tip_types.py module
:return:
"""
bohr_radius = 0.529 #TODO better to get it from core.constants
b1, b2, b3 = self._get_basis_vectors()
bn1 = b1 / shape[0]
bn2 = b2 / shape[1]
bn3 = b3 / shape[2]
basis = np.array([bn1, bn2, bn3])
transition_matrix = basis.transpose()
numer_of_orbitals = len(tip_types.orbitals(orb_type))
acc_orbitals = []
for i in range(numer_of_orbitals):
acc_orbitals.append(np.zeros(shape))
for i in range(shape[0]):
for j in range(shape[1]):
for k in range(shape[2]):
if i - x_shift >= shape[0] / 2:
x = i - shape[0] - x_shift
else:
x = i - x_shift
if j - y_shift >= shape[1] / 2:
y = j - shape[1] - y_shift
else:
y = j - y_shift
if k - z_shift >= shape[2] / 2:
z = k - shape[2] - z_shift
else:
z = k - z_shift
r = np.dot(transition_matrix, np.array([x, y, z])) / bohr_radius
for o, orbital in enumerate(acc_orbitals):
orbital[i][j][k] = tip_types.orbitals(orb_type)[o](r)
return acc_orbitals
def calculate_2D_ECSTM(self, z_position, tip_type='oxygen', dir='ECSTM', cutoff_in_Angstroms=5, all_z=False, from_CD=False):
"""
This function calculate 2D ECSTM images using GerisherMarcus module
:param z_position: position of tip under the investigated surface
:param tip_type: Type of tip orbital. Currenty only "oxygen" is available
:param dir: directiry to save images
:param cutoff_in_Angstroms: Cutoff over x,y and z, when overlap integral is calculated.
E.g. for x direction area of integration will be from x-cutoff to x+cutoff
:return:
"""
print(f"Starting to calculate 2D ECSTM; tip_type = {tip_type};")
xlen, ylen, zlen, elen = np.shape(self.WF_ijke)
b1, b2, b3 = self._get_basis_vectors()
bn1 = b1 / xlen
bn2 = b2 / ylen
bn3 = b3 / zlen
print (bn1, bn2, xlen, ylen)
if b3[0] != 0.0 or b3[1] != 0.0:
print("WARNING! You z_vector is not perpendicular to XY plane, Check calculate_2D_STM function")
z = int(zlen // 2 + z_position // np.linalg.norm(bn3))
real_z_position = z_position // np.linalg.norm(bn3) * np.linalg.norm(bn3)
print(f"Real z_position of tip = {real_z_position}")
R = []
for j in range(ylen):
for i in range(xlen):
R.append(i * bn1 + j * bn2)
X = np.array([x[0] for x in R])
Y = np.array([x[1] for x in R])
if any(tip_type == kw for kw in self.AVAILABLE_TIPS_TYPES):
cutoff = int(cutoff_in_Angstroms // np.linalg.norm(bn1))
if cutoff > xlen // 2 or cutoff > ylen // 2:
print("ERROR: Cutoff should be less than 1/2 of each dimension of the cell. "
"Try to reduce cutoff. Otherwise, result could be unpredictible")
print(f"Cutoff in int = {cutoff}")
ECSTM_ij = np.zeros((xlen, ylen))
acc_orbitals = self.generate_acceptor_orbitals(tip_type, (xlen, ylen, zlen), z_shift=z)
if all_z == True:
zmin = 0
zmax = zlen
elif z - cutoff >= 0 and z + cutoff <= zlen:
zmin = z - cutoff
zmax = z + cutoff
else:
print("Can't reduce integrating area in z dimention. "
"Will calculate overlapping for all z. You can ignore this message "
"if you don't care about efficiency")
zmin = 0
zmax = zlen
self._calculate_distributions()
for e in range(elen):
if self.y_redox[e] < self.threshold_value or self.y_fermi[e] < self.threshold_value:
continue
overlap_integrals_squared = self._get_overlap_integrals_squared(e, cutoff, acc_orbitals, zmin, zmax)
matrix_elements_squared = overlap_integrals_squared * self.linear_constant * np.linalg.norm(bn3)**3
#print ('Hif = ', matrix_elements_squared, 'eV')
#kappa = self._get_kappa(matrix_elements_squared)
#ECSTM_ij += kappa * self.y_fermi[e] * self.DOS[e] * self.y_redox[e] * self.dE * 2 * math.pi *\
# self.ELEM_CHARGE / self.PLANCK_CONSTANT
ECSTM_ij += 2 * np.pi / self.PLANCK_CONSTANT * matrix_elements_squared * self.y_fermi[e] * self.DOS[e] * self.y_redox[e] * self.dE
elif tip_type == 's':
ECSTM_ij = np.zeros((xlen, ylen))
self._calculate_distributions()
for e in range(elen):
if self.y_redox[e] < self.threshold_value or self.y_fermi[e] < self.threshold_value:
continue
if from_CD == True:
matrix_elements_squared = self.CD_ijke[:, :, z, e]
ECSTM_ij += 2 * np.pi / self.PLANCK_CONSTANT * matrix_elements_squared * self.y_fermi[e] * self.DOS[e] * self.y_redox[e] * self.dE
else:
matrix_elements_squared = np.abs(self.WF_ijke[:, :, z, e]) ** 2
#print ('Hif = ', matrix_elements_squared, 'eV')
#kappa = self._get_kappa(matrix_elements_squared)
#ECSTM_ij += kappa * self.y_fermi[e] * self.DOS[e] * self.y_redox[e] * self.dE * 2 * math.pi *\
# self.ELEM_CHARGE / self.PLANCK_CONSTANT\
#TODO - check formula!!!
ECSTM_ij += 2 * np.pi / self.PLANCK_CONSTANT * matrix_elements_squared * self.y_fermi[e] * self.DOS[e] * self.y_redox[e] * self.dE
elif tip_type == 'pz':
ECSTM_ij = np.zeros((xlen, ylen))
self._calculate_distributions()
for e in range(elen):
if self.y_redox[e] < self.threshold_value or self.y_fermi[e] < self.threshold_value:
continue
grad_z_WF_for_e = np.gradient(self.WF_ijke[:, :, :, e], axis=2)
matrix_elements_squared = (np.abs(grad_z_WF_for_e[:, :, z])) ** 2
print ('Hif = ', matrix_elements_squared, 'eV')
#kappa = self._get_kappa(matrix_elements_squared)
#ECSTM_ij += kappa * self.y_fermi[e] * self.DOS[e] * self.y_redox[e] * self.dE * 2 * math.pi *\
# self.ELEM_CHARGE / self.PLANCK_CONSTANT
ECSTM_ij += 2 * np.pi / self.PLANCK_CONSTANT * matrix_elements_squared * self.y_fermi[e] * self.DOS[e] * self.y_redox[e] * self.dE
if from_CD:
cd_flag='from_CD'
else:
cd_flag=''
filename = f"ecstm_{'%.2f'%real_z_position}_{tip_type}_{cd_flag}_{self.V_std}_{self.overpot}_{self.lambda_}"
self._plot_contour_map(ECSTM_ij, X, Y, dir, filename)
np.save(dir+'/'+filename, ECSTM_ij)
def calculate_2D_STM(self, STM_energy_range, z_position, tip_type='s', dir='STM_2D', cutoff_in_Angstroms=5, all_z=False, from_CD=False):
"""
Function for calculation 2D STM images
:param STM_energy_range: Energy range regarding Fermi level
:param z_position: Position of tip under the surface. This distance is in Angstroms assuming
that investigated surface XY is in the center of calculation cell (the middle of z axes)
:param tip_type: type of tip orbital
:param dir: directory for saving images
:param cutoff_in_Angstroms: Cutoff over x,y and z, when overlap integral is calculated.
E.g. for x direction area of integration will be from x-cutoff to x+cutoff
:return:
"""
emin = int((STM_energy_range[0] - self.energy_range[0]) / self.dE)
emax = int((STM_energy_range[1] - self.energy_range[0]) / self.dE)
print(f"Starting to calculate 2D STM; tip_type = {tip_type}; STM energy range = {STM_energy_range}")
e_array = np.arange(emin, emax)
xlen, ylen, zlen, elen = np.shape(self.WF_ijke)
b1, b2, b3 = self._get_basis_vectors()
bn1 = b1 / xlen
bn2 = b2 / ylen
bn3 = b3 / zlen
if b3[0] != 0.0 or b3[1] != 0.0 :
print("WARNING! You z_vector is not perpendicular to XY plane, Check calculate_2D_STM function")
z = int(zlen // 2 + z_position // np.linalg.norm(bn3))
real_z_position = z_position // np.linalg.norm(bn3) * np.linalg.norm(bn3)
print(f"Real z_position of tip = {real_z_position}")
R = []
for j in range(ylen):
for i in range(xlen):
R.append(i * bn1 + j * bn2)
X = np.array([x[0] for x in R])
Y = np.array([x[1] for x in R])
if any(tip_type == kw for kw in self.AVAILABLE_TIPS_TYPES):
cutoff = int(cutoff_in_Angstroms // np.linalg.norm(bn1))
if cutoff > xlen//2 or cutoff > ylen//2:
print("ERROR: Cutoff should be less than 1/2 of each dimension of the cell. "
"Try to reduce cutoff. Otherwise, result could be unpredictible")
print (f"Cutoff in int = {cutoff}")
STM_ij = np.zeros((xlen, ylen))
acc_orbitals = self.generate_acceptor_orbitals(tip_type, (xlen, ylen, zlen), z_shift=z)
if all_z == True:
zmin = 0
zmax = zlen
elif z - cutoff >=0 and z + cutoff <= zlen:
zmin = z-cutoff
zmax = z+cutoff
else:
print("Can't reduce integrating area in z dimention. "
"Will calculate overlapping for all z. You can ignore this message "
"if you don't care about efficiency")
zmin = 0
zmax = zlen
for e in e_array:
overlap_integrals_squared = self._get_overlap_integrals_squared(e, cutoff, acc_orbitals, zmin, zmax)
STM_ij += overlap_integrals_squared
elif tip_type == 's':
if from_CD == True:
STM_ij = np.zeros((xlen, ylen))
for e in e_array:
STM_ij += self.CD_ijke[:, :, z, e]
else:
STM_ij = np.zeros((xlen, ylen))
for e in e_array:
STM_ij += np.abs(self.WF_ijke[:, :, z, e])**2
elif tip_type == 'pz':
STM_ij = np.zeros((xlen, ylen))
for e in e_array:
grad_z_WF_for_e = np.gradient(self.WF_ijke[:, :, :, e], axis=2)
STM_ij += (np.abs(grad_z_WF_for_e[:, :, z])) ** 2
elif tip_type == 'px':
STM_ij = np.zeros((xlen, ylen))
for e in e_array:
grad_x_WF_for_e = np.gradient(self.WF_ijke[:, :, z, e], axis=0)
STM_ij += (np.abs(grad_x_WF_for_e)) ** 2
elif tip_type == 'p30':
STM_ij = np.zeros((xlen, ylen))
for e in e_array:
grad_x_WF_for_e = np.gradient(self.WF_ijke[:, :, z, e], axis=0)
grad_y_WF_for_e = np.gradient(self.WF_ijke[:, :, z, e], axis=1)
STM_ij += (np.abs(grad_y_WF_for_e + grad_x_WF_for_e)) ** 2
elif tip_type == 'p60':
STM_ij = np.zeros((xlen, ylen))
for e in e_array:
grad_y_WF_for_e = np.gradient(self.WF_ijke[:, :, z, e], axis=1)
STM_ij += (np.abs(grad_y_WF_for_e)) ** 2
elif tip_type == 'p90':
STM_ij = np.zeros((xlen, ylen))
for e in e_array:
grad_x_WF_for_e = np.gradient(self.WF_ijke[:, :, z, e], axis=0)
grad_y_WF_for_e = np.gradient(self.WF_ijke[:, :, z, e], axis=1)
STM_ij += (np.abs(grad_y_WF_for_e - grad_x_WF_for_e / 2.0)) ** 2
elif tip_type == 'p120':
STM_ij = np.zeros((xlen, ylen))
for e in e_array:
grad_x_WF_for_e = np.gradient(self.WF_ijke[:, :, z, e], axis=0)
grad_y_WF_for_e = np.gradient(self.WF_ijke[:, :, z, e], axis=1)
STM_ij += (np.abs(grad_y_WF_for_e - grad_x_WF_for_e)) ** 2
elif tip_type == 'p150':
STM_ij = np.zeros((xlen, ylen))
for e in e_array:
grad_x_WF_for_e = np.gradient(self.WF_ijke[:, :, z, e], axis=0)
grad_y_WF_for_e = np.gradient(self.WF_ijke[:, :, z, e], axis=1)
STM_ij += (np.abs(grad_y_WF_for_e / 2.0 - grad_x_WF_for_e)) ** 2
elif tip_type == 's+pz':
STM_ij = np.zeros((xlen, ylen))
for e in e_array:
grad_z_WF_for_e = np.gradient(self.WF_ijke[:, :, :, e], axis=2)
STM_ij += (np.abs(self.WF_ijke[:, :, z, e] + grad_z_WF_for_e[:, :, z])) ** 2
if from_CD:
cd_flag='from_CD'
else:
cd_flag=''
filename = 'stm_' + str('%.2f' % real_z_position) + '_' + tip_type + '_' + cd_flag
self._plot_contour_map(STM_ij, X, Y, dir, filename)
np.save(dir+'/'+filename, STM_ij)
def calculate_3D_STM(self, STM_energy_range, tip_type='s', dir='STM_3D', format='cube', from_CD=False):
emin = int((STM_energy_range[0] - self.energy_range[0]) / self.dE)
emax = int((STM_energy_range[1] - self.energy_range[0]) / self.dE)
print ('Starting to calculate 3D STM; tip_type ='+tip_type+'; STM energy range = ', STM_energy_range)
e_array = np.arange(emin, emax)
xlen, ylen, zlen, elen = np.shape(self.WF_ijke)
if tip_type == 's':
if from_CD == True:
STM_ijk = np.zeros((xlen, ylen, zlen))
for e in e_array:
STM_ijk += self.CD_ijke[:, :, :, e]
else:
STM_ijk = np.zeros((xlen, ylen, zlen))
for e in e_array:
STM_ijk += np.abs(self.WF_ijke[:, :, :, e]) ** 2
elif tip_type == 'pz':
STM_ijk = np.zeros((xlen, ylen, zlen))
for e in e_array:
grad_z_WF_for_e = np.gradient(self.WF_ijke[:, :, :, e], axis=2)
STM_ijk += (np.abs(grad_z_WF_for_e)) ** 2
elif tip_type == 'px':
STM_ijk = np.zeros((xlen, ylen, zlen))
for e in e_array:
grad_x_WF_for_e = np.gradient(self.WF_ijke[:, :, :, e], axis=0)
STM_ijk += (np.abs(grad_x_WF_for_e)) ** 2
elif tip_type == 'p30':
STM_ijk = np.zeros((xlen, ylen, zlen))
for e in e_array:
grad_x_WF_for_e = np.gradient(self.WF_ijke[:, :, :, e], axis=0)
grad_y_WF_for_e = np.gradient(self.WF_ijke[:, :, :, e], axis=1)
STM_ijk += (np.abs(grad_y_WF_for_e + grad_x_WF_for_e)) ** 2
elif tip_type == 'p60':
STM_ijk = np.zeros((xlen, ylen, zlen))
for e in e_array:
grad_y_WF_for_e = np.gradient(self.WF_ijke[:, :, :, e], axis=1)
STM_ijk += (np.abs(grad_y_WF_for_e)) ** 2
elif tip_type == 'p90':
STM_ijk = np.zeros((xlen, ylen, zlen))
for e in e_array:
grad_x_WF_for_e = np.gradient(self.WF_ijke[:, :, :, e], axis=0)
grad_y_WF_for_e = np.gradient(self.WF_ijke[:, :, :, e], axis=1)
STM_ijk += (np.abs(grad_y_WF_for_e - grad_x_WF_for_e / 2.0)) ** 2
elif tip_type == 'p120':
STM_ijk = np.zeros((xlen, ylen, zlen))
for e in e_array:
grad_x_WF_for_e = np.gradient(self.WF_ijke[:, :, :, e], axis=0)
grad_y_WF_for_e = np.gradient(self.WF_ijke[:, :, :, e], axis=1)
STM_ijk += (np.abs(grad_y_WF_for_e - grad_x_WF_for_e)) ** 2
elif tip_type == 'p150':
STM_ijk = np.zeros((xlen, ylen, zlen))
for e in e_array:
grad_x_WF_for_e = np.gradient(self.WF_ijke[:, :, :, e], axis=0)
grad_y_WF_for_e = np.gradient(self.WF_ijke[:, :, :, e], axis=1)
STM_ijk += (np.abs(grad_y_WF_for_e / 2.0 - grad_x_WF_for_e)) ** 2
elif tip_type == 's+pz':
STM_ijk = np.zeros((xlen, ylen, zlen))
for e in e_array:
grad_z_WF_for_e = np.gradient(self.WF_ijke[:, :, :, e], axis=2)
STM_ijk += (np.abs(self.WF_ijke[:, :, :, e] + grad_z_WF_for_e)) ** 2
if not os.path.exists(dir):
print(f"Directory {dir} does not exist. Creating directory")
os.mkdir(dir)
filename = 'stm_'+str(STM_energy_range[0])+'_'+str(STM_energy_range[1])+'_'+tip_type
if format == 'vasp':
self.save_as_vasp(STM_ijk, filename, dir)
elif format == 'cube':
self.save_as_cube(STM_ijk, filename, dir)
if __name__=="__main__":
t = time.time()
stm = STM(path_to_data='Saved_data', poscar_path='../POSCAR')
stm.load_data()
###CALCULATE 2D STM IMAGES###
#stm.calculate_2D_STM((-0.5, 0.0), 3.5, tip_type='oxygen', cutoff_in_Angstroms=5)
#stm.calculate_2D_STM((-0.5, 0.0), 3.0, tip_type='s')
#stm.calculate_2D_STM((-0.5, 0.0), 3.0, tip_type='pz')
#stm.calculate_2D_STM((-0.5, 0.0), 3.0, tip_type='s+pz')
#stm.calculate_2D_STM((-0.5, 0.0), 4.5, tip_type='IrCl6', cutoff_in_Angstroms=8)
#stm.calculate_2D_STM((-0.5, 0.0), 5.5, tip_type='RuNH3_6_NNN_plane', cutoff_in_Angstroms=8)
###CALCULATE 2D ECSTM IMAGES###
stm.load_data_for_ecstm(outcar_path='../OUTCAR', locpot_path='../LOCPOT')
#for V_std in [-1.0, -0.5, 0.0, 0.5, 1.0]:
#for V_std in [-0.5]:
# stm.set_ecstm_parameters(C_EDL=20, T=298, lambda_=0.9, V_std=V_std,
# overpot=0, effective_freq = 1E13, linear_constant=26, threshold_value=1e-5)
# stm.plot_distributions(E_range=[-7, 4], dE=0.05, sigma=1, fill_area_lower_Fermi_lvl=True,
# plot_Fermi_Dirac_distib=False, plot_redox_distrib=True)
# #stm.calculate_2D_ECSTM(z_position=4.5, tip_type='IrCl6', cutoff_in_Angstroms=6)
# #stm.calculate_2D_ECSTM(z_position=3.0, tip_type='s')
# #stm.calculate_2D_ECSTM(z_position=3.5, tip_type='pz')
# stm.calculate_2D_ECSTM(z_position=4.5, tip_type='s')
#stm.set_ecstm_parameters(C_EDL=20, T=298, lambda_=1.63, V_std=-0.6,
# overpot=0, effective_freq = 1E13, linear_constant=26, threshold_value=1e-5)
#stm.plot_distributions(E_range=[-7, 4], dE=0.05, sigma=1, fill_area_lower_Fermi_lvl=True,
# plot_Fermi_Dirac_distib=False, plot_redox_distrib=True)
#stm.calculate_2D_ECSTM(z_position=3.5, tip_type='oxygen', cutoff_in_Angstroms=5)
#stm.set_ecstm_parameters(C_EDL=20, T=298, lambda_=0.8, V_std=0.1,
# overpot=0, effective_freq = 1E13, linear_constant=26, threshold_value=1e-5)
#stm.plot_distributions(E_range=[-7, 4], dE=0.05, sigma=1, fill_area_lower_Fermi_lvl=True,
# plot_Fermi_Dirac_distib=False, plot_redox_distrib=True)
#stm.calculate_2D_ECSTM(z_position=5.5, tip_type='RuNH3_6_NNN_plane', cutoff_in_Angstroms=8)
#stm.calculate_2D_ECSTM(z_position=3.0, tip_type='s')
#stm.calculate_2D_ECSTM(z_position=3.0, tip_type='pz')
#stm.calculate_2D_ECSTM(z_position=5.0, tip_type='s')
#stm.calculate_2D_ECSTM(z_position=5.0, tip_type='pz')
#stm.calculate_2D_ECSTM(z_position=4.5, tip_type='s')
#stm.calculate_2D_ECSTM(z_position=4.5, tip_type='pz')
stm.set_ecstm_parameters(C_EDL=20, T=298, lambda_=1.2, V_std=0.87,
overpot=0, effective_freq = 1E13, linear_constant=26, threshold_value=1e-5)
stm.plot_distributions(E_range=[-7, 4], dE=0.05, sigma=1, fill_area_lower_Fermi_lvl=True,
plot_Fermi_Dirac_distib=False, plot_redox_distrib=True)
stm.calculate_2D_ECSTM(z_position=5.5, tip_type='IrCl6', cutoff_in_Angstroms=6)
#stm.calculate_2D_ECSTM(z_position=3.0, tip_type='s')
#stm.calculate_2D_ECSTM(z_position=3.0, tip_type='pz')
#stm.calculate_2D_ECSTM(z_position=5.0, tip_type='s')
#stm.calculate_2D_ECSTM(z_position=5.0, tip_type='pz')
#stm.calculate_2D_ECSTM(z_position=4.5, tip_type='s')
#stm.calculate_2D_ECSTM(z_position=4.5, tip_type='pz')
#stm.set_ecstm_parameters(C_EDL=20, T=298, lambda_=0.9, V_std=0.0,
# overpot=0, effective_freq = 1E13, linear_constant=26, threshold_value=1e-5)
#stm.plot_distributions(E_range=[-7, 4], dE=0.05, sigma=1, fill_area_lower_Fermi_lvl=True,
# plot_Fermi_Dirac_distib=False, plot_redox_distrib=True)
#stm.calculate_2D_ECSTM(z_position=3.0, tip_type='oxygen', cutoff_in_Angstroms=5)
#stm.set_ecstm_parameters(C_EDL=20, T=298, lambda_= 1.0, V_std=-0.5,
# overpot=0, effective_freq = 1E13, linear_constant=26, threshold_value=1e-5)
#stm.plot_distributions(E_range=[-7, 4], dE=0.05, sigma=1, fill_area_lower_Fermi_lvl=True,
# plot_Fermi_Dirac_distib=False, plot_redox_distrib=True)
#stm.calculate_2D_ECSTM(z_position=3.0, tip_type='oxygen', cutoff_in_Angstroms=5)
#stm.calculate_2D_ECSTM(z_position=5, tip_type='RuNH3_6_NNN_plane', cutoff_in_Angstroms=8, all_z=True)
#stm.calculate_2D_ECSTM(z_position=7, tip_type='RuNH3_6_NNN_plane', cutoff_in_Angstroms=8, all_z=True)
#stm.calculate_2D_ECSTM(z_position=4.5, tip_type='oxygen', cutoff_in_Angstroms=5)
#stm.calculate_2D_ECSTM(z_position=3.5, tip_type='oxygen', cutoff_in_Angstroms=5)
##CACLULATE 3D STM IMAGE OPENABLE WITH VESTA PACKAGE###
#stm.calculate_3D_STM((-0.5, 0.0), tip_type='s', format='cube')
#stm.calculate_3D_STM((0.0, 0.5), tip_type='s', format='cube')
###CHECK OXYGEN ORBITAL GENERATION###
#acc_orbitals = stm.generate_acceptor_orbitals('RuNH3_6_NNN_plane', np.shape(stm.WF_ijke)[0:3], z_shift = 20, x_shift=50, y_shift=50)
#for i, orbitals in enumerate(acc_orbitals):
# stm.save_as_cube(orbitals, 'RuNH3_NNN'+str(i), 'orbitals')
#acc_orbitals = stm.generate_acceptor_orbitals('RuNH3_6', np.shape(stm.WF_ijke)[0:3], z_shift = 20, x_shift=50, y_shift=50)
#for i, orbitals in enumerate(acc_orbitals):
# stm.save_as_cube(orbitals, 'RuNH3_standart_orient'+str(i), 'orbitals')
#acc_orbitals = stm.generate_acceptor_orbitals('RuNH3_6_perpendicular', np.shape(stm.WF_ijke)[0:3], z_shift = 20, x_shift=50, y_shift=50)
#for i, orbitals in enumerate(acc_orbitals):
# stm.save_as_cube(orbitals, 'RuNH3_perpendicular'+str(i), 'orbitals')
#acc_orbitals = stm.generate_acceptor_orbitals('oxygen_parallel_y', np.shape(stm.WF_ijke)[0:3], z_shift = 20, x_shift=50, y_shift=50)
#for i, orbitals in enumerate(acc_orbitals):
# stm.save_as_vasp(orbitals, 'ox_par_y_orb_'+str(i), 'orbitals')
#print(f"Job done! It takes: {time.time()-t} sec")

View File

@@ -0,0 +1,398 @@
import numpy as np
import math
def read_orbital_data(filename):
data=[]
with open(filename) as inp:
lines = inp.readlines()
for line in lines:
arr = line.strip().split()
try:
if arr[0] == 'GTF:':
a = list(line[44:61])
for n, i in enumerate(a):
if i=='D':
a[n]='E'
a = float(''.join(a))
if a!=0:
b = list(line[65:79])
for n, i in enumerate(b):
if i == 'D':
b[n] = 'E'
b = float(''.join(b))
type = line[32:36].strip()
type = type.replace(" ", "")
cen = line[18:21]
data.append([int(cen)-1, type, a, b])
except:
pass
return data
def get_mol_orbital(data, centers):
def inner_func(r):
r = np.array(r)
result = 0
for dat in data:
c_num, type, weight, exp = dat
c = centers[c_num]
R2center = (np.linalg.norm(r - c)) ** 2
if type == 'S':
result += weight * math.exp(-exp * R2center)
elif type == 'X':
result += weight * r[0] * math.exp(-exp * R2center)
elif type == 'Y':
result += weight * r[1] * math.exp(-exp * R2center)
elif type == 'Z':
result += weight * r[2] * math.exp(-exp * R2center)
elif type == 'XX':
result += weight * r[0] * r[0] * math.exp(-exp * R2center)
elif type == 'YY':
result += weight * r[1] * r[1] * math.exp(-exp * R2center)
elif type == 'ZZ':
result += weight * r[2] * r[2] * math.exp(-exp * R2center)
elif type == 'XY':
result += weight * r[0] * r[1] * math.exp(-exp * R2center)
elif type == 'XZ':
result += weight * r[0] * r[2] * math.exp(-exp * R2center)
elif type == 'YZ':
result += weight * r[1] * r[2] * math.exp(-exp * R2center)
else:
print('Error! This type is not described! type = ', type)
return result
return inner_func
def orbitals(tip_type):
if tip_type == 'oxygen':
O2_centers = [[0, 0, -1.188061], [0, 0, 1.188061]]
O2_26_data = [[0, 'X', 1.59780706, 15.539616], [0, 'X', 1.23102421, 3.5999336],\
[0, 'X', 0.540485934, 1.0137618], [0, 'X', 0.116494955, 0.27000582],\
[1, 'X', -1.59780706, 15.539616], [1, 'X', -1.23102421, 3.5999336],\
[1, 'X', -0.540485934, 1.0137618], [1, 'X', -0.116494955, 0.27000582]]
O2_27_data = [[0, 'Y', 1.59780706, 15.539616], [0, 'Y', 1.23102421, 3.5999336],\
[0, 'Y', 0.540485934, 1.0137618], [0, 'Y', 0.116494955, 0.27000582],\
[1, 'Y', -1.59780706, 15.539616], [1, 'Y', -1.23102421, 3.5999336],\
[1, 'Y', -0.540485934, 1.0137618], [1, 'Y', -0.116494955, 0.27000582]]
O2_POMO_26 = get_mol_orbital(O2_26_data, O2_centers)
O2_POMO_27 = get_mol_orbital(O2_27_data, O2_centers)
return [O2_POMO_26, O2_POMO_27]
elif tip_type == 'IrCl6':
IrCl6_centers = [[0.0, 0.0, 0.0], [0.0, 0.0, 4.791539], [0.0, 4.791539, 0.0], [-4.791539, 0.0, 0.0],\
[0.0, 0.0, -4.791539], [0.0, -4.791539, 0.0], [4.791539, 0.0, 0.0]]
IrCl6_29_data = [[0, 'YZ', 1.53018648, 1.24], [0, 'YZ', 0.316517948, 0.4647], [0, 'YZ', 0.0244957551, 0.1529],\
[1, 'Y', 0.143821066, 6.296], [1, 'Y', -0.129983297, 0.6333], [1, 'Y', -0.0361555127, 0.1819],\
[2, 'Z', 0.143821066, 6.296], [2, 'Z', -0.129983297, 0.6333], [2, 'Z', -0.0361555127, 0.1819],\
[4, 'Y', -0.143821066, 6.296], [4, 'Y', 0.129983297, 0.6333], [4, 'Y', 0.0361555127, 0.1819],\
[5, 'Z', -0.143821066, 6.296], [5, 'Z', 0.129983297, 0.6333], [5, 'Z', 0.0361555127, 0.1819]]
IrCl6_30_data = [[0, 'XZ', 1.53018648, 1.24], [0, 'XZ', 0.316517948, 0.4647], [0, 'XZ', 0.0244957551, 0.1529],\
[1, 'X', 0.143821066, 6.296], [1, 'X', -0.129983297, 0.6333], [1, 'X', -0.0361555127, 0.1819],\
[3, 'Z', -0.143821066, 6.296], [3, 'Z', 0.129983297, 0.6333], [3, 'Z', 0.0361555127, 0.1819],\
[4, 'X', -0.143821066, 6.296], [4, 'X', 0.129983297, 0.6333], [4, 'X', 0.0361555127, 0.1819],\
[6, 'Z', 0.143821066, 6.296], [6, 'Z', -0.129983297, 0.6333], [6, 'Z', -0.0361555127, 0.1819]]
IrCl6_31_data = [[0, 'XY', 1.53018648, 1.24], [0, 'XY', 0.316517948, 0.4647], [0, 'XY', 0.0244957551, 0.1529],\
[2, 'X', 0.143821066, 6.296], [2, 'X', -0.129983297, 0.6333], [2, 'X', -0.0361555127, 0.1819],\
[3, 'Y', -0.143821066, 6.296], [3, 'Y', 0.129983297, 0.6333], [3, 'Y', 0.0361555127, 0.1819],\
[5, 'X', -0.143821066, 6.296], [5, 'X', 0.129983297, 0.6333], [5, 'X', 0.0361555127, 0.1819],\
[6, 'Y', 0.143821066, 6.296], [6, 'Y', -0.129983297, 0.6333], [6, 'Y', -0.0361555127, 0.1819]]
IrCl6_29 = get_mol_orbital(IrCl6_29_data, IrCl6_centers)
IrCl6_30 = get_mol_orbital(IrCl6_30_data, IrCl6_centers)
IrCl6_31 = get_mol_orbital(IrCl6_31_data, IrCl6_centers)
return [IrCl6_29, IrCl6_30, IrCl6_31]
elif tip_type == 'RuNH3_6':
RuNH3_6_centers = [[-1.73717655E-03, 2.23398325E-03, 5.95596999E-04], [2.80310681E+00, 1.05571952E+00, -2.92279419E+00],\
[3.11970961E+00, -3.93834154E-01, -4.17275494E+00], [2.20095082E+00, 2.57871173E+00, -3.96265199E+00],\
[2.86486865E+00, 6.10478919E-01, 2.99160626E+00], [2.39084949E+00, -2.98868166E-01, 4.63779676E+00],\
[4.60991448E+00, -5.89373268E-02, 2.47213750E+00], [-1.16539862E+00, 4.01564374E+00, 3.25100126E-01],\
[-1.81856108E+00, 4.42909277E+00, 2.10458406E+00], [3.14711646E-01, 5.21963568E+00, -3.05318533E-02],\
[1.21468221E+00, -3.99589459E+00, -3.05565040E-01], [1.28176071E+00, -4.84983673E+00, 1.43473405E+00],\
[2.84949113E-02, -5.05488013E+00, -1.41702627E+00], [-2.87292734E+00, -6.18033007E-01, -2.98146293E+00],\
[-3.64380554E+00, -2.39428507E+00, -2.87082722E+00], [-4.34685477E+00, 6.34642890E-01, -2.85350843E+00],\
[-2.83649579E+00, -1.07571542E+00, 2.88962265E+00], [-2.30623961E+00, -5.65363155E-01, 4.68553427E+00],\
[-4.54655856E+00, -2.25539186E-01, 2.54576816E+00], [-3.15571140E+00, -2.98787230E+00, 2.91511808E+00],\
[-2.12017233E+00, -4.36184326E-01, -4.75944808E+00], [2.98933039E+00, -4.14797331E+00, -1.07307692E+00],\
[4.52142190E+00, 1.53602082E+00, -2.16060783E+00], [3.08027187E+00, 2.48844379E+00, 3.42328804E+00],\
[-2.57792824E+00, 4.48333677E+00, -9.20281760E-01]]
RuNH3_6_35_data = [[0, 'S', -5.90938527e-05, 1.508], [0, 'S', -6.69986041e-05, 0.5129], [0, 'S', 7.19232404e-05, 0.1362],\
[0, 'S', 0.000667825176, 2.565], [0, 'S', -0.00057234006, 1.508], [0, 'S', -0.000107459153, 0.5129],\
[0, 'S', 3.48252636e-05, 0.0417], [0, 'X', -0.000852926268, 4.859], [0, 'X', 0.00119048253, 1.219],\
[0, 'X', 0.000164935069, 0.4413], [0, 'Y', -6.78621664e-05, 4.859], [0, 'Y', 9.47194692e-05, 1.219],\
[0, 'Y', 1.31228824e-05, 0.4413], [0, 'Z', 0.000373774035, 4.859], [0, 'Z', -0.000521699794, 1.219],\
[0, 'Z', -7.22787521e-05, 0.4413], [0, 'X', -0.000230489569, 0.5725], [0, 'X', 0.000240730695, 0.083],\
[0, 'Y', 0.000424795214, 0.5725], [0, 'Y', -0.000443669739, 0.083], [0, 'Z', -0.000188882268, 0.5725],\
[0, 'Z', 0.000197274696, 0.083], [0, 'X', -1.04499827e-05, 0.025], [0, 'Y', 2.0331508e-05, 0.025],\
[0, 'Z', -8.67202565e-06, 0.025], [0, 'XX', -0.295461307, 4.195], [0, 'XX', -0.442053419, 1.377],\
[0, 'XX', -0.0792638374, 0.4828], [0, 'YY', -0.0672194557, 4.195], [0, 'YY', -0.100570158, 1.377],\
[0, 'YY', -0.0180330618, 0.4828], [0, 'ZZ', 0.362680763, 4.195], [0, 'ZZ', 0.542623577, 1.377],\
[0, 'ZZ', 0.0972968992, 0.4828], [0, 'XY', 0.0475038166, 4.195], [0, 'XY', 0.071072672, 1.377],\
[0, 'XY', 0.0127439184, 0.4828], [0, 'XZ', -0.360816255, 4.195], [0, 'XZ', -0.539834, 1.377],\
[0, 'XZ', -0.0967967049, 0.4828], [0, 'YZ', 1.21237734, 4.195], [0, 'YZ', 1.81389419, 1.377],\
[0, 'YZ', 0.325246244, 0.4828], [0, 'XX', -0.00640014621, 0.1501], [0, 'YY', -0.0015460197, 0.1501],\
[0, 'ZZ', 0.00794616591, 0.1501], [0, 'XY', 0.000762713575, 0.1501], [0, 'XZ', -0.0078740316, 0.1501],\
[0, 'YZ', 0.0264934952, 0.1501], [1, 'S', 8.84456152e-05, 5909.0], [1, 'S', 0.000163160035, 887.5],\
[1, 'S', 0.000263459435, 204.7], [1, 'S', 0.000357001585, 59.84], [1, 'S', 0.000372065476, 20.0],\
[1, 'S', 0.000216286771, 7.193], [1, 'S', 3.36894315e-05, 2.686], [1, 'S', 0.000104934209, 7.193],\
[1, 'S', -0.000120618546, 0.7], [1, 'S', -0.000271470039, 0.2133], [1, 'X', 0.00260803484, 26.79],\
[1, 'X', 0.0025401732, 5.956], [1, 'X', 0.00178461332, 1.707], [1, 'X', 0.00067760306, 0.5314],\
[1, 'Y', 0.0525694372, 26.79], [1, 'Y', 0.0512015687, 5.956], [1, 'Y', 0.0359719573, 1.707],\
[1, 'Y', 0.0136582575, 0.5314], [1, 'Z', 0.0190111972, 26.79], [1, 'Z', 0.0185165217, 5.956],\
[1, 'Z', 0.013008889, 1.707], [1, 'Z', 0.0049393686, 0.5314], [1, 'X', -0.000755895033, 0.1654],\
[1, 'Y', 0.00215785904, 0.1654], [1, 'Z', -4.18680836e-06, 0.1654], [2, 'S', -0.00652499257, 19.2406],\
[2, 'S', -0.0111143259, 2.8992], [2, 'S', -0.0128500417, 0.6534], [2, 'S', -0.00736568257, 0.1776],\
[3, 'S', 0.00283887518, 19.2406], [3, 'S', 0.00483558925, 2.8992], [3, 'S', 0.00559075956, 0.6534],\
[3, 'S', 0.0019287417, 0.1776], [4, 'S', -4.25579402e-05, 5909.0], [4, 'S', -7.85087534e-05, 887.5],\
[4, 'S', -0.000126770455, 204.7], [4, 'S', -0.000171780727, 59.84], [4, 'S', -0.000179029116, 20.0],\
[4, 'S', -0.000104072084, 7.193], [4, 'S', -1.62105584e-05, 2.686], [4, 'S', -2.38121517e-05, 7.193],\
[4, 'S', 2.73713134e-05, 0.7], [4, 'S', 0.000279429803, 0.2133], [4, 'X', 0.0263895084, 26.79],\
[4, 'X', 0.0257028475, 5.956], [4, 'X', 0.0180576837, 1.707], [4, 'X', 0.00685635457, 0.5314],\
[4, 'Y', -0.0427381048, 26.79], [4, 'Y', -0.0416260498, 5.956], [4, 'Y', -0.0292446213, 1.707],\
[4, 'Y', -0.0111039431, 0.5314], [4, 'Z', -0.0163681686, 26.79], [4, 'Z', -0.0159422652, 5.956],\
[4, 'Z', -0.0112003303, 1.707], [4, 'Z', -0.00425267367, 0.5314], [4, 'X', -0.000588236873, 0.1654],\
[4, 'Y', 0.000383927762, 0.1654], [4, 'Z', 0.000887710294, 0.1654], [5, 'S', -0.00112688506, 19.2406],\
[5, 'S', -0.00191947617, 2.8992], [5, 'S', -0.00221923931, 0.6534], [5, 'S', -0.00251590545, 0.1776],\
[6, 'S', 0.00787639039, 19.2406], [6, 'S', 0.0134162252, 2.8992], [6, 'S', 0.0155114269, 0.6534],\
[6, 'S', 0.0117906265, 0.1776], [7, 'S', 0.000290643176, 5909.0], [7, 'S', 0.000536163953, 887.5],\
[7, 'S', 0.000865760125, 204.7], [7, 'S', 0.00117315114, 59.84], [7, 'S', 0.00122265294, 20.0],\
[7, 'S', 0.000710744947, 7.193], [7, 'S', 0.000110707618, 2.686], [7, 'S', 0.000580000402, 7.193],\
[7, 'S', -0.000666692074, 0.7], [7, 'S', -0.000791507988, 0.2133], [7, 'X', -0.00114263758, 26.79],\
[7, 'X', -0.00111290589, 5.956], [7, 'X', -0.000781878451, 1.707], [7, 'X', -0.000296872842, 0.5314],\
[7, 'Y', 0.00554095051, 26.79], [7, 'Y', 0.00539677374, 5.956], [7, 'Y', 0.00379153451, 1.707],\
[7, 'Y', 0.00143961459, 0.5314], [7, 'Z', -0.0664334707, 26.79], [7, 'Z', -0.064704857, 5.956],\
[7, 'Z', -0.0454587704, 1.707], [7, 'Z', -0.0172603227, 0.5314], [7, 'X', 0.000922022693, 0.1654],\
[7, 'Y', -0.000104102274, 0.1654], [7, 'Z', -0.000908609724, 0.1654], [8, 'S', -0.00838784083, 19.2406],\
[8, 'S', -0.0142874027, 2.8992], [8, 'S', -0.0165186555, 0.6534], [8, 'S', -0.0103740236, 0.1776],\
[9, 'S', 0.00116448404, 19.2406], [9, 'S', 0.00198352029, 2.8992],[9, 'S', 0.00229328515, 0.6534],\
[9, 'S', 0.000746855298, 0.1776], [10, 'S', -0.000159483962, 5909.0], [10, 'S', -0.000294208013, 887.5],\
[10, 'S', -0.000475066562, 204.7], [10, 'S', -0.000643740529, 59.84], [10, 'S', -0.000670903538, 20.0],\
[10, 'S', -0.00039000544, 7.193], [10, 'S', -6.07483366e-05, 2.686], [10, 'S', -0.000287196538, 7.193],\
[10, 'S', 0.000330123316, 0.7], [10, 'S', 0.00050688431, 0.2133], [10, 'X', 0.0112645738, 26.79],\
[10, 'X', 0.0109714671, 5.956], [10, 'X', 0.00770806744, 1.707], [10, 'X', 0.00292669006, 0.5314],\
[10, 'Y', -0.000944376614, 26.79], [10, 'Y', -0.000919803725, 5.956], [10, 'Y', -0.00064621341, 1.707],\
[10, 'Y', -0.000245361938, 0.5314], [10, 'Z', 0.0596286316, 26.79], [10, 'Z', 0.0580770813, 5.956],\
[10, 'Z', 0.0408023883, 1.707], [10, 'Z', 0.0154923326, 0.5314], [10, 'X', 0.000209517748, 0.1654],\
[10, 'Y', -0.000360297199, 0.1654], [10, 'Z', -0.000498649525, 0.1654], [11, 'S', 0.00915046467, 19.2406],\
[11, 'S', 0.0155864157, 2.8992], [11, 'S', 0.0180205344, 0.6534], [11, 'S', 0.0129436385, 0.1776],\
[12, 'S', -0.00660194617, 19.2406], [12, 'S', -0.0112454046, 2.8992], [12, 'S', -0.0130015909, 0.6534],\
[12, 'S', -0.00966746285, 0.1776], [13, 'S', 7.83519574e-05, 5909.0], [13, 'S', 0.000144539761, 887.5],\
[13, 'S', 0.000233392717, 204.7], [13, 'S', 0.000316259579, 59.84], [13, 'S', 0.000329604337, 20.0],\
[13, 'S', 0.000191603528, 7.193], [13, 'S', 2.98447006e-05, 2.686], [13, 'S', 0.000246799817, 7.193],\
[13, 'S', -0.000283688566, 0.7], [13, 'S', -6.68862239e-05, 0.2133], [13, 'X', -0.0305463365, 26.79],\
[13, 'X', -0.0297515141, 5.956], [13, 'X', -0.0209020977, 1.707], [13, 'X', -0.00793635528, 0.5314],\
[13, 'Y', 0.0315627487, 26.79], [13, 'Y', 0.0307414789, 5.956], [13, 'Y', 0.0215976033, 1.707],\
[13, 'Y', 0.00820043303, 0.5314], [13, 'Z', 0.0231505973, 26.79], [13, 'Z', 0.0225482136, 5.956],\
[13, 'Z', 0.0158413775, 1.707], [13, 'Z', 0.00601484126, 0.5314], [13, 'X', 0.000237693867, 0.1654],\
[13, 'Y', -0.00123192073, 0.1654], [13, 'Z', 0.000288092875, 0.1654], [14, 'S', -0.00402881913, 19.2406],\
[14, 'S', -0.00686247659, 2.8992], [14, 'S', -0.00793418435, 0.6534], [14, 'S', -0.00693878318, 0.1776],\
[15, 'S', 0.00845874161, 19.2406], [15, 'S', 0.0144081713, 2.8992], [15, 'S', 0.0166582845, 0.6534],\
[15, 'S', 0.0131334049, 0.1776], [16, 'S', -0.000169669121, 5909.0], [16, 'S', -0.000312997083, 887.5],\
[16, 'S', -0.000505405842, 204.7], [16, 'S', -0.000684851871, 59.84], [16, 'S', -0.000713749597, 20.0],\
[16, 'S', -0.000414912443, 7.193], [16, 'S', -6.46279209e-05, 2.686], [16, 'S', -0.000409625509, 7.193],\
[16, 'S', 0.000470851536, 0.7], [16, 'S', 0.000158871955, 0.2133], [16, 'X', -0.0106805032, 26.79],\
[16, 'X', -0.0104025942, 5.956], [16, 'X', -0.00730840251, 1.707], [16, 'X', -0.00277494056, 0.5314],\
[16, 'Y', -0.0345284702, 26.79], [16, 'Y', -0.0336300317, 5.956], [16, 'Y', -0.0236269727, 1.707],\
[16, 'Y', -0.00897096797, 0.5314], [16, 'Z', -0.0277495771, 26.79], [16, 'Z', -0.0270275269, 5.956],\
[16, 'Z', -0.0189883449, 1.707], [16, 'Z', -0.007209719, 0.5314], [16, 'X', -0.000866107884, 0.1654],\
[16, 'Y', 0.000791526817, 0.1654], [16, 'Z', -0.000995436732, 0.1654], [17, 'S', -0.00496446317, 19.2406],\
[17, 'S', -0.00845620296, 2.8992], [17, 'S', -0.00977680176, 0.6534], [17, 'S', -0.00611518732, 0.1776],\
[18, 'S', -0.00155862643, 19.2406], [18, 'S', -0.0026548815, 2.8992], [18, 'S', -0.00306949233, 0.6534],\
[18, 'S', -0.00339973617, 0.1776], [19, 'S', 0.00651256261, 19.2406], [19, 'S', 0.0110931534, 2.8992],\
[19, 'S', 0.0128255627, 0.6534], [19, 'S', 0.00960542363, 0.1776], [20, 'S', -0.00442680693, 19.2406],\
[20, 'S', -0.00754038788, 2.8992], [20, 'S', -0.00871796453, 0.6534], [20, 'S', -0.00582235994, 0.1776],\
[21, 'S', -0.00271809997, 19.2406], [21, 'S', -0.00462986716, 2.8992], [21, 'S', -0.00535291, 0.6534],\
[21, 'S', -0.00443674741, 0.1776], [22, 'S', 0.00382464248, 19.2406], [22, 'S', 0.00651469293, 2.8992],\
[22, 'S', 0.00753208757, 0.6534], [22, 'S', 0.00564203493, 0.1776], [23, 'S', -0.00685678285, 19.2406],\
[23, 'S', -0.0116794798, 2.8992], [23, 'S', -0.0135034553, 0.6534], [23, 'S', -0.0100430099, 0.1776],\
[24, 'S', 0.00744861736, 19.2406], [24, 'S', 0.0126875793, 2.8992], [24, 'S', 0.0146689889, 0.6534],\
[24, 'S', 0.0111030771, 0.1776]]
RuNH3_6_36_data = [[0, 'S', 5.56219117e-05, 1.508], [0, 'S', 6.30622352e-05, 0.5129], [0, 'S', -6.76975344e-05, 0.1362],\
[0, 'S', -0.000531100526, 2.565], [0, 'S', 0.000455164192, 1.508], [0, 'S', 8.54589118e-05, 0.5129],\
[0, 'S', -4.21526227e-05, 0.0417], [0, 'X', 0.00055829555, 4.859], [0, 'X', -0.00077924801, 1.219],\
[0, 'X', -0.000107960698, 0.4413], [0, 'Y', -0.000233384918, 4.859], [0, 'Y', 0.000325749924, 1.219],\
[0, 'Y', 4.51309322e-05, 0.4413], [0, 'Z', -0.000209188811, 4.859], [0, 'Z', 0.000291977904, 1.219],\
[0, 'Z', 4.04519971e-05, 0.4413], [0, 'X', -0.000662019566, 0.5725], [0, 'X', 0.000691434456, 0.083],\
[0, 'Y', 0.00029307311, 0.5725], [0, 'Y', -0.00030609495, 0.083], [0, 'Z', 0.000292702989, 0.5725],\
[0, 'Z', -0.000305708385, 0.083], [0, 'X', -2.76526194e-05, 0.025], [0, 'Y', 1.48379268e-05, 0.025],\
[0, 'Z', 2.0610484e-05, 0.025], [0, 'XX', 0.31450656, 4.195], [0, 'XX', 0.470547909, 1.377],\
[0, 'XX', 0.0843731353, 0.4828], [0, 'YY', 0.229594293, 4.195], [0, 'YY', 0.343506713, 1.377],\
[0, 'YY', 0.0615935972, 0.4828], [0, 'ZZ', -0.544100853, 4.195], [0, 'ZZ', -0.814054622, 1.377],\
[0, 'ZZ', -0.145966732, 0.4828], [0, 'XY', 0.97411606, 4.195], [0, 'XY', 1.4574204, 1.377],\
[0, 'XY', 0.261327542, 0.4828], [0, 'XZ', -0.0405782461, 4.195], [0, 'XZ', -0.0607110035, 1.377],\
[0, 'XZ', -0.0108859855, 0.4828], [0, 'YZ', 0.454156155, 4.195], [0, 'YZ', 0.679484171, 1.377],\
[0, 'YZ', 0.121837137, 0.4828], [0, 'XX', 0.00679643964, 0.1501], [0, 'YY', 0.00510300971, 0.1501],\
[0, 'ZZ', -0.0118994494, 0.1501], [0, 'XY', 0.0213597971, 0.1501], [0, 'XZ', -0.000875545312, 0.1501],\
[0, 'YZ', 0.0101456122, 0.1501], [1, 'S', -1.09192934e-05, 5909.0], [1, 'S', -2.01433647e-05, 887.5],\
[1, 'S', -3.25260993e-05, 204.7], [1, 'S', -4.40745993e-05, 59.84], [1, 'S', -4.59343528e-05, 20.0],\
[1, 'S', -2.67022701e-05, 7.193], [1, 'S', -4.15922017e-06, 2.686], [1, 'S', -5.50655737e-05, 7.193],\
[1, 'S', 6.32961312e-05, 0.7], [1, 'S', -9.03633726e-05, 0.2133], [1, 'X', -0.0384295808, 26.79],\
[1, 'X', -0.0374296346, 5.956], [1, 'X', -0.0262964056, 1.707], [1, 'X', -0.00998452978, 0.5314],\
[1, 'Y', -0.0131017406, 26.79], [1, 'Y', -0.0127608304, 5.956], [1, 'Y', -0.00896519497, 1.707],\
[1, 'Y', -0.00340401109, 0.5314], [1, 'Z', -0.0385472955, 26.79], [1, 'Z', -0.0375442863, 5.956],\
[1, 'Z', -0.0263769548, 1.707], [1, 'Z', -0.0100151137, 0.5314], [1, 'X', -0.000651252471, 0.1654],\
[1, 'Y', 0.00153747643, 0.1654], [1, 'Z', -0.000247277414, 0.1654], [2, 'S', 0.00582315223, 19.2406],\
[2, 'S', 0.00991884832, 2.8992], [2, 'S', 0.0114678673, 0.6534], [2, 'S', 0.00948207442, 0.1776],\
[3, 'S', 0.00214294491, 19.2406], [3, 'S', 0.00365017859, 2.8992], [3, 'S', 0.0042202242, 0.6534],\
[3, 'S', 0.00147162814, 0.1776], [4, 'S', -6.82029859e-05, 5909.0], [4, 'S', -0.000125817447, 887.5],\
[4, 'S', -0.000203161231, 204.7], [4, 'S', -0.000275294304, 59.84], [4, 'S', -0.000286910508, 20.0],\
[4, 'S', -0.000166785019, 7.193], [4, 'S', -2.59789003e-05, 2.686], [4, 'S', -0.000209550435, 7.193],\
[4, 'S', 0.000240871582, 0.7], [4, 'S', 3.90585007e-05, 0.2133], [4, 'X', -0.0222719157, 26.79],\
[4, 'X', -0.0216923955, 5.956], [4, 'X', -0.0152401175, 1.707], [4, 'X', -0.00578654779, 0.5314],\
[4, 'Y', -0.0526912426, 26.79], [4, 'Y', -0.0513202047, 5.956], [4, 'Y', -0.0360553057, 1.707],\
[4, 'Y', -0.0136899042, 0.5314], [4, 'Z', 0.0304810868, 26.79], [4, 'Z', 0.0296879621, 5.956],\
[4, 'Z', 0.0208574489, 1.707], [4, 'Z', 0.0079194025, 0.5314], [4, 'X', 0.000167167683, 0.1654],\
[4, 'Y', -0.000251508023, 0.1654], [4, 'Z', -1.83135036e-05, 0.1654], [5, 'S', 0.00944561796, 19.2406],\
[5, 'S', 0.0160891641, 2.8992], [5, 'S', 0.0186017966, 0.6534], [5, 'S', 0.0134597372, 0.1776],\
[6, 'S', -0.00237882245, 19.2406], [6, 'S', -0.00405195985, 2.8992], [6, 'S', -0.00468475134, 0.6534],\
[6, 'S', -0.00406723053, 0.1776], [7, 'S', 1.23113753e-05, 5909.0], [7, 'S', 2.27114077e-05, 887.5],\
[7, 'S', 3.66727958e-05, 204.7], [7, 'S', 4.96935943e-05, 59.84], [7, 'S', 5.17904446e-05, 20.0],\
[7, 'S', 3.01064967e-05, 7.193], [7, 'S', 4.68947201e-06, 2.686], [7, 'S', 3.27892042e-06, 7.193],\
[7, 'S', -3.76901507e-06, 0.7], [7, 'S', -3.21084903e-06, 0.2133], [7, 'X', -0.0453716649, 26.79],\
[7, 'X', -0.0441910841, 5.956], [7, 'X', -0.0310467009, 1.707], [7, 'X', -0.011788178, 0.5314],\
[7, 'Y', -0.0105021073, 26.79], [7, 'Y', -0.0102288401, 5.956], [7, 'Y', -0.00718633057, 1.707],\
[7, 'Y', -0.00272859085, 0.5314], [7, 'Z', -0.00940613787, 26.79], [7, 'Z', -0.00916138807, 5.956],\
[7, 'Z', -0.00643638601, 1.707], [7, 'Z', -0.00244384304, 0.5314], [7, 'X', -0.00186778818, 0.1654],\
[7, 'Y', -0.000712057951, 0.1654], [7, 'Z', 0.00113608073, 0.1654], [8, 'S', -0.00100831405, 19.2406],\
[8, 'S', -0.00171750862, 2.8992], [8, 'S', -0.00198573063, 0.6534], [8, 'S', -0.00299073334, 0.1776],\
[9, 'S', -0.0044447342, 19.2406], [9, 'S', -0.00757092424, 2.8992], [9, 'S', -0.00875326973, 0.6534],\
[9, 'S', -0.00423832627, 0.1776], [10, 'S', -0.000457245237, 5909.0], [10, 'S', -0.000843503078, 887.5],\
[10, 'S', -0.00136202989, 204.7], [10, 'S', -0.00184562314, 59.84], [10, 'S', -0.00192350029, 20.0],\
[10, 'S', -0.00111815714, 7.193], [10, 'S', -0.000174167279, 2.686], [10, 'S', -0.00104676381, 7.193],\
[10, 'S', 0.00120322182, 0.7], [10, 'S', 0.000895658494, 0.2133], [10, 'X', 0.0293098929, 26.79],\
[10, 'X', 0.028547243, 5.956], [10, 'X', 0.0200560302, 1.707], [10, 'X', 0.00761511035, 0.5314],\
[10, 'Y', 0.00944318459, 26.79], [10, 'Y', 0.00919747083, 5.956], [10, 'Y', 0.00646173616, 1.707],\
[10, 'Y', 0.00245346828, 0.5314], [10, 'Z', 0.0281989298, 26.79], [10, 'Z', 0.0274651874, 5.956],\
[10, 'Z', 0.0192958257, 1.707], [10, 'Z', 0.00732646696, 0.5314], [10, 'X', -0.00057701544, 0.1654],\
[10, 'Y', -0.00063761305, 0.1654], [10, 'Z', 0.00163521154, 0.1654], [11, 'S', 0.00172354654, 19.2406],\
[11, 'S', 0.00293579767, 2.8992], [11, 'S', 0.00339427896, 0.6534], [11, 'S', 0.000364705439, 0.1776],\
[12, 'S', -0.00587728837, 19.2406], [12, 'S', -0.0100110609, 2.8992], [12, 'S', -0.0115744807, 0.6534],\
[12, 'S', -0.00840107361, 0.1776], [13, 'S', 0.000115874039, 5909.0], [13, 'S', 0.000213758615, 887.5],\
[13, 'S', 0.00034516249, 204.7], [13, 'S', 0.000467713583, 59.84], [13, 'S', 0.000487449032, 20.0],\
[13, 'S', 0.000283360817, 7.193], [13, 'S', 4.41370722e-05, 2.686], [13, 'S', 0.000129872941, 7.193],\
[13, 'S', -0.000149284829, 0.7], [13, 'S', -0.000476787014, 0.2133], [13, 'X', 0.0269341081, 26.79],\
[13, 'X', 0.0262332766, 5.956], [13, 'X', 0.0184303397, 1.707], [13, 'X', 0.00699784902, 0.5314],\
[13, 'Y', 0.0519580248, 26.79], [13, 'Y', 0.0506060655, 5.956], [13, 'Y', 0.0355535831, 1.707],\
[13, 'Y', 0.0134994042, 0.5314], [13, 'Z', -0.0383878092, 26.79], [13, 'Z', -0.0373889499, 5.956],\
[13, 'Z', -0.0262678223, 1.707], [13, 'Z', -0.00997367694, 0.5314], [13, 'X', -2.67012554e-05, 0.1654],\
[13, 'Y', 0.000904200797, 0.1654], [13, 'Z', 5.09914983e-05, 0.1654], [14, 'S', -0.00912908749, 19.2406],\
[14, 'S', -0.0155500029, 2.8992], [14, 'S', -0.017978435, 0.6534], [14, 'S', -0.0120460603, 0.1776],\
[15, 'S', 0.0016055472, 19.2406], [15, 'S', 0.00273480385, 2.8992], [15, 'S', 0.00316189609, 0.6534],\
[15, 'S', 0.00209218298, 0.1776], [16, 'S', 0.00056041656, 5909.0], [16, 'S', 0.00103382836, 887.5],\
[16, 'S', 0.00166935387, 204.7], [16, 'S', 0.00226206353, 59.84], [16, 'S', 0.00235751262, 20.0],\
[16, 'S', 0.00137045446, 7.193], [16, 'S', 0.000213465815, 2.686], [16, 'S', 0.00123513498, 7.193],\
[16, 'S', -0.00141974851, 0.7], [16, 'S', -0.00113528459, 0.2133], [16, 'X', 0.0251517247, 26.79],\
[16, 'X', 0.0244972713, 5.956], [16, 'X', 0.0172106992, 1.707], [16, 'X', 0.00653476148, 0.5314],\
[16, 'Y', 0.0249511719, 26.79], [16, 'Y', 0.0243019368, 5.956], [16, 'Y', 0.0170734658, 1.707],\
[16, 'Y', 0.00648265511, 0.5314], [16, 'Z', 0.0387229602, 26.79], [16, 'Z', 0.0377153802, 5.956],\
[16, 'Z', 0.0264971578, 1.707], [16, 'Z', 0.0100607537, 0.5314], [16, 'X', -0.000516896597, 0.1654],\
[16, 'Y', 0.000704840718, 0.1654], [16, 'Z', -0.000725627747, 0.1654], [17, 'S', 0.00856706598, 19.2406],\
[17, 'S', 0.0145926853, 2.8992], [17, 'S', 0.0168716139, 0.6534], [17, 'S', 0.0131577385, 0.1776],\
[18, 'S', -0.00424743898, 19.2406], [18, 'S', -0.00723486203, 2.8992], [18, 'S', -0.00836472495, 0.6534],\
[18, 'S', -0.00641875413, 0.1776], [19, 'S', -0.00404193473, 19.2406], [19, 'S', -0.006884817, 2.8992],\
[19, 'S', -0.00796001365, 0.6534], [19, 'S', -0.0048262046, 0.1776], [20, 'S', 0.00765131699, 19.2406],\
[20, 'S', 0.0130328471, 2.8992], [20, 'S', 0.0150681769, 0.6534], [20, 'S', 0.0107127476, 0.1776],\
[21, 'S', 0.00394124012, 19.2406], [21, 'S', 0.00671329916, 2.8992], [21, 'S', 0.00776171, 0.6534],\
[21, 'S', 0.00637671339, 0.1776], [22, 'S', -0.0080669874, 19.2406], [22, 'S', -0.013740878, 2.8992],\
[22, 'S', -0.0158867805, 0.6534], [22, 'S', -0.0112089415, 0.1776], [23, 'S', -0.00703771987, 19.2406],\
[23, 'S', -0.0119876783, 2.8992], [23, 'S', -0.013859785, 0.6534], [23, 'S', -0.00968297661, 0.1776],\
[24, 'S', 0.00557469287, 19.2406], [24, 'S', 0.00949563585, 2.8992], [24, 'S', 0.0109785621, 0.6534],\
[24, 'S', 0.00747695987, 0.1776]]
RuNH3_6_37_data = [[0, 'S', 6.46572809e-05, 1.508], [0, 'S', 7.33062301e-05, 0.5129], [0, 'S', -7.86944994e-05, 0.1362],\
[0, 'S', -0.000705332604, 2.565], [0, 'S', 0.000604484705, 1.508], [0, 'S', 0.00011349444, 0.5129],\
[0, 'S', -5.48831361e-05, 0.0417], [0, 'X', -0.00067295965, 4.859], [0, 'X', 0.000939291866, 1.219],\
[0, 'X', 0.000130133929, 0.4413], [0, 'Y', 0.000629472821, 4.859], [0, 'Y', -0.000878594579, 1.219],\
[0, 'Y', -0.00012172464, 0.4413], [0, 'Z', 0.000910094893, 4.859], [0, 'Z', -0.00127027635, 1.219],\
[0, 'Z', -0.000175990082, 0.4413], [0, 'X', -0.000236679238, 0.5725], [0, 'X', 0.000247195383, 0.083],\
[0, 'Y', -0.000540292625, 0.5725], [0, 'Y', 0.000564298937, 0.083], [0, 'Z', 0.000564723999, 0.5725],\
[0, 'Z', -0.000589815847, 0.083], [0, 'X', -1.10741654e-05, 0.025], [0, 'Y', -2.85595029e-05, 0.025],\
[0, 'Z', 3.006976e-05, 0.025], [0, 'XX', 0.631922415, 4.195], [0, 'XX', 0.94544855, 1.377],\
[0, 'XX', 0.169526751, 0.4828], [0, 'YY', -0.328395499, 4.195], [0, 'YY', -0.491327797, 1.377],\
[0, 'YY', -0.0880991412, 0.4828], [0, 'ZZ', -0.303526916, 4.195], [0, 'ZZ', -0.454120753, 1.377],\
[0, 'ZZ', -0.0814276102, 0.4828], [0, 'XY', -0.799007062, 4.195], [0, 'XY', -1.19543167, 1.377],\
[0, 'XY', -0.214350794, 0.4828], [0, 'XZ', -0.201426236, 4.195], [0, 'XZ', -0.301363172, 1.377],\
[0, 'XZ', -0.0540369113, 0.4828], [0, 'YZ', 0.42420367, 4.195], [0, 'YZ', 0.634670864, 1.377],\
[0, 'YZ', 0.11380174, 0.4828], [0, 'XX', 0.0140554236, 0.1501], [0, 'YY', -0.0072542984, 0.1501],\
[0, 'ZZ', -0.00680112517, 0.1501], [0, 'XY', -0.017662346, 0.1501], [0, 'XZ', -0.00442890466, 0.1501],\
[0, 'YZ', 0.00925994787, 0.1501], [1, 'S', -0.000144334932, 5909.0], [1, 'S', -0.000266261843, 887.5],\
[1, 'S', -0.000429941038, 204.7], [1, 'S', -0.000582593037, 59.84], [1, 'S', -0.000607175891, 20.0],\
[1, 'S', -0.000352959684, 7.193], [1, 'S', -5.49779862e-05, 2.686], [1, 'S', -0.000458434382, 7.193],\
[1, 'S', 0.000526955788, 0.7], [1, 'S', 0.000175791862, 0.2133], [1, 'X', -0.0348575911, 26.79],\
[1, 'X', -0.0339505889, 5.956], [1, 'X', -0.023852182, 1.707], [1, 'X', -0.00905647809, 0.5314],\
[1, 'Y', 0.041745699, 26.79], [1, 'Y', 0.0406594666, 5.956], [1, 'Y', 0.0285655427, 1.707],\
[1, 'Y', 0.0108461026, 0.5314], [1, 'Z', -0.0194515659, 26.79], [1, 'Z', -0.0189454318, 5.956],\
[1, 'Z', -0.0133102224, 1.707], [1, 'Z', -0.00505378239, 0.5314], [1, 'X', 0.000364263504, 0.1654],\
[1, 'Y', -0.000219696323, 0.1654], [1, 'Z', -0.000642960835, 0.1654], [2, 'S', -0.00472926304, 19.2406],\
[2, 'S', -0.00805557555, 2.8992], [2, 'S', -0.00931360867, 0.6534], [2, 'S', -0.00755418466, 0.1776],\
[3, 'S', 0.00921480538, 19.2406], [3, 'S', 0.0156960102, 2.8992], [3, 'S', 0.0181472442, 0.6534],\
[3, 'S', 0.012974182, 0.1776], [4, 'S', -1.64347564e-05, 5909.0], [4, 'S', -3.03180143e-05, 887.5],\
[4, 'S', -4.89554132e-05, 204.7], [4, 'S', -6.63371959e-05, 59.84], [4, 'S', -6.91363327e-05, 20.0],\
[4, 'S', -4.01898997e-05, 7.193], [4, 'S', -6.26009102e-06, 2.686], [4, 'S', -5.20037054e-05, 7.193],\
[4, 'S', 5.97766106e-05, 0.7], [4, 'S', -2.91336866e-05, 0.2133], [4, 'X', -0.0356018561, 26.79],\
[4, 'X', -0.0346754879, 5.956], [4, 'X', -0.024361464, 1.707], [4, 'X', -0.00924984829, 0.5314],\
[4, 'Y', 0.0194700911, 26.79], [4, 'Y', 0.018963475, 5.956], [4, 'Y', 0.0133228987, 1.707],\
[4, 'Y', 0.00505859549, 0.5314], [4, 'Z', 0.0298093313, 26.79], [4, 'Z', 0.0290336859, 5.956],\
[4, 'Z', 0.0203977834, 1.707], [4, 'Z', 0.00774487125, 0.5314], [4, 'X', -0.00181624823, 0.1654],\
[4, 'Y', 0.000367426852, 0.1654], [4, 'Z', 0.00140796861, 0.1654], [5, 'S', 0.00289171111, 19.2406],\
[5, 'S', 0.00492558716, 2.8992], [5, 'S', 0.00569481236, 0.6534], [5, 'S', 0.00293981612, 0.1776],\
[6, 'S', -0.00565730622, 19.2406], [6, 'S', -0.00963635504, 2.8992], [6, 'S', -0.0111412573, 0.6534],\
[6, 'S', -0.00601759579, 0.1776], [7, 'S', -0.000210847953, 5909.0], [7, 'S', -0.000388961728, 887.5],\
[7, 'S', -0.000628068248, 204.7], [7, 'S', -0.000851065974, 59.84], [7, 'S', -0.000886977201, 20.0],\
[7, 'S', -0.000515612028, 7.193], [7, 'S', -8.03131695e-05, 2.686], [7, 'S', -0.000492953053, 7.193],\
[7, 'S', 0.000566633906, 0.7], [7, 'S', 0.000291422681, 0.2133], [7, 'X', 0.0493648046, 26.79],\
[7, 'X', 0.0480803214, 5.956], [7, 'X', 0.033779107, 1.707], [7, 'X', 0.0128256502, 0.5314],\
[7, 'Y', 0.0125337243, 26.79], [7, 'Y', 0.012207594, 5.956], [7, 'Y', 0.00857651553, 1.707],\
[7, 'Y', 0.00325643269, 0.5314], [7, 'Z', -0.0185032854, 26.79], [7, 'Z', -0.0180218258, 5.956],\
[7, 'Z', -0.0126613376, 1.707], [7, 'Z', -0.00480740616, 0.5314], [7, 'X', -0.000499509519, 0.1654],\
[7, 'Y', -0.000173911673, 0.1654], [7, 'Z', 0.00047173766, 0.1654], [8, 'S', -0.00538615124, 19.2406],\
[8, 'S', -0.00917448403, 2.8992], [8, 'S', -0.0106072562, 0.6534], [8, 'S', -0.00807752979, 0.1776],\
[9, 'S', 0.00881402738, 19.2406], [9, 'S', 0.0150133462, 2.8992], [9, 'S', 0.0173579691, 0.6534],\
[9, 'S', 0.0129760346, 0.1776], [10, 'S', 0.000211422777, 5909.0], [10, 'S', 0.000390022135, 887.5],\
[10, 'S', 0.000629780518, 204.7], [10, 'S', 0.000853386191, 59.84], [10, 'S', 0.000889395322, 20.0],\
[10, 'S', 0.000517017715, 7.193], [10, 'S', 8.05321232e-05, 2.686], [10, 'S', 0.000563489539, 7.193],\
[10, 'S', -0.00064771336, 0.7], [10, 'S', -0.000133561341, 0.2133], [10, 'X', -0.0549054694, 26.79],\
[10, 'X', -0.0534768168, 5.956], [10, 'X', -0.037570446, 1.707], [10, 'X', -0.0142651906, 0.5314],\
[10, 'Y', -0.0225286335, 26.79], [10, 'Y', -0.0219424334, 5.956], [10, 'Y', -0.0154157831, 1.707],\
[10, 'Y', -0.00585324655, 0.5314], [10, 'Z', 0.0241438107, 26.79], [10, 'Z', 0.0235155834, 5.956],\
[10, 'Z', 0.0165210087, 1.707], [10, 'Z', 0.00627289166, 0.5314], [10, 'X', -0.00083675402, 0.1654],\
[10, 'Y', -0.000482939566, 0.1654], [10, 'Z', 0.0014981449, 0.1654], [11, 'S', 0.00348617662, 19.2406],\
[11, 'S', 0.00593816816, 2.8992], [11, 'S', 0.00686552737, 0.6534], [11, 'S', 0.0038172112, 0.1776],\
[12, 'S', 0.00515560836, 19.2406], [12, 'S', 0.00878178954, 2.8992], [12, 'S', 0.010153235, 0.6534],\
[12, 'S', 0.00750694743, 0.1776], [13, 'S', -5.49524391e-05, 5909.0], [13, 'S', -0.000101373503, 887.5],\
[13, 'S', -0.000163690857, 204.7], [13, 'S', -0.000221809841, 59.84], [13, 'S', -0.000231169237, 20.0],\
[13, 'S', -0.000134381852, 7.193], [13, 'S', -2.09316927e-05, 2.686], [13, 'S', -0.000245504263, 7.193],\
[13, 'S', 0.000282199367, 0.7], [13, 'S', -8.95241297e-05, 0.2133], [13, 'X', 0.0296979085, 26.79],\
[13, 'X', 0.0289251623, 5.956], [13, 'X', 0.0203215396, 1.707], [13, 'X', 0.00771592209, 0.5314],\
[13, 'Y', -0.0229310905, 26.79], [13, 'Y', -0.0223344184, 5.956], [13, 'Y', -0.0156911744, 1.707],\
[13, 'Y', -0.00595781038, 0.5314], [13, 'Z', -0.0240680915, 26.79], [13, 'Z', -0.0234418344, 5.956],\
[13, 'Z', -0.0164691959, 1.707], [13, 'Z', -0.00625321876, 0.5314], [13, 'X', 0.000248203201, 0.1654],\
[13, 'Y', -0.00195105995, 0.1654], [13, 'Z', 4.95942547e-05, 0.1654], [14, 'S', -6.3651167e-05, 19.2406],\
[14, 'S', -0.000108420018, 2.8992], [14, 'S', -0.00012535189, 0.6534], [14, 'S', -0.00142203548, 0.1776],\
[15, 'S', -0.0050424763, 19.2406], [15, 'S', -0.0085890864, 2.8992], [15, 'S', -0.0099304375, 0.6534],\
[15, 'S', -0.00595550103, 0.1776], [16, 'S', 0.000175383692, 5909.0], [16, 'S', 0.000323539038, 887.5],\
[16, 'S', 0.000522428254, 204.7], [16, 'S', 0.000707918148, 59.84], [16, 'S', 0.000737789169, 20.0],\
[16, 'S', 0.000428886976, 7.193], [16, 'S', 6.68046333e-05, 2.686], [16, 'S', 0.000476517161, 7.193],\
[16, 'S', -0.000547741369, 0.7], [16, 'S', -0.000331187193, 0.2133], [16, 'X', 0.0423760874, 26.79],\
[16, 'X', 0.0412734522, 5.956], [16, 'X', 0.0289969018, 1.707], [16, 'X', 0.0110098861, 0.5314],\
[16, 'Y', -0.0526163557, 26.79], [16, 'Y', -0.0512472664, 5.956], [16, 'Y', -0.0360040625, 1.707],\
[16, 'Y', -0.0136704476, 0.5314], [16, 'Z', 0.0217360893, 26.79], [16, 'Z', 0.0211705114, 5.956],\
[16, 'Z', 0.0148734648, 1.707], [16, 'Z', 0.00564733276, 0.5314], [16, 'X', 0.0015961123, 0.1654],\
[16, 'Y', -0.000999996156, 0.1654], [16, 'Z', 0.000321058399, 0.1654], [17, 'S', 0.00241879545, 19.2406],\
[17, 'S', 0.00412004775, 2.8992], [17, 'S', 0.00476347248, 0.6534], [17, 'S', 0.00311018499, 0.1776],\
[18, 'S', -0.0088536234, 19.2406], [18, 'S', -0.015080792, 2.8992], [18, 'S', -0.0174359479, 0.6534],\
[18, 'S', -0.0104878557, 0.1776], [19, 'S', 0.00653506894, 19.2406], [19, 'S', 0.0111314894, 2.8992],\
[19, 'S', 0.0128698857, 0.6534], [19, 'S', 0.00880857322, 0.1776], [20, 'S', 0.00514585371, 19.2406],\
[20, 'S', 0.00876517399, 2.8992], [20, 'S', 0.0101340246, 0.6534], [20, 'S', 0.00725308796, 0.1776],\
[21, 'S', -0.0085865535, 19.2406], [21, 'S', -0.0146258793, 2.8992], [21, 'S', -0.0169099918, 0.6534],\
[21, 'S', -0.0110080324, 0.1776], [22, 'S', -0.00458878599, 19.2406], [22, 'S', -0.00781629442, 2.8992],\
[22, 'S', -0.00903695918, 0.6534], [22, 'S', -0.00673431556, 0.1776], [23, 'S', 0.00282027644, 19.2406],\
[23, 'S', 0.00480390914, 2.8992], [23, 'S', 0.00555413198, 0.6534], [23, 'S', 0.00344027048, 0.1776],\
[24, 'S', -0.00359574202, 19.2406], [24, 'S', -0.00612479603, 2.8992], [24, 'S', -0.00708130078, 0.6534],\
[24, 'S', -0.00539612557, 0.1776]]
RuNH36_35 = get_mol_orbital(RuNH3_6_35_data, RuNH3_6_centers)
RuNH36_36 = get_mol_orbital(RuNH3_6_36_data, RuNH3_6_centers)
RuNH36_37 = get_mol_orbital(RuNH3_6_37_data, RuNH3_6_centers)
return [RuNH36_35, RuNH36_36, RuNH36_37]
else:
pass