def str_template(name, scale, cell): from pylada.crystal import Structure structure = Structure() structure.name = name structure.scale = scale structure.cell = cell return structure
def get_madelungenergy(latt_vec_array, charge, epsilon, cutoff): """ Function returns leading first order correction term, i.e., screened Madelung-like lattice energy of point charge Reference: M. Leslie and M. J. Gillan, J. Phys. C: Solid State Phys. 18 (1985) 973 Parameters defect = pylada.vasp.Extract object charge = charge of point defect. Default 1e0 elementary charge epsilon = dimensionless relative permittivity, SKW: isotropic average of dielectric constant cutoff = Ewald cutoff parameter Returns Madelung (electrostatic) energy in eV Note: 1. Units in this function are either handled by the module Quantities, or\ defaults to Angstrom and elementary charges 2. Function is adopted from Haowei Peng's version in pylada.defects modules """ ewald_cutoff = cutoff * Ry cell_scale = 1.0 # SKW: In notebook workflow cell parameters are converted to Cartesians and units of Angstroms # SKW: Create point charge in pylada.crystal.structure class (used for charge model) # http://pylada.github.io/pylada/userguide/crystal.html struc = Structure() struc.cell = latt_vec_array struc.scale = cell_scale struc.add_atom(0., 0., 0., "P", charge=charge) #Anuj_05/22/18: added "cutoff" in ewald syntax result = ewald(struc, cutoff=ewald_cutoff).energy / epsilon return -1 * result.rescale(eV)
def from_spglib(strc): """ converting the pylada structure object from the spglib format to pylada """ import numpy as np from pylada import periodic_table from pylada.crystal import Structure out_s = Structure() out_s.scale = 1. cell = strc[0] positions = strc[1] symbols = strc[2] out_s.cell = np.transpose(cell) for ii in range(len(positions)): pp=np.dot(np.transpose(cell),positions[ii]) ss=periodic_table.symbols[symbols[ii]-1] out_s.add_atom(pp[0],pp[1],pp[2],ss) return out_s
def first_order_charge_correction(structure, charge=None, epsilon=1e0, cutoff=20.0, **kwargs): """ First order charge correction of +1 charge in given supercell. Units in this function are either handled by the module Quantities, or defaults to Angstroems and elementary charges. :Parameters: structure : `pylada.crystal.Structure` Defect supercell, with cartesian positions in angstrom. charge Charge of the point-defect. Defaults to 1e0 elementary charge. If no units are attached, expects units of elementary charges. epsilon dimensionless relative permittivity. cutoff Ewald cutoff parameter. :return: Electrostatic energy in eV. """ from quantities import elementary_charge, eV from pylada.crystal import Structure from pylada.physics import Ry from pylada.ewald import ewald if charge is None: charge = 1 elif charge == 0: return 0e0 * eV if hasattr(charge, "units"): charge = float(charge.rescale(elementary_charge)) ewald_cutoff = cutoff * Ry struc = Structure() struc.cell = structure.cell struc.scale = structure.scale struc.add_atom(0e0, 0, 0, "A", charge=charge) result = ewald(struc, ewald_cutoff).energy / epsilon return -result.rescale(eV)
def get_madelungenergy(defect, charge=None, epsilon=1e0, cutoff=100.0): """ Function returns leading first order correction term, i.e., screened Madelung-like lattice energy of point charge Reference: M. Leslie and M. J. Gillan, J. Phys. C: Solid State Phys. 18 (1985) 973 Parameters defect = pylada.vasp.Extract object charge = charge of point defect. Default 1e0 elementary charge epsilon = dimensionless relative permittivity cutoff = Ewald cutoff parameter Returns Madelung (electrostatic) energy in eV Note: 1. Units in this function are either handled by the module Quantities, or\ defaults to Angstrom and elementary charges 2. Function is adopted from Haowei Peng's version in pylada.defects modules """ from quantities import elementary_charge, eV from pylada.crystal import Structure from pylada.physics import Ry from pylada.ewald import ewald if charge is None: charge = 1 elif charge == 0: return 0e0 * eV if hasattr(charge, "units"): charge = float(charge.rescale(elementary_charge)) ewald_cutoff = cutoff * Ry structure = defect.structure struc = Structure() struc.cell = structure.cell struc.scale = structure.scale struc.add_atom(0., 0., 0., "P", charge=charge) #Anuj_05/22/18: added "cutoff" in ewald syntax result = ewald(struc, cutoff=ewald_cutoff).energy / epsilon return -1 * result.rescale(eV)
def make_surface(structure=None, miller=None, nlayers=5, vacuum=15, acc=5): """Returns a slab from the 3D structure Takes a structure and makes a slab defined by the miller indices with nlayers number of layers and vacuum defining the size of the vacuum thickness. Variable acc determines the number of loops used to get the direct lattice vectors perpendicular and parallel to miller. For high index surfaces use larger acc value .. warning: (1) cell is always set such that miller is alogn z-axes (2) nlayers and vacuum are always along z-axes. :param structure: LaDa structure :param miller: 3x1 float64 array Miller indices defining the slab :param nlayers: integer Number of layers in the slab :param vacuum: real Vacuum thicness in angstroms :param acc: integer number of loops for finding the cell vectors of the slab structure """ direct_cell = transpose(structure.cell) reciprocal_cell = 2 * pi * transpose(inv(direct_cell)) orthogonal = [] # lattice vectors orthogonal to miller for n1 in arange(-acc, acc + 1): for n2 in arange(-acc, acc + 1): for n3 in arange(-acc, acc + 1): pom = array([n1, n2, n3]) if dot(pom, miller) == 0 and dot(pom, pom) != 0: orthogonal.append(array([n1, n2, n3])) # chose the shortest parallel and set it to be a3 lattice vector norm_orthogonal = [sqrt(dot(dot(x, direct_cell), dot(x, direct_cell))) for x in orthogonal] a1 = orthogonal[norm_orthogonal.index(min(norm_orthogonal))] # chose the shortest orthogonal to miller and not colinear with a1 and set it as a2 in_plane = [] for x in orthogonal: if dot(x, x) > 1e-3: v = cross(dot(x, direct_cell), dot(a1, direct_cell)) v = sqrt(dot(v, v)) if v > 1e-3: in_plane.append(x) norm_in_plane = [sqrt(dot(dot(x, direct_cell), dot(x, direct_cell))) for x in in_plane] a2 = in_plane[norm_in_plane.index(min(norm_in_plane))] a1 = dot(a1, direct_cell) a2 = dot(a2, direct_cell) # new cartesian axes z-along miller, x-along a1, and y-to define the right-hand orientation e1 = a1 / sqrt(dot(a1, a1)) e2 = a2 - dot(e1, a2) * e1 e2 = e2 / sqrt(dot(e2, e2)) e3 = cross(e1, e2) # find vectors parallel to miller and set the shortest to be a3 parallel = [] for n1 in arange(-acc, acc + 1): for n2 in arange(-acc, acc + 1): for n3 in arange(-acc, acc + 1): pom = dot(array([n1, n2, n3]), direct_cell) if sqrt(dot(pom, pom)) - dot(e3, pom) < 1e-8 and sqrt(dot(pom, pom)) > 1e-3: parallel.append(pom) # if there are no lattice vectors parallel to miller if len(parallel) == 0: for n1 in arange(-acc, acc + 1): for n2 in arange(-acc, acc + 1): for n3 in arange(-acc, acc + 1): pom = dot(array([n1, n2, n3]), direct_cell) if dot(e3, pom) > 1e-3: parallel.append(pom) parallel = [x for x in parallel if sqrt( dot(x - dot(e1, x) * e1 - dot(e2, x) * e2, x - dot(e1, x) * e1 - dot(e2, x) * e2)) > 1e-3] norm_parallel = [sqrt(dot(x, x)) for x in parallel] assert len(norm_parallel) != 0, "Increase acc, found no lattice vectors parallel to (hkl)" a3 = parallel[norm_parallel.index(min(norm_parallel))] # making a structure in the new unit cell - defined by the a1,a2,a3 new_direct_cell = array([a1, a2, a3]) assert abs(det(new_direct_cell)) > 1e-5, "Something is wrong your volume is equal to zero" # make sure determinant is positive if det(new_direct_cell) < 0.: new_direct_cell = array([-a1, a2, a3]) #structure = fill_structure(transpose(new_direct_cell),structure.to_lattice()) structure = supercell(lattice=structure, supercell=transpose(new_direct_cell)) # transformation matrix to new coordinates x' = dot(m,x) m = array([e1, e2, e3]) # seting output structure out_structure = Structure() out_structure.scale = structure.scale out_structure.cell = transpose(dot(new_direct_cell, transpose(m))) for atom in structure: p = dot(m, atom.pos) out_structure.add_atom(p[0], p[1], p[2], atom.type) # repaeting to get nlayers and vacuum repeat_cell = dot(out_structure.cell, array([[1., 0., 0.], [0., 1., 0.], [0., 0., nlayers]])) out_structure = supercell(lattice=out_structure, supercell=repeat_cell) # checking whether there are atoms close to the cell faces and putting them back to zero for i in range(len(out_structure)): scaled_pos = dot(out_structure[i].pos, inv(transpose(out_structure.cell))) for j in range(3): if abs(scaled_pos[j] - 1.) < 1e-5: scaled_pos[j] = 0. out_structure[i].pos = dot(scaled_pos, transpose(out_structure.cell)) # adding vaccum to the cell out_structure.cell = out_structure.cell + \ array([[0., 0., 0.], [0., 0., 0.], [0., 0., float(vacuum) / float(out_structure.scale)]]) # translating atoms so that center of the slab and the center of the cell along z-axes coincide max_z = max([x.pos[2] for x in out_structure]) min_z = min([x.pos[2] for x in out_structure]) center_atoms = 0.5 * (max_z + min_z) center_cell = 0.5 * out_structure.cell[2][2] for i in range(len(out_structure)): out_structure[i].pos = out_structure[i].pos + array([0., 0., center_cell - center_atoms]) # exporting the final structure return out_structure
def poscar(path="POSCAR", types=None): """ Tries to read a VASP POSCAR file. :param path: Path to the POSCAR file. Can also be an object with file-like behavior. :type path: str or file object :param types: Species in the POSCAR. :type types: None or sequence of str :return: `pylada.crystal.Structure` instance. """ import re from os.path import join, exists, isdir from copy import deepcopy from numpy import array, dot, transpose from numpy.linalg import det from quantities import angstrom from . import Structure from .. import error # if types is not none, converts to a list of strings. if types is not None: if isinstance(types, str): types = [types] # can't see another way of doing this... elif not hasattr(types, "__iter__"): types = [str(types)] # single lone vasp.specie.Specie else: types = [str(s) for s in types] if path is None: path = "POSCAR" if not hasattr(path, 'read'): assert exists(path), IOError("Could not find path %s." % (path)) if isdir(path): assert exists(join(path, "POSCAR")), IOError("Could not find POSCAR in %s." % (path)) path = join(path, "POSCAR") result = Structure() poscar = path if hasattr(path, "read") else open(path, 'r') try: # gets name of structure result.name = poscar.readline().strip() if len(result.name) > 0 and result.name[0] == "#": result.name = result.name[1:].strip() # reads scale scale = float(poscar.readline().split()[0]) # gets cell vectors. cell = [] for i in range(3): line = poscar.readline() assert len(line.split()) >= 3,\ RuntimeError("Could not read column vector from poscar: %s." % (line)) cell.append([float(f) for f in line.split()[:3]]) result.cell = transpose(array(cell)) vol = det(cell) if scale < 1.E-8: scale = abs(scale / vol) ** (1.0 / 3) print(result) print(scale) result.scale = scale * angstrom # checks for vasp 5 input. is_vasp_5 = True line = poscar.readline().split() for i in line: if not re.match(r"[A-Z][a-z]?", i): is_vasp_5 = False break if is_vasp_5: text_types = deepcopy(line) if types is not None and not set(text_types).issubset(set(types)): raise error.ValueError("Unknown species in poscar: {0} not in {1}." .format(text_types, types)) types = text_types line = poscar.readline().split() if types is None: raise RuntimeError("No atomic species given in POSCAR or input.") # checks/reads for number of each specie if len(types) < len(line): raise RuntimeError("Too many atomic species in POSCAR.") nb_atoms = [int(u) for u in line] # Check whether selective dynamics, cartesian, or direct. first_char = poscar.readline().strip().lower()[0] selective_dynamics = False if first_char == 's': selective_dynamics = True first_char = poscar.readline().strip().lower()[0] # Checks whether cartesian or direct. is_direct = first_char not in ['c', 'k'] # reads atoms. for n, specie in zip(nb_atoms, types): for i in range(n): line = poscar.readline().split() pos = array([float(u) for u in line[:3]], dtype="float64") if is_direct: pos = dot(result.cell, pos) result.add_atom(pos=pos, type=specie) if selective_dynamics: for which, freeze in zip(line[3:], ['x', 'y', 'z']): if which.lower()[0] == 't': result[-1].freeze = getattr(result[-1], 'freeze', '') + freeze finally: poscar.close() return result
def test_ingaas(): from numpy import abs, all, dot, array from quantities import eV, angstrom from pylada.crystal import Structure vff = functional() structure = Structure( 10.0, 0.5, 0.5, 0.00, 0.0, 0.5, 0.00, 0.5, 0.0, scale=6.5 )\ .add_atom(pos=(0.00, 0.00, 0.00), type="Ga") \ .add_atom(pos=(0.25, 0.25, 0.25), type="As") \ .add_atom(pos=(1.00, 0.00, 0.00), type="Ga") \ .add_atom(pos=(1.25, 0.25, 0.25), type="As") \ .add_atom(pos=(2.00, 0.00, 0.00), type="In") \ .add_atom(pos=(2.25, 0.25, 0.25), type="As") \ .add_atom(pos=(3.00, 0.00, 0.00), type="In") \ .add_atom(pos=(3.25, 0.25, 0.25), type="As") \ .add_atom(pos=(4.00, 0.00, 0.00), type="Ga") \ .add_atom(pos=(4.25, 0.25, 0.25), type="As") \ .add_atom(pos=(5.00, 0.00, 0.00), type="In") \ .add_atom(pos=(5.25, 0.25, 0.25), type="As") \ .add_atom(pos=(6.00, 0.00, 0.00), type="In") \ .add_atom(pos=(6.25, 0.25, 0.25), type="As") \ .add_atom(pos=(7.00, 0.00, 0.00), type="Ga") \ .add_atom(pos=(7.25, 0.25, 0.25), type="As") \ .add_atom(pos=(8.00, 0.00, 0.00), type="Ga") \ .add_atom(pos=(8.25, 0.25, 0.25), type="As") \ .add_atom(pos=(9.00, 0.00, 0.00), type="Ga") \ .add_atom(pos=(9.25, 0.25, 0.25), type="As") epsilon = array([[1e0, 0.1, 0], [0.1, 1e0, 0], [0, 0, 1e0]]) structure.cell = dot(epsilon, structure.cell) for atom in structure: atom.pos = dot(epsilon, atom.pos) out = vff._pyeval(structure) assert abs(out.energy - 12.7962141476*eV) < 1e-8 assert abs(vff.energy(structure) - 12.7962141476*eV) < 1e-8 stress = array([[ 0.07050804, 0.04862879, 0.00025269], [ 0.04862879, 0.07050804, -0.00025269], [ 0.00025269, -0.00025269, 0.06073765]]) * eV/angstrom**3 assert all(abs(out.stress - stress) < 1e-6) assert all(abs(vff.jacobian(structure)[0] - stress) < 1e-6) gradients = array([u.gradient for u in out]) check_gradients = array( [[ 9.15933995e-16, -5.96744876e-15, 1.12373933e+00], [ 2.22044605e-16, -9.43689571e-15, -1.12373933e+00], [ 6.19921859e-02, -6.19921859e-02, 1.00514227e+00], [ -5.99520433e-15, 2.19269047e-15, -1.12373933e+00], [ 7.66544525e-02, -7.66544525e-02, 1.02138259e+00], [ 3.82409256e-01, -3.82409256e-01, -1.65478066e+00], [ -1.89756333e-02, 1.89756333e-02, 1.15847492e+00], [ -4.16333634e-17, 8.81239526e-16, -1.09205526e+00], [ -7.54665596e-02, 7.54665596e-02, 1.14107325e+00], [ -3.64621516e-01, 3.64621516e-01, -5.74094841e-01], [ 7.66544525e-02, -7.66544525e-02, 1.02138259e+00], [ 3.82409256e-01, -3.82409256e-01, -1.65478066e+00], [ -1.89756333e-02, 1.89756333e-02, 1.15847492e+00], [ -1.19973476e-14, -2.32591724e-14, -1.09205526e+00], [ -1.37458746e-01, 1.37458746e-01, 1.25967031e+00], [ -3.64621516e-01, 3.64621516e-01, -5.74094841e-01], [ -7.77156117e-16, -3.10862447e-15, 1.12373933e+00], [ 8.29891711e-15, 0.00000000e+00, -1.12373933e+00], [ 3.33066907e-15, 7.16093851e-15, 1.12373933e+00], [ -4.44089210e-16, 7.85482790e-15, -1.12373933e+00]]) assert all(abs(gradients - check_gradients) < 1e-8) assert all(abs(vff.jacobian(structure)[1].magnitude - check_gradients) < 1e-8)
def poscar(path="POSCAR", types=None): """ Tries to read a VASP POSCAR file. :param path: Path to the POSCAR file. Can also be an object with file-like behavior. :type path: str or file object :param types: Species in the POSCAR. :type types: None or sequence of str :return: `pylada.crystal.Structure` instance. """ import re from os.path import join, exists, isdir from copy import deepcopy from numpy import array, dot, transpose from numpy.linalg import det from quantities import angstrom from . import Structure # if types is not none, converts to a list of strings. if types is not None: if isinstance(types, str): types = [types] # can't see another way of doing this... elif not hasattr(types, "__iter__"): types = [str(types)] # single lone vasp.specie.Specie else: types = [str(s) for s in types] if path is None: path = "POSCAR" if not hasattr(path, 'read'): assert exists(path), IOError("Could not find path %s." % (path)) if isdir(path): assert exists(join(path, "POSCAR")), IOError("Could not find POSCAR in %s." % (path)) path = join(path, "POSCAR") result = Structure() poscar = path if hasattr(path, "read") else open(path, 'r') try: # gets name of structure result.name = poscar.readline().strip() if len(result.name) > 0: if result.name[0] == "#": result.name = result.name[1:].strip() # reads scale scale = float(poscar.readline().split()[0]) # gets cell vectors. cell = [] for i in range(3): line = poscar.readline() assert len(line.split()) >= 3,\ RuntimeError("Could not read column vector from poscar: %s." % (line)) cell.append( [float(f) for f in line.split()[:3]] ) result.cell = transpose(array(cell)) vol = det(cell) if scale < 1.E-8 : scale = abs(scale/vol) **(1.0/3) result.scale = scale * angstrom # checks for vasp 5 input. is_vasp_5 = True line = poscar.readline().split() for i in line: if not re.match(r"[A-Z][a-z]?", i): is_vasp_5 = False break if is_vasp_5: text_types = deepcopy(line) if types is not None and not set(text_types).issubset(set(types)): raise RuntimeError( "Unknown species in poscar: {0} not in {1}."\ .format(text_types, types) ) types = text_types line = poscar.readline().split() assert types is not None, RuntimeError("No atomic species given in POSCAR or input.") # checks/reads for number of each specie assert len(types) >= len(line), RuntimeError("Too many atomic species in POSCAR.") nb_atoms = [int(u) for u in line] # Check whether selective dynamics, cartesian, or direct. first_char = poscar.readline().strip().lower()[0] selective_dynamics = False if first_char == 's': selective_dynamics = True first_char = poscar.readline().strip().lower()[0] # Checks whether cartesian or direct. is_direct = first_char not in ['c', 'k'] # reads atoms. for n, specie in zip(nb_atoms, types): for i in range(n): line = poscar.readline().split() pos = array([float(u) for u in line[:3]], dtype="float64") if is_direct: pos = dot(result.cell, pos) result.add_atom(pos=pos, type=specie) if selective_dynamics: for which, freeze in zip(line[3:], ['x', 'y', 'z']): if which.lower()[0] == 't': result[-1].freeze = getattr(result[-1], 'freeze', '') + freeze finally: poscar.close() return result
(4.3538700, -0.0001445, 4.3538615),\ (4.3538020, 4.3537935, -0.0001445) structure.add_atoms = [(7.61911540668, 7.61923876219, 7.61912846850), 'Cd'],\ [(1.08833559332, 1.08834823781, 1.08832253150), 'Cd'],\ [(4.35372550000, 4.35379350000, 4.35372550000), 'Ga'],\ [(4.35379775000, 2.17685850000, 2.17682450000), 'Ga'],\ [(2.17682450000, 4.35386575000, 2.17682875000), 'Ga'],\ [(2.17682875000, 2.17686275000, 4.35379775000), 'Ga'],\ [(2.32881212361, 2.32884849688, 2.32881647755), 'O'],\ [(2.32887187256, 4.20174404476, 4.20169148188), 'O'],\ [(4.20168277385, 2.32891695560, 4.20168347161), 'O'],\ [(4.20168782554, 4.20174474241, 2.32887622633), 'O'],\ [(6.37863887654, 6.37873414925, 6.37863016865), 'O'],\ [(6.37857477364, 4.50584295539, 4.50575516433), 'O'],\ [(4.50576822615, 6.37867004441, 4.50576752839), 'O'],\ [(4.50576317445, 4.50584225759, 6.37857477367), 'O'] # this is converged to less than 1meV third = third_order_charge_correction(structure, epsilon=10.0, n=20) assert abs(third - 0.11708438633232088*eV) < 1e-12 # to compare to Stephan Lany's 3rd0 (-1.299), multiply third by epsilon / (1.0 - 1.0/epsilon) # tries another cell. Jumping hoops since can't modify cell component directly. cell = structure.cell.copy() cell[0,0] = -30.0 structure.cell = cell third = third_order_charge_correction(structure, epsilon=10.0, n=20) assert abs(third - 0.17412996139970385*eV) < 1e-12 # to compare to Stephan Lany's 3rd0 (-1.928), multiply third by epsilon / (1.0 - 1.0/epsilon)
((2.25, 0.25, 0.25), "As"),\ ((3.00, 0.00, 0.00), "In"),\ ((3.25, 0.25, 0.25), "As"),\ ((4.00, 0.00, 0.00), "Ga"),\ ((4.25, 0.25, 0.25), "As"),\ ((5.00, 0.00, 0.00), "In"),\ ((5.25, 0.25, 0.25), "As"),\ ((6.00, 0.00, 0.00), "In"),\ ((6.25, 0.25, 0.25), "As"),\ ((7.00, 0.00, 0.00), "Ga"),\ ((7.25, 0.25, 0.25), "As"),\ ((8.00, 0.00, 0.00), "Ga"),\ ((8.25, 0.25, 0.25), "As"),\ ((9.00, 0.00, 0.00), "Ga"),\ ((9.25, 0.25, 0.25), "As"), structure.scale = vff.lattice.scale # + 0.1 # vff.direction = FreezeCell.a0 | FreezeCell.a1 # print vff # print structure epsilon = array([[1e0, 0.1, 0], [0.1, 1e0, 0], [0, 0, 1e0]]) structure.cell = dot(epsilon, structure.cell) for atom in structure.atoms: atom.pos = dot(epsilon, atom.pos) out = vff(structure, outdir = "work", comm = world, relax=False, overwrite=True) print out.energy print out.structure print repr(out.stress)