1use std::alloc::Allocator;
2use std::alloc::Global;
3
4use feanor_math::algorithms::matmul::ComputeInnerProduct;
5use feanor_math::integer::*;
6use feanor_math::matrix::*;
7use feanor_math::homomorphism::*;
8use feanor_math::seq::permute::permute_inv;
9use feanor_math::seq::*;
10use feanor_math::rings::zn::{ZnRingStore, ZnRing};
11use feanor_math::rings::zn::zn_64::*;
12use feanor_math::divisibility::DivisibilityRingStore;
13use feanor_math::ring::*;
14use feanor_math::ordered::OrderedRingStore;
15use tracing::instrument;
16
17use crate::ZZbig;
18use crate::ZZi128;
19use crate::ZZi64;
20
21use super::sort_unstable_permutation;
22use super::RNSOperation;
23
24pub struct AlmostExactBaseConversion<A = Global>
44 where A: Allocator + Clone
45{
46 from_summands_unordered: Vec<Zn>,
48 from_summands_ordered: Vec<Zn>,
50 from_summands_permutation: Vec<usize>,
52 to_summands_unordered: Vec<Zn>,
54 to_summands_ordered: Vec<Zn>,
56 to_summands_permutation: Vec<usize>,
58 q_over_Q: Vec<ZnEl>,
60 Q_over_q: Vec<ZnEl>,
62 Q_over_q_int: Vec<i128>,
64 Q_downscaled: i128,
65 Q_mod_q: Vec<ZnEl>,
67 allocator: A
68}
69
70impl<A> AlmostExactBaseConversion<A>
71 where A: Allocator + Clone
72{
73 #[instrument(skip_all)]
78 pub fn new_with(in_rings: Vec<Zn>, out_rings: Vec<Zn>, allocator: A) -> Self {
79 for i in 0..in_rings.len() {
80 assert!(in_rings.at(i).integer_ring().get_ring() == ZZi64.get_ring());
81 }
82 for i in 0..out_rings.len() {
83 assert!(out_rings.at(i).integer_ring().get_ring() == ZZi64.get_ring());
84 }
85 let Q = ZZbig.prod((0..in_rings.len()).map(|i| int_cast(*in_rings.at(i).modulus(), ZZbig, ZZi64)));
86 let in_rings_unordered = in_rings.clone();
87 let (in_rings_ordered, in_rings_permutation_inv) = sort_unstable_permutation(in_rings, |ring_l, ring_r| ZZi64.cmp(ring_l.modulus(), ring_r.modulus()));
88 let mut in_rings_permutation = (0..in_rings_permutation_inv.len()).collect::<Vec<_>>();
89 permute_inv(&mut in_rings_permutation, |i| in_rings_permutation_inv[i]);
90
91 let out_rings_unordered = out_rings.clone();
92 let (out_rings_ordered, out_rings_permutation_inv) = sort_unstable_permutation(out_rings, |ring_l, ring_r| ZZi64.cmp(ring_l.modulus(), ring_r.modulus()));
93 let mut out_rings_permutation = (0..out_rings_permutation_inv.len()).collect::<Vec<_>>();
94 permute_inv(&mut out_rings_permutation, |i| out_rings_permutation_inv[i]);
95
96 let log2_r = ZZi64.abs_log2_ceil(&(in_rings_ordered.len() as i64)).unwrap() as i64;
98 let log2_qmax = ZZi64.abs_log2_ceil(&(0..in_rings_ordered.len()).map(|i| *in_rings_ordered.at(i).modulus()).max().unwrap()).unwrap() as i64;
99 let log2_Q = ZZbig.abs_log2_ceil(&Q).unwrap() as i64;
100 let drop_bits = log2_Q - log2_r - log2_qmax - 5;
101 let drop_bits = if drop_bits < 0 { 0 } else { drop_bits as usize };
102 assert!((drop_bits as i64) < log2_Q);
103 assert!(i128::BITS as i64 - 1 > log2_r + log2_Q - drop_bits as i64);
104
105 Self {
106 Q_over_q: (0..(in_rings_ordered.len() * out_rings_ordered.len())).map(|idx|
107 out_rings_ordered.at(idx / in_rings_ordered.len()).coerce(&ZZbig, ZZbig.checked_div(&Q, &int_cast(*in_rings_ordered.at(idx % in_rings_ordered.len()).modulus(), ZZbig, ZZi64)).unwrap())
108 ).collect(),
109 Q_over_q_int: (0..in_rings_ordered.len()).map(|i|
110 int_cast(ZZbig.rounded_div(ZZbig.clone_el(&Q), &ZZbig.mul(int_cast(*in_rings_ordered.at(i).modulus(), ZZbig, ZZi64), ZZbig.power_of_two(drop_bits))), ZZi128, ZZbig)
111 ).collect(),
112 q_over_Q: (0..in_rings_ordered.len()).map(|i|
113 in_rings_ordered.at(i).invert(&in_rings_ordered.at(i).coerce(&ZZbig, ZZbig.checked_div(&Q, &int_cast(*in_rings_ordered.at(i).modulus(), ZZbig, ZZi64)).unwrap())).unwrap()
114 ).collect(),
115 Q_mod_q: (0..out_rings_ordered.len()).map(|i|
116 out_rings_ordered.at(i).coerce(&ZZbig, ZZbig.clone_el(&Q))
117 ).collect(),
118 Q_downscaled: int_cast(ZZbig.rounded_div(Q, &ZZbig.power_of_two(drop_bits)), ZZi128, ZZbig),
119 allocator: allocator,
120 from_summands_unordered: in_rings_unordered,
121 from_summands_ordered: in_rings_ordered,
122 from_summands_permutation: in_rings_permutation,
123 to_summands_ordered: out_rings_ordered,
124 to_summands_unordered: out_rings_unordered,
125 to_summands_permutation: out_rings_permutation
126 }
127 }
128}
129
130impl<A> RNSOperation for AlmostExactBaseConversion<A>
131 where A: Allocator + Clone
132{
133 type Ring = Zn;
134
135 type RingType = ZnBase;
136
137 fn input_rings<'a>(&'a self) -> &'a [Zn] {
138 &self.from_summands_unordered
139 }
140
141 fn output_rings<'a>(&'a self) -> &'a [Zn] {
142 &self.to_summands_unordered
143 }
144
145 #[instrument(skip_all)]
156 fn apply<V1, V2>(&self, input: Submatrix<V1, El<Self::Ring>>, mut output: SubmatrixMut<V2, El<Self::Ring>>)
157 where V1: AsPointerToSlice<El<Self::Ring>>,
158 V2: AsPointerToSlice<El<Self::Ring>>
159 {
160 assert_eq!(input.row_count(), self.input_rings().len());
161 assert_eq!(output.row_count(), self.output_rings().len());
162 assert_eq!(input.col_count(), output.col_count());
163
164 let in_len = input.row_count();
165 let col_count = input.col_count();
166 let out_len = output.row_count();
167
168 let i64_to_homs = (0..self.to_summands_ordered.len()).map(|k| self.to_summands_ordered.at(k).can_hom(&ZZi64).unwrap()).collect::<Vec<_>>();
169 let i128_to_homs = (0..self.to_summands_ordered.len()).map(|k| self.to_summands_ordered.at(k).can_hom(&ZZi128).unwrap()).collect::<Vec<_>>();
170
171 let mut lifts = Vec::with_capacity_in(col_count * in_len, self.allocator.clone());
173 lifts.extend((0..(in_len * col_count)).map(|_| 0));
174 let mut lifts = SubmatrixMut::from_1d(&mut lifts, in_len, col_count);
175 for i in 0..in_len {
176 for j in 0..col_count {
177 let input_i = self.from_summands_permutation[i];
178 debug_assert!(self.input_rings().at(input_i).get_ring() == self.from_summands_ordered[i].get_ring());
179 *lifts.at_mut(i, j) = self.from_summands_ordered[i].smallest_positive_lift(self.from_summands_ordered[i].mul_ref(input.at(input_i, j), self.q_over_Q.at(i)))
180 }
181 }
182
183 let mut out_ordered = Vec::with_capacity_in(col_count * out_len, self.allocator.clone());
184 out_ordered.extend(self.to_summands_ordered.iter().flat_map(|summand| std::iter::repeat_n(summand, col_count)).map(|summand| summand.zero()));
185 let mut out_ordered = SubmatrixMut::from_1d(&mut out_ordered, out_len, col_count);
186
187 for k in 0..out_ordered.row_count() {
190 let no_red_steps = (0..in_len).take_while(|i| ZZi64.is_gt(self.to_summands_ordered.at(k).modulus(), self.from_summands_ordered.at(*i).modulus())).count();
191 if cfg!(feature = "force_rns_conversion_full_reduction") {
193 for j in 0..col_count {
194 *out_ordered.at_mut(k, j) = <_ as ComputeInnerProduct>::inner_product_ref_fst(self.to_summands_ordered.at(k).get_ring(), (0..in_len).map(|i| {
195 (self.Q_over_q.at(i + in_len * k), i64_to_homs[k].map_ref(lifts.at(i, j)))
196 }));
197 }
198 } else if no_red_steps == in_len {
199 for j in 0..col_count {
200 *out_ordered.at_mut(k, j) = <_ as ComputeInnerProduct>::inner_product_ref_fst(self.to_summands_ordered.at(k).get_ring(), (0..no_red_steps).map(|i| {
201 (self.Q_over_q.at(i + in_len * k), self.to_summands_ordered.at(k).get_ring().from_int_promise_reduced(*lifts.at(i, j)))
202 }));
203 }
204 } else {
205 for j in 0..col_count {
206 *out_ordered.at_mut(k, j) = <_ as ComputeInnerProduct>::inner_product_ref_fst(self.to_summands_ordered.at(k).get_ring(), (0..no_red_steps).map(|i| {
207 (self.Q_over_q.at(i + in_len * k), self.to_summands_ordered.at(k).get_ring().from_int_promise_reduced(*lifts.at(i, j)))
208 }).chain((no_red_steps..in_len).map(|i| {
209 (self.Q_over_q.at(i + in_len * k), i64_to_homs[k].map_ref(lifts.at(i, j)))
210 })));
211 }
212 }
213 }
214
215 for j in 0..col_count {
218 let correction = ZZi128.rounded_div(<_ as ComputeInnerProduct>::inner_product(ZZi128.get_ring(),
219 (0..input.row_count()).map(|i| (*lifts.at(i, j) as i128, self.Q_over_q_int[i]))
220 ), &self.Q_downscaled);
221 for i in 0..out_ordered.row_count() {
222 let output_i = self.to_summands_permutation[i];
223 *output.at_mut(output_i, j) = self.to_summands_ordered[i].sub_ref_fst(out_ordered.at(i, j), self.to_summands_ordered[i].mul_ref_snd(i128_to_homs[i].map_ref(&correction), &self.Q_mod_q[i]));
224 }
225 }
226 }
227}
228
229#[cfg(test)]
230use feanor_math::assert_el_eq;
231#[cfg(test)]
232use test::Bencher;
233#[cfg(test)]
234use feanor_math::algorithms::miller_rabin::is_prime;
235#[cfg(test)]
236use feanor_math::rings::finite::FiniteRingStore;
237
238#[test]
239fn test_rns_base_conversion() {
240 let from = vec![Zn::new(17), Zn::new(97)];
241 let to = vec![Zn::new(17), Zn::new(97), Zn::new(113), Zn::new(257)];
242 let q = 17 * 97;
243 let table = AlmostExactBaseConversion::new_with(from.clone(), to.clone(), Global);
244
245 for k in -(q/4)..=(q/4) {
247 let input = from.iter().map(|R| R.int_hom().map(k)).collect::<Vec<_>>();
248 let expected = to.iter().map(|R| R.int_hom().map(k)).collect::<Vec<_>>();
249 let mut actual = to.iter().map(|R| R.int_hom().map(k)).collect::<Vec<_>>();
250
251 table.apply(
252 Submatrix::from_1d(&input, 2, 1),
253 SubmatrixMut::from_1d(&mut actual, 4, 1)
254 );
255
256 for j in 0..to.len() {
257 assert_el_eq!(to.at(j), expected.at(j), actual.at(j));
258 }
259 }
260
261 for k in -(q/2)..=(q/2) {
262 let input = from.iter().map(|R| R.int_hom().map(k)).collect::<Vec<_>>();
263 let expected = to.iter().map(|R| R.int_hom().map(k)).collect::<Vec<_>>();
264 let mut actual = to.iter().map(|R| R.int_hom().map(k)).collect::<Vec<_>>();
265
266 table.apply(
267 Submatrix::from_1d(&input, 2, 1),
268 SubmatrixMut::from_1d(&mut actual, 4, 1)
269 );
270
271 for j in 0..to.len() {
272 assert!(
273 to.at(j).eq_el(expected.at(j), actual.at(j)) ||
274 to.at(j).eq_el(&to.at(j).add_ref_fst(expected.at(j), to.at(j).int_hom().map(17 * 97)), actual.at(j)) ||
275 to.at(j).eq_el(&to.at(j).sub_ref_fst(expected.at(j), to.at(j).int_hom().map(17 * 97)), actual.at(j))
276 );
277 }
278 }
279}
280
281#[test]
282fn test_rns_base_conversion_unordered() {
283 let from = vec![Zn::new(31), Zn::new(29)];
284 let to = vec![Zn::new(5), Zn::new(17), Zn::new(23), Zn::new(19)];
285 let q = 31 * 29;
286 let table = AlmostExactBaseConversion::new_with(from.clone(), to.clone(), Global);
287
288 for k in -(q/2)..(q/2) {
289 let input = from.iter().map(|ring| ring.int_hom().map(k)).collect::<Vec<_>>();
290 let expected = to.iter().map(|ring| ring.int_hom().map(k)).collect::<Vec<_>>();
291 let mut actual = to.iter().map(|ring| ring.zero()).collect::<Vec<_>>();
292
293 table.apply(Submatrix::from_1d(&input, 2, 1), SubmatrixMut::from_1d(&mut actual, 4, 1));
294
295 for j in 0..to.len() {
296 assert!(
297 to.at(j).smallest_lift(to.at(j).sub_ref(expected.at(j), actual.at(j))).abs() == 0 ||
298 to.at(j).smallest_lift(to.at(j).sub_ref(expected.at(j), actual.at(j))).abs() == q as i64,
299 "Expected {} to be {} +/- {}",
300 to.at(j).format(actual.at(j)),
301 to.at(j).format(expected.at(j)),
302 q
303 );
304 }
305 }
306}
307
308#[test]
309fn test_rns_base_conversion_unordered_small() {
310 let from = vec![Zn::new(17), Zn::new(97)];
311 let to = vec![Zn::new(257), Zn::new(113)];
312 let q = 17 * 97;
313 let table = AlmostExactBaseConversion::new_with(from.clone(), to.clone(), Global);
314
315 for k in -(q/2)..(q/2) {
316 let input = from.iter().map(|ring| ring.int_hom().map(k)).collect::<Vec<_>>();
317 let expected = to.iter().map(|ring| ring.int_hom().map(k)).collect::<Vec<_>>();
318 let mut actual = to.iter().map(|ring| ring.zero()).collect::<Vec<_>>();
319
320 table.apply(Submatrix::from_1d(&input, 2, 1), SubmatrixMut::from_1d(&mut actual, 2, 1));
321
322 for j in 0..to.len() {
323 assert!(
324 to.at(j).smallest_lift(to.at(j).sub_ref(expected.at(j), actual.at(j))).abs() == 0 ||
325 to.at(j).smallest_lift(to.at(j).sub_ref(expected.at(j), actual.at(j))).abs() == q as i64,
326 "Expected {} to be {} +/- {}",
327 to.at(j).format(actual.at(j)),
328 to.at(j).format(expected.at(j)),
329 q
330 );
331 }
332 }
333}
334
335#[test]
336fn test_rns_base_conversion_small() {
337 let from = vec![Zn::new(3), Zn::new(97)];
338 let to = vec![Zn::new(17)];
339 let q = 3 * 97;
340 let table = AlmostExactBaseConversion::new_with(from.clone(), to.clone(), Global);
341
342 for k in -(q/2)..(q/2) {
343 let expected = to.iter().map(|ring| ring.int_hom().map(k)).collect::<Vec<_>>();
344 let mut actual = to.iter().map(|R| R.int_hom().map(k)).collect::<Vec<_>>();
345 table.apply(
346 Submatrix::from_1d(&[from[0].int_hom().map(k), from[1].int_hom().map(k)], 2, 1),
347 SubmatrixMut::from_1d(&mut actual, 1, 1)
348 );
349
350 assert!(
351 to.at(0).smallest_lift(to.at(0).sub_ref(expected.at(0), actual.at(0))).abs() == 0 ||
352 to.at(0).smallest_lift(to.at(0).sub_ref(expected.at(0), actual.at(0))).abs() == q as i64,
353 "Expected {} to be {} +/- {}",
354 to.at(0).format(actual.at(0)),
355 to.at(0).format(expected.at(0)),
356 q
357 );
358 }
359}
360
361#[test]
362fn test_rns_base_conversion_not_coprime() {
363 let from = vec![Zn::new(17), Zn::new(97), Zn::new(113)];
364 let to = vec![Zn::new(17), Zn::new(97), Zn::new(113), Zn::new(257)];
365 let q = 17 * 97 * 113;
366 let table = AlmostExactBaseConversion::new_with(from.clone(), to.clone(), Global);
367
368 for k in -(q/4)..=(q/4) {
369 let x = from.iter().map(|R| R.int_hom().map(k)).collect::<Vec<_>>();
370 let expected = to.iter().map(|R| R.int_hom().map(k)).collect::<Vec<_>>();
371 let mut actual = to.iter().map(|R| R.int_hom().map(k)).collect::<Vec<_>>();
372
373 table.apply(
374 Submatrix::from_1d(&x, 3, 1),
375 SubmatrixMut::from_1d(&mut actual, 4, 1)
376 );
377
378 for i in 0..expected.len() {
379 assert_el_eq!(&to[i], &expected[i], actual.at(i));
380 }
381 }
382}
383
384#[test]
385fn test_rns_base_conversion_not_coprime_permuted() {
386 let from = vec![Zn::new(113), Zn::new(17), Zn::new(97)];
387 let to = vec![Zn::new(17), Zn::new(97), Zn::new(113), Zn::new(257)];
388 let q = 17 * 97 * 113;
389 let table = AlmostExactBaseConversion::new_with(from.clone(), to.clone(), Global);
390
391 for k in -(q/4)..=(q/4) {
392 let input = from.iter().map(|R| R.int_hom().map(k)).collect::<Vec<_>>();
393 let expected = to.iter().map(|R| R.int_hom().map(k)).collect::<Vec<_>>();
394 let mut actual = to.iter().map(|R| R.int_hom().map(k)).collect::<Vec<_>>();
395
396 table.apply(
397 Submatrix::from_1d(&input, 3, 1),
398 SubmatrixMut::from_1d(&mut actual, 4, 1)
399 );
400
401 for i in 0..expected.len() {
402 assert_el_eq!(&to[i], &expected[i], actual.at(i));
403 }
404 }
405}
406
407#[test]
408fn test_rns_base_conversion_coprime() {
409 let from = vec![Zn::new(17), Zn::new(97), Zn::new(113)];
410 let to = vec![Zn::new(19), Zn::new(23), Zn::new(257)];
411 let q = 17 * 97 * 113;
412 let table = AlmostExactBaseConversion::new_with(from.clone(), to.clone(), Global);
413
414 for k in -(q/4)..=(q/4) {
415 let x = from.iter().map(|R| R.int_hom().map(k)).collect::<Vec<_>>();
416 let expected = to.iter().map(|R| R.int_hom().map(k)).collect::<Vec<_>>();
417 let mut actual = to.iter().map(|R| R.int_hom().map(k)).collect::<Vec<_>>();
418
419 table.apply(
420 Submatrix::from_1d(&x, 3, 1),
421 SubmatrixMut::from_1d(&mut actual, 3, 1)
422 );
423
424 for i in 0..expected.len() {
425 assert_el_eq!(&to[i], &expected[i], actual.at(i));
426 }
427 }
428}
429
430#[bench]
431fn bench_rns_base_conversion(bencher: &mut Bencher) {
432 let in_moduli_count = 20;
433 let out_moduli_count = 40;
434 let cols = 1000;
435 let mut primes = ((1 << 30)..).map(|k| (1 << 10) * k + 1).filter(|p| is_prime(&ZZi64, p, 10)).map(|p| Zn::new(p as u64));
436 let in_moduli = primes.by_ref().take(in_moduli_count).collect::<Vec<_>>();
437 let out_moduli = primes.take(out_moduli_count).collect::<Vec<_>>();
438 let conv = AlmostExactBaseConversion::new_with(in_moduli.clone(), out_moduli.clone(), Global);
439
440 let mut rng = oorandom::Rand64::new(1);
441 let mut in_data = (0..(in_moduli_count * cols)).map(|idx| in_moduli[idx / cols].zero()).collect::<Vec<_>>();
442 let mut in_matrix = SubmatrixMut::from_1d(&mut in_data, in_moduli_count, cols);
443 let mut out_data = (0..(out_moduli_count * cols)).map(|idx| out_moduli[idx / cols].zero()).collect::<Vec<_>>();
444 let mut out_matrix = SubmatrixMut::from_1d(&mut out_data, out_moduli_count, cols);
445
446 bencher.iter(|| {
447 for i in 0..in_moduli_count {
448 for j in 0..cols {
449 *in_matrix.at_mut(i, j) = in_moduli[i].random_element(|| rng.rand_u64());
450 }
451 }
452 conv.apply(in_matrix.as_const(), out_matrix.reborrow());
453 for i in 0..out_moduli_count {
454 for j in 0..cols {
455 std::hint::black_box(out_matrix.at(i, j));
456 }
457 }
458 });
459}
460
461#[test]
462fn test_base_conversion_large() {
463 let primes: [i64; 34] = [
464 72057594040066049,
465 288230376150870017,
466 288230376150876161,
467 288230376150878209,
468 288230376150890497,
469 288230376150945793,
470 288230376150956033,
471 288230376151062529,
472 288230376151123969,
473 288230376151130113,
474 288230376151191553,
475 288230376151388161,
476 288230376151422977,
477 288230376151529473,
478 288230376151545857,
479 288230376151554049,
480 288230376151601153,
481 288230376151625729,
482 288230376151683073,
483 288230376151748609,
484 288230376151760897,
485 288230376151779329,
486 288230376151812097,
487 288230376151902209,
488 288230376151951361,
489 288230376151994369,
490 288230376152027137,
491 288230376152061953,
492 288230376152137729,
493 288230376152154113,
494 288230376152156161,
495 288230376152205313,
496 288230376152227841,
497 288230376152340481,
498 ];
499 let in_len = 17;
500 let from = &primes[..in_len];
501 let from_prod = ZZbig.prod(from.iter().map(|p| int_cast(*p, ZZbig, ZZi64)));
502 let to = &primes[in_len..];
503 let number = ZZbig.get_ring().parse("156545561910861509258548850310120795193837265771491906959215072510998373539323526014165281634346450795208120921520265422129013635769405993324585707811035953253906720513250161495607960734366886366296007741500531044904559075687514262946086011957808717474666493477109586105297965072817051127737667010", 10).unwrap();
504 assert!(ZZbig.is_lt(&number, &from_prod));
505
506 let from = from.iter().map(|p| Zn::new(*p as u64)).collect::<Vec<_>>();
507 let to = to.iter().map(|p| Zn::new(*p as u64)).collect::<Vec<_>>();
508 let conversion = AlmostExactBaseConversion::new_with(from, to, Global);
509
510 let input = (0..in_len).map(|i| conversion.input_rings().at(i).coerce(&ZZbig, ZZbig.clone_el(&number))).collect::<Vec<_>>();
511 let expected = (0..(primes.len() - in_len)).map(|i| conversion.output_rings().at(i).coerce(&ZZbig, ZZbig.clone_el(&number))).collect::<Vec<_>>();
512 let mut output = (0..(primes.len() - in_len)).map(|i| conversion.output_rings().at(i).zero()).collect::<Vec<_>>();
513 conversion.apply(Submatrix::from_1d(&input, in_len, 1), SubmatrixMut::from_1d(&mut output, primes.len() - in_len, 1));
514
515 assert!(
516 expected.iter().zip(output.iter()).enumerate().all(|(i, (e, a))| conversion.output_rings().at(i).eq_el(e, a)) ||
517 expected.iter().zip(output.iter()).enumerate().all(|(i, (e, a))| conversion.output_rings().at(i).eq_el(e, &conversion.output_rings().at(i).add_ref_fst(a, conversion.output_rings().at(i).coerce(&ZZbig, ZZbig.clone_el(&from_prod))))) ||
518 expected.iter().zip(output.iter()).enumerate().all(|(i, (e, a))| conversion.output_rings().at(i).eq_el(e, &conversion.output_rings().at(i).sub_ref_fst(a, conversion.output_rings().at(i).coerce(&ZZbig, ZZbig.clone_el(&from_prod)))))
519 );
520}