15
15
# ****************************************************************************
16
16
17
17
from sage .structure .sage_object import SageObject
18
+ from matplotlib .cm import gist_rainbow , Greys
19
+ from sage .plot .matrix_plot import matrix_plot
20
+ from sage .matrix .constructor import Matrix
21
+ from sage .plot .text import text
22
+ from copy import copy
18
23
19
24
20
25
class OperationTable (SageObject ):
@@ -374,15 +379,12 @@ class OperationTable(SageObject):
374
379
k| k i g f d e l b h a j c
375
380
l| l c j i g a h e k b d f
376
381
377
- .. TODO::
378
-
379
- Provide color and grayscale graphical representations of tables.
380
- See commented-out stubs in source code.
381
-
382
- AUTHOR:
382
+ AUTHORS:
383
383
384
384
- Rob Beezer (2010-03-15)
385
+ - Bruno Edwards (2022-10-31)
385
386
"""
387
+
386
388
def __init__ (self , S , operation , names = 'letters' , elements = None ):
387
389
r"""
388
390
TESTS::
@@ -432,7 +434,7 @@ def __init__(self, S, operation, names='letters', elements=None):
432
434
supported = {
433
435
add : (add , '+' , '+' ),
434
436
mul : (mul , '*' , '\\ ast' )
435
- }
437
+ }
436
438
# default symbols for upper-left-hand-corner of table
437
439
self ._ascii_symbol = '.'
438
440
self ._latex_symbol = '\\ cdot'
@@ -451,7 +453,7 @@ def __init__(self, S, operation, names='letters', elements=None):
451
453
# the elements might not be hashable. But if they are it is much
452
454
# faster to lookup in a hash table rather than in a list!
453
455
try :
454
- get_row = {e : i for i ,e in enumerate (self ._elts )}.__getitem__
456
+ get_row = {e : i for i , e in enumerate (self ._elts )}.__getitem__
455
457
except TypeError :
456
458
get_row = self ._elts .index
457
459
@@ -461,7 +463,8 @@ def __init__(self, S, operation, names='letters', elements=None):
461
463
try :
462
464
result = self ._operation (g , h )
463
465
except Exception :
464
- raise TypeError ('elements %s and %s of %s are incompatible with operation: %s' % (g ,h ,S ,self ._operation ))
466
+ raise TypeError ('elements %s and %s of %s are incompatible with operation: %s' % (
467
+ g , h , S , self ._operation ))
465
468
466
469
try :
467
470
r = get_row (result )
@@ -477,7 +480,8 @@ def __init__(self, S, operation, names='letters', elements=None):
477
480
except (KeyError , ValueError ):
478
481
failed = True
479
482
if failed :
480
- raise ValueError ('%s%s%s=%s, and so the set is not closed' % (g , self ._ascii_symbol , h , result ))
483
+ raise ValueError ('%s%s%s=%s, and so the set is not closed' % (
484
+ g , self ._ascii_symbol , h , result ))
481
485
482
486
row .append (r )
483
487
self ._table .append (row )
@@ -558,7 +562,8 @@ def _name_maker(self, names):
558
562
else :
559
563
width = int (log (self ._n - 1 , base )) + 1
560
564
for i in range (self ._n ):
561
- places = Integer (i ).digits (base = base , digits = letters , padto = width )
565
+ places = Integer (i ).digits (
566
+ base = base , digits = letters , padto = width )
562
567
places .reverse ()
563
568
name_list .append ('' .join (places ))
564
569
elif names == 'elements' :
@@ -570,19 +575,22 @@ def _name_maker(self, names):
570
575
name_list .append (estr )
571
576
elif isinstance (names , list ):
572
577
if len (names ) != self ._n :
573
- raise ValueError ('list of element names must be the same size as the set, %s != %s' % (len (names ), self ._n ))
578
+ raise ValueError ('list of element names must be the same size as the set, %s != %s' % (
579
+ len (names ), self ._n ))
574
580
width = 0
575
581
for name in names :
576
582
if not isinstance (name , str ):
577
- raise ValueError ('list of element names must only contain strings, not %s' % name )
583
+ raise ValueError (
584
+ 'list of element names must only contain strings, not %s' % name )
578
585
if len (name ) > width :
579
586
width = len (name )
580
587
name_list .append (name )
581
588
else :
582
- raise ValueError ("element names must be a list, or one of the keywords: 'letters', 'digits', 'elements'" )
589
+ raise ValueError (
590
+ "element names must be a list, or one of the keywords: 'letters', 'digits', 'elements'" )
583
591
name_dict = {}
584
592
for i in range (self ._n ):
585
- name_dict [name_list [i ]]= self ._elts [i ]
593
+ name_dict [name_list [i ]] = self ._elts [i ]
586
594
return width , name_list , name_dict
587
595
588
596
def __getitem__ (self , pair ):
@@ -632,13 +640,15 @@ def __getitem__(self, pair):
632
640
IndexError: invalid indices of operation table: ((1,512), (1,3,2,4)(5,7))
633
641
"""
634
642
if not (isinstance (pair , tuple ) and len (pair ) == 2 ):
635
- raise TypeError ('indexing into an operation table requires exactly two elements' )
643
+ raise TypeError (
644
+ 'indexing into an operation table requires exactly two elements' )
636
645
g , h = pair
637
646
try :
638
647
row = self ._elts .index (g )
639
648
col = self ._elts .index (h )
640
649
except ValueError :
641
- raise IndexError ('invalid indices of operation table: (%s, %s)' % (g , h ))
650
+ raise IndexError (
651
+ 'invalid indices of operation table: (%s, %s)' % (g , h ))
642
652
return self ._elts [self ._table [row ][col ]]
643
653
644
654
def __eq__ (self , other ):
@@ -747,8 +757,9 @@ def set_print_symbols(self, ascii, latex):
747
757
...
748
758
ValueError: ASCII symbol should be a single character, not 5
749
759
"""
750
- if not isinstance (ascii , str ) or not len (ascii )== 1 :
751
- raise ValueError ('ASCII symbol should be a single character, not %s' % ascii )
760
+ if not isinstance (ascii , str ) or not len (ascii ) == 1 :
761
+ raise ValueError (
762
+ 'ASCII symbol should be a single character, not %s' % ascii )
752
763
if not isinstance (latex , str ):
753
764
raise ValueError ('LaTeX symbol must be a string, not %s' % latex )
754
765
self ._ascii_symbol = ascii
@@ -935,22 +946,79 @@ def matrix_of_variables(self):
935
946
from sage .rings .rational_field import QQ
936
947
R = PolynomialRing (QQ , 'x' , self ._n )
937
948
MS = MatrixSpace (R , self ._n , self ._n )
938
- entries = [R ('x' + str (self ._table [i ][j ])) for i in range (self ._n ) for j in range (self ._n )]
939
- return MS ( entries )
940
-
941
- #def color_table():
942
- #r"""
943
- #Returns a graphic image as a square grid where entries are color coded.
944
- #"""
945
- #pass
946
- #return None
947
-
948
- #def gray_table():
949
- #r"""
950
- #Returns a graphic image as a square grid where entries are coded as grayscale values.
951
- #"""
952
- #pass
953
- #return None
949
+ entries = [R ('x' + str (self ._table [i ][j ]))
950
+ for i in range (self ._n ) for j in range (self ._n )]
951
+ return MS (entries )
952
+
953
+ # documentation hack
954
+ # makes the cmap default argument look nice in the docs
955
+ # by copying the gist_rainbow object and overriding __repr__
956
+ gist_rainbow_copy = copy (gist_rainbow )
957
+ class ReprOverrideLinearSegmentedColormap (gist_rainbow_copy .__class__ ):
958
+ def __repr__ (self ):
959
+ return "gist_rainbow"
960
+ gist_rainbow_copy .__class__ = ReprOverrideLinearSegmentedColormap
961
+
962
+
963
+ def color_table (self , element_names = True , cmap = gist_rainbow_copy , ** options ):
964
+ r"""
965
+ Returns a graphic image as a square grid where entries are color coded.
966
+
967
+ INPUT:
968
+
969
+ - ``element_names`` - (default : ``True``) Whether to display text with element names on the image
970
+
971
+ - ``cmap`` - (default : ``gist_rainbow``) colour map for plot, see matplotlib.cm
972
+
973
+ - ``**options`` - passed on to matrix_plot call
974
+
975
+ EXAMPLES::
976
+
977
+ sage: from sage.matrix.operation_table import OperationTable
978
+ sage: OTa = OperationTable(SymmetricGroup(3), operation=operator.mul)
979
+ sage: OTa.color_table()
980
+ Graphics object consisting of 37 graphics primitives
981
+
982
+ .. PLOT::
983
+
984
+ from sage.matrix.operation_table import OperationTable
985
+ OTa = OperationTable(SymmetricGroup(3), operation=operator.mul)
986
+ sphinx_plot(OTa.color_table(), figsize=(3.0,3.0))
987
+ """
988
+
989
+ # Base matrix plot object, without text
990
+ plot = matrix_plot (Matrix (self ._table ), cmap = cmap ,
991
+ frame = False , ** options )
992
+
993
+ if element_names :
994
+
995
+ # adapted from ._ascii_table()
996
+ # prepare widenames[] list for labelling on image
997
+ n = self ._n
998
+ width = self ._width
999
+
1000
+ widenames = []
1001
+ for name in self ._names :
1002
+ widenames .append ("{0: >{1}s}" .format (name , width ))
1003
+
1004
+ # iterate through each element
1005
+ for g in range (n ):
1006
+ for h in range (n ):
1007
+
1008
+ # add text to the plot
1009
+ tPos = (g , h )
1010
+ tText = widenames [self ._table [g ][h ]]
1011
+ t = text (tText , tPos , rgbcolor = (0 , 0 , 0 ))
1012
+ plot = plot + t
1013
+
1014
+ # https://moyix.blogspot.com/2022/09/someones-been-messing-with-my-subnormals.html
1015
+ import warnings
1016
+ warnings .filterwarnings ("ignore" , message = "The value of the smallest subnormal for" )
1017
+
1018
+ return plot
1019
+
1020
+ def gray_table (self , ** options ):
1021
+ return self .color_table (cmap = Greys , ** options )
954
1022
955
1023
def _ascii_table (self ):
956
1024
r"""
@@ -1031,7 +1099,7 @@ def _ascii_table(self):
1031
1099
widenames .append ('{0: >{1}s}' .format (name , width ))
1032
1100
1033
1101
# Headers
1034
- table = ['{0: >{1}s} ' .format (self ._ascii_symbol ,width )]
1102
+ table = ['{0: >{1}s} ' .format (self ._ascii_symbol , width )]
1035
1103
table += [' ' + widenames [i ] for i in range (n )]+ ['\n ' ]
1036
1104
table += [' ' ]* width + ['+' ] + ['-' ]* (n * (width + 1 ))+ ['\n ' ]
1037
1105
@@ -1068,7 +1136,8 @@ def _latex_(self):
1068
1136
1069
1137
# Row label and body of table
1070
1138
for g in range (n ):
1071
- table .append ('{}' ) # Interrupts newline and [], so not line spacing
1139
+ # Interrupts newline and [], so not line spacing
1140
+ table .append ('{}' )
1072
1141
table .append (names [g ])
1073
1142
for h in range (n ):
1074
1143
table .append ('&' + names [self ._table [g ][h ]])
0 commit comments