diff --git a/.github/workflows/oldest-support.yaml b/.github/workflows/oldest-support.yaml index 17759acc..f2de3dcd 100644 --- a/.github/workflows/oldest-support.yaml +++ b/.github/workflows/oldest-support.yaml @@ -33,7 +33,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v5 with: - python-version: '3.9' # Find a way to globally define our minimum python once + python-version: '3.10' # Find a way to globally define our minimum python once - name: Install dependencies with minimum versions run: | diff --git a/.github/workflows/regression-tests.yml b/.github/workflows/regression-tests.yml index 68c8f436..227f9d44 100644 --- a/.github/workflows/regression-tests.yml +++ b/.github/workflows/regression-tests.yml @@ -16,7 +16,7 @@ jobs: strategy: fail-fast: false matrix: # Keep these in ascending order for automagic with coverage - python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"] + python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"] steps: - uses: actions/checkout@v4 diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 714f508b..c33b7b4d 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -8,7 +8,7 @@ version: 2 build: os: ubuntu-22.04 tools: - python: "3.9" + python: "3.10" # Build documentation in the "docs/" directory with Sphinx sphinx: diff --git a/docs/source/conf.py b/docs/source/conf.py index d9198ec1..69ed391b 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -72,7 +72,7 @@ intersphinx_mapping = { "numpy": ("https://numpy.org/doc/stable/", None), - "python": ("http://docs.python.org/3.9/", None), + "python": ("http://docs.python.org/3.10/", None), "matplotlib": ("https://matplotlib.org/stable/", None), } diff --git a/profiling/algorithms_profiling.ipynb b/profiling/algorithms_profiling.ipynb index fd10076e..af2c3f6e 100644 --- a/profiling/algorithms_profiling.ipynb +++ b/profiling/algorithms_profiling.ipynb @@ -22,9 +22,11 @@ "import pstats\n", "import subprocess\n", "from pathlib import Path\n", - "from typing import Callable, Optional, Union\n", "\n", - "from pyttb import cp_als, cp_apr, hosvd, import_data, sptensor, tensor, tucker_als" + "from pyttb import cp_als, cp_apr, hosvd, import_data, sptensor, tensor, tucker_als\n", + "\n", + "if TYPE_CHECKING:\n", + " from collections.abc import Callable" ] }, { diff --git a/pyproject.toml b/pyproject.toml index 7aa29841..983189b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,22 +7,23 @@ authors = [ ] license = { text="BSD 2-Clause License" } readme = "README.md" -requires-python = ">=3.9" +requires-python = ">=3.10" dependencies = [ "numpy<3.0,>=1.24", "numpy_groupies>0.11", - "scipy<1.15,>1.9", + "scipy<2.0,>1.9", "matplotlib>3.7", ] classifiers = [ "License :: OSI Approved :: BSD License", "Operating System :: OS Independent", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", ] [project.urls] @@ -68,7 +69,7 @@ requires = ["setuptools>=61.0", "numpy", "numpy_groupies", "scipy", "wheel"] build-backend = "setuptools.build_meta" [tool.ruff.lint] -select = ["E", "F", "PL", "W", "I", "N", "NPY", "RUF", "B", "D", "FA", "UP", "TC"] +select = ["E", "F", "PL", "W", "I", "N", "NPY", "RUF", "B", "D", "FA", "UP", "TC", "ARG", "PIE"] ignore = [ # Ignored in conversion to ruff since not previously enforced "PLR2004", @@ -86,6 +87,8 @@ ignore = [ "B028", # Personal preference on magic method "D105", + # Rule removed in newer version of ruff + "UP038", ] [tool.ruff.lint.pydocstyle] convention = "numpy" diff --git a/pyttb/cp_apr.py b/pyttb/cp_apr.py index aebb016e..92595727 100644 --- a/pyttb/cp_apr.py +++ b/pyttb/cp_apr.py @@ -664,7 +664,7 @@ def tt_cp_apr_pdnr( # noqa: PLR0912,PLR0913,PLR0915 nInnerIters[iteration] += countInnerIters[n] # Save output items for the outer iteration. - num_zero = 0 + num_zero = np.intp(0) for n in range(N): num_zero += np.count_nonzero(M.factor_matrices[n] == 0) # [0].size @@ -1065,7 +1065,7 @@ def tt_cp_apr_pqnr( # noqa: PLR0912,PLR0913,PLR0915 nInnerIters[iteration] += countInnerIters[n] # Save output items for the outer iteration. - num_zero = 0 + num_zero = np.intp(0) for n in range(N): num_zero += np.count_nonzero(M.factor_matrices[n] == 0) # [0].size diff --git a/pyttb/create_problem.py b/pyttb/create_problem.py index f601090e..8277d5ba 100644 --- a/pyttb/create_problem.py +++ b/pyttb/create_problem.py @@ -4,8 +4,9 @@ import logging import math +from collections.abc import Callable from dataclasses import dataclass, field -from typing import Callable, Union, cast, overload +from typing import cast, overload import numpy as np from numpy_groupies import aggregate as accumarray @@ -14,9 +15,7 @@ from pyttb.pyttb_utils import Shape, parse_shape solution_generator = Callable[[tuple[int, ...]], np.ndarray] -core_generator_t = Callable[ - [tuple[int, ...]], Union[ttb.tensor, ttb.sptensor, np.ndarray] -] +core_generator_t = Callable[[tuple[int, ...]], ttb.tensor | ttb.sptensor | np.ndarray] def randn(shape: tuple[int, ...]) -> np.ndarray: @@ -471,7 +470,7 @@ def generate_solution_factors(base_params: BaseProblem) -> list[np.ndarray]: f"{nfactors} and {shape}" ) factor_matrices = [] - for shape_i, nfactors_i in zip(shape, nfactors): + for shape_i, nfactors_i in zip(shape, nfactors, strict=False): factor_matrices.append(base_params.factor_generator((shape_i, nfactors_i))) if base_params.symmetric is not None: diff --git a/pyttb/gcp/fg_setup.py b/pyttb/gcp/fg_setup.py index 87eccc5c..8ed6af12 100644 --- a/pyttb/gcp/fg_setup.py +++ b/pyttb/gcp/fg_setup.py @@ -6,8 +6,8 @@ from __future__ import annotations +from collections.abc import Callable from functools import partial -from typing import Callable import numpy as np diff --git a/pyttb/gcp/optimizers.py b/pyttb/gcp/optimizers.py index 5e4c7462..e3ba5f7d 100644 --- a/pyttb/gcp/optimizers.py +++ b/pyttb/gcp/optimizers.py @@ -10,7 +10,7 @@ import time from abc import ABC, abstractmethod from math import inf -from typing import TYPE_CHECKING, Callable, TypedDict +from typing import TYPE_CHECKING, TypedDict import numpy as np from scipy.optimize import fmin_l_bfgs_b @@ -21,6 +21,8 @@ from pyttb.gcp.samplers import GCPSampler if TYPE_CHECKING: + from collections.abc import Callable + from pyttb.gcp.fg_setup import function_type @@ -252,7 +254,7 @@ def update_step( # noqa: D102 step = self._decay**self._nfails * self._rate factor_matrices = [ np.maximum(lower_bound, factor - step * grad) - for factor, grad in zip(model.factor_matrices, gradient) + for factor, grad in zip(model.factor_matrices, gradient, strict=False) ] return factor_matrices, step @@ -348,11 +350,11 @@ def update_step( # noqa: D102 self._v_prev = self._v.copy() self._m = [ self._beta_1 * mk + (1 - self._beta_1) * gk - for mk, gk in zip(self._m, gradient) + for mk, gk in zip(self._m, gradient, strict=False) ] self._v = [ self._beta_2 * vk + (1 - self._beta_2) * gk**2 - for vk, gk in zip(self._v, gradient) + for vk, gk in zip(self._v, gradient, strict=False) ] mhat = [mk / (1 - self._beta_1**self._total_iterations) for mk in self._m] vhat = [vk / (1 - self._beta_2**self._total_iterations) for vk in self._v] @@ -360,7 +362,9 @@ def update_step( # noqa: D102 np.maximum( lower_bound, factor_k - step * mhk / (np.sqrt(vhk) + self._epsilon) ) - for factor_k, mhk, vhk in zip(model.factor_matrices, mhat, vhat) + for factor_k, mhk, vhk in zip( + model.factor_matrices, mhat, vhat, strict=False + ) ] return factor_matrices, step @@ -401,7 +405,7 @@ def update_step( # noqa: D102 step = 1.0 / np.sqrt(self._gnormsum) factor_matrices = [ np.maximum(lower_bound, factor_k - step * gk) - for factor_k, gk in zip(model.factor_matrices, gradient) + for factor_k, gk in zip(model.factor_matrices, gradient, strict=False) ] return factor_matrices, step diff --git a/pyttb/gcp/samplers.py b/pyttb/gcp/samplers.py index 8a75a3fc..923959fa 100644 --- a/pyttb/gcp/samplers.py +++ b/pyttb/gcp/samplers.py @@ -7,11 +7,12 @@ from __future__ import annotations import logging +from collections.abc import Callable from dataclasses import dataclass from enum import Enum from functools import partial from math import ceil -from typing import Callable, Union, cast +from typing import cast import numpy as np @@ -21,7 +22,7 @@ from pyttb.tensor import tensor sample_type = tuple[np.ndarray, np.ndarray, np.ndarray] -sampler_type = Callable[[Union[tensor, sptensor]], sample_type] +sampler_type = Callable[[tensor | sptensor], sample_type] @dataclass diff --git a/pyttb/ktensor.py b/pyttb/ktensor.py index e4518391..6021bef9 100644 --- a/pyttb/ktensor.py +++ b/pyttb/ktensor.py @@ -8,11 +8,10 @@ import logging import warnings -from collections.abc import Sequence +from collections.abc import Callable, Sequence from math import prod from typing import ( TYPE_CHECKING, - Callable, Literal, cast, overload, diff --git a/pyttb/matlab/matlab_support.py b/pyttb/matlab/matlab_support.py index 859a1192..aff628b6 100644 --- a/pyttb/matlab/matlab_support.py +++ b/pyttb/matlab/matlab_support.py @@ -5,15 +5,13 @@ # U.S. Government retains certain rights in this software. from __future__ import annotations -from typing import Union - import numpy as np from pyttb.tensor import tensor from .matlab_utilities import _matlab_array_str -PRINT_CLASSES = Union[tensor, np.ndarray] +PRINT_CLASSES = tensor | np.ndarray def matlab_print( diff --git a/pyttb/pyttb_utils.py b/pyttb/pyttb_utils.py index 04cadd67..f1271c6b 100644 --- a/pyttb/pyttb_utils.py +++ b/pyttb/pyttb_utils.py @@ -12,7 +12,6 @@ from typing import ( Any, Literal, - Union, cast, get_args, overload, @@ -23,16 +22,16 @@ import pyttb as ttb -Shape = Union[int, Iterable[int]] +Shape = int | Iterable[int] """Shape represents the object size or dimensions. It can be specified as either a single integer or an iterable of integers, which will be normalized to a tuple internally.""" -OneDArray = Union[int, float, Iterable[int], Iterable[float], np.ndarray] +OneDArray = int | float | Iterable[int] | Iterable[float] | np.ndarray """OneDArray represents any one-dimensional array, which can be a single integer or float, and iterable of integerss or floats, or a NumPy array.""" -MemoryLayout = Union[Literal["F"], Literal["C"]] +MemoryLayout = Literal["F"] | Literal["C"] """MemoryLayout is the set of options for the layout of a tensor. It can be "F", meaning Fortran ordered and analogous to column-major for matrices, or "C", meaning C ordered and analogous to row-major for matrices. @@ -347,7 +346,7 @@ def tt_renumber( """ newshape = np.array(shape) newsubs = subs - for i in range(0, len(shape)): + for i in range(len(shape)): if not number_range[i] == slice(None, None, None): if subs.size == 0: if not isinstance(number_range[i], slice): @@ -365,7 +364,7 @@ def tt_renumber( # without assert number_range_i = number_range[i] assert isinstance(number_range_i, slice) - newshape[i] = len(range(0, shape[i])[number_range_i]) + newshape[i] = len(range(shape[i])[number_range_i]) else: newsubs[:, i], newshape[i] = tt_renumberdim( subs[:, i], shape[i], number_range[i] @@ -397,7 +396,7 @@ def tt_renumberdim( number_range = [int(number_range)] newshape = 0 elif isinstance(number_range, slice): - number_range = list(range(0, shape))[number_range] + number_range = list(range(shape))[number_range] newshape = len(number_range) elif isinstance(number_range, (Sequence, np.ndarray)): newshape = len(number_range) @@ -406,7 +405,7 @@ def tt_renumberdim( # Create map from old range to the new range idx_map = np.zeros(shape=shape) - for i in range(0, newshape): + for i in range(newshape): idx_map[number_range[i]] = int(i) # Do the mapping @@ -492,7 +491,7 @@ def tt_ind2sub( return np.array(np.unravel_index(idx, shape, order=order)).transpose() -def tt_subsubsref(obj: np.ndarray, s: Any) -> float | np.ndarray: +def tt_subsubsref(obj: np.ndarray, s: Any) -> float | np.ndarray: # noqa: ARG001 """Helper function for tensor toolbox subsref. Parameters @@ -759,8 +758,8 @@ class IndexVariant(Enum): # We probably want to create a specific file for utility types -LinearIndexType = Union[int, np.integer, slice] -IndexType = Union[LinearIndexType, Sequence[int], np.ndarray] +LinearIndexType = int | np.integer | slice +IndexType = LinearIndexType | Sequence[int] | np.ndarray def get_index_variant(indices: IndexType) -> IndexVariant: diff --git a/pyttb/sptenmat.py b/pyttb/sptenmat.py index 9b9911b7..8a2502e3 100644 --- a/pyttb/sptenmat.py +++ b/pyttb/sptenmat.py @@ -328,7 +328,7 @@ def shape(self) -> tuple[int, ...]: n = np.prod(np.array(self.tshape)[self.cdims]) return int(m), int(n) - def double(self, immutable: bool = False) -> sparse.coo_matrix: + def double(self, immutable: bool = False) -> sparse.coo_matrix: # noqa: ARG002 """ Convert a :class:`pyttb.sptenmat` to a COO :class:`scipy.sparse.coo_matrix`. @@ -621,7 +621,7 @@ def __repr__(self): # An empty ndarray with minimum dimensions still has a shape if self.subs.size > 0: - for i in range(0, self.subs.shape[0]): + for i in range(self.subs.shape[0]): s += "\t" s += "[" idx = self.subs[i, :] diff --git a/pyttb/sptensor.py b/pyttb/sptensor.py index d1c7574e..f584c0aa 100644 --- a/pyttb/sptensor.py +++ b/pyttb/sptensor.py @@ -8,12 +8,11 @@ import logging import warnings -from collections.abc import Iterable, Sequence +from collections.abc import Callable, Iterable, Sequence from math import prod from operator import ge, gt, le, lt from typing import ( Any, - Callable, Literal, cast, overload, @@ -428,11 +427,11 @@ def allsubs(self) -> np.ndarray: # Generate appropriately sized ones vectors o = [] - for n in range(0, self.ndims): + for n in range(self.ndims): o.append(np.ones((self.shape[n], 1))) # Generate each column of the subscripts in turn - for n in range(0, self.ndims): + for n in range(self.ndims): i: list[np.ndarray] = o.copy() i[n] = np.expand_dims(np.arange(0, self.shape[n]), axis=1) s[:, n] = np.squeeze(ttb.khatrirao(*i)) @@ -1889,7 +1888,7 @@ def subdims(self, region: Sequence[int | np.ndarray | slice]) -> np.ndarray: loc = np.arange(0, len(self.subs)) - for i in range(0, self.ndims): + for i in range(self.ndims): # TODO: Consider cleaner typing coercion # Find subscripts that match in dimension i if isinstance(region[i], (int, np.generic)): @@ -1897,7 +1896,7 @@ def subdims(self, region: Sequence[int | np.ndarray | slice]) -> np.ndarray: elif isinstance(region[i], (np.ndarray, list)): tf = np.isin(self.subs[loc, i], cast("np.ndarray", region[i])) elif isinstance(region[i], slice): - sliceRegion = range(0, self.shape[i])[region[i]] + sliceRegion = range(self.shape[i])[region[i]] tf = np.isin(self.subs[loc, i], sliceRegion) else: raise ValueError( @@ -2407,7 +2406,7 @@ def _set_subscripts(self, key, value): # noqa: PLR0912 # Process Group B: Removing Values if np.sum(idxb) > 0: removesubs = loc[idxb] - keepsubs = np.setdiff1d(range(0, self.nnz), removesubs) + keepsubs = np.setdiff1d(range(self.nnz), removesubs) self.subs = self.subs[keepsubs, :] self.vals = self.vals[keepsubs] # Process Group C: Adding new, nonzero values @@ -2474,7 +2473,7 @@ def _set_subtensor(self, key, value): # noqa: PLR0912, PLR0915 ) # Delete what currently occupies the specified range rmloc = self.subdims(key) - kploc = np.setdiff1d(range(0, self.nnz), rmloc) + kploc = np.setdiff1d(range(self.nnz), rmloc) # TODO: evaluate solution for assigning value to empty sptensor if len(self.subs.shape) > 1: newsubs = self.subs[kploc.astype(int), :] @@ -2502,7 +2501,7 @@ def _set_subtensor(self, key, value): # noqa: PLR0912, PLR0915 # First, resize the tensor, determine new size of existing modes newsz = [] - for n in range(0, self.ndims): + for n in range(self.ndims): if isinstance(key[n], slice): if key[n].stop is None: newsz.append(self.shape[n]) @@ -2544,7 +2543,7 @@ def _set_subtensor(self, key, value): # noqa: PLR0912, PLR0915 if isinstance(value, (int, float)) and value == 0: # Delete what currently occupies the specified range rmloc = self.subdims(key) - kploc = np.setdiff1d(range(0, self.nnz), rmloc).astype(int) + kploc = np.setdiff1d(range(self.nnz), rmloc).astype(int) self.subs = self.subs[kploc, :] self.vals = self.vals[kploc] return @@ -2556,7 +2555,7 @@ def _set_subtensor(self, key, value): # noqa: PLR0912, PLR0915 keyCopy = [None] * N # Figure out how many indices are in each dimension nssubs = np.zeros((N, 1)) - for n in range(0, N): + for n in range(N): if isinstance(key[n], slice): # Generate slice explicitly to determine its length keyCopy[n] = np.arange(0, self.shape[n])[key[n]] @@ -3446,7 +3445,7 @@ def __repr__(self): # pragma: no cover r = input("Are you sure you want to print all nonzeros? (Y/N)") if r.upper() != "Y": return s - for i in range(0, self.subs.shape[0]): + for i in range(self.subs.shape[0]): s += "[" idx = self.subs[i, :] s += str(idx.tolist())[1:] diff --git a/pyttb/tensor.py b/pyttb/tensor.py index 38cca47a..e1c05dc2 100644 --- a/pyttb/tensor.py +++ b/pyttb/tensor.py @@ -8,13 +8,12 @@ import logging import textwrap -from collections.abc import Iterable, Sequence +from collections.abc import Callable, Iterable, Sequence from inspect import signature from itertools import combinations_with_replacement, permutations from math import factorial, prod from typing import ( Any, - Callable, Literal, cast, overload, @@ -420,7 +419,7 @@ def collapse( ## Apply the collapse function B = np.zeros((A.shape[0], 1), order=self.order) - for i in range(0, A.shape[0]): + for i in range(A.shape[0]): B[i] = fun(A[i, :]) ## Form and return the final result @@ -503,7 +502,7 @@ def contract(self, i1: int, i2: int) -> np.ndarray | tensor: # Add diagonal entries for each slice newdata = np.zeros((m, 1), order=self.order) - for idx in range(0, n): + for idx in range(n): newdata += data[:, idx, idx][:, None] # Reshape result @@ -1214,7 +1213,7 @@ def nnz(self) -> int: >>> T.nnz 8 """ - return np.count_nonzero(self.data) + return int(np.count_nonzero(self.data)) def norm(self) -> float: """Frobenius norm of the tensor. @@ -1498,7 +1497,7 @@ def symmetrize( # noqa: PLR0912,PLR0915 # Use default newer faster version if version is None: ngrps = len(grps) - for i in range(0, ngrps): + for i in range(ngrps): # Extract current group thisgrp = grps[i] @@ -1544,36 +1543,36 @@ def symmetrize( # noqa: PLR0912,PLR0915 else: # Original version # Check tensor dimensions for compatibility with symmetrization ngrps = len(grps) - for i in range(0, ngrps): + for i in range(ngrps): dims = grps[i] for j in dims[1:]: if sz[j] != sz[dims[0]]: assert False, "Dimension mismatch for symmetrization" # Check for no overlap in sets - for i in range(0, ngrps): + for i in range(ngrps): for j in range(i + 1, ngrps): if not np.intersect1d(grps[i, :], grps[j, :]).size == 0: assert False, "Cannot have overlapping symmetries" # Create the combinations for each symmetrized subset combos = [] - for i in range(0, ngrps): + for i in range(ngrps): combos.append(np.array(list(permutations(grps[i, :])))) # Create all the permutations to be averaged combo_lengths = [len(perm) for perm in combos] total_perms = prod(combo_lengths) sym_perms = np.tile(np.arange(0, n), [total_perms, 1]) - for i in range(0, ngrps): + for i in range(ngrps): ntimes = np.prod(combo_lengths[0:i], dtype=int) ncopies = np.prod(combo_lengths[i + 1 :], dtype=int) nelems = len(combos[i]) perm_idx = 0 - for _ in range(0, ntimes): - for k in range(0, nelems): - for _ in range(0, ncopies): + for _ in range(ntimes): + for k in range(nelems): + for _ in range(ncopies): # TODO: Does this do anything? Matches MATLAB # at very least should be able to flatten sym_perms[perm_idx, grps[i]] = combos[i][k, :] @@ -1581,7 +1580,7 @@ def symmetrize( # noqa: PLR0912,PLR0915 # Create an average tensor Y = ttb.tensor(np.zeros(self.shape), copy=False) - for i in range(0, total_perms): + for i in range(total_perms): Y += self.permute(sym_perms[i, :]) Y /= total_perms @@ -1590,7 +1589,7 @@ def symmetrize( # noqa: PLR0912,PLR0915 # summations and so on, so let's fix that. # Idea borrowed from Gergana Bounova: # http://www.mit.edu/~gerganaa/downloads/matlab/symmetrize.m - for i in range(0, total_perms): + for i in range(total_perms): Z = Y.permute(sym_perms[i, :]) Y.data[:] = np.maximum(Y.data[:], Z.data[:]) @@ -1677,9 +1676,9 @@ def ttm( # old version (ver=0) shape = np.array(self.shape, dtype=int) n = dims[0] - order = np.array([n, *list(range(0, n)), *list(range(n + 1, self.ndims))]) + order = np.array([n, *list(range(n)), *list(range(n + 1, self.ndims))]) newdata = self.permute(order).data - ids = np.array(list(range(0, n)) + list(range(n + 1, self.ndims))) + ids = np.array(list(range(n)) + list(range(n + 1, self.ndims))) second_dim = 1 if len(ids) > 0: second_dim = np.prod(shape[ids]) @@ -1692,7 +1691,7 @@ def ttm( p = matrix.shape[0] newshape = np.array( - [p, *list(shape[range(0, n)]), *list(shape[range(n + 1, self.ndims)])] + [p, *list(shape[range(n)]), *list(shape[range(n + 1, self.ndims)])] ) Y_data: np.ndarray = np.reshape(newdata, newshape, order=self.order) Y_data = np.transpose(Y_data, np.argsort(order)) @@ -3019,6 +3018,7 @@ def tenrand(shape: Shape, order: MemoryLayout = "F") -> tensor: # mypy issue: 1484 def unit_uniform(pass_through_shape: tuple[int, ...]) -> np.ndarray: data = np.random.uniform(low=0, high=1, size=np.prod(pass_through_shape)) + data = data.reshape(pass_through_shape, order=order) return data return tensor.from_function(unit_uniform, shape) @@ -3114,7 +3114,7 @@ def teneye(ndims: int, size: int, order: MemoryLayout = "F") -> tensor: for j in range(ndims // 2): s[:, j] = p[:, 2 * j - 1] == p[:, 2 * j] v = np.sum(np.sum(s, axis=1) == ndims // 2) - A[tuple(zip(*p))] = v / factorial(ndims) + A[tuple(zip(*p, strict=False))] = v / factorial(ndims) return A diff --git a/pyttb/ttensor.py b/pyttb/ttensor.py index 419233b9..f17f8362 100644 --- a/pyttb/ttensor.py +++ b/pyttb/ttensor.py @@ -269,7 +269,7 @@ def isequal(self, other: ttensor) -> bool: return self.core.isequal(other.core) and all( np.array_equal(this_factor, other_factor) for this_factor, other_factor in zip( - self.factor_matrices, other.factor_matrices + self.factor_matrices, other.factor_matrices, strict=False ) ) @@ -318,7 +318,7 @@ def innerprod( return other.innerprod(self) W = [] for this_factor, other_factor in zip( - self.factor_matrices, other.factor_matrices + self.factor_matrices, other.factor_matrices, strict=False ): W.append(this_factor.transpose().dot(other_factor)) J = other.core.ttm(W) @@ -450,7 +450,7 @@ def mttkrp( W = [np.empty((), order=self.order)] * self.ndims if isinstance(U, ttb.ktensor): U = U.factor_matrices - for i in range(0, self.ndims): + for i in range(self.ndims): if i == n: continue W[i] = self.factor_matrices[i].transpose().dot(U[i]) @@ -605,7 +605,7 @@ def reconstruct( # noqa: PLR0912 ) full_samples = [np.array([], order=self.order)] * self.ndims - for sample, mode in zip(samples, modes): + for sample, mode in zip(samples, modes, strict=False): if np.isscalar(sample): full_samples[mode] = np.array([sample], order=self.order) else: diff --git a/tests/gcp/test_fg.py b/tests/gcp/test_fg.py index f8389d30..3b93472f 100644 --- a/tests/gcp/test_fg.py +++ b/tests/gcp/test_fg.py @@ -20,7 +20,7 @@ def test_evaluate(): data = ttb.tensor() evaluate(model, data) - def no_op(data, model): + def no_op(data, model): # noqa: ARG001 """Function handle that does nothing""" return data diff --git a/tests/gcp/test_fg_est.py b/tests/gcp/test_fg_est.py index 9699adc1..d79debec 100644 --- a/tests/gcp/test_fg_est.py +++ b/tests/gcp/test_fg_est.py @@ -44,7 +44,7 @@ def test_estimate(): data_vals = np.ones((2,)) weights = np.ones_like(data_vals) - def no_op(data, model): + def no_op(data, model): # noqa: ARG001 """Function handle that does nothing""" return data diff --git a/tests/test_cp_als.py b/tests/test_cp_als.py index b1a180b3..64a76b2e 100644 --- a/tests/test_cp_als.py +++ b/tests/test_cp_als.py @@ -75,7 +75,7 @@ def test_cp_als_tensor_ktensor_init(capsys, sample_tensor): assert pytest.approx(output["fit"]) == 1 -def test_cp_als_incorrect_init(capsys, sample_tensor): +def test_cp_als_incorrect_init(sample_tensor): (data, T) = sample_tensor # unsupported init type @@ -162,7 +162,7 @@ def test_cp_als_tensor_dimorder(capsys, sample_tensor): ) -def test_cp_als_tensor_zeros(capsys, sample_tensor): +def test_cp_als_tensor_zeros(capsys): # 2-way tensor T2 = ttb.tensor.from_function(np.zeros, (2, 2)) (M2, Minit2, output2) = ttb.cp_als(T2, 2) diff --git a/tests/test_hosvd.py b/tests/test_hosvd.py index cfe0c62b..e69f3c02 100644 --- a/tests/test_hosvd.py +++ b/tests/test_hosvd.py @@ -27,7 +27,7 @@ def sample_tensor_3way(): return params, tensorInstance -def test_hosvd_simple_convergence(capsys, sample_tensor): +def test_hosvd_simple_convergence(sample_tensor): (data, T) = sample_tensor tol = 1e-4 result = ttb.hosvd(T, tol) @@ -47,25 +47,25 @@ def test_hosvd_simple_convergence(capsys, sample_tensor): ) -def test_hosvd_default_init(capsys, sample_tensor): +def test_hosvd_default_init(sample_tensor): (data, T) = sample_tensor _ = ttb.hosvd(T, 1) -def test_hosvd_smoke_test_verbosity(capsys, sample_tensor): +def test_hosvd_smoke_test_verbosity(sample_tensor): """For now just make sure verbosity calcs don't crash""" (data, T) = sample_tensor ttb.hosvd(T, 1, verbosity=10) -def test_hosvd_incorrect_ranks(capsys, sample_tensor): +def test_hosvd_incorrect_ranks(sample_tensor): (data, T) = sample_tensor ranks = list(range(T.ndims - 1)) with pytest.raises(ValueError): _ = ttb.hosvd(T, 1, ranks=ranks) -def test_hosvd_incorrect_dimorder(capsys, sample_tensor): +def test_hosvd_incorrect_dimorder(sample_tensor): (data, T) = sample_tensor dimorder = list(range(T.ndims - 1)) with pytest.raises(ValueError): diff --git a/tests/test_ktensor.py b/tests/test_ktensor.py index 40977118..56487ce0 100644 --- a/tests/test_ktensor.py +++ b/tests/test_ktensor.py @@ -1184,7 +1184,7 @@ def test_ktensor__sub__(sample_ktensor_2way, sample_ktensor_3way): assert "Cannot subtract instance of this type from a ktensor" in str(excinfo) -def test_ktensor__mul__(sample_ktensor_2way, sample_ktensor_3way): +def test_ktensor__mul__(sample_ktensor_2way): (data0, K0) = sample_ktensor_2way K1 = 2 * K0 assert np.array_equal(2 * data0["weights"], K1.weights) diff --git a/tests/test_sptenmat.py b/tests/test_sptenmat.py index 13eac3f1..2b505135 100644 --- a/tests/test_sptenmat.py +++ b/tests/test_sptenmat.py @@ -261,7 +261,7 @@ def test_sptenmat_neg(sample_sptensor_2way): assert differences.size == 0, f"Spmatrix: {spmatrix}\nSptenmat: {sptenmat_matrix}" -def test_sptenmat_setitem(sample_sptensor_2way): +def test_sptenmat_setitem(): S = ttb.sptensor(shape=(4, 3)).to_sptenmat(rdims=np.array([0]), cdims=np.array([1])) with pytest.raises(IndexError): S[[0, 0]] = 1 @@ -331,7 +331,7 @@ def test_sptenmat__str__(sample_sptensor_3way): s += "[ " + (", ").join([str(int(d)) for d in sptenmatInstance3.cdims]) + " ] " s += "(modes of sptensor corresponding to columns)\n" - for i in range(0, sptenmatInstance3.subs.shape[0]): + for i in range(sptenmatInstance3.subs.shape[0]): s += "\t" s += "[" idx = sptenmatInstance3.subs[i, :] diff --git a/tests/test_sptensor.py b/tests/test_sptensor.py index 3d7eeea7..287ca2a8 100644 --- a/tests/test_sptensor.py +++ b/tests/test_sptensor.py @@ -80,7 +80,7 @@ def test_sptensor_initialization_from_data(sample_sptensor): def test_sptensor_initialization_from_function(): # Random Tensor Success - def function_handle(*args): + def function_handle(*args): # noqa: ARG001 return np.array([[0.5], [1.5], [2.5], [3.5], [4.5], [5.5]]) np.random.seed(123) @@ -117,7 +117,7 @@ def function_handle(*args): ) -def test_sptensor_initialization_from_aggregator(sample_sptensor): +def test_sptensor_initialization_from_aggregator(): subs = np.array([[1, 1, 1], [1, 1, 3], [2, 2, 2], [3, 3, 3], [1, 1, 1], [1, 1, 1]]) vals = np.array([[0.5], [1.5], [2.5], [3.5], [4.5], [5.5]]) shape = (4, 4, 4) @@ -668,9 +668,9 @@ def test_sptensor_norm(sample_sptensor): def test_sptensor_allsubs(sample_sptensor): (data, sptensorInstance) = sample_sptensor result = [] - for i in range(0, data["shape"][0]): - for j in range(0, data["shape"][1]): - for k in range(0, data["shape"][2]): + for i in range(data["shape"][0]): + for j in range(data["shape"][1]): + for k in range(data["shape"][2]): result.append([i, j, k]) assert np.array_equal(sptensorInstance.allsubs(), np.array(result)) @@ -682,9 +682,9 @@ def test_sptensor_logical_not(sample_sptensor): (data, sptensorInstance) = sample_sptensor result = [] data_subs = data["subs"].tolist() - for i in range(0, data["shape"][0]): - for j in range(0, data["shape"][1]): - for k in range(0, data["shape"][2]): + for i in range(data["shape"][0]): + for j in range(data["shape"][1]): + for k in range(data["shape"][2]): if [i, j, k] not in data_subs: result.append([i, j, k]) notSptensorInstance = sptensorInstance.logical_not() @@ -1037,7 +1037,7 @@ def test_sptensor_double(sample_sptensor): double_array[0] = 1 -def test_sptensor_compare(sample_sptensor): +def test_sptensor_compare(): # This is kind of a test just for coverage sake # mostly make clear that the operator check was intentional empty_sptensor = ttb.sptensor() diff --git a/tests/test_tensor.py b/tests/test_tensor.py index 5bfe6afd..57c05517 100644 --- a/tests/test_tensor.py +++ b/tests/test_tensor.py @@ -94,7 +94,8 @@ def test_tensor_initialization_from_data(sample_tensor_2way): def test_tensor_initialization_from_function(memory_layout): order = memory_layout["order"] - def function_handle(x): + # Dummy function handle + def function_handle(x): # noqa: ARG001 return np.array([[1, 2, 3], [4, 5, 6]], order=order) shape = (2, 3) @@ -1090,7 +1091,7 @@ def test_tensor__repr__(sample_tensor_2way): str(ttb.tensor()) -def test_tensor_exp(sample_tensor_2way, sample_tensor_3way, sample_tensor_4way): +def test_tensor_exp(sample_tensor_2way): (params, tensorInstance) = sample_tensor_2way exp_tensor = tensorInstance.exp() assert np.array_equal(tensorInstance.exp().data, np.exp(params["data"])) @@ -1259,7 +1260,7 @@ def test_tensor_ttm(sample_tensor_2way, sample_tensor_3way, sample_tensor_4way): assert "dims must contain values in [0,self.dims)" in str(excinfo) -def test_tensor_ttt(sample_tensor_2way, sample_tensor_3way, sample_tensor_4way): +def test_tensor_ttt(): M31 = ttb.tensor(np.reshape(np.arange(1, 2 * 3 * 4 + 1), [4, 3, 2], order="F")) M32 = ttb.tensor(np.reshape(np.arange(1, 2 * 3 * 4 + 1), [3, 4, 2], order="F")) @@ -1559,7 +1560,7 @@ def test_tensor_symmetrize(sample_tensor_2way): assert "Dimension mismatch for symmetrization" in str(excinfo) -def test_tensor__str__(sample_tensor_2way): +def test_tensor__str__(): # Test 1D data = np.random.normal(size=(4,)) tensorInstance = ttb.tensor(data) diff --git a/tests/test_ttensor.py b/tests/test_ttensor.py index f624bce6..caa71303 100644 --- a/tests/test_ttensor.py +++ b/tests/test_ttensor.py @@ -442,6 +442,9 @@ def test_ttensor_nvecs(random_ttensor): assert np.allclose(ttensor_eigvals, sparse_factors_ttensor_eigvals) +@pytest.mark.skip( + reason="I don't think this test makes sense. See TODO for adding better test." +) def test_ttensor_nvecs_all_zeros(random_ttensor): """Perform nvecs calculation on all zeros tensor to exercise sparsity edge cases""" ttensorInstance = random_ttensor diff --git a/tests/test_tucker_als.py b/tests/test_tucker_als.py index 4b13cf64..f4130e15 100644 --- a/tests/test_tucker_als.py +++ b/tests/test_tucker_als.py @@ -38,7 +38,7 @@ def test_tucker_als_tensor_default_init(capsys, sample_tensor): assert pytest.approx(output["fit"], 1) == 0 -def test_tucker_als_tensor_incorrect_init(capsys, sample_tensor): +def test_tucker_als_tensor_incorrect_init(sample_tensor): (data, T) = sample_tensor non_list = np.array([1]) # TODO: Consider generalizing to iterable @@ -59,7 +59,7 @@ def test_tucker_als_tensor_incorrect_init(capsys, sample_tensor): _ = ttb.tucker_als(T, 2, init=wrong_shape) -def test_tucker_als_tensor_incorrect_steptol(capsys, sample_tensor): +def test_tucker_als_tensor_incorrect_steptol(sample_tensor): (data, T) = sample_tensor non_scalar = np.array([1]) @@ -67,7 +67,7 @@ def test_tucker_als_tensor_incorrect_steptol(capsys, sample_tensor): _ = ttb.tucker_als(T, 2, stoptol=non_scalar) -def test_tucker_als_tensor_incorrect_maxiters(capsys, sample_tensor): +def test_tucker_als_tensor_incorrect_maxiters(sample_tensor): (data, T) = sample_tensor negative_value = -1 @@ -79,7 +79,7 @@ def test_tucker_als_tensor_incorrect_maxiters(capsys, sample_tensor): _ = ttb.tucker_als(T, 2, maxiters=non_scalar) -def test_tucker_als_tensor_incorrect_printitn(capsys, sample_tensor): +def test_tucker_als_tensor_incorrect_printitn(sample_tensor): (data, T) = sample_tensor non_scalar = np.array([1]) @@ -87,7 +87,7 @@ def test_tucker_als_tensor_incorrect_printitn(capsys, sample_tensor): _ = ttb.tucker_als(T, 2, printitn=non_scalar) -def test_tucker_als_tensor_incorrect_dimorder(capsys, sample_tensor): +def test_tucker_als_tensor_incorrect_dimorder(sample_tensor): (data, T) = sample_tensor non_list = np.array([1]) # TODO: Consider generalizing to iterable