outsourcing tensor math to mechanics class

strain calculation is generalize to arbitrary order and simplified: No
need for svd, F^T F/F F^T does the job.
This commit is contained in:
Martin Diehl 2019-10-19 12:54:16 +02:00
parent e51f6cee72
commit b31de5d0f6
2 changed files with 324 additions and 195 deletions

View File

@ -360,7 +360,7 @@ class DADF5():
def cell_coordinates(self): def cell_coordinates(self):
"""Initial coordinates of the cell centers.""" """Return initial coordinates of the cell centers."""
if self.structured: if self.structured:
delta = self.size/self.grid*0.5 delta = self.size/self.grid*0.5
z, y, x = np.meshgrid(np.linspace(delta[2],self.size[2]-delta[2],self.grid[2]), z, y, x = np.meshgrid(np.linspace(delta[2],self.size[2]-delta[2],self.grid[2]),
@ -375,62 +375,72 @@ class DADF5():
def add_Cauchy(self,P='P',F='F'): def add_Cauchy(self,P='P',F='F'):
""" """
Adds Cauchy stress calculated from 1st Piola-Kirchhoff stress and deformation gradient. Add Cauchy stress calculated from 1. Piola-Kirchhoff stress and deformation gradient.
Resulting tensor is symmetrized as the Cauchy stress should be symmetric. Parameters
----------
P : str, optional
Label of the dataset containing the 1. Piola-Kirchhoff stress. Default value is P.
F : str, optional
Label of the dataset containing the deformation gradient. Default value is F.
""" """
def Cauchy(F,P): def __add_Cauchy(F,P):
return {
'data' : mechanics.Cauchy(F['data'],P['data']), return {
'label' : 'sigma', 'data': mechanics.Cauchy(F['data'],P['data']),
'meta' : { 'label': 'sigma',
'Unit' : P['meta']['Unit'], 'meta': {
'Description' : 'Cauchy stress calculated from {} ({}) '.format(P['label'],P['meta']['Description'])+\ 'Unit': P['meta']['Unit'],
'and deformation gradient {} ({})'.format(F['label'],F['meta']['Description']), 'Description': 'Cauchy stress calculated from {} ({}) '.format(P['label'],P['meta']['Description'])+\
'Creator' : 'dadf5.py:add_Cauchy v{}'.format(version) 'and deformation gradient {} ({})'.format(F['label'],F['meta']['Description']),
'Creator': 'dadf5.py:add_Cauchy v{}'.format(version)
} }
} }
requested = [{'label':F,'arg':'F'}, requested = [{'label':F,'arg':'F'},
{'label':P,'arg':'P'} ] {'label':P,'arg':'P'} ]
self.__add_generic_pointwise(Cauchy,requested) self.__add_generic_pointwise(__add_Cauchy,requested)
def add_Mises(self,x): def add_Mises(self,x):
"""Adds the equivalent Mises stress or strain of a tensor.""" """
def Mises(x): Add the equivalent Mises stress or strain of a symmetric tensor.
if x['meta']['Unit'] == 'Pa': #ToDo: Should we use this? Then add_Cauchy and add_strain_tensors also should perform sanity checks Parameters
t = 'stress' ----------
elif x['meta']['Unit'] == '1': x : str
t = 'strain' Label of the dataset containing a symmetric stress or strain tensor
else: """
print(x['meta']['Unit']) def __add_Mises(x):
raise ValueError
return { return {
'data' : mechanics.Mises_strain(x['data']) if t=='strain' else mechanics.Mises_stress(x['data']), 'data': mechanics.Mises_strain(x) if t=='strain' else mechanics.Mises_stress(x),
'label' : '{}_vM'.format(x['label']), 'label': '{}_vM'.format(x['label']),
'meta' : { 'meta': {
'Unit' : x['meta']['Unit'], 'Unit': x['meta']['Unit'],
'Description' : 'Mises equivalent {} of {} ({})'.format(t,x['label'],x['meta']['Description']), 'Description': 'Mises equivalent {} of {} ({})'.format(t,x['label'],x['meta']['Description']),
'Creator' : 'dadf5.py:add_Mises_stress v{}'.format(version) 'Creator': 'dadf5.py:add_Mises v{}'.format(version)
} }
} }
requested = [{'label':x,'arg':'x'}] requested = [{'label':x,'arg':'x'}]
self.__add_generic_pointwise(Mises,requested) self.__add_generic_pointwise(__add_Mises,requested)
def add_norm(self,x,ord=None): def add_norm(self,x,ord=None):
""" """
Adds norm of vector or tensor. Add the norm of vector or tensor.
See numpy.linalg.norm manual for details. Parameters
----------
x : str
Label of the dataset containing a vector or tensor.
ord : {non-zero int, inf, -inf, fro, nuc}, optional
Order of the norm. inf means numpys inf object. For details refer to numpy.linalg.norm.
""" """
def norm(x,ord): def __add_norm(x,ord):
o = ord o = ord
if len(x['data'].shape) == 2: if len(x['data'].shape) == 2:
@ -444,220 +454,258 @@ class DADF5():
else: else:
raise ValueError raise ValueError
return { return {
'data' : np.linalg.norm(x['data'],ord=o,axis=axis,keepdims=True), 'data': np.linalg.norm(x['data'],ord=o,axis=axis,keepdims=True),
'label' : '|{}|_{}'.format(x['label'],o), 'label': '|{}|_{}'.format(x['label'],o),
'meta' : { 'meta': {
'Unit' : x['meta']['Unit'], 'Unit': x['meta']['Unit'],
'Description' : '{}-Norm of {} {} ({})'.format(ord,t,x['label'],x['meta']['Description']), 'Description': '{}-Norm of {} {} ({})'.format(ord,t,x['label'],x['meta']['Description']),
'Creator' : 'dadf5.py:add_norm v{}'.format(version) 'Creator': 'dadf5.py:add_norm v{}'.format(version)
} }
} }
requested = [{'label':x,'arg':'x'}] requested = [{'label':x,'arg':'x'}]
self.__add_generic_pointwise(norm,requested,{'ord':ord}) self.__add_generic_pointwise(__add_norm,requested,{'ord':ord})
def add_absolute(self,x): def add_absolute(self,x):
"""Adds absolute value.""" """
def absolute(x): Add absolute value.
return { Parameters
'data' : np.abs(x['data']), ----------
'label' : '|{}|'.format(x['label']), x : str
'meta' : { Label of the dataset containing a scalar, vector, or tensor.
'Unit' : x['meta']['Unit'], """
'Description' : 'Absolute value of {} ({})'.format(x['label'],x['meta']['Description']), def __add_absolute(x):
'Creator' : 'dadf5.py:add_abs v{}'.format(version)
return {
'data': np.abs(x['data']),
'label': '|{}|'.format(x['label']),
'meta': {
'Unit': x['meta']['Unit'],
'Description': 'Absolute value of {} ({})'.format(x['label'],x['meta']['Description']),
'Creator': 'dadf5.py:add_abs v{}'.format(version)
} }
} }
requested = [{'label':x,'arg':'x'}] requested = [{'label':x,'arg':'x'}]
self.__add_generic_pointwise(absolute,requested) self.__add_generic_pointwise(__add_absolute,requested)
def add_determinant(self,x): def add_determinant(self,x):
"""Adds the determinant component of a tensor.""" """
def determinant(x): Add the determinant of a tensor.
return { Parameters
'data' : np.linalg.det(x['data']), ----------
'label' : 'det({})'.format(x['label']), x : str
'meta' : { Label of the dataset containing a tensor.
'Unit' : x['meta']['Unit'], """
'Description' : 'Determinant of tensor {} ({})'.format(x['label'],x['meta']['Description']), def __add_determinant(x):
'Creator' : 'dadf5.py:add_determinant v{}'.format(version)
return {
'data': np.linalg.det(x['data']),
'label': 'det({})'.format(x['label']),
'meta': {
'Unit': x['meta']['Unit'],
'Description': 'Determinant of tensor {} ({})'.format(x['label'],x['meta']['Description']),
'Creator': 'dadf5.py:add_determinant v{}'.format(version)
} }
} }
requested = [{'label':x,'arg':'x'}] requested = [{'label':x,'arg':'x'}]
self.__add_generic_pointwise(determinant,requested) self.__add_generic_pointwise(__add_determinant,requested)
def add_spherical(self,x): def add_spherical(self,x):
"""Adds the spherical component of a tensor.""" """
def spherical(x): Add the spherical (hydrostatic) part of a tensor.
Parameters
----------
x : str
Label of the dataset containing a tensor.
"""
def __add_spherical(x):
if not np.all(np.array(x['data'].shape[1:]) == np.array([3,3])): if not np.all(np.array(x['data'].shape[1:]) == np.array([3,3])):
raise ValueError raise ValueError
return { return {
'data' : np.trace(x['data'],axis1=1,axis2=2)/3.0, 'data': mechanics.spherical_part(x),
'label' : 'sph({})'.format(x['label']), 'label': 'p_{}'.format(x['label']),
'meta' : { 'meta': {
'Unit' : x['meta']['Unit'], 'Unit': x['meta']['Unit'],
'Description' : 'Spherical component of tensor {} ({})'.format(x['label'],x['meta']['Description']), 'Description': 'Spherical component of tensor {} ({})'.format(x['label'],x['meta']['Description']),
'Creator' : 'dadf5.py:add_spherical v{}'.format(version) 'Creator': 'dadf5.py:add_spherical v{}'.format(version)
} }
} }
requested = [{'label':x,'arg':'x'}] requested = [{'label':x,'arg':'x'}]
self.__add_generic_pointwise(spherical,requested) self.__add_generic_pointwise(__add_spherical,requested)
def add_deviator(self,x): def add_deviator(self,x):
"""Adds the deviator of a tensor.""" """
def deviator(x): Add the deviatoric part of a tensor.
Parameters
----------
x : str
Label of the dataset containing a tensor.
"""
def __add_deviator(x):
if not np.all(np.array(x['data'].shape[1:]) == np.array([3,3])): if not np.all(np.array(x['data'].shape[1:]) == np.array([3,3])):
raise ValueError raise ValueError
return { return {
'data' : mechanics.deviator(x['data']), 'data': mechanics.deviator(x['data']),
'label' : 'dev({})'.format(x['label']), 'label': 's_{}'.format(x['label']),
'meta' : { 'meta': {
'Unit' : x['meta']['Unit'], 'Unit': x['meta']['Unit'],
'Description' : 'Deviator of tensor {} ({})'.format(x['label'],x['meta']['Description']), 'Description': 'Deviator of tensor {} ({})'.format(x['label'],x['meta']['Description']),
'Creator' : 'dadf5.py:add_deviator v{}'.format(version) 'Creator': 'dadf5.py:add_deviator v{}'.format(version)
} }
} }
requested = [{'label':x,'arg':'x'}] requested = [{'label':x,'arg':'x'}]
self.__add_generic_pointwise(deviator,requested) self.__add_generic_pointwise(__add_deviator,requested)
def add_calculation(self,formula,label,unit='n/a',description=None,vectorized=True): def add_calculation(self,formula,label,unit='n/a',description=None,vectorized=True):
""" """
General formula. Add result of a general formula.
Works currently only for vectorized expressions
Parameters
----------
formula : str
Formula, refer to datasets by #Label#.
label : str
Label of the dataset containing the result of the calculation.
unit : str, optional
Physical unit of the result.
description : str, optional
Human readable description of the result.
vectorized : bool, optional
Indicate whether the formula is written in vectorized form.
""" """
if vectorized is not True: if vectorized is not True:
raise NotImplementedError raise NotImplementedError
def calculation(**kwargs): def __add_calculation(**kwargs):
formula = kwargs['formula'] formula = kwargs['formula']
for d in re.findall(r'#(.*?)#',formula): for d in re.findall(r'#(.*?)#',formula):
formula = formula.replace('#{}#'.format(d),"kwargs['{}']['data']".format(d)) formula = formula.replace('#{}#'.format(d),"kwargs['{}']['data']".format(d))
return { return {
'data' : eval(formula), 'data': eval(formula),
'label' : kwargs['label'], 'label': kwargs['label'],
'meta' : { 'meta': {
'Unit' : kwargs['unit'], 'Unit': kwargs['unit'],
'Description' : '{} (formula: {})'.format(kwargs['description'],kwargs['formula']), 'Description': '{} (formula: {})'.format(kwargs['description'],kwargs['formula']),
'Creator' : 'dadf5.py:add_calculation v{}'.format(version) 'Creator': 'dadf5.py:add_calculation v{}'.format(version)
} }
} }
requested = [{'label':d,'arg':d} for d in set(re.findall(r'#(.*?)#',formula))] # datasets used in the formula requested = [{'label':d,'arg':d} for d in set(re.findall(r'#(.*?)#',formula))] # datasets used in the formula
pass_through = {'formula':formula,'label':label,'unit':unit,'description':description} pass_through = {'formula':formula,'label':label,'unit':unit,'description':description}
self.__add_generic_pointwise(calculation,requested,pass_through) self.__add_generic_pointwise(__add_calculation,requested,pass_through)
def add_strain_tensor(self,t,ord,defgrad='F'): def add_strain_tensor(self,F='F',t='U',ord=0):
""" """
Adds the a strain tensor. Add strain tensor calculated from a deformation gradient.
Albrecht Bertram: Elasticity and Plasticity of Large Deformations An Introduction (3rd Edition, 2012), p. 102. For details refer to damask.mechanics.strain_tensor
Parameters
----------
F : str, optional
Label of the dataset containing the deformation gradient. Default value is F.
t : {V, U}, optional
Type of the polar decomposition, V for right stretch tensor and U for left stretch tensor.
Defaults value is U.
ord : float, optional
Order of the strain calculation. Default value is 0.0.
""" """
def strain_tensor(defgrad,t,ord): def __add_strain_tensor(F,t,ord):
operator = { return {
'V#ln': lambda V: np.log(V), 'data': mechanics.strain_tensor(F['data'],t,ord),
'U#ln': lambda U: np.log(U), 'label': 'epsilon_{}^{}({})'.format(t,ord,F['label']),
'V#Biot': lambda V: np.broadcast_to(np.ones(3),[V.shape[0],3]) - 1.0/V, 'meta': {
'U#Biot': lambda U: U - np.broadcast_to(np.ones(3),[U.shape[0],3]), 'Unit': F['meta']['Unit'],
'V#Green':lambda V: np.broadcast_to(np.ones(3),[V.shape[0],3]) - 1.0/V**2, 'Description': 'Strain tensor of {} ({})'.format(F['label'],F['meta']['Description']),
'U#Green':lambda U: U**2 - np.broadcast_to(np.ones(3),[U.shape[0],3]), 'Creator': 'dadf5.py:add_strain_tensor v{}'.format(version)
}
if t.lower() in ['l','left']:
stretch = 'V'
elif t.lower() in ['r','right']:
stretch = 'U'
else:
raise KeyError
(U,S,Vh) = np.linalg.svd(defgrad['data']) # singular value decomposition
R_inv = np.transpose(np.matmul(U,Vh),(0,2,1)) # transposed rotation of polar decomposition
s = np.matmul(R_inv,defgrad['data']) if stretch == 'U' else \
np.matmul(defgrad['data'],R_inv) # compute either left or right stretch
(D,V) = np.linalg.eigh((s+np.transpose(s,(0,2,1)))*.5) # eigen decomposition (of symmetric(ed) matrix)
d = operator[stretch+'#'+{0:'ln',1:'Biot',2:'Green'}[ord]](D)
a = np.matmul(V,np.einsum('ij,ikj->ijk',d,V))
return {
'data' : a,
'label' : 'epsilon_{}^{}({})'.format(stretch,ord,defgrad['label']),
'meta' : {
'Unit' : defgrad['meta']['Unit'],
'Description' : 'Strain tensor of {} ({})'.format(defgrad['label'],defgrad['meta']['Description']),
'Creator' : 'dadf5.py:add_strain_tensor v{}'.format(version)
} }
} }
requested = [{'label':defgrad,'arg':'defgrad'}] requested = [{'label':F,'arg':'F'}]
self.__add_generic_pointwise(strain_tensor,requested,{'t':t,'ord':ord}) self.__add_generic_pointwise(__add_strain_tensor,requested,{'t':t,'ord':ord})
def add_principal_components(self,x): def add_principal_components(self,x):
"""Adds principal components of symmetric tensor.""" """
def principal_components(x): Add principal components of symmetric tensor.
return { The principal components are sorted in descending order, each repeated according to its multiplicity.
'data' : np.linalg.eigvalsh((x['data']+np.transpose(x['data'],(0,2,1)))*.5)[:,::-1], # eigenvalues (of symmetric(ed) matrix)
'label' : 'lambda_{}'.format(x['label']), Parameters
'meta' : { ----------
'Unit' : x['meta']['Unit'], x : str
'Description' : 'Pricipal components of {} ({})'.format(x['label'],x['meta']['Description']), Label of the dataset containing a symmetric tensor.
'Creator' : 'dadf5.py:add_principal_components v{}'.format(version) """
def __add_principal_components(x):
return {
'data': mechanics.principal_components(x),
'label': 'lambda_{}'.format(x['label']),
'meta': {
'Unit': x['meta']['Unit'],
'Description': 'Pricipal components of {} ({})'.format(x['label'],x['meta']['Description']),
'Creator': 'dadf5.py:add_principal_components v{}'.format(version)
} }
} }
requested = [{'label':x,'arg':'x'}] requested = [{'label':x,'arg':'x'}]
self.__add_generic_pointwise(principal_components,requested) self.__add_generic_pointwise(__add_principal_components,requested)
def add_maximum_shear(self,x): def add_maximum_shear(self,x):
"""Adds maximum shear components of symmetric tensor.""" """
def maximum_shear(x): Add maximum shear components of symmetric tensor.
w = np.linalg.eigvalsh((x['data']+np.transpose(x['data'],(0,2,1)))*.5) # eigenvalues (of symmetric(ed) matrix) Parameters
----------
x : str
Label of the dataset containing a symmetric tensor.
"""
def __add_maximum_shear(x):
return { return {
'data' : (w[:,2] - w[:,0])*0.5, 'data': mechanics.maximum_shear(x),
'label' : 'max_shear({})'.format(x['label']), 'label': 'max_shear({})'.format(x['label']),
'meta' : { 'meta': {
'Unit' : x['meta']['Unit'], 'Unit': x['meta']['Unit'],
'Description' : 'Maximum shear component of of {} ({})'.format(x['label'],x['meta']['Description']), 'Description': 'Maximum shear component of of {} ({})'.format(x['label'],x['meta']['Description']),
'Creator' : 'dadf5.py:add_maximum_shear v{}'.format(version) 'Creator': 'dadf5.py:add_maximum_shear v{}'.format(version)
} }
} }
requested = [{'label':x,'arg':'x'}] requested = [{'label':x,'arg':'x'}]
self.__add_generic_pointwise(maximum_shear,requested) self.__add_generic_pointwise(__add_maximum_shear,requested)
def __add_generic_pointwise(self,func,datasets_requested,extra_args={}): def __add_generic_pointwise(self,func,datasets_requested,extra_args={}):
@ -666,13 +714,12 @@ class DADF5():
Parameters Parameters
---------- ----------
func : function func : function
Function that calculates a new dataset from one or more datasets per HDF5 group. Function that calculates a new dataset from one or more datasets per HDF5 group.
datasets_requested : list of dictionaries datasets_requested : list of dictionaries
Details of the datasets to be used: label (in HDF5 file) and arg (argument to which the data is parsed in func). Details of the datasets to be used: label (in HDF5 file) and arg (argument to which the data is parsed in func).
extra_args : dictionary, optional extra_args : dictionary, optional
Any extra arguments parsed to func. Any extra arguments parsed to func.
""" """
def job(args): def job(args):
"""Call function with input data + extra arguments, returns results + group.""" """Call function with input data + extra arguments, returns results + group."""

View File

@ -2,84 +2,166 @@ import numpy as np
def Cauchy(F,P): def Cauchy(F,P):
""" """
Calculate Cauchy stress from 1st Piola-Kirchhoff stress and deformation gradient. Return Cauchy stress calculated from 1. Piola-Kirchhoff stress and deformation gradient.
Resulting tensor is symmetrized as the Cauchy stress needs to be symmetric. Resulting tensor is symmetrized as the Cauchy stress needs to be symmetric.
Parameters Parameters
---------- ----------
F : numpy.array of shape (x,3,3) or (3,3) F : numpy.array of shape (x,3,3) or (3,3)
Deformation gradient. Deformation gradient.
P : numpy.array of shape (x,3,3) or (3,3) P : numpy.array of shape (x,3,3) or (3,3)
1st Piola-Kirchhoff. 1. Piola-Kirchhoff stress.
""" """
if np.shape(F) == np.shape(P) == (3,3): if np.shape(F) == np.shape(P) == (3,3):
sigma = 1.0/np.linalg.det(F) * np.dot(F,P) sigma = 1.0/np.linalg.det(F) * np.dot(F,P)
return (sigma+sigma.T)*0.5
else: else:
sigma = np.einsum('i,ijk,ilk->ijl',1.0/np.linalg.det(F),P,F) sigma = np.einsum('i,ijk,ilk->ijl',1.0/np.linalg.det(F),P,F)
return (sigma + np.transpose(sigma,(0,2,1)))*0.5 return symmetric(sigma)
def strain_tensor(F,t,ord):
"""
Return strain tensor calculated from deformation gradient.
For details refer to Albrecht Bertram: Elasticity and Plasticity of Large Deformations:
An Introduction (3rd Edition, 2012), p. 102.
Parameters
----------
F : numpy.array of shape (x,3,3) or (3,3)
Deformation gradient.
t : {V, U}
Type of the polar decomposition, V for right stretch tensor and U for left stretch tensor.
ord : float
Order of the strain
"""
F_expanded = F if len(F.shape) == 3 else F.reshape(1,3,3)
if t == 'U':
B = np.matmul(F_expanded,transpose(F_expanded))
U,n = np.linalg.eigh(symmetric(B))
l = np.log(U) if ord == 0 else U**ord - np.broadcast_to(np.ones(3),[U.shape[0],3])
elif t == 'V':
C = np.matmul(transpose(F_expanded),F_expanded)
V,n = np.linalg.eigh(symmetric(C))
l = np.log(V) if ord == 0 else np.broadcast_to(np.ones(3),[V.shape[0],3]) - 1.0/V**ord
epsilon = np.matmul(n,np.einsum('ij,ikj->ijk',l,n))
return epsilon.reshape((3,3)) if np.shape(F) == (3,3) else \
epsilon
def deviatoric_part(x): def deviatoric_part(x):
""" """
Calculate deviatoric part of a tensor. Return deviatoric part of a tensor.
Parameters Parameters
---------- ----------
x : numpy.array of shape (x,3,3) or (3,3) x : numpy.array of shape (x,3,3) or (3,3)
Tensor. Tensor.
""" """
if np.shape(x) == (3,3): return x - np.eye(3)*spherical_part(x) if np.shape(x) == (3,3) else \
return x - np.eye(3)*np.trace(x)/3.0 x - np.einsum('ijk,i->ijk',np.broadcast_to(np.eye(3),[x.shape[0],3,3]),spherical_part(x))
else:
return x - np.einsum('ijk,i->ijk',np.broadcast_to(np.eye(3),[x.shape[0],3,3]),np.trace(x,axis1=1,axis2=2)/3.0)
def spherical_part(x): def spherical_part(x):
""" """
Calculate spherical(hydrostatic) part of a tensor. Return spherical (hydrostatic) part of a tensor.
A single scalar is returned, i.e. the hydrostatic part is not mapped on the 3rd order identity matrix. A single scalar is returned, i.e. the hydrostatic part is not mapped on the 3rd order identity
matrix.
Parameters Parameters
---------- ----------
x : numpy.array of shape (x,3,3) or (3,3) x : numpy.array of shape (x,3,3) or (3,3)
Tensor. Tensor.
""" """
if np.shape(x) == (3,3):
return np.trace(x)/3.0 return np.trace(x)/3.0 if np.shape(x) == (3,3) else \
else: np.trace(x,axis1=1,axis2=2)/3.0
return np.trace(x,axis1=1,axis2=2)/3.0
def Mises_stress(sigma): def Mises_stress(sigma):
""" """
Calculate the Mises equivalent of a stress tensor. Return the Mises equivalent of a stress tensor.
Parameters Parameters
---------- ----------
sigma : numpy.array of shape (x,3,3) or (3,3) sigma : numpy.array of shape (x,3,3) or (3,3)
Symmetric stress tensor. Symmetric stress tensor.
""" """
s = deviatoric_part(sigma) s = deviatoric_part(sigma)
if np.shape(sigma) == (3,3): return np.sqrt(3.0/2.0*np.trace(s)) if np.shape(sigma) == (3,3) else \
return np.sqrt(3.0/2.0*np.trace(s)) np.sqrt(3.0/2.0*np.einsum('ijk->i',s))
else:
return np.sqrt(np.einsum('ijk->i',s)*3.0/2.0)
def Mises_strain(epsilon): def Mises_strain(epsilon):
""" """
Calculate the Mises equivalent of a strain tensor. Return the Mises equivalent of a strain tensor.
Parameters Parameters
---------- ----------
sigma : numpy.array of shape (x,3,3) or (3,3) epsilon : numpy.array of shape (x,3,3) or (3,3)
Symmetric strain tensor. Symmetric strain tensor.
""" """
s = deviatoric_part(epsilon) s = deviatoric_part(epsilon)
if np.shape(epsilon) == (3,3): return np.sqrt(2.0/3.0*np.trace(s)) if np.shape(epsilon) == (3,3) else \
return np.sqrt(2.0/3.0*np.trace(s)) np.sqrt(2.0/3.0*np.einsum('ijk->i',s))
else:
return np.sqrt(2.0/3.0*np.einsum('ijk->i',s))
def symmetric(x):
"""
Return the symmetrized tensor.
Parameters
----------
x : numpy.array of shape (x,3,3) or (3,3)
Tensor.
"""
return (x+transpose(x))*0.5
def maximum_shear(x):
"""
Return the maximum shear component of a symmetric tensor.
Parameters
----------
x : numpy.array of shape (x,3,3) or (3,3)
Symmetric tensor.
"""
w = np.linalg.eigvalsh(symmetric(x)) # eigenvalues in ascending order
return (w[2] - w[0])*0.5 if np.shape(epsilon) == (3,3) else \
(w[:,2] - w[:,0])*0.5
def principal_components(x):
"""
Return the principal components of a symmetric tensor.
The principal components (eigenvalues) are sorted in descending order, each repeated according to
its multiplicity.
Parameters
----------
x : numpy.array of shape (x,3,3) or (3,3)
Symmetric tensor.
"""
w = np.linalg.eigvalsh(symmetric(x)) # eigenvalues in ascending order
return w[::-1] if np.shape(epsilon) == (3,3) else \
w[:,::-1]
def transpose(x):
"""
Return the transpose of a tensor.
Parameters
----------
x : numpy.array of shape (x,3,3) or (3,3)
Tensor.
"""
return x.T if np.shape(x) == (3,3) else \
np.transpose(x,(0,2,1))