Skip to main content

embeddenator_vsa/
ternary.rs

1//! Foundational Balanced Ternary Primitives
2//!
3//! This module implements the mathematically rigorous single-trit layer.
4//! Everything builds on these primitives - they MUST be proven correct.
5//!
6//! # Representation
7//!
8//! Single Trit: {N, Z, P} = {-1, 0, +1}
9//! - N (Negative): -1
10//! - Z (Zero): 0  
11//! - P (Positive): +1
12//!
13//! # Algebraic Properties (Must Hold)
14//!
15//! ## Addition (Bundle primitive)
16//! - Commutative: a + b = b + a
17//! - Associative: (a + b) + c = a + (b + c)
18//! - Identity: a + Z = a
19//! - Inverse: a + (-a) = Z
20//!
21//! ## Multiplication (Bind primitive)
22//! - Commutative: a × b = b × a
23//! - Associative: (a × b) × c = a × (b × c)
24//! - Identity: a × P = a
25//! - Self-inverse: a × a = P (for non-zero)
26//! - Zero annihilator: a × Z = Z
27//!
28//! # Reconstruction Guarantee
29//!
30//! The key insight: VSA operations are "approximate" only when you lose
31//! information through superposition without tracking. We guarantee
32//! reconstruction by:
33//!
34//! 1. Exact residual storage for anything basis can't capture
35//! 2. Codebook lookup (not similarity matching) for known patterns
36//! 3. Semantic markers for high-entropy regions
37//! 4. Parity trits for error detection
38
39use serde::{Deserialize, Serialize};
40use std::fmt;
41
42/// Single balanced ternary digit: the atomic unit
43///
44/// This is THE foundational type. All math builds on this.
45#[derive(Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
46#[repr(i8)]
47pub enum Trit {
48    /// Negative: -1
49    N = -1,
50    /// Zero: 0
51    #[default]
52    Z = 0,
53    /// Positive: +1
54    P = 1,
55}
56
57impl fmt::Debug for Trit {
58    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59        match self {
60            Trit::N => write!(f, "N"),
61            Trit::Z => write!(f, "Z"),
62            Trit::P => write!(f, "P"),
63        }
64    }
65}
66
67impl fmt::Display for Trit {
68    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
69        match self {
70            Trit::N => write!(f, "-"),
71            Trit::Z => write!(f, "0"),
72            Trit::P => write!(f, "+"),
73        }
74    }
75}
76
77impl Trit {
78    /// All possible trit values in order
79    pub const ALL: [Trit; 3] = [Trit::N, Trit::Z, Trit::P];
80
81    /// Convert from i8 with clamping
82    #[inline]
83    pub const fn from_i8_clamped(v: i8) -> Self {
84        match v {
85            i8::MIN..=-1 => Trit::N,
86            0 => Trit::Z,
87            1..=i8::MAX => Trit::P,
88        }
89    }
90
91    /// Convert from i8, returning None if out of range
92    #[inline]
93    pub const fn from_i8_exact(v: i8) -> Option<Self> {
94        match v {
95            -1 => Some(Trit::N),
96            0 => Some(Trit::Z),
97            1 => Some(Trit::P),
98            _ => None,
99        }
100    }
101
102    /// Convert to i8
103    #[inline]
104    pub const fn to_i8(self) -> i8 {
105        self as i8
106    }
107
108    /// Negate: -N = P, -Z = Z, -P = N
109    #[inline]
110    pub const fn neg(self) -> Trit {
111        match self {
112            Trit::N => Trit::P,
113            Trit::Z => Trit::Z,
114            Trit::P => Trit::N,
115        }
116    }
117
118    /// Absolute value: |N| = P, |Z| = Z, |P| = P
119    #[inline]
120    pub const fn abs(self) -> Trit {
121        match self {
122            Trit::N => Trit::P,
123            Trit::Z => Trit::Z,
124            Trit::P => Trit::P,
125        }
126    }
127
128    /// Sign: returns -1, 0, or 1
129    #[inline]
130    pub const fn sign(self) -> i8 {
131        self as i8
132    }
133
134    /// Is zero?
135    #[inline]
136    pub const fn is_zero(self) -> bool {
137        matches!(self, Trit::Z)
138    }
139
140    /// Is non-zero?
141    #[inline]
142    pub const fn is_nonzero(self) -> bool {
143        !self.is_zero()
144    }
145
146    /// Trit multiplication (bind operation)
147    ///
148    /// Truth table:
149    /// ```text
150    ///   × | N  Z  P
151    /// ----+--------
152    ///   N | P  Z  N
153    ///   Z | Z  Z  Z
154    ///   P | N  Z  P
155    /// ```
156    ///
157    /// Key property: a × a = P for a ∈ {N, P} (self-inverse)
158    #[inline]
159    pub const fn mul(self, other: Trit) -> Trit {
160        match (self, other) {
161            (Trit::Z, _) | (_, Trit::Z) => Trit::Z,
162            (Trit::P, Trit::P) | (Trit::N, Trit::N) => Trit::P,
163            (Trit::P, Trit::N) | (Trit::N, Trit::P) => Trit::N,
164        }
165    }
166
167    /// Trit addition with carry (for multi-trit arithmetic)
168    ///
169    /// Returns (sum, carry) where both are trits
170    ///
171    /// In balanced ternary:
172    /// - Sum of 3 trits ranges from -3 to +3
173    /// - We express this as (sum_trit, carry_trit) where result = sum + 3*carry
174    #[inline]
175    pub const fn add_with_carry(self, other: Trit, carry_in: Trit) -> (Trit, Trit) {
176        let sum = self.to_i8() + other.to_i8() + carry_in.to_i8();
177        match sum {
178            -3 => (Trit::Z, Trit::N), // -3 = 0 + 3×(-1)
179            -2 => (Trit::P, Trit::N), // -2 = 1 + 3×(-1)
180            -1 => (Trit::N, Trit::Z), // -1 = -1 + 3×0
181            0 => (Trit::Z, Trit::Z),  // 0 = 0 + 3×0
182            1 => (Trit::P, Trit::Z),  // 1 = 1 + 3×0
183            2 => (Trit::N, Trit::P),  // 2 = -1 + 3×1
184            3 => (Trit::Z, Trit::P),  // 3 = 0 + 3×1
185            _ => unreachable!(),
186        }
187    }
188
189    /// Simple trit addition (saturating to trit range)
190    /// Used for bundle majority voting
191    #[inline]
192    pub const fn add_saturating(self, other: Trit) -> Trit {
193        let sum = self.to_i8() + other.to_i8();
194        Trit::from_i8_clamped(sum)
195    }
196
197    /// Majority of three trits (used in multi-way bundling)
198    #[inline]
199    pub const fn majority3(a: Trit, b: Trit, c: Trit) -> Trit {
200        let sum = a.to_i8() + b.to_i8() + c.to_i8();
201        Trit::from_i8_clamped(sum)
202    }
203
204    /// Encode two bits as a trit (with one invalid state)
205    /// 00 -> Z, 01 -> P, 10 -> N, 11 -> invalid (returns None)
206    #[inline]
207    pub const fn from_bits(b1: bool, b0: bool) -> Option<Trit> {
208        match (b1, b0) {
209            (false, false) => Some(Trit::Z),
210            (false, true) => Some(Trit::P),
211            (true, false) => Some(Trit::N),
212            (true, true) => None, // Invalid encoding
213        }
214    }
215
216    /// Decode trit to two bits
217    /// Z -> (0, 0), P -> (0, 1), N -> (1, 0)
218    #[inline]
219    pub const fn to_bits(self) -> (bool, bool) {
220        match self {
221            Trit::Z => (false, false),
222            Trit::P => (false, true),
223            Trit::N => (true, false),
224        }
225    }
226}
227
228impl std::ops::Neg for Trit {
229    type Output = Trit;
230    #[inline]
231    fn neg(self) -> Trit {
232        Trit::neg(self)
233    }
234}
235
236impl std::ops::Mul for Trit {
237    type Output = Trit;
238    #[inline]
239    fn mul(self, rhs: Trit) -> Trit {
240        Trit::mul(self, rhs)
241    }
242}
243
244impl std::ops::MulAssign for Trit {
245    #[inline]
246    fn mul_assign(&mut self, rhs: Trit) {
247        *self = *self * rhs;
248    }
249}
250
251/// A tryte: exactly 3 trits = 27 states
252///
253/// Range: -13 to +13 in balanced ternary
254///
255/// Layout: [trit0 (LST), trit1, trit2 (MST)]
256/// Value = trit0 + 3×trit1 + 9×trit2
257#[derive(Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
258pub struct Tryte3 {
259    /// Three trits, index 0 is least significant
260    pub trits: [Trit; 3],
261}
262
263impl fmt::Debug for Tryte3 {
264    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
265        write!(
266            f,
267            "Tryte3[{}{}{} = {}]",
268            self.trits[2],
269            self.trits[1],
270            self.trits[0],
271            self.to_i8()
272        )
273    }
274}
275
276impl fmt::Display for Tryte3 {
277    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
278        write!(f, "{}{}{}", self.trits[2], self.trits[1], self.trits[0])
279    }
280}
281
282impl Tryte3 {
283    /// Zero tryte
284    pub const ZERO: Tryte3 = Tryte3 {
285        trits: [Trit::Z, Trit::Z, Trit::Z],
286    };
287
288    /// Maximum value: +++ = 13
289    pub const MAX: Tryte3 = Tryte3 {
290        trits: [Trit::P, Trit::P, Trit::P],
291    };
292
293    /// Minimum value: --- = -13
294    pub const MIN: Tryte3 = Tryte3 {
295        trits: [Trit::N, Trit::N, Trit::N],
296    };
297
298    /// Maximum representable value
299    pub const MAX_VALUE: i8 = 13;
300
301    /// Minimum representable value
302    pub const MIN_VALUE: i8 = -13;
303
304    /// Number of distinct states
305    pub const NUM_STATES: u8 = 27;
306
307    /// Create from three trits [LST, middle, MST]
308    #[inline]
309    pub const fn new(t0: Trit, t1: Trit, t2: Trit) -> Self {
310        Tryte3 {
311            trits: [t0, t1, t2],
312        }
313    }
314
315    /// Create from integer value (-13 to 13)
316    pub const fn from_i8(mut value: i8) -> Option<Self> {
317        if value < Self::MIN_VALUE || value > Self::MAX_VALUE {
318            return None;
319        }
320
321        let negative = value < 0;
322        if negative {
323            value = -value;
324        }
325
326        let mut trits = [Trit::Z; 3];
327        let mut i = 0;
328
329        while i < 3 && value != 0 {
330            let remainder = value % 3;
331            value /= 3;
332
333            trits[i] = match remainder {
334                0 => Trit::Z,
335                1 => Trit::P,
336                2 => {
337                    value += 1; // Carry
338                    Trit::N
339                }
340                _ => return None, // unreachable
341            };
342            i += 1;
343        }
344
345        if negative {
346            trits[0] = trits[0].neg();
347            trits[1] = trits[1].neg();
348            trits[2] = trits[2].neg();
349        }
350
351        Some(Tryte3 { trits })
352    }
353
354    /// Convert to integer value
355    #[inline]
356    pub const fn to_i8(self) -> i8 {
357        self.trits[0].to_i8() + 3 * self.trits[1].to_i8() + 9 * self.trits[2].to_i8()
358    }
359
360    /// Negate all trits
361    #[inline]
362    pub const fn neg(self) -> Self {
363        Tryte3 {
364            trits: [
365                self.trits[0].neg(),
366                self.trits[1].neg(),
367                self.trits[2].neg(),
368            ],
369        }
370    }
371
372    /// Trit-wise multiplication (bind)
373    #[inline]
374    pub const fn mul(self, other: Tryte3) -> Tryte3 {
375        Tryte3 {
376            trits: [
377                self.trits[0].mul(other.trits[0]),
378                self.trits[1].mul(other.trits[1]),
379                self.trits[2].mul(other.trits[2]),
380            ],
381        }
382    }
383
384    /// Trit-wise majority voting (bundle)
385    #[inline]
386    pub const fn bundle(self, other: Tryte3) -> Tryte3 {
387        Tryte3 {
388            trits: [
389                self.trits[0].add_saturating(other.trits[0]),
390                self.trits[1].add_saturating(other.trits[1]),
391                self.trits[2].add_saturating(other.trits[2]),
392            ],
393        }
394    }
395
396    /// Arithmetic addition with carry out
397    pub const fn add_with_carry(self, other: Tryte3, carry_in: Trit) -> (Tryte3, Trit) {
398        let (t0, c0) = self.trits[0].add_with_carry(other.trits[0], carry_in);
399        let (t1, c1) = self.trits[1].add_with_carry(other.trits[1], c0);
400        let (t2, c2) = self.trits[2].add_with_carry(other.trits[2], c1);
401
402        (
403            Tryte3 {
404                trits: [t0, t1, t2],
405            },
406            c2,
407        )
408    }
409
410    /// Dot product (for similarity)
411    #[inline]
412    pub const fn dot(self, other: Tryte3) -> i8 {
413        self.trits[0].to_i8() * other.trits[0].to_i8()
414            + self.trits[1].to_i8() * other.trits[1].to_i8()
415            + self.trits[2].to_i8() * other.trits[2].to_i8()
416    }
417
418    /// Count non-zero trits
419    #[inline]
420    pub const fn nnz(self) -> u8 {
421        self.trits[0].is_nonzero() as u8
422            + self.trits[1].is_nonzero() as u8
423            + self.trits[2].is_nonzero() as u8
424    }
425
426    /// Pack into a single byte (5 bits needed for 27 states)
427    /// Returns value 0-26
428    #[inline]
429    pub const fn pack(self) -> u8 {
430        // Map each trit: N->0, Z->1, P->2, then compute base-3 number
431        let t0 = (self.trits[0].to_i8() + 1) as u8; // 0, 1, 2
432        let t1 = (self.trits[1].to_i8() + 1) as u8;
433        let t2 = (self.trits[2].to_i8() + 1) as u8;
434        t0 + 3 * t1 + 9 * t2
435    }
436
437    /// Unpack from byte (value 0-26)
438    #[inline]
439    pub const fn unpack(byte: u8) -> Option<Self> {
440        if byte >= 27 {
441            return None;
442        }
443
444        let t0 = (byte % 3) as i8 - 1;
445        let t1 = ((byte / 3) % 3) as i8 - 1;
446        let t2 = (byte / 9) as i8 - 1;
447
448        Some(Tryte3 {
449            trits: [
450                Trit::from_i8_clamped(t0),
451                Trit::from_i8_clamped(t1),
452                Trit::from_i8_clamped(t2),
453            ],
454        })
455    }
456}
457
458impl std::ops::Neg for Tryte3 {
459    type Output = Tryte3;
460    fn neg(self) -> Tryte3 {
461        Tryte3::neg(self)
462    }
463}
464
465impl std::ops::Mul for Tryte3 {
466    type Output = Tryte3;
467    fn mul(self, rhs: Tryte3) -> Tryte3 {
468        Tryte3::mul(self, rhs)
469    }
470}
471
472/// A word: 6 trits = 729 states ≈ 9.51 bits
473///
474/// Range: -364 to +364 in balanced ternary
475///
476/// This fits nicely in operations and provides good precision
477/// for coefficients and residuals.
478#[derive(Clone, Copy, PartialEq, Eq, Hash, Default, Serialize, Deserialize)]
479pub struct Word6 {
480    /// Two trytes
481    pub low: Tryte3,
482    pub high: Tryte3,
483}
484
485impl fmt::Debug for Word6 {
486    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
487        write!(f, "Word6[{}{} = {}]", self.high, self.low, self.to_i16())
488    }
489}
490
491impl Word6 {
492    /// Zero word
493    pub const ZERO: Word6 = Word6 {
494        low: Tryte3::ZERO,
495        high: Tryte3::ZERO,
496    };
497
498    /// Maximum value: ++++++ = 364
499    pub const MAX_VALUE: i16 = 364;
500
501    /// Minimum value: ------ = -364
502    pub const MIN_VALUE: i16 = -364;
503
504    /// Number of distinct states
505    pub const NUM_STATES: u16 = 729;
506
507    /// Create from integer value
508    pub fn from_i16(value: i16) -> Option<Self> {
509        if !(Self::MIN_VALUE..=Self::MAX_VALUE).contains(&value) {
510            return None;
511        }
512
513        // Split into low and high trytes
514        // low = value mod 27 (in balanced ternary sense)
515        // high = value / 27
516
517        let mut v = value;
518        let negative = v < 0;
519        if negative {
520            v = -v;
521        }
522
523        // Convert to base-27 with balanced representation
524        let low_val = balanced_mod(v, 27);
525        let high_val = balanced_div(v, 27);
526
527        let low = Tryte3::from_i8(if negative { -low_val } else { low_val })?;
528        let high = Tryte3::from_i8(if negative { -high_val } else { high_val })?;
529
530        Some(Word6 { low, high })
531    }
532
533    /// Convert to integer value
534    #[inline]
535    pub fn to_i16(self) -> i16 {
536        self.low.to_i8() as i16 + 27 * self.high.to_i8() as i16
537    }
538
539    /// Trit-wise multiplication (bind)
540    #[allow(clippy::should_implement_trait)]
541    pub fn mul(self, other: Word6) -> Word6 {
542        Word6 {
543            low: self.low.mul(other.low),
544            high: self.high.mul(other.high),
545        }
546    }
547
548    /// Trit-wise majority (bundle)
549    pub fn bundle(self, other: Word6) -> Word6 {
550        Word6 {
551            low: self.low.bundle(other.low),
552            high: self.high.bundle(other.high),
553        }
554    }
555
556    /// Pack into 10 bits (stored in u16)
557    pub fn pack(self) -> u16 {
558        self.low.pack() as u16 + 27 * self.high.pack() as u16
559    }
560
561    /// Unpack from 10 bits
562    pub fn unpack(bits: u16) -> Option<Self> {
563        if bits >= 729 {
564            return None;
565        }
566        let low = Tryte3::unpack((bits % 27) as u8)?;
567        let high = Tryte3::unpack((bits / 27) as u8)?;
568        Some(Word6 { low, high })
569    }
570}
571
572/// Balanced modulo: result in range [-(n-1)/2, (n-1)/2]
573const fn balanced_mod(value: i16, n: i16) -> i8 {
574    let r = value % n;
575    if r > n / 2 {
576        (r - n) as i8
577    } else if r < -(n / 2) {
578        (r + n) as i8
579    } else {
580        r as i8
581    }
582}
583
584/// Balanced division companion to balanced_mod
585const fn balanced_div(value: i16, n: i16) -> i8 {
586    let r = balanced_mod(value, n);
587    ((value - r as i16) / n) as i8
588}
589
590/// Reconstruction correction entry
591///
592/// This is the key to 100% reconstruction guarantee.
593/// When VSA operations produce approximation errors,
594/// we store exact corrections here.
595#[derive(Clone, Debug, Serialize, Deserialize)]
596pub struct CorrectionEntry {
597    /// Position in the data
598    pub position: u64,
599    /// The exact value that should be there
600    pub exact_value: Vec<u8>,
601    /// Hash of the original for verification
602    pub verification_hash: [u8; 8],
603}
604
605/// Parity trit for error detection
606///
607/// Computed as: sum of all trits mod 3, balanced
608#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
609pub struct ParityTrit(pub Trit);
610
611impl ParityTrit {
612    /// Compute parity for a slice of trits
613    pub fn compute(trits: &[Trit]) -> Self {
614        let sum: i32 = trits.iter().map(|t| t.to_i8() as i32).sum();
615        let parity = sum.rem_euclid(3);
616        ParityTrit(match parity {
617            0 => Trit::Z,
618            1 => Trit::P,
619            2 => Trit::N,
620            _ => unreachable!(),
621        })
622    }
623
624    /// Verify parity matches
625    pub fn verify(&self, trits: &[Trit]) -> bool {
626        Self::compute(trits) == *self
627    }
628}
629
630#[cfg(test)]
631mod tests {
632    use super::*;
633
634    // ==================== TRIT TESTS ====================
635
636    #[test]
637    fn test_trit_multiplication_truth_table() {
638        // Complete truth table verification
639        assert_eq!(Trit::N * Trit::N, Trit::P, "N × N = P");
640        assert_eq!(Trit::N * Trit::Z, Trit::Z, "N × Z = Z");
641        assert_eq!(Trit::N * Trit::P, Trit::N, "N × P = N");
642        assert_eq!(Trit::Z * Trit::N, Trit::Z, "Z × N = Z");
643        assert_eq!(Trit::Z * Trit::Z, Trit::Z, "Z × Z = Z");
644        assert_eq!(Trit::Z * Trit::P, Trit::Z, "Z × P = Z");
645        assert_eq!(Trit::P * Trit::N, Trit::N, "P × N = N");
646        assert_eq!(Trit::P * Trit::Z, Trit::Z, "P × Z = Z");
647        assert_eq!(Trit::P * Trit::P, Trit::P, "P × P = P");
648    }
649
650    #[test]
651    fn test_trit_self_inverse() {
652        // Key VSA property: a × a = P for non-zero a
653        assert_eq!(Trit::P * Trit::P, Trit::P, "P is self-inverse");
654        assert_eq!(Trit::N * Trit::N, Trit::P, "N is self-inverse");
655    }
656
657    #[test]
658    fn test_trit_multiplication_commutative() {
659        for &a in &Trit::ALL {
660            for &b in &Trit::ALL {
661                assert_eq!(a * b, b * a, "Commutativity: {:?} × {:?}", a, b);
662            }
663        }
664    }
665
666    #[test]
667    fn test_trit_multiplication_associative() {
668        for &a in &Trit::ALL {
669            for &b in &Trit::ALL {
670                for &c in &Trit::ALL {
671                    assert_eq!(
672                        (a * b) * c,
673                        a * (b * c),
674                        "Associativity: ({:?} × {:?}) × {:?}",
675                        a,
676                        b,
677                        c
678                    );
679                }
680            }
681        }
682    }
683
684    #[test]
685    fn test_trit_negation() {
686        assert_eq!(-Trit::N, Trit::P);
687        assert_eq!(-Trit::Z, Trit::Z);
688        assert_eq!(-Trit::P, Trit::N);
689
690        // Double negation is identity
691        for &t in &Trit::ALL {
692            assert_eq!(-(-t), t, "Double negation of {:?}", t);
693        }
694    }
695
696    #[test]
697    fn test_trit_add_with_carry_complete() {
698        // Verify all 27 combinations
699        #[allow(clippy::type_complexity)]
700        let expected: [((Trit, Trit, Trit), (Trit, Trit)); 27] = [
701            // carry_in = N
702            ((Trit::N, Trit::N, Trit::N), (Trit::Z, Trit::N)), // -3
703            ((Trit::N, Trit::Z, Trit::N), (Trit::P, Trit::N)), // -2
704            ((Trit::N, Trit::P, Trit::N), (Trit::N, Trit::Z)), // -1
705            ((Trit::Z, Trit::N, Trit::N), (Trit::P, Trit::N)), // -2
706            ((Trit::Z, Trit::Z, Trit::N), (Trit::N, Trit::Z)), // -1
707            ((Trit::Z, Trit::P, Trit::N), (Trit::Z, Trit::Z)), // 0
708            ((Trit::P, Trit::N, Trit::N), (Trit::N, Trit::Z)), // -1
709            ((Trit::P, Trit::Z, Trit::N), (Trit::Z, Trit::Z)), // 0
710            ((Trit::P, Trit::P, Trit::N), (Trit::P, Trit::Z)), // 1
711            // carry_in = Z
712            ((Trit::N, Trit::N, Trit::Z), (Trit::P, Trit::N)), // -2
713            ((Trit::N, Trit::Z, Trit::Z), (Trit::N, Trit::Z)), // -1
714            ((Trit::N, Trit::P, Trit::Z), (Trit::Z, Trit::Z)), // 0
715            ((Trit::Z, Trit::N, Trit::Z), (Trit::N, Trit::Z)), // -1
716            ((Trit::Z, Trit::Z, Trit::Z), (Trit::Z, Trit::Z)), // 0
717            ((Trit::Z, Trit::P, Trit::Z), (Trit::P, Trit::Z)), // 1
718            ((Trit::P, Trit::N, Trit::Z), (Trit::Z, Trit::Z)), // 0
719            ((Trit::P, Trit::Z, Trit::Z), (Trit::P, Trit::Z)), // 1
720            ((Trit::P, Trit::P, Trit::Z), (Trit::N, Trit::P)), // 2
721            // carry_in = P
722            ((Trit::N, Trit::N, Trit::P), (Trit::N, Trit::Z)), // -1
723            ((Trit::N, Trit::Z, Trit::P), (Trit::Z, Trit::Z)), // 0
724            ((Trit::N, Trit::P, Trit::P), (Trit::P, Trit::Z)), // 1
725            ((Trit::Z, Trit::N, Trit::P), (Trit::Z, Trit::Z)), // 0
726            ((Trit::Z, Trit::Z, Trit::P), (Trit::P, Trit::Z)), // 1
727            ((Trit::Z, Trit::P, Trit::P), (Trit::N, Trit::P)), // 2
728            ((Trit::P, Trit::N, Trit::P), (Trit::P, Trit::Z)), // 1
729            ((Trit::P, Trit::Z, Trit::P), (Trit::N, Trit::P)), // 2
730            ((Trit::P, Trit::P, Trit::P), (Trit::Z, Trit::P)), // 3
731        ];
732
733        for ((a, b, c), (expected_sum, expected_carry)) in expected {
734            let (sum, carry) = a.add_with_carry(b, c);
735            assert_eq!(
736                (sum, carry),
737                (expected_sum, expected_carry),
738                "add_with_carry({:?}, {:?}, {:?})",
739                a,
740                b,
741                c
742            );
743        }
744    }
745
746    // ==================== TRYTE3 TESTS ====================
747
748    #[test]
749    fn test_tryte3_roundtrip() {
750        for v in Tryte3::MIN_VALUE..=Tryte3::MAX_VALUE {
751            let tryte =
752                Tryte3::from_i8(v).unwrap_or_else(|| panic!("Should create tryte for {}", v));
753            let decoded = tryte.to_i8();
754            assert_eq!(v, decoded, "Roundtrip failed for {}", v);
755        }
756    }
757
758    #[test]
759    fn test_tryte3_pack_unpack() {
760        for packed in 0..27u8 {
761            let tryte = Tryte3::unpack(packed).expect("Tryte3 unpack must succeed for values 0-26");
762            let repacked = tryte.pack();
763            assert_eq!(packed, repacked, "Pack/unpack failed for {}", packed);
764        }
765    }
766
767    #[test]
768    fn test_tryte3_bind_self_inverse() {
769        for v in Tryte3::MIN_VALUE..=Tryte3::MAX_VALUE {
770            // Safe: v is in valid range [MIN_VALUE, MAX_VALUE]
771            let tryte =
772                Tryte3::from_i8(v).expect("Tryte3::from_i8 must succeed for values in valid range");
773            let bound = tryte * tryte;
774
775            // Self-bind should produce all P (or Z for zero trits)
776            for i in 0..3 {
777                if tryte.trits[i].is_nonzero() {
778                    assert_eq!(
779                        bound.trits[i],
780                        Trit::P,
781                        "Self-bind trit {} should be P for value {}",
782                        i,
783                        v
784                    );
785                }
786            }
787        }
788    }
789
790    // ==================== WORD6 TESTS ====================
791
792    #[test]
793    fn test_word6_roundtrip() {
794        let test_values = [0, 1, -1, 13, -13, 100, -100, 364, -364];
795        for &v in &test_values {
796            let word = Word6::from_i16(v).unwrap_or_else(|| panic!("Should create word for {}", v));
797            let decoded = word.to_i16();
798            assert_eq!(v, decoded, "Roundtrip failed for {}", v);
799        }
800    }
801
802    #[test]
803    fn test_word6_pack_unpack() {
804        for packed in (0..729u16).step_by(7) {
805            // Sample every 7th value
806            let word = Word6::unpack(packed).expect("Word6 unpack must succeed for values 0-728");
807            let repacked = word.pack();
808            assert_eq!(packed, repacked, "Pack/unpack failed for {}", packed);
809        }
810    }
811
812    // ==================== PARITY TESTS ====================
813
814    #[test]
815    fn test_parity_detection() {
816        let trits = vec![Trit::P, Trit::N, Trit::P, Trit::Z, Trit::N];
817        let parity = ParityTrit::compute(&trits);
818        assert!(parity.verify(&trits), "Parity should verify");
819
820        // Flip one trit and verify parity fails
821        let mut corrupted = trits.clone();
822        corrupted[0] = Trit::N;
823        assert!(
824            !parity.verify(&corrupted),
825            "Parity should fail on corrupted data"
826        );
827    }
828}