def test_convert_to_ieee(self): for xtal in self.ieee_data.keys(): orig = TensorBase(self.ieee_data[xtal]['original_tensor']) ieee = TensorBase(self.ieee_data[xtal]['ieee_tensor']) struct = Structure.from_dict(self.ieee_data[xtal]['structure']) diff = np.max(abs(ieee - orig.convert_to_ieee(struct))) err_msg = "{} IEEE conversion failed with max diff {}".format( xtal, diff) self.assertArrayAlmostEqual(ieee, orig.convert_to_ieee(struct), err_msg = err_msg, decimal=3)
def test_zeroed(self): self.assertArrayEqual( self.low_val.zeroed(), TensorBase([[0, 1 + 1e-5, 0], [1 + 1e-6, 0, 0], [0, 0, 1 + 1e-5]])) self.assertArrayEqual( self.low_val.zeroed(tol=1e-6), TensorBase([[1e-6, 1 + 1e-5, 1e-6], [1 + 1e-6, 1e-6, 1e-6], [0, 0, 1 + 1e-5]])) self.assertArrayEqual( TensorBase([[1e-6, -30, 1], [1e-7, 1, 0], [1e-8, 0, 1]]).zeroed(), TensorBase([[0, -30, 1], [0, 1, 0], [0, 0, 1]]))
def test_convert_to_ieee(self): for entry in self.ieee_data: xtal = entry['xtal'] orig = TensorBase(entry['original_tensor']) ieee = TensorBase(entry['ieee_tensor']) struct = Structure.from_dict(entry['structure']) diff = np.max(abs(ieee - orig.convert_to_ieee(struct))) err_msg = "{} IEEE conversion failed with max diff {}".format( xtal, diff) self.assertArrayAlmostEqual(ieee, orig.convert_to_ieee(struct), err_msg = err_msg, decimal=3)
def test_from_voigt(self): with self.assertRaises(ValueError): TensorBase.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 TensorBase.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]])
def test_transform(self): # Rank 3 tensor = TensorBase(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 __new__(cls, input_array, tol=1e-3): """ Create an ElasticTensor object. The constructor throws an error if the shape of the input_matrix argument is not 3x3x3x3, i. e. in true tensor notation. Issues a warning if the input_matrix argument does not satisfy standard symmetries. Note that the constructor uses __new__ rather than __init__ according to the standard method of subclassing numpy ndarrays. Args: input_array (3x3x3x3 array-like): the 3x3x3x3 array-like representing the elastic tensor tol (float): tolerance for initial symmetry test of tensor """ obj = TensorBase(input_array).view(cls) if obj.shape != (3, 3, 3, 3): raise ValueError("Default elastic tensor constructor requires " "input to be the true 3x3x3x3 representation. " "To construct from an elastic tensor from " "6x6 Voigt array, use ElasticTensor.from_voigt") if not ((obj - np.transpose(obj, (1, 0, 2, 3)) < tol).all() and (obj - np.transpose(obj, (0, 1, 3, 2)) < tol).all() and (obj - np.transpose(obj, (1, 0, 3, 2)) < tol).all() and (obj - np.transpose(obj, (3, 2, 0, 1)) < tol).all()): warnings.warn("Input elasticity tensor does " "not satisfy standard symmetries") return obj
def test_transform(self): # Rank 3 tensor = TensorBase(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 __new__(cls, input_array, tol=1e-3): """ Create an PiezoTensor object. The constructor throws an error if the shape of the input_matrix argument is not 3x3x3, i. e. in true tensor notation. Note that the constructor uses __new__ rather than __init__ according to the standard method of subclassing numpy ndarrays. Args: input_matrix (3x3x3 array-like): the 3x6 array-like representing the piezo tensor """ obj = TensorBase(input_array).view(cls) if obj.shape != (3, 3, 3): raise ValueError("Default piezo tensor constructor requires " "input argument to be the true 3x3x3 " "array. To construct from a 3x6 array, use " "PiezoTensor.from_voigt") if not (obj - np.transpose(obj, (0, 2, 1)) < tol).all(): warnings.warn("Input piezo tensor does " "not satisfy standard symmetries") return obj
def setUp(self): self.vec = TensorBase([1., 0., 0.]) self.rand_rank2 = TensorBase(np.random.randn(3, 3)) self.rand_rank3 = TensorBase(np.random.randn(3, 3, 3)) self.rand_rank4 = TensorBase(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 = TensorBase([[1e-6, 1 + 1e-5, 1e-6], [1 + 1e-6, 1e-6, 1e-6], [1e-7, 1e-7, 1 + 1e-5]]) self.symm_rank2 = TensorBase([[1, 2, 3], [2, 4, 5], [3, 5, 6]]) self.symm_rank3 = TensorBase([[[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 = TensorBase([[[[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.92, 0.11, -0.02], [0.11, 0.09, 0.], [-0.02, 0., -0.3]]], [[[0.4, 0.05, 0.11], [0.05, -0.47, 0.09], [0.11, 0.09, 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.09, 0.62, 0.3], [0., 0.3, -0.18]]], [[[-0.92, 0.11, -0.02], [0.11, 0.09, 0.], [-0.02, 0, -0.3]], [[0.11, 0.09, 0.], [0.09, 0.62, 0.3], [0., 0.3, -0.18]], [[-0.02, 0., -0.3], [0., 0.3, -0.18], [-0.3, -0.18, -0.51]]]]) # Structural symmetries tested using BaNiO3 piezo/elastic tensors self.fit_r3 = TensorBase([[[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]]]) self.fit_r4 = TensorBase([[[[157.9, 0., 0.], [0., 63.1, 0.], [0., 0., 29.4]], [[0., 47.4, 0.], [47.4, 0., 0.], [0., 0., 0.]], [[0., 0., 4.3], [0., 0., 0.], [4.3, 0., 0.]]], [[[0., 47.4, 0.], [47.4, 0., 0.], [0., 0., 0.]], [[63.1, 0., 0.], [0., 157.9, 0.], [0., 0., 29.4]], [[0., 0., 0.], [0., 0., 4.3], [0., 4.3, 0.]]], [[[0., 0., 4.3], [0., 0., 0.], [4.3, 0., 0.]], [[0., 0., 0.], [0., 0., 4.3], [0., 4.3, 0.]], [[29.4, 0., 0.], [0., 29.4, 0.], [0., 0., 207.6]]]]) self.unfit4 = TensorBase([[[[161.26, 0., 0.], [0., 62.76, 0.], [0., 0., 30.18]], [[0., 47.08, 0.], [47.08, 0., 0.], [0., 0., 0.]], [[0., 0., 4.23], [0., 0., 0.], [4.23, 0., 0.]]], [[[0., 47.08, 0.], [47.08, 0., 0.], [0., 0., 0.]], [[62.76, 0., 0.], [0., 155.28, -0.06], [0., -0.06, 28.53]], [[0., 0., 0.], [0., -0.06, 4.44], [0., 4.44, 0.]]], [[[0., 0., 4.23], [0., 0., 0.], [4.23, 0., 0.]], [[0., 0., 0.], [0., -0.06, 4.44], [0., 4.44, 0.]], [[30.18, 0., 0.], [0., 28.53, 0.], [0., 0., 207.57]]]]) self.structure = self.get_structure('BaNiO3') ieee_file_path = os.path.join(test_dir, "ieee_conversion_data.json") with open(ieee_file_path) as f: self.ieee_data = json.load(f)
class TensorBaseTest(PymatgenTest): def setUp(self): self.vec = TensorBase([1., 0., 0.]) self.rand_rank2 = TensorBase(np.random.randn(3, 3)) self.rand_rank3 = TensorBase(np.random.randn(3, 3, 3)) self.rand_rank4 = TensorBase(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 = TensorBase([[1e-6, 1 + 1e-5, 1e-6], [1 + 1e-6, 1e-6, 1e-6], [1e-7, 1e-7, 1 + 1e-5]]) self.symm_rank2 = TensorBase([[1, 2, 3], [2, 4, 5], [3, 5, 6]]) self.symm_rank3 = TensorBase([[[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 = TensorBase([[[[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.92, 0.11, -0.02], [0.11, 0.09, 0.], [-0.02, 0., -0.3]]], [[[0.4, 0.05, 0.11], [0.05, -0.47, 0.09], [0.11, 0.09, 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.09, 0.62, 0.3], [0., 0.3, -0.18]]], [[[-0.92, 0.11, -0.02], [0.11, 0.09, 0.], [-0.02, 0, -0.3]], [[0.11, 0.09, 0.], [0.09, 0.62, 0.3], [0., 0.3, -0.18]], [[-0.02, 0., -0.3], [0., 0.3, -0.18], [-0.3, -0.18, -0.51]]]]) # Structural symmetries tested using BaNiO3 piezo/elastic tensors self.fit_r3 = TensorBase([[[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]]]) self.fit_r4 = TensorBase([[[[157.9, 0., 0.], [0., 63.1, 0.], [0., 0., 29.4]], [[0., 47.4, 0.], [47.4, 0., 0.], [0., 0., 0.]], [[0., 0., 4.3], [0., 0., 0.], [4.3, 0., 0.]]], [[[0., 47.4, 0.], [47.4, 0., 0.], [0., 0., 0.]], [[63.1, 0., 0.], [0., 157.9, 0.], [0., 0., 29.4]], [[0., 0., 0.], [0., 0., 4.3], [0., 4.3, 0.]]], [[[0., 0., 4.3], [0., 0., 0.], [4.3, 0., 0.]], [[0., 0., 0.], [0., 0., 4.3], [0., 4.3, 0.]], [[29.4, 0., 0.], [0., 29.4, 0.], [0., 0., 207.6]]]]) self.unfit4 = TensorBase([[[[161.26, 0., 0.], [0., 62.76, 0.], [0., 0., 30.18]], [[0., 47.08, 0.], [47.08, 0., 0.], [0., 0., 0.]], [[0., 0., 4.23], [0., 0., 0.], [4.23, 0., 0.]]], [[[0., 47.08, 0.], [47.08, 0., 0.], [0., 0., 0.]], [[62.76, 0., 0.], [0., 155.28, -0.06], [0., -0.06, 28.53]], [[0., 0., 0.], [0., -0.06, 4.44], [0., 4.44, 0.]]], [[[0., 0., 4.23], [0., 0., 0.], [4.23, 0., 0.]], [[0., 0., 0.], [0., -0.06, 4.44], [0., 4.44, 0.]], [[30.18, 0., 0.], [0., 28.53, 0.], [0., 0., 207.57]]]]) self.structure = self.get_structure('BaNiO3') ieee_file_path = os.path.join(test_dir, "ieee_conversion_data.json") with open(ieee_file_path) as f: self.ieee_data = json.load(f) def test_new(self): bad_2 = np.zeros((4, 4)) bad_3 = np.zeros((4, 4, 4)) self.assertRaises(ValueError, TensorBase, bad_2) self.assertRaises(ValueError, TensorBase, 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(), TensorBase([[0, 1 + 1e-5, 0], [1 + 1e-6, 0, 0], [0, 0, 1 + 1e-5]])) self.assertArrayEqual( self.low_val.zeroed(tol=1e-6), TensorBase([[1e-6, 1 + 1e-5, 1e-6], [1 + 1e-6, 1e-6, 1e-6], [0, 0, 1 + 1e-5]])) self.assertArrayEqual( TensorBase([[1e-6, -30, 1], [1e-7, 1, 0], [1e-8, 0, 1]]).zeroed(), TensorBase([[0, -30, 1], [0, 1, 0], [0, 0, 1]])) def test_transform(self): # Rank 3 tensor = TensorBase(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_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 xtal in self.ieee_data.keys(): orig = TensorBase(self.ieee_data[xtal]['original_tensor']) ieee = TensorBase(self.ieee_data[xtal]['ieee_tensor']) struct = Structure.from_dict(self.ieee_data[xtal]['structure']) diff = np.max(abs(ieee - orig.convert_to_ieee(struct))) err_msg = "{} IEEE conversion failed with max diff {}".format( xtal, diff) self.assertArrayAlmostEqual(ieee, orig.convert_to_ieee(struct), err_msg=err_msg, decimal=3)
def setUp(self): self.vec = TensorBase([1., 0., 0.]) self.rand_rank2 = TensorBase(np.random.randn(3,3)) self.rand_rank3 = TensorBase(np.random.randn(3,3,3)) self.rand_rank4 = TensorBase(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 = TensorBase([[1e-6, 1 + 1e-5, 1e-6], [1 + 1e-6, 1e-6, 1e-6], [1e-7, 1e-7, 1 + 1e-5]]) self.symm_rank2 = TensorBase([[1, 2, 3], [2, 4, 5], [3, 5, 6]]) self.symm_rank3 = TensorBase([[[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 = TensorBase([[[[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.92, 0.11, -0.02], [0.11, 0.09, 0.], [-0.02, 0., -0.3]]], [[[0.4, 0.05, 0.11], [0.05, -0.47, 0.09], [0.11, 0.09, 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.09, 0.62, 0.3], [0., 0.3, -0.18]]], [[[-0.92, 0.11, -0.02], [0.11, 0.09, 0.], [-0.02, 0, -0.3]], [[0.11, 0.09, 0.], [0.09, 0.62, 0.3], [0., 0.3, -0.18]], [[-0.02, 0., -0.3], [0., 0.3, -0.18], [-0.3, -0.18, -0.51]]]]) # Structural symmetries tested using BaNiO3 piezo/elastic tensors self.fit_r3 = TensorBase([[[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]]]) self.fit_r4 = TensorBase([[[[157.9, 0., 0.], [0., 63.1, 0.], [0., 0., 29.4]], [[0., 47.4, 0.], [47.4, 0., 0.], [0., 0., 0.]], [[0., 0., 4.3], [0., 0., 0.], [4.3, 0., 0.]]], [[[0., 47.4, 0.], [47.4, 0., 0.], [0., 0., 0.]], [[63.1, 0., 0.], [0., 157.9, 0.], [0., 0., 29.4]], [[0., 0., 0.], [0., 0., 4.3], [0., 4.3, 0.]]], [[[0., 0., 4.3], [0., 0., 0.], [4.3, 0., 0.]], [[0., 0., 0.], [0., 0., 4.3], [0., 4.3, 0.]], [[29.4, 0., 0.], [0., 29.4, 0.], [0., 0., 207.6]]]]) self.unfit4 = TensorBase([[[[161.26, 0., 0.], [0., 62.76, 0.], [0., 0., 30.18]], [[0., 47.08, 0.], [47.08, 0., 0.], [0., 0., 0.]], [[0., 0., 4.23], [0., 0., 0.], [4.23, 0., 0.]]], [[[0., 47.08, 0.], [47.08, 0., 0.], [0., 0., 0.]], [[62.76, 0., 0.], [0., 155.28, -0.06], [0., -0.06, 28.53]], [[0., 0., 0.], [0., -0.06, 4.44], [0., 4.44, 0.]]], [[[0., 0., 4.23], [0., 0., 0.], [4.23, 0., 0.]], [[0., 0., 0.], [0., -0.06, 4.44], [0., 4.44, 0.]], [[30.18, 0., 0.], [0., 28.53, 0.], [0., 0., 207.57]]]]) self.structure = self.get_structure('BaNiO3') ieee_file_path = os.path.join(test_dir, "ieee_conversion_data.json") with open(ieee_file_path) as f: self.ieee_data = json.load(f)
class TensorBaseTest(PymatgenTest): def setUp(self): self.vec = TensorBase([1., 0., 0.]) self.rand_rank2 = TensorBase(np.random.randn(3,3)) self.rand_rank3 = TensorBase(np.random.randn(3,3,3)) self.rand_rank4 = TensorBase(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 = TensorBase([[1e-6, 1 + 1e-5, 1e-6], [1 + 1e-6, 1e-6, 1e-6], [1e-7, 1e-7, 1 + 1e-5]]) self.symm_rank2 = TensorBase([[1, 2, 3], [2, 4, 5], [3, 5, 6]]) self.symm_rank3 = TensorBase([[[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 = TensorBase([[[[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.92, 0.11, -0.02], [0.11, 0.09, 0.], [-0.02, 0., -0.3]]], [[[0.4, 0.05, 0.11], [0.05, -0.47, 0.09], [0.11, 0.09, 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.09, 0.62, 0.3], [0., 0.3, -0.18]]], [[[-0.92, 0.11, -0.02], [0.11, 0.09, 0.], [-0.02, 0, -0.3]], [[0.11, 0.09, 0.], [0.09, 0.62, 0.3], [0., 0.3, -0.18]], [[-0.02, 0., -0.3], [0., 0.3, -0.18], [-0.3, -0.18, -0.51]]]]) # Structural symmetries tested using BaNiO3 piezo/elastic tensors self.fit_r3 = TensorBase([[[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]]]) self.fit_r4 = TensorBase([[[[157.9, 0., 0.], [0., 63.1, 0.], [0., 0., 29.4]], [[0., 47.4, 0.], [47.4, 0., 0.], [0., 0., 0.]], [[0., 0., 4.3], [0., 0., 0.], [4.3, 0., 0.]]], [[[0., 47.4, 0.], [47.4, 0., 0.], [0., 0., 0.]], [[63.1, 0., 0.], [0., 157.9, 0.], [0., 0., 29.4]], [[0., 0., 0.], [0., 0., 4.3], [0., 4.3, 0.]]], [[[0., 0., 4.3], [0., 0., 0.], [4.3, 0., 0.]], [[0., 0., 0.], [0., 0., 4.3], [0., 4.3, 0.]], [[29.4, 0., 0.], [0., 29.4, 0.], [0., 0., 207.6]]]]) self.unfit4 = TensorBase([[[[161.26, 0., 0.], [0., 62.76, 0.], [0., 0., 30.18]], [[0., 47.08, 0.], [47.08, 0., 0.], [0., 0., 0.]], [[0., 0., 4.23], [0., 0., 0.], [4.23, 0., 0.]]], [[[0., 47.08, 0.], [47.08, 0., 0.], [0., 0., 0.]], [[62.76, 0., 0.], [0., 155.28, -0.06], [0., -0.06, 28.53]], [[0., 0., 0.], [0., -0.06, 4.44], [0., 4.44, 0.]]], [[[0., 0., 4.23], [0., 0., 0.], [4.23, 0., 0.]], [[0., 0., 0.], [0., -0.06, 4.44], [0., 4.44, 0.]], [[30.18, 0., 0.], [0., 28.53, 0.], [0., 0., 207.57]]]]) self.structure = self.get_structure('BaNiO3') ieee_file_path = os.path.join(test_dir, "ieee_conversion_data.json") with open(ieee_file_path) as f: self.ieee_data = json.load(f) def test_new(self): bad_2 = np.zeros((4, 4)) bad_3 = np.zeros((4, 4, 4)) self.assertRaises(ValueError, TensorBase, bad_2) self.assertRaises(ValueError, TensorBase, 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(), TensorBase([[0, 1 + 1e-5, 0], [1 + 1e-6, 0, 0], [0, 0, 1 + 1e-5]])) self.assertArrayEqual(self.low_val.zeroed(tol=1e-6), TensorBase([[1e-6, 1 + 1e-5, 1e-6], [1 + 1e-6, 1e-6, 1e-6], [0, 0, 1 + 1e-5]])) self.assertArrayEqual(TensorBase([[1e-6, -30, 1], [1e-7, 1, 0], [1e-8, 0, 1]]).zeroed(), TensorBase([[0, -30, 1], [0, 1, 0], [0, 0, 1]])) def test_transform(self): # Rank 3 tensor = TensorBase(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_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 xtal in self.ieee_data.keys(): orig = TensorBase(self.ieee_data[xtal]['original_tensor']) ieee = TensorBase(self.ieee_data[xtal]['ieee_tensor']) struct = Structure.from_dict(self.ieee_data[xtal]['structure']) diff = np.max(abs(ieee - orig.convert_to_ieee(struct))) err_msg = "{} IEEE conversion failed with max diff {}".format( xtal, diff) self.assertArrayAlmostEqual(ieee, orig.convert_to_ieee(struct), err_msg = err_msg, decimal=3)
def toec_fit(strains, stresses, eq_stress = None, zero_crit=1e-10): """ A third-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 and second 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 second and third-order elastic constants appropriately. Args: strains (nx3x3 array-like): Array of 3x3 strains to use in fitting of TOEC and SOEC stresses (nx3x3 array-like): Array of 3x3 stresses to use in fitting of TOEC and SOEC. 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. zero_crit (float): value for which strains below are ignored in identifying strain states. """ if len(stresses) != len(strains): raise ValueError("Length of strains and stresses are not equivalent") vstresses = np.array([Stress(stress).voigt for stress in stresses]) vstrains = np.array([Strain(strain).voigt for strain in strains]) vstrains[np.abs(vstrains) < zero_crit] = 0 # Try to find eq_stress if not specified if eq_stress is not None: veq_stress = Stress(eq_stress).voigt else: veq_stress = vstresses[np.all(vstrains==0, axis=1)] if veq_stress: if np.shape(veq_stress) > 1 and not \ (abs(veq_stress - veq_stress[0]) < 1e-8).all(): raise ValueError("Multiple stresses found for equilibrium strain" " state, please specify equilibrium stress or " " remove extraneous stresses.") veq_stress = veq_stress[0] else: veq_stress = np.zeros(6) # Collect independent strain states: independent = set([tuple(np.nonzero(vstrain)[0].tolist()) for vstrain in vstrains]) strain_states = [] dsde = np.zeros((6, len(independent))) d2sde2 = np.zeros((6, len(independent))) for n, ind in enumerate(independent): # match strains with templates template = np.zeros(6, dtype=bool) np.put(template, ind, True) template = np.tile(template, [vstresses.shape[0], 1]) mode = (template == (np.abs(vstrains) > 1e-10)).all(axis=1) mstresses = vstresses[mode] mstrains = vstrains[mode] # add zero strain state mstrains = np.vstack([mstrains, np.zeros(6)]) mstresses = np.vstack([mstresses, np.zeros(6)]) # sort strains/stresses by strain values mstresses = mstresses[mstrains[:, ind[0]].argsort()] mstrains = mstrains[mstrains[:, ind[0]].argsort()] strain_states.append(mstrains[-1] / \ np.min(mstrains[-1][np.nonzero(mstrains[0])])) diff = np.diff(mstrains, axis=0) if not (abs(diff - diff[0]) < 1e-8).all(): raise ValueError("Stencil for strain state {} must be odd-sampling" " centered at 0.".format(ind)) h = np.min(diff[np.nonzero(diff)]) coef1 = central_diff_weights(len(mstresses), 1) coef2 = central_diff_weights(len(mstresses), 2) if eq_stress is not None: mstresses[len(mstresses) // 2] = veq_stress dsde[:, n] = np.dot(np.transpose(mstresses), coef1) / h d2sde2[:, n] = np.dot(np.transpose(mstresses), coef2) / h**2 m2i, m3i = generate_pseudo(strain_states) s2vec = np.ravel(dsde.T) c2vec = np.dot(m2i, s2vec) c2 = np.zeros((6, 6)) c2[np.triu_indices(6)] = c2vec c2 = c2 + c2.T - np.diag(np.diag(c2)) c3 = np.zeros((6, 6, 6)) s3vec = np.ravel(d2sde2.T) c3vec = np.dot(m3i, s3vec) list_indices = list(itertools.combinations_with_replacement(range(6), r=3)) indices_ij = itertools.combinations_with_replacement(range(6), r=3) indices = list(itertools.combinations_with_replacement(range(6), r=3)) for n, (i, j, k) in enumerate(indices): c3[i,j,k] = c3[i,k,j] = c3[j,i,k] = c3[j,k,i] = \ c3[k,i,j] = c3[k,j,i] = c3vec[n] return TensorBase.from_voigt(c2), TensorBase.from_voigt(c3)
def test_convert_to_ieee(self): for xtal in self.ieee_data.keys(): orig = TensorBase(self.ieee_data[xtal]['original_tensor']) ieee = TensorBase(self.ieee_data[xtal]['ieee_tensor']) struct = Structure.from_dict(self.ieee_data[xtal]['structure']) self.assertArrayAlmostEqual(ieee, orig.convert_to_ieee(struct))