How to Use Operator Syntax

Every core operation has an operator form. The operators are thin wrappers around the named methods, so a + b and a.bundle(b) do the same thing. Use whichever reads more clearly for the expression at hand.

Operator-to-method map

Operator

Expression

Method

Operation

+

a + b

a.bundle(b)

Bundle

*

a * b

a.bind(b)

Bind

/

a / b

a.unbind(b)

Unbind

~

~a

a.inverse()

Binding inverse

>>

a >> k

a.permute(shift=k)

Permute (cyclic shift by +k)

<<

a << k

a.permute(shift=-k)

Permute (cyclic shift by -k)

Bundle, bind, unbind

The binary arithmetic operators route to the encoding’s bundle, bind, and unbind functions:

import pyhdc

enc   = pyhdc.MAP_C(dimension=10_000)
a     = enc.generate()
b     = enc.generate()

bundled = a + b            # same as a.bundle(b)
bound   = a * b            # same as a.bind(b)
back    = bound / a        # same as bound.unbind(a)

print(back.similarity(b))  # ~= 1.0

The operators dispatch through the encoding, so they obey the same batch rules as the methods. + and * chain left to right:

c       = enc.generate()
record  = a * b + a * c    # binds first, then bundles the two pairs

Inverse

~a returns the binding inverse, the same value as a.inverse():

enc = pyhdc.HRR(dimension=10_000)
a   = enc.generate()

inv = ~a                   # same as a.inverse()
# binding a with its inverse recovers the identity
print((a * inv).similarity(enc.generate()))

Permute with shifts

>> and << cyclically shift along axis 0 (the dimension). A right shift by k and a left shift by k are exact inverses, so (a >> 3) << 3 recovers a:

enc = pyhdc.MAP_C(dimension=10_000)
a   = enc.generate()

shifted = a >> 3           # same as a.permute(shift=3)
back    = shifted << 3     # same as a.permute(shift=-3)

print(back.similarity(a))  # ~= 1.0

Permute is defined for every encoding by default (as a cyclic shift), so >> and << never raise on the encoding family.

Right-hand-side rules

The shift operators accept an integer count and reject everything else. A Python int and NumPy integers (int32, int64) all work. A bool is rejected even though True and False are integers in Python, and a float or any non-integral value raises TypeError:

a >> 3            # ok: Python int
a >> np.int64(3)  # ok: NumPy integer
a >> True         # TypeError: bool is not accepted
a >> 3.0          # TypeError: float is not accepted

For +, *, and / the right operand must be a Hypervector. A non-Hypervector operand raises a standard TypeError, there are no reflected operators, so other + hv with a non-Hypervector left operand fails the same way:

a + 5             # TypeError: unsupported operand type(s)
5 + a             # TypeError: no reflected operator

Per-family caveats

The operators inherit every per-family restriction from the methods they call. / (unbind) and ~ (inverse) raise NotImplementedError for the families that do not define those operations.

Encoding

/ unbind

~ inverse

Notes

MAP_C

Yes

Raises

No inverse function; ~a raises NotImplementedError

MAP_I, MAP_I_Bits, MAP_B

Yes

Yes

Element-wise multiply is self-inverse

HRR family, FHRR

Yes

Yes

Circular convolution inverse

VTB, MBAT

Yes

Raises

No inverse function; ~a raises NotImplementedError

BSC

Yes

Yes

XOR is self-inverse

BSDC_S, BSDC_SEG, BSDC_THIN

Yes

Raises

No inverse function; ~a raises NotImplementedError

BSDC_CDT

Raises

Raises

Both / and ~ raise NotImplementedError

The non-element-wise binders (HRR circular convolution, shifting binders, matrix binding, VTB, BSDC_CDT) are applied per column under * and / on batched (ndim > 1) input, returning one batched result. See How to Bind and Unbind Key-Value Pairs for the per-family binding details.

See also

For the method-level reference and the full list of dunder definitions, see the Special methods section of Hypervector.