def test_convert_strain_to_deformation(self): strain = Tensor(np.random.random((3, 3))).symmetrized while not (np.linalg.eigvals(strain) > 0).all(): strain = Tensor(np.random.random((3, 3))).symmetrized upper = convert_strain_to_deformation(strain, shape="upper") symm = convert_strain_to_deformation(strain, shape="symmetric") self.assertArrayAlmostEqual(np.triu(upper), upper) self.assertTrue(Tensor(symm).is_symmetric()) for defo in upper, symm: self.assertArrayAlmostEqual(defo.green_lagrange_strain, strain)
def diff_fit(strains, stresses, eq_stress=None, order=2, tol=1e-10): """ nth order elastic constant fitting function based on central-difference derivatives with respect to distinct strain states. The algorithm is summarized as follows: 1. Identify distinct strain states as sets of indices for which nonzero strain values exist, typically [(0), (1), (2), (3), (4), (5), (0, 1) etc.] 2. For each strain state, find and sort strains and stresses by strain value. 3. Find first, second .. nth derivatives of each stress with respect to scalar variable corresponding to the smallest perturbation in the strain. 4. Use the pseudoinverse of a matrix-vector expression corresponding to the parameterized stress-strain relationship and multiply that matrix by the respective calculated first or second derivatives from the previous step. 5. Place the calculated nth-order elastic constants appropriately. Args: order (int): order of the elastic tensor set to return strains (nx3x3 array-like): Array of 3x3 strains to use in fitting of ECs stresses (nx3x3 array-like): Array of 3x3 stresses to use in fitting ECs. These should be PK2 stresses. eq_stress (3x3 array-like): stress corresponding to equilibrium strain (i. e. "0" strain state). If not specified, function will try to find the state in the list of provided stresses and strains. If not found, defaults to 0. tol (float): value for which strains below are ignored in identifying strain states. Returns: Set of tensors corresponding to nth order expansion of the stress/strain relation """ strain_state_dict = get_strain_state_dict( strains, stresses, eq_stress=eq_stress, tol=tol, add_eq=True, sort=True) # Collect derivative data c_list = [] dei_dsi = np.zeros((order - 1, 6, len(strain_state_dict))) for n, (strain_state, data) in enumerate(strain_state_dict.items()): hvec = data["strains"][:, strain_state.index(1)] for i in range(1, order): coef = get_diff_coeff(hvec, i) dei_dsi[i-1, :, n] = np.dot(coef, data["stresses"]) m, absent = generate_pseudo(list(strain_state_dict.keys()), order) for i in range(1, order): cvec, carr = get_symbol_list(i+1) svec = np.ravel(dei_dsi[i-1].T) cmap = dict(zip(cvec, np.dot(m[i-1], svec))) c_list.append(v_subs(carr, cmap)) return [Tensor.from_voigt(c) for c in c_list]
def get_symmetric_wallace_tensor(self, tau): """ Gets the symmetrized wallace tensor for determining yield strength criteria. Args: tau (3x3 array-like): stress at which to evaluate the wallace tensor. """ wallace = self.get_wallace_tensor(tau) return Tensor(0.5 * (wallace + np.transpose(wallace, [2, 3, 0, 1])))
def generate_elastic_workflow(structure, tags=None): """ Generates a standard production workflow. Notes: Uses a primitive structure transformed into the conventional basis (for equivalent deformations). Adds the "minimal" category to the minimal portion of the workflow necessary to generate the elastic tensor, and the "minimal_full_stencil" category to the portion that includes all of the strain stencil, but is symmetrically complete """ if tags == None: tags = [] # transform the structure ieee_rot = Tensor.get_ieee_rotation(structure) if not SquareTensor(ieee_rot).is_rotation(tol=0.005): raise ValueError("Rotation matrix does not satisfy rotation conditions") symm_op = SymmOp.from_rotation_and_translation(ieee_rot) ieee_structure = structure.copy() ieee_structure.apply_operation(symm_op) # construct workflow wf = wf_elastic_constant(ieee_structure) # Set categories, starting with optimization opt_fws = get_fws_and_tasks(wf, fw_name_constraint="optimization") wf.fws[opt_fws[0][0]].spec['elastic_category'] = "minimal" # find minimal set of fireworks using symmetry reduction fws_by_strain = {Strain(fw.tasks[-1]['pass_dict']['strain']): n for n, fw in enumerate(wf.fws) if 'deformation' in fw.name} unique_tensors = symmetry_reduce(list(fws_by_strain.keys()), ieee_structure) for unique_tensor in unique_tensors: fw_index = get_tkd_value(fws_by_strain, unique_tensor) if np.isclose(unique_tensor, 0.005).any(): wf.fws[fw_index].spec['elastic_category'] = "minimal" else: wf.fws[fw_index].spec['elastic_category'] = "minimal_full_stencil" # Add tags if tags: wf = add_tags(wf, tags) wf = add_modify_incar(wf) priority = 500 - structure.num_sites wf = add_priority(wf, priority) for fw in wf.fws: if fw.spec.get('elastic_category') == 'minimal': fw.spec['_priority'] += 2000 elif fw.spec.get('elastic_category') == 'minimal_full_stencil': fw.spec['_priority'] += 1000 return wf
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 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