Skip to content

Commit 605dd28

Browse files
authored
Nick/pyttb utils improvement (#353)
* PYTTB_UTILS: Finish adding types without refactor * PYTTB_UTILS: Fix typing on tt_tenfun * PYTTB_UTILS: Doc cleanup * Remove unneeded method * Update examples/docs most places * Mark remaining improvements * PYTTB_UTILS: Doc cleanup * Update doc strings for remaining utils * Make tt_tenfun a tensor method to more closely align to elemfun and MATLAB * RUFF: Enable Pydocstyle * Mostly adding periods, newlines, and imperitive phrasing * RUFF: forgot to run format after doc updates * Fix merge errors and capture stricter requirements on main. * Fix type hint for more flexible tensor collapse. * TTENSOR: Fix missing newline and indentation.
1 parent 396fa7f commit 605dd28

34 files changed

+737
-645
lines changed

conftest.py

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
"""Pyttb pytest configuration."""
12
# Copyright 2024 National Technology & Engineering Solutions of Sandia,
23
# LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the
34
# U.S. Government retains certain rights in this software.
@@ -11,12 +12,12 @@
1112

1213

1314
@pytest.fixture(autouse=True)
14-
def add_packages(doctest_namespace):
15+
def add_packages(doctest_namespace): # noqa: D103
1516
doctest_namespace["np"] = numpy
1617
doctest_namespace["ttb"] = pyttb
1718

1819

19-
def pytest_addoption(parser):
20+
def pytest_addoption(parser): # noqa: D103
2021
parser.addoption(
2122
"--packaging",
2223
action="store_true",
@@ -26,6 +27,6 @@ def pytest_addoption(parser):
2627
)
2728

2829

29-
def pytest_configure(config):
30+
def pytest_configure(config): # noqa: D103
3031
if not config.option.packaging:
3132
config.option.markexpr = "not packaging"

docs/source/matlab/common.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ Methods
5656
+-----------------+----------------------+------------------------------------------------------------------------+
5757
| ``subsref`` | ``__getitem__`` | ``X[index]`` |
5858
+-----------------+----------------------+------------------------------------------------------------------------+
59-
| ``tenfun`` | ``tt_tenfun`` | e.g., ``pyttb.tt_tenfun(lambda x: x + 1, A)`` |
59+
| ``tenfun`` | ``tenfun`` | ``X.tenfun(lambda x: x + 1)`` |
6060
+-----------------+----------------------+------------------------------------------------------------------------+
6161
| ``times`` | ``__mul__`` | ``X * Y`` |
6262
+-----------------+----------------------+------------------------------------------------------------------------+

docs/source/tutorial/algorithm_gcp_opt.ipynb

-1
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,6 @@
158158
"import sys\n",
159159
"import pyttb as ttb\n",
160160
"import numpy as np\n",
161-
"from pyttb.pyttb_utils import tt_tenfun\n",
162161
"\n",
163162
"from pyttb.gcp.fg_setup import function_type, setup\n",
164163
"from pyttb.gcp.handles import Objectives\n",

docs/source/tutorial/class_tensor.ipynb

+6-7
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,7 @@
2727
"source": [
2828
"import pyttb as ttb\n",
2929
"import numpy as np\n",
30-
"import sys\n",
31-
"from pyttb.pyttb_utils import tt_tenfun"
30+
"import sys"
3231
]
3332
},
3433
{
@@ -847,8 +846,8 @@
847846
"cell_type": "markdown",
848847
"metadata": {},
849848
"source": [
850-
"## Using `tt_tenfun` for elementwise operations on one or more `tensor`s\n",
851-
"The function `tt_tenfun` applies a specified function to a number of `tensor`s. This can be used for any function that is not predefined for `tensor`s."
849+
"## Using `tenfun` for elementwise operations on one or more `tensor`s\n",
850+
"The method `tenfun` applies a specified function to a number of `tensor`s. This can be used for any function that is not predefined for `tensor`s."
852851
]
853852
},
854853
{
@@ -859,7 +858,7 @@
859858
"source": [
860859
"np.random.seed(0)\n",
861860
"A = ttb.tensor(np.floor(3 * np.random.rand(2, 2, 3))) # Generate some data.\n",
862-
"tt_tenfun(lambda x: x + 1, A) # Increment every element of A by one."
861+
"A.tenfun(lambda x: x + 1) # Increment every element of A by one."
863862
]
864863
},
865864
{
@@ -873,7 +872,7 @@
873872
" return np.maximum(a, b)\n",
874873
"\n",
875874
"\n",
876-
"tt_tenfun(max_elements, A, B) # Max of A and B, elementwise."
875+
"A.tenfun(max_elements, B) # Max of A and B, elementwise."
877876
]
878877
},
879878
{
@@ -891,7 +890,7 @@
891890
" return np.floor(np.mean(X, axis=0))\n",
892891
"\n",
893892
"\n",
894-
"tt_tenfun(elementwise_mean, A, B, C) # Elementwise means for A, B, and C."
893+
"A.tenfun(elementwise_mean, B, C) # Elementwise means for A, B, and C."
895894
]
896895
},
897896
{

pyproject.toml

+8-5
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ requires = ["setuptools>=61.0", "numpy", "numpy_groupies", "scipy", "wheel"]
6767
build-backend = "setuptools.build_meta"
6868

6969
[tool.ruff.lint]
70-
select = ["E", "F", "PL", "W", "I", "N", "NPY", "RUF", "B"]
70+
select = ["E", "F", "PL", "W", "I", "N", "NPY", "RUF", "B", "D"]
7171
ignore = [
7272
# Ignored in conversion to ruff since not previously enforced
7373
"PLR2004",
@@ -84,15 +84,18 @@ ignore = [
8484
# There is ongoing discussion about logging/warning etc
8585
"B028",
8686
]
87+
[tool.ruff.lint.pydocstyle]
88+
convention = "numpy"
89+
8790
[tool.ruff.lint.per-file-ignores]
8891
# See see https://github.com/astral-sh/ruff/issues/3172 for details on this becoming simpler
8992

9093
# Everything but I, F (to catch import mess and potential logic errors)
91-
"tests/**.py" = ["E", "PL", "W", "N", "NPY", "RUF", "B"]
94+
"tests/**.py" = ["E", "PL", "W", "N", "NPY", "RUF", "B", "D"]
9295
# Ignore everything for now
93-
"docs/**.py" = ["E", "F", "PL", "W", "I", "N", "NPY", "RUF", "B"]
94-
"docs/**.ipynb" = ["E", "F", "PL", "W", "I", "N", "NPY", "RUF", "B"]
95-
"profiling/**.ipynb" = ["E", "F", "PL", "W", "I", "N", "NPY", "RUF", "B"]
96+
"docs/**.py" = ["E", "F", "PL", "W", "I", "N", "NPY", "RUF", "B", "D"]
97+
"docs/**.ipynb" = ["E", "F", "PL", "W", "I", "N", "NPY", "RUF", "B", "D"]
98+
"profiling/**.ipynb" = ["E", "F", "PL", "W", "I", "N", "NPY", "RUF", "B", "D"]
9699

97100
[tool.ruff.format]
98101
docstring-code-format = true

pyttb/__init__.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""pyttb: Python Tensor Toolbox"""
1+
"""pyttb: Python Tensor Toolbox."""
22

33
# Copyright 2024 National Technology & Engineering Solutions of Sandia,
44
# LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the
@@ -32,7 +32,7 @@
3232

3333

3434
def ignore_warnings(ignore=True):
35-
"""Helper to disable warnings"""
35+
"""Disable warnings."""
3636
if ignore:
3737
warnings.simplefilter("ignore")
3838
else:

pyttb/cp_als.py

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""CP Decomposition via Alternating Least Squares"""
1+
"""CP Decomposition via Alternating Least Squares."""
22

33
# Copyright 2024 National Technology & Engineering Solutions of Sandia,
44
# LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the
@@ -25,8 +25,7 @@ def cp_als( # noqa: PLR0912,PLR0913,PLR0915
2525
printitn: int = 1,
2626
fixsigns: bool = True,
2727
) -> Tuple[ttb.ktensor, ttb.ktensor, Dict]:
28-
"""
29-
Compute CP decomposition with alternating least squares
28+
"""Compute CP decomposition with alternating least squares.
3029
3130
Parameters
3231
----------
@@ -129,7 +128,6 @@ def cp_als( # noqa: PLR0912,PLR0913,PLR0915
129128
Iter 1: f = ... f-delta = ...
130129
Final f = ...
131130
"""
132-
133131
# Extract number of dimensions and norm of tensor
134132
N = input_tensor.ndims
135133
normX = input_tensor.norm()

pyttb/cp_apr.py

+15-30
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Non-negative CP decomposition with alternating Poisson regression"""
1+
"""Non-negative CP decomposition with alternating Poisson regression."""
22

33
# Copyright 2024 National Technology & Engineering Solutions of Sandia,
44
# LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the
@@ -230,9 +230,6 @@ def tt_cp_apr_mu( # noqa: PLR0912,PLR0913,PLR0915
230230
kappatol:
231231
MU ALGORITHM PARAMETER: Tolerance on complementary slackness
232232
233-
Returns
234-
-------
235-
236233
Notes
237234
-----
238235
REFERENCE: E. C. Chi and T. G. Kolda. On Tensors, Sparsity, and
@@ -405,9 +402,9 @@ def tt_cp_apr_pdnr( # noqa: PLR0912,PLR0913,PLR0915
405402
precompinds: bool,
406403
inexact: bool,
407404
) -> Tuple[ttb.ktensor, Dict]:
408-
"""
409-
Compute nonnegative CP with alternating Poisson regression
410-
computes an estimate of the best rank-R
405+
"""Compute nonnegative CP with alternating Poisson regression.
406+
407+
Computes an estimate of the best rank-R
411408
CP model of a tensor X using an alternating Poisson regression.
412409
The algorithm solves "row subproblems" in each alternating subproblem,
413410
using a Hessian of size R^2.
@@ -1272,9 +1269,7 @@ def get_search_dir_pdnr( # noqa: PLR0913
12721269
mu: float,
12731270
epsActSet: float,
12741271
) -> Tuple[np.ndarray, np.ndarray]:
1275-
"""
1276-
Compute the search direction for PDNR using a two-metric projection with
1277-
damped Hessian
1272+
"""Compute the search direction using a two-metric projection with damped Hessian.
12781273
12791274
Parameters
12801275
----------
@@ -1372,8 +1367,7 @@ def tt_linesearch_prowsubprob( # noqa: PLR0913
13721367
phi_row: np.ndarray,
13731368
display_warning: bool,
13741369
) -> Tuple[np.ndarray, float, float, float, int]:
1375-
"""
1376-
Perform a line search on a row subproblem
1370+
"""Perform a line search on a row subproblem.
13771371
13781372
Parameters
13791373
----------
@@ -1488,9 +1482,9 @@ def tt_linesearch_prowsubprob( # noqa: PLR0913
14881482
def get_hessian(
14891483
upsilon: np.ndarray, Pi: np.ndarray, free_indices: np.ndarray
14901484
) -> np.ndarray:
1491-
"""
1492-
Return the Hessian for one PDNR row subproblem of Model[n], for just the rows and
1493-
columns corresponding to the free variables
1485+
"""Return the Hessian for one PDNR row subproblem of Model[n].
1486+
1487+
Only for just the rows and columns corresponding to the free variables.
14941488
14951489
Parameters
14961490
----------
@@ -1505,7 +1499,6 @@ def get_hessian(
15051499
Sub-block of full Hessian identified by free-indices
15061500
15071501
"""
1508-
15091502
num_free = len(free_indices)
15101503
H = np.zeros((num_free, num_free))
15111504
for i in range(num_free):
@@ -1523,8 +1516,7 @@ def tt_loglikelihood_row(
15231516
model_row: np.ndarray,
15241517
Pi: np.ndarray,
15251518
) -> float:
1526-
"""
1527-
Compute log-likelihood of one row subproblem
1519+
"""Compute log-likelihood of one row subproblem.
15281520
15291521
Parameters
15301522
----------
@@ -1618,7 +1610,6 @@ def get_search_dir_pqnr( # noqa: PLR0913
16181610
URL: http://arxiv.org/abs/1304.4964. Submitted for publication.
16191611
16201612
"""
1621-
16221613
lbfgsSize = delta_model.shape[1]
16231614

16241615
# Determine active and free variables.
@@ -1679,8 +1670,7 @@ def calc_grad(
16791670
data_row: np.ndarray,
16801671
model_row: np.ndarray,
16811672
) -> Tuple[np.ndarray, np.ndarray]:
1682-
"""
1683-
Compute the gradient for a PQNR row subproblem
1673+
"""Compute the gradient for a PQNR row subproblem.
16841674
16851675
Parameters
16861676
----------
@@ -1710,6 +1700,7 @@ def calc_grad(
17101700
return grad_row, phi_row
17111701

17121702

1703+
# TODO verify what pi is
17131704
# Mu helper functions
17141705
def calculate_pi(
17151706
Data: Union[ttb.sptensor, ttb.tensor],
@@ -1718,9 +1709,7 @@ def calculate_pi(
17181709
factorIndex: int,
17191710
ndims: int,
17201711
) -> np.ndarray:
1721-
"""
1722-
Helper function to calculate Pi matrix
1723-
# TODO verify what pi is
1712+
"""Calculate Pi matrix.
17241713
17251714
Parameters
17261715
----------
@@ -1758,7 +1747,7 @@ def calculate_phi( # noqa: PLR0913
17581747
Pi: np.ndarray,
17591748
epsilon: float,
17601749
) -> np.ndarray:
1761-
"""
1750+
"""Calcualte Phi.
17621751
17631752
Parameters
17641753
----------
@@ -1769,9 +1758,6 @@ def calculate_phi( # noqa: PLR0913
17691758
Pi:
17701759
epsilon:
17711760
1772-
Returns
1773-
-------
1774-
17751761
"""
17761762
if isinstance(Data, ttb.sptensor):
17771763
Phi = -np.ones((Data.shape[factorIndex], rank))
@@ -1846,8 +1832,7 @@ def tt_loglikelihood(
18461832

18471833

18481834
def vectorize_for_mu(matrix: np.ndarray) -> np.ndarray:
1849-
"""
1850-
Helper Function to unravel matrix into vector
1835+
"""Unravel matrix into vector.
18511836
18521837
Parameters
18531838
----------

pyttb/export_data.py

+9-11
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""Utilities for saving tensor data"""
1+
"""Utilities for saving tensor data."""
22

33
# Copyright 2024 National Technology & Engineering Solutions of Sandia,
44
# LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the
@@ -20,9 +20,7 @@ def export_data(
2020
fmt_data: Optional[str] = None,
2121
fmt_weights: Optional[str] = None,
2222
):
23-
"""
24-
Export tensor-related data to a file.
25-
"""
23+
"""Export tensor-related data to a file."""
2624
if not isinstance(data, (ttb.tensor, ttb.sptensor, ttb.ktensor, np.ndarray)):
2725
assert False, f"Invalid data type for export: {type(data)}"
2826

@@ -58,36 +56,36 @@ def export_data(
5856

5957

6058
def export_size(fp: TextIO, shape: Shape):
61-
"""Export the size of something to a file"""
59+
"""Export the size of something to a file."""
6260
shape = parse_shape(shape)
6361
print(f"{len(shape)}", file=fp) # # of dimensions on one line
6462
shape_str = " ".join([str(d) for d in shape])
6563
print(f"{shape_str}", file=fp) # size of each dimensions on the next line
6664

6765

6866
def export_rank(fp: TextIO, data: ttb.ktensor):
69-
"""Export the rank of a ktensor to a file"""
67+
"""Export the rank of a ktensor to a file."""
7068
print(f"{len(data.weights)}", file=fp) # ktensor rank on one line
7169

7270

7371
def export_weights(fp: TextIO, data: ttb.ktensor, fmt_weights: Optional[str]):
74-
"""Export KTensor weights"""
72+
"""Export KTensor weights."""
7573
if not fmt_weights:
7674
fmt_weights = "%.16e"
7775
data.weights.tofile(fp, sep=" ", format=fmt_weights)
7876
print(file=fp)
7977

8078

8179
def export_array(fp: TextIO, data: np.ndarray, fmt_data: Optional[str]):
82-
"""Export dense data"""
80+
"""Export dense data."""
8381
if not fmt_data:
8482
fmt_data = "%.16e"
8583
data.tofile(fp, sep="\n", format=fmt_data)
8684
print(file=fp)
8785

8886

8987
def export_factor(fp: TextIO, data: np.ndarray, fmt_data: Optional[str]):
90-
"""Export KTensor factor"""
88+
"""Export KTensor factor."""
9189
if not fmt_data:
9290
fmt_data = "%.16e"
9391
for i in range(data.shape[0]):
@@ -97,15 +95,15 @@ def export_factor(fp: TextIO, data: np.ndarray, fmt_data: Optional[str]):
9795

9896

9997
def export_sparse_size(fp: TextIO, A: ttb.sptensor):
100-
"""Export the size of something to a file"""
98+
"""Export the size of something to a file."""
10199
print(f"{len(A.shape)}", file=fp) # # of dimensions on one line
102100
shape_str = " ".join([str(d) for d in A.shape])
103101
print(f"{shape_str}", file=fp) # size of each dimensions on the next line
104102
print(f"{A.nnz}", file=fp) # number of nonzeros
105103

106104

107105
def export_sparse_array(fp: TextIO, A: ttb.sptensor, fmt_data: Optional[str]):
108-
"""Export sparse array data in coordinate format"""
106+
"""Export sparse array data in coordinate format."""
109107
if not fmt_data:
110108
fmt_data = "%.16e"
111109
# TODO: looping through all values may take a long time, can this be more efficient?

pyttb/gcp/__init__.py

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
"""Generalized CP Decomposition Support Code."""

0 commit comments

Comments
 (0)