Skip to content

Hall Projection

log_signatures_pytorch.hall_projection.HallProjector dataclass

Projector from tensor algebra coordinates to Hall basis coordinates.

This class computes and caches projection matrices that convert log-signature tensors from tensor algebra coordinates to Hall basis coordinates. The projection matrices are computed using QR decomposition or pseudoinverse.

Parameters:

Name Type Description Default
width int

Path dimension (size of the alphabet).

required
depth int

Truncation depth.

required
device device

Device on which to store projection matrices.

required
dtype dtype

Data type for projection matrices.

required

Attributes:

Name Type Description
width int

Path dimension.

depth int

Truncation depth.

device device

Device for projection matrices.

dtype dtype

Data type for projection matrices.

Examples:

>>> import torch
>>> from log_signatures_pytorch.hall_projection import HallProjector
>>>
>>> projector = HallProjector(width=2, depth=2, device=torch.device("cpu"), dtype=torch.float32)
>>> # Project log-signature tensors
>>> log_sig_tensors = [
...     torch.tensor([[1.0, 2.0]]),  # depth 1
...     torch.tensor([[[0.5, 0.3], [0.2, 0.1]]]),  # depth 2
... ]
>>> result = projector.project(log_sig_tensors)
>>> result.shape
torch.Size([1, 3])
Source code in src/log_signatures_pytorch/hall_projection.py
@dataclass
class HallProjector:
    """Projector from tensor algebra coordinates to Hall basis coordinates.

    This class computes and caches projection matrices that convert log-signature
    tensors from tensor algebra coordinates to Hall basis coordinates. The
    projection matrices are computed using QR decomposition or pseudoinverse.

    Parameters
    ----------
    width : int
        Path dimension (size of the alphabet).
    depth : int
        Truncation depth.
    device : torch.device
        Device on which to store projection matrices.
    dtype : torch.dtype
        Data type for projection matrices.

    Attributes
    ----------
    width : int
        Path dimension.
    depth : int
        Truncation depth.
    device : torch.device
        Device for projection matrices.
    dtype : torch.dtype
        Data type for projection matrices.

    Examples
    --------
    >>> import torch
    >>> from log_signatures_pytorch.hall_projection import HallProjector
    >>>
    >>> projector = HallProjector(width=2, depth=2, device=torch.device("cpu"), dtype=torch.float32)
    >>> # Project log-signature tensors
    >>> log_sig_tensors = [
    ...     torch.tensor([[1.0, 2.0]]),  # depth 1
    ...     torch.tensor([[[0.5, 0.3], [0.2, 0.1]]]),  # depth 2
    ... ]
    >>> result = projector.project(log_sig_tensors)
    >>> result.shape
    torch.Size([1, 3])
    """

    width: int
    depth: int
    device: torch.device
    dtype: torch.dtype

    def __post_init__(self) -> None:
        basis, grouped = _hall_basis_with_depths(self.width, self.depth)
        self._basis = list(basis)
        self._depth_offsets: Dict[int, Tuple[int, int]] = {}
        offset = 0
        for d in range(1, self.depth + 1):
            count = len(grouped.get(d, ()))
            self._depth_offsets[d] = (offset, offset + count)
            offset += count
        base_mats = _hall_projection_matrices(self.width, self.depth)
        self._matrices: Dict[int, torch.Tensor] = {
            depth: mat.to(device=self.device, dtype=self.dtype)
            for depth, mat in base_mats.items()
        }

    def project(self, log_sig_tensors: List[torch.Tensor]) -> torch.Tensor:
        """Project log-signature tensors onto Hall basis.

        Converts log-signature tensors from tensor algebra coordinates to
        Hall basis coordinates using precomputed projection matrices.

        Parameters
        ----------
        log_sig_tensors : List[torch.Tensor]
            List of log-signature tensors in tensor algebra coordinates, where
            entry ``k`` has shape ``(batch, width, ..., width)`` with ``k+1``
            trailing width axes.

        Returns
        -------
        torch.Tensor
            Tensor of shape ``(batch, logsigdim(width, depth))`` containing
            the log-signature in Hall basis coordinates.

        Examples
        --------
        >>> import torch
        >>> from log_signatures_pytorch.hall_projection import HallProjector
        >>>
        >>> projector = HallProjector(width=2, depth=2, device=torch.device("cpu"), dtype=torch.float32)
        >>> log_sig_tensors = [
        ...     torch.tensor([[1.0, 2.0]]),  # depth 1: (batch=1, width=2)
        ...     torch.tensor([[[0.5, 0.3], [0.2, 0.1]]]),  # depth 2: (batch=1, width=2, width=2)
        ... ]
        >>> result = projector.project(log_sig_tensors)
        >>> result.shape
        torch.Size([1, 3])
        """
        if not log_sig_tensors:
            return torch.zeros(0, device=self.device, dtype=self.dtype)

        batch = log_sig_tensors[0].shape[0]
        coeffs: List[torch.Tensor] = []

        for current_depth in range(1, self.depth + 1):
            start, end = self._depth_offsets[current_depth]
            count = end - start
            if count == 0:
                continue

            tensor = log_sig_tensors[current_depth - 1]
            mat = self._matrices[current_depth]
            flattened = tensor.reshape(batch, -1)
            coeffs.append(flattened @ mat)

        if not coeffs:
            return torch.zeros(batch, 0, device=self.device, dtype=self.dtype)

        return torch.cat(coeffs, dim=1)

project(log_sig_tensors)

Project log-signature tensors onto Hall basis.

Converts log-signature tensors from tensor algebra coordinates to Hall basis coordinates using precomputed projection matrices.

Parameters:

Name Type Description Default
log_sig_tensors List[Tensor]

List of log-signature tensors in tensor algebra coordinates, where entry k has shape (batch, width, ..., width) with k+1 trailing width axes.

required

Returns:

Type Description
Tensor

Tensor of shape (batch, logsigdim(width, depth)) containing the log-signature in Hall basis coordinates.

Examples:

>>> import torch
>>> from log_signatures_pytorch.hall_projection import HallProjector
>>>
>>> projector = HallProjector(width=2, depth=2, device=torch.device("cpu"), dtype=torch.float32)
>>> log_sig_tensors = [
...     torch.tensor([[1.0, 2.0]]),  # depth 1: (batch=1, width=2)
...     torch.tensor([[[0.5, 0.3], [0.2, 0.1]]]),  # depth 2: (batch=1, width=2, width=2)
... ]
>>> result = projector.project(log_sig_tensors)
>>> result.shape
torch.Size([1, 3])
Source code in src/log_signatures_pytorch/hall_projection.py
def project(self, log_sig_tensors: List[torch.Tensor]) -> torch.Tensor:
    """Project log-signature tensors onto Hall basis.

    Converts log-signature tensors from tensor algebra coordinates to
    Hall basis coordinates using precomputed projection matrices.

    Parameters
    ----------
    log_sig_tensors : List[torch.Tensor]
        List of log-signature tensors in tensor algebra coordinates, where
        entry ``k`` has shape ``(batch, width, ..., width)`` with ``k+1``
        trailing width axes.

    Returns
    -------
    torch.Tensor
        Tensor of shape ``(batch, logsigdim(width, depth))`` containing
        the log-signature in Hall basis coordinates.

    Examples
    --------
    >>> import torch
    >>> from log_signatures_pytorch.hall_projection import HallProjector
    >>>
    >>> projector = HallProjector(width=2, depth=2, device=torch.device("cpu"), dtype=torch.float32)
    >>> log_sig_tensors = [
    ...     torch.tensor([[1.0, 2.0]]),  # depth 1: (batch=1, width=2)
    ...     torch.tensor([[[0.5, 0.3], [0.2, 0.1]]]),  # depth 2: (batch=1, width=2, width=2)
    ... ]
    >>> result = projector.project(log_sig_tensors)
    >>> result.shape
    torch.Size([1, 3])
    """
    if not log_sig_tensors:
        return torch.zeros(0, device=self.device, dtype=self.dtype)

    batch = log_sig_tensors[0].shape[0]
    coeffs: List[torch.Tensor] = []

    for current_depth in range(1, self.depth + 1):
        start, end = self._depth_offsets[current_depth]
        count = end - start
        if count == 0:
            continue

        tensor = log_sig_tensors[current_depth - 1]
        mat = self._matrices[current_depth]
        flattened = tensor.reshape(batch, -1)
        coeffs.append(flattened @ mat)

    if not coeffs:
        return torch.zeros(batch, 0, device=self.device, dtype=self.dtype)

    return torch.cat(coeffs, dim=1)

log_signatures_pytorch.hall_projection.get_hall_projector(width, depth, device, dtype)

Get or create a cached Hall projector.

Returns a cached :class:HallProjector instance for the given parameters. Projectors are cached to avoid recomputing projection matrices for the same width, depth, device, and dtype combination.

Parameters:

Name Type Description Default
width int

Path dimension (size of the alphabet).

required
depth int

Truncation depth.

required
device device

Device on which to store projection matrices.

required
dtype dtype

Data type for projection matrices.

required

Returns:

Type Description
HallProjector

A cached projector instance for the specified parameters.

Examples:

>>> import torch
>>> from log_signatures_pytorch.hall_projection import get_hall_projector
>>>
>>> # First call creates and caches the projector
>>> projector1 = get_hall_projector(2, 2, torch.device("cpu"), torch.float32)
>>>
>>> # Second call returns the cached projector
>>> projector2 = get_hall_projector(2, 2, torch.device("cpu"), torch.float32)
>>> projector1 is projector2
True
Source code in src/log_signatures_pytorch/hall_projection.py
def get_hall_projector(
    width: int, depth: int, device: torch.device, dtype: torch.dtype
) -> HallProjector:
    """Get or create a cached Hall projector.

    Returns a cached :class:`HallProjector` instance for the given parameters.
    Projectors are cached to avoid recomputing projection matrices for the same
    width, depth, device, and dtype combination.

    Parameters
    ----------
    width : int
        Path dimension (size of the alphabet).
    depth : int
        Truncation depth.
    device : torch.device
        Device on which to store projection matrices.
    dtype : torch.dtype
        Data type for projection matrices.

    Returns
    -------
    HallProjector
        A cached projector instance for the specified parameters.

    Examples
    --------
    >>> import torch
    >>> from log_signatures_pytorch.hall_projection import get_hall_projector
    >>>
    >>> # First call creates and caches the projector
    >>> projector1 = get_hall_projector(2, 2, torch.device("cpu"), torch.float32)
    >>>
    >>> # Second call returns the cached projector
    >>> projector2 = get_hall_projector(2, 2, torch.device("cpu"), torch.float32)
    >>> projector1 is projector2
    True
    """
    key = (width, depth, device, dtype)
    if key not in _PROJECTOR_CACHE:
        _PROJECTOR_CACHE[key] = HallProjector(width, depth, device, dtype)
    return _PROJECTOR_CACHE[key]

log_signatures_pytorch.hall_projection.hall_basis(width, depth)

Return Hall basis elements up to depth over an alphabet of size width.

The Hall basis is a particular basis for the free Lie algebra. Elements are ordered first by depth, then lexicographically by the recursive Hall ordering. Degree-1 elements are labeled 1..width and higher degrees are nested tuples representing Lie brackets.

Parameters:

Name Type Description Default
width int

Size of the alphabet (path dimension). Must be >= 1.

required
depth int

Maximum depth to generate basis elements. Must be >= 1.

required

Returns:

Type Description
List[HallBasisElement]

Hall basis elements, where each element is either an integer (degree 1) or a nested tuple representing a Lie bracket (higher degrees).

Raises:

Type Description
ValueError

If width < 1 or depth < 1.

Source code in src/log_signatures_pytorch/hall_projection.py
def hall_basis(width: int, depth: int) -> List[HallBasisElement]:
    """Return Hall basis elements up to ``depth`` over an alphabet of size ``width``.

    The Hall basis is a particular basis for the free Lie algebra. Elements are
    ordered first by depth, then lexicographically by the recursive Hall ordering.
    Degree-1 elements are labeled 1..width and higher degrees are nested tuples
    representing Lie brackets.

    Parameters
    ----------
    width : int
        Size of the alphabet (path dimension). Must be >= 1.
    depth : int
        Maximum depth to generate basis elements. Must be >= 1.

    Returns
    -------
    List[HallBasisElement]
        Hall basis elements, where each element is either an integer (degree 1)
        or a nested tuple representing a Lie bracket (higher degrees).

    Raises
    ------
    ValueError
        If ``width < 1`` or ``depth < 1``.
    """
    if width < 1:
        raise ValueError("width must be >= 1")
    if depth < 1:
        raise ValueError("depth must be >= 1")

    depth_groups: Dict[int, List[HallBasisElement]] = {}
    letters = list(range(1, width + 1))
    depth_groups[1] = letters
    basis: List[HallBasisElement] = list(letters)

    for current_depth in range(2, depth + 1):
        candidates: List[HallBasisElement] = []
        for left_depth in range(1, current_depth):
            right_depth = current_depth - left_depth
            for left in depth_groups[left_depth]:
                for right in depth_groups[right_depth]:
                    if _hall_is_valid_pair(left, right):
                        candidates.append((left, right))
        candidates.sort(key=_hall_basis_key)
        depth_groups[current_depth] = candidates
        basis.extend(candidates)

    return basis

log_signatures_pytorch.hall_projection.logsigdim(width, depth)

Dimension of the truncated log-signature in the Hall basis.

Source code in src/log_signatures_pytorch/hall_projection.py
def logsigdim(width: int, depth: int) -> int:
    """Dimension of the truncated log-signature in the Hall basis."""

    return len(hall_basis(width, depth))

log_signatures_pytorch.hall_projection.logsigkeys(width, depth)

Human-readable labels for Hall basis elements (esig-compatible).

Source code in src/log_signatures_pytorch/hall_projection.py
def logsigkeys(width: int, depth: int) -> List[str]:
    """Human-readable labels for Hall basis elements (esig-compatible)."""

    def _to_str(elem: HallBasisElement) -> str:
        if isinstance(elem, int):
            return str(elem)
        left, right = elem
        return f"[{_to_str(left)},{_to_str(right)}]"

    return [_to_str(elem) for elem in hall_basis(width, depth)]