TensorPlay
latest (dev)
Copy
View Markdown

Latest development documentation · Updated 2026-10-15

Source code for tensorplay._shape_funcs

"""

Complements the codegen-generated ``tensorplay/functional.py``. These
composites are expressed over existing primitives (reshape/permute/
cat/stack/mm/narrow/expand), so autograd flows through them wherever the
underlying ops are differentiable. ``broadcast_tensors`` carries an
explicit backward (ExpandBackward equivalent) because raw ``expand``
views are not yet recorded by the autograd codegen.
"""

import tensorplay
import tensorplay._C as _C
from tensorplay._C import DType
from tensorplay.graph import capture_call as _capture_call
from tensorplay.autograd.function import Function

__all__ = [
    "broadcast_shapes",
    "broadcast_tensors",
    "atleast_1d",
    "atleast_2d",
    "atleast_3d",
    "hstack",
    "vstack",
    "row_stack",
    "dstack",
    "column_stack",
    "tensor_split",
    "hsplit",
    "vsplit",
    "dsplit",
    "tensordot",
    "block_diag",
    "meshgrid",
    "unravel_index",
]


class _BroadcastTo(Function):
    """``expand`` with an ExpandBackward-style backward."""

    @staticmethod
    def forward(ctx, input, shape):
        ctx.input_shape = tuple(input.size())
        return input.expand(list(shape))

    @staticmethod
    def backward(ctx, grad_output):
        shape = ctx.input_shape
        extra = grad_output.dim() - len(shape)
        dims = list(range(max(0, extra)))
        for i, s in enumerate(shape):
            if s == 1:
                dims.append(extra + i)
        if dims:
            grad_output = grad_output.sum(dims, keepdim=True)
        grad_output = grad_output.reshape(list(shape))
        return (grad_output,) + (None,) * (len(ctx.needs_input_grad) - 1)



[docs]
def broadcast_shapes(*shapes):
    """Returns the broadcast shape of the given shapes (right-aligned)."""
    _captured = _capture_call(broadcast_shapes, shapes, {})
    if _captured is not None:
        return _captured
    result = []
    for shape in shapes:
        sh = tuple(int(d) for d in shape)
        for d in sh:
            if d < 0:
                raise RuntimeError(
                    f"Dimension sizes must be non-negative, got {d}."
                )
        if len(sh) > len(result):
            result = [1] * (len(sh) - len(result)) + result
        offset = len(result) - len(sh)
        for j, d in enumerate(sh):
            idx = offset + j
            r = result[idx]
            if d == 1:
                continue
            if r == 1:
                result[idx] = d
            elif d != r:
                raise RuntimeError(
                    "Shape mismatch: objects cannot be broadcast to a single shape."
                )
    return tuple(result)




[docs]
def broadcast_tensors(*tensors):
    """Broadcasts the given tensors to a common shape."""
    _captured = _capture_call(broadcast_tensors, tensors, {})
    if _captured is not None:
        return _captured
    if not tensors:
        return []
    for t in tensors:
        if not isinstance(t, tensorplay.Tensor):
            raise TypeError(
                f"broadcast_tensors(): expected Tensor arguments, got {type(t).__name__}"
            )
    shape = broadcast_shapes(*(tuple(t.size()) for t in tensors))
    return [_BroadcastTo.apply(t, shape) for t in tensors]



def atleast_1d(*tensors):
    """Returns each tensor with at least 1 dimension."""
    _captured = _capture_call(atleast_1d, tensors, {})
    if _captured is not None:
        return _captured
    out = []
    for t in tensors:
        if t.dim() == 0:
            out.append(t.reshape([1]))
        else:
            out.append(t)
    return out[0] if len(out) == 1 else out


def atleast_2d(*tensors):
    """Returns each tensor with at least 2 dimensions."""
    _captured = _capture_call(atleast_2d, tensors, {})
    if _captured is not None:
        return _captured
    out = []
    for t in tensors:
        ndim = t.dim()
        if ndim == 0:
            out.append(t.reshape([1, 1]))
        elif ndim == 1:
            out.append(t.reshape([1, t.size(0)]))
        else:
            out.append(t)
    return out[0] if len(out) == 1 else out


def atleast_3d(*tensors):
    """Returns each tensor with at least 3 dimensions."""
    _captured = _capture_call(atleast_3d, tensors, {})
    if _captured is not None:
        return _captured
    out = []
    for t in tensors:
        ndim = t.dim()
        if ndim == 0:
            out.append(t.reshape([1, 1, 1]))
        elif ndim == 1:
            out.append(t.reshape([1, t.size(0), 1]))
        elif ndim == 2:
            out.append(t.reshape([t.size(0), t.size(1), 1]))
        else:
            out.append(t)
    return out[0] if len(out) == 1 else out


def hstack(tensors):
    """Stacks tensors in sequence horizontally (column wise).

    All-1-D inputs concatenate along dim 0; otherwise all inputs must have
    at least 2 dimensions and concatenate along dim 1.
    """
    _captured = _capture_call(hstack, (tensors,), {})
    if _captured is not None:
        return _captured
    ts = list(tensors)
    promoted = [t.reshape([1]) if t.dim() == 0 else t for t in ts]
    ndims = sorted({t.dim() for t in promoted})
    if len(ndims) > 1:
        raise RuntimeError(
            f"Tensors must have same number of dimensions: got {ndims[0]} and {ndims[-1]}"
        )
    if ndims[0] == 1:
        return _C.cat(tensors=promoted, dim=0)
    return _C.cat(tensors=promoted, dim=1)


def vstack(tensors):
    """Stacks tensors in sequence vertically (row wise)."""
    _captured = _capture_call(vstack, (tensors,), {})
    if _captured is not None:
        return _captured
    ts = list(tensors)
    promoted = [t.reshape([1]) if t.dim() == 0 else t for t in ts]
    aligned = [t.reshape([1, t.numel()]) if t.dim() == 1 else t for t in promoted]
    return _C.cat(tensors=aligned, dim=0)


def row_stack(tensors):
    """Alias of :func:`vstack`."""
    return vstack(tensors)


def dstack(tensors):
    """Stacks tensors in sequence depth wise (along third dimension)."""
    _captured = _capture_call(dstack, (tensors,), {})
    if _captured is not None:
        return _captured
    aligned = []
    for t in tensors:
        ndim = t.dim()
        if ndim == 0:
            aligned.append(t.reshape([1, 1, 1]))
        elif ndim == 1:
            aligned.append(t.reshape([1, t.numel(), 1]))
        elif ndim == 2:
            aligned.append(t.reshape([t.size(0), t.size(1), 1]))
        else:
            aligned.append(t)
    return _C.cat(tensors=aligned, dim=2)


def column_stack(tensors):
    """Stacks 1-D tensors as columns and concatenates along dim 1."""
    _captured = _capture_call(column_stack, (tensors,), {})
    if _captured is not None:
        return _captured
    aligned = []
    for t in tensors:
        ndim = t.dim()
        if ndim == 0:
            aligned.append(t.reshape([1, 1]))
        elif ndim == 1:
            aligned.append(t.reshape([t.numel(), 1]))
        else:
            aligned.append(t)
    return _C.cat(tensors=aligned, dim=1)


def _normalize_dim(dim, ndim):
    d = int(dim)
    if d < 0:
        d += ndim
    if d < 0 or d >= ndim:
        raise IndexError(
            f"Dimension out of range (expected to be in range of [{-ndim}, {ndim - 1}], but got {dim})"
        )
    return d


def _split_sections(dim_size, sections):
    if sections < 1:
        raise RuntimeError(
            f"Number of sections must be greater than 0, but got {sections}"
        )
    base, rem = divmod(dim_size, sections)
    sizes = [base + 1] * rem + [base] * (sections - rem)
    points = []
    acc = 0
    for s in sizes[:-1]:
        acc += s
        points.append(acc)
    return points


def tensor_split(input, indices_or_sections, dim=0):
    """Splits ``input`` into multiple views along ``dim``.

    ``indices_or_sections`` is either an int (near-equal sections) or a
    """
    _captured = _capture_call(tensor_split, (input, indices_or_sections, dim), {})
    if _captured is not None:
        return _captured
    ndim = input.dim()
    d = _normalize_dim(dim, ndim)
    dim_size = input.size(d)
    if isinstance(indices_or_sections, int) and not isinstance(indices_or_sections, bool):
        points = _split_sections(dim_size, indices_or_sections)
    else:
        points = []
        for p in indices_or_sections:
            p = int(p)
            if p < 0 or p > dim_size:
                raise RuntimeError(
                    f"tensor_split: split point {p} is out of range for dimension size {dim_size}"
                )
            points.append(p)
    bounds = [0] + points + [dim_size]
    pieces = []
    prev = bounds[0]
    for b in bounds[1:]:
        length = max(0, b - prev)
        pieces.append(input.narrow(d, prev, length))
        prev = b if b > prev else prev
    return tuple(pieces)


def hsplit(input, indices_or_sections):
    """Splits along dimension 1 (dimension 0 for 1-D tensors)."""
    _captured = _capture_call(hsplit, (input, indices_or_sections), {})
    if _captured is not None:
        return _captured
    if input.dim() < 1:
        raise RuntimeError(
        )
    dim = 0 if input.dim() == 1 else 1
    return tensor_split(input, indices_or_sections, dim=dim)


def vsplit(input, indices_or_sections):
    """Splits along dimension 0 (requires at least 2 dimensions)."""
    _captured = _capture_call(vsplit, (input, indices_or_sections), {})
    if _captured is not None:
        return _captured
    if input.dim() < 2:
        raise RuntimeError(
        )
    return tensor_split(input, indices_or_sections, dim=0)


def dsplit(input, indices_or_sections):
    """Splits along dimension 2 (requires at least 3 dimensions)."""
    _captured = _capture_call(dsplit, (input, indices_or_sections), {})
    if _captured is not None:
        return _captured
    if input.dim() < 3:
        raise RuntimeError(
        )
    return tensor_split(input, indices_or_sections, dim=2)



[docs]
def tensordot(input, other, dims=2):
    """Contracts ``input`` and ``other`` over the given dimensions.

    ``dims`` may be a non-negative int (contract the last ``dims`` dims of
    ``input`` with the first ``dims`` dims of ``other``) or a pair
    ``(dims_a, dims_b)`` of dimension lists paired positionally.
    """
    _captured = _capture_call(tensordot, (input, other, dims), {})
    if _captured is not None:
        return _captured
    nd_a = input.dim()
    nd_b = other.dim()

    if isinstance(dims, int) and not isinstance(dims, bool):
        if dims < 0:
            raise RuntimeError(f"tensordot expects dims >= 0, but got dims={dims}")
        if dims > min(nd_a, nd_b):
            raise RuntimeError(
                f"tensordot: dims ({dims}) must be <= the minimum number of dimensions of the inputs"
            )
        dims_a = list(range(nd_a - dims, nd_a))
        dims_b = list(range(dims))
    else:
        da, db = dims
        dims_a = [int(da)] if isinstance(da, int) else [int(x) for x in da]
        dims_b = [int(db)] if isinstance(db, int) else [int(x) for x in db]
        if len(dims_a) != len(dims_b):
            raise RuntimeError("tensordot: both dimension lists must have the same length")
        norm_a = []
        for x in dims_a:
            n = x + nd_a if x < 0 else x
            if n < 0 or n >= nd_a:
                raise RuntimeError(f"tensordot: dimension {x} out of range")
            norm_a.append(n)
        norm_b = []
        for x in dims_b:
            n = x + nd_b if x < 0 else x
            if n < 0 or n >= nd_b:
                raise RuntimeError(f"tensordot: dimension {x} out of range")
            norm_b.append(n)
        dims_a, dims_b = norm_a, norm_b

    for x, y in zip(dims_a, dims_b):
        sa = input.size(x)
        sb = other.size(y)
        if sa != sb:
            raise RuntimeError(
                f"contracted dimensions need to match, but first has size {sa} in dim {x} "
                f"and second has size {sb} in dim {y}"
            )

    free_a = [i for i in range(nd_a) if i not in set(dims_a)]
    free_b = [i for i in range(nd_b) if i not in set(dims_b)]

    # of `input` aligns with the t-th of `other`.  The joint order is fixed by
    # `other`'s axis indices (sorted), and `input`'s pairing follows it --
    pair_order = sorted(range(len(dims_a)), key=lambda t: dims_b[t])
    da_ordered = [dims_a[t] for t in pair_order]
    db_sorted = sorted(dims_b)

    perm_a = free_a + da_ordered
    perm_b = db_sorted + free_b

    contract_size = 1
    for x in dims_a:
        contract_size *= input.size(x)
    fa_shape = [input.size(i) for i in free_a]
    fb_shape = [other.size(i) for i in free_b]

    if not dims_a:
        a2 = input.reshape([-1, 1])
        b2 = other.reshape([1, -1])
    else:
        a2 = input.permute(perm_a).reshape([-1, contract_size])
        b2 = other.permute(perm_b).reshape([contract_size, -1])
    out = a2.mm(b2)
    return out.reshape(fa_shape + fb_shape)




[docs]
def block_diag(*tensors):
    """Builds a block diagonal matrix from the given blocks.

    0-D blocks become 1x1 matrices and 1-D blocks become single rows.  The
    blocks are promoted to one type and must share a device; the zeros around
    them are made on that device by the operator itself.
    """
    _captured = _capture_call(block_diag, tensors, {})
    if _captured is not None:
        return _captured
    if len(tensors) == 0:
        return _C.zeros(size=[1, 0], dtype=DType.float32)
    for t in tensors:
        if not isinstance(t, tensorplay.Tensor):
            raise TypeError(
                f"block_diag(): expected Tensor arguments, got {type(t).__name__}"
            )
    return _C.block_diag(list(tensors))



def meshgrid(*tensors, indexing="ij"):
    """Coordinate grids from 1-D tensors, one grid per input.

    The tensors come either as separate arguments or as one sequence.  With
    ``indexing="ij"`` the grids' axes follow the inputs' order; ``"xy"``
    swaps the first two, the order Cartesian coordinates are read in.
    """
    if len(tensors) == 1 and isinstance(tensors[0], (list, tuple)):
        tensors = tuple(tensors[0])
    _captured = _capture_call(meshgrid, tensors, {"indexing": indexing})
    if _captured is not None:
        return _captured
    return tuple(_C.meshgrid(list(tensors), indexing))



[docs]
def unravel_index(indices, shape):
    """Converts flat indices into coordinate tuples (one LongTensor per dim).

    """
    _captured = _capture_call(unravel_index, (indices, shape), {})
    if _captured is not None:
        return _captured
    if not isinstance(indices, tensorplay.Tensor):
        raise TypeError(
            f"unravel_index(): expected Tensor indices, got {type(indices).__name__}"
        )
    shp = [int(s) for s in shape]
    total = 1
    for s in shp:
        if s < 0:
            raise RuntimeError(f"unravel_index(): shapes must be non-negative, got {s}")
        total *= s
    if total == 0:
        raise RuntimeError("unravel_index(): shape must have at least one non-zero element")

    idx = indices.to(DType.int64)
    remaining = idx - _int_floordiv(idx, total) * total

    coords = []
    for s in reversed(shp):
        c = remaining - _int_floordiv(remaining, s) * s
        coords.append(c)
        remaining = _int_floordiv(remaining - c, s)
    coords.reverse()
    return tuple(coords)



def _int_floordiv(x, m):
    q = (x.to(DType.float64) / m).floor()
    return q.to(DType.int64)
Ask DeepWiki