Files
VASP_calc/electrochemistry/echem/io_data/ddec.py

234 lines
8.0 KiB
Python
Raw Normal View History

2026-01-08 19:47:32 +03:00
from matplotlib import colors
import numpy as np
from monty.re import regrep
from echem.core.structure import Structure
from nptyping import NDArray, Shape, Number
from dataclasses import dataclass
from echem.core.constants import ElemNum2Name, Bohr2Angstrom
@dataclass()
class LocalMultipoleMoments:
net_charges: NDArray[Shape['Natoms'], Number]
dipoles: NDArray[Shape['Natoms, 4'], Number]
quadrupoles: NDArray[Shape['Natoms, 8'], Number]
class Output_DDEC:
def __init__(self,
structure: Structure,
lmm_hirshfeld: LocalMultipoleMoments,
lmm_ddec: LocalMultipoleMoments,
charges_cm5: NDArray[Shape['Natons'], Number]):
self.structure = structure
self.lmm_hirshfeld = lmm_hirshfeld
self.lmm_ddec = lmm_ddec
self.charges_cm5 = charges_cm5
@staticmethod
def _process_lmm_(data, line_number, natoms):
charges_ddec = np.zeros(natoms)
dipoles_ddec = np.zeros((natoms, 4))
quadrupoles_ddec = np.zeros((natoms, 8))
idx = 0
while len(line := data[line_number].split()) != 0:
charges_ddec[idx] = float(line[5])
dipoles_ddec[idx] = list(map(float, line[6: 10]))
quadrupoles_ddec[idx] = list(map(float, line[10:]))
line_number += 1
idx += 1
return LocalMultipoleMoments(charges_ddec, dipoles_ddec, quadrupoles_ddec)
@staticmethod
def from_file(filepath):
file = open(filepath, 'r')
data = file.readlines()
file.close()
patterns = {'lattice': r' parameters',
'lmm': r'Multipole analysis for each of the expansion sites.',
'cm5': r'The computed CM5 net atomic charges are:'}
matches = regrep(filepath, patterns)
lattice = np.zeros((3, 3))
i = matches['lattice'][0][1]
natoms = int((data[i + 1].split()[0]).split('.')[0])
line = data[i + 2].split()
NX = int(line[0].split('.')[0])
lattice[0] = np.array([float(line[1]), float(line[2]), float(line[3])])
line = data[i + 3].split()
NY = int(line[0].split('.')[0])
lattice[1] = np.array([float(line[1]), float(line[2]), float(line[3])])
line = data[i + 4].split()
NZ = int(line[0].split('.')[0])
lattice[2] = np.array([float(line[1]), float(line[2]), float(line[3])])
if NX > 0 and NY > 0 and NZ > 0:
units = 'Bohr'
elif NX < 0 and NY < 0 and NZ < 0:
units = 'Angstrom'
else:
raise ValueError('The sign of the number of all voxels should be > 0 or < 0')
if units == 'Angstrom':
NX, NY, NZ = -NX, -NY, -NZ
lattice = lattice * np.array([NX, NY, NZ]).reshape((-1, 1)) * Bohr2Angstrom
coords = np.zeros((natoms, 3))
species = []
line_number = matches['lmm'][0][1] + 3
idx = 0
while len(line := data[line_number].split()) != 0:
species.append(ElemNum2Name[int(line[1])])
coords[idx] = list(map(float, line[2:5]))
line_number += 1
structure = Structure(lattice, species, coords)
line_number = matches['lmm'][0][1] + 3
lmm_hirshfeld = Output_DDEC._process_lmm_(data, line_number, natoms)
line_number = matches['lmm'][1][1] + 3
lmm_ddec = Output_DDEC._process_lmm_(data, line_number, natoms)
line_number = matches['cm5'][0][1] + 1
charges_cm5 = []
i = 0
while i < natoms:
charges = list(map(float, data[line_number].split()))
charges_cm5 += charges
line_number += 1
i += len(charges)
return Output_DDEC(structure, lmm_hirshfeld, lmm_ddec, np.array(charges_cm5))
class AtomicNetCharges:
"""Class that operates with DDEC output file DDEC6_even_tempered_net_atomic_charges.xyz"""
def __init__(self, structure: Structure, net_charges, dipoles_xyz=None,
dipoles_mag=None, Qs=None, quadrupole_tensor_eigs=None, date=None):
"""
Create a DDEC class object.
Args:
structure (Structure class): a base class that contains lattice, coords and species information
net_charges:
dipoles_xyz:
dipoles_mag:
Qs:
quadrupole_tensor_eigs:
"""
self.structure = structure
self.net_charges = net_charges
self.dipoles_xyz = dipoles_xyz
self.dipoles_mag = dipoles_mag
self.Qs = Qs
self.quadrupole_tensor_eigs = quadrupole_tensor_eigs
self.date = date
@staticmethod
def from_file(filepath):
"""
Read the positions of atoms and theirs charges
from file "DDEC6_even_tempered_net_atomic_charges.xyz"
Parameters:
----------
filepath: str
Path to file with atomic charges
Returns:
-------
DDEC class instance
"""
file = open(filepath, 'r')
data = file.readlines()
file.close()
patterns = {'date': r'\s+(\d\d\d\d/\d\d/\d\d\s+\d\d:\d\d:\d\d)'}
matches = regrep(filepath, patterns)
date = matches['date'][0][0][0]
natoms = int(data[0])
x_axis = data[1].split()[10:13]
y_axis = data[1].split()[15:18]
z_axis = data[1].split()[20:23]
lattice = np.array([x_axis, y_axis, z_axis], dtype=np.float32)
for start_line, string in enumerate(data):
if 'The following XYZ coordinates are in angstroms' in string:
break
coords = np.zeros((natoms, 3))
species = []
net_charges = np.zeros(natoms)
dipoles_xyz = np.zeros((natoms, 3))
dipoles_mag = np.zeros(natoms)
Qs = np.zeros((natoms, 5))
quadrupole_tensor_eigs = np.zeros((natoms, 3))
for i, j in enumerate(range(start_line + 2, start_line + 2 + natoms)):
line_splitted = data[j].split()
species.append(line_splitted[1])
coords[i] = line_splitted[2:5]
net_charges[i] = line_splitted[5]
dipoles_xyz[i] = line_splitted[6:9]
dipoles_mag[i] = line_splitted[9]
Qs[i] = line_splitted[10:15]
quadrupole_tensor_eigs[i] = line_splitted[15:18]
structure = Structure(lattice, species, coords, coords_are_cartesian=True)
return AtomicNetCharges(structure, net_charges, dipoles_xyz, dipoles_mag, Qs, quadrupole_tensor_eigs, date)
class AtomicSpinMoments:
"""Class that operates with DDEC output file DDEC6_even_tempered_atomic_spin_moments.xyz"""
def __init__(self, structure: Structure, spin_moments, date):
self.structure = structure
self.spin_moments = spin_moments
self.date = date
@staticmethod
def from_file(filepath):
file = open(filepath, 'r')
data = file.readlines()
file.close()
patterns = {'date': r'\s+(\d\d\d\d/\d\d/\d\d\s+\d\d:\d\d:\d\d)'}
matches = regrep(filepath, patterns)
date = matches['date'][0][0][0]
natoms = int(data[0])
x_axis = data[1].split()[10:13]
y_axis = data[1].split()[15:18]
z_axis = data[1].split()[20:23]
lattice = np.array([x_axis, y_axis, z_axis], dtype=np.float32)
coords = np.zeros((natoms, 3))
species = []
spin_moments = np.zeros(natoms)
for i, j in enumerate(range(2, 2 + natoms)):
line_splitted = data[j].split()
species += [line_splitted[0]]
coords[i] = line_splitted[1:4]
spin_moments[i] = line_splitted[4]
structure = Structure(lattice, species, coords, coords_are_cartesian=True)
return AtomicSpinMoments(structure, spin_moments, date)
class MidpointNormalize(colors.Normalize):
def __init__(self, vmin=None, vmax=None, midpoint=None, clip=False):
self.midpoint = midpoint
colors.Normalize.__init__(self, vmin, vmax, clip)
def __call__(self, value, clip=None):
x, y = [self.vmin, self.midpoint, self.vmax], [0, 0.5, 1]
return np.ma.masked_array(np.interp(value, x, y))