torchnd 0.2.0__tar.gz → 0.3.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {torchnd-0.2.0 → torchnd-0.3.0}/PKG-INFO +15 -8
- {torchnd-0.2.0 → torchnd-0.3.0}/README.md +14 -7
- {torchnd-0.2.0 → torchnd-0.3.0}/pyproject.toml +1 -1
- {torchnd-0.2.0 → torchnd-0.3.0}/src/torchnd/__init__.py +1 -1
- {torchnd-0.2.0 → torchnd-0.3.0}/src/torchnd/functional.py +18 -1
- {torchnd-0.2.0 → torchnd-0.3.0}/src/torchnd/modules.py +34 -4
- {torchnd-0.2.0 → torchnd-0.3.0}/src/torchnd.egg-info/PKG-INFO +15 -8
- {torchnd-0.2.0 → torchnd-0.3.0}/src/torchnd.egg-info/SOURCES.txt +2 -0
- torchnd-0.3.0/tests/test_convolution_mode.py +227 -0
- torchnd-0.3.0/tests/test_module_inheritance.py +50 -0
- {torchnd-0.2.0 → torchnd-0.3.0}/tests/test_modules.py +22 -18
- {torchnd-0.2.0 → torchnd-0.3.0}/setup.cfg +0 -0
- {torchnd-0.2.0 → torchnd-0.3.0}/src/torchnd/py.typed +0 -0
- {torchnd-0.2.0 → torchnd-0.3.0}/src/torchnd.egg-info/dependency_links.txt +0 -0
- {torchnd-0.2.0 → torchnd-0.3.0}/src/torchnd.egg-info/requires.txt +0 -0
- {torchnd-0.2.0 → torchnd-0.3.0}/src/torchnd.egg-info/top_level.txt +0 -0
- {torchnd-0.2.0 → torchnd-0.3.0}/tests/test_functional.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: torchnd
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: ND operations for PyTorch
|
|
5
5
|
Author-email: Felix Zimmermann <fzimmermann89@gmail.com>
|
|
6
6
|
License: GPL-3.0-or-later
|
|
@@ -85,13 +85,7 @@ out = conv_nd(x, weight, dim=(-2, -1), padding=1)
|
|
|
85
85
|
```python
|
|
86
86
|
x = torch.randn(2, 4, 16, 16)
|
|
87
87
|
weight = torch.randn(8, 4, 3, 3)
|
|
88
|
-
out = conv_nd(
|
|
89
|
-
x, weight,
|
|
90
|
-
dim=(-2, -1),
|
|
91
|
-
stride=(2, 1),
|
|
92
|
-
padding=(1, 2),
|
|
93
|
-
dilation=(1, 2)
|
|
94
|
-
)
|
|
88
|
+
out = conv_nd(x, weight, dim=(-2, -1), stride=(2, 1), padding=(1, 2), dilation=(1, 2))
|
|
95
89
|
```
|
|
96
90
|
|
|
97
91
|
**Modules:**
|
|
@@ -146,6 +140,19 @@ N-dimensional convolution with flexible dimension specification. Supports arbitr
|
|
|
146
140
|
|
|
147
141
|
The `dim` parameter specifies which dimensions are spatial. The `channel_dim` parameter specifies the channel dimension. This allows for arbitrary tensor layouts (e.g., channel-last).
|
|
148
142
|
|
|
143
|
+
`conv_nd`, `ConvNd`, and `ConvTransposeNd` accept `convolution=True` for
|
|
144
|
+
mathematical convolution. The default keeps PyTorch's cross-correlation
|
|
145
|
+
convention. Each call reverses the spatial kernel axes without changing the
|
|
146
|
+
stored weight. Forward and transposed calls do not conjugate complex weights;
|
|
147
|
+
`adjoint()` conjugates them and keeps the chosen convention. See the `conv_nd`
|
|
148
|
+
docstring for the exact indexing and functional adjoint arguments.
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
conv = ConvNd(4, 8, 3, padding=1, bias=False, convolution=True)
|
|
152
|
+
y = conv(x)
|
|
153
|
+
x_adjoint = conv.adjoint(y)
|
|
154
|
+
```
|
|
155
|
+
|
|
149
156
|
### pad_nd
|
|
150
157
|
|
|
151
158
|
N-dimensional padding and cropping with flexible dimension specification. Supports arbitrary dimension layouts, not limited to the last N dimensions. Negative padding values crop the tensor.
|
|
@@ -54,13 +54,7 @@ out = conv_nd(x, weight, dim=(-2, -1), padding=1)
|
|
|
54
54
|
```python
|
|
55
55
|
x = torch.randn(2, 4, 16, 16)
|
|
56
56
|
weight = torch.randn(8, 4, 3, 3)
|
|
57
|
-
out = conv_nd(
|
|
58
|
-
x, weight,
|
|
59
|
-
dim=(-2, -1),
|
|
60
|
-
stride=(2, 1),
|
|
61
|
-
padding=(1, 2),
|
|
62
|
-
dilation=(1, 2)
|
|
63
|
-
)
|
|
57
|
+
out = conv_nd(x, weight, dim=(-2, -1), stride=(2, 1), padding=(1, 2), dilation=(1, 2))
|
|
64
58
|
```
|
|
65
59
|
|
|
66
60
|
**Modules:**
|
|
@@ -115,6 +109,19 @@ N-dimensional convolution with flexible dimension specification. Supports arbitr
|
|
|
115
109
|
|
|
116
110
|
The `dim` parameter specifies which dimensions are spatial. The `channel_dim` parameter specifies the channel dimension. This allows for arbitrary tensor layouts (e.g., channel-last).
|
|
117
111
|
|
|
112
|
+
`conv_nd`, `ConvNd`, and `ConvTransposeNd` accept `convolution=True` for
|
|
113
|
+
mathematical convolution. The default keeps PyTorch's cross-correlation
|
|
114
|
+
convention. Each call reverses the spatial kernel axes without changing the
|
|
115
|
+
stored weight. Forward and transposed calls do not conjugate complex weights;
|
|
116
|
+
`adjoint()` conjugates them and keeps the chosen convention. See the `conv_nd`
|
|
117
|
+
docstring for the exact indexing and functional adjoint arguments.
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
conv = ConvNd(4, 8, 3, padding=1, bias=False, convolution=True)
|
|
121
|
+
y = conv(x)
|
|
122
|
+
x_adjoint = conv.adjoint(y)
|
|
123
|
+
```
|
|
124
|
+
|
|
118
125
|
### pad_nd
|
|
119
126
|
|
|
120
127
|
N-dimensional padding and cropping with flexible dimension specification. Supports arbitrary dimension layouts, not limited to the last N dimensions. Negative padding values crop the tensor.
|
|
@@ -341,13 +341,16 @@ def conv_nd(
|
|
|
341
341
|
groups: int = 1,
|
|
342
342
|
transposed: bool = False,
|
|
343
343
|
output_padding: int | tuple[int, ...] = 0,
|
|
344
|
+
convolution: bool = False,
|
|
344
345
|
) -> Tensor:
|
|
345
346
|
"""N-dimensional convolution with flexible dimension specification.
|
|
346
347
|
|
|
347
348
|
Supports arbitrary spatial dimensions via recursive decomposition when
|
|
348
349
|
native PyTorch implementations (1D, 2D, 3D) are unavailable.
|
|
349
350
|
|
|
350
|
-
|
|
351
|
+
By default, uses PyTorch's cross-correlation convention without conjugating
|
|
352
|
+
the kernel. Set ``convolution=True`` to reverse all spatial kernel axes
|
|
353
|
+
before applying the same operation. The stored/passed weight is unchanged.
|
|
351
354
|
|
|
352
355
|
Parameters
|
|
353
356
|
----------
|
|
@@ -373,11 +376,25 @@ def conv_nd(
|
|
|
373
376
|
If True, perform transposed (fractionally-strided) convolution.
|
|
374
377
|
output_padding
|
|
375
378
|
Additional size for transposed conv output (disambiguates shape).
|
|
379
|
+
convolution
|
|
380
|
+
If True, reverse the spatial kernel axes on each call. For one spatial
|
|
381
|
+
axis, the forward operation is
|
|
382
|
+
``y[o, n] = sum(w[o, c, K-1-k] * x[c, n*s-p+k*d])``;
|
|
383
|
+
otherwise it uses ``w[o, c, k]``. Indices outside the input are zero.
|
|
384
|
+
Sums run over kernel indices and input channels within each group.
|
|
385
|
+
With ``transposed=True``, scatter ``x[c, n] * w[c, o, K-1-k]``
|
|
386
|
+
(or ``w[c, o, k]``) to ``y[o, n*s-p+k*d]``. Neither operation
|
|
387
|
+
conjugates the weight. To obtain the Hermitian adjoint, toggle
|
|
388
|
+
``transposed`` and conjugate the weight, keeping ``convolution``
|
|
389
|
+
unchanged and choosing ``output_padding`` to recover the input shape.
|
|
376
390
|
|
|
377
391
|
Returns
|
|
378
392
|
-------
|
|
379
393
|
Convolved output preserving input's dimension layout.
|
|
380
394
|
"""
|
|
395
|
+
if convolution:
|
|
396
|
+
weight = weight.flip(tuple(range(2, weight.ndim)))
|
|
397
|
+
|
|
381
398
|
num_spatial = len(dim)
|
|
382
399
|
stride = _normalize_tuple(stride, num_spatial)
|
|
383
400
|
padding = _normalize_tuple(padding, num_spatial)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import math
|
|
6
|
-
from abc import
|
|
6
|
+
from abc import ABCMeta, abstractmethod
|
|
7
7
|
|
|
8
8
|
import torch
|
|
9
9
|
from torch import Tensor
|
|
@@ -12,7 +12,7 @@ from torch.utils.checkpoint import checkpoint
|
|
|
12
12
|
from .functional import _normalize_tuple, adjoint_pad_nd, conv_nd, pad_nd
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
class _ConvNdBase(torch.nn.Module,
|
|
15
|
+
class _ConvNdBase(torch.nn.Module, metaclass=ABCMeta):
|
|
16
16
|
"""Base class for N-dimensional convolution modules."""
|
|
17
17
|
|
|
18
18
|
transposed: bool = False
|
|
@@ -27,6 +27,7 @@ class _ConvNdBase(torch.nn.Module, ABC):
|
|
|
27
27
|
dilation: tuple[int, ...]
|
|
28
28
|
groups: int
|
|
29
29
|
padding_mode: tuple[str, ...]
|
|
30
|
+
convolution: bool = False
|
|
30
31
|
|
|
31
32
|
def __init__(
|
|
32
33
|
self,
|
|
@@ -45,6 +46,7 @@ class _ConvNdBase(torch.nn.Module, ABC):
|
|
|
45
46
|
device: torch.device | None = None,
|
|
46
47
|
dtype: torch.dtype | None = None,
|
|
47
48
|
weight: Tensor | None = None,
|
|
49
|
+
convolution: bool = False,
|
|
48
50
|
) -> None:
|
|
49
51
|
"""Initialize N-dimensional convolution.
|
|
50
52
|
|
|
@@ -83,6 +85,11 @@ class _ConvNdBase(torch.nn.Module, ABC):
|
|
|
83
85
|
weight
|
|
84
86
|
If provided, uses as external weight (enables hypernetwork usage).
|
|
85
87
|
Registered as buffer to be included in state_dict.
|
|
88
|
+
convolution
|
|
89
|
+
If True, reverse all spatial kernel axes on each call to use
|
|
90
|
+
mathematical convolution. Defaults to PyTorch cross-correlation.
|
|
91
|
+
Forward never conjugates the weight; adjoint conjugates it and
|
|
92
|
+
keeps the same spatial reversal. See ``conv_nd`` for index formulas.
|
|
86
93
|
"""
|
|
87
94
|
super().__init__()
|
|
88
95
|
|
|
@@ -102,6 +109,7 @@ class _ConvNdBase(torch.nn.Module, ABC):
|
|
|
102
109
|
self.output_padding = _normalize_tuple(output_padding, num_spatial)
|
|
103
110
|
self.dilation = _normalize_tuple(dilation, num_spatial)
|
|
104
111
|
self.groups = groups
|
|
112
|
+
self.convolution = convolution
|
|
105
113
|
|
|
106
114
|
if isinstance(padding_mode, str):
|
|
107
115
|
self.padding_mode = (padding_mode,) * num_spatial
|
|
@@ -205,6 +213,8 @@ class _ConvNdBase(torch.nn.Module, ABC):
|
|
|
205
213
|
parts.append(f"output_padding={self.output_padding}")
|
|
206
214
|
if self.groups != 1:
|
|
207
215
|
parts.append(f"groups={self.groups}")
|
|
216
|
+
if self.convolution:
|
|
217
|
+
parts.append("convolution=True")
|
|
208
218
|
if self.bias is None:
|
|
209
219
|
parts.append("bias=False")
|
|
210
220
|
if not all(m == "zeros" for m in self.padding_mode):
|
|
@@ -261,6 +271,7 @@ class ConvNd(_ConvNdBase):
|
|
|
261
271
|
device: torch.device | None = None,
|
|
262
272
|
dtype: torch.dtype | None = None,
|
|
263
273
|
weight: Tensor | None = None,
|
|
274
|
+
convolution: bool = False,
|
|
264
275
|
) -> None:
|
|
265
276
|
"""Initialize N-dimensional convolution.
|
|
266
277
|
|
|
@@ -295,6 +306,11 @@ class ConvNd(_ConvNdBase):
|
|
|
295
306
|
Data type for parameters. Complex dtypes enable complex convolution.
|
|
296
307
|
weight
|
|
297
308
|
If provided, uses as external weight. Registered as buffer for state_dict.
|
|
309
|
+
convolution
|
|
310
|
+
If True, reverse all spatial kernel axes on each call to use
|
|
311
|
+
mathematical convolution. Defaults to PyTorch cross-correlation.
|
|
312
|
+
Forward never conjugates the weight; adjoint conjugates it and
|
|
313
|
+
keeps the same spatial reversal. See ``conv_nd`` for index formulas.
|
|
298
314
|
"""
|
|
299
315
|
super().__init__(
|
|
300
316
|
in_channels=in_channels,
|
|
@@ -312,6 +328,7 @@ class ConvNd(_ConvNdBase):
|
|
|
312
328
|
device=device,
|
|
313
329
|
dtype=dtype,
|
|
314
330
|
weight=weight,
|
|
331
|
+
convolution=convolution,
|
|
315
332
|
)
|
|
316
333
|
|
|
317
334
|
def forward(self, x: Tensor) -> Tensor:
|
|
@@ -351,6 +368,7 @@ class ConvNd(_ConvNdBase):
|
|
|
351
368
|
dilation=self.dilation,
|
|
352
369
|
groups=self.groups,
|
|
353
370
|
transposed=False,
|
|
371
|
+
convolution=self.convolution,
|
|
354
372
|
)
|
|
355
373
|
|
|
356
374
|
if self.bias is not None:
|
|
@@ -364,7 +382,8 @@ class ConvNd(_ConvNdBase):
|
|
|
364
382
|
"""Apply the adjoint convolution operator.
|
|
365
383
|
|
|
366
384
|
The adjoint of a convolution is the transposed convolution with
|
|
367
|
-
conjugated weights (for complex) or identical weights (for real)
|
|
385
|
+
conjugated weights (for complex) or identical weights (for real),
|
|
386
|
+
retaining the same ``convolution`` setting.
|
|
368
387
|
|
|
369
388
|
This method applies the adjoint using the same weights (and does not
|
|
370
389
|
allocate a new module). Intended for iterative algorithms, not separate
|
|
@@ -436,6 +455,7 @@ class ConvNd(_ConvNdBase):
|
|
|
436
455
|
dilation=self.dilation,
|
|
437
456
|
groups=self.groups,
|
|
438
457
|
transposed=True,
|
|
458
|
+
convolution=self.convolution,
|
|
439
459
|
output_padding=out_pad,
|
|
440
460
|
)
|
|
441
461
|
|
|
@@ -484,6 +504,7 @@ class ConvTransposeNd(_ConvNdBase):
|
|
|
484
504
|
device: torch.device | None = None,
|
|
485
505
|
dtype: torch.dtype | None = None,
|
|
486
506
|
weight: Tensor | None = None,
|
|
507
|
+
convolution: bool = False,
|
|
487
508
|
) -> None:
|
|
488
509
|
"""Initialize N-dimensional transposed convolution.
|
|
489
510
|
|
|
@@ -520,6 +541,11 @@ class ConvTransposeNd(_ConvNdBase):
|
|
|
520
541
|
Data type for parameters. Complex dtypes enable complex convolution.
|
|
521
542
|
weight
|
|
522
543
|
If provided, uses as external weight. Registered as buffer for state_dict.
|
|
544
|
+
convolution
|
|
545
|
+
If True, reverse all spatial kernel axes on each call to use
|
|
546
|
+
mathematical convolution. Defaults to PyTorch cross-correlation.
|
|
547
|
+
Forward never conjugates the weight; adjoint conjugates it and
|
|
548
|
+
keeps the same spatial reversal. See ``conv_nd`` for index formulas.
|
|
523
549
|
"""
|
|
524
550
|
super().__init__(
|
|
525
551
|
in_channels=in_channels,
|
|
@@ -537,6 +563,7 @@ class ConvTransposeNd(_ConvNdBase):
|
|
|
537
563
|
device=device,
|
|
538
564
|
dtype=dtype,
|
|
539
565
|
weight=weight,
|
|
566
|
+
convolution=convolution,
|
|
540
567
|
)
|
|
541
568
|
|
|
542
569
|
def forward(self, x: Tensor) -> Tensor:
|
|
@@ -568,6 +595,7 @@ class ConvTransposeNd(_ConvNdBase):
|
|
|
568
595
|
dilation=self.dilation,
|
|
569
596
|
groups=self.groups,
|
|
570
597
|
transposed=True,
|
|
598
|
+
convolution=self.convolution,
|
|
571
599
|
output_padding=self.output_padding,
|
|
572
600
|
)
|
|
573
601
|
|
|
@@ -602,7 +630,8 @@ class ConvTransposeNd(_ConvNdBase):
|
|
|
602
630
|
"""Apply the adjoint convolution operator.
|
|
603
631
|
|
|
604
632
|
The adjoint of a transposed convolution is the (forward) convolution with
|
|
605
|
-
conjugated weights (for complex) or identical weights (for real)
|
|
633
|
+
conjugated weights (for complex) or identical weights (for real),
|
|
634
|
+
retaining the same ``convolution`` setting.
|
|
606
635
|
|
|
607
636
|
This method applies the adjoint using the same weights (and does not
|
|
608
637
|
allocate a new module). Intended for iterative algorithms, not separate
|
|
@@ -650,4 +679,5 @@ class ConvTransposeNd(_ConvNdBase):
|
|
|
650
679
|
dilation=self.dilation,
|
|
651
680
|
groups=self.groups,
|
|
652
681
|
transposed=False,
|
|
682
|
+
convolution=self.convolution,
|
|
653
683
|
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: torchnd
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: ND operations for PyTorch
|
|
5
5
|
Author-email: Felix Zimmermann <fzimmermann89@gmail.com>
|
|
6
6
|
License: GPL-3.0-or-later
|
|
@@ -85,13 +85,7 @@ out = conv_nd(x, weight, dim=(-2, -1), padding=1)
|
|
|
85
85
|
```python
|
|
86
86
|
x = torch.randn(2, 4, 16, 16)
|
|
87
87
|
weight = torch.randn(8, 4, 3, 3)
|
|
88
|
-
out = conv_nd(
|
|
89
|
-
x, weight,
|
|
90
|
-
dim=(-2, -1),
|
|
91
|
-
stride=(2, 1),
|
|
92
|
-
padding=(1, 2),
|
|
93
|
-
dilation=(1, 2)
|
|
94
|
-
)
|
|
88
|
+
out = conv_nd(x, weight, dim=(-2, -1), stride=(2, 1), padding=(1, 2), dilation=(1, 2))
|
|
95
89
|
```
|
|
96
90
|
|
|
97
91
|
**Modules:**
|
|
@@ -146,6 +140,19 @@ N-dimensional convolution with flexible dimension specification. Supports arbitr
|
|
|
146
140
|
|
|
147
141
|
The `dim` parameter specifies which dimensions are spatial. The `channel_dim` parameter specifies the channel dimension. This allows for arbitrary tensor layouts (e.g., channel-last).
|
|
148
142
|
|
|
143
|
+
`conv_nd`, `ConvNd`, and `ConvTransposeNd` accept `convolution=True` for
|
|
144
|
+
mathematical convolution. The default keeps PyTorch's cross-correlation
|
|
145
|
+
convention. Each call reverses the spatial kernel axes without changing the
|
|
146
|
+
stored weight. Forward and transposed calls do not conjugate complex weights;
|
|
147
|
+
`adjoint()` conjugates them and keeps the chosen convention. See the `conv_nd`
|
|
148
|
+
docstring for the exact indexing and functional adjoint arguments.
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
conv = ConvNd(4, 8, 3, padding=1, bias=False, convolution=True)
|
|
152
|
+
y = conv(x)
|
|
153
|
+
x_adjoint = conv.adjoint(y)
|
|
154
|
+
```
|
|
155
|
+
|
|
149
156
|
### pad_nd
|
|
150
157
|
|
|
151
158
|
N-dimensional padding and cropping with flexible dimension specification. Supports arbitrary dimension layouts, not limited to the last N dimensions. Negative padding values crop the tensor.
|
|
@@ -9,5 +9,7 @@ src/torchnd.egg-info/SOURCES.txt
|
|
|
9
9
|
src/torchnd.egg-info/dependency_links.txt
|
|
10
10
|
src/torchnd.egg-info/requires.txt
|
|
11
11
|
src/torchnd.egg-info/top_level.txt
|
|
12
|
+
tests/test_convolution_mode.py
|
|
12
13
|
tests/test_functional.py
|
|
14
|
+
tests/test_module_inheritance.py
|
|
13
15
|
tests/test_modules.py
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""Independent values and adjoints for both kernel conventions."""
|
|
2
|
+
|
|
3
|
+
from itertools import product
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
import torch
|
|
7
|
+
|
|
8
|
+
from torchnd import ConvNd, ConvTransposeNd, conv_nd
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def reference_conv(
|
|
12
|
+
x: torch.Tensor,
|
|
13
|
+
weight: torch.Tensor,
|
|
14
|
+
*,
|
|
15
|
+
convolution: bool,
|
|
16
|
+
transposed: bool,
|
|
17
|
+
stride: int = 1,
|
|
18
|
+
padding: int = 0,
|
|
19
|
+
dilation: int = 1,
|
|
20
|
+
groups: int = 1,
|
|
21
|
+
output_padding: int = 0,
|
|
22
|
+
) -> torch.Tensor:
|
|
23
|
+
"""Evaluate the defining sums directly, without any convolution backend."""
|
|
24
|
+
spatial = x.shape[2:]
|
|
25
|
+
kernel = weight.shape[2:]
|
|
26
|
+
if transposed:
|
|
27
|
+
out_shape = tuple(
|
|
28
|
+
(n - 1) * stride - 2 * padding + dilation * (k - 1) + 1 + output_padding for n, k in zip(spatial, kernel, strict=True)
|
|
29
|
+
)
|
|
30
|
+
out_channels = weight.shape[1] * groups
|
|
31
|
+
else:
|
|
32
|
+
out_shape = tuple((n + 2 * padding - dilation * (k - 1) - 1) // stride + 1 for n, k in zip(spatial, kernel, strict=True))
|
|
33
|
+
out_channels = weight.shape[0]
|
|
34
|
+
out = x.new_zeros(x.shape[0], out_channels, *out_shape)
|
|
35
|
+
in_per_group = x.shape[1] // groups
|
|
36
|
+
out_per_group = out_channels // groups
|
|
37
|
+
positions = spatial if transposed else out_shape
|
|
38
|
+
for batch, group, ci, co in product(range(x.shape[0]), range(groups), range(in_per_group), range(out_per_group)):
|
|
39
|
+
ic, oc = group * in_per_group + ci, group * out_per_group + co
|
|
40
|
+
for pos, offset in product(product(*(range(n) for n in positions)), product(*(range(k) for k in kernel))):
|
|
41
|
+
target = tuple(n * stride - padding + k * dilation for n, k in zip(pos, offset, strict=True))
|
|
42
|
+
limits = out_shape if transposed else spatial
|
|
43
|
+
if any(i < 0 or i >= n for i, n in zip(target, limits, strict=True)):
|
|
44
|
+
continue
|
|
45
|
+
wi = tuple(k - 1 - i for k, i in zip(kernel, offset, strict=True)) if convolution else offset
|
|
46
|
+
weight_index = (ic, co, *wi) if transposed else (oc, ci, *wi)
|
|
47
|
+
out_index = (batch, oc, *target) if transposed else (batch, oc, *pos)
|
|
48
|
+
in_index = (batch, ic, *pos) if transposed else (batch, ic, *target)
|
|
49
|
+
out[out_index] += x[in_index] * weight[weight_index]
|
|
50
|
+
return out
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@pytest.mark.parametrize("convolution", [False, True])
|
|
54
|
+
@pytest.mark.parametrize("transposed", [False, True])
|
|
55
|
+
@pytest.mark.parametrize("ndim", [0, 1, 2, 4])
|
|
56
|
+
@pytest.mark.parametrize("dtype", [torch.float64, torch.complex128])
|
|
57
|
+
def test_functional_reference(convolution: bool, transposed: bool, ndim: int, dtype: torch.dtype) -> None:
|
|
58
|
+
"""Check grouped, strided, dilated kernels and channel-last layouts."""
|
|
59
|
+
generator = torch.Generator().manual_seed(3)
|
|
60
|
+
x = torch.randn(1, 4, *((3,) * ndim), dtype=dtype, generator=generator)
|
|
61
|
+
weight = torch.randn(4, 2, *((2,) * ndim), dtype=dtype, generator=generator)
|
|
62
|
+
expected = reference_conv(
|
|
63
|
+
x,
|
|
64
|
+
weight,
|
|
65
|
+
convolution=convolution,
|
|
66
|
+
transposed=transposed,
|
|
67
|
+
stride=2,
|
|
68
|
+
padding=1,
|
|
69
|
+
dilation=2,
|
|
70
|
+
groups=2,
|
|
71
|
+
output_padding=int(transposed),
|
|
72
|
+
)
|
|
73
|
+
actual = conv_nd(
|
|
74
|
+
x.moveaxis(1, -1),
|
|
75
|
+
weight,
|
|
76
|
+
dim=tuple(range(1, ndim + 1)),
|
|
77
|
+
channel_dim=-1,
|
|
78
|
+
stride=2,
|
|
79
|
+
padding=1,
|
|
80
|
+
dilation=2,
|
|
81
|
+
groups=2,
|
|
82
|
+
transposed=transposed,
|
|
83
|
+
output_padding=int(transposed),
|
|
84
|
+
convolution=convolution,
|
|
85
|
+
)
|
|
86
|
+
torch.testing.assert_close(actual.moveaxis(-1, 1), expected)
|
|
87
|
+
if not convolution:
|
|
88
|
+
default = conv_nd(
|
|
89
|
+
x.moveaxis(1, -1),
|
|
90
|
+
weight,
|
|
91
|
+
dim=tuple(range(1, ndim + 1)),
|
|
92
|
+
channel_dim=-1,
|
|
93
|
+
stride=2,
|
|
94
|
+
padding=1,
|
|
95
|
+
dilation=2,
|
|
96
|
+
groups=2,
|
|
97
|
+
transposed=transposed,
|
|
98
|
+
output_padding=int(transposed),
|
|
99
|
+
)
|
|
100
|
+
assert torch.equal(actual, default)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
@pytest.mark.parametrize("convolution", [False, True])
|
|
104
|
+
@pytest.mark.parametrize("conv_type", [ConvNd, ConvTransposeNd])
|
|
105
|
+
@pytest.mark.parametrize("ndim", [1, 4])
|
|
106
|
+
@pytest.mark.parametrize("dtype", [torch.float64, torch.complex128])
|
|
107
|
+
def test_module_reference_and_adjoint(
|
|
108
|
+
convolution: bool,
|
|
109
|
+
conv_type: type[ConvNd] | type[ConvTransposeNd],
|
|
110
|
+
ndim: int,
|
|
111
|
+
dtype: torch.dtype,
|
|
112
|
+
) -> None:
|
|
113
|
+
"""Modules and their adjoints agree with the independent matrix entries."""
|
|
114
|
+
generator = torch.Generator().manual_seed(5)
|
|
115
|
+
x = torch.randn(1, 4, *((3,) * ndim), dtype=dtype, generator=generator, requires_grad=True)
|
|
116
|
+
weight = torch.randn(4, 2, *((2,) * ndim), dtype=dtype, generator=generator)
|
|
117
|
+
module = conv_type(
|
|
118
|
+
4,
|
|
119
|
+
4,
|
|
120
|
+
2,
|
|
121
|
+
dim=tuple(range(2, ndim + 2)),
|
|
122
|
+
stride=2,
|
|
123
|
+
padding=1,
|
|
124
|
+
groups=2,
|
|
125
|
+
bias=False,
|
|
126
|
+
weight=weight,
|
|
127
|
+
convolution=convolution,
|
|
128
|
+
)
|
|
129
|
+
out = module(x)
|
|
130
|
+
expected = reference_conv(x, weight, convolution=convolution, transposed=module.transposed, stride=2, padding=1, groups=2)
|
|
131
|
+
torch.testing.assert_close(out, expected)
|
|
132
|
+
y = torch.randn(out.shape, dtype=dtype, generator=generator)
|
|
133
|
+
adjoint = module.adjoint(y, input_shape=(3,) * ndim) if isinstance(module, ConvNd) else module.adjoint(y)
|
|
134
|
+
(expected_adjoint,) = torch.autograd.grad(expected, x, y)
|
|
135
|
+
torch.testing.assert_close(adjoint, expected_adjoint)
|
|
136
|
+
torch.testing.assert_close(torch.vdot(out.flatten(), y.flatten()), torch.vdot(x.flatten(), adjoint.flatten()))
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
@pytest.mark.parametrize("convolution", [False, True])
|
|
140
|
+
@pytest.mark.parametrize("conv_type", [ConvNd, ConvTransposeNd])
|
|
141
|
+
@pytest.mark.parametrize("padding_mode", ["zeros", "reflect", "replicate", "circular"])
|
|
142
|
+
def test_padded_adjoint(
|
|
143
|
+
convolution: bool,
|
|
144
|
+
conv_type: type[ConvNd] | type[ConvTransposeNd],
|
|
145
|
+
padding_mode: str,
|
|
146
|
+
) -> None:
|
|
147
|
+
"""Boundary extension and its adjoint retain the chosen kernel convention."""
|
|
148
|
+
generator = torch.Generator().manual_seed(8)
|
|
149
|
+
module = conv_type(
|
|
150
|
+
2, 2, 3, dim=(-1,), padding=1, padding_mode=padding_mode, bias=False, dtype=torch.complex128, convolution=convolution
|
|
151
|
+
)
|
|
152
|
+
x = torch.randn(1, 2, 5, dtype=torch.complex128, generator=generator, requires_grad=True)
|
|
153
|
+
out = module(x)
|
|
154
|
+
y = torch.randn(out.shape, dtype=out.dtype, generator=generator)
|
|
155
|
+
(expected,) = torch.autograd.grad(out, x, y)
|
|
156
|
+
torch.testing.assert_close(module.adjoint(y), expected)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
@pytest.mark.parametrize("conv_type", [ConvNd, ConvTransposeNd])
|
|
160
|
+
@pytest.mark.parametrize("external", ["internal", "parameter", "buffer"])
|
|
161
|
+
@pytest.mark.parametrize("dtype", [torch.float64, torch.complex128])
|
|
162
|
+
@pytest.mark.parametrize(
|
|
163
|
+
"device", ["cpu", pytest.param("cuda", marks=pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA unavailable"))]
|
|
164
|
+
)
|
|
165
|
+
def test_weight_updates(conv_type: type[ConvNd] | type[ConvTransposeNd], external: str, dtype: torch.dtype, device: str) -> None:
|
|
166
|
+
"""Each call uses the current weight and builds a fresh differentiable flip."""
|
|
167
|
+
weight = torch.arange(1.0, 4.0, device=device).to(dtype).unflatten(0, (1, 1, 3))
|
|
168
|
+
if dtype.is_complex:
|
|
169
|
+
weight = weight + 0.5j * weight.square()
|
|
170
|
+
if external == "parameter":
|
|
171
|
+
weight = torch.nn.Parameter(weight)
|
|
172
|
+
else:
|
|
173
|
+
weight.requires_grad_()
|
|
174
|
+
module = conv_type(
|
|
175
|
+
1,
|
|
176
|
+
1,
|
|
177
|
+
3,
|
|
178
|
+
dim=(-1,),
|
|
179
|
+
bias=False,
|
|
180
|
+
device=torch.device(device),
|
|
181
|
+
dtype=dtype,
|
|
182
|
+
weight=None if external == "internal" else weight,
|
|
183
|
+
convolution=True,
|
|
184
|
+
)
|
|
185
|
+
if external == "internal":
|
|
186
|
+
weight = module.weight
|
|
187
|
+
assert module.weight is weight
|
|
188
|
+
if external != "internal":
|
|
189
|
+
assert dict(module.named_buffers())["weight"] is weight
|
|
190
|
+
else:
|
|
191
|
+
assert dict(module.named_parameters())["weight"] is weight
|
|
192
|
+
x = torch.tensor([[[1.0, 2.0, 4.0, 8.0]]], dtype=dtype, device=device)
|
|
193
|
+
for step in range(2):
|
|
194
|
+
out = module(x)
|
|
195
|
+
reference_weight = weight.detach().clone().requires_grad_()
|
|
196
|
+
expected = reference_conv(x, reference_weight, convolution=True, transposed=module.transposed)
|
|
197
|
+
torch.testing.assert_close(out, expected)
|
|
198
|
+
out.abs().square().sum().backward()
|
|
199
|
+
(expected_grad,) = torch.autograd.grad(expected.abs().square().sum(), reference_weight)
|
|
200
|
+
torch.testing.assert_close(weight.grad, expected_grad)
|
|
201
|
+
y = torch.ones_like(out)
|
|
202
|
+
weight.grad = None
|
|
203
|
+
module.adjoint(y).abs().square().sum().backward()
|
|
204
|
+
expected_adjoint = reference_conv(y, reference_weight.conj(), convolution=True, transposed=not module.transposed)
|
|
205
|
+
(expected_grad,) = torch.autograd.grad(expected_adjoint.abs().square().sum(), reference_weight)
|
|
206
|
+
torch.testing.assert_close(weight.grad, expected_grad)
|
|
207
|
+
assert weight.grad is not None
|
|
208
|
+
assert torch.isfinite(weight.grad).all()
|
|
209
|
+
torch.testing.assert_close(module.state_dict()["weight"], weight)
|
|
210
|
+
with torch.no_grad():
|
|
211
|
+
weight.add_(step + 1)
|
|
212
|
+
weight.grad = None
|
|
213
|
+
assert "convolution=True" in repr(module)
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
@pytest.mark.parametrize("conv_type", [ConvNd, ConvTransposeNd])
|
|
217
|
+
def test_legacy_module_default(conv_type: type[ConvNd] | type[ConvTransposeNd]) -> None:
|
|
218
|
+
"""Modules pickled before the setting existed keep cross-correlation."""
|
|
219
|
+
module = conv_type(1, 1, 3, dim=(-1,), bias=False, dtype=torch.float64)
|
|
220
|
+
x = torch.arange(5.0, dtype=torch.float64).unflatten(0, (1, 1, 5))
|
|
221
|
+
expected = module(x)
|
|
222
|
+
del module.convolution
|
|
223
|
+
assert module.convolution is False
|
|
224
|
+
torch.testing.assert_close(module(x), expected)
|
|
225
|
+
torch.testing.assert_close(
|
|
226
|
+
module.adjoint(expected), reference_conv(expected, module.weight, convolution=False, transposed=not module.transposed)
|
|
227
|
+
)
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"""Convolution modules can share an abstract torch module base."""
|
|
2
|
+
|
|
3
|
+
from abc import ABC, abstractmethod
|
|
4
|
+
|
|
5
|
+
import pytest
|
|
6
|
+
import torch
|
|
7
|
+
|
|
8
|
+
from torchnd import ConvNd, ConvTransposeNd
|
|
9
|
+
from torchnd.modules import _ConvNdBase
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class AbstractModule(ABC, torch.nn.Module):
|
|
13
|
+
"""An abstract interface that places ABC before Module, as mr2 does."""
|
|
14
|
+
|
|
15
|
+
@abstractmethod
|
|
16
|
+
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
|
17
|
+
"""Apply the module."""
|
|
18
|
+
raise NotImplementedError
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@pytest.mark.parametrize("conv_type", [ConvNd, ConvTransposeNd])
|
|
22
|
+
@pytest.mark.parametrize("abstract_first", [False, True])
|
|
23
|
+
def test_multiple_inheritance(conv_type: type[ConvNd] | type[ConvTransposeNd], abstract_first: bool) -> None:
|
|
24
|
+
"""Either base order preserves construction, parameters, gradients, and the adjoint."""
|
|
25
|
+
bases = (AbstractModule, conv_type) if abstract_first else (conv_type, AbstractModule)
|
|
26
|
+
combined_type = type("CombinedConvolution", bases, {"forward": conv_type.forward})
|
|
27
|
+
module = combined_type(2, 3, 3, dim=(-1,), padding=1, bias=False, dtype=torch.float64)
|
|
28
|
+
assert isinstance(module, AbstractModule)
|
|
29
|
+
assert isinstance(module, conv_type)
|
|
30
|
+
assert dict(module.named_parameters())["weight"] is module.weight
|
|
31
|
+
torch.testing.assert_close(module.state_dict()["weight"], module.weight)
|
|
32
|
+
|
|
33
|
+
generator = torch.Generator().manual_seed(0)
|
|
34
|
+
x = torch.randn(1, 2, 5, generator=generator, dtype=torch.float64, requires_grad=True)
|
|
35
|
+
y = module(x)
|
|
36
|
+
assert y.shape == (1, 3, 5)
|
|
37
|
+
v = torch.randn(y.shape, generator=generator, dtype=y.dtype)
|
|
38
|
+
expected_adjoint = module.adjoint(v)
|
|
39
|
+
(gradient,) = torch.autograd.grad(y, x, grad_outputs=v, retain_graph=True)
|
|
40
|
+
torch.testing.assert_close(gradient, expected_adjoint)
|
|
41
|
+
y.square().sum().backward()
|
|
42
|
+
assert module.weight.grad is not None
|
|
43
|
+
assert module.float() is module
|
|
44
|
+
assert module.weight.dtype == torch.float32
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def test_base_remains_abstract() -> None:
|
|
48
|
+
"""Using ABCMeta directly still prevents constructing the incomplete base class."""
|
|
49
|
+
with pytest.raises(TypeError, match="abstract"):
|
|
50
|
+
_ConvNdBase(1, 1, 3) # type: ignore[abstract]
|
|
@@ -122,7 +122,7 @@ class TestAdjoint:
|
|
|
122
122
|
"""Test adjoint operator correctness."""
|
|
123
123
|
|
|
124
124
|
@pytest.mark.parametrize("ndim", [1, 2, 3])
|
|
125
|
-
@pytest.mark.parametrize("dtype", [torch.
|
|
125
|
+
@pytest.mark.parametrize("dtype", [torch.float64, torch.complex128])
|
|
126
126
|
@pytest.mark.parametrize(
|
|
127
127
|
"config",
|
|
128
128
|
[
|
|
@@ -153,11 +153,10 @@ class TestAdjoint:
|
|
|
153
153
|
lhs = torch.vdot(conv(x).flatten(), y.flatten())
|
|
154
154
|
rhs = torch.vdot(x.flatten(), conv.adjoint(y, input_shape=input_shape[2:]).flatten())
|
|
155
155
|
|
|
156
|
-
rtol
|
|
157
|
-
torch.testing.assert_close(lhs, rhs, rtol=rtol, atol=rtol)
|
|
156
|
+
torch.testing.assert_close(lhs, rhs, rtol=1e-10, atol=1e-10)
|
|
158
157
|
|
|
159
158
|
@pytest.mark.parametrize("ndim", [1, 2, 3])
|
|
160
|
-
@pytest.mark.parametrize("dtype", [torch.
|
|
159
|
+
@pytest.mark.parametrize("dtype", [torch.float64, torch.complex128])
|
|
161
160
|
@pytest.mark.parametrize(
|
|
162
161
|
"config",
|
|
163
162
|
[
|
|
@@ -190,8 +189,7 @@ class TestAdjoint:
|
|
|
190
189
|
lhs = torch.vdot(conv(x).flatten(), y.flatten())
|
|
191
190
|
rhs = torch.vdot(x.flatten(), conv.adjoint(y).flatten())
|
|
192
191
|
|
|
193
|
-
rtol
|
|
194
|
-
torch.testing.assert_close(lhs, rhs, rtol=rtol, atol=rtol)
|
|
192
|
+
torch.testing.assert_close(lhs, rhs, rtol=1e-10, atol=1e-10)
|
|
195
193
|
|
|
196
194
|
def test_raises_with_bias(self) -> None:
|
|
197
195
|
"""Adjoint raises ValueError if bias is present."""
|
|
@@ -460,16 +458,17 @@ class TestPaddingModeAdjoint:
|
|
|
460
458
|
padding=2,
|
|
461
459
|
padding_mode=mode,
|
|
462
460
|
bias=False,
|
|
461
|
+
dtype=torch.float64,
|
|
463
462
|
)
|
|
464
463
|
|
|
465
464
|
input_shape = (x_shape[0], in_ch, *x_shape[2:])
|
|
466
|
-
x = torch.randn(input_shape)
|
|
465
|
+
x = torch.randn(input_shape, dtype=torch.float64)
|
|
467
466
|
y = torch.randn_like(conv(x))
|
|
468
467
|
|
|
469
468
|
lhs = torch.vdot(conv(x).flatten(), y.flatten())
|
|
470
469
|
rhs = torch.vdot(x.flatten(), conv.adjoint(y, input_shape=input_shape[2:]).flatten())
|
|
471
470
|
|
|
472
|
-
torch.testing.assert_close(lhs, rhs, rtol=1e-
|
|
471
|
+
torch.testing.assert_close(lhs, rhs, rtol=1e-10, atol=1e-10)
|
|
473
472
|
|
|
474
473
|
@pytest.mark.parametrize("mode", ["zeros", "reflect", "replicate", "circular"])
|
|
475
474
|
@pytest.mark.parametrize("ndim", [1, 2])
|
|
@@ -486,18 +485,19 @@ class TestPaddingModeAdjoint:
|
|
|
486
485
|
padding=2,
|
|
487
486
|
padding_mode=mode,
|
|
488
487
|
bias=False,
|
|
488
|
+
dtype=torch.float64,
|
|
489
489
|
)
|
|
490
490
|
|
|
491
491
|
spatial_in = tuple(s // 2 for s in x_shape[2:])
|
|
492
492
|
input_shape = (x_shape[0], in_ch, *spatial_in)
|
|
493
493
|
|
|
494
|
-
x = torch.randn(input_shape)
|
|
494
|
+
x = torch.randn(input_shape, dtype=torch.float64)
|
|
495
495
|
y = torch.randn_like(conv(x))
|
|
496
496
|
|
|
497
497
|
lhs = torch.vdot(conv(x).flatten(), y.flatten())
|
|
498
498
|
rhs = torch.vdot(x.flatten(), conv.adjoint(y).flatten())
|
|
499
499
|
|
|
500
|
-
torch.testing.assert_close(lhs, rhs, rtol=1e-
|
|
500
|
+
torch.testing.assert_close(lhs, rhs, rtol=1e-10, atol=1e-10)
|
|
501
501
|
|
|
502
502
|
@pytest.mark.parametrize(
|
|
503
503
|
"modes",
|
|
@@ -518,15 +518,16 @@ class TestPaddingModeAdjoint:
|
|
|
518
518
|
padding=2,
|
|
519
519
|
padding_mode=modes,
|
|
520
520
|
bias=False,
|
|
521
|
+
dtype=torch.float64,
|
|
521
522
|
)
|
|
522
523
|
|
|
523
|
-
x = torch.randn(2, 4, 16, 16)
|
|
524
|
+
x = torch.randn(2, 4, 16, 16, dtype=torch.float64)
|
|
524
525
|
y = torch.randn_like(conv(x))
|
|
525
526
|
|
|
526
527
|
lhs = torch.vdot(conv(x).flatten(), y.flatten())
|
|
527
528
|
rhs = torch.vdot(x.flatten(), conv.adjoint(y, input_shape=(16, 16)).flatten())
|
|
528
529
|
|
|
529
|
-
torch.testing.assert_close(lhs, rhs, rtol=1e-
|
|
530
|
+
torch.testing.assert_close(lhs, rhs, rtol=1e-10, atol=1e-10)
|
|
530
531
|
|
|
531
532
|
@pytest.mark.parametrize(
|
|
532
533
|
"modes",
|
|
@@ -547,15 +548,16 @@ class TestPaddingModeAdjoint:
|
|
|
547
548
|
padding=2,
|
|
548
549
|
padding_mode=modes,
|
|
549
550
|
bias=False,
|
|
551
|
+
dtype=torch.float64,
|
|
550
552
|
)
|
|
551
553
|
|
|
552
|
-
x = torch.randn(2, 8, 8, 8)
|
|
554
|
+
x = torch.randn(2, 8, 8, 8, dtype=torch.float64)
|
|
553
555
|
y = torch.randn_like(conv(x))
|
|
554
556
|
|
|
555
557
|
lhs = torch.vdot(conv(x).flatten(), y.flatten())
|
|
556
558
|
rhs = torch.vdot(x.flatten(), conv.adjoint(y).flatten())
|
|
557
559
|
|
|
558
|
-
torch.testing.assert_close(lhs, rhs, rtol=1e-
|
|
560
|
+
torch.testing.assert_close(lhs, rhs, rtol=1e-10, atol=1e-10)
|
|
559
561
|
|
|
560
562
|
def test_3d_mixed_modes_adjoint(self) -> None:
|
|
561
563
|
"""Verify adjoint identity with 3 different padding modes."""
|
|
@@ -567,15 +569,16 @@ class TestPaddingModeAdjoint:
|
|
|
567
569
|
padding=1,
|
|
568
570
|
padding_mode=("reflect", "replicate", "circular"),
|
|
569
571
|
bias=False,
|
|
572
|
+
dtype=torch.float64,
|
|
570
573
|
)
|
|
571
574
|
|
|
572
|
-
x = torch.randn(2, 4, 8, 8, 8)
|
|
575
|
+
x = torch.randn(2, 4, 8, 8, 8, dtype=torch.float64)
|
|
573
576
|
y = torch.randn_like(conv(x))
|
|
574
577
|
|
|
575
578
|
lhs = torch.vdot(conv(x).flatten(), y.flatten())
|
|
576
579
|
rhs = torch.vdot(x.flatten(), conv.adjoint(y, input_shape=(8, 8, 8)).flatten())
|
|
577
580
|
|
|
578
|
-
torch.testing.assert_close(lhs, rhs, rtol=1e-
|
|
581
|
+
torch.testing.assert_close(lhs, rhs, rtol=1e-10, atol=1e-10)
|
|
579
582
|
|
|
580
583
|
|
|
581
584
|
class TestAdjointWithOutputPadding:
|
|
@@ -598,13 +601,14 @@ class TestAdjointWithOutputPadding:
|
|
|
598
601
|
padding=1,
|
|
599
602
|
output_padding=output_padding,
|
|
600
603
|
bias=False,
|
|
604
|
+
dtype=torch.float64,
|
|
601
605
|
)
|
|
602
606
|
|
|
603
607
|
input_shape = (2, in_ch, *spatial_in)
|
|
604
|
-
x = torch.randn(input_shape)
|
|
608
|
+
x = torch.randn(input_shape, dtype=torch.float64)
|
|
605
609
|
y = torch.randn_like(conv(x))
|
|
606
610
|
|
|
607
611
|
lhs = torch.vdot(conv(x).flatten(), y.flatten())
|
|
608
612
|
rhs = torch.vdot(x.flatten(), conv.adjoint(y).flatten())
|
|
609
613
|
|
|
610
|
-
torch.testing.assert_close(lhs, rhs, rtol=1e-
|
|
614
|
+
torch.testing.assert_close(lhs, rhs, rtol=1e-10, atol=1e-10)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|