def linear_rprofile(extract, direction=(0,0,1), nbpoints=20, sigma=0.2, indices=None): """ Computes profile for given direction of each wavefunction. :Parameters: extract Extraction object from an escan calculation. nbpoints Number of points to consider along the direction. sigma Smearing to use when creating profile. indices Indices of the wavefunctions for which to compute the profile. Can also be None, in which case the profile of each wavefunction is computed. Mostly for debugging purposes. Makes much uglier plots than linear_profile. """ from numpy import dot, max, arange, array, exp, multiply, sum from numpy.linalg import inv, norm from quantities import angstrom from pylada.physics import a0 direction = array(direction, dtype="float64") assert norm(direction) > 1e-12, ValueError("Direction cannot be null.") assert hasattr(extract, "gwfns"), ValueError("extract does not seem to be an escan extraction object.") if indices is None: indices = range(extract.gwfns) assert nbpoints > 1, ValueError("The number of points should be strictly larger than 1.") # first computes intersection of direction and cell. At the end, we should # end up in a0, knowing the structure cell should be in angstrom. direction = array(direction) udir = direction / norm(direction) dir = dot(inv(extract.structure.cell), direction) direction = direction/max(abs(dir)) * extract.structure.scale * angstrom.rescale(a0) # then creates array of points along direction. zpoints = arange(nbpoints+1, dtype="float64").reshape(nbpoints+1, 1) / float(nbpoints) zpoints = multiply(zpoints, direction) x = sum(zpoints*udir, axis=1).rescale(angstrom) if sigma is None: sigma = 0.75 * norm(zpoints[1] - zpoints[0]) * zpoints.units if not hasattr(sigma, "units"): sigma *= angstrom.rescale(a0) zpoints = sum(zpoints*udir, axis=1) gaussians = array([exp(-(sum(extract.rvectors*udir, axis=1)-z)**2/sigma**2) for z in zpoints]) results = [] for i, wfn in enumerate(extract.rwfns): if i not in indices: continue results.append( dot(gaussians, wfn.density) ) results[-1][-1] = results[-1][0] return x, array(results)
def read_structure(filename): """ Reads crystal structure from Espresso input """ from numpy import dot, array from quantities import bohr_radius as a0, angstrom, Ry from ..crystal import Structure from .. import error from . import Namelist from .card import read_cards namelists = Namelist() namelists.read(filename) cards = read_cards(filename) if 'atomic_positions' not in set([u.name for u in cards]): raise error.RuntimeError("Card ATOMIC_POSITIONS is missing from input") positions = [u for u in cards if u.name == 'atomic_positions'][0] if 'cell_parameters' not in set([u.name for u in cards]): cell_parameters = None if namelists.system.ibrav == 0: raise error.RuntimeError("Card CELL_PARAMETERS is missing") else: cell_parameters = [u for u in cards if u.name == 'cell_parameters'][0] cell, scale = read_cell_and_scale(namelists.system, cell_parameters) result = Structure() result.cell = cell result.scale = scale for line in positions.value.rstrip().lstrip().split('\n'): line = line.split() result.add_atom(array(line[1:4], dtype='float64'), line[0]) if positions.subtitle == 'bohr': factor = float(a0.rescale(result.scale)) for atom in result: atom.pos *= factor elif positions.subtitle == 'angstrom': factor = float(angstrom.rescale(result.scale)) for atom in result: atom.pos *= factor elif positions.subtitle == 'crystal': for atom in result: atom.pos = dot(result.cell, atom.pos) elif positions.subtitle == 'crystal_sg': raise error.RuntimeError( "Reading symmetric atomic positions is not implemented") if 'atomic_forces' in set([u.name for u in cards]): atomic_forces = [u for u in cards if u.name == 'atomic_forces'][0] if atomic_forces.value is None: raise error.RuntimeError("Atomic forces card is empty") lines = atomic_forces.value.rstrip().lstrip().split('\n') if len(lines) != len(result): raise error.RuntimeError( "Number forces and number of atoms do not match") for atom, force in zip(result, lines): atom.force = array(force.rstrip().lstrip().split()[1:4], dtype='float64') * Ry / a0 return result
def read_structure(filename): """ Reads crystal structure from Espresso input """ from numpy import dot, array from quantities import bohr_radius as a0, angstrom, Ry from ..crystal import Structure from .. import error from . import Namelist from .card import read_cards namelists = Namelist() namelists.read(filename) cards = read_cards(filename) if 'atomic_positions' not in set([u.name for u in cards]): raise error.RuntimeError("Card ATOMIC_POSITIONS is missing from input") positions = [u for u in cards if u.name == 'atomic_positions'][0] if 'cell_parameters' not in set([u.name for u in cards]): cell_parameters = None if namelists.system.ibrav == 0: raise error.RuntimeError("Card CELL_PARAMETERS is missing") else: cell_parameters = [u for u in cards if u.name == 'cell_parameters'][0] cell, scale = read_cell_and_scale(namelists.system, cell_parameters) result = Structure() result.cell = cell result.scale = scale for line in positions.value.rstrip().lstrip().split('\n'): line = line.split() result.add_atom(array(line[1:4], dtype='float64'), line[0]) if positions.subtitle == 'bohr': factor = float(a0.rescale(result.scale)) for atom in result: atom.pos *= factor elif positions.subtitle == 'angstrom': factor = float(angstrom.rescale(result.scale)) for atom in result: atom.pos *= factor elif positions.subtitle == 'crystal': for atom in result: atom.pos = dot(result.cell, atom.pos) elif positions.subtitle == 'crystal_sg': raise error.RuntimeError("Reading symmetric atomic positions is not implemented") if 'atomic_forces' in set([u.name for u in cards]): atomic_forces = [u for u in cards if u.name == 'atomic_forces'][0] if atomic_forces.value is None: raise error.RuntimeError("Atomic forces card is empty") lines = atomic_forces.value.rstrip().lstrip().split('\n') if len(lines) != len(result): raise error.RuntimeError("Number forces and number of atoms do not match") for atom, force in zip(result, lines): atom.force = array(force.rstrip().lstrip().split()[1:4], dtype='float64') * Ry / a0 return result
def thirdO(latt_vec_array, charge, n): """ Function returns 3rd order image charge correction, same as LZ fortran script Reference: S. Lany and A. Zunger, Phys. Rev. B 78, 235104 (2008) S. Lany and A. Zunger, Model. Simul. Mater. Sci. Eng. 17, 0842002 (2009) [Eq. 6, 7] Parameters defect = pylada.vasp.Extract object charge = charge of point defect. Default 1e0 elementary charge n = precision in integral of Eq. 7 (LZ 2009), larger the better Returns third image correction in eV """ cell_scale = 1.0 # SKW: In notebook workflow cell parameters are converted to Cartesians and units of Angstroms cell = (latt_vec_array * cell_scale) * angstrom.rescale(a0) #Anuj_05/22/18:modified to "third_order" thirdO = third_order( cell, n) * (4e0 * pi / 3e0) * Ry.rescale(eV) * charge * charge return thirdO
def ewald(structure, charges=None, cutoff=None, **kwargs): """ Ewald summation. Run-of-the-mill Ewald summation. Nothing fancy, so not very fast for large structures. :param structure: The structure to optimize. The charge of each atom can be given as a ``charge`` attribute. Otherwise, they should be in the ``charges`` map. :type structure: py:attr:`~pylada.crystal.Structure` :param dict charges: Map from atomic-types to charges. If not signed by a unit, then should be in units of elementary electronic charge. If an atom has a ``charge`` attribute, the attribute takes priority ove items in this map. :param float cutoff: Cutoff energy when computing reciprocal space part. Defaults to :py:math:`15 Ry`. """ from numpy import array, dot, zeros from numpy.linalg import inv from quantities import elementary_charge as em, Ry, a0, angstrom from .cppwrappers import ewald invcell = inv(structure.cell) positions = array([dot(invcell, a.pos) for a in structure], dtype='float64') cell = structure.cell.copy(order='F') * structure.scale \ * float(angstrom.rescale(a0)) if cutoff is None: cutoff = 15 elif hasattr(cutoff, 'rescale'): cutoff = float(cutoff.rescale(Ry)) else: cutoff = float(cutoff) c = [] for atom in structure: if hasattr(atom, 'charge'): if hasattr(atom.charge, 'rescale'): c.append(float(atom.charge.rescale(em))) continue try: dummy = float(atom.charge) except: raise ValueError('charge attribute is not a floating point.') else: c.append(dummy) continue if charges is None: raise ValueError( 'Atom has no charge attribute ' \ 'and charge dictionary is empty.' ) if atom.type not in charges: raise ValueError( 'Atom has no charge attribute ' \ 'and atomic type {0} not in dictionary' \ .format(atom.type) ) dummy = c[atom.type] if hasattr(dummy, 'rescale'): c.append(float(dummy.rescale(em))) else: try: dummy = float(dummy) except: raise ValueError('charge attribute is not a floating point.') else: c.append(dummy) c = array(c, order='F', dtype='float64') energy, force, cforces, stress = ewald(cell, positions, c, cutoff) result = structure.copy() for atom, force in zip(result, cforces): atom.force = force * Ry / a0 result.energy = energy * Ry result.stress = zeros((3, 3), dtype='float64') * Ry result.stress[0, 0] = stress[0] * Ry result.stress[1, 1] = stress[1] * Ry result.stress[2, 2] = stress[2] * Ry result.stress[0, 1] = stress[3] * Ry result.stress[1, 0] = stress[3] * Ry result.stress[1, 2] = stress[4] * Ry result.stress[2, 1] = stress[4] * Ry result.stress[0, 2] = stress[5] * Ry result.stress[2, 0] = stress[5] * Ry return result
def linear_profile(extract, direction=(0,0,1), nbpoints=20, sigma=None, indices=None): """ Computes profile for given direction of each wavefunction. :Parameters: extract Extraction object from an escan calculation. nbpoints Number of points to consider along the direction. sigma Smearing to use when creating profile. If None, defaults to 3/4 of distance between two points along the profile. indices Indices of the wavefunctions for which to compute the profile. Can also be None, in which case the profile of each wavefunction is computed. To get nice plots, we compute the convolution of the real-space density and a gaussian in fourier space. Comes down to an interpolation in real-space. """ from numpy import dot, max, arange, array, tensordot, exp, multiply, sum from numpy.linalg import inv, norm from quantities import angstrom from pylada.physics import a0 direction = array(direction, dtype="float64") assert norm(direction) > 1e-12, ValueError("Direction cannot be null.") assert hasattr(extract, "gwfns"), ValueError("extract does not seem to be an escan extraction object.") if indices is None: indices = range(extract.gwfns) assert nbpoints > 1, ValueError("The number of points should be strictly larger than 1.") # first computes intersection of direction and cell. At the end, we should # end up in a0, knowing the structure cell should be in angstrom. udir = direction / norm(direction) dir = dot(inv(extract.structure.cell), direction) direction = direction/max(abs(dir)) * extract.structure.scale * angstrom.rescale(a0) # then creates array of points along direction. zpoints = arange(nbpoints+1, dtype="float64").reshape(nbpoints+1, 1) / float(nbpoints) zpoints = multiply(zpoints, direction.magnitude) * direction.units x = sum(zpoints*udir, axis=1).rescale(angstrom) # computes sigma in g-space. if sigma is None: sigma = 0.75 * norm(zpoints[1] - zpoints[0]) * zpoints.units if not hasattr(sigma, "units"): sigma *= angstrom.rescale(a0) gsigma = 1e0/sigma # finds gvectors with zero components in directions perpendicular to direction. # dot does not work well with units. gvectors = extract.gvectors nonzero = (gvectors - dot(gvectors.magnitude, udir).reshape(gvectors.shape[0], 1) * udir * gvectors.units)**2 nonzero = sum(nonzero, axis=1) < 1e-12 gvectors = extract.gvectors[nonzero, :] # computes all exponentials exp(-i r.g), with r in first dim, and g in second. # tensordot does not conserve units. Must do it by hand. units = (zpoints.units * gvectors.units).simplified translations = exp(1j * tensordot(zpoints, gvectors, ((1),(1))) * units) # creates gaussian x translations matrix. gpoints = (dot(gvectors.magnitude, udir) * gvectors.units / gsigma).simplified gaussians = multiply(exp(-gpoints**2), translations) # fourrier operator to get auto-correlation function in g-space. units = (extract.rvectors.units * gvectors.units).simplified fourier = exp(-1j * tensordot(extract.rvectors, gvectors, ((1),(1))) * units) # finally computes profile in fourier space. results = [] for i, wfn in enumerate(extract.rwfns): if i not in indices: continue # compute fourier transform of real-space density. autocorr = dot(fourier.T, wfn.density) # now performs summation with gaussian in fourier space. results.append(dot(gaussians, autocorr)) return x, array(results)