def cast_elastic_tensor( elastic_tensor: Union[int, float, List[List[float]], np.ndarray] ) -> np.ndarray: """Cast elastic tensor from single value or Voigt to full 3x3x3x3 tensor. Args: elastic_tensor: A single number, 6x6 Voigt tensor, or 3x3x3x3 tensor. Returns: The elastic constant as a 3x3x3x3 tensor. """ from pymatgen.core.tensors import Tensor from amset.constants import numeric_types if isinstance(elastic_tensor, numeric_types): elastic_tensor = np.eye(6) * elastic_tensor elastic_tensor[([3, 4, 5], [3, 4, 5])] /= 2 elastic_tensor = np.array(elastic_tensor) if elastic_tensor.shape == (6, 6): elastic_tensor = Tensor.from_voigt(elastic_tensor) if elastic_tensor.shape != (3, 3, 3, 3): raise ValueError( "Unsupported elastic tensor shape. Should be (6, 6) or (3, 3, 3, 3)." ) return np.array(elastic_tensor)
def setUp(self): with open(os.path.join(test_dir, 'test_toec_data.json')) as f: self.data_dict = json.load(f) self.strains = [Strain(sm) for sm in self.data_dict['strains']] self.pk_stresses = [Stress(d) for d in self.data_dict['pk_stresses']] self.c2 = self.data_dict["C2_raw"] self.c3 = self.data_dict["C3_raw"] self.exp = ElasticTensorExpansion.from_voigt([self.c2, self.c3]) self.cu = Structure.from_spacegroup("Fm-3m", Lattice.cubic(3.623), ["Cu"], [[0] * 3]) indices = [(0, 0), (0, 1), (3, 3)] values = [167.8, 113.5, 74.5] cu_c2 = ElasticTensor.from_values_indices(values, indices, structure=self.cu, populate=True) indices = [(0, 0, 0), (0, 0, 1), (0, 1, 2), (0, 3, 3), (0, 5, 5), (3, 4, 5)] values = [-1507., -965., -71., -7., -901., 45.] cu_c3 = Tensor.from_values_indices(values, indices, structure=self.cu, populate=True) self.exp_cu = ElasticTensorExpansion([cu_c2, cu_c3]) cu_c4 = Tensor.from_voigt(self.data_dict["Cu_fourth_order"]) self.exp_cu_4 = ElasticTensorExpansion([cu_c2, cu_c3, cu_c4]) warnings.simplefilter("ignore")
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 test_symmetry_reduce(self): tbs = [Tensor.from_voigt(row) for row in np.eye(6) * 0.01] reduced = symmetry_reduce(tbs, self.get_structure("Sn")) self.assertEqual(len(reduced), 2) self.assertArrayEqual([len(i) for i in reduced.values()], [2, 2]) reconstructed = [] for k, v in reduced.items(): reconstructed.extend([k.voigt] + [k.transform(op).voigt for op in v]) reconstructed = sorted(reconstructed, key=lambda x: np.argmax(x)) self.assertArrayAlmostEqual([tb for tb in reconstructed], np.eye(6) * 0.01)
def test_populate(self): test_data = loadfn( os.path.join(PymatgenTest.TEST_FILES_DIR, "test_toec_data.json")) sn = self.get_structure("Sn") vtens = np.zeros((6, 6)) vtens[0, 0] = 259.31 vtens[0, 1] = 160.71 vtens[3, 3] = 73.48 et = Tensor.from_voigt(vtens) populated = et.populate(sn, prec=1e-3).voigt.round(2) self.assertAlmostEqual(populated[1, 1], 259.31) self.assertAlmostEqual(populated[2, 2], 259.31) self.assertAlmostEqual(populated[0, 2], 160.71) self.assertAlmostEqual(populated[1, 2], 160.71) self.assertAlmostEqual(populated[4, 4], 73.48) self.assertAlmostEqual(populated[5, 5], 73.48) # test a rank 6 example vtens = np.zeros([6] * 3) indices = [(0, 0, 0), (0, 0, 1), (0, 1, 2), (0, 3, 3), (0, 5, 5), (3, 4, 5)] values = [-1271.0, -814.0, -50.0, -3.0, -780.0, -95.0] for v, idx in zip(values, indices): vtens[idx] = v toec = Tensor.from_voigt(vtens) toec = toec.populate(sn, prec=1e-3, verbose=True) self.assertAlmostEqual(toec.voigt[1, 1, 1], -1271) self.assertAlmostEqual(toec.voigt[0, 1, 1], -814) self.assertAlmostEqual(toec.voigt[0, 2, 2], -814) self.assertAlmostEqual(toec.voigt[1, 4, 4], -3) self.assertAlmostEqual(toec.voigt[2, 5, 5], -3) self.assertAlmostEqual(toec.voigt[1, 2, 0], -50) self.assertAlmostEqual(toec.voigt[4, 5, 3], -95) et = Tensor.from_voigt(test_data["C3_raw"]).fit_to_structure(sn) new = np.zeros(et.voigt.shape) for idx in indices: new[idx] = et.voigt[idx] new = Tensor.from_voigt(new).populate(sn) self.assertArrayAlmostEqual(new, et, decimal=2)
def test_tensor_mapping(self): # Test get tbs = [Tensor.from_voigt(row) for row in np.eye(6) * 0.01] reduced = symmetry_reduce(tbs, self.get_structure("Sn")) tkey = Tensor.from_values_indices([0.01], [(0, 0)]) tval = reduced[tkey] for tens_1, tens_2 in zip(tval, reduced[tbs[0]]): self.assertAlmostEqual(tens_1, tens_2) # Test set reduced[tkey] = "test_val" self.assertEqual(reduced[tkey], "test_val") # Test empty initialization empty = TensorMapping() self.assertEqual(empty._tensor_list, [])
def test_from_voigt(self): with self.assertRaises(ValueError): Tensor.from_voigt([ [59.33, 28.08, 28.08, 0], [28.08, 59.31, 28.07, 0], [28.08, 28.07, 59.32, 0, 0], [0, 0, 0, 26.35, 0], [0, 0, 0, 0, 26.35], ]) # Rank 4 Tensor.from_voigt([ [59.33, 28.08, 28.08, 0, 0, 0], [28.08, 59.31, 28.07, 0, 0, 0], [28.08, 28.07, 59.32, 0, 0, 0], [0, 0, 0, 26.35, 0, 0], [0, 0, 0, 0, 26.35, 0], [0, 0, 0, 0, 0, 26.35], ]) # Rank 3 Tensor.from_voigt(np.zeros((3, 6))) # Rank 2 Tensor.from_voigt(np.zeros(6)) # Addresses occasional cast issues for integers Tensor.from_voigt(np.arange(6))
def cast_piezoelectric_tensor( piezoelectric_tensor: Union[np.ndarray, List[List[float]], np.ndarray] ) -> np.ndarray: """Cast piezoelectric tensor from Voigt form to full 3x3x3 tensor. Args: piezoelectric_tensor: A 3x6 Voigt tensor, or 3x3x3 tensor. Returns: The piezoelectric constant as a 3x3x3 tensor. """ from pymatgen.core.tensors import Tensor piezoelectric_tensor = np.array(piezoelectric_tensor) if piezoelectric_tensor.shape == (3, 6): piezoelectric_tensor = Tensor.from_voigt(piezoelectric_tensor) if piezoelectric_tensor.shape != (3, 3, 3): raise ValueError( "Unsupported piezoelectric tensor shape. Should be (3, 6) or (3, 3, 3)." ) return np.array(piezoelectric_tensor)
def setUp(self): with open(os.path.join(test_dir, 'test_toec_data.json')) as f: self.data_dict = json.load(f) self.strains = [Strain(sm) for sm in self.data_dict['strains']] self.pk_stresses = [Stress(d) for d in self.data_dict['pk_stresses']] self.c2 = self.data_dict["C2_raw"] self.c3 = self.data_dict["C3_raw"] self.exp = ElasticTensorExpansion.from_voigt([self.c2, self.c3]) self.cu = Structure.from_spacegroup("Fm-3m", Lattice.cubic(3.623), ["Cu"], [[0]*3]) indices = [(0, 0), (0, 1), (3, 3)] values = [167.8, 113.5, 74.5] cu_c2 = ElasticTensor.from_values_indices(values, indices, structure=self.cu, populate=True) indices = [(0, 0, 0), (0, 0, 1), (0, 1, 2), (0, 3, 3), (0, 5, 5), (3, 4, 5)] values = [-1507., -965., -71., -7., -901., 45.] cu_c3 = Tensor.from_values_indices(values, indices, structure=self.cu, populate=True) self.exp_cu = ElasticTensorExpansion([cu_c2, cu_c3]) cu_c4 = Tensor.from_voigt(self.data_dict["Cu_fourth_order"]) self.exp_cu_4 = ElasticTensorExpansion([cu_c2, cu_c3, cu_c4]) warnings.simplefilter("ignore")
def test_init(self): cijkl = Tensor.from_voigt(self.c2) cijklmn = Tensor.from_voigt(self.c3) exp = ElasticTensorExpansion([cijkl, cijklmn]) from_voigt = ElasticTensorExpansion.from_voigt([self.c2, self.c3]) self.assertEqual(exp.order, 3)
- np.identity(3)) / 2.0, h))) eps_voigt = np.empty([6, eps_mat.shape[0]], "d") eps_voigt[0] = eps_mat[:, 0, 0] eps_voigt[1] = eps_mat[:, 1, 1] eps_voigt[2] = eps_mat[:, 2, 2] eps_voigt[3] = eps_mat[:, 2, 1] eps_voigt[4] = eps_mat[:, 2, 0] eps_voigt[5] = eps_mat[:, 1, 0] eps_voigt_mean = np.mean(eps_voigt, axis=1) Smat = np.zeros([6, 6], "d") for i in range(6): for j in range(i, 6): Smat[i, j] = np.mean(eps_voigt[i] * eps_voigt[j]) - eps_voigt_mean[i] * eps_voigt_mean[j] if i != j: Smat[j, i] = Smat[i, j] T = 300 V = np.linalg.det(h0) Cmat = np.linalg.inv(Smat) * ((1.3806488E-23 * T) / (V * 1E-30)) * 1E-9 Cmat = np.around(Cmat, decimals=2) print(Cmat) Cmat_eig = np.linalg.eigvals(Cmat) mechanical_eig_max.append(max(Cmat_eig)) mechanical_eig_min.append(min(Cmat_eig)) print(mechanical_eig_max) print(mechanical_eig_min) mat = Tensor.from_voigt(Cmat) elastic = ElasticTensor(mat) print(elastic.k_vrh) #np.savetxt("pcu_1.cij", Cmat)
def test_init(self): cijkl = Tensor.from_voigt(self.c2) cijklmn = Tensor.from_voigt(self.c3) exp = ElasticTensorExpansion([cijkl, cijklmn]) from_voigt = ElasticTensorExpansion.from_voigt([self.c2, self.c3]) self.assertEqual(exp.order, 3)
def test_list_based_functions(self): # zeroed tc = TensorCollection([1e-4 * Tensor(np.eye(3))] * 4) for t in tc.zeroed(): self.assertArrayEqual(t, np.zeros((3, 3))) for t in tc.zeroed(1e-5): self.assertArrayEqual(t, 1e-4 * np.eye(3)) self.list_based_function_check("zeroed", tc) self.list_based_function_check("zeroed", tc, tol=1e-5) # transform symm_op = SymmOp.from_axis_angle_and_translation([0, 0, 1], 30, False, [0, 0, 1]) self.list_based_function_check("transform", self.seq_tc, symm_op=symm_op) # symmetrized self.list_based_function_check("symmetrized", self.seq_tc) # rotation a = 3.14 * 42.5 / 180 rotation = SquareTensor([[math.cos(a), 0, math.sin(a)], [0, 1, 0], [-math.sin(a), 0, math.cos(a)]]) self.list_based_function_check("rotate", self.diff_rank, matrix=rotation) # is_symmetric self.assertFalse(self.seq_tc.is_symmetric()) self.assertTrue(self.diff_rank.is_symmetric()) # fit_to_structure self.list_based_function_check("fit_to_structure", self.diff_rank, self.struct) self.list_based_function_check("fit_to_structure", self.seq_tc, self.struct) # fit_to_structure self.list_based_function_check("fit_to_structure", self.diff_rank, self.struct) self.list_based_function_check("fit_to_structure", self.seq_tc, self.struct) # voigt self.list_based_function_check("voigt", self.diff_rank) # is_voigt_symmetric self.assertTrue(self.diff_rank.is_voigt_symmetric()) self.assertFalse(self.seq_tc.is_voigt_symmetric()) # Convert to ieee for entry in self.ieee_data[:2]: entry["xtal"] tc = TensorCollection([entry["original_tensor"]] * 3) struct = entry["structure"] self.list_based_function_check("convert_to_ieee", tc, struct) # from_voigt tc_input = [t for t in np.random.random((3, 6, 6))] tc = TensorCollection.from_voigt(tc_input) for t_input, t in zip(tc_input, tc): self.assertArrayAlmostEqual(Tensor.from_voigt(t_input), t)