Skip to main content

embeddenator_vsa/
dimensional.rs

1//! Dimensional Configuration - Variable Precision Hyperdimensional Substrate
2//!
3//! This module implements configurable dimensional encoding where:
4//! - Number of dimensions is tunable (sparse vs dense tradeoff)
5//! - Trit depth per dimension is variable (precision vs storage tradeoff)
6//! - Differential encoding reduces actual storage to deltas from codebook
7//!
8//! # Mathematical Foundation
9//!
10//! Each dimension `d` has a configurable trit depth `T_d`:
11//! - 1 trit  = 3 states {-1, 0, +1} = 1.585 bits
12//! - 2 trits = 9 states = 3.17 bits
13//! - 3 trits = 27 states = 4.75 bits
14//! - N trits = 3^N states = N × 1.585 bits
15//!
16//! Total information capacity: Σ(T_d × 1.585) bits across all dimensions
17//!
18//! # Algebraic Primitives
19//!
20//! These operations form the low-level compute substrate:
21//! - **Bundle (⊕)**: Superposition - trit-wise majority voting
22//! - **Bind (⊙)**: Composition - trit-wise multiplication  
23//! - **Permute (ρ)**: Sequence encoding - cyclic index shift
24//! - **Cosine**: Similarity - normalized dot product
25//! - **Factorize**: Decomposition - resonator-based unbinding
26//!
27//! All operations preserve balanced ternary properties and are algebraically closed.
28
29use serde::{Deserialize, Serialize};
30use std::ops::{Mul, Neg};
31
32/// Single trit: balanced ternary digit {-1, 0, +1}
33#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
34#[repr(i8)]
35pub enum Trit {
36    Neg = -1,
37    Zero = 0,
38    Pos = 1,
39}
40
41impl Trit {
42    /// Multiply two trits (binding operation at trit level)
43    #[inline]
44    #[allow(clippy::should_implement_trait)]
45    pub fn mul(self, other: Trit) -> Trit {
46        match (self, other) {
47            (Trit::Zero, _) | (_, Trit::Zero) => Trit::Zero,
48            (Trit::Pos, Trit::Pos) | (Trit::Neg, Trit::Neg) => Trit::Pos,
49            (Trit::Pos, Trit::Neg) | (Trit::Neg, Trit::Pos) => Trit::Neg,
50        }
51    }
52
53    /// Add two trits with carry (returns sum, carry)
54    #[inline]
55    pub fn add_with_carry(self, other: Trit, carry_in: Trit) -> (Trit, Trit) {
56        let sum = (self as i8) + (other as i8) + (carry_in as i8);
57        match sum {
58            -3 => (Trit::Zero, Trit::Neg),
59            -2 => (Trit::Pos, Trit::Neg),
60            -1 => (Trit::Neg, Trit::Zero),
61            0 => (Trit::Zero, Trit::Zero),
62            1 => (Trit::Pos, Trit::Zero),
63            2 => (Trit::Neg, Trit::Pos),
64            3 => (Trit::Zero, Trit::Pos),
65            _ => unreachable!(),
66        }
67    }
68
69    /// Negate a trit
70    #[inline]
71    #[allow(clippy::should_implement_trait)]
72    pub fn neg(self) -> Trit {
73        match self {
74            Trit::Neg => Trit::Pos,
75            Trit::Zero => Trit::Zero,
76            Trit::Pos => Trit::Neg,
77        }
78    }
79
80    /// Convert from i8
81    #[inline]
82    pub fn from_i8(v: i8) -> Self {
83        match v.signum() {
84            -1 => Trit::Neg,
85            0 => Trit::Zero,
86            1 => Trit::Pos,
87            _ => unreachable!(),
88        }
89    }
90
91    /// Convert to i8
92    #[inline]
93    pub fn to_i8(self) -> i8 {
94        self as i8
95    }
96}
97
98impl Neg for Trit {
99    type Output = Trit;
100    fn neg(self) -> Trit {
101        Trit::neg(self)
102    }
103}
104
105impl Mul for Trit {
106    type Output = Trit;
107    fn mul(self, rhs: Trit) -> Trit {
108        Trit::mul(self, rhs)
109    }
110}
111
112/// Tryte: A group of trits (configurable size)
113/// Default is 6 trits = 729 states ≈ 9.51 bits
114#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
115pub struct Tryte {
116    /// The trits composing this tryte (LST first)
117    pub trits: Vec<Trit>,
118}
119
120impl Tryte {
121    /// Create a zero tryte of given size
122    pub fn zero(num_trits: usize) -> Self {
123        Tryte {
124            trits: vec![Trit::Zero; num_trits],
125        }
126    }
127
128    /// Create from integer value
129    pub fn from_i64(mut value: i64, num_trits: usize) -> Self {
130        let mut trits = Vec::with_capacity(num_trits);
131        let negative = value < 0;
132        if negative {
133            value = -value;
134        }
135
136        for _ in 0..num_trits {
137            let remainder = (value % 3) as i8;
138            value /= 3;
139
140            // Balanced ternary conversion
141            let trit = match remainder {
142                0 => Trit::Zero,
143                1 => Trit::Pos,
144                2 => {
145                    value += 1; // Carry
146                    Trit::Neg
147                }
148                _ => unreachable!(),
149            };
150            trits.push(if negative { -trit } else { trit });
151        }
152
153        Tryte { trits }
154    }
155
156    /// Convert to integer value
157    pub fn to_i64(&self) -> i64 {
158        let mut result: i64 = 0;
159        let mut power: i64 = 1;
160
161        for trit in &self.trits {
162            result += trit.to_i8() as i64 * power;
163            power *= 3;
164        }
165
166        result
167    }
168
169    /// Get the number of trits
170    pub fn len(&self) -> usize {
171        self.trits.len()
172    }
173
174    /// Check if empty
175    pub fn is_empty(&self) -> bool {
176        self.trits.is_empty()
177    }
178
179    /// Maximum representable value for this tryte size
180    pub fn max_value(num_trits: usize) -> i64 {
181        (3i64.pow(num_trits as u32) - 1) / 2
182    }
183
184    /// Minimum representable value for this tryte size  
185    pub fn min_value(num_trits: usize) -> i64 {
186        -Self::max_value(num_trits)
187    }
188
189    /// Trit-wise multiplication (bind at tryte level)
190    pub fn bind(&self, other: &Tryte) -> Tryte {
191        assert_eq!(self.len(), other.len(), "Tryte sizes must match for bind");
192        let trits = self
193            .trits
194            .iter()
195            .zip(other.trits.iter())
196            .map(|(&a, &b)| a * b)
197            .collect();
198        Tryte { trits }
199    }
200
201    /// Trit-wise majority voting (bundle at tryte level)
202    pub fn bundle(&self, other: &Tryte) -> Tryte {
203        assert_eq!(self.len(), other.len(), "Tryte sizes must match for bundle");
204        let trits = self
205            .trits
206            .iter()
207            .zip(other.trits.iter())
208            .map(|(&a, &b)| {
209                let sum = a.to_i8() + b.to_i8();
210                Trit::from_i8(sum.signum())
211            })
212            .collect();
213        Tryte { trits }
214    }
215
216    /// Dot product contribution (for cosine similarity)
217    pub fn dot(&self, other: &Tryte) -> i64 {
218        self.trits
219            .iter()
220            .zip(other.trits.iter())
221            .map(|(&a, &b)| (a.to_i8() * b.to_i8()) as i64)
222            .sum()
223    }
224
225    /// L2 norm squared (count of non-zero trits)
226    pub fn norm_squared(&self) -> i64 {
227        self.trits
228            .iter()
229            .map(|&t| if t == Trit::Zero { 0 } else { 1 })
230            .sum()
231    }
232}
233
234/// Configuration for dimensional encoding
235#[derive(Clone, Debug, Serialize, Deserialize)]
236pub struct DimensionalConfig {
237    /// Number of dimensions in the vector space
238    pub num_dimensions: usize,
239
240    /// Trit depth per dimension (uniform or variable)
241    pub trit_depth: TritDepthConfig,
242
243    /// Sparsity target (fraction of non-zero dimensions)
244    pub target_sparsity: f64,
245
246    /// Whether to use adaptive precision
247    pub adaptive_precision: bool,
248}
249
250/// Configuration for trit depth across dimensions
251#[derive(Clone, Debug, Serialize, Deserialize)]
252pub enum TritDepthConfig {
253    /// All dimensions have the same trit depth
254    Uniform(u8),
255
256    /// Variable trit depth per dimension
257    Variable(Vec<u8>),
258
259    /// Adaptive: start with base, expand where needed
260    Adaptive { base_depth: u8, max_depth: u8 },
261}
262
263impl Default for DimensionalConfig {
264    fn default() -> Self {
265        DimensionalConfig {
266            num_dimensions: 10_000,
267            trit_depth: TritDepthConfig::Uniform(6), // 6 trits = 729 states
268            target_sparsity: 0.02,                   // 2% non-zero
269            adaptive_precision: false,
270        }
271    }
272}
273
274impl DimensionalConfig {
275    /// Create a high-precision configuration
276    pub fn high_precision() -> Self {
277        DimensionalConfig {
278            num_dimensions: 100_000,
279            trit_depth: TritDepthConfig::Uniform(8), // 8 trits = 6561 states
280            target_sparsity: 0.002,
281            adaptive_precision: true,
282        }
283    }
284
285    /// Create a compact configuration for constrained environments
286    pub fn compact() -> Self {
287        DimensionalConfig {
288            num_dimensions: 4_096,
289            trit_depth: TritDepthConfig::Uniform(4), // 4 trits = 81 states
290            target_sparsity: 0.05,
291            adaptive_precision: false,
292        }
293    }
294
295    /// Create adaptive configuration
296    pub fn adaptive(num_dims: usize, base_depth: u8, max_depth: u8) -> Self {
297        DimensionalConfig {
298            num_dimensions: num_dims,
299            trit_depth: TritDepthConfig::Adaptive {
300                base_depth,
301                max_depth,
302            },
303            target_sparsity: 200.0 / num_dims as f64,
304            adaptive_precision: true,
305        }
306    }
307
308    /// Get trit depth for a specific dimension
309    pub fn depth_for_dimension(&self, dim: usize) -> u8 {
310        match &self.trit_depth {
311            TritDepthConfig::Uniform(d) => *d,
312            TritDepthConfig::Variable(depths) => depths.get(dim).copied().unwrap_or(6),
313            TritDepthConfig::Adaptive { base_depth, .. } => *base_depth,
314        }
315    }
316
317    /// Calculate total information capacity in bits
318    pub fn total_capacity_bits(&self) -> f64 {
319        let log2_3: f64 = 3.0f64.log2(); // ≈ 1.585
320
321        match &self.trit_depth {
322            TritDepthConfig::Uniform(d) => self.num_dimensions as f64 * (*d as f64) * log2_3,
323            TritDepthConfig::Variable(depths) => depths.iter().map(|&d| d as f64 * log2_3).sum(),
324            TritDepthConfig::Adaptive { base_depth, .. } => {
325                self.num_dimensions as f64 * (*base_depth as f64) * log2_3
326            }
327        }
328    }
329
330    /// Calculate expected storage size in bytes (sparse representation)
331    pub fn expected_storage_bytes(&self) -> usize {
332        let non_zero_dims = (self.num_dimensions as f64 * self.target_sparsity) as usize;
333        let avg_depth = match &self.trit_depth {
334            TritDepthConfig::Uniform(d) => *d as usize,
335            TritDepthConfig::Variable(depths) => {
336                depths.iter().map(|&d| d as usize).sum::<usize>() / depths.len().max(1)
337            }
338            TritDepthConfig::Adaptive { base_depth, .. } => *base_depth as usize,
339        };
340
341        // Index (4 bytes) + trits (ceil(avg_depth * 1.585 / 8)) per non-zero
342        non_zero_dims * (4 + (avg_depth * 2).div_ceil(8))
343    }
344}
345
346/// A hyperdimensional vector with configurable dimensional depth
347#[derive(Clone, Debug, Serialize, Deserialize)]
348pub struct HyperVec {
349    /// Configuration for this vector
350    pub config: DimensionalConfig,
351
352    /// Sparse storage: dimension index -> tryte value
353    /// Only non-zero dimensions are stored
354    pub dimensions: std::collections::BTreeMap<usize, Tryte>,
355}
356
357impl HyperVec {
358    /// Create a new zero vector
359    pub fn new(config: DimensionalConfig) -> Self {
360        HyperVec {
361            config,
362            dimensions: std::collections::BTreeMap::new(),
363        }
364    }
365
366    /// Create from dense representation
367    pub fn from_dense(config: DimensionalConfig, values: &[i64]) -> Self {
368        let mut vec = HyperVec::new(config.clone());
369
370        for (dim, &value) in values.iter().enumerate() {
371            if dim >= config.num_dimensions {
372                break;
373            }
374            if value != 0 {
375                let depth = config.depth_for_dimension(dim);
376                let tryte = Tryte::from_i64(value, depth as usize);
377                vec.dimensions.insert(dim, tryte);
378            }
379        }
380
381        vec
382    }
383
384    /// Get value at dimension (returns 0 for unset dimensions)
385    pub fn get(&self, dim: usize) -> i64 {
386        self.dimensions.get(&dim).map(|t| t.to_i64()).unwrap_or(0)
387    }
388
389    /// Set value at dimension
390    pub fn set(&mut self, dim: usize, value: i64) {
391        if value == 0 {
392            self.dimensions.remove(&dim);
393        } else {
394            let depth = self.config.depth_for_dimension(dim);
395            let tryte = Tryte::from_i64(value, depth as usize);
396            self.dimensions.insert(dim, tryte);
397        }
398    }
399
400    /// Number of non-zero dimensions (sparsity count)
401    pub fn nnz(&self) -> usize {
402        self.dimensions.len()
403    }
404
405    /// Current sparsity ratio
406    pub fn sparsity(&self) -> f64 {
407        self.dimensions.len() as f64 / self.config.num_dimensions as f64
408    }
409
410    /// Bundle operation (⊕): Superposition via majority voting
411    ///
412    /// Algebraic properties:
413    /// - Associative: (A ⊕ B) ⊕ C = A ⊕ (B ⊕ C)
414    /// - Commutative: A ⊕ B = B ⊕ A
415    /// - Preserves similarity to inputs
416    pub fn bundle(&self, other: &HyperVec) -> HyperVec {
417        assert_eq!(
418            self.config.num_dimensions, other.config.num_dimensions,
419            "Dimension count must match for bundle"
420        );
421
422        let mut result = HyperVec::new(self.config.clone());
423
424        // Union of all dimension indices
425        let all_dims: std::collections::BTreeSet<_> = self
426            .dimensions
427            .keys()
428            .chain(other.dimensions.keys())
429            .copied()
430            .collect();
431
432        for dim in all_dims {
433            let depth = self.config.depth_for_dimension(dim);
434            let zero = Tryte::zero(depth as usize);
435
436            let a = self.dimensions.get(&dim).unwrap_or(&zero);
437            let b = other.dimensions.get(&dim).unwrap_or(&zero);
438
439            let bundled = a.bundle(b);
440
441            // Only store if non-zero
442            if bundled.to_i64() != 0 {
443                result.dimensions.insert(dim, bundled);
444            }
445        }
446
447        result
448    }
449
450    /// Bind operation (⊙): Composition via trit-wise multiplication
451    ///
452    /// Algebraic properties:
453    /// - Self-inverse: A ⊙ A ≈ I (identity, up to sparsity)
454    /// - Distributes over bundle: A ⊙ (B ⊕ C) = (A ⊙ B) ⊕ (A ⊙ C)
455    /// - Creates quasi-orthogonal result
456    pub fn bind(&self, other: &HyperVec) -> HyperVec {
457        assert_eq!(
458            self.config.num_dimensions, other.config.num_dimensions,
459            "Dimension count must match for bind"
460        );
461
462        let mut result = HyperVec::new(self.config.clone());
463
464        // Only dimensions present in BOTH vectors contribute
465        for (dim, tryte_a) in &self.dimensions {
466            if let Some(tryte_b) = other.dimensions.get(dim) {
467                let bound = tryte_a.bind(tryte_b);
468                if bound.to_i64() != 0 {
469                    result.dimensions.insert(*dim, bound);
470                }
471            }
472        }
473
474        result
475    }
476
477    /// Permute operation (ρ): Cyclic index shift for sequence encoding
478    ///
479    /// Algebraic properties:
480    /// - ρ^n(A) cycles through orthogonal representations
481    /// - Preserves sparsity and magnitude
482    /// - Enables positional encoding
483    pub fn permute(&self, shift: usize) -> HyperVec {
484        let mut result = HyperVec::new(self.config.clone());
485        let n = self.config.num_dimensions;
486
487        for (&dim, tryte) in &self.dimensions {
488            let new_dim = (dim + shift) % n;
489            result.dimensions.insert(new_dim, tryte.clone());
490        }
491
492        result
493    }
494
495    /// Inverse permute (ρ⁻¹): Reverse cyclic shift
496    pub fn inverse_permute(&self, shift: usize) -> HyperVec {
497        let n = self.config.num_dimensions;
498        self.permute(n - (shift % n))
499    }
500
501    /// Cosine similarity: Normalized dot product
502    ///
503    /// Returns value in [-1, 1]:
504    /// - 1.0: Identical vectors
505    /// - 0.0: Orthogonal vectors
506    /// - -1.0: Opposite vectors
507    pub fn cosine(&self, other: &HyperVec) -> f64 {
508        let mut dot: i64 = 0;
509        let mut norm_a: i64 = 0;
510        let mut norm_b: i64 = 0;
511
512        // Calculate norms
513        for tryte in self.dimensions.values() {
514            norm_a += tryte.norm_squared();
515        }
516        for tryte in other.dimensions.values() {
517            norm_b += tryte.norm_squared();
518        }
519
520        // Calculate dot product (only overlapping dimensions contribute)
521        for (dim, tryte_a) in &self.dimensions {
522            if let Some(tryte_b) = other.dimensions.get(dim) {
523                dot += tryte_a.dot(tryte_b);
524            }
525        }
526
527        if norm_a == 0 || norm_b == 0 {
528            return 0.0;
529        }
530
531        dot as f64 / ((norm_a as f64).sqrt() * (norm_b as f64).sqrt())
532    }
533
534    /// Thin to target sparsity (Context-Dependent Thinning)
535    ///
536    /// Reduces vector density while preserving signal structure
537    pub fn thin(&self, target_nnz: usize) -> HyperVec {
538        if self.nnz() <= target_nnz {
539            return self.clone();
540        }
541
542        let mut result = HyperVec::new(self.config.clone());
543
544        // Sort by magnitude, keep highest
545        let mut indexed: Vec<_> = self
546            .dimensions
547            .iter()
548            .map(|(&d, t)| (d, t.clone(), t.to_i64().abs()))
549            .collect();
550
551        indexed.sort_by(|a, b| b.2.cmp(&a.2));
552
553        for (dim, tryte, _) in indexed.into_iter().take(target_nnz) {
554            result.dimensions.insert(dim, tryte);
555        }
556
557        result
558    }
559
560    /// Expand precision at specific dimension (adaptive depth)
561    pub fn expand_precision(&mut self, dim: usize, new_depth: u8) {
562        if let TritDepthConfig::Adaptive { max_depth, .. } = &self.config.trit_depth {
563            if new_depth > *max_depth {
564                return; // Can't exceed max
565            }
566        }
567
568        if let Some(tryte) = self.dimensions.get(&dim) {
569            let value = tryte.to_i64();
570            let new_tryte = Tryte::from_i64(value, new_depth as usize);
571            self.dimensions.insert(dim, new_tryte);
572        }
573    }
574
575    /// Pack to bytes for storage/transmission
576    pub fn pack(&self) -> Vec<u8> {
577        // Format: [num_entries: u32][entries...]
578        // Entry: [dim: u32][num_trits: u8][trit_data...]
579        let mut bytes = Vec::new();
580
581        // Number of entries
582        bytes.extend((self.dimensions.len() as u32).to_le_bytes());
583
584        for (&dim, tryte) in &self.dimensions {
585            // Dimension index
586            bytes.extend((dim as u32).to_le_bytes());
587            // Number of trits
588            bytes.push(tryte.len() as u8);
589            // Pack trits: 5 trits per byte (3^5 = 243 < 256)
590            let packed_trits = pack_trits(&tryte.trits);
591            bytes.extend(packed_trits);
592        }
593
594        bytes
595    }
596
597    /// Unpack from bytes
598    pub fn unpack(config: DimensionalConfig, bytes: &[u8]) -> Option<Self> {
599        if bytes.len() < 4 {
600            return None;
601        }
602
603        let num_entries = u32::from_le_bytes(bytes[0..4].try_into().ok()?) as usize;
604        let mut vec = HyperVec::new(config);
605        let mut offset = 4;
606
607        for _ in 0..num_entries {
608            if offset + 5 > bytes.len() {
609                return None;
610            }
611
612            let dim = u32::from_le_bytes(bytes[offset..offset + 4].try_into().ok()?) as usize;
613            offset += 4;
614            let num_trits = bytes[offset] as usize;
615            offset += 1;
616
617            let packed_bytes = (num_trits).div_ceil(5); // 5 trits per byte
618            if offset + packed_bytes > bytes.len() {
619                return None;
620            }
621
622            let trits = unpack_trits(&bytes[offset..offset + packed_bytes], num_trits);
623            offset += packed_bytes;
624
625            vec.dimensions.insert(dim, Tryte { trits });
626        }
627
628        Some(vec)
629    }
630}
631
632/// Pack trits into bytes (5 trits per byte)
633fn pack_trits(trits: &[Trit]) -> Vec<u8> {
634    let mut bytes = Vec::new();
635
636    for chunk in trits.chunks(5) {
637        let mut byte: u8 = 0;
638        let mut power: u8 = 1;
639
640        for &trit in chunk {
641            // Encode: -1 -> 2, 0 -> 0, +1 -> 1
642            let encoded = match trit {
643                Trit::Neg => 2,
644                Trit::Zero => 0,
645                Trit::Pos => 1,
646            };
647            byte += encoded * power;
648            power *= 3;
649        }
650
651        bytes.push(byte);
652    }
653
654    bytes
655}
656
657/// Unpack bytes to trits
658fn unpack_trits(bytes: &[u8], num_trits: usize) -> Vec<Trit> {
659    let mut trits = Vec::with_capacity(num_trits);
660
661    for &byte in bytes {
662        let mut remaining = byte;
663        for _ in 0..5 {
664            if trits.len() >= num_trits {
665                break;
666            }
667
668            let encoded = remaining % 3;
669            remaining /= 3;
670
671            // Decode: 2 -> -1, 0 -> 0, 1 -> +1
672            let trit = match encoded {
673                2 => Trit::Neg,
674                0 => Trit::Zero,
675                1 => Trit::Pos,
676                _ => unreachable!(),
677            };
678            trits.push(trit);
679        }
680    }
681
682    trits
683}
684
685/// Differential encoding result
686#[derive(Clone, Debug, Serialize, Deserialize)]
687pub struct DifferentialEncoding {
688    /// Coefficients against codebook basis (sparse)
689    pub coefficients: HyperVec,
690
691    /// Residual that couldn't be captured by basis
692    pub residual: HyperVec,
693
694    /// Dimensions that needed precision expansion
695    pub expanded_dims: Vec<(usize, u8)>,
696
697    /// Reconstruction quality (1.0 = perfect)
698    pub quality: f64,
699}
700
701/// Differential encoder using a codebook basis
702pub struct DifferentialEncoder {
703    /// Configuration
704    pub config: DimensionalConfig,
705
706    /// Basis vectors (the codebook)
707    pub basis: Vec<HyperVec>,
708
709    /// Threshold for considering a match
710    pub match_threshold: f64,
711
712    /// Threshold for expanding precision
713    pub precision_threshold: f64,
714}
715
716impl DifferentialEncoder {
717    /// Create a new encoder
718    pub fn new(config: DimensionalConfig) -> Self {
719        DifferentialEncoder {
720            config,
721            basis: Vec::new(),
722            match_threshold: 0.3,
723            precision_threshold: 0.001,
724        }
725    }
726
727    /// Add a basis vector to the codebook
728    pub fn add_basis(&mut self, vector: HyperVec) {
729        self.basis.push(vector);
730    }
731
732    /// Encode data differentially against the codebook
733    pub fn encode(&self, data: &HyperVec) -> DifferentialEncoding {
734        let mut coefficients = HyperVec::new(self.config.clone());
735        let mut expanded_dims = Vec::new();
736
737        // Project data onto each basis vector
738        for (basis_idx, basis_vec) in self.basis.iter().enumerate() {
739            let similarity = data.cosine(basis_vec);
740
741            if similarity.abs() > self.match_threshold {
742                // Quantize coefficient to balanced ternary
743                let coef_value = (similarity * 100.0) as i64;
744                coefficients.set(basis_idx, coef_value);
745            }
746        }
747
748        // Compute residual (what basis couldn't capture)
749        let reconstructed = self.reconstruct_from_coefficients(&coefficients);
750        let mut residual = HyperVec::new(self.config.clone());
751
752        for dim in 0..self.config.num_dimensions {
753            let original_val = data.get(dim);
754            let reconstructed_val = reconstructed.get(dim);
755            let diff = original_val - reconstructed_val;
756
757            if diff.abs() > 0 {
758                residual.set(dim, diff);
759
760                // Check if we need more precision
761                let relative_error = if original_val != 0 {
762                    (diff as f64).abs() / (original_val as f64).abs()
763                } else {
764                    0.0
765                };
766
767                if relative_error > self.precision_threshold {
768                    if let TritDepthConfig::Adaptive {
769                        base_depth,
770                        max_depth,
771                    } = &self.config.trit_depth
772                    {
773                        let new_depth = (*base_depth + 2).min(*max_depth);
774                        expanded_dims.push((dim, new_depth));
775                    }
776                }
777            }
778        }
779
780        // Calculate quality
781        let quality = self.calculate_quality(data, &coefficients, &residual);
782
783        DifferentialEncoding {
784            coefficients,
785            residual,
786            expanded_dims,
787            quality,
788        }
789    }
790
791    /// Reconstruct from coefficients only
792    fn reconstruct_from_coefficients(&self, coefficients: &HyperVec) -> HyperVec {
793        let mut result = HyperVec::new(self.config.clone());
794
795        for (&basis_idx, coef_tryte) in &coefficients.dimensions {
796            if let Some(basis_vec) = self.basis.get(basis_idx) {
797                let coef = coef_tryte.to_i64() as f64 / 100.0;
798
799                // Scale basis vector by coefficient and bundle
800                for (&dim, tryte) in &basis_vec.dimensions {
801                    let scaled = (tryte.to_i64() as f64 * coef) as i64;
802                    let current = result.get(dim);
803                    result.set(dim, current + scaled);
804                }
805            }
806        }
807
808        result
809    }
810
811    /// Full reconstruction from differential encoding
812    pub fn decode(&self, encoding: &DifferentialEncoding) -> HyperVec {
813        let mut result = self.reconstruct_from_coefficients(&encoding.coefficients);
814
815        // Add residual corrections
816        for (&dim, tryte) in &encoding.residual.dimensions {
817            let current = result.get(dim);
818            result.set(dim, current + tryte.to_i64());
819        }
820
821        result
822    }
823
824    /// Calculate reconstruction quality
825    fn calculate_quality(
826        &self,
827        original: &HyperVec,
828        coefficients: &HyperVec,
829        residual: &HyperVec,
830    ) -> f64 {
831        let reconstructed = self.reconstruct_from_coefficients(coefficients);
832
833        let mut total_error: f64 = 0.0;
834        let mut total_energy: f64 = 0.0;
835
836        for dim in 0..self.config.num_dimensions {
837            let orig = original.get(dim) as f64;
838            let recon = reconstructed.get(dim) as f64;
839            let res = residual.get(dim) as f64;
840
841            total_energy += orig * orig;
842            total_error += (orig - recon - res).powi(2);
843        }
844
845        if total_energy == 0.0 {
846            return 1.0;
847        }
848
849        1.0 - (total_error / total_energy).sqrt()
850    }
851}
852
853#[cfg(test)]
854mod tests {
855    use super::*;
856
857    #[test]
858    fn test_trit_multiplication() {
859        assert_eq!(Trit::Pos * Trit::Pos, Trit::Pos);
860        assert_eq!(Trit::Pos * Trit::Neg, Trit::Neg);
861        assert_eq!(Trit::Neg * Trit::Neg, Trit::Pos);
862        assert_eq!(Trit::Zero * Trit::Pos, Trit::Zero);
863    }
864
865    #[test]
866    fn test_tryte_roundtrip() {
867        let test_values = [0i64, 1, -1, 42, -42, 100, -100, 364, -364];
868
869        for &value in &test_values {
870            let tryte = Tryte::from_i64(value, 6);
871            let decoded = tryte.to_i64();
872            assert_eq!(value, decoded, "Roundtrip failed for {}", value);
873        }
874    }
875
876    #[test]
877    fn test_tryte_max_values() {
878        // 6 trits = 729 states = range [-364, 364]
879        assert_eq!(Tryte::max_value(6), 364);
880        assert_eq!(Tryte::min_value(6), -364);
881
882        // 8 trits = 6561 states = range [-3280, 3280]
883        assert_eq!(Tryte::max_value(8), 3280);
884    }
885
886    #[test]
887    fn test_hypervec_bundle_commutative() {
888        let config = DimensionalConfig::default();
889        let a = HyperVec::from_dense(config.clone(), &[1, 0, -1, 1, 0]);
890        let b = HyperVec::from_dense(config.clone(), &[0, 1, -1, 0, 1]);
891
892        let ab = a.bundle(&b);
893        let ba = b.bundle(&a);
894
895        assert_eq!(ab.dimensions.len(), ba.dimensions.len());
896        for (dim, tryte) in &ab.dimensions {
897            assert_eq!(tryte.to_i64(), ba.get(*dim));
898        }
899    }
900
901    #[test]
902    fn test_hypervec_bind_self_inverse() {
903        let config = DimensionalConfig::default();
904        let mut a = HyperVec::new(config.clone());
905        a.set(0, 1);
906        a.set(1, -1);
907        a.set(2, 1);
908
909        // A ⊙ A should produce all 1s (positive) for non-zero dimensions
910        let aa = a.bind(&a);
911
912        for tryte in aa.dimensions.values() {
913            assert_eq!(tryte.to_i64(), 1, "Self-bind should produce +1");
914        }
915    }
916
917    #[test]
918    fn test_hypervec_permute_inverse() {
919        let config = DimensionalConfig::compact();
920        let mut a = HyperVec::new(config.clone());
921        a.set(0, 1);
922        a.set(100, -1);
923        a.set(500, 1);
924
925        let permuted = a.permute(1000);
926        let recovered = permuted.inverse_permute(1000);
927
928        assert_eq!(a.get(0), recovered.get(0));
929        assert_eq!(a.get(100), recovered.get(100));
930        assert_eq!(a.get(500), recovered.get(500));
931    }
932
933    #[test]
934    fn test_hypervec_cosine_identical() {
935        let config = DimensionalConfig::default();
936        let mut a = HyperVec::new(config.clone());
937        a.set(0, 1);
938        a.set(1, -1);
939        a.set(2, 1);
940
941        let similarity = a.cosine(&a);
942        assert!(
943            (similarity - 1.0).abs() < 0.001,
944            "Self-similarity should be 1.0"
945        );
946    }
947
948    #[test]
949    fn test_pack_unpack_roundtrip() {
950        let config = DimensionalConfig::compact();
951        let mut vec = HyperVec::new(config.clone());
952        vec.set(0, 42);
953        vec.set(100, -17);
954        vec.set(1000, 100);
955
956        let packed = vec.pack();
957        let unpacked = HyperVec::unpack(config, &packed)
958            .expect("HyperVec unpack must succeed for valid packed data");
959
960        assert_eq!(vec.get(0), unpacked.get(0));
961        assert_eq!(vec.get(100), unpacked.get(100));
962        assert_eq!(vec.get(1000), unpacked.get(1000));
963    }
964
965    #[test]
966    fn test_differential_encoding() {
967        let config = DimensionalConfig::compact();
968        let mut encoder = DifferentialEncoder::new(config.clone());
969
970        // Add some basis vectors
971        let mut basis1 = HyperVec::new(config.clone());
972        basis1.set(0, 1);
973        basis1.set(1, 1);
974        encoder.add_basis(basis1);
975
976        // Create data similar to basis
977        let mut data = HyperVec::new(config.clone());
978        data.set(0, 1);
979        data.set(1, 1);
980        data.set(2, 1); // Extra dimension
981
982        let encoding = encoder.encode(&data);
983        let decoded = encoder.decode(&encoding);
984
985        // Should reconstruct original
986        assert_eq!(data.get(0), decoded.get(0));
987        assert_eq!(data.get(1), decoded.get(1));
988        assert_eq!(data.get(2), decoded.get(2));
989    }
990}