Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Nick/numpy 2 #307

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
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
15 changes: 7 additions & 8 deletions .github/workflows/regression-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,28 +16,27 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11"]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]

steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
- name: Install base dependencies
run: |
python -m pip install --upgrade pip
pip install .
# Basic check for minimal deployed env requirements
python -c "import pyttb"
- name: Install dev dependencies
run: |
python -m pip install --upgrade coverage coveralls sphinx_rtd_theme
pip install ".[dev]"
- name: Check auto-formatters
run: |
black --check .
# - name: Lint with flake8
# run: |
# # stop the build if there are Python syntax errors or undefined names
# flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
# # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
# flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
- name: Run tests
run: |
coverage run --source pyttb -m pytest tests/
Expand Down
6 changes: 4 additions & 2 deletions pyttb/gcp/handles.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,15 +115,17 @@ def huber_grad(data: ttb.tensor, model: ttb.tensor, threshold: float) -> np.ndar
) * np.logical_not(below_threshold)


# FIXME: Num trials should be enforced as integer here and in MATLAB
# requires updating our regression test values to calculate MATLAB integer version
def negative_binomial(
data: np.ndarray, model: np.ndarray, num_trials: int
data: np.ndarray, model: np.ndarray, num_trials: float
) -> np.ndarray:
"""Return objective function for negative binomial distributions"""
return (num_trials + data) * np.log(model + 1) - data * np.log(model + EPS)


def negative_binomial_grad(
data: np.ndarray, model: np.ndarray, num_trials: int
data: np.ndarray, model: np.ndarray, num_trials: float
) -> np.ndarray:
"""Return gradient function for negative binomial distributions"""
return (num_trials + 1) / (1 + model) - data / (model + EPS)
Expand Down
7 changes: 5 additions & 2 deletions pyttb/gcp/samplers.py
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,7 @@ def zeros(

# Select out just the zeros
tmpidx = tt_sub2ind(data.shape, tmpsubs)
iszero = np.logical_not(np.in1d(tmpidx, nz_idx))
iszero = np.logical_not(np.isin(tmpidx, nz_idx))
tmpsubs = tmpsubs[iszero, :]

# Trim back to desired numb of samples
Expand Down Expand Up @@ -425,7 +425,7 @@ def semistrat(data: ttb.sptensor, num_nonzeros: int, num_zeros: int) -> sample_t


def stratified(
data: ttb.sptensor,
data: Union[ttb.sptensor, ttb.tensor],
nz_idx: np.ndarray,
num_nonzeros: int,
num_zeros: int,
Expand All @@ -450,6 +450,9 @@ def stratified(
-------
Subscripts, values, and weights of samples (Nonzeros then zeros).
"""
assert isinstance(
data, ttb.sptensor
), "For stratified sampling Sparse Tensor must be provided"
[nonzero_subs, nonzero_vals] = nonzeros(data, num_nonzeros, with_replacement=True)
nonzero_weights = np.ones((num_nonzeros,))
if num_nonzeros > 0:
Expand Down
15 changes: 8 additions & 7 deletions pyttb/ktensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@

import numpy as np
import scipy.sparse.linalg
from typing_extensions import Self

import pyttb as ttb
from pyttb.pyttb_utils import (
get_mttkrp_factors,
isrow,
isvector,
np_to_python,
tt_dimscheck,
tt_ind2sub,
)
Expand Down Expand Up @@ -695,7 +695,8 @@ def extract(
invalid_entries.append(component)
if len(invalid_entries) > 0:
assert False, (
f"Invalid component indices to be extracted: {invalid_entries} "
f"Invalid component indices to be extracted: "
f"{np_to_python(invalid_entries)} "
f"not in range({self.ncomponents})"
)
new_weights = self.weights[components]
Expand All @@ -706,7 +707,7 @@ def extract(
else:
assert False, "Input parameter must be an int, tuple, list or numpy.ndarray"

def fixsigns(self, other: Optional[ktensor] = None) -> Self: # noqa: PLR0912
def fixsigns(self, other: Optional[ktensor] = None) -> ktensor: # noqa: PLR0912
"""
Change the elements of a :class:`pyttb.ktensor` in place so that the
largest magnitude entries for each column vector in each factor
Expand Down Expand Up @@ -1184,7 +1185,7 @@ def normalize(
sort: Optional[bool] = False,
normtype: float = 2,
mode: Optional[int] = None,
) -> Self:
) -> ktensor:
"""
Normalize the columns of the factor matrices of a
:class:`pyttb.ktensor` in place, then optionally
Expand Down Expand Up @@ -1405,7 +1406,7 @@ def permute(self, order: np.ndarray) -> ktensor:

return ttb.ktensor([self.factor_matrices[i] for i in order], self.weights)

def redistribute(self, mode: int) -> Self:
def redistribute(self, mode: int) -> ktensor:
"""
Distribute weights of a :class:`pyttb.ktensor` to the specified mode.
The redistribution is performed in place.
Expand Down Expand Up @@ -1621,7 +1622,7 @@ def score(

# Rearrange the components of A according to the best matching
foo = np.arange(RA)
tf = np.in1d(foo, best_perm)
tf = np.isin(foo, best_perm)
best_perm[RB : RA + 1] = foo[~tf]
A.arrange(permutation=best_perm)
return best_score, A, flag, best_perm
Expand Down Expand Up @@ -1999,7 +2000,7 @@ def ttv(
factor_matrices.append(self.factor_matrices[i])
return ttb.ktensor(factor_matrices, new_weights, copy=False)

def update(self, modes: Union[int, Iterable[int]], data: np.ndarray) -> Self:
def update(self, modes: Union[int, Iterable[int]], data: np.ndarray) -> ktensor:
"""
Updates a :class:`pyttb.ktensor` in the specific dimensions with the
values in `data` (in vector or matrix form). The value of `modes` must
Expand Down
30 changes: 29 additions & 1 deletion pyttb/pyttb_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,16 @@

from enum import Enum
from inspect import signature
from typing import List, Literal, Optional, Tuple, Union, get_args, overload
from typing import (
Iterable,
List,
Literal,
Optional,
Tuple,
Union,
get_args,
overload,
)

import numpy as np

Expand Down Expand Up @@ -921,3 +930,22 @@ def gather_wrap_dims(

assert rdims is not None and cdims is not None
return rdims.astype(int), cdims.astype(int)


def np_to_python(
iterable: Iterable,
) -> Iterable:
"""Convert a structure containing numpy scalars to pure python types.

Mostly useful for prettier printing post numpy 2.0.

Parameters
----------
iterable:
Structure potentially containing numpy scalars.
"""
output_type = type(iterable)
return output_type( # type: ignore [call-arg]
element.item() if isinstance(element, np.generic) else element
for element in iterable
)
2 changes: 1 addition & 1 deletion pyttb/sptenmat.py
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ def __init__( # noqa: PLR0913
# Sum the corresponding values
# Squeeze to convert from column vector to row vector
newvals = accumarray(
loc, np.squeeze(vals, axis=1), size=newsubs.shape[0], func=sum
loc.flatten(), np.squeeze(vals, axis=1), size=newsubs.shape[0], func=sum
)

# Find the nonzero indices of the new values
Expand Down
21 changes: 14 additions & 7 deletions pyttb/sptensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
gather_wrap_dims,
get_index_variant,
get_mttkrp_factors,
np_to_python,
tt_dimscheck,
tt_ind2sub,
tt_intersect_rows,
Expand Down Expand Up @@ -325,7 +326,10 @@ def from_aggregator(
# Sum the corresponding values
# Squeeze to convert from column vector to row vector
newvals = accumarray(
loc, np.squeeze(vals), size=newsubs.shape[0], func=function_handle
loc.flatten(),
np.squeeze(vals),
size=newsubs.shape[0],
func=function_handle,
)

# Find the nonzero indices of the new values
Expand Down Expand Up @@ -445,7 +449,10 @@ def collapse(

# Check for the case where we accumulate over *all* dimensions
if remdims.size == 0:
return function_handle(self.vals.transpose()[0])
result = function_handle(self.vals.transpose()[0])
if isinstance(result, np.generic):
result = result.item()
return result

# Calculate the size of the result
newsize = np.array(self.shape)[remdims]
Expand Down Expand Up @@ -1319,7 +1326,7 @@ def nnz(self) -> int:
return 0
return self.subs.shape[0]

def norm(self) -> np.floating:
def norm(self) -> float:
"""
Compute the norm (i.e., Frobenius norm, or square root of the sum of
squares of entries) of the :class:`pyttb.sptensor`.
Expand All @@ -1339,7 +1346,7 @@ def norm(self) -> np.floating:
>>> S.norm() # doctest: +ELLIPSIS
5.47722557...
"""
return np.linalg.norm(self.vals)
return np.linalg.norm(self.vals).item()

def nvecs(self, n: int, r: int, flipsign: bool = True) -> np.ndarray:
"""
Expand Down Expand Up @@ -1945,7 +1952,7 @@ def ttv( # noqa: PLR0912

# Case 0: If all dimensions were used, then just return the sum
if remdims.size == 0:
return np.sum(newvals)
return np.sum(newvals).item()

# Otherwise, figure out new subscripts and accumulate the results.
newsiz = np.array(self.shape, dtype=int)[remdims]
Expand Down Expand Up @@ -3439,10 +3446,10 @@ def __repr__(self): # pragma: no cover
"""
nz = self.nnz
if nz == 0:
s = f"empty sparse tensor of shape {self.shape!r}"
s = f"empty sparse tensor of shape {np_to_python(self.shape)!r}"
return s

s = f"sparse tensor of shape {self.shape!r}"
s = f"sparse tensor of shape {np_to_python(self.shape)!r}"
s += f" with {nz} nonzeros\n"

# Stop insane printouts
Expand Down
14 changes: 9 additions & 5 deletions pyttb/sumtensor.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import numpy as np

import pyttb as ttb
from pyttb.pyttb_utils import np_to_python


class sumtensor:
Expand Down Expand Up @@ -115,7 +116,10 @@ def __repr__(self):
"""
if len(self.parts) == 0:
return "Empty sumtensor"
s = f"sumtensor of shape {self.shape} with {len(self.parts)} parts:"
s = (
f"sumtensor of shape {np_to_python(self.shape)} "
f"with {len(self.parts)} parts:"
)
for i, part in enumerate(self.parts):
s += f"\nPart {i}: \n"
s += indent(str(part), prefix="\t")
Expand Down Expand Up @@ -298,15 +302,15 @@ def innerprod(

Examples
--------
>>> T1 = ttb.tensor(np.array([[1, 0], [0, 4]]))
>>> T1 = ttb.tensor(np.array([[1., 0.], [0., 4.]]))
>>> T2 = T1.to_sptensor()
>>> S = ttb.sumtensor([T1, T2])
>>> T1.innerprod(T1)
17
17.0
>>> T1.innerprod(T2)
17
17.0
>>> S.innerprod(T1)
34
34.0
"""
result = self.parts[0].innerprod(other)
for part in self.parts[1:]:
Expand Down
9 changes: 6 additions & 3 deletions pyttb/tenmat.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def __init__( # noqa: PLR0912
):
"""
Construct a :class:`pyttb.tenmat` from explicit components.
If you already have a tensor see :method:`pyttb.tensor.to_tenmat`.
If you already have a tensor see :meth:`pyttb.tensor.to_tenmat`.

Parameters
----------
Expand Down Expand Up @@ -176,9 +176,12 @@ def copy(self) -> tenmat:
>>> TM2 = TM1
>>> TM3 = TM1.copy()
>>> TM1[0,0] = 3
>>> TM1[0,0] == TM2[0,0]

# Item to convert numpy boolean to python boolena for nicer printing

>>> (TM1[0,0] == TM2[0,0]).item()
True
>>> TM1[0,0] == TM3[0,0]
>>> (TM1[0,0] == TM3[0,0]).item()
False
"""
# Create tenmat
Expand Down
Loading
Loading