Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .github/workflows/oldest-support.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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: |
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/regression-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion .readthedocs.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion docs/source/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -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),
}

Expand Down
6 changes: 4 additions & 2 deletions profiling/algorithms_profiling.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -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"
]
},
{
Expand Down
11 changes: 7 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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",
Expand All @@ -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"
Expand Down
4 changes: 2 additions & 2 deletions pyttb/cp_apr.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
9 changes: 4 additions & 5 deletions pyttb/create_problem.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion pyttb/gcp/fg_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
16 changes: 10 additions & 6 deletions pyttb/gcp/optimizers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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


Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -348,19 +350,21 @@ 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]
factor_matrices = [
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

Expand Down Expand Up @@ -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

Expand Down
5 changes: 3 additions & 2 deletions pyttb/gcp/samplers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
Expand Down
3 changes: 1 addition & 2 deletions pyttb/ktensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
4 changes: 1 addition & 3 deletions pyttb/matlab/matlab_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
21 changes: 10 additions & 11 deletions pyttb/pyttb_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
from typing import (
Any,
Literal,
Union,
cast,
get_args,
overload,
Expand All @@ -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.
Expand Down Expand Up @@ -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):
Expand All @@ -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]
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
4 changes: 2 additions & 2 deletions pyttb/sptenmat.py
Original file line number Diff line number Diff line change
Expand Up @@ -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`.

Expand Down Expand Up @@ -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, :]
Expand Down
Loading
Loading