Source code for qmpy.analysis.pdf

import numpy as np
import numpy.linalg as linalg
import itertools
from collections import defaultdict
import logging

import qmpy
from qmpy.utils import *

logger = logging.getLogger(__name__)


[docs]class PDF(object): """ Container class for a Pair-distribution function. Attributes: structure: :mod:`~qmpy.Structure` pairs: dict of (atom1, atom2):[distances] limit: maximum distance """ def __init__(self, structure, limit=10): elements = list(structure.comp.keys()) pairs = itertools.combinations_with_replacement(elements, r=2) self.pairs = [self.get_pair(pair) for pair in pairs] self.distances = dict((p, []) for p in self.pairs) self.weights = dict((p, []) for p in self.pairs) structure = structure.copy() # `get_symmetry_dataset` cannot handle the reduced output? structure.reduce() structure.symmetrize() self.structure = structure self.cell = self.structure.cell self.uniq = self.structure.uniq_sites self.sites = self.structure.sites self.limit = limit self.limit2 = limit ** 2 lp = structure.find_lattice_points_within_distance(limit) self.lattice_points = np.array([np.dot(p, self.cell) for p in lp]) def get_pair(self, pair): return tuple(sorted(pair))
[docs] def get_pair_distances(self): """ Loops over pairs of atoms that are within radius max_dist of one another. Returns a dict of (atom1, atom2):[list of distances]. """ for s1 in self.uniq: for s2 in self.sites: _dist = s1.cart_coord - s2.cart_coord for lp in self.lattice_points: dist = _dist + lp if any([abs(p) > self.limit for p in dist]): continue dist2 = np.dot(dist, dist) if dist2 < 1e-6: continue if dist2 > self.limit2: continue distance = dist2 ** 0.5 for a1, a2 in itertools.product(s1, s2): pair = self.get_pair([a1.element_id, a2.element_id]) w = s1.multiplicity * a1.occupancy * a2.occupancy self.distances[pair].append(float(distance)) self.weights[pair].append(float(w))
def plot(self, smearing=0.1): renderer = Renderer() N = len(self.structure) V = self.structure.get_volume() xs = np.mgrid[0.5 : self.limit : 1000j] dr = xs[1] - xs[0] norms = [((x + dr / 2) ** 3 - (x - dr / 2) ** 3) for x in xs] for pair in self.pairs: e1, e2 = pair vals = np.zeros(xs.shape) nanb = self.structure.comp[e1] * self.structure.comp[e2] prefactor = 1.0 / (smearing * np.sqrt(2 * np.pi)) # prefactor = V**2 * (N-1)/N for w, d in zip(self.weights[pair], self.distances[pair]): if not d: continue vals += np.exp(-((d - xs) ** 2) / (2 * smearing ** 2)) * w vals = prefactor * vals / norms vals = [v if v > 1e-4 else 0.0 for v in vals] line = Line(list(zip(xs, vals)), label="%s-%s" % (e1, e2)) renderer.add(line) renderer.xaxis.label = "interatomic distance [&#8491;]" return renderer