feanor-math 3.5.18

A library for number theory, providing implementations for arithmetic in various rings and algorithms working on them.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
use std::alloc::Global;

use oorandom;

use crate::MAX_PROBABILISTIC_REPETITIONS;
use crate::algorithms::convolution::STANDARD_CONVOLUTION;
use crate::algorithms::int_factor::is_prime_power;
use crate::algorithms::unity_root::get_prim_root_of_unity;
use crate::computation::*;
use crate::divisibility::DivisibilityRingStore;
use crate::field::{Field, FieldStore};
use crate::homomorphism::*;
use crate::integer::*;
use crate::pid::*;
use crate::ring::*;
use crate::rings::extension::extension_impl::FreeAlgebraImpl;
use crate::rings::extension::{FreeAlgebra, FreeAlgebraStore};
use crate::rings::field::{AsField, AsFieldBase};
use crate::rings::finite::{FiniteRing, FiniteRingStore};
use crate::rings::poly::dense_poly::DensePolyRing;
use crate::rings::poly::{PolyRing, PolyRingStore};
use crate::seq::VectorFn;

/// Takes a squarefree polynomial `f` in the form of the ring `R[X]/(f)` and
/// returns whether `f` is irreducible over the base ring `R`.
#[stability::unstable(feature = "enable")]
pub fn squarefree_is_irreducible_base<P, R>(poly_ring: P, mod_f_ring: R) -> bool
where
    P: RingStore,
    P::Type: PolyRing + EuclideanRing,
    R: RingStore,
    R::Type: FreeAlgebra,
    <<R as RingStore>::Type as RingExtension>::BaseRing:
        RingStore<Type = <<<P as RingStore>::Type as RingExtension>::BaseRing as RingStore>::Type>,
    <<<P as RingStore>::Type as RingExtension>::BaseRing as RingStore>::Type: FiniteRing + Field,
{
    if mod_f_ring.rank() == 1 {
        return true;
    }
    assert!(poly_ring.base_ring().get_ring() == mod_f_ring.base_ring().get_ring());
    let ZZ = BigIntRing::RING;
    let q = poly_ring.base_ring().size(&ZZ).unwrap();
    debug_assert!(ZZ.eq_el(
        &is_prime_power(&ZZ, &q).unwrap().0,
        &poly_ring.base_ring().characteristic(&ZZ).unwrap()
    ));
    let d = mod_f_ring.rank();

    // we build the polynomial `g = prod_(2i <= d) (X^(p^i) - X)`; if `g mod f` is coprime
    // to `f` then `f` must be irreducible
    let mut g_mod_f = mod_f_ring.one();
    let mut x_power_Q_mod_f = mod_f_ring.canonical_gen();
    let mut current_deg = 0;
    while 2 * current_deg < d {
        current_deg += 1;
        x_power_Q_mod_f = mod_f_ring.pow_gen(x_power_Q_mod_f, &q, ZZ);
        debug_assert!(current_deg < d);
        mod_f_ring.mul_assign(
            &mut g_mod_f,
            mod_f_ring.sub_ref_fst(&x_power_Q_mod_f, mod_f_ring.canonical_gen()),
        );
    }
    return poly_ring.is_unit(&poly_ring.ideal_gen(
        &mod_f_ring.poly_repr(&poly_ring, &g_mod_f, poly_ring.base_ring().identity()),
        &mod_f_ring.generating_poly(&poly_ring, poly_ring.base_ring().identity()),
    ));
}

/// As [`distinct_degree_factorization()`], but takes `f` in the form of the ring `R[X]/(f)`,
/// which is the internal representation that is used to actually compute the factorization.
#[stability::unstable(feature = "enable")]
pub fn distinct_degree_factorization_base<P, R, Controller>(
    poly_ring: P,
    mod_f_ring: R,
    controller: Controller,
) -> Vec<El<P>>
where
    P: RingStore,
    P::Type: PolyRing + EuclideanRing,
    R: RingStore,
    R::Type: FreeAlgebra,
    <<R as RingStore>::Type as RingExtension>::BaseRing:
        RingStore<Type = <<<P as RingStore>::Type as RingExtension>::BaseRing as RingStore>::Type>,
    <<<P as RingStore>::Type as RingExtension>::BaseRing as RingStore>::Type: FiniteRing + Field,
    Controller: ComputationController,
{
    assert!(poly_ring.base_ring().get_ring() == mod_f_ring.base_ring().get_ring());
    let ZZ = BigIntRing::RING;
    let q = poly_ring.base_ring().size(&ZZ).unwrap();
    debug_assert!(ZZ.eq_el(
        &is_prime_power(&ZZ, &q).unwrap().0,
        &poly_ring.base_ring().characteristic(&ZZ).unwrap()
    ));

    controller.run_computation(
        format_args!("distinct_degree_factor(deg={})", mod_f_ring.rank()),
        |controller| {
            let mut f = mod_f_ring.generating_poly(&poly_ring, &poly_ring.base_ring().identity());
            if mod_f_ring.rank() == 1 {
                return vec![poly_ring.one(), f];
            }

            let mut result = Vec::new();
            let mut current_deg = 0;
            result.push(poly_ring.one());
            let mut x_power_Q_mod_f = mod_f_ring.canonical_gen();
            while 2 * current_deg <= poly_ring.degree(&f).unwrap() {
                current_deg += 1;
                x_power_Q_mod_f = mod_f_ring.pow_gen(x_power_Q_mod_f, &q, ZZ);
                let fq_defining_poly_mod_f = poly_ring.sub(
                    mod_f_ring.poly_repr(&poly_ring, &x_power_Q_mod_f, &poly_ring.base_ring().identity()),
                    poly_ring.indeterminate(),
                );
                let deg_i_factor = poly_ring.normalize(poly_ring.get_ring().ideal_gen_with_controller(
                    &f,
                    &fq_defining_poly_mod_f,
                    controller.clone(),
                ));
                f = poly_ring.checked_div(&f, &deg_i_factor).unwrap();
                result.push(deg_i_factor);
                log_progress!(controller, ".");
            }
            if poly_ring.degree(&f).unwrap() >= result.len() {
                result.resize_with(poly_ring.degree(&f).unwrap(), || poly_ring.one());
                result.push(f);
            }
            return result;
        },
    )
}

/// Computes the distinct-degree factorization of a polynomial over a finite
/// field. The given polynomial must be square-free.
///
/// Concretely, if a univariate polynomial `f(X)` factors uniquely as
/// `f(X) = f1(X) ... fr(X)`, then the `d`-th distinct-degree factor of `f` is
/// `prod_i fi(X)` where `i` runs through all indices with `deg(fi(X)) = d`.
/// This function returns a list whose `d`-th entry is the `d`-th distinct degree
/// factor. Once the list ends, all further `d`-th distinct degree factors should
/// be considered to be `1`.
///
/// To get a square-free polynomial, consider using [`super::poly_squarefree_part()`].
///
/// # Example
///
/// ```rust
/// # use feanor_math::ring::*;
/// # use feanor_math::rings::zn::*;
/// # use feanor_math::rings::zn::zn_64::*;
/// # use feanor_math::rings::poly::*;
/// # use feanor_math::rings::poly::dense_poly::*;
/// # use feanor_math::rings::rational::*;
/// # use feanor_math::divisibility::*;
/// # use feanor_math::computation::*;
/// # use crate::feanor_math::homomorphism::Homomorphism;
/// # use feanor_math::algorithms::poly_factor::cantor_zassenhaus::*;
/// let Fp = Zn::new(3).as_field().ok().unwrap();
/// let FpX = DensePolyRing::new(Fp, "X");
/// // f = (X^3 + 2 X^2 + 1) (X^3 + 2 X + 1) (X^2 + 1)
/// let f = FpX.prod([
///     FpX.from_terms([(Fp.one(), 0), (Fp.one(), 2)].into_iter()),
///     FpX.from_terms([(Fp.one(), 0), (Fp.int_hom().map(2), 1), (Fp.one(), 3)].into_iter()),
///     FpX.from_terms([(Fp.one(), 0), (Fp.int_hom().map(2), 2), (Fp.one(), 3)].into_iter())
/// ].into_iter());
/// let factorization = distinct_degree_factorization(&FpX, f, DontObserve);
/// assert_eq!(4, factorization.len());
/// assert!(FpX.is_unit(&factorization[0]));
/// assert!(FpX.is_unit(&factorization[1]));
/// assert!(!FpX.is_unit(&factorization[2])); // factorization[2] is some scalar multiple of (X^2 + 1)
/// assert!(!FpX.is_unit(&factorization[3])); // factorization[3] is some scalar multiple of (X^3 + 2 X^2 + 1) (X^3 + 2 X + 1)
/// ```
#[stability::unstable(feature = "enable")]
pub fn distinct_degree_factorization<P, Controller>(poly_ring: P, mut f: El<P>, controller: Controller) -> Vec<El<P>>
where
    P: RingStore,
    P::Type: PolyRing + EuclideanRing,
    <<P as RingStore>::Type as RingExtension>::BaseRing: FieldStore + RingStore,
    <<<P as RingStore>::Type as RingExtension>::BaseRing as RingStore>::Type: FiniteRing + Field,
    Controller: ComputationController,
{
    let lc = poly_ring.base_ring().clone_el(poly_ring.lc(&f).unwrap());
    f = poly_ring.normalize(f);

    let f_coeffs = (0..poly_ring.degree(&f).unwrap())
        .map(|i| {
            poly_ring
                .base_ring()
                .negate(poly_ring.base_ring().clone_el(poly_ring.coefficient_at(&f, i)))
        })
        .collect::<Vec<_>>();
    let mod_f_ring = FreeAlgebraImpl::new(poly_ring.base_ring(), f_coeffs.len(), &f_coeffs);

    let mut result = distinct_degree_factorization_base(&poly_ring, mod_f_ring, controller);
    poly_ring.inclusion().mul_assign_map(&mut result[0], lc);
    return result;
}

/// As [`cantor_zassenhaus()`], but takes `f` in the form of the ring `R[X]/(f)`, which is the
/// internal representation that is used to actually compute the factorization.
#[stability::unstable(feature = "enable")]
pub fn cantor_zassenhaus_base<P, R, Controller>(poly_ring: P, mod_f_ring: R, d: usize, controller: Controller) -> El<P>
where
    P: RingStore,
    P::Type: PolyRing + EuclideanRing,
    R: RingStore,
    R::Type: FreeAlgebra,
    <<R as RingStore>::Type as RingExtension>::BaseRing:
        RingStore<Type = <<<P as RingStore>::Type as RingExtension>::BaseRing as RingStore>::Type>,
    <<<P as RingStore>::Type as RingExtension>::BaseRing as RingStore>::Type: FiniteRing + Field,
    Controller: ComputationController,
{
    assert!(poly_ring.base_ring().get_ring() == mod_f_ring.base_ring().get_ring());
    let ZZ = BigIntRing::RING;
    let q = poly_ring.base_ring().size(&ZZ).unwrap();
    debug_assert!(ZZ.eq_el(
        &is_prime_power(&ZZ, &q).unwrap().0,
        &poly_ring.base_ring().characteristic(&ZZ).unwrap()
    ));
    assert!(ZZ.is_odd(&q));
    assert!(mod_f_ring.rank().is_multiple_of(d));
    assert!(mod_f_ring.rank() > d);

    controller.run_computation(
        format_args!("cantor_zassenhaus(deg={}, fdeg={})", mod_f_ring.rank(), d),
        |_| {
            let mut rng = oorandom::Rand64::new(ZZ.default_hash(&q) as u128);
            let exp = ZZ.half_exact(ZZ.sub(ZZ.pow(q, d), ZZ.one()));
            let f = mod_f_ring.generating_poly(&poly_ring, &poly_ring.base_ring().identity());

            for _ in 0..MAX_PROBABILISTIC_REPETITIONS {
                let T = mod_f_ring.from_canonical_basis(
                    (0..mod_f_ring.rank()).map(|_| poly_ring.base_ring().random_element(|| rng.rand_u64())),
                );
                let G = mod_f_ring.sub(mod_f_ring.pow_gen(T, &exp, ZZ), mod_f_ring.one());
                let g = poly_ring.get_ring().ideal_gen(
                    &f,
                    &mod_f_ring.poly_repr(&poly_ring, &G, &poly_ring.base_ring().identity()),
                );
                if !poly_ring.is_unit(&g) && poly_ring.checked_div(&g, &f).is_none() {
                    return g;
                }
            }
            unreachable!()
        },
    )
}

/// Uses the Cantor-Zassenhaus algorithm to find a nontrivial, factor of a polynomial f
/// over a finite field, that is squarefree and consists only of irreducible factors of
/// degree d.
///
/// # Algorithm
///
/// The algorithm relies on the fact that for some monic polynomial T over Fq have
/// ```text
///   T^Q - T = T (T^((Q - 1)/2) + 1) (T^((Q - 1)/2) - 1)
/// ```
/// where `Q = q^d`. Furthermore, the three factors are pairwise coprime.
/// Since `X^Q - X` divides `T^Q - T`, and f is squarefree (so divides `X^Q - X`),
/// we see that `f` also divides `T^Q - T` and so
/// ```text
///   f = gcd(T, f) gcd((T^((Q - 1)/2) + 1, f) gcd(T^((Q - 1)/2) - 1, f)
/// ```
/// The idea is now to choose a random T and check whether `gcd(T^((Q - 1)/2) - 1, f)`
/// gives a nontrivial factor of f. When f has two irreducible factors, with roots a, b
/// in FQ, then this works if exactly one of them maps to zero under the polynomial
/// `T^((Q - 1)/2) - 1`. Now observe that this is the case if and only if `T(a)` resp.
/// `T(b)` is a square in FQ. Now apparently, for a polynomial chosen uniformly at random
/// among all monic polynomials of very large degree in `Fq[X]`, the values `T(a)` and `T(b)` are
/// independent and uniform on FQ, and thus the probability that one is a square and the
/// other is not is approximately 1/2. This also does not change if we replace the polynomia
/// `T` of very large degree with `T mod f`, i.e. with a random polynomial of degree `d - 1`.
#[stability::unstable(feature = "enable")]
pub fn cantor_zassenhaus<P, Controller>(poly_ring: P, mut f: El<P>, d: usize, controller: Controller) -> El<P>
where
    P: RingStore,
    P::Type: PolyRing + EuclideanRing,
    <<P as RingStore>::Type as RingExtension>::BaseRing: RingStore + FieldStore,
    <<<P as RingStore>::Type as RingExtension>::BaseRing as RingStore>::Type: FiniteRing + Field,
    Controller: ComputationController,
{
    f = poly_ring.normalize(f);
    let f_coeffs = (0..poly_ring.degree(&f).unwrap())
        .map(|i| {
            poly_ring
                .base_ring()
                .negate(poly_ring.base_ring().clone_el(poly_ring.coefficient_at(&f, i)))
        })
        .collect::<Vec<_>>();
    let mod_f_ring = FreeAlgebraImpl::new(poly_ring.base_ring(), f_coeffs.len(), &f_coeffs);
    let result = cantor_zassenhaus_base(&poly_ring, mod_f_ring, d, controller);
    return result;
}

/// Same as [`cantor_zassenhaus_even_base()`], but assumes that the base ring contains a 3rd root of
/// unity.
///
/// Note that when using this in [`cantor_zassenhaus_even()`], some factors that might be returned
/// by this function don't work. In this case, we repeat, but clearly the randomness must be new.
/// Thus, we allow passing a seed.
fn cantor_zassenhaus_even_base_with_root_of_unity<P, R, Controller>(
    poly_ring: P,
    mod_f_ring: R,
    d: usize,
    seed: u64,
    controller: Controller,
) -> El<P>
where
    P: RingStore,
    P::Type: PolyRing + EuclideanRing,
    R: RingStore,
    R::Type: FreeAlgebra,
    <<R as RingStore>::Type as RingExtension>::BaseRing:
        RingStore<Type = <<<P as RingStore>::Type as RingExtension>::BaseRing as RingStore>::Type>,
    <<<P as RingStore>::Type as RingExtension>::BaseRing as RingStore>::Type: FiniteRing + Field,
    Controller: ComputationController,
{
    assert!(poly_ring.base_ring().get_ring() == mod_f_ring.base_ring().get_ring());
    let ZZ = BigIntRing::RING;
    let Fq: &_ = poly_ring.base_ring();
    let q = Fq.size(&ZZ).unwrap();
    let e = ZZ.abs_log2_ceil(&q).unwrap();
    assert_el_eq!(ZZ, ZZ.power_of_two(e), q);

    let mut rng = oorandom::Rand64::new((ZZ.default_hash(&q) as u128) | ((seed as u128) << u64::BITS));
    let zeta3 = get_prim_root_of_unity(&Fq, 3).unwrap();
    let exp = if (d * e).is_multiple_of(2) {
        ZZ.checked_div(&ZZ.sub(ZZ.power_of_two(d * e), ZZ.one()), &ZZ.int_hom().map(3))
            .unwrap()
    } else {
        ZZ.checked_div(&ZZ.sub(ZZ.power_of_two(2 * d * e), ZZ.one()), &ZZ.int_hom().map(3))
            .unwrap()
    };
    let f = mod_f_ring.generating_poly(&poly_ring, &poly_ring.base_ring().identity());

    // as in the standard case, we consider a random polynomial `T` and use the factorization
    // `T^(q^d') - T = T (T^e + 1) (T^e + zeta) (T^e + zeta^2)`; here `d'` is either `d` or
    // `2d`, and `e = (q^d' - 1) / 3`

    for _ in 0..MAX_PROBABILISTIC_REPETITIONS {
        let T = mod_f_ring.from_canonical_basis(
            (0..mod_f_ring.rank()).map(|_| poly_ring.base_ring().random_element(|| rng.rand_u64())),
        );
        let T_pow_exp = mod_f_ring.pow_gen(T, &exp, ZZ);
        let T_pow_exp_poly = mod_f_ring.poly_repr(&poly_ring, &T_pow_exp, &Fq.identity());
        let g = poly_ring.get_ring().ideal_gen_with_controller(
            &f,
            &poly_ring.add_ref_fst(&T_pow_exp_poly, poly_ring.one()),
            controller.clone(),
        );
        if !poly_ring.is_unit(&g) && poly_ring.degree(&g) != poly_ring.degree(&f) {
            return g;
        }
        let g = poly_ring.get_ring().ideal_gen_with_controller(
            &f,
            &poly_ring.add(T_pow_exp_poly, poly_ring.inclusion().map_ref(&zeta3)),
            controller.clone(),
        );
        if !poly_ring.is_unit(&g) && poly_ring.degree(&g) != poly_ring.degree(&f) {
            return g;
        }
    }
    unreachable!()
}

/// Finds a nontrivial factor of a square-free polynomial over `Fq` for `q` a power of two,
/// assuming all its irreducible factors have degree `d`.
#[stability::unstable(feature = "enable")]
pub fn cantor_zassenhaus_even_base<P, R, Controller>(
    poly_ring: P,
    mod_f_ring: R,
    d: usize,
    controller: Controller,
) -> El<P>
where
    P: RingStore,
    P::Type: PolyRing + EuclideanRing,
    R: RingStore,
    R::Type: FreeAlgebra,
    <<R as RingStore>::Type as RingExtension>::BaseRing:
        RingStore<Type = <<<P as RingStore>::Type as RingExtension>::BaseRing as RingStore>::Type>,
    <<<P as RingStore>::Type as RingExtension>::BaseRing as RingStore>::Type: FiniteRing + Field + SelfIso,
    Controller: ComputationController,
{
    assert!(poly_ring.base_ring().get_ring() == mod_f_ring.base_ring().get_ring());
    assert!(mod_f_ring.rank().is_multiple_of(d));
    assert!(mod_f_ring.rank() > d);

    controller.run_computation(
        format_args!("cantor_zassenhaus(deg={}, fdeg={})", mod_f_ring.rank(), d),
        |controller| {
            let ZZ = BigIntRing::RING;
            let Fq = poly_ring.base_ring();
            let q = Fq.size(&ZZ).unwrap();
            let e = ZZ.abs_log2_ceil(&q).unwrap();
            assert_el_eq!(ZZ, ZZ.power_of_two(e), q);
            let f = mod_f_ring.generating_poly(&poly_ring, &poly_ring.base_ring().identity());

            if e % 2 != 0 {
                // adjoin a third root of unity, this will enable use to use the main idea
                // use `promise_as_field()`, since `as_field().unwrap()` can cause infinite generic
                // expansion (always adding a `&`)
                let new_base_ring = AsField::from(AsFieldBase::promise_is_perfect_field(
                    FreeAlgebraImpl::new_with_convolution(
                        Fq,
                        2,
                        [Fq.neg_one(), Fq.neg_one()],
                        "𝝵",
                        Global,
                        STANDARD_CONVOLUTION,
                    ),
                ));
                let new_x_pow_rank = mod_f_ring
                    .wrt_canonical_basis(&mod_f_ring.pow(mod_f_ring.canonical_gen(), mod_f_ring.rank()))
                    .into_iter()
                    .map(|x| new_base_ring.inclusion().map(x))
                    .collect::<Vec<_>>();
                // once we have any kind of tensoring operation, maybe we can find a way to do this
                // that preserves e.g. sparse implementations?
                let new_mod_f_ring = FreeAlgebraImpl::new_with_convolution(
                    &new_base_ring,
                    new_x_pow_rank.len(),
                    &new_x_pow_rank,
                    "x",
                    Global,
                    STANDARD_CONVOLUTION,
                );
                let new_poly_ring = DensePolyRing::new(&new_base_ring, "X");

                // it might happen that cantor_zassenhaus gives a nontrivial factor over the
                // extension, but that factor only induces a trivial factor over the
                // base ring; in this case repeat
                for seed in 0..u64::MAX {
                    let factor = new_poly_ring.normalize(cantor_zassenhaus_even_base_with_root_of_unity(
                        &new_poly_ring,
                        &new_mod_f_ring,
                        d,
                        seed,
                        controller.clone(),
                    ));

                    if new_poly_ring
                        .terms(&factor)
                        .all(|(c, _)| Fq.is_zero(&new_base_ring.wrt_canonical_basis(c).at(1)))
                    {
                        // factor already lives in Fq
                        return poly_ring.from_terms(
                            new_poly_ring
                                .terms(&factor)
                                .map(|(c, i)| (new_base_ring.wrt_canonical_basis(c).at(0), i)),
                        );
                    } else {
                        assert!(d.is_multiple_of(2));
                        // if d is even, the factor might only live in `new_base_ring`, but we can
                        // just use its norm; the automorphism is X -> X^2
                        let factor_conjugate = new_poly_ring.from_terms(new_poly_ring.terms(&factor).map(|(c, i)| {
                            let c_vec = new_base_ring.wrt_canonical_basis(c);
                            let new_c = new_base_ring
                                .from_canonical_basis([Fq.sub(c_vec.at(0), c_vec.at(1)), Fq.negate(c_vec.at(1))]);
                            (new_c, i)
                        }));
                        let factor_norm = new_poly_ring.mul(factor, factor_conjugate);
                        let factor_norm_Fq = poly_ring.from_terms(
                            new_poly_ring
                                .terms(&factor_norm)
                                .map(|(c, i)| (new_base_ring.wrt_canonical_basis(c).at(0), i)),
                        );
                        let potential_result = poly_ring.ideal_gen(&f, &factor_norm_Fq);
                        if poly_ring.degree(&potential_result).unwrap() < mod_f_ring.rank() {
                            return potential_result;
                        }
                        // we are unlucky, and got `factor` that contains exactly one factor over
                        // the extension ring of each irreducible factor of `f`;
                        // in this case, `factor` is a nontrivial factor of `f`, but `N(factor)` is
                        // `f` up to units
                    }
                }
                unreachable!()
            } else {
                return cantor_zassenhaus_even_base_with_root_of_unity(poly_ring, mod_f_ring, d, 0, controller);
            }
        },
    )
}

/// Finds a nontrivial factor of a square-free polynomial over `Fq` for `q` a power of two,
/// assuming all its irreducible factors have degree `d`.
#[stability::unstable(feature = "enable")]
pub fn cantor_zassenhaus_even<P, Controller>(poly_ring: P, mut f: El<P>, d: usize, controller: Controller) -> El<P>
where
    P: RingStore,
    P::Type: PolyRing + EuclideanRing,
    <<P as RingStore>::Type as RingExtension>::BaseRing: RingStore + FieldStore,
    <<<P as RingStore>::Type as RingExtension>::BaseRing as RingStore>::Type: FiniteRing + Field + SelfIso,
    Controller: ComputationController,
{
    f = poly_ring.normalize(f);
    let f_coeffs = (0..poly_ring.degree(&f).unwrap())
        .map(|i| {
            poly_ring
                .base_ring()
                .negate(poly_ring.base_ring().clone_el(poly_ring.coefficient_at(&f, i)))
        })
        .collect::<Vec<_>>();
    let mod_f_ring = FreeAlgebraImpl::new_with_convolution(
        poly_ring.base_ring(),
        f_coeffs.len(),
        &f_coeffs,
        "x",
        Global,
        STANDARD_CONVOLUTION,
    );
    let result = cantor_zassenhaus_even_base(&poly_ring, &mod_f_ring, d, controller);
    return result;
}

#[cfg(test)]
use crate::rings::zn::zn_static::Fp;

#[test]
fn test_distinct_degree_factorization() {
    let field = Fp::<2>::RING;
    let ring = DensePolyRing::new(field, "X");

    // one degree 3 factor
    let a0 = ring.one();
    let a1 = ring.mul(ring.indeterminate(), ring.from_terms([(1, 0), (1, 1)].into_iter()));
    let a2 = ring.from_terms([(1, 0), (1, 1), (1, 2)].into_iter());
    let a3 = ring.from_terms([(1, 0), (1, 1), (1, 3)].into_iter());
    let a = ring.prod([&a0, &a1, &a2, &a3].into_iter().map(|x| ring.clone_el(x)));
    let expected = vec![a0, a1, a2, a3];
    let actual = distinct_degree_factorization(&ring, a, LOG_PROGRESS);
    assert_eq!(expected.len(), actual.len());
    for (f, e) in actual.into_iter().zip(expected.into_iter()) {
        assert_el_eq!(ring, e, ring.normalize(f));
    }

    // two degree 3 factors
    let a0 = ring.one();
    let a1 = ring.mul(ring.indeterminate(), ring.from_terms([(1, 0), (1, 1)].into_iter()));
    let a2 = ring.from_terms([(1, 0), (1, 1), (1, 2)].into_iter());
    let a3 = ring.mul(
        ring.from_terms([(1, 0), (1, 1), (1, 3)].into_iter()),
        ring.from_terms([(1, 0), (1, 2), (1, 3)].into_iter()),
    );
    let a = ring.prod([&a0, &a1, &a2, &a3].into_iter().map(|x| ring.clone_el(x)));
    let expected = vec![a0, a1, a2, a3];
    let actual = distinct_degree_factorization(&ring, a, LOG_PROGRESS);
    assert_eq!(expected.len(), actual.len());
    for (f, e) in actual.into_iter().zip(expected.into_iter()) {
        assert_el_eq!(ring, e, ring.normalize(f));
    }
}

#[test]
fn test_is_irreducible() {
    let field = Fp::<3>::RING;
    let ring = DensePolyRing::new(field, "X");

    let [f1, f2, f3, f4] = ring.with_wrapped_indeterminate(|X| {
        [
            X.pow_ref(2) - 1,
            X.pow_ref(2) + 1,
            X.pow_ref(4) + X.pow_ref(2) + 1,
            X.pow_ref(4) + X + 2,
        ]
    });
    let create_extension_ring = |f| {
        FreeAlgebraImpl::new(
            field,
            ring.degree(f).unwrap(),
            (0..ring.degree(f).unwrap())
                .map(|i| field.negate(*ring.coefficient_at(f, i)))
                .collect::<Vec<_>>(),
        )
    };
    assert_eq!(false, squarefree_is_irreducible_base(&ring, create_extension_ring(&f1)));
    assert_eq!(true, squarefree_is_irreducible_base(&ring, create_extension_ring(&f2)));
    assert_eq!(false, squarefree_is_irreducible_base(&ring, create_extension_ring(&f3)));
    assert_eq!(true, squarefree_is_irreducible_base(&ring, create_extension_ring(&f4)));
}

#[test]
fn test_cantor_zassenhaus() {
    let ring = DensePolyRing::new(Fp::<7>::RING, "X");
    let f = ring.from_terms([(1, 0), (1, 2)].into_iter());
    let g = ring.from_terms([(3, 0), (1, 1), (1, 2)].into_iter());
    let p = ring.mul_ref(&f, &g);
    let factor = ring.normalize(cantor_zassenhaus(&ring, p, 2, DontObserve));
    assert!(ring.eq_el(&factor, &f) || ring.eq_el(&factor, &g));
}

#[test]
fn test_cantor_zassenhaus_even() {
    let ring = DensePolyRing::new(Fp::<2>::RING, "X");
    // (X^3 + X + 1) (X^3 + X^2 + 1)
    let f = ring.from_terms([(1, 0), (1, 1), (1, 3)].into_iter());
    let g = ring.from_terms([(1, 0), (1, 2), (1, 3)].into_iter());
    let h = ring.mul_ref(&f, &g);
    let factor = ring.normalize(cantor_zassenhaus_even(&ring, h, 3, DontObserve));
    assert!(ring.eq_el(&factor, &f) || ring.eq_el(&factor, &g));

    // (X^4 + X + 1) (X^4 + X^3 + 1)
    let f = ring.from_terms([(1, 0), (1, 1), (1, 4)].into_iter());
    let g = ring.from_terms([(1, 0), (1, 3), (1, 4)].into_iter());
    let h = ring.mul_ref(&f, &g);
    let factor = ring.normalize(cantor_zassenhaus_even(&ring, h, 4, DontObserve));
    assert!(ring.eq_el(&factor, &f) || ring.eq_el(&factor, &g));
}

#[test]
fn test_cantor_zassenhaus_even_extension_field() {
    let Fq = FreeAlgebraImpl::new(Fp::<2>::RING, 4, [1, 1, 0, 0])
        .as_field()
        .ok()
        .unwrap();
    let ring = DensePolyRing::new(&Fq, "X");

    // (X^3 + X + 1) (X^3 + X^2 + 1)
    let f = ring.from_terms([(Fq.one(), 0), (Fq.one(), 1), (Fq.one(), 3)].into_iter());
    let g = ring.from_terms([(Fq.one(), 0), (Fq.one(), 2), (Fq.one(), 3)].into_iter());
    let h = ring.mul_ref(&f, &g);
    let factor = ring.normalize(cantor_zassenhaus_even(&ring, h, 3, DontObserve));
    assert!(ring.eq_el(&factor, &f) || ring.eq_el(&factor, &g));

    // (X^4 + X + 1) = (X + a) (X + a + 1) (X + a^2) (X + a^2 + 1)
    let f1 = ring.from_terms([(Fq.canonical_gen(), 0), (Fq.one(), 1)].into_iter());
    let f2 = ring.from_terms([(Fq.add(Fq.canonical_gen(), Fq.one()), 0), (Fq.one(), 1)].into_iter());
    let f3 = ring.from_terms([(Fq.pow(Fq.canonical_gen(), 2), 0), (Fq.one(), 1)].into_iter());
    let f4 = ring.from_terms([(Fq.add(Fq.pow(Fq.canonical_gen(), 2), Fq.one()), 0), (Fq.one(), 1)].into_iter());
    let h = ring.from_terms([(Fq.one(), 0), (Fq.one(), 1), (Fq.one(), 4)].into_iter());
    let factor = ring.normalize(cantor_zassenhaus_even(&ring, h, 1, DontObserve));
    assert!(
        ring.eq_el(&factor, &f1) || ring.eq_el(&factor, &f2) || ring.eq_el(&factor, &f3) || ring.eq_el(&factor, &f4)
    );

    let Fq = FreeAlgebraImpl::new(Fp::<2>::RING, 3, [1, 1, 0])
        .as_field()
        .ok()
        .unwrap();
    let ring = DensePolyRing::new(&Fq, "X");

    // (X^4 + X + 1) (X^4 + X^3 + 1)
    let f = ring.from_terms([(Fq.one(), 0), (Fq.one(), 1), (Fq.one(), 4)].into_iter());
    let g = ring.from_terms([(Fq.one(), 0), (Fq.one(), 3), (Fq.one(), 4)].into_iter());
    let h = ring.mul_ref(&f, &g);
    let factor = ring.normalize(cantor_zassenhaus_even(&ring, h, 4, DontObserve));
    assert!(ring.eq_el(&factor, &f) || ring.eq_el(&factor, &g));
}