From d362173edb951d92606540c8c3ec619881a202e6 Mon Sep 17 00:00:00 2001 From: Gausshj Date: Fri, 29 Aug 2025 14:49:32 +0800 Subject: [PATCH 1/6] chore: update max line length --- .pre-commit-config.yaml | 13 +-- grassmann_tensor/__init__.py | 2 +- grassmann_tensor/tensor.py | 169 +++++++++++++++++++++++++++-------- grassmann_tensor/version.py | 1 + pyproject.toml | 10 +++ tests/arithmetic_test.py | 109 +++++++++++++--------- tests/attributes_test.py | 31 ++++--- tests/clone_test.py | 9 +- tests/conversion_test.py | 7 +- tests/creation_test.py | 53 ++++++----- tests/import_test.py | 1 + tests/matmul_test.py | 65 ++++++++------ tests/permute_test.py | 104 ++++++++++++++++----- tests/reshape_test.py | 16 +++- tests/reverse_test.py | 123 ++++++++++++++++++++----- tests/update_mask_test.py | 57 ++++++------ 16 files changed, 550 insertions(+), 220 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d21b56f..6369bbc 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -38,12 +38,6 @@ repos: - id: sort-simple-yaml - id: trailing-whitespace -- repo: https://github.com/google/yapf - rev: v0.40.2 - hooks: - - id: yapf - language: system - - repo: https://github.com/pylint-dev/pylint rev: v3.3.1 hooks: @@ -55,3 +49,10 @@ repos: hooks: - id: mypy language: system + +- repo: https://github.com/astral-sh/ruff-pre-commit + rev: v0.12.11 + hooks: + - id: ruff-check + args: [--fix] + - id: ruff-format diff --git a/grassmann_tensor/__init__.py b/grassmann_tensor/__init__.py index a6dde9b..e932442 100644 --- a/grassmann_tensor/__init__.py +++ b/grassmann_tensor/__init__.py @@ -4,5 +4,5 @@ __all__ = ["__version__", "GrassmannTensor"] -from .version import __version__ from .tensor import GrassmannTensor +from .version import __version__ diff --git a/grassmann_tensor/tensor.py b/grassmann_tensor/tensor.py index 697bb7e..01aee25 100644 --- a/grassmann_tensor/tensor.py +++ b/grassmann_tensor/tensor.py @@ -9,6 +9,7 @@ import dataclasses import functools import typing + import torch @@ -64,7 +65,13 @@ def mask(self) -> torch.Tensor: self._mask = self._tensor_mask() return self._mask - def to(self, whatever: torch.device | torch.dtype | str | None = None, *, device: torch.device | None = None, dtype: torch.dtype | None = None) -> GrassmannTensor: + def to( + self, + whatever: torch.device | torch.dtype | str | None = None, + *, + device: torch.device | None = None, + dtype: torch.dtype | None = None, + ) -> GrassmannTensor: """ Copy the tensor to a specified device or copy it to a specified data type. """ @@ -92,14 +99,18 @@ def to(self, whatever: torch.device | torch.dtype | str | None = None, *, device return dataclasses.replace( self, _tensor=self._tensor.to(device=device), - _parity=tuple(p.to(device) for p in self._parity) if self._parity is not None else None, + _parity=tuple(p.to(device) for p in self._parity) + if self._parity is not None + else None, _mask=self._mask.to(device) if self._mask is not None else None, ) case _: return dataclasses.replace( self, _tensor=self._tensor.to(device=device, dtype=dtype), - _parity=tuple(p.to(device=device) for p in self._parity) if self._parity is not None else None, + _parity=tuple(p.to(device=device) for p in self._parity) + if self._parity is not None + else None, _mask=self._mask.to(device=device) if self._mask is not None else None, ) @@ -114,8 +125,12 @@ def permute(self, before_by_after: tuple[int, ...]) -> GrassmannTensor: """ Permute the indices of the Grassmann tensor. """ - assert len(before_by_after) == len(set(before_by_after)), "Permutation indices must be unique." - assert set(before_by_after) == set(range(self.tensor.dim())), "Permutation indices must cover all dimensions." + assert len(before_by_after) == len(set(before_by_after)), ( + "Permutation indices must be unique." + ) + assert set(before_by_after) == set(range(self.tensor.dim())), ( + "Permutation indices must cover all dimensions." + ) arrow = tuple(self.arrow[i] for i in before_by_after) edges = tuple(self.edges[i] for i in before_by_after) @@ -126,10 +141,14 @@ def permute(self, before_by_after: tuple[int, ...]) -> GrassmannTensor: total_parity = functools.reduce( torch.logical_xor, ( - torch.logical_and(self._unsqueeze(parity[i], i, self.tensor.dim()), self._unsqueeze(parity[j], j, self.tensor.dim())) + torch.logical_and( + self._unsqueeze(parity[i], i, self.tensor.dim()), + self._unsqueeze(parity[j], j, self.tensor.dim()), + ) for j in range(self.tensor.dim()) for i in range(0, j) # all 0 <= i < j < dim - if before_by_after[i] > before_by_after[j]), + if before_by_after[i] > before_by_after[j] + ), torch.zeros([], dtype=torch.bool, device=self.tensor.device), ) tensor = torch.where(total_parity, -tensor, +tensor) @@ -151,14 +170,20 @@ def reverse(self, indices: tuple[int, ...]) -> GrassmannTensor: This package always applies it to the tensor with arrow as True. """ assert len(set(indices)) == len(indices), f"Indices must be unique. Got {indices}." - assert all(0 <= i < self.tensor.dim() for i in indices), f"Indices must be within tensor dimensions. Got {indices}." + assert all(0 <= i < self.tensor.dim() for i in indices), ( + f"Indices must be within tensor dimensions. Got {indices}." + ) arrow = tuple(self.arrow[i] ^ i in indices for i in range(self.tensor.dim())) tensor = self.tensor total_parity = functools.reduce( torch.logical_xor, - (self._unsqueeze(parity, index, self.tensor.dim()) for index, parity in enumerate(self.parity) if index in indices and self.arrow[index]), + ( + self._unsqueeze(parity, index, self.tensor.dim()) + for index, parity in enumerate(self.parity) + if index in indices and self.arrow[index] + ), torch.zeros([], dtype=torch.bool, device=self.tensor.device), ) tensor = torch.where(total_parity, -tensor, +tensor) @@ -169,10 +194,15 @@ def reverse(self, indices: tuple[int, ...]) -> GrassmannTensor: _tensor=tensor, ) - def _reorder_indices(self, edges: tuple[tuple[int, int], ...]) -> tuple[int, int, torch.Tensor, torch.Tensor]: + def _reorder_indices( + self, edges: tuple[tuple[int, int], ...] + ) -> tuple[int, int, torch.Tensor, torch.Tensor]: parity = functools.reduce( torch.logical_xor, - (self._unsqueeze(self._edge_mask(even, odd), index, len(edges)) for index, (even, odd) in enumerate(edges)), + ( + self._unsqueeze(self._edge_mask(even, odd), index, len(edges)) + for index, (even, odd) in enumerate(edges) + ), torch.zeros([], dtype=torch.bool, device=self.tensor.device), ) flatten_parity = parity.flatten() @@ -182,7 +212,10 @@ def _reorder_indices(self, edges: tuple[tuple[int, int], ...]) -> tuple[int, int total = functools.reduce( torch.add, - (self._unsqueeze(self._edge_mask(even, odd), index, len(edges)).to(dtype=torch.int16) for index, (even, odd) in enumerate(edges)), + ( + self._unsqueeze(self._edge_mask(even, odd), index, len(edges)).to(dtype=torch.int16) + for index, (even, odd) in enumerate(edges) + ), torch.zeros([], dtype=torch.int16, device=self.tensor.device), ) count = total * (total - 1) @@ -235,7 +268,11 @@ def reshape(self, new_shape: tuple[int | tuple[int, int], ...]) -> GrassmannTens cursor_plan += 1 else: cursor_new_shape = new_shape[cursor_plan] - total = cursor_new_shape if isinstance(cursor_new_shape, int) else cursor_new_shape[0] + cursor_new_shape[1] + total = ( + cursor_new_shape + if isinstance(cursor_new_shape, int) + else cursor_new_shape[0] + cursor_new_shape[1] + ) if total >= self.tensor.shape[cursor_self]: # Merging new_cursor_self = cursor_self @@ -245,14 +282,26 @@ def reshape(self, new_shape: tuple[int | tuple[int, int], ...]) -> GrassmannTens new_cursor_self += 1 if self_total == total: break - assert self_total < total, f"Dimension mismatch with edges {self.edges} and new shape {new_shape}." - assert new_cursor_self < self.tensor.dim(), f"New shape {new_shape} exceeds tensor dimensions {self.tensor.dim()}." - even, odd, reorder, sign = self._reorder_indices(self.edges[cursor_self:new_cursor_self]) + assert self_total < total, ( + f"Dimension mismatch with edges {self.edges} and new shape {new_shape}." + ) + assert new_cursor_self < self.tensor.dim(), ( + f"New shape {new_shape} exceeds tensor dimensions {self.tensor.dim()}." + ) + even, odd, reorder, sign = self._reorder_indices( + self.edges[cursor_self:new_cursor_self] + ) if isinstance(cursor_new_shape, tuple): - assert (even, odd) == cursor_new_shape, f"New even and odd number mismatch during merging {self.edges} to {new_shape}." + assert (even, odd) == cursor_new_shape, ( + f"New even and odd number mismatch during merging {self.edges} to {new_shape}." + ) arrow.append(self.arrow[cursor_self]) assert all( - self_arrow == arrow[-1] for self_arrow in self.arrow[cursor_self:new_cursor_self]), f"Cannot merge edges with different arrows {self.arrow[cursor_self:new_cursor_self]}." + self_arrow == arrow[-1] + for self_arrow in self.arrow[cursor_self:new_cursor_self] + ), ( + f"Cannot merge edges with different arrows {self.arrow[cursor_self:new_cursor_self]}." + ) edges.append((even, odd)) shape.append(total) if cursor_self + 1 != new_cursor_self: @@ -267,16 +316,28 @@ def reshape(self, new_shape: tuple[int | tuple[int, int], ...]) -> GrassmannTens plan_total = 1 while True: new_cursor_new_shape = new_shape[new_cursor_plan] - assert isinstance(new_cursor_new_shape, tuple), f"New shape must be a pair when splitting, got {new_cursor_new_shape}." + assert isinstance(new_cursor_new_shape, tuple), ( + f"New shape must be a pair when splitting, got {new_cursor_new_shape}." + ) plan_total *= new_cursor_new_shape[0] + new_cursor_new_shape[1] new_cursor_plan += 1 if plan_total == self.tensor.shape[cursor_self]: break - assert plan_total < self.tensor.shape[cursor_self], f"Dimension mismatch with edges {self.edges} and new shape {new_shape}." - assert new_cursor_plan < len(new_shape), f"New shape {new_shape} exceeds specified dimensions {len(new_shape)}." + assert plan_total < self.tensor.shape[cursor_self], ( + f"Dimension mismatch with edges {self.edges} and new shape {new_shape}." + ) + assert new_cursor_plan < len(new_shape), ( + f"New shape {new_shape} exceeds specified dimensions {len(new_shape)}." + ) # new_shape has been verified to be tuple[int, int] in the loop - even, odd, reorder, sign = self._reorder_indices(typing.cast(tuple[tuple[int, int], ...], new_shape[cursor_plan:new_cursor_plan])) - assert (even, odd) == self.edges[cursor_self], f"New even and odd number mismatch during splitting {self.edges[cursor_self]} to {new_shape[cursor_plan:new_cursor_plan]}." + even, odd, reorder, sign = self._reorder_indices( + typing.cast( + tuple[tuple[int, int], ...], new_shape[cursor_plan:new_cursor_plan] + ) + ) + assert (even, odd) == self.edges[cursor_self], ( + f"New even and odd number mismatch during splitting {self.edges[cursor_self]} to {new_shape[cursor_plan:new_cursor_plan]}." + ) for i in range(cursor_plan, new_cursor_plan): # new_shape has been verified to be tuple[int, int] in the loop new_cursor_new_shape = typing.cast(tuple[int, int], new_shape[i]) @@ -300,7 +361,11 @@ def reshape(self, new_shape: tuple[int | tuple[int, int], ...]) -> GrassmannTens splitting_parity = functools.reduce( torch.logical_xor, - (self._unsqueeze(sign, index, self.tensor.dim()) for index, sign in splitting_sign if self.arrow[index]), + ( + self._unsqueeze(sign, index, self.tensor.dim()) + for index, sign in splitting_sign + if self.arrow[index] + ), torch.zeros([], dtype=torch.bool, device=self.tensor.device), ) tensor = torch.where(splitting_parity, -tensor, +tensor) @@ -309,7 +374,11 @@ def reshape(self, new_shape: tuple[int | tuple[int, int], ...]) -> GrassmannTens merging_parity = functools.reduce( torch.logical_xor, - (self._unsqueeze(sign, index, tensor.dim()) for index, sign in merging_sign if arrow[index]), + ( + self._unsqueeze(sign, index, tensor.dim()) + for index, sign in merging_sign + if arrow[index] + ), torch.zeros([], dtype=torch.bool, device=self.tensor.device), ) tensor = torch.where(merging_parity, -tensor, +tensor) @@ -338,8 +407,12 @@ def matmul(self, other: GrassmannTensor) -> GrassmannTensor: tensor_b = tensor_b.reshape((-1, (1, 0))) vector_b = True - assert all(odd == 0 for (even, odd) in tensor_a.edges[:-2]), f"All edges except the last two must be pure even. Got {tensor_a.edges[:-2]}." - assert all(odd == 0 for (even, odd) in tensor_b.edges[:-2]), f"All edges except the last two must be pure even. Got {tensor_b.edges[:-2]}." + assert all(odd == 0 for (even, odd) in tensor_a.edges[:-2]), ( + f"All edges except the last two must be pure even. Got {tensor_a.edges[:-2]}." + ) + assert all(odd == 0 for (even, odd) in tensor_b.edges[:-2]), ( + f"All edges except the last two must be pure even. Got {tensor_b.edges[:-2]}." + ) if tensor_a.arrow[-1] is not True: tensor_a = tensor_a.reverse((tensor_a.tensor.dim() - 1,)) @@ -358,7 +431,9 @@ def matmul(self, other: GrassmannTensor) -> GrassmannTensor: candidate_a = tensor_a.edges[i - 2][0] if i >= -broadcast_b: candidate_b = tensor_b.edges[i - 2][0] - assert candidate_a == candidate_b or candidate_a == 1 or candidate_b == 1, f"Cannot broadcast edges {tensor_a.edges[i - 2]} and {tensor_b.edges[i - 2]}." + assert candidate_a == candidate_b or candidate_a == 1 or candidate_b == 1, ( + f"Cannot broadcast edges {tensor_a.edges[i - 2]} and {tensor_b.edges[i - 2]}." + ) edges.append((max(candidate_a, candidate_b), 0)) if not vector_a: arrow.append(tensor_a.arrow[-2]) @@ -379,21 +454,35 @@ def matmul(self, other: GrassmannTensor) -> GrassmannTensor: ) def __post_init__(self) -> None: - assert len(self._arrow) == self._tensor.dim(), f"Arrow length ({len(self._arrow)}) must match tensor dimensions ({self._tensor.dim()})." - assert len(self._edges) == self._tensor.dim(), f"Edges length ({len(self._edges)}) must match tensor dimensions ({self._tensor.dim()})." - for dim, (even, odd) in zip(self._tensor.shape, self._edges): - assert even >= 0 and odd >= 0 and dim == even + odd, f"Dimension {dim} must equal sum of even ({even}) and odd ({odd}) parts, and both must be non-negative." + assert len(self._arrow) == self._tensor.dim(), ( + f"Arrow length ({len(self._arrow)}) must match tensor dimensions ({self._tensor.dim()})." + ) + assert len(self._edges) == self._tensor.dim(), ( + f"Edges length ({len(self._edges)}) must match tensor dimensions ({self._tensor.dim()})." + ) + for dim, (even, odd) in zip(self._tensor.shape, self._edges, strict=False): + assert even >= 0 and odd >= 0 and dim == even + odd, ( + f"Dimension {dim} must equal sum of even ({even}) and odd ({odd}) parts, and both must be non-negative." + ) def _unsqueeze(self, tensor: torch.Tensor, index: int, dim: int) -> torch.Tensor: return tensor.view([-1 if i == index else 1 for i in range(dim)]) def _edge_mask(self, even: int, odd: int) -> torch.Tensor: - return torch.cat([torch.zeros(even, dtype=torch.bool, device=self.tensor.device), torch.ones(odd, dtype=torch.bool, device=self.tensor.device)]) + return torch.cat( + [ + torch.zeros(even, dtype=torch.bool, device=self.tensor.device), + torch.ones(odd, dtype=torch.bool, device=self.tensor.device), + ] + ) def _tensor_mask(self) -> torch.Tensor: return functools.reduce( torch.logical_xor, - (self._unsqueeze(parity, index, self._tensor.dim()) for index, parity in enumerate(self.parity)), + ( + self._unsqueeze(parity, index, self._tensor.dim()) + for index, parity in enumerate(self.parity) + ), torch.zeros_like(self._tensor, dtype=torch.bool), ) @@ -401,8 +490,12 @@ def _validate_edge_compatibility(self, other: GrassmannTensor) -> None: """ Validate that the edges of two ParityTensor instances are compatible for arithmetic operations. """ - assert self._arrow == other.arrow, f"Arrows must match for arithmetic operations. Got {self._arrow} and {other.arrow}." - assert self._edges == other.edges, f"Edges must match for arithmetic operations. Got {self._edges} and {other.edges}." + assert self._arrow == other.arrow, ( + f"Arrows must match for arithmetic operations. Got {self._arrow} and {other.arrow}." + ) + assert self._edges == other.edges, ( + f"Edges must match for arithmetic operations. Got {self._edges} and {other.edges}." + ) def __pos__(self) -> GrassmannTensor: return dataclasses.replace( @@ -595,7 +688,9 @@ def clone(self) -> GrassmannTensor: return dataclasses.replace( self, _tensor=self._tensor.clone(), - _parity=tuple(parity.clone() for parity in self._parity) if self._parity is not None else None, + _parity=tuple(parity.clone() for parity in self._parity) + if self._parity is not None + else None, _mask=self._mask.clone() if self._mask is not None else None, ) diff --git a/grassmann_tensor/version.py b/grassmann_tensor/version.py index 2d474b2..0c775ec 100644 --- a/grassmann_tensor/version.py +++ b/grassmann_tensor/version.py @@ -11,6 +11,7 @@ except ModuleNotFoundError: try: import importlib.metadata + __version__ = importlib.metadata.version("parity") except importlib.metadata.PackageNotFoundError: __version__ = "0.0.0" diff --git a/pyproject.toml b/pyproject.toml index 926a9a8..b81242b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,3 +44,13 @@ disallow_untyped_calls = true disallow_untyped_defs = true disallow_incomplete_defs = true check_untyped_defs = true + +[tool.ruff] +line-length = 100 +target-version = "py312" + +[tool.ruff.lint] +select = ["E", "F", "I", "B", "UP"] +ignore = ["E501"] + +[tool.ruff.format] diff --git a/tests/arithmetic_test.py b/tests/arithmetic_test.py index d63c00c..8aff6ba 100644 --- a/tests/arithmetic_test.py +++ b/tests/arithmetic_test.py @@ -1,56 +1,76 @@ from __future__ import annotations + import typing + import pytest import torch + from grassmann_tensor import GrassmannTensor -@pytest.fixture(params=[ - ( - GrassmannTensor((False, False), ((2, 2), (1, 3)), torch.randn([4, 4])), - GrassmannTensor((False, False), ((2, 2), (1, 3)), torch.randn([4, 4])), - ), - ( - GrassmannTensor((True, False, True), ((1, 1), (2, 2), (3, 1)), torch.randn([2, 4, 4])), - GrassmannTensor((True, False, True), ((1, 1), (2, 2), (3, 1)), torch.randn([2, 4, 4])), - ), - ( - GrassmannTensor((True, True, False, False), ((1, 2), (2, 2), (1, 1), (3, 1)), torch.randn([3, 4, 2, 4])), - GrassmannTensor((True, True, False, False), ((1, 2), (2, 2), (1, 1), (3, 1)), torch.randn([3, 4, 2, 4])), - ), -]) +@pytest.fixture( + params=[ + ( + GrassmannTensor((False, False), ((2, 2), (1, 3)), torch.randn([4, 4])), + GrassmannTensor((False, False), ((2, 2), (1, 3)), torch.randn([4, 4])), + ), + ( + GrassmannTensor((True, False, True), ((1, 1), (2, 2), (3, 1)), torch.randn([2, 4, 4])), + GrassmannTensor((True, False, True), ((1, 1), (2, 2), (3, 1)), torch.randn([2, 4, 4])), + ), + ( + GrassmannTensor( + (True, True, False, False), + ((1, 2), (2, 2), (1, 1), (3, 1)), + torch.randn([3, 4, 2, 4]), + ), + GrassmannTensor( + (True, True, False, False), + ((1, 2), (2, 2), (1, 1), (3, 1)), + torch.randn([3, 4, 2, 4]), + ), + ), + ] +) def tensors(request: pytest.FixtureRequest) -> tuple[GrassmannTensor, GrassmannTensor]: return request.param -@pytest.fixture(params=[ - ( - GrassmannTensor((False, False), ((2, 2), (1, 3)), torch.randn([4, 4])), - GrassmannTensor((False,), ((2, 2),), torch.randn([4])), - ), - ( - GrassmannTensor((True, False, True), ((1, 1), (2, 2), (3, 1)), torch.randn([2, 4, 4])), - GrassmannTensor((True, False, True), ((1, 2), (2, 2), (3, 1)), torch.randn([3, 4, 4])), - ), - ( - GrassmannTensor((True, True, False), ((1, 2), (2, 2), (3, 1)), torch.randn([3, 4, 4])), - GrassmannTensor((True, True, False, False), ((3, 2), (2, 2), (1, 1), (3, 1)), torch.randn([5, 4, 2, 4])), - ), -]) +@pytest.fixture( + params=[ + ( + GrassmannTensor((False, False), ((2, 2), (1, 3)), torch.randn([4, 4])), + GrassmannTensor((False,), ((2, 2),), torch.randn([4])), + ), + ( + GrassmannTensor((True, False, True), ((1, 1), (2, 2), (3, 1)), torch.randn([2, 4, 4])), + GrassmannTensor((True, False, True), ((1, 2), (2, 2), (3, 1)), torch.randn([3, 4, 4])), + ), + ( + GrassmannTensor((True, True, False), ((1, 2), (2, 2), (3, 1)), torch.randn([3, 4, 4])), + GrassmannTensor( + (True, True, False, False), + ((3, 2), (2, 2), (1, 1), (3, 1)), + torch.randn([5, 4, 2, 4]), + ), + ), + ] +) def mismatch_tensors(request: pytest.FixtureRequest) -> tuple[GrassmannTensor, GrassmannTensor]: return request.param -@pytest.fixture(params=[ - torch.randn([]), - torch.randn([]).item(), -]) +@pytest.fixture( + params=[ + torch.randn([]), + torch.randn([]).item(), + ] +) def scalar(request: pytest.FixtureRequest) -> torch.Tensor | float: return request.param class FakeTensor: - def __init__(self) -> None: pass @@ -106,15 +126,20 @@ def __rtruediv__(self, other: typing.Any) -> FakeTensor: @pytest.mark.parametrize( "unsupported_type", [ - "string", #string - None, #NoneType - {"key", "value"}, #dict - [1, 2, 3], #list - {1, 2}, #set - object(), #arbitrary object - FakeTensor(), #an ill defined tensor-like object - ]) -def test_arithmetic(unsupported_type: typing.Any, tensors: tuple[GrassmannTensor, GrassmannTensor], scalar: torch.Tensor | float) -> None: + "string", # string + None, # NoneType + {"key", "value"}, # dict + [1, 2, 3], # list + {1, 2}, # set + object(), # arbitrary object + FakeTensor(), # an ill defined tensor-like object + ], +) +def test_arithmetic( + unsupported_type: typing.Any, + tensors: tuple[GrassmannTensor, GrassmannTensor], + scalar: torch.Tensor | float, +) -> None: tensor_a, tensor_b = tensors # Test __pos__ method. diff --git a/tests/attributes_test.py b/tests/attributes_test.py index 7a8e57c..b6878c6 100644 --- a/tests/attributes_test.py +++ b/tests/attributes_test.py @@ -1,21 +1,24 @@ import pytest import torch + from grassmann_tensor import GrassmannTensor Initialization = tuple[tuple[bool, ...], tuple[tuple[int, int], ...], torch.Tensor] -@pytest.fixture(params=[ - ((False, False), ((2, 2), (2, 2)), torch.randn([4, 4])), - ((False, True), ((2, 2), (1, 3)), torch.randn([4, 4])), - ((False, True), ((2, 0), (1, 3)), torch.randn([2, 4])), - ((True, False), ((0, 2), (1, 3)), torch.randn([2, 4])), - ((True, False), ((0, 0), (1, 3)), torch.randn([0, 4])), - ((True,), ((2, 0),), torch.randn([2])), - ((False,), ((0, 2),), torch.randn([2])), - ((), (), torch.randn([])), - ((False, False, True), ((2, 2), (1, 3), (4, 0)), torch.randn([4, 4, 4])), -]) +@pytest.fixture( + params=[ + ((False, False), ((2, 2), (2, 2)), torch.randn([4, 4])), + ((False, True), ((2, 2), (1, 3)), torch.randn([4, 4])), + ((False, True), ((2, 0), (1, 3)), torch.randn([2, 4])), + ((True, False), ((0, 2), (1, 3)), torch.randn([2, 4])), + ((True, False), ((0, 0), (1, 3)), torch.randn([0, 4])), + ((True,), ((2, 0),), torch.randn([2])), + ((False,), ((0, 2),), torch.randn([2])), + ((), (), torch.randn([])), + ((False, False, True), ((2, 2), (1, 3), (4, 0)), torch.randn([4, 4, 4])), + ] +) def x(request: pytest.FixtureRequest) -> Initialization: return request.param @@ -38,7 +41,7 @@ def test_tensor(x: Initialization) -> None: def test_parity(x: Initialization) -> None: tensor = GrassmannTensor(*x) assert len(tensor.parity) == tensor.tensor.dim() - for [even, odd], parity in zip(x[1], tensor.parity): + for [even, odd], parity in zip(x[1], tensor.parity, strict=False): total = even + odd assert parity.shape == (total,) assert parity.dtype == torch.bool @@ -50,7 +53,9 @@ def test_mask(x: Initialization) -> None: tensor = GrassmannTensor(*x) assert tensor.mask.shape == tensor.tensor.shape assert tensor.mask.dtype == torch.bool - for indices in zip(*torch.unravel_index(torch.arange(tensor.tensor.numel()), tensor.tensor.shape)): + for indices in zip( + *torch.unravel_index(torch.arange(tensor.tensor.numel()), tensor.tensor.shape), strict=False + ): mask = tensor.mask[indices] expect = False for rank, parity in enumerate(tensor.parity): diff --git a/tests/clone_test.py b/tests/clone_test.py index 6a67bcc..ad11239 100644 --- a/tests/clone_test.py +++ b/tests/clone_test.py @@ -1,7 +1,9 @@ -import typing import copy +import typing + import pytest import torch + from grassmann_tensor import GrassmannTensor @@ -37,7 +39,10 @@ def test_clone( if parity: assert cloned_tensor._parity is not None assert original_tensor._parity is not None - assert all(torch.equal(c, o) for c, o in zip(cloned_tensor._parity, original_tensor._parity)) + assert all( + torch.equal(c, o) + for c, o in zip(cloned_tensor._parity, original_tensor._parity, strict=False) + ) else: assert cloned_tensor._parity is original_tensor._parity if mask: diff --git a/tests/conversion_test.py b/tests/conversion_test.py index 6c49f82..d8e59f0 100644 --- a/tests/conversion_test.py +++ b/tests/conversion_test.py @@ -1,6 +1,8 @@ import typing + import pytest import torch + from grassmann_tensor import GrassmannTensor @@ -47,7 +49,10 @@ def test_conversion( assert y.arrow == x.arrow assert y.edges == x.edges assert y.tensor.dtype == torch.complex128 if dtype_arg != "none" else torch.float32 - assert y.tensor.device.type == (torch.device(device_str) if device_arg != "none" else torch.device("cpu:0")).type + assert ( + y.tensor.device.type + == (torch.device(device_str) if device_arg != "none" else torch.device("cpu:0")).type + ) assert torch.allclose(y.tensor, x.tensor.to(dtype=y.tensor.dtype, device=y.tensor.device)) diff --git a/tests/creation_test.py b/tests/creation_test.py index fd8adbc..acf9075 100644 --- a/tests/creation_test.py +++ b/tests/creation_test.py @@ -1,44 +1,57 @@ import pytest import torch + from grassmann_tensor import GrassmannTensor Initialization = tuple[tuple[bool, ...], tuple[tuple[int, int], ...], torch.Tensor] -@pytest.mark.parametrize("x", [ - ((False, False), ((2, 2), (1, 3)), torch.randn([4, 4])), - ((True, False), ((2, 2), (3, 1)), torch.randn([4, 4])), - ((False, True, False), ((1, 1), (2, 2), (1, 1)), torch.randn([2, 4, 2])), -]) +@pytest.mark.parametrize( + "x", + [ + ((False, False), ((2, 2), (1, 3)), torch.randn([4, 4])), + ((True, False), ((2, 2), (3, 1)), torch.randn([4, 4])), + ((False, True, False), ((1, 1), (2, 2), (1, 1)), torch.randn([2, 4, 2])), + ], +) def test_creation_success(x: Initialization) -> None: GrassmannTensor(*x) -@pytest.mark.parametrize("x", [ - ((False,), ((2, 2), (1, 3)), torch.randn([4, 4])), - ((True, False, True), ((2, 2), (3, 1)), torch.randn([4, 4])), - ((False, True), ((1, 1), (2, 2), (1, 1)), torch.randn([2, 4, 2])), -]) +@pytest.mark.parametrize( + "x", + [ + ((False,), ((2, 2), (1, 3)), torch.randn([4, 4])), + ((True, False, True), ((2, 2), (3, 1)), torch.randn([4, 4])), + ((False, True), ((1, 1), (2, 2), (1, 1)), torch.randn([2, 4, 2])), + ], +) def test_creation_invalid_arrow(x: Initialization) -> None: with pytest.raises(AssertionError): GrassmannTensor(*x) -@pytest.mark.parametrize("x", [ - ((False, False), ((2, 2),), torch.randn([4, 4])), - ((True, False), ((2, 2), (1, 1), (3, 1)), torch.randn([4, 4])), - ((False, True, False), ((1, 1), (1, 1)), torch.randn([2, 4, 2])), -]) +@pytest.mark.parametrize( + "x", + [ + ((False, False), ((2, 2),), torch.randn([4, 4])), + ((True, False), ((2, 2), (1, 1), (3, 1)), torch.randn([4, 4])), + ((False, True, False), ((1, 1), (1, 1)), torch.randn([2, 4, 2])), + ], +) def test_creation_invalid_edges(x: Initialization) -> None: with pytest.raises(AssertionError): GrassmannTensor(*x) -@pytest.mark.parametrize("x", [ - ((False, False), ((2, 2), (1, 3)), torch.randn([4, 2])), - ((True, False), ((2, 2), (3, 1)), torch.randn([2, 4])), - ((False, True, False), ((1, 1), (2, 2), (1, 1)), torch.randn([4, 4, 2])), -]) +@pytest.mark.parametrize( + "x", + [ + ((False, False), ((2, 2), (1, 3)), torch.randn([4, 2])), + ((True, False), ((2, 2), (3, 1)), torch.randn([2, 4])), + ((False, True, False), ((1, 1), (2, 2), (1, 1)), torch.randn([4, 4, 2])), + ], +) def test_creation_invalid_shape(x: Initialization) -> None: with pytest.raises(AssertionError): GrassmannTensor(*x) diff --git a/tests/import_test.py b/tests/import_test.py index 5a91002..20b0985 100644 --- a/tests/import_test.py +++ b/tests/import_test.py @@ -1,3 +1,4 @@ def test_import() -> None: from grassmann_tensor import GrassmannTensor + assert isinstance(GrassmannTensor, type) diff --git a/tests/matmul_test.py b/tests/matmul_test.py index d7eb048..06808ac 100644 --- a/tests/matmul_test.py +++ b/tests/matmul_test.py @@ -1,27 +1,35 @@ import pytest import torch + from grassmann_tensor import GrassmannTensor MatmulMatrixCase = tuple[bool, bool, tuple[int, int], tuple[int, int], tuple[int, int]] -@pytest.mark.parametrize("x", [ - (False, False, (1, 1), (1, 1), (1, 1)), - (False, True, (1, 1), (1, 1), (1, 1)), - (True, False, (1, 1), (1, 1), (1, 1)), - (True, True, (1, 1), (1, 1), (1, 1)), - (False, False, (2, 2), (2, 2), (2, 2)), - (False, True, (2, 2), (2, 2), (2, 2)), - (True, False, (2, 2), (2, 2), (2, 2)), - (True, True, (2, 2), (2, 2), (2, 2)), -]) +@pytest.mark.parametrize( + "x", + [ + (False, False, (1, 1), (1, 1), (1, 1)), + (False, True, (1, 1), (1, 1), (1, 1)), + (True, False, (1, 1), (1, 1), (1, 1)), + (True, True, (1, 1), (1, 1), (1, 1)), + (False, False, (2, 2), (2, 2), (2, 2)), + (False, True, (2, 2), (2, 2), (2, 2)), + (True, False, (2, 2), (2, 2), (2, 2)), + (True, True, (2, 2), (2, 2), (2, 2)), + ], +) def test_matmul_matrix_tf(x: MatmulMatrixCase) -> None: arrow_a, arrow_b, edge_a, edge_common, edge_b = x dim_a = sum(edge_a) dim_common = sum(edge_common) dim_b = sum(edge_b) - a = GrassmannTensor((arrow_a, True), (edge_a, edge_common), torch.randn([dim_a, dim_common])).update_mask() - b = GrassmannTensor((False, arrow_b), (edge_common, edge_b), torch.randn([dim_common, dim_b])).update_mask() + a = GrassmannTensor( + (arrow_a, True), (edge_a, edge_common), torch.randn([dim_a, dim_common]) + ).update_mask() + b = GrassmannTensor( + (False, arrow_b), (edge_common, edge_b), torch.randn([dim_common, dim_b]) + ).update_mask() c = a.matmul(b) expected = a.tensor.matmul(b.tensor) assert c.arrow == (arrow_a, arrow_b) @@ -29,26 +37,33 @@ def test_matmul_matrix_tf(x: MatmulMatrixCase) -> None: assert torch.allclose(c.tensor, expected) -@pytest.mark.parametrize("x", [ - (False, False, (1, 1), (1, 1), (1, 1)), - (False, True, (1, 1), (1, 1), (1, 1)), - (True, False, (1, 1), (1, 1), (1, 1)), - (True, True, (1, 1), (1, 1), (1, 1)), - (False, False, (2, 2), (2, 2), (2, 2)), - (False, True, (2, 2), (2, 2), (2, 2)), - (True, False, (2, 2), (2, 2), (2, 2)), - (True, True, (2, 2), (2, 2), (2, 2)), -]) +@pytest.mark.parametrize( + "x", + [ + (False, False, (1, 1), (1, 1), (1, 1)), + (False, True, (1, 1), (1, 1), (1, 1)), + (True, False, (1, 1), (1, 1), (1, 1)), + (True, True, (1, 1), (1, 1), (1, 1)), + (False, False, (2, 2), (2, 2), (2, 2)), + (False, True, (2, 2), (2, 2), (2, 2)), + (True, False, (2, 2), (2, 2), (2, 2)), + (True, True, (2, 2), (2, 2), (2, 2)), + ], +) def test_matmul_matrix_ft(x: MatmulMatrixCase) -> None: arrow_a, arrow_b, edge_a, edge_common, edge_b = x dim_a = sum(edge_a) dim_common = sum(edge_common) dim_b = sum(edge_b) - a = GrassmannTensor((arrow_a, False), (edge_a, edge_common), torch.randn([dim_a, dim_common])).update_mask() - b = GrassmannTensor((True, arrow_b), (edge_common, edge_b), torch.randn([dim_common, dim_b])).update_mask() + a = GrassmannTensor( + (arrow_a, False), (edge_a, edge_common), torch.randn([dim_a, dim_common]) + ).update_mask() + b = GrassmannTensor( + (True, arrow_b), (edge_common, edge_b), torch.randn([dim_common, dim_b]) + ).update_mask() c = a.matmul(b) expected = a.tensor.matmul(b.tensor) - expected[edge_a[0]:, edge_b[0]:] *= -1 + expected[edge_a[0] :, edge_b[0] :] *= -1 assert c.arrow == (arrow_a, arrow_b) assert c.edges == (edge_a, edge_b) assert torch.allclose(c.tensor, expected) diff --git a/tests/permute_test.py b/tests/permute_test.py index 99bb2fe..b2d16bd 100644 --- a/tests/permute_test.py +++ b/tests/permute_test.py @@ -1,22 +1,70 @@ import pytest import torch + from grassmann_tensor import GrassmannTensor -PermuteCase = tuple[tuple[bool, ...], tuple[tuple[int, int], ...], torch.Tensor, tuple[int, ...], torch.Tensor] +PermuteCase = tuple[ + tuple[bool, ...], tuple[tuple[int, int], ...], torch.Tensor, tuple[int, ...], torch.Tensor +] -@pytest.mark.parametrize("x", [ - ((), (), torch.tensor(6), (), torch.tensor(6)), - ((False,), ((1, 1),), torch.tensor([1, 2]), (0,), torch.tensor([1, 2])), - ((False, True), ((1, 1), (0, 0)), torch.zeros([2, 0]), (1, 0), torch.zeros([0, 2])), - ((False, True), ((1, 1), (0, 1)), torch.tensor([[0], [4]]), (1, 0), torch.tensor([[0, -4]])), - ((False, False), ((1, 1), (1, 1)), torch.tensor([[1, 0], [0, 4]]), (0, 1), torch.tensor([[1, 0], [0, 4]])), - ((True, False), ((1, 1), (1, 1)), torch.tensor([[1, 0], [0, 4]]), (1, 0), torch.tensor([[1, 0], [0, -4]])), - ((False, True, True), ((1, 1), (1, 1), (1, 1)), torch.tensor([[[1, 0], [0, 2]], [[0, 3], [4, 0]]]), (0, 2, 1), torch.tensor([[[1, 0], [0, -2]], [[0, 4], [3, 0]]])), - ((True, True, True), ((1, 1), (1, 1), (1, 1)), torch.tensor([[[1, 0], [0, 2]], [[0, 3], [4, 0]]]), (1, 0, 2), torch.tensor([[[1, 0], [0, 3]], [[0, 2], [-4, 0]]])), - ((True, False, False), ((1, 1), (1, 1), (1, 1)), torch.tensor([[[1, 0], [0, 2]], [[0, 3], [4, 0]]]), (2, 1, 0), torch.tensor([[[1, 0], [0, -4]], [[0, -3], [-2, 0]]])), - ((False, False, False), ((1, 1), (1, 1), (1, 1)), torch.tensor([[[1, 0], [0, 2]], [[0, 3], [4, 0]]]), (2, 0, 1), torch.tensor([[[1, 0], [0, 4]], [[0, -2], [-3, 0]]])), -]) +@pytest.mark.parametrize( + "x", + [ + ((), (), torch.tensor(6), (), torch.tensor(6)), + ((False,), ((1, 1),), torch.tensor([1, 2]), (0,), torch.tensor([1, 2])), + ((False, True), ((1, 1), (0, 0)), torch.zeros([2, 0]), (1, 0), torch.zeros([0, 2])), + ( + (False, True), + ((1, 1), (0, 1)), + torch.tensor([[0], [4]]), + (1, 0), + torch.tensor([[0, -4]]), + ), + ( + (False, False), + ((1, 1), (1, 1)), + torch.tensor([[1, 0], [0, 4]]), + (0, 1), + torch.tensor([[1, 0], [0, 4]]), + ), + ( + (True, False), + ((1, 1), (1, 1)), + torch.tensor([[1, 0], [0, 4]]), + (1, 0), + torch.tensor([[1, 0], [0, -4]]), + ), + ( + (False, True, True), + ((1, 1), (1, 1), (1, 1)), + torch.tensor([[[1, 0], [0, 2]], [[0, 3], [4, 0]]]), + (0, 2, 1), + torch.tensor([[[1, 0], [0, -2]], [[0, 4], [3, 0]]]), + ), + ( + (True, True, True), + ((1, 1), (1, 1), (1, 1)), + torch.tensor([[[1, 0], [0, 2]], [[0, 3], [4, 0]]]), + (1, 0, 2), + torch.tensor([[[1, 0], [0, 3]], [[0, 2], [-4, 0]]]), + ), + ( + (True, False, False), + ((1, 1), (1, 1), (1, 1)), + torch.tensor([[[1, 0], [0, 2]], [[0, 3], [4, 0]]]), + (2, 1, 0), + torch.tensor([[[1, 0], [0, -4]], [[0, -3], [-2, 0]]]), + ), + ( + (False, False, False), + ((1, 1), (1, 1), (1, 1)), + torch.tensor([[[1, 0], [0, 2]], [[0, 3], [4, 0]]]), + (2, 0, 1), + torch.tensor([[[1, 0], [0, 4]], [[0, -2], [-3, 0]]]), + ), + ], +) def test_permute(x: PermuteCase) -> None: arrow, edges, tensor, before_by_after, expected = x grassmann_tensor = GrassmannTensor(arrow, edges, tensor) @@ -24,14 +72,19 @@ def test_permute(x: PermuteCase) -> None: assert torch.allclose(result.tensor, expected) -PermuteFailCase = tuple[tuple[bool, ...], tuple[tuple[int, int], ...], torch.Tensor, tuple[int, ...]] +PermuteFailCase = tuple[ + tuple[bool, ...], tuple[tuple[int, int], ...], torch.Tensor, tuple[int, ...] +] -@pytest.mark.parametrize("x", [ - ((False, False), ((1, 1), (1, 1)), torch.tensor([[1, 0], [0, 4]]), (0, 0)), - ((False, False), ((1, 1), (1, 1)), torch.tensor([[1, 0], [0, 4]]), (2, 0)), - ((False, False), ((1, 1), (1, 1)), torch.tensor([[1, 0], [0, 4]]), (0, 0, 1)), -]) +@pytest.mark.parametrize( + "x", + [ + ((False, False), ((1, 1), (1, 1)), torch.tensor([[1, 0], [0, 4]]), (0, 0)), + ((False, False), ((1, 1), (1, 1)), torch.tensor([[1, 0], [0, 4]]), (2, 0)), + ((False, False), ((1, 1), (1, 1)), torch.tensor([[1, 0], [0, 4]]), (0, 0, 1)), + ], +) def test_permute_fail(x: PermuteFailCase) -> None: arrow, edges, tensor, before_by_after = x grassmann_tensor = GrassmannTensor(arrow, edges, tensor) @@ -39,9 +92,14 @@ def test_permute_fail(x: PermuteFailCase) -> None: grassmann_tensor.permute(before_by_after) +# ruff: noqa: E741 def test_permute_high_order() -> None: edge = (2, 2) - a = GrassmannTensor((False, False, False, False, False, False), (edge, edge, edge, edge, edge, edge), torch.randn(4, 4, 4, 4, 4, 4)).update_mask() + a = GrassmannTensor( + (False, False, False, False, False, False), + (edge, edge, edge, edge, edge, edge), + torch.randn(4, 4, 4, 4, 4, 4), + ).update_mask() # a[i, j, k, l, m, n] -> b[l, j, i, n, k, m] b = a.permute((3, 1, 0, 5, 2, 4)) for i in range(4): @@ -57,7 +115,11 @@ def test_permute_high_order() -> None: # (l) (i j k) m n # l (j) (i) k m n # l j i (n) (k m) - sign = (p[3] & (p[0] ^ p[1] ^ p[2])) ^ (p[1] & p[0]) ^ (p[5] & (p[2] ^ p[4])) + sign = ( + (p[3] & (p[0] ^ p[1] ^ p[2])) + ^ (p[1] & p[0]) + ^ (p[5] & (p[2] ^ p[4])) + ) if sign: assert b.tensor[l, j, i, n, k, m] == -a.tensor[i, j, k, l, m, n] else: diff --git a/tests/reshape_test.py b/tests/reshape_test.py index 6d32e90..05f845e 100644 --- a/tests/reshape_test.py +++ b/tests/reshape_test.py @@ -1,9 +1,21 @@ import pytest import torch + from grassmann_tensor import GrassmannTensor -@pytest.mark.parametrize("arrow", [(i, j, k, l, m) for i in [False, True] for j in [False, True] for k in [False, True] for l in [False, True] for m in [False, True]]) +# ruff: noqa: E741 +@pytest.mark.parametrize( + "arrow", + [ + (i, j, k, l, m) + for i in [False, True] + for j in [False, True] + for k in [False, True] + for l in [False, True] + for m in [False, True] + ], +) @pytest.mark.parametrize("plan_range", [(i, j) for i in range(5) for j in range(5) if j > i]) def test_reshape_consistency(arrow: tuple[bool, ...], plan_range: tuple[int, int]) -> None: l, h = plan_range @@ -11,7 +23,7 @@ def test_reshape_consistency(arrow: tuple[bool, ...], plan_range: tuple[int, int pytest.skip("Invalid reshape plan for the given arrow configuration.") edge = (2, 2) a = GrassmannTensor(arrow, (edge, edge, edge, edge, edge), torch.randn([4, 4, 4, 4, 4])) - plan = tuple([-1] * l + [4**(h - l)] + [-1] * (5 - h)) + plan = tuple([-1] * l + [4 ** (h - l)] + [-1] * (5 - h)) b = a.reshape(plan) c = b.reshape(a.edges) assert torch.allclose(a.tensor, c.tensor) diff --git a/tests/reverse_test.py b/tests/reverse_test.py index 7e1b3c6..a4a2d01 100644 --- a/tests/reverse_test.py +++ b/tests/reverse_test.py @@ -1,25 +1,97 @@ import pytest import torch + from grassmann_tensor.tensor import GrassmannTensor -ReverseCase = tuple[tuple[bool, ...], tuple[tuple[int, int], ...], torch.Tensor, tuple[int, ...], torch.Tensor] - - -@pytest.mark.parametrize("x", [ - ((), (), torch.tensor(6), (), torch.tensor(6)), - ((False, False), ((1, 1), (0, 0)), torch.zeros([2, 0]), (0,), torch.zeros([2, 0])), - ((False, False), ((1, 1), (0, 1)), torch.tensor([[0], [4]]), (0,), torch.tensor([[0], [4]])), - ((False, False), ((1, 1), (1, 1)), torch.tensor([[1, 0], [0, 4]]), (), torch.tensor([[1, 0], [0, 4]])), - ((False, False), ((1, 1), (1, 1)), torch.tensor([[1, 0], [0, 4]]), (0,), torch.tensor([[1, 0], [0, 4]])), - ((True, False), ((1, 1), (1, 1)), torch.tensor([[1, 0], [0, 4]]), (0,), torch.tensor([[1, 0], [0, -4]])), - ((False, False), ((1, 1), (1, 1)), torch.tensor([[1, 0], [0, 4]]), (1,), torch.tensor([[1, 0], [0, 4]])), - ((False, True), ((1, 1), (1, 1)), torch.tensor([[1, 0], [0, 4]]), (1,), torch.tensor([[1, 0], [0, -4]])), - ((False, False, False), ((1, 1), (1, 1), (1, 1)), torch.tensor([[[1, 0], [0, 2]], [[0, 3], [4, 0]]]), (0,), torch.tensor([[[1, 0], [0, 2]], [[0, 3], [4, 0]]])), - ((True, False, False), ((1, 1), (1, 1), (1, 1)), torch.tensor([[[1, 0], [0, 2]], [[0, 3], [4, 0]]]), (0,), torch.tensor([[[1, 0], [0, 2]], [[0, -3], [-4, 0]]])), - ((False, True, True), ((1, 1), (1, 1), (1, 1)), torch.tensor([[[1, 0], [0, 2]], [[0, 3], [4, 0]]]), (2,), torch.tensor([[[1, 0], [0, -2]], [[0, -3], [4, 0]]])), - ((True, False, True), ((1, 1), (1, 1), (1, 1)), torch.tensor([[[1, 0], [0, 2]], [[0, 3], [4, 0]]]), (0, 1), torch.tensor([[[1, 0], [0, 2]], [[0, -3], [-4, 0]]])), - ((True, True, True), ((1, 1), (1, 1), (1, 1)), torch.tensor([[[1, 0], [0, 2]], [[0, 3], [4, 0]]]), (0, 1), torch.tensor([[[1, 0], [0, -2]], [[0, -3], [4, 0]]])), -]) +ReverseCase = tuple[ + tuple[bool, ...], tuple[tuple[int, int], ...], torch.Tensor, tuple[int, ...], torch.Tensor +] + + +@pytest.mark.parametrize( + "x", + [ + ((), (), torch.tensor(6), (), torch.tensor(6)), + ((False, False), ((1, 1), (0, 0)), torch.zeros([2, 0]), (0,), torch.zeros([2, 0])), + ( + (False, False), + ((1, 1), (0, 1)), + torch.tensor([[0], [4]]), + (0,), + torch.tensor([[0], [4]]), + ), + ( + (False, False), + ((1, 1), (1, 1)), + torch.tensor([[1, 0], [0, 4]]), + (), + torch.tensor([[1, 0], [0, 4]]), + ), + ( + (False, False), + ((1, 1), (1, 1)), + torch.tensor([[1, 0], [0, 4]]), + (0,), + torch.tensor([[1, 0], [0, 4]]), + ), + ( + (True, False), + ((1, 1), (1, 1)), + torch.tensor([[1, 0], [0, 4]]), + (0,), + torch.tensor([[1, 0], [0, -4]]), + ), + ( + (False, False), + ((1, 1), (1, 1)), + torch.tensor([[1, 0], [0, 4]]), + (1,), + torch.tensor([[1, 0], [0, 4]]), + ), + ( + (False, True), + ((1, 1), (1, 1)), + torch.tensor([[1, 0], [0, 4]]), + (1,), + torch.tensor([[1, 0], [0, -4]]), + ), + ( + (False, False, False), + ((1, 1), (1, 1), (1, 1)), + torch.tensor([[[1, 0], [0, 2]], [[0, 3], [4, 0]]]), + (0,), + torch.tensor([[[1, 0], [0, 2]], [[0, 3], [4, 0]]]), + ), + ( + (True, False, False), + ((1, 1), (1, 1), (1, 1)), + torch.tensor([[[1, 0], [0, 2]], [[0, 3], [4, 0]]]), + (0,), + torch.tensor([[[1, 0], [0, 2]], [[0, -3], [-4, 0]]]), + ), + ( + (False, True, True), + ((1, 1), (1, 1), (1, 1)), + torch.tensor([[[1, 0], [0, 2]], [[0, 3], [4, 0]]]), + (2,), + torch.tensor([[[1, 0], [0, -2]], [[0, -3], [4, 0]]]), + ), + ( + (True, False, True), + ((1, 1), (1, 1), (1, 1)), + torch.tensor([[[1, 0], [0, 2]], [[0, 3], [4, 0]]]), + (0, 1), + torch.tensor([[[1, 0], [0, 2]], [[0, -3], [-4, 0]]]), + ), + ( + (True, True, True), + ((1, 1), (1, 1), (1, 1)), + torch.tensor([[[1, 0], [0, 2]], [[0, 3], [4, 0]]]), + (0, 1), + torch.tensor([[[1, 0], [0, -2]], [[0, -3], [4, 0]]]), + ), + ], +) def test_reverse(x: ReverseCase) -> None: arrow, edges, tensor, reverse_by, expected = x grassmann_tensor = GrassmannTensor(arrow, edges, tensor) @@ -27,13 +99,18 @@ def test_reverse(x: ReverseCase) -> None: assert torch.allclose(result.tensor, expected) -ReverseFailCase = tuple[tuple[bool, ...], tuple[tuple[int, int], ...], torch.Tensor, tuple[int, ...]] +ReverseFailCase = tuple[ + tuple[bool, ...], tuple[tuple[int, int], ...], torch.Tensor, tuple[int, ...] +] -@pytest.mark.parametrize("x", [ - ((False, False), ((1, 1), (1, 1)), torch.tensor([[1, 0], [0, 4]]), (0, 0)), - ((False, False), ((1, 1), (1, 1)), torch.tensor([[1, 0], [0, 4]]), (2,)), -]) +@pytest.mark.parametrize( + "x", + [ + ((False, False), ((1, 1), (1, 1)), torch.tensor([[1, 0], [0, 4]]), (0, 0)), + ((False, False), ((1, 1), (1, 1)), torch.tensor([[1, 0], [0, 4]]), (2,)), + ], +) def test_reverse_fail(x: ReverseFailCase) -> None: arrow, edges, tensor, reverse_by = x grassmann_tensor = GrassmannTensor(arrow, edges, tensor) diff --git a/tests/update_mask_test.py b/tests/update_mask_test.py index 84ea25f..8573855 100644 --- a/tests/update_mask_test.py +++ b/tests/update_mask_test.py @@ -1,37 +1,40 @@ import pytest import torch + from grassmann_tensor import GrassmannTensor Initialization = tuple[tuple[bool, ...], tuple[tuple[int, int], ...], torch.Tensor] -@pytest.fixture(params=[ - ( - (), - (), - torch.randn([], dtype=torch.float32), - ), - ( - (False, True), - ((2, 2), (0, 2)), - torch.randn([4, 2], dtype=torch.float32), - ), - ( - (False, True), - ((2, 2), (0, 0)), - torch.randn([4, 0], dtype=torch.float32), - ), - ( - (True, False), - ((2, 2), (3, 1)), - torch.randn([4, 4], dtype=torch.float32), - ), - ( - (False, True, False), - ((1, 1), (2, 2), (1, 1)), - torch.randn([2, 4, 2], dtype=torch.float32), - ), -]) +@pytest.fixture( + params=[ + ( + (), + (), + torch.randn([], dtype=torch.float32), + ), + ( + (False, True), + ((2, 2), (0, 2)), + torch.randn([4, 2], dtype=torch.float32), + ), + ( + (False, True), + ((2, 2), (0, 0)), + torch.randn([4, 0], dtype=torch.float32), + ), + ( + (True, False), + ((2, 2), (3, 1)), + torch.randn([4, 4], dtype=torch.float32), + ), + ( + (False, True, False), + ((1, 1), (2, 2), (1, 1)), + torch.randn([2, 4, 2], dtype=torch.float32), + ), + ] +) def x(request: pytest.FixtureRequest) -> Initialization: return request.param From 11544128d9683fb91f94d0aad019787f69be2843 Mon Sep 17 00:00:00 2001 From: Gausshj Date: Mon, 1 Sep 2025 11:04:34 +0800 Subject: [PATCH 2/6] chore: remove legacy yapf configuration --- pyproject.toml | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index b81242b..dbe9766 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ dynamic = ["version"] dependencies = [ "torch", ] -requires-python = ">=3" +requires-python = ">=3.10" authors = [{ email = "hzhangxyz@outlook.com", name = "Hao Zhang" }] description = "A Grassmann algebra tensor package" readme = "README.md" @@ -16,11 +16,11 @@ license = "GPL-3.0-or-later" [project.optional-dependencies] dev = [ - "yapf", "pylint", "mypy", "pytest", "pytest-cov", + "ruff", ] [tool.setuptools_scm] @@ -28,10 +28,6 @@ version_file = "grassmann_tensor/_version.py" version_scheme = "no-guess-dev" fallback_version = "0.0.0" -[tool.yapf] -based_on_style = "google" -column_limit = 200 - [tool.pylint] max-line-length = 200 ignore-paths = [ @@ -47,7 +43,7 @@ check_untyped_defs = true [tool.ruff] line-length = 100 -target-version = "py312" +target-version = "py310" [tool.ruff.lint] select = ["E", "F", "I", "B", "UP"] From 094b76c3c01c93f0319928af25587ef8ff974c99 Mon Sep 17 00:00:00 2001 From: Gausshj Date: Mon, 1 Sep 2025 17:01:46 +0800 Subject: [PATCH 3/6] chore: remove legacy pylint configuration --- .pre-commit-config.yaml | 6 ------ 1 file changed, 6 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 6369bbc..6c13b96 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -38,12 +38,6 @@ repos: - id: sort-simple-yaml - id: trailing-whitespace -- repo: https://github.com/pylint-dev/pylint - rev: v3.3.1 - hooks: - - id: pylint - language: system - - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.13.0 hooks: From 6bc640b72b37fb607f4602af3431d2494fbd445d Mon Sep 17 00:00:00 2001 From: Gausshj Date: Mon, 1 Sep 2025 18:12:33 +0800 Subject: [PATCH 4/6] ci(pytest): add matrix testing for multiple python versions and os --- .github/workflows/pytest.yml | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index f5e9493..5f3c3e5 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -6,16 +6,27 @@ on: jobs: pytest: - runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest, macos-latest] + python-version: ["3.10", "3.11", "3.12"] + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - - uses: actions/setup-python@v5 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: ${{ matrix.python-version }} cache: 'pip' + - name: Show Python + run: | + python -V + python -c "import sys,platform;print('executable=',sys.executable);print('platform=',platform.platform())" + - name: Install dependencies run: pip install '.[dev]' @@ -25,3 +36,4 @@ jobs: - uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} + flags: os-${{ matrix.os }}, python-${{ matrix.python-version }} From 026dce37275bf610dd4bd2b8a43c8d224b5c6080 Mon Sep 17 00:00:00 2001 From: Gausshj Date: Mon, 1 Sep 2025 18:24:29 +0800 Subject: [PATCH 5/6] ci(workflow): restrict GITHUB_TOKEN permissions to contents:read --- .github/workflows/pre-commit.yml | 3 +++ .github/workflows/pytest.yml | 3 +++ .github/workflows/wheels.yml | 3 +++ 3 files changed, 9 insertions(+) diff --git a/.github/workflows/pre-commit.yml b/.github/workflows/pre-commit.yml index 90af94c..1404d92 100644 --- a/.github/workflows/pre-commit.yml +++ b/.github/workflows/pre-commit.yml @@ -4,6 +4,9 @@ on: - push - pull_request +permissions: + contents: read + jobs: pre-commit: runs-on: ubuntu-latest diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 5f3c3e5..15963a9 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -4,6 +4,9 @@ on: - pull_request - push +permissions: + contents: read + jobs: pytest: strategy: diff --git a/.github/workflows/wheels.yml b/.github/workflows/wheels.yml index 36a7394..d3146b2 100644 --- a/.github/workflows/wheels.yml +++ b/.github/workflows/wheels.yml @@ -4,6 +4,9 @@ on: - push - pull_request +permissions: + contents: read + jobs: build: name: Build distribution From 0b77996877b7ecef89a1d9787a1d9e774312402d Mon Sep 17 00:00:00 2001 From: Hao Zhang Date: Tue, 2 Sep 2025 15:03:44 +0800 Subject: [PATCH 6/6] Use python only in pytest action script to show python information. --- .github/workflows/pytest.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 15963a9..1325e85 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -27,8 +27,7 @@ jobs: - name: Show Python run: | - python -V - python -c "import sys,platform;print('executable=',sys.executable);print('platform=',platform.platform())" + python -c "import sys, platform; print(f'Executable: {sys.executable}\nVersion: {platform.python_version()}\nImplementation: {platform.python_implementation()}\nPlatform: {platform.platform()}')" - name: Install dependencies run: pip install '.[dev]'