File Coverage

blib/lib/Math/Groups.pm
Criterion Covered Total %
statement 155 176 88.0
branch 40 64 62.5
condition 9 15 60.0
subroutine 31 33 93.9
pod 14 23 60.8
total 249 311 80.0


line stmt bran cond sub pod time code
1             =head1 Name
2              
3             Math::Groups - Find automorphisms of groups and isomorphisms between groups.
4              
5             =head1 Synopsis
6              
7             use Math::Groups;
8             use Data::Dump qw(dump);
9             use Math::Cartesian::Product;
10              
11             # Print a cyclic group of order 4
12              
13             print dump(Group{($_[0]*$_[1]) % 5} 1..4)."\n";
14              
15             # elements => {
16             # 1 => { 1 => 1, 2 => 2, 3 => 3, 4 => 4 },
17             # 2 => { 1 => 2, 2 => 4, 3 => 1, 4 => 3 },
18             # 3 => { 1 => 3, 2 => 1, 3 => 4, 4 => 2 },
19             # 4 => { 1 => 4, 2 => 3, 3 => 2, 4 => 1 },
20             # },
21             # identity => 1,
22             # inverses => { 1 => 1, 2 => 3, 3 => 2, 4 => 4 },
23             # orders => { 1 => 0, 2 => 4, 3 => 4, 4 => 2 },
24              
25              
26             # Find the automorphisms of the cyclic group of order 4
27              
28             autoMorphisms {print dump({@_})."\n"}
29             Group{($_[0]+$_[1]) % 4} 0..3;
30              
31             # { 1 => 1, 2 => 2, 3 => 3 }
32             # { 1 => 3, 2 => 2, 3 => 1 }
33              
34             # Find the automorphisms of dihedral group of order 4
35              
36             my $corners = [cartesian {1} ([1,-1]) x 2];
37             my $cornerNumbers;
38             map {my ($a, $b) = @{$$corners[$_]};
39             $cornerNumbers->{$a}{$b} = $_
40             } 0..$#$corners;
41              
42             autoMorphisms {print dump({@_})."\n"}
43             Group
44             {my ($a, $b, $c, $d) = map {@$_} @$corners[@_];
45             $cornerNumbers->{$a*$c}{$b*$d}
46             } 0..$#$corners;
47              
48             # { 1 => 1, 2 => 2, 3 => 3 }
49             # { 1 => 1, 2 => 3, 3 => 2 }
50             # { 1 => 2, 2 => 1, 3 => 3 }
51             # { 1 => 3, 2 => 1, 3 => 2 }
52             # { 1 => 2, 2 => 3, 3 => 1 }
53             # { 1 => 3, 2 => 2, 3 => 1 }
54             =cut
55              
56             package Math::Groups;
57              
58             #-------------------------------------------------------------------------------
59             # Mathematical Groups
60             # Philip R Brenan at gmail dot com, Appa Apps Ltd Inc, 2015
61             #-------------------------------------------------------------------------------
62              
63 1     1   20121 use v5.18;
  1         3  
64 1     1   4 use warnings FATAL => qw(all);
  1         2  
  1         44  
65 1     1   11 use Carp;
  1         6  
  1         51  
66 1     1   4 use strict;
  1         1  
  1         21  
67 1     1   4 use utf8;
  1         2  
  1         3  
68 1     1   720 use Math::Cartesian::Product;
  1         721  
  1         46  
69 1     1   691 use Math::Permute::List;
  1         344  
  1         47  
70 1     1   696 use Data::Dump qw(dump);
  1         7758  
  1         18481  
71              
72 360     360 0 949 sub Elements {qw(elements)} # Constants
73 170     170 0 374 sub Identity {qw(identity)}
74 39     39 0 73 sub Inverses {qw(inverses)}
75 40     40 0 154 sub Orders {qw(orders)}
76              
77 204     204 0 342 sub 𝗲($) {$_[0]->{&Elements}} # Multiplication table
78 159     159 0 290 sub e($) {$_[0]->{&Identity}} # Identity
79 24     24 0 43 sub i($) {$_[0]->{&Inverses}} # Inverses
80              
81             sub o($$$;$$) # Add one or two products to the group or retrieve a prior product
82 0     0 0 0 {my ($g, $a, $b, $c, $𝗰) = @_; # Group, first element $a, second element $b, $a*$b, $b*$a
83 0         0 my $𝗲 = 𝗲($g); # Elements
84 0 0       0 if (@_ == 3) # Retrieve a prior product
85 0         0 {return $𝗲->{$a}{$b};
86             }
87 0 0 0     0 if (@_ == 4 or @_ == 5) # Add a product to the group for a*b
88 0         0 {$g->{&Identity} = $g->{&Inverses} = undef; # Destroy cached identity and inverses as we have modified the group
89             }
90 0 0       0 if (@_ == 4) # Add a product to the group for a*b
91 0         0 {$𝗲->{$a}{$b} = $c;
92 0         0 return $g; # Return group to allow for chaining if desired
93             }
94 0 0       0 if (@_ == 5) # Add products to the group for a*b and b*a
95 0         0 {$𝗲->{$a}{$b} = $c;
96 0         0 $𝗲->{$b}{$a} = $𝗰;
97 0         0 return $g; # Return group to allow for chaining if desired
98             }
99 0         0 confess "Wrong number of parameters, should be 3 to get a prior product or 4 for single put or 5 for double put!";
100             }
101              
102             sub identity($) # Find the identity element - assumes that the group has been checked for uniqueness and closure
103 15     15 1 17 {my ($g) = @_; # Group
104 15         23 my $e = e($g); # Identity from cache
105 15 100       51 return $e if $e; # Check cache
106 11         16 my $𝗲 = 𝗲($g); # Elements
107 11         23 for my $a(keys %$𝗲) # Find the identity and confirm that there is only one
108 43         44 {my $n = 0; # Number of elements for which $a is an identity
109 43         76 for my $b(keys %$𝗲)
110 76 100 66     270 {last unless $𝗲->{$a}{$b} eq $b and $𝗲->{$b}{$a} eq $b; # Check whether it could be an indentity
111 44         52 $n++ # Possible identity
112             }
113 43 100       114 return $g->{&Identity} = $a if $n == keys %$𝗲 # Save identity in cache
114             }
115 0         0 confess "No identity found!";
116             }
117              
118             sub inverse($$) # Find the inverse of an element - assumes that identity has been dound
119 24     24 1 41 {my ($g, $a) = @_; # Group, element for which an inverse is required
120 24         41 my $𝗲 = 𝗲($g); # Elements
121 24         50 my $i = i($g); # Inverses
122 24 50       64 confess "Not a group element: $a" unless defined $𝗲->{$a}; # Validate element
123 24 100 66     161 return $i->{$a} if defined($i) and defined($i->{$a}); # Return if inverse is in cache
124 12         18 my $e = identity($g); # Find identity
125 12         47 for my $b(keys %$𝗲) # Each element
126 30         43 {my ($p, $q) = ($𝗲->{$a}{$b}, $𝗲->{$b}{$a}); # Product each way
127 30 100 66     91 if ($p eq $e and $q eq $e) # Inverse if both products equal identity
128 12         18 {$g->{&Inverses}->{$a} = $b; # Cache inverse
129 12         39 return $b # Inverse
130             }
131             }
132 0         0 confess "No inverse found for $a"
133             }
134              
135             sub orders($) # Order of each element
136 3     3 1 4 {my ($g) = @_;
137 3         6 my $e = e($g); # Identity
138 3         4 my $𝗲 = 𝗲($g); # Elements
139 3         9 for my $A(keys %$𝗲) # Each element
140 12         12 {my $a = $A;
141 12         13 my $o = 1;
142 12         21 for(1..keys %$𝗲) # Multiply until we reach the identity
143 29 100       53 {last if $a eq $e;
144 17         22 $a = $𝗲->{$a}{$A};
145 17         21 ++$o;
146             }
147 12         20 $g->{&Orders}{$A} = $o; # Save order
148             }
149 3         6 $g->{&Orders}{$e} = 0; # Correct order of identity
150             }
151              
152             sub order($;$) # Order of an element
153 28     28 1 426 {my ($g, $a) = @_;
154 28         50 my $𝗲 = 𝗲($g);
155 28 100       104 return scalar keys %$𝗲 if @_ == 1; # Order of group
156 12         29 $g->{&Orders}{$a}; # Order of element
157             }
158              
159             sub elements($) # Elements in group
160 0     0 1 0 {my ($g) = @_;
161 0         0 my $𝗲 = 𝗲($g);
162 0         0 sort keys %$𝗲
163             }
164              
165             sub check($) # Check that it really is a group
166 3     3 0 4 {my ($g) = @_;
167 3         8 my $𝗲 = 𝗲($g); # Elements
168 3         9 for my $a(keys %$𝗲) # Check each operation
169 12         12 {my %row; my %col; # Check each element is unique in each row and in each column
170 12         21 for my $b(keys %$𝗲)
171 48         67 {my ($c, $𝗰) = ($𝗲->{$a}{$b}, $𝗲->{$b}{$a}); # Result of operation each way
172 48 50       85 confess "Missing product for $a * $b" unless defined $c;
173 48 50       67 confess "Missing product for $b * $a" unless defined $𝗰;
174 48 50       82 confess "Group not closed for $c == $a * $b" unless defined $𝗲->{$c};
175 48 50       77 confess "Group not closed for $𝗰 == $b * $a" unless defined $𝗲->{$𝗰};
176 48 50       88 if (defined(my $p = $row{$c})) # Check each product in a row is unique
177 0         0 {confess "Duplicate product $c == $a * $b and $a * $p"; # Helpfully provided duplicated product
178             }
179 48         57 $row{$c} = $b; # Record product as already present in this row
180 48 50       85 if (defined(my $p = $col{$𝗰})) # Check each product in a column is unique
181 0         0 {confess "Duplicate product $𝗰 == $b * $a and $b * $p"; # Helpfully provided duplicated product
182             }
183 48         87 $row{$c} = $b; # Record product as already present in this column
184             }
185             }
186 3         9 identity($g); # Check that the group has an identity
187 3         7 for my $a(keys %$𝗲) # Find the identity and confirm that there is only one
188 12 50       39 {confess "No inverse for: $a" unless defined inverse($g, $a); # Helpfully indicate element with no inverse
189             }
190 3         8 orders($g); # Order if each element
191 3         3 1 # It is a group
192             }
193              
194             sub Group(&@) # Create a group
195 3     3 1 1482 {my $sub = shift; # Operator, elements
196 3         6 my $g = bless {&Elements=>{}, &Inverses=>{}, &Orders=>{}}; # Empty group
197 3         7 for my $a(@_) # Create multiplication table
198 12         18 {for my $b(@_)
199 48         77 {$g->{&Elements}{$a}{$b} = &$sub($a, $b);
200             }
201             }
202 3         6 check($g); # Check we have a group
203 3         6 $g # Return results
204             }
205              
206             sub abelian($) # Abelian?
207 3     3 1 21 {my ($g) = @_; # Group
208 3         13 my $𝗲 = 𝗲($g); # Elements
209 3         9 for my $a(keys %$𝗲) # Check each operation
210 12         22 {for my $b(keys %$𝗲)
211 48 50       70 {return 0 unless $g->{&Elements}{$a}{$b} == $g->{&Elements}{$b}{$a};
212             }
213             }
214             1 # Abelian
215 3         11 }
216              
217             sub cyclic($) # Cyclic - return a generating element or undef if no such element
218 3     3 1 6 {my ($g) = @_; # Group
219 3         7 my $N = order($g);
220 3         5 while(my ($e, $o) = each %{$g->{&Orders}}) # Order of each element
  10         17  
221 9 100 100     46 {return $e if $o && $o == $N; # Return generating element
222             }
223             undef # Not cyclic
224 1         42 }
225              
226             sub subGroup($@) # Sub group
227 9     9 1 15 {my $g = shift; # Group followed by sub group elements excluding identity
228 9         26 my %g = map {$_=>1} @_, $g->e; # Add identity as that is always present in a sub group
  18         43  
229 9         19 for my $a(@_) # Check each product
230 9         14 {for my $b(@_)
231 9 100       16 {return 0 unless $g{$g->{&Elements}{$a}{$b}}; # Not a sub group unless product is within sub group
232             }
233             }
234             1 # Sub group
235 5         18 }
236              
237             sub homoMorphic($$@) # Homomorphism between two groups
238 61     61 1 83 {my $g = shift; # First group
239 61         65 my $𝗴 = shift; # Second group
240 61 50       187 ref($𝗴) eq __PACKAGE__ or confess "Second parameter must be a group too!"; # Check it is a group isomorphism
241 61         144 my %m = @_; # Mapping between groups
242 61         103 $m{e($g)} = e($𝗴); # Include identity to identity in mapping
243 61         114 my $e = 𝗲($g); # Elements in first group
244 61         107 my $𝗲 = 𝗲($𝗴); # Elements in second group
245 61         185 while(my ($a, $b) = each %m) # Check elements come from the correct groups
246 244 50       445 {confess "Not a group element of first group: $a" unless $e->{$a};
247 244 50       873 confess "Not a group element of second group: $b" unless $𝗲->{$b};
248             }
249 61         137 for my $a(keys %m) # Check each product
250 200         337 {for my $b(keys %m)
251 756 100       2056 {return 0 unless $m{$e->{$a}{$b}} eq $𝗲->{$m{$a}}{$m{$b}}; # Apply
252             }
253             }
254             1 # Homomorphic
255 43         252 }
256              
257             sub isoMorphic($$@) # Isomorphic
258 39     39 1 50 {my $g = shift; # First group
259 39         43 my $𝗴 = shift; # Second group
260 39 50       99 ref($𝗴) eq __PACKAGE__ or confess "Second parameter must be a group too!"; # Check it is a group isomorphism
261 39         96 my %m = @_; # Mapping between groups
262 39         109 my %𝗺 = reverse %m; # Mapping between groups
263 39 50       102 keys(%m) == keys(%𝗺) or confess "Please supply a bijective mapping!"; # Check that the mapping is bijective
264 39 100       106 $g->homoMorphic($𝗴, %m) && $𝗴->homoMorphic($g, %𝗺) # Bijective homomorphism is an isomorphism
265             }
266              
267             sub isoMorphisms(&$$) # Find all the isomorphisms between two groups
268 5     5 1 353 {my ($sub, $g, $𝗴) = @_; # Sub to call to process found isomorphisms, first group, second group
269 5 50       19 ref($𝗴) eq __PACKAGE__ or confess "Second parameter must be a group too!"; # Check it is a group
270 5 50       13 order($g) == order($𝗴) or confess "Groups have different orders!"; # Check groups have same order
271 5         11 my $i = e($g); # Identity of first group
272 5         13 my $𝗶 = e($𝗴); # Identity of second group
273 5         8 my $e = [grep {$_ ne $i} sort keys %{𝗲($g)}]; # Elements of first group in fixed order without identity
  20         53  
  5         13  
274 5         10 my $𝗲 = [grep {$_ ne $𝗶} sort keys %{𝗲($𝗴)}]; # Elements of second group in fixed order without identity
  20         40  
  5         11  
275             permute # Permute the elements to obtain all possible mappings
276 30     30   3263 {my %m = map {$$e[$_]=>$$𝗲[$_[$_]]} 0..$#_; # Mapping to test
  90         245  
277 30 100       95 &$sub(%m) if isoMorphic($g, $𝗴, %m); # Process mapping if isomorphic
278 5         43 } 0..$#$e; # Elements to permute
279             }
280              
281             sub autoMorphic($@) # Automorphic
282 5     5 1 7 {my $g = shift; # Group
283 5         12 $g->isoMorphic($g, @_) # Check
284             }
285              
286             sub autoMorphisms(&$) # Find all the automorphisms of a group
287 3     3 1 6 {my ($sub, $g) = @_; # Sub to call to process found automorphisms, group
288 3         7 &isoMorphisms($sub,$g,$g)
289             }
290              
291             # Export details
292              
293             require 5;
294             require Exporter;
295              
296 1     1   20 use vars qw(@ISA @EXPORT $VERSION);
  1         3  
  1         152  
297              
298             @ISA = qw(Exporter);
299             @EXPORT = qw(Group autoMorphisms isoMorphisms);
300              
301             our $VERSION = '1.002'; # Sunday 23 Aug 2015
302              
303             =head1 Description
304              
305             Find automorphisms of groups and isomorphisms between groups.
306              
307             A group automorphism is a bijection on the set of elements of a group which
308             preserves the group product.
309              
310             A group isomorphism is a bijection between the sets of elements of two groups
311             which preserves the group product.
312              
313             =head2 identity(group)
314              
315             Returns the identity element.
316              
317             =head2 inverse(group, element)
318              
319             Returns the inverse of an element.
320              
321             =head2 orders(group)
322              
323             Returns a hash which supplies the order of each element. The identity is
324             assigned an order of zero.
325              
326             =head2 order(group, element)
327              
328             Returns the order of an element with the group.
329              
330             =head2 elements(group)
331              
332             Returns a hash whose keys are the elements if the group. The value at each key
333             of this hash is another hash which gives the product in this group.
334              
335             =head2 Group sub elements...
336              
337             Creates a group with the specified elements as multiplied by C. The first
338             parameter is a subroutine that forms the product of each pair of elements drawn
339             from the following list of elements.
340              
341             =head2 abelian(group)
342              
343             Returns 1 if the group is Abelian, else 0.
344              
345             =head2 cyclic(group)
346              
347             If the group is cyclic, returns an element that generates the group, else
348             undef.
349              
350             =head2 subGroup(groups, elements...)
351              
352             Returns 1 if the elements specified plus the identity element form a sub group
353             of the group else 0.
354              
355             =head2 homoMorphic(group1, group2, mapping...)
356              
357             Returns 1 if mapping forms a homomorphism from group 1 to group 2, else 0.
358              
359             The mapping is a subset of the Cartesian product of the elements of
360             group 1 and the elements of group 2 flattened into a list. The pair:
361              
362             (identity of group 1, identity of group 2)
363              
364             is added for you so there is no need to specify it unless you wish to.
365              
366             =head2 isoMorphic(group1, group2, mapping...)
367              
368             Returns 1 if the mapping is an isomorphism from group 1 to group 2, else 0.
369              
370             The mapping is a subset of the Cartesian product of the elements of
371             group 1 and the elements of group 2 flattened into a list. The pair:
372              
373             (identity of group 1, identity of group 2)
374              
375             is added for you so there is no need to specify it unless you wish to.
376              
377             =head2 isoMorphisms sub group1, group 2
378              
379             Finds all the isomorphisms between two groups and calls C to process each
380             of them as they are discovered.
381              
382             The parameter list to sub is a pair for each element of group 1 indicating the
383             corresponding element of group 2 under the isomorphism.
384              
385             =head2 autoMorphic(group, mapping)
386              
387             Returns 1 if the mapping is an automorphism from the group to itself, else 0.
388              
389             The mapping is a subset of the Cartesian product of the elements of
390             the group squared flattened into a list. The pair:
391              
392             (identity of group, identity of group)
393              
394             is added for you so there is no need to specify it unless you wish to.
395              
396             =head2 autoMorphisms sub group
397              
398             Finds all the automorphisms of the groups and calls C to process each
399             of them as they are discovered.
400              
401             The parameter list to sub is a pair for each element of the group indicating the
402             corresponding element under the automorphism.
403              
404             =head1 Export
405              
406             The C, C, C functions are exported.
407              
408             =head1 Installation
409              
410             Standard Module::Build process for building and installing modules:
411              
412             perl Build.PL
413             ./Build
414             ./Build test
415             ./Build install
416              
417             Or, if you're on a platform (like DOS or Windows) that doesn't require
418             the "./" notation, you can do this:
419              
420             perl Build.PL
421             Build
422             Build test
423             Build install
424              
425             =head1 Author
426              
427             Philip R Brenan at gmail dot com
428              
429             http://www.appaapps.com
430              
431             =head1 See Also
432              
433             =over
434              
435             =item L
436              
437             =item L
438              
439             =back
440              
441             =head1 Copyright
442              
443             This module is free software. It may be used, redistributed and/or
444             modified under the same terms as Perl itself.
445              
446             =cut