-
Notifications
You must be signed in to change notification settings - Fork 0
/
BettiCharacters.m2
3713 lines (3499 loc) · 121 KB
/
BettiCharacters.m2
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
--------------------------------------------------------------------------------
-- Copyright 2021-2024 Federico Galetto
--
-- This program is free software: you can redistribute it and/or modify it under
-- the terms of the GNU General Public License as published by the Free Software
-- Foundation, either version 3 of the License, or (at your option) any later
-- version.
--
-- This program is distributed in the hope that it will be useful, but WITHOUT
-- ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
-- FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
-- details.
--
-- You should have received a copy of the GNU General Public License along with
-- this program. If not, see <http://www.gnu.org/licenses/>.
--------------------------------------------------------------------------------
newPackage(
"BettiCharacters",
Version => "2.4",
Date => "July 13, 2024",
AuxiliaryFiles => false,
Authors => {{Name => "Federico Galetto",
Email => "[email protected]",
HomePage => "http://math.galetto.org"}},
Headline => "finite group characters on free resolutions and graded modules",
DebuggingMode => false
)
export {
"action",
"Action",
"ActionOnComplex",
"ActionOnGradedModule",
"actors",
"character",
"characterTable",
"Character",
"CharacterDecomposition",
"CharacterTable",
"decomposeCharacter",
"degreeOrbit",
"degreeRepresentative",
"Labels",
"numActors",
"ringActors",
"Semidirect",
"Sub",
"symmetricGroupActors",
"symmetricGroupTable"
}
----------------------------------------------------------------------
-- Types
----------------------------------------------------------------------
Character = new Type of HashTable
CharacterTable = new Type of HashTable
CharacterDecomposition = new Type of HashTable
Action = new Type of HashTable
ActionOnComplex = new Type of Action
ActionOnGradedModule = new Type of Action
-- equality check for actions implemented below
-- equality for characters as raw hash tables
Character == Character := (A,B) -> A === B
----------------------------------------------------------------------
-- Characters and character tables -----------------------------------
----------------------------------------------------------------------
-- method for returning characters of various action types
character = method(TypicalValue=>Character)
-- construct a finite dimensional character by hand
-- this constructor is new after v2.1
-- it is intended to make characters independent of a
-- particular polynomial ring, relying instead on the
-- field of definition and degree length of the grading
-- INPUT:
-- 1) coefficient ring (must be a field)
-- 2) degree length (must be a positive integer)
-- 3) integer: character length (or number of actors)
-- 4) hash table for raw character: (homdeg,deg) => character matrix
character(Ring,ZZ,ZZ,HashTable) := Character => (F,dl,cl,H) -> (
-- check first argument is a field
if not isField F then (
error "character: expected first argument to be a field";
);
-- check degree length is a positive integer
if dl <= 0 then (
error "character: second argument must be a positive integer";
);
-- check keys are in the right format
k := keys H;
if any(k, i -> class i =!= Sequence or #i != 2 or
class i#0 =!= ZZ or class i#1 =!= List) then (
error "character: expected keys of the form (ZZ,List)";
);
-- check degree vectors are allowed
degs := apply(k,last);
if any(degs, i -> #i != dl or any(i, j -> class j =!= ZZ)) then (
error ("character: expected integer degree vectors of length " | toString(dl));
);
-- check character vectors are allowed
v := values H;
if any(v, i -> class i =!= Matrix) then (
error "character: expected characters to be matrices";
);
if any(v, i -> numColumns i != cl) then (
error ("character: expected characters to be one-row matrices with " | toString(cl) | " columns");
);
-- move character values into given ring
H2 := try applyValues(H, v -> promote(v,F)) else (
error "character: could not promote characters to given ring";
);
new Character from {
cache => new CacheTable,
(symbol ring) => F,
(symbol degreeLength) => dl,
(symbol numActors) => cl,
(symbol characters) => H2,
}
)
-- constructor over polynomial rings
-- (default before v2.1, kept for compatibility)
character(PolynomialRing,ZZ,HashTable) := Character => (R,cl,H) -> (
character(coefficientRing R,degreeLength R,cl,H)
)
-- direct sum of characters
-- modeled after code in Macaulay2/Core/matrix.m2
-- plus and + added after v2.1 to match difference
Character + Character := Character => directSum
plus(Character,Character) := Character => directSum
Character ++ Character := Character => directSum
directSum Character := c -> Character.directSum (1 : c)
Character.directSum = args -> (
-- check ring is the same for all summands
R := (args#0).ring;
if any(args, c -> c.ring =!= R)
then error "directSum: expected characters all over the same field";
-- check degree length is the same for all summands
dl := (args#0).degreeLength;
if any(args, c -> c.degreeLength != dl)
then error "directSum: expected characters all with the same degree length";
-- check character length is the same for all summands
cl := (args#0).numActors;
if any(args, c -> c.numActors != cl)
then error "directSum: expected characters all of the same length";
-- raw character of direct sum (could have zero entries)
H := fold( (c1,c2) -> merge(c1,c2,plus), apply(args, c -> c.characters) );
new Character from {
cache => new CacheTable,
(symbol ring) => R,
(symbol degreeLength) => dl,
(symbol numActors) => cl,
-- add raw characters
(symbol characters) => applyPairs(H,(k,v)->if not zero v then (k,v)),
}
)
-- tensor product of characters (auxiliary functions)
-- function to add sequences (homological,internal) degrees
addDegrees = (d1,d2) -> apply(d1,d2,plus)
-- function to multiply character matrices (Hadamard product)
multiplyCharacters = (c1,c2) -> (
e1 := flatten entries c1;
e2 := flatten entries c2;
m := apply(e1,e2,times);
matrix{m}
)
-- tensor product of characters
-- modeled after directSum, but only works for two characters
Character ** Character := Character => tensor
tensor(Character,Character) := Character => {} >> opts -> (c1,c2) -> (
-- check ring is the same for all factors
R := c1.ring;
if (c2.ring =!= R)
then error "tensor: expected characters all over the same field";
-- check degree length is the same for all summands
dl := c1.degreeLength;
if (c2.degreeLength != dl)
then error "tensor: expected characters all with the same degree length";
-- check character length is the same for all summands
cl := c1.numActors;
if (c2.numActors != cl)
then error "tensor: expected characters all of the same length";
-- raw character of tensor product (may contain zeros)
H := combine(c1.characters,c2.characters,addDegrees,multiplyCharacters,plus);
new Character from {
cache => new CacheTable,
(symbol ring) => R,
(symbol degreeLength) => dl,
(symbol numActors) => cl,
-- multiply raw characters
(symbol characters) => applyPairs(H,(k,v)->if not zero v then (k,v))
}
)
-- shift homological degree of characters
Character Array := Character => (C,A) -> (
if # A =!= 1 then error "Character Array: expected array of length 1";
n := A#0;
if not instance(n,ZZ) then error "Character Array: expected an integer";
new Character from {
cache => new CacheTable,
(symbol ring) => C.ring,
(symbol degreeLength) => C.degreeLength,
(symbol numActors) => C.numActors,
-- homological shift raw characters
(symbol characters) => applyKeys(C.characters,
k -> (k#0 - n, k#1))
}
)
-- character dual
-- borrowing default options from alexander dual method
alexopts = {Strategy=>0};
-- character of dual/contragredient representation with conjugation
dual(Character,RingMap) := Character => alexopts >> o -> (c,phi) -> (
-- check characteristic
F := c.ring;
if char(F) != 0 then (
error "dual: use permutation constructor in positive characteristic";
);
-- check conjugation map
if (source phi =!= F or target phi =!= F or phi^2 =!= id_F) then (
error "dual: expected an order 2 automorphism of the base field";
);
-- error if characters cannot be lifted to coefficient field
H := try applyValues(c.characters, v -> lift(v,F)) else (
error "dual: could not lift characters to base field";
);
new Character from {
cache => new CacheTable,
(symbol ring) => F,
(symbol degreeLength) => c.degreeLength,
(symbol numActors) => c.numActors,
(symbol characters) => applyPairs(H,
(k,v) -> ( apply(k,minus), phi v )
)
}
)
-- character of dual/contragredient representation without conjugation
dual(Character,List) := Character => alexopts >> o -> (c,perm) -> (
n := c.numActors;
if #perm != n then (
error "dual: expected permutation size to match character length";
);
-- check permutation has the right entries
if set perm =!= set(1..n) then (
error ("dual: expected a permutation of {1,..," | toString(n) | "}");
);
new Character from {
cache => new CacheTable,
(symbol ring) => c.ring,
(symbol degreeLength) => c.degreeLength,
(symbol numActors) => n,
(symbol characters) => applyPairs(c.characters,
(k,v) -> ( apply(k,minus), v_(apply(perm, i -> i-1)) )
)
}
)
-- extract character by homological dimension (added after v2.1)
Character _ ZZ := Character => (c,i) -> (
H := select(pairs c.characters, p -> first first p == i);
new Character from {
cache => new CacheTable,
(symbol ring) => c.ring,
(symbol degreeLength) => c.degreeLength,
(symbol numActors) => c.numActors,
(symbol characters) => hashTable H
}
)
-- extract several characters by hom dim (added after v2.1)
Character _ List := Character => (c,l) -> (
if any(l, i -> not instance(i,ZZ)) then (
error "Character_List: expected a list of integers";
);
directSum(apply(l, i -> c_i))
)
-- extract characters by degree (added after v2.1)
Character ^ List := Character => (c,degs) -> (
-- if single degree, repackage as list (defer checks)
if any(degs,i->not instance(i,List)) then (
degs = {degs};
);
-- check all degrees are compatible
if all(degs,d->all(d,i->instance(i,ZZ)) and #d==c.degreeLength) then (
H := select(pairs c.characters, p -> member(last first p,degs));
return new Character from {
cache => new CacheTable,
(symbol ring) => c.ring,
(symbol degreeLength) => c.degreeLength,
(symbol numActors) => c.numActors,
(symbol characters) => hashTable H
}
) else (
error ("Character^List: expected a (list of) (multi)degree(s) of length " | toString(c.degreeLength));
);
)
-- multiplication of character with a scalar (added after v2.1)
ZZ * Character :=
QQ * Character :=
RingElement * Character := Character => (r,c) -> (
try a := promote(r,ring c) else (
error "RingElement*Character: could not promote scalar to field of character";
);
H := applyPairs(c.characters,(k,v)->(
w := a*v;
if not zero w then (k,w)
)
);
new Character from {
cache => new CacheTable,
(symbol ring) => c.ring,
(symbol degreeLength) => c.degreeLength,
(symbol numActors) => c.numActors,
(symbol characters) => H
}
)
-- for commutativity
Character * ZZ :=
Character * QQ :=
Character * RingElement := Character => (c,r) -> r*c
-- additive inverse of a character (added after v2.1)
- Character :=
minus Character := Character => c -> (
new Character from {
cache => new CacheTable,
(symbol ring) => c.ring,
(symbol degreeLength) => c.degreeLength,
(symbol numActors) => c.numActors,
(symbol characters) => applyValues(c.characters,v->-v)
}
)
-- difference of characters (added after v2.1)
Character - Character :=
difference(Character,Character) := Character =>
(c1,c2) -> directSum(c1,-c2)
-- method to construct character tables
characterTable = method(TypicalValue=>CharacterTable,Options=>{Labels => {}});
-- character table constructor using conjugation
-- modified after v2.1 to be defined over a field
-- modified after v2.1 to allow TeX labels
-- INPUT:
-- 1) list of conjugacy class sizes
-- 2) matrix of irreducible character values
-- 3) field over which to construct the table
-- 4) ring map, conjugation of coefficients
-- OPTIONAL: lists of labels for irreducible characters
characterTable(List,Matrix,Ring,RingMap) := CharacterTable =>
o -> (conjSize,charTable,F,phi) -> (
-- check third argument is a field
if not isField F then (
error "characterTable: expected third argument to be a field";
);
-- check characteristic
if char(F) != 0 then (
error "characterTable: use permutation constructor in positive characteristic";
);
n := #conjSize;
-- check all arguments have the right size
if numRows charTable != n or numColumns charTable != n then (
error "characterTable: expected matrix size to match number of conjugacy classes";
);
-- promote character matrix to F
X := try promote(charTable,F) else (
error "characterTable: could not promote character table to given field";
);
if (source phi =!= F or target phi =!= F or phi^2 =!= id_F) then (
error "characterTable: expected an order 2 automorphism of the coefficient ring";
);
-- check orthogonality relations
ordG := sum conjSize;
C := diagonalMatrix(F,conjSize);
m := C*transpose(phi charTable);
-- if x is a character in a one-row matrix, then x*m is the one-row matrix
-- containing the inner products of x with the irreducible characters
if X*m != ordG*map(F^n) then (
error "characterTable: orthogonality relations not satisfied";
);
-- get user labels or create default ones
if o.Labels == {} then (
netLabels := for i to n-1 list net(expression("ꭓ")_(expression i));
texLabels := for i to n-1 list ("\\chi_{" | toString(i) | "}");
)
else if (#o.Labels == 2 and all(o.Labels,x -> class x === List)) then (
netLabels = first o.Labels;
texLabels = last o.Labels;
)
else (
netLabels = o.Labels;
texLabels = o.Labels;
);
-- check labels have the right format
if (#netLabels != n or #texLabels != n) then (
error ("characterTable: expected " | toString(n) | " labels");
);
if not all(netLabels, i -> instance(i, Net)) then (
error "characterTable: expected labels to be strings (or nets)";
);
if not all(texLabels, i -> instance(i, Net)) then (
error "characterTable: expected labels to be strings (or nets)";
);
new CharacterTable from {
(symbol numActors) => #conjSize,
(symbol size) => conjSize,
(symbol table) => X,
(symbol ring) => F,
(symbol matrix) => m,
(symbol Labels) => {netLabels,texLabels},
}
)
-- character table constructor without conjugation
-- modified after v2.1 to be defined over a field
-- modified after v2.1 to allow TeX labels
-- INPUT:
-- 1) list of conjugacy class sizes
-- 2) matrix of irreducible character values
-- 3) field over which to construct the table
-- 4) list, permutation of conjugacy class inverses
-- OPTIONAL: lists of labels for irreducible characters
characterTable(List,Matrix,Ring,List) := CharacterTable =>
o -> (conjSize,charTable,F,perm) -> (
-- check third argument is a field
if not isField F then (
error "characterTable: expected third argument to be a field";
);
n := #conjSize;
-- check all arguments have the right size
if numRows charTable != n or numColumns charTable != n then (
error "characterTable: expected matrix size to match number of conjugacy classes";
);
if #perm != n then (
error "characterTable: expected permutation size to match number of conjugacy classes";
);
-- promote character matrix to F
X := try promote(charTable,F) else (
error "characterTable: could not promote character table to given field";
);
-- check permutation has the right entries
if set perm =!= set(1..n) then (
error ("characterTable: expected a permutation of {1,..," | toString(n) | "}");
);
-- check characteristic
ordG := sum conjSize;
if ordG % char(F) == 0 then (
error "characterTable: characteristic divides order of the group";
);
-- check orthogonality relations
C := diagonalMatrix(F,conjSize);
P := map(F^n)_(apply(perm, i -> i-1));
m := C*transpose(X*P);
-- if x is a character in a one-row matrix, then x*m is the one-row matrix
-- containing the inner products of x with the irreducible characters
if X*m != ordG*map(F^n) then (
error "characterTable: orthogonality relations not satisfied";
);
-- get user labels or create default ones
if o.Labels == {} then (
netLabels := for i to n-1 list net(expression("ꭓ")_(expression i));
texLabels := for i to n-1 list ("\\chi_{" | toString(i) | "}");
)
else if (#o.Labels == 2 and all(o.Labels,x -> class x === List)) then (
netLabels = first o.Labels;
texLabels = last o.Labels;
)
else (
netLabels = o.Labels;
texLabels = o.Labels;
);
-- check labels have the right format
if (#netLabels != n or #texLabels != n) then (
error ("characterTable: expected " | toString(n) | " labels");
);
if not all(netLabels, i -> instance(i, Net)) then (
error "characterTable: expected labels to be strings (or nets)";
);
if not all(texLabels, i -> instance(i, Net)) then (
error "characterTable: expected labels to be strings (or nets)";
);
new CharacterTable from {
(symbol numActors) => #conjSize,
(symbol size) => conjSize,
(symbol table) => X,
(symbol ring) => F,
(symbol matrix) => m,
(symbol Labels) => {netLabels,texLabels},
}
)
-- new method for character decomposition
decomposeCharacter = method(TypicalValue=>CharacterDecomposition);
-- decompose a character against a character table
decomposeCharacter(Character,CharacterTable) :=
CharacterDecomposition => (C,T) -> (
-- check character and table are over same ring
R := C.ring;
if T.ring =!= R then (
error "decomposeCharacter: expected character and table over the same field";
);
-- check number of actors is the same
if C.numActors != T.numActors then (
error "decomposeCharacter: character length does not match table";
);
ord := sum T.size;
-- create decomposition hash table
D := applyValues(C.characters, char -> 1/ord*char*T.matrix);
-- find non zero columns of table for printing
M := matrix apply(values D, m -> flatten entries m);
p := positions(toList(0..numColumns M - 1), i -> M_i != 0*M_0);
new CharacterDecomposition from {
(symbol numActors) => C.numActors,
(symbol ring) => R,
(symbol degreeLength) => C.degreeLength,
(symbol Labels) => T.Labels,
(symbol decompose) => D,
(symbol positions) => p
}
)
-- shortcut for character decomposition
Character / CharacterTable := CharacterDecomposition => decomposeCharacter
-- recreate a character from decomposition
character(CharacterDecomposition,CharacterTable) :=
Character => (D,T) -> (
new Character from {
cache => new CacheTable,
(symbol ring) => D.ring,
(symbol degreeLength) => D.degreeLength,
(symbol numActors) => D.numActors,
(symbol characters) => applyValues(D.decompose, i -> i*T.table),
}
)
-- shortcut to recreate character from decomposition
CharacterDecomposition * CharacterTable := Character => character
----------------------------------------------------------------------
-- Actions on complexes and characters of complexes ------------------
----------------------------------------------------------------------
-- constructor for action on resolutions and modules
-- optional argument Sub=>true means ring actors are passed
-- as one-row matrices of substitutions, Sub=>false means
-- ring actors are passed as matrices
-- Semidirect option (added after v2.2)
action = method(TypicalValue=>Action,Options=>{Sub=>true,Semidirect=>(d -> {d},identity)})
-- constructor for action on resolutions
-- INPUT:
-- 1) a resolution
-- 2) a list of actors on the ring variables
-- 3) a list of actors on the i-th module of the resolution
-- 4) homological index i
action(ChainComplex,List,List,ZZ):=ActionOnComplex=>op->(C,l,l0,i) -> (
--check C is a homogeneous min free res over a poly ring over a field
R := ring C;
if not isPolynomialRing R then (
error "action: expected a complex over a polynomial ring";
);
if not isField coefficientRing R then (
error "action: expected coefficients in a field";
);
if not all(length C,i -> isFreeModule C_(i+min(C))) then (
error "action: expected a complex of free modules";
);
if not isHomogeneous C then (
error "action: complex is not homogeneous";
);
--check the matrix of the action on the variables has right size
n := dim R;
if not all(l,g->numColumns(g)==n) then (
error "action: ring actor matrix has wrong number of columns";
);
--move ring actors to ring for uniformity
l = apply(l, g -> promote(g,R));
if op.Sub then (
--if ring actors are substitutions, they must be one-row matrices
if not all(l,g->numRows(g)==1) then (
error "action: expected ring actor matrix to be a one-row substitution matrix";
);
) else (
--if ring actors are matrices, they must be square
if not all(l,g->numRows(g)==n) then (
error "action: ring actor matrix has wrong number of rows";
);
--convert them to substitutions
l = apply(l, g -> (vars R) * g);
);
--check list of group elements has same length
if #l != #l0 then (
error "action: lists of actors must have equal length";
);
--check size of module actors matches rank of starting module
r := rank C_i;
if not all(l0,g->numColumns(g)==r and numRows(g)==r) then (
error "action: module actor matrix has wrong number of rows or columns";
);
--store everything into a hash table
new ActionOnComplex from {
cache => new CacheTable from {
(symbol actors,i) => apply(l0,g->map(C_i,C_i,g))
},
(symbol ring) => R,
(symbol target) => C,
(symbol numActors) => #l,
(symbol ringActors) => l,
(symbol degreeOrbit) => first op.Semidirect,
(symbol degreeRepresentative) => last op.Semidirect,
}
)
-- shortcut constructor for resolutions of quotient rings
-- actors on generator are assumed to be trivial
action(ChainComplex,List) := ActionOnComplex => op -> (C,l) -> (
R := ring C;
l0 := toList(#l:(id_(R^1)));
action(C,l,l0,min C,Sub=>op.Sub,Semidirect=>op.Semidirect)
)
-- equality check for actions on complexes
-- user provided action is stored in cache because user
-- may provide initial action in different homological degrees
-- then it is not enough to compare as raw hash tables
-- so we compare actors in all homological degrees
ActionOnComplex == ActionOnComplex := (A,B) -> (
-- first compare raw hash tables
if A =!= B then return false;
-- if same, compare action which is stored in cache
C := A.target;
all(min C .. max C, i -> actors(A, i) == actors(B, i))
)
-- returns number of actors
numActors = method(TypicalValue=>ZZ)
numActors(Action) := ZZ => A -> A.numActors
-- returns action on ring variables
-- Sub=>true returns one-row substitution matrices
-- Sub=>false returns square matrices
ringActors = method(TypicalValue=>List,Options=>{Sub=>true})
ringActors(Action) := List => op -> A -> (
if not op.Sub then (
GB := gb(vars ring A,StopWithMinimalGenerators=>true,ChangeMatrix=>true);
apply(A.ringActors,g->g//GB)
)
else A.ringActors
)
-- returns various group actors
actors = method(TypicalValue=>List)
-- returns actors on resolution in a given homological degree
-- if homological degree is not the one passed by user,
-- the actors are computed and stored
actors(ActionOnComplex,ZZ) := List => (A,i) -> (
-- if not cached, compute
if not A.cache#?(symbol actors,i) then (
-- homological degrees where action is already cached
places := apply(select(keys A.cache, k -> instance(k,Sequence) and k#0 == symbol actors), k -> k#1);
-- get the complex
C := target A;
-- if zero in that hom degree, return zeros
if zero(C_i) then return toList(numActors(A):map(C_i));
-- if hom degree is to the right of previously computed
if i > max places then (
-- compute GB of differential but only up to min gens
-- NOTE: does not work if ChangeMatrix=>false (which is default)
GB := gb(C.dd_i,StopWithMinimalGenerators=>true,ChangeMatrix=>true);
A.cache#(symbol actors,i) =
apply(A.ringActors, actors(A,i-1),
-- given a map of free modules C.dd_i : F <-- F',
-- the group action on the ring (as substitution)
-- and the group action on F, computes the group action on F'
(g,g0) -> g0*sub(C.dd_i,g)//GB
);
)
-- if hom degree is to the left of previously computed
else (
-- may need to compute inverse of ring actors
if not A.cache.?inverse then (
--convert variable substitutions to matrices
--then invert and convert back to substitutions
R := ring A;
b := gb(vars R,StopWithMinimalGenerators=>true,ChangeMatrix=>true);
A.cache.inverse = apply(A.ringActors, g ->
(vars R) * (inverse lift(g//b,coefficientRing R))
);
);
GB = gb(transpose(C.dd_(i+1)),StopWithMinimalGenerators=>true,ChangeMatrix=>true);
A.cache#(symbol actors,i) =
apply(A.cache.inverse,actors(A,i+1),
-- given a map of free modules C.dd_i : F <-- F',
-- the inverse group action on the ring (as substitution)
-- and the group action on F', computes the group action on F
(gInv,g0) -> (
transpose(transpose(sub(C.dd_(i+1),gInv)*g0)//GB)
)
);
);
);
-- return cached value
A.cache#(symbol actors,i)
)
-- return the character of one free module of a resolution
-- in a given homological degree
character(ActionOnComplex,ZZ) := Character => (A,i) -> (
-- if not cached, compute
if not A.cache#?(symbol character,i) then (
F := coefficientRing ring A;
-- if complex is zero in hom degree i, return empty character, don't cache
if zero (target A)_i then (
return new Character from {
cache => new CacheTable,
(symbol ring) => F,
(symbol degreeLength) => degreeLength ring A,
(symbol numActors) => numActors A,
(symbol characters) => hashTable {},
};
);
-- record position of degrees of i-th free module
-- based on their unique degree orbit rep
degs := partition(j -> A.degreeRepresentative degree ((target A)_i)_j,
toList(0..rank((target A)_i)-1));
-- create raw character from actors
H := applyPairs(degs,
(d,indx) -> ((i,d),
lift(matrix{apply(actors(A,i), g -> trace g_indx^indx)},F)
)
);
-- cache character
A.cache#(symbol character,i) = new Character from {
cache => new CacheTable,
(symbol ring) => F,
(symbol degreeLength) => degreeLength ring A,
(symbol numActors) => numActors A,
(symbol characters) => H,
};
);
-- return cached value
A.cache#(symbol character,i)
)
-- return characters of all free modules in a resolution
-- by repeatedly using previous function
character ActionOnComplex := Character => A -> (
C := target A;
directSum for i from min(C) to min(C)+length(C) list character(A,i)
)
----------------------------------------------------------------------
-- Actions on modules and characters of modules ----------------------
----------------------------------------------------------------------
-- constructor for action on various kinds of graded modules
-- INPUT:
-- 1) a graded module (polynomial ring or quotient, module, ideal)
-- 2) a list of actors on the ring variables
-- 3) a list of actors on the generators of the ambient free module
action(PolynomialRing,List,List) :=
action(QuotientRing,List,List) :=
action(Ideal,List,List) :=
action(Module,List,List):=ActionOnGradedModule=>op->(M,l,l0) -> (
-- check M is graded over a poly ring over a field
-- the way to get the ring depends on the class of M
if instance(M,Ring) then (
R := ambient M;
) else (
R = ring M;
);
if not isPolynomialRing R then (
error "action: expected a module/ideal/quotient over a polynomial ring";
);
if not isField coefficientRing R then (
error "action: expected coefficients in a field";
);
if not isHomogeneous M then (
error "action: module/ideal/quotient is not graded";
);
--check matrix of action on variables has right size
n := dim R;
if not all(l,g->numColumns(g)==n) then (
error "action: ring actor matrix has wrong number of columns";
);
--move ring actors to ring for uniformity
l = apply(l, g -> promote(g,R));
if op.Sub then (
--if ring actors are substitutions, they must be one-row matrices
if not all(l,g->numRows(g)==1) then (
error "action: expected ring actor matrix to be a one-row substitution matrix";
);
) else (
--if ring actors are matrices, they must be square
if not all(l,g->numRows(g)==n) then (
error "action: ring actor matrix has wrong number of rows";
);
--convert them to substitutions
l = apply(l, g -> (vars R) * g);
);
--check list of group elements has same length
if #l != #l0 then (
error "action: lists of actors must have equal length";
);
--check size of module actors matches rank of ambient module
if instance(M,Module) then (
F := ambient M;
) else ( F = R^1; );
r := rank F;
if not all(l0,g->numColumns(g)==r and numRows(g)==r) then (
error "action: module actor matrix has wrong number of rows or columns";
);
--turn input object into a module M'
if instance(M,QuotientRing) then (
M' := coker presentation M;
) else if instance(M,Module) then (
M' = M;
) else (
M' = module M;
);
--store everything into a hash table
new ActionOnGradedModule from {
cache => new CacheTable,
(symbol ring) => R,
(symbol target) => M,
(symbol numActors) => #l,
(symbol ringActors) => l,
(symbol actors) => apply(l0,g->map(F,F,g)),
(symbol module) => M',
(symbol relations) => gb image relations M',
(symbol degreeOrbit) => first op.Semidirect,
(symbol degreeRepresentative) => last op.Semidirect,
}
)
-- shortcut constructor when actors on generator are trivial
action(PolynomialRing,List) :=
action(QuotientRing,List) :=
action(Ideal,List) :=
action(Module,List) := ActionOnGradedModule => op -> (M,l) -> (
if instance(M,Module) then (
l0 := toList(#l:(id_(ambient M)));
) else if instance(M,Ideal) then (
l0 = toList(#l:(id_(ambient module M)));
) else (
l0 = toList(#l:(id_(module ambient M)));
);
action(M,l,l0,Sub=>op.Sub,Semidirect=>op.Semidirect)
)
-- equality check for actions on graded modules
-- since the user provided action on generators is stored
-- it is enough to compare as raw hash tables
ActionOnGradedModule == ActionOnGradedModule := (A,B) -> A === B
-- returns actors on component of given multidegree
-- the actors are computed and stored
actors(ActionOnGradedModule,List) := List => (A,d) -> (
-- ensure function is computed with rep of degree orbit
degRep := A.degreeRepresentative d;
-- if not cached, compute
if not A.cache#?(symbol actors,degRep) then (
M := A.module;
-- get basis in degree d as map of free modules
-- (after semidirect: single degree d replaced by degree orbit)
degList := A.degreeOrbit d;
-- collect bases for degrees in orbit and join horizontally
b := rsort fold( (x,y) -> x|y, apply(degList, d -> ambient basis(d,M)));
-- the basis command returns a matrix with columns in decreasing order
-- joining basis from different degrees may break this order
-- the rsort at the beginning recovers M2's default order
-- this sorting was made necessary after introducing semidirect options
-- actors matrix would be useless without out as it may not match basis
if zero b then (
A.cache#(symbol actors,degRep) = toList(numActors(A):map(source b));
)
else (
GB := gb(b,StopWithMinimalGenerators=>true,ChangeMatrix=>true);
A.cache#(symbol actors,degRep) =
apply(A.ringActors, A.actors,
--g0*b acts on the basis of the ambient module
--sub(-,g) acts on the polynomial coefficients
--result must be reduced against module relations
--then factored by original basis to get action matrix
(g,g0) -> (sub(g0*b,g) % A.relations) // GB
);
);
);
-- return cached value
A.cache#(symbol actors,degRep)
)
-- returns actors on component of given degree
actors(ActionOnGradedModule,ZZ) := List => (A,d) -> actors(A,{d})
-- return character of component of given multidegree
character(ActionOnGradedModule,List) := Character => (A,d) -> (
-- ensure function is computed with rep of degree orbit
degRep := A.degreeRepresentative d;
-- if not cached, compute
if not A.cache#?(symbol character,degRep) then (
F := coefficientRing ring A;
-- zero action, return empty character and don't cache
acts := actors(A,degRep);
if all(acts,zero) then (
return new Character from {
cache => new CacheTable,
(symbol ring) => F,
(symbol degreeLength) => degreeLength ring A,
(symbol numActors) => numActors A,
(symbol characters) => hashTable {},
};
);
-- otherwise make character of A in degree d
A.cache#(symbol character,degRep) = new Character from {
cache => new CacheTable,
(symbol ring) => F,
(symbol degreeLength) => degreeLength ring A,
(symbol numActors) => numActors A,
(symbol characters) => hashTable {(0,degRep) => lift(matrix{apply(acts, trace)},F)},
};
);
-- return cached value
A.cache#(symbol character,degRep)
)
-- return character of component of given degree
character(ActionOnGradedModule,ZZ) := Character => (A,d) -> (
character(A,{d})
)
-- return character of components in a range of degrees
character(ActionOnGradedModule,ZZ,ZZ) := Character => (A,lo,hi) -> (
if not all(gens ring A, v->(degree v)=={1}) then (
error "character: expected a ZZ-graded polynomial ring";
);
directSum for d from lo to hi list character(A,d)
)
---------------------------------------------------------------------
-- Specialized functions for symmetric groups -----------------------
---------------------------------------------------------------------
-- take r boxes from partition mu along border
-- unexported auxiliary function for Murnaghan-Nakayama
strip := (mu,r) -> (
-- if one row, strip r boxes
if #mu == 1 then return {mu_0 - r};
-- if possible, strip r boxes in 1st row
d := mu_0 - mu_1;
if d >= r then (
return {mu_0 - r} | drop(mu,1);
);
-- else, remove d+1 boxes and iterate
{mu_0-d-1} | strip(drop(mu,1),r-d-1)
)
-- irreducible Sn character chi^lambda
-- evaluated at conjugacy class of cycle type rho
-- unexported
murnaghanNakayama := (lambda,rho) -> (
-- if both empty, character is 1
if lambda == {} and rho == {} then return 1;
r := rho#0;
-- check if border strip fits ending at each row
borderStrips := select(
-- for all c remove first c parts, check if strip fits in the rest
for c to #lambda-1 list (take(lambda,c) | strip(drop(lambda,c),r)),
-- function that checks if list is a partition (0 allowed)
mu -> (
-- check no negative parts
if any(mu, i -> i<0) then return false;
-- check non increasing
for i to #mu-2 do (
if mu_i < mu_(i+1) then return false;
);