"""Strain/Stress tensors matricial storage and procedures.
This module contains fundamental procedures associated with the matricial
storage of strain/stress tensorial quantities and related manipulations.
Among others, it includes functions that set the strain/stress components
symmetric and nonsymmetric matricial storage order and that perform the
conversion between tensorial and matricial forms of second- and fourth-order
tensors following the Kelvin notation [#]_.
.. [#] Nagel, T., Görke, U.-J., Moerman, K. M., and Kolditz, O. (2016). On
       advantages of the Kelvin mapping in finite element implementations of
       deformation processes. Environmental Earth Sciences, 75(11):937 (see
       `here <https://dspace.mit.edu/handle/1721.1/105251>`_)
Functions
---------
get_problem_type_parameters
    Get parameters dependent on the problem type.
get_tensor_mf
    Get tensor matricial form.
get_tensor_from_mf
    Recover tensor from associated matricial form.
kelvin_factor
    Get Kelvin notation coefficient of given strain/stress component.
get_condensed_matrix
    Perform condensation of matrix given a set of rows and columns.
get_state_3Dmf_from_2Dmf
    Build 3D counterpart of 2D strain/stress second-order tensor.
get_state_2Dmf_from_3Dmf
    Build 2D counterpart of 3D strain/stress second- or fourth-order tensor.
matrix_root
    Compute p-th root of general nonsymmetric square matrix.
"""
#
#                                                                       Modules
# =============================================================================
# Third-party
import numpy as np
import scipy.linalg
import itertools as it
#
#                                                          Authorship & Credits
# =============================================================================
__author__ = 'Bernardo Ferreira (bernardo_ferreira@brown.edu)'
__credits__ = ['Bernardo Ferreira', ]
__status__ = 'Stable'
# =============================================================================
#
# =============================================================================
#
#                                                       Problem type parameters
# =============================================================================
[docs]def get_problem_type_parameters(problem_type):
    """Get parameters dependent on the problem type.
    Parameters
    ----------
    problem_type : int
        Problem type: 2D plane strain (1), 2D plane stress (2), 2D axisymmetric
        (3) and 3D (4).
    Returns
    -------
    n_dim : int
        Problem number of spatial dimensions.
    comp_order_sym : list
        Strain/Stress components symmetric order.
    comp_order_nsym : list
        Strain/Stress components nonsymmetric order.
    """
    # Set problem number of spatial dimensions and strain/stress components
    # symmetric and nonsymmetric order
    if problem_type == 1:
        n_dim = 2
        comp_order_sym = ['11', '22', '12']
        comp_order_nsym = ['11', '21', '12', '22']
    elif problem_type == 4:
        n_dim = 3
        comp_order_sym = ['11', '22', '33', '12', '23', '13']
        comp_order_nsym = ['11', '21', '31', '12', '22', '32', '13', '23',
                           '33']
    else:
        raise RuntimeError('Unavailable problem type.')
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    # Return
    return n_dim, comp_order_sym, comp_order_nsym 
#
#                                        Tensorial - Matricial forms conversion
# =============================================================================
[docs]def get_tensor_mf(tensor, n_dim, comp_order):
    """Get tensor matricial form.
    Store a given second-order or fourth-order tensor in matricial form for a
    given number of problem spatial dimensions and given ordered strain/stress
    components list. If the second-order tensor is symmetric or the
    fourth-order tensor has minor symmetry (component list only contains
    independent components), then the Kelvin notation[#]_ is employed to
    perform the storage. Otherwise, the matricial form is built columnwise.
    .. [#] Nagel, T., Görke, U.-J., Moerman, K. M., and Kolditz, O. (2016). On
           advantages of the Kelvin mapping in finite element implementations
           of deformation processes. Environmental Earth Sciences, 75(11):937
           (see `here <https://dspace.mit.edu/handle/1721.1/105251>`_)
    ----
    Parameters
    ----------
    tensor : numpy.ndarray (2d or 4d)
        Tensor to be stored in matricial form.
    n_dim : int
        Problem number of spatial dimensions.
    comp_order : list
        Strain/Stress components order associated to matricial form.
    Returns
    -------
    tensor_mf : numpy.ndarray (1d or 2d)
        Matricial form of input tensor.
    """
    # Get tensor order
    tensor_order = len(tensor.shape)
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    # Check input arguments validity
    if tensor_order not in [2, 4]:
        raise RuntimeError('Matricial form storage is only available for '
                           'second-order or fourth-order tensors.')
    elif any([int(x) not in range(1, n_dim + 1)
              for x in list(''.join(comp_order))]):
        raise RuntimeError('Invalid component in strain/stress components '
                           'order.')
    elif any([tensor.shape[i] != n_dim for i in range(len(tensor.shape))]):
        raise RuntimeError('Invalid tensor dimensions.')
    elif any([len(comp) != 2 for comp in comp_order]):
        raise RuntimeError('Invalid component in strain/stress components '
                           'order.')
    elif len(list(dict.fromkeys(comp_order))) != len(comp_order):
        raise RuntimeError('Duplicated component in strain/stress components '
                           'order.')
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    # Set Kelvin notation flag
    if len(comp_order) == n_dim**2:
        is_kelvin_notation = False
    elif len(comp_order) == sum(range(n_dim + 1)):
        is_kelvin_notation = True
    else:
        raise RuntimeError('Invalid number of components in strain/stress '
                           'components order.')
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    # Store tensor according to tensor order
    if tensor_order == 2:
        # Set second-order and matricial form indexes
        so_indexes = list()
        mf_indexes = list()
        for i in range(len(comp_order)):
            so_indexes.append([int(x) - 1 for x in list(comp_order[i])])
            mf_indexes.append(comp_order.index(comp_order[i]))
        # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        # Initialize tensor matricial form
        if tensor.dtype == 'complex':
            tensor_mf = np.zeros(len(comp_order), dtype=complex)
        else:
            tensor_mf = np.zeros(len(comp_order))
        # Store tensor in matricial form
        for i in range(len(mf_indexes)):
            mf_idx = mf_indexes[i]
            so_idx = tuple(so_indexes[i])
            factor = 1.0
            if is_kelvin_notation and not so_idx[0] == so_idx[1]:
                factor = np.sqrt(2)
            tensor_mf[mf_idx] = factor*tensor[so_idx]
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    elif tensor_order == 4:
        # Set cartesian product of component list
        comps = list(it.product(comp_order, comp_order))
        # Set fourth-order and matricial form indexes
        fo_indexes = list()
        mf_indexes = list()
        for i in range(len(comp_order)**2):
            fo_indexes.append([int(x) - 1 for x in
                               list(comps[i][0] + comps[i][1])])
            mf_indexes.append([x for x in [comp_order.index(comps[i][0]),
                                           comp_order.index(comps[i][1])]])
        # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        # Initialize tensor matricial form
        if tensor.dtype == 'complex':
            tensor_mf = np.zeros((len(comp_order), len(comp_order)),
                                 dtype=complex)
        else:
            tensor_mf = np.zeros((len(comp_order), len(comp_order)))
        # Store tensor in matricial form
        for i in range(len(mf_indexes)):
            mf_idx = tuple(mf_indexes[i])
            fo_idx = tuple(fo_indexes[i])
            factor = 1.0
            if is_kelvin_notation and not (fo_idx[0] == fo_idx[1]
                                           and fo_idx[2] == fo_idx[3]):
                factor = \
                    
factor*np.sqrt(2) if fo_idx[0] != fo_idx[1] else factor
                factor = \
                    
factor*np.sqrt(2) if fo_idx[2] != fo_idx[3] else factor
            tensor_mf[mf_idx] = factor*tensor[fo_idx]
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    # Return
    return tensor_mf 
# =============================================================================
[docs]def get_tensor_from_mf(tensor_mf, n_dim, comp_order):
    """Recover tensor from associated matricial form.
    Recover a given second-order or fourth-order tensor from the associated
    matricial form, given the problem number of spatial dimensions and given a
    (compatible) ordered strain/stress components list. If the second-order
    tensor is symmetric or the fourth-order tensor has minor symmetry
    (component list only contains independent components), then matricial form
    is assumed to follow the Kelvin notation [#]_. Otherwise, a columnwise
    matricial form is assumed.
    .. [#] Nagel, T., Görke, U.-J., Moerman, K. M., and Kolditz, O. (2016). On
           advantages of the Kelvin mapping in finite element implementations
           of deformation processes. Environmental Earth Sciences, 75(11):937
           (see `here <https://dspace.mit.edu/handle/1721.1/105251>`_)
    ----
    Parameters
    ----------
    tensor_mf : numpy.ndarray (1d or 2d)
        Tensor stored in matricial form.
    n_dim : int
        Problem number of spatial dimensions.
    comp_order : list
        Strain/Stress components order associated to matricial form.
    Returns
    -------
    tensor : numpy.ndarray
        Tensor recovered from matricial form.
    """
    # Set tensor order
    if len(tensor_mf.shape) == 1:
        tensor_order = 2
        if tensor_mf.shape[0] != n_dim**2 and \
                
tensor_mf.shape[0] != sum(range(n_dim + 1)):
            raise RuntimeError('Invalid number of components in tensor '
                               'matricial form.')
    elif len(tensor_mf.shape) == 2:
        tensor_order = 4
        if tensor_mf.shape[0] != tensor_mf.shape[1]:
            raise RuntimeError('Fourth-order tensor matricial form must be a'
                               'square matrix.')
        elif tensor_mf.shape[0] != n_dim**2 and \
                
tensor_mf.shape[0] != sum(range(n_dim + 1)):
            raise RuntimeError('Invalid number of components in tensor '
                               'matricial form.')
    else:
        raise RuntimeError('Tensor matricial form must be a vector or a '
                           'matrix.')
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    # Check input arguments validity
    if any([int(x) not in range(1, n_dim + 1)
            for x in list(''.join(comp_order))]):
        raise RuntimeError('Invalid component in strain/stress components '
                           'order.')
    elif any([len(comp) != 2 for comp in comp_order]):
        raise RuntimeError('Invalid component in strain/stress components '
                           'order.')
    elif len(list(dict.fromkeys(comp_order))) != len(comp_order):
        raise RuntimeError('Duplicated component in strain/stress components '
                           'order.')
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    # Set Kelvin notation flag
    if len(comp_order) == n_dim**2:
        is_kelvin_notation = False
    elif len(comp_order) == sum(range(n_dim + 1)):
        is_kelvin_notation = True
    else:
        raise RuntimeError('Invalid number of components in strain/stress '
                           'components order.')
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    # Get tensor according to tensor order
    if tensor_order == 2:
        # Set second-order and matricial form indexes
        so_indexes = list()
        mf_indexes = list()
        for i in range(len(comp_order)):
            so_indexes.append([int(x) - 1 for x in list(comp_order[i])])
            mf_indexes.append(comp_order.index(comp_order[i]))
        # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        # Initialize tensor
        if tensor_mf.dtype == 'complex':
            tensor = np.zeros(tensor_order*(n_dim,), dtype=complex)
        else:
            tensor = np.zeros(tensor_order*(n_dim,))
        # Get 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 is_kelvin_notation and not so_idx[0] == so_idx[1]:
                factor = np.sqrt(2)
                tensor[so_idx[::-1]] = (1.0/factor)*tensor_mf[mf_idx]
            tensor[so_idx] = (1.0/factor)*tensor_mf[mf_idx]
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    elif tensor_order == 4:
        # Set cartesian product of component list
        comps = list(it.product(comp_order, comp_order))
        # Set fourth-order and matricial form indexes
        mf_indexes = list()
        fo_indexes = list()
        for i in range(len(comp_order)**2):
            fo_indexes.append([int(x) - 1
                               for x in list(comps[i][0] + comps[i][1])])
            mf_indexes.append([x for x in [comp_order.index(comps[i][0]),
                                           comp_order.index(comps[i][1])]])
        # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        # Initialize tensor
        if tensor_mf.dtype == 'complex':
            tensor = np.zeros(tensor_order*(n_dim, ), dtype=complex)
        else:
            tensor = np.zeros(tensor_order*(n_dim, ))
        # Get tensor from matricial form
        for i in range(len(mf_indexes)):
            mf_idx = tuple(mf_indexes[i])
            fo_idx = tuple(fo_indexes[i])
            factor = 1.0
            if is_kelvin_notation and not (fo_idx[0] == fo_idx[1]
                                           and fo_idx[2] == fo_idx[3]):
                factor = \
                    
factor*np.sqrt(2) if fo_idx[0] != fo_idx[1] else factor
                factor = \
                    
factor*np.sqrt(2) if fo_idx[2] != fo_idx[3] else factor
                if fo_idx[0] != fo_idx[1] and fo_idx[2] != fo_idx[3]:
                    tensor[tuple(fo_idx[1::-1]+fo_idx[2:])] = \
                        
(1.0/factor)*tensor_mf[mf_idx]
                    tensor[tuple(fo_idx[:2]+fo_idx[3:1:-1])] = \
                        
(1.0/factor)*tensor_mf[mf_idx]
                    tensor[tuple(fo_idx[1::-1]+fo_idx[3:1:-1])] = \
                        
(1.0/factor)*tensor_mf[mf_idx]
                elif fo_idx[0] != fo_idx[1]:
                    tensor[tuple(fo_idx[1::-1]+fo_idx[2:])] = \
                        
(1.0/factor)*tensor_mf[mf_idx]
                elif fo_idx[2] != fo_idx[3]:
                    tensor[tuple(fo_idx[:2]+fo_idx[3:1:-1])] = \
                        
(1.0/factor)*tensor_mf[mf_idx]
            tensor[fo_idx] = (1.0/factor)*tensor_mf[mf_idx]
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    # Return
    return tensor 
# =============================================================================
[docs]def kelvin_factor(idx, comp_order):
    """Get Kelvin notation coefficient of given strain/stress component.
    The Kelvin notation [#]_ is a particular way of building the matricial form
    of tensorial quantities.
    .. [#] Nagel, T., Görke, U.-J., Moerman, K. M., and Kolditz, O. (2016). On
           advantages of the Kelvin mapping in finite element implementations
           of deformation processes. Environmental Earth Sciences, 75(11):937
           (see `here <https://dspace.mit.edu/handle/1721.1/105251>`_)
    ----
    Parameters
    ----------
    idx : int (or list[int])
        Index of strain/stress component. Alternatively, a pair of
        strain/stress components indexes (associated to a given fourth-order
        tensor matricial form element) can also be provided.
    comp_order : list
        Strain/Stress components order associated to matricial form.
    Returns
    -------
    factor : float
        Kelvin notation coefficient.
    """
    if len(comp_order) == 4 or len(comp_order) == 9:
        # Set Kelvin coefficient associated to a non-symmetric tensor matricial
        # storage
        factor = 1.0
    else:
        # Set Kelvin coefficient associated to symmetric tensor matricial
        # storage
        if isinstance(idx, int) or isinstance(idx, np.integer):
            # Set Kelvin coefficient associated with single strain/stress
            # component
            if int(list(comp_order[idx])[0]) == int(list(comp_order[idx])[1]):
                factor = 1.0
            else:
                factor = np.sqrt(2)
        # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        elif isinstance(idx, list) and len(idx) == 2:
            # Set Kelvin coefficient associated with pair of strain/stress
            # components
            factor = 1.0
            for i in idx:
                if int(list(comp_order[i])[0]) != int(list(comp_order[i])[1]):
                    factor = factor*np.sqrt(2)
        # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        else:
            raise RuntimeError('Invalid strain/stress component(s) index(es).')
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    return factor 
#
#                                                           Matrix condensation
# =============================================================================
[docs]def get_condensed_matrix(matrix, rows, cols, is_check_indexes=False):
    """Perform condensation of matrix given a set of rows and columns.
    Parameters
    ----------
    matrix : numpy.ndarray (2d)
        Matrix to be condensed.
    rows : numpy.ndarray (1d)
        Indexes of rows to keep in condensed matrix.
    cols : numpy.ndarray (1d)
        Indexes of columns to keep in condensed matrix.
    is_check_indexes : bool, default=False
        If True, then check validity of condensed rows and columns indexes
        before performing matrix condensation. May yield non-negligible
        overhead cost when condensing large matrix.
    Returns
    -------
    matrix_condensed : numpy.ndarray (2d)
        Condensed matrix.
    """
    # Check validity of rows and columns indexes to perform the condensation
    if is_check_indexes:
        if not np.all([isinstance(rows[i], int)
                       or isinstance(rows[i], np.integer)
                       for i in range(len(rows))]):
            raise RuntimeError('All the indexes specified to perform a matrix '
                               'condensation must be non-negative integers.')
        elif not np.all([isinstance(cols[i], int)
                        or isinstance(cols[i], np.integer)
                        for i in range(len(cols))]):
            raise RuntimeError('All the indexes specified to perform a matrix '
                               'condensation must be non-negative integers.')
        elif len(list(dict.fromkeys(rows))) != len(rows) or \
                
len(list(dict.fromkeys(cols))) != len(cols):
            raise RuntimeError('Duplicated rows or columns indexes.')
        elif np.any([rows[i] not in range(matrix.shape[0])
                    for i in range(len(rows))]):
            raise RuntimeError('Out-of-bounds row index.')
        elif np.any([cols[i] not in range(matrix.shape[1])
                    for i in range(len(cols))]):
            raise RuntimeError('Out-of-bounds column index.')
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    # Build auxiliary matrices with rows and columns condensation indexes
    rows_matrix = np.zeros((len(rows), len(cols)), dtype=int)
    cols_matrix = np.zeros((len(rows), len(cols)), dtype=int)
    for j in range(len(cols)):
        rows_matrix[:, j] = rows
    for i in range(len(rows)):
        cols_matrix[i, :] = cols
    # Build condensed matrix
    matrix_condensed = matrix[rows_matrix, cols_matrix]
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    # Return condensed matrix
    return matrix_condensed 
#
#                              Strain/Stress 2D - 3D matricial form conversions
# =============================================================================
[docs]def get_state_3Dmf_from_2Dmf(problem_type, mf_2d, comp_33):
    """Build 3D counterpart of 2D strain/stress second-order tensor.
    Parameters
    ----------
    problem_type : int
        Problem type: 2D plane strain (1), 2D plane stress (2),
        2D axisymmetric (3) and 3D (4).
    mf_2d : numpy.ndarray (1d)
        Matricial form of 2D strain/stress second-order tensor.
    comp_33 : float
        Out-of-plane strain/stress component.
    Returns
    -------
    mf_3d : numpy.ndarray (1d)
        Matricial form of 3D strain/stress second-order tensor.
    """
    # Get 2D strain/stress components order in symmetric and nonsymmetric cases
    _, comp_order_sym_2d, comp_order_nsym_2d = \
        
get_problem_type_parameters(problem_type=1)
    # Get 3D strain/stress components order in symmetric and nonsymmetric cases
    _, comp_order_sym_3d, comp_order_nsym_3d = \
        
get_problem_type_parameters(problem_type=4)
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    # Set required strain/stress component order according to strain tensor
    # symmetry
    if len(mf_2d) == len(comp_order_sym_2d):
        comp_order_2d = comp_order_sym_2d
        comp_order_3d = comp_order_sym_3d
    else:
        comp_order_2d = comp_order_nsym_2d
        comp_order_3d = comp_order_nsym_3d
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    # Build 3D strain/stress second-order tensor (matricial form)
    mf_3d = np.zeros(len(comp_order_3d))
    if problem_type in [3, 4]:
        raise RuntimeError('Unavailable problem type.')
    else:
        # Include out-of-plane strain/stress component under 2D plane
        # strain/stress conditions
        for i in range(len(comp_order_2d)):
            comp = comp_order_2d[i]
            mf_3d[comp_order_3d.index(comp)] = mf_2d[i]
        mf_3d[comp_order_3d.index('33')] = comp_33
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    # Return
    return mf_3d 
# =============================================================================
[docs]def get_state_2Dmf_from_3Dmf(problem_type, mf_3d):
    """Build 2D counterpart of 3D strain/stress second- or fourth-order tensor.
    Parameters
    ----------
    problem_type : int
        Problem type: 2D plane strain (1), 2D plane stress (2),
        2D axisymmetric (3) and 3D (4).
    mf_3d : numpy.ndarray (1d or 2d)
        Matricial form of 3D strain/stress related tensor.
    Returns
    -------
    mf_2d : numpy.ndarray (1d or 2d)
        Matricial form of 2D strain/stress related tensor.
    """
    # Get 2D strain/stress components order in symmetric and nonsymmetric cases
    _, comp_order_sym_2d, comp_order_nsym_2d = \
        
get_problem_type_parameters(problem_type=1)
    # Get 3D strain/stress components order in symmetric and nonsymmetric cases
    _, comp_order_sym_3d, comp_order_nsym_3d = \
        
get_problem_type_parameters(problem_type=4)
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    # Set required strain/stress component order according to strain tensor
    # symmetry
    if len(mf_3d) == len(comp_order_sym_3d):
        comp_order_2d = comp_order_sym_2d
        comp_order_3d = comp_order_sym_3d
    else:
        comp_order_2d = comp_order_nsym_2d
        comp_order_3d = comp_order_nsym_3d
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    # Build 2D strain/stress related tensor (matricial form)
    mf_2d = np.zeros(len(mf_3d.shape)*(len(comp_order_2d),))
    if len(mf_3d.shape) == 1:
        for i in range(len(comp_order_2d)):
            comp = comp_order_2d[i]
            mf_2d[i] = mf_3d[comp_order_3d.index(comp)]
    elif len(mf_3d.shape) == 2:
        for j in range(len(comp_order_2d)):
            comp_j = comp_order_2d[j]
            for i in range(len(comp_order_2d)):
                comp_i = comp_order_2d[i]
                mf_2d[i, j] = mf_3d[comp_order_3d.index(comp_i),
                                    comp_order_3d.index(comp_j)]
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    # Return
    return mf_2d 
#
#                                                    P-th root of square matrix
# =============================================================================
[docs]def matrix_root(matrix, p):
    """Compute p-th root of general nonsymmetric square matrix.
    Parameters
    ----------
    matrix : numpy.ndarray (2d)
        Square matrix.
    p : float
        Factor which defines the p-th root of square matrix.
    Returns
    -------
    matrix_proot : numpy.ndarray (2d)
        p-th root of square matrix.
    """
    # Compute p-th root of square matrix
    matrix_proot = scipy.linalg.expm(p*scipy.linalg.logm(matrix))
    # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    # Return
    return matrix_proot