Source code for torchhd.tensors.mcr

#
# 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.base import VSATensor
from torchhd.tensors.basemcr import BaseMCRTensor


[docs] class MCRTensor(BaseMCRTensor): r"""Modular Composite Representation (MCR) Proposed in `Modular Composite Representation <https://link.springer.com/article/10.1007/s12559-013-9243-y>`_, this model works with modular integer vectors. """
[docs] @classmethod def empty( cls, num_vectors: int, dimensions: int, *, block_size: int, generator=None, dtype=torch.int64, device=None, requires_grad=False, ) -> "MCRTensor": 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, ) -> "MCRTensor": 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, ) -> "MCRTensor": 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: "MCRTensor") -> "MCRTensor": r"""Bundle the hypervector with normalized complex vector addition. This produces a hypervector maximally similar to both. The bundling operation is used to aggregate information into a single hypervector. Args: other (MCR): other input hypervector Shapes: - Self: :math:`(*)` - Other: :math:`(*)` - Output: :math:`(*)` Examples:: >>> a, b = torchhd.MCRTensor.random(2, 10, block_size=64) >>> a MCRTensor([32, 26, 22, 22, 34, 30, 2, 2, 40, 43]) >>> b MCRTensor([33, 27, 39, 54, 27, 60, 60, 4, 24, 5]) >>> a.bundle(b) MCRTensor([32, 26, 39, 54, 27, 60, 2, 4, 40, 5]) """ self_phasor = self.to_complex_unit() other_phasor = other.to_complex_unit() # Adding the vectors of each element sum_of_phasors = self_phasor + other_phasor # To define the ultimate number that the summation will land on # we first find the theta of summation then quantize it to block_size angels = torch.angle(sum_of_phasors) result = self.block_size * (angels / (2 * torch.pi)) # In cases where the two elements are inverse of each other # the sum will be 0 + 0j and it makes the final result to be nan. # We return the average of two operands in such a case. is_zero = torch.isclose(sum_of_phasors, torch.zeros_like(sum_of_phasors)) result = torch.where(is_zero, (self + other) / 2, result).round() return torch.remainder(result, self.block_size).type(self.dtype)
[docs] def multibundle(self) -> "MCRTensor": """Bundle multiple hypervectors""" self_phasor = self.to_complex_unit() sum_of_phasors = torch.sum(self_phasor, dim=-2) # To define the ultimate number that the summation will land on # we first find the theta of summation then quantize it to block_size angels = torch.angle(sum_of_phasors) result = self.block_size * (angels / (2 * torch.pi)) # In cases where the two elements are inverse of each other # the sum will be 0 + 0j and it makes the final result to be nan. # We return the average of two operands in such a case. is_zero = torch.isclose(sum_of_phasors, torch.zeros_like(sum_of_phasors)) result = torch.where(is_zero, torch.mean(self, dim=-2, dtype=torch.float), result).round() return torch.remainder(result, self.block_size).type(self.dtype)
[docs] def bind(self, other: "MCRTensor") -> "MCRTensor": return super().bind(other)
[docs] def multibind(self) -> "MCRTensor": """Bind multiple hypervectors""" return super().multibind()
[docs] def inverse(self) -> "MCRTensor": return super().inverse()
[docs] def permute(self, shifts: int = 1) -> "MCRTensor": return super().permute(shifts=shifts)
[docs] def normalize(self) -> "MCRTensor": return super().normalize()
[docs] def dot_similarity(self, others: "MCRTensor", *, dtype=None) -> Tensor: return super().dot_similarity(others, dtype=dtype)
[docs] def cosine_similarity(self, others: "MCRTensor", *, dtype=None) -> Tensor: return super().cosine_similarity(others, dtype=dtype)