#
# MIT License
#
# Copyright (c) 2023 Mike Heddes, Igor Nunes, Pere Vergés, Denis Kleyko, and Danny Abraham
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.
#
import torch
from torch import Tensor
import torch.nn.functional as F
from typing import Set
from torchhd.tensors.basemcr import BaseMCRTensor
[docs]
class CGRTensor(BaseMCRTensor):
r"""Cyclic Group Representation (CGR)
First introduced in `Modular Composite Representation <https://link.springer.com/article/10.1007/s12559-013-9243-y>`_ and then better elaborated in `Understanding hyperdimensional computing for parallel single-pass learning <https://proceedings.neurips.cc/paper_files/paper/2022/file/080be5eb7e887319ff30c792c2cbc28c-Paper-Conference.pdf>`_, this model works with modular integer vectors. It works similar to the MCR class, but uses a bundling based on element-wise mode instead of addition of complex numbers.
"""
[docs]
@classmethod
def empty(
cls,
num_vectors: int,
dimensions: int,
*,
block_size: int,
generator=None,
dtype=torch.int64,
device=None,
requires_grad=False,
) -> "CGRTensor":
return super().empty(
num_vectors,
dimensions,
block_size=block_size,
generator=generator,
dtype=dtype,
device=device,
requires_grad=requires_grad,
)
[docs]
@classmethod
def identity(
cls,
num_vectors: int,
dimensions: int,
*,
block_size: int,
dtype=torch.int64,
device=None,
requires_grad=False,
) -> "CGRTensor":
return super().identity(
num_vectors,
dimensions,
block_size=block_size,
dtype=dtype,
device=device,
requires_grad=requires_grad,
)
[docs]
@classmethod
def random(
cls,
num_vectors: int,
dimensions: int,
*,
block_size: int,
generator=None,
dtype=torch.int64,
device=None,
requires_grad=False,
) -> "CGRTensor":
return super().random(
num_vectors,
dimensions,
block_size=block_size,
generator=generator,
dtype=dtype,
device=device,
requires_grad=requires_grad,
)
[docs]
def bundle(self, other: "CGRTensor") -> "CGRTensor":
r"""Bundle the hypervector with majority voting. Ties might be broken at random. However, the expected result is that the tie representing the lowest value wins.
This produces a hypervector maximally similar to both.
The bundling operation is used to aggregate information into a single hypervector.
Args:
other (CGR): other input hypervector
Shapes:
- Self: :math:`(*)`
- Other: :math:`(*)`
- Output: :math:`(*)`
Examples::
>>> a, b = torchhd.CGRTensor.random(2, 10, block_size=64)
>>> a
CGRTensor([32, 26, 22, 22, 34, 30, 2, 4, 40, 43])
>>> b
CGRTensor([32, 26, 39, 54, 27, 60, 60, 4, 40, 5])
>>> a.bundle(b)
CGRTensor([32, 26, 39, 22, 27, 60, 2, 4, 40, 5])
"""
# Ensure hypervectors are in the same shape, i.e., [..., 1, DIM]
t1 = self
if t1.dim() == 1:
t1 = t1.unsqueeze(0)
t2 = other
if t2.dim() == 1:
t2 = t2.unsqueeze(0)
t = torch.stack((t1, t2), dim=-2)
val = t.multibundle()
# Convert shape back to [DIM] if inputs are plain hypervectors
need_squeeze = self.dim() == 1 and other.dim() == 1
if need_squeeze:
return val.squeeze(0)
return val
[docs]
def multibundle(self) -> "CGRTensor":
"""Bundle multiple hypervectors"""
# The use of torch.mode() makes untying deterministic as it always
# returns the lowest index among the ties. For example, if there is an
# equal amount of 0s and 1s in a bundle, 0 is returned.
val, _ = torch.mode(self, dim=-2)
return val
[docs]
def bind(self, other: "CGRTensor") -> "CGRTensor":
return super().bind(other)
[docs]
def multibind(self) -> "CGRTensor":
"""Bind multiple hypervectors"""
return super().multibind()
[docs]
def inverse(self) -> "CGRTensor":
return super().inverse()
[docs]
def permute(self, shifts: int = 1) -> "CGRTensor":
return super().permute(shifts=shifts)
[docs]
def normalize(self) -> "CGRTensor":
return super().normalize()
[docs]
def dot_similarity(self, others: "CGRTensor", *, dtype=None) -> Tensor:
return super().dot_similarity(others, dtype=dtype)
[docs]
def cosine_similarity(self, others: "CGRTensor", *, dtype=None) -> Tensor:
return super().cosine_similarity(others, dtype=dtype)