Skip to content

Readonlytensor

ReadOnlyTensor (Tensor)

A special type of tensor which is read-only.

This is a subclass of torch.Tensor which explicitly disallows operations that would cause in-place modifications.

Since ReadOnlyTensor if a subclass of torch.Tensor, most non-destructive PyTorch operations are on this tensor are supported.

Cloning a ReadOnlyTensor using the clone() method or Python's deepcopy(...) function results in a regular PyTorch tensor.

Reshaping or slicing operations might return a ReadOnlyTensor if the result ends up being a view of the original ReadOnlyTensor; otherwise, the returned tensor is a regular torch.Tensor.

Source code in evotorch/tools/readonlytensor.py
class ReadOnlyTensor(torch.Tensor):
    """
    A special type of tensor which is read-only.

    This is a subclass of `torch.Tensor` which explicitly disallows
    operations that would cause in-place modifications.

    Since ReadOnlyTensor if a subclass of `torch.Tensor`, most
    non-destructive PyTorch operations are on this tensor are supported.

    Cloning a ReadOnlyTensor using the `clone()` method or Python's
    `deepcopy(...)` function results in a regular PyTorch tensor.

    Reshaping or slicing operations might return a ReadOnlyTensor if the
    result ends up being a view of the original ReadOnlyTensor; otherwise,
    the returned tensor is a regular `torch.Tensor`.
    """

    def __getattribute__(self, attribute_name: str) -> Any:
        if (
            isinstance(attribute_name, str)
            and attribute_name.endswith("_")
            and (not ((attribute_name.startswith("__")) and (attribute_name.endswith("__"))))
        ):
            raise AttributeError(
                f"A ReadOnlyTensor explicitly disables all members whose names end with '_'."
                f" Cannot access member {repr(attribute_name)}."
            )
        else:
            return super().__getattribute__(attribute_name)

    def __cannot_modify(self, *ignore, **ignore_too):
        raise TypeError("The contents of a ReadOnlyTensor cannot be modified")

    __setitem__ = __cannot_modify
    __iadd__ = __cannot_modify
    __iand__ = __cannot_modify
    __idiv__ = __cannot_modify
    __ifloordiv__ = __cannot_modify
    __ilshift__ = __cannot_modify
    __imatmul__ = __cannot_modify
    __imod__ = __cannot_modify
    __imul__ = __cannot_modify
    __ior__ = __cannot_modify
    __ipow__ = __cannot_modify
    __irshift__ = __cannot_modify
    __isub__ = __cannot_modify
    __itruediv__ = __cannot_modify
    __ixor__ = __cannot_modify

    if _torch_older_than_1_12:
        # Define __str__ and __repr__ for when using PyTorch 1.11 or older.
        # With PyTorch 1.12, overriding __str__ and __repr__ are not necessary.
        def __to_string(self) -> str:
            s = super().__repr__()
            if "\n" not in s:
                return f"ReadOnlyTensor({super().__repr__()})"
            else:
                indenter = " " * 4
                s = (indenter + s.replace("\n", "\n" + indenter)).rstrip()
                return f"ReadOnlyTensor(\n{s}\n)"

        __str__ = __to_string
        __repr__ = __to_string

    def clone(self, *, preserve_read_only: bool = False) -> torch.Tensor:
        result = super().clone()
        if not preserve_read_only:
            result = result.as_subclass(torch.Tensor)
        return result

    def __mutable_if_independent(self, other: torch.Tensor) -> torch.Tensor:
        from .misc import storage_ptr

        self_ptr = storage_ptr(self)
        other_ptr = storage_ptr(other)
        if self_ptr != other_ptr:
            other = other.as_subclass(torch.Tensor)
        return other

    def __getitem__(self, index_or_slice) -> torch.Tensor:
        result = super().__getitem__(index_or_slice)
        return self.__mutable_if_independent(result)

    def reshape(self, *args, **kwargs) -> torch.Tensor:
        result = super().reshape(*args, **kwargs)
        return self.__mutable_if_independent(result)

    def numpy(self) -> np.ndarray:
        arr: np.ndarray = torch.Tensor.numpy(self)
        arr.flags["WRITEABLE"] = False
        return arr

    def __array__(self, *args, **kwargs) -> np.ndarray:
        arr: np.ndarray = super().__array__(*args, **kwargs)
        arr.flags["WRITEABLE"] = False
        return arr

    def __copy__(self):
        return self.clone(preserve_read_only=True)

    def __deepcopy__(self, memo):
        return self.clone(preserve_read_only=True)

    @classmethod
    def __torch_function__(cls, func: Callable, types: Iterable, args: tuple = (), kwargs: Optional[Mapping] = None):
        if (kwargs is not None) and ("out" in kwargs):
            if isinstance(kwargs["out"], ReadOnlyTensor):
                raise TypeError(
                    f"The `out` keyword argument passed to {func} is a ReadOnlyTensor."
                    f" A ReadOnlyTensor explicitly fails when referenced via the `out` keyword argument of any torch"
                    f" function."
                    f" This restriction is for making sure that the torch operations which could normally do in-place"
                    f" modifications do not operate on ReadOnlyTensor instances."
                )
        return super().__torch_function__(func, types, args, kwargs)

__torch_function__(func, types, args=(), kwargs=None) classmethod special

This torch_function implementation wraps subclasses such that methods called on subclasses return a subclass instance instead of a torch.Tensor instance.

One corollary to this is that you need coverage for torch.Tensor methods if implementing torch_function for subclasses.

We recommend always calling super().__torch_function__ as the base case when doing the above.

While not mandatory, we recommend making __torch_function__ a classmethod.

Source code in evotorch/tools/readonlytensor.py
@classmethod
def __torch_function__(cls, func: Callable, types: Iterable, args: tuple = (), kwargs: Optional[Mapping] = None):
    if (kwargs is not None) and ("out" in kwargs):
        if isinstance(kwargs["out"], ReadOnlyTensor):
            raise TypeError(
                f"The `out` keyword argument passed to {func} is a ReadOnlyTensor."
                f" A ReadOnlyTensor explicitly fails when referenced via the `out` keyword argument of any torch"
                f" function."
                f" This restriction is for making sure that the torch operations which could normally do in-place"
                f" modifications do not operate on ReadOnlyTensor instances."
            )
    return super().__torch_function__(func, types, args, kwargs)

clone(self, *, preserve_read_only=False)

clone(*, memory_format=torch.preserve_format) -> Tensor

See :func:torch.clone

Source code in evotorch/tools/readonlytensor.py
def clone(self, *, preserve_read_only: bool = False) -> torch.Tensor:
    result = super().clone()
    if not preserve_read_only:
        result = result.as_subclass(torch.Tensor)
    return result

numpy(self)

numpy(*, force=False) -> numpy.ndarray

Returns the tensor as a NumPy :class:ndarray.

If :attr:force is False (the default), the conversion is performed only if the tensor is on the CPU, does not require grad, does not have its conjugate bit set, and is a dtype and layout that NumPy supports. The returned ndarray and the tensor will share their storage, so changes to the tensor will be reflected in the ndarray and vice versa.

If :attr:force is True this is equivalent to calling t.detach().cpu().resolve_conj().resolve_neg().numpy(). If the tensor isn't on the CPU or the conjugate or negative bit is set, the tensor won't share its storage with the returned ndarray. Setting :attr:force to True can be a useful shorthand.

Parameters:

Name Type Description Default
force bool

if True, the ndarray may be a copy of the tensor instead of always sharing memory, defaults to False.

required
Source code in evotorch/tools/readonlytensor.py
def numpy(self) -> np.ndarray:
    arr: np.ndarray = torch.Tensor.numpy(self)
    arr.flags["WRITEABLE"] = False
    return arr

reshape(self, *args, **kwargs)

reshape(*shape) -> Tensor

Returns a tensor with the same data and number of elements as :attr:self but with the specified shape. This method returns a view if :attr:shape is compatible with the current shape. See :meth:torch.Tensor.view on when it is possible to return a view.

See :func:torch.reshape

Parameters:

Name Type Description Default
shape tuple of ints or int...

the desired shape

required
Source code in evotorch/tools/readonlytensor.py
def reshape(self, *args, **kwargs) -> torch.Tensor:
    result = super().reshape(*args, **kwargs)
    return self.__mutable_if_independent(result)

as_read_only_tensor(x, *, dtype=None, device=None)

Convert the given object to a ReadOnlyTensor.

The provided object can be a scalar, or an Iterable of numeric data, or an ObjectArray.

This function can be thought as the read-only counterpart of PyTorch's torch.as_tensor(...) function.

Parameters:

Name Type Description Default
x Any

The object to be converted to a ReadOnlyTensor.

required
dtype Optional[torch.dtype]

The dtype of the new ReadOnlyTensor (e.g. torch.float32). If this argument is not specified, dtype will be inferred from x. For example, if x is a PyTorch tensor or a numpy array, its existing dtype will be kept.

None
device Union[str, torch.device]

The device in which the ReadOnlyTensor will be stored (e.g. "cpu"). If this argument is not specified, the device which is storing the original x will be re-used.

None

Returns:

Type Description
Iterable

The read-only counterpart of the provided object.

Source code in evotorch/tools/readonlytensor.py
def as_read_only_tensor(
    x: Any, *, dtype: Optional[torch.dtype] = None, device: Optional[Union[str, torch.device]] = None
) -> Iterable:
    """
    Convert the given object to a ReadOnlyTensor.

    The provided object can be a scalar, or an Iterable of numeric data,
    or an ObjectArray.

    This function can be thought as the read-only counterpart of PyTorch's
    `torch.as_tensor(...)` function.

    Args:
        x: The object to be converted to a ReadOnlyTensor.
        dtype: The dtype of the new ReadOnlyTensor (e.g. torch.float32).
            If this argument is not specified, dtype will be inferred from `x`.
            For example, if `x` is a PyTorch tensor or a numpy array, its
            existing dtype will be kept.
        device: The device in which the ReadOnlyTensor will be stored
            (e.g. "cpu").
            If this argument is not specified, the device which is storing
            the original `x` will be re-used.
    Returns:
        The read-only counterpart of the provided object.
    """
    from .objectarray import ObjectArray

    kwargs = _device_and_dtype_kwargs(dtype=dtype, device=device)
    if isinstance(x, ObjectArray):
        if len(kwargs) != 0:
            raise ValueError(
                f"read_only_tensor(...): when making a read-only tensor from an ObjectArray,"
                f" the arguments `dtype` and `device` were not expected."
                f" However, the received keyword arguments are: {kwargs}."
            )
        return x.get_read_only_view()
    else:
        return torch.as_tensor(x, **kwargs).as_subclass(ReadOnlyTensor)

read_only_tensor(x, *, dtype=None, device=None)

Make a ReadOnlyTensor from the given object.

The provided object can be a scalar, or an Iterable of numeric data, or an ObjectArray.

This function can be thought as the read-only counterpart of PyTorch's torch.tensor(...) function.

Parameters:

Name Type Description Default
x Any

The object from which the new ReadOnlyTensor will be made.

required
dtype Optional[torch.dtype]

The dtype of the new ReadOnlyTensor (e.g. torch.float32).

None
device Union[str, torch.device]

The device in which the ReadOnlyTensor will be stored (e.g. "cpu").

None

Returns:

Type Description
Iterable

The new read-only tensor.

Source code in evotorch/tools/readonlytensor.py
def read_only_tensor(
    x: Any, *, dtype: Optional[torch.dtype] = None, device: Optional[Union[str, torch.device]] = None
) -> Iterable:
    """
    Make a ReadOnlyTensor from the given object.

    The provided object can be a scalar, or an Iterable of numeric data,
    or an ObjectArray.

    This function can be thought as the read-only counterpart of PyTorch's
    `torch.tensor(...)` function.

    Args:
        x: The object from which the new ReadOnlyTensor will be made.
        dtype: The dtype of the new ReadOnlyTensor (e.g. torch.float32).
        device: The device in which the ReadOnlyTensor will be stored
            (e.g. "cpu").
    Returns:
        The new read-only tensor.
    """
    from .objectarray import ObjectArray

    kwargs = _device_and_dtype_kwargs(dtype=dtype, device=device)
    if isinstance(x, ObjectArray):
        if len(kwargs) != 0:
            raise ValueError(
                f"read_only_tensor(...): when making a read-only tensor from an ObjectArray,"
                f" the arguments `dtype` and `device` were not expected."
                f" However, the received keyword arguments are: {kwargs}."
            )
        return x.get_read_only_view()
    else:
        return torch.as_tensor(x, **kwargs).as_subclass(ReadOnlyTensor)