def generate_random_perovskite(lat=None): ''' This generates a random valid perovskite structure in ASE format. Useful for testing. Binary and organic perovskites are not considered. ''' if not lat: lat = round(random.uniform(3.5, Perovskite_tilting.OCTAHEDRON_BOND_LENGTH_LIMIT*2), 3) A_site = random.choice(Perovskite_Structure.A) B_site = random.choice(Perovskite_Structure.B) Ci_site = random.choice(Perovskite_Structure.C) Cii_site = random.choice(Perovskite_Structure.C) while covalent_radii[chemical_symbols.index(A_site)] - \ covalent_radii[chemical_symbols.index(B_site)] < 0.05 or \ covalent_radii[chemical_symbols.index(A_site)] - \ covalent_radii[chemical_symbols.index(B_site)] > 0.5: A_site = random.choice(Perovskite_Structure.A) B_site = random.choice(Perovskite_Structure.B) return crystal( [A_site, B_site, Ci_site, Cii_site], [(0.5, 0.25, 0.0), (0.0, 0.0, 0.0), (0.0, 0.25, 0.0), (0.25, 0.0, 0.75)], spacegroup=62, cellpar=[lat*math.sqrt(2), 2*lat, lat*math.sqrt(2), 90, 90, 90] )
def analyse_all_bonds(model, verbose=True, abnormal=True): ''' Analyse bonds and return all abnormal bond types and list of these TODO: Make this more bullet proof - what happens if abnormal bonds aren't requested. A table of bond distance analysis for the supplied model is also possible Parameters: model: Atoms object Structure for which the analysis is to be conducted verbose: Boolean Determines whether the output should be printed to screen abnormal: Boolean Collect information about rogue looking bond lengths. (Does enabling this by default add a large time overhead?) ''' # set() to ensure unique chemical symbols list list_of_symbols = list(set(model.get_chemical_symbols())) # Combination as AB = BA for bonds, avoiding redundancy from itertools import combinations_with_replacement all_bonds = combinations_with_replacement(list_of_symbols, 2) # Define lists to collect abnormal observations abnormal_bonds = [] list_of_abnormal_bonds = [] # Table heading if verbose: print_bond_table_header() from ase.data import chemical_symbols, covalent_radii # Iterate over all arrangements of chemical symbols for bonds in all_bonds: print_AB, AB_Bonds, AB_BondsValues = analyse_bonds(model, bonds[0], bonds[1], verbose=verbose, multirow=True) if abnormal and AB_BondsValues is not None: sum_of_covalent_radii = covalent_radii[chemical_symbols.index( bonds[0])] + covalent_radii[chemical_symbols.index(bonds[1])] abnormal_cutoff = max(0.4, sum_of_covalent_radii * 0.75) for values in AB_BondsValues: abnormal_values = [i for i in values if i < abnormal_cutoff] if len(abnormal_values): abnormal_bonds.append(len(abnormal_values)) list_of_abnormal_bonds.append(print_AB) # This now returns empty arrays if no abnormal bond checks are done, # or if genuinely there are no abnormal bonds. return abnormal_bonds, list_of_abnormal_bonds
def build_central_rdf(self, nbins=5, pcty=False): atoms = self.build_atoms_obj().copy() # center atoms at origin (COP) atoms.positions -= atoms.positions.mean(0) # calculate distances from origin dists = np.linalg.norm(atoms.positions, axis=1) # calculate distances from COP for each metal type dist_m1 = dists[atoms.symbols == self.metal1] dist_m2 = dists[atoms.symbols == self.metal2] fig, ax = plt.subplots() # get jmol color for each metal m1_color = jmol_colors[chemical_symbols.index(self.metal1)] m2_color = jmol_colors[chemical_symbols.index(self.metal2)] # set nbins to num shells if not given if not nbins: nbins = self.nanoparticle.num_shells counts_m1, bin_edges = np.histogram(dist_m1, bins=nbins) counts_m2, bin_edges = np.histogram(dist_m2, bins=nbins) # get bins for ax.hist bins = np.linspace(0, dists.max(), nbins + 1) x = (bin_edges[1:] + bin_edges[:-1]) / 2 ax.plot(x, counts_m1, 'o-', markeredgecolor='k', color=m1_color, markersize=8, label=self.metal1) ax.plot(x, counts_m2, 'o-', markeredgecolor='k', color=m2_color, markersize=8, label=self.metal2) ax.set_xlabel('Distance from Core ($\\rm \\AA$)') ax.set_ylabel('Number of Atoms') ax.legend() fig.tight_layout() plt.show() return fig
def get_checksum(self): ''' Retrieve unique hash in a cross-platform manner: this is how calculation identity is determined ''' if self._checksum: return self._checksum if not self._filename: raise RuntimeError('Source calc file is required in order to properly save the data!') calc_checksum = hashlib.sha224() struc_repr = "" for ase_obj in self.structures: struc_repr += "%3.6f %3.6f %3.6f %3.6f %3.6f %3.6f %3.6f %3.6f %3.6f " % tuple(map(abs, [ase_obj.cell[0][0], ase_obj.cell[0][1], ase_obj.cell[0][2], ase_obj.cell[1][0], ase_obj.cell[1][1], ase_obj.cell[1][2], ase_obj.cell[2][0], ase_obj.cell[2][1], ase_obj.cell[2][2]])) # NB beware of length & minus zeros for atom in ase_obj: struc_repr += "%s %3.6f %3.6f %3.6f " % tuple(map(abs, [chemical_symbols.index(atom.symbol), atom.x, atom.y, atom.z])) # NB beware of length & minus zeros if self.info["energy"] is None: energy = str(None) else: energy = str(round(self.info['energy'], 11 - int(math.log10(math.fabs(self.info['energy']))))) calc_checksum.update(( struc_repr + "\n" + energy + "\n" + self.info['prog'] + "\n" + str(self.info['input']) + "\n" + str(sum([2**x for x in self.info['calctypes']])) ).encode('ascii')) # NB this is fixed and should not be changed result = base64.b32encode(calc_checksum.digest()).decode('ascii') result = result[:result.index('=')] + 'CI' return result
def write_lammps_data(filename, atoms, atom_types, comment=None, cutoff=None, molecule_ids=None, charges=None, units='metal'): if isinstance(filename, str): fh = open(filename, 'w') else: fh = filename if comment is None: comment = 'lammpslib autogenerated data file' fh.write(comment.strip() + '\n\n') fh.write('{0} atoms\n'.format(len(atoms))) fh.write('{0} atom types\n'.format(len(atom_types))) fh.write('\n') cell, coord_transform = convert_cell(atoms.get_cell()) fh.write('{0:16.8e} {1:16.8e} xlo xhi\n'.format(0.0, cell[0, 0])) fh.write('{0:16.8e} {1:16.8e} ylo yhi\n'.format(0.0, cell[1, 1])) fh.write('{0:16.8e} {1:16.8e} zlo zhi\n'.format(0.0, cell[2, 2])) fh.write('{0:16.8e} {1:16.8e} {2:16.8e} xy xz yz\n' ''.format(cell[0, 1], cell[0, 2], cell[1, 2])) fh.write('\nMasses\n\n') sym_mass = {} masses = atoms.get_masses() symbols = atoms.get_chemical_symbols() for sym in atom_types: for i in range(len(atoms)): # TODO: Make this more efficient if symbols[i] == sym: sym_mass[sym] = convert(masses[i], "mass", "ASE", units) break else: sym_mass[sym] = convert( ase_atomic_masses[ase_chemical_symbols.index(sym)], "mass", "ASE", units) for (sym, typ) in sorted(atom_types.items(), key=operator.itemgetter(1)): fh.write('{0} {1}\n'.format(typ, sym_mass[sym])) fh.write('\nAtoms # full\n\n') if molecule_ids is None: molecule_ids = np.zeros(len(atoms), dtype=int) if charges is None: charges = atoms.get_initial_charges() for i, (sym, mol, q, pos) in enumerate( zip(symbols, molecule_ids, charges, atoms.get_positions())): typ = atom_types[sym] fh.write( '{0} {1} {2} {3:16.8e} {4:16.8e} {5:16.8e} {6:16.8e}\n'.format( i + 1, mol, typ, q, pos[0], pos[1], pos[2])) if isinstance(filename, str): fh.close()
def get_atom_kinds(atoms, props={}): # symbols = atoms.symbols # formula = atoms.symbols.formula # atom_kinds = formula.count() if hasattr(atoms, 'kinds'): kinds = list(set(atoms.kinds)) else: atoms.kinds = atoms.get_chemical_symbols() kinds = list(set(atoms.kinds)) # print(kinds) atom_kinds = {} for kind in kinds: atom_kinds[kind] = {} element = kind.split('_')[0] number = chemical_symbols.index(element) inds = [ atom.index for atom in atoms if atoms.kinds[atom.index] == kind ] color = jmol_colors[number] radius = covalent_radii[number] atom_kinds[kind]['element'] = element atom_kinds[kind]['positions'] = atoms[inds].positions atom_kinds[kind]['number'] = number atom_kinds[kind]['color'] = color atom_kinds[kind]['transmit'] = 1.0 atom_kinds[kind]['radius'] = radius atom_kinds[kind]['balltype'] = None if props: if kind in props.keys(): for prop, value in props[kind].items(): atom_kinds[kind][prop] = value return atom_kinds
def __init__(self, tilde_calc): self.weight = 0 for a in tilde_calc.structures[-1]: if not a.symbol in chemical_symbols: raise ModuleError('Unexpected atom has been found!') try: self.weight += atomic_masses[chemical_symbols.index(a.symbol)] except IndexError: raise ModuleError('Application error!') self.weight = round(self.weight)
def get_APF(ase_obj): """ Example crystal structure descriptor: https://en.wikipedia.org/wiki/Atomic_packing_factor """ volume = 0.0 for atom in ase_obj: volume += 4 / 3 * np.pi * covalent_radii[chemical_symbols.index( atom.symbol)]**3 return volume / abs(np.linalg.det(ase_obj.cell))
def __init__(self, symbols: List[str]): if NULL_SYMBOL in symbols: raise RuntimeError( f'Place holder symbol {NULL_SYMBOL} cannot be in list of symbols' ) if len(symbols) < 1: raise RuntimeError('List of symbols cannot be empty') # Ensure that all symbols are valid for symbol in symbols: chemical_symbols.index(symbol) # Ensure that there are no duplicates if len(set(symbols)) != len(symbols): raise RuntimeError( f'List of symbols {symbols} cannot contain duplicates') self._symbols = [NULL_SYMBOL] + symbols
def __init__(self, tilde_calc): self.weight = 0 for a in tilde_calc.structures[-1]: if not a.symbol in chemical_symbols: raise ModuleError('Unexpected atom has been found!') try: self.weight += atomic_masses[chemical_symbols.index(a.symbol)] except IndexError: raise ModuleError('Application error!') self.weight = round(self.weight)
def molecule_search(self, element_pool={ 'C': 2, 'H': 6 }, load_molecules=True, multiple_bond_search=False): """Return the enumeration of molecules which can be produced from the specified atoms. Parameters: ----------- element_pool: dict Atomic symbols keys paired with the maximum number of that atom. load_molecules: bool Load any existing molecules from the database. multiple_bond_search: bool Allow atoms to form bonds with other atoms in the molecule. """ numbers = np.zeros(len(self.base_valence)) for k, v in element_pool.items(): numbers[chemical_symbols.index(k)] = v self.element_pool = numbers self.multiple_bond_search = multiple_bond_search molecules = {} if load_molecules: self.molecules = self.load_molecules(binned=True) search_molecules = [] for el, cnt in enumerate(self.element_pool): if cnt == 0: continue molecule = Gratoms(symbols=[el]) molecule.nodes[0]['valence'] = self.base_valence[el] search_molecules += [molecule] comp_tag, bond_tag = molecule.get_chemical_tags() if comp_tag not in self.molecules: molecules[comp_tag] = {bond_tag: [molecule]} for molecule in search_molecules: # Recusive use of molecules new_molecules, molecules = self._branch_molecule( molecule, molecules) search_molecules += new_molecules self.save_molecules(molecules)
def chemical_symbols_to_numbers(symbols: List[str]) -> List[int]: """Returns the atomic numbers equivalent to the input chemical symbols. Parameters ---------- symbols chemical symbols """ numbers = [chemical_symbols.index(symbols) for symbols in symbols] return numbers
def search_abnormal_bonds(model, verbose=True): ''' Check all bond lengths in the model for abnormally short ones. Parameters: model: Atoms object or string. If string it will read a file in the same folder, e.g. "name.traj" ''' # Abnormality check abnormal_bonds, list_of_abnormal_bonds = analyse_all_bonds(model, verbose=verbose, abnormal=True) from ase.data import chemical_symbols, covalent_radii import numpy as np if list_of_abnormal_bonds: sums_of_covalent_radii = [] for i in list_of_abnormal_bonds: bond_chem_symbols = i.split("-") sums_of_covalent_radii += [ covalent_radii[chemical_symbols.index(bond_chem_symbols[0])] + covalent_radii[chemical_symbols.index(bond_chem_symbols[1])] ] # Check against possible covalent radii values averaged * 0.75 if len(abnormal_bonds) > 0: if verbose: print("-" * 40) print( "A total of", len(abnormal_bonds), "abnormal bond lengths observed (<" + str(max(0.4, np.average(sums_of_covalent_radii) * 0.75)) + " A") print("Identities:", list_of_abnormal_bonds) print("-" * 40) return False else: return True
def get_polyhedra_kinds(atoms, bondlist={}, transmit=0.8, polyhedra={}): """ Two modes: (1) Search atoms bonded to kind polyhedra: {'kind': ligands} """ from scipy.spatial import ConvexHull from ase.data import covalent_radii from ase.neighborlist import NeighborList tstart = time.time() polyhedra_kinds = {} for kind, ligand in polyhedra.items(): # print(kind, ligand) if kind not in polyhedra_kinds.keys(): element = kind.split('_')[0] number = chemical_symbols.index(element) color = jmol_colors[number] polyhedra_kinds[kind] = {'vertices': [], 'edges': [], 'faces': []} polyhedra_kinds[kind]['material'] = { 'diffuseColor': tuple(color), 'transparency': 0.5 } inds = [atom.index for atom in atoms if atom.symbol == kind] for ind in inds: vertice = [] for bond in bondlist[ind]: a2, offset = bond if atoms[a2].symbol in ligand: temp_pos = atoms[a2].position + np.dot(offset, atoms.cell) vertice.append(temp_pos) nverts = len(vertice) # print(ind, nverts) if nverts > 3: # print(ind, vertice) # search convex polyhedra hull = ConvexHull(vertice) face = hull.simplices nverts = len(polyhedra_kinds[kind]['vertices']) face = face + nverts edge = [] for f in face: edge.append([f[0], f[1]]) edge.append([f[0], f[2]]) edge.append([f[1], f[2]]) polyhedra_kinds[kind]['vertices'] = polyhedra_kinds[kind][ 'vertices'] + list(vertice) polyhedra_kinds[kind][ 'edges'] = polyhedra_kinds[kind]['edges'] + list(edge) polyhedra_kinds[kind][ 'faces'] = polyhedra_kinds[kind]['faces'] + list(face) # print('get_polyhedra_kinds: {0:10.2f} s'.format(time.time() - tstart)) return polyhedra_kinds
def classify(tilde_obj): if not len(tilde_obj.info['elements']) == 1 or tilde_obj.info['contents'][0] != 1: return tilde_obj dims = tilde_obj.info['dims'] if tilde_obj.structures[-1].periodicity == 3 else abs(det(tilde_obj.structures[-1].cell)) if tilde_obj.structures[-1].periodicity == 0 or \ float( dims / covalent_radii[chemical_symbols.index(tilde_obj.info['elements'][0])] ) > REL: # atomic radius should be REL times less than cell dimensions tilde_obj.info['periodicity'] = -1 tilde_obj.structures[-1].periodicity = -1 return tilde_obj
def _get_molecule_spec(atoms, nuclear_props): ''' Generate the molecule specification section to write to the Gaussian input file, from the Atoms object and dict of nuclear properties''' molecule_spec = [] for i, atom in enumerate(atoms): symbol_section = atom.symbol + '(' # Check whether any nuclear properties of the atom have been set, # and if so, add them to the symbol section. nuclear_props_set = False for keyword, array in nuclear_props.items(): if array is not None and array[i] is not None: string = keyword + '=' + str(array[i]) + ', ' symbol_section += string nuclear_props_set = True # Check whether the mass of the atom has been modified, # and if so, add it to the symbol section: mass_set = False symbol = atom.symbol expected_mass = atomic_masses_iupac2016[chemical_symbols.index(symbol)] if expected_mass != atoms[i].mass: mass_set = True string = 'iso' + '=' + str(atoms[i].mass) symbol_section += string if nuclear_props_set or mass_set: symbol_section = symbol_section.strip(', ') symbol_section += ')' else: symbol_section = symbol_section.strip('(') # Then attach the properties appropriately # this formatting was chosen for backwards compatibility reasons, but # it would probably be better to # 1) Ensure proper spacing between entries with explicit spaces # 2) Use fewer columns for the element # 3) Use 'e' (scientific notation) instead of 'f' for positions molecule_spec.append('{:<10s}{:20.10f}{:20.10f}{:20.10f}'.format( symbol_section, *atom.position)) # unit cell vectors, in case of periodic boundary conditions for ipbc, tv in zip(atoms.pbc, atoms.cell): if ipbc: molecule_spec.append('TV {:20.10f}{:20.10f}{:20.10f}'.format(*tv)) molecule_spec.append('') return molecule_spec
def classify(tilde_obj): if not len(tilde_obj.info['elements'] ) == 1 or tilde_obj.info['contents'][0] != 1: return tilde_obj dims = tilde_obj.info['dims'] if tilde_obj.structures[ -1].periodicity == 3 else abs(det(tilde_obj.structures[-1].cell)) if tilde_obj.structures[-1].periodicity == 0 or \ float( dims / covalent_radii[chemical_symbols.index(tilde_obj.info['elements'][0])] ) > REL: # atomic radius should be REL times less than cell dimensions tilde_obj.info['periodicity'] = -1 tilde_obj.structures[-1].periodicity = -1 return tilde_obj
def classify(tilde_obj): if sum(tilde_obj.structures[-1].get_pbc() ) == 3: # only those initially 3-periodic zi, mi = tilde_obj.info['cellpar'].index( max(tilde_obj.info['cellpar'] [0:3])), tilde_obj.info['cellpar'].index( min(tilde_obj.info['cellpar'][0:3])) cmpveci = [i for i in range(3) if i not in [zi, mi]][0] # vacuum per one atom (av) atoms_volume = 0.0 for i in tilde_obj.structures[-1]: atoms_volume += 4 / 3 * math.pi * ( covalent_radii[chemical_symbols.index(i.symbol)] + r_EC)**3 av = (abs(det(tilde_obj.structures[-1].cell)) - atoms_volume) / len( tilde_obj.structures[-1]) #print "av:", av # too much vacuum if av > ADDED_VACUUM_3D_TOL: # 2D if tilde_obj.info['cellpar'][cmpveci] * L < tilde_obj.info[ 'cellpar'][zi]: tilde_obj.info['techs'].append( 'vacuum %sA' % int(round(tilde_obj.info['cellpar'][zi]))) tilde_obj.structures[-1].set_pbc((True, True, False)) # TODO: 1D ? # 0D elif av > ADDED_VACUUM_3D_TOL * 4: # TODO tilde_obj.structures[-1].set_pbc((False, False, False)) # extend the last ASE object tilde_obj.structures[-1].periodicity = int( sum(tilde_obj.structures[-1].get_pbc())) tilde_obj.structures[-1].dims = abs(det(tilde_obj.structures[-1].cell)) # TODO: area for surfaces tilde_obj.info['dims'] = tilde_obj.structures[-1].dims tilde_obj.info['periodicity'] = tilde_obj.structures[-1].periodicity return tilde_obj
def get_bond_kinds(atoms, atom_kinds, bondlist): ''' Build faces for instancing bonds. The radius of bonds is determined by nbins. mesh.from_pydata(vertices, [], faces) ''' # view(atoms) bond_kinds = {} for ind1, pairs in bondlist.items(): kind = atoms.kinds[ind1] if kind not in bond_kinds.keys(): lengths = [] centers = [] normals = [] bond_kinds[kind] = { 'lengths': lengths, 'centers': centers, 'normals': normals } number = chemical_symbols.index(kind) color = atom_kinds[kind]['color'] radius = covalent_radii[number] bond_kinds[kind]['number'] = number bond_kinds[kind]['color'] = color bond_kinds[kind]['transmit'] = atom_kinds[kind]['transmit'] for bond in pairs: ind2, offset = bond R = np.dot(offset, atoms.cell) # print(inds, offset) pos = [atoms.positions[ind1], atoms.positions[ind2] + R] # print(pos) center0 = (pos[0] + pos[1]) / 2.0 if pos[0][2] > pos[1][2]: vec = pos[0] - pos[1] else: vec = pos[1] - pos[0] # print(vec) length = np.linalg.norm(vec) nvec = vec / length # kinds = [atoms[ind].symbol for ind in [a, b]] center = (center0 + pos[0]) / 2.0 bond_kinds[kind]['centers'].append(center) bond_kinds[kind]['lengths'].append(length / 4.0) bond_kinds[kind]['normals'].append(nvec) # pprint.pprint(bond_kinds) return bond_kinds
def plot_pdos(self, energies=None, pdos_kinds=None, Emin=-5, Emax=5, ax=None, total=False, select=None, fill=True, output=None, legend=False, xylabel=True, smearing=None): ''' ''' if energies is None: energies = self.pdos_energies if pdos_kinds is None: pdos_kinds = self.pdos_kinds xindex = (energies > Emin) & (energies < Emax) # print(xindex) if ax is None: fig, ax = plt.subplots(figsize=(6, 3)) # if total: # self.plot_data(self.pdos_energies, self.pdos_tot, label = 'pdos', ax = ax, fill = fill) for kind, channels in pdos_kinds.items(): if select and kind not in select: continue number = chemical_symbols.index(kind) color = jmol_colors[number] for channel, pdos in channels.items(): if select and channel[-1] not in select[kind]: continue label = '{0}-{1}'.format(kind, channel) self.plot_data(energies, pdos, label=label, ax=ax, xindex=xindex, fill=fill, color=color, smearing=smearing) if legend: ax.legend() if xylabel: ax.set_xlabel('Energy (eV)') ax.set_ylabel('PDOS (a.u.)') if output is not None: plt.savefig('%s' % output) return ax
def get_checksum(self): ''' retrieve unique hash in a cross-platform manner: this is how unique identity is determined ''' if self._checksum: return self._checksum if not self._filename: raise RuntimeError( 'Source calc file is required in order to properly save the data!' ) calc_checksum = hashlib.sha224() struc_repr = "" for ase_repr in self.structures: struc_repr += "%3.6f %3.6f %3.6f %3.6f %3.6f %3.6f %3.6f %3.6f %3.6f " % tuple( map(abs, [ ase_repr.cell[0][0], ase_repr.cell[0][1], ase_repr.cell[0][2], ase_repr.cell[1][0], ase_repr.cell[1][1], ase_repr.cell[1][2], ase_repr.cell[2][0], ase_repr.cell[2][1], ase_repr.cell[2][2] ])) # NB beware of length & minus zeros for atom in ase_repr: struc_repr += "%s %3.6f %3.6f %3.6f " % tuple( map(abs, [ chemical_symbols.index(atom.symbol), atom.x, atom.y, atom.z ])) # NB beware of length & minus zeros if self.info["energy"] is None: energy = str(None) else: energy = str( round(self.info['energy'], 11 - int(math.log10(math.fabs(self.info['energy']))))) calc_checksum.update( (struc_repr + energy + " " + self.info['prog'] + " " + str(self.info['input']) + " " + str(sum([2**x for x in self.info['calctypes']]))).encode('ascii') ) # this is fixed in DB schema 5.11 and should not be changed result = base64.b32encode(calc_checksum.digest()).decode('ascii') result = result[:result.index('=')] + 'CI' return result
def get_occupied_primitive_structure( structure: Atoms, allowed_species: List[List[str]], symprec: float) -> Tuple[Atoms, List[Tuple[str, ...]]]: """ Returns an occupied primitive structure. Will put hydrogen on sublattice 1, Helium on sublattice 2 and so on Parameters ---------- structure input structure allowed_species chemical symbols that are allowed on each site symprec tolerance imposed when analyzing the symmetry using spglib Todo ---- simplify the revert back to unsorted symbols """ if len(structure) != len(allowed_species): raise ValueError( 'structure and chemical symbols need to be the same size.') sorted_symbols = sorted({tuple(sorted(s)) for s in allowed_species}) decorated_primitive = structure.copy() for i, sym in enumerate(allowed_species): sublattice = sorted_symbols.index(tuple(sorted(sym))) + 1 decorated_primitive[i].symbol = chemical_symbols[sublattice] decorated_primitive = get_primitive_structure(decorated_primitive, symprec=symprec) decorated_primitive.wrap() primitive_chemical_symbols: List[Tuple[str, ...]] = [] for atom in decorated_primitive: sublattice = chemical_symbols.index(atom.symbol) primitive_chemical_symbols.append(sorted_symbols[sublattice - 1]) for symbols in allowed_species: if tuple(sorted(symbols)) in primitive_chemical_symbols: index = primitive_chemical_symbols.index(tuple(sorted(symbols))) primitive_chemical_symbols[index] = symbols return decorated_primitive, primitive_chemical_symbols
def get_atom_kinds(atoms, scale, props={}): tstart = time.time() if atoms.info and 'kinds' in atoms.info: assert len(atoms.info['kinds']) == len( atoms), """ \n\n kinds not equal to number of atoms. You increase atoms by *[x, x, x]? Please set atoms.info['kinds'] again. Or remove the original one.\n""" kinds = list(set(atoms.info['kinds'])) else: atoms.info['kinds'] = atoms.get_chemical_symbols() kinds = list(set(atoms.info['kinds'])) atom_kinds = {} for kind in kinds: atom_kinds[kind] = {} element = kind.split('_')[0] number = chemical_symbols.index(element) inds = [ atom.index for atom in atoms if atoms.info['kinds'][atom.index] == kind ] color = jmol_colors[number] radius = covalent_radii[number] atom_kinds[kind]['element'] = element atom_kinds[kind]['indexs'] = inds atom_kinds[kind]['positions'] = np.round(atoms[inds].positions, decimals=2) atom_kinds[kind]['number'] = number atom_kinds[kind]['material'] = { 'diffuseColor': tuple(color), 'transparency': 0.01 } atom_kinds[kind]['sphere'] = {'radius': radius * scale} atom_kinds[kind]['balltype'] = None # bond atom_kinds[kind]['lengths'] = [] atom_kinds[kind]['centers'] = [] atom_kinds[kind]['rotations'] = [] if props: if kind in props.keys(): for prop, value in props[kind].items(): atom_kinds[kind][prop] = value # print('get_atom_kinds: {0:10.2f} s'.format(time.time() - tstart)) return atom_kinds
def write_lammps_atoms(self, atoms): """Write atoms infor for LAMMPS""" fileobj = 'lammps_atoms' if isinstance(fileobj, str): fileobj = open(fileobj, 'w') write_lammps_data(fileobj, atoms, specorder=atoms.types, speclist=(atoms.get_tags() + 1), ) # masses fileobj.write('\nMasses\n\n') for i, typ in enumerate(atoms.types): cs = atoms.split_symbol(typ)[0] fileobj.write('%6d %g # %s -> %s\n' % (i + 1, atomic_masses[chemical_symbols.index(cs)], typ, cs)) # bonds btypes, blist = self.get_bonds(atoms) fileobj.write('\n' + str(len(btypes)) + ' bond types\n') fileobj.write(str(len(blist)) + ' bonds\n') fileobj.write('\nBonds\n\n') for ib, bvals in enumerate(blist): fileobj.write('%8d %6d %6d %6d\n' % (ib + 1, bvals[0] + 1, bvals[1] + 1, bvals[2] + 1)) # angles atypes, alist = self.get_angles() fileobj.write('\n' + str(len(atypes)) + ' angle types\n') fileobj.write(str(len(alist)) + ' angles\n') fileobj.write('\nAngles\n\n') for ia, avals in enumerate(alist): fileobj.write('%8d %6d %6d %6d %6d\n' % (ia + 1, avals[0] + 1, avals[1] + 1, avals[2] + 1, avals[3] + 1)) return btypes, atypes
def get_atom_kinds(atoms, props={}): # symbols = atoms.symbols # formula = atoms.symbols.formula # atom_kinds = formula.count() tstart = time.time() if hasattr(atoms, 'kinds'): kinds = list(set(atoms.kinds)) else: atoms.kinds = atoms.get_chemical_symbols() kinds = list(set(atoms.kinds)) # print(kinds) atom_kinds = {} for kind in kinds: atom_kinds[kind] = {} element = kind.split('_')[0] print(kind, element) number = chemical_symbols.index(element) inds = [ atom.index for atom in atoms if atoms.kinds[atom.index] == kind ] color = jmol_colors[number] radius = covalent_radii[number] atom_kinds[kind]['element'] = element atom_kinds[kind]['positions'] = atoms[inds].positions atom_kinds[kind]['number'] = number atom_kinds[kind]['color'] = color atom_kinds[kind]['transmit'] = 1.0 atom_kinds[kind]['radius'] = radius atom_kinds[kind]['balltype'] = None # bond atom_kinds[kind]['lengths'] = [] atom_kinds[kind]['centers'] = [] atom_kinds[kind]['normals'] = [] atom_kinds[kind]['verts'] = [] atom_kinds[kind]['faces'] = [] if props: if kind in props.keys(): for prop, value in props[kind].items(): atom_kinds[kind][prop] = value print('get_atom_kinds: {0:10.2f} s'.format(time.time() - tstart)) return atom_kinds
def set_missing_parameters(self): """Verify that all necessary variables are set. """ symbols = self.atoms.get_chemical_symbols() # If unspecified default to atom types in alphabetic order if not self.parameters.specorder: self.parameters.specorder = sorted(set(symbols)) # !TODO: handle cases were setting masses actual lead to errors if not self.parameters.masses: self.parameters.masses = [] for type_id, specie in enumerate(self.parameters.specorder): mass = atomic_masses[chemical_symbols.index(specie)] self.parameters.masses += [ "{0:d} {1:f}".format(type_id + 1, mass) ] # set boundary condtions if not self.parameters.boundary: b_str = " ".join(["fp"[int(x)] for x in self.atoms.get_pbc()]) self.parameters.boundary = b_str
def get_atomic_numbers(formula, return_count=False): ''' Return the atomic numbers associated with a chemical formula. Parameters ---------- formula : string A chemical formula to parse into atomic numbers. return_count : bool Return the count of each element in the formula. Returns ------- numbers : ndarray (n,) Element numbers in associated species. counts : ndarray (n,) Count of each element in a species. ''' parse = re.findall('[A-Z][a-z]?|[0-9]+', formula) values = {} for i, e in enumerate(parse): if e.isdigit(): values[parse[i - 1]] += int(e) - 1 else: if e not in values: values[e] = 1 else: values[e] += 1 numbers = np.array([chemical_symbols.index(k) for k in values.keys()]) srt = np.argsort(numbers) numbers = numbers[srt] if return_count: counts = np.array([v for v in values.values()])[srt] return numbers, counts return numbers
def default_atom_kind(element, positions, color = 'VESTA'): """ """ from ase.data import chemical_symbols from ase.data.colors import jmol_colors, cpk_colors from blase.default_data import vesta_color atom_kind = {} number = chemical_symbols.index(element) if color.upper() == 'JMOL': color = jmol_colors[number] elif color.upper() == 'CPK': color = jmol_colors[number] elif color.upper() == 'VESTA': color = vesta_color[element] radius = covalent_radii[number] atom_kind['element'] = element atom_kind['color'] = color atom_kind['transmit'] = 1.0 atom_kind['radius'] = radius atom_kind['positions'] = positions atom_kind['balltype'] = None return atom_kind
def default_bond_kind(element, color = 'VESTA'): """ """ from ase.data import chemical_symbols from ase.data.colors import jmol_colors, cpk_colors from blase.default_data import vesta_color bond_kind = {} number = chemical_symbols.index(element) if color.upper() == 'JMOL': color = jmol_colors[number] elif color.upper() == 'CPK': color = jmol_colors[number] elif color.upper() == 'VESTA': color = vesta_color[element] bond_kind['element'] = element bond_kind['color'] = color bond_kind['transmit'] = 1.0 bond_kind['lengths'] = [] bond_kind['centers'] = [] bond_kind['normals'] = [] bond_kind['verts'] = [] bond_kind['faces'] = [] return bond_kind
def classify(tilde_obj): if sum(tilde_obj.structures[-1].get_pbc()) == 3: # only those initially 3-periodic zi, mi = tilde_obj.info['cellpar'].index(max(tilde_obj.info['cellpar'][0:3])), tilde_obj.info['cellpar'].index(min(tilde_obj.info['cellpar'][0:3])) cmpveci = [i for i in range(3) if i not in [zi, mi]][0] # vacuum per one atom (av) atoms_volume = 0.0 for i in tilde_obj.structures[-1]: atoms_volume += 4/3 * math.pi * (covalent_radii[ chemical_symbols.index( i.symbol ) ] + r_EC) ** 3 av = (abs(det(tilde_obj.structures[-1].cell)) - atoms_volume)/len(tilde_obj.structures[-1]) #print "av:", av # too much vacuum if av > ADDED_VACUUM_3D_TOL: # 2D if tilde_obj.info['cellpar'][cmpveci] * L < tilde_obj.info['cellpar'][zi]: tilde_obj.info['techs'].append( 'vacuum %sA' % int(round(tilde_obj.info['cellpar'][zi])) ) tilde_obj.structures[-1].set_pbc((True, True, False)) # TODO: 1D ? # 0D elif av > ADDED_VACUUM_3D_TOL*4: # TODO tilde_obj.structures[-1].set_pbc((False, False, False)) # extend the last ASE object tilde_obj.structures[-1].periodicity = int(sum(tilde_obj.structures[-1].get_pbc())) tilde_obj.structures[-1].dims = abs(det(tilde_obj.structures[-1].cell)) # TODO: area for surfaces tilde_obj.info['dims'] = tilde_obj.structures[-1].dims tilde_obj.info['periodicity'] = tilde_obj.structures[-1].periodicity return tilde_obj
def write_lammps_atoms(self, atoms): """Write atoms infor for LAMMPS""" fileobj = self.prefix + '_atoms' if isinstance(fileobj, str): fileobj = open(fileobj, 'w') # header fileobj.write(fileobj.name + ' (by ' + str(self.__class__) + ')\n\n') fileobj.write(str(len(atoms)) + ' atoms\n') fileobj.write(str(len(atoms.types)) + ' atom types\n') btypes, blist = self.get_bonds(atoms) if len(blist): fileobj.write(str(len(blist)) + ' bonds\n') fileobj.write(str(len(btypes)) + ' bond types\n') atypes, alist = self.get_angles() if len(alist): fileobj.write(str(len(alist)) + ' angles\n') fileobj.write(str(len(atypes)) + ' angle types\n') dtypes, dlist = self.get_dihedrals(alist, atypes) if len(dlist): fileobj.write(str(len(dlist)) + ' dihedrals\n') fileobj.write(str(len(dtypes)) + ' dihedral types\n') # cell p = prism(atoms.get_cell()) xhi, yhi, zhi, xy, xz, yz = p.get_lammps_prism_str() fileobj.write('\n0.0 %s xlo xhi\n' % xhi) fileobj.write('0.0 %s ylo yhi\n' % yhi) fileobj.write('0.0 %s zlo zhi\n' % zhi) # atoms fileobj.write('\nAtoms\n\n') tag = atoms.get_tags() for i, r in enumerate(map(p.pos_to_lammps_str, atoms.get_positions())): q = 0 # charge will be overwritten fileobj.write('%6d %3d %3d %s %s %s %s' % ((i + 1, 1, tag[i] + 1, q) + tuple(r))) fileobj.write(' # ' + atoms.types[tag[i]] + '\n') # velocities velocities = atoms.get_velocities() if velocities is not None: fileobj.write('\nVelocities\n\n') for i, v in enumerate(velocities): fileobj.write('%6d %g %g %g\n' % (i + 1, v[0], v[1], v[2])) # masses fileobj.write('\nMasses\n\n') for i, typ in enumerate(atoms.types): cs = atoms.split_symbol(typ)[0] fileobj.write('%6d %g # %s -> %s\n' % (i + 1, atomic_masses[chemical_symbols.index(cs)], typ, cs)) # bonds if len(blist): fileobj.write('\nBonds\n\n') for ib, bvals in enumerate(blist): fileobj.write('%8d %6d %6d %6d ' % (ib + 1, bvals[0] + 1, bvals[1] + 1, bvals[2] + 1)) fileobj.write('# ' + btypes[bvals[0]] + '\n') # angles if len(alist): fileobj.write('\nAngles\n\n') for ia, avals in enumerate(alist): fileobj.write('%8d %6d %6d %6d %6d ' % (ia + 1, avals[0] + 1, avals[1] + 1, avals[2] + 1, avals[3] + 1)) fileobj.write('# ' + atypes[avals[0]] + '\n') # dihedrals if len(dlist): fileobj.write('\nDihedrals\n\n') for i, dvals in enumerate(dlist): fileobj.write('%8d %6d %6d %6d %6d %6d ' % (i + 1, dvals[0] + 1, dvals[1] + 1, dvals[2] + 1, dvals[3] + 1, dvals[4] + 1)) fileobj.write('# ' + dtypes[dvals[0]] + '\n') return btypes, atypes, dtypes
def write_lammps_atoms(self, atoms): """Write atoms infor for LAMMPS""" fileobj = self.prefix + '_atoms' if isinstance(fileobj, str): fileobj = open(fileobj, 'w') # header fileobj.write(fileobj.name + ' (by ' + str(self.__class__) + ')\n\n') fileobj.write(str(len(atoms)) + ' atoms\n') fileobj.write(str(len(atoms.types)) + ' atom types\n') btypes, blist = self.get_bonds(atoms) if len(blist): fileobj.write(str(len(blist)) + ' bonds\n') fileobj.write(str(len(btypes)) + ' bond types\n') atypes, alist = self.get_angles() if len(alist): fileobj.write(str(len(alist)) + ' angles\n') fileobj.write(str(len(atypes)) + ' angle types\n') dtypes, dlist = self.get_dihedrals(alist, atypes) if len(dlist): fileobj.write(str(len(dlist)) + ' dihedrals\n') fileobj.write(str(len(dtypes)) + ' dihedral types\n') # cell p = prism(atoms.get_cell()) xhi, yhi, zhi, xy, xz, yz = p.get_lammps_prism_str() fileobj.write('\n0.0 %s xlo xhi\n' % xhi) fileobj.write('0.0 %s ylo yhi\n' % yhi) fileobj.write('0.0 %s zlo zhi\n' % zhi) # atoms fileobj.write('\nAtoms\n\n') tag = atoms.get_tags() for i, r in enumerate(map(p.pos_to_lammps_str, atoms.get_positions())): q = 0 # charge will be overwritten fileobj.write('%6d %3d %3d %s %s %s %s' % ((i + 1, 1, tag[i] + 1, q) + tuple(r))) fileobj.write(' # ' + atoms.types[tag[i]] + '\n') # velocities velocities = atoms.get_velocities() if velocities is not None: fileobj.write('\nVelocities\n\n') for i, v in enumerate(velocities): fileobj.write('%6d %g %g %g\n' % (i + 1, v[0], v[1], v[2])) # masses fileobj.write('\nMasses\n\n') for i, typ in enumerate(atoms.types): cs = atoms.split_symbol(typ)[0] fileobj.write( '%6d %g # %s -> %s\n' % (i + 1, atomic_masses[chemical_symbols.index(cs)], typ, cs)) # bonds if len(blist): fileobj.write('\nBonds\n\n') for ib, bvals in enumerate(blist): fileobj.write( '%8d %6d %6d %6d ' % (ib + 1, bvals[0] + 1, bvals[1] + 1, bvals[2] + 1)) fileobj.write('# ' + btypes[bvals[0]] + '\n') # angles if len(alist): fileobj.write('\nAngles\n\n') for ia, avals in enumerate(alist): fileobj.write('%8d %6d %6d %6d %6d ' % (ia + 1, avals[0] + 1, avals[1] + 1, avals[2] + 1, avals[3] + 1)) fileobj.write('# ' + atypes[avals[0]] + '\n') # dihedrals if len(dlist): fileobj.write('\nDihedrals\n\n') for i, dvals in enumerate(dlist): fileobj.write('%8d %6d %6d %6d %6d %6d ' % (i + 1, dvals[0] + 1, dvals[1] + 1, dvals[2] + 1, dvals[3] + 1, dvals[4] + 1)) fileobj.write('# ' + dtypes[dvals[0]] + '\n') return btypes, atypes, dtypes
def write_ion(fileobj, atoms, pseudo_dir=None, scaled=True, add_constraints=False, copy_psp=True, comment=''): """ Standard ase io file for reading the sparc-x .ion format inputs: atoms (ase atoms object): an ase atoms object of the system being written to a file pseudos (list): a list of the locations of the pseudopotential files to be used """ directory = os.path.dirname(fileobj.name) elements = sorted(list(set(atoms.get_chemical_symbols()))) fileobj.write('# Input File Generated By SPARC ASE Calculator #\n') fileobj.write('#CELL:') for comp in atoms.cell: fileobj.write(' {}'.format( format(np.linalg.norm(comp) / Bohr, ' .15f'))) fileobj.write('\n#LATVEC\n') for comp in atoms.cell: fileobj.write('#') comp_n = comp / np.linalg.norm(comp) # normalize for i in comp_n: fileobj.write(' {}'.format(format(i, ' .15f'))) fileobj.write('\n') if atoms.pbc is not None: fileobj.write('#PBC: ') for condition in atoms.pbc: fileobj.write(str(condition) + ' ') fileobj.write('\n') fileobj.write('# ' + comment + '\n\n\n') # translate the constraints to usable strings if add_constraints == True: cons_indices = [] cons_strings = [] for constraint in atoms.constraints: name = type(constraint).__name__ if name == 'FixAtoms': cons_indices += list(constraint.index) cons_strings += ['0 0 0\n'] * len(constraint.index) elif name == 'FixedLine' or 'FixedPlane': line_dir = [int(np.ceil(a)) for a in constraint.dir] max_dirs = 1 # for FixedLine # The API of FixedLine and FixedPlane is the same # except for the need for an inversion of indices if name == 'FixedPlane': line_dir = [int(not a) for a in line_dir] max_dirs = 2 if line_dir.count(1) > max_dirs: warnings.warn('The .ion filetype can only support' 'freezing entire dimensions (x,y,z).' 'The {} constraint on this atoms' ' Object is being converted to allow ' 'movement in any directions in which the' ' atom had some freedom to move'.format(name)) cons_indices += constraint.get_indices() cons_strings.append('{} {} {}\n'.format(*line_dir)) else: warnings.warn('The constraint type {} is not supported by' ' the .ion format. This constraint will be' ' ignored') # just make sure that there aren't repeats assert len(set(cons_indices)) == len(cons_indices) for i, element in enumerate(elements): fileobj.write('ATOM_TYPE: ') fileobj.write(element + '\n') fileobj.write('N_TYPE_ATOM: ') fileobj.write(str(atoms.get_chemical_symbols().count(element)) + '\n') if add_constraints: constraints_string = 'RELAX:\n' magmoms = [float(a) for a in atoms.get_initial_magnetic_moments()] if magmoms != [0.] * len(atoms): spin_string = 'SPIN:\n' # TODO: fix this psuedopotential finding code if pseudo_dir is not None:#there is a pseudopotential directory provided if not os.path.isdir(pseudo_dir):#if it is not a directory, then the SPARC_PSP_PATH is used pseudo_dir = os.environ['SPARC_PSP_PATH'] warnings.warn('The path entered for `pseudo_dir` could ' 'not be found. Using SPARC_PSP_PATH and generic pseudopotential ' 'file names (<symbol>.pot). Psuedopotentials must be added ' 'manually for SPARC to execute successfully.') fileobj.write('PSEUDO_POT: {}.pot\n'.format(element)) else: pseudos_in_dir = [a for a in os.listdir(pseudo_dir) if a.endswith(element+'.pot') or a.endswith(element+'.psp8')] filename = [a for a in os.listdir(pseudo_dir) if a.endswith(element+'.pot') or a.endswith(element+'.psp8')] if len(filename) == 0: filename = element+'.pot' warnings.warn('No pseudopotential detected for '+element+'. ' 'Using generic filename ('+element + '.pot). The pseudopotential ' ' must be added manually for SPARC to execute successfully.') else: if len(filename) > 1: warnings.warn('Multiple psudopotentials detected for ' '{} ({}).'.format(element, str(filename))+' Using '+filename[0]+'.') if copy_psp: filename = filename[0] full_psp_path = os.path.abspath(os.path.join(pseudo_dir, filename)) full_dest_path = os.path.abspath(os.path.join(directory, filename)) if full_psp_path != full_dest_path: #shutil.copyfile(os.path.join(pseudo_dir, filename), # os.path.join(directory, filename)) shutil.copyfile(full_psp_path, full_dest_path) else: filename = os.path.join(pseudo_dir, filename[0]) #os.system('cp $SPARC_PSP_PATH/' + filename + ' .') fileobj.write('PSEUDO_POT: {}\n'.format(filename)) else: fileobj.write('PSEUDO_POT: {}.pot\n'.format(element)) atomic_number = chemical_symbols.index(element) atomic_mass = atomic_masses_iupac2016[atomic_number] fileobj.write('ATOMIC_MASS: {}\n'.format(atomic_mass)) if scaled == False: fileobj.write('COORD:\n') positions = atoms.get_positions(wrap=False) positions /= Bohr else: fileobj.write('COORD_FRAC:\n') positions = atoms.get_scaled_positions(wrap=False) for atom, position in zip(atoms, positions): if atom.symbol == element: for component in position: fileobj.write(' {}'.format(format(component, ' .15f'))) fileobj.write(' # index {}'.format(atom.index)) fileobj.write('\n') # mess with the constraints if add_constraints: if atom.index in cons_indices: constraints_indices_index = cons_indices.index( atom.index) constraints_string += cons_strings[constraints_indices_index] else: constraints_string += '1 1 1\n' if 'spin_string' in locals(): spin_string += format(atom.magmom, ' .15f') + '\n' # dump in the constraints if add_constraints: fileobj.write(constraints_string) if 'spin_string' in locals(): fileobj.write(spin_string) fileobj.write('\n\n')
def write_lammps_atoms(self, atoms, connectivities): """Write atoms input for LAMMPS""" fname = self.prefix + '_atoms' fileobj = open(fname, 'w') # header fileobj.write(fileobj.name + ' (by ' + str(self.__class__) + ')\n\n') fileobj.write(str(len(atoms)) + ' atoms\n') fileobj.write(str(len(atoms.types)) + ' atom types\n') blist = connectivities['bonds'] if len(blist): btypes = connectivities['bond types'] fileobj.write(str(len(blist)) + ' bonds\n') fileobj.write(str(len(btypes)) + ' bond types\n') alist = connectivities['angles'] if len(alist): atypes = connectivities['angle types'] fileobj.write(str(len(alist)) + ' angles\n') fileobj.write(str(len(atypes)) + ' angle types\n') dlist = connectivities['dihedrals'] if len(dlist): dtypes = connectivities['dihedral types'] fileobj.write(str(len(dlist)) + ' dihedrals\n') fileobj.write(str(len(dtypes)) + ' dihedral types\n') # cell p = Prism(atoms.get_cell()) xhi, yhi, zhi, xy, xz, yz = p.get_lammps_prism_str() fileobj.write('\n0.0 %s xlo xhi\n' % xhi) fileobj.write('0.0 %s ylo yhi\n' % yhi) fileobj.write('0.0 %s zlo zhi\n' % zhi) # atoms fileobj.write('\nAtoms\n\n') tag = atoms.get_tags() if atoms.has('molid'): molid = atoms.get_array('molid') else: molid = [1] * len(atoms) for i, r in enumerate(p.positions_to_lammps_strs( atoms.get_positions())): atype = atoms.types[tag[i]] if len(atype) < 2: atype = atype + ' ' q = self.data['one'][atype][2] fileobj.write('%6d %3d %3d %s %s %s %s' % ((i + 1, molid[i], tag[i] + 1, q) + tuple(r))) fileobj.write(' # ' + atoms.types[tag[i]] + '\n') # velocities velocities = atoms.get_velocities() if velocities is not None: fileobj.write('\nVelocities\n\n') for i, v in enumerate(velocities): fileobj.write('%6d %g %g %g\n' % (i + 1, v[0], v[1], v[2])) # masses fileobj.write('\nMasses\n\n') for i, typ in enumerate(atoms.types): cs = atoms.split_symbol(typ)[0] fileobj.write( '%6d %g # %s -> %s\n' % (i + 1, atomic_masses[chemical_symbols.index(cs)], typ, cs)) # bonds if len(blist): fileobj.write('\nBonds\n\n') for ib, bvals in enumerate(blist): fileobj.write( '%8d %6d %6d %6d ' % (ib + 1, bvals[0] + 1, bvals[1] + 1, bvals[2] + 1)) try: fileobj.write('# ' + btypes[bvals[0]]) except: pass fileobj.write('\n') # angles if len(alist): fileobj.write('\nAngles\n\n') for ia, avals in enumerate(alist): fileobj.write('%8d %6d %6d %6d %6d ' % (ia + 1, avals[0] + 1, avals[1] + 1, avals[2] + 1, avals[3] + 1)) try: fileobj.write('# ' + atypes[avals[0]]) except: pass fileobj.write('\n') # dihedrals if len(dlist): fileobj.write('\nDihedrals\n\n') for i, dvals in enumerate(dlist): fileobj.write('%8d %6d %6d %6d %6d %6d ' % (i + 1, dvals[0] + 1, dvals[1] + 1, dvals[2] + 1, dvals[3] + 1, dvals[4] + 1)) try: fileobj.write('# ' + dtypes[dvals[0]]) except: pass fileobj.write('\n')
x = [] while len(x) < n: a, b, c = array([numpy_random.normal(init[0], s), numpy_random.normal(init[1], s), numpy_random.normal(init[2], s)]) if abs(a) < 1 and abs(b) < 1 and abs(c) < 1: x.append([a,b,c]) X.extend(x) X = array(X)[:N] return X if DISTRIB == 'RANDOM': set_x, set_y = [random.choice(chemical_symbols) for i in range(N)], [random.choice(chemical_symbols) for i in range(N)] set_z = [round(random.uniform(0.1, 15.0), 2) for i in range(N)] data, ref = [], [] for i in range(N): formula = set_x[i] + set_y[i] set_x[i] = get_element_group(chemical_symbols.index(set_x[i])) set_y[i] = get_element_group(chemical_symbols.index(set_y[i])) data.append(Point([set_x[i], set_y[i], set_z[i]], formula)) ref.append([set_x[i], set_y[i], set_z[i]]) else: nte = len(chemical_symbols) G = gaussian_distribution(N, k_from_n(N)) set_x = (G[:,0] + 1)/2*nte set_x = map(lambda x: int(math.floor(x)), set_x.tolist()) set_y = (G[:,1] + 1)/2*nte set_y = map(lambda x: int(math.floor(x)), set_y.tolist()) set_z = (G[:,2] + 1)/2*15
def build_shell_dist_fig(bimet, show=False): """ Creates shell distribution figure of BimetallicResult Args: Returns: (plt.Figure) """ # get atoms object atoms = bimet.build_atoms_obj().copy() # build shells dictionary shape = bimet.shape num_shells = bimet.nanoparticle.num_shells shells_dict = build_atoms_in_shell_dict(shape, num_shells) # list of all shells in NP (0 is core atom) shell_ls = sorted(shells_dict) # calc total and metal counts for each shell tot_count = np.zeros(len(shell_ls)) m1_count = np.zeros(len(shell_ls)) m2_count = np.zeros(len(shell_ls)) for i, shell in enumerate(sorted(shells_dict)): # indices of atoms in current shell indices = shells_dict[shell] # total atom count of current shell tot_count[i] = len(indices) # metal type counts for current shell m1_count[i] = (atoms[indices].symbols == bimet.metal1).sum() m2_count[i] = (atoms[indices].symbols == bimet.metal2).sum() # normalize counts to concentrations norm_m1 = m1_count / tot_count norm_m2 = m2_count / tot_count fig, axes = plt.subplots(2, 1, sharex=True) ax1, ax2 = axes m1_color = jmol_colors[chemical_symbols.index(bimet.metal1)] m2_color = jmol_colors[chemical_symbols.index(bimet.metal2)] ax1.plot(shell_ls, m1_count, 'o-', markeredgecolor='k', color=m1_color, markersize=8, label=bimet.metal1) ax1.plot(shell_ls, m2_count, 'o-', markeredgecolor='k', color=m2_color, markersize=8, label=bimet.metal2) ax1.legend(loc='upper left') ax1.set_xticks(list(range(1, bimet.nanoparticle.num_shells + 1))) # set ylim to maximum number of atoms on surface high = int(round((tot_count[-1] + 10) * 10) / 10) ax1.set_ylim(-2, high) ax1.set_ylabel('# of Atoms') ax2.plot(shell_ls, norm_m1, 'o-', markeredgecolor='k', color=m1_color, markersize=8) ax2.plot(shell_ls, norm_m2, 'o-', markeredgecolor='k', color=m2_color, markersize=8) ax2.set_ylabel('Concentration') yticks = [0, 0.25, 0.5, 0.75, 1] ax2.set_yticks(yticks) ax2.set_yticklabels(['{:,.0%}'.format(x) for x in yticks]) ax2.set_xticks(shell_ls) # create xtick labels based on shell number xticklabels = ['Shell %i' % i for i in shell_ls] xticklabels[0] = 'Core' xticklabels[-1] = 'Surface' ax2.set_xticklabels(xticklabels, rotation=45) fig.suptitle(bimet.build_chem_formula(latex=True) + ' - %s' % shape) fig.tight_layout(rect=(0, 0, 1, 0.9)) if show: plt.show() return fig
def classify(tilde_obj): if len(tilde_obj.info['elements']) == 1: return tilde_obj C_site = [e for e in tilde_obj.info['elements'] if e in Perovskite_Structure.C] if not C_site: return tilde_obj A_site = [e for e in tilde_obj.info['elements'] if e in Perovskite_Structure.A] B_site = [e for e in tilde_obj.info['elements'] if e in Perovskite_Structure.B] # proportional content coefficient D_prop AB, C = 0, 0 for i in set(A_site + B_site): AB += tilde_obj.info['contents'][ tilde_obj.info['elements'].index(i) ] for i in C_site: C += tilde_obj.info['contents'][ tilde_obj.info['elements'].index(i) ] try: D_prop = float(C) / AB except ZeroDivisionError: return tilde_obj # 2-component pseudo-perovskites # TODO account other pseudo-perovskites e.g. Mn2O3 or binary-metal ones if tilde_obj.info['elements'][0] in ['W', 'Re'] and len(tilde_obj.info['elements']) == 2: if round(D_prop) == 3: tilde_obj.info['tags'].append(0x4) return tilde_obj if not A_site or not B_site: return tilde_obj if not 1.3 < D_prop < 2.3: return tilde_obj # D_prop grows for 2D adsorption cases (>1.9) n_combs, n_offs = 0, 0 for A in A_site: for B in B_site: if B == A: continue for C in C_site: rA = covalent_radii[chemical_symbols.index(A)] rB = covalent_radii[chemical_symbols.index(B)] rC = covalent_radii[chemical_symbols.index(C)] # Goldschmidt tolerance factor # t = (rA + rC) / sqrt(2) * (rB + rC) # 0.71 =< t =< 1.2 # t < 0.71 ilmenite, corundum or KNbO3 structure # t > 1 hexagonal perovskite polytypes # http://en.wikipedia.org/wiki/Goldschmidt_tolerance_factor factor = (rA + rC) / (math.sqrt(2) * (rB + rC)) if not 0.71 <= factor <= 1.4: n_offs += 1 n_combs += 1 if n_offs == n_combs: return tilde_obj tilde_obj.info['tags'].append(0x4) if tilde_obj.structures[-1].periodicity != 3: return tilde_obj # all below is for 3d case : TODO contents = [] impurities, A_hosts, B_hosts = {}, {}, {} # What is a defect? # Empirical criteria of defect for ab initio modeling: =< 25% of the content for n, i in enumerate(tilde_obj.info['elements']): contents.append( [n, i, float(tilde_obj.info['contents'][n])/sum(tilde_obj.info['contents'])] ) contents = sorted(contents, key = lambda i: i[2]) for num in range(len(contents)): try: contents[num+1] except IndexError: break # defect content differs at least 2x from the smallest content; defect partial weight <= 1/16 if contents[num][2] <= 0.0625 and contents[num][2] / contents[num+1][2] <= 0.5: impurities[ contents[num][1] ] = tilde_obj.info['contents'][ contents[num][0] ] # ex: ['Fe', 2] elif contents[num][1] in Perovskite_Structure.A: A_hosts[ contents[num][1] ] = tilde_obj.info['contents'][ contents[num][0] ] elif contents[num][1] in Perovskite_Structure.B: B_hosts[ contents[num][1] ] = tilde_obj.info['contents'][ contents[num][0] ] #print impurities, A_hosts, B_hosts if len(A_hosts) > 1 or len(B_hosts) > 1: return tilde_obj # skip complex perovskites and those where an element may occupy either A or B # A site or B site? num = 0 for impurity_element, content in six.iteritems(impurities): e = tilde_obj.info['elements'].index(impurity_element) tilde_obj.info['elements'].pop(e) # TODO tilde_obj.info['contents'].pop(e) # TODO tilde_obj.info['impurity' + str(num)] = impurity_element + str(content) if content > 1 else impurity_element num += 1 if impurity_element in Perovskite_Structure.A: A_hosts[A_hosts.keys()[0]] += content elif impurity_element in Perovskite_Structure.B: B_hosts[B_hosts.keys()[0]] += content for n, i in enumerate(tilde_obj.info['elements']): if i in A_hosts: tilde_obj.info['contents'][n] = A_hosts[i] # TODO elif i in B_hosts: tilde_obj.info['contents'][n] = B_hosts[i] # TODO for i in C_site: c_content = tilde_obj.info['contents'][ tilde_obj.info['elements'].index(i) ] tot_content = sum(tilde_obj.info['contents']) D_O = float(c_content) / tot_content if D_O < 0.6: # C-site lack tilde_obj.info['lack'] = i break # TODO return tilde_obj
def write_lammps_data(filename, atoms, atom_types, comment=None, cutoff=None, molecule_ids=None, charges=None, units='metal', bond_types=None, angle_types=None, dihedral_types=None): if isinstance(filename, basestring): fh = open(filename, 'w') else: fh = filename if comment is None: comment = 'lammpslib autogenerated data file' fh.write(comment.strip() + '\n\n') fh.write('{0} atoms\n'.format(len(atoms))) fh.write('{0} atom types\n'.format(len(atom_types))) if bond_types: from matscipy.neighbours import neighbour_list i_list, j_list = neighbour_list('ij', atoms, cutoff) print 'Bonds:' bonds = [] for bond_type, (Z1, Z2) in enumerate(bond_types): bond_mask = (atoms.numbers[i_list] == Z1) & (atoms.numbers[j_list] == Z2) print (Z1, Z2), bond_mask.sum() for (I, J) in zip(i_list[bond_mask], j_list[bond_mask]): #NB: LAMMPS uses 1-based indices for bond types and particle indices bond = (bond_type+1, I+1, J+1) bonds.append(bond) print if len(bonds) > 0: fh.write('{0} bonds\n'.format(len(bonds))) fh.write('{0} bond types\n'.format(len(bond_types))) if angle_types: print 'Angles:' angle_count = { angle : 0 for angle in angle_types } angles = [] for I in range(len(atoms)): for J in j_list[i_list == I]: for K in j_list[i_list == J]: if J < K: continue Zi, Zj, Zk = atoms.numbers[[I, J, K]] if (Zj, Zi, Zk) in angle_types: angle = (angle_types.index((Zj, Zi, Zk))+1, J+1, I+1, K+1) angle_count[(Zj, Zi, Zk)] += 1 angles.append(angle) for angle in angle_types: print angle, angle_count[angle] print if len(angles) > 0: fh.write('{0} angles\n'.format(len(angles))) fh.write('{0} angle types\n'.format(len(angle_types))) if dihedral_types: print 'Dihedrals:' dihedral_count = { dihedral : 0 for dihedral in dihedral_types } dihedrals = [] for I in range(len(atoms)): for J in j_list[i_list == I]: for K in j_list[i_list == J]: for L in j_list[i_list == K]: Zi, Zj, Zk, Zl = atoms.numbers[[I, J, K, L]] if (Zi, Zj, Zk, Zl) in dihedral_types: dihedral = (dihedral_types.index((Zi, Zj, Zk, Zl))+1, I+1, J+1, K+1, L+1) dihedral_count[(Zi, Zj, Zk, Zl)] += 1 dihedrals.append(dihedral) for dihedral in dihedral_types: print dihedral, dihedral_count[dihedral] print if len(dihedrals) > 0: fh.write('{0} dihedrals\n'.format(len(dihedrals))) fh.write('{0} dihedral types\n'.format(len(dihedral_types))) fh.write('\n') cell, coord_transform = convert_cell(atoms.get_cell()) fh.write('{0:16.8e} {1:16.8e} xlo xhi\n'.format(0.0, cell[0, 0])) fh.write('{0:16.8e} {1:16.8e} ylo yhi\n'.format(0.0, cell[1, 1])) fh.write('{0:16.8e} {1:16.8e} zlo zhi\n'.format(0.0, cell[2, 2])) fh.write('{0:16.8e} {1:16.8e} {2:16.8e} xy xz yz\n'.format(cell[0, 1], cell[0, 2], cell[1, 2])) fh.write('\nMasses\n\n') sym_mass = {} masses = atoms.get_masses() symbols = atoms.get_chemical_symbols() for sym in atom_types: for i in range(len(atoms)): if symbols[i] == sym: sym_mass[sym] = masses[i] / unit_convert("mass", units) break else: sym_mass[sym] = atomic_masses[chemical_symbols.index(sym)] / unit_convert("mass", units) for (sym, typ) in sorted(atom_types.items(), key=operator.itemgetter(1)): fh.write('{0} {1}\n'.format(typ, sym_mass[sym])) fh.write('\nAtoms # full\n\n') if molecule_ids is None: molecule_ids = np.zeros(len(atoms), dtype=int) if charges is None: charges = atoms.get_initial_charges() for i, (sym, mol, q, pos) in enumerate(zip(symbols, molecule_ids, charges, atoms.get_positions())): typ = atom_types[sym] fh.write('{0} {1} {2} {3:16.8e} {4:16.8e} {5:16.8e} {6:16.8e}\n' .format(i+1, mol, typ, q, pos[0], pos[1], pos[2])) if bond_types and len(bonds) > 0: fh.write('\nBonds\n\n') for idx, bond in enumerate(bonds): fh.write('{0} {1} {2} {3}\n' .format(*[idx+1] + list(bond))) if angle_types and len(angles) > 0: fh.write('\nAngles\n\n') for idx, angle in enumerate(angles): fh.write('{0} {1} {2} {3} {4}\n' .format(*[idx+1] + list(angle))) if dihedral_types and len(dihedrals) > 0: fh.write('\nDihedrals\n\n') for idx, dihedral in enumerate(dihedrals): fh.write('{0} {1} {2} {3} {4} {5}\n' .format(*[idx+1] + list(dihedral))) if isinstance(filename, basestring): fh.close()
def write_lammps_atoms(self, atoms, connectivities): """Write atoms input for LAMMPS""" fname = self.prefix + '_atoms' fileobj = open(fname, 'w') # header fileobj.write(fileobj.name + ' (by ' + str(self.__class__) + ')\n\n') fileobj.write(str(len(atoms)) + ' atoms\n') fileobj.write(str(len(atoms.types)) + ' atom types\n') blist = connectivities['bonds'] if len(blist): btypes = connectivities['bond types'] fileobj.write(str(len(blist)) + ' bonds\n') fileobj.write(str(len(btypes)) + ' bond types\n') alist = connectivities['angles'] if len(alist): atypes = connectivities['angle types'] fileobj.write(str(len(alist)) + ' angles\n') fileobj.write(str(len(atypes)) + ' angle types\n') dlist = connectivities['dihedrals'] if len(dlist): dtypes = connectivities['dihedral types'] fileobj.write(str(len(dlist)) + ' dihedrals\n') fileobj.write(str(len(dtypes)) + ' dihedral types\n') # cell p = Prism(atoms.get_cell()) xhi, yhi, zhi, xy, xz, yz = p.get_lammps_prism_str() fileobj.write('\n0.0 %s xlo xhi\n' % xhi) fileobj.write('0.0 %s ylo yhi\n' % yhi) fileobj.write('0.0 %s zlo zhi\n' % zhi) # atoms fileobj.write('\nAtoms\n\n') tag = atoms.get_tags() if atoms.has('molid'): molid = atoms.get_array('molid') else: molid = [1] * len(atoms) for i, r in enumerate(map(p.pos_to_lammps_str, atoms.get_positions())): q = self.data['one'][atoms.types[tag[i]]][2] fileobj.write('%6d %3d %3d %s %s %s %s' % ((i + 1, molid[i], tag[i] + 1, q) + tuple(r))) fileobj.write(' # ' + atoms.types[tag[i]] + '\n') # velocities velocities = atoms.get_velocities() if velocities is not None: fileobj.write('\nVelocities\n\n') for i, v in enumerate(velocities): fileobj.write('%6d %g %g %g\n' % (i + 1, v[0], v[1], v[2])) # masses fileobj.write('\nMasses\n\n') for i, typ in enumerate(atoms.types): cs = atoms.split_symbol(typ)[0] fileobj.write('%6d %g # %s -> %s\n' % (i + 1, atomic_masses[chemical_symbols.index(cs)], typ, cs)) # bonds if len(blist): fileobj.write('\nBonds\n\n') for ib, bvals in enumerate(blist): fileobj.write('%8d %6d %6d %6d ' % (ib + 1, bvals[0] + 1, bvals[1] + 1, bvals[2] + 1)) try: fileobj.write('# ' + btypes[bvals[0]]) except: pass fileobj.write('\n') # angles if len(alist): fileobj.write('\nAngles\n\n') for ia, avals in enumerate(alist): fileobj.write('%8d %6d %6d %6d %6d ' % (ia + 1, avals[0] + 1, avals[1] + 1, avals[2] + 1, avals[3] + 1)) try: fileobj.write('# ' + atypes[avals[0]]) except: pass fileobj.write('\n') # dihedrals if len(dlist): fileobj.write('\nDihedrals\n\n') for i, dvals in enumerate(dlist): fileobj.write('%8d %6d %6d %6d %6d %6d ' % (i + 1, dvals[0] + 1, dvals[1] + 1, dvals[2] + 1, dvals[3] + 1, dvals[4] + 1)) try: fileobj.write('# ' + dtypes[dvals[0]]) except: pass fileobj.write('\n')
def write_cfg(f, a): """Write atomic configuration to a CFG-file (native AtomEye format). See: http://mt.seas.upenn.edu/Archive/Graphics/A/ """ if isinstance(f, basestring): f = paropen(f, 'w') if isinstance(a, list): if len(a) == 1: a = a[0] else: raise RuntimeError('Cannot write sequence to single .cfg file.') f.write('Number of particles = %i\n' % len(a)) f.write('A = 1.0 Angstrom\n') cell = a.get_cell(complete=True) for i in range(3): for j in range(3): f.write('H0(%1.1i,%1.1i) = %f A\n' % (i + 1, j + 1, cell[i, j])) entry_count = 3 for x in a.arrays.keys(): if x not in cfg_default_fields: if len(a.get_array(x).shape) == 1: entry_count += 1 else: entry_count += a.get_array(x).shape[1] vels = a.get_velocities() if isinstance(vels, np.ndarray): entry_count += 3 else: f.write('.NO_VELOCITY.\n') f.write('entry_count = %i\n' % entry_count) i = 0 for name, aux in a.arrays.items(): if name not in cfg_default_fields: if len(aux.shape) == 1: f.write('auxiliary[%i] = %s [a.u.]\n' % (i, name)) i += 1 else: if aux.shape[1] == 3: for j in range(3): f.write('auxiliary[%i] = %s_%s [a.u.]\n' % (i, name, chr(ord('x') + j))) i += 1 else: for j in range(aux.shape[1]): f.write('auxiliary[%i] = %s_%1.1i [a.u.]\n' % (i, name, j)) i += 1 # Distinct elements spos = a.get_scaled_positions() for i in a: el = i.symbol f.write('%f\n' % ase.data.atomic_masses[chemical_symbols.index(el)]) f.write('%s\n' % el) x, y, z = spos[i.index, :] s = '%e %e %e ' % (x, y, z) if isinstance(vels, np.ndarray): vx, vy, vz = vels[i.index, :] s = s + ' %e %e %e ' % (vx, vy, vz) for name, aux in a.arrays.items(): if name not in cfg_default_fields: if len(aux.shape) == 1: s += ' %e' % aux[i.index] else: s += (aux.shape[1] * ' %e') % tuple(aux[i.index].tolist()) f.write('%s\n' % s)
def bdplotter(task, **kwargs): ''' bdplotter is based on the fact that phonon DOS/bands and electron DOS/bands are the objects of the same kind. 1) DOS is formatted precomputed / smeared according to a normal distribution 2) bands are formatted precomputed / interpolated through natural cubic spline function ''' if task == 'bands': # CRYSTAL, "VASP", EXCITING results = [] if 'precomputed' in kwargs: for n in range(len(kwargs['precomputed']['ticks'])): if kwargs['precomputed']['ticks'][n][1] == 'GAMMA': kwargs['precomputed']['ticks'][n][1] = 'Γ' for stripe in kwargs['precomputed']['stripes']: results.append({'color':'#000000', 'data':[], 'ticks':kwargs['precomputed']['ticks']}) for n, val in enumerate(stripe): results[-1]['data'].append([ kwargs['precomputed']['abscissa'][n], val]) else: if not 'order' in kwargs: order = sorted( kwargs['values'].keys() ) # TODO else: order = kwargs['order'] nullstand = '0 0 0' if not '0 0 0' in kwargs['values']: # possible case when there is shifted Gamma point nullstand = order[0] # reduce k if too much if len(order)>20: red_order = [] for i in range(0, len(order), int(math.floor(len(order)/10))): red_order.append(order[i]) order = red_order for N in range(len( kwargs['values'][nullstand] )): # interpolate for each curve throughout the BZ results.append({'color':'#000000', 'data':[], 'ticks':[]}) d = 0.0 x = [] y = [] bz_vec_ref = [0, 0, 0] for bz in order: y.append( kwargs['values'][bz][N] ) bz_coords = map(frac2float, bz.split() ) bz_vec_cur = dot( bz_coords, linalg.inv( kwargs['xyz_matrix'] ).transpose() ) bz_vec_dir = map(sum, zip(bz_vec_cur, bz_vec_ref)) bz_vec_ref = bz_vec_cur d += linalg.norm( bz_vec_dir ) x.append(d) results[-1]['ticks'].append( [d, bz.replace(' ', '')] ) # end in nullstand point (normally, Gamma) #y.append(kwargs['values'][nullstand][N]) #if d == 0: d+=0.5 #else: d += linalg.norm( bz_vec_ref ) #x.append(d) #results[-1]['ticks'].append( [d, nullstand.replace(' ', '')] ) divider = 10 if len(order)<10 else 1.5 step = (max(x)-min(x)) / len(kwargs['values']) / divider xnew = arange(min(x), max(x)+step/2, step).tolist() ynew = [] f = NaturalCubicSpline( array(x), array(y) ) for i in xnew: results[-1]['data'].append([ round( i, 3 ), round( f(i), 3 ) ]) # round to reduce output return results elif task == 'dos': # CRYSTAL, VASP, EXCITING results = [] if 'precomputed' in kwargs: total_dos = [[i, kwargs['precomputed']['total'][n]] for n, i in enumerate(kwargs['precomputed']['x'])] else: tdos = TotalDos( kwargs['eigenvalues'], sigma=kwargs['sigma'] ) tdos.set_draw_area(omega_min=kwargs['omega_min'], omega_max=kwargs['omega_max'], omega_pitch=kwargs['omega_pitch']) total_dos = tdos.calculate() results.append({'label':'total', 'color': '#000000', 'data': total_dos}) if 'precomputed' in kwargs: partial_doses = [] for k in kwargs['precomputed'].keys(): if k in ['x', 'total']: continue partial_doses.append({ 'label': k, 'data': [[i, kwargs['precomputed'][k][n]] for n, i in enumerate(kwargs['precomputed']['x'])] }) elif 'impacts' in kwargs and 'atomtypes' in kwargs: # get the order of atoms to evaluate their partial impact labels = {} types = [] index, subtractor = 0, 0 for k, atom in enumerate(kwargs['atomtypes']): # determine the order of atoms for the partial impact of every type if atom not in labels: #if atom == 'X' and not calc.phonons: # artificial GHOST case for phonons, decrease atomic index # subtractor += 1 # continue labels[atom] = index types.append([k+1-subtractor]) index += 1 else: types[ labels[atom] ].append(k+1-subtractor) pdos = PartialDos( kwargs['eigenvalues'], kwargs['impacts'], sigma=kwargs['sigma'] ) pdos.set_draw_area(omega_min=kwargs['omega_min'], omega_max=kwargs['omega_max'], omega_pitch=kwargs['omega_pitch']) partial_doses = pdos.calculate( types, labels ) # add colors to partials for i in range(len(partial_doses)): if partial_doses[i]['label'] == 'X': color = '#000000' elif partial_doses[i]['label'] == 'H': color = '#CCCCCC' else: try: color = jmol_to_hex( jmol_colors[ chemical_symbols.index(partial_doses[i]['label']) ] ) except ValueError: color = '#FFCC66' partial_doses[i].update({'color': color}) results.extend(partial_doses) return results
def save(self, calc, session): ''' Saves tilde_obj into the database NB: this is the PUBLIC method @returns checksum, error ''' checksum = calc.get_checksum() try: existing_calc = session.query(model.Calculation).filter(model.Calculation.checksum == checksum).one() except NoResultFound: pass else: del calc return None, "This calculation already exists!" if not calc.download_size: for f in calc.related_files: calc.download_size += os.stat(f).st_size ormcalc = model.Calculation(checksum = checksum) if calc._calcset: ormcalc.meta_data = model.Metadata(chemical_formula = calc.info['standard'], download_size = calc.download_size) for child in session.query(model.Calculation).filter(model.Calculation.checksum.in_(calc._calcset)).all(): ormcalc.children.append(child) ormcalc.siblings_count = len(ormcalc.children) ormcalc.nested_depth = calc._nested_depth else: # prepare phonon data for saving # this is actually a dict to list conversion TODO re-structure this if calc.phonons['modes']: phonons_json = [] for bzpoint, frqset in calc.phonons['modes'].items(): # re-orientate eigenvectors for i in range(0, len(calc.phonons['ph_eigvecs'][bzpoint])): for j in range(0, len(calc.phonons['ph_eigvecs'][bzpoint][i])//3): eigv = array([calc.phonons['ph_eigvecs'][bzpoint][i][j*3], calc.phonons['ph_eigvecs'][bzpoint][i][j*3+1], calc.phonons['ph_eigvecs'][bzpoint][i][j*3+2]]) R = dot( eigv, calc.structures[-1].cell ).tolist() calc.phonons['ph_eigvecs'][bzpoint][i][j*3], calc.phonons['ph_eigvecs'][bzpoint][i][j*3+1], calc.phonons['ph_eigvecs'][bzpoint][i][j*3+2] = [round(x, 3) for x in R] try: irreps = calc.phonons['irreps'][bzpoint] except KeyError: empty = [] for i in range(len(frqset)): empty.append('') irreps = empty phonons_json.append({ 'bzpoint':bzpoint, 'freqs':frqset, 'irreps':irreps, 'ph_eigvecs':calc.phonons['ph_eigvecs'][bzpoint] }) if bzpoint == '0 0 0': phonons_json[-1]['ir_active'] = calc.phonons['ir_active'] phonons_json[-1]['raman_active'] = calc.phonons['raman_active'] if calc.phonons['ph_k_degeneracy']: phonons_json[-1]['ph_k_degeneracy'] = calc.phonons['ph_k_degeneracy'][bzpoint] ormcalc.phonons = model.Phonons() ormcalc.spectra.append( model.Spectra(kind = model.Spectra.PHONON, eigenvalues = json.dumps(phonons_json)) ) # prepare electron data for saving TODO re-structure this for task in ['dos', 'bands']: # projected? if calc.electrons[task]: calc.electrons[task] = calc.electrons[task].todict() if calc.electrons['dos'] or calc.electrons['bands']: ormcalc.electrons = model.Electrons(gap = calc.info['bandgap']) if 'bandgaptype' in calc.info: ormcalc.electrons.is_direct = 1 if calc.info['bandgaptype'] == 'direct' else -1 ormcalc.spectra.append(model.Spectra( kind = model.Spectra.ELECTRON, dos = json.dumps(calc.electrons['dos']), bands = json.dumps(calc.electrons['bands']), projected = json.dumps(calc.electrons['projected']), eigenvalues = json.dumps(calc.electrons['eigvals']) )) # construct ORM for other props calc.related_files = list(map(virtualize_path, calc.related_files)) ormcalc.meta_data = model.Metadata(location = calc.info['location'], finished = calc.info['finished'], raw_input = calc.info['input'], modeling_time = calc.info['duration'], chemical_formula = html_formula(calc.info['standard']), download_size = calc.download_size, filenames = json.dumps(calc.related_files)) codefamily = model.Codefamily.as_unique(session, content = calc.info['framework']) codeversion = model.Codeversion.as_unique(session, content = calc.info['prog']) codeversion.instances.append( ormcalc.meta_data ) codefamily.versions.append( codeversion ) pot = model.Pottype.as_unique(session, name = calc.info['H']) pot.instances.append(ormcalc) ormcalc.recipinteg = model.Recipinteg(kgrid = calc.info['k'], kshift = calc.info['kshift'], smearing = calc.info['smear'], smeartype = calc.info['smeartype']) ormcalc.basis = model.Basis(kind = calc.info['ansatz'], content = json.dumps(calc.electrons['basis_set']) if calc.electrons['basis_set'] else None) ormcalc.energy = model.Energy(convergence = json.dumps(calc.convergence), total = calc.info['energy']) ormcalc.spacegroup = model.Spacegroup(n=calc.info['ng']) ormcalc.struct_ratios = model.Struct_ratios(chemical_formula=calc.info['standard'], formula_units=calc.info['expanded'], nelem=calc.info['nelem'], dimensions=calc.info['dims']) if len(calc.tresholds) > 1: ormcalc.struct_optimisation = model.Struct_optimisation(tresholds=json.dumps(calc.tresholds), ncycles=json.dumps(calc.ncycles)) for n, ase_repr in enumerate(calc.structures): is_final = True if n == len(calc.structures)-1 else False struct = model.Structure(step = n, final = is_final) s = cell_to_cellpar(ase_repr.cell) struct.lattice = model.Lattice(a=s[0], b=s[1], c=s[2], alpha=s[3], beta=s[4], gamma=s[5], a11=ase_repr.cell[0][0], a12=ase_repr.cell[0][1], a13=ase_repr.cell[0][2], a21=ase_repr.cell[1][0], a22=ase_repr.cell[1][1], a23=ase_repr.cell[1][2], a31=ase_repr.cell[2][0], a32=ase_repr.cell[2][1], a33=ase_repr.cell[2][2]) #rmts = ase_repr.get_array('rmts') if 'rmts' in ase_repr.arrays else [None for j in range(len(ase_repr))] charges = ase_repr.get_array('charges') if 'charges' in ase_repr.arrays else [None for j in range(len(ase_repr))] magmoms = ase_repr.get_array('magmoms') if 'magmoms' in ase_repr.arrays else [None for j in range(len(ase_repr))] for n, i in enumerate(ase_repr): struct.atoms.append( model.Atom( number=chemical_symbols.index(i.symbol), x=i.x, y=i.y, z=i.z, charge=charges[n], magmom=magmoms[n] ) ) ormcalc.structures.append(struct) # TODO Forces ormcalc.uigrid = model.Grid(info=json.dumps(calc.info)) # tags ORM uitopics = [] for entity in self.hierarchy: if not entity['creates_topic']: continue if entity['multiple'] or calc._calcset: for item in calc.info.get( entity['source'], [] ): uitopics.append( model.topic(cid=entity['cid'], topic=item) ) else: topic = calc.info.get(entity['source']) if topic or not entity['optional']: uitopics.append( model.topic(cid=entity['cid'], topic=topic) ) uitopics = [model.Topic.as_unique(session, cid=x.cid, topic="%s" % x.topic) for x in uitopics] ormcalc.uitopics.extend(uitopics) if calc._calcset: session.add(ormcalc) else: session.add_all([codefamily, codeversion, pot, ormcalc]) session.commit() del calc, ormcalc return checksum, None