def WriteSlabFilm(output, atoms, precision=0.06, smear=0.6, slab_identifers=(78, 79), excluded_from_density=(8, 1), formula_prefix=("Pt", "Au"), formula_suffix=("O", "H")): ''' Write the slab and film layer/type information to the output file, returns an array of slab layer tags. After the script runs, the atoms will be tagged to appropriate layers. ''' #First generate an array of the density of atoms on each step of the Z axis. #This step is influenced by precision and smear. density = [] for step in DRange(0, atoms.get_cell()[2][2], precision): #Keep track of how many atoms are in this density step dcount = 0 for atom in atoms: #Exclude some atoms from the density count. Hydrogen and oxygen are ignored #by default as they typically arn't represented in layers very well. if not (atom.number in excluded_from_density): if ((atom.position[2] - smear) < step) and ( (atom.position[2] + smear) > step): dcount += 1 density.append(dcount) #At this point density is populated with the density of atoms on the Z axis #Next is identifying the layer ranges. startx = -1 endx = -1 density_max = -1 #Ranges are defined layer_ranges = [] #Iterate over all the density values for d_index in xrange(0, len(density), 1): #Move the value to a local variable value = density[d_index] #if there is no know endx if endx == -1: #check if the value is higher than 1/6th the last known highest density for this section. if value > (density_max / 6): #set all the relevant values startx = d_index endx = d_index density_max = value #if you have found a starting index and the value has increased, then restart the range elif value > density_max: startx = d_index endx = d_index density_max = value #if the value is the same, extend the range elif value == density_max: endx = d_index #if the value ever drops under 1/6th of the highest known density for this section, then the #layer can be defined with known values and the startx/endx can be reset. Don't reset the density #max as there is no good reason to. All layers should have densities that are at least that similar #or the definition of a layer is hard to define. Also might cause errors as moving down away from #the density peak. elif value <= density_max / 6: layer_ranges.append((startx - (smear / precision), endx - startx, endx + (smear / precision))) startx = -1 endx = -1 #Find which atoms belong in which layers and tag them. for index, layer_range in enumerate(layer_ranges): atoms_in_layer = [ atom for atom in atoms if (atom.position[2] / precision > layer_range[0]) and ( atom.position[2] / precision < layer_range[2]) ] for atom in atoms_in_layer: atom.tag = index + 1 #Define a dictionary of atom tag keys and if they are part of the slab slab_layers = {} #Try to indentify which layers belong in the slab for atom in atoms: if atom.tag == 0: continue if atom.number in slab_identifers: slab_layers[atom.tag] = True else: #This part is designed to not overwrite a value if it already exists. You never want #to overwrite a True value. try: slab_layers[atom.tag] = (slab_layers[atom.tag]) except: slab_layers[atom.tag] = False for atom in atoms: #if atom is not assigned if atom.tag == 0: new_layer = 0 distance = 1000 #loop over all layer ranges for index, layer_range in enumerate(layer_ranges): #the atoms excluded from the density are not part of the slab if (atom.number in excluded_from_density) and (slab_layers[index + 1]): continue start = abs((atom.position[2] / smear * precision) - layer_range[0]) end = abs((atom.position[2] / smear * precision) - layer_range[2]) if (distance >= start): new_layer = index distance = start if (distance > end): new_layer = index distance = start atom.tag = new_layer + 1 slab_layer_count = 0 film_layer_count = 0 for slab_layer in slab_layers: if slab_layers[slab_layer]: slab_layer_count += 1 else: film_layer_count += 1 output['SlabLayers'] = slab_layer_count output['FilmLayers'] = film_layer_count if film_layer_count == 0: return Reports.NoFilmLayers, None if slab_layer_count == 0: return Reports.NoSlabLayers, None slab = Atoms([atom for atom in atoms if (slab_layers[atom.tag])]) slab.set_cell(atoms.get_cell()) film = Atoms([atom for atom in atoms if not (slab_layers[atom.tag])]) film.set_cell(atoms.get_cell()) slabform = slab.get_chemical_formula() filmform = film.get_chemical_formula() #Obtain the layers of the slab as individual layers for additional slab analysis layers = [] known_layers = set() for atom in slab: known_layers.add(atom.tag) for layer in known_layers: this_layer = [] for atom in atoms: if atom.tag == layer: this_layer.append(atom) layers.append(Atoms(this_layer)) layers.sort(key=lambda layer: layer[0].position[2]) layer_formulas = [] for layer in layers: layer_formulas.append(layer.get_chemical_formula()) slab_numbers = slab.get_atomic_numbers() slabtype = "" #H**o, Hetero, Skin skintype = "" #Only defined if skinned | Plate, Anneal skincomp = "" #Only defined if skinned annealcomp = "" #Reduced composition of annealed layer slabcomp = layer_formulas[0] if (CountList(slab_numbers, slab_numbers[0]) == len(slab_numbers)): slabtype = "H**o" elif CountList(layer_formulas, layer_formulas[0]) == len(layer_formulas): slabtype = "Hetero" elif ((CountList(layer_formulas, layer_formulas[0]) == len(layer_formulas) - 1) or (CountList(layer_formulas, layer_formulas[1]) == len(layer_formulas) - 1)): #Plated Skin slabtype = "Skin" skintype = "Plate" skincomp = layer_formulas[len(layer_formulas) - 1] elif ((CountList(layer_formulas, layer_formulas[0]) == len(layer_formulas) - 2) or (CountList( layer_formulas, layer_formulas[1]) == len(layer_formulas) - 2) or (CountList(layer_formulas, layer_formulas[2]) == len(layer_formulas) - 2)): #Annealed Skin slabtype = "Skin" skintype = "Anneal" skincomp = layer_formulas[len(layer_formulas) - 1] annealcomp = layer_formulas[len(layer_formulas) - 2] else: #Failed return Reports.SlabTypeIDFail, None output['SystemFormula'] = FormatFormula(atoms.get_chemical_formula(), formula_prefix, formula_suffix, red=False) output['SlabFormula'] = FormatFormula(slabform, formula_prefix, formula_suffix, red=False) output['SlabType'] = slabtype output['SkinType'] = skintype output['SkinComp'] = FormatFormula(skincomp, formula_prefix, formula_suffix) output['AnnealComp'] = FormatFormula(annealcomp, formula_prefix, formula_suffix) output['SlabComp'] = FormatFormula(slabcomp, formula_prefix, formula_suffix) output['FilmFormula'] = FormatFormula(filmform, formula_prefix, formula_suffix, red=False) output['FilmComp'] = FormatFormula(filmform, formula_prefix, formula_suffix) return False, slab_layers
def WriteSlabFilm(output, atoms, precision = 0.06, smear = 0.6, slab_identifers = (78, 79), excluded_from_density = (8, 1), formula_prefix = ("Pt","Au"), formula_suffix = ("O", "H")): #First generate an array of the density of atoms on each step of the Z axis. #This step is influenced by precision and smear. density = [] for step in DRange(0,atoms.get_cell()[2][2],precision): #Keep track of how many atoms are in this density step dcount=0 for atom in atoms: #Exclude some atoms from the density count. Hydrogen and oxygen are ignored #by default as they typically arn't represented in layers very well. if not (atom.number in excluded_from_density): if ((atom.position[2]-smear) < step) and ((atom.position[2]+smear) > step): dcount+=1 density.append(dcount) #At this point density is populated with the density of atoms on the Z axis #Next is identifying the layer ranges. startx = -1 endx = -1 density_max = -1 #Ranges are defined layer_ranges = [] #Iterate over all the density values for d_index in xrange(0,len(density),1): #Move the value to a local variable value = density[d_index] #if there is no know endx if endx == -1: #check if the value is higher than 1/6th the last known highest density for this section. if value > (density_max/6): #set all the relevant values startx = d_index endx = d_index density_max = value #if you have found a starting index and the value has increased, then restart the range elif value > density_max: startx = d_index endx = d_index density_max = value #if the value is the same, extend the range elif value == density_max: endx = d_index #if the value ever drops under 1/6th of the highest known density for this section, then the #layer can be defined with known values and the startx/endx can be reset. Don't reset the density #max as there is no good reason to. All layers should have densities that are at least that similar #or the definition of a layer is hard to define. Also might cause errors as moving down away from #the density peak. elif value <= density_max/6: layer_ranges.append((startx-(smear/precision),endx-startx,endx+(smear/precision))) startx = -1 endx = -1 #Find which atoms belong in which layers and tag them. for index, layer_range in enumerate(layer_ranges): atoms_in_layer = [atom for atom in atoms if (atom.position[2]/precision > layer_range[0]) and (atom.position[2]/precision < layer_range[2])] for atom in atoms_in_layer: atom.tag=index+1 #Define a dictionary of atom tag keys and if they are part of the slab slab_layers = {} #Try to indentify which layers belong in the slab for atom in atoms: if atom.tag == 0: continue if atom.number in slab_identifers: slab_layers[atom.tag] = True else: #This part is designed to not overwrite a value if it already exists. You never want #to overwrite a True value. try: slab_layers[atom.tag]=(slab_layers[atom.tag]) except: slab_layers[atom.tag]=False for atom in atoms: #if atom is not assigned if atom.tag == 0: new_layer = 0 distance = 1000 #loop over all layer ranges for index, layer_range in enumerate(layer_ranges): #the atoms excluded from the density are not part of the slab if (atom.number in excluded_from_density) and (slab_layers[index+1]): continue start = abs((atom.position[2]/smear*precision)-layer_range[0]) end = abs((atom.position[2]/smear*precision)-layer_range[2]) if (distance>=start): new_layer = index distance = start if (distance>end): new_layer = index distance = start atom.tag=new_layer+1 slab_layer_count=0 film_layer_count=0 for slab_layer in slab_layers: if slab_layers[slab_layer]: slab_layer_count+=1 else: film_layer_count+=1 output[K._VSF_SlabLayers_]=slab_layer_count output[K._VSF_FilmLayers_]=film_layer_count if film_layer_count == 0: return E._NoFilmLayers_, None if slab_layer_count == 0: return E._NoSlabLayers_, None slab = Atoms([atom for atom in atoms if (slab_layers[atom.tag])]) slab.set_cell(atoms.get_cell()) film = Atoms([atom for atom in atoms if not (slab_layers[atom.tag])]) film.set_cell(atoms.get_cell()) slabform = slab.get_chemical_formula() filmform = film.get_chemical_formula() #Obtain the layers of the slab as individual layers for additional slab analysis layers=[] known_layers=set() for atom in slab: known_layers.add(atom.tag) for layer in known_layers: this_layer=[] for atom in atoms: if atom.tag == layer: this_layer.append(atom) layers.append(Atoms(this_layer)) layers.sort(key=lambda layer: layer[0].position[2]) layer_formulas = [] for layer in layers: layer_formulas.append(layer.get_chemical_formula()) slab_numbers = slab.get_atomic_numbers() slabtype = "" #H**o, Hetero, Skin skintype = "" #Only defined if skinned | Plate, Anneal skincomp = "" #Only defined if skinned annealcomp = "" #Reduced composition of annealed layer slabcomp = layer_formulas[0] if (Count(slab_numbers, slab_numbers[0])==len(slab_numbers)): slabtype = "H**o" elif Count(layer_formulas, layer_formulas[0])==len(layer_formulas): slabtype = "Hetero" elif ((Count(layer_formulas, layer_formulas[0])==len(layer_formulas)-1) or (Count(layer_formulas, layer_formulas[1])==len(layer_formulas)-1)): #Plated Skin slabtype = "Skin" skintype = "Plate" skincomp = layer_formulas[len(layer_formulas)-1] elif ((Count(layer_formulas, layer_formulas[0])==len(layer_formulas)-2) or (Count(layer_formulas, layer_formulas[1])==len(layer_formulas)-2) or (Count(layer_formulas, layer_formulas[2])==len(layer_formulas)-2)): #Annealed Skin slabtype = "Skin" skintype = "Anneal" skincomp = layer_formulas[len(layer_formulas)-1] annealcomp = layer_formulas[len(layer_formulas)-2] else: #Failed return E._SlabTypeIDFail_, None output[K._SystemFormula_] = FormatForm(atoms.get_chemical_formula(), formula_prefix, formula_suffix, red = False) output[K._VSF_SlabFormula_] = FormatForm(slabform, formula_prefix, formula_suffix, red = False) output[K._VSF_SlabType_] = slabtype output[K._VSF_SkinType_] = skintype output[K._VSF_SkinComp_] = FormatForm(skincomp, formula_prefix, formula_suffix) output[K._VSF_AnnealComp_] = FormatForm(annealcomp, formula_prefix, formula_suffix) output[K._VSF_SlabComp_] = FormatForm(slabcomp, formula_prefix, formula_suffix) output[K._VSF_FilmFormula_] = FormatForm(filmform, formula_prefix, formula_suffix, red = False) output[K._VSF_FilmComp_] = FormatForm(filmform, formula_prefix, formula_suffix) return False, slab_layers
def extract_atoms(molecule): """Return a string with all atoms in molecule""" if molecule == '': return molecule try: return float(molecule) except BaseException: pass sign = '' molecule, prefactor = get_prefactor(molecule) if prefactor < 0: sign = '-' prefactor = abs(prefactor) atoms = Atoms(molecule) atoms = atoms.get_chemical_formula(mode='all') if prefactor % 1 == 0: atoms *= int(prefactor) elif prefactor % 1 == 0.5: atoms_sort = sorted(atoms) N = len(atoms) atoms = '' for n in range(N): for m in range(int(prefactor - 0.5)): atoms += atoms_sort[n] if n % 2 == 0: atoms += atoms_sort[n] return sign + ''.join(sorted(atoms))
def get_all_atoms(molecule): molecule = clear_state(molecule) molecule, prefactor = get_prefactor(molecule) atoms = Atoms(molecule) molecule = ''.join(sorted(atoms.get_chemical_formula(mode='all'))) return molecule, prefactor
def test_formula(): for sym in ['', 'Pu', 'Pu2', 'U2Pu2', 'U2((Pu2)2H)']: for mode in ['all', 'reduce', 'hill', 'metal']: for empirical in [False, True]: if empirical and mode in ['all', 'reduce']: continue atoms = Atoms(sym) formula = atoms.get_chemical_formula(mode=mode, empirical=empirical) atoms2 = Atoms(formula) print(repr(sym), '->', repr(formula)) n1 = np.sort(atoms.numbers) n2 = np.sort(atoms2.numbers) if empirical and len(atoms) > 0: reduction = len(n1) // len(n2) n2 = np.repeat(n2, reduction) assert (n1 == n2).all()
def test_isolation_1D(): atoms = Atoms(symbols='Cl6Ti2', pbc=True, cell=[[6.27, 0, 0], [-3.135, 5.43, 0], [0, 0, 5.82]], positions=[[1.97505, 0, 1.455], [0.987525, 1.71044347, 4.365], [-0.987525, 1.71044347, 1.455], [4.29495, 0, 4.365], [2.147475, 3.71953581, 1.455], [-2.147475, 3.71953581, 4.365], [0, 0, 0], [0, 0, 2.91]]) result = isolate_components(atoms) assert len(result) == 1 key, components = list(result.items())[0] assert key == '1D' assert len(components) == 1 chain = components[0] assert (chain.pbc == [False, False, True]).all() assert chain.get_chemical_formula() == atoms.get_chemical_formula()
def redundancy_check(blank_coords, adsonly_positions, fp_dict, repeat_unit_cell, symmetry_tol): inv_ruc = np.linalg.inv(repeat_unit_cell) atoms = Atoms() for c in blank_coords: atoms.append(Atom(c[0], c[1])) formula = atoms.get_chemical_formula() atoms.set_cell(repeat_unit_cell.T) atoms.pbc = True struct = AseAtomsAdaptor.get_structure(atoms) sga = SpacegroupAnalyzer(struct, symprec=symmetry_tol) sgs = sga.get_space_group_symbol() sgn = sga.get_space_group_number() adsonly_positions = np.asarray(adsonly_positions) dists = [] for i in range(len(adsonly_positions)): icoord = np.dot(inv_ruc, adsonly_positions[i]) for j in range(i + 1, len(adsonly_positions)): jcoord = np.dot(inv_ruc, adsonly_positions[j]) fdist, sym = PBC3DF_sym(icoord, jcoord) dist = np.linalg.norm(np.dot(repeat_unit_cell, fdist)) dists.append(dist) dists.sort() index = 0 advance = True for ind in range(len(fp_dict[sgn])): fp = fp_dict[sgn][ind] if len(fp) == len(dists): if np.allclose(fp, dists): index = ind advance = False break index = str(index) return advance, index, sgs, sgn, dists, atoms, formula
def atomic_structure_generator(symbols, fu=None, ndensity=None, volume=None, mindis=None, nstr=None, maxatomn=None, cspd_file=None, lw=None, format=None, clean=None, sgn=None, to_primitive=None): """ This function will read crystal structure prototypes from CSPD.db file and return a list of ASE Atoms object for the symbols defined by users. It could also write the output structures into any file format supported by ASE. The symbols is the only parameter required to define. The function has very justified default values for the rest of the parameters. You could also customize them according to your own understanding of your system. :param symbols: str (formula) or list of str Can be a string formula, a list of symbols or a list of Atom objects. Examples: 'H2O', 'COPt12', ['H', 'H', 'O'], [Atom('Ne', (x, y, z)), ...]. Same as the symbols in ASE Atoms class (https://wiki.fysik.dtu.dk/ase/ase/atoms.html). This parameter is passed to the Atoms class. :param fu: list of int Range of formula unit. The symbols is multiplied by every formula unit in this range. The length of this list has to be 2. :param ndensity: float Total number of atoms divided by volume. It controls how dense the atoms stack. :param volume: float Average volume of the structure per symbols. If ndensity is defined, volume will be ignored. I would strongly recommend to use ndensity rather than volume. :param mindis: list of lists of float Minimum inter-atomic distances. The dimension is nxn for the structure has number of n type of element. For example, we could define it as [[1.7, 1.4],[1.4, 1.2]] for binary compound. mindis[0][1] defines the minimum distance between element 1 with element 2. :param nstr: int This number of structures will be returned. Less of structures might be returned when there's not enough qualified structures in the database. :param maxatomn: int Maximum number of atoms in the structure. :param cspd_file: str Path and file name of CSPD.db. :param lw: logical Whether to write the structures into files. The structures are put in structure_folder. :param format: str Used to specify the file-format. Same as the format in ase.io.write() function. Check out the supported file-format at their website (https://wiki.fysik.dtu.dk/ase/ase/io/io.html). :param sgn: list of int Range of space group sequential number as given in the International Tables for Crystallography. :return: list of ASE Atoms object """ random.seed(a=27173) strulist = [] newstrulist = [] locmindis = {} rdr = 0.61 scale_ndensity = 2.22 structure_folder = 'structure_folder' lwf = False if fu == None: fu = [2, 8] if nstr == None: nstr = 1600 if maxatomn == None: maxatomn = 60 if cspd_file == None: cspd_file = '~/CSPD.db' if lw == None: lw = False if format == None: format = 'cif' if clean == None: clean = True if sgn == None: sgn = [1, 230] if to_primitive == None: to_primitive = False tmpstru = Atoms(symbols) intctype = count_atoms(tmpstru.numbers) atomnn = unify_an(tmpstru.numbers) nele, strctype, gcd = nele_ctype_fu(intctype) # intctype=[int(float(i)/gcd+0.5) for i in intctype] fulist = [i * gcd for i in range(fu[0], fu[1] + 1)] if format == 'db': dbw = connect(structure_folder + '/' + tmpstru.get_chemical_formula() + '.db', append=False) if volume: volume = float(volume) if ndensity: ndensity = float(ndensity) if mindis: for i, an in enumerate(atomnn.keys()): for j, an2 in enumerate(atomnn.keys()): locmindis[str(an) + '_' + str(an2)] = mindis[i][j] elif not (ndensity or volume): for an in atomnn.keys(): for an2 in atomnn.keys(): locmindis[str(an) + '_' + str(an2)] = (covalent_radii[an] + covalent_radii[an2]) * rdr if not ndensity: if volume: ndensity = len(tmpstru.numbers) / volume elif mindis: ndensity = scale_ndensity * sum(atomnn.values()) / (4 / 3.0 * math.pi / ((2 * rdr) ** 3) / 0.34 * sum( [atomnn[sym] * mindis[i][i] ** 3 for i, sym in enumerate(atomnn.keys())])) else: ndensity = scale_ndensity * sum(atomnn.values()) / ( 4 / 3.0 * math.pi / 0.34 * sum([atomnn[sym] * covalent_radii[sym] ** 3 for sym in atomnn.keys()])) if (ndensity or volume) and (not mindis): for an in atomnn.keys(): for an2 in atomnn.keys(): locmindis[str(an) + '_' + str(an2)] = (covalent_radii[an] + covalent_radii[an2]) * rdr # Need to improve!!! calculate locmindis according to ndensity db = connect(cspd_file) for row in db.select('ctype=_' + strctype): if row.lfocp and sgn[0] <= row.sgn <= sgn[1]: tempstru = row.toatoms() if to_primitive: pcell = standardize_cell( (tempstru.cell, tempstru.get_scaled_positions(), tempstru.numbers), to_primitive=True, symprec=0.01) # sgsn=get_spacegroup((strulist[struid].cell,strulist[struid].get_scaled_positions(),strulist[struid].numbers),symprec=0.01) # sgsn2=get_spacegroup(pcell,symprec=0.01) if pcell: tempstru = Atoms(cell=pcell[0], scaled_positions=pcell[1], numbers=pcell[2], pbc=True) natoms = len(tempstru.numbers) if natoms <= maxatomn: if to_primitive: final_fu = int(row.fu / row.natoms * natoms + 0.5) else: final_fu = row.fu for i in fulist: if final_fu == i: strulist.append(tempstru) strulist[-1].sgn = row.sgn strulist[-1].dname = row.dname strulist[-1].oid = row.oid # Or construct a list of rows and convert part of them to atoms object. break if lw: if os.path.exists(structure_folder): if clean: for tpfn in os.listdir(structure_folder): path_file = os.path.join(structure_folder, tpfn) if os.path.isfile(path_file): os.remove(path_file) else: os.makedirs(structure_folder) isucc = 0 ifail = 0 for i in range(len(strulist)): struid = int(random.random() * len(strulist)) tempstru = strulist[struid] tempstru.set_cell( tempstru.cell * (len(tempstru.numbers) / tempstru.get_volume() / ndensity) ** (1.0 / 3), scale_atoms=True) tempstru.set_atomic_numbers(subst_ele(tempstru.numbers, atomnn)) # Add break points will change the random number!!! Wired!!! if checkdis(tempstru, locmindis): newstrulist.append(tempstru) isucc += 1 else: if lwf: write(structure_folder + '/' + str(ifail + 1) + '_failed_' + tempstru.get_chemical_formula() + '_{}'.format(tempstru.sgn) + '.cif' , tempstru) del strulist[struid] ifail += 1 continue if lw: suffix = '' if format == 'cif': suffix = '.cif' elif format == 'vasp': suffix = '.vasp' if (format == 'db'): dbw.write(newstrulist[-1], sgn=newstrulist[-1].sgn, dname=newstrulist[-1].dname, oid=newstrulist[-1].oid) else: write(structure_folder + '/' + str(isucc) + '_' + newstrulist[-1].get_chemical_formula() + '_{}'.format(newstrulist[-1].sgn) + suffix , newstrulist[-1], format=format) print('Chemical Formula: {:9} Space Group: {:4d}'.format(newstrulist[-1].get_chemical_formula(), newstrulist[-1].sgn)) if isucc == nstr: break del strulist[struid] print('{} structures generated\n{} physically unjustified structures are filtered out'.format(isucc, ifail)) return newstrulist
def read_ion(fileobj, recover_indices=True, recover_constraints=True): text = fileobj.read() comments_removed = [] comments = [] label = fileobj.name.split('.ion')[0] for line in text.splitlines(): # break into lines # remove and store the comments entry = line.split('#') if not entry[0]: pass else: comments_removed.append(entry[0].strip()) try: comments.append(line.split('#')[1]) except: pass atoms = Atoms() ################################## # Parse the unit cell ################################## """ because the unit cell is not included in the .ion atomic positions file in SPARC, this interface writes the information into the comments of .ion files. If a .inpt file is present this code will read that if it is not it will attempt to read the comments to find that information. The logic for doing this is quite complicated (as seen below) """ inpt_file_usable = True lat_vec_speficied = False comments_bad = False lat_array = [] # Loop to read cell/latvec from either .inpt or the comment # this is pretty complicated while True: # try to get the unit cell from the .inpt file in the same directory if label + '.inpt' in os.listdir('.') and inpt_file_usable == True: with open(label + '.inpt', 'r') as f: input_file = f.read() if 'CELL' not in input_file: # We can't find the CELL in the input file # set the flag to false and re-run the while loop. inpt_file_usable = False del input_file continue input_file = input_file.split('\n') for line in input_file: if 'CELL' in line: # find the line with the cell info cell = line.strip().split()[1:] cell = np.array([float(a) for a in cell]) if 'LATVEC' in line: # get lattice vectors from next 3 lines lat_vec_speficied = True index = input_file.index(line) for lat_vec in [input_file[a] for a in range(index + 1, index + 4)]: vec = lat_vec.strip().split() vec = np.array([float(a) for a in vec]) lat_array.append( vec / np.linalg.norm(vec)) # normalize lat_array = np.array(lat_array) if lat_vec_speficied == False: lat_array = np.eye(3) if 'cell' in locals() and 'lat_array' in locals(): atoms.cell = (lat_array.T * cell).T * Bohr break # we got the cell, leave the while loop else: inpt_file_usable = False del input_file continue # if the input file isn't usable, check the comments of the .ion file elif comments != [] and comments_bad == False: if 'CELL' in comments[1]: # Check only the second line for a CELL cell = np.empty((3, 3)) try: cell = comments[1].strip().split()[1:] cell = [float(a) for a in cell] for lat_vec in comments[3:6]: # check only these lines vec = lat_vec.strip().split() vec = np.array([float(a) for a in vec]) lat_array.append( vec / np.linalg.norm(vec)) # normalize lat_array = np.array(lat_array) atoms.cell = (lat_array.T * cell).T * Bohr break except: comments_bad = True else: # if getting it from the comments fails, return 0 unit cell warnings.warn('No lattice vectors were found in either the .inpt' ' file or in the comments of the .ion file. Thus no' ' unit cell was set for the resulting atoms object. ' 'Use the output atoms at your own risk') atoms.cell = np.zeros((3, 3)) break else: # if there is no cell in the .inpt file, and the .ion file, return 0 unit cell warnings.warn('No lattice vectors were found in either the .inpt file ' 'or in the comments of the .ion file. Thus no unit cell ' 'was set for the resulting atoms object. Use the output ' 'atoms at your own risk') atoms.cell = np.zeros((3, 3)) break ############################################ # parse the atoms ############################################ # parse apart the comments to try to recover the indices and boundary conditions """ The strategy of this code is to get the input text separated from the comments. The comments are then used to glean the information that is normally stored in the .inpt file, as well as the original indices of the atoms if this file was made by this wrapper. from there figure out where the different "Atom Type" blocks are located in the full text with the comments removed. Once that has been found parse these sections to gain recover the atomic positions and elemental identies of these "atom types." We also need to find the locations of the "RELAX" blocks that contain the information on which atoms are constained in each "Atom Type" block. """ indices_from_comments = [] constraints = [] spins = [] for comment in comments: if 'index' in comment: if len(comment.split()) == 2: try: index = int(comment.split()[1]) indices_from_comments.append(index) except: pass if 'PBC:' in comment: pbc_list = [] pbc = comment.split()[1:] for c in pbc: if c == 'True' or c == 'true': pbc_list.append(True) else: pbc_list.append(False) atoms.set_pbc(pbc_list) del pbc_list, pbc # find the index of line for all the different atom types atom_types = [i for i, x in enumerate( comments_removed) if 'ATOM_TYPE:' in x] relax_blocks = [i for i, x in enumerate(comments_removed) if 'RELAX:' in x] spin_blocks = [i for i, x in enumerate(comments_removed) if 'SPIN:' in x] for i, atom_type in enumerate(atom_types): type_dict = {} if i == len(atom_types) - 1: # treat the last block differently # Get the slice of text associated with this atom type type_slice = comments_removed[atom_types[i]:] # figure out if there are constraints after this block if recover_constraints: relax_block_index = [a for a in relax_blocks if a > atom_type] spin_block_index = [a for a in spin_blocks if a > atom_type] else: # Get the slice of text associated with this atom type type_slice = comments_removed[atom_types[i]: atom_types[i+1]] [a for a in relax_blocks if a > atom_type] # figure out if there are constraints after this block. # the constraint index will be sandwiched between the indicies # the current block and the next block if recover_constraints: relax_block_index = [ a for a in relax_blocks if a > atom_types[i]] relax_block_index = [ a for a in relax_block_index if a < atom_types[i+1]] spin_block_index = [a for a in spin_blocks if a > atom_types[i]] spin_block_index = [ a for a in spin_block_index if a < atom_types[i+1]] # extract informaton about the atom type from the section header for info in ['PSEUDO_POT', 'ATOM_TYPE', 'ATOMIC_MASS', 'COORD', 'N_TYPE_ATOM']: for line in type_slice[:15]: # narrow the search for speed if info in line: if 'COORD' in line: if 'FRAC' in line: type_dict['COORD_FRAC'] = 1 else: type_dict['COORD'] = 1 elif 'COORD' not in line: type_dict[info] = line.split()[1] # get the lines that contain the constraints block if recover_constraints: if len(relax_block_index) == 0: pass elif len(relax_block_index) == 1: # offest by one line relax_block_index = relax_block_index[0] + 1 relax_block_end = relax_block_index + \ int(type_dict['N_TYPE_ATOM']) relax_slice = comments_removed[relax_block_index: relax_block_end] elif len(relax_block_index) > 1: raise Exception('There appear to be multiple blocks of' ' constraints in one or more of the atom' ' types in your .ion file. Please inspect' ' it to repair it or pass in ' '`recover_constraints = False` to ingore' ' constraints') # the same as the code above, but for spin if len(spin_block_index) == 0: pass elif len(spin_block_index) == 1: spin_block_index = spin_block_index[0] + 1 # offest by one line spin_block_end = spin_block_index + int(type_dict['N_TYPE_ATOM']) spin_slice = comments_removed[spin_block_index: spin_block_end] elif len(spin_block_index) > 1: raise Exception('There appear to be multiple blocks of' ' spin values in one or more of the atom' ' types in your .ion file. Please inspect' ' it to repair it or pass in') # now parse out the atomic positions for coord_set in type_slice[len(type_dict):int(type_dict['N_TYPE_ATOM']) + len(type_dict)]: if 'COORD_FRAC' in type_dict.keys(): x1, x2, x3 = [float(a) for a in coord_set.split()[:3]] x, y, z = sum( [x * a for x, a in zip([x1, x2, x3], atoms.cell)]) elif 'COORD' in type_dict.keys(): x, y, z = [float(a) * Bohr for a in coord_set.split()[:3]] atoms += Atom(symbol=type_dict['ATOM_TYPE'], position=(x, y, z)) # get the constraints if recover_constraints: if 'relax_slice' in locals(): for cons_set in relax_slice: constraints.append([int(a) for a in cons_set.split()]) del relax_slice else: # there aren't constraints with this block, put in empty lists constraints += [[]] * int(type_dict['N_TYPE_ATOM']) if 'spin_slice' in locals(): for init_spin in spin_slice: spins.append(float(init_spin)) del spin_slice else: # there aren't spins with this block, put in zeros spins += [0] * int(type_dict['N_TYPE_ATOM']) # check if we can reorganize the indices if len(indices_from_comments) == len(atoms) and recover_indices: new_atoms = Atoms(['X'] * len(atoms), positions=[(0, 0, 0)] * len(atoms)) new_atoms.set_cell(atoms.cell) new_spins = [None] * len(atoms) # reassign indicies for old_index, new_index in enumerate(indices_from_comments): new_atoms[new_index].symbol = atoms[old_index].symbol new_atoms[new_index].position = atoms[old_index].position new_atoms.pbc = atoms.pbc new_spins[new_index] = spins[old_index] assert new_atoms.get_chemical_formula() == atoms.get_chemical_formula() atoms = new_atoms spins = new_spins # reorganize the constraints now if recover_constraints: new_constraints = [0] * len(atoms) for old_index, new_index in enumerate(indices_from_comments): new_constraints[new_index] = constraints[old_index] constraints = new_constraints atoms.set_initial_magnetic_moments(spins) # add constraints if recover_constraints: constraints = decipher_constraints(constraints) atoms.set_constraint(constraints) return atoms
def fast_specified_adsorption_enumeration(sites, loading, atoms, adsorbate, outdir, occluded_sites, write_format='cif', suffix='BCC'): lattice = atoms.get_cell() sites = [s for s in sites if s not in occluded_sites] site_indices = [i for i in range(len(sites))] distinct_combs = itertools.combinations(site_indices, loading) degeneracy_check = [] distinct_sites = [] metal_positions = atoms.get_positions() metal_atom_numb = list(atoms.get_chemical_symbols()) index = 0 for comb in distinct_combs: if loading == 1: indices = [0] + list(comb) else: indices = list(comb) new_sites = [np.array(sites[i]) for i in indices] if len(new_sites) > 1: ns_pd = [ np.linalg.norm(np.dot(lattice, PBC3DF_sym(i, j)[0])) for i, j in itertools.combinations(new_sites, 2) ] if min(ns_pd) < 2.5: continue occ_sites = [np.array(sites[i]) for i in indices ] #+ [np.array(s) for s in occluded_sites] comb_sites = np.array(occ_sites) # loading 1 special case if loading in (1, 9): pair_distances = 0.0 # remaining cases else: pair_distances = [ np.linalg.norm(np.dot(lattice, PBC3DF_sym(i, j)[0])) for i, j in itertools.combinations(comb_sites, 2) ] if min(pair_distances) < 2.0: continue pair_distances = np.average([ np.linalg.norm(np.dot(lattice, PBC3DF_sym(i, j)[0])) for i, j in itertools.combinations(comb_sites, 2) ]) all_coords = np.r_[comb_sites, metal_positions] all_anumbs = [adsorbate for x in range(loading)] + metal_atom_numb test_atoms = Atoms() for i, j in zip(all_anumbs, all_coords): test_atoms.append(Atom(i, j)) test_atoms.set_cell(lattice) test_atoms.pbc = True struct = AseAtomsAdaptor.get_structure(test_atoms) sga = SpacegroupAnalyzer(struct, symprec=1.0e-5) sgn = sga.get_space_group_number() # break and continue conditions for loadings above 2 degen = (sgn, pair_distances) if degen in degeneracy_check: continue else: index += 1 degeneracy_check.append(degen) distinct_sites.append((index, sgn, pair_distances, comb_sites)) for config in distinct_sites: index, spgn, spec, positions = config loaded_atoms = Atoms() loaded_atoms.set_cell(lattice) for m in atoms: loaded_atoms.append(m) for vec in positions: cvec = np.dot(lattice, vec) loaded_atoms.append(Atom(adsorbate, cvec)) comp = loaded_atoms.get_chemical_formula() write(outdir + os.sep + comp + '_' + str(spgn) + '_' + str(index) + '_' + suffix + '.' + write_format, loaded_atoms, format=write_format) return loaded_atoms
import numpy as np from ase import Atoms from ase.formula import Formula assert Atoms('MoS2').get_chemical_formula() == 'MoS2' assert Atoms('SnO2').get_chemical_formula(mode='metal') == 'SnO2' if sys.version_info >= (3, 6): assert Formula('A3B2C2D').format('abc') == 'DB2C2A3' for sym in ['', 'Pu', 'Pu2', 'U2Pu2', 'U2((Pu2)2H)']: for mode in ['all', 'reduce', 'hill', 'metal']: for empirical in [False, True]: if empirical and mode in ['all', 'reduce']: continue atoms = Atoms(sym) formula = atoms.get_chemical_formula(mode=mode, empirical=empirical) atoms2 = Atoms(formula) print(repr(sym), '->', repr(formula)) n1 = np.sort(atoms.numbers) n2 = np.sort(atoms2.numbers) if empirical and len(atoms) > 0: reduction = len(n1) // len(n2) n2 = np.repeat(n2, reduction) assert (n1 == n2).all() for x in ['H2O', '10H2O', '2(CuO2(H2O)2)10', 'Cu20+H2', 'H' * 15, 'AuBC2', '']: f = Formula(x) y = str(f) assert y == x print(f.count(), '{:latex}'.format(f)) a, b = divmod(f, 'H2O')
def get_feed_dict(self, atoms: Atoms, print_time=False): """ Return the feed dict. """ rc = self._params['rc'] angular = self._params['angular'] elements = self._params['elements'] all_kbody_terms = get_kbody_terms(elements, angular)[0] kbody_sizes = compute_dimension(all_kbody_terms, len(self._params['eta']), len(self._params['omega']), len(self._params['beta']), len(self._params['gamma']), len(self._params['zeta']))[1] offsets = np.insert(np.cumsum(kbody_sizes), 0, 0) symbols = atoms.get_chemical_symbols() c = Counter(symbols) max_occurs = Counter({el: max(1, c[el]) for el in elements}) vap = VirtualAtomMap(max_occurs, symbols) formula = atoms.get_chemical_formula('reduce') if formula not in self.vap_cache: self.vap_cache[formula] = vap nij_max, nijk_max = get_nij_and_nijk(atoms, self._params['rc'], angular=self._params['angular']) g2_map = get_g2_map(atoms, rc, nij_max, all_kbody_terms, vap, offsets, for_prediction=True, print_time=print_time) positions = vap.map_array_to_gsl(atoms.positions).astype(np.float32) cell = np.asarray(atoms.cell).astype(np.float32) mask = vap.atom_masks.astype(np.float32) n_atoms_vap = np.int32(vap.max_vap_n_atoms) volume = np.float32(atoms.get_volume()) pulay_stress = np.float32(0.0) row_splits = vap.splits.astype(np.int32) n_elements = len(elements) composition = np.zeros(n_elements, dtype=np.float32) for element, count in Counter(symbols).items(): composition[elements.index(element)] = np.float32(count) feed_dict = { self._placeholders['positions']: positions, self._placeholders['cells']: cell, self._placeholders['n_atoms_plus_virt']: n_atoms_vap, self._placeholders['mask']: mask, self._placeholders['composition']: composition, self._placeholders['row_splits']: row_splits, self._placeholders['volume']: volume, self._placeholders['pulay_stress']: pulay_stress, } for key, value in g2_map.items(): feed_dict[self._placeholders[key]] = value if angular: g4_map = get_g4_map(atoms, g2_map, all_kbody_terms, offsets, vap, nijk_max, for_prediction=True) for key, value in g4_map.items(): feed_dict[self._placeholders[key]] = value return feed_dict
if __name__ != '__main__': exit() max_E = 3.0 bond_dist_dict = {"max_E": max_E} if not os.path.exists('csv'): os.makedirs('csv') for a0 in range(1, 100): for a1 in range(1, 100): a = Atoms([a0, a1]) s0, s1 = a[0].symbol, a[1].symbol dirname = '%02d-%02d' % (a0, a1) if os.path.exists(dirname): logging.info('Info: ' + dirname + ' ' + a.get_chemical_formula()) csv = dirname + '/' + a.get_chemical_formula() + '_1' csv_new = 'csv/' + a.get_chemical_formula() if not os.path.exists(csv): logging.info('dirname: ' + dirname + ' cvs :' + csv + ' not exists') continue df = pd.read_csv(csv) logging.info(df) if len(df) == 0: logging.error(dirname + ' dataframe is empty') # os.system('rm -f '+dirname+'/Done') continue try: df['energy'] -= [min(df['energy'])] * len(df['energy']) except:
def vibrate(self, atoms: Atoms, indices: list, read_only=False): ''' This method uses ase.vibrations module, see more for info. User provides the FHI-aims parameters, the Atoms object and list of indices of atoms to be vibrated. Variables related to FHI-aims are governed by the React object. Calculation folders are generated automatically and a sockets calculator is used for efficiency. Work in progress Args: atoms: Atoms object indices: list List of indices of atoms that require vibrations read_only: bool Flag for postprocessing - if True, the method only extracts information from existing files, no calculations are performed Returns: Zero-Point Energy: float ''' '''Retrieve common properties''' basis_set = self.basis_set hpc = self.hpc params = self.params parent_dir = os.getcwd() dimensions = sum(atoms.pbc) if not self.filename: '''develop a naming scheme based on chemical formula''' self.filename = atoms.get_chemical_formula() vib_dir = parent_dir + "/VibData_" + self.filename + "/Vibs" print(vib_dir) vib = Vibrations(atoms, indices=indices, name=vib_dir) '''If a calculation was terminated prematurely (e.g. time limit) empty .json files remain and the calculation of the corresponding stretch modes would be skipped on restart. The line below prevents this''' vib.clean(empty_files=True) '''Extract vibration data from existing files''' if read_only: vib.read() else: '''Calculate required vibration modes''' required_cache = [ os.path.join(vib_dir, "cache." + str(x) + y + ".json") for x in indices for y in ["x+", "x-", "y+", "y-", "y-", "z+", "z-"] ] check_required_modes_files = np.array( [os.path.exists(file) for file in required_cache]) if np.all(check_required_modes_files == True): vib.read() else: '''Set the environment variables for geometry optimisation''' set_aims_command(hpc=hpc, basis_set=basis_set, defaults=2020, nodes_per_instance=self.nodes_per_instance) '''Generate a unique folder for aims calculation''' counter, subdirectory_name = self._restart_setup( "Vib", filename=self.filename, restart=False, verbose=False) os.makedirs(subdirectory_name, exist_ok=True) os.chdir(subdirectory_name) '''Name the aims output file''' out = str(counter) + "_" + str(self.filename) + ".out" '''Calculate vibrations and write the in a separate directory''' with _calc_generator(params, out_fn=out, dimensions=dimensions)[0] as calculator: if not self.dry_run: atoms.calc = calculator else: atoms.calc = EMT() vib = Vibrations(atoms, indices=indices, name=vib_dir) vib.run() vib.summary() '''Generate a unique folder for aims calculation''' if not read_only: os.chdir(vib_dir) vib.write_mode() os.chdir(parent_dir) return vib.get_zero_point_energy()
def from_atoms(cls, atoms: Atoms, extra_info=None, store_calc=True): """ASE's original implementation""" reserved_keys = { 'n_atoms', 'cell', 'pbc', 'calculator_name', 'calculator_parameters', 'derived', 'formula' } arrays_keys = set(atoms.arrays.keys()) info_keys = set(atoms.info.keys()) results_keys = set( atoms.calc.results.keys()) if store_calc and atoms.calc else {} all_keys = (reserved_keys, arrays_keys, info_keys, results_keys) if len(set.union(*all_keys)) != sum(map(len, all_keys)): print(all_keys) raise ValueError('All the keys must be unique!') item = cls() n_atoms = len(atoms) dct = { 'n_atoms': n_atoms, 'cell': atoms.cell.tolist(), 'pbc': atoms.pbc.tolist(), 'formula': atoms.get_chemical_formula() } info_keys.update({'n_atoms', 'cell', 'pbc', 'formula'}) for key, value in atoms.arrays.items(): if isinstance(value, np.ndarray): dct[key] = value.tolist() else: dct[key] = value for key, value in atoms.info.items(): if isinstance(value, np.ndarray): dct[key] = value.tolist() else: dct[key] = value if store_calc and atoms.calc: dct['calculator_name'] = atoms.calc.__class__.__name__ dct['calculator_parameters'] = atoms.calc.todict() info_keys.update({'calculator_name', 'calculator_parameters'}) for key, value in atoms.calc.results.items(): if isinstance(value, np.ndarray): if value.shape[0] == n_atoms: arrays_keys.update(key) else: info_keys.update(key) dct[key] = value.tolist() item.arrays_keys = list(arrays_keys) item.info_keys = list(info_keys) item.results_keys = list(results_keys) item.update(dct) if extra_info: item.info_keys.extend(extra_info.keys()) item.update(extra_info) item.pre_save() return item