"""FETorch: Strain/Stress tensors Voigt notation matricial storage.
This module contains standard finite element procedures associated with the
storage of strain/stress tensorial quantities following the Voigt notation.
Functions
---------
get_strain_from_vmf
Recover strain tensor from associated Voigt matricial form.
vget_strain_from_vmf
Recover strain tensor from associated Voigt matricial form.
get_stress_vmf
Get stress tensor Voigt matricial form.
vget_stress_vmf
Get stress tensor Voigt matricial form.
get_projection_tensors_vmf(n_dim, comp_order_sym, device='cpu')
Get volumetric and deviatoric projection tensors Voigt matricial form.
"""
#
# Modules
# =============================================================================
# Standard
import itertools as it
# Third-party
import torch
#
# Authorship & Credits
# =============================================================================
__author__ = 'Bernardo Ferreira (bernardo_ferreira@brown.edu)'
__credits__ = ['Bernardo Ferreira', ]
__status__ = 'Stable'
# =============================================================================
#
# =============================================================================
def get_strain_from_vmf(strain_vmf, n_dim, comp_order_sym, device=None):
"""Recover strain tensor from associated Voigt matricial form.
Parameters
----------
strain_vmf : torch.Tensor(1d)
Strain tensor stored in Voigt matricial form.
n_dim : int
Problem number of spatial dimensions.
comp_order_sym : tuple
Strain/Stress components symmetric order.
device : torch.device, default=None
Device on which torch.Tensor is allocated.
Returns
-------
strain : torch.Tensor(2d)
Strain tensor recovered from Voigt matricial form.
"""
# Get device from input strain tensor
if device is None:
device = strain_vmf.device
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Check strain tensor Voigt matricial form
if not isinstance(strain_vmf, torch.Tensor):
raise RuntimeError('Strain tensor Voigt matricial form must be '
'torch.Tensor.')
elif len(strain_vmf.shape) != 1:
raise RuntimeError('Strain tensor Voigt matricial form must be '
'torch.Tensor(1d).')
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Check input arguments validity
if any([int(x) not in range(1, n_dim + 1)
for x in list(''.join(comp_order_sym))]):
raise RuntimeError('Invalid component in strain/stress components '
'order.')
elif any([len(comp) != 2 for comp in comp_order_sym]):
raise RuntimeError('Invalid component in strain/stress components '
'order.')
elif len(list(dict.fromkeys(comp_order_sym))) != len(comp_order_sym):
raise RuntimeError('Duplicated component in strain/stress components '
'order.')
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Set second-order and matricial form indexes
so_indexes = list()
mf_indexes = list()
for i in range(len(comp_order_sym)):
so_indexes.append([int(x) - 1 for x in list(comp_order_sym[i])])
mf_indexes.append(comp_order_sym.index(comp_order_sym[i]))
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Initialize strain tensor
strain = torch.zeros((n_dim, n_dim), device=device)
# Get strain tensor from matricial form
for i in range(len(mf_indexes)):
mf_idx = mf_indexes[i]
so_idx = tuple(so_indexes[i])
factor = 1.0
if so_idx[0] != so_idx[1]:
factor = 2.0
strain[so_idx[::-1]] = (1.0/factor)*strain_vmf[mf_idx]
strain[so_idx] = (1.0/factor)*strain_vmf[mf_idx]
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
return strain
# =============================================================================
[docs]def vget_strain_from_vmf(strain_vmf, n_dim, comp_order_sym, device=None):
"""Recover strain tensor from associated Voigt matricial form.
Compatible with vectorized mapping.
Parameters
----------
strain_vmf : torch.Tensor(1d)
Strain tensor stored in Voigt matricial form.
n_dim : int
Problem number of spatial dimensions.
comp_order_sym : tuple
Strain/Stress components symmetric order.
device : torch.device, default=None
Device on which torch.Tensor is allocated.
Returns
-------
strain : torch.Tensor(2d)
Strain tensor recovered from Voigt matricial form.
"""
# Get device from input strain tensor
if device is None:
device = strain_vmf.device
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Set row major components order
row_major_order = tuple(f'{i + 1}{j + 1}'
for i, j in it.product(range(n_dim), repeat=2))
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Build indexing inverse Voigt factor
index_voigt_inv = torch.tensor(
[1.0/2.0 if x[0] != x[1] else 1.0 for x in comp_order_sym],
device=device)
# Build indexing mapping
index_map = [comp_order_sym.index(x) if x in comp_order_sym
else comp_order_sym.index(x[::-1]) for x in row_major_order]
# Get tensor from matricial form
strain = \
torch.mul(strain_vmf, index_voigt_inv)[index_map].view(n_dim, n_dim)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
return strain
# =============================================================================
def get_stress_vmf(stress, n_dim, comp_order_sym, device=None):
"""Get stress tensor Voigt matricial form.
Parameters
----------
stress : torch.Tensor(2d)
Stress tensor to be stored in Voigt matricial form.
n_dim : int
Problem number of spatial dimensions.
comp_order_sym : tuple
Strain/Stress components symmetric order.
device : torch.device, default=None
Device on which torch.Tensor is allocated.
Returns
-------
stress_vmf : torch.Tensor(1d)
Stress tensor stored in Voigt matricial form.
"""
# Get device from input stress tensor
if device is None:
device = stress.device
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Check stress tensor
if not isinstance(stress, torch.Tensor):
raise RuntimeError('Stress tensor must be torch.Tensor.')
elif len(stress.shape) != 2:
raise RuntimeError('Stress tensor must be torch.Tensor(2d).')
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Check input arguments validity
if any([int(x) not in range(1, n_dim + 1)
for x in list(''.join(comp_order_sym))]):
raise RuntimeError('Invalid component in strain/stress components '
'order.')
elif any([len(comp) != 2 for comp in comp_order_sym]):
raise RuntimeError('Invalid component in strain/stress components '
'order.')
elif len(list(dict.fromkeys(comp_order_sym))) != len(comp_order_sym):
raise RuntimeError('Duplicated component in strain/stress components '
'order.')
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Set second-order and matricial form indexes
so_indexes = list()
mf_indexes = list()
for i in range(len(comp_order_sym)):
so_indexes.append([int(x) - 1 for x in list(comp_order_sym[i])])
mf_indexes.append(comp_order_sym.index(comp_order_sym[i]))
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Initialize stress tensor Voigt matricial form
stress_vmf = torch.zeros(len(comp_order_sym), device=device)
# Store stress tensor in Voigt matricial form
for i in range(len(mf_indexes)):
mf_idx = mf_indexes[i]
so_idx = tuple(so_indexes[i])
stress_vmf[mf_idx] = stress[so_idx]
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
return stress_vmf
# =============================================================================
[docs]def vget_stress_vmf(stress, n_dim, comp_order_sym, device=None):
"""Get stress tensor Voigt matricial form.
Compatible with vectorized mapping.
Parameters
----------
stress : torch.Tensor(2d)
Stress tensor to be stored in Voigt matricial form.
n_dim : int
Problem number of spatial dimensions.
comp_order_sym : tuple
Strain/Stress components symmetric order.
device : torch.device, default=None
Device on which torch.Tensor is allocated.
Returns
-------
stress_vmf : torch.Tensor(1d)
Stress tensor stored in Voigt matricial form.
"""
# Get device from input stress tensor
if device is None:
device = stress.device
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Build indexing mapping
index_map = \
tuple([int(x[i]) - 1 for x in comp_order_sym] for i in range(2))
# Compute tensor Voigt matricial form
stress_vmf = stress[index_map]
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
return stress_vmf
# =============================================================================
[docs]def get_projection_tensors_vmf(n_dim, comp_order_sym, device='cpu'):
"""Get volumetric and deviatoric projection tensors Voigt matricial form.
Parameters
----------
n_dim : int
Number of spatial dimensions.
comp_order_sym : tuple
Strain/Stress components symmetric order.
device : torch.device, default='cpu'
Device on which torch.Tensor is allocated.
Returns
-------
vol_proj_vmf : torch.Tensor(2d)
Volumetric projection tensor Voigt matricial form.
dev_proj_vmf : torch.Tensor(2d)
Deviatoric projection tensor Voigt matricial form.
"""
# Get number of strain components
n_comp = len(comp_order_sym)
# Set second-order identity tensor (strain dimensionality)
soid = torch.eye(n_comp, device=device)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Set volumetric indicator
vol_indicator = torch.tensor(
[1.0 if x[0] == x[1] else 0.0 for x in comp_order_sym], device=device)
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Build volumetric projection operator
vol_proj_vmf = (1.0/n_dim)*torch.outer(vol_indicator, vol_indicator)
# Build deviatoric projection operator
dev_proj_vmf = soid - vol_proj_vmf
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
return vol_proj_vmf, dev_proj_vmf