latest (dev)
Copy
Latest development documentation · Updated 2026-10-08
Source code for tensorplay.masked.maskedtensor.core
# mypy: allow-untyped-defs
import warnings
from typing import Any
import tensorplay as tp
__all__ = [
"MaskedTensor",
"is_masked_tensor",
]
[docs]
def is_masked_tensor(obj: Any, /) -> bool:
"""Return True if the input is a MaskedTensor, else False.
Args:
obj: any input
Examples:
>>> # xdoctest: +SKIP
>>> from tensorplay.masked import MaskedTensor
>>> data = tensorplay.arange(6).reshape(2, 3)
>>> mask = tp.tensor([[True, False, False], [True, True, False]])
>>> mt = MaskedTensor(data, mask)
>>> is_masked_tensor(mt)
True
"""
return isinstance(obj, MaskedTensor)
def _tensors_match(a, b, exact=True, rtol=1e-05, atol=1e-08):
"""Compare two plain tensors elementwise, recursing into the
coordinate components when the layout is sparse."""
if is_masked_tensor(a) or is_masked_tensor(b):
raise ValueError("Neither `a` nor `b` can be a MaskedTensor.")
if a.layout != b.layout:
raise ValueError(
f"`a` and `b` must have the same layout. Got {a.layout} and {b.layout}"
)
if a.dtype != b.dtype:
b = b.type(a.dtype)
if a.layout == b.layout == tp.sparse_coo:
return _tensors_match(a.values(), b.values(), exact) and _tensors_match(
a.indices(), b.indices(), exact
)
elif a.layout == b.layout == tp.sparse_csr:
return (
_tensors_match(a.crow_indices(), b.crow_indices(), exact)
and _tensors_match(a.col_indices(), b.col_indices(), exact)
and _tensors_match(a.values(), b.values(), exact)
)
if exact:
return (a.dim() == b.dim()) and tp.eq(a, b).all().item()
return (a.dim() == b.dim()) and tp.allclose(a, b, rtol=rtol, atol=atol)
def _masks_match(a, b):
if is_masked_tensor(a) and is_masked_tensor(b):
mask_a = a.get_mask()
mask_b = b.get_mask()
return _tensors_match(mask_a, mask_b, exact=True)
return True
def _map_mt_args_kwargs(args, kwargs, map_fn):
def _helper(a, map_fn):
if is_masked_tensor(a):
return map_fn(a)
elif tp.is_tensor(a):
return a
elif isinstance(a, list):
a_impl, _ = _map_mt_args_kwargs(a, {}, map_fn)
return a_impl
elif isinstance(a, tuple):
a_impl, _ = _map_mt_args_kwargs(a, {}, map_fn)
return tuple(a_impl)
else:
return a
if kwargs is None:
kwargs = {}
impl_args = []
for a in args:
impl_args.append(_helper(a, map_fn))
impl_kwargs = {}
for k in kwargs:
impl_kwargs[k] = _helper(a, map_fn)
return impl_args, impl_kwargs
def _wrap_result(result_data, result_mask):
if isinstance(result_data, list):
return [_wrap_result(r, m) for (r, m) in zip(result_data, result_mask)]
if isinstance(result_data, tuple):
return tuple(_wrap_result(r, m) for (r, m) in zip(result_data, result_mask))
if tp.is_tensor(result_data):
return MaskedTensor(result_data, result_mask)
# Expect result_data and result_mask to be Tensors only
return NotImplemented
def _masked_tensor_str(data, mask, formatter):
if data.layout in {tp.sparse_coo, tp.sparse_csr}:
data = data.to_dense()
mask = mask.to_dense()
if data.dim() == 1:
formatted_elements = [
formatter.format(d.item()) if isinstance(d.item(), float) else str(d.item())
for d in data
]
max_len = max(8 if x[1] else len(x[0]) for x in zip(formatted_elements, ~mask))
return (
"["
+ ", ".join(
[
"--".rjust(max_len) if m else e
for (e, m) in zip(formatted_elements, ~mask)
]
)
+ "]"
)
sub_strings = [_masked_tensor_str(d, m, formatter) for (d, m) in zip(data, mask)]
sub_strings = ["\n".join([" " + si for si in s.split("\n")]) for s in sub_strings]
return "[\n" + ",\n".join(sub_strings) + "\n]"
def _get_data(a):
if is_masked_tensor(a):
return a._masked_data
return a
def _maybe_get_mask(a):
if is_masked_tensor(a):
return a.get_mask()
return None
[docs]
class MaskedTensor:
"""A pair of plain tensors ``data`` and ``mask`` presented as a single
value.
``mask`` is boolean and has the same shape as ``data``. An element of
``data`` participates in computations only where the corresponding
element of ``mask`` is True; positions where the mask is False are
rendered as ``--`` in the string representation and are replaced by a
caller-supplied fill value by :meth:`to_tensor`.
The mask is always the source of truth for validity: masked-out entries
of ``data`` may hold arbitrary values and are never read semantically.
This class is not a tensor subclass and does not hook into a dispatcher.
Operations are applied through explicit methods (elementwise ops,
reductions and structural ops) or through the masking-aware functions in
``tensorplay.masked``, which accept both plain tensors and MaskedTensor
inputs.
"""
def __init__(self, data, mask, requires_grad=False):
if is_masked_tensor(data) or not tp.is_tensor(data):
raise TypeError("data must be a Tensor")
if is_masked_tensor(mask) or not tp.is_tensor(mask):
raise TypeError("mask must be a Tensor")
warnings.warn(
(
"The MaskedTensor API is in prototype stage and will change "
"in the near future. Please open an issue for feature requests "
"and see the documentation of the tensorplay.masked module for "
"further information about the project."
),
UserWarning,
stacklevel=2,
)
if data.requires_grad:
warnings.warn(
"It is not recommended to create a MaskedTensor with a tensor that requires_grad. "
"To avoid this, you can use data.detach().clone()",
UserWarning,
stacklevel=2,
)
self._requires_grad = requires_grad
self._preprocess_data(data, mask)
self._validate_members()
def _preprocess_data(self, data, mask):
from .._ops import _sparse_coo_where, _sparse_csr_where
if data.layout != mask.layout:
raise TypeError("data and mask must have the same layout.")
if data.layout == tp.sparse_coo:
data = data.coalesce()
mask = mask.coalesce()
if data._nnz() != mask._nnz():
data = _sparse_coo_where(mask, data, tp.tensor(0))
elif data.layout == tp.sparse_csr:
if data._nnz() != mask._nnz():
data = _sparse_csr_where(mask, data, tp.tensor(0))
# Have to pick awkward names to not conflict with existing fields such as data
self._masked_data = data.clone()
self._masked_mask = mask.clone()
def _validate_members(self):
data = self._masked_data
mask = self.get_mask()
if type(data) is not type(mask):
raise TypeError(
f"data and mask must have the same type. Got {type(data)} and {type(mask)}"
)
if data.layout not in {tp.strided, tp.sparse_coo, tp.sparse_csr}:
raise TypeError(f"data layout of {data.layout} is not supported.")
if data.layout == tp.sparse_coo:
if not _tensors_match(data.indices(), mask.indices(), exact=True):
raise ValueError(
"data and mask are both sparse COO tensors but do not have the same indices."
)
elif data.layout == tp.sparse_csr:
if not _tensors_match(
data.crow_indices(), mask.crow_indices(), exact=True
) or not _tensors_match(data.col_indices(), mask.col_indices(), exact=True):
raise ValueError(
"data and mask are both sparse CSR tensors but do not share either crow or col indices."
)
if mask.dtype != tp.bool:
raise TypeError("mask must have dtype bool.")
if not (
data.dtype == tp.float16
or data.dtype == tp.float32
or data.dtype == tp.float64
or data.dtype == tp.bool
or data.dtype == tp.int8
or data.dtype == tp.int16
or data.dtype == tp.int32
or data.dtype == tp.int64
):
raise TypeError(f"{data.dtype} is not supported in MaskedTensor.")
if data.dim() != mask.dim():
raise ValueError("data.dim() must equal mask.dim()")
if data.size() != mask.size():
raise ValueError("data.size() must equal mask.size()")
@staticmethod
def _from_values(data, mask):
"""Differentiable constructor for MaskedTensor"""
class Constructor(tp.autograd.Function):
@staticmethod
def forward(ctx, data, mask):
return MaskedTensor(data, mask)
@staticmethod
def backward(ctx, grad_output):
return grad_output, None
result = Constructor.apply(data, mask)
return result
def _set_data_mask(self, data, mask):
self._masked_data = data
self._masked_mask = mask
self._validate_members()
# Shape, dtype and device metadata live on the stored data tensor.
@property
def shape(self):
return self._masked_data.shape
@property
def ndim(self):
return self._masked_data.ndim
def dim(self):
return self._masked_data.dim()
def size(self, *args):
return self._masked_data.size(*args)
@property
def dtype(self):
return self._masked_data.dtype
@property
def device(self):
return self._masked_data.device
@property
def layout(self):
return self._masked_data.layout
@property
def requires_grad(self):
return self._masked_data.requires_grad
def is_floating_point(self):
return self._masked_data.is_floating_point()
def is_complex(self):
return self._masked_data.is_complex()
def is_signed(self):
return self._masked_data.is_signed()
def numel(self):
return self._masked_data.numel()
def __repr__(self): # type: ignore[override]
formatter = "{0:8.4f}"
if self.dim() == 0:
scalar_data = self.get_data().item()
data_formatted = (
formatter.format(scalar_data)
if isinstance(scalar_data, float)
else str(scalar_data)
)
if not self.get_mask().item():
data_formatted = "--"
return (
"MaskedTensor("
+ data_formatted
+ ", "
+ str(self.get_mask().item())
+ ")"
)
s = _masked_tensor_str(self.get_data(), self.get_mask(), formatter)
s = "\n".join(" " + si for si in s.split("\n"))
return "MaskedTensor(\n" + s + "\n)"
@classmethod
def unary(cls, fn, data, mask):
return MaskedTensor(fn(data), mask)
def __lt__(self, other):
if is_masked_tensor(other):
return MaskedTensor(self.get_data() < _get_data(other), self.get_mask())
return MaskedTensor(self.get_data() < other, self.get_mask())
def to_tensor(self, value):
if self.layout in {tp.sparse_coo, tp.sparse_csr}:
# Dense fallback: this backend does not provide a boolean
# inversion for sparse masks.
return self.get_data().to_dense().masked_fill(
~self.get_mask().to_dense(), value
)
return self.get_data().masked_fill(~self.get_mask(), value)
def get_data(self):
class GetData(tp.autograd.Function):
@staticmethod
def forward(ctx, self):
return self._masked_data.detach()
@staticmethod
def backward(ctx, grad_output):
if is_masked_tensor(grad_output):
return grad_output
return MaskedTensor(grad_output, self.get_mask())
return GetData.apply(self)
def get_mask(self):
return self._masked_mask
@classmethod
def _apply_functional(cls, fn, *args, **kwargs):
"""Apply a masking-aware implementation of ``fn`` to the arguments.
This is the explicit entry point that stands in for interpreter-level
dispatch on tensor subclasses: the callable ``fn`` (a module function
or a tensor method of this package) is looked up in the operation
tables of ``_ops_refs`` and executed with MaskedTensor semantics.
The arguments are passed in the same order the operation expects,
which for most operations puts the MaskedTensor first.
"""
from ._ops_refs import _MASKEDTENSOR_DISPATCH_TABLE, _MASKEDTENSOR_FUNCTION_TABLE
if fn in _MASKEDTENSOR_FUNCTION_TABLE:
return _MASKEDTENSOR_FUNCTION_TABLE[fn](*args, **kwargs)
if fn in _MASKEDTENSOR_DISPATCH_TABLE:
return _MASKEDTENSOR_DISPATCH_TABLE[fn](*args, **kwargs)
raise TypeError(
f"{fn!r} is not implemented for MaskedTensor. If you would like "
"this operation to be supported, please propose its semantics "
"together with a minimal reproducible snippet."
)
# Operator dunders route through the explicit operation tables. They are
# lazily imported to avoid a circular import at module load time.
def __add__(self, other):
from .binary import _apply_native_binary
return _apply_native_binary(tp.add, self, other)
def __radd__(self, other):
from .binary import _apply_native_binary
return _apply_native_binary(tp.add, other, self)
def __sub__(self, other):
from .binary import _apply_native_binary
return _apply_native_binary(tp.sub, self, other)
def __rsub__(self, other):
from .binary import _apply_native_binary
return _apply_native_binary(tp.sub, other, self)
def __mul__(self, other):
from .binary import _apply_native_binary
return _apply_native_binary(tp.mul, self, other)
def __rmul__(self, other):
from .binary import _apply_native_binary
return _apply_native_binary(tp.mul, other, self)
def __truediv__(self, other):
from .binary import _apply_native_binary
return _apply_native_binary(tp.div, self, other)
def __rtruediv__(self, other):
from .binary import _apply_native_binary
return _apply_native_binary(tp.div, other, self)
def __neg__(self):
from .unary import _apply_native_unary
return _apply_native_unary(tp.neg, self)
def __abs__(self):
from .unary import _apply_native_unary
return _apply_native_unary(tp.abs, self)
def __eq__(self, other):
from .binary import _apply_native_binary
return _apply_native_binary(tp.eq, self, other)
def __ne__(self, other):
from .binary import _apply_native_binary
return _apply_native_binary(tp.ne, self, other)
def __le__(self, other):
from .binary import _apply_native_binary
return _apply_native_binary(tp.le, self, other)
def __ge__(self, other):
from .binary import _apply_native_binary
return _apply_native_binary(tp.ge, self, other)
def __gt__(self, other):
from .binary import _apply_native_binary
return _apply_native_binary(tp.gt, self, other)
# Identity hashing keeps MaskedTensor usable in sets and dicts despite
# defining elementwise __eq__.
def __hash__(self):
return id(self)
def is_sparse_coo(self):
return self.layout == tp.sparse_coo
def is_sparse_csr(self): # type: ignore[override]
return self.layout == tp.sparse_csr
# Update later to support more sparse layouts
@property
def is_sparse(self): # type: ignore[override]
return self.is_sparse_coo() or self.is_sparse_csr()Help improve this page
Found an error, an unclear step, or a missing example?
Was this page helpful?

