/
ffdoms.spad
3419 lines (3036 loc) · 128 KB
/
ffdoms.spad
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
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
)abbrev domain IPF InnerPrimeField
++ Authors: N.N., J.Grabmeier, A.Scheerhorn
++ Date Created: ?, November 1990, 26.03.1991
++ Revision 05.06.2018 by J. Grabmeier
++ Basic Operations:
++ Related Constructors: PrimeField
++ Also See:
++ AMS Classifications:
++ Keywords: prime characteristic, prime field, finite field
++ References:
++ R.Lidl, H.Niederreiter: Finite Field, Encycoldia of Mathematics and
++ Its Applications, Vol. 20, Cambridge Univ. Press, 1983, ISBN 0 521 30240 4
++ J. Grabmeier, A. Scheerhorn: Finite Fields in Axiom.
++ Axiom Technical Report Series, ATR/5 NP2522.
++ K. Pommerening: lectures on Cryptology,
++ http://www.staff.uni-mainz.de/pommeren/Cryptology
++ Description:
++ InnerPrimeField(p) implements the field with p elements by
++ using IntegerMod p.
++ Note: argument p MUST be a prime (this domain does not check).
++ See \spadtype{PrimeField} for a domain that does check.
++ In addition to the inherited operations of IntegerMod p, the domain
++ provides exploits the structure of the cyclic group of its invertible
++ elements. It stores a primitive element w, i.a. generator of this
++ group and it stores a logarithm table for w as soon as this is required.
++ sqrt was added in 2018.
InnerPrimeField(p : PositiveInteger) : Exports == Implementation where
I ==> Integer
NNI ==> NonNegativeInteger
PI ==> PositiveInteger
TBL ==> Table(PI, NNI)
R ==> Record(key : PI, entry : NNI)
SUP ==> SparseUnivariatePolynomial
OF ==> OutputForm
Exports ==> Join(FiniteFieldCategory, FiniteAlgebraicExtensionField(%), _
ConvertibleTo(Integer), Canonical) with
sqrt : % -> %
++ sqrt(x) computes one y such that y^2 = x, error if there is no
++ square root, i.e. jacobi(x,p) = -1.
++ Implementation according to
++ http://www.staff.uni-mainz.de/pommeren/Cryptology/Asymmetric/5_NTh/
quadraticNonResidue: () -> %
++ quadraticNonResidue() computes the smallest non negative integer,
++ which represents a quadratic non residue.
Implementation ==> IntegerMod p add
-- local variables ==========================================================
primitiveElementNotPresent? : Boolean := true
-- gets false after initialization of the primitive Element.
primitiveElt : PI := 1
-- primitiveElt stores a primitive element as soon as it is computed by
-- createPrimitiveElement().
cyclicGroupSize : NNI := qcoerce(p-1)@NNI
-- the size of the cyclic group of invertible elements.
factorsOfCyclicGroupSize: List Record(factor : Integer,
exponent : NonNegativeInteger) := []
-- will hold the factorization of the cyclic group size of the group of
-- invertible elements, as soon as the logarithm tables are required.
logarithmTableNotPresent? : Boolean := true
-- gets false after initialization of the logarithm tables.
discLogTable : Table(PI, TBL) := table()$Table(PI, TBL)
-- tables indexed by the prime factors of the size q of the cyclic group
-- discLogTable.factor is a table with keys
-- primitiveElement() ^ (i * (q quo factor)) and entries i for
-- i in 0..n-1, n computed in initialize() in order to use
-- the minimal size limit 'limit' optimal.
-- local functions ========================================================
twoPowerOfCyclicGroupSize : NNI :=
cGS : NNI := cyclicGroupSize
r : NNI := 0
while cGS rem 2 = 0 repeat
cGS := cGS quo 2
r := r+1
r
twoPower: % -> NNI
++ twoPower(x: %) computes the highest power of two in the
++ factorization of the order of x ~= 0.
twoPower(x: %): NNI ==
x = 0 => error "twoPower: argument must not be 0."
ord : NNI := order x
r : NNI := 0
while ord rem 2 = 0 repeat
ord := ord quo 2
r := r+1
r
initializePrimitiveElement : () -> Void
++ initializePrimitiveElement() initializes the computation of a
++ primitive Element to be stored.
initializePrimitiveElement(): Void ==
factorsOfCyclicGroupSize := factors(factor(cyclicGroupSize)$I
)$(Factored I)
-- get a primitive element:
primitiveElt := lookup(createPrimitiveElement())
-- set initialization flag:
primitiveElementNotPresent? := false
void
initializeLogarithmTable : () -> Void
++ initializeLogarithmTable() initializes the computation of a
++ logarithm table for the ++ primitive Element to be stored.
initializeLogarithmTable(): Void ==
if primitiveElementNotPresent? then initializePrimitiveElement()
-- now set up tables for discrete logarithm computations
limit : Integer := 30
-- the minimum size for the discrete logarithm table
for rec in factorsOfCyclicGroupSize repeat
primeDivisor := rec.factor
base : % := primitiveElement() ^ (cyclicGroupSize quo primeDivisor)
l : Integer := length primeDivisor
n : Integer := if odd? l
then n := shift(primeDivisor, -(l quo 2))
else n := shift(1, (l quo 2))
if n < limit then
d := (primeDivisor - 1) quo limit + 1
n := (primeDivisor - 1) quo d + 1
tbl : TBL := table()$TBL
a : % := 1
for i in 0..(n-1)::NNI repeat
insert!([lookup a , i::NNI]$R, tbl)$TBL
a := a * base
insert!([primeDivisor::PI, copy(tbl)$TBL]$Record(key: PI, entry: TBL),
discLogTable)
-- set initialization flag:
logarithmTableNotPresent? := false
void
-- exported functions from % ==============================================
quadraticNonResidue(): % ==
found? : Boolean := false
q : I := 1
while not found? repeat
q := q+1
found? := jacobi(q, p)$IntegerNumberTheoryFunctions = -1
q :: %
sqrt(x: %): % ==
zero? x => x
jacobi(convert(x)@I, p)$IntegerNumberTheoryFunctions = -1 =>
error "sqrt: argument does not have a square root by Jacobi symbol."
3 = (p rem 4) =>
y : % := x ^ ((p+1) quo 4)
-- we choose the smaller one of y and -y of their canonical
-- representatives in 0..p-1
if convert(y)@I < convert(-y)@I then y else -y
-- now we must have 1 = (p rem 4) and jacobi(x,p) = 1 (provided p is
-- really a prime).
b : % := quadraticNonResidue()
e : NNI := twoPowerOfCyclicGroupSize
u : NNI := ((p-1) :: NNI) quo (2^e)
z : % := x
r : NNI := twoPower z
lr : List NNI := [r]
while r > 0 repeat
z := z * b ^ (2 ^ ( (e-r) :: NNI))
r := twoPower z
lr := cons(r, lr)
y : % := z ^ ( (u+1) quo 2)
for r in rest lr repeat
y := y / b ^ (2 ^ (e-r-1) :: NNI)
if convert(y)@I < convert(-y)@I then y else -y
generator() == 1
-- This uses Fermat's little theorem x^(p-1)=1 (mod p), so x^(q(p-1)+r)
-- = x^r (mod p)
x : % ^ n : Integer ==
zero?(n) => 1
zero?(x) => 0
r := positiveRemainder(n, p-1)::NNI
((x pretend IntegerMod p) ^$IntegerMod(p) r) pretend %
if p <= convert(max()$SingleInteger)@Integer then
q := p::SingleInteger
recip x ==
zero?(y := convert(x)@Integer :: SingleInteger) => "failed"
invmod(y, q)::Integer::%
else
recip x ==
zero?(y := convert(x)@Integer) => "failed"
invmod(y, p)::%
convert(x : %) : I == x pretend I
normalElement() == 1
createNormalElement() == 1
characteristic() == p
factorsOfCyclicGroupSize() ==
-- this fixes an infinite loop of function calls, problem was that
-- factors factor(1) is the empty list:
p = 2 => factorsOfCyclicGroupSize
if empty? factorsOfCyclicGroupSize then initializePrimitiveElement()
factorsOfCyclicGroupSize
representationType() == "prime"
tableForDiscreteLogarithm(fac) ==
if logarithmTableNotPresent? then initializeLogarithmTable()
tbl := search(fac::PI, discLogTable)$Table(PI, TBL)
tbl case "failed" =>
error "tableForDiscreteLogarithm: argument must be prime divisor_
of the order of the multiplicative group"
tbl
primitiveElement() ==
if primitiveElementNotPresent? then initializePrimitiveElement()
index(primitiveElt)
degree(x) : PI == 1::PositiveInteger
extensionDegree() : PI == 1::PositiveInteger
-- sizeOfGroundField() == p::NonNegativeInteger
inGroundField?(x) == true
coordinates(x : %) : Vector(%) == new(1, x)$(Vector %)
represents(v) == v(1)
retract(x) == x
retractIfCan(x) == x
basis() == new(1, 1::%)$(Vector %)
basis(n : PI) ==
n = 1 => basis()
error("basis: argument must divide extension degree")
definingPolynomial() ==
monomial(1, 1)$(SUP %) - monomial(1, 0)$(SUP %)
minimalPolynomial(x) ==
monomial(1, 1)$(SUP %) - monomial(x, 0)$(SUP %)
charthRoot(x : %) : % == x
)abbrev domain PF PrimeField
++ Authors: N.N.,
++ Date Created: November 1990, 26.03.1991
++ Basic Operations:
++ Related Constructors:
++ Also See:
++ AMS Classifications:
++ Keywords: prime characteristic, prime field, finite field
++ References:
++ R.Lidl, H.Niederreiter: Finite Field, Encycoldia of Mathematics and
++ Its Applications, Vol. 20, Cambridge Univ. Press, 1983, ISBN 0 521 30240 4
++ Description:
++ PrimeField(p) implements the field with p elements if p is a
++ prime number.
++ Error: if p is not prime.
--++ with new compiler, want to put the error check before the add
PrimeField(p : PositiveInteger) : Exp == Impl where
Exp ==> Join(FiniteFieldCategory, FiniteAlgebraicExtensionField(%), _
ConvertibleTo(Integer), Canonical) with
sqrt : % -> %
++ sqrt(x) computes one y such that y^2 = x, error if there is no square
++ root, i.e. jacobi(x,p) = -1.
Impl ==> InnerPrimeField(p) add
if not prime?(p)$IntegerPrimesPackage(Integer) then
error "Argument to prime field must be a prime"
)abbrev package FFPOLY FiniteFieldPolynomialPackage
++ Author: A. Bouyer, J. Grabmeier, A. Scheerhorn, R. Sutor, B. Trager
++ Date Created: January 1991
++ Basic Operations:
++ Related Constructors:
++ Also See:
++ AMS Classifications:
++ Keywords: finite field, polynomial, irreducible polynomial, normal
++ polynomial, primitive polynomial, random polynomials
++ References:
++ [LS] Lenstra, H. W. & Schoof, R. J., "Primitivive Normal Bases
++ for Finite Fields", Math. Comp. 48, 1987, pp. 217-231
++ [LN] Lidl, R. & Niederreiter, H., "Finite Fields",
++ Encycl. of Math. 20, Addison-Wesley, 1983
++ J. Grabmeier, A. Scheerhorn: Finite Fields in Axiom.
++ Axiom Technical Report Series, ATR/5 NP2522.
++ Description:
++ This package provides a number of functions for generating, counting
++ and testing irreducible, normal, primitive, random polynomials
++ over finite fields.
FiniteFieldPolynomialPackage(GF): Exports == Implementation where
GF : FiniteFieldCategory
I ==> Integer
L ==> List
NNI ==> NonNegativeInteger
PI ==> PositiveInteger
Rec ==> Record(expnt : NNI, coeff : GF)
Repr ==> L Rec
SUP ==> SparseUnivariatePolynomial GF
Exports ==> with
-- qEulerPhiCyclotomic : PI -> PI
-- ++ qEulerPhiCyclotomic(n)$FFPOLY(GF) yields the q-Euler's function
-- ++ of the n-th cyclotomic polynomial over the field {\em GF} of
-- ++ order q (cf. [LN] p.122);
-- ++ error if n is a multiple of the field characteristic.
primitive? : SUP -> Boolean
++ primitive?(f) tests whether the polynomial f over a finite
++ field is primitive, i.e. all its roots are primitive.
normal? : SUP -> Boolean
++ normal?(f) tests whether the polynomial f over a finite field is
++ normal, i.e. its roots are linearly independent over the field.
numberOfIrreduciblePoly : PI -> PI
++ numberOfIrreduciblePoly(n)$FFPOLY(GF) yields the number of
++ monic irreducible univariate polynomials of degree n
++ over the finite field {\em GF}.
numberOfPrimitivePoly : PI -> PI
++ numberOfPrimitivePoly(n)$FFPOLY(GF) yields the number of
++ primitive polynomials of degree n over the finite field {\em GF}.
numberOfNormalPoly : PI -> PI
++ numberOfNormalPoly(n)$FFPOLY(GF) yields the number of
++ normal polynomials of degree n over the finite field {\em GF}.
createIrreduciblePoly : PI -> SUP
++ createIrreduciblePoly(n)$FFPOLY(GF) generates a monic irreducible
++ univariate polynomial of degree n over the finite field {\em GF}.
createPrimitivePoly : PI -> SUP
++ createPrimitivePoly(n)$FFPOLY(GF) generates a primitive polynomial
++ of degree n over the finite field {\em GF}.
createNormalPoly : PI -> SUP
++ createNormalPoly(n)$FFPOLY(GF) generates a normal polynomial
++ of degree n over the finite field {\em GF}.
createNormalPrimitivePoly : PI -> SUP
++ createNormalPrimitivePoly(n)$FFPOLY(GF) generates a normal and
++ primitive polynomial of degree n over the field {\em GF}.
++ Note: this function is equivalent to createPrimitiveNormalPoly(n)
createPrimitiveNormalPoly : PI -> SUP
++ createPrimitiveNormalPoly(n)$FFPOLY(GF) generates a normal and
++ primitive polynomial of degree n over the field {\em GF}.
lexSmaller? : (SUP, SUP) -> Boolean
++ lexSmaller?(f, g) compares monic f and g of the same
++ degree in the following order.
++ Error: if f or g is not monic or if f and g have different
++ degrees or if common degree is 0.
++ \spad{f < g} if
++ the number of monomials of f is less
++ than this number for g.
++ If f and g have the same number of monomials,
++ the lists of exponents are compared lexicographically.
++ If these lists are also equal, the lists of coefficients
++ are compared according to the lexicographic ordering induced by
++ the ordering of the elements of {\em GF} given by {\em lookup}.
clexSmaller? : (SUP, SUP) -> Boolean
++ clexSmaller?(f, g) compares monic f and g of the same
++ degree in the following order.
++ Error: if f or g is not monic or if f and g have different
++ degrees or if common degree is 0.
++ \spad{f < g} if the constant term of \spad{f} is zero
++ and constant term of \spad{g} is nonzero.
++ If both
++ constant term of \spad{f} and \spad{g} are nonzero
++ then \spad{f < g} if the {\em lookup} of the constant term
++ of f is less than
++ this number for g.
++ If these values are equal, then \spad{lexSmaller?} is used
++ as ordering predicate.
nlexSmaller? : (SUP, SUP) -> Boolean
++ nlexSmaller?(f, g) compares monic f and g of the same
++ degree \spad{n} in the following order.
++ Error: if f or g is not monic or if f and g have different
++ degrees or if common degree is 0.
++ \spad{f < g} if the coefficient of the term of degree {\em n-1}
++ of \spad{f} is zero and than that for g is nonzero.
++ Also, \spad{f < g} if both coefficients are nonzero and
++ {\em lookup} of the coefficient of \spad{f} is less than
++ that for \spad{g}.
++ In case those coefficients are equal, then \spad{lexSmaller?}
++ is used as ordering predicate.
cnlexSmaller? : (SUP, SUP) -> Boolean
++ cnlexSmaller?(f, g) compares monic f and g of the same
++ degree \spad{n} in the following order.
++ Error: if f or g is not monic or if f and g have different
++ degrees or if common degree is 0.
++ \spad{f < g} if the constant term of \spad{f} is zero
++ and constant term of \spad{g} is nonzero. If both
++ constant term of \spad{f} and \spad{g} are nonzero
++ then \spad{f < g} if the {\em lookup} of the constant term
++ of f is less than
++ this number for g.
++ If constant terms are equal then \spad{nlexSmaller?}
++ is used as ordering predicate.
nextIrreduciblePoly : SUP -> Union(SUP, "failed")
++ nextIrreduciblePoly(f) yields the next monic irreducible polynomial
++ over a finite field {\em GF} of the same degree as f in the following
++ order, or "failed" if there are no greater ones.
++ Error: if f has degree 0.
++ Note: the input polynomial f is made monic.
++ \spad{lexSmaller?} is used as ordering predicate.
nextPrimitivePoly : SUP -> Union(SUP, "failed")
++ nextPrimitivePoly(f) yields the next primitive polynomial over
++ a finite field {\em GF} of the same degree as f in the following
++ order, or "failed" if there are no greater ones.
++ Error: if f has degree 0.
++ Note: the input polynomial f is made monic.
++ \spad{clexSmaller?} is used as ordering predicate.
nextNormalPoly : SUP -> Union(SUP, "failed")
++ nextNormalPoly(f) yields the next normal polynomial over
++ a finite field {\em GF} of the same degree as f in the following
++ order, or "failed" if there are no greater ones.
++ Error: if f has degree 0.
++ Note: the input polynomial f is made monic.
++ \spad{nlexSmaller?} is used as ordering predicate.
nextNormalPrimitivePoly : SUP -> Union(SUP, "failed")
++ nextNormalPrimitivePoly(f) yields the next normal primitive polynomial
++ over a finite field {\em GF} of the same degree as f in the following
++ order, or "failed" if there are no greater ones.
++ Error: if f has degree 0.
++ Note: the input polynomial f is made monic.
++ \spad{cnlexSmaller?} is used as ordering predicate.
++ This operation is equivalent to nextPrimitiveNormalPoly(f).
nextPrimitiveNormalPoly : SUP -> Union(SUP, "failed")
++ nextPrimitiveNormalPoly(f) yields the next primitive normal polynomial
++ over a finite field {\em GF} of the same degree as f in the following
++ order, or "failed" if there are no greater ones.
++ Error: if f has degree 0.
++ Note: the input polynomial f is made monic.
++ \spad{cnlexSmaller?} is used as ordering predicate.
++ This operation is equivalent to nextNormalPrimitivePoly(f).
-- random : () -> SUP
-- ++ random()$FFPOLY(GF) generates a random monic polynomial
-- ++ of random degree over the field {\em GF}
random : PI -> SUP
++ random(n)$FFPOLY(GF) generates a random monic polynomial
++ of degree n over the finite field {\em GF}.
random : (PI, PI) -> SUP
++ random(m, n)$FFPOLY(GF) generates a random monic polynomial
++ of degree d over the finite field {\em GF}, d between m and n.
leastAffineMultiple : SUP -> SUP
++ leastAffineMultiple(f) computes the least affine polynomial which
++ is divisible by the polynomial f over the finite field {\em GF},
++ i.e. a polynomial whose exponents are 0 or a power of q, the
++ size of {\em GF}.
reducedQPowers : SUP -> PrimitiveArray SUP
++ reducedQPowers(f)
++ generates \spad{[x, x^q, x^(q^2), ..., x^(q^(n-1))]}
++ reduced modulo f where \spad{q = size()$GF} and \spad{n = degree f}.
--
-- we intend to implement also the functions
-- cyclotomicPoly: PI -> SUP, order: SUP -> PI,
-- and maybe a new version of irreducible?
Implementation ==> add
import from IntegerNumberTheoryFunctions
sizeGF : PI := size()$GF :: PI
char_GF : PI := characteristic()$GF :: PI
deg_GF : NNI := 0
get_deg_GF() : PI ==
if deg_GF = 0 then
n : PI := 1$PI;
ss : NNI := sizeGF;
while ss > char_GF repeat
ss := (ss exquo$NNI char_GF)::NNI
n := n +$PI 1$PI
deg_GF := n
deg_GF::PI
elem1 : GF := index(1)$GF
poly_or_prime_rep := representationType()$GF case "polynomial"
or representationType()$GF case "prime"
MM := ModMonic(GF, SUP)
rep_to_SUP(l) ==> l pretend SUP
listToSUP(l : Repr) : SUP ==
newl : Repr := [copy t for t in l]
rep_to_SUP(newl)
nextSubset : (L NNI, NNI) -> Union(L NNI, "failed")
-- for a list s of length m with 1 <= s.1 < ... < s.m <= bound,
-- nextSubset(s, bound) yields the immediate successor of s
-- (resp. "failed" if s = [1,...,bound])
-- where s < t if and only if:
-- (i) #s < #t; or
-- (ii) #s = #t and s < t in the lexicographical order;
-- (we have chosen to fix the signature with NNI instead of PI
-- to avoid coercions in the main functions)
reducedQPowers(f) ==
m : PI := qcoerce(degree(f)$SUP)
m1 : I := m-1
setPoly(f)$MM
e := reduce(monomial(1, 1)$SUP)$MM ^ sizeGF
w := 1$MM
qpow : PrimitiveArray SUP := new(m, 0)
qpow(0) := 1$SUP
for i in 1..m1 repeat qpow(i) := lift(w := w*e)$MM
qexp : PrimitiveArray SUP := new(m, 0)
m = 1 =>
qexp(0$I) := (-coefficient(f, 0$NNI)$SUP)::SUP
qexp
qexp(0$I) := monomial(1, 1)$SUP
h := qpow(1)
qexp(1) := h
for i in 2..m1 repeat
g := 0$SUP
while h ~= 0 repeat
g := g + leadingCoefficient(h) * qpow(degree(h))
h := reductum(h)
qexp(i) := (h := g)
qexp
leastAffineMultiple(f) ==
-- [LS] p.112
if (lcf := leadingCoefficient f) ~= 1 then f := (inv lcf) * f
qexp := reducedQPowers(f)
n := degree(f)$SUP
b : Matrix GF := transpose(matrix([entries(vectorise(qexp(ii), n))
for ii in 0..n-1]))
col1 : Matrix GF := new(n, 1, 0)
col1(1, 1) := 1
ns : List Vector GF := nullSpace (horizConcat(col1, b) )
----------------------------------------------------------------
-- perhaps one should use that the first vector in ns is already
-- the right one
----------------------------------------------------------------
dim := n+2
coeffVector : Vector GF
for nse in ns repeat
i : PI := qcoerce(n + 1)
while nse(i) = 0 repeat
i := qcoerce(i - 1)
if i < dim then
dim := i
coeffVector := nse
(coeffVector(1)::SUP) +(+/[monomial(coeffVector.k, _
sizeGF^((k-2)::NNI))$SUP for k in 2..dim])
-- qEulerPhiCyclotomic n ==
-- n = 1 => qcoerce(sizeGF - 1)
-- p : PI := characteristic()$GF :: PI
-- (n rem p) = 0 => error
-- "cyclotomic polynomial not defined for this argument value"
-- q : PI := sizeGF
-- -- determine the multiplicative order of q modulo n
-- e : PI := 1
-- qe : PI := q
-- while (qe rem n) ~= 1 repeat
-- e := e + 1
-- qe := qe * q
-- qcoerce((qe - 1) ^ (qcoerce(eulerPhi(n) quo e)) )
numberOfIrreduciblePoly n ==
-- we compute the number Nq(n) of monic irreducible polynomials
-- of degree n over the field GF of order q by the formula
-- Nq(n) = (1/n)* sum(moebiusMu(n/d)*q^d) where the sum extends
-- over all divisors d of n (cf. [LN] p.93, Th. 3.25)
n = 1 => sizeGF
-- the contribution of d = 1 :
lastd : PI := 1
qd : PI := sizeGF
sum : I := moebiusMu(n) * qd
-- the divisors d > 1 of n :
divisorsOfn : L PI := rest(divisors n) pretend L PI
for d in divisorsOfn repeat
qd := qd * (sizeGF) ^ (qcoerce(d - lastd))
sum := sum + moebiusMu(n quo d) * qd
lastd := d
(sum quo n) :: PI
numberOfPrimitivePoly n == (eulerPhi((sizeGF ^ n) - 1) quo n) :: PI
-- [each root of a primitive polynomial of degree n over a field
-- with q elements is a generator of the multiplicative group
-- of a field of order q^n (definition), and the number of such
-- generators is precisely eulerPhi(q^n - 1)]
numberOfNormalPoly n ==
-- we compute the number Nq(n) of normal polynomials of degree n
-- in GF[X], with GF of order q, by the formula
-- Nq(n) = (1/n) * qPhi(X^n - 1) (cf. [LN] p.124) where,
-- for any polynomial f in GF[X] of positive degree n,
-- qPhi(f) = q^n * (1 - q^(-n1)) *...* (1 - q^(-nr)) =
-- q^n * ((q^(n1)-1) / q^(n1)) *...* ((q^(nr)-1) / q^(n_r)),
-- the ni being the degrees of the distinct irreducible factors
-- of f in its canonical factorization over GF
-- ([LN] p.122, Lemma 3.69).
-- hence, if n = m * p^r where p is the characteristic of GF
-- and gcd(m, p) = 1, we get
-- Nq(n) = (1/n)* q^(n-m) * qPhi(X^m - 1)
-- now X^m - 1 is the product of the (pairwise relatively prime)
-- cyclotomic polynomials Qd(X) for which d divides m
-- ([LN] p.64, Th. 2.45), and each Qd(X) factors into
-- eulerPhi(d)/e (distinct) monic irreducible polynomials in GF[X]
-- of the same degree e, where e is the least positive integer k
-- such that d divides q^k - 1 ([LN] p.65, Th. 2.47)
n = 1 => (sizeGF - 1) :: NNI :: PI
m : PI := n
p : PI := characteristic()$GF :: PI
q : PI := sizeGF
while (m rem p) = 0 repeat -- find m such that
m := (m quo p) :: PI -- n = m * p^r and gcd(m, p) = 1
m = 1 =>
-- know that n is a power of p
(((q ^ ((n-1)::NNI) ) * (q - 1) ) quo n) :: PI
prod : I := q - 1
divisorsOfm : L PI := rest(divisors m) pretend L PI
for d in divisorsOfm repeat
-- determine the multiplicative order of q modulo d
e : PI := 1
qe : PI := q
while (qe rem d) ~= 1 repeat
e := e + 1
qe := qe * q
prod := prod * _
qcoerce((qe - 1) ^ (qcoerce(eulerPhi(d) quo e)) )
qcoerce(q^(qcoerce(n - m)@NNI) * prod quo n)
-- test if monic irreducible f is primitive
primitive_i?(f : SUP) : Boolean ==
-- let GF be a field of order q; a monic polynomial f in GF[X]
-- of degree n is primitive over GF if and only if its constant
-- term is non-zero, f is irreducible and,
-- for each prime divisor d of q^n - 1,
-- f does not divide X^((q^n - 1) / d) - 1
-- (cf. [LN] p.89, Th. 3.16, and p.87, following Th. 3.11)
n : NNI := degree f
n = 0 => false
q : PI := sizeGF
qn1 : PI := (q^n - 1) :: NNI :: PI
setPoly f
x := reduce(monomial(1, 1)$SUP)$MM -- X rem f represented in MM
lrec := factorList(factor qn1)
lfact : L PI := [] -- collect the prime factors
for rec in lrec repeat -- of q^n - 1
lfact := cons((rec.factor) :: PI, lfact)
for d in lfact repeat
if (expt := (qn1 quo d)) >= n then
lift(x ^ expt)$MM = 1 => return false
true
primitive?(f) ==
degree(f) = 0 => false
leadingCoefficient f ~= 1 => false
coefficient(f, 0) = 0 => false
-- cheaper and prunes better than computation of x^(q^n - 1)
not(irreducible?(f)$UnivariateFiniteFieldFactorize(GF, SUP)) => false
primitive_i?(f)
normal? f ==
-- let GF be a field with q elements; a monic irreducible
-- polynomial f in GF[X] of degree n is normal if its roots
-- x, x^q, ... , x^(q^(n-1)) are linearly independent over GF
n : NNI := degree f
n = 0 => false
leadingCoefficient f ~= 1 => false
coefficient(f, 0) = 0 => false
n = 1 => true
not irreducible?(f)$UnivariateFiniteFieldFactorize(GF, SUP) => false
g := reducedQPowers(f)
l := [entries vectorise(g.i, n)$SUP for i in 0..(n-1)::NNI]
rank(matrix(l)$Matrix(GF)) = n => true
false
normal_and_primitive?(f : SUP) : Boolean ==
normal?(f) and primitive_i?(f)
nextSubset(s, bound) ==
m : NNI := #(s)
m = 0 => [1]
-- find the first element s(i) of s such that s(i) + 1 < s(i+1) :
noGap : Boolean := true
i : NNI := 0
restOfs : L NNI
while noGap and not empty?(restOfs := rest s) repeat
-- after i steps (0 <= i <= m-1) we have s = [s(i), ... , s(m)]
-- and restOfs = [s(i+1), ... , s(m)]
secondOfs := first restOfs -- s(i+1)
firstOfsPlus1 := first s + 1 -- s(i) + 1
secondOfs = firstOfsPlus1 =>
s := restOfs
i := i + 1
setfirst!(s, firstOfsPlus1) -- s := [s(i)+1, s(i+1), ..., s(m)]
noGap := false
if noGap then -- here s = [s(m)]
firstOfs := first s
firstOfs < bound => setfirst!(s, firstOfs + 1) -- s := [s(m)+1]
m < bound =>
setfirst!(s, m + 1) -- s := [m+1]
i := m
return "failed" -- (here m = s(m) = bound)
for j in i..1 by -1 repeat -- reconstruct the destroyed
s := cons(j, s) -- initial part of s
s
lexSmaller?(f : SUP, g : SUP) : Boolean ==
n := degree(f)
n ~= degree(g) => error "polynomials must have equal degrees"
n < 1 => error "polynomials must have positive degree"
k1 := numberOfMonomials(f)
k2 := numberOfMonomials(g)
k1 < k2 => true
k2 < k1 => false
f1 := reductum(f)
g1 := reductum(g)
while f1 ~= 0 repeat
k1 := degree(f1)
k2 := degree(g1)
k1 < k2 => return true
k2 < k1 => return false
f1 := reductum(f1)
g1 := reductum(g1)
f1 := reductum(f)
g1 := reductum(g)
while f1 ~= 0 repeat
k1 := lookup(leadingCoefficient(f1))
k2 := lookup(leadingCoefficient(g1))
k1 < k2 => return true
k2 < k1 => return false
f1 := reductum(f1)
g1 := reductum(g1)
false
ll_cmp(x1 : GF, x2 : GF) : SingleInteger ==
x1 = 0 and x2 ~= 0 => -1
x1 ~= 0 and x2 = 0 => 1
k1 := lookup(x1)
k2 := lookup(x2)
k1 < k2 => -1
k2 < k2 => 1
0
clexSmaller?(f : SUP, g : SUP) : Boolean ==
n := degree(f)
n ~= degree(g) => error "polynomials must have equal degrees"
n < 1 => error "polynomials must have positive degree"
s := ll_cmp(coefficient(f, 0), coefficient(g, 0))
s < 0 => true
0 < s => false
lexSmaller?(f, g)
nlexSmaller?(f : SUP, g : SUP) : Boolean ==
n := degree(f)
n ~= degree(g) => error "polynomials must have equal degrees"
n < 1 => error "polynomials must have positive degree"
s := ll_cmp(coefficient(f, (n - 1)::NNI), coefficient(g, (n - 1)::NNI))
s < 0 => true
0 < s => false
lexSmaller?(f, g)
cnlexSmaller?(f : SUP, g : SUP) : Boolean ==
n := degree(f)
n ~= degree(g) => error "polynomials must have equal degrees"
n < 1 => error "polynomials must have positive degree"
s := ll_cmp(coefficient(f, 0), coefficient(g, 0))
s < 0 => true
0 < s => false
nlexSmaller?(f, g)
v_NNI ==> PrimitiveArray(NNI)
v_GF ==> PrimitiveArray(GF)
vecs_to_pol(exp_v : v_NNI, coeff_v : v_GF, w : NNI) : SUP ==
resl : Repr := []
for i in 0..w repeat
resl := cons([exp_v(i), coeff_v(i)], resl)
rep_to_SUP(resl)
do_weight(exp_v : v_NNI, ind_v : v_NNI, coeff_v : v_GF,
min_i : NNI, max_i : NNI, w : NNI,
tp? : SUP -> Boolean) : Union(SUP, "failed") ==
-- skip reducible cases
if sizeGF = 2 and odd?(w) then
-- fill vectors with expected values
for i in min_i..(max_i - 1) repeat
exp_v(i) := i
coeff_v(i) := elem1
ind_v(i) := 1
return "failed"
repeat
i := min_i
while i < max_i repeat
j := ind_v(i)
j := j + 1 -- lookup(f_i)$GF + 1
j = sizeGF =>
ind_v(i) := 1
coeff_v(i) := elem1
i := i + 1
ind_v(i) := j
coeff_v(i) := index(j::PI)
break
-- if coeffs wrapped around need to take next
-- exponents tuple
if i = max_i then
i := max(min_i, 1)
while i < max_i repeat
j := exp_v(i)
j := j + 1
j = exp_v(i + 1) =>
exp_v(i) := exp_v(i - 1) + 1
i := i + 1
exp_v(i) := j
break
-- need bigger weight, fail this one
i = max_i => return "failed"
pol := vecs_to_pol(exp_v, coeff_v, w)
-- print(pol::OutputForm)
tp?(pol) => return pol
-- run through the possible weights, starting from w
do_weights(exp_v : v_NNI, ind_v : v_NNI, coeff_v : v_GF,
min_i : NNI, max_i : NNI, w : NNI,
tp? : SUP -> Boolean) : Union(SUP, "failed") ==
n := exp_v(w)
repeat
-- skip weight if there nothing else to increase
if max_i > min_i then
resu := do_weight(exp_v, ind_v, coeff_v, min_i,
max_i, w, tp?)
resu case SUP => return resu@SUP
w = n => return "failed"
w1 := w
w := w + 1
i := w
for i1 in w1..max_i by -1 repeat
exp_v(i) := exp_v(i1)
coeff_v(i) := coeff_v(i1)
i := i1
exp_v(max_i) := max_i
ind_v(max_i) := 1
coeff_v(max_i) := elem1
max_i := max_i + 1
-- artificial, we will bump it in do_weight
ind_v(min_i) := 0
get_rep(f) ==> f pretend Repr
nextIrreduciblePoly f ==
n : NNI := degree f
n = 0 => error "polynomial must have positive degree"
-- make f monic
if (lcf := leadingCoefficient f) ~= 1 then f := (inv lcf) * f
f_rep : Repr := get_rep(f)
c0 := coefficient(f, 0)
n = 1 =>
xn := first(f_rep)
lc : NNI := (c0 = 0 => 0; lookup(c0))
lc = sizeGF - 1 => "failed"
c := index((lc + 1)::PI)
rep_to_SUP([xn, [0, c]$Rec])
w : NNI := (#(f_rep) - 1) :: NNI
if c0 = 0 then w := w + 1
good_binomials := true
-- skip binomials if all are reducible
if n > 1 and w = 1 then
facs := factors(factor(n))
good_binomials : Boolean := odd?(char_GF)
for fac in facs while good_binomials repeat
fac.factor = 2 and fac.exponent > 1 =>
(sizeGF - 1) rem 4 ~= 0 =>
good_binomials := false
if (sizeGF - 1) rem fac.factor ~= 0 then
good_binomials := false
if not(good_binomials) then w := 2
exp_v : v_NNI := new(n + 1, 0)
ind_v : v_NNI := new(n + 1, 0)
coeff_v : v_GF := new(n + 1, 0)
for term in f_rep for i in 0.. repeat
exp_v(w - i) := term.expnt
ci := term.coeff
coeff_v(w - i) := ci
ind_v(w - i) := lookup(ci)
if not(good_binomials) then
exp_v(1) := 1
coeff_v(1) := elem1
ind_v(1) := 1
do_weights(exp_v, ind_v, coeff_v, 0, w, w,
irreducible?$UnivariateFiniteFieldFactorize(GF, SUP))
lc_Rec ==> Record(nl : PI, nc : GF)
-- Find nl bigger or equal than l such that m1_to_n*index(nl)
-- is primitive. If successful return [nl, index(nl)],
-- otherwise return "failed"
get_next_GF_generator(l : NNI, m1_to_n : GF) : Union(lc_Rec, "failed") ==
l1 := l::PI
while l1 < sizeGF repeat
c := index(l1)$GF
primitive?(m1_to_n*c)$GF => return [l1, c]
l1 := l1 + 1
"failed"
nextPrimitivePoly f ==
n : NNI := degree f
n = 0 => error "polynomial must have positive degree"
-- make f monic
if (lcf := leadingCoefficient f) ~= 1 then f := (inv lcf) * f
f_rep : Repr := get_rep(f)
xn : Rec := first(f_rep)
c0 : GF := coefficient(f, 0)
lc : NNI := lookup(c0)$GF
n = 1 =>
m1_to_n := -(1$GF)
-- x + c is primitive iff -c is primitive
lcu := get_next_GF_generator(lc + 1, m1_to_n)
lcu case "failed" => "failed"
c := (lcu@lc_Rec).nc
rep_to_SUP([xn, [0, c]$Rec])
w : NNI := (#(f_rep) - 1)::NNI
if c0 = 0 then w := w + 1
exp_v : v_NNI := new(n + 1, 0)
ind_v : v_NNI := new(n + 1, 0)
coeff_v : v_GF := new(n + 1, 0)
for term in f_rep for i in 0.. repeat
exp_v(w - i) := term.expnt
ci := term.coeff
coeff_v(w - i) := ci
ind_v(w - i) := lookup(ci)
-- a necessary condition for a monic polynomial f of degree n
-- over GF to be primitive is that (-1)^n * f(0) be a
-- primitive element of GF (cf. [LN] p.90, Th. 3.18)
c : GF := c0
m1_to_n := ((-1$Integer)^n)::GF
-- if c = 0 then set lc to 1
if c = 0 then lc := 1
repeat
lcu := get_next_GF_generator(lc, m1_to_n)
lcu case "failed" => return "failed"
lcr := lcu@lc_Rec
-- move to next constant term
lc := lcr.nl + 1
ind_v(0) := lcr.nl
coeff_v(0) := c := lcr.nc
if not(c = c0 and w > 1) then
-- X^n + c can not be primitive for n > 1 (cf. [LN] p.90,
-- Th. 3.18); next possible polynomial is X^n + X + c
w := 2
exp_v(1) := 1
ind_v(1) := 0
coeff_v(1) := 0
exp_v(w) := n
ind_v(w) := 1
coeff_v(w) := 1
resu := do_weights(exp_v, ind_v, coeff_v, 1, w, w, primitive?)
resu case SUP => return resu@SUP
nextNormalPoly f ==
n : NNI := degree f
n = 0 => error "polynomial must have positive degree"
n1 := (n - 1)::NNI
-- make f monic
if (lcf := leadingCoefficient(f)) ~= 1 then f := (inv lcf) * f
f_rep : Repr := get_rep(f)
a0 : GF := coefficient(f, n1)
la : NNI := lookup(a0)$GF rem sizeGF
n = 1 =>
xn : Rec := first(f_rep)
-- the polynomial X + a is normal if and only if a is not zero
la = sizeGF - 1 => "failed"
rep_to_SUP([xn, [0, index((la + 1)::PI)$GF]$Rec])
-- if the polynomial X^n + a * X^(n-1) + ... is normal then
-- a = -(x + x^q +...+ x^(q^n)) can not be zero (where q = #GF)
a : GF := a0
-- if a = 0 then set la := 1
if la = 0 then
la := 1
a := elem1
w : NNI := (#(f_rep) - 1) :: NNI
if coefficient(f, 0) = 0 then w := w + 1
exp_v : v_NNI := new(n + 1, 0)
ind_v : v_NNI := new(n + 1, 0)
coeff_v : v_GF := new(n + 1, 0)
for term in f_rep for i in 0.. repeat
exp_v(w - i) := term.expnt
ci := term.coeff
coeff_v(w - i) := ci
ind_v(w - i) := lookup(ci)
while la < sizeGF repeat
-- (run through the possible values of a)
if not(a = a0) then
w := 2
ind_v(0) := 0
coeff_v(0) := 0
exp_v(w - 1) := n1
ind_v(w - 1) := la
coeff_v(w - 1) := a
exp_v(w) := n
ind_v(w) := 1
coeff_v(w) := 1
resu := do_weights(exp_v, ind_v, coeff_v, 0, (w - 1)::NNI,
w, normal?)