Source code for matchest.casteputils
"""
Utility module for analysing CASTEP data
"""
import numpy as np
[docs]
def atoms_to_castep(atoms, index):
"""Convert ase atoms' index to castep like
return (Specie, Ion) Depricatede, use ase_to_castep_index"""
atom = atoms[index]
symbol = atom.symbol
# Start counter
count = 0
for atom in atoms:
if atom.symbol == symbol:
count += 1
if atom.index == index:
break
return symbol, count
[docs]
def ase_to_castep_index(atoms, indices):
"""Convert a list of indices to castep syle
return list of (element, i in the same element)"""
if isinstance(indices, int):
indices = [indices]
symbols = np.array(atoms.get_chemical_symbols())
iatoms = np.arange(len(atoms))
res = []
# Iterate through given indices
for i in indices:
mask = symbols == symbols[i] # Select the same species
# Find the index via counting from first occurance
for c, a in enumerate(iatoms[mask]):
if a == i:
res.append((symbols[i], c + 1)) # Castep start counting from 1
break
return res
[docs]
def generate_ionic_fix_cons(atoms, indices, mask=None):
"""
create ionic constraint section via indices and ase Atoms
mask: a list of 3 integers, must be 0 (no fix) or 1 (fix this cartesian)
"""
castep_indices = ase_to_castep_index(atoms, indices)
count = 1
lines = []
if mask is None:
mask = (1, 1, 1)
for symbol, i in castep_indices:
if mask[0]:
lines.append(f"{count:<4d} {symbol:<2} {i:<4d} 1 0 0")
if mask[1]:
lines.append(f"{count + 1:<4d} {symbol:<2} {i:<4d} 0 1 0")
if mask[2]:
lines.append(f"{count + 2:<4d} {symbol:<2} {i:<4d} 0 0 1")
count += sum(mask)
return lines
[docs]
def castep_to_atoms(atoms, specie, ion):
"""Convert castep like index to ase Atoms index"""
return [atom for atom in atoms if atom.symbol == specie][ion - 1].index
[docs]
def sort_atoms_castep(atoms, copy=True, order=(0, 1, 2)):
"""
Sort atoms to castep style
:param copy: If True then return a copy of the atoms.
:param order: orders of coordinates. (0, 1, 2) means the sorted atoms
will be ascending by x, then y, then z if there are equal x or ys.
"""
if copy:
atoms = atoms.copy()
# Sort castep style
for i in reversed(order):
isort = np.argsort(atoms.positions[:, i], kind="mergesort")
atoms.positions = atoms.positions[isort]
atoms.numbers = atoms.numbers[isort]
isort = np.argsort(atoms.numbers, kind="mergesort")
atoms.positions = atoms.positions[isort]
atoms.numbers = atoms.numbers[isort]
return atoms
[docs]
def take_popn(seed):
"""
Take section of population analysis from a seed.castep file
Return a list of StringIO of the population analysis section
"""
import io
popns = []
rec = False
with open(seed + ".castep") as fh:
for line in fh:
if "Atomic Populations (Mulliken)" in line:
record = io.StringIO()
rec = True
# record information
if rec is True:
if line.strip() == "":
rec = False
record.seek(0)
popns.append(record)
else:
record.write(line)
return popns
[docs]
def read_popn(fn):
"""Read population file into pandas dataframe"""
import pandas as pd
table = pd.read_table(fn, sep=r"\s\s+", header=2, comment="=", engine="python")
return table
[docs]
def count_scf_lines(lines):
"""
Extract the number of SCF cycles in the CASTEP files
"""
counter = -1
lengths = []
for line in lines:
if "Initial" in line:
counter = 1
continue
if "----" in line and counter >= 1:
lengths.append(counter)
counter = -1
continue
# Counting the SCF lines
if counter >= 1:
counter += 1
return lengths