Skip to main content

ternary_transform/
lib.rs

1#![forbid(unsafe_code)]
2
3//! Transform theory for ternary data on {-1, 0, +1}.
4//!
5//! Provides TernaryWavelet transform, ternary Fourier features, random features
6//! approximation, kernel methods with ternary kernels, and RBF-like similarity.
7
8/// A ternary value.
9#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
10pub enum Ternary {
11    Neg,
12    Zero,
13    Pos,
14}
15
16impl Ternary {
17    pub fn to_f64(self) -> f64 {
18        match self {
19            Ternary::Neg => -1.0,
20            Ternary::Zero => 0.0,
21            Ternary::Pos => 1.0,
22        }
23    }
24
25    pub fn from_i8(v: i8) -> Option<Self> {
26        match v {
27            -1 => Some(Ternary::Neg),
28            0 => Some(Ternary::Zero),
29            1 => Some(Ternary::Pos),
30            _ => None,
31        }
32    }
33
34    pub fn values() -> [Ternary; 3] {
35        [Ternary::Neg, Ternary::Zero, Ternary::Pos]
36    }
37}
38
39use std::f64::consts::{PI, E};
40
41// ==================== Ternary Wavelet Transform ====================
42
43/// Haar-like wavelet transform adapted for ternary data.
44pub struct TernaryWavelet {
45    pub levels: usize,
46}
47
48impl TernaryWavelet {
49    pub fn new(levels: usize) -> Self {
50        TernaryWavelet { levels }
51    }
52
53    /// Forward ternary wavelet transform.
54    pub fn forward(&self, data: &[f64]) -> Vec<f64> {
55        let n = data.len();
56        if n < 2 {
57            return data.to_vec();
58        }
59        let mut result = data.to_vec();
60        let mut current_n = n;
61
62        for _ in 0..self.levels.min(log2_floor(n)) {
63            let half = current_n / 2;
64            let mut approx = vec![0.0; half];
65            let mut detail = vec![0.0; half];
66
67            for i in 0..half {
68                // Ternary-aware: emphasize the sign structure
69                let a = result[2 * i];
70                let b = result[2 * i + 1];
71                approx[i] = (a + b) / 2.0;
72                detail[i] = (a - b) / 2.0;
73            }
74
75            // Ternary rounding on approximation coefficients
76            for i in 0..half {
77                result[i] = approx[i];
78            }
79            for i in 0..half {
80                result[half + i] = detail[i];
81            }
82            current_n = half;
83        }
84
85        result
86    }
87
88    /// Inverse ternary wavelet transform.
89    pub fn inverse(&self, coeffs: &[f64]) -> Vec<f64> {
90        let n = coeffs.len();
91        if n < 2 {
92            return coeffs.to_vec();
93        }
94
95        let levels = self.levels.min(log2_floor(n));
96        let mut result = coeffs.to_vec();
97
98        for _ in 0..levels {
99            let current_n = smallest_reconstruct(n, levels);
100            let half = current_n / 2;
101            let mut reconstructed = vec![0.0; current_n];
102
103            for i in 0..half {
104                let a = result[i];
105                let d = result[half + i];
106                reconstructed[2 * i] = a + d;
107                reconstructed[2 * i + 1] = a - d;
108            }
109
110            for i in 0..current_n {
111                result[i] = reconstructed[i];
112            }
113        }
114
115        result
116    }
117
118    /// Get wavelet energy at each level.
119    pub fn energy_per_level(&self, coeffs: &[f64]) -> Vec<f64> {
120        let n = coeffs.len();
121        let levels = self.levels.min(log2_floor(n));
122        let mut energies = Vec::new();
123        let mut current_n = n;
124
125        for _level in 0..levels {
126            let half = current_n / 2;
127            let start = half;
128            let energy: f64 = coeffs[start..current_n]
129                .iter()
130                .map(|&c| c * c)
131                .sum();
132            energies.push(energy);
133            current_n = half;
134        }
135
136        energies
137    }
138
139    /// Denoise by thresholding detail coefficients.
140    pub fn denoise(&self, data: &[f64], threshold: f64) -> Vec<f64> {
141        let coeffs = self.forward(data);
142        let n = coeffs.len();
143        let levels = self.levels.min(log2_floor(n));
144
145        let mut denoised = coeffs.clone();
146        let mut current_n = n;
147
148        for _ in 0..levels {
149            let half = current_n / 2;
150            for i in half..current_n {
151                if denoised[i].abs() < threshold {
152                    denoised[i] = 0.0;
153                }
154            }
155            current_n = half;
156        }
157
158        self.inverse(&denoised)
159    }
160}
161
162fn log2_floor(n: usize) -> usize {
163    if n == 0 { 0 } else { (n as f64).log2() as usize }
164}
165
166fn smallest_reconstruct(n: usize, levels: usize) -> usize {
167    // For reconstruction, start from the smallest level
168    n / (1 << (levels - 1).max(0))
169}
170
171// ==================== Ternary Fourier Features ====================
172
173/// Ternary Fourier feature extraction.
174pub struct TernaryFourier {
175    pub n_features: usize,
176    pub frequencies: Vec<f64>,
177}
178
179impl TernaryFourier {
180    pub fn new(n_features: usize) -> Self {
181        let frequencies: Vec<f64> = (1..=n_features).map(|i| i as f64).collect();
182        TernaryFourier {
183            n_features,
184            frequencies,
185        }
186    }
187
188    /// Compute Fourier features for a ternary sequence.
189    pub fn transform(&self, data: &[Ternary]) -> Vec<f64> {
190        let n = data.len() as f64;
191        let mut features = Vec::with_capacity(self.n_features * 2);
192
193        for &freq in &self.frequencies {
194            let mut cos_sum = 0.0;
195            let mut sin_sum = 0.0;
196            for (t, &val) in data.iter().enumerate() {
197                let x = val.to_f64();
198                let phase = 2.0 * PI * freq * t as f64 / n;
199                cos_sum += x * phase.cos();
200                sin_sum += x * phase.sin();
201            }
202            features.push(cos_sum / n);
203            features.push(sin_sum / n);
204        }
205
206        features
207    }
208
209    /// Compute the power spectrum.
210    pub fn power_spectrum(&self, data: &[Ternary]) -> Vec<f64> {
211        let features = self.transform(data);
212        features
213            .chunks(2)
214            .map(|chunk| {
215                let cos_v = chunk[0];
216                let sin_v = if chunk.len() > 1 { chunk[1] } else { 0.0 };
217                cos_v * cos_v + sin_v * sin_v
218            })
219            .collect()
220    }
221
222    /// Reconstruct from Fourier features (approximate).
223    pub fn reconstruct(&self, features: &[f64], length: usize) -> Vec<f64> {
224        let mut result = vec![0.0; length];
225        let n = length as f64;
226
227        for (i, chunk) in features.chunks(2).enumerate() {
228            let cos_v = chunk[0];
229            let sin_v = if chunk.len() > 1 { chunk[1] } else { 0.0 };
230            let freq = (i + 1) as f64;
231            for t in 0..length {
232                let phase = 2.0 * PI * freq * t as f64 / n;
233                result[t] += cos_v * phase.cos() + sin_v * phase.sin();
234            }
235        }
236
237        result
238    }
239}
240
241// ==================== Random Features Approximation ====================
242
243/// Random Fourier features for kernel approximation.
244pub struct RandomFeatures {
245    pub n_features: usize,
246    pub dim: usize,
247    pub weights: Vec<Vec<f64>>,
248    pub biases: Vec<f64>,
249}
250
251impl RandomFeatures {
252    pub fn new(n_features: usize, dim: usize, seed: u64) -> Self {
253        let weights: Vec<Vec<f64>> = (0..n_features)
254            .map(|i| {
255                (0..dim)
256                    .map(|j| {
257                        // Simple deterministic random from seed
258                        let s = seed.wrapping_add((i * dim + j) as u64);
259                        pseudo_random(s)
260                    })
261                    .collect()
262            })
263            .collect();
264
265        let biases: Vec<f64> = (0..n_features)
266            .map(|i| pseudo_random(seed.wrapping_add(n_features as u64 * dim as u64 + i as u64)) * 2.0 * PI)
267            .collect();
268
269        RandomFeatures {
270            n_features,
271            dim,
272            weights,
273            biases,
274        }
275    }
276
277    /// Transform input using random features.
278    pub fn transform(&self, input: &[f64]) -> Vec<f64> {
279        self.weights
280            .iter()
281            .zip(self.biases.iter())
282            .map(|(w, &b)| {
283                let dot: f64 = w.iter().zip(input.iter()).map(|(&a, &x)| a * x).sum();
284                (dot + b).cos()
285            })
286            .collect()
287    }
288
289    /// Approximate kernel value between two inputs.
290    pub fn kernel_approx(&self, a: &[f64], b: &[f64]) -> f64 {
291        let fa = self.transform(a);
292        let fb = self.transform(b);
293        let dot: f64 = fa.iter().zip(fb.iter()).map(|(&x, &y)| x * y).sum();
294        dot / self.n_features as f64
295    }
296}
297
298fn pseudo_random(seed: u64) -> f64 {
299    // Simple LCG-based pseudo-random in [-1, 1]
300    let s = seed.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
301    ((s >> 33) as i64 as f64) / (1i64 << 31) as f64
302}
303
304// ==================== Ternary Kernels ====================
305
306/// Kernel functions for ternary data.
307pub struct TernaryKernel {
308    pub sigma: f64,
309}
310
311impl TernaryKernel {
312    pub fn new(sigma: f64) -> Self {
313        TernaryKernel { sigma }
314    }
315
316    /// RBF-like kernel for ternary vectors.
317    pub fn rbf(&self, a: &[Ternary], b: &[Ternary]) -> f64 {
318        let dist_sq: f64 = a.iter()
319            .zip(b.iter())
320            .map(|(&x, &y)| {
321                let d = x.to_f64() - y.to_f64();
322                d * d
323            })
324            .sum();
325        (-dist_sq / (2.0 * self.sigma * self.sigma)).exp()
326    }
327
328    /// Ternary matching kernel: counts matching positions.
329    pub fn matching(&self, a: &[Ternary], b: &[Ternary]) -> f64 {
330        let matches = a.iter().zip(b.iter()).filter(|(&x, &y)| x == y).count();
331        matches as f64 / a.len().max(1) as f64
332    }
333
334    /// Ternary agreement kernel: +1 for same, -1 for opposite, 0 otherwise.
335    pub fn agreement(&self, a: &[Ternary], b: &[Ternary]) -> f64 {
336        let score: f64 = a.iter()
337            .zip(b.iter())
338            .map(|(&x, &y)| {
339                let xv = x.to_f64();
340                let yv = y.to_f64();
341                xv * yv
342            })
343            .sum();
344        score / a.len().max(1) as f64
345    }
346
347    /// Polynomial kernel for ternary vectors.
348    pub fn polynomial(&self, a: &[Ternary], b: &[Ternary], degree: i32, offset: f64) -> f64 {
349        let dot: f64 = a.iter()
350            .zip(b.iter())
351            .map(|(&x, &y)| x.to_f64() * y.to_f64())
352            .sum();
353        (dot + offset).powi(degree)
354    }
355
356    /// Compute full kernel matrix for a set of ternary vectors.
357    pub fn kernel_matrix(&self, data: &[Vec<Ternary>], kernel_type: KernelType) -> Vec<Vec<f64>> {
358        let n = data.len();
359        let mut matrix = vec![vec![0.0; n]; n];
360        for i in 0..n {
361            for j in 0..n {
362                matrix[i][j] = match kernel_type {
363                    KernelType::Rbf => self.rbf(&data[i], &data[j]),
364                    KernelType::Matching => self.matching(&data[i], &data[j]),
365                    KernelType::Agreement => self.agreement(&data[i], &data[j]),
366                    KernelType::Polynomial(d, o) => self.polynomial(&data[i], &data[j], d, o),
367                };
368            }
369        }
370        matrix
371    }
372}
373
374/// Types of ternary kernels.
375#[derive(Debug, Clone, Copy)]
376pub enum KernelType {
377    Rbf,
378    Matching,
379    Agreement,
380    Polynomial(i32, f64),
381}
382
383// ==================== RBF-like Similarity ====================
384
385/// RBF-like similarity measures for ternary data.
386pub struct TernaryRBFSimilarity {
387    pub gamma: f64,
388}
389
390impl TernaryRBFSimilarity {
391    pub fn new(gamma: f64) -> Self {
392        TernaryRBFSimilarity { gamma }
393    }
394
395    /// Compute similarity between two ternary sequences.
396    pub fn similarity(&self, a: &[Ternary], b: &[Ternary]) -> f64 {
397        let dist = self.hamming_distance(a, b) as f64;
398        (-self.gamma * dist).exp()
399    }
400
401    /// Hamming distance between ternary sequences.
402    pub fn hamming_distance(&self, a: &[Ternary], b: &[Ternary]) -> usize {
403        a.iter().zip(b.iter()).filter(|(&x, &y)| x != y).count()
404    }
405
406    /// Weighted Hamming: opposite signs cost more than one being zero.
407    pub fn weighted_distance(&self, a: &[Ternary], b: &[Ternary]) -> f64 {
408        a.iter().zip(b.iter())
409            .map(|(&x, &y)| {
410                let xv = x.to_f64();
411                let yv = y.to_f64();
412                (xv - yv).abs()
413            })
414            .sum()
415    }
416
417    /// Similarity matrix for a set of sequences.
418    pub fn similarity_matrix(&self, data: &[Vec<Ternary>]) -> Vec<Vec<f64>> {
419        let n = data.len();
420        let mut matrix = vec![vec![0.0; n]; n];
421        for i in 0..n {
422            matrix[i][i] = 1.0;
423            for j in (i + 1)..n {
424                let s = self.similarity(&data[i], &data[j]);
425                matrix[i][j] = s;
426                matrix[j][i] = s;
427            }
428        }
429        matrix
430    }
431
432    /// Find k nearest neighbors.
433    pub fn knn(&self, query: &[Ternary], data: &[Vec<Ternary>], k: usize) -> Vec<(usize, f64)> {
434        let mut scored: Vec<(usize, f64)> = data.iter()
435            .enumerate()
436            .map(|(i, d)| (i, self.similarity(query, d)))
437            .collect();
438        scored.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
439        scored.truncate(k);
440        scored
441    }
442}
443
444#[cfg(test)]
445mod tests {
446    use super::*;
447
448    #[test]
449    fn test_wavelet_forward() {
450        let w = TernaryWavelet::new(1);
451        let data = vec![1.0, -1.0, 1.0, -1.0];
452        let coeffs = w.forward(&data);
453        assert_eq!(coeffs.len(), 4);
454        // Approximation of alternating signal
455        assert!(coeffs[0].abs() < 1e-10);
456        assert!(coeffs[1].abs() < 1e-10);
457    }
458
459    #[test]
460    fn test_wavelet_inverse() {
461        let w = TernaryWavelet::new(1);
462        let data = vec![3.0, 1.0, -1.0, 5.0];
463        let coeffs = w.forward(&data);
464        let reconstructed = w.inverse(&coeffs);
465        // Should approximately reconstruct
466        for i in 0..data.len() {
467            assert!((reconstructed[i] - data[i]).abs() < 1e-10);
468        }
469    }
470
471    #[test]
472    fn test_wavelet_energy() {
473        let w = TernaryWavelet::new(2);
474        let data = vec![1.0, -1.0, 1.0, -1.0];
475        let coeffs = w.forward(&data);
476        let energy = w.energy_per_level(&coeffs);
477        assert_eq!(energy.len(), 2);
478    }
479
480    #[test]
481    fn test_wavelet_denoise() {
482        let w = TernaryWavelet::new(1);
483        let data = vec![1.0, 0.01, -1.0, 0.01];
484        let denoised = w.denoise(&data, 0.05);
485        assert_eq!(denoised.len(), 4);
486    }
487
488    #[test]
489    fn test_wavelet_single_element() {
490        let w = TernaryWavelet::new(1);
491        let data = vec![5.0];
492        let coeffs = w.forward(&data);
493        assert_eq!(coeffs, vec![5.0]);
494    }
495
496    #[test]
497    fn test_fourier_transform() {
498        let f = TernaryFourier::new(3);
499        let data = vec![Ternary::Pos, Ternary::Neg, Ternary::Pos, Ternary::Neg];
500        let features = f.transform(&data);
501        assert_eq!(features.len(), 6); // 3 frequencies × 2 (cos + sin)
502    }
503
504    #[test]
505    fn test_fourier_power_spectrum() {
506        let f = TernaryFourier::new(4);
507        let data = vec![Ternary::Pos, Ternary::Neg, Ternary::Pos, Ternary::Neg];
508        let spectrum = f.power_spectrum(&data);
509        assert_eq!(spectrum.len(), 4);
510        // High frequency alternating should have strong frequency-2 component
511        assert!(spectrum[1] > spectrum[0]);
512    }
513
514    #[test]
515    fn test_fourier_reconstruct() {
516        let f = TernaryFourier::new(2);
517        let data = vec![Ternary::Pos, Ternary::Zero, Ternary::Neg, Ternary::Zero];
518        let features = f.transform(&data);
519        let reconstructed = f.reconstruct(&features, 4);
520        assert_eq!(reconstructed.len(), 4);
521    }
522
523    #[test]
524    fn test_fourier_constant_signal() {
525        let f = TernaryFourier::new(2);
526        let data = vec![Ternary::Zero, Ternary::Zero, Ternary::Zero, Ternary::Zero];
527        let features = f.transform(&data);
528        // All features should be ~0 for constant zero signal
529        for &v in &features {
530            assert!(v.abs() < 1e-10);
531        }
532    }
533
534    #[test]
535    fn test_random_features_transform() {
536        let rf = RandomFeatures::new(10, 3, 42);
537        let input = vec![1.0, 0.0, -1.0];
538        let features = rf.transform(&input);
539        assert_eq!(features.len(), 10);
540        // Each feature should be in [-1, 1] since it's cos()
541        for &v in &features {
542            assert!(v >= -1.0 && v <= 1.0);
543        }
544    }
545
546    #[test]
547    fn test_random_features_kernel() {
548        let rf = RandomFeatures::new(100, 3, 42);
549        let a = vec![1.0, 0.0, -1.0];
550        let b = vec![1.0, 0.0, -1.0];
551        let k = rf.kernel_approx(&a, &b);
552        // Self-kernel should be close to 1
553        assert!(k > 0.5);
554    }
555
556    #[test]
557    fn test_ternary_kernel_rbf() {
558        let k = TernaryKernel::new(1.0);
559        let a = vec![Ternary::Pos, Ternary::Pos];
560        let b = vec![Ternary::Pos, Ternary::Pos];
561        let rbf = k.rbf(&a, &b);
562        assert!((rbf - 1.0).abs() < 1e-10);
563    }
564
565    #[test]
566    fn test_ternary_kernel_matching() {
567        let k = TernaryKernel::new(1.0);
568        let a = vec![Ternary::Pos, Ternary::Neg];
569        let b = vec![Ternary::Pos, Ternary::Zero];
570        let m = k.matching(&a, &b);
571        assert!((m - 0.5).abs() < 1e-10);
572    }
573
574    #[test]
575    fn test_ternary_kernel_agreement() {
576        let k = TernaryKernel::new(1.0);
577        let a = vec![Ternary::Pos, Ternary::Neg];
578        let b = vec![Ternary::Neg, Ternary::Pos];
579        let ag = k.agreement(&a, &b);
580        assert!((ag - (-1.0)).abs() < 1e-10);
581    }
582
583    #[test]
584    fn test_ternary_kernel_polynomial() {
585        let k = TernaryKernel::new(1.0);
586        let a = vec![Ternary::Pos, Ternary::Pos];
587        let b = vec![Ternary::Pos, Ternary::Pos];
588        let p = k.polynomial(&a, &b, 2, 1.0);
589        assert!((p - 9.0).abs() < 1e-10); // (2+1)^2 = 9
590    }
591
592    #[test]
593    fn test_kernel_matrix() {
594        let k = TernaryKernel::new(1.0);
595        let data = vec![
596            vec![Ternary::Pos, Ternary::Pos],
597            vec![Ternary::Neg, Ternary::Neg],
598        ];
599        let m = k.kernel_matrix(&data, KernelType::Matching);
600        assert!((m[0][0] - 1.0).abs() < 1e-10);
601        assert!((m[1][1] - 1.0).abs() < 1e-10);
602        assert!(m[0][1] < 1.0);
603    }
604
605    #[test]
606    fn test_rbf_similarity() {
607        let sim = TernaryRBFSimilarity::new(1.0);
608        let a = vec![Ternary::Pos, Ternary::Pos];
609        let b = vec![Ternary::Pos, Ternary::Pos];
610        assert!((sim.similarity(&a, &b) - 1.0).abs() < 1e-10);
611    }
612
613    #[test]
614    fn test_rbf_hamming() {
615        let sim = TernaryRBFSimilarity::new(1.0);
616        let a = vec![Ternary::Pos, Ternary::Neg, Ternary::Zero];
617        let b = vec![Ternary::Pos, Ternary::Zero, Ternary::Zero];
618        assert_eq!(sim.hamming_distance(&a, &b), 1);
619    }
620
621    #[test]
622    fn test_rbf_weighted_distance() {
623        let sim = TernaryRBFSimilarity::new(1.0);
624        let a = vec![Ternary::Pos];
625        let b = vec![Ternary::Neg];
626        let d = sim.weighted_distance(&a, &b);
627        assert!((d - 2.0).abs() < 1e-10);
628    }
629
630    #[test]
631    fn test_rbf_similarity_matrix() {
632        let sim = TernaryRBFSimilarity::new(1.0);
633        let data = vec![
634            vec![Ternary::Pos],
635            vec![Ternary::Neg],
636        ];
637        let m = sim.similarity_matrix(&data);
638        assert!((m[0][0] - 1.0).abs() < 1e-10);
639        assert!((m[0][1] - m[1][0]).abs() < 1e-10);
640    }
641
642    #[test]
643    fn test_rbf_knn() {
644        let sim = TernaryRBFSimilarity::new(1.0);
645        let query = vec![Ternary::Pos, Ternary::Pos];
646        let data = vec![
647            vec![Ternary::Pos, Ternary::Pos],
648            vec![Ternary::Neg, Ternary::Neg],
649            vec![Ternary::Zero, Ternary::Zero],
650        ];
651        let knn = sim.knn(&query, &data, 2);
652        assert_eq!(knn.len(), 2);
653        assert_eq!(knn[0].0, 0); // Most similar
654    }
655}