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 have matching dimensions for any charge sectors shared by both tensors; sectors exclusive to one tensor are included in the output via union.

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 matching dimensions for any charge sectors shared by both A and B; sectors exclusive to one tensor are included in the output via union.

None

Returns:

Type Description
Tensor

Direct sum with merged indices on specified axes

Raises:

Type Description
ValueError

If tensors have incompatible structure or if shared sectors on non-merged axes have mismatched dimensions

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: shared charge sectors must have identical dimensions; sectors exclusive to one tensor are included in the output index via union
  • Charge conservation is maintained in the output tensor

Description

Creates a direct sum (block diagonal concatenation) of two tensors. The tensors must have:

  • The same number of indices
  • Matching directions for every corresponding index
  • The same symmetry group on every corresponding index

The axes parameter controls which indices are merged (block-diagonal stacking) and which are non-merged (shared). For non-merged axes, only charge sectors that appear in both tensors must agree in dimension. Sectors exclusive to one tensor are included in the output via the union of sectors and contribute their blocks independently.

When charges coincide on merged axes, blocks are placed on the block diagonal (not summed).

See Also

Notes

  • The output index for each merged axis is the union of sectors from both tensors, with dimensions added (dim_A + dim_B per shared charge, or dim_A / dim_B for exclusive charges).
  • The output index for each non-merged axis is also the union of sectors, but dimensions of shared charges must be equal (they are not summed).
  • For non-Abelian groups, linearly dependent multiplicity components are automatically compressed away after the merge.