From 795307c33f3b6568b21ea26070fa60cba7dbdbec Mon Sep 17 00:00:00 2001 From: Gausshj Date: Fri, 29 Aug 2025 14:49:32 +0800 Subject: [PATCH 1/2] chore: Use ruff instead of yapf and pylint and update github action. This commit is squashed by: chore: update max line length chore: remove legacy yapf configuration chore: remove legacy pylint configuration ci(pytest): add matrix testing for multiple python versions and os ci(workflow): restrict GITHUB_TOKEN permissions to contents:read chore: remove legacy pylint configuration ci(pytest): add support for python=3.13 style: correct unconventional YAML and adjust print statements fix: update the rule of ruff fix: update the rule of ruff Use python only in pytest action script to show python information. Temporarily recovery python codes. --- .github/workflows/pre-commit.yml | 3 +++ .github/workflows/pytest.yml | 17 +++++++++++++++-- .github/workflows/wheels.yml | 3 +++ .pre-commit-config.yaml | 19 +++++++------------ pyproject.toml | 24 ++++++++++-------------- 5 files changed, 38 insertions(+), 28 deletions(-) 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 f5e9493..6a63493 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -4,18 +4,30 @@ on: - pull_request - push +permissions: + contents: read + 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", "3.13"] + runs-on: ${{ matrix.os }} steps: - uses: actions/checkout@v4 - uses: actions/setup-python@v5 with: - python-version: '3.12' + python-version: ${{ matrix.python-version }} cache: 'pip' + - name: Show Python + run: | + 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]' @@ -25,3 +37,4 @@ jobs: - uses: codecov/codecov-action@v5 with: token: ${{ secrets.CODECOV_TOKEN }} + flags: os-${{ matrix.os }}, python-${{ matrix.python-version }} 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 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index d21b56f..6c13b96 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -38,20 +38,15 @@ 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: - - id: pylint - language: system - - repo: https://github.com/pre-commit/mirrors-mypy rev: v1.13.0 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/pyproject.toml b/pyproject.toml index 926a9a8..2c0bda0 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,10 @@ license = "GPL-3.0-or-later" [project.optional-dependencies] dev = [ - "yapf", - "pylint", "mypy", "pytest", "pytest-cov", + "ruff", ] [tool.setuptools_scm] @@ -28,19 +27,16 @@ 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 = [ - "grassmann_tensor/_version.py", - "tests/*", -] - [tool.mypy] disallow_untyped_calls = true disallow_untyped_defs = true disallow_incomplete_defs = true check_untyped_defs = true + +[tool.ruff] +line-length = 100 + +[tool.ruff.lint] +select = ["E4", "E7", "E9", "F"] + +[tool.ruff.format] From 018916100d182888ddd49031c4d6d4873b850523 Mon Sep 17 00:00:00 2001 From: Gausshj Date: Tue, 2 Sep 2025 20:14:46 +0800 Subject: [PATCH 2/2] style: Reformat the codes. --- grassmann_tensor/tensor.py | 166 ++++++++++++++++++++++++++++-------- grassmann_tensor/version.py | 1 + tests/arithmetic_test.py | 106 ++++++++++++++--------- tests/attributes_test.py | 28 +++--- tests/clone_test.py | 4 +- tests/conversion_test.py | 5 +- tests/creation_test.py | 52 ++++++----- tests/import_test.py | 1 + tests/matmul_test.py | 64 ++++++++------ tests/permute_test.py | 122 +++++++++++++++++++++----- tests/reshape_test.py | 16 +++- tests/reverse_test.py | 130 +++++++++++++++++++++++----- tests/update_mask_test.py | 56 ++++++------ 13 files changed, 541 insertions(+), 210 deletions(-) diff --git a/grassmann_tensor/tensor.py b/grassmann_tensor/tensor.py index 697bb7e..0236dfc 100644 --- a/grassmann_tensor/tensor.py +++ b/grassmann_tensor/tensor.py @@ -64,7 +64,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 +98,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 +124,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 +140,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 +169,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 +193,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 +211,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 +267,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 +281,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 +315,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 +360,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 +373,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 +406,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 +430,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 +453,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()})." + 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 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 +489,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 +687,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/tests/arithmetic_test.py b/tests/arithmetic_test.py index 8f56b59..c5f758c 100644 --- a/tests/arithmetic_test.py +++ b/tests/arithmetic_test.py @@ -5,52 +5,69 @@ 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 +123,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..9a12a82 100644 --- a/tests/attributes_test.py +++ b/tests/attributes_test.py @@ -5,17 +5,19 @@ 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 @@ -50,7 +52,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) + ): 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..28d498a 100644 --- a/tests/clone_test.py +++ b/tests/clone_test.py @@ -37,7 +37,9 @@ 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) + ) else: assert cloned_tensor._parity is original_tensor._parity if mask: diff --git a/tests/conversion_test.py b/tests/conversion_test.py index 0151dc8..29b1a59 100644 --- a/tests/conversion_test.py +++ b/tests/conversion_test.py @@ -47,7 +47,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 e4eb406..1185293 100644 --- a/tests/creation_test.py +++ b/tests/creation_test.py @@ -5,40 +5,52 @@ 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, match="Arrow length"): 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, match="Edges length"): 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, match="must equal sum of"): 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..f00316e 100644 --- a/tests/matmul_test.py +++ b/tests/matmul_test.py @@ -5,23 +5,30 @@ 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 +36,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 f7197f7..12f6949 100644 --- a/tests/permute_test.py +++ b/tests/permute_test.py @@ -2,21 +2,68 @@ 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 +71,37 @@ def test_permute(x: PermuteCase) -> None: assert torch.allclose(result.tensor, expected) -PermuteFailCase = tuple[tuple[bool, ...], tuple[tuple[int, int], ...], torch.Tensor, tuple[int, ...], str] +PermuteFailCase = tuple[ + tuple[bool, ...], tuple[tuple[int, int], ...], torch.Tensor, tuple[int, ...], str +] -@pytest.mark.parametrize("x", [ - ((False, False), ((1, 1), (1, 1)), torch.tensor([[1, 0], [0, 4]]), (0, 0), "Permutation indices must be unique"), - ((False, False), ((1, 1), (1, 1)), torch.tensor([[1, 0], [0, 4]]), (2, 0), "Permutation indices must cover all dimensions"), - ((False, False), ((1, 1), (1, 1)), torch.tensor([[1, 0], [0, 4]]), (0, 0, 1), "Permutation indices must be unique"), -]) +@pytest.mark.parametrize( + "x", + [ + ( + (False, False), + ((1, 1), (1, 1)), + torch.tensor([[1, 0], [0, 4]]), + (0, 0), + "Permutation indices must be unique", + ), + ( + (False, False), + ((1, 1), (1, 1)), + torch.tensor([[1, 0], [0, 4]]), + (2, 0), + "Permutation indices must cover all dimensions", + ), + ( + (False, False), + ((1, 1), (1, 1)), + torch.tensor([[1, 0], [0, 4]]), + (0, 0, 1), + "Permutation indices must be unique", + ), + ], +) def test_permute_fail(x: PermuteFailCase) -> None: arrow, edges, tensor, before_by_after, message = x grassmann_tensor = GrassmannTensor(arrow, edges, tensor) @@ -41,13 +111,17 @@ def test_permute_fail(x: PermuteFailCase) -> None: 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): for j in range(4): for k in range(4): - for l in range(4): + for l in range(4): # noqa: E741 for m in range(4): for n in range(4): p = [bool(x & 2) for x in (i, j, k, l, m, n)] @@ -57,7 +131,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 f04b273..60f693c 100644 --- a/tests/reshape_test.py +++ b/tests/reshape_test.py @@ -3,15 +3,25 @@ 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]]) +@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] # noqa: E741 + 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 + l, h = plan_range # noqa: E741 if not all(arrow[l:h]) and any(arrow[l:h]): 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 59cb9f8..ead3806 100644 --- a/tests/reverse_test.py +++ b/tests/reverse_test.py @@ -2,24 +2,95 @@ import torch from grassmann_tensor.tensor import GrassmannTensor -ReverseCase = tuple[tuple[bool, ...], tuple[tuple[int, int], ...], torch.Tensor, tuple[int, ...], torch.Tensor] +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]]])), -]) +@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 +98,30 @@ def test_reverse(x: ReverseCase) -> None: assert torch.allclose(result.tensor, expected) -ReverseFailCase = tuple[tuple[bool, ...], tuple[tuple[int, int], ...], torch.Tensor, tuple[int, ...], str] +ReverseFailCase = tuple[ + tuple[bool, ...], tuple[tuple[int, int], ...], torch.Tensor, tuple[int, ...], str +] -@pytest.mark.parametrize("x", [ - ((False, False), ((1, 1), (1, 1)), torch.tensor([[1, 0], [0, 4]]), (0, 0), "Indices must be unique"), - ((False, False), ((1, 1), (1, 1)), torch.tensor([[1, 0], [0, 4]]), (2,), "Indices must be within tensor dimensions"), -]) +@pytest.mark.parametrize( + "x", + [ + ( + (False, False), + ((1, 1), (1, 1)), + torch.tensor([[1, 0], [0, 4]]), + (0, 0), + "Indices must be unique", + ), + ( + (False, False), + ((1, 1), (1, 1)), + torch.tensor([[1, 0], [0, 4]]), + (2,), + "Indices must be within tensor dimensions", + ), + ], +) def test_reverse_fail(x: ReverseFailCase) -> None: arrow, edges, tensor, reverse_by, message = x grassmann_tensor = GrassmannTensor(arrow, edges, tensor) diff --git a/tests/update_mask_test.py b/tests/update_mask_test.py index 84ea25f..fe52f15 100644 --- a/tests/update_mask_test.py +++ b/tests/update_mask_test.py @@ -5,33 +5,35 @@ 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