# Path: discretize/utils/code_utils.py
# def is_scalar(f):
#     """Determine if the input argument is a scalar.
# 
#     The function **is_scalar** returns *True* if the input is an integer,
#     float or complex number. The function returns *False* otherwise.
# 
#     Parameters
#     ----------
#     f :
#         Any input quantity
# 
#     Returns
#     -------
#     bool :
# 
#         - *True* if the input argument is an integer, float or complex number
#         - *False* otherwise
# 
# 
#     """
# 
#     if isinstance(f, SCALARTYPES):
#         return True
#     elif isinstance(f, np.ndarray) and f.size == 1 and isinstance(f[0], SCALARTYPES):
#         return True
#     return False
# 
# def deprecate_function(new_function, old_name, removal_version=None, future_warn=False):
#     if future_warn:
#         Warning = FutureWarning
#     else:
#         Warning = DeprecationWarning
#     new_name = new_function.__name__
#     if removal_version is not None:
#         tag = f" It will be removed in version {removal_version} of discretize."
#     else:
#         tag = " It will be removed in a future version of discretize."
# 
#     def dep_function(*args, **kwargs):
#         warnings.warn(
#             f"{old_name} has been deprecated, please use {new_name}." + tag,
#             Warning,
#         )
#         return new_function(*args, **kwargs)
# 
#     doc = f"""
#     `{old_name}` has been deprecated. See `{new_name}` for documentation
# 
#     See Also
#     --------
#     {new_name}
#     """
#     dep_function.__doc__ = doc
#     return dep_function


import numpy as np
import scipy.sparse as sp
import warnings
from discretize.utils.code_utils import is_scalar, deprecate_function
        \vec{j} = \Sigma \vec{e} \;\;\; where \;\;\;
        \Sigma = \begin{bmatrix}
        \sigma_{xx} & \sigma_{xy} & \sigma_{xz} \\
        \sigma_{xy} & \sigma_{yy} & \sigma_{yz} \\
        \sigma_{xz} & \sigma_{yz} & \sigma_{zz}
        \end{bmatrix}

    In 3D, the tensor is defined by 6 independent element (3 independent elements in
    2D). When using the input argument *tensor* to define the consitutive relationship
    for every cell in the *mesh*, there are 4 classifications recognized by discretize:

    - **Scalar:** :math:`\vec{j} = \sigma \vec{e}`, where :math:`\sigma` a constant.
      Thus the input argument *tensor* is a float.
    - **Isotropic:** :math:`\vec{j} = \sigma \vec{e}`, where :math:`\sigma` varies
      spatially. Thus the input argument *tensor* is a 1D array that provides a
      :math:`\sigma` value for every cell in the mesh.
    - **Anisotropic:** :math:`\vec{j} = \Sigma \vec{e}`, where the off-diagonal elements
      are zero. That is, :math:`\Sigma` is diagonal. In this case, the input argument
      *tensor* defining the physical properties in each cell is a :class:`numpy.ndarray`
      of shape (*nCells*, *dim*).
    - **Tensor:** :math:`\vec{j} = \Sigma \vec{e}`, where off-diagonal elements are
      non-zero and :math:`\Sigma` is a full tensor. In this case, the input argument
      *tensor* defining the physical properties in each cell is a :class:`numpy.ndarray`
      of shape (*nCells*, *nParam*). In 2D, *nParam* = 3 and in 3D, *nParam* = 6.
    """

    def __init__(self, mesh, tensor):
        if tensor is None:  # default is ones
            self._tt = -1
            self._tts = "none"

# Path: discretize/utils/code_utils.py
# def is_scalar(f):
#     """Determine if the input argument is a scalar.
# 
#     The function **is_scalar** returns *True* if the input is an integer,
#     float or complex number. The function returns *False* otherwise.
# 
#     Parameters
#     ----------
#     f :
#         Any input quantity
# 
#     Returns
#     -------
#     bool :
# 
#         - *True* if the input argument is an integer, float or complex number
#         - *False* otherwise
# 
# 
#     """
# 
#     if isinstance(f, SCALARTYPES):
#         return True
#     elif isinstance(f, np.ndarray) and f.size == 1 and isinstance(f[0], SCALARTYPES):
#         return True
#     return False
# 
# def deprecate_function(new_function, old_name, removal_version=None, future_warn=False):
#     if future_warn:
#         Warning = FutureWarning
#     else:
#         Warning = DeprecationWarning
#     new_name = new_function.__name__
#     if removal_version is not None:
#         tag = f" It will be removed in version {removal_version} of discretize."
#     else:
#         tag = " It will be removed in a future version of discretize."
# 
#     def dep_function(*args, **kwargs):
#         warnings.warn(
#             f"{old_name} has been deprecated, please use {new_name}." + tag,
#             Warning,
#         )
#         return new_function(*args, **kwargs)
# 
#     doc = f"""
#     `{old_name}` has been deprecated. See `{new_name}` for documentation
# 
#     See Also
#     --------
#     {new_name}
#     """
#     dep_function.__doc__ = doc
#     return dep_function


# Path: discretize/utils/matrix_utils.py
import numpy as np
import scipy.sparse as sp
import warnings
from discretize.utils.code_utils import is_scalar, deprecate_function

        \vec{j} = \Sigma \vec{e} \;\;\; where \;\;\;
        \Sigma = \begin{bmatrix}
        \sigma_{xx} & \sigma_{xy} & \sigma_{xz} \\
        \sigma_{xy} & \sigma_{yy} & \sigma_{yz} \\
        \sigma_{xz} & \sigma_{yz} & \sigma_{zz}
        \end{bmatrix}

    In 3D, the tensor is defined by 6 independent element (3 independent elements in
    2D). When using the input argument *tensor* to define the consitutive relationship
    for every cell in the *mesh*, there are 4 classifications recognized by discretize:

    - **Scalar:** :math:`\vec{j} = \sigma \vec{e}`, where :math:`\sigma` a constant.
      Thus the input argument *tensor* is a float.
    - **Isotropic:** :math:`\vec{j} = \sigma \vec{e}`, where :math:`\sigma` varies
      spatially. Thus the input argument *tensor* is a 1D array that provides a
      :math:`\sigma` value for every cell in the mesh.
    - **Anisotropic:** :math:`\vec{j} = \Sigma \vec{e}`, where the off-diagonal elements
      are zero. That is, :math:`\Sigma` is diagonal. In this case, the input argument
      *tensor* defining the physical properties in each cell is a :class:`numpy.ndarray`
      of shape (*nCells*, *dim*).
    - **Tensor:** :math:`\vec{j} = \Sigma \vec{e}`, where off-diagonal elements are
      non-zero and :math:`\Sigma` is a full tensor. In this case, the input argument
      *tensor* defining the physical properties in each cell is a :class:`numpy.ndarray`
      of shape (*nCells*, *nParam*). In 2D, *nParam* = 3 and in 3D, *nParam* = 6.
    """

    def __init__(self, mesh, tensor):
        if tensor is None:  # default is ones
            self._tt = -1
            self._tts = "none"

        elif is_scalar(tensor):