general tensor functionality
This commit is contained in:
parent
894a8de9f9
commit
6e5cb60132
|
@ -1,4 +1,12 @@
|
||||||
"""Tools for pre and post processing of DAMASK simulations."""
|
"""
|
||||||
|
Tools for pre and post processing of DAMASK simulations.
|
||||||
|
|
||||||
|
Modules that contain only one class (of the same name),
|
||||||
|
are prefixed by a '_'. For example, '_colormap' contains
|
||||||
|
a class called 'Colormap' which is imported as 'damask.Colormap'.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
from pathlib import Path as _Path
|
from pathlib import Path as _Path
|
||||||
import re as _re
|
import re as _re
|
||||||
|
|
||||||
|
|
|
@ -649,7 +649,7 @@ class Result:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _add_deviator(T):
|
def _add_deviator(T):
|
||||||
return {
|
return {
|
||||||
'data': mechanics.deviatoric_part(T['data']),
|
'data': tensor.deviatoric(T['data']),
|
||||||
'label': f"s_{T['label']}",
|
'label': f"s_{T['label']}",
|
||||||
'meta': {
|
'meta': {
|
||||||
'Unit': T['meta']['Unit'],
|
'Unit': T['meta']['Unit'],
|
||||||
|
@ -968,7 +968,7 @@ class Result:
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _add_spherical(T):
|
def _add_spherical(T):
|
||||||
return {
|
return {
|
||||||
'data': mechanics.spherical_part(T['data']),
|
'data': tensor.spherical(T['data'],False),
|
||||||
'label': f"p_{T['label']}",
|
'label': f"p_{T['label']}",
|
||||||
'meta': {
|
'meta': {
|
||||||
'Unit': T['meta']['Unit'],
|
'Unit': T['meta']['Unit'],
|
||||||
|
|
|
@ -17,7 +17,7 @@ def deformation_Cauchy_Green_left(F):
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
B : numpy.ndarray of shape (...,3,3)
|
B : numpy.ndarray of shape (...,3,3)
|
||||||
Left Cauchy-Green deformation _tensor.
|
Left Cauchy-Green deformation tensor.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return _np.matmul(F,_tensor.transpose(F))
|
return _np.matmul(F,_tensor.transpose(F))
|
||||||
|
@ -35,30 +35,12 @@ def deformation_Cauchy_Green_right(F):
|
||||||
Returns
|
Returns
|
||||||
-------
|
-------
|
||||||
C : numpy.ndarray of shape (...,3,3)
|
C : numpy.ndarray of shape (...,3,3)
|
||||||
Right Cauchy-Green deformation _tensor.
|
Right Cauchy-Green deformation tensor.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return _np.matmul(_tensor.transpose(F),F)
|
return _np.matmul(_tensor.transpose(F),F)
|
||||||
|
|
||||||
|
|
||||||
def deviatoric_part(T):
|
|
||||||
"""
|
|
||||||
Calculate deviatoric part of a tensor.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
T : numpy.ndarray of shape (...,3,3)
|
|
||||||
Tensor of which the deviatoric part is computed.
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
T' : numpy.ndarray of shape (...,3,3)
|
|
||||||
Deviatoric part of T.
|
|
||||||
|
|
||||||
"""
|
|
||||||
return T - spherical_part(T,tensor=True)
|
|
||||||
|
|
||||||
|
|
||||||
def equivalent_strain_Mises(epsilon):
|
def equivalent_strain_Mises(epsilon):
|
||||||
"""
|
"""
|
||||||
Calculate the Mises equivalent of a strain tensor.
|
Calculate the Mises equivalent of a strain tensor.
|
||||||
|
@ -132,29 +114,6 @@ def rotational_part(T):
|
||||||
return _polar_decomposition(T,'R')[0]
|
return _polar_decomposition(T,'R')[0]
|
||||||
|
|
||||||
|
|
||||||
def spherical_part(T,tensor=False):
|
|
||||||
"""
|
|
||||||
Calculate spherical (hydrostatic) part of a tensor.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
T : numpy.ndarray of shape (...,3,3)
|
|
||||||
Tensor of which the hydrostatic part is computed.
|
|
||||||
tensor : bool, optional
|
|
||||||
Map spherical part onto identity _tensor. Defaults to false
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
p : numpy.ndarray of shape (...)
|
|
||||||
unless tensor == True: shape (...,3,3)
|
|
||||||
Spherical part of tensor T, e.g. the hydrostatic part/pressure
|
|
||||||
of a stress _tensor.
|
|
||||||
|
|
||||||
"""
|
|
||||||
sph = _np.trace(T,axis2=-2,axis1=-1)/3.0
|
|
||||||
return _np.einsum('...jk,...',_np.eye(3),sph) if tensor else sph
|
|
||||||
|
|
||||||
|
|
||||||
def strain(F,t,m):
|
def strain(F,t,m):
|
||||||
"""
|
"""
|
||||||
Calculate strain tensor (Seth–Hill family).
|
Calculate strain tensor (Seth–Hill family).
|
||||||
|
@ -168,7 +127,7 @@ def strain(F,t,m):
|
||||||
Deformation gradient.
|
Deformation gradient.
|
||||||
t : {‘V’, ‘U’}
|
t : {‘V’, ‘U’}
|
||||||
Type of the polar decomposition, ‘V’ for left stretch tensor
|
Type of the polar decomposition, ‘V’ for left stretch tensor
|
||||||
and ‘U’ for right stretch _tensor.
|
and ‘U’ for right stretch tensor.
|
||||||
m : float
|
m : float
|
||||||
Order of the strain.
|
Order of the strain.
|
||||||
|
|
||||||
|
@ -242,7 +201,7 @@ def stress_second_Piola_Kirchhoff(P,F):
|
||||||
|
|
||||||
def stretch_left(T):
|
def stretch_left(T):
|
||||||
"""
|
"""
|
||||||
Calculate left stretch of a _tensor.
|
Calculate left stretch of a tensor.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
|
@ -260,7 +219,7 @@ def stretch_left(T):
|
||||||
|
|
||||||
def stretch_right(T):
|
def stretch_right(T):
|
||||||
"""
|
"""
|
||||||
Calculate right stretch of a _tensor.
|
Calculate right stretch of a tensor.
|
||||||
|
|
||||||
Parameters
|
Parameters
|
||||||
----------
|
----------
|
||||||
|
@ -318,5 +277,5 @@ def _equivalent_Mises(T_sym,s):
|
||||||
Scaling factor (2/3 for strain, 3/2 for stress).
|
Scaling factor (2/3 for strain, 3/2 for stress).
|
||||||
|
|
||||||
"""
|
"""
|
||||||
d = deviatoric_part(T_sym)
|
d = _tensor.deviatoric(T_sym)
|
||||||
return _np.sqrt(s*_np.sum(d**2.0,axis=(-1,-2)))
|
return _np.sqrt(s*_np.sum(d**2.0,axis=(-1,-2)))
|
||||||
|
|
|
@ -11,6 +11,24 @@ to operate on numpy.ndarrays of shape (...,3,3).
|
||||||
import numpy as _np
|
import numpy as _np
|
||||||
|
|
||||||
|
|
||||||
|
def deviatoric(T):
|
||||||
|
"""
|
||||||
|
Calculate deviatoric part of a tensor.
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
T : numpy.ndarray of shape (...,3,3)
|
||||||
|
Tensor of which the deviatoric part is computed.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
T' : numpy.ndarray of shape (...,3,3)
|
||||||
|
Deviatoric part of T.
|
||||||
|
|
||||||
|
"""
|
||||||
|
return T - spherical(T,tensor=True)
|
||||||
|
|
||||||
|
|
||||||
def eigenvalues(T_sym):
|
def eigenvalues(T_sym):
|
||||||
"""
|
"""
|
||||||
Eigenvalues, i.e. principal components, of a symmetric tensor.
|
Eigenvalues, i.e. principal components, of a symmetric tensor.
|
||||||
|
@ -55,6 +73,29 @@ def eigenvectors(T_sym,RHS=False):
|
||||||
return v
|
return v
|
||||||
|
|
||||||
|
|
||||||
|
def spherical(T,tensor=True):
|
||||||
|
"""
|
||||||
|
Calculate spherical part of a tensor.
|
||||||
|
|
||||||
|
|
||||||
|
Parameters
|
||||||
|
----------
|
||||||
|
T : numpy.ndarray of shape (...,3,3)
|
||||||
|
Tensor of which the spherical part is computed.
|
||||||
|
tensor : bool, optional
|
||||||
|
Map spherical part onto identity tensor. Defaults to True.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
-------
|
||||||
|
p : numpy.ndarray of shape (...,3,3)
|
||||||
|
unless tensor == False: shape (...,)
|
||||||
|
Spherical part of tensor T. p is an isotropic tensor.
|
||||||
|
|
||||||
|
"""
|
||||||
|
sph = _np.trace(T,axis2=-2,axis1=-1)/3.0
|
||||||
|
return _np.einsum('...jk,...',_np.eye(3),sph) if tensor else sph
|
||||||
|
|
||||||
|
|
||||||
def symmetric(T):
|
def symmetric(T):
|
||||||
"""
|
"""
|
||||||
Symmetrize tensor.
|
Symmetrize tensor.
|
||||||
|
|
|
@ -143,7 +143,7 @@ class TestResult:
|
||||||
default.add_deviator('P')
|
default.add_deviator('P')
|
||||||
loc = {'P' :default.get_dataset_location('P'),
|
loc = {'P' :default.get_dataset_location('P'),
|
||||||
's_P':default.get_dataset_location('s_P')}
|
's_P':default.get_dataset_location('s_P')}
|
||||||
in_memory = mechanics.deviatoric_part(default.read_dataset(loc['P'],0))
|
in_memory = tensor.deviatoric(default.read_dataset(loc['P'],0))
|
||||||
in_file = default.read_dataset(loc['s_P'],0)
|
in_file = default.read_dataset(loc['s_P'],0)
|
||||||
assert np.allclose(in_memory,in_file)
|
assert np.allclose(in_memory,in_file)
|
||||||
|
|
||||||
|
@ -273,7 +273,7 @@ class TestResult:
|
||||||
default.add_spherical('P')
|
default.add_spherical('P')
|
||||||
loc = {'P': default.get_dataset_location('P'),
|
loc = {'P': default.get_dataset_location('P'),
|
||||||
'p_P': default.get_dataset_location('p_P')}
|
'p_P': default.get_dataset_location('p_P')}
|
||||||
in_memory = mechanics.spherical_part(default.read_dataset(loc['P'],0)).reshape(-1,1)
|
in_memory = tensor.spherical(default.read_dataset(loc['P'],0),False).reshape(-1,1)
|
||||||
in_file = default.read_dataset(loc['p_P'],0)
|
in_file = default.read_dataset(loc['p_P'],0)
|
||||||
assert np.allclose(in_memory,in_file)
|
assert np.allclose(in_memory,in_file)
|
||||||
|
|
||||||
|
|
|
@ -9,10 +9,6 @@ def stress_Cauchy(P,F):
|
||||||
return symmetric(sigma)
|
return symmetric(sigma)
|
||||||
|
|
||||||
|
|
||||||
def deviatoric_part(T):
|
|
||||||
return T - np.eye(3)*spherical_part(T)
|
|
||||||
|
|
||||||
|
|
||||||
def eigenvalues(T_sym):
|
def eigenvalues(T_sym):
|
||||||
return np.linalg.eigvalsh(symmetric(T_sym))
|
return np.linalg.eigvalsh(symmetric(T_sym))
|
||||||
|
|
||||||
|
@ -39,11 +35,6 @@ def rotational_part(T):
|
||||||
return polar_decomposition(T,'R')[0]
|
return polar_decomposition(T,'R')[0]
|
||||||
|
|
||||||
|
|
||||||
def spherical_part(T,tensor=False):
|
|
||||||
sph = np.trace(T)/3.0
|
|
||||||
return sph if not tensor else np.eye(3)*sph
|
|
||||||
|
|
||||||
|
|
||||||
def strain(F,t,m):
|
def strain(F,t,m):
|
||||||
|
|
||||||
if t == 'V':
|
if t == 'V':
|
||||||
|
@ -92,31 +83,20 @@ def polar_decomposition(T,requested):
|
||||||
return tuple(output)
|
return tuple(output)
|
||||||
|
|
||||||
def equivalent_Mises(T_sym,s):
|
def equivalent_Mises(T_sym,s):
|
||||||
return np.sqrt(s*(np.sum(deviatoric_part(T_sym)**2.0)))
|
return np.sqrt(s*(np.sum(deviatoric(T_sym)**2.0)))
|
||||||
|
|
||||||
|
def deviatoric(T):
|
||||||
|
return T - np.eye(3)*np.trace(T)/3.0
|
||||||
|
|
||||||
class TestMechanics:
|
class TestMechanics:
|
||||||
|
|
||||||
n = 1000
|
n = 1000
|
||||||
c = np.random.randint(n)
|
c = np.random.randint(n)
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('vectorized,single',[(mechanics.maximum_shear, maximum_shear),
|
||||||
@pytest.mark.parametrize('vectorized,single',[(mechanics.deviatoric_part, deviatoric_part),
|
|
||||||
(mechanics.spherical_part, spherical_part)
|
|
||||||
])
|
|
||||||
def test_vectorize_1_arg_(self,vectorized,single):
|
|
||||||
print("done")
|
|
||||||
test_data_flat = np.random.rand(self.n,3,3)
|
|
||||||
test_data = np.reshape(test_data_flat,(self.n//10,10,3,3))
|
|
||||||
for i,v in enumerate(np.reshape(vectorized(test_data),vectorized(test_data_flat).shape)):
|
|
||||||
assert np.allclose(single(test_data_flat[i]),v)
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('vectorized,single',[(mechanics.deviatoric_part, deviatoric_part),
|
|
||||||
(mechanics.maximum_shear, maximum_shear),
|
|
||||||
(mechanics.equivalent_stress_Mises, equivalent_stress_Mises),
|
(mechanics.equivalent_stress_Mises, equivalent_stress_Mises),
|
||||||
(mechanics.equivalent_strain_Mises, equivalent_strain_Mises),
|
(mechanics.equivalent_strain_Mises, equivalent_strain_Mises),
|
||||||
(mechanics.rotational_part, rotational_part),
|
(mechanics.rotational_part, rotational_part),
|
||||||
(mechanics.spherical_part, spherical_part),
|
|
||||||
(mechanics.stretch_left, stretch_left),
|
(mechanics.stretch_left, stretch_left),
|
||||||
(mechanics.stretch_right, stretch_right),
|
(mechanics.stretch_right, stretch_right),
|
||||||
])
|
])
|
||||||
|
@ -155,11 +135,6 @@ class TestMechanics:
|
||||||
P = np.random.rand(self.n,3,3)
|
P = np.random.rand(self.n,3,3)
|
||||||
assert np.allclose(function(P,np.broadcast_to(np.eye(3),(self.n,3,3))),tensor.symmetric(P))
|
assert np.allclose(function(P,np.broadcast_to(np.eye(3),(self.n,3,3))),tensor.symmetric(P))
|
||||||
|
|
||||||
def test_deviatoric_part(self):
|
|
||||||
I_n = np.broadcast_to(np.eye(3),(self.n,3,3))
|
|
||||||
r = np.logical_not(I_n)*np.random.rand(self.n,3,3)
|
|
||||||
assert np.allclose(mechanics.deviatoric_part(I_n+r),r)
|
|
||||||
|
|
||||||
def test_polar_decomposition(self):
|
def test_polar_decomposition(self):
|
||||||
"""F = RU = VR."""
|
"""F = RU = VR."""
|
||||||
F = np.broadcast_to(np.eye(3),[self.n,3,3])*np.random.rand(self.n,3,3)
|
F = np.broadcast_to(np.eye(3),[self.n,3,3])*np.random.rand(self.n,3,3)
|
||||||
|
@ -201,34 +176,20 @@ class TestMechanics:
|
||||||
assert np.allclose(np.abs(np.linalg.det(mechanics.rotational_part(x))),
|
assert np.allclose(np.abs(np.linalg.det(mechanics.rotational_part(x))),
|
||||||
1.0)
|
1.0)
|
||||||
|
|
||||||
def test_spherical_deviatoric_part(self):
|
|
||||||
"""Ensure that full tensor is sum of spherical and deviatoric part."""
|
|
||||||
x = np.random.rand(self.n,3,3)
|
|
||||||
sph = mechanics.spherical_part(x,True)
|
|
||||||
assert np.allclose(sph + mechanics.deviatoric_part(x),
|
|
||||||
x)
|
|
||||||
|
|
||||||
def test_deviatoric_Mises(self):
|
def test_deviatoric_Mises(self):
|
||||||
"""Ensure that Mises equivalent stress depends only on deviatoric part."""
|
"""Ensure that Mises equivalent stress depends only on deviatoric part."""
|
||||||
x = np.random.rand(self.n,3,3)
|
x = np.random.rand(self.n,3,3)
|
||||||
full = mechanics.equivalent_stress_Mises(x)
|
full = mechanics.equivalent_stress_Mises(x)
|
||||||
dev = mechanics.equivalent_stress_Mises(mechanics.deviatoric_part(x))
|
dev = mechanics.equivalent_stress_Mises(tensor.deviatoric(x))
|
||||||
assert np.allclose(full,
|
assert np.allclose(full,
|
||||||
dev)
|
dev)
|
||||||
|
|
||||||
def test_spherical_mapping(self):
|
@pytest.mark.parametrize('Mises_equivalent',[mechanics.equivalent_strain_Mises,
|
||||||
"""Ensure that mapping to tensor is correct."""
|
mechanics.equivalent_stress_Mises])
|
||||||
|
def test_spherical_Mises(self,Mises_equivalent):
|
||||||
|
"""Ensure that Mises equivalent strain/stress of spherical strain is 0."""
|
||||||
x = np.random.rand(self.n,3,3)
|
x = np.random.rand(self.n,3,3)
|
||||||
tnsr = mechanics.spherical_part(x,True)
|
assert np.allclose(Mises_equivalent(tensor.spherical(x,True)),
|
||||||
scalar = mechanics.spherical_part(x)
|
|
||||||
assert np.allclose(np.linalg.det(tnsr),
|
|
||||||
scalar**3.0)
|
|
||||||
|
|
||||||
def test_spherical_Mises(self):
|
|
||||||
"""Ensure that Mises equivalent strain of spherical strain is 0."""
|
|
||||||
x = np.random.rand(self.n,3,3)
|
|
||||||
sph = mechanics.spherical_part(x,True)
|
|
||||||
assert np.allclose(mechanics.equivalent_strain_Mises(sph),
|
|
||||||
0.0)
|
0.0)
|
||||||
|
|
||||||
|
|
||||||
|
@ -240,5 +201,5 @@ class TestMechanics:
|
||||||
|
|
||||||
def test_spherical_no_shear(self):
|
def test_spherical_no_shear(self):
|
||||||
"""Ensure that sherical stress has max shear of 0.0."""
|
"""Ensure that sherical stress has max shear of 0.0."""
|
||||||
A = mechanics.spherical_part(tensor.symmetric(np.random.rand(self.n,3,3)),True)
|
A = tensor.spherical(tensor.symmetric(np.random.rand(self.n,3,3)),True)
|
||||||
assert np.allclose(mechanics.maximum_shear(A),0.0)
|
assert np.allclose(mechanics.maximum_shear(A),0.0)
|
||||||
|
|
|
@ -3,6 +3,8 @@ import numpy as np
|
||||||
|
|
||||||
from damask import tensor
|
from damask import tensor
|
||||||
|
|
||||||
|
def deviatoric(T):
|
||||||
|
return T - spherical(T)
|
||||||
|
|
||||||
def eigenvalues(T_sym):
|
def eigenvalues(T_sym):
|
||||||
return np.linalg.eigvalsh(symmetric(T_sym))
|
return np.linalg.eigvalsh(symmetric(T_sym))
|
||||||
|
@ -20,6 +22,10 @@ def symmetric(T):
|
||||||
def transpose(T):
|
def transpose(T):
|
||||||
return T.T
|
return T.T
|
||||||
|
|
||||||
|
def spherical(T,tensor=True):
|
||||||
|
sph = np.trace(T)/3.0
|
||||||
|
return sph if not tensor else np.eye(3)*sph
|
||||||
|
|
||||||
|
|
||||||
class TestTensor:
|
class TestTensor:
|
||||||
|
|
||||||
|
@ -27,10 +33,12 @@ class TestTensor:
|
||||||
c = np.random.randint(n)
|
c = np.random.randint(n)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('vectorized,single',[(tensor.eigenvalues , eigenvalues ),
|
@pytest.mark.parametrize('vectorized,single',[(tensor.deviatoric, deviatoric),
|
||||||
(tensor.eigenvectors , eigenvectors ),
|
(tensor.eigenvalues, eigenvalues),
|
||||||
(tensor.symmetric , symmetric ),
|
(tensor.eigenvectors, eigenvectors),
|
||||||
(tensor.transpose , transpose ),
|
(tensor.symmetric, symmetric),
|
||||||
|
(tensor.transpose, transpose),
|
||||||
|
(tensor.spherical, spherical),
|
||||||
])
|
])
|
||||||
def test_vectorize_1_arg(self,vectorized,single):
|
def test_vectorize_1_arg(self,vectorized,single):
|
||||||
epsilon = np.random.rand(self.n,3,3)
|
epsilon = np.random.rand(self.n,3,3)
|
||||||
|
@ -71,3 +79,21 @@ class TestTensor:
|
||||||
LRHS = np.linalg.det(tensor.eigenvectors(A,RHS=False))
|
LRHS = np.linalg.det(tensor.eigenvectors(A,RHS=False))
|
||||||
RHS = np.linalg.det(tensor.eigenvectors(A,RHS=True))
|
RHS = np.linalg.det(tensor.eigenvectors(A,RHS=True))
|
||||||
assert np.allclose(np.abs(LRHS),RHS)
|
assert np.allclose(np.abs(LRHS),RHS)
|
||||||
|
|
||||||
|
def test_spherical_deviatoric_part(self):
|
||||||
|
"""Ensure that full tensor is sum of spherical and deviatoric part."""
|
||||||
|
x = np.random.rand(self.n,3,3)
|
||||||
|
assert np.allclose(tensor.spherical(x,True) + tensor.deviatoric(x),
|
||||||
|
x)
|
||||||
|
def test_spherical_mapping(self):
|
||||||
|
"""Ensure that mapping to tensor is correct."""
|
||||||
|
x = np.random.rand(self.n,3,3)
|
||||||
|
tnsr = tensor.spherical(x,True)
|
||||||
|
scalar = tensor.spherical(x,False)
|
||||||
|
assert np.allclose(np.linalg.det(tnsr),
|
||||||
|
scalar**3.0)
|
||||||
|
|
||||||
|
def test_deviatoric(self):
|
||||||
|
I_n = np.broadcast_to(np.eye(3),(self.n,3,3))
|
||||||
|
r = np.logical_not(I_n)*np.random.rand(self.n,3,3)
|
||||||
|
assert np.allclose(tensor.deviatoric(I_n+r),r)
|
||||||
|
|
Loading…
Reference in New Issue