Skip to content

oplus

Direct sum (block diagonal) of tensors.

oplus

oplus(
    A: Tensor,
    B: Tensor,
    axes: Optional[Union[int, str, Sequence[int], Sequence[str]]] = None,
) -> Tensor

Direct sum of two tensors with selective axis merging.

Combines two tensors by merging their sector structures along specified axes and arranging blocks in a block-diagonal fashion. Axes not specified must match exactly (same sectors, same dimensions).

For non-Abelian tensors, blocks are padded with zeros and combined using block_add, which merges intertwiner weights (collinear weights combine directly; non-collinear weights are concatenated). block_compress is then applied to remove any resulting linear dependence among components.

Parameters:

Name Type Description Default
A Tensor

First tensor

required
B Tensor

Second tensor

required
axes Optional[Union[int, str, Sequence[int], Sequence[str]]]

Axes to merge. Can be:

  • None: merge all axes (default)
  • Single integer: axis position (e.g., 0)
  • Sequence of integers: axis positions (e.g., [0, 2])
  • Single string: itag name (e.g., 'i')
  • Sequence of strings: itag names (e.g., ['i', 'k'])
Axes not specified must have identical Index structure in A and B.

None

Returns:

Type Description
Tensor

Direct sum with merged indices on specified axes

Raises:

Type Description
ValueError

If tensors have incompatible structure or if non-merged axes don't match exactly

Examples:

>>> from nicole import Tensor, U1Group, Direction, Index, Sector, oplus
>>> import torch
>>> 
>>> group = U1Group()
>>> 
>>> # Example 1: Default - merge all axes
>>> idx_A0 = Index(Direction.OUT, group, sectors=(Sector(0, 2), Sector(1, 3)))
>>> idx_A1 = Index(Direction.IN, group, sectors=(Sector(0, 3), Sector(1, 2)))
>>> A = Tensor.random([idx_A0, idx_A1], seed=1, itags=['i', 'j'])
>>> 
>>> idx_B0 = Index(Direction.OUT, group, sectors=(Sector(0, 1), Sector(2, 2)))
>>> idx_B1 = Index(Direction.IN, group, sectors=(Sector(0, 2), Sector(2, 1)))
>>> B = Tensor.random([idx_B0, idx_B1], seed=2, itags=['i', 'j'])
>>> 
>>> C = oplus(A, B)  # Merges both axes
>>> # C.indices[0] has sectors: [(0, 3), (1, 3), (2, 2)]
>>> # C.indices[1] has sectors: [(0, 5), (1, 2), (2, 1)]
>>> 
>>> # Example 2: Selective - merge only first axis
>>> idx_A1_match = Index(Direction.IN, group, sectors=(Sector(0, 5),))
>>> idx_B1_match = Index(Direction.IN, group, sectors=(Sector(0, 5),))  # Must match!
>>> A2 = Tensor.random([idx_A0, idx_A1_match], seed=3, itags=['i', 'j'])
>>> B2 = Tensor.random([idx_B0, idx_B1_match], seed=4, itags=['i', 'j'])
>>> 
>>> C2 = oplus(A2, B2, axes=0)  # or axes='i', or axes=[0], or axes=['i']
>>> # C2.indices[0] has sectors: [(0, 3), (1, 3), (2, 2)]  ← merged
>>> # C2.indices[1] has sectors: [(0, 5)]  ← unchanged (matched exactly)
Notes
  • Blocks are arranged in a block-diagonal fashion along merged axes
  • For merged axes, dimensions add for sectors with the same charge
  • Non-merged axes must have identical sectors and dimensions
  • Charge conservation is maintained in the output tensor

Description

Creates a direct sum (block diagonal concatenation) of multiple tensors. All tensors must have:

  • Same number of indices
  • Matching directions for corresponding indices
  • Same symmetry groups
  • Matching tags

When charges collide, blocks are placed on the block diagonal.

See Also

Notes

Resulting tensor has combined sectors from all input tensors. For colliding charges, blocks are arranged diagonally (not summed). For non-Abelian groups, linearly dependent components are automatically compressed away after the merge.