From ae8cc9f4d8c10ef973c8b9ae20fa546e36cd19ea Mon Sep 17 00:00:00 2001 From: Nick Johnson <24689722+ntjohnson1@users.noreply.github.com> Date: Fri, 23 May 2025 16:59:56 -0400 Subject: [PATCH 1/8] Remove stubs --- docs/source/matlab/symktensor.rst | 8 -------- docs/source/matlab/symtensor.rst | 8 -------- pyttb/__init__.py | 6 ------ pyttb/sptensor3.py | 12 ------------ pyttb/symktensor.py | 12 ------------ pyttb/symtensor.py | 12 ------------ tests/test_sptensor3.py | 13 ------------- tests/test_symktensor.py | 13 ------------- tests/test_symtensor.py | 13 ------------- 9 files changed, 97 deletions(-) delete mode 100644 docs/source/matlab/symktensor.rst delete mode 100644 docs/source/matlab/symtensor.rst delete mode 100644 pyttb/sptensor3.py delete mode 100644 pyttb/symktensor.py delete mode 100644 pyttb/symtensor.py delete mode 100644 tests/test_sptensor3.py delete mode 100644 tests/test_symktensor.py delete mode 100644 tests/test_symtensor.py diff --git a/docs/source/matlab/symktensor.rst b/docs/source/matlab/symktensor.rst deleted file mode 100644 index 19e215a8..00000000 --- a/docs/source/matlab/symktensor.rst +++ /dev/null @@ -1,8 +0,0 @@ -``symktensor`` --------------------- - -Data members -^^^^^^^^^^^^ - -Methods -^^^^^^^ \ No newline at end of file diff --git a/docs/source/matlab/symtensor.rst b/docs/source/matlab/symtensor.rst deleted file mode 100644 index 8d673c32..00000000 --- a/docs/source/matlab/symtensor.rst +++ /dev/null @@ -1,8 +0,0 @@ -``symtensor`` -------------------- - -Data members -^^^^^^^^^^^^ - -Methods -^^^^^^^ \ No newline at end of file diff --git a/pyttb/__init__.py b/pyttb/__init__.py index dbec0889..87a70514 100644 --- a/pyttb/__init__.py +++ b/pyttb/__init__.py @@ -22,10 +22,7 @@ from pyttb.matlab import matlab_support from pyttb.sptenmat import sptenmat from pyttb.sptensor import sptendiag, sptenrand, sptensor -from pyttb.sptensor3 import sptensor3 from pyttb.sumtensor import sumtensor -from pyttb.symktensor import symktensor -from pyttb.symtensor import symtensor from pyttb.tenmat import tenmat from pyttb.tensor import tendiag, teneye, tenones, tenrand, tensor, tenzeros from pyttb.ttensor import ttensor @@ -55,10 +52,7 @@ def ignore_warnings(ignore=True): sptendiag.__name__, sptenrand.__name__, sptensor.__name__, - sptensor3.__name__, sumtensor.__name__, - symktensor.__name__, - symtensor.__name__, teneye.__name__, tenmat.__name__, tendiag.__name__, diff --git a/pyttb/sptensor3.py b/pyttb/sptensor3.py deleted file mode 100644 index 2d469b06..00000000 --- a/pyttb/sptensor3.py +++ /dev/null @@ -1,12 +0,0 @@ -"""Sparse Tensor 3 Class Placeholder.""" - -# Copyright 2025 National Technology & Engineering Solutions of Sandia, -# LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the -# U.S. Government retains certain rights in this software. - - -class sptensor3: - """A sparse tensor variant.""" - - def __init__(self): - assert False, "SPTENSOR3 class not yet implemented" diff --git a/pyttb/symktensor.py b/pyttb/symktensor.py deleted file mode 100644 index 67a05e26..00000000 --- a/pyttb/symktensor.py +++ /dev/null @@ -1,12 +0,0 @@ -"""Symmetric Kruskal Tensor Class Placeholder.""" - -# Copyright 2025 National Technology & Engineering Solutions of Sandia, -# LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the -# U.S. Government retains certain rights in this software. - - -class symktensor: - """Class for symmetric Kruskal tensors (decomposed).""" - - def __init__(self): - assert False, "SYMKTENSOR class not yet implemented" diff --git a/pyttb/symtensor.py b/pyttb/symtensor.py deleted file mode 100644 index bd57e5c0..00000000 --- a/pyttb/symtensor.py +++ /dev/null @@ -1,12 +0,0 @@ -"""Symmetric Tensor Class Placeholder.""" - -# Copyright 2025 National Technology & Engineering Solutions of Sandia, -# LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the -# U.S. Government retains certain rights in this software. - - -class symtensor: - """Class for storing only unique entries of symmetric tensor.""" - - def __init__(self): - assert False, "SYMTENSOR class not yet implemented" diff --git a/tests/test_sptensor3.py b/tests/test_sptensor3.py deleted file mode 100644 index fd7cd94f..00000000 --- a/tests/test_sptensor3.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2024 National Technology & Engineering Solutions of Sandia, -# LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the -# U.S. Government retains certain rights in this software. - -import pytest - -import pyttb as ttb - - -def test_sptensor3_initialization_empty(): - with pytest.raises(AssertionError) as excinfo: - ttb.sptensor3() - assert "SPTENSOR3 class not yet implemented" in str(excinfo) diff --git a/tests/test_symktensor.py b/tests/test_symktensor.py deleted file mode 100644 index 0265d6a7..00000000 --- a/tests/test_symktensor.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2024 National Technology & Engineering Solutions of Sandia, -# LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the -# U.S. Government retains certain rights in this software. - -import pytest - -import pyttb as ttb - - -def test_symktensor_initialization_empty(): - with pytest.raises(AssertionError) as excinfo: - ttb.symktensor() - assert "SYMKTENSOR class not yet implemented" in str(excinfo) diff --git a/tests/test_symtensor.py b/tests/test_symtensor.py deleted file mode 100644 index 5ee45bcf..00000000 --- a/tests/test_symtensor.py +++ /dev/null @@ -1,13 +0,0 @@ -# Copyright 2024 National Technology & Engineering Solutions of Sandia, -# LLC (NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the -# U.S. Government retains certain rights in this software. - -import pytest - -import pyttb as ttb - - -def test_symtensor_initialization_empty(): - with pytest.raises(AssertionError) as excinfo: - ttb.symtensor() - assert "SYMTENSOR class not yet implemented" in str(excinfo) From fb6f006bf0be37dc5efceaf6123710c821677b89 Mon Sep 17 00:00:00 2001 From: Nick Johnson <24689722+ntjohnson1@users.noreply.github.com> Date: Fri, 23 May 2025 17:07:08 -0400 Subject: [PATCH 2/8] Fix rst keyword typo --- pyttb/sptenmat.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyttb/sptenmat.py b/pyttb/sptenmat.py index 8670da5d..9a0a143c 100644 --- a/pyttb/sptenmat.py +++ b/pyttb/sptenmat.py @@ -40,7 +40,7 @@ def __init__( # noqa: PLR0913 and values (vals) along with the mappings of the row (rdims) and column indices (cdims) and the shape of the original tensor (tshape). - If you already have an sparse tensor see :method:`pyttb.sptensor.to_sptenmat`. + If you already have an sparse tensor see :meth:`pyttb.sptensor.to_sptenmat`. Parameters ---------- From c6e091e611037249a75b3a9c09b98d7de618f4e3 Mon Sep 17 00:00:00 2001 From: Nick Johnson <24689722+ntjohnson1@users.noreply.github.com> Date: Fri, 23 May 2025 17:07:37 -0400 Subject: [PATCH 3/8] Handle numerical precision error seen locally --- pyttb/ktensor.py | 27 ++++++++++++++++++--------- tests/test_ktensor.py | 16 ++++++++++------ 2 files changed, 28 insertions(+), 15 deletions(-) diff --git a/pyttb/ktensor.py b/pyttb/ktensor.py index 11ed3f19..a2637f8b 100644 --- a/pyttb/ktensor.py +++ b/pyttb/ktensor.py @@ -1606,7 +1606,7 @@ def score( component :class:`pyttb.ktensor` instances that have been normalized so that their weights are `self.weights` and `other.weights`, and their factor matrices are single column vectors containing [a1,a2,...,an] and - [b1,b2,...bn], rescpetively, then the score is defined as + [b1,b2,...bn], respectively, then the score is defined as score = penalty * (a1.T*b1) * (a2.T*b2) * ... * (an.T*bn), @@ -1653,23 +1653,31 @@ def score( Create two :class:`pyttb.ktensor` instances and compute the score between them: - >>> factors = [np.ones((3, 3)), np.ones((4, 3)), np.ones((5, 3))] + >>> factors = [ + ... np.ones((3, 3)) + 0.1, + ... np.ones((4, 3)) + 0.2, + ... np.ones((5, 3)) + 0.3, + ... ] >>> weights = np.array([2.0, 1.0, 3.0]) >>> K = ttb.ktensor(factors, weights) - >>> factors_2 = [np.ones((3, 2)), np.ones((4, 2)), np.ones((5, 2))] + >>> factors_2 = [ + ... np.ones((3, 2)) + 0.1, + ... np.ones((4, 2)) + 0.2, + ... np.ones((5, 2)) + 0.3, + ... ] >>> weights_2 = np.array([2.0, 4.0]) >>> K2 = ttb.ktensor(factors_2, weights_2) >>> score, Kperm, flag, perm = K.score(K2) - >>> print(score) - 0.875 + >>> print(np.isclose(score, 0.875)) + True >>> print(perm) [0 2 1] Compute score without using weights: >>> score, Kperm, flag, perm = K.score(K2, weight_penalty=False) - >>> print(score) - 1.0 + >>> print(np.isclose(score, 1.0)) + True >>> print(perm) [0 1 2] """ @@ -1733,8 +1741,9 @@ def score( best_perm = -1 * np.ones((RA), dtype=int) best_score = 0.0 for _ in range(RB): - idx = np.argmax(C.reshape(prod(C.shape), order=self.order)) - ij = tt_ind2sub((RA, RB), np.array(idx)) + flatten_C = C.reshape(prod(C.shape), order=self.order) + idx = np.argmax(flatten_C) + ij = tt_ind2sub((RA, RB), np.array(idx, dtype=int), order=self.order) best_score = best_score + C[ij[0], ij[1]] C[ij[0], :] = -10 C[:, ij[1]] = -10 diff --git a/tests/test_ktensor.py b/tests/test_ktensor.py index 6c560c26..6abda068 100644 --- a/tests/test_ktensor.py +++ b/tests/test_ktensor.py @@ -779,23 +779,27 @@ def test_ktensor_redistribute(sample_ktensor_2way): def test_ktensor_score(): A = ttb.ktensor( - [np.ones((3, 3)), np.ones((4, 3)), np.ones((5, 3))], np.array([2.0, 1.0, 3.0]) + [np.ones((3, 3)) + 0.1, np.ones((4, 3)) + 0.2, np.ones((5, 3)) + 0.3], + np.array([2.0, 1.0, 3.0]), ) B = ttb.ktensor( - [np.ones((3, 2)), np.ones((4, 2)), np.ones((5, 2))], np.array([2.0, 4.0]) + [np.ones((3, 2)) + 0.1, np.ones((4, 2)) + 0.2, np.ones((5, 2)) + 0.3], + np.array([2.0, 4.0]), ) + A_norm = A.copy().normalize() + # defaults score, Aperm, flag, best_perm = A.score(B) - assert score == 0.875 - assert np.allclose(Aperm.weights, np.array([15.49193338, 23.23790008, 7.74596669])) + assert np.isclose(score, 0.875) + assert np.allclose(Aperm.weights, A_norm.weights[best_perm]) assert flag assert np.array_equal(best_perm, np.array([0, 2, 1])) # compare just factor matrices (i.e., do not use weights) score, Aperm, flag, best_perm = A.score(B, weight_penalty=False) - assert score == 1.0 - assert np.allclose(Aperm.weights, np.array([15.49193338, 7.74596669, 23.23790008])) + assert np.isclose(score, 1.0) + assert np.allclose(Aperm.weights, A_norm.weights[best_perm]) assert not flag assert np.array_equal(best_perm, np.array([0, 1, 2])) From cc17f7c91c20099eee08966211018d05bd3ba411 Mon Sep 17 00:00:00 2001 From: Nick Johnson <24689722+ntjohnson1@users.noreply.github.com> Date: Sat, 24 May 2025 17:44:56 -0400 Subject: [PATCH 4/8] Add coverage for our missed doc components --- docs/source/index.rst | 5 +++++ docs/source/io.rst | 6 ++++++ docs/source/matlab/additional_support.rst | 4 ++++ docs/source/pyttb_utils.rst | 6 ++++-- docs/source/reference.rst | 1 + 5 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 docs/source/io.rst create mode 100644 docs/source/matlab/additional_support.rst diff --git a/docs/source/index.rst b/docs/source/index.rst index 930010c1..3183c0a8 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -47,8 +47,13 @@ algorithms for computing low-rank tensor models. decompositions such as Poisson Tensor Factorization via alternating Poisson regression. +- `IO`_ + + Storing and retrieving tensors from disk. + .. _Tensor Classes: tensor_classes.html .. _Algorithms: algorithms.html +.. _IO: io.html Getting Started diff --git a/docs/source/io.rst b/docs/source/io.rst new file mode 100644 index 00000000..10ccc1fc --- /dev/null +++ b/docs/source/io.rst @@ -0,0 +1,6 @@ +Input/Output +------------ +Storing or reading tensors from disk. + +.. autofunction:: pyttb.import_data.import_data +.. autofunction:: pyttb.export_data.export_data \ No newline at end of file diff --git a/docs/source/matlab/additional_support.rst b/docs/source/matlab/additional_support.rst new file mode 100644 index 00000000..f1df8eab --- /dev/null +++ b/docs/source/matlab/additional_support.rst @@ -0,0 +1,4 @@ +Additional Utilities For MATLAB User Transition +----------------------------------------------- + +.. autofunction:: pyttb.matlab.matlab_support.matlab_print \ No newline at end of file diff --git a/docs/source/pyttb_utils.rst b/docs/source/pyttb_utils.rst index 7cc02f4c..0627df89 100644 --- a/docs/source/pyttb_utils.rst +++ b/docs/source/pyttb_utils.rst @@ -1,5 +1,7 @@ -Helper Functions (:mod:`pyttb_utils`) -------------------------------------- +Helper Functions (:mod:`pyttb_utils`, :mod:`khatrirao`) +-------------------------------------------------------- + +.. autofunction:: pyttb.khatrirao.khatrirao .. automodule:: pyttb.pyttb_utils :members: diff --git a/docs/source/reference.rst b/docs/source/reference.rst index 46cab023..8d7529fc 100644 --- a/docs/source/reference.rst +++ b/docs/source/reference.rst @@ -6,3 +6,4 @@ Reference (:mod:`pyttb`) tensor_classes.rst algorithms.rst + io.rst From 54c12c6f7bf31a857b0aff3a7cabb122919606bb Mon Sep 17 00:00:00 2001 From: Nick Johnson <24689722+ntjohnson1@users.noreply.github.com> Date: Sat, 24 May 2025 18:04:55 -0400 Subject: [PATCH 5/8] Add python 3.13 but only do coveralls for oldest supported --- .github/workflows/regression-tests.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/regression-tests.yml b/.github/workflows/regression-tests.yml index 72dda521..68c8f436 100644 --- a/.github/workflows/regression-tests.yml +++ b/.github/workflows/regression-tests.yml @@ -15,8 +15,8 @@ jobs: runs-on: ubuntu-latest strategy: fail-fast: false - matrix: - python-version: ["3.9", "3.10", "3.11", "3.12"] + matrix: # Keep these in ascending order for automagic with coverage + python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] steps: - uses: actions/checkout@v4 @@ -32,13 +32,18 @@ jobs: python -c "import pyttb" - name: Install dev dependencies run: | - python -m pip install --upgrade coverage coveralls sphinx_rtd_theme + python -m pip install --upgrade coverage sphinx_rtd_theme pip install ".[dev]" - name: Run tests run: | coverage run --source pyttb -m pytest tests/ coverage report + - name: Add coveralls dependencies + if: strategy.job-index == 0 + run: | + python -m pip install --upgrade coveralls - name: Upload coverage to Coveralls + if: strategy.job-index == 0 uses: coverallsapp/github-action@v2 #env: # GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From 6feadb1ca1734fc0c7f2c07242ada0468b8148d0 Mon Sep 17 00:00:00 2001 From: Nick Johnson <24689722+ntjohnson1@users.noreply.github.com> Date: Fri, 30 May 2025 21:09:04 -0400 Subject: [PATCH 6/8] Only return a reference from tensor full --- pyttb/tensor.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pyttb/tensor.py b/pyttb/tensor.py index 1fe81dd3..0f6cde35 100644 --- a/pyttb/tensor.py +++ b/pyttb/tensor.py @@ -586,16 +586,18 @@ def to_sptensor(self) -> ttb.sptensor: subs, vals = self.find() return ttb.sptensor(subs, vals, self.shape, copy=False) - # TODO: do we need this, now that we have copy() and __deepcopy__()? def full(self) -> tensor: """ - Convert dense tensor to dense tensor. + Create a dense tensor from dense tensor. + + Convenience method to maintain common interface with other + tensor types. Returns ------- - Deep copy + Shallow copy """ - return ttb.tensor(self.data) + return self def to_tenmat( self, From 10c2ca164bc553a99943afd3bf61f8ae0d36cca6 Mon Sep 17 00:00:00 2001 From: Nick Johnson <24689722+ntjohnson1@users.noreply.github.com> Date: Fri, 30 May 2025 21:44:30 -0400 Subject: [PATCH 7/8] Add mutability control for double method for efficiency --- pyttb/ktensor.py | 9 +++++++-- pyttb/sptenmat.py | 6 +++++- pyttb/sptensor.py | 9 ++++++++- pyttb/sumtensor.py | 11 ++++++----- pyttb/tenmat.py | 20 ++++++++++++++------ pyttb/tensor.py | 14 +++++++++++--- pyttb/ttensor.py | 11 ++++++----- tests/test_sptenmat.py | 3 +++ tests/test_sptensor.py | 5 +++++ tests/test_sumtensor.py | 5 +++++ tests/test_tenmat.py | 5 +++++ tests/test_tensor.py | 5 +++++ tests/test_ttensor.py | 5 +++++ 13 files changed, 85 insertions(+), 23 deletions(-) diff --git a/pyttb/ktensor.py b/pyttb/ktensor.py index a2637f8b..730087d8 100644 --- a/pyttb/ktensor.py +++ b/pyttb/ktensor.py @@ -639,10 +639,15 @@ def __deepcopy__(self, memo): """Return deep copy of ktensor.""" return self.copy() - def double(self) -> np.ndarray: + def double(self, immutable: bool = False) -> np.ndarray: """ Convert :class:`pyttb.ktensor` to :class:`numpy.ndarray`. + Parameters + ---------- + immutable: Whether or not the returned data cam be mutated. May enable + additional optimizations. + Returns ------- Array of re-assembled ktensor. @@ -660,7 +665,7 @@ def double(self) -> np.ndarray: >>> type(K.double()) """ - return self.full().double() + return self.full().double(immutable) def extract( self, idx: Optional[Union[int, tuple, list, np.ndarray]] = None diff --git a/pyttb/sptenmat.py b/pyttb/sptenmat.py index 9a0a143c..9677849f 100644 --- a/pyttb/sptenmat.py +++ b/pyttb/sptenmat.py @@ -328,10 +328,14 @@ def shape(self) -> Tuple[int, ...]: n = np.prod(np.array(self.tshape)[self.cdims]) return int(m), int(n) - def double(self) -> sparse.coo_matrix: + def double(self, immutable: bool = False) -> sparse.coo_matrix: """ Convert a :class:`pyttb.sptenmat` to a COO :class:`scipy.sparse.coo_matrix`. + Parameters + ---------- + immutable: Parameter for compatibility but coo_matrix doesn't allow assignment. + Examples -------- >>> S1 = ttb.sptensor(shape=(2, 2, 2)) diff --git a/pyttb/sptensor.py b/pyttb/sptensor.py index 942a212e..7e1f0c66 100644 --- a/pyttb/sptensor.py +++ b/pyttb/sptensor.py @@ -588,10 +588,15 @@ def contract(self, i_0: int, i_1: int) -> Union[np.ndarray, sptensor, ttb.tensor return y.to_tensor() return y - def double(self) -> np.ndarray: + def double(self, immutable: bool = False) -> np.ndarray: """ Convert the :class:`pyttb.sptensor` to a :class:`numpy.ndarray`. + Parameters + ---------- + immutable: Whether or not the returned data cam be mutated. May enable + additional optimizations. + Examples -------- Create a :class:`pyttb.sptensor` with two elements and convert it to a @@ -608,6 +613,8 @@ def double(self) -> np.ndarray: a = np.zeros(self.shape, order=self.order) if self.nnz > 0: a[tuple(self.subs.transpose())] = self.vals.transpose()[0] + if immutable: + a.flags.writeable = False return a def elemfun(self, function_handle: Callable[[np.ndarray], np.ndarray]) -> sptensor: diff --git a/pyttb/sumtensor.py b/pyttb/sumtensor.py index db41b2c6..80104501 100644 --- a/pyttb/sumtensor.py +++ b/pyttb/sumtensor.py @@ -289,13 +289,14 @@ def full(self) -> ttb.tensor: result += part return result - def double(self) -> np.ndarray: + def double(self, immutable: bool = False) -> np.ndarray: """ Convert `:class:pyttb.tensor` to an `:class:numpy.ndarray` of doubles. - Returns - ------- - Copy of tensor data. + Parameters + ---------- + immutable: Whether or not the returned data cam be mutated. May enable + additional optimizations. Examples -------- @@ -305,7 +306,7 @@ def double(self) -> np.ndarray: array([[2., 2.], [2., 2.]]) """ - return self.full().double() + return self.full().double(immutable) def innerprod( self, other: Union[ttb.tensor, ttb.sptensor, ttb.ktensor, ttb.ttensor] diff --git a/pyttb/tenmat.py b/pyttb/tenmat.py index b33127c6..a8fda0ca 100644 --- a/pyttb/tenmat.py +++ b/pyttb/tenmat.py @@ -317,10 +317,15 @@ def ctranspose(self) -> tenmat: copy=True, ) - def double(self) -> np.ndarray: + def double(self, immutable: bool = False) -> np.ndarray: """ Convert a :class:`pyttb.tenmat` to an array of doubles. + Parameters + ---------- + immutable: Whether or not the returned data cam be mutated. May enable + additional optimizations. + Examples -------- >>> T = ttb.tenones((2, 2, 2)) @@ -335,12 +340,15 @@ def double(self) -> np.ndarray: >>> TM.double() # doctest: +NORMALIZE_WHITESPACE array([[1., 1., 1., 1.], [1., 1., 1., 1.]]) - - Returns - ------- - Copy of tenmat data. """ - return to_memory_order(self.data, self.order, copy=True).astype(np.float64) + double = to_memory_order(self.data, self.order, copy=not immutable).astype( + np.float64 + ) + if immutable: + double.flags.writeable = False + elif np.shares_memory(double, self.data): + double = double.copy() + return double @property def ndims(self) -> int: diff --git a/pyttb/tensor.py b/pyttb/tensor.py index 0f6cde35..cdeafbf5 100644 --- a/pyttb/tensor.py +++ b/pyttb/tensor.py @@ -496,13 +496,18 @@ def contract(self, i1: int, i2: int) -> Union[np.ndarray, tensor]: return ttb.tensor(newdata, newsize, copy=False) - def double(self) -> np.ndarray: + def double(self, immutable: bool = False) -> np.ndarray: """ Convert `:class:pyttb.tensor` to an `:class:numpy.ndarray` of doubles. + Parameters + ---------- + immutable: Whether or not the returned data cam be mutated. May enable + additional optimizations. + Returns ------- - Copy of tensor data. + Array of tensor data. Examples -------- @@ -511,7 +516,10 @@ def double(self) -> np.ndarray: array([[1., 1.], [1., 1.]]) """ - return self.data.astype(np.float64, order=self.order, copy=True) + double = self.data.astype(np.float64, order=self.order, copy=not immutable) + if immutable: + double.flags.writeable = False + return double def exp(self) -> tensor: """ diff --git a/pyttb/ttensor.py b/pyttb/ttensor.py index eb58d4f4..377c2569 100644 --- a/pyttb/ttensor.py +++ b/pyttb/ttensor.py @@ -228,14 +228,15 @@ def full(self) -> ttb.tensor: recomposed_tensor = recomposed_tensor.to_tensor() return recomposed_tensor - def double(self) -> np.ndarray: + def double(self, immutable: bool = False) -> np.ndarray: """Convert ttensor to an array of doubles. - Returns - ------- - Copy of tensor data. + Parameters + ---------- + immutable: Whether or not the returned data cam be mutated. May enable + additional optimizations. """ - return self.full().double() + return self.full().double(immutable) @property def ndims(self) -> int: diff --git a/tests/test_sptenmat.py b/tests/test_sptenmat.py index c084d396..0bae0256 100644 --- a/tests/test_sptenmat.py +++ b/tests/test_sptenmat.py @@ -222,6 +222,9 @@ def test_sptenmat_double(sample_sptensor_2way): f"Spmatrix: {spmatrix}\n" f"Sptenmat: {sptenmat_matrix}" ) + # Smoke test to make sure flag works coo_matrix is effectively already immutable + S.double(True) + def test_sptenmat_full(sample_sptensor_2way): params3, sptensorInstance = sample_sptensor_2way diff --git a/tests/test_sptensor.py b/tests/test_sptensor.py index 0e41e23a..af8b06e3 100644 --- a/tests/test_sptensor.py +++ b/tests/test_sptensor.py @@ -1030,6 +1030,11 @@ def test_sptensor_double(sample_sptensor): assert double_array.shape == data["shape"] assert_consistent_order(sptensorInstance, double_array) + # Verify immutability + double_array = sptensorInstance.double(True) + with pytest.raises(ValueError): + double_array[0] = 1 + def test_sptensor_compare(sample_sptensor): # This is kind of a test just for coverage sake diff --git a/tests/test_sumtensor.py b/tests/test_sumtensor.py index 0eb9997e..c0c36200 100644 --- a/tests/test_sumtensor.py +++ b/tests/test_sumtensor.py @@ -168,6 +168,11 @@ def test_sumtensor_full_double(example_ttensor, example_kensor): assert isinstance(double_array, np.ndarray) assert_consistent_order(S, double_array) + # Verify immutability + double_array = S.double(True) + with pytest.raises(ValueError): + double_array[0] = 1 + def test_sumtensor_innerprod(example_ttensor, example_kensor): T1 = ttb.tenones((2, 2)) diff --git a/tests/test_tenmat.py b/tests/test_tenmat.py index 0265fc6a..822e1be7 100644 --- a/tests/test_tenmat.py +++ b/tests/test_tenmat.py @@ -315,6 +315,11 @@ def test_tenmat_double(sample_tenmat_4way): assert (double_array == tenmatInstance.data.astype(np.float64)).all() assert_consistent_order(tenmatInstance, double_array) + # Verify immutability + double_array = tenmatInstance.double(True) + with pytest.raises(ValueError): + double_array[0] = 1 + def test_tenmat_ndims(sample_tenmat_4way): (params, tenmatInstance) = sample_tenmat_4way diff --git a/tests/test_tensor.py b/tests/test_tensor.py index 20f913a8..92a84e09 100644 --- a/tests/test_tensor.py +++ b/tests/test_tensor.py @@ -789,6 +789,11 @@ def test_tensor_double(sample_tensor_2way): assert np.array_equal(double_array, params["data"]) assert_consistent_order(tensorInstance, double_array) + # Verify immutability + double_array = tensorInstance.double(True) + with pytest.raises(ValueError): + double_array[0] = 1 + def test_tensor_isequal(sample_tensor_2way): (params, tensorInstance) = sample_tensor_2way diff --git a/tests/test_ttensor.py b/tests/test_ttensor.py index f64e7f59..e09bbe25 100644 --- a/tests/test_ttensor.py +++ b/tests/test_ttensor.py @@ -152,6 +152,11 @@ def test_ttensor_double(sample_ttensor): assert ttensorInstance.double() == np.prod(ttensorInstance.core.shape) assert_consistent_order(ttensorInstance, ttensorInstance.double()) + # Verify immutability + double_array = ttensorInstance.double(True) + with pytest.raises(ValueError): + double_array[0] = 1 + def test_ttensor_ndims(sample_ttensor): ttensorInstance = sample_ttensor From 2ddcd108ff3e7501ae4472371ee10b8bf3882a9e Mon Sep 17 00:00:00 2001 From: Nick Johnson <24689722+ntjohnson1@users.noreply.github.com> Date: Sat, 31 May 2025 08:38:15 -0400 Subject: [PATCH 8/8] Resolve all warnings in docs and treat future warnings as errors --- .readthedocs.yaml | 1 + CONTRIBUTING.md | 5 +++++ docs/source/conf.py | 5 +++-- docs/source/gcpopt.rst | 4 ++-- docs/source/index.rst | 1 - docs/source/tensor.rst | 4 ++-- docs/source/ttensor.rst | 4 ++-- docs/source/tuckerals.rst | 4 ++-- pyttb/ktensor.py | 8 ++++---- pyttb/sptensor.py | 4 ++-- pyttb/tensor.py | 12 ++++++------ 11 files changed, 29 insertions(+), 23 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index c3758249..714f508b 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -13,6 +13,7 @@ build: # Build documentation in the "docs/" directory with Sphinx sphinx: configuration: docs/source/conf.py + fail_on_warning: true # Optionally build your docs in additional formats such as PDF and ePub # formats: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d0b75ae9..2d36e338 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -74,6 +74,11 @@ current or filing a new [issue](https://github.com/sandialabs/pyttb/issues). ```commandline sphinx-build ./docs/source ./docs/build ``` + 1. For the CI version which is more strict + ```commandline + sphinx-build ./docs/source ./docs/build -W -n --keep-going + ``` + 2. If not on Windows optionally add `-j auto` for parallelization 2. Clear notebook outputs if run locally see `nbstripout` in our [pre-commit configuration](.pre-commit-config.yaml) ### Adding tutorials diff --git a/docs/source/conf.py b/docs/source/conf.py index dee16974..595ea407 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -69,8 +69,9 @@ napoleon_use_rtype = False intersphinx_mapping = { - "numpy": ("http://docs.scipy.org/doc/numpy/", "numpy.inv"), - "python": ("http://docs.python.org/3.8/", "python.inv"), + "numpy": ("https://numpy.org/doc/stable/", None), + "python": ("http://docs.python.org/3.9/", None), + "matplotlib": ("https://matplotlib.org/stable/", None), } # Add any paths that contain templates here, relative to this directory. diff --git a/docs/source/gcpopt.rst b/docs/source/gcpopt.rst index 57bdaebe..39415cf0 100644 --- a/docs/source/gcpopt.rst +++ b/docs/source/gcpopt.rst @@ -1,5 +1,5 @@ -Generalized CP Optimization (:obj:`gcp_opt`) -============================================ +Generalized CP Optimization (:obj:`pyttb.gcp_opt`) +================================================== .. note:: The ``gcp_opt`` function defined in ``gcp_opt.py`` has been promoted to the ``pyttb`` namespace. diff --git a/docs/source/index.rst b/docs/source/index.rst index 3183c0a8..1f400b3b 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -14,7 +14,6 @@ analysis. - Reports of bugs and feature requests can be `submitted on github `_. - For more information or for feedback on this project, please `contact us`_. -.. _`LICENSE`: ../../../LICENSE .. _contact us: #contact diff --git a/docs/source/tensor.rst b/docs/source/tensor.rst index f27fe87e..bc0e61ba 100644 --- a/docs/source/tensor.rst +++ b/docs/source/tensor.rst @@ -1,5 +1,5 @@ -Dense Tensor (:class:`tensor`) ------------------------------- +Dense Tensor (:class:`pyttb.tensor`) +------------------------------------ .. note:: Classes and functions defined in ``tensor.py`` have been promoted to the ``pyttb`` namespace. diff --git a/docs/source/ttensor.rst b/docs/source/ttensor.rst index b803d318..31a698f2 100644 --- a/docs/source/ttensor.rst +++ b/docs/source/ttensor.rst @@ -1,5 +1,5 @@ -Tucker tensor (:class:`ttensor`) --------------------------------- +Tucker tensor (:class:`pyttb.ttensor`) +-------------------------------------- .. note:: The ``ttensor`` class defined in ``ttensor.py`` has been promoted to the ``pyttb`` namespace. diff --git a/docs/source/tuckerals.rst b/docs/source/tuckerals.rst index e9d2478e..6859e775 100644 --- a/docs/source/tuckerals.rst +++ b/docs/source/tuckerals.rst @@ -1,5 +1,5 @@ -Tucker Alternating Least Squares (:obj:`tucker_als`) -==================================================== +Tucker Alternating Least Squares (:obj:`pyttb.tucker_als`) +========================================================== .. note:: The ``tucker_als`` function defined in ``tucker_als.py`` has been promoted to the ``pyttb`` namespace. diff --git a/pyttb/ktensor.py b/pyttb/ktensor.py index 730087d8..b6cde7a2 100644 --- a/pyttb/ktensor.py +++ b/pyttb/ktensor.py @@ -1170,7 +1170,7 @@ def issymmetric( def mask(self, W: Union[ttb.tensor, ttb.sptensor]) -> np.ndarray: """Extract :class:`pyttb.ktensor` values as specified by `W`. - `W` is a + `W` is a :class:`pyttb.tensor` or :class:`pyttb.sptensor` containing only values of zeros (0) and ones (1). The values in the :class:`pyttb.ktensor` corresponding to the indices for the @@ -2292,7 +2292,7 @@ def viz( # noqa: PLR0912, PLR0913 of a factor. Function for mode i must have signature `f(v_i,ax)` where `v_i` is :class:`numpy.ndarray` vector of dimension `n_i` and - `ax` is a :class:`matplotlib.axes.Axes' on which to plot. + `ax` is a :class:`matplotlib.axes.Axes` on which to plot. show_figure: Boolean determining if the resulting figure should be shown. normalize: @@ -2322,9 +2322,9 @@ def viz( # noqa: PLR0912, PLR0913 Returns ------- fig: - :class:`matplotlib.figure.Figure' handle for the generated figure + :class:`matplotlib.figure.Figure` handle for the generated figure axs: - :class:`matplotlib.axes.Axes' for the generated figure + :class:`matplotlib.axes.Axes` for the generated figure Examples -------- diff --git a/pyttb/sptensor.py b/pyttb/sptensor.py index 7e1f0c66..25c7f2af 100644 --- a/pyttb/sptensor.py +++ b/pyttb/sptensor.py @@ -94,7 +94,7 @@ def __init__( ): """Construct a :class:`pyttb.sptensor`. - Constructed from a set of `subs` (subscripts), + Constructed from a set of `subs` (subscripts), `vals` (values), and `shape`. No validation is performed. For initializer with error checking see :meth:`from_aggregator`. @@ -268,7 +268,7 @@ def from_aggregator( ) -> sptensor: """Construct a :class:`pyttb.sptensor`. - Constructed from a set of `subs` (subscripts), + Constructed from a set of `subs` (subscripts), `vals` (values), and `shape` after an aggregation function is applied to the values. diff --git a/pyttb/tensor.py b/pyttb/tensor.py index cdeafbf5..e9a13f48 100644 --- a/pyttb/tensor.py +++ b/pyttb/tensor.py @@ -58,11 +58,11 @@ class tensor: ---------- data : numpy.ndarray Data of the tensor - shape : tuple of integers + shape : Tuple[int] Size of the tensor - Instances of :class:`pyttb.tensor` can be created using :meth:`__init__` - or the following methods: + Instances of :class:`pyttb.tensor` can be created using + :meth:`pyttb.tensor.tensor.__init__` or the following methods: * :meth:`from_function` - Create a tensor from a function * :meth:`copy` - Make a deep copy of a tensor @@ -324,7 +324,7 @@ def collapse( dims: optional Dimensions to collapse (default: all). fun: optional - Method used to collapse dimensions (default: :meth:`numpy.sum`). + Function used to collapse dimensions (default: :func:`numpy.sum`). Returns ------- @@ -1917,8 +1917,8 @@ def tenfun( ) -> ttb.tensor: """Apply a function to each element in a tensor or tensors. - See :meth:`pyttb.tensor.tenfun_binary` and - :meth:`pyttb.tensor.tenfun_binary_unary` for supported + See :meth:`pyttb.tensor.tensor.tenfun_binary` and + :meth:`pyttb.tensor.tensor.tenfun_unary` for supported options. """ assert callable(function_handle), "function_handle must be callable"