d1[i] = 1 d2 = {} for i in list_b: if i in d2: d2[i] += 1 else: d2[i] = 1 for key, value in d1.items(): if key not in d2 or value > d2[key]: return False return True s1 = Structure.from_file('POSCAR.orig') s2 = Structure.from_file('CONTCAR') spacegroup1 = SpacegroupAnalyzer(s1) spacegroup2 = SpacegroupAnalyzer(s2) print('space group symbol of structure1:', spacegroup1.get_space_group_symbol()) print('space group number of structure1:', spacegroup1.get_space_group_number()) print('space group symbol of structure2:', spacegroup2.get_space_group_symbol()) print('space group number of structure2:', spacegroup2.get_space_group_number()) Voronoi = VoronoiCoordFinder(s2, target=None) site = s2.num_sites print("s2[0]:", s2[0]) polyhedra = Voronoi.get_voronoi_polyhedra(1) coordinate = Voronoi.get_coordination_number(1) coordinate_sites = Voronoi.get_coordinated_sites(1)
from pymatgen.symmetry.analyzer import SpacegroupAnalyzer from ase.io import read from pymatgen.io.ase import AseAtomsAdaptor s_ase = read("OUTCAR", format = "vasp-out") s_pmg = AseAtomsAdaptor().get_structure(s_ase) space_group_number = SpacegroupAnalyzer(s_pmg).get_space_group_number() space_group_symbol = SpacegroupAnalyzer(s_pmg).get_space_group_symbol() print("{} ({})".format(space_group_symbol, space_group_number))
def get_plot_2d(self, structure: Structure) -> go.Figure: """ Generates the 2D diffraction pattern of the input structure. Args: structure (Structure): The input structure. Returns: Figure """ if self.symprec: finder = SpacegroupAnalyzer(structure, symprec=self.symprec) structure = finder.get_refined_structure() points = self.generate_points(-10, 11) tem_dots = self.tem_dots(structure, points) xs = [] ys = [] hkls = [] intensities = [] for dot in tem_dots: xs.append(dot.position[0]) ys.append(dot.position[1]) hkls.append(str(dot.hkl)) intensities.append(dot.intensity) hkls = list(map(unicodeify_spacegroup, list(map(latexify_spacegroup, hkls)))) data = [ go.Scatter( x=xs, y=ys, text=hkls, hoverinfo="text", mode="markers", marker=dict( size=8, cmax=1, cmin=0, color=intensities, colorscale=[[0, "black"], [1.0, "white"]], ), showlegend=False, ), go.Scatter( x=[0], y=[0], text="(0, 0, 0): Direct beam", hoverinfo="text", mode="markers", marker=dict(size=14, cmax=1, cmin=0, color="white"), showlegend=False, ), ] layout = go.Layout( title="2D Diffraction Pattern<br>Beam Direction: " + "".join(str(e) for e in self.beam_direction), font=dict(size=14, color="#7f7f7f"), hovermode="closest", xaxis=dict( range=[-4, 4], showgrid=False, zeroline=False, showline=False, ticks="", showticklabels=False, ), yaxis=dict( range=[-4, 4], showgrid=False, zeroline=False, showline=False, ticks="", showticklabels=False, ), width=550, height=550, paper_bgcolor="rgba(100,110,110,0.5)", plot_bgcolor="black", ) fig = go.Figure(data=data, layout=layout) return fig
def __init__( self, structures: Dict[str, Structure], symprec: Optional[float] = 0.01, perturb_sigma: Optional[float] = None, remove_oxidation_states: bool = True, reciprocal_coordination: bool = True, ): # make a deep copy to avoid modifying structures in place self.structures = deepcopy(structures) self.symprec = symprec self.reciprocal_coordination = reciprocal_coordination # use this to cache benchmark results self._benchmark: Dict[NearNeighbors, Dict[str, List]] = defaultdict(dict) for name, structure in structures.items(): if "coordination" not in structure.site_properties: raise AttributeError( "{} structure does not have the 'coordination' site " "property".format(name) ) if perturb_sigma: for name, structure in self.structures.items(): self.structures[name] = perturb_einstein_crystal( structure, perturb_sigma ) # precompute the symmetrized structures to save time during the benchmark. Also, # determine the total number of unique cations/anions each structure. self.site_information: Dict[str, Dict[str, Any]] = {} self.max_nsites = 0 n_structures_with_oxi = 0 for name, structure in self.structures.items(): if self.symprec: sga = SpacegroupAnalyzer(structure, symprec=self.symprec) equiv_sites = sga.get_symmetrized_structure().equivalent_sites unique_site_idxs = np.unique( sga.get_symmetry_dataset()["equivalent_atoms"] ) else: equiv_sites = [[s] for s in structure] unique_site_idxs = list(range(len(structure))) # cation_idxs and anion_idxs are the indexes of the cations/anions # in the list of unique sites (not the list of ALL sites). cation_degens = [] cation_idxs = [] anion_degens = [] anion_idxs = [] cations = set() anions = set() for i, sites in enumerate(equiv_sites): # Specie check needed to see if site has an oxidation state at all. if ( isinstance(sites[0].specie, Specie) and sites[0].specie.oxi_state >= 0 ): # based on previous implementation, neutral ions will be scored as # cations, however, neutral to neutral bonding is allowed, hence # don't add the element to the cations set as this will be used to # prevent cation-cation bonding later on. cation_degens.append(len(sites)) cation_idxs.append(i) if sites[0].specie.oxi_state > 0: cations.add(sites[0].specie.name) elif ( isinstance(sites[0].specie, Specie) and sites[0].specie.oxi_state < 0 ): anion_degens.append(len(sites)) anion_idxs.append(i) anions.add(sites[0].specie.name) all_degens = [len(x) for x in equiv_sites] total_all = sum(all_degens) total_cations = sum(cation_degens) total_anions = sum(anion_degens) self.site_information[name] = { "unique_idxs": unique_site_idxs, "all_idxs": list(range(len(unique_site_idxs))), "cation_idxs": cation_idxs, "anion_idxs": anion_idxs, "all_degens": all_degens, "cation_degens": cation_degens, "anion_degens": anion_degens, "all_total": total_all, "cation_total": total_cations, "anion_total": total_anions, "cations": cations, "anions": anions, } self.max_nsites = max(self.max_nsites, len(unique_site_idxs)) if cation_idxs or anion_idxs: n_structures_with_oxi += 1 # useful to know if all structures have oxidation states when calculating scores self.all_structures_have_oxi = n_structures_with_oxi == len(structures) if remove_oxidation_states: for structure in self.structures.values(): structure.remove_oxidation_states()
def calc_shiftk(structure, symprec=0.01, angle_tolerance=5): """ Find the values of ``shiftk`` and ``nshiftk`` appropriated for the sampling of the Brillouin zone. When the primitive vectors of the lattice do NOT form a FCC or a BCC lattice, the usual (shifted) Monkhorst-Pack grids are formed by using nshiftk=1 and shiftk 0.5 0.5 0.5 . This is often the preferred k point sampling. For a non-shifted Monkhorst-Pack grid, use `nshiftk=1` and `shiftk 0.0 0.0 0.0`, but there is little reason to do that. When the primitive vectors of the lattice form a FCC lattice, with rprim:: 0.0 0.5 0.5 0.5 0.0 0.5 0.5 0.5 0.0 the (very efficient) usual Monkhorst-Pack sampling will be generated by using nshiftk= 4 and shiftk:: 0.5 0.5 0.5 0.5 0.0 0.0 0.0 0.5 0.0 0.0 0.0 0.5 When the primitive vectors of the lattice form a BCC lattice, with rprim:: -0.5 0.5 0.5 0.5 -0.5 0.5 0.5 0.5 -0.5 the usual Monkhorst-Pack sampling will be generated by using nshiftk= 2 and shiftk:: 0.25 0.25 0.25 -0.25 -0.25 -0.25 However, the simple sampling nshiftk=1 and shiftk 0.5 0.5 0.5 is excellent. For hexagonal lattices with hexagonal axes, e.g. rprim:: 1.0 0.0 0.0 -0.5 sqrt(3)/2 0.0 0.0 0.0 1.0 one can use nshiftk= 1 and shiftk 0.0 0.0 0.5 In rhombohedral axes, e.g. using angdeg 3*60., this corresponds to shiftk 0.5 0.5 0.5, to keep the shift along the symmetry axis. Returns: Suggested value of shiftk. """ # Find lattice type. from pymatgen.symmetry.analyzer import SpacegroupAnalyzer sym = SpacegroupAnalyzer(structure, symprec=symprec, angle_tolerance=angle_tolerance) lattice_type, spg_symbol = sym.get_lattice_type( ), sym.get_space_group_symbol() # Check if the cell is primitive is_primitive = len(sym.find_primitive()) == len(structure) # Generate the appropriate set of shifts. shiftk = None if is_primitive: if lattice_type == "cubic": if "F" in spg_symbol: # FCC shiftk = [ 0.5, 0.5, 0.5, 0.5, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.5 ] elif "I" in spg_symbol: # BCC shiftk = [0.25, 0.25, 0.25, -0.25, -0.25, -0.25] # shiftk = [0.5, 0.5, 05]) elif lattice_type == "hexagonal": # Find the hexagonal axis and set the shift along it. for i, angle in enumerate(structure.lattice.angles): if abs(angle - 120) < 1.0: j = (i + 1) % 3 k = (i + 2) % 3 hex_ax = [ax for ax in range(3) if ax not in [j, k]][0] break else: raise ValueError("Cannot find hexagonal axis") shiftk = [0.0, 0.0, 0.0] shiftk[hex_ax] = 0.5 elif lattice_type == "tetragonal": if "I" in spg_symbol: # BCT shiftk = [0.25, 0.25, 0.25, -0.25, -0.25, -0.25] if shiftk is None: # Use default value. shiftk = [0.5, 0.5, 0.5] return np.reshape(shiftk, (-1, 3))
def structure_lines( structure, cell_flg=True, frac_flg=True, anion_shell_flg=True, cation_shell_flg=False, symm_flg=True, ): """ Generates GULP input string corresponding to pymatgen structure. Args: structure: pymatgen Structure object cell_flg (default = True): Option to use lattice parameters. fractional_flg (default = True): If True, fractional coordinates are used. Else, cartesian coodinates in Angstroms are used. ****** GULP convention is to use fractional coordinates for periodic structures and cartesian coordinates for non-periodic structures. ****** anion_shell_flg (default = True): If True, anions are considered polarizable. cation_shell_flg (default = False): If True, cations are considered polarizable. symm_flg (default = True): If True, symmetry information is also written. Returns: string containing structure for GULP input """ gin = "" if cell_flg: gin += "cell\n" l = structure.lattice lat_str = "{0:6f} {1:6f} {2:6f} {3:6f} {4:6f} {5:6f}".format( l.a, l.b, l.c, l.alpha, l.beta, l.gamma) gin += lat_str + "\n" if frac_flg: gin += "frac\n" coord_attr = "frac_coords" else: gin += "cart\n" coord_attr = "coords" for site in structure.sites: coord = [str(i) for i in getattr(site, coord_attr)] specie = site.specie core_site_desc = specie.symbol + " core " + " ".join(coord) + "\n" gin += core_site_desc if (specie in _anions and anion_shell_flg) or (specie in _cations and cation_shell_flg): shel_site_desc = specie.symbol + " shel " + " ".join( coord) + "\n" gin += shel_site_desc else: pass if symm_flg: gin += "space\n" gin += str( SpacegroupAnalyzer(structure).get_space_group_number()) + "\n" return gin
def populate(self, structure, prec=1e-5, maxiter=200, verbose=False, precond=True, vsym=True): """ Takes a partially populated tensor, and populates the non-zero entries according to the following procedure, iterated until the desired convergence (specified via prec) is achieved. 1. Find non-zero entries 2. Symmetrize the tensor with respect to crystal symmetry and (optionally) voigt symmetry 3. Reset the non-zero entries of the original tensor Args: structure (structure object) prec (float): precision for determining a non-zero value maxiter (int): maximum iterations for populating the tensor verbose (bool): whether to populate verbosely precond (bool): whether to precondition by cycling through all symmops and storing new nonzero values, default True vsym (bool): whether to enforce voigt symmetry, defaults to True """ if precond: # Generate the guess from populated sops = SpacegroupAnalyzer(structure).get_symmetry_operations() guess = Tensor(np.zeros(self.shape)) mask = abs(self) > prec guess[mask] = self[mask] def merge(old, new): gmask = np.abs(old) > prec nmask = np.abs(new) > prec new_mask = np.logical_not(gmask)*nmask avg_mask = gmask*nmask old[avg_mask] = (old[avg_mask] + new[avg_mask]) / 2. old[new_mask] = new[new_mask] if verbose: print("Preconditioning for {} symmops".format(len(sops))) for sop in sops: rot = guess.transform(sop) # Store non-zero entries of new that weren't previously # in the guess in the guess merge(guess, rot) if verbose: print("Preconditioning for voigt symmetry".format(len(sops))) if vsym: v = guess.voigt perms = list(itertools.permutations(range(len(v.shape)))) for perm in perms: vtrans = np.transpose(v, perm) merge(v, vtrans) guess = Tensor.from_voigt(v) else: guess = np.zeros(self.shape) assert guess.shape == self.shape, "Guess must have same shape" converged = False test_new, test_old = [guess.copy()]*2 for i in range(maxiter): test_new = test_old.fit_to_structure(structure) if vsym: test_new = test_new.voigt_symmetrized diff = np.abs(test_old - test_new) converged = (diff < prec).all() if converged: break test_new[mask] = self[mask] test_old = test_new if verbose: print("Iteration {}: {}".format(i, np.max(diff))) if not converged: max_diff = np.max(np.abs(self - test_new)) warnings.warn("Warning, populated tensor is not converged " "with max diff of {}".format(max_diff)) return self.__class__(test_new)
def apply_transformation(self, structure, return_ranked_list=False): """ Return either a single ordered structure or a sequence of all ordered structures. Args: structure: Structure to order. return_ranked_list (bool): Whether or not multiple structures are returned. If return_ranked_list is a number, that number of structures is returned. Returns: Depending on returned_ranked list, either a transformed structure or a list of dictionaries, where each dictionary is of the form {"structure" = .... , "other_arguments"} The list of ordered structures is ranked by ewald energy / atom, if the input structure is an oxidation state decorated structure. Otherwise, it is ranked by number of sites, with smallest number of sites first. """ try: num_to_return = int(return_ranked_list) except ValueError: num_to_return = 1 if self.refine_structure: finder = SpacegroupAnalyzer(structure, self.symm_prec) structure = finder.get_refined_structure() contains_oxidation_state = all( [hasattr(sp, "oxi_state") and sp.oxi_state != 0 for sp in structure.composition.elements] ) structures = None if structure.is_ordered: warn("Enumeration skipped for structure with composition {} " "because it is ordered".format(structure.composition)) structures = [structure.copy()] if self.max_disordered_sites: ndisordered = sum([1 for site in structure if not site.is_ordered]) if ndisordered > self.max_disordered_sites: raise ValueError( "Too many disordered sites! ({} > {})".format( ndisordered, self.max_disordered_sites)) max_cell_sizes = range(self.min_cell_size, int( math.floor(self.max_disordered_sites / ndisordered)) + 1) else: max_cell_sizes = [self.max_cell_size] for max_cell_size in max_cell_sizes: adaptor = EnumlibAdaptor( structure, min_cell_size=self.min_cell_size, max_cell_size=max_cell_size, symm_prec=self.symm_prec, refine_structure=False, enum_precision_parameter=self.enum_precision_parameter, check_ordered_symmetry=self.check_ordered_symmetry) try: adaptor.run() except EnumError: warn("Unable to enumerate for max_cell_size = %d".format( max_cell_size)) structures = adaptor.structures if structures: break if structures is None: raise ValueError("Unable to enumerate") original_latt = structure.lattice inv_latt = np.linalg.inv(original_latt.matrix) ewald_matrices = {} all_structures = [] for s in structures: new_latt = s.lattice transformation = np.dot(new_latt.matrix, inv_latt) transformation = tuple([tuple([int(round(cell)) for cell in row]) for row in transformation]) if contains_oxidation_state and self.sort_criteria == "ewald": if transformation not in ewald_matrices: s_supercell = structure * transformation ewald = EwaldSummation(s_supercell) ewald_matrices[transformation] = ewald else: ewald = ewald_matrices[transformation] energy = ewald.compute_sub_structure(s) all_structures.append({"num_sites": len(s), "energy": energy, "structure": s}) else: all_structures.append({"num_sites": len(s), "structure": s}) def sort_func(s): return s["energy"] / s["num_sites"] \ if contains_oxidation_state and self.sort_criteria == "ewald" \ else s["num_sites"] self._all_structures = sorted(all_structures, key=sort_func) if return_ranked_list: return self._all_structures[0:num_to_return] else: return self._all_structures[0]["structure"]
def apply_transformation(self, structure, return_ranked_list=False): """ Apply MagOrderTransformation to an input structure. :param structure: Any ordered structure. :param return_ranked_list: As in other Transformations. :return: """ if not structure.is_ordered: raise ValueError("Create an ordered approximation of " "your input structure first.") # retrieve order parameters order_parameters = [MagOrderParameterConstraint.from_dict(op_dict) for op_dict in self.order_parameter] # add dummy species on which to perform enumeration structure = self._add_dummy_species(structure, order_parameters) # trivial case if structure.is_ordered: structure = self._remove_dummy_species(structure) return [structure] if return_ranked_list > 1 else structure enum_kwargs = self.enum_kwargs.copy() enum_kwargs["min_cell_size"] = max( int(self.determine_min_cell(structure)), enum_kwargs.get("min_cell_size", 1) ) max_cell = enum_kwargs.get('max_cell_size') if max_cell: if enum_kwargs["min_cell_size"] > max_cell: raise ValueError('Specified max cell size is smaller' ' than the minimum enumerable cell size') else: enum_kwargs["max_cell_size"] = enum_kwargs["min_cell_size"] t = EnumerateStructureTransformation(**enum_kwargs) alls = t.apply_transformation(structure, return_ranked_list=return_ranked_list) # handle the fact that EnumerateStructureTransformation can either # return a single Structure or a list if isinstance(alls, Structure): # remove dummy species and replace Spin.up or Spin.down # with spin magnitudes given in mag_species_spin arg alls = self._remove_dummy_species(alls) alls = self._add_spin_magnitudes(alls) else: for idx, _ in enumerate(alls): alls[idx]["structure"] = self._remove_dummy_species(alls[idx]["structure"]) alls[idx]["structure"] = self._add_spin_magnitudes(alls[idx]["structure"]) try: num_to_return = int(return_ranked_list) except ValueError: num_to_return = 1 if num_to_return == 1 or not return_ranked_list: return alls[0]["structure"] if num_to_return else alls # remove duplicate structures and group according to energy model m = StructureMatcher(comparator=SpinComparator()) key = lambda x: SpacegroupAnalyzer(x, 0.1).get_space_group_number() out = [] for _, g in groupby(sorted([d["structure"] for d in alls], key=key), key): g = list(g) grouped = m.group_structures(g) out.extend([{"structure": g[0], "energy": self.energy_model.get_energy(g[0])} for g in grouped]) self._all_structures = sorted(out, key=lambda d: d["energy"]) return self._all_structures[0:num_to_return]
print("there are {} folders".format(N)) df_100 = df.sort_values(by=['E_eV_atom'])[:N] df_100['Spg_num_opt'] = np.nan df_100['Spg_sym_opt'] = np.nan df_100['Entropy_eV_cell'] = np.nan print(df.head(10)) # read the CONTCAR in the folders 1 to 3 # and extract the enthalpy and entropy from the OUTCAR/OSZICAR files for dir_name in range(1, N + 1): dir_name = str(dir_name) opt_struct = Structure.from_file(dir_name + '/' + "CONTCAR") # analyze the space group using SpacegroupAnalyzer, if RuntimeError or TypeError happens, raise the error try: spg_sym_opt = SpacegroupAnalyzer(opt_struct, symprec=.1).get_space_group_symbol() except RuntimeError: print("RuntimError: Space group analysis failed for structure " + dir_name) spg_sym_opt = "None" continue except TypeError: print("TypeError: Space group analysis failed for structure " + dir_name) spg_sym_opt = "None" continue # save spg_sym_opt to the column Spg_sym_opt in the df_100 # df_100.loc[df_100['preliminary_order'] == dir_name, 'Spg_sym_opt'] = spg_sym_opt df_100.loc[df_100['preliminary_order'] == float(dir_name), 'Spg_sym_opt'] = spg_sym_opt print("*****")
with open('bulk_modeling.log', 'w') as f: start_time = time.time() num_ini = 239 num_fin = 243 for i in range(num_ini, num_fin): mp_id = sorted_entries[i]['material_id'] formula = sorted_entries[i]['pretty_formula'] struc = sorted_entries[i]['structure'] INCAR['SYSTEM'] = formula createFolder('%03d_%s' % (i + 1.0, formula)) # getting conventional unit cell sga = SpacegroupAnalyzer(struc) conv_struc = sga.get_conventional_standard_structure() conv_struc.to(filename="%03d_%s/POSCAR" % (i + 1.0, formula)) # MP_incar load mp_incar = sorted_entries[i]['input.incar'] # MAGMOM setting using Exception cases alkali_element = [x for x in alkali_elements if x in formula] if alkali_element is not None: INCAR['MAGMOM'] = [0.6, 5.0, 0.6, 0.6, 0.6] else: INCAR['MAGMOM'] = [5.0, 5.0, 0.6, 0.6, 0.6] # Hubbard U setting try:
def generate_doc(self, dir_name, vasprun_files): """ Overridden """ try: fullpath = os.path.abspath(dir_name) d = {k: v for k, v in self.additional_fields.items()} d["dir_name"] = fullpath d["schema_version"] = VaspToDbTaskDrone.__version__ d["calculations"] = [ self.process_vasprun(dir_name, taskname, filename) for taskname, filename in vasprun_files.items() ] d1 = d["calculations"][0] d2 = d["calculations"][-1] # Now map some useful info to the root level. for root_key in [ "completed_at", "nsites", "unit_cell_formula", "reduced_cell_formula", "pretty_formula", "elements", "nelements", "cif", "density", "is_hubbard", "hubbards", "run_type" ]: d[root_key] = d2[root_key] d["chemsys"] = "-".join(sorted(d2["elements"])) # store any overrides to the exchange correlation functional xc = d2["input"]["incar"].get("GGA") if xc: xc = xc.upper() d["input"] = { "crystal": d1["input"]["crystal"], "is_lasph": d2["input"]["incar"].get("LASPH", False), "potcar_spec": d1["input"].get("potcar_spec"), "xc_override": xc } vals = sorted(d2["reduced_cell_formula"].values()) d["anonymous_formula"] = { string.ascii_uppercase[i]: float(vals[i]) for i in range(len(vals)) } d["output"] = { "crystal": d2["output"]["crystal"], "final_energy": d2["output"]["final_energy"], "final_energy_per_atom": d2["output"]["final_energy_per_atom"] } d["name"] = "vasp" p = d2["input"]["potcar_type"][0].split("_") pot_type = p[0] functional = "lda" if len(pot_type) == 1 else "_".join(p[1:]) d["pseudo_potential"] = { "functional": functional.lower(), "pot_type": pot_type.lower(), "labels": d2["input"]["potcar"] } if len(d["calculations"]) == len( self.runs) or list(vasprun_files.keys())[0] != "relax1": d["state"] = "successful" if d2["has_vasp_completed"] \ else "unsuccessful" else: d["state"] = "stopped" d["analysis"] = analysis_and_error_checks(d) sg = SpacegroupAnalyzer( Structure.from_dict(d["output"]["crystal"]), 0.1) d["spacegroup"] = { "symbol": sg.get_spacegroup_symbol(), "number": sg.get_spacegroup_number(), "point_group": sg.get_point_group(), "source": "spglib", "crystal_system": sg.get_crystal_system(), "hall": sg.get_hall() } d["last_updated"] = datetime.datetime.today() return d except Exception as ex: import traceback print(traceback.format_exc()) logger.error("Error in " + os.path.abspath(dir_name) + ".\n" + traceback.format_exc()) return None
def test_symmetrization(self): # Restricted to primitive_elemental materials due to the risk of # broken stoichiometry. For compound materials, use is_polar() # Get all slabs for P6/mmm Ti and Fm-3m Ag up to index of 2 all_Ti_slabs = generate_all_slabs( self.ti, 2, 10, 10, bonds=None, tol=1e-3, max_broken_bonds=0, lll_reduce=False, center_slab=False, primitive=True, max_normal_search=2, symmetrize=True, ) all_Ag_fcc_slabs = generate_all_slabs( self.agfcc, 2, 10, 10, bonds=None, tol=1e-3, max_broken_bonds=0, lll_reduce=False, center_slab=False, primitive=True, max_normal_search=2, symmetrize=True, ) all_slabs = [all_Ti_slabs, all_Ag_fcc_slabs] for i, slabs in enumerate(all_slabs): assymetric_count = 0 symmetric_count = 0 for i, slab in enumerate(slabs): sg = SpacegroupAnalyzer(slab) # Check if a slab is symmetric if not sg.is_laue(): assymetric_count += 1 else: symmetric_count += 1 # Check if slabs are all symmetric self.assertEqual(assymetric_count, 0) self.assertEqual(symmetric_count, len(slabs)) # Check if we can generate symmetric slabs from bulk with no inversion all_non_laue_slabs = generate_all_slabs(self.nonlaue, 1, 15, 15, symmetrize=True) self.assertTrue(len(all_non_laue_slabs) > 0)
def __init__(self, structure, symprec=0.01, angle_tolerance=5, atol=1e-8): """ Args: structure (Structure): Structure object symprec (float): Tolerance for symmetry finding angle_tolerance (float): Angle tolerance for symmetry finding. atol (float): Absolute tolerance used to compare the input structure with the one expected as primitive standard. A warning will be issued if the lattices don't match. """ self._structure = structure self._sym = SpacegroupAnalyzer(structure, symprec=symprec, angle_tolerance=angle_tolerance) self._prim = self._sym \ .get_primitive_standard_structure(international_monoclinic=False) self._conv = self._sym.get_conventional_standard_structure(international_monoclinic=False) self._prim_rec = self._prim.lattice.reciprocal_lattice self._kpath = None # Note: this warning will be issued for space groups 38-41, since the primitive cell must be # reformatted to match Setyawan/Curtarolo convention in order to work with the current k-path # generation scheme. if not np.allclose(self._structure.lattice.matrix, self._prim.lattice.matrix, atol=atol): warnings.warn("The input structure does not match the expected standard primitive! " "The path can be incorrect. Use at your own risk.") lattice_type = self._sym.get_lattice_type() spg_symbol = self._sym.get_space_group_symbol() if lattice_type == "cubic": if "P" in spg_symbol: self._kpath = self.cubic() elif "F" in spg_symbol: self._kpath = self.fcc() elif "I" in spg_symbol: self._kpath = self.bcc() else: warn("Unexpected value for spg_symbol: %s" % spg_symbol) elif lattice_type == "tetragonal": if "P" in spg_symbol: self._kpath = self.tet() elif "I" in spg_symbol: a = self._conv.lattice.abc[0] c = self._conv.lattice.abc[2] if c < a: self._kpath = self.bctet1(c, a) else: self._kpath = self.bctet2(c, a) else: warn("Unexpected value for spg_symbol: %s" % spg_symbol) elif lattice_type == "orthorhombic": a = self._conv.lattice.abc[0] b = self._conv.lattice.abc[1] c = self._conv.lattice.abc[2] if "P" in spg_symbol: self._kpath = self.orc() elif "F" in spg_symbol: if 1 / a ** 2 > 1 / b ** 2 + 1 / c ** 2: self._kpath = self.orcf1(a, b, c) elif 1 / a ** 2 < 1 / b ** 2 + 1 / c ** 2: self._kpath = self.orcf2(a, b, c) else: self._kpath = self.orcf3(a, b, c) elif "I" in spg_symbol: self._kpath = self.orci(a, b, c) elif "C" in spg_symbol or "A" in spg_symbol: self._kpath = self.orcc(a, b, c) else: warn("Unexpected value for spg_symbol: %s" % spg_symbol) elif lattice_type == "hexagonal": self._kpath = self.hex() elif lattice_type == "rhombohedral": alpha = self._prim.lattice.angles[0] if alpha < 90: self._kpath = self.rhl1(alpha * pi / 180) else: self._kpath = self.rhl2(alpha * pi / 180) elif lattice_type == "monoclinic": a, b, c = self._conv.lattice.abc alpha = self._conv.lattice.angles[0] if "P" in spg_symbol: self._kpath = self.mcl(b, c, alpha * pi / 180) elif "C" in spg_symbol: kgamma = self._prim_rec.angles[2] if kgamma > 90: self._kpath = self.mclc1(a, b, c, alpha * pi / 180) if kgamma == 90: self._kpath = self.mclc2(a, b, c, alpha * pi / 180) if kgamma < 90: if b * cos(alpha * pi / 180) / c \ + b ** 2 * sin(alpha * pi / 180) ** 2 / a ** 2 < 1: self._kpath = self.mclc3(a, b, c, alpha * pi / 180) if b * cos(alpha * pi / 180) / c \ + b ** 2 * sin(alpha * pi / 180) ** 2 / a ** 2 == 1: self._kpath = self.mclc4(a, b, c, alpha * pi / 180) if b * cos(alpha * pi / 180) / c \ + b ** 2 * sin(alpha * pi / 180) ** 2 / a ** 2 > 1: self._kpath = self.mclc5(a, b, c, alpha * pi / 180) else: warn("Unexpected value for spg_symbol: %s" % spg_symbol) elif lattice_type == "triclinic": kalpha = self._prim_rec.angles[0] kbeta = self._prim_rec.angles[1] kgamma = self._prim_rec.angles[2] if kalpha > 90 and kbeta > 90 and kgamma > 90: self._kpath = self.tria() if kalpha < 90 and kbeta < 90 and kgamma < 90: self._kpath = self.trib() if kalpha > 90 and kbeta > 90 and kgamma == 90: self._kpath = self.tria() if kalpha < 90 and kbeta < 90 and kgamma == 90: self._kpath = self.trib() else: warn("Unknown lattice type %s" % lattice_type)
def get_bs_extrema(bs, coeff_file, nk_ibz=17, v_cut=1e4, min_normdiff=0.05, Ecut=None, nex_max=0, return_global=False, niter=10): """ returns a dictionary of p-type (valence) and n-type (conduction) band extrema k-points by looking at the 1st and 2nd derivatives of the bands Args: bs (pymatgen BandStructure object): must containt Structure and have the same number of valence electrons and settings as the vasprun.xml from which coeff_file is generated. coeff_file (str): path to the cube file from BoltzTraP run nk_ibz (int): maximum number of k-points in one direction in IBZ v_cut (float): threshold under which the derivative is assumed 0 [cm/s] min_normdiff (float): the minimum allowed distance norm(fractional k) in extrema; this is important to avoid numerical instability errors Ecut (float or dict): max energy difference with CBM/VBM allowed for extrema nex_max (int): max number of low-velocity kpts tested for being extrema return_global (bool): in addition to the extrema, return the actual CBM (global minimum) and VBM (global maximum) w/ their k-point niter (int): number of iterations in basinhoopping for finding the global extremum Returns (dict): {'n': list of extrema fractional coordinates, 'p': same} """ #TODO: MAJOR cleanup needed in this function; also look into parallelizing get_analytical_energy at all kpts if it's time consuming #TODO: if decided to only include one of many symmetrically equivalent extrema, write a method to keep only one of symmetrically equivalent extrema as a representative Ecut = Ecut or 10 * k_B * 300 if not isinstance(Ecut, dict): Ecut = {'n': Ecut, 'p': Ecut} actual_cbm_vbm = {'n': {}, 'p': {}} vbm_idx, _ = get_bindex_bspin(bs.get_vbm(), is_cbm=False) # vbm_idx = bs.get_vbm()['band_index'][Spin.up][0] ibands = [1, 2] # in this notation, 1 is the last valence band ibands = [i + vbm_idx for i in ibands] ibz = HighSymmKpath(bs.structure) sg = SpacegroupAnalyzer(bs.structure) kmesh = sg.get_ir_reciprocal_mesh(mesh=(nk_ibz, nk_ibz, nk_ibz)) kpts = [k_n_w[0] for k_n_w in kmesh] kpts.extend( insert_intermediate_kpoints(ibz.kpath['kpoints'].values(), n=10)) # grid = {'energy': [], 'velocity': [], 'mass': [], 'normv': []} extrema = {'n': [], 'p': []} engre, nwave, nsym, nstv, vec, vec2, out_vec2, br_dir = get_energy_args( coeff_file=coeff_file, ibands=ibands) cbmk = np.array(bs.get_cbm()['kpoint'].frac_coords) vbmk = np.array(bs.get_cbm()['kpoint'].frac_coords) bounds = [(-0.5, 0.5), (-0.5, 0.5), (-0.5, 0.5)] func = lambda x: calc_analytical_energy(x, engre[1], nwave, nsym, nstv, vec, vec2, out_vec2, br_dir, sgn=-1, scissor=0)[0] opt = basinhopping(func, x0=cbmk, niter=20, T=0.1, minimizer_kwargs={'bounds': bounds}) kpts.append(opt.x) func = lambda x: -calc_analytical_energy(x, engre[0], nwave, nsym, nstv, vec, vec2, out_vec2, br_dir, sgn=+1, scissor=0)[0] opt = basinhopping(func, x0=vbmk, niter=niter, T=0.1, minimizer_kwargs={'bounds': bounds}) kpts.append(opt.x) for iband in range(len(ibands)): is_cb = [False, True][iband] tp = ['p', 'n'][iband] energies = [] velocities = [] normv = [] masses = [] ###################################################################### # kpts_list = [np.array([ 0., 0., 0.]), # [-0.5, -0.5, -0.5], # [-0.5, 0.0, 0.0], # [0., -0.5, 0.], # [0., 0., -0.5], # [0.5, 0.5, 0.5], # [0.5, 0.0, 0.0], # [0., 0.5, 0.], # [0., 0., 0.5], # np.array([0., 0., 0.]), # [ 0.5, 0., 0.5], # [0., -0.5, -0.5], # [0.5 , 0.5 , 0.], # [-0.5, 0., -0.5], # [0., 0.5, 0.5], # [-0.5, -0.5, 0.] # ] # kpts_list = [ [0.0, 0.0, 0.0], # [ 0. , 0.44, 0.44], # [ 0.44, 0.44 ,0. ], # [ 0. , -0.44, -0.44], # [-0.44 ,-0.44, 0. ], # [ 0.44 , 0. , 0.44], # [-0.44 , 0. , -0.44], # [ 0. , -0.44 ,-0.44], # [-0.44 ,-0.44 , 0. ], # [ 0. , 0.44 , 0.44], # [ 0.44, 0.44 , 0. ], # [-0.44 , 0. , -0.44], # [ 0.44 , 0. , 0.44] , # [ 0.5, 0. , 0.5], # [ 0. , 0.5, 0.5], # [ 0.5 , 0.5, 0. ], # [-0.5 , 0. ,-0.5], # [ 0. , -0.5 ,-0.5], # [-0.5 ,-0.5 , 0. ] ] # print('here kpts_list:') # print(kpts_list) # print('here the energies') # for ik, kpt in enumerate(kpts_list): # en, v, mass = calc_analytical_energy(kpt, engre[iband], nwave, # nsym, nstv, vec, vec2, out_vec2, br_dir, sgn=1, scissor=0) # print en ###################################################################### for ik, kpt in enumerate(kpts): en, v, mass = calc_analytical_energy(kpt, engre[iband], nwave, nsym, nstv, vec, vec2, out_vec2, br_dir, sgn=1, scissor=0) energies.append(en) velocities.append(abs(v)) normv.append(norm(v)) masses.append(mass.trace() / 3) indexes = np.argsort(normv) energies = [energies[i] for i in indexes] normv = [normv[i] for i in indexes] velocities = [velocities[i] for i in indexes] masses = [masses[i] for i in indexes] kpts = [np.array(kpts[i]) for i in indexes] # print('here') # cbmk = np.array([ 0.44, 0.44, 0. ]) # print(np.vstack((bs.get_sym_eq_kpoints(cbmk),bs.get_sym_eq_kpoints(-cbmk)))) # cbmk = np.array([ 0.5, 0. , 0.5]) # print(np.vstack((bs.get_sym_eq_kpoints(cbmk),bs.get_sym_eq_kpoints(-cbmk)))) # print('here values') # print energies[:10] # print normv[:10] # print kpts[:10] # print masses[:10] if is_cb: iextrem = np.argmin(energies) extremum0 = energies[iextrem] # extremum0 is numerical CBM here actual_cbm_vbm[tp]['energy'] = extremum0 actual_cbm_vbm[tp]['kpoint'] = kpts[iextrem] # The following is in case CBM doesn't have a zero numerical norm(v) closest_cbm = get_closest_k( kpts[iextrem], np.vstack((bs.get_sym_eq_kpoints(cbmk), bs.get_sym_eq_kpoints(-cbmk)))) if norm(np.array(kpts[iextrem]) - closest_cbm) < min_normdiff and \ abs(bs.get_cbm()['energy']-extremum0) < 0.05: extrema['n'].append(cbmk) else: extrema['n'].append(kpts[iextrem]) else: iextrem = np.argmax(energies) extremum0 = energies[iextrem] actual_cbm_vbm[tp]['energy'] = extremum0 actual_cbm_vbm[tp]['kpoint'] = kpts[iextrem] closest_vbm = get_closest_k( kpts[iextrem], np.vstack((bs.get_sym_eq_kpoints(vbmk), bs.get_sym_eq_kpoints(-vbmk)))) if norm(np.array(kpts[iextrem]) - closest_vbm) < min_normdiff and \ abs(bs.get_vbm()['energy']-extremum0) < 0.05: extrema['p'].append(vbmk) else: extrema['p'].append(kpts[iextrem]) if normv[0] > v_cut: raise ValueError('No extremum point (v<{}) found!'.format(v_cut)) for i in range(0, len(kpts[:nex_max])): # if (velocities[i] > v_cut).all() : if normv[i] > v_cut: break else: far_enough = True for k in extrema[tp]: if norm( get_closest_k(kpts[i], np.vstack( (bs.get_sym_eq_kpoints(k), bs.get_sym_eq_kpoints(-k))), return_diff=True)) <= min_normdiff: # if norm(kpts[i] - k) <= min_normdiff: far_enough = False if far_enough \ and abs(energies[i] - extremum0) < Ecut[tp] \ and masses[i] * ((-1) ** (int(is_cb) + 1)) >= 0: extrema[tp].append(kpts[i]) if not return_global: return extrema else: return extrema, actual_cbm_vbm
from mpinterfaces.nanoparticle import Nanoparticle # ----------------------------------- # nanoparticle specifications # ----------------------------------- # max radius in angstroms rmax = 15 # surface families to be chopped off hkl_family = [(1, 0, 0), (1, 1, 1)] # surfac energies could be in any units, will be normalized surface_energies = [28, 25] # ----------------------------------- # initial structure # ----------------------------------- # caution: set the structure wrt which the the miller indices are # specified. use your own key structure = get_struct_from_mp('PbS', MAPI_KEY="") # primitive ---> conventional cell sa = SpacegroupAnalyzer(structure) structure_conventional = sa.get_conventional_standard_structure() # ----------------------------------- # create nanoparticle # ----------------------------------- nanoparticle = Nanoparticle(structure_conventional, rmax=rmax, hkl_family=hkl_family, surface_energies=surface_energies) nanoparticle.create() nanoparticle.to(fmt='xyz', filename='nanoparticle.xyz')
def get_valences(self, structure): """ Returns a list of valences for the structure. This currently works only for ordered structures only. Args: structure: Structure to analyze Returns: A list of valences for each site in the structure (for an ordered structure), e.g., [1, 1, -2] or a list of lists with the valences for each fractional element of each site in the structure (for an unordered structure), e.g., [[2, 4], [3], [-2], [-2], [-2]] Raises: A ValueError if the valences cannot be determined. """ els = [Element(el.symbol) for el in structure.composition.elements] if not set(els).issubset(set(BV_PARAMS.keys())): raise ValueError("Structure contains elements not in set of BV parameters!") # Perform symmetry determination and get sites grouped by symmetry. if self.symm_tol: finder = SpacegroupAnalyzer(structure, self.symm_tol) symm_structure = finder.get_symmetrized_structure() equi_sites = symm_structure.equivalent_sites else: equi_sites = [[site] for site in structure] # Sort the equivalent sites by decreasing electronegativity. equi_sites = sorted(equi_sites, key=lambda sites: -sites[0].species.average_electroneg) # Get a list of valences and probabilities for each symmetrically # distinct site. valences = [] all_prob = [] if structure.is_ordered: for sites in equi_sites: test_site = sites[0] nn = structure.get_neighbors(test_site, self.max_radius) prob = self._calc_site_probabilities(test_site, nn) all_prob.append(prob) val = list(prob.keys()) # Sort valences in order of decreasing probability. val = sorted(val, key=lambda v: -prob[v]) # Retain probabilities that are at least 1/100 of highest prob. valences.append(list(filter(lambda v: prob[v] > 0.01 * prob[val[0]], val))) else: full_all_prob = [] for sites in equi_sites: test_site = sites[0] nn = structure.get_neighbors(test_site, self.max_radius) prob = self._calc_site_probabilities_unordered(test_site, nn) all_prob.append(prob) full_all_prob.extend(prob.values()) vals = [] for (elsp, occ) in get_z_ordered_elmap(test_site.species): val = list(prob[elsp.symbol].keys()) # Sort valences in order of decreasing probability. val = sorted(val, key=lambda v: -prob[elsp.symbol][v]) # Retain probabilities that are at least 1/100 of highest # prob. vals.append( list( filter( lambda v: prob[elsp.symbol][v] > 0.001 * prob[elsp.symbol][val[0]], val, ) ) ) valences.append(vals) # make variables needed for recursion if structure.is_ordered: nsites = np.array([len(i) for i in equi_sites]) vmin = np.array([min(i) for i in valences]) vmax = np.array([max(i) for i in valences]) self._n = 0 self._best_score = 0 self._best_vset = None def evaluate_assignment(v_set): el_oxi = collections.defaultdict(list) for i, sites in enumerate(equi_sites): el_oxi[sites[0].specie.symbol].append(v_set[i]) max_diff = max([max(v) - min(v) for v in el_oxi.values()]) if max_diff > 1: return score = functools.reduce(operator.mul, [all_prob[i][v] for i, v in enumerate(v_set)]) if score > self._best_score: self._best_vset = v_set self._best_score = score def _recurse(assigned=[]): # recurses to find permutations of valences based on whether a # charge balanced assignment can still be found if self._n > self.max_permutations: return None i = len(assigned) highest = vmax.copy() highest[:i] = assigned highest *= nsites highest = np.sum(highest) lowest = vmin.copy() lowest[:i] = assigned lowest *= nsites lowest = np.sum(lowest) if highest < 0 or lowest > 0: self._n += 1 return None if i == len(valences): evaluate_assignment(assigned) self._n += 1 return None for v in valences[i]: new_assigned = list(assigned) _recurse(new_assigned + [v]) return None else: nsites = np.array([len(i) for i in equi_sites]) tmp = [] attrib = [] for insite, nsite in enumerate(nsites): for val in valences[insite]: tmp.append(nsite) attrib.append(insite) new_nsites = np.array(tmp) fractions = [] elements = [] for sites in equi_sites: for sp, occu in get_z_ordered_elmap(sites[0].species): elements.append(sp.symbol) fractions.append(occu) fractions = np.array(fractions, np.float) new_valences = [] for vals in valences: for val in vals: new_valences.append(val) vmin = np.array([min(i) for i in new_valences], np.float) vmax = np.array([max(i) for i in new_valences], np.float) self._n = 0 self._best_score = 0 self._best_vset = None def evaluate_assignment(v_set): el_oxi = collections.defaultdict(list) jj = 0 for i, sites in enumerate(equi_sites): for specie, occu in get_z_ordered_elmap(sites[0].species): el_oxi[specie.symbol].append(v_set[jj]) jj += 1 max_diff = max([max(v) - min(v) for v in el_oxi.values()]) if max_diff > 2: return score = functools.reduce( operator.mul, [all_prob[attrib[iv]][elements[iv]][vv] for iv, vv in enumerate(v_set)], ) if score > self._best_score: self._best_vset = v_set self._best_score = score def _recurse(assigned=[]): # recurses to find permutations of valences based on whether a # charge balanced assignment can still be found if self._n > self.max_permutations: return None i = len(assigned) highest = vmax.copy() highest[:i] = assigned highest *= new_nsites highest *= fractions highest = np.sum(highest) lowest = vmin.copy() lowest[:i] = assigned lowest *= new_nsites lowest *= fractions lowest = np.sum(lowest) if highest < -self.charge_neutrality_tolerance or lowest > self.charge_neutrality_tolerance: self._n += 1 return None if i == len(new_valences): evaluate_assignment(assigned) self._n += 1 return None for v in new_valences[i]: new_assigned = list(assigned) _recurse(new_assigned + [v]) return None _recurse() if self._best_vset: if structure.is_ordered: assigned = {} for val, sites in zip(self._best_vset, equi_sites): for site in sites: assigned[site] = val return [int(assigned[site]) for site in structure] assigned = {} new_best_vset = [] for ii in range(len(equi_sites)): new_best_vset.append(list()) for ival, val in enumerate(self._best_vset): new_best_vset[attrib[ival]].append(val) for val, sites in zip(new_best_vset, equi_sites): for site in sites: assigned[site] = val return [[int(frac_site) for frac_site in assigned[site]] for site in structure] raise ValueError("Valences cannot be assigned!")
def _gen_input_file(self): """ Generate the necessary struct_enum.in file for enumlib. See enumlib documentation for details. """ coord_format = "{:.6f} {:.6f} {:.6f}" # Using symmetry finder, get the symmetrically distinct sites. fitter = SpacegroupAnalyzer(self.structure, self.symm_prec) symmetrized_structure = fitter.get_symmetrized_structure() logger.debug("Spacegroup {} ({}) with {} distinct sites".format( fitter.get_space_group_symbol(), fitter.get_space_group_number(), len(symmetrized_structure.equivalent_sites)) ) """ Enumlib doesn"t work when the number of species get too large. To simplify matters, we generate the input file only with disordered sites and exclude the ordered sites from the enumeration. The fact that different disordered sites with the exact same species may belong to different equivalent sites is dealt with by having determined the spacegroup earlier and labelling the species differently. """ # index_species and index_amounts store mappings between the indices # used in the enum input file, and the actual species and amounts. index_species = [] index_amounts = [] # Stores the ordered sites, which are not enumerated. ordered_sites = [] disordered_sites = [] coord_str = [] for sites in symmetrized_structure.equivalent_sites: if sites[0].is_ordered: ordered_sites.append(sites) else: sp_label = [] species = {k: v for k, v in sites[0].species.items()} if sum(species.values()) < 1 - EnumlibAdaptor.amount_tol: # Let us first make add a dummy element for every single # site whose total occupancies don't sum to 1. species[DummySpecie("X")] = 1 - sum(species.values()) for sp in species.keys(): if sp not in index_species: index_species.append(sp) sp_label.append(len(index_species) - 1) index_amounts.append(species[sp] * len(sites)) else: ind = index_species.index(sp) sp_label.append(ind) index_amounts[ind] += species[sp] * len(sites) sp_label = "/".join(["{}".format(i) for i in sorted(sp_label)]) for site in sites: coord_str.append("{} {}".format( coord_format.format(*site.coords), sp_label)) disordered_sites.append(sites) def get_sg_info(ss): finder = SpacegroupAnalyzer(Structure.from_sites(ss), self.symm_prec) return finder.get_space_group_number() target_sgnum = get_sg_info(symmetrized_structure.sites) curr_sites = list(itertools.chain.from_iterable(disordered_sites)) sgnum = get_sg_info(curr_sites) ordered_sites = sorted(ordered_sites, key=lambda sites: len(sites)) logger.debug("Disordered sites has sg # %d" % (sgnum)) self.ordered_sites = [] # progressively add ordered sites to our disordered sites # until we match the symmetry of our input structure if self.check_ordered_symmetry: while sgnum != target_sgnum and len(ordered_sites) > 0: sites = ordered_sites.pop(0) temp_sites = list(curr_sites) + sites new_sgnum = get_sg_info(temp_sites) if sgnum != new_sgnum: logger.debug("Adding %s in enum. New sg # %d" % (sites[0].specie, new_sgnum)) index_species.append(sites[0].specie) index_amounts.append(len(sites)) sp_label = len(index_species) - 1 for site in sites: coord_str.append("{} {}".format( coord_format.format(*site.coords), sp_label)) disordered_sites.append(sites) curr_sites = temp_sites sgnum = new_sgnum else: self.ordered_sites.extend(sites) for sites in ordered_sites: self.ordered_sites.extend(sites) self.index_species = index_species lattice = self.structure.lattice output = [self.structure.formula, "bulk"] for vec in lattice.matrix: output.append(coord_format.format(*vec)) output.append("%d" % len(index_species)) output.append("%d" % len(coord_str)) output.extend(coord_str) output.append("{} {}".format(self.min_cell_size, self.max_cell_size)) output.append(str(self.enum_precision_parameter)) output.append("full") ndisordered = sum([len(s) for s in disordered_sites]) base = int(ndisordered*lcm(*[f.limit_denominator(ndisordered * self.max_cell_size).denominator for f in map(fractions.Fraction, index_amounts)])) # This multiplicative factor of 10 is to prevent having too small bases # which can lead to rounding issues in the next step. # An old bug was that a base was set to 8, with a conc of 0.4:0.6. That # resulted in a range that overlaps and a conc of 0.5 satisfying this # enumeration. See Cu7Te5.cif test file. base *= 10 # base = ndisordered #10 ** int(math.ceil(math.log10(ndisordered))) # To get a reasonable number of structures, we fix concentrations to the # range expected in the original structure. total_amounts = sum(index_amounts) for amt in index_amounts: conc = amt / total_amounts if abs(conc * base - round(conc * base)) < 1e-5: output.append("{} {} {}".format(int(round(conc * base)), int(round(conc * base)), base)) else: min_conc = int(math.floor(conc * base)) output.append("{} {} {}".format(min_conc - 1, min_conc + 1, base)) output.append("") logger.debug("Generated input file:\n{}".format("\n".join(output))) with open("struct_enum.in", "w") as f: f.write("\n".join(output))
def get_ieee_rotation(structure): """ Given a structure associated with a tensor, determines the rotation matrix for IEEE conversion according to the 1987 IEEE standards. Args: structure (Structure): a structure associated with the tensor to be converted to the IEEE standard """ # Check conventional setting: sga = SpacegroupAnalyzer(structure) dataset = sga.get_symmetry_dataset() trans_mat = dataset['transformation_matrix'] conv_latt = Lattice(np.transpose(np.dot(np.transpose( structure.lattice.matrix), np.linalg.inv(trans_mat)))) xtal_sys = sga.get_crystal_system() vecs = conv_latt.matrix lengths = np.array(conv_latt.abc) angles = np.array(conv_latt.angles) rotation = np.zeros((3, 3)) # IEEE rules: a,b,c || x1,x2,x3 if xtal_sys == "cubic": rotation = [vecs[i] / lengths[i] for i in range(3)] # IEEE rules: a=b in length; c,a || x3, x1 elif xtal_sys == "tetragonal": rotation = np.array([vec / mag for (mag, vec) in sorted(zip(lengths, vecs), key=lambda x: x[0])]) if abs(lengths[2] - lengths[1]) < abs(lengths[1] - lengths[0]): rotation[0], rotation[2] = rotation[2], rotation[0].copy() rotation[1] = get_uvec(np.cross(rotation[2], rotation[0])) # IEEE rules: c<a<b; c,a || x3,x1 elif xtal_sys == "orthorhombic": rotation = [vec / mag for (mag, vec) in sorted(zip(lengths, vecs))] rotation = np.roll(rotation, 2, axis=0) # IEEE rules: c,a || x3,x1, c is threefold axis # Note this also includes rhombohedral crystal systems elif xtal_sys in ("trigonal", "hexagonal"): # find threefold axis: tf_index = np.argmin(abs(angles - 120.)) non_tf_mask = np.logical_not(angles == angles[tf_index]) rotation[2] = get_uvec(vecs[tf_index]) rotation[0] = get_uvec(vecs[non_tf_mask][0]) rotation[1] = get_uvec(np.cross(rotation[2], rotation[0])) # IEEE rules: b,c || x2,x3; alpha=beta=90, c<a elif xtal_sys == "monoclinic": # Find unique axis u_index = np.argmax(abs(angles - 90.)) n_umask = np.logical_not(angles == angles[u_index]) rotation[1] = get_uvec(vecs[u_index]) # Shorter of remaining lattice vectors for c axis c = [vec / mag for (mag, vec) in sorted(zip(lengths[n_umask], vecs[n_umask]))][0] rotation[2] = np.array(c) rotation[0] = np.cross(rotation[1], rotation[2]) # IEEE rules: c || x3 elif xtal_sys == "triclinic": rotation = [vec / mag for (mag, vec) in sorted(zip(lengths, vecs))] rotation = np.roll(rotation, 2, axis=0) rotation[1] = get_uvec(np.cross(rotation[2], rotation[1])) rotation[0] = np.cross(rotation[1], rotation[2]) return rotation
def get_sg_info(ss): finder = SpacegroupAnalyzer(Structure.from_sites(ss), self.symm_prec) return finder.get_space_group_number()
def write_etree(self, celltype, cartesian=False, bandstr=False, symprec=0.4, angle_tolerance=5): """ :param celltype: :param cartesian: :param bandstr: :param symprec: :param angle_tolerance: :return: """ root = ET.Element('input') root.set( '{http://www.w3.org/2001/XMLSchema-instance}noNamespaceSchemaLocation', 'http://xml.exciting-code.org/excitinginput.xsd') title = ET.SubElement(root, 'title') title.text = self.title if cartesian: structure = ET.SubElement(root, 'structure', cartesian="true", speciespath="./") else: structure = ET.SubElement(root, 'structure', speciespath="./") crystal = ET.SubElement(structure, 'crystal') # set scale such that lattice vector can be given in Angstrom ang2bohr = const.value('Angstrom star') / const.value('Bohr radius') crystal.set('scale', str(ang2bohr)) # determine which structure to use finder = SpacegroupAnalyzer(self.structure, symprec=symprec, angle_tolerance=angle_tolerance) if celltype == 'primitive': new_struct = finder.get_primitive_standard_structure( international_monoclinic=False) elif celltype == 'conventional': new_struct = finder.get_conventional_standard_structure( international_monoclinic=False) elif celltype == 'unchanged': new_struct = self.structure else: raise ValueError('Type of unit cell not recognized!') # write lattice basis = new_struct.lattice.matrix for i in range(3): basevect = ET.SubElement(crystal, 'basevect') basevect.text = "%16.8f %16.8f %16.8f" % (basis[i][0], basis[i][1], basis[i][2]) # write atomic positions for each species index = 0 for i in sorted(new_struct.types_of_species, key=lambda el: el.X): species = ET.SubElement(structure, 'species', speciesfile=i.symbol + '.xml') sites = new_struct.indices_from_symbol(i.symbol) for j in sites: coord = "%16.8f %16.8f %16.8f" % (new_struct[j].frac_coords[0], new_struct[j].frac_coords[1], new_struct[j].frac_coords[2]) # obtain cartesian coords from fractional ones if needed if cartesian: coord2 = [] for k in range(3): inter = (new_struct[j].frac_coords[k] * basis[0][k] + new_struct[j].frac_coords[k] * basis[1][k] + new_struct[j].frac_coords[k] * basis[2][k]) * ang2bohr coord2.append(inter) coord = "%16.8f %16.8f %16.8f" % (coord2[0], coord2[1], coord2[2]) # write atomic positions index = index + 1 _ = ET.SubElement(species, 'atom', coord=coord) # write bandstructure if needed if bandstr and celltype == 'primitive': kpath = HighSymmKpath(new_struct, symprec=symprec, angle_tolerance=angle_tolerance) prop = ET.SubElement(root, 'properties') bandstrct = ET.SubElement(prop, 'bandstructure') for i in range(len(kpath.kpath['path'])): plot = ET.SubElement(bandstrct, 'plot1d') path = ET.SubElement(plot, 'path', steps='100') for j in range(len(kpath.kpath['path'][i])): symbol = kpath.kpath['path'][i][j] coords = kpath.kpath['kpoints'][symbol] coord = "%16.8f %16.8f %16.8f" % (coords[0], coords[1], coords[2]) if symbol == '\\Gamma': symbol = 'GAMMA' _ = ET.SubElement(path, 'point', coord=coord, label=symbol) elif bandstr and celltype != 'primitive': raise ValueError("Bandstructure is only implemented for the \ standard primitive unit cell!") return root
'Ba': 2, 'Ca': 2, 'Ti': 4, 'O': 2, 'F': 1, 'La': 3, 'Mn': 3, 'Er': 3, 'Ni': 3 } ''' Define args for minimizer. ''' num_atoms = structure.composition.num_atoms # number of atoms in cell lattice = structure.lattice() # lattice vectors # Check and save space group number space_group = SpacegroupAnalyzer(structure, symprec=0.01).get_spacegroup_number() # wyckoff list used to sets bounds for space group symmetries wycks = SpacegroupAnalyzer(structure, symprec=0.01).get_symmetry_dataset()['wyckoffs'] Species_list = structure.species #list of ion names. '''sum of Shannon ionic radii to account for hard spheres distances of anions''' Shannon_anion_anion = 2.7 myargs = (cations, anions, lattice, Species_list, num_atoms, cutoff_distance, \ BVpara, Formal_Valence, wycks, space_group, b0, center_atom_in_regular_poly, max_angle,\ nearest_neighbor_distance, Shannon_anion_anion) '''Define site symmetries for each wyckoff position in cell. Currently requires user input, that can be obtained from SITESYM on the Bilbao Cyst. Server. Possible Python wrapper for this may exist to automate process in Compuational Crystallography toolbox https://cctbx.github.io''' site_operations = {
def get_conventional_cell(self): # # infomation of original primitive cell # self.prim_atom_info = {} data_set = self.spg.get_symmetry_dataset() wyckoffs = data_set['wyckoffs'] natoms = len(self.atoms) atom_info_tmp = [] for iatom in range(natoms): element = self.atoms[iatom], wyckoff_letter = wyckoffs[iatom] info = {'element': self.atoms[iatom], 'wyckoff_letter': wyckoffs[iatom]} atom_info_tmp.append(info) element_list = [] atom_info = [] for info in atom_info_tmp: element = info['element'] wyckoff_letter = info['wyckoff_letter'] if element not in element_list: element_list.append(element) info = {} info['element'] = element info['wyckoff_letter'] = [wyckoff_letter] atom_info.append(info) else: atom_info[-1]['wyckoff_letter'].append(wyckoff_letter) self.primitive_cell_info = {'ispg': self.ispg, 'natoms': natoms, 'atom_info': atom_info} # # conventional unit cell # data = str(self.spg.get_conventional_standard_structure()) data_set = self.spg.get_symmetry_dataset() wyckoffs = data_set['wyckoffs'] data_region = False atom_info = [] self.atoms = [] atomic_positions = [] for line in data.split('\n'): linebuf = line.strip() if re.search('abc\s+:', linebuf): lattice_length = \ [float(x) for x in linebuf.split(':')[1].split()] if re.search('angles\s*:', linebuf): lattice_angle = \ [float(x) for x in linebuf.split(':')[1].split()] if re.search('^#\s+SP', linebuf): data_region = True continue if data_region: if re.search('^---', linebuf): continue data_line = linebuf.split() element = data_line[1] position = [float(x) for x in data_line[2:]] self.atoms.append(element) atomic_positions.append(position) # # generate conventional infomation # a, b, c = lattice_length alpha, beta, gamma = lattice_angle lattice = mg.Lattice.from_parameters(a, b, c, alpha, beta, gamma) try: self.structure = mg.Structure(lattice, self.atoms, atomic_positions) self.spg = SpacegroupAnalyzer(self.structure, symprec=self.symprec, angle_tolerance=self.angle_trelance) self.ispg = self.spg.get_space_group_number() self.hmname = self.spg.get_space_group_symbol() except TypeError: print() print(' **** INTERNAL LIBRARY WARNING ****') print(' pymatgen and spglib cannot identify space group', end='') print(' for smaller primitive cell.') print(' we expect this bug will be fixed near future.', end='') print(' metis uses original primitive cell.') self.spg = None self.ispg = None self.hmname = None return self
def site_weighted_spectrum(xas_list: List["XAS"], num_samples: int = 500) -> "XAS": """ Obtain site-weighted XAS object based on site multiplicity for each absorbing index and its corresponding site-wise spectrum. Args: xas_list([XAS]): List of XAS object to be weighted num_samples(int): Number of samples for interpolation Returns: XAS object: The site-weighted spectrum """ m = StructureMatcher() groups = m.group_structures([i.structure for i in xas_list]) if len(groups) > 1: raise ValueError("The input structures mismatch") if not len({i.absorbing_element for i in xas_list}) == len({i.edge for i in xas_list}) == 1: raise ValueError( "Can only perform site-weighting for spectra with same absorbing element and same absorbing edge." ) if len({i.absorbing_index for i in xas_list }) == 1 or None in {i.absorbing_index for i in xas_list}: raise ValueError( "Need at least two site-wise spectra to perform site-weighting") sa = SpacegroupAnalyzer(groups[0][0]) ss = sa.get_symmetrized_structure() maxes, mines = [], [] fs = [] multiplicities = [] for xas in xas_list: multiplicity = len(ss.find_equivalent_sites(ss[xas.absorbing_index])) multiplicities.append(multiplicity) maxes.append(max(xas.x)) mines.append(min(xas.x)) # use 3rd-order spline interpolation for mu (idx 3) vs energy (idx 0). f = interp1d( np.asarray(xas.x), np.asarray(xas.y), bounds_error=False, fill_value=0, kind="cubic", ) fs.append(f) # Interpolation within the intersection of x-axis ranges. x_axis = np.linspace(max(mines), min(maxes), num=num_samples) weighted_spectrum = np.zeros(num_samples) sum_multiplicities = sum(multiplicities) for i, j in enumerate(multiplicities): weighted_spectrum += (j * fs[i](x_axis)) / sum_multiplicities return XAS( x_axis, weighted_spectrum, ss, xas.absorbing_element, xas.edge, xas.spectrum_type, )
return supercell_size_test else: supercell_size = supercell_size_test # Set Materials Project structure ID here: structure_id = 'mp-1265' rester = pymatgen.MPRester(os.environ['PMG_MAPI_KEY']) pmg_structure = rester.get_structure_by_material_id(structure_id) pmg_band = rester.get_bandstructure_by_material_id(structure_id) material_name = pmg_structure.formula.replace('1', '').replace(' ', '') spa = SpacegroupAnalyzer(pmg_structure) conventional = spa.get_conventional_standard_structure() primitive = spa.get_primitive_standard_structure() print('Conventional cell') print conventional primitive_matrix = np.dot(np.linalg.inv(conventional.lattice.matrix), primitive.lattice.matrix) primitive_matrix = np.round(primitive_matrix, decimals=6).tolist() structure = StructureData(pymatgen=conventional).store() print('Primitive matrix') print primitive_matrix
from pymatgen.core.structure import Structure from pymatgen.symmetry.analyzer import SpacegroupAnalyzer from jarvis.lammps.NEW_LAMMPS import main_func, write_lammps_data, write_lammps_in from pymatgen.io.vasp.inputs import Poscar """ Step-1: writing LAMMPS data file Import VASP's POSCAR or cif format file,make it 15x15x15 with conventional cell """ c_size = 15 p = Structure.from_file("POSCAR") sg_mat = SpacegroupAnalyzer(p) mat_cvn = sg_mat.get_conventional_standard_structure() #mat_cvn.to(fmt='poscar',filename='POSCAR_cvb.vasp') #can be visualized using VESTA dim1 = int((float(c_size) / float(max(abs(mat_cvn.lattice.matrix[0]))))) + 1 dim2 = int(float(c_size) / float(max(abs(mat_cvn.lattice.matrix[1])))) + 1 dim3 = int(float(c_size) / float(max(abs(mat_cvn.lattice.matrix[2])))) + 1 p.make_supercell([dim1, dim2, dim3]) write_lammps_data(p, file='dat.dat') #can be visulaized using Ovito """ Step-2: writing LAMMPS input file Adjust parameters according to LAMMPS executable, force-field file, main input file that you'll be using, pair style and atom style """ parameters = { 'exec': 'mpirun /cluster/bin/lmp_ctcms-14439-knc6 <in.elastic >out', 'pair_coeff': '/users/knc6/Software/jarvis/jarvis/lammps/examples/Mishin-Ni-Al-2009.eam.alloy', 'control_file': '/users/knc6/inelast.mod', 'pair_style': 'eam/alloy', 'atom_style': 'charge', 'cluster': 'pbs' }
def geo_table_row(cl=None, st=None, name='', show_angle=0, mnpo4_hack=False, param_order=None): #return list of geo data for cl, which can be used for table """ mnpo4_hack (bool) - if true exchange a and c for mnpo4 phase param_order - default [0,1,2] """ po = param_order if po is None: po = [0, 1, 2] if cl: st = cl.end # if not name: if header.pymatgen_flag: spg = st.get_space_group_info() spg = latex_spg(spg[0]) #transform to standard st_mp = st.convert2pymatgen() symprec = 0.1 sf = SpacegroupAnalyzer(st_mp, symprec=symprec) st_mp_prim = sf.find_primitive() st_mp_conv = sf.get_conventional_standard_structure() else: spg = '-' # st_mp_prim. # print('primitive,', st_mp_prim.lattice) # print('conventio,', st_mp_conv.lattice) # st_mp_prim = sf.get_primitive_standard_structure() # st_mp_prim = sf.get_conventional_standard_structure() if not name: # print(dir(st_mp )) # st.printme() name = st.get_reduced_formula() name = latex_chem(name) alpha, beta, gamma = st.get_angles() elem = np.array(st.get_elements()) if 'a' in show_angle: angle = '& {:5.2f}'.format(alpha) elif 'b' in show_angle: angle = '& {:5.2f}'.format(beta) elif 'g' in show_angle: angle = '& {:5.2f}'.format(gamma) else: angle = '' v = st.vlength a, b, c = v if mnpo4_hack and 'MnPO' in name: c, b, a = v ps = [a, b, c] p = [] # print(ps) # print(po) for o in po: p.append(ps[o]) # print(p) # sys.exit() return '{:15s} &{:s} & {:5.2f} & {:5.2f} & {:5.2f} '.format( name, 'DFT+U', p[0], p[1], p[2]) + angle + '& {:5.1f} & {:s}'.format( st.vol, spg)
def generate_doc(self, dir_name, vasprun_files, outcar_files): """ Adapted from matgendb.creator.generate_doc """ try: # basic properties, incl. calcs_reversed and run_stats fullpath = os.path.abspath(dir_name) d = jsanitize(self.additional_fields, strict=True) d["schema"] = {"code": "atomate", "version": VaspDrone.__version__} d["dir_name"] = fullpath d["calcs_reversed"] = [ self.process_vasprun(dir_name, taskname, filename) for taskname, filename in vasprun_files.items() ] outcar_data = [ Outcar(os.path.join(dir_name, filename)).as_dict() for taskname, filename in outcar_files.items() ] run_stats = {} for i, d_calc in enumerate(d["calcs_reversed"]): run_stats[d_calc["task"]["name"]] = outcar_data[i].pop( "run_stats") if d_calc.get("output"): d_calc["output"].update({"outcar": outcar_data[i]}) else: d_calc["output"] = {"outcar": outcar_data[i]} try: overall_run_stats = {} for key in [ "Total CPU time used (sec)", "User time (sec)", "System time (sec)", "Elapsed time (sec)" ]: overall_run_stats[key] = sum( [v[key] for v in run_stats.values()]) run_stats["overall"] = overall_run_stats except: logger.error("Bad run stats for {}.".format(fullpath)) d["run_stats"] = run_stats # reverse the calculations data order so newest calc is first d["calcs_reversed"].reverse() # set root formula/composition keys based on initial and final calcs d_calc_init = d["calcs_reversed"][-1] d_calc_final = d["calcs_reversed"][0] d["chemsys"] = "-".join(sorted(d_calc_final["elements"])) comp = Composition(d_calc_final["composition_unit_cell"]) d["formula_anonymous"] = comp.anonymized_formula d["formula_reduced_abc"] = comp.reduced_composition.alphabetical_formula for root_key in [ "completed_at", "nsites", "composition_unit_cell", "composition_reduced", "formula_pretty", "elements", "nelements" ]: d[root_key] = d_calc_final[root_key] # store the input key based on initial calc # store any overrides to the exchange correlation functional xc = d_calc_init["input"]["incar"].get("GGA") if xc: xc = xc.upper() p = d_calc_init["input"]["potcar_type"][0].split("_") pot_type = p[0] functional = "lda" if len(pot_type) == 1 else "_".join(p[1:]) d["input"] = { "structure": d_calc_init["input"]["structure"], "is_hubbard": d_calc_init.pop("is_hubbard"), "hubbards": d_calc_init.pop("hubbards"), "is_lasph": d_calc_init["input"]["incar"].get("LASPH", False), "potcar_spec": d_calc_init["input"].get("potcar_spec"), "xc_override": xc, "pseudo_potential": { "functional": functional.lower(), "pot_type": pot_type.lower(), "labels": d_calc_init["input"]["potcar"] }, "parameters": d_calc_init["input"]["parameters"], "incar": d_calc_init["input"]["incar"] } # store the output key based on final calc d["output"] = { "structure": d_calc_final["output"]["structure"], "density": d_calc_final.pop("density"), "energy": d_calc_final["output"]["energy"], "energy_per_atom": d_calc_final["output"]["energy_per_atom"], "forces": d_calc_final["output"]["ionic_steps"][-1].get("forces"), "stress": d_calc_final["output"]["ionic_steps"][-1].get("stress") } # patch calculated magnetic moments into final structure if len(d_calc_final["output"]["outcar"]["magnetization"]) != 0: magmoms = [ m["tot"] for m in d_calc_final["output"]["outcar"]["magnetization"] ] s = Structure.from_dict(d["output"]["structure"]) s.add_site_property('magmom', magmoms) d["output"]["structure"] = s.as_dict() calc = d["calcs_reversed"][0] # copy band gap and properties into output d["output"].update({ "bandgap": calc["output"]["bandgap"], "cbm": calc["output"]["cbm"], "vbm": calc["output"]["vbm"], "is_gap_direct": calc["output"]["is_gap_direct"] }) try: d["output"].update({"is_metal": calc["output"]["is_metal"]}) if not calc["output"]["is_gap_direct"]: d["output"]["direct_gap"] = calc["output"]["direct_gap"] if "transition" in calc["output"]: d["output"]["transition"] = calc["output"]["transition"] except Exception: if self.bandstructure_mode is True: logger.error(traceback.format_exc()) logger.error("Error in " + os.path.abspath(dir_name) + ".\n" + traceback.format_exc()) raise # Store symmetry information sg = SpacegroupAnalyzer( Structure.from_dict(d_calc_final["output"]["structure"]), 0.1) if not sg.get_symmetry_dataset(): sg = SpacegroupAnalyzer( Structure.from_dict(d_calc_final["output"]["structure"]), 1e-3, 1) d["output"]["spacegroup"] = { "source": "spglib", "symbol": sg.get_space_group_symbol(), "number": sg.get_space_group_number(), "point_group": sg.get_point_group_symbol(), "crystal_system": sg.get_crystal_system(), "hall": sg.get_hall() } # store dieelctric and piezo information if d["input"]["parameters"].get("LEPSILON"): for k in [ 'epsilon_static', 'epsilon_static_wolfe', 'epsilon_ionic' ]: d["output"][k] = d_calc_final["output"][k] if SymmOp.inversion() not in sg.get_symmetry_operations(): for k in ["piezo_ionic_tensor", "piezo_tensor"]: d["output"][k] = d_calc_final["output"]["outcar"][k] d["state"] = "successful" if d_calc[ "has_vasp_completed"] else "unsuccessful" self.set_analysis(d) d["last_updated"] = datetime.datetime.utcnow() return d except Exception: logger.error(traceback.format_exc()) logger.error("Error in " + os.path.abspath(dir_name) + ".\n" + traceback.format_exc()) raise
def get_plot_2d_concise(self, structure: Structure) -> go.Figure: """ Generates the concise 2D diffraction pattern of the input structure of a smaller size and without layout. Does not display. Args: structure (Structure): The input structure. Returns: Figure """ if self.symprec: finder = SpacegroupAnalyzer(structure, symprec=self.symprec) structure = finder.get_refined_structure() points = self.generate_points(-10, 11) tem_dots = self.tem_dots(structure, points) xs = [] ys = [] hkls = [] intensities = [] for dot in tem_dots: if dot.hkl != (0, 0, 0): xs.append(dot.position[0]) ys.append(dot.position[1]) hkls.append(dot.hkl) intensities.append(dot.intensity) data = [ go.Scatter( x=xs, y=ys, text=hkls, mode="markers", hoverinfo="skip", marker=dict( size=4, cmax=1, cmin=0, color=intensities, colorscale=[[0, "black"], [1.0, "white"]], ), showlegend=False, ) ] layout = go.Layout( xaxis=dict( range=[-4, 4], showgrid=False, zeroline=False, showline=False, ticks="", showticklabels=False, ), yaxis=dict( range=[-4, 4], showgrid=False, zeroline=False, showline=False, ticks="", showticklabels=False, ), plot_bgcolor="black", margin={"l": 0, "r": 0, "t": 0, "b": 0}, width=121, height=121, ) fig = go.Figure(data=data, layout=layout) fig.layout.update(showlegend=False) return fig
def process_item(self, item): """ Process the tasks and materials into a dielectrics collection Args: item dict: a dict of material_id, structure, and tasks Returns: dict: a dieletrics dictionary """ def poly(matrix): diags = np.diagonal(matrix) return np.prod(diags) / np.sum( np.prod(comb) for comb in combinations(diags, 2)) d = {self.dielectric.key: item[self.materials.key]} structure = Structure.from_dict(item["structure"]) if item.get("dielectric", False): ionic = Tensor( item["dielectric"]["ionic"]).symmetrized.fit_to_structure( structure).convert_to_ieee(structure) static = Tensor( item["dielectric"]["static"]).symmetrized.fit_to_structure( structure).convert_to_ieee(structure) total = ionic + static d["dielectric"] = { "total": total, "ionic": ionic, "static": static, "e_total": poly(total), "e_ionic": poly(ionic), "e_static": poly(static) } sga = SpacegroupAnalyzer(structure) # Update piezo if non_centrosymmetric if item.get("piezo", False) and not sga.is_laue(): static = PiezoTensor.from_voigt(np.array( item['piezo']["static"])).symmetrized.fit_to_structure( structure).convert_to_ieee(structure).voigt ionic = PiezoTensor.from_voigt(np.array( item['piezo']["ionic"])).symmetrized.fit_to_structure( structure).convert_to_ieee(structure).voigt total = ionic + static directions, charges, strains = np.linalg.svd(total) max_index = np.argmax(np.abs(charges)) d["piezo"] = { "total": total, "ionic": ionic, "static": static, "e_ij_max": charges[max_index], "max_direction": directions[max_index], "strain_for_max": strains[max_index] } if len(d) > 1: return d return None