1use std::alloc::{Allocator, Global};
2use std::cmp::max;
3
4use bluestein::BluesteinFFT;
5use dense_poly::DensePolyRing;
6use feanor_math::algorithms::eea::{signed_eea, signed_gcd};
7use feanor_math::algorithms::int_factor::factor;
8use feanor_math::algorithms::cyclotomic::cyclotomic_polynomial;
9use feanor_math::algorithms::unity_root::{get_prim_root_of_unity, get_prim_root_of_unity_pow2};
10use feanor_math::algorithms::fft::*;
11use feanor_math::integer::IntegerRingStore;
12use feanor_math::integer::*;
13use feanor_math::rings::poly::*;
14use feanor_math::divisibility::DivisibilityRing;
15use feanor_math::primitive_int::*;
16use feanor_math::ring::*;
17use feanor_math::homomorphism::*;
18use feanor_math::rings::poly::sparse_poly::SparsePolyRing;
19use feanor_math::rings::zn::*;
20use subvector::SubvectorView;
21use tracing::instrument;
22use feanor_math::seq::*;
23
24use crate::euler_phi_squarefree;
25use crate::cyclotomic::*;
26use super::{HECyclotomicNumberRing, HECyclotomicNumberRingMod, HENumberRing, HENumberRingMod};
27
28pub struct OddCyclotomicNumberRing {
32 n_factorization_squarefree: Vec<i64>,
33}
34
35impl OddCyclotomicNumberRing {
36
37 pub fn new(n: usize) -> Self {
38 assert!(n % 2 == 1);
39 assert!(n > 1);
40 let factorization = factor(StaticRing::<i64>::RING, n as i64);
41 for (_, e) in &factorization {
45 assert!(*e == 1, "n = {} is not squarefree", n);
46 }
47 Self {
48 n_factorization_squarefree: factorization.iter().map(|(p, _)| *p).collect(),
49 }
50 }
51
52 pub fn powful_inf_to_can_norm_expansion_factor(&self) -> f64 {
71 let rank = euler_phi_squarefree(&self.n_factorization_squarefree);
72 (rank as f64).powi(3).sqrt()
76 }
77
78 pub fn can_to_powful_inf_norm_expansion_factor(&self) -> f64 {
87 2f64.powi(self.n_factorization_squarefree.len() as i32)
94 }
95
96 pub fn powful_inf_to_inf_norm_expansion_factor(&self) -> f64 {
105 let rank = euler_phi_squarefree(&self.n_factorization_squarefree);
113 return rank as f64;
114 }
115}
116
117impl Clone for OddCyclotomicNumberRing {
118 fn clone(&self) -> Self {
119 Self {
120 n_factorization_squarefree: self.n_factorization_squarefree.clone()
121 }
122 }
123}
124
125impl PartialEq for OddCyclotomicNumberRing {
126
127 fn eq(&self, other: &Self) -> bool {
128 self.n_factorization_squarefree == other.n_factorization_squarefree
129 }
130}
131
132impl HENumberRing for OddCyclotomicNumberRing {
133
134 type Decomposed = OddCyclotomicDecomposedNumberRing<BluesteinFFT<zn_64::ZnBase, zn_64::ZnBase, Identity<zn_64::Zn>>>;
135
136 fn can_to_inf_norm_expansion_factor(&self) -> f64 {
137 self.powful_inf_to_inf_norm_expansion_factor() * self.can_to_powful_inf_norm_expansion_factor()
138 }
139
140 fn inf_to_can_norm_expansion_factor(&self) -> f64 {
141 let rank = euler_phi_squarefree(&self.n_factorization_squarefree);
142 (rank as f64).powi(3).sqrt()
146 }
147
148 fn mod_p(&self, Fp: zn_64::Zn) -> Self::Decomposed {
149 let n_factorization = &self.n_factorization_squarefree;
150 let n = n_factorization.iter().copied().product::<i64>();
151
152 let Fp_as_field = (&Fp).as_field().ok().unwrap();
153 let zeta = get_prim_root_of_unity(&Fp_as_field, 2 * n as usize).unwrap();
154 let zeta = Fp_as_field.get_ring().unwrap_element(zeta);
155 let log2_m = StaticRing::<i64>::RING.abs_log2_ceil(&n).unwrap() + 1;
156 let zeta_m = get_prim_root_of_unity_pow2(&Fp_as_field, log2_m).unwrap();
157 let zeta_m = Fp_as_field.get_ring().unwrap_element(zeta_m);
158 let fft_table = BluesteinFFT::new(Fp.clone(), zeta, zeta_m, n as usize, log2_m, Global);
159
160 return OddCyclotomicDecomposedNumberRing::create_squarefree(fft_table, Fp, &self.n_factorization_squarefree, Global);
161 }
162
163 fn mod_p_required_root_of_unity(&self) -> usize {
164 let n = <_ as HECyclotomicNumberRing>::n(self);
165 let log2_m = StaticRing::<i64>::RING.abs_log2_ceil(&(n as i64)).unwrap() + 2;
166 return n << log2_m;
167 }
168
169 fn generating_poly<P>(&self, poly_ring: P) -> El<P>
170 where P: RingStore,
171 P::Type: PolyRing + DivisibilityRing,
172 <<P::Type as RingExtension>::BaseRing as RingStore>::Type: IntegerRing
173 {
174 cyclotomic_polynomial(&poly_ring, <_ as HECyclotomicNumberRing>::n(self) as usize)
175 }
176
177 fn rank(&self) -> usize {
178 euler_phi_squarefree(&self.n_factorization_squarefree) as usize
179 }
180}
181
182impl HECyclotomicNumberRing for OddCyclotomicNumberRing {
183
184 fn n(&self) -> usize {
185 self.n_factorization_squarefree.iter().copied().product::<i64>() as usize
186 }
187}
188
189#[derive(Clone)]
195pub struct CompositeCyclotomicNumberRing {
196 tensor_factor1: OddCyclotomicNumberRing,
197 tensor_factor2: OddCyclotomicNumberRing
198}
199
200impl CompositeCyclotomicNumberRing {
201
202 pub fn new(n1: usize, n2: usize) -> Self {
203 assert!(n1 % 2 == 1);
204 assert!(n2 % 2 == 1);
205 assert!(n1 > 1);
206 assert!(n2 > 1);
207 assert!(signed_gcd(n1 as i64, n2 as i64, StaticRing::<i64>::RING) == 1);
208 Self {
209 tensor_factor1: OddCyclotomicNumberRing::new(n1),
210 tensor_factor2: OddCyclotomicNumberRing::new(n2)
211 }
212 }
213}
214
215impl PartialEq for CompositeCyclotomicNumberRing {
216
217 fn eq(&self, other: &Self) -> bool {
218 self.tensor_factor1 == other.tensor_factor1 && self.tensor_factor2 == other.tensor_factor2
219 }
220}
221
222impl HECyclotomicNumberRing for CompositeCyclotomicNumberRing {
223
224 fn n(&self) -> usize {
225 <_ as HECyclotomicNumberRing>::n(&self.tensor_factor1) * <_ as HECyclotomicNumberRing>::n(&self.tensor_factor2)
226 }
227}
228
229impl HENumberRing for CompositeCyclotomicNumberRing {
230
231 type Decomposed = CompositeCyclotomicDecomposedNumberRing<BluesteinFFT<zn_64::ZnBase, zn_64::ZnBase, Identity<zn_64::Zn>>>;
232
233 fn mod_p(&self, Fp: zn_64::Zn) -> Self::Decomposed {
234 let n1 = <_ as HECyclotomicNumberRing>::n(&self.tensor_factor1) as i64;
235 let n2 = <_ as HECyclotomicNumberRing>::n(&self.tensor_factor2) as i64;
236 let n = n1 * n2;
237 let r1 = <_ as HENumberRing>::rank(&self.tensor_factor1) as i64;
238 let r2 = <_ as HENumberRing>::rank(&self.tensor_factor2) as i64;
239
240 let poly_ring = SparsePolyRing::new(StaticRing::<i64>::RING, "X");
241 let poly_ring = &poly_ring;
242 let Phi_n1 = self.tensor_factor1.generating_poly(&poly_ring);
243 let Phi_n2 = self.tensor_factor2.generating_poly(&poly_ring);
244 let hom = Fp.can_hom(Fp.integer_ring()).unwrap().compose(Fp.integer_ring().can_hom(poly_ring.base_ring()).unwrap());
245 let hom_ref = &hom;
246
247 let (s, t, d) = signed_eea(n1, n2, StaticRing::<i64>::RING);
248 assert_eq!(1, d);
249
250 let mut small_to_coeff_conversion_matrix = (0..(r1 * r2)).map(|_| Vec::new()).collect::<Vec<_>>();
255 let mut coeff_to_small_conversion_matrix = (0..(r1 * r2)).map(|_| Vec::new()).collect::<Vec<_>>();
256
257 let dense_poly_ring = DensePolyRing::new(poly_ring.base_ring(), "X");
258 let Phi_n_sparse = cyclotomic_polynomial(&poly_ring, n as usize);
259 let Phi_n = dense_poly_ring.can_hom(&poly_ring).unwrap().map_ref(&Phi_n_sparse);
260 let mut X_pow_i = None;
261 for i in 0..(n1 * n2) {
262
263 let i1 = ((t * i % n1) + n1) % n1;
264 let i2 = ((s * i % n2) + n2) % n2;
265 debug_assert_eq!(i, (i1 * n / n1 + i2 * n / n2) % n);
266
267 if i < r1 * r2 {
268 let X1_power_reduced = poly_ring.div_rem_monic(poly_ring.pow(poly_ring.indeterminate(), i1 as usize), &Phi_n1).1;
269 let X2_power_reduced = poly_ring.div_rem_monic(poly_ring.pow(poly_ring.indeterminate(), i2 as usize), &Phi_n2).1;
270
271 coeff_to_small_conversion_matrix[i as usize] = poly_ring.terms(&X1_power_reduced).flat_map(|(c1, j1)| poly_ring.terms(&X2_power_reduced).map(move |(c2, j2)|
272 (j1 + j2 * r1 as usize, hom_ref.map(poly_ring.base_ring().mul_ref(c1, c2))
273 ))).collect::<Vec<_>>();
274 }
275
276 if i1 < r1 && i2 < r2 {
277 if let Some(X_pow_i) = &X_pow_i {
278 small_to_coeff_conversion_matrix[(i2 * r1 + i1) as usize] = dense_poly_ring.terms(X_pow_i).map(|(c, i)| {
279 assert!(i < (r1 * r2) as usize);
280 (i, hom_ref.map_ref(c))
281 }).collect::<Vec<_>>();
282 } else {
283 small_to_coeff_conversion_matrix[(i2 * r1 + i1) as usize] = vec![(i as usize, hom_ref.codomain().one())];
284 }
285 }
286
287 if i == (r1 * r2) - 1 {
288 X_pow_i = Some(dense_poly_ring.from_terms([(dense_poly_ring.base_ring().one(), (r1 * r2 - 1) as usize)]));
289 }
290 if let Some(X_pow_i) = &mut X_pow_i {
291 dense_poly_ring.mul_assign_monomial(X_pow_i, 1);
292 let lc = dense_poly_ring.coefficient_at(X_pow_i, (r1 * r2) as usize);
293 if dense_poly_ring.base_ring().is_zero(&lc) {
295 } else if dense_poly_ring.base_ring().is_one(&lc) {
297 dense_poly_ring.get_ring().add_assign_from_terms(X_pow_i, dense_poly_ring.terms(&Phi_n).map(|(c, i)| (dense_poly_ring.base_ring().negate(dense_poly_ring.base_ring().clone_el(c)), i)));
298 } else if dense_poly_ring.base_ring().is_neg_one(&lc) {
299 dense_poly_ring.get_ring().add_assign_from_terms(X_pow_i, dense_poly_ring.terms(&Phi_n).map(|(c, i)| (dense_poly_ring.base_ring().clone_el(c), i)));
300 } else {
301 let lc = dense_poly_ring.base_ring().clone_el(lc);
302 dense_poly_ring.get_ring().add_assign_from_terms(X_pow_i, dense_poly_ring.terms(&Phi_n).map(|(c, i)| (dense_poly_ring.base_ring().negate(dense_poly_ring.base_ring().mul_ref(c, &lc)), i)));
303 }
304 }
305 }
306
307 CompositeCyclotomicDecomposedNumberRing {
308 small_to_coeff_conversion_matrix: small_to_coeff_conversion_matrix,
309 coeff_to_small_conversion_matrix: coeff_to_small_conversion_matrix,
310 tensor_factor1: self.tensor_factor1.mod_p(Fp.clone()),
311 tensor_factor2: self.tensor_factor2.mod_p(Fp)
312 }
313 }
314
315 fn mod_p_required_root_of_unity(&self) -> usize {
316 let n = <_ as HECyclotomicNumberRing>::n(self);
317 let log2_m = max(
318 StaticRing::<i64>::RING.abs_log2_ceil(&(<_ as HECyclotomicNumberRing>::n(&self.tensor_factor1) as i64)).unwrap() + 2,
319 StaticRing::<i64>::RING.abs_log2_ceil(&(<_ as HECyclotomicNumberRing>::n(&self.tensor_factor2) as i64)).unwrap() + 2
320 );
321 return n << log2_m;
322 }
323
324 fn inf_to_can_norm_expansion_factor(&self) -> f64 {
325 return <_ as HENumberRing>::inf_to_can_norm_expansion_factor(&self.tensor_factor1) * <_ as HENumberRing>::inf_to_can_norm_expansion_factor(&self.tensor_factor2);
326 }
327
328 fn can_to_inf_norm_expansion_factor(&self) -> f64 {
329 return <_ as HENumberRing>::can_to_inf_norm_expansion_factor(&self.tensor_factor1) * <_ as HENumberRing>::can_to_inf_norm_expansion_factor(&self.tensor_factor2);
330 }
331
332 fn generating_poly<P>(&self, poly_ring: P) -> El<P>
333 where P: RingStore,
334 P::Type: PolyRing + DivisibilityRing,
335 <<P::Type as RingExtension>::BaseRing as RingStore>::Type: IntegerRing
336 {
337 cyclotomic_polynomial(&poly_ring, <_ as HECyclotomicNumberRing>::n(self) as usize)
338 }
339
340 fn rank(&self) -> usize {
341 <_ as HENumberRing>::rank(&self.tensor_factor1) * <_ as HENumberRing>::rank(&self.tensor_factor2)
342 }
343}
344
345pub struct OddCyclotomicDecomposedNumberRing<F, A = Global>
346 where F: FFTAlgorithm<zn_64::ZnBase> + PartialEq,
347 A: Allocator + Clone
348{
349 ring: zn_64::Zn,
350 fft_table: F,
351 fft_output_indices_to_indices: Vec<usize>,
353 zeta_pow_rank: Vec<(usize, zn_64::ZnEl)>,
354 rank: usize,
355 allocator: A
356}
357
358impl<F, A> PartialEq for OddCyclotomicDecomposedNumberRing<F, A>
359 where F: FFTAlgorithm<zn_64::ZnBase> + PartialEq,
360 A: Allocator + Clone
361{
362 fn eq(&self, other: &Self) -> bool {
363 self.ring.get_ring() == other.ring.get_ring() && self.fft_table == other.fft_table
364 }
365}
366
367impl<F, A> OddCyclotomicDecomposedNumberRing<F, A>
368 where F: FFTAlgorithm<zn_64::ZnBase> + PartialEq,
369 A: Allocator + Clone
370{
371 fn create_squarefree(fft_table: F, Fp: zn_64::Zn, n_factorization: &[i64], allocator: A) -> Self {
372 let n = n_factorization.iter().copied().product::<i64>();
373 let rank = euler_phi_squarefree(&n_factorization) as usize;
374
375 let Fp_as_field = (&Fp).as_field().ok().unwrap();
376 let poly_ring = SparsePolyRing::new(Fp_as_field.clone(), "X");
377 let cyclotomic_poly = cyclotomic_polynomial(&poly_ring, n as usize);
378 assert_eq!(poly_ring.degree(&cyclotomic_poly).unwrap(), rank);
379 let mut zeta_pow_rank = Vec::new();
380 for (a, i) in poly_ring.terms(&cyclotomic_poly) {
381 if i != rank {
382 zeta_pow_rank.push((i, Fp.negate(Fp_as_field.get_ring().unwrap_element(Fp_as_field.clone_el(a)))));
383 }
384 }
385 zeta_pow_rank.sort_unstable_by_key(|(i, _)| *i);
386
387 let fft_output_indices_to_indices = (0..fft_table.len()).scan(0, |state, i| {
388 let power = fft_table.unordered_fft_permutation(i);
389 if n_factorization.iter().all(|p| power as i64 % *p != 0) {
390 *state += 1;
391 return Some(*state - 1);
392 } else {
393 return Some(usize::MAX);
394 }
395 }).collect::<Vec<_>>();
396
397 return Self { ring: Fp, fft_table, zeta_pow_rank, rank, allocator, fft_output_indices_to_indices };
398 }
399
400 fn fft_output_indices<'a>(&'a self) -> impl Iterator<Item = (usize, usize)> + 'a {
408 self.fft_output_indices_to_indices.iter().enumerate().filter_map(|(i, j)| if *j == usize::MAX { None } else { Some((*j, i)) })
409 }
410}
411
412impl<F, A> HENumberRingMod for OddCyclotomicDecomposedNumberRing<F, A>
413 where F: Send + Sync + FFTAlgorithm<zn_64::ZnBase> + PartialEq,
414 A: Send + Sync + Allocator + Clone
415{
416 fn mult_basis_to_small_basis<V>(&self, mut data: V)
417 where V: VectorViewMut<zn_64::ZnEl>
418 {
419 let ring = self.base_ring();
420 let mut tmp = Vec::with_capacity_in(self.fft_table.len(), &self.allocator);
421 tmp.extend((0..self.fft_table.len()).map(|_| ring.zero()));
422 for (i, j) in self.fft_output_indices() {
423 tmp[j] = ring.clone_el(data.at(i));
424 }
425
426 self.fft_table.unordered_inv_fft(&mut tmp[..], ring);
427
428 for i in (self.rank()..self.fft_table.len()).rev() {
429 let factor = ring.clone_el(&tmp[i]);
430 for (j, c) in self.zeta_pow_rank.iter() {
431 let mut add = ring.clone_el(&factor);
432 ring.mul_assign_ref(&mut add, c);
433 ring.add_assign(&mut tmp[i - self.rank() + *j], add);
434 }
435 }
436
437 for i in 0..self.rank() {
438 *data.at_mut(i) = ring.clone_el(&tmp[i]);
439 }
440 }
441
442 fn small_basis_to_mult_basis<V>(&self, mut data: V)
443 where V: VectorViewMut<zn_64::ZnEl>
444 {
445 let ring = self.base_ring();
446 let mut tmp = Vec::with_capacity_in(self.fft_table.len(), self.allocator.clone());
447 tmp.extend((0..self.fft_table.len()).map(|_| ring.zero()));
448 for i in 0..self.rank() {
449 tmp[i] = ring.clone_el(data.at(i));
450 }
451
452 self.fft_table.unordered_fft(&mut tmp[..], ring);
453
454 for (i, j) in self.fft_output_indices() {
455 *data.at_mut(i) = ring.clone_el(&tmp[j]);
456 }
457 }
458
459 fn coeff_basis_to_small_basis<V>(&self, _data: V)
460 where V: VectorViewMut<zn_64::ZnEl>
461 {}
462
463 fn small_basis_to_coeff_basis<V>(&self, _data: V)
464 where V: VectorViewMut<zn_64::ZnEl>
465 {}
466
467 fn rank(&self) -> usize {
468 self.rank
469 }
470
471 fn base_ring(&self) -> &zn_64::Zn {
472 &self.ring
473 }
474}
475
476impl<F, A> HECyclotomicNumberRingMod for OddCyclotomicDecomposedNumberRing<F, A>
477 where F: Send + Sync + FFTAlgorithm<zn_64::ZnBase> + PartialEq,
478 A: Send + Sync + Allocator + Clone
479{
480 fn n(&self) -> usize {
481 self.fft_table.len()
482 }
483
484 fn permute_galois_action<V1, V2>(&self, src: V1, mut dst: V2, galois_element: CyclotomicGaloisGroupEl)
485 where V1: VectorView<zn_64::ZnEl>,
486 V2: SwappableVectorViewMut<zn_64::ZnEl>
487 {
488 assert_eq!(self.rank(), src.len());
489 assert_eq!(self.rank(), dst.len());
490 let ring = self.base_ring();
491 let galois_group = self.galois_group();
492 let index_ring = galois_group.underlying_ring();
493 let hom = index_ring.can_hom(&StaticRing::<i64>::RING).unwrap();
494
495 for (j, i) in self.fft_output_indices() {
496 *dst.at_mut(j) = ring.clone_el(src.at(self.fft_output_indices_to_indices[self.fft_table.unordered_fft_permutation_inv(
497 index_ring.smallest_positive_lift(index_ring.mul(galois_group.to_ring_el(galois_element), hom.map(self.fft_table.unordered_fft_permutation(i) as i64))) as usize
498 )]));
499 }
500 }
501}
502
503pub struct CompositeCyclotomicDecomposedNumberRing<F, A = Global>
513 where F: FFTAlgorithm<zn_64::ZnBase> + PartialEq,
514 A: Allocator + Clone
515{
516 tensor_factor1: OddCyclotomicDecomposedNumberRing<F, A>,
517 tensor_factor2: OddCyclotomicDecomposedNumberRing<F, A>,
518 small_to_coeff_conversion_matrix: Vec<Vec<(usize, zn_64::ZnEl)>>,
521 coeff_to_small_conversion_matrix: Vec<Vec<(usize, zn_64::ZnEl)>>
523}
524
525impl<F, A> PartialEq for CompositeCyclotomicDecomposedNumberRing<F, A>
526 where F: FFTAlgorithm<zn_64::ZnBase> + PartialEq,
527 A: Allocator + Clone
528{
529 fn eq(&self, other: &Self) -> bool {
530 self.tensor_factor1 == other.tensor_factor1 && self.tensor_factor2 == other.tensor_factor2
531 }
532}
533
534impl<F, A> HENumberRingMod for CompositeCyclotomicDecomposedNumberRing<F, A>
535 where F: Send + Sync + FFTAlgorithm<zn_64::ZnBase> + PartialEq,
536 A: Send + Sync + Allocator + Clone
537{
538 #[instrument(skip_all)]
539 fn small_basis_to_mult_basis<V>(&self, mut data: V)
540 where V: SwappableVectorViewMut<zn_64::ZnEl>
541 {
542 for i in 0..self.tensor_factor2.rank() {
543 self.tensor_factor1.small_basis_to_mult_basis(SubvectorView::new(&mut data).restrict((i * self.tensor_factor1.rank())..((i + 1) * self.tensor_factor1.rank())));
544 }
545 for j in 0..self.tensor_factor1.rank() {
546 self.tensor_factor2.small_basis_to_mult_basis(SubvectorView::new(&mut data).restrict(j..).step_by_view(self.tensor_factor1.rank()));
547 }
548 }
549
550 #[instrument(skip_all)]
551 fn mult_basis_to_small_basis<V>(&self, mut data: V)
552 where V: SwappableVectorViewMut<zn_64::ZnEl>
553 {
554 for j in 0..self.tensor_factor1.rank() {
555 self.tensor_factor2.mult_basis_to_small_basis(SubvectorView::new(&mut data).restrict(j..).step_by_view(self.tensor_factor1.rank()));
556 }
557 for i in 0..self.tensor_factor2.rank() {
558 self.tensor_factor1.mult_basis_to_small_basis(SubvectorView::new(&mut data).restrict((i * self.tensor_factor1.rank())..((i + 1) * self.tensor_factor1.rank())));
559 }
560 }
561
562 #[instrument(skip_all)]
563 fn coeff_basis_to_small_basis<V>(&self, mut data: V)
564 where V: SwappableVectorViewMut<zn_64::ZnEl>
565 {
566 let mut result = Vec::with_capacity_in(self.rank(), &self.tensor_factor1.allocator);
567 result.resize_with(self.rank(), || self.base_ring().zero());
568 for i in 0..self.rank() {
569 for (j, c) in &self.coeff_to_small_conversion_matrix[i] {
570 self.base_ring().add_assign(&mut result[*j], self.base_ring().mul_ref(data.at(i), c));
571 }
572 }
573 for (i, c) in result.drain(..).enumerate() {
574 *data.at_mut(i) = c;
575 }
576 }
577
578 #[instrument(skip_all)]
579 fn small_basis_to_coeff_basis<V>(&self, mut data: V)
580 where V: SwappableVectorViewMut<zn_64::ZnEl>
581 {
582 let mut result = Vec::with_capacity_in(self.rank(), &self.tensor_factor1.allocator);
583 result.resize_with(self.rank(), || self.base_ring().zero());
584 for i in 0..self.rank() {
585 for (j, c) in &self.small_to_coeff_conversion_matrix[i] {
586 self.base_ring().add_assign(&mut result[*j], self.base_ring().mul_ref(data.at(i), c));
587 }
588 }
589 for (i, c) in result.drain(..).enumerate() {
590 *data.at_mut(i) = c;
591 }
592 }
593
594 fn rank(&self) -> usize {
595 self.tensor_factor1.rank() * self.tensor_factor2.rank()
596 }
597
598 fn base_ring(&self) -> &zn_64::Zn {
599 self.tensor_factor1.base_ring()
600 }
601}
602
603impl<F, A> HECyclotomicNumberRingMod for CompositeCyclotomicDecomposedNumberRing<F, A>
604 where F: Send + Sync + FFTAlgorithm<zn_64::ZnBase> + PartialEq,
605 A: Send + Sync + Allocator + Clone
606{
607 fn n(&self) -> usize {
608 self.tensor_factor1.n() * self.tensor_factor2.n()
609 }
610
611 fn permute_galois_action<V1, V2>(&self, src: V1, mut dst: V2, galois_element: CyclotomicGaloisGroupEl)
612 where V1: VectorView<zn_64::ZnEl>,
613 V2: SwappableVectorViewMut<zn_64::ZnEl>
614 {
615 let index_ring = self.galois_group();
616 let ring_factor1 = self.tensor_factor1.galois_group();
617 let ring_factor2 = self.tensor_factor2.galois_group();
618
619 let g1 = ring_factor1.from_representative(index_ring.representative(galois_element) as i64);
620 let g2 = ring_factor2.from_representative(index_ring.representative(galois_element) as i64);
621 let mut tmp = Vec::with_capacity_in(self.rank(), &self.tensor_factor1.allocator);
622 tmp.resize_with(self.rank(), || self.base_ring().zero());
623 for i in 0..self.tensor_factor2.rank() {
624 self.tensor_factor1.permute_galois_action(
625 SubvectorView::new(&src).restrict((i * self.tensor_factor1.rank())..((i + 1) * self.tensor_factor1.rank())),
626 &mut tmp[(i * self.tensor_factor1.rank())..((i + 1) * self.tensor_factor1.rank())],
627 g1
628 );
629 }
630 for j in 0..self.tensor_factor1.rank() {
631 self.tensor_factor2.permute_galois_action(
632 SubvectorView::new(&tmp[..]).restrict(j..).step_by_view(self.tensor_factor1.rank()),
633 SubvectorView::new(&mut dst).restrict(j..).step_by_view(self.tensor_factor1.rank()),
634 g2
635 );
636 }
637 }
638}
639
640#[cfg(test)]
641use feanor_math::assert_el_eq;
642#[cfg(test)]
643use crate::ciphertext_ring::double_rns_ring;
644#[cfg(test)]
645use crate::ciphertext_ring::single_rns_ring;
646#[cfg(test)]
647use crate::number_ring::quotient;
648#[cfg(test)]
649use crate::ring_literal;
650
651#[test]
652fn test_odd_cyclotomic_double_rns_ring() {
653 double_rns_ring::test_with_number_ring(OddCyclotomicNumberRing::new(5));
654 double_rns_ring::test_with_number_ring(OddCyclotomicNumberRing::new(7));
655 double_rns_ring::test_with_number_ring(CompositeCyclotomicNumberRing::new(3, 5));
656 double_rns_ring::test_with_number_ring(CompositeCyclotomicNumberRing::new(3, 7));
657}
658
659#[test]
660fn test_odd_cyclotomic_single_rns_ring() {
661 single_rns_ring::test_with_number_ring(OddCyclotomicNumberRing::new(5));
662 single_rns_ring::test_with_number_ring(OddCyclotomicNumberRing::new(7));
663 single_rns_ring::test_with_number_ring(CompositeCyclotomicNumberRing::new(3, 5));
664 single_rns_ring::test_with_number_ring(CompositeCyclotomicNumberRing::new(3, 7));
665}
666
667#[test]
668fn test_odd_cyclotomic_decomposition_ring() {
669 quotient::test_with_number_ring(OddCyclotomicNumberRing::new(5));
670 quotient::test_with_number_ring(OddCyclotomicNumberRing::new(7));
671 let ring = CompositeCyclotomicNumberRing::new(3, 5);
672 quotient::test_with_number_ring(ring);
673 quotient::test_with_number_ring(CompositeCyclotomicNumberRing::new(3, 7));
674}
675
676#[test]
677fn test_small_coeff_basis_conversion() {
678 let ring = zn_64::Zn::new(241);
679 let number_ring = CompositeCyclotomicNumberRing::new(3, 5);
680 let decomposition = number_ring.mod_p(ring);
681
682 let arr_create = |data: [i32; 8]| std::array::from_fn::<_, 8, _>(|i| ring.int_hom().map(data[i]));
683 let assert_arr_eq = |fst: [zn_64::ZnEl; 8], snd: [zn_64::ZnEl; 8]| assert!(
684 fst.iter().zip(snd.iter()).all(|(x, y)| ring.eq_el(x, y)),
685 "expected {:?} = {:?}",
686 std::array::from_fn::<_, 8, _>(|i| ring.format(&fst[i])),
687 std::array::from_fn::<_, 8, _>(|i| ring.format(&snd[i]))
688 );
689
690 let original = arr_create([1, 0, 0, 0, 0, 0, 0, 0]);
691 let expected = arr_create([1, 0, 0, 0, 0, 0, 0, 0]);
692 let mut actual = original;
693 decomposition.coeff_basis_to_small_basis(&mut actual);
694 assert_arr_eq(expected, actual);
695 decomposition.small_basis_to_coeff_basis(&mut actual);
696 assert_arr_eq(original, actual);
697
698 let original = arr_create([0, 1, 0, 0, 0, 0, 0, 0]);
700 let expected = arr_create([0, 0, 0, 0, 240, 240, 0, 0]);
701 let mut actual = original;
702 decomposition.coeff_basis_to_small_basis(&mut actual);
703 assert_arr_eq(expected, actual);
704 decomposition.small_basis_to_coeff_basis(&mut actual);
705 assert_arr_eq(original, actual);
706
707 let original = arr_create([0, 0, 240, 0, 0, 0, 0, 0]);
708 let expected = arr_create([0, 1, 0, 1, 0, 1, 0, 1]);
709 let mut actual = original;
710 decomposition.coeff_basis_to_small_basis(&mut actual);
711 assert_arr_eq(expected, actual);
712 decomposition.small_basis_to_coeff_basis(&mut actual);
713 assert_arr_eq(original, actual);
714
715 let original = arr_create([0, 0, 0, 1, 0, 0, 0, 0]);
716 let expected = arr_create([0, 0, 1, 0, 0, 0, 0, 0]);
717 let mut actual = original;
718 decomposition.coeff_basis_to_small_basis(&mut actual);
719 assert_arr_eq(expected, actual);
720 decomposition.small_basis_to_coeff_basis(&mut actual);
721 assert_arr_eq(original, actual);
722
723 let original = arr_create([0, 0, 0, 0, 0, 1, 0, 0]);
724 let expected = arr_create([0, 1, 0, 0, 0, 0, 0, 0]);
725 let mut actual = original;
726 decomposition.coeff_basis_to_small_basis(&mut actual);
727 assert_arr_eq(expected, actual);
728 decomposition.small_basis_to_coeff_basis(&mut actual);
729 assert_arr_eq(original, actual);
730}
731
732#[test]
733fn test_permute_galois_automorphism() {
734 let Fp = zn_64::Zn::new(257);
735 let R = quotient::NumberRingQuotientBase::new(OddCyclotomicNumberRing::new(7), Fp);
736 let gal_el = |x: i64| R.galois_group().from_representative(x);
737
738 assert_el_eq!(R, ring_literal(&R, &[0, 0, 1, 0, 0, 0]), R.get_ring().apply_galois_action(&ring_literal(&R, &[0, 1, 0, 0, 0, 0]), gal_el(2)));
739 assert_el_eq!(R, ring_literal(&R, &[0, 0, 0, 1, 0, 0]), R.get_ring().apply_galois_action(&ring_literal(&R, &[0, 1, 0, 0, 0, 0]), gal_el(3)));
740 assert_el_eq!(R, ring_literal(&R, &[0, 0, 0, 0, 1, 0]), R.get_ring().apply_galois_action(&ring_literal(&R, &[0, 0, 1, 0, 0, 0]), gal_el(2)));
741 assert_el_eq!(R, ring_literal(&R, &[-1, -1, -1, -1, -1, -1]), R.get_ring().apply_galois_action(&ring_literal(&R, &[0, 0, 1, 0, 0, 0]), gal_el(3)));
742
743 let R = quotient::NumberRingQuotientBase::new(CompositeCyclotomicNumberRing::new(5, 3), Fp);
744 let gal_el = |x: i64| R.galois_group().from_representative(x);
745
746 assert_el_eq!(R, ring_literal(&R, &[0, 0, 1, 0, 0, 0, 0, 0]), R.get_ring().apply_galois_action(&ring_literal(&R, &[0, 1, 0, 0, 0, 0, 0, 0]), gal_el(2)));
747 assert_el_eq!(R, ring_literal(&R, &[0, 0, 0, 0, 1, 0, 0, 0]), R.get_ring().apply_galois_action(&ring_literal(&R, &[0, 1, 0, 0, 0, 0, 0, 0]), gal_el(4)));
748 assert_el_eq!(R, ring_literal(&R, &[-1, 1, 0, -1, 1, -1, 0, 1]), R.get_ring().apply_galois_action(&ring_literal(&R, &[0, 1, 0, 0, 0, 0, 0, 0]), gal_el(8)));
749 assert_el_eq!(R, ring_literal(&R, &[-1, 1, 0, -1, 1, -1, 0, 1]), R.get_ring().apply_galois_action(&ring_literal(&R, &[0, 0, 0, 0, 1, 0, 0, 0]), gal_el(2)));
750}