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 is_centro(structure): sga = SpacegroupAnalyzer(structure) return SymmOp.inversion() in sga.get_symmetry_operations() def poly(matrix): diags = np.diagonal(matrix) return np.prod(diags) / np.sum( np.prod(comb) for comb in combinations(diags, 2)) structure = Structure.from_dict(item["structure"]) # Start with higher task ids first? for t in sorted(item["tasks"], lambda x: x[self.tasks.lu_field], reverse=True): if "dielectric" in TaskTagger.task_type(t): output = t["calcs_reversed"][0]["output"] total = np.sum(output["epsilon_ionic"], output["epsilon_static"]) d = { "material_id": item['material_id'], "dielectric": { "ionic": output["epsilon_ionic"], "electronic": output["epsilon_static"], "total": total, "e_total": poly(total), "e_ionic": poly(output["epsilon_ionic"]), "e_electronic": poly(output["epsilon_static"]), } } # Update piezo if non_centrosymmetric if not is_centro(item["structure"]): pt_e = PiezoTensor.from_voigt( np.array(output["outcar"]["piezo_tensor"])) pt_i = PiezoTensor.from_voigt( np.array(output["outcar"]["piezo_ionic_tensor"])) pt_total = pt_e + pt_i pt_total = pt_total.symmetrized.fit_to_structure(structure) d["piezo"] = { "piezoelectric_tensor": pt_total.voigt, "eij_max": np.max(pt_total.voigt), } # TODO Add in more analysis: v_max ? # TODO: Add in unstable phonon mode analysis of piezoelectric for potentially ferroelectric return d return None
def post_process(self, mat): """ Any extra post-processing on a material doc """ # Add structure metadata back into document and convert back to conventional standard if "structure" in mat: structure = Structure.from_dict(mat["structure"]) sga = SpacegroupAnalyzer(structure, symprec=SYMPREC) mat["structure"] = structure.as_dict() mat.update(structure_metadata(structure)) # Deprecate materials with bad structures or energies if "structure" in mat["invalid_props"]: mat.update({"deprecated": True}) elif "thermo.energy_per_atom" in mat["invalid_props"]: mat.update({"deprecated": True}) else: mat.update({"deprecated": False}) # Reorder voigt output from VASP to standard voigt notation if has(mat, "piezo.ionic"): mat["piezo"]["ionic"] = PiezoTensor.from_vasp_voigt( mat["piezo"]["ionic"]).voigt.tolist() if has(mat, "piezo.static"): mat["piezo"]["static"] = PiezoTensor.from_vasp_voigt( mat["piezo"]["static"]).voigt.tolist()
def from_ionic_and_electronic(cls, ionic: Matrix3D, electronic: Matrix3D): total = BasePiezoTensor.from_voigt(np.sum(ionic, electronic)) # type: ignore directions, charges, strains = np.linalg.svd(total, full_matrices=False) max_index = np.argmax(np.abs(charges)) max_direction = directions[max_index] # Allow a max miller index of 10 min_val = np.abs(max_direction) min_val = min_val[min_val > (np.max(min_val) / SETTINGS.MAX_PIEZO_MILLER)] min_val = np.min(min_val) return cls( **{ "total": total.zeroed().voigt, "ionic": ionic, "static": electronic, "e_ij_max": charges[max_index], "max_direction": np.round(max_direction / min_val), "strain_for_max": strains[max_index], })
def runTest(self): piezo_struc = self.get_structure('BaNiO3') tensor = np.asarray([[0., 0., 0., 0., 0.03839, 0.], [0., 0., 0., 0.03839, 0., 0.], [6.89822, 6.89822, 27.46280, 0., 0., 0.]]).reshape(3, 6) pt = PiezoTensor(tensor) full_tensor = [[[0., 0., 0.03839], [0., 0., 0.], [0.03839, 0., 0.]], [[0., 0., 0.], [0., 0., 0.03839], [0., 0.03839, 0.]], [[6.89822, 0., 0.], [0., 6.89822, 0.], [0., 0., 27.4628]]] bad_pt = PiezoTensor([[0., 0., 1., 0., 0.03839, 2.], [0., 0., 0., 0.03839, 0., 0.], [6.89822, 6.89822, 27.4628, 0., 0., 0.]]) sym_pt = bad_pt.symmeterize(piezo_struc) alt_tensor = pt.from_full_tensor(full_tensor) self.assertArrayAlmostEqual(pt.full_tensor, full_tensor) self.assertArrayEqual(pt, alt_tensor) self.assertTrue(pt.is_valid(piezo_struc)) self.assertTrue(sym_pt.is_valid(piezo_struc)) self.assertArrayAlmostEqual(sym_pt, pt)
def test_from_vasp_voigt(self): bad_voigt = np.zeros((3, 7)) pt = PiezoTensor.from_vasp_voigt(self.vasp_matrix) self.assertArrayEqual(pt, self.full_tensor_array) self.assertRaises(ValueError, PiezoTensor.from_voigt, bad_voigt) self.assertArrayEqual(self.voigt_matrix, pt.voigt)
def test_new(self): pt = PiezoTensor(self.full_tensor_array) self.assertArrayAlmostEqual(pt, self.full_tensor_array) bad_dim_array = np.zeros((3, 3)) self.assertRaises(ValueError, PiezoTensor, bad_dim_array)
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
def test_from_voigt(self): bad_voigt = np.zeros((3, 7)) pt = PiezoTensor.from_voigt(self.voigt_matrix) self.assertArrayEqual(pt, self.full_tensor_array) self.assertRaises(ValueError, PiezoTensor.from_voigt, bad_voigt) self.assertArrayEqual(self.voigt_matrix, pt.voigt)
def calc(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)) if item["bandstructure"]["band_gap"] > 0: structure = Structure.from_dict( item.get("dielectric", {}).get("structure", None)) ionic = Tensor( item["dielectric"]["ionic"]).convert_to_ieee(structure) static = Tensor( item["dielectric"]["static"]).convert_to_ieee(structure) total = ionic + static d = { "dielectric": { "total": total, "ionic": ionic, "static": static, "e_total": np.average(np.diagonal(total)), "e_ionic": np.average(np.diagonal(ionic)), "e_static": np.average(np.diagonal(static)), "n": np.sqrt(np.average(np.diagonal(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"])) ionic = PiezoTensor.from_voigt(np.array( item["piezo"]["ionic"])) total = ionic + static # Symmeterize Convert to IEEE orientation total = total.convert_to_ieee(structure) ionic = ionic.convert_to_ieee(structure) static = static.convert_to_ieee(structure) directions, charges, strains = np.linalg.svd( total.voigt, full_matrices=False) max_index = np.argmax(np.abs(charges)) max_direction = directions[max_index] # Allow a max miller index of 10 min_val = np.abs(max_direction) min_val = min_val[min_val > (np.max(min_val) / self.max_miller)] min_val = np.min(min_val) d["piezo"] = { "total": total.zeroed().voigt, "ionic": ionic.zeroed().voigt, "static": static.zeroed().voigt, "e_ij_max": charges[max_index], "max_direction": np.round(max_direction / min_val), "strain_for_max": strains[max_index], } else: d = { "dielectric": {}, "_warnings": [ "Dielectric calculated for likely metal. Values are unlikely to be converged" ], } if item.get("piezo", False): d.update({ "piezo": {}, "_warnings": [ "Piezoelectric calculated for likely metal. Values are unlikely to be converged" ], }) return d
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 = {"material_id": item["material_id"]} structure = Structure.from_dict(item["structure"]) if item.get("dielectric") is not None: ionic = Tensor(d["dielectric"]["ionic"]) static = Tensor(d["dielectric"]["static"]) total = ionic + static d["dielectric"] = { "total": total.symmetrized.fit_to_structure(structure).convert_to_ieee( structure), "ionic": ionic.symmetrized.fit_to_structure(structure).convert_to_ieee( structure), "static": static.symmetrized.fit_to_structure(structure).convert_to_ieee( structure), "e_total": poly(total), "e_ionic": poly(ionic), "e_static": poly(static) } # Update piezo if non_centrosymmetric if item.get("piezo") is not None: static = PiezoTensor.from_voigt( np.array(item['piezo']["piezo_tensor"])) ionic = PiezoTensor.from_voigt( np.array(item['piezo']["piezo_ionic_tensor"])) total = ionic + static d["piezo"] = { "total": total.symmetrized.fit_to_structure(structure).convert_to_ieee( structure).voigt, "ionic": ionic.symmetrized.fit_to_structure(structure).convert_to_ieee( structure).voigt, "static": static.symmetrized.fit_to_structure(structure).convert_to_ieee( structure).voigt, "e_ij_max": np.max(total.voigt) } # TODO Add in more analysis: v_max ? # TODO: Add in unstable phonon mode analysis of piezoelectric for potentially ferroelectric if len(d) > 1: return d return None