poulpy/math/src/ring/impl_u64/automorphism.rs
math/src/ring/impl_u64/automorphism.rsRaw
1use crate::modulus::WordOps;
2use crate::poly::Poly;
3use crate::ring::Ring;
4
5/// Returns a lookup table for the automorphism X^{i} -> X^{i * k mod nth_root}.
6/// Method will panic if n or nth_root are not power-of-two.
7/// Method will panic if gal_el is not coprime with nth_root.
8pub fn automorphism_index<const NTT: bool>(n: usize, nth_root: usize, gal_el: usize) -> Vec<usize> {
9 assert!(n & (n - 1) != 0, "invalid n={}: not a power-of-two", n);
10 assert!(
11 nth_root & (nth_root - 1) == 0,
12 "invalid nth_root={}: not a power-of-two",
13 n
14 );
15 assert!(
16 gal_el & 1 == 1,
17 "invalid gal_el={}: not coprime with nth_root={}",
18 gal_el,
19 nth_root
20 );
21
22 let mut index: Vec<usize> = Vec::with_capacity(n);
23
24 if NTT {
25 let mask = nth_root - 1;
26 let log_nth_root_half: u32 = nth_root.log2() as u32 - 1;
27 for i in 0..n {
28 let i_rev: usize = 2 * i.reverse_bits_msb(log_nth_root_half) + 1;
29 let gal_el_i: usize = ((gal_el * i_rev) & mask) >> 1;
30 index.push(gal_el_i.reverse_bits_msb(log_nth_root_half));
31 }
32 } else {
33 let log_n: usize = n.log2();
34 let mask: usize = (n - 1) as usize;
35 for i in 0..n {
36 let gal_el_i: usize = i as usize * gal_el;
37 let sign: usize = (gal_el_i >> log_n) & 1;
38 let i_out: usize = (gal_el_i & mask) | (sign << (usize::BITS - 1));
39 index.push(i_out)
40 }
41 }
42
43 index
44}
45
46impl Ring<u64> {
47 pub fn automorphism<const NTT: bool>(
48 &self,
49 a: &Poly<u64>,
50 gal_el: usize,
51 nth_root: usize,
52 b: &mut Poly<u64>,
53 ) {
54 debug_assert!(
55 a.n() == b.n(),
56 "invalid inputs: a.n() = {} != b.n() = {}",
57 a.n(),
58 b.n()
59 );
60
61 assert!(
62 gal_el & 1 == 1,
63 "invalid gal_el={}: not coprime with nth_root={}",
64 gal_el,
65 nth_root
66 );
67
68 assert!(
69 nth_root & (nth_root - 1) == 0,
70 "invalid nth_root={}: not a power-of-two",
71 nth_root
72 );
73
74 let b_vec: &mut Vec<u64> = &mut b.0;
75 let a_vec: &Vec<u64> = &a.0;
76
77 if NTT {
78 let mask: usize = nth_root - 1;
79 let log_nth_root_half: u32 = nth_root.log2() as u32 - 1;
80 a_vec.iter().enumerate().for_each(|(i, ai)| {
81 let i_rev: usize = 2 * i.reverse_bits_msb(log_nth_root_half) + 1;
82 let gal_el_i: usize = (((gal_el * i_rev) & mask) - 1) >> 1;
83 let idx: usize = gal_el_i.reverse_bits_msb(log_nth_root_half);
84 b_vec[idx] = *ai;
85 });
86 } else {
87 let n: usize = a.n();
88 let mask: usize = n - 1;
89 let log_n: usize = n.log2();
90 let q: u64 = self.modulus.q();
91 a_vec.iter().enumerate().for_each(|(i, ai)| {
92 let gal_el_i: usize = i * gal_el;
93 let sign: u64 = ((gal_el_i >> log_n) & 1) as u64;
94 let i_out: usize = gal_el_i & mask;
95 b_vec[i_out] = ai * (sign ^ 1) | (q - ai) * sign
96 });
97 }
98 }
99
100 pub fn automorphism_from_index<const NTT: bool>(
101 &self,
102 a: &Poly<u64>,
103 idx: &[usize],
104 b: &mut Poly<u64>,
105 ) {
106 debug_assert!(
107 a.n() == b.n(),
108 "invalid inputs: a.n() = {} != b.n() = {}",
109 a.n(),
110 b.n()
111 );
112
113 let b_vec: &mut Vec<u64> = &mut b.0;
114 let a_vec: &Vec<u64> = &a.0;
115
116 if NTT {
117 a_vec.iter().enumerate().for_each(|(i, ai)| {
118 b_vec[idx[i]] = *ai;
119 });
120 } else {
121 let n: usize = a.n();
122 let mask: usize = n - 1;
123 let q: u64 = self.modulus.q();
124 a_vec.iter().enumerate().for_each(|(i, ai)| {
125 let sign: u64 = (idx[i] >> usize::BITS - 1) as u64;
126 b_vec[idx[i] & mask] = ai * (sign ^ 1) | (q - ai) * sign;
127 });
128 }
129 }
130}