def from_stress_dict(cls, stress_dict, tol=0.1, vasp=True): """ Constructs the elastic tensor from IndependentStrain-Stress dictionary corresponding to legacy behavior of elasticity package. Args: stress_dict (dict): dictionary of stresses indexed by corresponding IndependentStrain objects. """ inds = [(0, 0), (1, 1), (2, 2), (1, 2), (0, 2), (0, 1)] c_ij = np.array([[ np.polyfit([ strain[ind1] for strain in list(stress_dict.keys()) if (strain.i, strain.j) == ind1 ], [ stress_dict[strain][ind2] for strain in list(stress_dict.keys()) if (strain.i, strain.j) == ind1 ], 1)[0] for ind1 in inds ] for ind2 in inds]) if vasp: c_ij *= -0.1 # Convert units/sign convention of vasp stress tensor c_ij[0:, 3:] = 0.5 * c_ij[0:, 3:] # account for voigt doubling of e4,e5,e6 c_ij = SQTensor(c_ij) c_ij = c_ij.zeroed(tol) return cls(c_ij)
def __new__(cls, strain_matrix, dfm=None): """ Create a Strain object. Note that the constructor uses __new__ rather than __init__ according to the standard method of subclassing numpy ndarrays. Note also that the default constructor does not include the deformation gradient Args: strain_matrix (3x3 array-like): the 3x3 array-like representing the Green-Lagrange strain """ obj = SQTensor(strain_matrix).view(cls) obj._dfm = dfm if not obj.is_symmetric(): raise ValueError("Strain objects must be initialized " "with a symmetric array-like.") if dfm is None: warnings.warn("Constructing a strain object without a deformation " "matrix makes many methods unusable. Use " "Strain.from_deformation to construct a Strain object" " from a deformation gradient.") elif (np.array(dfm) - obj < 1e-5).all(): warnings.warn("Warning: deformation matrix does not correspond " "to input strain_matrix value") return obj
def __new__(cls, strain_matrix, dfm=None): """ Create a Strain object. Note that the constructor uses __new__ rather than __init__ according to the standard method of subclassing numpy ndarrays. Note also that the default constructor does not include the deformation gradient Args: strain_matrix (3x3 array-like): the 3x3 array-like representing the Green-Lagrange strain """ obj = SQTensor(strain_matrix).view(cls) obj._dfm = dfm if not obj.is_symmetric(): raise ValueError("Strain objects must be initialized " "with a symmetric array-like.") if dfm is None: warnings.warn( "Constructing a strain object without a deformation " "matrix makes many methods unusable. Use " "Strain.from_deformation to construct a Strain object" " from a deformation gradient.") elif (np.array(dfm) - obj < 1e-5).all(): warnings.warn("Warning: deformation matrix does not correspond " "to input strain_matrix value") return obj
def test_zeroed(self): self.assertArrayEqual(self.low_val.zeroed(), SQTensor([[0, 1 + 1e-5, 0], [1 + 1e-6, 0, 0], [0, 0, 1 + 1e-5]])) self.assertArrayEqual(self.low_val.zeroed(tol=1e-6), SQTensor([[1e-6, 1 + 1e-5, 1e-6], [1 + 1e-6, 1e-6, 1e-6], [0, 0, 1 + 1e-5]]))
def test_rotate(self): self.assertArrayAlmostEqual(self.non_symm.rotate(self.rotation), SQTensor([[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_sqtensor)
def test_properties(self): # transpose self.assertArrayEqual(self.non_symm.trans, SQTensor([[0.1, 0.4, 0.2], [0.2, 0.5, 0.5], [0.3, 0.6, 0.5]])) self.assertArrayEqual(self.rand_sqtensor.trans, np.transpose(self.rand_sqtensor)) self.assertArrayEqual(self.symm_sqtensor, self.symm_sqtensor.trans) # inverse self.assertArrayEqual(self.non_symm.inv, np.linalg.inv(self.non_symm)) with self.assertRaises(ValueError): self.non_invertible.inv # determinant self.assertEqual(self.rand_sqtensor.det, np.linalg.det(self.rand_sqtensor)) self.assertEqual(self.non_invertible.det, 0.0) self.assertEqual(self.non_symm.det, 0.009) # symmetrized self.assertArrayEqual(self.rand_sqtensor.symmetrized, 0.5 * (self.rand_sqtensor + self.rand_sqtensor.trans)) self.assertArrayEqual(self.symm_sqtensor, self.symm_sqtensor.symmetrized) self.assertArrayAlmostEqual(self.non_symm.symmetrized, SQTensor([[0.1, 0.3, 0.25], [0.3, 0.5, 0.55], [0.25, 0.55, 0.5]])) # invariants i1 = np.trace(self.rand_sqtensor) i2 = self.rand_sqtensor[0, 0] * self.rand_sqtensor[1, 1] + \ self.rand_sqtensor[1, 1] * self.rand_sqtensor[2, 2] + \ self.rand_sqtensor[2, 2] * self.rand_sqtensor[0, 0] - \ self.rand_sqtensor[0, 1] * self.rand_sqtensor[1, 0] - \ self.rand_sqtensor[0, 2] * self.rand_sqtensor[2, 0] - \ self.rand_sqtensor[2, 1] * self.rand_sqtensor[1, 2] i3 = np.linalg.det(self.rand_sqtensor) self.assertArrayAlmostEqual([i1, i2, i3], self.rand_sqtensor.principal_invariants)
def piola_kirchoff_1(self, def_grad): """ calculates the first Piola-Kirchoff stress Args: def_gradient (3x3 array-like): deformation gradient tensor """ if not self.is_symmetric: raise ValueError("The stress tensor is not symmetric, \ PK stress is based on a symmetric stress tensor.") def_grad = SQTensor(def_grad) return def_grad.det*np.dot(self, def_grad.inv.trans)
def __new__(cls, stress_matrix): """ Create a Stress object. Note that the constructor uses __new__ rather than __init__ according to the standard method of subclassing numpy ndarrays. Args: stress_matrix (3x3 array-like): the 3x3 array-like representing the stress """ obj = SQTensor(stress_matrix).view(cls) return obj
def __new__(cls, deformation_gradient): """ Create a Deformation object. Note that the constructor uses __new__ rather than __init__ according to the standard method of subclassing numpy ndarrays. Args: deformation_gradient (3x3 array-like): the 3x3 array-like representing the deformation gradient """ obj = SQTensor(deformation_gradient).view(cls) return obj
def __new__(cls, input_matrix): """ Create an ElasticTensor object. The constructor throws an error if the shape of the input_matrix argument is not 6x6, i. e. in Voigt- notation. Also issues a warning if the input_matrix argument is not symmetric. Note that the constructor uses __new__ rather than __init__ according to the standard method of subclassing numpy ndarrays. Args: input_matrix (6x6 array-like): the Voigt-notation 6x6 array-like representing the elastic tensor """ obj = SQTensor(input_matrix).view(cls) if obj.shape != (6, 6): raise ValueError("Default elastic tensor constructor requires " "input argument to be the Voigt-notation 6x6 " "array. To construct from a 3x3x3x3 array, use " "ElasticTensor.from_full_tensor") if not obj.is_symmetric(): warnings.warn("Elastic tensor input is not symmetric!") return obj
def from_stress_dict(cls, stress_dict, tol=0.1, vasp=True): """ Constructs the elastic tensor from IndependentStrain-Stress dictionary corresponding to legacy behavior of elasticity package. Args: stress_dict (dict): dictionary of stresses indexed by corresponding IndependentStrain objects. """ inds = [(0, 0), (1, 1), (2, 2), (1, 2), (0, 2), (0, 1)] c_ij = np.array([[np.polyfit([strain[ind1] for strain in list(stress_dict.keys()) if (strain.i, strain.j) == ind1], [stress_dict[strain][ind2] for strain in list(stress_dict.keys()) if (strain.i, strain.j) == ind1], 1)[0] for ind1 in inds] for ind2 in inds]) if vasp: c_ij *= -0.1 # Convert units/sign convention of vasp stress tensor c_ij[0:, 3:] = 0.5 * c_ij[0:, 3:] # account for voigt doubling of e4,e5,e6 c_ij = SQTensor(c_ij) c_ij = c_ij.zeroed(tol) return cls(c_ij)
def piola_kirchoff_2(self, def_grad): """ calculates the second Piola-Kirchoff stress Args: f (3x3 array-like): rate of deformation tensor """ def_grad = SQTensor(def_grad) if not self.is_symmetric: raise ValueError("The stress tensor is not symmetric, \ PK stress is based on a symmetric stress tensor.") return def_grad.det*np.dot(np.dot(def_grad.inv, self), def_grad.inv.trans)
def setUp(self): self.rand_sqtensor = SQTensor(np.random.randn(3, 3)) self.symm_sqtensor = SQTensor([[0.1, 0.3, 0.4], [0.3, 0.5, 0.2], [0.4, 0.2, 0.6]]) self.non_invertible = SQTensor([[0.1, 0, 0], [0.2, 0, 0], [0, 0, 0]]) self.non_symm = SQTensor([[0.1, 0.2, 0.3], [0.4, 0.5, 0.6], [0.2, 0.5, 0.5]]) self.low_val = SQTensor([[1e-6, 1 + 1e-5, 1e-6], [1 + 1e-6, 1e-6, 1e-6], [1e-7, 1e-7, 1 + 1e-5]]) self.low_val_2 = SQTensor([[1e-6, -1 - 1e-6, 1e-6], [1 + 1e-7, 1e-6, 1e-6], [1e-7, 1e-7, 1 + 1e-6]]) a = 3.14 * 42.5 / 180 self.rotation = SQTensor([[math.cos(a), 0, math.sin(a)], [0, 1, 0], [-math.sin(a), 0, math.cos(a)]])
def test_get_scaled(self): self.assertArrayEqual(self.non_symm.get_scaled(10.), SQTensor([[1, 2, 3], [4, 5, 6], [2, 5, 5]]))
class SQTensorTest(PymatgenTest): def setUp(self): self.rand_sqtensor = SQTensor(np.random.randn(3, 3)) self.symm_sqtensor = SQTensor([[0.1, 0.3, 0.4], [0.3, 0.5, 0.2], [0.4, 0.2, 0.6]]) self.non_invertible = SQTensor([[0.1, 0, 0], [0.2, 0, 0], [0, 0, 0]]) self.non_symm = SQTensor([[0.1, 0.2, 0.3], [0.4, 0.5, 0.6], [0.2, 0.5, 0.5]]) self.low_val = SQTensor([[1e-6, 1 + 1e-5, 1e-6], [1 + 1e-6, 1e-6, 1e-6], [1e-7, 1e-7, 1 + 1e-5]]) self.low_val_2 = SQTensor([[1e-6, -1 - 1e-6, 1e-6], [1 + 1e-7, 1e-6, 1e-6], [1e-7, 1e-7, 1 + 1e-6]]) a = 3.14 * 42.5 / 180 self.rotation = SQTensor([[math.cos(a), 0, math.sin(a)], [0, 1, 0], [-math.sin(a), 0, math.cos(a)]]) def test_new(self): non_sq_matrix = [[0.1, 0.2, 0.1], [0.1, 0.2, 0.3], [0.1, 0.2, 0.3], [0.1, 0.1, 0.1]] bad_matrix = [[0.1, 0.2], [0.2, 0.3, 0.4], [0.2, 0.3, 0.5]] self.assertRaises(ValueError, SQTensor, non_sq_matrix) self.assertRaises(ValueError, SQTensor, bad_matrix) def test_properties(self): # transpose self.assertArrayEqual(self.non_symm.trans, SQTensor([[0.1, 0.4, 0.2], [0.2, 0.5, 0.5], [0.3, 0.6, 0.5]])) self.assertArrayEqual(self.rand_sqtensor.trans, np.transpose(self.rand_sqtensor)) self.assertArrayEqual(self.symm_sqtensor, self.symm_sqtensor.trans) # inverse self.assertArrayEqual(self.non_symm.inv, np.linalg.inv(self.non_symm)) with self.assertRaises(ValueError): self.non_invertible.inv # determinant self.assertEqual(self.rand_sqtensor.det, np.linalg.det(self.rand_sqtensor)) self.assertEqual(self.non_invertible.det, 0.0) self.assertEqual(self.non_symm.det, 0.009) # symmetrized self.assertArrayEqual(self.rand_sqtensor.symmetrized, 0.5 * (self.rand_sqtensor + self.rand_sqtensor.trans)) self.assertArrayEqual(self.symm_sqtensor, self.symm_sqtensor.symmetrized) self.assertArrayAlmostEqual(self.non_symm.symmetrized, SQTensor([[0.1, 0.3, 0.25], [0.3, 0.5, 0.55], [0.25, 0.55, 0.5]])) # invariants i1 = np.trace(self.rand_sqtensor) i2 = self.rand_sqtensor[0, 0] * self.rand_sqtensor[1, 1] + \ self.rand_sqtensor[1, 1] * self.rand_sqtensor[2, 2] + \ self.rand_sqtensor[2, 2] * self.rand_sqtensor[0, 0] - \ self.rand_sqtensor[0, 1] * self.rand_sqtensor[1, 0] - \ self.rand_sqtensor[0, 2] * self.rand_sqtensor[2, 0] - \ self.rand_sqtensor[2, 1] * self.rand_sqtensor[1, 2] i3 = np.linalg.det(self.rand_sqtensor) self.assertArrayAlmostEqual([i1, i2, i3], self.rand_sqtensor.principal_invariants) def test_symmetry(self): self.assertTrue(self.symm_sqtensor.is_symmetric()) self.assertFalse(self.non_symm.is_symmetric()) self.assertTrue(self.low_val.is_symmetric()) self.assertFalse(self.low_val.is_symmetric(tol=1e-8)) def test_rotation(self): self.assertTrue(self.rotation.is_rotation()) self.assertFalse(self.symm_sqtensor.is_rotation()) self.assertTrue(self.low_val_2.is_rotation()) self.assertFalse(self.low_val_2.is_rotation(tol=1e-8)) def test_rotate(self): self.assertArrayAlmostEqual(self.non_symm.rotate(self.rotation), SQTensor([[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_sqtensor) def test_get_scaled(self): self.assertArrayEqual(self.non_symm.get_scaled(10.), SQTensor([[1, 2, 3], [4, 5, 6], [2, 5, 5]])) def test_polar_decomposition(self): u, p = self.rand_sqtensor.polar_decomposition() self.assertArrayAlmostEqual(np.dot(u, p), self.rand_sqtensor) self.assertArrayAlmostEqual(np.eye(3), np.dot(u, np.conjugate(np.transpose(u)))) def test_zeroed(self): self.assertArrayEqual(self.low_val.zeroed(), SQTensor([[0, 1 + 1e-5, 0], [1 + 1e-6, 0, 0], [0, 0, 1 + 1e-5]])) self.assertArrayEqual(self.low_val.zeroed(tol=1e-6), SQTensor([[1e-6, 1 + 1e-5, 1e-6], [1 + 1e-6, 1e-6, 1e-6], [0, 0, 1 + 1e-5]]))
class SQTensorTest(PymatgenTest): def setUp(self): self.rand_sqtensor = SQTensor(np.random.randn(3, 3)) self.symm_sqtensor = SQTensor([[0.1, 0.3, 0.4], [0.3, 0.5, 0.2], [0.4, 0.2, 0.6]]) self.non_invertible = SQTensor([[0.1, 0, 0], [0.2, 0, 0], [0, 0, 0]]) self.non_symm = SQTensor([[0.1, 0.2, 0.3], [0.4, 0.5, 0.6], [0.2, 0.5, 0.5]]) self.low_val = SQTensor([[1e-6, 1 + 1e-5, 1e-6], [1 + 1e-6, 1e-6, 1e-6], [1e-7, 1e-7, 1 + 1e-5]]) self.low_val_2 = SQTensor([[1e-6, -1 - 1e-6, 1e-6], [1 + 1e-7, 1e-6, 1e-6], [1e-7, 1e-7, 1 + 1e-6]]) a = 3.14 * 42.5 / 180 self.rotation = SQTensor([[math.cos(a), 0, math.sin(a)], [0, 1, 0], [-math.sin(a), 0, math.cos(a)]]) def test_new(self): non_sq_matrix = [[0.1, 0.2, 0.1], [0.1, 0.2, 0.3], [0.1, 0.2, 0.3], [0.1, 0.1, 0.1]] bad_matrix = [[0.1, 0.2], [0.2, 0.3, 0.4], [0.2, 0.3, 0.5]] self.assertRaises(ValueError, SQTensor, non_sq_matrix) self.assertRaises(ValueError, SQTensor, bad_matrix) def test_properties(self): # transpose self.assertArrayEqual( self.non_symm.trans, SQTensor([[0.1, 0.4, 0.2], [0.2, 0.5, 0.5], [0.3, 0.6, 0.5]])) self.assertArrayEqual(self.rand_sqtensor.trans, np.transpose(self.rand_sqtensor)) self.assertArrayEqual(self.symm_sqtensor, self.symm_sqtensor.trans) # inverse self.assertArrayEqual(self.non_symm.inv, np.linalg.inv(self.non_symm)) with self.assertRaises(ValueError): self.non_invertible.inv # determinant self.assertEqual(self.rand_sqtensor.det, np.linalg.det(self.rand_sqtensor)) self.assertEqual(self.non_invertible.det, 0.0) self.assertEqual(self.non_symm.det, 0.009) # symmetrized self.assertArrayEqual( self.rand_sqtensor.symmetrized, 0.5 * (self.rand_sqtensor + self.rand_sqtensor.trans)) self.assertArrayEqual(self.symm_sqtensor, self.symm_sqtensor.symmetrized) self.assertArrayAlmostEqual( self.non_symm.symmetrized, SQTensor([[0.1, 0.3, 0.25], [0.3, 0.5, 0.55], [0.25, 0.55, 0.5]])) # invariants i1 = np.trace(self.rand_sqtensor) i2 = self.rand_sqtensor[0, 0] * self.rand_sqtensor[1, 1] + \ self.rand_sqtensor[1, 1] * self.rand_sqtensor[2, 2] + \ self.rand_sqtensor[2, 2] * self.rand_sqtensor[0, 0] - \ self.rand_sqtensor[0, 1] * self.rand_sqtensor[1, 0] - \ self.rand_sqtensor[0, 2] * self.rand_sqtensor[2, 0] - \ self.rand_sqtensor[2, 1] * self.rand_sqtensor[1, 2] i3 = np.linalg.det(self.rand_sqtensor) self.assertArrayAlmostEqual([i1, i2, i3], self.rand_sqtensor.principal_invariants) def test_symmetry(self): self.assertTrue(self.symm_sqtensor.is_symmetric()) self.assertFalse(self.non_symm.is_symmetric()) self.assertTrue(self.low_val.is_symmetric()) self.assertFalse(self.low_val.is_symmetric(tol=1e-8)) def test_rotation(self): self.assertTrue(self.rotation.is_rotation()) self.assertFalse(self.symm_sqtensor.is_rotation()) self.assertTrue(self.low_val_2.is_rotation()) self.assertFalse(self.low_val_2.is_rotation(tol=1e-8)) def test_rotate(self): self.assertArrayAlmostEqual(self.non_symm.rotate(self.rotation), SQTensor([[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_sqtensor) def test_get_scaled(self): self.assertArrayEqual(self.non_symm.get_scaled(10.), SQTensor([[1, 2, 3], [4, 5, 6], [2, 5, 5]])) def test_polar_decomposition(self): u, p = self.rand_sqtensor.polar_decomposition() self.assertArrayAlmostEqual(np.dot(u, p), self.rand_sqtensor) self.assertArrayAlmostEqual(np.eye(3), np.dot(u, np.conjugate(np.transpose(u)))) def test_zeroed(self): self.assertArrayEqual( self.low_val.zeroed(), SQTensor([[0, 1 + 1e-5, 0], [1 + 1e-6, 0, 0], [0, 0, 1 + 1e-5]])) self.assertArrayEqual( self.low_val.zeroed(tol=1e-6), SQTensor([[1e-6, 1 + 1e-5, 1e-6], [1 + 1e-6, 1e-6, 1e-6], [0, 0, 1 + 1e-5]])) self.assertArrayEqual( SQTensor([[1e-6, -30, 1], [1e-7, 1, 0], [1e-8, 0, 1]]).zeroed(), SQTensor([[0, -30, 1], [0, 1, 0], [0, 0, 1]]))