1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
//! `HoloSequence` — an ordered sequence packed into one hypervector by binding
//! each element to its position with a permutation.
//!
//! Bundling alone is a *set*: it forgets order. To remember position `i`, rotate
//! the element's hypervector by `i` before bundling — `memory = Σ ρ^i(atom(s_i))`.
//! Rotation is a permutation that makes the same symbol look different at each
//! position, so reading position `i` back is just the inverse rotation followed by
//! cleanup. Decoding the whole sequence is one batched matmul: every position's
//! un-rotated probe is cleaned up against the codebook at once.
use crate::codebook::Codebook;
use crate::hyper::Hyper;
/// An ordered sequence of symbol ids stored in one superposition vector.
#[derive(Clone)]
pub struct HoloSequence {
codebook: Codebook,
memory: Hyper,
len: usize,
}
impl HoloSequence {
/// Create an empty sequence over a `dim`-dimensional space with `alphabet`
/// possible symbols.
pub fn new(dim: usize, alphabet: usize, seed: u64) -> Self {
let codebook = Codebook::new(dim, alphabet, seed);
let memory = Hyper::zeros(dim);
HoloSequence {
codebook,
memory,
len: 0,
}
}
/// Hypervector dimensionality.
#[inline]
pub fn dim(&self) -> usize {
self.codebook.dim()
}
/// Current sequence length.
#[inline]
pub fn len(&self) -> usize {
self.len
}
/// True if the sequence is empty.
#[inline]
pub fn is_empty(&self) -> bool {
self.len == 0
}
/// The underlying superposition vector — the whole sequence in one tensor.
#[inline]
pub fn memory(&self) -> &Hyper {
&self.memory
}
/// Append `id` to the end of the sequence, binding it to its position by a
/// rotation of `len`.
pub fn push(&mut self, id: usize) {
let positioned = self.codebook.atom(id).permute(self.len);
self.memory.bundle_into(&positioned);
self.len += 1;
}
/// The un-rotated probe for position `pos`, ready for cleanup.
fn probe(&self, pos: usize) -> Hyper {
self.memory.inverse_permute(pos)
}
/// Read the symbol at `pos`, returning `(symbol_id, score)`.
pub fn get(&self, pos: usize) -> (usize, f32) {
self.codebook.cleanup(&self.probe(pos))
}
/// Decode the whole sequence with a single batched matmul over all positions.
pub fn decode(&self) -> Vec<usize> {
let probes: Vec<Hyper> = (0..self.len).map(|p| self.probe(p)).collect();
self.codebook
.cleanup_batch(&probes)
.into_iter()
.map(|(id, _)| id)
.collect()
}
}
impl core::fmt::Debug for HoloSequence {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
f,
"HoloSequence {{ dim: {}, len: {} }}",
self.dim(),
self.len
)
}
}