Source code for abipy.core.site_symmetries

"""
This module provides objects related to site symmetries
"""
from __future__ import annotations

import numpy as np
import pandas as pd
import sympy as sp

from collections import OrderedDict
from monty.termcolor import cprint
from abipy.core.mixins import Has_Structure
from abipy.core.structure import Structure


[docs] class SiteSymmetries(Has_Structure): def __init__(self, structure: Structure): """ Args: structure: |Structure| object. """ # Get spacegroup from spglib if not available from Abinit. if not structure.has_abi_spacegroup: structure.spgset_abi_spacegroup(has_timerev=True, overwrite=False) self._structure = structure abispg = structure.abi_spacegroup nsym = len(abispg.symrel) indsym = self.structure.indsym #self.eq_atoms = structure.spget_equivalent_atoms() # Precompute sympy objects used to solve linear system of equations. self.sp_symrel, self.sp_symrec = [], [] self.sp_tnons, self.sp_inv_symrel = [], [] from abipy.core.symmetries import mati3inv for symr, symc, tau in zip(abispg.symrel, abispg.symrec, abispg.tnons): self.sp_symrel.append(sp.Matrix((symr))) inv_symr = mati3inv(symr, trans=False) self.sp_inv_symrel.append(sp.Matrix((inv_symr))) self.sp_symrec.append(sp.Matrix((symc))) # FIXME: Should convert to rational numbers # Permissible translations are unit cell translations or fractions thereof # that are consistent with the rotational symmetry (e.g. 1/2, 1/3, 1/4, and 1/6), plus combinations. tau = np.around(tau, decimals=5) self.sp_tnons.append(sp.Matrix(tau)) #from abipy.core.symmetries import indsym_from_symrel #other_indsym = indsym_from_symrel(abispg.symrel, abispg.tnons, self.structure, tolsym=1e-8) #assert np.all(self.structure.indsym == other_indsym) # Compute symmetry operations in Cartesian coordinates (numpy arrays) a = self.structure.lattice.matrix.T self.symcart = np.matmul(a, np.matmul(abispg.symrel, np.linalg.inv(a))) import spglib self.sitesym_labels = [] for iatom, site in enumerate(self.structure): rotations = [abispg.symrel[isym] for isym in range(nsym) if indsym[iatom, isym, 3] == iatom and abispg.symafm[isym] == 1] # Passing a 0-length rotations list to spglib can segfault. herm_symbol, ptg_num = "1", 1 if len(rotations) != 0: herm_symbol, ptg_num, trans_mat = spglib.get_pointgroup(rotations) self.sitesym_labels.append("%s (#%d,nsym:%d)" % (herm_symbol.strip(), ptg_num, len(rotations))) #for irred_isite in self.irred_isites: # for isite_eq, rm1, tau, l0 in self.eq_sites[irred_isite]: # # isite_eq = rm1(irred_site - tau) + l0 @property def structure(self) -> Structure: """|Structure| object.""" return self._structure def __str__(self) -> str: return self.to_string()
[docs] def to_string(self, verbose=0) -> str: """String representation with verbosity level verbose.""" lines = []; app = lines.append app(self.structure.to_string(verbose=verbose, title="Structure")) return "\n".join(lines)
[docs] def get_wyckoff_dataframe(self, view="all", select_symbols=None, decimals=5, verbose=0) -> pd.DataFrame: """ Find Wyckoff positions. Args: view: select_symbols: decimals: Number of decimal places to round to. If decimals is negative, it specifies the number of positions to the left of the decimal point. verbose: Verbosity level. Return |pandas-DataFrame| with cartesian tensor components as columns and (inequivalent) sites along the rows. """ # Select atoms. aview = self._get_atomview(view, select_symbols, verbose=verbose) sitesym_labels = self.structure.spget_site_symmetries() frac_symbols = sp.symbols("xfrac, yfrac, zfrac") vector = sp.Matrix((frac_symbols)) indsym = self.structure.indsym abispg = self.structure.abi_spacegroup rows = [] for (iatom, wlabel) in zip(aview.iatom_list, aview.wyck_labels): site = self.structure[iatom] system = [] for isym, (rm1, tau) in enumerate(zip(self.sp_inv_symrel, self.sp_tnons)): if indsym[iatom, isym, 3] != iatom: continue l0 = indsym[iatom, isym, :3] l0 = sp.Matrix(l0) m = rm1 * (vector - tau) - (l0 + vector) if verbose: print(92 * "=") print("System of linear equations for iatom %d, %s" % (iatom, repr(site))) sp.pprint(m) print("Rotation:") sp.pprint(rm1) system.append(m) # Solve system of linear equations. solutions = sp.solve(system, dict=True) #print(solutions) if verbose and not solutions: cprint("No solution for iatom %d" % iatom, "yellow") if solutions: d = OrderedDict() d["element"] = site.specie.symbol d["site_index"] = iatom d["cart_coords"] = np.round(site.coords, decimals=decimals) d["frac_coords"] = np.round(site.frac_coords, decimals=decimals) d["wyckoff"] = wlabel d["site_symmetry"] = sitesym_labels[iatom] for s in frac_symbols: d[str(s)] = str(s) if len(solutions) > 1: cprint("Found multiple solutions for iatom %d" % iatom, "red") d.update({str(k): str(v) for k, v in solutions[0].items()}) rows.append(d) df = pd.DataFrame(rows, index=None, columns=list(rows[0].keys()) if rows else None) return df
[docs] def get_tensor_rank2_dataframe(self, view="all", select_symbols=None, decimals=5, verbose=0) -> pd.DataFrame: """ Use site symmetries to detect indipendent elements of rank 2 tensor. Args: view: select_symbols: decimals: Number of decimal places to round to. If decimals is negative, it specifies the number of positions to the left of the decimal point. verbose: Verbosity level Return |pandas-DataFrame| with cartesian tensor components as columns and (inequivalent) sites along the rows. """ # Select atoms. aview = self._get_atomview(view, select_symbols, verbose=verbose) sitesym_labels = self.structure.spget_site_symmetries() # Symmetric tensor in reduced coordinates (direct lattice) # Operations in reduced coords are given by integer matrices and this facilitates the solution # of the system of equations with sympy. Txx, Tyy, Tzz, Txy, Txz, Tyz = symbols = sp.symbols('Txx, Tyy, Tzz, Txy, Txz, Tyz') tensor = sp.Matrix(([Txx, Txy, Txz], [Txy, Tyy, Tyz], [Txz, Tyz, Tzz])) indsym = self.structure.indsym rows = [] for (iatom, wlabel) in zip(aview.iatom_list, aview.wyck_labels): site = self.structure[iatom] system = [] for isym, rotf in enumerate(self.sp_symrel): if indsym[iatom, isym, 3] != iatom: continue m = rotf * tensor * rotf.T - tensor if verbose: print(92 * "=") print("System of linear equations for iatom %d, %s" % (iatom, repr(site))) sp.pprint(m) print("Rotation:") sp.pprint(rotf) print(92 * "=") system.append(m) # Solve system of linear equations. Print only sites for which we have contraints. solutions = sp.solve(system, dict=True) #print(solutions) if verbose and not solutions: cprint("No solution for iatom %d" % iatom, "yellow") if solutions: d = OrderedDict() d["element"] = site.specie.symbol d["site_index"] = iatom d["frac_coords"] = np.round(site.frac_coords, decimals=decimals) d["cart_coords"] = np.round(site.coords, decimals=decimals) d["wyckoff"] = wlabel d["site_symmetry"] = sitesym_labels[iatom] for s in symbols: d[str(s)] = str(s) if len(solutions) > 1: cprint("Found multiple solutions for iatom %d" % iatom, "red") d.update({str(k): str(v) for k, v in solutions[0].items()}) rows.append(d) import pandas as pd df = pd.DataFrame(rows, index=None, columns=list(rows[0].keys()) if rows else None) return df
[docs] def check_site_symmetries(self, tcart, verbose=0) -> float: """ Test whether a set of tensors associated to the crystalline sites are compatible with the space group symmetries. Args: tcart: (natom, 3, 3) array verbose: Verbosity level. Return: max_err """ natom = len(self.structure) indsym = self.structure.indsym nsym = len(self.symcart) tcart = np.reshape(tcart, (natom, 3, 3)) max_err = 0.0 for iatom in range(natom): ref_mat = tcart[iatom] sym_mat = np.zeros_like(ref_mat) count = 0 for isym, scart in enumerate(self.symcart): if indsym[iatom, isym, 3] != iatom: continue count += 1 sym_mat += np.matmul(scart, np.matmul(ref_mat, scart.T)) #sym_mat += np.matmul(scart.T, np.matmul(ref_mat, scart)) if (nsym // count) * count != nsym: max_err = 1e+23 sym_mat /= count diff_mat = sym_mat - ref_mat max_err = max(max_err, np.abs(diff_mat).sum()) if count != 1 and verbose: print("For iatom", iatom, "on-site symmetries with count:", count) print("ref_mat:\n", ref_mat, "\nsym_mat:\n", sym_mat, "\ndiff_mat:\n", diff_mat) for iatom in range(natom): ref_mat = tcart[iatom] for isym, scart in enumerate(self.symcart): jatom = indsym[iatom, isym, 3] if jatom == iatom: continue #sym_mat = np.matmul(scart, np.matmul(ref_mat, scart.T)) sym_mat = np.matmul(scart.T, np.matmul(ref_mat, scart)) diff_mat = sym_mat - tcart[jatom] max_err = max(max_err, np.abs(diff_mat).sum()) if verbose: print("For iatom", iatom, "ref_mat, sym_mat, diff_mat") print("ref_mat:\n", tcart[jatom], "\nsym_mat:\n", sym_mat, "\ndiff_mat:\n", diff_mat) if verbose: print("Max symmetrization error:", max_err) return max_err