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 all commits
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
7 changes: 6 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,9 @@ htmlcov
__pycache__
.vs*
TestResults
.DS_Store
.DS_Store
# Static tensor data
profiling/**/*.tns
# Profiling results
*.pstats
profiling/**/*.png
11 changes: 11 additions & 0 deletions profiling/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Profiling PYTTB
Profiling support for pyttb is limited at this time with a focus on feature coverage.
Proposals and contributions welcome!
However, this directory contains minimal initial support.

## Setup
* `pip install .[dev,profiling]`
* Get the dev and profiling dependencies for a local install from source
* Generate the profiling data in `./data`
* Unfortunately this currently depends on MATLAB and the MATLAB TensorToolbox
* Run the `algorithms_profiling.ipynb` which currently takes ~10s of minutes
44 changes: 43 additions & 1 deletion profiling/algorithms_profiling.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
"import glob\n",
"import os\n",
"import pstats\n",
"import subprocess\n",
"from pathlib import Path\n",
"from typing import Callable, Dict, List, Optional, Union\n",
"\n",
"from pyttb import cp_als, cp_apr, hosvd, import_data, sptensor, tensor, tucker_als"
Expand Down Expand Up @@ -194,6 +196,28 @@
" )"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def generate_all_images():\n",
" \"\"\"Gathers all pstats files and renders pngs for inspection\"\"\"\n",
" stats_files = Path(\".\").glob(\"**/*.pstats\")\n",
" for a_file in stats_files:\n",
" algorithm = a_file.parts[-2]\n",
" experiment_name = a_file.stem\n",
" print(f\"For {algorithm}: generating {experiment_name}\")\n",
" Path(f\"./gprof2dot_images/{algorithm}\").mkdir(parents=True, exist_ok=True)\n",
" subprocess.run(\n",
" f\"gprof2dot -f pstats {a_file} |\"\n",
" f\" dot -Tpng -o ./gprof2dot_images/{algorithm}/{experiment_name}.png\",\n",
" shell=True,\n",
" check=True,\n",
" )"
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down Expand Up @@ -380,6 +404,24 @@
"## Visualizing Profiling Output with ***gprof2dot***"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Generating all algorithms' profiling images\n",
" \n",
"The cell bellow will generate all profiling images for all algorithms in `./gprof2dot_images/<specific_algorithm>`"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"generate_all_images()"
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down Expand Up @@ -414,5 +456,5 @@
],
"metadata": {},
"nbformat": 4,
"nbformat_minor": 2
"nbformat_minor": 4
}
4 changes: 4 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ doc = [
"myst-nb",
"matplotlib",
]
profiling = [
"gprof2dot",
"graphviz",
]

[tool.setuptools.packages.find]
include = ["pyttb*"]
Expand Down
11 changes: 5 additions & 6 deletions pyttb/cp_apr.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@
from numpy_groupies import aggregate as accumarray

import pyttb as ttb
from pyttb.pyttb_utils import tt_to_dense_matrix


def cp_apr( # noqa: PLR0913
Expand Down Expand Up @@ -524,7 +523,7 @@ def tt_cp_apr_pdnr( # noqa: PLR0912,PLR0913,PLR0915
if isinstance(input_tensor, ttb.tensor) and isSparse is False:
# Data is not a sparse tensor.
Pi = tt_calcpi_prowsubprob(input_tensor, M, rank, n, N, isSparse)
X_mat = tt_to_dense_matrix(input_tensor, n)
X_mat = input_tensor.to_tenmat(np.array([n]), copy=False).data

num_rows = M.factor_matrices[n].shape[0]
isRowNOTconverged = np.zeros((num_rows,))
Expand Down Expand Up @@ -879,7 +878,7 @@ def tt_cp_apr_pqnr( # noqa: PLR0912,PLR0913,PLR0915
if not isinstance(input_tensor, ttb.sptensor) and not isSparse:
# Data is not a sparse tensor.
Pi = tt_calcpi_prowsubprob(input_tensor, M, rank, n, N, isSparse)
X_mat = tt_to_dense_matrix(input_tensor, n)
X_mat = input_tensor.to_tenmat(np.array([n]), copy=False).data

num_rows = M.factor_matrices[n].shape[0]
isRowNOTconverged = np.zeros((num_rows,))
Expand Down Expand Up @@ -1786,7 +1785,7 @@ def calculate_phi( # noqa: PLR0913
)
Phi[:, r] = Yr
else:
Xn = tt_to_dense_matrix(Data, factorIndex)
Xn = Data.to_tenmat(np.array([factorIndex]), copy=False).data
V = Model.factor_matrices[factorIndex].dot(Pi.transpose())
W = Xn / np.maximum(V, epsilon)
Y = W.dot(Pi)
Expand Down Expand Up @@ -1831,8 +1830,8 @@ def tt_loglikelihood(
np.sum(Data.vals * np.log(np.sum(A, axis=1))[:, None])
- np.sum(Model.factor_matrices[0])
)
dX = tt_to_dense_matrix(Data, 1)
dM = tt_to_dense_matrix(Model, 1)
dX = Data.to_tenmat(np.array([1]), copy=False).data
dM = Model.to_tenmat(np.array([1]), copy=False).data
f = 0
for i in range(dX.shape[0]):
for j in range(dX.shape[1]):
Expand Down
72 changes: 72 additions & 0 deletions pyttb/ktensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -891,6 +891,78 @@ def full(self) -> ttb.tensor:
data = self.weights @ ttb.khatrirao(*self.factor_matrices, reverse=True).T
return ttb.tensor(data, self.shape, copy=False)

def to_tenmat(
self,
rdims: Optional[np.ndarray] = None,
cdims: Optional[np.ndarray] = None,
cdims_cyclic: Optional[
Union[Literal["fc"], Literal["bc"], Literal["t"]]
] = None,
copy: bool = True,
) -> ttb.tenmat:
"""
Construct a :class:`pyttb.tenmat` from a :class:`pyttb.ktensor` and
unwrapping details.

Parameters
----------
rdims:
Mapping of row indices.
cdims:
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)
in the order range(rdims,self.ndims()) followed by range(0, rdims).
_bc_ (backward cyclic) range(rdims-1, -1, -1) then
range(self.ndims(), rdims, -1).
copy:
Whether to make a copy of provided data or just reference it.

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
in multiway analysis. J. Chemometrics 14, 105-122.
.. [2] DE LATHAUWER, L., DE MOOR, B., AND VANDEWALLE, J. 2000b. On the best
rank-1 and rank-(R1, R2, ... , RN ) approximation of higher-order
tensors. SIAM J. Matrix Anal. Appl. 21, 4, 1324-1342.

Examples
--------
>>> weights = np.array([1., 2.])
>>> fm0 = np.array([[1., 2.], [3., 4.]])
>>> fm1 = np.array([[5., 6.], [7., 8.]])
>>> K = ttb.ktensor([fm0, fm1], weights)
>>> print(K)
ktensor of shape (2, 2)
weights=[1. 2.]
factor_matrices[0] =
[[1. 2.]
[3. 4.]]
factor_matrices[1] =
[[5. 6.]
[7. 8.]]
>>> K.full() # doctest: +NORMALIZE_WHITESPACE
tensor of shape (2, 2)
data[:, :] =
[[29. 39.]
[63. 85.]]
>>> K.to_tenmat(np.array([0])) # doctest: +NORMALIZE_WHITESPACE
matrix corresponding to a tensor of shape (2, 2)
rindices = [ 0 ] (modes of tensor corresponding to rows)
cindices = [ 1 ] (modes of tensor corresponding to columns)
data[:, :] =
[[29. 39.]
[63. 85.]]
"""
# Simplest but slightly less efficient solution
return self.full().to_tenmat(rdims, cdims, cdims_cyclic, copy)

def innerprod(
self, other: Union[ttb.tensor, ttb.sptensor, ktensor, ttb.ttensor]
) -> float:
Expand Down
71 changes: 0 additions & 71 deletions pyttb/pyttb_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,77 +14,6 @@
import pyttb as ttb


def tt_to_dense_matrix(
tensorInstance: Union[ttb.tensor, ttb.ktensor], mode: int, transpose: bool = False
) -> np.ndarray:
"""
Helper function to unwrap tensor into dense matrix, should replace the core need
for tenmat

Parameters
----------
tensorInstance:
Tensor to matricize
mode:
Mode around which to unwrap tensor
transpose:
Whether or not to tranpose unwrapped tensor

Returns
-------
Resultant matrix.
"""
siz = np.array(tensorInstance.shape).astype(int)
old = np.setdiff1d(np.arange(tensorInstance.ndims), mode).astype(int)
permutation: np.ndarray = np.concatenate((np.array([mode]), old))
# This mimics how tenmat handles ktensors
# TODO check if full can be done after permutation and reshape for efficiency
if isinstance(tensorInstance, ttb.ktensor):
tensorInstance = tensorInstance.full()
tensorInstance = tensorInstance.permute(permutation).reshape(
(siz[mode], np.prod(siz[old]))
)
matrix = tensorInstance.data
if transpose:
matrix = np.transpose(matrix)
return matrix


def tt_from_dense_matrix(
matrix: np.ndarray,
shape: Tuple[int, ...],
mode: int,
idx: int,
) -> ttb.tensor:
"""
Helper function to wrap dense matrix into tensor.
Inverse of :class:`pyttb.tt_to_dense_matrix`

Parameters
----------
matrix:
Matrix to (re-)create tensor from.
mode:
Mode around which tensor was unwrapped
idx:
In {0,1}, idx of mode in matrix, s.b. 0 for tranpose=True

Returns
-------
Dense tensor.
"""
tensorInstance = ttb.tensor(matrix)
if idx == 0:
tensorInstance = tensorInstance.permute(np.array([1, 0]))
tensorInstance = tensorInstance.reshape(shape)
tensorInstance = tensorInstance.permute(
np.concatenate(
(np.arange(1, mode + 1), np.array([0]), np.arange(mode + 1, len(shape)))
)
)
return tensorInstance


def tt_union_rows(MatrixA: np.ndarray, MatrixB: np.ndarray) -> np.ndarray:
"""
Helper function to reproduce functionality of MATLABS intersect(a,b,'rows')
Expand Down
Loading
Loading