def get_rand_IST(self, max_force=1): """ Generate a random internal strain tensor which obeys a structure's symmetry and the acoustic sum rule Args: max_force(float): maximum born effective charge value Return: InternalStrainTensor object """ l = len(self.structure) IST = np.zeros((l, 3, 3, 3)) for atom, ops in enumerate(self.IST_operations): temp_tensor = np.zeros([3, 3, 3]) for op in ops: temp_tensor += op[1].transform_tensor(IST[op[0]]) if len(ops) == 0: temp_tensor = Tensor(np.random.rand(3, 3, 3) - 0.5) for dim in range(3): temp_tensor[dim] = (temp_tensor[dim] + temp_tensor[dim].T) / 2 temp_tensor = sum([ temp_tensor.transform(symm_op) for symm_op in self.pointops[atom] ]) / len(self.pointops[atom]) IST[atom] = temp_tensor if len(ops) != 0: IST[atom] = IST[atom] / len(ops) return IST * max_force
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_einsum_sequence(self): x = [1, 0, 0] test = Tensor(np.arange(0, 3**4).reshape((3, 3, 3, 3))) self.assertArrayAlmostEqual([0, 27, 54], test.einsum_sequence([x] * 3)) self.assertEqual(360, test.einsum_sequence([np.eye(3)] * 2)) self.assertRaises(ValueError, test.einsum_sequence, Tensor(np.zeros(3)))
def test_transform(self): # Rank 3 tensor = Tensor(np.arange(0, 27).reshape(3, 3, 3)) symm_op = SymmOp.from_axis_angle_and_translation([0, 0, 1], 30, False, [0, 0, 1]) new_tensor = tensor.transform(symm_op) self.assertArrayAlmostEqual( new_tensor, [ [ [-0.871, -2.884, -1.928], [-2.152, -6.665, -4.196], [-1.026, -2.830, -1.572], ], [ [0.044, 1.531, 1.804], [4.263, 21.008, 17.928], [5.170, 23.026, 18.722], ], [ [1.679, 7.268, 5.821], [9.268, 38.321, 29.919], [8.285, 33.651, 26.000], ], ], 3, )
def test_serialization(self): # Test base serialize-deserialize d = self.symm_rank2.as_dict() new = Tensor.from_dict(d) self.assertArrayAlmostEqual(new, self.symm_rank2) d = self.symm_rank3.as_dict(voigt=True) new = Tensor.from_dict(d) self.assertArrayAlmostEqual(new, self.symm_rank3)
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 ff(prop): # format tensor properties if isinstance(prop, np.ndarray): if prop.shape == (3, 3): return _tensor_str.format(*prop.ravel()) elif prop.shape == (3, 3, 3): return _piezo_tensor_str.format(*Tensor(prop).voigt.ravel()) elif prop.shape == (3, 3, 3, 3): return _elastic_tensor_str.format(*Tensor(prop).voigt.ravel()) return prop
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_zeroed(self): self.assertArrayEqual( self.low_val.zeroed(), Tensor([[0, 1 + 1e-5, 0], [1 + 1e-6, 0, 0], [0, 0, 1 + 1e-5]]), ) self.assertArrayEqual( self.low_val.zeroed(tol=1e-6), Tensor([[1e-6, 1 + 1e-5, 1e-6], [1 + 1e-6, 1e-6, 1e-6], [0, 0, 1 + 1e-5]]), ) self.assertArrayEqual( Tensor([[1e-6, -30, 1], [1e-7, 1, 0], [1e-8, 0, 1]]).zeroed(), Tensor([[0, -30, 1], [0, 1, 0], [0, 0, 1]]), )
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 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 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 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 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 get_rand_BEC(self, max_charge=1): """ Generate a random born effective charge tensor which obeys a structure's symmetry and the acoustic sum rule Args: max_charge (float): maximum born effective charge value Return: np.array Born effective charge tensor """ struc = self.structure symstruc = sga(struc) symstruc = symstruc.get_symmetrized_structure() l = len(struc) BEC = np.zeros((l, 3, 3)) for atom, ops in enumerate(self.BEC_operations): if ops[0] == ops[1]: temp_tensor = Tensor(np.random.rand(3, 3) - 0.5) temp_tensor = sum(temp_tensor.transform(symm_op) for symm_op in self.pointops[atom]) / len( self.pointops[atom] ) BEC[atom] = temp_tensor else: tempfcm = np.zeros([3, 3]) for op in ops[2]: tempfcm += op.transform_tensor(BEC[self.BEC_operations[atom][1]]) BEC[ops[0]] = tempfcm if len(ops[2]) != 0: BEC[ops[0]] = BEC[ops[0]] / len(ops[2]) # Enforce Acoustic Sum disp_charge = np.einsum("ijk->jk", BEC) / l add = np.zeros([l, 3, 3]) for atom, ops in enumerate(self.BEC_operations): if ops[0] == ops[1]: temp_tensor = Tensor(disp_charge) temp_tensor = sum(temp_tensor.transform(symm_op) for symm_op in self.pointops[atom]) / len( self.pointops[atom] ) add[ops[0]] = temp_tensor else: temp_tensor = np.zeros([3, 3]) for op in ops[2]: temp_tensor += op.transform_tensor(add[self.BEC_operations[atom][1]]) add[ops[0]] = temp_tensor if len(ops) != 0: add[ops[0]] = add[ops[0]] / len(ops[2]) BEC = BEC - add return BEC * max_charge
def test_from_values_indices(self): sn = self.get_structure("Sn") indices = [(0, 0), (0, 1), (3, 3)] values = [259.31, 160.71, 73.48] et = Tensor.from_values_indices(values, indices, structure=sn, populate=True).voigt.round(4) self.assertAlmostEqual(et[1, 1], 259.31) self.assertAlmostEqual(et[2, 2], 259.31) self.assertAlmostEqual(et[0, 2], 160.71) self.assertAlmostEqual(et[1, 2], 160.71) self.assertAlmostEqual(et[4, 4], 73.48) self.assertAlmostEqual(et[5, 5], 73.48)
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_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 test_serialization(self): # Test base serialize-deserialize d = self.rand_sqtensor.as_dict() new = SquareTensor.from_dict(d) self.assertArrayAlmostEqual(new, self.rand_sqtensor) self.assertIsInstance(new, SquareTensor) # Ensure proper object-independent deserialization obj = MontyDecoder().process_decoded(d) self.assertIsInstance(obj, SquareTensor) with warnings.catch_warnings(record=True): vsym = self.rand_sqtensor.voigt_symmetrized d_vsym = vsym.as_dict(voigt=True) new_voigt = Tensor.from_dict(d_vsym) self.assertArrayAlmostEqual(vsym, new_voigt)
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 test_convert_to_ieee(self): for entry in self.ieee_data: xtal = entry["xtal"] struct = entry["structure"] orig = Tensor(entry["original_tensor"]) ieee = Tensor(entry["ieee_tensor"]) diff = np.max(abs(ieee - orig.convert_to_ieee(struct))) err_msg = f"{xtal} IEEE conversion failed with max diff {diff}. Numpy version: {np.__version__}" converted = orig.convert_to_ieee(struct, refine_rotation=False) self.assertArrayAlmostEqual(ieee, converted, err_msg=err_msg, decimal=3) converted_refined = orig.convert_to_ieee(struct, refine_rotation=True) err_msg = "{} IEEE conversion with refinement failed with max diff {}. Numpy version: {}".format( xtal, diff, np.__version__) self.assertArrayAlmostEqual(ieee, converted_refined, err_msg=err_msg, decimal=2)
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)
class TensorTest(PymatgenTest): _multiprocess_shared_ = True def setUp(self): self.vec = Tensor([1.0, 0.0, 0.0]) self.rand_rank2 = Tensor(np.random.randn(3, 3)) self.rand_rank3 = Tensor(np.random.randn(3, 3, 3)) self.rand_rank4 = Tensor(np.random.randn(3, 3, 3, 3)) a = 3.14 * 42.5 / 180 self.non_symm = SquareTensor([[0.1, 0.2, 0.3], [0.4, 0.5, 0.6], [0.2, 0.5, 0.5]]) self.rotation = SquareTensor([[math.cos(a), 0, math.sin(a)], [0, 1, 0], [-math.sin(a), 0, math.cos(a)]]) self.low_val = Tensor([[1e-6, 1 + 1e-5, 1e-6], [1 + 1e-6, 1e-6, 1e-6], [1e-7, 1e-7, 1 + 1e-5]]) self.symm_rank2 = Tensor([[1, 2, 3], [2, 4, 5], [3, 5, 6]]) self.symm_rank3 = Tensor([ [[1, 2, 3], [2, 4, 5], [3, 5, 6]], [[2, 4, 5], [4, 7, 8], [5, 8, 9]], [[3, 5, 6], [5, 8, 9], [6, 9, 10]], ]) self.symm_rank4 = Tensor([ [ [[1.2, 0.4, -0.92], [0.4, 0.05, 0.11], [-0.92, 0.11, -0.02]], [[0.4, 0.05, 0.11], [0.05, -0.47, 0.09], [0.11, 0.09, -0.0]], [[-0.92, 0.11, -0.02], [0.11, 0.09, 0.0], [-0.02, 0.0, -0.3]], ], [ [[0.4, 0.05, 0.11], [0.05, -0.47, 0.09], [0.11, 0.09, 0.0]], [[0.05, -0.47, 0.09], [-0.47, 0.17, 0.62], [0.09, 0.62, 0.3]], [[0.11, 0.09, 0.0], [0.09, 0.62, 0.3], [0.0, 0.3, -0.18]], ], [ [[-0.92, 0.11, -0.02], [0.11, 0.09, 0.0], [-0.02, 0, -0.3]], [[0.11, 0.09, 0.0], [0.09, 0.62, 0.3], [0.0, 0.3, -0.18]], [[-0.02, 0.0, -0.3], [0.0, 0.3, -0.18], [-0.3, -0.18, -0.51]], ], ]) # Structural symmetries tested using BaNiO3 piezo/elastic tensors self.fit_r3 = Tensor([ [[0.0, 0.0, 0.03839], [0.0, 0.0, 0.0], [0.03839, 0.0, 0.0]], [[0.0, 0.0, 0.0], [0.0, 0.0, 0.03839], [0.0, 0.03839, 0.0]], [[6.89822, 0.0, 0.0], [0.0, 6.89822, 0.0], [0.0, 0.0, 27.4628]], ]) self.fit_r4 = Tensor([ [ [[157.9, 0.0, 0.0], [0.0, 63.1, 0.0], [0.0, 0.0, 29.4]], [[0.0, 47.4, 0.0], [47.4, 0.0, 0.0], [0.0, 0.0, 0.0]], [[0.0, 0.0, 4.3], [0.0, 0.0, 0.0], [4.3, 0.0, 0.0]], ], [ [[0.0, 47.4, 0.0], [47.4, 0.0, 0.0], [0.0, 0.0, 0.0]], [[63.1, 0.0, 0.0], [0.0, 157.9, 0.0], [0.0, 0.0, 29.4]], [[0.0, 0.0, 0.0], [0.0, 0.0, 4.3], [0.0, 4.3, 0.0]], ], [ [[0.0, 0.0, 4.3], [0.0, 0.0, 0.0], [4.3, 0.0, 0.0]], [[0.0, 0.0, 0.0], [0.0, 0.0, 4.3], [0.0, 4.3, 0.0]], [[29.4, 0.0, 0.0], [0.0, 29.4, 0.0], [0.0, 0.0, 207.6]], ], ]) self.unfit4 = Tensor([ [ [[161.26, 0.0, 0.0], [0.0, 62.76, 0.0], [0.0, 0.0, 30.18]], [[0.0, 47.08, 0.0], [47.08, 0.0, 0.0], [0.0, 0.0, 0.0]], [[0.0, 0.0, 4.23], [0.0, 0.0, 0.0], [4.23, 0.0, 0.0]], ], [ [[0.0, 47.08, 0.0], [47.08, 0.0, 0.0], [0.0, 0.0, 0.0]], [[62.76, 0.0, 0.0], [0.0, 155.28, -0.06], [0.0, -0.06, 28.53]], [[0.0, 0.0, 0.0], [0.0, -0.06, 4.44], [0.0, 4.44, 0.0]], ], [ [[0.0, 0.0, 4.23], [0.0, 0.0, 0.0], [4.23, 0.0, 0.0]], [[0.0, 0.0, 0.0], [0.0, -0.06, 4.44], [0.0, 4.44, 0.0]], [[30.18, 0.0, 0.0], [0.0, 28.53, 0.0], [0.0, 0.0, 207.57]], ], ]) self.structure = self.get_structure("BaNiO3") ieee_file_path = os.path.join(PymatgenTest.TEST_FILES_DIR, "ieee_conversion_data.json") self.ones = Tensor(np.ones((3, 3))) self.ieee_data = loadfn(ieee_file_path) def test_new(self): bad_2 = np.zeros((4, 4)) bad_3 = np.zeros((4, 4, 4)) self.assertRaises(ValueError, Tensor, bad_2) self.assertRaises(ValueError, Tensor, bad_3) self.assertEqual(self.rand_rank2.rank, 2) self.assertEqual(self.rand_rank3.rank, 3) self.assertEqual(self.rand_rank4.rank, 4) def test_zeroed(self): self.assertArrayEqual( self.low_val.zeroed(), Tensor([[0, 1 + 1e-5, 0], [1 + 1e-6, 0, 0], [0, 0, 1 + 1e-5]]), ) self.assertArrayEqual( self.low_val.zeroed(tol=1e-6), Tensor([[1e-6, 1 + 1e-5, 1e-6], [1 + 1e-6, 1e-6, 1e-6], [0, 0, 1 + 1e-5]]), ) self.assertArrayEqual( Tensor([[1e-6, -30, 1], [1e-7, 1, 0], [1e-8, 0, 1]]).zeroed(), Tensor([[0, -30, 1], [0, 1, 0], [0, 0, 1]]), ) def test_transform(self): # Rank 3 tensor = Tensor(np.arange(0, 27).reshape(3, 3, 3)) symm_op = SymmOp.from_axis_angle_and_translation([0, 0, 1], 30, False, [0, 0, 1]) new_tensor = tensor.transform(symm_op) self.assertArrayAlmostEqual( new_tensor, [ [ [-0.871, -2.884, -1.928], [-2.152, -6.665, -4.196], [-1.026, -2.830, -1.572], ], [ [0.044, 1.531, 1.804], [4.263, 21.008, 17.928], [5.170, 23.026, 18.722], ], [ [1.679, 7.268, 5.821], [9.268, 38.321, 29.919], [8.285, 33.651, 26.000], ], ], 3, ) def test_rotate(self): self.assertArrayEqual( self.vec.rotate([[0, -1, 0], [1, 0, 0], [0, 0, 1]]), [0, 1, 0]) self.assertArrayAlmostEqual( self.non_symm.rotate(self.rotation), SquareTensor([[0.531, 0.485, 0.271], [0.700, 0.5, 0.172], [0.171, 0.233, 0.068]]), decimal=3, ) self.assertRaises(ValueError, self.non_symm.rotate, self.symm_rank2) def test_einsum_sequence(self): x = [1, 0, 0] test = Tensor(np.arange(0, 3**4).reshape((3, 3, 3, 3))) self.assertArrayAlmostEqual([0, 27, 54], test.einsum_sequence([x] * 3)) self.assertEqual(360, test.einsum_sequence([np.eye(3)] * 2)) self.assertRaises(ValueError, test.einsum_sequence, Tensor(np.zeros(3))) def test_symmetrized(self): self.assertTrue(self.rand_rank2.symmetrized.is_symmetric()) self.assertTrue(self.rand_rank3.symmetrized.is_symmetric()) self.assertTrue(self.rand_rank4.symmetrized.is_symmetric()) def test_is_symmetric(self): self.assertTrue(self.symm_rank2.is_symmetric()) self.assertTrue(self.symm_rank3.is_symmetric()) self.assertTrue(self.symm_rank4.is_symmetric()) tol_test = self.symm_rank4 tol_test[0, 1, 2, 2] += 1e-6 self.assertFalse(self.low_val.is_symmetric(tol=1e-8)) def test_fit_to_structure(self): new_fit = self.unfit4.fit_to_structure(self.structure) self.assertArrayAlmostEqual(new_fit, self.fit_r4, 1) def test_is_fit_to_structure(self): self.assertFalse(self.unfit4.is_fit_to_structure(self.structure)) self.assertTrue(self.fit_r3.is_fit_to_structure(self.structure)) self.assertTrue(self.fit_r4.is_fit_to_structure(self.structure)) def test_convert_to_ieee(self): for entry in self.ieee_data: xtal = entry["xtal"] struct = entry["structure"] orig = Tensor(entry["original_tensor"]) ieee = Tensor(entry["ieee_tensor"]) diff = np.max(abs(ieee - orig.convert_to_ieee(struct))) err_msg = f"{xtal} IEEE conversion failed with max diff {diff}. Numpy version: {np.__version__}" converted = orig.convert_to_ieee(struct, refine_rotation=False) self.assertArrayAlmostEqual(ieee, converted, err_msg=err_msg, decimal=3) converted_refined = orig.convert_to_ieee(struct, refine_rotation=True) err_msg = "{} IEEE conversion with refinement failed with max diff {}. Numpy version: {}".format( xtal, diff, np.__version__) self.assertArrayAlmostEqual(ieee, converted_refined, err_msg=err_msg, decimal=2) def test_structure_transform(self): # Test trivial case trivial = self.fit_r4.structure_transform(self.structure, self.structure.copy()) self.assertArrayAlmostEqual(trivial, self.fit_r4) # Test simple rotation rot_symm_op = SymmOp.from_axis_angle_and_translation([1, 1, 1], 55.5) rot_struct = self.structure.copy() rot_struct.apply_operation(rot_symm_op) rot_tensor = self.fit_r4.rotate(rot_symm_op.rotation_matrix) trans_tensor = self.fit_r4.structure_transform(self.structure, rot_struct) self.assertArrayAlmostEqual(rot_tensor, trans_tensor) # Test supercell bigcell = self.structure.copy() bigcell.make_supercell([2, 2, 3]) trans_tensor = self.fit_r4.structure_transform(self.structure, bigcell) self.assertArrayAlmostEqual(self.fit_r4, trans_tensor) # Test rotated primitive to conventional for fcc structure sn = self.get_structure("Sn") sn_prim = SpacegroupAnalyzer(sn).get_primitive_standard_structure() sn_prim.apply_operation(rot_symm_op) rotated = self.fit_r4.rotate(rot_symm_op.rotation_matrix) transformed = self.fit_r4.structure_transform(sn, sn_prim) self.assertArrayAlmostEqual(rotated, transformed) 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 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_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_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_from_values_indices(self): sn = self.get_structure("Sn") indices = [(0, 0), (0, 1), (3, 3)] values = [259.31, 160.71, 73.48] et = Tensor.from_values_indices(values, indices, structure=sn, populate=True).voigt.round(4) self.assertAlmostEqual(et[1, 1], 259.31) self.assertAlmostEqual(et[2, 2], 259.31) self.assertAlmostEqual(et[0, 2], 160.71) self.assertAlmostEqual(et[1, 2], 160.71) self.assertAlmostEqual(et[4, 4], 73.48) self.assertAlmostEqual(et[5, 5], 73.48) def test_serialization(self): # Test base serialize-deserialize d = self.symm_rank2.as_dict() new = Tensor.from_dict(d) self.assertArrayAlmostEqual(new, self.symm_rank2) d = self.symm_rank3.as_dict(voigt=True) new = Tensor.from_dict(d) self.assertArrayAlmostEqual(new, self.symm_rank3) def test_projection_methods(self): self.assertAlmostEqual(self.rand_rank2.project([1, 0, 0]), self.rand_rank2[0, 0]) self.assertAlmostEqual(self.rand_rank2.project([1, 1, 1]), np.sum(self.rand_rank2) / 3) # Test integration self.assertArrayAlmostEqual(self.ones.average_over_unit_sphere(), 1) def test_summary_methods(self): self.assertEqual( set(self.ones.get_grouped_indices()[0]), set(itertools.product(range(3), range(3))), ) self.assertEqual( self.ones.get_grouped_indices(voigt=True)[0], [(i, ) for i in range(6)]) self.assertEqual(self.ones.get_symbol_dict(), {"T_1": 1}) self.assertEqual(self.ones.get_symbol_dict(voigt=False), {"T_11": 1}) def test_round(self): test = self.non_symm + 0.01 rounded = test.round(1) self.assertArrayAlmostEqual(rounded, self.non_symm) self.assertTrue(isinstance(rounded, Tensor))
def setUp(self): self.vec = Tensor([1.0, 0.0, 0.0]) self.rand_rank2 = Tensor(np.random.randn(3, 3)) self.rand_rank3 = Tensor(np.random.randn(3, 3, 3)) self.rand_rank4 = Tensor(np.random.randn(3, 3, 3, 3)) a = 3.14 * 42.5 / 180 self.non_symm = SquareTensor([[0.1, 0.2, 0.3], [0.4, 0.5, 0.6], [0.2, 0.5, 0.5]]) self.rotation = SquareTensor([[math.cos(a), 0, math.sin(a)], [0, 1, 0], [-math.sin(a), 0, math.cos(a)]]) self.low_val = Tensor([[1e-6, 1 + 1e-5, 1e-6], [1 + 1e-6, 1e-6, 1e-6], [1e-7, 1e-7, 1 + 1e-5]]) self.symm_rank2 = Tensor([[1, 2, 3], [2, 4, 5], [3, 5, 6]]) self.symm_rank3 = Tensor([ [[1, 2, 3], [2, 4, 5], [3, 5, 6]], [[2, 4, 5], [4, 7, 8], [5, 8, 9]], [[3, 5, 6], [5, 8, 9], [6, 9, 10]], ]) self.symm_rank4 = Tensor([ [ [[1.2, 0.4, -0.92], [0.4, 0.05, 0.11], [-0.92, 0.11, -0.02]], [[0.4, 0.05, 0.11], [0.05, -0.47, 0.09], [0.11, 0.09, -0.0]], [[-0.92, 0.11, -0.02], [0.11, 0.09, 0.0], [-0.02, 0.0, -0.3]], ], [ [[0.4, 0.05, 0.11], [0.05, -0.47, 0.09], [0.11, 0.09, 0.0]], [[0.05, -0.47, 0.09], [-0.47, 0.17, 0.62], [0.09, 0.62, 0.3]], [[0.11, 0.09, 0.0], [0.09, 0.62, 0.3], [0.0, 0.3, -0.18]], ], [ [[-0.92, 0.11, -0.02], [0.11, 0.09, 0.0], [-0.02, 0, -0.3]], [[0.11, 0.09, 0.0], [0.09, 0.62, 0.3], [0.0, 0.3, -0.18]], [[-0.02, 0.0, -0.3], [0.0, 0.3, -0.18], [-0.3, -0.18, -0.51]], ], ]) # Structural symmetries tested using BaNiO3 piezo/elastic tensors self.fit_r3 = Tensor([ [[0.0, 0.0, 0.03839], [0.0, 0.0, 0.0], [0.03839, 0.0, 0.0]], [[0.0, 0.0, 0.0], [0.0, 0.0, 0.03839], [0.0, 0.03839, 0.0]], [[6.89822, 0.0, 0.0], [0.0, 6.89822, 0.0], [0.0, 0.0, 27.4628]], ]) self.fit_r4 = Tensor([ [ [[157.9, 0.0, 0.0], [0.0, 63.1, 0.0], [0.0, 0.0, 29.4]], [[0.0, 47.4, 0.0], [47.4, 0.0, 0.0], [0.0, 0.0, 0.0]], [[0.0, 0.0, 4.3], [0.0, 0.0, 0.0], [4.3, 0.0, 0.0]], ], [ [[0.0, 47.4, 0.0], [47.4, 0.0, 0.0], [0.0, 0.0, 0.0]], [[63.1, 0.0, 0.0], [0.0, 157.9, 0.0], [0.0, 0.0, 29.4]], [[0.0, 0.0, 0.0], [0.0, 0.0, 4.3], [0.0, 4.3, 0.0]], ], [ [[0.0, 0.0, 4.3], [0.0, 0.0, 0.0], [4.3, 0.0, 0.0]], [[0.0, 0.0, 0.0], [0.0, 0.0, 4.3], [0.0, 4.3, 0.0]], [[29.4, 0.0, 0.0], [0.0, 29.4, 0.0], [0.0, 0.0, 207.6]], ], ]) self.unfit4 = Tensor([ [ [[161.26, 0.0, 0.0], [0.0, 62.76, 0.0], [0.0, 0.0, 30.18]], [[0.0, 47.08, 0.0], [47.08, 0.0, 0.0], [0.0, 0.0, 0.0]], [[0.0, 0.0, 4.23], [0.0, 0.0, 0.0], [4.23, 0.0, 0.0]], ], [ [[0.0, 47.08, 0.0], [47.08, 0.0, 0.0], [0.0, 0.0, 0.0]], [[62.76, 0.0, 0.0], [0.0, 155.28, -0.06], [0.0, -0.06, 28.53]], [[0.0, 0.0, 0.0], [0.0, -0.06, 4.44], [0.0, 4.44, 0.0]], ], [ [[0.0, 0.0, 4.23], [0.0, 0.0, 0.0], [4.23, 0.0, 0.0]], [[0.0, 0.0, 0.0], [0.0, -0.06, 4.44], [0.0, 4.44, 0.0]], [[30.18, 0.0, 0.0], [0.0, 28.53, 0.0], [0.0, 0.0, 207.57]], ], ]) self.structure = self.get_structure("BaNiO3") ieee_file_path = os.path.join(PymatgenTest.TEST_FILES_DIR, "ieee_conversion_data.json") self.ones = Tensor(np.ones((3, 3))) self.ieee_data = loadfn(ieee_file_path)
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 get_asum_FCM(self, fcm, numiter=15): """ Generate a symmeterized force constant matrix that obeys the objects symmetry constraints and obeys the acoustic sum rule through an iterative procedure Args: fcm (numpy array): 3Nx3N unsymmeterized force constant matrix numiter (int): number of iterations to attempt to obey the acoustic sum rule Return: numpy array representing the force constant matrix """ # set max force in reciprocal space operations = self.FCM_operations numsites = len(self.structure) D = np.ones([numsites * 3, numsites * 3]) for num in range(numiter): X = np.real(fcm) # symmetry operations pastrow = 0 total = np.zeros([3, 3]) for col in range(numsites): total = total + X[0:3, col * 3:col * 3 + 3] total = total / (numsites) for op in operations: same = 0 transpose = 0 if op[0] == op[1] and op[0] == op[2] and op[0] == op[3]: same = 1 if op[0] == op[3] and op[1] == op[2]: transpose = 1 if transpose == 0 and same == 0: D[3 * op[0]:3 * op[0] + 3, 3 * op[1]:3 * op[1] + 3] = np.zeros([3, 3]) for symop in op[4]: tempfcm = D[3 * op[2]:3 * op[2] + 3, 3 * op[3]:3 * op[3] + 3] tempfcm = symop.transform_tensor(tempfcm) D[3 * op[0]:3 * op[0] + 3, 3 * op[1]:3 * op[1] + 3] += tempfcm if len(op[4]) != 0: D[3 * op[0]:3 * op[0] + 3, 3 * op[1]:3 * op[1] + 3] = D[3 * op[0]:3 * op[0] + 3, 3 * op[1]:3 * op[1] + 3] / len(op[4]) D[3 * op[1]:3 * op[1] + 3, 3 * op[0]:3 * op[0] + 3] = D[3 * op[0]:3 * op[0] + 3, 3 * op[1]:3 * op[1] + 3].T continue # Get the difference in the sum up to this point currrow = op[0] if currrow != pastrow: total = np.zeros([3, 3]) for col in range(numsites): total = total + X[currrow * 3:currrow * 3 + 3, col * 3:col * 3 + 3] for col in range(currrow): total = total - D[currrow * 3:currrow * 3 + 3, col * 3:col * 3 + 3] total = total / (numsites - currrow) pastrow = currrow # Apply the point symmetry operations of the site temp_tensor = Tensor(total) temp_tensor_sum = sum([ temp_tensor.transform(symm_op) for symm_op in self.sharedops[op[0]][op[1]] ]) if len(self.sharedops[op[0]][op[1]]) != 0: temp_tensor_sum = temp_tensor_sum / (len( self.sharedops[op[0]][op[1]])) # Apply the proper transformation if there is an equivalent already if op[0] != op[1]: for pair in range(len(op[4])): temp_tensor2 = temp_tensor_sum.T temp_tensor2 = op[4][pair].transform_tensor( temp_tensor2) temp_tensor_sum = (temp_tensor_sum + temp_tensor2) / 2 else: temp_tensor_sum = (temp_tensor_sum + temp_tensor_sum.T) / 2 D[3 * op[0]:3 * op[0] + 3, 3 * op[1]:3 * op[1] + 3] = temp_tensor_sum D[3 * op[1]:3 * op[1] + 3, 3 * op[0]:3 * op[0] + 3] = temp_tensor_sum.T fcm = fcm - D return fcm
def get_symmetrized_FCM(self, unsymmetrized_fcm, max_force=1): """ Generate a symmeterized force constant matrix from an unsymmeterized matrix Args: unsymmetrized_fcm (numpy array): unsymmeterized force constant matrix max_charge (float): maximum born effective charge value Return: 3Nx3N numpy array representing the force constant matrix """ operations = self.FCM_operations D = unsymmetrized_fcm for op in operations: same = 0 transpose = 0 if op[0] == op[1] and op[0] == operations[2] and op[0] == op[3]: same = 1 if op[0] == op[3] and op[1] == op[2]: transpose = 1 if transpose == 0 and same == 0: D[3 * op[0]:3 * op[0] + 3, 3 * op[1]:3 * op[1] + 3] = np.zeros([3, 3]) for symop in op[4]: tempfcm = D[3 * op[2]:3 * op[2] + 3, 3 * op[3]:3 * op[3] + 3] tempfcm = symop.transform_tensor(tempfcm) D[3 * op[0]:3 * op[0] + 3, 3 * op[1]:3 * op[1] + 3] += tempfcm if len(op[4]) != 0: D[3 * op[0]:3 * op[0] + 3, 3 * op[1]:3 * op[1] + 3] = D[3 * op[0]:3 * op[0] + 3, 3 * op[1]:3 * op[1] + 3] / len(op[4]) D[3 * op[1]:3 * op[1] + 3, 3 * op[0]:3 * op[0] + 3] = D[3 * op[0]:3 * op[0] + 3, 3 * op[1]:3 * op[1] + 3].T continue temp_tensor = Tensor(D[3 * op[0]:3 * op[0] + 3, 3 * op[1]:3 * op[1] + 3]) temp_tensor_sum = sum([ temp_tensor.transform(symm_op) for symm_op in self.sharedops[op[0]][op[1]] ]) if len(self.sharedops[op[0]][op[1]]) != 0: temp_tensor_sum = temp_tensor_sum / (len( self.sharedops[op[0]][op[1]])) # Apply the proper transformation if there is an equivalent already if op[0] != op[1]: for pair in range(len(op[4])): temp_tensor2 = temp_tensor_sum.T temp_tensor2 = op[4][pair].transform_tensor(temp_tensor2) temp_tensor_sum = (temp_tensor_sum + temp_tensor2) / 2 else: temp_tensor_sum = (temp_tensor_sum + temp_tensor_sum.T) / 2 D[3 * op[0]:3 * op[0] + 3, 3 * op[1]:3 * op[1] + 3] = temp_tensor_sum D[3 * op[1]:3 * op[1] + 3, 3 * op[0]:3 * op[0] + 3] = temp_tensor_sum.T return D
def get_unstable_FCM(self, max_force=1): """ Generate an unsymmeterized force constant matrix Args: max_charge (float): maximum born effective charge value Return: numpy array representing the force constant matrix """ struc = self.structure operations = self.FCM_operations # set max force in reciprocal space numsites = len(struc.sites) D = (1 / max_force) * 2 * (np.ones([numsites * 3, numsites * 3])) for op in operations: same = 0 transpose = 0 if op[0] == op[1] and op[0] == op[2] and op[0] == op[3]: same = 1 if op[0] == op[3] and op[1] == op[2]: transpose = 1 if transpose == 0 and same == 0: D[3 * op[0]:3 * op[0] + 3, 3 * op[1]:3 * op[1] + 3] = np.zeros([3, 3]) D[3 * op[1]:3 * op[1] + 3, 3 * op[0]:3 * op[0] + 3] = np.zeros([3, 3]) for symop in op[4]: tempfcm = D[3 * op[2]:3 * op[2] + 3, 3 * op[3]:3 * op[3] + 3] tempfcm = symop.transform_tensor(tempfcm) D[3 * op[0]:3 * op[0] + 3, 3 * op[1]:3 * op[1] + 3] += tempfcm if len(op[4]) != 0: D[3 * op[0]:3 * op[0] + 3, 3 * op[1]:3 * op[1] + 3] = D[3 * op[0]:3 * op[0] + 3, 3 * op[1]:3 * op[1] + 3] / len(op[4]) D[3 * op[1]:3 * op[1] + 3, 3 * op[0]:3 * op[0] + 3] = D[3 * op[0]:3 * op[0] + 3, 3 * op[1]:3 * op[1] + 3].T continue temp_tensor = Tensor(np.random.rand(3, 3) - 0.5) * max_force temp_tensor_sum = sum([ temp_tensor.transform(symm_op) for symm_op in self.sharedops[op[0]][op[1]] ]) temp_tensor_sum = temp_tensor_sum / (len( self.sharedops[op[0]][op[1]])) if op[0] != op[1]: for pair in range(len(op[4])): temp_tensor2 = temp_tensor_sum.T temp_tensor2 = op[4][pair].transform_tensor(temp_tensor2) temp_tensor_sum = (temp_tensor_sum + temp_tensor2) / 2 else: temp_tensor_sum = (temp_tensor_sum + temp_tensor_sum.T) / 2 D[3 * op[0]:3 * op[0] + 3, 3 * op[1]:3 * op[1] + 3] = temp_tensor_sum D[3 * op[1]:3 * op[1] + 3, 3 * op[0]:3 * op[0] + 3] = temp_tensor_sum.T return D
- 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)