Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nick/tenmat docs #294

Open
wants to merge 18 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Improve some documentation for updated constructors/methods.
  • Loading branch information
ntjohnson1 committed Dec 13, 2023
commit 8a04061cb934d197d6ee6ed99c6fe91ff17a755a
10 changes: 3 additions & 7 deletions docs/source/tutorial/class_sptenmat.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@
"metadata": {},
"outputs": [],
"source": [
"A = X.to_sptenmat(cdims=np.array([1, 2])) # Specify column dimensions.\n",
"A = X.to_sptenmat(cdims=np.array([1, 2])) # Specify column dimensions.\n",
"A"
]
},
Expand Down Expand Up @@ -224,9 +224,7 @@
"metadata": {},
"outputs": [],
"source": [
"B = ttb.sptenmat(\n",
" A.subs, A.vals, A.rdims, A.cdims, A.tshape\n",
") # Effectively copies A\n",
"B = ttb.sptenmat(A.subs, A.vals, A.rdims, A.cdims, A.tshape) # Effectively copies A\n",
"B"
]
},
Expand All @@ -245,9 +243,7 @@
"metadata": {},
"outputs": [],
"source": [
"A = ttb.sptenmat(\n",
" rdims=A.rdims, cdims=A.cdims, tshape=A.tshape\n",
") # An empty sptenmat\n",
"A = ttb.sptenmat(rdims=A.rdims, cdims=A.cdims, tshape=A.tshape) # An empty sptenmat\n",
"A"
]
},
Expand Down
49 changes: 43 additions & 6 deletions pyttb/sptenmat.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from scipy import sparse

import pyttb as ttb
from pyttb.pyttb_utils import tt_ind2sub
from pyttb.pyttb_utils import gather_wrap_dims, tt_ind2sub


class sptenmat:
Expand Down Expand Up @@ -79,19 +79,34 @@ def __init__( # noqa: PLR0913
[1, 6] = 6
[1, 7] = 7
"""
# Empty case
if rdims is None and cdims is None:
assert (
subs is None and vals is None
), "Must provide rdims or cdims with values"
self.subs = np.array([], ndmin=2, dtype=int)
self.vals = np.array([], ndmin=2)
self.rdims = np.array([], dtype=int)
self.cdims = np.array([], dtype=int)
self.tshape: Union[Tuple[()], Tuple[int, ...]] = ()
return

if subs is None:
subs = np.array([], ndmin=2, dtype=int)
if vals is None:
vals = np.array([], ndmin=2)
if rdims is None:
rdims = np.array([], dtype=int)
if cdims is None:
cdims = np.array([], dtype=int)

n = len(tshape)
alldims = np.array([range(n)])
# Error check
dims = np.hstack([rdims, cdims], dtype=int)
rdims, cdims = gather_wrap_dims(n, rdims, cdims)
# if rdims or cdims is empty, hstack will output an array of float not int
if rdims.size == 0:
dims = cdims.copy()
elif cdims.size == 0:
dims = rdims.copy()
else:
dims = np.hstack([rdims, cdims], dtype=int)
assert len(dims) == n and (alldims == np.sort(dims)).all(), (
"Incorrect specification of dimensions, the sorted concatenation of "
"rdims and cdims must be range(len(tshape))."
Expand Down Expand Up @@ -154,6 +169,28 @@ def from_array(
Mapping of column indices.
tshape:
Shape of the original tensor.

Examples
--------
Create a :class:`pyttb.sptenmat` from a sparse matrix and unwrapping
dimensions. Infer column dimensions from row dimensions specification.

>>> data = np.array([6, 7])
>>> rows = np.array([1, 1])
>>> cols = np.array([6, 7])
>>> sparse_matrix = sparse.coo_matrix((data, (rows, cols)))
>>> tshape = (4, 4, 4)
>>> S = ttb.sptenmat.from_array(\
sparse_matrix,\
rdims=np.array([0]),\
tshape=tshape\
)
>>> S # doctest: +NORMALIZE_WHITESPACE
sptenmat corresponding to a sptensor of shape (4, 4, 4) with 2 nonzeros
rdims = [ 0 ] (modes of sptensor corresponding to rows)
cdims = [ 1, 2 ] (modes of sptensor corresponding to columns)
[1, 6] = 6
[1, 7] = 7
"""
vals = None
if isinstance(array, np.ndarray):
Expand Down
9 changes: 7 additions & 2 deletions pyttb/sptensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -707,11 +707,16 @@ def to_sptenmat(
Mapping of column indices.
cdims_cyclic:
When only rdims is specified maps a single rdim to the rows and
the remaining dimensons span the columns. _fc_ (forward cyclic)
the remaining dimensons span the columns. _fc_ (forward cyclic[1]_)
in the order range(rdims,self.ndims()) followed by range(0, rdims).
_bc_ (backward cyclic) range(rdims-1, -1, -1) then
_bc_ (backward cyclic[2]_) range(rdims-1, -1, -1) then
range(self.ndims(), rdims, -1).

Notes
-----
Forward cyclic is defined by Kiers [1]_ and backward cyclic is defined by
De Lathauwer, De Moor, and Vandewalle [2]_.

References
----------
.. [1] KIERS, H. A. L. 2000. Towards a standardized notation and terminology
Expand Down
149 changes: 135 additions & 14 deletions pyttb/tenmat.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,58 @@ def __init__( # noqa: PLR0912
tshape: Optional[Tuple[int, ...]] = None,
):
"""
Creates a tenmat from explicit description.
Construct a :class:`pyttb.tenmat` from explicit components.
If you already have a tensor see :method:`pyttb.tensor.to_tenmat`.

Parameters
----------
data:
Tensor source data
Flattened tensor data.
rdims:
Which dimensions of original tensor map to rows.
cdims:
Which dimensions of original tensor map to columns.
tshape:
Original tensor shape.

Returns
-------
Constructed tenmat
Examples
--------
Create an empty :class:`pyttb.tenmat`.

>>> ttb.tenmat() # doctest: +NORMALIZE_WHITESPACE
matrix corresponding to a tensor of shape ()
rindices = [ ] (modes of tensor corresponding to rows)
cindices = [ ] (modes of tensor corresponding to columns)
data = []

Create tensor shaped data.

>>> tshape = (2, 2, 2)
>>> data = np.reshape(np.arange(np.prod(tshape), dtype=np.double), tshape)
>>> data # doctest: +NORMALIZE_WHITESPACE
array([[[0., 1.],
[2., 3.]],
[[4., 5.],
[6., 7.]]])

Manually matrize the tensor.

>>> flat_data = np.reshape(data, (2,4), order="F")
>>> flat_data # doctest: +NORMALIZE_WHITESPACE
array([[0., 2., 1., 3.],
[4., 6., 5., 7.]])

Encode matrication into :class:`pyttb.tenmat`.

>>> tm = ttb.tenmat(flat_data, rdims=np.array([0]), tshape=tshape)

Extract original tensor shaped data.

>>> tm.to_tensor().double() # doctest: +NORMALIZE_WHITESPACE
array([[[0., 1.],
[2., 3.]],
[[4., 5.],
[6., 7.]]])
"""

# Case 0a: Empty Contructor
Expand Down Expand Up @@ -153,7 +192,43 @@ def __deepcopy__(self, memo):
return self.copy()

def to_tensor(self) -> ttb.tensor:
"""Return copy of tenmat data as a tensor"""
"""
Return copy of tenmat data as a tensor.

Examples
--------
Create tensor shaped data.

>>> tshape = (2, 2, 2)
>>> data = np.reshape(np.arange(np.prod(tshape), dtype=np.double), tshape)
>>> data # doctest: +NORMALIZE_WHITESPACE
array([[[0., 1.],
[2., 3.]],
[[4., 5.],
[6., 7.]]])

Manually matrize the tensor.

>>> flat_data = np.reshape(data, (2,4), order="F")
>>> flat_data # doctest: +NORMALIZE_WHITESPACE
array([[0., 2., 1., 3.],
[4., 6., 5., 7.]])

Encode matrication into :class:`pyttb.tenmat`.

>>> tm = ttb.tenmat(flat_data, rdims=np.array([0]), tshape=tshape)

Extract original tensor shaped data.

>>> tm.to_tensor() # doctest: +NORMALIZE_WHITESPACE
tensor of shape (2, 2, 2)
data[0, :, :] =
[[0. 1.]
[2. 3.]]
data[1, :, :] =
[[4. 5.]
[6. 7.]]
"""
# RESHAPE TENSOR-AS-MATRIX
# Here we just reverse what was done in the tenmat constructor.
# First we reshape the data to be an MDA, then we un-permute
Expand All @@ -169,14 +244,29 @@ def ctranspose(self) -> tenmat:
"""
Complex conjugate transpose for tenmat.

Parameters
----------

Returns
-------
:class:`pyttb.tenmat`
Examples
--------
Create :class:`pyttb.tensor` then convert to :class:`pyttb.tenmat`.

>>> T = ttb.tenones((2,2,2))
>>> TM = T.to_tenmat(rdims=np.array([0]))
>>> TM # doctest: +NORMALIZE_WHITESPACE
matrix corresponding to a tensor of shape (2, 2, 2)
rindices = [ 0 ] (modes of tensor corresponding to rows)
cindices = [ 1, 2 ] (modes of tensor corresponding to columns)
data[:, :] =
[[1. 1. 1. 1.]
[1. 1. 1. 1.]]
>>> TM.ctranspose() # doctest: +NORMALIZE_WHITESPACE
matrix corresponding to a tensor of shape (2, 2, 2)
rindices = [ 1, 2 ] (modes of tensor corresponding to rows)
cindices = [ 0 ] (modes of tensor corresponding to columns)
data[:, :] =
[[1. 1.]
[1. 1.]
[1. 1.]
[1. 1.]]
"""

tenmatInstance = tenmat()
tenmatInstance.rindices = self.cindices.copy()
tenmatInstance.cindices = self.rindices.copy()
Expand All @@ -188,6 +278,21 @@ def double(self) -> np.ndarray:
"""
Convert tenmat to an array of doubles

Examples
--------
>>> T = ttb.tenones((2,2,2))
>>> TM = T.to_tenmat(rdims=np.array([0]))
>>> TM # doctest: +NORMALIZE_WHITESPACE
matrix corresponding to a tensor of shape (2, 2, 2)
rindices = [ 0 ] (modes of tensor corresponding to rows)
cindices = [ 1, 2 ] (modes of tensor corresponding to columns)
data[:, :] =
[[1. 1. 1. 1.]
[1. 1. 1. 1.]]
>>> TM.double() # doctest: +NORMALIZE_WHITESPACE
array([[1., 1., 1., 1.],
[1., 1., 1., 1.]])

Returns
-------
Copy of tenmat data.
Expand All @@ -200,7 +305,23 @@ def ndims(self) -> int:
return len(self.shape)

def norm(self) -> float:
"""Frobenius norm of a tenmat."""
"""
Frobenius norm of a tenmat.

Examples
--------
>>> T = ttb.tenones((2,2,2))
>>> TM = T.to_tenmat(rdims=np.array([0]))
>>> TM # doctest: +NORMALIZE_WHITESPACE
matrix corresponding to a tensor of shape (2, 2, 2)
rindices = [ 0 ] (modes of tensor corresponding to rows)
cindices = [ 1, 2 ] (modes of tensor corresponding to columns)
data[:, :] =
[[1. 1. 1. 1.]
[1. 1. 1. 1.]]
>>> TM.norm() # doctest: +ELLIPSIS
2.82...
"""
# default of np.linalg.norm is to vectorize the data and compute the vector
# norm, which is equivalent to the Frobenius norm for multidimensional arrays.
# However, the argument 'fro' only works for 1-D and 2-D
Expand Down
5 changes: 5 additions & 0 deletions pyttb/tensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,11 @@ def to_tenmat(
_bc_ (backward cyclic) range(rdims-1, -1, -1) then
range(self.ndims(), rdims, -1).

Notes
-----
Forward cyclic is defined by Kiers [1]_ and backward cyclic is defined by
De Lathauwer, De Moor, and Vandewalle [2]_.

References
----------
.. [1] KIERS, H. A. L. 2000. Towards a standardized notation and terminology
Expand Down