he_ring/rnsconv/
lift.rs

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
24///
25/// Stores values for an almost exact conversion between RNS bases.
26/// A complete conversion refers to the function
27/// ```text
28///   Z/QZ -> Z/Q'Z, x -> [lift(x)]
29/// ```
30/// In our case, the output of the function is allowed to have an error of `{ -Q, 0, Q }`,
31/// unless the shortest lift of the input is bounded by `Q/4`, in which case the result
32/// is always correct.
33/// 
34/// # Implementation
35/// 
36/// Implementation is changed to approximating the lifted value using lower precision integers,
37/// which can be used to determine the overflow when computing
38/// ```text
39///   lift(x) = sum_q lift(x * q/Q mod q) * Q/q
40/// ```
41/// modulo some `q'`.
42/// 
43pub struct AlmostExactBaseConversion<A = Global>
44    where A: Allocator + Clone
45{
46    /// ordered as supplied when instantiating the object
47    from_summands_unordered: Vec<Zn>,
48    /// ordered ascendingly
49    from_summands_ordered: Vec<Zn>,
50    /// the `i`-th entry points to the position of `from_summands_ordered[i]` in `from_summands_unordered`
51    from_summands_permutation: Vec<usize>,
52    /// ordered as supplied when instantiating the object
53    to_summands_unordered: Vec<Zn>,
54    /// ordered ascendingly
55    to_summands_ordered: Vec<Zn>,
56    /// the `i`-th entry points to the position of `to_summands_ordered[i]` in `to_summands_unordered`
57    to_summands_permutation: Vec<usize>,
58    /// the values `q/Q mod q` for each RNS factor q dividing Q (ordered as `from_summands_ordered`)
59    q_over_Q: Vec<ZnEl>,
60    /// the values `Q/q mod q'` for each RNS factor q dividing Q (ordered as `from_summands_ordered`) and q' dividing Q'
61    Q_over_q: Vec<ZnEl>,
62    /// the values `Q/q/2^drop_bits'` for each RNS factor q dividing Q (ordered as `from_summands_ordered`)
63    Q_over_q_int: Vec<i128>,
64    Q_downscaled: i128,
65    /// `Q mod q'` for every `q'` dividing `Q'` (ordered as `to_summands_ordered`)
66    Q_mod_q: Vec<ZnEl>,
67    allocator: A
68}
69
70impl<A> AlmostExactBaseConversion<A> 
71    where A: Allocator + Clone
72{
73    ///
74    /// Creates a new [`AlmostExactBaseConversion`] from `q` to `q'`. The moduli belonging to `q'`
75    /// are expected to be sorted.
76    /// 
77    #[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        // When computing the approximate lifted value, we can drop `k` bits where `k <= 1 + log(Q/(4 r max(q + 1)))` and `q | Q`
97        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    ///
146    /// Performs the (almost) exact RNS base conversion
147    /// ```text
148    ///   Z/QZ -> Z/Q'Z, x -> smallest_lift(x) + kQ mod Q''
149    /// ```
150    /// where `k in { -1, 0, 1 }`.
151    /// 
152    /// Furthermore, if the shortest lift of the input is bounded by `Q/4`,
153    /// then the result is guaranteed to be exact.
154    /// 
155    #[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        // lifts contains `lift(x * q/Q mod q)` for every input element `x` and input rns base component `q`
172        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        // the main computational task is now to compute the matrix multiplication `sum_q lift(x * q/Q mod q) Q/q mod q'`
188        // for every input element `x` and output rns base component `q'`
189        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            // will we make use of the fact that for `q' >= q`, we don't have to explicitly reduce `lift(x * q/Q mod q)` modulo `q'`?
192            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        // finally, estimate `round((sum_q lift(x * q/Q mod q) Q/q) / Q)` by using fixed point arithmetic (which we just perform using `i128`s),
216        // and write the result to output
217        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    // within this area, we guarantee that no error occurs
246    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}