Give a dataclass that bundles tensors, arrays, and lists functionality that mimics regular tensor manipulation. For example, this allows you to slice every element of a dataclass at once.
| 17 | |
| 18 | |
| 19 | class Manipulable: |
| 20 | """Give a dataclass that bundles tensors, arrays, and lists functionality that |
| 21 | mimics regular tensor manipulation. For example, this allows you to slice every |
| 22 | element of a dataclass at once. |
| 23 | """ |
| 24 | |
| 25 | def to(self: T, device: torch.device) -> T: |
| 26 | """Return a shallow copy of this instance in which all tensors have been moved |
| 27 | to the specified device. |
| 28 | """ |
| 29 | |
| 30 | replacements = {} |
| 31 | |
| 32 | for field in fields(self): |
| 33 | # Move fields that are torch.Tensor to the specified device. |
| 34 | value = getattr(self, field.name) |
| 35 | if isinstance(value, Tensor): |
| 36 | replacements[field.name] = value.to(device) |
| 37 | |
| 38 | return replace(self, **replacements) |
| 39 | |
| 40 | def __getitem__(self: T, slices: slice | tuple[SliceLike, ...]) -> T: |
| 41 | """Return a shallow copy of this instance in which all tensors, arrays, and |
| 42 | lists have been sliced according to the specified slices. If the number of |
| 43 | slices exceeds the number of dimensions of a particular dataclass element, |
| 44 | ignore the trailing slices. For now, ellipses are not supported. |
| 45 | """ |
| 46 | |
| 47 | # To simplify the implementation below, ensure that slices is a tuple of slices. |
| 48 | if not isinstance(slices, tuple): |
| 49 | slices = (slices,) |
| 50 | |
| 51 | replacements = {} |
| 52 | |
| 53 | # Recursively slice any sliceable fields. |
| 54 | for field in fields(self): |
| 55 | value = getattr(self, field.name) |
| 56 | value = Manipulable.recursive_slice(value, slices) |
| 57 | replacements[field.name] = value |
| 58 | |
| 59 | return replace(self, **replacements) |
| 60 | |
| 61 | @staticmethod |
| 62 | def recursive_slice(value: Any, slices: tuple[SliceLike, ...]) -> Any: |
| 63 | if not isinstance(value, Sliceable): |
| 64 | return value |
| 65 | |
| 66 | # Torch tensors and NumPy arrays don't need to be recursively sliced, since they |
| 67 | # already support tuples of slices. |
| 68 | if isinstance(value, Tensor) or isinstance(value, np.ndarray): |
| 69 | # If the number of slices exceeds the number of dimensions, drop the |
| 70 | # trailing slices. |
| 71 | slices = slices[: value.ndim] |
| 72 | |
| 73 | # Apply the remaining slices. |
| 74 | return value[slices] |
| 75 | |
| 76 | # Lists and tuples need to be recursively sliced. To check if we should recurse |
nothing calls this directly
no outgoing calls
no test coverage detected