Skip to content

Commit

Permalink
HOSVD: Finish output and test coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
ntjohnson1 committed Mar 16, 2023
1 parent 77d3dec commit b34e10f
Show file tree
Hide file tree
Showing 3 changed files with 65 additions and 13 deletions.
44 changes: 39 additions & 5 deletions pyttb/hosvd.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,21 @@
"""Higher Order SVD Implementation"""
import warnings
from typing import List, Optional

import numpy as np
import scipy

import pyttb as ttb


def hosvd(tensor, tol, verbosity=1, dimorder=None, sequential=True, ranks=None):
def hosvd(
tensor,
tol: float,
verbosity: float = 1,
dimorder: Optional[List[int]] = None,
sequential: bool = True,
ranks: Optional[List[int]] = None,
):
"""Compute sequentially-truncated higher-order SVD (Tucker).
Computes a Tucker decomposition with relative error
Expand All @@ -19,6 +30,16 @@ def hosvd(tensor, tol, verbosity=1, dimorder=None, sequential=True, ranks=None):
dimorder: Order to loop through dimensions
sequential: Use sequentially-truncated version
ranks: Specify ranks to consider rather than computing
Example
-------
>>> data = np.array([[29, 39.], [63., 85.]])
>>> tol = 1e-4
>>> disable_printing = -1
>>> tensorInstance = ttb.tensor().from_data(data)
>>> result = hosvd(tensorInstance, tol, verbosity=disable_printing)
>>> ((result.full() - tensorInstance).norm() / tensorInstance.norm()) < tol
True
"""
# In tucker als this is N
d = tensor.ndims
Expand Down Expand Up @@ -54,11 +75,11 @@ def hosvd(tensor, tol, verbosity=1, dimorder=None, sequential=True, ranks=None):
print(
f"||X||^2 = {normxsqr: g}\n"
f"tol = {tol: g}\n"
f"eigenvalue sum threshold = tol^2 ||X||^2 / d = {eigsumthresh: g}\n"
f"eigenvalue sum threshold = tol^2 ||X||^2 / d = {eigsumthresh: g}"
)

# Main Loop
factor_matrices = [None] * d
factor_matrices = [np.empty(1)] * d
# Copy input tensor, shrinks every step for sequential
Y = ttb.tensor.from_tensor_type(tensor)

Expand Down Expand Up @@ -97,8 +118,21 @@ def hosvd(tensor, tol, verbosity=1, dimorder=None, sequential=True, ranks=None):
if sequential:
G = Y
else:
G = Y.ttm(Y, factor_matrices, transpose=True)
G = Y.ttm(factor_matrices, transpose=True)

result = ttb.ttensor.from_data(G, factor_matrices)
# TODO final printout

if verbosity > 0:
diffnormsqr = ((tensor - result.full()) ** 2).collapse()
relnorm = np.sqrt(diffnormsqr / normxsqr)
print(f" Size of core: {G.shape}")
if relnorm <= tol:
print(f"||X-T||/||X|| = {relnorm: g} <=" f"{tol: f} (tol)")
else:
print(
"Tolerance not satisfied!! "
f"||X-T||/||X|| = {relnorm: g} >="
f"{tol: f} (tol)"
)
warnings.warn("Specified tolerance was not achieved")
return result
2 changes: 1 addition & 1 deletion pyttb/tensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -1060,7 +1060,7 @@ def ttm(
return Y

if not isinstance(matrix, np.ndarray):
assert False, "matrix must be of type numpy.ndarray"
assert False, f"matrix must be of type numpy.ndarray but got:\n{matrix}"

if not (dims.size == 1 and np.isin(dims, np.arange(self.ndims))):
assert False, "dims must contain values in [0,self.dims)"
Expand Down
32 changes: 25 additions & 7 deletions tests/test_hosvd.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,31 +14,49 @@ def sample_tensor():


@pytest.mark.indevelopment
def test_tucker_als_tensor_simple_convergence(capsys, sample_tensor):
def test_hosvd_simple_convergence(capsys, sample_tensor):
(data, T) = sample_tensor
tol = 1e-4
result = ttb.hosvd(T, tol)
assert (result.full()-T).norm()/T.norm() < tol, (
f"Failed to converge"
)
assert (result.full() - T).norm() / T.norm() < tol, f"Failed to converge"

tol = 1e-4
result = ttb.hosvd(T, tol, sequential=False)
assert (
result.full() - T
).norm() / T.norm() < tol, f"Failed to converge for non-sequential option"

impossible_tol = 1e-20
with pytest.warns(UserWarning):
result = ttb.hosvd(T, impossible_tol)
assert (
result.full() - T
).norm() / T.norm() > impossible_tol, f"Converged beyond provided precision"


@pytest.mark.indevelopment
def test_tucker_als_tensor_default_init(capsys, sample_tensor):
def test_hosvd_default_init(capsys, sample_tensor):
(data, T) = sample_tensor
_ = ttb.hosvd(T, 1)


@pytest.mark.indevelopment
def test_tucker_als_tensor_incorrect_ranks(capsys, sample_tensor):
def test_hosvd_smoke_test_verbosity(capsys, sample_tensor):
"""For now just make sure verbosity calcs don't crash"""
(data, T) = sample_tensor
ttb.hosvd(T, 1, verbosity=10)


@pytest.mark.indevelopment
def test_hosvd_incorrect_ranks(capsys, sample_tensor):
(data, T) = sample_tensor
ranks = list(range(T.ndims - 1))
with pytest.raises(ValueError):
_ = ttb.hosvd(T, 1, ranks=ranks)


@pytest.mark.indevelopment
def test_tucker_als_tensor_incorrect_dimorder(capsys, sample_tensor):
def test_hosvd_incorrect_dimorder(capsys, sample_tensor):
(data, T) = sample_tensor
dimorder = list(range(T.ndims - 1))
with pytest.raises(ValueError):
Expand Down

0 comments on commit b34e10f

Please sign in to comment.