home *** CD-ROM | disk | FTP | other *** search
Text File | 1993-05-05 | 301.7 KB | 7,012 lines |
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- %%
- %A aboutgap.tex GAP documentation Thomas Breuer
- %A & Frank Celler
- %A & Juergen Mnich
- %A & Goetz Pfeiffer
- %A & Martin Schoenert
- %%
- %A @(#)$Id: aboutgap.tex,v 3.49 1993/02/19 10:48:42 gap Exp $
- %%
- %Y Copyright 1990-1992, Lehrstuhl D fuer Mathematik, RWTH Aachen, Germany
- %%
- %% This file contains a tutorial introduction to the GAP system.
- %%
- %H $Log: aboutgap.tex,v $
- %H Revision 3.49 1993/02/19 10:48:42 gap
- %H adjustments in line length and spelling
- %H
- %H Revision 3.48 1993/02/18 13:51:52 felsch
- %H still another example fixed
- %H
- %H Revision 3.47 1993/02/18 08:19:33 felsch
- %H correction in tex
- %H
- %H Revision 3.46 1993/02/18 08:04:34 felsch
- %H another example fixed
- %H
- %H Revision 3.45 1993/02/17 15:13:27 felsch
- %H examples fixed
- %H
- %H Revision 3.44 1993/02/11 12:20:47 martin
- %H changed reference "Strings" to "Strings and Characters"
- %H
- %H Revision 3.43 1993/02/10 19:15:24 martin
- %H changed a couple of informations to information
- %H
- %H Revision 3.42 1992/08/07 14:36:02 sam
- %H added pictures for online help
- %H
- %H Revision 3.41 1992/07/03 12:30:43 sam
- %H little change in 'CharTableSplitClasses'
- %H
- %H Revision 3.40 1992/04/30 12:39:59 martin
- %H inserted a *not*
- %H
- %H Revision 3.39 1992/04/30 11:53:53 martin
- %H changed a few sentences to avoid bold non-roman fonts
- %H
- %H Revision 3.38 1992/04/06 11:10:46 martin
- %H fixed a few more typos
- %H
- %H Revision 3.37 1992/04/02 21:06:23 martin
- %H changed *domain functions* to *set theoretic functions*
- %H
- %H Revision 3.36 1992/04/02 18:03:40 martin
- %H added the banner
- %H
- %H Revision 3.35 1992/04/02 14:11:40 martin
- %H made the final corrections
- %H
- %H Revision 3.34 1992/04/02 10:46:31 martin
- %H reformatted sam's sections
- %H
- %H Revision 3.33 1992/04/01 13:12:27 martin
- %H added "About Group Libraries"
- %H
- %H Revision 3.32 1992/04/01 08:22:11 fceller
- %H fixed typos in "About Mappings and Homomoprhisms"
- %H
- %H Revision 3.31 1992/04/01 07:38:57 sam
- %H some more corrections in 'About Character Tables'
- %H
- %H Revision 3.30 1992/03/31 12:16:18 martin
- %H added new versions of "About Fields" and "About Matrix Groups"
- %H
- %H Revision 3.29 1992/03/31 10:32:57 martin
- %H renamed chapter to "About GAP" and fixed a few typos
- %H
- %H Revision 3.28 1992/03/31 10:04:29 martin
- %H added examples for 'InnerAutomorphism'
- %H
- %H Revision 3.27 1992/03/30 07:53:07 fceller
- %H changed "About Mappings and Homomorphisms"
- %H
- %H Revision 3.26 1992/03/26 15:18:05 martin
- %H changed 'SemiDirectProduct' to 'SemidirectProduct'
- %H
- %H Revision 3.25 1992/03/23 16:04:57 martin
- %H added "About Defining New Parametrized Domains"
- %H
- %H Revision 3.24 1992/03/23 13:58:42 martin
- %H fixed several typos in the first sections
- %H
- %H Revision 3.23 1992/03/18 18:44:00 martin
- %H added a new "About Domains and Categories"
- %H
- %H Revision 3.22 1992/03/13 17:54:28 sam
- %H some corrections
- %H
- %H Revision 3.21 1992/03/13 15:24:38 goetz
- %H slight improvements.
- %H
- %H Revision 3.20 1992/03/13 14:19:33 martin
- %H fixed some more typos
- %H
- %H Revision 3.19 1992/03/13 12:04:05 goetz
- %H split "About Ranges and Loops", renamed "About Operators" to
- %H "About Constants and Operators", introduced perms and strings.
- %H
- %H Revision 3.18 1992/03/13 11:37:51 martin
- %H fixed several typos
- %H
- %H Revision 3.17 1992/03/13 11:03:53 martin
- %H improved "About Operations of Groups"
- %H
- %H Revision 3.16 1992/03/12 17:14:50 sam
- %H improvements in 'About Groups'
- %H
- %H Revision 3.15 1992/03/12 16:40:20 martin
- %H fixed several typos in "About Operations of Groups"
- %H
- %H Revision 3.14 1992/03/12 16:18:51 martin
- %H added "About Defining New Domains"
- %H
- %H Revision 3.13 1992/03/12 11:01:37 martin
- %H extended 'ResidueClassOps' to allow cyclic group definitions
- %H
- %H Revision 3.12 1992/03/12 10:46:26 martin
- %H extended "About Operations of Groups" to mention normal forms
- %H
- %H Revision 3.11 1992/03/11 17:23:32 martin
- %H fixed several typos
- %H
- %H Revision 3.10 1992/03/11 16:05:04 sam
- %H renamed chapter "Number Fields" to "Subfields of Cyclotomic Fields"
- %H
- %H Revision 3.9 1992/03/11 12:40:52 martin
- %H fixed a few things in "About Operations of Groups"
- %H
- %H Revision 3.8 1992/03/10 20:45:15 martin
- %H added "About Operations of Groups"
- %H
- %H Revision 3.7 1992/03/10 18:30:27 goetz
- %H some remarks about 'List' and 'Filtered'.
- %H
- %H Revision 3.6 1992/03/10 11:55:51 sam
- %H some more corrections in 'About Character Tables'
- %H
- %H Revision 3.5 1992/03/09 13:12:44 martin
- %H added "About Domain" and "About Mappings and Homomorphisms"
- %H
- %H Revision 3.4 1992/03/09 10:06:17 sam
- %H corrections in 'About Character Tables'
- %H
- %H Revision 3.3 1992/03/04 12:28:02 goetz
- %H some corrections.
- %H
- %H Revision 3.2 1992/03/03 15:43:28 sam
- %H improvements in 'About Groups'
- %H
- %H Revision 3.1 1992/03/03 08:56:48 sam
- %H added Juergen's sections 'About Fields' and 'About Matrix Groups'
- %H
- %H Revision 3.0 1992/02/24 17:58:17 goetz
- %H initial revision.
- %H
- %%
- \Chapter{About GAP}
-
- This chapter introduces you to the {\GAP} system. It describes how to
- start {\GAP} (you may have to ask your system administrator to install it
- correctly) and how to leave it. Then a step by step introduction should
- give you an impression of how the {\GAP} system works. Further sections
- will give an overview about the features of {\GAP}. After reading this
- chapter the reader should know what kind of problems can be handled with
- {\GAP} and how they can be handled.
-
- There is some repetition in this chapter and much of the material is
- repeated in later chapters in a more compact and precise way. Yes, there
- are even some little inaccuracies in this chapter simplifying things for
- better understanding. It should be used as a tutorial introduction while
- later chapters form the reference manual.
-
- {\GAP} is an interactive system. It continuously executes a
- read--evaluate--print cycle. Each expression you type at the keyboard is
- read by {\GAP}, evaluated, and then the result is printed.
-
- The interactive nature of {\GAP} allows you to type an expression at the
- keyboard and see its value immediately. You can define a function and
- apply it to arguments to see how it works. You may even write whole
- programs containing lots of functions and test them without leaving the
- program.
-
- When your program is large it will be more convenient to write it on a
- file and then read that file into {\GAP}. Preparing your functions in a
- file has several advantages. You can compose your functions more
- carefully in a file (with your favorite text editor), you can correct
- errors without retyping the whole function and you can keep a copy for
- later use. Moreover you can write lots of comments into the program
- text, which are ignored by {\GAP}, but are very useful for human readers
- of your program text.
-
- {\GAP} treats input from a file in the same way that it treats input from
- the keyboard.
-
- The printed examples in this first chapter encourage you to try running
- {\GAP} on your computer. This will support your feeling for {\GAP} as a
- tool, which is the leading aim of this chapter. Do not believe any
- statement in this chapter so long as you cannot verify it for your own
- version of {\GAP}. You will learn to distinguish between small
- deviations of the behavior of your personal {\GAP} from the printed
- examples and serious nonsense.
-
- Since the printing routines of {\GAP} are in some sense machine dependent
- you will for instance encounter a different layout of the printed objects
- in different environments. But the contents should always be the same.
-
- In case you encounter serious nonsense it is highly recommended that you
- send a bug report to 'gap-forum@samson.math.rwth-aachen.de'.
-
- If you read this introduction on-line you should now enter ?> to read the
- next section.
-
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \Section{About Conventions}
-
- Throughout this manual both the input given to {\GAP} and the output that
- {\GAP} returns are printed in 'typewriter' font just as if they were
- typed at the keyboard.
-
- An <italic> font is used for keys that have no printed representation,
- such as e.g.\ the <newline> key and the <ctl> key. This font is also
- used for the formal parameters of functions that are described in later
- chapters.
-
- A combination like <ctl>-'P' means pressing both keys, that is holding
- the control key <ctl> and pressing the key 'P' while <ctl> is still
- pressed.
-
- New terms are introduced in *bold face*.
-
- In most places *whitespace* characters (i.e. <space>s, <tab>s and
- <newline>s) are insignificant for the meaning of {\GAP} input.
- Identifiers and keywords must however not contain any whitespace. On the
- other hand, sometimes there must be whitespace around identifiers and
- keywords to separate them from each other and from numbers. We will use
- whitespace to format more complicated commands for better readability.
-
- A *comment* in {\GAP} starts with the symbol '\#' and continues to the
- end of the line. Comments are treated like whitespace by {\GAP}.
-
- Besides of such comments which are part of the input of a {\GAP} session,
- we use additional comments which are part of the manual description, but
- not of the respective {\GAP} session. In the printed version of this
- manual these comments will be printed in a normal font for better
- readability, hence they start with the symbol \#.
-
- The example of {\GAP} sessions given in ths manual have been run with the
- line length set to 72 (by the 'SizeScreen' command). You should be able
- to reproduce the results except of a few lines of output which we have
- edited a little bit with respect to blanks or line breaks in order to
- improve the readability. Note, however, that for each particular section
- we have run the examples in one continuous session. Hence, as soon as
- random processes are involved, you may get differnt results if you
- extract single examples and run them separately.
-
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \Section{About Starting and Leaving GAP}
-
- If the program is correctly installed then you start {\GAP} by simply
- typing 'gap' at the prompt of your operating system followed by the
- <return> or the <newline> key.
-
- | $ gap|
-
- {\GAP} answers your request with its beautiful banner (which you can
- surpress with the command line option '-b') and then it shows its own
- prompt 'gap>' asking you for further input.
-
- | gap>|
-
- The usual way to end a {\GAP} session is to type 'quit;' at the 'gap>'
- prompt. Do not omit the semicolon!
-
- | gap> quit;
- $ |
-
- On some systems you may as well type <ctl>-'D' to yield the same effect.
- In any situation {\GAP} is ended by typing <ctl>-'C' twice within a
- second.
-
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \Section{About First Steps}
-
- A simple calculation with {\GAP} is as easy as one can imagine. You type
- the problem just after the prompt, terminate it with a semicolon and then
- pass the problem to the program with the <return> key. For example, to
- multiply the difference between 9 and 7 by the sum of 5 and 6, that is to
- calculate $(9 - 7) \* (5 + 6)$, you type exactly this last sequence of
- symbols followed by ';' and <return>.
-
- | gap> (9 - 7) * (5 + 6);
- 22
- gap> |
-
- Then {\GAP} echoes the result 22 on the next line and shows with the
- prompt that it is ready for the next problem.
-
- If you did omit the semicolon at the end of the line but have already
- typed <return>, then {\GAP} has read everything you typed, but does not
- know that the command is complete. The program is waiting for further
- input and indicates this with a partial prompt '>'. This little problem
- is solved by simply typing the missing semicolon on the next line of
- input. Then the result is printed and the normal prompt returns.
-
- | gap> (9 - 7) * (5 + 6)
- > ;
- 22
- gap> |
-
- Whenever you see this partial prompt and you cannot decide what {\GAP} is
- still waiting for, then you have to type semicolons until the normal
- prompt returns.
-
- In every situation this is the exact meaning of the prompt 'gap>' \:\ the
- program is waiting for a new problem. In the following examples we will
- omit this prompt on the line after the result. Considering each example
- as a continuation of its predecessor this prompt occurs in the next
- example.
-
- %% Summary %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- In this section you have seen how simple arithmetic problems can be
- solved by {\GAP} by simply typing them in. You have seen that it
- doesn\'t matter whether you complete your input on one line. {\GAP}
- reads your input line by line and starts evaluating if it has seen the
- terminating semicolon and <return>.
-
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \Section{About Syntax Errors}
-
- Even if you mistyped the command you do not have to type it all again as
- {\GAP} permits a lot of command line editing. Maybe you mistyped or
- forgot the last closing parenthesis. Then your command is syntactically
- incorrect and {\GAP} will notice it, incapable of computing the desired
- result.
-
- | gap> (9 - 7) * (5 + 6;
- Syntax error: ) expected
- (9 - 7) * (5 + 6;
- ^|
-
- Instead of the result an error message occurs indicating the place where
- an unexpected symbol occurred with an arrow sign '\^' under it. As a
- computer program cannot know what your intentions really were, this is
- only a hint. But in this case {\GAP} is right by claiming that there
- should be a closing parenthesis before the semicolon. Now you can type
- <ctl>-'P' to recover the last line of input. It will be written after
- the prompt with the cursor in the first position. Type <ctl>-'E' to take
- the cursor to the end of the line, then <ctl>-'B' to move the cursor one
- character back. The cursor is now on the position of the semicolon.
- Enter the missing parenthesis by simply typing ')'. Now the line is
- correct and may be passed to {\GAP} by hitting the <newline> key. Note
- that for this action it is not necessary to move the cursor past the last
- character of the input line.
-
- Each line of commands you type is sent to {\GAP} for evaluation by
- pressing <newline> regardless of the position of the cursor in that line.
- We will no longer mention the <newline> key from now on.
-
- Sometimes a syntax error will cause {\GAP} to enter a *break loop*. This
- is indicated by the special prompt 'brk>'. You can leave the break loop
- by either typing 'return;' or by hitting <ctl>-'D'. Then {\GAP} will
- return to its normal state and show its normal prompt again.
-
- %% Summary %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- In this section you learned that mistyped input will not lead to big
- confusion. If {\GAP} detects a syntax error it will print an error
- message and return to its normal state. The command line editing allows
- you in a comfortable way to manipulate earlier input lines.
-
- For the definition of the {\GAP} syntax see chapter "The Programming
- Language". A complete list of command line editing facilities is found
- in "Line Editing". The break loop is described in "Break Loops".
-
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \Section{About Constants and Operators}
-
- In an expression like |(9 - 7) * (5 + 6)| the constants 5, 6, 7, and 9
- are being composed by the operators |+|, |*| and |-| to result in a new
- value.
-
- There are three kinds of operators in {\GAP}, arithmetical operators,
- comparison operators, and logical operators. You have already seen that
- it is possible to form the sum, the difference, and the product of two
- integer values. There are some more operators applicable to integers in
- {\GAP}. Of course integers may be divided by each other, possibly
- resulting in noninteger rational values.
-
- | gap> 12345/25;
- 2469/5 |
-
- Note that the numerator and denominator are divided by their greatest
- common divisor and that the result is uniquely represented as a division
- instruction.
-
- We haven\'t met negative numbers yet. So consider the following
- self--explanatory examples.
-
- | gap> -3; 17 - 23;
- -3
- -6 |
-
- The exponentiation operator is written as '\^'. This operation in
- particular might lead to very large numbers. This is no problem for
- {\GAP} as it can handle numbers of (almost) arbitrary size.
-
- | gap> 3^132;
- 955004950796825236893190701774414011919935138974343129836853841 |
-
- The 'mod' operator allows you to compute one value modulo another.
-
- | gap> 17 mod 3;
- 2 |
-
- Note that there must be whitespace around the keyword 'mod' in this
- example since '17mod3' or '17mod' would be interpreted as identifiers.
-
- {\GAP} knows a precedence between operators that may be overridden by
- parentheses.
-
- | gap> (9 - 7) * 5 = 9 - 7 * 5;
- false |
-
- Besides these arithmetical operators there are comparison operators in
- {\GAP}. A comparison results in a *boolean value* which is another kind
- of constant. Every two objects within {\GAP} are comparable via |=|,
- |<>|, |<|, |<=|, |>| and |>=|, that is the tests for equality,
- inequality, less than, less than or equal, greater than and greater than
- or equal. There is an ordering defined on the set of all {\GAP} objects
- that respects orders on subsets that one might expect. For example the
- integers are ordered in the usual way.
-
- | gap> 10^5 < 10^4;
- false |
-
- The boolean values 'true' and 'false' can be manipulated via logical
- operators, i.~e., the unary operator 'not' and the binary operators 'and'
- and 'or'. Of course boolean values can be compared, too.
-
- | gap> not true; true and false; true or false;
- false
- false
- true
- gap> 10 > 0 and 10 < 100;
- true |
-
- Another important type of constants in {\GAP} are *permutations*. They
- are written in cycle notation and they can be multiplied.
-
- | gap> (1,2,3);
- (1,2,3)
- gap> (1,2,3) * (1,2);
- (2,3) |
-
- The inverse of the permutation '(1,2,3)' is denoted by |(1,2,3)^-1|.
- Moreover the caret operator |^| is used to determine the image of a point
- under a permutation and to conjugate one permutation by another.
-
- | gap> (1,2,3)^-1;
- (1,3,2)
- gap> 2^(1,2,3);
- 3
- gap> (1,2,3)^(1,2);
- (1,3,2) |
-
- The last type of constants we want to introduce here are the *strings*.
- In {\GAP} strings are just normal text enclosed in double quotes.
-
- | gap> "This is a string";
- "This is a string"
- gap> "Whitespace is relevant in strings.";
- "Whitespace is relevant in strings." |
-
- There are no operators defined for strings except that strings can be
- compared.
-
- %% Summary %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- In this section you have seen that values may be preceded by unary
- operators and combined by binary operators placed between the operands.
- There are rules for precedence which may be overridden by parentheses.
- It is possible to compare any two objects. A comparison results in a
- boolean value. Boolean values are combined via logical operators.
- Moreover you have seen that {\GAP} handles numbers of arbitrary size.
- Numbers and boolean values are constants. There are other types of
- constants in {\GAP} like permutations and strings. You are now in a
- position to use {\GAP} as a simple desktop calculator.
-
- Operators are explained in more detail in "Comparisons" and "Operations".
- Moreover there are sections about operators and comparisons for special
- types of objects in almost every chapter of this manual. You will find
- more information about boolean values in chapters "Booleans" and "Boolean
- Lists". Permutations are described in chapter "Permutations" and strings
- are described in chapter "Strings and Characters".
-
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \Section{About Variables and Assignments}
-
- Values may be assigned to variables. A variable enables you to refer to
- an object via a name. The name of a variable is called an *identifier*.
- The assignment operator is |:=|. There must be no white space between
- the |:| and the |=|. Do not confuse the assignment operator |:=| with
- the single equality sign |=| which is in {\GAP} only used for the test of
- equality.
-
- | gap> a:= (9 - 7) * (5 + 6);
- 22
- gap> a;
- 22
- gap> a * (a + 1);
- 506
- gap> a:= 10;
- 10
- gap> a * (a + 1);
- 110 |
-
- After an assignment the assigned value is echoed on the next line. The
- printing of the value of a statement may be in every case prevented by
- typing a double semicolon.
-
- | gap> w:= 2;; |
-
- After the assignment the variable evaluates to that value if evaluated.
- Thus it is possible to refer to that value by the name of the variable in
- any situation.
-
- This is in fact the whole secret of an assignment. An identifier is
- bound to a value and from this moment points to that value. Nothing
- more. This binding is changed by the next assignment to that identifier.
- An identifier does not denote a block of memory as in some other
- programming languages. It simply points to a value, which has been given
- its place in memory by the {\GAP} storage manager. This place may change
- during a {\GAP} session, but that doesn\'t bother the identifier.
-
- *The identifier points to the value, not to a place in the memory.*
-
- For the same it is reason not the identifier that has a type but the
- object. This means on the other hand that the identifier 'a' which now
- is bound to an integer value may in the same session point to any other
- value regardless of its type.
-
- Identifiers may be sequences of letters and digits containing at least
- one letter. For example 'abc' and 'a0bc1' are valid identifiers. But
- also '123a' is a valid identifier as it cannot be confused with any
- number. Just '1234' indicates the number 1234 and cannot be at the same
- time the name of a variable.
-
- Since {\GAP} distinguishes upper and lower case, 'a1' and 'A1' are
- different identifiers. Keywords such as 'quit' must not be used as
- identifiers. You will see more keywords in the following sections.
-
- In the remaining part of this manual we will ignore the difference
- between variables, their names (identifiers), and the values they point
- at. It may be useful to think from time to time about what is really
- meant by terms such as the integer 'w'.
-
- There are some predefined variables coming with {\GAP}. Many of them you
- will find in the remaining chapters of this manual, since functions are
- also referred to via identifiers.
-
- This seems to be the right place to state the following rule.
-
- The name of every function in the {\GAP} library starts with a *capital
- letter.*
-
- Thus if you choose only names starting with a small letter for your own
- variables you will not overwrite any predefined function.
-
- But there are some further interesting variables one of which shall be
- introduced now.
-
- Whenever {\GAP} returns a value by printing it on the next line this
- value is assigned to the variable 'last'. So if you computed
-
- | gap> (9 - 7) * (5 + 6);
- 22 |
-
- and forgot to assign the value to the variable 'a' for further use, you
- can still do it by the following assignment.
-
- | gap> a:= last;
- 22 |
-
- Moreover there are variables 'last2' and 'last3', guess their values.
-
- %% Summary %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- In this section you have seen how to assign values to variables. These
- values can later be accessed through the name of the variable, its
- identifier. You have also encountered the useful concept of the 'last'
- variables storing the latest returned values. And you have learned that
- a double semicolon prevents the result of a statement from being printed.
-
- Variables and assignments are described in more detail in "Variables" and
- "Assignments". A complete list of keywords is contained in "Keywords".
-
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \Section{About Functions}
-
- A program written in the {\GAP} language is called a *function*.
- Functions are special {\GAP} objects. Most of them behave like
- mathematical functions. They are applied to objects and will return a
- new object depending on the input. The function 'Factorial', for
- example, can be applied to an integer and will return the factorial of
- this integer.
-
- | gap> Factorial(17);
- 355687428096000 |
-
- Applying a function to arguments means to write the arguments in
- parentheses following the function. Several arguments are separated by
- commas, as for the function 'Gcd' which computes the greatest common
- divisor of two integers.
-
- | gap> Gcd(1234, 5678);
- 2 |
-
- There are other functions that do not return a value but only produce a
- side effect. They change for example one of their arguments. These
- functions are sometimes called procedures. The function 'Print' is only
- called for the side effect to print something on the screen.
-
- | gap> Print(1234, "\n");
- 1234 |
-
- In order to be able to compose arbitrary text with 'Print', this function
- itself will not produce a line break after printing. Thus we had another
- newline character |"\n"| printed to start a new line.
-
- Some functions will both change an argument and return a value such as
- the function 'Sortex' that sorts a list and returns the permutation of
- the list elements that it has performed.
-
- You will not understand right now what it means to change an object. We
- will return to this subject several times in the next sections.
-
- A comfortable way to define a function is given by the *maps--to*
- operator '->' consisting of a minus sign and a greater sign with no
- whitespace between them. The function 'cubed' which maps a number to its
- cube is defined on the following line.
-
- | gap> cubed:= x -> x^3;
- function ( x ) ... end |
-
- After the function has been defined, it can now be applied.
-
- | gap> cubed(5);
- 125 |
-
- Not every {\GAP} function can be defined in this way. You will see how
- to write your own {\GAP} functions in a later section.
-
- %% Summary %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- In this section you have seen {\GAP} objects of type function. You have
- learned how to apply a function to arguments. This yields as result a
- new object or a side effect. A side effect may change an argument of the
- function. Moreover you have seen an easy way to define a function in
- {\GAP} with the maps-to operator.
-
- Function calls are described in "Function Calls" and in "Procedure
- Calls". The functions of the {\GAP} library are described in detail in
- the remaining chapters of this manual, the Reference Manual.
-
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \Section{About Lists}
-
- A *list* is a collection of objects separated by commas and enclosed in
- brackets. Let us for example construct the list 'primes' of the first 10
- prime numbers.
-
- | gap> primes:= [2, 3, 5, 7, 11, 13, 17, 19, 23, 29];
- [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29 ] |
-
- The next two primes are 31 and 37. They may be appended to the existing
- list by the function 'Append' which takes the existing list as its first
- and another list as a second argument. The second argument is appended
- to the list 'primes' and no value is returned. Note that by appending
- another list the object 'primes' is changed.
-
- | gap> Append(primes, [31, 37]);
- gap> primes;
- [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37 ] |
-
- You can as well add single new elements to existing lists by the function
- 'Add' which takes the existing list as its first argument and a new
- element as its second argument. The new element is added to the list
- 'primes' and again no value is returned but the list 'primes' is changed.
-
- | gap> Add(primes, 41);
- gap> primes;
- [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41 ] |
-
- Single elements of a list are referred to by their position in the list.
- To get the value of the seventh prime, that is the seventh entry in our
- list 'primes', you simply type
-
- | gap> primes[7];
- 17 |
-
- and you will get the value of the seventh prime. This value can be
- handled like any other value, for example multiplied by 2 or assigned to
- a variable. On the other hand this mechanism allows to assign a value to
- a position in a list. So the next prime 43 may be inserted in the list
- directly after the last occupied position of 'primes'. This last
- occupied position is returned by the function 'Length'.
-
- | gap> Length(primes);
- 13
- gap> primes[14]:= 43;
- 43
- gap> primes;
- [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43 ] |
-
- Note that this operation again has changed the object 'primes'. Not only
- the next position of a list is capable of taking a new value. If you
- know that 71 is the 20th prime, you can as well enter it right now in the
- 20th position of 'primes'. This will result in a list with holes which
- is however still a list and has length 20 now.
-
- | gap> primes[20]:= 71;
- 71
- gap> primes;
- [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43,,,,,, 71 ]
- gap> Length(primes);
- 20 |
-
- The list itself however must exist before a value can be assigned to a
- position of the list. This list may be the empty list '[ ]'.
-
- | gap> lll[1]:= 2;
- Error, Variable: 'lll' must have a value
- gap> lll:= [];
- [ ]
- gap> lll[1]:= 2;
- 2 |
-
- Of course existing entries of a list can be changed by this mechanism,
- too. We will not do it here because 'primes' then may no longer be a
- list of primes. Try for yourself to change the 17 in the list into a 9.
-
- To get the position of 17 in the list 'primes' use the function
- 'Position' which takes the list as its first argument and the element as
- its second argument and returns the position of the first occurrence of
- the element 17 in the list 'primes'. 'Position' will return 'false' if
- the element is not contained in the list.
-
- | gap> Position(primes, 17);
- 7
- gap> Position(primes, 20);
- false |
-
- In all of the above changes to the list 'primes', the list has been
- automatically resized. There is no need for you to tell {\GAP} how big
- you want a list to be. This is all done dynamically.
-
- It is not necessary for the objects collected in a list to be of the same
- type.
-
- | gap> lll:= [true, "This is a String",,, 3];
- [ true, "This is a String",,, 3 ] |
-
- In the same way a list may be part of another list. A list may even be
- part of itself.
-
- | gap> lll[3]:= [4,5,6];; lll;
- [ true, "This is a String", [ 4, 5, 6 ],, 3 ]
- gap> lll[4]:= lll;
- [ true, "This is a String", [ 4, 5, 6 ], ~, 3 ] |
-
- Now the tilde |~| in the fourth position of 'lll' denotes the object that
- is currently printed. Note that the result of the last operation is the
- actual value of the object 'lll' on the right hand side of the
- assignment. But in fact it is identical to the value of the whole list
- 'lll' on the left hand side of the assignment.
-
- %% Summary %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- In this long section you have encountered the fundamental concept of a
- list. You have seen how to construct lists, how to extend them and how
- to refer to single elements of a list. Moreover you have seen that lists
- may contain elements of different types, even holes (unbound entries).
- But this is still not all we have to tell you about lists.
-
- You will find a discussion about identity and equality of lists in the
- next section. Moreover you will see special kinds of lists like sets (in
- "About Sets"), vectors and matrices (in "About Vectors and Matrices") and
- ranges (in "About Ranges").
-
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \Section{About Identical Lists}
-
- This second section about lists is dedicated to the subtle difference
- between equality and identity of lists. It is really important to
- understand this difference in order to understand how complex data
- structures are realized in {\GAP}. This section applies to all {\GAP}
- objects that have subobjects, i.~e., to lists and to records. After
- reading the section about records ("About Records") you should return to
- this section and translate it into the record context.
-
- Two lists are equal if all their entries are equal. This means that the
- equality operator '=' returns 'true' for the comparison of two lists if
- and only if these two lists are of the same length and for each position
- the values in the respective lists are equal.
-
- | gap> numbers:= primes;
- [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43,,,,,, 71 ]
- gap> numbers = primes;
- true |
-
- We assigned the list 'primes' to the variable 'numbers' and, of course
- they are equal as they have both the same length and the same entries.
- Now we will change the third number to 4 and compare the result again
- with 'primes'.
-
- | gap> numbers[3]:= 4;
- 4
- gap> numbers = primes;
- true |
-
- You see that 'numbers' and 'primes' are still equal, check this by
- printing the value of 'primes'. The list 'primes' is no longer a list of
- primes! What has happened? The truth is that the lists 'primes' and
- 'numbers' are not only equal but they are identical. 'primes' and
- 'numbers' are two variables pointing to the same list. If you change the
- value of the subobject 'numbers[3]' of 'numbers' this will also change
- 'primes'. Variables do *not* point to a certain block of storage memory
- but they do point to an object that occupies storage memory. So the
- assignment |numbers:= primes| did *not* create a new list in a different
- place of memory but only created the new name 'numbers' for the same old
- list of primes.
-
- *The same object can have several names.*
-
- If you want to change a list with the contents of 'primes' independently
- from 'primes' you will have to make a *copy* of 'primes' by the function
- 'Copy' which takes an object as its argument and returns a copy of the
- argument. (We will first restore the old value of 'primes'.)
-
- | gap> primes[3]:= 5;
- 5
- gap> primes;
- [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43,,,,,, 71 ]
- gap> numbers:= Copy(primes);
- [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43,,,,,, 71 ]
- gap> numbers = primes;
- true
- gap> numbers[3]:= 4;
- 4
- gap> numbers = primes;
- false |
-
- Now 'numbers' is no longer equal to 'primes' and 'primes' still is a list
- of primes. Check this by printing the values of 'numbers' and 'primes'.
-
- The only objects that can be changed this way are records and lists,
- because only {\GAP} objects of these types have subobjects. To clarify
- this statement consider the following example.
-
- | gap> i:= 1;; j:= i;; i:= i+1;; |
-
- By adding 1 to 'i' the value of 'i' has changed. What happens to 'j'?
- After the second statement 'j' points to the same object as 'i', namely
- to the integer 1. The addition does *not* change the object '1' but
- creates a new object according to the instruction 'i+1'. It is actually
- the assignment that changes the value of 'i'. Therefore 'j' still points
- to the object '1'. Integers (like strings and booleans) have no
- subobjects. Objects of these types cannot be changed but can only be
- replaced by other objects. And a replacement does not change the values
- of other variables. In the above example an assignment of a new value to
- the variable 'numbers' would also not change the value of 'primes'.
-
- Finally try the following examples and explain the results.
-
- | gap> l:= [];
- [ ]
- gap> l:= [l];
- [ [ ] ]
- gap> l[1]:= l;
- [ ~ ] |
-
- Now return to the preceding section "About Lists" and find out whether
- the functions 'Add' and 'Append' change their arguments.
-
- %% Summary %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- In this section you have seen the difference between equal lists and
- identical lists. Lists are objects that have subobjects and therefore
- can be changed. Changing an object will change the values of all
- variables that point to that object. Be careful, since one object can
- have several names. The function 'Copy' creates a copy of a list which
- is then a new object.
-
- You will find more about lists in chapter "Lists", and more about
- identical lists in "Identical Lists".
-
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \Section{About Sets}
-
- {\GAP} knows several special kinds of lists. A set in {\GAP} is a
- special kind of list. A set contains no holes and its elements are
- sorted according to the {\GAP} ordering of all its objects. Moreover a
- set contains no object twice.
-
- The function 'IsSet' tests whether an object is a set. It returns a
- boolean value. For any list there exists a corresponding set. This set
- is constructed by the function 'Set' which takes the list as its argument
- and returns a set obtained from this list by ignoring holes and
- duplicates and by sorting the elements.
-
- The elements of the sets used in the examples of this section are
- strings.
-
- | gap> fruits:= ["apple", "strawberry", "cherry", "plum"];
- [ "apple", "strawberry", "cherry", "plum" ]
- gap> IsSet(fruits);
- false
- gap> fruits:= Set(fruits);
- [ "apple", "cherry", "plum", "strawberry" ] |
-
- Note that the original list 'fruits' is not changed by the function
- 'Set'. We have to make a new assignment to the variable 'fruits' in
- order to make it a set.
-
- The 'in' operator is used to test whether an object is an element of a
- set. It returns a boolean value 'true' or 'false'.
-
- | gap> "apple" in fruits;
- true
- gap> "banana" in fruits;
- false |
-
- The 'in' operator may as well be applied to ordinary lists. It is
- however much faster to perform a membership test for sets since sets are
- always sorted and a binary search can be used instead of a linear search.
-
- New elements may be added to a set by the function 'AddSet' which takes
- the set 'fruits' as its first argument and an element as its second
- argument and adds the element to the set if it wasn\'t already there.
- Note that the object 'fruits' is changed.
-
- | gap> AddSet(fruits, "banana");
- gap> fruits; # The banana is inserted in the right place.
- [ "apple", "banana", "cherry", "plum", "strawberry" ]
- gap> AddSet(fruits, "apple");
- gap> fruits; # 'fruits' has not changed.
- [ "apple", "banana", "cherry", "plum", "strawberry" ] |
-
- Sets can be intersected by the function 'Intersection' and united by the
- function 'Union' which both take two sets as their arguments and return
- the intersection (union) of the two sets as a new object.
-
- | gap> breakfast:= ["tea", "apple", "egg"];
- [ "tea", "apple", "egg" ]
- gap> Intersection(breakfast, fruits);
- [ "apple" ] |
-
- It is however not necessary for the objects collected in a set to be of
- the same type. You may as well have additional integers and boolean
- values for 'breakfast'.
-
- The arguments of the functions 'Intersection' and 'Union' may as well be
- ordinary lists, while their result is always a set. Note that in the
- preceding example at least one argument of 'Intersection' was not a set.
-
- The functions 'IntersectSet' and 'UniteSet' also form the intersection
- resp.~union of two sets. They will however not return the result but
- change their first argument to be the result. Try them carefully.
-
- %% Summary %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- In this section you have seen that sets are a special kind of list.
- There are functions to expand sets, intersect or unite sets, and there is
- the membership test with the 'in' operator.
-
- A more detailed description of strings is contained in chapter "Strings
- and Characters". Sets are described in more detail in chapter "Sets".
-
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \Section{About Vectors and Matrices}
-
- A *vector* is a list of elements from a common field. A *matrix* is a
- list of vectors of equal length. Vectors and matrices are special kinds
- of lists without holes.
-
- | gap> v:= [3, 6, 2, 5/2];
- [ 3, 6, 2, 5/2 ]
- gap> IsVector(v);
- true |
-
- Vectors may be multiplied by scalars from their field. Multiplication of
- vectors of equal length results in their scalar product.
-
- | gap> 2 * v;
- [ 6, 12, 4, 5 ]
- gap> v * 1/3;
- [ 1, 2, 2/3, 5/6 ]
- gap> v * v;
- 221/4 # the scalar product of 'v' with itself |
-
- Note that the expression 'v \*\ 1/3' is actually evaluated by first
- multiplying 'v' by 1 (which yields again 'v') and by then dividing by 3.
- This is also an allowed scalar operation. The expression 'v/3' would
- result in the same value.
-
- A matrix is a list of vectors of equal length.
-
- | gap> m:= [[1, -1, 1],
- > [2, 0, -1],
- > [1, 1, 1]];
- [ [ 1, -1, 1 ], [ 2, 0, -1 ], [ 1, 1, 1 ] ]
- gap> m[2][1];
- 2 |
-
- Syntactically a matrix is a list of lists. So the number 2 in the second
- row and the first column of the matrix 'm' is referred to as the first
- element of the second element of the list 'm' via 'm[2][1]'.
-
- A matrix may be multiplied by scalars, vectors and other matrices. The
- vectors and matrices involved in such a multiplication must however have
- suitable dimensions.
-
- | gap> m:= [[1, 2, 3, 4],
- > [5, 6, 7, 8],
- > [9,10,11,12]];
- [ [ 1, 2, 3, 4 ], [ 5, 6, 7, 8 ], [ 9, 10, 11, 12 ] ]
- gap> PrintArray(m);
- [ [ 1, 2, 3, 4 ],
- [ 5, 6, 7, 8 ],
- [ 9, 10, 11, 12 ] ]
- gap> [1, 0, 0, 0] * m;
- Error, Vector *: vectors must have the same length
- gap> [1, 0, 0] * m;
- [ 1, 2, 3, 4 ]
- gap> m * [1, 0, 0];
- Error, Vector *: vectors must have the same length
- gap> m * [1, 0, 0, 0];
- [ 1, 5, 9 ]
- gap> m * [0, 1, 0, 0];
- [ 2, 6, 10 ] |
-
- Note that multiplication of a vector with a matrix will result in a
- linear combination of the rows of the matrix, while multiplication of a
- matrix with a vector results in a linear combination of the columns of
- the matrix. In the latter case the vector is considered as a column
- vector.
-
- %% Summary %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- In this section you have met vectors and matrices as special lists. You
- have seen how to refer to elements of a matrix and how to multiply
- scalars, vectors, and matrices.
-
- Fields are described in chapter "Fields". The known fields in {\GAP} are
- described in chapters "Rationals", "Cyclotomics", "Gaussians", "Subfields
- of Cyclotomic Fields" and "Finite Fields". Vectors and matrices are
- described in more detail in chapters "Vectors" and "Matrices". Vector
- spaces are described in chapter "Vector Spaces" and further matrix
- related structures are described in chapters "Matrix Rings" and "Matrix
- Groups".
-
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \Section{About Records}
-
- A record provides another way to build new data structures. Like a list
- a record is a collection of other objects. In a record the elements are
- not indexed by numbers but by names (i.e., identifiers). An entry in a
- record is called a *record component* (or sometimes also record field).
-
- | gap> date:= rec(year:= 1992,
- > month:= "Jan",
- > day:= 13);
- rec(
- year := 1992,
- month := "Jan",
- day := 13 ) |
-
- Initially a record is defined as a comma separated list of assignments to
- its record components. Then the value of a record component is
- accessible by the record name and the record component name separated by
- one dot as the record component selector.
-
- | gap> date.year;
- 1992
- gap> date.time:= rec(hour:= 19, minute:= 23, second:= 12);
- rec(
- hour := 19,
- minute := 23,
- second := 12 )
- gap> date;
- rec(
- year := 1992,
- month := "Jan",
- day := 13,
- time := rec(
- hour := 19,
- minute := 23,
- second := 12 ) ) |
-
- Assignments to new record components are possible in the same way. The
- record is automatically resized to hold the new component.
-
- Most of the complex structures that are handled by {\GAP} are represented
- as records, for instance groups and character tables.
-
- Records are objects that may be changed. An assignment to a record
- component changes the original object. There are many functions in the
- library that will do such assignments to a record component of one of
- their arguments. The function 'Size' for example, will compute the size
- of its argument which may be a group for instance, and then store the
- value in the record component 'size'. The next call of 'Size' for this
- object will use this stored value rather than compute it again.
-
- Lists and records are the *only* types of {\GAP} objects that can be
- changed.
-
- Sometimes it is interesting to know which components of a certain record
- are bound. This information is available from the function 'RecFields'
- (yes, this function should be called 'RecComponentNames'), which takes a
- record as its argument and returns a list of all bound components of this
- record as a list of strings.
-
- | gap> RecFields(date);
- [ "year", "month", "day", "time" ] |
-
- Finally try the following examples and explain the results.
-
- | gap> r:= rec();
- rec(
- )
- gap> r:= rec(r:= r);
- rec(
- r := rec(
- ) )
- gap> r.r:= r;
- rec(
- r := ~ ) |
-
- Now return to section "About Identical Lists" and find out what that
- section means for records.
-
- %% Summary %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- In this section you have seen how to define and how to use records.
- Record objects are changed by assignments to record fields. Lists and
- records are the only types of objects that can be changed.
-
- Records and functions for records are described in detail in chapter
- "Records". More about identical records is found in "Identical Records".
-
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \Section{About Ranges}
-
- A *range* is a finite ascending sequence of consecutive integers. This
- is another special kind of list. A range is described by its minimum and
- its maximum separated by two dots and enclosed in brackets.
-
- | gap> [1..999999]; # a range of almost a million numbers
- [ 1 .. 999999 ] |
-
- This compact printed representation of a fairly long list corresponds to
- a compact internal representation. The function 'IsRange' tests whether
- an object is a range. If this is true for a list but the list is not yet
- represented in the compact form of a range this will be done then.
-
- | gap> a:= [-2,-1,0,1,2,3,4,5];
- [ -2, -1, 0, 1, 2, 3, 4, 5 ]
- gap> IsRange(a);
- true
- gap> a;
- [ -2 .. 5 ]
- gap> a[5];
- 2
- gap> Length(a);
- 8 |
-
- Note that this change of representation does *not* change the value of
- the list 'a'. The list 'a' still behaves in any context in the same way
- as it would have in the long representation.
-
- %% Summary %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- In this section you have seen that ascending lists of consecutive
- integers can be represented in a compact way as ranges.
-
- Chapter "Ranges" contains a detailed description of ranges. A
- fundamental application of ranges is introduced in the next section.
-
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \Section{About Loops}
-
- Given a list 'pp' of permutations we can form their product by means of a
- 'for' loop instead of writing down the product explicitly.
-
- | gap> pp:= [ (1,3,2,6,8)(4,5,9), (1,6)(2,7,8)(4,9), (1,5,7)(2,3,8,6),
- > (1,8,9)(2,3,5,6,4), (1,9,8,6,3,4,7,2) ];;
- gap> prod:= ();
- ()
- gap> for p in pp do
- > prod:= prod * p;
- > od;
- gap> prod;
- (1,8,4,2,3,6,5) |
-
- First a new variable 'prod' is initialized to the identity permutation
- '()'. Then the loop variable 'p' takes as its value one permutation
- after the other from the list 'pp' and is multiplied with the present
- value of 'prod' resulting in a new value which is then assigned to
- 'prod'.
-
- The 'for' loop has the following syntax.
-
- 'for <var> in <list> do <statements> od;'
-
- The effect of the 'for' loop is to execute the <statements> for every
- element of the <list>. A 'for' loop is a statement and therefore
- terminated by a semicolon. The list of <statements> is enclosed by the
- keywords 'do' and 'od' (reverse 'do'). A 'for' loop returns no value.
- Therefore we had to ask explicitly for the value of 'prod' in the
- preceding example.
-
- The 'for' loop can loop over any kind of list, even a list with holes.
- In many programming languages (and in former versions of {\GAP}, too)
- the 'for' loop has the form
-
- 'for <var> from <first> to <last> do <statements> od;'
-
- But this is merely a special case of the general 'for' loop as defined
- above where the <list> in the loop body is a range.
-
- 'for <var> in [<first>..<last>] do <statements> od;'
-
- You can for instance loop over a range to compute the factorial $15!$ of
- the number 15 in the following way.
-
- | gap> ff:= 1;
- 1
- gap> for i in [1..15] do
- > ff:= ff * i;
- > od;
- gap> ff;
- 1307674368000 |
-
- The following example introduces the 'while' loop which has the following
- syntax.
-
- 'while <condition> do <statements> od;'
-
- The 'while' loop loops over the <statements> as long as the <condition>
- evaluates to 'true'. Like the 'for' loop the 'while' loop is terminated
- by the keyword 'od' followed by a semicolon.
-
- We can use our list 'primes' to perform a very simple factorization. We
- begin by initializing a list 'factors' to the empty list. In this list
- we want to collect the prime factors of the number 1333. Remember that a
- list has to exist before any values can be assigned to positions of the
- list. Then we will loop over the list 'primes' and test for each prime
- whether it divides the number. If it does we will divide the number by
- that prime, add it to the list 'factors' and continue.
-
- | gap> n:= 1333;
- 1333
- gap> factors:= [];
- [ ]
- gap> for p in primes do
- > while n mod p = 0 do
- > n:= n/p;
- > Add(factors, p);
- > od;
- > od;
- gap> factors;
- [ 31, 43 ]
- gap> n;
- 1 |
-
- As 'n' now has the value 1 all prime factors of 1333 have been found and
- 'factors' contains a complete factorization of 1333. This can of course
- be verified by multiplying 31 and 43.
-
- This loop may be applied to arbitrary numbers in order to find prime
- factors. But as 'primes' is not a complete list of all primes this loop
- may fail to find all prime factors of a number greater than 2000, say.
- You can try to improve it in such a way that new primes are added to the
- list 'primes' if needed.
-
- You have already seen that list objects may be changed. This holds of
- course also for the list in a loop body. In most cases you have to be
- careful not to change this list, but there are situations where this is
- quite useful. The following example shows a quick way to determine the
- primes smaller than 1000 by a sieve method. Here we will make use of the
- function 'Unbind' to delete entries from a list.
-
- | gap> primes:= [];
- [ ]
- gap> numbers:= [2..1000];
- [ 2 .. 1000 ]
- gap> for p in numbers do
- > Add(primes, p);
- > for n in numbers do
- > if n mod p = 0 then
- > Unbind(numbers[n-1]);
- > fi;
- > od;
- > od; |
-
- The inner loop removes all entries from 'numbers' that are divisible by
- the last detected prime 'p'. This is done by the function 'Unbind' which
- deletes the binding of the list position 'numbers[n-1]' to the value 'n'
- so that afterwards 'numbers[n-1]' no longer has an assigned value. The
- next element encountered in 'numbers' by the outer loop necessarily is
- the next prime.
-
- In a similar way it is possible to enlarge the list which is looped over.
- This yields a nice and short orbit algorithm for the action of a group,
- for example.
-
- %% Summary %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- In this section you have learned how to loop over a list by the 'for'
- loop and how to loop with respect to a logical condition with the 'while'
- loop. You have seen that even the list in the loop body can be changed.
-
- The 'for' loop is described in "For". The 'while' loop is described in
- "While".
-
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \Section{About Further List Operations}
-
- There is however a more comfortable way to compute the product of a list
- of numbers or permutations.
-
- | gap> Product([1..15]);
- 1307674368000
- gap> Product(pp);
- (1,8,4,2,3,6,5) |
-
- The function 'Product' takes a list as its argument and computes the
- product of the elements of the list. This is possible whenever a
- multiplication of the elements of the list is defined. So 'Product' is
- just an implementation of the loop in the example above as a function.
-
- There are other often used loops available as functions. Guess what the
- function 'Sum' does. The function 'List' may take a list and a function
- as its arguments. It will then apply the function to each element of the
- list and return the corresponding list of results. A list of cubes is
- produced as follows with the function 'cubed' from "About Functions".
-
- | gap> List([2..10], cubed);
- [ 8, 27, 64, 125, 216, 343, 512, 729, 1000 ] |
-
- To add all these cubes we might apply the function 'Sum' to the last
- list. But we may as well give the function 'cubed' to 'Sum' as an
- additional argument.
-
- | gap> Sum(last) = Sum([2..10], cubed);
- true |
-
- The primes less than 30 can be retrieved out of the list 'primes' from
- section "About Lists" by the function 'Filtered'. This function takes
- the list 'primes' and a property as its arguments and will return the
- list of those elements of 'primes' which have this property. Such a
- property will be represented by a function that returns a boolean value.
- In this example the property of being less than 30 can be reresented by
- the function 'x-> x \< 30' since 'x \< 30' will evaluate to 'true' for
- values 'x' less than 30 and to 'false' otherwise.
-
- | gap> Filtered(primes, x-> x < 30);
- [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29 ] |
-
- Another useful thing is the function 'Sublist'. It takes a list as its
- first and argument another list of positions as its second argument and
- will return the list of elements from the first list corresponding to the
- positions given in the second list.
-
- | gap> Sublist(primes, [1 .. 10]);
- [ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29 ] |
-
- %% Summary %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- In this section you have seen some functions which implement often used
- 'for' loops. There are functions like 'Product' to form the product of
- the elements of a list. The function 'List' can apply a function to all
- elements of a list and the functions 'Filtered' and 'Sublist' create
- sublists of a given list.
-
- You will find more predefined 'for' loops in chapter "Lists".
-
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \Section{About Writing Functions}
-
- You have already seen how to use the functions of the {\GAP} library,
- i.e., how to apply them to arguments. This section will show you how to
- write your own functions.
-
- Writing a function that prints 'hello, world.' on the screen is a simple
- exercise in {\GAP}.
-
- | gap> sayhello:= function()
- > Print("hello, world.\n");
- > end;
- function ( ) ... end |
-
- This function when called will only execute the 'Print' statement in the
- second line. This will print the string 'hello, world.' on the screen
- followed by a newline character |\n| that causes the {\GAP} prompt to
- appear on the next line rather than immediately following the printed
- characters.
-
- The function definition has the following syntax.
-
- 'function(<arguments>) <statements> end'
-
- A function definition starts with the keyword 'function' followed by the
- formal parameter list <arguments> enclosed in parenthesis. The formal
- parameter list may be empty as in the example. Several parameters are
- separated by commas. Note that there must be *no* semicolon behind the
- closing parenthesis. The function definition is terminated by the
- keyword 'end'.
-
- A {\GAP} function is an expression like integers, sums and lists. It
- therefore may be assigned to a variable. The terminating semicolon in
- the example does not belong to the function definition but terminates the
- assignment of the function to the name 'sayhello'. Unlike in the case of
- integers, sums, and lists the value of the function 'sayhello' is echoed
- in the abbreviated fashion 'function ( ) ... end'. This shows the most
- interesting part of a function\:\ its formal parameter list (which is
- empty in this example). The complete value of 'sayhello' is returned if
- you use the function 'Print'.
-
- | gap> Print(sayhello, "\n");
- function ( )
- Print( "hello, world.\n" );
- end |
-
- Note the additional newline character |"\n"| in the 'Print' statement.
- It is printed after the object 'sayhello' to start a new line.
-
- The newly defined function 'sayhello' is executed by calling 'sayhello()'
- with an empty argument list.
-
- | gap> sayhello();
- hello, world. |
-
- This is however not a typical example as no value is returned but only a
- string is printed.
-
- A more useful function is given in the following example. We define a
- function 'sign' which shall determine the sign of a number.
-
- | gap> sign:= function(n)
- > if n < 0 then
- > return -1;
- > elif n = 0 then
- > return 0;
- > else
- > return 1;
- > fi;
- > end;
- function ( n ) ... end
- gap> sign(0); sign(-99); sign(11);
- 0
- -1
- 1
- gap> sign("abc");
- 1 # strings are defined to be greater than 0 |
-
- This example also introduces the 'if' statement which is used to execute
- statements depending on a condition. The 'if' statement has the
- following syntax.
-
- 'if <condition> then <statements> elif <condition> then <statements> else
- <statements> fi;'
-
- There may be several 'elif' parts. The 'elif' part as well as the 'else'
- part of the 'if' statement may be omitted. An 'if' statement is no
- expression and can therefore not be assigned to a variable. Furthermore
- an 'if' statement does not return a value.
-
- Fibonacci numbers are defined recursively by $f(1) = f(2) = 1$ and $f(n)
- = f(n-1) + f(n-2)$. Since functions in {\GAP} may call themselves, a
- function 'fib' that computes Fibonacci numbers can be implemented
- basically by typing the above equations.
-
- | gap> fib:= function(n)
- > if n in [1, 2] then
- > return 1;
- > else
- > return fib(n-1) + fib(n-2);
- > fi;
- > end;
- function ( n ) ... end
- gap> fib(15);
- 610 |
-
- There should be additional tests for the argument 'n' being a positive
- integer. This function 'fib' might lead to strange results if called
- with other arguments. Try to insert the tests in this example.
-
- A function 'gcd' that computes the greatest common divisor of two
- integers by Euclid\'s algorithm will need a variable in addition to the
- formal arguments.
-
- | gap> gcd:= function(a, b)
- > local c;
- > while b <> 0 do
- > c:= b;
- > b:= a mod b;
- > a:= c;
- > od;
- > return c;
- > end;
- function ( a, b ) ... end
- gap> gcd(30, 63);
- 3 |
-
- The additional variable 'c' is declared as a *local* variable in the
- 'local' statement of the function definition. The 'local' statement, if
- present, must be the first statement of a function definition. When
- several local variables are declared in only one 'local' statement they
- are separated by commas.
-
- The variable 'c' is indeed a local variable, that is local to the
- function 'gcd'. If you try to use the value of 'c' in the main loop you
- will see that 'c' has no assigned value unless you have already assigned
- a value to the variable 'c' in the main loop. In this case the local
- nature of 'c' in the function 'gcd' prevents the value of the 'c' in the
- main loop from being overwritten.
-
- We say that in a given scope an identifier identifies a unique variable.
- A *scope* is a lexical part of a program text. There is the global scope
- that encloses the entire program text, and there are local scopes that
- range from the 'function' keyword, denoting the beginning of a function
- definition, to the corresponding 'end' keyword. A local scope introduces
- new variables, whose identifiers are given in the formal argument list
- and the local declaration of the function. The usage of an identifier in
- a program text refers to the variable in the innermost scope that has
- this identifier as its name.
-
- We will now write a function to determine the number of partitions of a
- positive integer. A partition of a positive integer is a descending list
- of numbers whose sum is the given integer. For example $[4,2,1,1]$ is a
- partition of 8. The complete set of all partitions of an integer $n$ may
- be divided into subsets with respect to the largest element. The number
- of partitions of $n$ therefore equals the sum of the numbers of
- partitions of $n-i$ with elements less than $i$ for all possible $i$.
- More generally the number of partitions of $n$ with elements less than
- $m$ is the sum of the numbers of partitions of $n-i$ with elements less
- than $i$ for $i$ less than $m$ and $n$. This description yields the
- following function.
-
- | gap> nrparts:= function(n)
- > local np;
- > np:= function(n, m)
- > local i, res;
- > if n = 0 then
- > return 1;
- > fi;
- > res:= 0;
- > for i in [1..Minimum(n,m)] do
- > res:= res + np(n-i, i);
- > od;
- > return res;
- > end;
- > return np(n,n);
- > end;
- function ( n ) ... end |
-
- We wanted to write a function that takes one argument. We solved the
- problem of determining the number of partitions in terms of a recursive
- procedure with two arguments. So we had to write in fact two functions.
- The function 'nrparts' that can be used to compute the number of
- partitions takes indeed only one argument. The function 'np' takes two
- arguments and solves the problem in the indicated way. The only task of
- the function 'nrparts' is to call 'np' with two equal arguments.
-
- We made 'np' local to 'nrparts'. This illustrates the possibility of
- having local functions in {\GAP}. It is however not necessary to put it
- there. 'np' could as well be defined on the main level. But then the
- identifier 'np' would be bound and could not be used for other purposes.
- And if it were used the essential function 'np' would no longer be
- available for 'nrparts'.
-
- Now have a look at the function 'np'. It has two local variables 'res'
- and 'i'. The variable 'res' is used to collect the sum and 'i' is a loop
- variable. In the loop the function 'np' calls itself again with other
- arguments. It would be very disturbing if this call of 'np' would use
- the same 'i' and 'res' as the calling 'np'. Since the new call of 'np'
- creates a new scope with new variables this is fortunately not the case.
-
- The formal parameters $n$ and $m$ are treated like local variables.
-
- It is however cheaper (in terms of computing time) to avoid such a
- recursive solution if this is possible (and it is possible in this case),
- because a function call is not very cheap.
-
- %% Summary %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- In this section you have seen how to write functions in the {\GAP}
- language. You have also seen how to use the 'if' statement. Functions
- may have local variables which are declared in an initial 'local'
- statement in the function definition. Functions may call themselves.
-
- The function syntax is described in "Functions". The 'if' statement is
- described in more detail in "If". More about Fibonacci numbers is found
- in "Fibonacci" and more about partitions in "Partitions".
-
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \Section{About Groups}
-
- In this section we will show some easy computations with groups. The
- example uses permutation groups, but this is visible for the user only
- because the output contains permutations. The functions, like 'Group',
- 'Size' or 'SylowSubgroup' (for detailed information, see chapters
- "Domains", "Groups"), are the same for all kinds of groups, although the
- algorithms which compute the information of course will be different in
- most cases.
-
- It is not even necessary to know more about permutations than the two
- facts that they are elements of permutation groups and that they are
- written in disjoint cycle notation (see chapter "Permutations"). So
- let\'s construct a permutation group\:
-
- | gap> s8:= Group( (1,2), (1,2,3,4,5,6,7,8) );
- Group( (1,2), (1,2,3,4,5,6,7,8) )|
-
- We formed the group generated by the permutations '(1,2)' and
- '(1,2,3,4,5,6,7,8)', which is well known as the symmetric group on eight
- points, and assigned it to the identifier 's8'. 's8' contains the
- alternating group on eight points which can be described in several ways,
- e.g., as group of all even permutations in 's8', or as its commutator
- subgroup.
-
- | gap> a8:= CommutatorSubgroup( s8, s8 );
- Subgroup( Group( (1,2), (1,2,3,4,5,6,7,8) ),
- [ (1,3,2), (2,4,3), (3,5,4), (4,6,5), (5,7,6), (6,8,7) ] )|
-
- The alternating group 'a8' is printed as instruction to compute that
- subgroup of the group 's8' that is generated by the given six
- permutations. This representation is much shorter than the internal
- structure, and it is completely self--explanatory; one could, for
- example, print such a group to a file and read it into {\GAP} later. But
- if one object occurs several times it is useful to refer to this object;
- this can be settled by assigning a name to the group.
-
- | gap> s8.name:= "s8";
- "s8"
- gap> a8;
- Subgroup( s8, [ (1,3,2), (2,4,3), (3,5,4), (4,6,5), (5,7,6), (6,8,7)
- ] )
- gap> a8.name:= "a8";
- "a8"
- gap> a8;
- a8|
-
- Whenever a group has a component 'name', {\GAP} prints this name instead
- of the group itself. Note that there is no link between the name and the
- identifier, but it is of course useful to choose name and identifier
- compatible.
-
- | gap> copya8:= Copy( a8 );
- a8|
-
- We examine the group 'a8'. Like all complex {\GAP} structures, it is
- represented as a record (see "Group Records").
-
- | gap> RecFields( a8 );
- [ "isDomain", "isGroup", "parent", "identity", "generators",
- "operations", "isPermGroup", "1", "2", "3", "4", "5", "orbit",
- "transversal", "stabilizer", "name" ]|
-
- Many functions store information about the group in this group record,
- this avoids duplicate computations. But we are not interested in the
- organisation of data but in the group, e.g., some of its properties (see
- chapter "Groups", especially "Properties and Property Tests")\:
-
- | gap> Size( a8 ); IsAbelian( a8 ); IsPerfect( a8 );
- 20160
- false
- true|
-
- Some interesting subgroups are the Sylow $p$ subgroups for prime divisors
- $p$ of the group order; a call of 'SylowSubgroup' stores the required
- subgroup in the group record\:
-
- | gap> Set( Factors( Size( a8 ) ) );
- [ 2, 3, 5, 7 ]
- gap> for p in last do
- > SylowSubgroup( a8, p );
- > od;
- gap> a8.sylowSubgroups;
- [ , Subgroup( s8, [ (1,7)(5,6), (2,3)(5,6), (1,7)(4,8), (2,5)(3,6),
- (1,4)(2,5)(3,6)(7,8), (1,2)(3,7)(4,5)(6,8) ] ),
- Subgroup( s8, [ (3,6,4), (2,5,8)(3,4,6) ] ),,
- Subgroup( s8, [ (3,4,8,6,5) ] ),,
- Subgroup( s8, [ (2,6,3,8,4,7,5) ] ) ]|
-
- The record component 'sylowSubgroups' is a list which stores at the
- $p$--th position, if bound, the Sylow $p$ subgroup; in this example this
- means that there are holes at positions 1, 4 and 6. Note that a call of
- 'SylowSubgroup' for the cyclic group of order 65521 and for the prime
- 65521 would cause {\GAP} to store the group at the end of a list of
- length 65521, so there are special situations where it is possible to
- bring {\GAP} and yourselves into troubles.
-
- We now can investigate the Sylow 2 subgroup.
-
- | gap> syl2:= last[2];;
- gap> Size( syl2 );
- 64
- gap> Normalizer( a8, syl2 );
- Subgroup( s8, [ (1,7)(5,6), (2,3)(5,6), (1,7)(4,8), (2,5)(3,6),
- (1,4)(2,5)(3,6)(7,8), (1,2)(3,7)(4,5)(6,8), (4,8)(5,6) ] )
- gap> last = syl2;
- true
- gap> Centre( syl2 );
- Subgroup( s8, [ ( 1, 7)( 2, 3)( 4, 8)( 5, 6) ] )
- gap> cent:= Centralizer( a8, last );
- Subgroup( s8, [ ( 1, 7)( 2, 3)( 4, 8)( 5, 6), (4,5)(6,8), (4,6)(5,8),
- (2,3)(5,6), (2,4)(3,8), (1,2)(3,7) ] )
- gap> Size( cent );
- 192
- gap> DerivedSeries( cent );
- [ Subgroup( s8, [ ( 1, 7)( 2, 3)( 4, 8)( 5, 6), (4,5)(6,8),
- (4,6)(5,8), (2,3)(5,6), (2,4)(3,8), (1,2)(3,7) ] ),
- Subgroup( s8, [ (4,8)(5,6), (2,4,5)(3,8,6), (1,7)(2,3),
- (1,2,4)(3,8,7) ] ),
- Subgroup( s8, [ (2,3)(4,8), (1,7)(4,8), (1,5)(2,4)(3,8)(6,7),
- (1,2)(3,7)(4,5)(6,8) ] ),
- Subgroup( s8, [ (1,7)(2,3)(4,8)(5,6) ] ), Subgroup( s8, [ ] ) ]
- gap> List( last, Size );
- [ 192, 96, 32, 2, 1 ]
- gap> low:= LowerCentralSeries( cent );
- [ Subgroup( s8, [ ( 1, 7)( 2, 3)( 4, 8)( 5, 6), (4,5)(6,8),
- (4,6)(5,8), (2,3)(5,6), (2,4)(3,8), (1,2)(3,7) ] ),
- Subgroup( s8, [ (4,8)(5,6), (2,4,5)(3,8,6), (1,7)(2,3),
- (1,2,4)(3,8,7) ] ) ]|
-
- Another kind of subgroups is given by the point stabilizers.
-
- | gap> stab:= Stabilizer( a8, 1 );
- Subgroup( s8, [ (2,7,3), (2,4,3), (3,8,4), (4,6,8), (5,6,8) ] )
- gap> Size( stab );
- 2520
- gap> Index( a8, stab );
- 8|
-
- We can fetch an arbitrary group element and look at its centralizer in
- 'a8', and then get other subgroups by conjugation and intersection of
- already known subgroups. Note that we form the subgroups inside 'a8',
- but {\GAP} regards these groups as subgroups of 's8' because this is the
- common ``parent\'\'\ group of all these groups and of 'a8' (for the idea
- of parent groups, see "More about Groups and Subgroups").
-
- | gap> Random( a8 );
- (1,4,3,8,2,5,7)
- gap> Random( a8 );
- (1,5,7,6,2,4)(3,8)
- gap> cent:= Centralizer( a8, (1,2)(3,4)(5,8)(6,7) );
- Subgroup( s8, [ (1,2)(3,4)(5,8)(6,7), (5,6)(7,8), (5,7)(6,8),
- (3,4)(6,7), (3,5)(4,8), (1,3)(2,4) ] )
- gap> Size( cent );
- 192
- gap> conj:= ConjugateSubgroup( cent, (2,3,4) );
- Subgroup( s8, [ (1,3)(2,4)(5,8)(6,7), (5,6)(7,8), (5,7)(6,8),
- (2,4)(6,7), (2,8)(4,5), (1,4)(2,3) ] )
- gap> inter:= Intersection( cent, conj );
- Subgroup( s8, [ (5,6)(7,8), (5,7)(6,8), (1,2)(3,4), (1,3)(2,4) ] )
- gap> Size( inter );
- 16
- gap> IsElementaryAbelian( inter );
- true
- gap> norm:= Normalizer( a8, inter );
- Subgroup( s8, [ (5,6)(7,8), (5,7)(6,8), (1,2)(3,4), (1,3)(2,4),
- (6,7,8), (3,4)(7,8), (2,3)(7,8), (1,5)(2,6)(3,7)(4,8) ] )
- gap> Size( norm );
- 576|
-
- Suppose we do not only look which funny things may appear in our group
- but want to construct a subgroup, e.g., a group of structure
- $2^3\:L_3(2)$ in 'a8'. One idea is to look for an appropriate $2^3$
- which is specified by the fact that all its involutions are fixed point
- free, and then compute its normalizer in 'a8'\:
-
- | gap> elab:= Group( (1,2)(3,4)(5,6)(7,8), (1,3)(2,4)(5,7)(6,8),
- > (1,5)(2,6)(3,7)(4,8) );;
- gap> Size( elab );
- 8
- gap> IsElementaryAbelian( elab );
- true
- gap> norm:= Normalizer( a8, AsSubgroup( s8, elab ) );
- Subgroup( s8, [ (1,2)(3,4)(5,6)(7,8), (1,3)(2,4)(5,7)(6,8),
- (1,5)(2,6)(3,7)(4,8), (5,6)(7,8), (5,7)(6,8), (3,4)(7,8),
- (3,5)(4,6), (2,3)(6,7) ] )
- gap> Size( norm );
- 1344|
-
- Note that 'elab' was defined as separate group, thus we had to call
- 'AsSubgroup' to achieve that it has the same parent group as 'a8'.
- Let\'s look at some usual misuses\:
-
- | gap> Normalizer( a8, elab );
- Error, <G> and <H> must have the same parent group in
- arg[1].operations.Parent( arg ) called from
- Parent( G, U ) called from
- Normalizer( a8, elab ) called from
- main loop
- brk> quit;|
-
- Intuitively, it is clear that we wanted to compute the normalizer of
- 'elab' in 'a8'. But {\GAP} expects the subgroup to be explicitly given
- as subgroup; for that, we must apply 'AsSubgroup'.
-
- | gap> IsSubgroup( a8, AsSubgroup( a8, elab ) );
- Error, <G> must be a parent group in
- AsSubgroup( a8, elab ) called from
- main loop
- brk> quit;
- gap> IsSubgroup( a8, AsSubgroup( s8, elab ) );
- true|
-
- What we tried next was better but not good enough. Since all our
- computations up to now are done inside 's8' which is the parent of 'a8',
- it is easy to understand that 'IsSubgroup' works for two subgroups with
- this parent.
-
- By the way, you should not try the operator '\<' instead of the function
- 'IsSubgroup'. Something like
-
- | gap> elab < a8;
- false|
-
- or
-
- | gap> AsSubgroup( s8, elab ) < a8;
- false|
-
- will not cause an error, but the result does not tell anything about the
- inclusion of one group in another; '\<' looks at the element lists for
- the two domains which means that it computes them if they are not already
- stored --which is not desirable to do for large groups-- and then simply
- compares the lists with respect to lexicographical order (see
- "Comparisons of Domains").
-
- On the other hand, the equality operator '=' in fact does test the
- equality of groups. Thus
-
- | gap> elab = AsSubgroup( s8, elab );
- true|
-
- means that the two groups are equal in the sense that they have the same
- elements. Note that they may behave differently since they have
- different parent groups. In our example, it is necessary to work with
- subgroups of 's8'\:
-
- | gap> elab:= AsSubgroup( s8, elab );;
- gap> elab.name:= "elab";;|
-
- If we are given the subgroup 'norm' of order 1344 and its subgroup
- 'elab', the factor group can be considered.
-
- | gap> f:= norm / elab;
- (Subgroup( s8, [ (1,2)(3,4)(5,6)(7,8), (1,3)(2,4)(5,7)(6,8),
- (1,5)(2,6)(3,7)(4,8), (5,6)(7,8), (5,7)(6,8), (3,4)(7,8),
- (3,5)(4,6), (2,3)(6,7) ] ) / elab)
- gap> Size( f );
- 168|
-
- As the output shows, this is not a permutation group. The factor group
- and its elements can, however, be handled in the usual way.
-
- | gap> Random( f );
- FactorGroupElement( elab, (2,8,7)(4,6,5) )
- gap> Order( f, last );
- 3|
-
- The natural link between the group 'norm' and its factor group 'f' is the
- natural homomorphism onto 'f', mapping each element of 'norm' to its
- coset modulo the kernel 'elab'. In {\GAP} you can construct the
- homomorphism, but note that the images lie in 'f' since they are elements
- of the factor group, but the preimage of each such element is only a
- coset, not a group element (for cosets, see the relevant sections in
- chapter "Groups", for homomorphisms see chapters "Operations of Groups"
- and "Mappings").
-
- | gap> f.name:= "f";;
- gap> hom:= NaturalHomomorphism( norm, f );
- NaturalHomomorphism( Subgroup( s8,
- [ (1,2)(3,4)(5,6)(7,8), (1,3)(2,4)(5,7)(6,8), (1,5)(2,6)(3,7)(4,8),
- (5,6)(7,8), (5,7)(6,8), (3,4)(7,8), (3,5)(4,6), (2,3)(6,7)
- ] ), (Subgroup( s8, [ (1,2)(3,4)(5,6)(7,8), (1,3)(2,4)(5,7)(6,8),
- (1,5)(2,6)(3,7)(4,8), (5,6)(7,8), (5,7)(6,8), (3,4)(7,8),
- (3,5)(4,6), (2,3)(6,7) ] ) / elab) )
- gap> Kernel( hom ) = elab;
- true
- gap> x:= Random( norm );
- (1,5,6,2)(3,4,8,7)
- gap> Image( hom, x );
- FactorGroupElement( elab, (2,5)(3,8) )
- gap> coset:= PreImages( hom, last );
- (elab*(2,5)(3,8))
- gap> IsCoset( coset );
- true
- gap> x in coset;
- true
- gap> coset in f;
- false|
-
- The group 'f' acts on its elements (*not* on the cosets) via right
- multiplication, yielding the regular permutation representation of 'f'
- and thus a new permutation group, namely the linear group $L_3(2)$. A
- more elaborate discussion of operations of groups can be found in section
- "About Operations of Groups" and chapter "Operations of Groups".
-
- | gap> op:= Operation( f, Elements( f ), OnRight );;
- gap> IsPermGroup( op );
- true
- gap> Maximum( List( op.generators, LargestMovedPointPerm ) );
- 168
- gap> IsSimple( op );
- true|
-
- 'norm' acts on the seven nontrivial elements of its normal subgroup
- 'elab' by conjugation, yielding a representation of $L_3(2)$ on seven
- points. We embed this permutation group in 'norm' and deduce that 'norm'
- is a split extension of an elementary abelian group $2^3$ with $L_3(2)$.
-
- | gap> op:= Operation( norm, Elements( elab ), OnPoints );
- Group( (5,6)(7,8), (5,7)(6,8), (3,4)(7,8), (3,5)(4,6), (2,3)(6,7) )
- gap> IsSubgroup( a8, AsSubgroup( s8, op ) );
- true
- gap> IsSubgroup( norm, AsSubgroup( s8, op ) );
- true
- gap> Intersection( elab, op );
- Group( () )|
-
- Yet another kind of information about our 'a8' concerns its conjugacy
- classes.
-
- | gap> ccl:= ConjugacyClasses( a8 );
- [ ConjugacyClass( a8, () ), ConjugacyClass( a8, (6,7,8) ),
- ConjugacyClass( a8, (5,6)(7,8) ), ConjugacyClass( a8, (4,5,6,7,8) ),
- ConjugacyClass( a8, (3,4)(5,6,7,8) ),
- ConjugacyClass( a8, (3,4,5)(6,7,8) ),
- ConjugacyClass( a8, (2,3)(4,5)(6,7,8) ),
- ConjugacyClass( a8, (2,3,4,5,6,7,8) ),
- ConjugacyClass( a8, (2,3,4,5,6,8,7) ),
- ConjugacyClass( a8, (1,2)(3,4)(5,6)(7,8) ),
- ConjugacyClass( a8, (1,2)(3,4,5,6,7,8) ),
- ConjugacyClass( a8, (1,2,3)(4,5,6,7,8) ),
- ConjugacyClass( a8, (1,2,3)(4,5,6,8,7) ),
- ConjugacyClass( a8, (1,2,3,4)(5,6,7,8) ) ]
- gap> Length( ccl );
- 14
- gap> reps:= List( ccl, Representative );
- [ (), (6,7,8), (5,6)(7,8), (4,5,6,7,8), (3,4)(5,6,7,8),
- (3,4,5)(6,7,8), (2,3)(4,5)(6,7,8), (2,3,4,5,6,7,8), (2,3,4,5,6,8,7),
- (1,2)(3,4)(5,6)(7,8), (1,2)(3,4,5,6,7,8), (1,2,3)(4,5,6,7,8),
- (1,2,3)(4,5,6,8,7), (1,2,3,4)(5,6,7,8) ]
- gap> List( reps, r -> Order( a8, r ) );
- [ 1, 3, 2, 5, 4, 3, 6, 7, 7, 2, 6, 15, 15, 4 ]
- gap> List( ccl, Size );
- [ 1, 112, 210, 1344, 2520, 1120, 1680, 2880, 2880, 105, 3360, 1344,
- 1344, 1260 ]|
-
- Note the difference between 'Order' (which means the element order),
- 'Size' (which means the size of the conjugacy class) and 'Length' (which
- means the length of a list).
-
- Having the conjugacy classes, we can consider class functions, i.e., maps
- that are defined on the group elements, and that are constant on each
- conjugacy class. One nice example is the number of fixed points; here we
- use that permutations act on points via '\^'.
-
- | gap> nrfixedpoints:= function( perm, support )
- > return Number( [ 1 .. support ], x -> x^perm = x );
- > end;
- function ( perm, support ) ... end|
-
- Note that we must specify the support since a permutation does not know
- about the group it is an element of; e.g. the trivial permutation '()'
- has as many fixed points as the support denotes.
-
- | gap> permchar1:= List( reps, x -> nrfixedpoints( x, 8 ) );
- [ 8, 5, 4, 3, 2, 2, 1, 1, 1, 0, 0, 0, 0, 0 ]|
-
- This is the character of the natural permutation representation of 'a8'
- (More about characters can be found in chapters "Character Tables" ff.).
- In order to get another representation of 'a8', we consider another
- action, namely that on the elements of a conjugacy class by conjugation;
- note that this is denoted by 'OnPoints', too.
-
- | gap> class:= ccl[2];;
- gap> op:= Operation( a8, Elements( class ), OnPoints );;|
-
- We get a permutation representation 'op' on 112 points. It is more
- useful to look for properties than at the permutations.
-
- | gap> IsPrimitive( op, [ 1 .. 112 ] );
- false
- gap> blocks:= Blocks( op, [ 1 .. 112 ] );
- [ [ 1, 2 ], [ 6, 8 ], [ 4, 7 ], [ 17, 20 ], [ 3, 5 ], [ 14, 19 ],
- [ 11, 18 ], [ 36, 40 ], [ 13, 16 ], [ 10, 15 ], [ 32, 39 ],
- [ 28, 38 ], [ 24, 37 ], [ 65, 70 ], [ 31, 35 ], [ 9, 12 ],
- [ 27, 34 ], [ 23, 33 ], [ 60, 69 ], [ 55, 68 ], [ 50, 67 ],
- [ 45, 66 ], [ 106, 112 ], [ 59, 64 ], [ 26, 30 ], [ 22, 29 ],
- [ 54, 63 ], [ 49, 62 ], [ 44, 61 ], [ 100, 111 ], [ 94, 110 ],
- [ 88, 109 ], [ 76, 107 ], [ 99, 105 ], [ 53, 58 ], [ 21, 25 ],
- [ 48, 57 ], [ 43, 56 ], [ 93, 104 ], [ 87, 103 ], [ 75, 101 ],
- [ 82, 108 ], [ 92, 98 ], [ 47, 52 ], [ 42, 51 ], [ 86, 97 ],
- [ 74, 95 ], [ 81, 102 ], [ 85, 91 ], [ 41, 46 ], [ 73, 89 ],
- [ 80, 96 ], [ 79, 90 ], [ 78, 84 ], [ 72, 83 ], [ 71, 77 ] ]
- gap> op2:= Operation( op, blocks, OnSets );;
- gap> IsPrimitive( op2, [ 1 .. 56 ] );
- true|
-
- The action of 'op' on the given block system gave us a new representation
- on 56 points which is primitive, i.e., the point stabilizer is a maximal
- subgroup. We compute its preimage in the representation on eight points
- using homomorphisms (which of course are monomorphisms).
-
- | gap> ophom := OperationHomomorphism( a8, op );;
- gap> Kernel(ophom);
- Subgroup( s8, [ ] )
- gap> ophom2:= OperationHomomorphism( op, op2 );;
- gap> stab:= Stabilizer( op2, 1 );;
- gap> Size( stab );
- 360
- gap> composition:= ophom * ophom2;;
- gap> preim:= PreImage( composition, stab );
- Subgroup( s8, [ (1,3,2), (2,4,3), (1,2)(7,8), (3,5,4), (6,8,7) ] )|
-
- And this is the permutation character (with respect to the succession of
- conjugacy classes in 'ccl')\:
-
- | gap> permchar2:= List( reps, x->nrfixedpoints(x^composition,56) );
- [ 56, 11, 12, 1, 2, 2, 3, 0, 0, 0, 0, 1, 1, 0 ]|
-
- The normalizer of an element in the conjugacy class 'class' is a group of
- order 360, too. In fact, it is essentially the same as the maximal
- subgroup we had found before:
-
- | gap> sgp:= Normalizer( a8,
- > Subgroup( s8, [ Representative(class) ] ) );
- Subgroup( s8, [ (6,7,8), (3,4,5), (2,3)(4,5), (1,2)(4,5), (4,5)(7,8)
- ] )
- gap> Size( sgp );
- 360
- gap> IsConjugate( a8, sgp, preim );
- true|
-
- The scalar product of permutation characters of two subgroups $U$, $V$,
- say, equals the number of $(U,V)$--double cosets (again, see chapters
- "Character Tables" ff. for the details). For example, the norm of the
- permutation character 'permchar1' of degree eight is two since the action
- of 'a8' on the cosets of a point stabilizer is at least doubly
- transitive\:
-
- | gap> stab:= Stabilizer( a8, 1 );;
- gap> double:= DoubleCosets( a8, stab, stab );
- [ DoubleCoset( Subgroup( s8, [ (3,8,7), (2,3,8), (2,3,4), (2,6,4),
- (4,5,6) ] ), (2,5,8,3,4,6,7), Subgroup( s8,
- [ (3,8,7), (2,3,8), (2,3,4), (2,6,4), (4,5,6) ] ) ),
- DoubleCoset( Subgroup( s8, [ (3,8,7), (2,3,8), (2,3,4), (2,6,4),
- (4,5,6) ] ), (1,2,5,7)(3,4,6,8), Subgroup( s8,
- [ (3,8,7), (2,3,8), (2,3,4), (2,6,4), (4,5,6) ] ) ) ]
- gap> Length( double );
- 2|
-
- We compute the numbers of $('sgp','sgp')$ and $('sgp','stab')$ double
- cosets.
-
- | gap> Length( DoubleCosets( a8, sgp, sgp ) );
- 4
- gap> Length( DoubleCosets( a8, sgp, stab ) );
- 2|
-
- Thus both irreducible constituents of 'permchar1' are also constituents
- of 'permchar2', i.e., the difference of the two permutation characters is
- a proper character of 'a8' of norm two.
-
- | gap> permchar2 - permchar1;
- [ 48, 6, 8, -2, 0, 0, 2, -1, -1, 0, 0, 1, 1, 0 ]|
-
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \Section{About Operations of Groups}
-
- One of the most important tools in group theory is the *operation* or
- *action* of a group on a certain set.
-
- We say that a group $G$ operates on a set $D$ if we have a function that
- takes each pair $(d,g)$ with $d \in D$ and $g \in G$ to another element
- $d^g \in D$, which we call the image of $d$ under $g$, such that
- $d^{identity} = d$ and $(d^g)^h = d^{gh}$ for each $d \in D$ and $g,h \in
- G$.
-
- This is equivalent to saying that an operation is a homomorphism of the
- group $G$ into the full symmetric group on $D$. We usually call $D$ the
- *domain* of the operation and its elements *points*.
-
- In this section we will demonstrate how you can compute with operations
- of groups. For an example we will use the alternating group on 8 points.
-
- | gap> a8 := Group( (1,2,3), (2,3,4,5,6,7,8) );;
- gap> a8.name := "a8";; |
-
- It is important to note however, that the applicability of the functions
- from the operation package is not restricted to permutation groups. All
- the functions mentioned in this section can also be used to compute with
- the operation of a matrix group on the vectors, etc. We only use a
- permutation group here because this makes the examples more compact.
-
- The standard operation in {\GAP} is always denoted by the caret ('\^')
- operator. That means that when no other operation is specified (we will
- see below how this can be done) all the functions from the operations
- package will compute the image of a point <p> under an element <g> as
- '<p>\^<g>'. Note that this can already denote different operations,
- depending on the type of points and the type of elements. For example if
- the group elements are permutations it can either denote the normal
- operation when the points are integers or the conjugation when the points
- are permutations themselves (see "Operations for Permutations"). For
- another example if the group elements are matrices it can either denote
- the multiplication from the right when the points are vectors or again
- the conjugation when the points are matrices (of the same dimension)
- themselves (see "Operations for Matrices"). Which operations are
- available through the caret operator for a particular type of group
- elements is described in the chapter for this type of group elements.
-
- | gap> 2 ^ (1,2,3);
- 3
- gap> 1 ^ a8.2;
- 1
- gap> (2,4) ^ (1,2,3);
- (3,4) |
-
- The most basic function of the operations package is the function
- 'Orbit', which computes the orbit of a point under the operation of the
- group.
-
- | gap> Orbit( a8, 2 );
- [ 2, 3, 1, 4, 5, 6, 7, 8 ] |
-
- Note that the orbit is not a set, because it is not sorted. See "Orbit"
- for the definition in which order the points appear in an orbit.
-
- We will try to find several subgroups in 'a8' using the operations
- package. One subgroup is immediately available, namely the stabilizer of
- one point. The index of the stabilizer must of course be equal to the
- length of the orbit, i.e., 8.
-
- | gap> u8 := Stabilizer( a8, 1 );
- Subgroup( a8, [ (2,3,4,5,6,7,8), (3,8,7) ] )
- gap> Index( a8, u8 );
- 8 |
-
- This gives us a hint how to find further subgroups. Each subgroup is the
- stabilizer of a point of an appropriate transitive operation (namely the
- operation on the cosets of that subgroup or another operation that is
- equivalent to this operation).
-
- So the question is how to find other operations. The obvious thing is to
- operate on pairs of points. So using the function 'Tuples' (see
- "Tuples") we first generate a list of all pairs.
-
- | gap> pairs := Tuples( [1..8], 2 );
- [ [ 1, 1 ], [ 1, 2 ], [ 1, 3 ], [ 1, 4 ], [ 1, 5 ], [ 1, 6 ],
- [ 1, 7 ], [ 1, 8 ], [ 2, 1 ], [ 2, 2 ], [ 2, 3 ], [ 2, 4 ],
- [ 2, 5 ], [ 2, 6 ], [ 2, 7 ], [ 2, 8 ], [ 3, 1 ], [ 3, 2 ],
- [ 3, 3 ], [ 3, 4 ], [ 3, 5 ], [ 3, 6 ], [ 3, 7 ], [ 3, 8 ],
- [ 4, 1 ], [ 4, 2 ], [ 4, 3 ], [ 4, 4 ], [ 4, 5 ], [ 4, 6 ],
- [ 4, 7 ], [ 4, 8 ], [ 5, 1 ], [ 5, 2 ], [ 5, 3 ], [ 5, 4 ],
- [ 5, 5 ], [ 5, 6 ], [ 5, 7 ], [ 5, 8 ], [ 6, 1 ], [ 6, 2 ],
- [ 6, 3 ], [ 6, 4 ], [ 6, 5 ], [ 6, 6 ], [ 6, 7 ], [ 6, 8 ],
- [ 7, 1 ], [ 7, 2 ], [ 7, 3 ], [ 7, 4 ], [ 7, 5 ], [ 7, 6 ],
- [ 7, 7 ], [ 7, 8 ], [ 8, 1 ], [ 8, 2 ], [ 8, 3 ], [ 8, 4 ],
- [ 8, 5 ], [ 8, 6 ], [ 8, 7 ], [ 8, 8 ] ] |
-
- Now we would like to have 'a8' operate on this domain. But we cannot use
- the default operation (denoted by the caret) because '<list> \^\ <perm>'
- is not defined. So we must tell the functions from the operations
- package how the group elements operate on the elements of the domain. In
- our example we can do this by simply passing 'OnPairs' as optional last
- argument. All functions from the operations package accept such an
- optional argument that describes the operation. See "Other Operations"
- for a list of the available nonstandard operations.
-
- Note that those operations are in fact simply functions that take an
- element of the domain and an element of the group and return the image of
- the element of the domain under the group element. So to compute the
- image of the pair '[1,2]' under the permutation '(1,4,5)' we can simply
- write
-
- | gap> OnPairs( [1,2], (1,4,5) );
- [ 4, 2 ] |
-
- As was mentioned above we have to make sure that the operation is
- transitive. So we check this.
-
- | gap> IsTransitive( a8, pairs, OnPairs );
- false |
-
- The operation is not transitive, so we want to find out what the orbits
- are. The function 'Orbits' does that for you. It returns a list of all
- the orbits.
-
- | gap> orbs := Orbits( a8, pairs, OnPairs );
- [ [ [ 1, 1 ], [ 2, 2 ], [ 3, 3 ], [ 4, 4 ], [ 5, 5 ], [ 6, 6 ],
- [ 7, 7 ], [ 8, 8 ] ],
- [ [ 1, 2 ], [ 2, 3 ], [ 1, 3 ], [ 3, 1 ], [ 3, 4 ], [ 2, 1 ],
- [ 1, 4 ], [ 4, 1 ], [ 4, 5 ], [ 3, 2 ], [ 2, 4 ], [ 1, 5 ],
- [ 4, 2 ], [ 5, 1 ], [ 5, 6 ], [ 4, 3 ], [ 3, 5 ], [ 2, 5 ],
- [ 1, 6 ], [ 5, 3 ], [ 5, 2 ], [ 6, 1 ], [ 6, 7 ], [ 5, 4 ],
- [ 4, 6 ], [ 3, 6 ], [ 2, 6 ], [ 1, 7 ], [ 6, 4 ], [ 6, 3 ],
- [ 6, 2 ], [ 7, 1 ], [ 7, 8 ], [ 6, 5 ], [ 5, 7 ], [ 4, 7 ],
- [ 3, 7 ], [ 2, 7 ], [ 1, 8 ], [ 7, 5 ], [ 7, 4 ], [ 7, 3 ],
- [ 7, 2 ], [ 8, 1 ], [ 8, 2 ], [ 7, 6 ], [ 6, 8 ], [ 5, 8 ],
- [ 4, 8 ], [ 3, 8 ], [ 2, 8 ], [ 8, 6 ], [ 8, 5 ], [ 8, 4 ],
- [ 8, 3 ], [ 8, 7 ] ] ] |
-
- The operation of 'a8' on the first orbit is of course equivalent to the
- original operation, so we ignore it and work with the second orbit.
-
- | gap> u56 := Stabilizer( a8, [1,2], OnPairs );
- Subgroup( a8, [ (3,8,7), (3,6)(4,7,5,8), (6,7,8) ] )
- gap> Index( a8, u56 );
- 56 |
-
- So now we have found a second subgroup. To make the following
- computations a little bit easier and more efficient we would now like to
- work on the points '[1..56]' instead of the list of pairs. The function
- 'Operation' does what we need. It creates a new group that operates on
- '[1..56]' in the same way that 'a8' operates on the second orbit.
-
- | gap> a8_56 := Operation( a8, orbs[2], OnPairs );
- Group( ( 1, 2, 4)( 3, 6,10)( 5, 7,11)( 8,13,16)(12,18,17)(14,21,20)
- (19,27,26)(22,31,30)(28,38,37)(32,43,42)(39,51,50)(44,45,55),
- ( 1, 3, 7,12,19,28,39)( 2, 5, 9,15,23,33,45)( 4, 8,14,22,32,44, 6)
- (10,16,24,34,46,56,51)(11,17,25,35,47,43,55)(13,20,29,40,52,38,50)
- (18,26,36,48,31,42,54)(21,30,41,53,27,37,49) )
- gap> a8_56.name := "a8_56";; |
-
- We would now like to know if the subgroup 'u56' of index 56 that we found
- is maximal or not. Again we can make use of a function from the
- operations package. Namely a subgroup is maximal if the operation on the
- cosets of this subgroup is primitive, i.e., if there is no partition of
- the set of cosets into subsets such that the group operates setwise on
- those subsets.
-
- | gap> IsPrimitive( a8_56, [1..56] );
- false |
-
- Note that we must specify the domain of the operation. You might think
- that in the last example 'IsPrimitive' could use '[1..56]' as default
- domain if no domain was given. But this is not so simple, for example
- would the default domain of 'Group( (2,3,4) )' be '[1..4]' or '[2..4]'?
- To avoid confusion, all operations package functions require that you
- specify the domain of operation.
-
- We see that 'a8\_56' is not primitive. This means of course that the
- operation of 'a8' on 'orb[2]' is not primitive, because those two
- operations are equivalent. So the stabilizer 'u56' is not maximal. Let
- us try to find its supergroups. We use the function 'Blocks' to find a
- block system. The (optional) third argument in the following example
- tells 'Blocks' that we want a block system where 1 and 10 lie in one
- block. There are several other block systems, which we could compute by
- specifying a different pair, it just turns out that '[1,10]' makes the
- following computation more interesting.
-
- | gap> blocks := Blocks( a8_56, [1..56], [1,10] );
- [ [ 1, 10, 13, 21, 31, 43, 45 ], [ 2, 3, 16, 20, 30, 42, 55 ],
- [ 4, 6, 8, 14, 22, 32, 44 ], [ 5, 7, 11, 24, 29, 41, 54 ],
- [ 9, 12, 17, 18, 34, 40, 53 ], [ 15, 19, 25, 26, 27, 46, 52 ],
- [ 23, 28, 35, 36, 37, 38, 56 ], [ 33, 39, 47, 48, 49, 50, 51 ] ] |
-
- The result is a list of sets, i.e., sorted lists, such that 'a8\_56'
- operates on those sets. Now we would like the stabilizer of this
- operation on the sets. Because we wanted to operate on the sets we have
- to pass 'OnSets' as third argument.
-
- | gap> u8_56 := Stabilizer( a8_56, blocks[1], OnSets );
- Subgroup( a8_56,
- [ (15,35,48)(19,28,39)(22,32,44)(23,33,52)(25,36,49)(26,37,50)
- (27,38,51)(29,41,54)(30,42,55)(31,43,45)(34,40,53)(46,56,47),
- ( 9,25)(12,19)(14,22)(15,34)(17,26)(18,27)(20,30)(21,31)(23,48)
- (24,29)(28,39)(32,44)(33,56)(35,47)(36,49)(37,50)(38,51)(40,52)
- (41,54)(42,55)(43,45)(46,53), ( 5,17)( 7,12)( 8,14)( 9,24)(11,18)
- (13,21)(15,25)(16,20)(23,47)(28,39)(29,34)(32,44)(33,56)(35,49)
- (36,48)(37,50)(38,51)(40,54)(41,53)(42,55)(43,45)(46,52),
- ( 2,11)( 3, 7)( 4, 8)( 5,16)( 9,17)(10,13)(20,24)(23,47)(25,26)
- (28,39)(29,30)(32,44)(33,56)(35,48)(36,50)(37,49)(38,51)(40,53)
- (41,55)(42,54)(43,45)(46,52), ( 1,10)( 2, 6)( 3, 4)( 5, 7)( 8,16)
- (12,17)(14,20)(19,26)(22,30)(23,47)(28,50)(32,55)(33,56)(35,48)
- (36,49)(37,39)(38,51)(40,53)(41,54)(42,44)(43,45)(46,52) ] )
- gap> Index( a8_56, u8_56 );
- 8 |
-
- Now we have a problem. We have found a new subgroup, but not as a
- subgroup of 'a8', instead it is a subgroup of 'a8\_56'. We know that
- 'a8\_56' is isomorphic to 'a8' (in general the result of 'Operation' is
- only isomorphic to a factor group of the original group, but in this case
- it must be isomorphic to 'a8', because 'a8' is simple and has only the
- full group as nontrivial factor group). But we only know that an
- isomorphism exists, we do not know it.
-
- Another function comes to our rescue. 'OperationHomomorphism' returns
- the homomorphism of a group onto the group that was constructed by
- 'Operation'. A later section in this chapter will introduce mappings and
- homomorphisms in general, but for the moment we can just regard the
- result of 'OperationHomomorphism' as a black box that we can use to
- transfer information from 'a8' to 'a8\_56' and back.
-
- | gap> h56 := OperationHomomorphism( a8, a8_56 );
- OperationHomomorphism( a8, a8_56 )
- gap> u8b := PreImages( h56, u8_56 );
- Subgroup( a8, [ (6,7,8), (5,6)(7,8), (4,5)(7,8), (3,4)(7,8),
- (1,3)(7,8) ] )
- gap> Index( a8, u8b );
- 8
- gap> u8 = u8b;
- false |
-
- So we have in fact found a new subgroup. However if we look closer we
- note that 'u8b' is not totally new. It fixes the point 2, thus it lies
- in the stabilizer of 2, and because it has the same index as this
- stabilizer it must in fact be the stabilizer. Thus 'u8b' is conjugated
- to 'u8'. A nice way to check this is to check that the operation on the
- 8 blocks is equivalent to the original operation.
-
- | gap> IsEquivalentOperation( a8, [1..8], a8_56, blocks, OnSets );
- true |
-
- Now the choice of the third argument '[1,10]' of 'Blocks' becomes clear.
- Had we not given that argument we would have obtained the block system
- that has '[1,3,7,12,19,28,39]' as first block. The preimage of the
- stabilizer of this set would have been 'u8' itself, and we would not have
- been able to introduce 'IsEquivalentOperation'. Of course we could also
- use the general function 'IsConjugate', but we want to demonstrate
- 'IsEquivalentOperation'.
-
- Actually there is a third block system of 'a8\_56' that gives rise to a
- third subgroup.
-
- | gap> blocks := Blocks( a8_56, [1..56], [1,6] );
- [ [ 1, 6 ], [ 2, 10 ], [ 3, 4 ], [ 5, 16 ], [ 7, 8 ], [ 9, 24 ],
- [ 11, 13 ], [ 12, 14 ], [ 15, 34 ], [ 17, 20 ], [ 18, 21 ],
- [ 19, 22 ], [ 23, 46 ], [ 25, 29 ], [ 26, 30 ], [ 27, 31 ],
- [ 28, 32 ], [ 33, 56 ], [ 35, 40 ], [ 36, 41 ], [ 37, 42 ],
- [ 38, 43 ], [ 39, 44 ], [ 45, 51 ], [ 47, 52 ], [ 48, 53 ],
- [ 49, 54 ], [ 50, 55 ] ]
- gap> u28_56 := Stabilizer( a8_56, [1,6], OnSets );
- Subgroup( a8_56,
- [ ( 2,38,51)( 3,28,39)( 4,32,44)( 5,41,54)(10,43,45)(16,36,49)
- (17,40,53)(20,35,48)(23,47,30)(26,46,52)(33,55,37)(42,56,50),
- ( 5,17,26,37,50)( 7,12,19,28,39)( 8,14,22,32,44)( 9,15,23,33,54)
- (11,18,27,38,51)(13,21,31,43,45)(16,20,30,42,55)(24,34,46,56,49)
- (25,35,47,41,53)(29,40,52,36,48),
- ( 1, 6)( 2,39,38,19,18, 7)( 3,51,28,27,12,11)( 4,45,32,31,14,13)
- ( 5,55,33,23,15, 9)( 8,10,44,43,22,21)(16,50,56,46,34,24)
- (17,54,42,47,35,25)(20,49,37,52,40,29)(26,53,41,30,48,36) ] )
- gap> u28 := PreImages( h56, u28_56 );
- Subgroup( a8, [ (3,7,8), (4,5,6,7,8), (1,2)(3,8,7,6,5,4) ] )
- gap> Index( a8, u28 );
- 28 |
-
- We know that the subgroup 'u28' of index 28 is maximal, because we know
- that 'a8' has no subgroups of index 2, 4, or 7. However we can also
- quickly verify this by checking that 'a8\_56' operates primitively on the
- 28 blocks.
-
- | gap> IsPrimitive( a8_56, blocks, OnSets );
- true |
-
- Note that 'Stabilizer' is not only applicable to groups like 'a8' but
- also to their subgroups like 'u56'. So another method to find a new
- subgroup is to compute the stabilizer of another point in 'u56'. Note
- that 'u56' already leaves 1 and 2 fixed.
-
- | gap> u336 := Stabilizer( u56, 3 );
- Subgroup( a8, [ (4,6,5), (5,6)(7,8), (6,7,8) ] )
- gap> Index( a8, u336 );
- 336 |
-
- Other functions are also applicable to subgroups. In the following we
- show that 'u336' operates regularly on the 60 triples of '[4..8]' which
- contain no element twice, which means that this operation is equivalent
- to the operations of 'u336' on its 60 elements from the right. Note that
- 'OnTuples' is a generalization of 'OnPairs'.
-
- | gap> IsRegular( u336, Orbit( u336, [4,5,6], OnTuples ), OnTuples );
- true |
-
- Just as we did in the case of the operation on the pairs above, we now
- construct a new permutation group that operates on '[1..336]' in the same
- way that 'a8' operates on the cosets of 'u336'. Note that the operation
- of a group on the cosets is by multiplication from the right, thus we
- have to specify 'OnRight'.
-
- | gap> a8_336 := Operation( a8, Cosets( a8, u336 ), OnRight );;
- gap> a8_336.name := "a8_336";; |
-
- To find subgroups above 'u336' we again check if the operation is
- primitive.
-
- | gap> blocks := Blocks( a8_336, [1..336], [1,43] );
- [ [ 1, 43, 85 ], [ 2, 102, 205 ], [ 3, 95, 165 ], [ 4, 106, 251 ],
- [ 5, 117, 334 ], [ 6, 110, 294 ], [ 7, 122, 127 ], [ 8, 144, 247 ],
- [ 9, 137, 207 ], [ 10, 148, 293 ], [ 11, 45, 159 ],
- [ 12, 152, 336 ], [ 13, 164, 169 ], [ 14, 186, 289 ],
- [ 15, 179, 249 ], [ 16, 190, 335 ], [ 17, 124, 201 ],
- [ 18, 44, 194 ], [ 19, 206, 211 ], [ 20, 228, 331 ],
- [ 21, 221, 291 ], [ 22, 46, 232 ], [ 23, 166, 243 ],
- [ 24, 126, 236 ], [ 25, 248, 253 ], [ 26, 48, 270 ],
- [ 27, 263, 333 ], [ 28, 125, 274 ], [ 29, 208, 285 ],
- [ 30, 168, 278 ], [ 31, 290, 295 ], [ 32, 121, 312 ],
- [ 33, 47, 305 ], [ 34, 167, 316 ], [ 35, 250, 327 ],
- [ 36, 210, 320 ], [ 37, 74, 332 ], [ 38, 49, 163 ], [ 39, 81, 123 ],
- [ 40, 59, 209 ], [ 41, 70, 292 ], [ 42, 66, 252 ], [ 50, 142, 230 ],
- [ 51, 138, 196 ], [ 52, 146, 266 ], [ 53, 87, 131 ],
- [ 54, 153, 302 ], [ 55, 160, 174 ], [ 56, 182, 268 ],
- [ 57, 178, 234 ], [ 58, 189, 304 ], [ 60, 86, 199 ],
- [ 61, 198, 214 ], [ 62, 225, 306 ], [ 63, 218, 269 ],
- [ 64, 88, 235 ], [ 65, 162, 245 ], [ 67, 233, 254 ],
- [ 68, 90, 271 ], [ 69, 261, 301 ], [ 71, 197, 288 ],
- [ 72, 161, 281 ], [ 73, 265, 297 ], [ 75, 89, 307 ],
- [ 76, 157, 317 ], [ 77, 229, 328 ], [ 78, 193, 324 ],
- [ 79, 116, 303 ], [ 80, 91, 158 ], [ 82, 101, 195 ],
- [ 83, 112, 267 ], [ 84, 108, 231 ], [ 92, 143, 237 ],
- [ 93, 133, 200 ], [ 94, 150, 273 ], [ 96, 154, 309 ],
- [ 97, 129, 173 ], [ 98, 184, 272 ], [ 99, 180, 238 ],
- [ 100, 188, 308 ], [ 103, 202, 216 ], [ 104, 224, 310 ],
- [ 105, 220, 276 ], [ 107, 128, 241 ], [ 109, 240, 256 ],
- [ 111, 260, 311 ], [ 113, 204, 287 ], [ 114, 130, 277 ],
- [ 115, 275, 296 ], [ 118, 132, 313 ], [ 119, 239, 330 ],
- [ 120, 203, 323 ], [ 134, 185, 279 ], [ 135, 175, 242 ],
- [ 136, 192, 315 ], [ 139, 171, 215 ], [ 140, 226, 314 ],
- [ 141, 222, 280 ], [ 145, 244, 258 ], [ 147, 262, 318 ],
- [ 149, 170, 283 ], [ 151, 282, 298 ], [ 155, 246, 329 ],
- [ 156, 172, 319 ], [ 176, 227, 321 ], [ 177, 217, 284 ],
- [ 181, 213, 257 ], [ 183, 264, 322 ], [ 187, 286, 300 ],
- [ 191, 212, 325 ], [ 219, 259, 326 ], [ 223, 255, 299 ] ] |
-
- To find the subgroup of index 112 that belongs to this operation we could
- use the same methods as before, but we actually use a different trick.
- From the above we see that the subgroup is the union of 'u336' with its
- 43rd and its 85th coset. Thus we simply add a representative of the 43rd
- coset to the generators of 'u336'.
-
- | gap> u112 := Closure( u336, Representative( Cosets(a8,u336)[43] ) );
- Subgroup( a8, [ (4,6,5), (5,6)(7,8), (6,7,8), (1,3,2) ] )
- gap> Index( a8, u112 );
- 112 |
-
- Above this subgroup of index 112 lies a subgroup of index 56, which is
- not conjugate to 'u56'. In fact, unlike 'u56' it is maximal. We obtain
- this subgroup in the same way that we obtained 'u112', this time forcing
- two points, namely 39 and 43 into the first block.
-
- | gap> blocks := Blocks( a8_336, [1..336], [1,39,43] );;
- gap> Length( blocks );
- 56
- gap> u56b := Closure( u112, Representative( Cosets(a8,u336)[39] ) );
- Subgroup( a8, [ (4,6,5), (5,6)(7,8), (6,7,8), (1,3,2), (2,3)(7,8) ] )
- gap> Index( a8, u56b );
- 56
- gap> IsPrimitive( a8_336, blocks, OnSets );
- true |
-
- We already mentioned in the beginning that there is another standard
- operation of permutations, namely the conjugation. E.g., because no
- other operation is specified in the following example 'OrbitLength'
- simply operates using the caret operator and because '<perm1>\^<perm2>'
- is defined as the conjugation of <perm2> on <perm1> we effectively
- compute the length of the conjugacy class of '(1,2)(3,4)(5,6)(7,8)'. (In
- fact '<element1>\^<element2>' is always defined as the conjugation if
- <element1> and <element2> are group elements of the same type. So the
- length of a conjugacy class of any element <elm> in an arbitrary group
- <G> can be computed as 'OrbitLength( <G>, <elm> )'. In general however
- this may not be a good idea, 'Size( ConjugacyClass( <G>, <elm> ) )' is
- probably more efficient.)
-
- | gap> OrbitLength( a8, (1,2)(3,4)(5,6)(7,8) );
- 105
- gap> orb := Orbit( a8, (1,2)(3,4)(5,6)(7,8) );;
- gap> u105 := Stabilizer( a8, (1,2)(3,4)(5,6)(7,8) );
- Subgroup( a8, [ (5,6)(7,8), (1,2)(3,4)(5,6)(7,8), (5,7)(6,8),
- (3,4)(7,8), (3,5)(4,6), (1,3)(2,4) ] )
- gap> Index( a8, u105 );
- 105 |
-
- Of course the last stabilizer is in fact the centralizer of the element
- '(1,2)(3,4)(5,6)(7,8)'. 'Stabilizer' notices that and computes the
- stabilizer using the centralizer algorithm for permutation groups.
-
- In the usual way we now look for the subgroups that lie above 'u105'.
-
- | gap> blocks := Blocks( a8, orb );;
- gap> Length( blocks );
- 15
- gap> blocks[1];
- [ (1,2)(3,4)(5,6)(7,8), (1,3)(2,4)(5,7)(6,8), (1,4)(2,3)(5,8)(6,7),
- (1,5)(2,6)(3,7)(4,8), (1,6)(2,5)(3,8)(4,7), (1,7)(2,8)(3,5)(4,6),
- (1,8)(2,7)(3,6)(4,5) ] |
-
- To find the subgroup of index 15 we again use closure. Now we must be a
- little bit careful to avoid confusion. 'u105' is the stabilizer of
- '(1,2)(3,4)(5,6)(7,8)'. We know that there is a correspondence between
- the points of the orbit and the cosets of 'u105'. The point
- '(1,2)(3,4)(5,6)(7,8)' corresponds to 'u105'. To get the subgroup of
- index 15 we must add to 'u105' an element of the coset that corresponds
- to the point '(1,3)(2,4)(5,7)(6,8)' (or any other point in the first
- block). That means that we must use an element of 'a8' that maps
- '(1,2)(3,4)(5,6)(7,8)' to '(1,3)(2,4)(5,7)(6,8)'. The important thing is
- that '(1,3)(2,4)(5,7)(6,8)' will not do, in fact '(1,3)(2,4)(5,7)(6,8)'
- lies in 'u105'.
-
- The function 'RepresentativeOperation' does what we need. It takes a
- group and two points and returns an element of the group that maps the
- first point to the second. In fact it also allows you to specify the
- operation as optional fourth argument as usual, but we do not need this
- here. If no such element exists in the group, i.e., if the two points do
- not lie in one orbit under the group, 'RepresentativeOperation' returns
- 'false'.
-
- | gap> rep := RepresentativeOperation( a8, (1,2)(3,4)(5,6)(7,8),
- > (1,3)(2,4)(5,7)(6,8) );
- (2,3)(6,7)
- gap> u15 := Closure( u105, rep );
- Subgroup( a8, [ (5,6)(7,8), (1,2)(3,4)(5,6)(7,8), (5,7)(6,8),
- (3,4)(7,8), (3,5)(4,6), (1,3)(2,4), (2,3)(6,7) ] )
- gap> Index( a8, u15 );
- 15 |
-
- 'u15' is of course a maximal subgroup, because 'a8' has no subgroups of
- index 3 or 5.
-
- There is in fact another class of subgroups of index 15 above 'u105' that
- we get by adding '(2,3)(6,8)' to 'u105'.
-
- | gap> u15b := Closure( u105, (2,3)(6,8) );
- Subgroup( a8, [ (5,6)(7,8), (1,2)(3,4)(5,6)(7,8), (5,7)(6,8),
- (3,4)(7,8), (3,5)(4,6), (1,3)(2,4), (2,3)(6,8) ] )
- gap> Index( a8, u15b );
- 15 |
-
- We now show that 'u15' and 'u15b' are not conjugate. We showed that 'u8'
- and 'u8b' are conjugate by showing that the operations on the cosets
- where equivalent. We could show that 'u15' and 'u15b' are not conjugate
- by showing that the operations on their cosets are not equivalent.
- Instead we simply call 'RepresentativeOperation' again.
-
- | gap> RepresentativeOperation( a8, u15, u15b );
- false |
-
- 'RepresentativeOperation' tells us that there is no element <g> in 'a8'
- such that 'u15\^<g> = u15b'. Because '\^' also denotes the conjugation
- of subgroups this tells us that 'u15' and 'u15b' are not conjugate. Note
- that this operation should only be used rarely, because it is usually not
- very efficient. The test in this case is however reasonable efficient,
- and is in fact the one employed by 'IsConjugate' (see "IsConjugate").
-
- This concludes our example. In this section we demonstrated some
- functions from the operations package. There is a whole class of
- functions that we did not mention, namely those that take a single
- element instead of a whole group as first argument, e.g., 'Cycle' and
- 'Permutation'. All functions are described in the chapter "Operations of
- Groups".
-
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \Section{About Fields}
-
- In this section we will show you some basic computations with fields.
- {\GAP} supports at present the following fields. The rationals,
- cyclotomic extensions of rationals and their subfields (which we will
- refer to as number fields in the following), and finite fields.
-
- Let us first take a look at the infinite fields mentioned above. While
- the set of rational numbers is a predefined domain in {\GAP} to which you
- may refer by its identifier 'Rationals', cyclotomic fields are
- constructed by using the function 'CyclotomicField', which may be
- abbreviated as 'CF'.
-
- | gap> IsField( Rationals );
- true
- gap> Size( Rationals );
- "infinity"
- gap> f := CyclotomicField( 8 );
- CF(8)
- gap> IsSubset( f, Rationals );
- true|
-
- The integer argument 'n' of the function call to 'CF' specifies that the
- cyclotomic field containing all n-th roots of unity should be returned.
-
- Cyclotomic fields are constructed as extensions of the Rationals by
- primitive roots of unity. Thus a primitive n-th root of unity is always
- an element of CF(n), where n is a natural number. In {\GAP}, one may
- construct a primitive n-th root of unity by calling 'E(n)'.
-
- | gap> (E(8) + E(8)^3)^2;
- -2
- gap> E(8) in f;
- true|
-
- For every field extension you can compute the Galois group, i.e., the
- group of automorphisms that leave the subfield fixed. For an example,
- cyclotomic fields are an extension of the rationals, so you can compute
- their Galois group over the rationals.
-
- | gap> Galf := GaloisGroup( f );
- Group( NFAutomorphism( CF(8) , 7 ), NFAutomorphism( CF(8) , 5 ) )
- gap> Size( Galf );
- 4|
-
- The above cyclotomic field is a small example where the galois group is
- not cyclic.
-
- | gap> IsCyclic( Galf );
- false
- gap> IsAbelian( Galf );
- true
- gap> AbelianInvariants( Galf );
- [ 2, 2 ]|
-
- This shows us that the 8th cyclotomic field has a Galois group which is
- isomorphic to group $V_4$.
-
- The elements of the Galois group are {\GAP} automorphisms, so they may be
- applied to the elements of the field in the same way as all mappings are
- usually applied to objects in {\GAP}.
-
- | gap> g := Galf.generators[1];
- NFAutomorphism( CF(8) , 7 )
- gap> E(8) ^ g;
- -E(8)^3|
-
- There are two functions, 'Norm' and 'Trace', which compute the norm and
- the trace of elements of the field, respectively. The norm and the trace
- of an element $a$ are defined to be the product and the sum of the images
- of $a$ under the Galois group. You should usually specify the field as a
- first argument. This argument is however optional. If you omit a
- default field will be used. For a cyclotomic $a$ this is the smallest
- cyclotomic field that contains $a$ (note that this is not the smallest
- field that contains $a$, which may be a number field that is not a
- cyclotomic field).
-
- | gap> orb := List( Elements( Galf ), x -> E(8) ^ x );
- [ E(8), E(8)^3, -E(8), -E(8)^3 ]
- gap> Sum( orb ) = Trace( f, E(8) );
- true
- gap> Product( orb ) = Norm( f, E(8) );
- true
- gap> Trace( f, 1 );
- 4|
-
- The basic way to construct a finite field is to use the function
- 'GaloisField' which may be abbreviated, as usual in algebra, as 'GF'.
- Thus
-
- | gap> k := GF( 3, 4 );
- GF(3^4)|
-
- or
-
- | gap> k := GaloisField( 81 );
- GF(3^4)|
-
- will assign the finite field of order $3^4$ to the variable 'k'.
-
- In fact, what 'GF' does is to set up a record which contains all
- necessary information, telling that it represents a finite field of
- degree 4 over its prime field with 3 elements. Of course, all arguments
- to 'GF' others than those which represent a prime power are rejected --
- for obvious reasons.
-
- Some of the more important entries of the field record are 'zero', 'one'
- and 'root', which hold the corresponding elements of the field. All
- elements of a finite field are represented as a certain power of an
- appropriate primitive root, which is written as 'Z(<q>)'. As can be seen
- below the smallest possible primitive root is used.
-
- | gap> k.one + k.root + k.root^10 - k.zero;
- Z(3^4)^52
- gap> k.root;
- Z(3^4)
- gap> k.root ^ 20;
- Z(3^2)^2
- gap> k.one;
- Z(3)^0|
-
- Note that of course elements from fields of different characteristic
- cannot be combined in operations.
-
- | gap> Z(3^2) * k.root + k.zero + Z(3^8);
- Z(3^8)^6534
- gap> Z(2) * k.one;
- Error, Finite field *: operands must have the same characteristic|
-
- In this example we tried to multiply a primitive root of the field with
- two elements with the identity element of the field 'k'. As the
- characteristic of 'k' equals 3, there is no way to perform the
- multiplication. The first statement of the example shows, that if all
- the elements of the expression belong to fields of the same
- characteristic, the result will be computed.
-
- As soon as a primitive root is demanded, {\GAP} internally sets up all
- relevant data structures that are necessary to compute in the
- corresponding finite field. Each finite field is constructed as a
- splitting field of a Conway polynomial. These polynomials, as a set,
- have special properties that make it easy to embed smaller fields in
- larger ones and to convert the representation of the elements when doing
- so. All Conway polynomials for fields up to an order of 65536 have been
- computed and installed in the {\GAP} kernel.
-
- But now look at the following example.
-
- | gap> Z(3^3) * Z(3^4);
- Error, Finite field *: smallest common superfield to large|
-
- Although both factors are elements of fields of characteristic 3, the
- product can not be evaluated by {\GAP}. The reason for this is very easy
- to explain\: In order to compute the product, {\GAP} has to find a field
- in which both of the factors lie. Here in our example the smallest field
- containing $Z(3^3)$ and $Z(3^4)$ is $GF(3^12)$, the field with $531441$
- elements. As we have mentioned above that the size of finite fields in
- {\GAP} is limited at present by $65536$ we now see that there is no
- chance to set up the internal data structures for the common field to
- perform the computation.
-
- As before with cyclotomic fields, the Galois group of a finite field and
- the norm and trace of its elements may be computed. The calling
- conventions are the same as for cyclotomic fields.
-
- | gap> Galk := GaloisGroup( k );
- Group( FrobeniusAutomorphism( GF(3^4) ) )
- gap> Size( Galk );
- 4
- gap> IsCyclic( Galk );
- true
- gap> Norm( k, k.root ^ 20 );
- Z(3)^0
- gap> Trace( k, k.root ^ 20 );
- 0*Z(3)|
-
- So far, in our examples, we were always interested in the Galois group of
- a field extension $k$ over its prime field. In fact it often will occur
- that, given a subfield $l$ of $k$ the Galois group of $k$ over $l$ is
- desired. In {\GAP} it is possible to change the structure of a field by
- using the '/' operator. So typing
-
- | gap> l := GF(3^2);
- GF(3^2)
- gap> IsSubset( k, l );
- true
- gap> k / l;
- GF(3^4)/GF(3^2)|
-
- changes the representation of $k$ from a field extension of degree 4 over
- $GF(3)$ to a field given as an extension of degree 2 over $GF(3^2)$. The
- actual elements of the fields are still the same, only the structure of
- the field has changed.
-
- | gap> k = k / l;
- true
- gap> Galkl := GaloisGroup( k / l );
- Group( FrobeniusAutomorphism( GF(3^4)/GF(3^2) )^2 )
- gap> Size( Galkl );
- 2|
-
- Of course, all the relevant functions behave in a different way when they
- refer to 'k / l' instead of 'k'
-
- | gap> Norm( k / l, k.root ^ 20 );
- Z(3)
- gap> Trace( k / l, k.root ^ 20 );
- Z(3^2)^6|
-
- This feature, to change the structure of the field without changing the
- underlying set of elements, is also available for cyclotomic fields,
- which we have seen at the beginning of this chapter.
-
- | gap> g := CyclotomicField( 4 );
- GaussianRationals
- gap> IsSubset( f, g );
- true
- gap> f / g;
- CF(8)/GaussianRationals
- gap> Galfg := GaloisGroup( f / g );
- Group( NFAutomorphism( CF(8)/GaussianRationals , 5 ) )
- gap> Size( Galfg );
- 2|
-
- The examples should have shown that, although the structure of finite
- fields and cyclotomic fields is rather different, there is a similar
- interface to them in {\GAP}, which makes it easy to write programs that
- deal with both types of fields in the same way.
-
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \Section{About Matrix Groups}
-
- This section intends to show you the things you could do with matrix
- groups in {\GAP}. In principle all the set theoretic functions mentioned
- in chapter "Domains" and all group functions mentioned in chapter
- "Groups" can be applied to matrix groups. However, you should note that
- at present only very few functions can work efficiently with matrix
- groups. Especially infinite matrix groups (over the rationals or
- cyclotomic fields) can not be dealt with at all.
-
- Matrix groups are created in the same way as the other types of groups,
- by using the function 'Group'. Of course, in this case the arguments
- have to be invertable matrices over a field.
-
- | gap> m1 := [ [ Z(3)^0, Z(3)^0, Z(3) ],
- > [ Z(3), 0*Z(3), Z(3) ],
- > [ 0*Z(3), Z(3), 0*Z(3) ] ];;
- gap> m2 := [ [ Z(3), Z(3), Z(3)^0 ],
- > [ Z(3), 0*Z(3), Z(3) ],
- > [ Z(3)^0, 0*Z(3), Z(3) ] ];;
- gap> m := Group( m1, m2 );
- Group( [ [ Z(3)^0, Z(3)^0, Z(3) ], [ Z(3), 0*Z(3), Z(3) ],
- [ 0*Z(3), Z(3), 0*Z(3) ] ],
- [ [ Z(3), Z(3), Z(3)^0 ], [ Z(3), 0*Z(3), Z(3) ],
- [ Z(3)^0, 0*Z(3), Z(3) ] ] )|
-
- As usual for groups, the matrix group that we have constructed is
- represented by a record with several entries. For matrix groups, there
- is one additional entry which holds the field over which the matrix group
- is written.
-
- | gap> m.field;
- GF(3)|
-
- Note that you do not specify the field when you construct the group.
- 'Group' automatically takes the smallest field over which all its
- arguments can be written.
-
- At this point there is the question what special functions are available
- for matrix groups. The size of our group, for example, may be computed
- using the function 'Size'.
-
- | gap> Size( m );
- 864|
-
- If we now compute the size of the corresponding general linear group
-
- | gap> (3^3 - 3^0) * (3^3 - 3^1) * (3^3 - 3^2);
- 11232|
-
- we see that we have constructed a proper subgroup of index 13 of
- $GL(3,3)$.
-
- Let us now set up a subgroup of 'm', which is generated by the matrix
- 'm2'.
-
- | gap> n := Subgroup( m, [ m2 ] );
- Subgroup( Group( [ [ Z(3)^0, Z(3)^0, Z(3) ], [ Z(3), 0*Z(3), Z(3) ],
- [ 0*Z(3), Z(3), 0*Z(3) ] ],
- [ [ Z(3), Z(3), Z(3)^0 ], [ Z(3), 0*Z(3), Z(3) ],
- [ Z(3)^0, 0*Z(3), Z(3) ] ] ),
- [ [ [ Z(3), Z(3), Z(3)^0 ], [ Z(3), 0*Z(3), Z(3) ],
- [ Z(3)^0, 0*Z(3), Z(3) ] ] ] )
- gap> Size( n );
- 6|
-
- And to round up this example we now compute the centralizer of this
- subgroup in 'm'.
-
- | gap> c := Centralizer( m, n );
- Subgroup( Group( [ [ Z(3)^0, Z(3)^0, Z(3) ], [ Z(3), 0*Z(3), Z(3) ],
- [ 0*Z(3), Z(3), 0*Z(3) ] ],
- [ [ Z(3), Z(3), Z(3)^0 ], [ Z(3), 0*Z(3), Z(3) ],
- [ Z(3)^0, 0*Z(3), Z(3) ] ] ),
- [ [ [ Z(3), Z(3), Z(3)^0 ], [ Z(3), 0*Z(3), Z(3) ],
- [ Z(3)^0, 0*Z(3), Z(3) ] ],
- [ [ Z(3), 0*Z(3), 0*Z(3) ], [ 0*Z(3), Z(3), 0*Z(3) ],
- [ 0*Z(3), 0*Z(3), Z(3) ] ] ] )
- gap> Size( c );
- 12|
-
- In this section you have seen that matrix groups are constructed in the
- same way that all groups are constructed. You have also been warned that
- only very few functions can work efficiently with matrix groups. See
- chapter "Matrix Groups" to read more about matrix groups.
-
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \Section{About Domains and Categories}
-
- *Domain* is {\GAP}\'s name for structured sets. We already saw examples
- of domains in the previous sections. For example, the groups 's8' and
- 'a8' in sections "About Groups" and "About Operations of Groups" are
- domains. Likewise the fields in section "About Fields" are domains.
- *Categories* are sets of domains. For example, the set of all groups
- forms a category, as does the set of all fields.
-
- In those sections we treated the domains as black boxes. They were
- constructed by special functions such as 'Group' and 'GaloisField', and
- they could be passed as arguments to other functions such as 'Size' and
- 'Orbits'.
-
- In this section we will also treat domains as black boxes. We will
- describe how domains are created in general and what functions are
- applicable to all domains. Next we will show how domains with the same
- structure are grouped into categories and will give an overview of the
- categories that are available. Then we will discuss how the organization
- of the {\GAP} library around the concept of domains and categories is
- reflected in this manual. In a later section we will open the black
- boxes and give an overview of the mechanism that makes all this work (see
- "About the Implementation of Domains").
-
- The first thing you must know is how you can obtain domains. You have
- basically three possibilities. You can use the domains that are
- predefined in the library, you can create new domains with domain
- constructors, and you can use the domains returned by many library
- functions. We will now discuss those three possibilities in turn.
-
- The {\GAP} library predefines some domains. That means that there is a
- global variable whose value is this domain. The following example shows
- some of the more important predefined domains.
-
- | gap> Integers;
- Integers # the ring of all integers
- gap> Size( Integers );
- "infinity"
- gap> GaussianRationals;
- GaussianRationals # the field of all Gaussian
- gap> (1/2+E(4)) in GaussianRationals;
- true # 'E(4)' is {\GAP}\'s name for the complex root of -1
- gap> Permutations;
- Permutations # the domain of all permutations |
-
- Note that {\GAP} prints those domains using the name of the global
- variable.
-
- You can create new domains using *domain constructors* such as 'Group',
- 'Field', etc. A domain constructor is a function that takes a certain
- number of arguments and returns the domain described by those arguments.
- For example, 'Group' takes an arbitrary number of group elements (of the
- same type) and returns the group generated by those elements.
-
- | gap> gf16 := GaloisField( 16 );
- GF(2^4) # the finite field with 16 elements
- gap> Intersection( gf16, GaloisField( 64 ) );
- GF(2^2)
- gap> a5 := Group( (1,2,3), (3,4,5) );
- Group( (1,2,3), (3,4,5) ) # the alternating group on 5 points
- gap> Size( a5 );
- 60 |
-
- Again {\GAP} prints those domains using more or less the expression that
- you entered to obtain the domain.
-
- As with groups (see "About Groups") a name can be assigned to an
- arbitrary domain <D> with the assignment '<D> \:= <string>;', and {\GAP}
- will use this name from then on in the output.
-
- Many functions in the {\GAP} library return domains. In the last example
- you already saw that 'Intersection' returned a finite field domain.
- Below are more examples.
-
- | gap> GaloisGroup( gf16 );
- Group( FrobeniusAutomorphism( GF(2^4) ) )
- gap> SylowSubgroup( a5, 2 );
- Subgroup( Group( (1,2,3), (3,4,5) ), [ (2,5)(3,4), (2,3)(4,5) ] ) |
-
- The distinction between domain constructors and functions that return
- domains is a little bit arbitrary. It is also not important for the
- understanding of what follows. If you are nevertheless interested, here
- are the principal differences. A constructor performs no computation,
- while a function performs a more or less complicated computation. A
- constructor creates the representation of the domain, while a function
- relies on a constructor to create the domain. A constructor knows the
- dirty details of the domain\'s representation, while a function may be
- independent of the domain\'s representation. A constructor may appear as
- printed representation of a domain, while a function usually does not.
-
- After showing how domains are created, we will now discuss what you can
- do with domains. You can assign a domain to a variable, put a domain
- into a list or into a record, pass a domain as argument to a function,
- and return a domain as result of a function. In this regard there is no
- difference between an integer value such as 17 and a domain such as
- 'Group( (1,2,3), (3,4,5) )'. Of course many functions will signal an
- error when you call them with domains as arguments. For example, 'Gcd'
- does not accept two groups as arguments, because they lie in no Euclidean
- ring.
-
- There are some functions that accept domains of any type as their
- arguments. Those functions are called the *set theoretic functions*.
- The full list of set theoretic functions is given in chapter "Domains".
-
- Above we already used one of those functions, namely 'Size'. If you look
- back you will see that we applied 'Size' to the domain 'Integers', which
- is a ring, and the domain 'a5', which is a group. Remember that a domain
- was a structured set. The size of the domain is the number of elements
- in the set. 'Size' returns this number or the string '\"infinity\"' if
- the domain is infinite. Below are more examples.
-
- | gap> Size( GaussianRationals );
- "infinity" # this string is returned for infinite domains
- gap> Size( SylowSubgroup( a5, 2 ) );
- 4 |
-
- 'IsFinite( <D> )' returns 'true' if the domain <D> is finite and 'false'
- otherwise. You could also test if a domain is finite using 'Size( <D> )
- \<\ \"infinity\"' ({\GAP} evaluates '<n> \<\ \"infinity\"' to 'true' for
- any number <n>). 'IsFinite' is more efficient. For example, if <D> is a
- permutation group, 'IsFinite( <D> )' can immediately return 'true', while
- 'Size( <D> )' may take quite a while to compute the size of <D>.
-
- The other function that you already saw is 'Intersection'. Above we
- computed the intersection of the field with 16 elements and the field
- with 64 elements. The following example is similar.
-
- | gap> Intersection( a5, Group( (1,2), (1,2,3,4) ) );
- Group( (2,3,4), (1,2)(3,4) ) # alternating group on 4 points |
-
- In general 'Intersection' tries to return a domain. In general this is
- not possible however. Remember that a domain is a structured set. If
- the two domain arguments have different structure the intersection may
- not have any structure at all. In this case 'Intersection' returns the
- result as a proper set, i.e., as a sorted list without holes and
- duplicates. The following example shows such a case. 'ConjugacyClass'
- returns the conjugacy class of '(1,2,3,4,5)' in the alternating group on
- 6 points as a domain. If we intersect this class with the symmetric
- group on 5 points we obtain a proper set of 12 permutations, which is
- only one half of the conjugacy class of 5 cycles in 's5'.
-
- | gap> a6 := Group( (1,2,3), (2,3,4,5,6) );
- Group( (1,2,3), (2,3,4,5,6) )
- gap> class := ConjugacyClass( a6, (1,2,3,4,5) );
- ConjugacyClass( Group( (1,2,3), (2,3,4,5,6) ), (1,2,3,4,5) )
- gap> Size( class );
- 72
- gap> s5 := Group( (1,2), (2,3,4,5) );
- Group( (1,2), (2,3,4,5) )
- gap> Intersection( class, s5 );
- [ (1,2,3,4,5), (1,2,4,5,3), (1,2,5,3,4), (1,3,5,4,2), (1,3,2,5,4),
- (1,3,4,2,5), (1,4,3,5,2), (1,4,5,2,3), (1,4,2,3,5), (1,5,4,3,2),
- (1,5,2,4,3), (1,5,3,2,4) ] |
-
- You can intersect arbitrary domains as the following example shows.
-
- | gap> Intersection( Integers, a5 );
- [ ] # the empty set |
-
- Note that we optimized 'Intersection' for typical cases, e.g., computing
- the intersection of two permutation groups, etc. The above computation
- is done with a very simple--minded method, all elements of 'a5' are
- listed (with 'Elements', described below), and for each element
- 'Intersection' tests whether it lies in 'Integers' (with 'in', described
- below). So the same computation with the alternating group on 10 points
- instead of 'a5' will probably exhaust your patience.
-
- Just as 'Intersection' returns a proper set occasionally, it also accepts
- proper sets as arguments. 'Intersection' also takes an arbitrary number
- of arguments. And finally it also accepts a list of domains or sets to
- intersect as single argument.
-
- | gap> Intersection( a5, [ (1,2), (1,2,3), (1,2,3,4), (1,2,3,4,5) ] );
- [ (1,2,3), (1,2,3,4,5) ]
- gap> Intersection( [2,4,6,8,10], [3,6,9,12,15], [5,10,15,20,25] );
- [ ]
- gap> Intersection( [ [1,2,4], [2,3,4], [1,3,4] ] );
- [ 4 ] |
-
- The function 'Union' is the obvious counterpart of 'Intersection'. Note
- that 'Union' usually does *not* return a domain. This is because the
- union of two domains, even of the same type, is usually not again a
- domain of that type. For example, the union of two subgroups is a
- subgroup if and only if one of the subgroups is a subset of the other.
- Of course this is exactly the reason why 'Union' is less important than
- 'Intersection' in algebra.
-
- Because domains are structured sets there ought to be a membership test
- that tests whether an object lies in this domain or not. This is not
- implemented by a function, instead the operator 'in' is used. '<elm> in
- <D>' returns 'true' if the element <elm> lies in the domain <D> and
- 'false' otherwise. We already used the 'in' operator above when we
- tested whether '1/2 + E(4)' lies in the domain of Gaussian integers.
-
- | gap> (1,2,3) in a5;
- true
- gap> (1,2) in a5;
- false
- gap> (1,2,3,4,5,6,7) in a5;
- false
- gap> 17 in a5;
- false # of course an integer does not lie in a permutation group
- gap> a5 in a5;
- false |
-
- As you can see in the last example, 'in' only implements the membership
- test. It does not allow you to test whether a domain is a subset of
- another domain. For such tests the function 'IsSubset' is available.
-
- | gap> IsSubset( a5, a5 );
- true
- gap> IsSubset( a5, Group( (1,2,3) ) );
- true
- gap> IsSubset( Group( (1,2,3) ), a5 );
- false |
-
- In the above example you can see that 'IsSubset' tests whether the second
- argument is a subset of the first argument. As a general rule {\GAP}
- library functions take as *first* arguments those arguments that are in
- some sense *larger* or more structured.
-
- Suppose that you want to loop over all elements of a domain. For
- example, suppose that you want to compute the set of element orders of
- elements in the group 'a5'. To use the 'for' loop you need a list of
- elements in the domain <D>, because 'for <var> in <D> do <statements> od'
- will not work. The function 'Elements' does exactly that. It takes a
- domain <D> and returns the proper set of elements of <D>.
-
- | gap> Elements( Group( (1,2,3), (2,3,4) ) );
- [ (), (2,3,4), (2,4,3), (1,2)(3,4), (1,2,3), (1,2,4), (1,3,2),
- (1,3,4), (1,3)(2,4), (1,4,2), (1,4,3), (1,4)(2,3) ]
- gap> ords := [];;
- gap> for elm in Elements( a5 ) do
- > Add( ords, Order( a5, elm ) );
- > od;
- gap> Set( ords );
- [ 1, 2, 3, 5 ]
- gap> Set( List( Elements( a5 ), elm -> Order( a5, elm ) ) );
- [ 1, 2, 3, 5 ] # an easier way to compute the set of orders |
-
- Of course, if you apply 'Elements' to an infinite domain, 'Elements' will
- signal an error. It is also not a good idea to apply 'Elements' to very
- large domains because the list of elements will take much space and
- computing this large list will probably exhaust your patience.
-
- | gap> Elements( GaussianIntegers );
- Error, the ring <R> must be finite to compute its elements in
- D.operations.Elements( D ) called from
- Elements( GaussianIntegers ) called from
- main loop
- brk> quit;|
-
- There are a few more set theoretic functions. See chapter "Domains" for
- a complete list.
-
- All the set theoretic functions treat the domains as if they had no
- structure. Now a domain is a structured set (excuse us for repeating
- this again and again, but it is really important to get this across). If
- the functions ignore the structure than they are effectively viewing a
- domain only as the set of elements.
-
- In fact all set theoretic functions also accept proper sets, i.e., sorted
- lists without holes and duplicates as arguments (we already mentioned
- this for 'Intersection'). Also set theoretic functions may occasionally
- return proper sets instead of domains as result.
-
- This equivalence of a domain and its set of elements is particularly
- important for the definition of equality of domains. Two domains <D> and
- <E> are equal (in the sense that '<D> = <E>' evaluates to 'true') if and
- only if the set of elements of <D> is equal to the set of elements of <E>
- (as returned by 'Elements( <D> )' and 'Elements( <E> )'). As a special
- case either of the operands of '=' may also be a proper set, and the
- value is 'true' if this set is equal to the set of elements of the
- domain.
-
- | gap> a4 := Group( (1,2,3), (2,3,4) );
- Group( (1,2,3), (2,3,4) )
- gap> elms := Elements( a4 );
- [ (), (2,3,4), (2,4,3), (1,2)(3,4), (1,2,3), (1,2,4), (1,3,2),
- (1,3,4), (1,3)(2,4), (1,4,2), (1,4,3), (1,4)(2,3) ]
- gap> elms = a4;
- true |
-
- However the following example shows that this does not imply that all
- functions return the same answer for two domains (or a domain and a
- proper set) that are equal. This is because those function may take the
- structure into account.
-
- | gap> IsGroup( a4 );
- true
- gap> IsGroup( elms );
- false
- gap> Intersection( a4, Group( (1,2), (1,2,3) ) );
- Group( (1,2,3) )
- gap> Intersection( elms, Group( (1,2), (1,2,3) ) );
- [ (), (1,2,3), (1,3,2) ] # this is not a group
- gap> last = last2;
- true # but it is equal to the above result
- gap> Centre( a4 );
- Subgroup( Group( (1,2,3), (2,3,4) ), [ ] )
- gap> Centre( elms );
- Error, <G> must be a group in
- Centre( elms ) called from
- main loop
- brk> quit;|
-
- Generally three things may happen if you have two domains <D> and <E>
- that are equal but have different structure (or a domain <D> and a set
- <E> that are equal). First a function that tests whether a domain has a
- certain structure may return 'true' for <D> and 'false' for <E>. Second
- a function may return a domain for <D> and a proper set for <E>. Third a
- function may work for <D> and fail for <E>, because it requires the
- structure.
-
- A slightly more complex example for the second case is the following.
-
- | gap> v4 := Subgroup( a4, [ (1,2)(3,4), (1,3)(2,4) ] );
- Subgroup( Group( (1,2,3), (2,3,4) ), [ (1,2)(3,4), (1,3)(2,4) ] )
- gap> v4.name := "v4";;
- gap> rc := v4 * (1,2,3);
- (v4*(2,4,3))
- gap> lc := (1,2,3) * v4;
- ((1,2,3)*v4)
- gap> rc = lc;
- true
- gap> rc * (1,3,2);
- (v4*())
- gap> lc * (1,3,2);
- [ (1,3)(2,4), (), (1,2)(3,4), (1,4)(2,3) ]
- gap> last = last2;
- false |
-
- The two domains 'rc' and 'lc' (yes, cosets are domains too) are equal,
- because they have the same set of elements. However if we multiply both
- with '(1,3,2)' we obtain the trivial right coset for 'rc' and a list for
- 'lc'. The result for 'lc' is *not* a proper set, because it is not
- sorted, therefore '=' evaluates to 'false'. (For the curious. The
- multiplication of a left coset with an element from the right will
- generally not yield another coset, i.e., nothing that can easily be
- represented as a domain. Thus to multiply 'lc' with '(1,3,2)' {\GAP}
- first converts 'lc' to the set of its elements with 'Elements'. But the
- definition of multiplication requires that a list <l> multiplied by an
- element <e> yields a new list <n> such that each element '<n>[<i>]' in
- the new list is the product of the element '<l>[<i>]' at the *same
- position* of the operand list <l> with <e>.)
-
- Note that the above definition only defines *what* the result of the
- equality comparison of two domains <D> and <E> should be. It does not
- prescribe that this comparison is actually performed by listing all
- elements of <D> and <E>. For example, if <D> and <E> are groups, it is
- sufficient to check that all generators of <D> lie in <E> and that all
- generators of <E> lie in <D>. If {\GAP} would really compute the whole
- set of elements, the following test could not be performed on any
- computer.
-
- | gap> Group( (1,2), (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18) )
- > = Group( (17,18), (1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18) );
- true |
-
- If we could only apply the set theoretic functions to domains, domains
- would be of little use. Luckily this is not so. We already saw that we
- could apply 'GaloisGroup' to the finite field with 16 elements, and
- 'SylowSubgroup' to the group 'a5'. But those functions are not
- applicable to all domains. The argument of 'GaloisGroup' must be a
- field, and the argument of 'SylowSubgroup' must be a group.
-
- A *category* is a set of domains. So we say that the argument of
- 'GaloisGroup' must be an element of the category of fields, and the
- argument of 'SylowSubgroup' must be an element of the category of groups.
- The most important categories are *rings*, *fields*, *groups*, and
- *vector spaces*. Which category a domain belongs to determines which
- functions are applicable to this domain and its elements. We want to
- emphasize the each domain belongs to *one and only one* category. This
- is necessary because domains in different categories have, sometimes
- incompatible, representations.
-
- Note that the categories only exist conceptually. That means that there
- is no {\GAP} object for the categories, e.g., there is no object
- 'Groups'. For each category there exists a function that tests whether a
- domain is an element of this category.
-
- | gap> IsRing( gf16 );
- false
- gap> IsField( gf16 );
- true
- gap> IsGroup( gf16 );
- false
- gap> IsVectorSpace( gf16 );
- false |
-
- Note that of course mathematically the field 'gf16' is also a ring and a
- vector space. However in {\GAP} a domain can only belong to one
- category. So a domain is conceptually a set of elements with *one*
- structure, e.g., a field structure. That the same set of elements may
- also support a different structure, e.g., a ring or vector space
- structure, can not be represented by this domain. So you need a
- different domain to represent this different structure. (We are planning
- to add functions that changes the structure of a domain, e.g. 'AsRing(
- <field> )' should return a new domain with the same elements as <field>
- but with a ring structure.)
-
- Domains may have certain properties. For example a ring may be
- commutative and a group may be nilpotent. Whether a domain has a certain
- property <Property> can be tested with the function 'Is<Property>'.
-
- | gap> IsCommutativeRing( GaussianIntegers );
- true
- gap> IsNilpotent( a5 );
- false |
-
- There are also similar functions that test whether a domain (especially a
- group) is represented in a certain way. For example 'IsPermGroup' tests
- whether a group is represented as a permutation group.
-
- | gap> IsPermGroup( a5 );
- true
- gap> IsPermGroup( a4 / v4 );
- false # 'a4 / v4' is represented as a generic factor group |
-
- There is a slight difference between a function such as 'IsNilpotent' and
- a function such as 'IsPermGroup'. The former tests properties of an
- *abstract* group and its outcome is independent of the representation of
- that group. The latter tests whether a group is given in a certain
- representation.
-
- This (rather philosophical) issue is further complicated by the fact that
- sometimes representations and properties are not independent. This is
- especially subtle with 'IsSolvable' (see "IsSolvable") and 'IsAgGroup'
- (see "IsAgGroup"). 'IsSolvable' tests whether a group <G> is solvable.
- 'IsAgGroup' tests whether a group <G> is represented as a finite
- polycyclic group, i.e., by a finite presentation that allows to
- efficiently compute canonical normal forms of elements (see "Finite
- Polycyclic Groups"). Of course every finite polycyclic group is
- solvable, so 'IsAgGroup( <G> )' implies 'IsSolvable( <G> )'. On the
- other hand 'IsSolvable( <G> )' does not imply 'IsAgGroup( <G> )',
- because, even though each solvable group *can* be represented as a finite
- polycyclic group, it *need* not, e.g., it could also be represented as a
- permutation group.
-
- The organization of the manual follows the structure of domains and
- categories.
-
- After the description of the programming language and the environment
- chapter "Domains" describes the domains and the functions applicable to
- all domains.
-
- Next come the chapters that describe the categories rings, fields,
- groups, and vector spaces.
-
- The remaining chapters describe {\GAP}\'s data--types and the domains one
- can make with those elements of those data-types. The order of those
- chapters roughly follows the order of the categories. The data--types
- whose elements form rings and fields come first (e.g., integers and
- finite fields), followed by those whose elements form groups (e.g.,
- permutations), and so on. The data--types whose elements support little
- or no algebraic structure come last (e.g., booleans and strings). In
- some cases there may be two chapters for one data--type, one describing
- the elements and the other describing the domains made with those
- elements (e.g., permutations and permutation groups).
-
- The {\GAP} manual not only describes what you can do, it also gives some
- hints how {\GAP} performs its computations. However, it can be tricky to
- find those hints. The index of this manual can help you.
-
- Suppose that you want to intersect two permutation groups. If you read
- the section that describes the function 'Intersection' (see
- "Intersection") you will see that the last paragraph describes the
- default method used by 'Intersection'. Such a last paragraph that
- describes the default method is rather typical. In this case it says
- that 'Intersection' computes the proper set of elements of both domains
- and intersect them. It also says that this method is often overlaid
- with a more efficient one. You wonder whether this is the case for
- permutation groups. How can you find out? Well you look in the index
- under *Intersection*. There you will find a reference *Intersection, for
- permutation groups* to section *Set Functions for Permutation Groups*
- (see "Set Functions for Permutation Groups"). This section tells you
- that 'Intersection' uses a backtrack for permutation groups (and cites a
- book where you can find a description of the backtrack).
-
- Let us now suppose that you intersect two factor groups. There is no
- reference in the index for *Intersection, for factor groups*. But there
- is a reference for *Intersection, for groups* to the section *Set
- Functions for Groups* (see "Set Functions for Groups"). Since this is
- the next best thing, look there. This section further directs you to the
- section *Intersection for Groups* (see "Intersection for Groups"). This
- section finally tells you that 'Intersection' computes the intersection
- of two groups <G> and <H> as the stabilizer in <G> of the trivial coset
- of <H> under the operation of <G> on the right cosets of <H>.
-
- In this section we introduced domains and categories. You have learned
- that a domain is a structured set, and that domains are either
- predefined, created by domain constructors, or returned by library
- functions. You have seen most functions that are applicable to all
- domains. Those functions generally ignore the structure and treat a
- domain as the set of its elements. You have learned that categories are
- sets of domains, and that the category a domain belongs to determines
- which functions are applicable to this domain.
-
- More information about domains can be found in chapter "Domains".
- Chapters "Rings", "Fields", "Groups", and "Vector Spaces" define the
- categories known to {\GAP}. The section "About the Implementation of
- Domains" opens that black boxes and shows how all this works.
-
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \Section{About Mappings and Homomorphisms}
-
- A mapping is an object which maps each element of its source to a value
- in its range. Source and range can be arbitrary sets of elements. But
- in most applications the source and range are structured sets and the
- mapping, in such applications called homomorphism, is compatible with
- this structure.
-
- In the last sections you have already encountered examples of
- homomorphisms, namely natural homomorphisms of groups onto their factor
- groups and operation homomorphisms of groups into symmetric groups.
-
- Finite fields also bear a structure and homomorphisms between fields are
- always bijections. The galois group of a finite field is generated by
- the Frobenius automorphism. It is very easy to construct.
-
- | gap> f := FrobeniusAutomorphism( GF(81) );
- FrobeniusAutomorphism( GF(3^4) )
- gap> Image( f, Z(3^4) );
- Z(3^4)^3
- gap> A := Group( f );
- Group( FrobeniusAutomorphism( GF(3^4) ) )
- gap> Size( A );
- 4
- gap> IsCyclic( A );
- true
- gap> Order( Mappings, f );
- 4
- gap> Kernel( f );
- [ 0*Z(3) ] |
-
- For finite fields and cyclotomic fields the function 'GaloisGroup' is an
- easy way to construct the galois group.
-
- | gap> GaloisGroup( GF(81) );
- Group( FrobeniusAutomorphism( GF(3^4) ) )
- gap> Size( last );
- 4
- gap> GaloisGroup( CyclotomicField( 18 ) );
- Group( NFAutomorphism( CF(9) , 2 ) )
- gap> Size( last );
- 6 |
-
- Not all group homomorphisms are bijections of course, natural
- homomorphisms do have a kernel in most cases and operation homomorphisms
- need neither be surjective nor injective.
-
- | gap> s4 := Group( (1,2,3,4), (1,2) );
- Group( (1,2,3,4), (1,2) )
- gap> s4.name := "s4";;
- gap> v4 := Subgroup( s4, [ (1,2)(3,4), (1,3)(2,4) ] );
- Subgroup( s4, [ (1,2)(3,4), (1,3)(2,4) ] )
- gap> v4.name := "v4";;
- gap> s3 := s4 / v4;
- (s4 / v4)
- gap> f := NaturalHomomorphism( s4, s3 );
- NaturalHomomorphism( s4, (s4 / v4) )
- gap> IsHomomorphism( f );
- true
- gap> IsEpimorphism( f );
- true
- gap> Image( f );
- (s4 / v4)
- gap> IsMonomorphism( f );
- false
- gap> Kernel( f );
- v4 |
-
- The image of a group homomorphism is always one element of the range but
- the preimage can be a coset. In order to get one representative of this
- coset you can use the function 'PreImagesRepresentative'.
-
- | gap> Image( f, (1,2,3,4) );
- FactorGroupElement( v4, (2,4) )
- gap> PreImages( f, s3.generators[1] );
- (v4*(2,4))
- gap> PreImagesRepresentative( f, s3.generators[1] );
- (2,4) |
-
- But even if the homomorphism is a monomorphism but not surjective you can
- use the function 'PreImagesRepresentative' in order to get the preimage
- of an element of the range.
-
- | gap> A := Z(3) * [ [ 0, 1 ], [ 1, 0 ] ];;
- gap> B := Z(3) * [ [ 0, 1 ], [ -1, 0 ] ];;
- gap> G := Group( A, B );
- Group( [ [ 0*Z(3), Z(3) ], [ Z(3), 0*Z(3) ] ],
- [ [ 0*Z(3), Z(3) ], [ Z(3)^0, 0*Z(3) ] ] )
- gap> Size( G );
- 8
- gap> G.name := "G";;
- gap> d8 := Operation( G, Orbit( G, Z(3)*[1,0] ) );
- Group( (1,2)(3,4), (1,2,3,4) )
- gap> e := OperationHomomorphism( Subgroup( G, [B] ), d8 );
- OperationHomomorphism( Subgroup( G,
- [ [ [ 0*Z(3), Z(3) ], [ Z(3)^0, 0*Z(3) ] ] ] ), Group( (1,2)(3,4),
- (1,2,3,4) ) )
- gap> Kernel( e );
- Subgroup( G, [ ] )
- gap> IsSurjective( e );
- false
- gap> PreImages( e, (1,3)(2,4) );
- (Subgroup( G, [ ] )*[ [ Z(3), 0*Z(3) ], [ 0*Z(3), Z(3) ] ])
- gap> PreImage( e, (1,3)(2,4) );
- Error, <bij> must be a bijection, not an arbitrary mapping in
- bij.operations.PreImageElm( bij, img ) called from
- PreImage( e, (1,3)(2,4) ) called from
- main loop
- brk> quit;
- gap> PreImagesRepresentative( e, (1,3)(2,4) );
- [ [ Z(3), 0*Z(3) ], [ 0*Z(3), Z(3) ] ] |
-
- Only bijections allow 'PreImage' in order to get the preimage of an
- element of the range.
-
- | gap> Operation( G, Orbit( G, Z(3)*[1,0] ) );
- Group( (1,2)(3,4), (1,2,3,4) )
- gap> d := OperationHomomorphism( G, last );
- OperationHomomorphism( G, Group( (1,2)(3,4), (1,2,3,4) ) )
- gap> PreImage( d, (1,3)(2,4) );
- [ [ Z(3), 0*Z(3) ], [ 0*Z(3), Z(3) ] ] |
-
- Both 'PreImage' and 'PreImages' can also be applied to sets. They return
- the complete preimage.
-
- | gap> PreImages( d, Group( (1,2)(3,4), (1,3)(2,4) ) );
- Subgroup( G, [ [ [ 0*Z(3), Z(3) ], [ Z(3), 0*Z(3) ] ],
- [ [ Z(3), 0*Z(3) ], [ 0*Z(3), Z(3) ] ] ] )
- gap> Size( last );
- 4
- gap> f := NaturalHomomorphism( s4, s3 );
- NaturalHomomorphism( s4, (s4 / v4) )
- gap> PreImages( f, s3 );
- Subgroup( s4, [ (1,2)(3,4), (1,3)(2,4), (2,4), (3,4) ] )
- gap> Size( last );
- 24 |
-
- Another way to construct a group automorphism is to use elements in the
- normalizer of a subgroup and construct the induced automorphism. A
- special case is the inner automorphism induced by an element of a group,
- a more general case is a surjective homomorphism induced by arbitrary
- elements of the parent group.
-
- | gap> d12 := Group((1,2,3,4,5,6),(2,6)(3,5));; d12.name := "d12";;
- gap> i1 := InnerAutomorphism( d12, (1,2,3,4,5,6) );
- InnerAutomorphism( d12, (1,2,3,4,5,6) )
- gap> Image( i1, (2,6)(3,5) );
- (1,3)(4,6)
- gap> IsAutomorphism( i1 );
- true |
-
- Mappings can also be multiplied, provided that the range of the first
- mapping is a subgroup of the source of the second mapping. The
- multiplication is of course defined as the composition. Note that, in
- line with the fact that mappings operate *from the right*, 'Image( <map1>
- \*\ <map2>, <elm> )' is defined as 'Image( <map2>, Image( <map1>, <elm> )
- )'.
-
- | gap> i2 := InnerAutomorphism( d12, (2,6)(3,5) );
- InnerAutomorphism( d12, (2,6)(3,5) )
- gap> i1 * i2;
- InnerAutomorphism( d12, (1,6)(2,5)(3,4) )
- gap> Image( last, (2,6)(3,5) );
- (1,5)(2,4) |
-
- Mappings can also be inverted, provided that they are bijections.
-
- | gap> i1 ^ -1;
- InnerAutomorphism( d12, (1,6,5,4,3,2) )
- gap> Image( last, (2,6)(3,5) );
- (1,5)(2,4) |
-
- Whenever you have a set of bijective mappings on a finite set (or domain)
- you can construct the group generated by those mappings. So in the
- following example we create the group of inner automorphisms of 'd12'.
-
- | gap> autd12 := Group( i1, i2 );
- Group( InnerAutomorphism( d12,
- (1,2,3,4,5,6) ), InnerAutomorphism( d12, (2,6)(3,5) ) )
- gap> Size( autd12 );
- 6
- gap> Index( d12, Centre( d12 ) );
- 6 |
-
- Note that the computation with such automorphism groups in their present
- implementation is not very efficient. For example to compute the size of
- such an automorphism group all elements are computed. Thus work with
- such automorphism groups should be restricted to very small examples.
-
- The function 'ConjugationGroupHomomorphism' is a generalization of
- 'InnerAutomorphism'. It accepts a source and a range and an element that
- conjugates the source into the range. Source and range must lie in a
- common parent group, and the conjugating element must also lie in this
- parent group.
-
- | gap> c2 := Subgroup( d12, [ (2,6)(3,5) ] );
- Subgroup( d12, [ (2,6)(3,5) ] )
- gap> v4 := Subgroup( d12, [ (1,2)(3,6)(4,5), (1,4)(2,5)(3,6) ] );
- Subgroup( d12, [ (1,2)(3,6)(4,5), (1,4)(2,5)(3,6) ] )
- gap> x := ConjugationGroupHomomorphism( c2, v4, (1,3,5)(2,4,6) );
- ConjugationGroupHomomorphism( Subgroup( d12,
- [ (2,6)(3,5) ] ), Subgroup( d12, [ (1,2)(3,6)(4,5), (1,4)(2,5)(3,6)
- ] ), (1,3,5)(2,4,6) )
- gap> IsSurjective( x );
- false
- gap> Image( x );
- Subgroup( d12, [ (1,5)(2,4) ] ) |
-
- But how can we construct homomorphisms which are not induced by elements
- of the parent group? The most general way to construct a group
- homomorphism is to define the source, range and the images of the
- generators under the homomorphism in mind.
-
- | gap> c := GroupHomomorphismByImages( G, s4, [A,B], [(1,2),(3,4)] );
- GroupHomomorphismByImages( G, s4,
- [ [ [ 0*Z(3), Z(3) ], [ Z(3), 0*Z(3) ] ],
- [ [ 0*Z(3), Z(3) ], [ Z(3)^0, 0*Z(3) ] ] ], [ (1,2), (3,4) ] )
- gap> Kernel( c );
- Subgroup( G, [ [ [ Z(3), 0*Z(3) ], [ 0*Z(3), Z(3) ] ] ] )
- gap> Image( c );
- Subgroup( s4, [ (1,2), (3,4) ] )
- gap> IsHomomorphism( c );
- true
- gap> Image( c, A );
- (1,2)
- gap> PreImages( c, (1,2) );
- (Subgroup( G, [ [ [ Z(3), 0*Z(3) ], [ 0*Z(3), Z(3) ] ] ] )*
- [ [ 0*Z(3), Z(3) ], [ Z(3), 0*Z(3) ] ]) |
-
- Note that it is possible to construct a general mapping this way that is
- not a homomorphism, because 'GroupHomomorphismByImages' does not check if
- the given images fulfill the relations of the generators.
-
- | gap> b := GroupHomomorphismByImages( G, s4, [A,B], [(1,2,3),(3,4)] );
- GroupHomomorphismByImages( G, s4,
- [ [ [ 0*Z(3), Z(3) ], [ Z(3), 0*Z(3) ] ],
- [ [ 0*Z(3), Z(3) ], [ Z(3)^0, 0*Z(3) ] ] ], [ (1,2,3), (3,4) ] )
- gap> IsHomomorphism( b );
- false
- gap> Images( b, A );
- (Subgroup( s4, [ (1,3,2), (2,3,4), (1,3,4), (1,4)(2,3), (1,4,2)
- ] )*()) |
-
- The result is a *multi valued mapping*, i.e., one that maps each element
- of its source to a set of elements in its range. The set of images of
- 'A' under 'b' is defined as follows. Take all the words of two letters
- $w( x, y )$ such that $w( A, B ) = A$, e.g., $x$ and $x y x y x$. Then
- the set of images is the set of elements that you get by inserting the
- images of 'A' and 'B' in those words, i.e., $w( (1,2,3), (3,4) )$, e.g.,
- $(1,2,3)$ and $(1,4,2)$. One can show that the set of images of the
- identity under a multi valued mapping such as 'b' is a subgroup and that
- the set of images of other elements are cosets of this subgroup.
-
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \Section{About Character Tables}
-
- \def\colon{\char58}
-
- This section contains some examples of the use of {\GAP} in character
- theory. First a few very simple commands for handling character tables
- are introduced, and afterwards we will construct the character tables of
- $(A_5\times 3)\!\colon\!2$ and of $A_6.2^2$.
-
- {\GAP} has a large library of character tables, so let us look at one of
- these tables, e.g., the table of the Mathieu group $M_{11}$\:
-
- | gap> m11:= CharTable( "M11" );;|
-
- The double semicolon is very useful for character tables since they are
- printed in full length, and they contain a lot of information.
-
- | gap> DisplayCharTable( m11 );
- M11
-
- 2 4 4 1 3 . 1 3 3 . .
- 3 2 1 2 . . 1 . . . .
- 5 1 . . . 1 . . . . .
- 11 1 . . . . . . . 1 1
-
- 1a 2a 3a 4a 5a 6a 8a 8b 11a 11b
- 2P 1a 1a 3a 2a 5a 3a 4a 4a 11b 11a
- 3P 1a 2a 1a 4a 5a 2a 8a 8b 11a 11b
- 5P 1a 2a 3a 4a 1a 6a 8b 8a 11a 11b
- 11P 1a 2a 3a 4a 5a 6a 8a 8b 1a 1a
-
- X.1 1 1 1 1 1 1 1 1 1 1
- X.2 10 2 1 2 . -1 . . -1 -1
- X.3 10 -2 1 . . 1 A -A -1 -1
- X.4 10 -2 1 . . 1 -A A -1 -1
- X.5 11 3 2 -1 1 . -1 -1 . .
- X.6 16 . -2 . 1 . . . B /B
- X.7 16 . -2 . 1 . . . /B B
- X.8 44 4 -1 . -1 1 . . . .
- X.9 45 -3 . 1 . . -1 -1 1 1
- X.10 55 -1 1 -1 . -1 1 1 . .
-
- A = E(8)+E(8)^3
- = ER(-2) = i2
- B = E(11)+E(11)^3+E(11)^4+E(11)^5+E(11)^9
- = (-1+ER(-11))/2 = b11|
-
- We are not too much interested in the internal structure of this
- character table (see "Character Table Records"); but of course we can
- access all information about the centralizer orders (first four lines),
- element orders (next line), power maps for the prime divisors of the
- group order (next four lines), irreducible characters (lines parametrized
- by 'X.1' \ldots 'X.10') and irrational character values (last four
- lines), see "DisplayCharTable" for a detailed description of the format
- of the displayed table. E.g., the irreducible characters are a list with
- name 'm11.irreducibles', and each character is a list of cyclotomic
- integers (see chapter "Cyclotomics"). There are various ways to describe
- the irrationalities; e.g., the square root of $-2$ can be entered as
- 'E(8) + E(8)\^3' or 'ER(-2)', the famous ATLAS of Finite
- Groups~\cite{CCN85} denotes it as 'i2'.
-
- | gap> m11.irreducibles[3];
- [ 10, -2, 1, 0, 0, 1, E(8)+E(8)^3, -E(8)-E(8)^3, -1, -1 ]|
-
- We can for instance form tensor products of this character with all
- irreducibles, and compute the decomposition into irreducibles.
-
- | gap> tens:= Tensored( [ last ], m11.irreducibles );;
- gap> MatScalarProducts( m11, m11.irreducibles, tens );
- [ [ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 ], [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1 ],
- [ 0, 0, 0, 0, 1, 0, 0, 1, 1, 0 ], [ 1, 0, 0, 0, 0, 0, 0, 1, 0, 1 ],
- [ 0, 0, 0, 1, 0, 0, 0, 0, 1, 1 ], [ 0, 0, 0, 0, 0, 0, 1, 1, 1, 1 ],
- [ 0, 0, 0, 0, 0, 1, 0, 1, 1, 1 ], [ 0, 0, 1, 1, 0, 1, 1, 2, 3, 3 ],
- [ 0, 1, 0, 1, 1, 1, 1, 3, 2, 3 ], [ 0, 1, 1, 0, 1, 1, 1, 3, 3, 4 ] ]|
-
- The decomposition means for example that the third character in the list
- 'tens' is the sum of the irreducible characters at positions 5, 8 and 9.
-
- | gap> tens[3];
- [ 100, 4, 1, 0, 0, 1, -2, -2, 1, 1 ]
- gap> tens[3] = Sum( Sublist( m11.irreducibles, [ 5, 8, 9 ] ) );
- true|
-
- Or we can compute symmetrizations, e.g., the characters $\chi^{2+}$,
- defined by $\chi^{2+}(g) = \frac{1}{2} ( \chi^2(g) + \chi(g^2) )$, for
- all irreducibles.
-
- | gap> sym:= SymmetricParts( m11, m11.irreducibles, 2 );;
- gap> MatScalarProducts( m11, m11.irreducibles, sym );
- [ [ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], [ 1, 1, 0, 0, 0, 0, 0, 1, 0, 0 ],
- [ 0, 0, 0, 0, 1, 0, 0, 1, 0, 0 ], [ 0, 0, 0, 0, 1, 0, 0, 1, 0, 0 ],
- [ 1, 1, 0, 0, 1, 0, 0, 1, 0, 0 ], [ 0, 1, 0, 0, 1, 0, 1, 1, 0, 1 ],
- [ 0, 1, 0, 0, 1, 1, 0, 1, 0, 1 ], [ 1, 3, 0, 0, 3, 2, 2, 8, 4, 6 ],
- [ 1, 2, 0, 0, 3, 2, 2, 8, 4, 7 ],
- [ 1, 3, 1, 1, 4, 3, 3, 11, 7, 10 ] ]
- gap> sym[2];
- [ 55, 7, 1, 3, 0, 1, 1, 1, 0, 0 ]
- gap> sym[2] = Sum( Sublist( m11.irreducibles, [ 1, 2, 8 ] ) );
- true|
-
- If the subgroup fusion into a supergroup is known, characters can be
- induced to this group, e.g., to obtain the permutation character of the
- action of $M_{12}$ on the cosets of $M_{11}$.
-
- | gap> m12:= CharTable( "M12" );;
- gap> permchar:= Induced( m11, m12, [ m11.irreducibles[1] ] );
- [ [ 12, 0, 4, 3, 0, 4, 0, 2, 0, 1, 2, 0, 0, 1, 1 ] ]
- gap> MatScalarProducts( m12, m12.irreducibles, last );
- [ [ 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ]
- gap> DisplayCharTable( m12, rec( chars:= permchar ) );
- M12
-
- 2 6 4 6 1 2 5 5 1 2 1 3 3 1 . .
- 3 3 1 1 3 2 . . . 1 1 . . . . .
- 5 1 1 . . . . . 1 . . . . 1 . .
- 11 1 . . . . . . . . . . . . 1 1
-
- 1a 2a 2b 3a 3b 4a 4b 5a 6a 6b 8a 8b 10a 11a 11b
- 2P 1a 1a 1a 3a 3b 2b 2b 5a 3b 3a 4a 4b 5a 11b 11a
- 3P 1a 2a 2b 1a 1a 4a 4b 5a 2a 2b 8a 8b 10a 11a 11b
- 5P 1a 2a 2b 3a 3b 4a 4b 1a 6a 6b 8a 8b 2a 11a 11b
- 11P 1a 2a 2b 3a 3b 4a 4b 5a 6a 6b 8a 8b 10a 1a 1a
-
- Y.1 12 . 4 3 . 4 . 2 . 1 2 . . 1 1|
-
- It should be emphasized that the heart of character theory is dealing
- with lists. Characters are lists, and also the maps which occur are
- represented as lists. Note that the multiplication of group elements is
- not available, so we neither have homomorphisms. All we can talk of are
- class functions, and the lists are regarded as such functions, being the
- lists of images with respect to a fixed order of conjugacy classes.
- Therefore we do not write 'chi( cl )' or 'cl\^chi' for the value of the
- character 'chi' on the class 'cl', but 'chi[i]' where 'i' is the position
- of the class 'cl'.
-
- Since the data structures are so basic, most calculations involve
- compositions of maps; for example, the embedding of a subgroup in a group
- is described by the so--called subgroup fusion which is a class function
- that maps each class $c$ of the subgroup to that class of the group that
- contains $c$. Consider the symmetric group $S_5 \cong A_5.2$ as subgroup
- of $M_{11}$. (Do not worry about the names that are used to get library
- tables, see "CharTable" for an overview.)
-
- | gap> s5:= CharTable("A5.2");;
- gap> map:= GetFusionMap( s5, m11 );
- [ 1, 2, 3, 5, 2, 4, 6 ]|
-
- The subgroup fusion is already stored on the table. We see that class 1
- of 's5' is mapped to class 1 of 'm11' (which means that the identity of
- $S_5$ maps to the identity of $M_{11}$), classes 2 and 5 of 's5' both map
- to class 2 of 'm11' (which means that all involutions of $S_5$ are
- conjugate in $M_{11}$), and so on.
-
- The restriction of a character of 'm11' to 's5' is just the composition
- of this character with the subgroup fusion map. Viewing this map as list
- one would call this composition an indirection.
-
- | gap> chi:= m11.irreducibles[3];
- [ 10, -2, 1, 0, 0, 1, E(8)+E(8)^3, -E(8)-E(8)^3, -1, -1 ]
- gap> rest:= List( map, x -> chi[x] );
- [ 10, -2, 1, 0, -2, 0, 1 ]|
-
- This looks very easy, and many {\GAP} functions in character theory do
- such simple calculations. But note that it is not always obvious that a
- list is regarded as a map, where preimages and/or images refer to
- positions of certain conjugacy classes.
-
- | gap> alt:= s5.irreducibles[2];
- [ 1, 1, 1, 1, -1, -1, -1 ]
- gap> kernel:= KernelChar( last );
- [ 1, 2, 3, 4 ]|
-
- The kernel of a character is represented as the list of (positions of)
- classes lying in the kernel. We know that the kernel of the alternating
- character 'alt' of 's5' is the alternating group $A_5$. The order of the
- kernel can be computed as sum of the lengths of the contained classes
- from the character table, using that the classlengths are stored in the
- 'classes' component of the table.
-
- | gap> s5.classes;
- [ 1, 15, 20, 24, 10, 30, 20 ]
- gap> Sublist( last, kernel );
- [ 1, 15, 20, 24 ]
- gap> Sum( last );
- 60|
-
- We chose those classlengths of 's5' that belong to the $S_5$--classes
- contained in the alternating group. The same thing is done in the
- following command, reflecting the view of the kernel as map.
-
- | gap> List( kernel, x -> s5.classes[x] );
- [ 1, 15, 20, 24 ]
- gap> Sum( kernel, x -> s5.classes[x] );
- 60|
-
- This small example shows how the functions 'Sublist', 'List', and 'Sum'
- can be used. These functions as well as 'Filtered' were introduced in
- "About Further List Operations", and we will make heavy use of them; in
- many cases such a command might look very strange, but it is just the
- translation of a (hardly less complicated) mathematical formula to
- character theory.
-
- And now let us construct some small character tables!
-
- %ignore
- \setlength{\unitlength}{0.1cm}
- \begin{minipage}[t]{70mm}
- The group $G = (A_5\times 3)\!\colon\!2$ is a maximal subgroup of the
- alternating group $A_8$; $G$ extends to $S_5\times S_3$ in $S_8$. We
- want to construct the character table of $G$.
-
- First the tables of the subgroup $A_5\times 3$ and the supergroup
- $S_5\times S_3$ are constructed; the tables of the factors of each direct
- product are again got from the table library using admissible names, see
- "CharTable" for this.
- \end{minipage} \ \ \begin{picture}(0,0)
- % \catcode`\*=11
- \put(10,-37){\begin{picture}(50,45)
- \put(25,5){\circle{1}}
- \put(15,15){\circle{1}}
- \put(25,25){\circle{1}}
- \put(25,30){\circle{1}}
- \put(25,35){\circle{1}}
- \put(35,15){\circle{1}}
- \put(10,20){\circle{1}}
- \put(40,20){\circle{1}}
- \put(20,30){\circle{1}}
- \put(30,30){\circle{1}}
- \put(7,20){\makebox(0,0){$S_5$}}
- \put(12,15){\makebox(0,0){$A_5$}}
- \put(43,20){\makebox(0,0){$S_3$}}
- \put(38,15){\makebox(0,0){$3$}}
- \put(27,30){\makebox(0,0){$G$}}
- \put(25,37){\makebox(0,0){$S_5\times S_3$}}
- \put(14,30){\makebox(0,0){$S_5\times 3$}}
- \put(38,30){\makebox(0,0){$A_5\times S_3$}}
- \put(25,5){\line(-1,1){15}}
- \put(25,5){\line(1,1){15}}
- \put(35,15){\line(-1,1){15}}
- \put(15,15){\line(1,1){15}}
- \put(40,20){\line(-1,1){15}}
- \put(10,20){\line(1,1){15}}
- \put(25,25){\line(0,1){10}}
- \end{picture}}
- \end{picture}
- %end
- %display
- %The group G = (A_5 x 3):2 is a S_5 x S_3
- %maximal subgroup of the alternating . | .
- %group A_8; G extends to S_5 x S_3 . | .
- %in S_8. We want to construct the S_5 x 3 G A_5 x S_3
- %character table of G. . . | . .
- % . . | . .
- %First the tables of the subgroup . A_5 x 3 .
- %A_5 x 3 and the supergroup S_5 . . S_3
- %S_5 x S_3 are constructed; the . . . .
- %tables of the factors of each direct . . . .
- %product are again got from the table A_5 3
- %library using admissible names, see . .
- %"CharTable" for this. . .
- % . .
- % .
- %end
-
- | gap> a5:= CharTable( "A5" );;
- gap> c3:= CharTable( "Cyclic", 3 );;
- gap> a5xc3:= CharTableDirectProduct( a5, c3 );;
- gap> s5:= CharTable( "A5.2" );;
- gap> s3:= CharTable( "Symmetric", 3 );;
- gap> s3.irreducibles;
- [ [ 1, -1, 1 ], [ 2, 0, -1 ], [ 1, 1, 1 ] ]
- # The trivial character shall be the first one.
- gap> SortCharactersCharTable( s3 ); # returns the applied permutation
- (1,2,3)
- gap> s5xs3:= CharTableDirectProduct( s5, s3 );;|
-
- $G$ is the normal subgroup of index 2 in $S_5\times S_3$ which contains
- neither $S_5$ nor the normal $S_3$. We want to find the classes of
- 's5xs3' whose union is $G$. For that, we compute the set of kernels of
- irreducibles --remember that they are given simply by lists of numbers of
- contained classes-- and then choose those kernels belonging to normal
- subgroups of index 2.
-
- | gap> kernels:= Set( List( s5xs3.irreducibles, KernelChar ) );
- [ [ 1 ], [ 1, 2, 3 ], [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ],
- [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18,
- 19, 20, 21 ], [ 1, 3 ],
- [ 1, 3, 4, 6, 7, 9, 10, 12, 13, 15, 16, 18, 19, 21 ],
- [ 1, 3, 4, 6, 7, 9, 10, 12, 14, 17, 20 ], [ 1, 4, 7, 10 ],
- [ 1, 4, 7, 10, 13, 16, 19 ] ]
- gap> sizes:= List( kernels, x -> Sum( Sublist( s5xs3.classes, x ) ) );
- [ 1, 6, 360, 720, 3, 360, 360, 60, 120 ]
- gap> s5xs3.order;
- 720
- gap> index2:= Sublist( kernels, [ 3, 6, 7 ] );
- [ [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 ],
- [ 1, 3, 4, 6, 7, 9, 10, 12, 13, 15, 16, 18, 19, 21 ],
- [ 1, 3, 4, 6, 7, 9, 10, 12, 14, 17, 20 ] ]|
-
- In order to decide which kernel describes $G$, we consider the embeddings
- of 's5' and 's3' in 's5xs3', given by the subgroup fusions.
-
- | gap> s5ins5xs3:= GetFusionMap( s5, s5xs3 );
- [ 1, 4, 7, 10, 13, 16, 19 ]
- gap> s3ins5xs3:= GetFusionMap( s3, s5xs3 );
- [ 1, 2, 3 ]
- gap> Filtered( index2, x->Intersection(x,s5ins5xs3)<>s5ins5xs3 and
- > Intersection(x,s3ins5xs3)<>s3ins5xs3 );
- [ [ 1, 3, 4, 6, 7, 9, 10, 12, 14, 17, 20 ] ]
- gap> nsg:= last[1];
- [ 1, 3, 4, 6, 7, 9, 10, 12, 14, 17, 20 ]|
-
- We now construct a first approximation of the character table of this
- normal subgroup, namely the restriction of 's5xs3' to the classes given
- by 'nsg'.
-
- | gap> sub:= CharTableNormalSubgroup( s5xs3, nsg );
- &I CharTableNormalSubgroup: classes in [ 8 ] necessarily split
- rec( name := "Rest(A5.2xS3,[ 1, 3, 4, 6, 7, 9, 10, 12, 14, 17, 20 ])",\
- order := 360, centralizers := [ 360, 180, 24, 12, 18, 9, 15, 15/2,
- 12, 4, 6 ], orders := [ 1, 3, 2, 6, 3, 3, 5, 15, 2, 4, 6
- ], powermap := [ , [ 1, 2, 1, 2, 5, 6, 7, 8, 1, 3, 5 ],
- [ 1, 1, 3, 3, 1, 1, 7, 7, 9, 10, 9 ],,
- [ 1, 2, 3, 4, 5, 6, 1, 2, 9, 10, 11 ] ], classes :=
- [ 1, 2, 15, 30, 20, 40, 24, 48, 30, 90, 60
- ], operations := CharTableOps, irreducibles :=
- [ [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ],
- [ 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1 ],
- [ 2, -1, 2, -1, 2, -1, 2, -1, 0, 0, 0 ],
- [ 6, 6, -2, -2, 0, 0, 1, 1, 0, 0, 0 ],
- [ 4, 4, 0, 0, 1, 1, -1, -1, 2, 0, -1 ],
- [ 4, 4, 0, 0, 1, 1, -1, -1, -2, 0, 1 ],
- [ 8, -4, 0, 0, 2, -1, -2, 1, 0, 0, 0 ],
- [ 5, 5, 1, 1, -1, -1, 0, 0, 1, -1, 1 ],
- [ 5, 5, 1, 1, -1, -1, 0, 0, -1, 1, -1 ],
- [ 10, -5, 2, -1, -2, 1, 0, 0, 0, 0, 0 ] ], fusions := [ rec(
- name := "A5.2xS3",
- map := [ 1, 3, 4, 6, 7, 9, 10, 12, 14, 17, 20 ] ) ] ) |
-
- Not all restrictions of irreducible characters of 's5xs3' to 'sub' remain
- irreducible. We compute those restrictions with norm larger than 1.
-
- | gap> red:= Filtered( Restricted( s5xs3, sub, s5xs3.irreducibles ),
- > x -> ScalarProduct( sub, x, x ) > 1 );
- [ [ 12, -6, -4, 2, 0, 0, 2, -1, 0, 0, 0 ] ]
- gap> Filtered( [ 1 .. Length( nsg ) ],
- > x -> not IsInt( sub.centralizers[x] ) );
- [ 8 ]|
-
- Note that 'sub' is not actually a character table in the sense of
- mathematics but only a record with components like a character table.
- {\GAP} does not know about this subtleties and treats it as a character
- table.
-
- As the list 'centralizers' of centralizer orders shows, at least class 8
- splits into two conjugacy classes in $G$, since this is the only
- possibility to achieve integral centralizer orders.
-
- Since 10 restrictions of irreducible characters remain irreducible for
- $G$ ('sub' contains 10 irreducibles), only one of the 11 irreducibles of
- $S_5\times S_3$ splits into two irreducibles of $G$, in other words,
- class 8 is the only splitting class.
-
- Thus we create a new approximation of the desired character table (which
- we call 'split') where this class is split; 8th and 9th column of the
- known irreducibles are of course equal, and due to the splitting the
- second powermap for these columns is ambiguous.
-
- | gap> splitting:= [ 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 10, 11 ];;
- gap> split:= CharTableSplitClasses( sub, splitting );
- rec( name := "Split(Rest(A5.2xS3,[ 1, 3, 4, 6, 7, 9, 10, 12, 14, 17, 2\
- 0 ]),[ 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 10, 11 ])", order :=
- 360, centralizers := [ 360, 180, 24, 12, 18, 9, 15, 15, 15, 12, 4, 6
- ], classes := [ 1, 2, 15, 30, 20, 40, 24, 24, 24, 30, 90, 60
- ], orders := [ 1, 3, 2, 6, 3, 3, 5, 15, 15, 2, 4, 6 ], powermap :=
- [ , [ 1, 2, 1, 2, 5, 6, 7, [ 8, 9 ], [ 8, 9 ], 1, 3, 5 ],
- [ 1, 1, 3, 3, 1, 1, 7, 7, 7, 10, 11, 10 ],,
- [ 1, 2, 3, 4, 5, 6, 1, 2, 2, 10, 11, 12 ] ], irreducibles :=
- [ [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ],
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1 ],
- [ 2, -1, 2, -1, 2, -1, 2, -1, -1, 0, 0, 0 ],
- [ 6, 6, -2, -2, 0, 0, 1, 1, 1, 0, 0, 0 ],
- [ 4, 4, 0, 0, 1, 1, -1, -1, -1, 2, 0, -1 ],
- [ 4, 4, 0, 0, 1, 1, -1, -1, -1, -2, 0, 1 ],
- [ 8, -4, 0, 0, 2, -1, -2, 1, 1, 0, 0, 0 ],
- [ 5, 5, 1, 1, -1, -1, 0, 0, 0, 1, -1, 1 ],
- [ 5, 5, 1, 1, -1, -1, 0, 0, 0, -1, 1, -1 ],
- [ 10, -5, 2, -1, -2, 1, 0, 0, 0, 0, 0, 0 ] ], fusions := [ rec(
- name := "Rest(A5.2xS3,[ 1, 3, 4, 6, 7, 9, 10, 12, 14, 17, 20 ])"
- ,
- map := [ 1, 2, 3, 4, 5, 6, 7, 8, 8, 9, 10, 11 ] )
- ], operations := CharTableOps )
- gap> Restricted( sub, split, red );
- [ [ 12, -6, -4, 2, 0, 0, 2, -1, -1, 0, 0, 0 ] ]|
-
- To complete the table means to find the missing two irreducibles and to
- complete the powermaps. For this, there are different possibilities.
- First, one can try to embed $G$ in $A_8$.
-
- | gap> a8:= CharTable( "A8" );;
- gap> fus:= SubgroupFusions( split, a8 );
- [ [ 1, 4, 3, 9, 4, 5, 8, 13, 14, 3, 7, 9 ],
- [ 1, 4, 3, 9, 4, 5, 8, 14, 13, 3, 7, 9 ] ]
- gap> fus:= RepresentativesFusions( split, fus, a8 );
- &I RepresentativesFusions: no subtable automorphisms stored
- [ [ 1, 4, 3, 9, 4, 5, 8, 13, 14, 3, 7, 9 ] ]
- gap> StoreFusion( split, a8, fus[1] );|
-
- The subgroup fusion is unique up to table automorphisms. Now we restrict
- the irreducibles of $A_8$ to $G$ and reduce.
-
- | gap> rest:= Restricted( a8, split, a8.irreducibles );;
- gap> red:= Reduced( split, split.irreducibles, rest );
- rec(
- remainders := [ ],
- irreducibles :=
- [ [ 6, -3, -2, 1, 0, 0, 1, -E(15)-E(15)^2-E(15)^4-E(15)^8,
- -E(15)^7-E(15)^11-E(15)^13-E(15)^14, 0, 0, 0 ],
- [ 6, -3, -2, 1, 0, 0, 1, -E(15)^7-E(15)^11-E(15)^13-E(15)^14,
- -E(15)-E(15)^2-E(15)^4-E(15)^8, 0, 0, 0 ] ] )
- gap> Append( split.irreducibles, red.irreducibles );|
-
- The list of irreducibles is now complete, but the powermaps are not yet
- adjusted. To complete the 2nd powermap, we transfer that of $A_8$ to $G$
- using the subgroup fusion.
-
- | gap> split.powermap;
- [ , [ 1, 2, 1, 2, 5, 6, 7, [ 8, 9 ], [ 8, 9 ], 1, 3, 5 ],
- [ 1, 1, 3, 3, 1, 1, 7, 7, 7, 10, 11, 10 ],,
- [ 1, 2, 3, 4, 5, 6, 1, 2, 2, 10, 11, 12 ] ]
- gap> TransferDiagram( split.powermap[2], fus[1], a8.powermap[2] );;|
-
- And this is the complete table.
-
- | gap> split.name:= "(A5x3):2";;
- gap> DisplayCharTable( split );
- (A5x3):2
-
- 2 3 2 3 2 1 . . . . 2 2 1
- 3 2 2 1 1 2 2 1 1 1 1 . 1
- 5 1 1 . . . . 1 1 1 . . .
-
- 1a 3a 2a 6a 3b 3c 5a 15a 15b 2b 4a 6b
- 2P 1a 3a 1a 3a 3b 3c 5a 15a 15b 1a 2a 3b
- 3P 1a 1a 2a 2a 1a 1a 5a 5a 5a 2b 4a 2b
- 5P 1a 3a 2a 6a 3b 3c 1a 3a 3a 2b 4a 6b
-
- X.1 1 1 1 1 1 1 1 1 1 1 1 1
- X.2 1 1 1 1 1 1 1 1 1 -1 -1 -1
- X.3 2 -1 2 -1 2 -1 2 -1 -1 . . .
- X.4 6 6 -2 -2 . . 1 1 1 . . .
- X.5 4 4 . . 1 1 -1 -1 -1 2 . -1
- X.6 4 4 . . 1 1 -1 -1 -1 -2 . 1
- X.7 8 -4 . . 2 -1 -2 1 1 . . .
- X.8 5 5 1 1 -1 -1 . . . 1 -1 1
- X.9 5 5 1 1 -1 -1 . . . -1 1 -1
- X.10 10 -5 2 -1 -2 1 . . . . . .
- X.11 6 -3 -2 1 . . 1 A /A . . .
- X.12 6 -3 -2 1 . . 1 /A A . . .
-
- A = -E(15)-E(15)^2-E(15)^4-E(15)^8
- = (-1-ER(-15))/2 = -1-b15|
-
- There are many ways around the block, so two further methods to complete
- the table 'split' shall be demonstrated; but we will not go into details.
-
- Without use of {\GAP} one could work as follows\:
-
- The irrationalities --and there must be irrational entries in the
- character table of $G$, since the outer 2 can conjugate at most two of
- the four Galois conjugate classes of elements of order 15-- could also
- have been found from the structure of $G$ and the restriction of the
- irreducible $S_5\times S_3$ character of degree 12.
-
- On the classes that did not split the values of this character must just
- be divided by 2. Let $x$ be one of the irrationalities. The second
- orthogonality relation tells us that $x\cdot\overline{x} = 4$ (at class
- '15a') and $x + x\ast = -1$ (at classes '1a' and '15a'); here $x\ast$
- denotes the nontrivial Galois conjugate of $x$. This has no solution for
- $x = \overline{x}$, otherwise it leads to the quadratic equation
- $x^2+x+4 = 0$ with solutions $b15 = \frac{1}{2}(-1+\sqrt{-15})$ and
- $-1-b15$.
-
- The third possibility to complete the table is to embed $A_5\times 3$\:
-
- | gap> split.irreducibles := split.irreducibles{ [ 1 .. 10 ] };;
- gap> SubgroupFusions( a5xc3, split );
- [ [ 1, 2, 2, 3, 4, 4, 5, 6, 6, 7, [ 8, 9 ], [ 8, 9 ], 7, [ 8, 9 ],
- [ 8, 9 ] ] ]|
-
- The images of the four classes of element order 15 are not determined,
- the returned list parametrizes the $2^4$ possibilities.
-
- | gap> fus:= ContainedMaps( last[1] );;
- gap> Length( fus );
- 16
- gap> fus[1];
- [ 1, 2, 2, 3, 4, 4, 5, 6, 6, 7, 8, 8, 7, 8, 8 ]|
-
- Most of these 16 possibilities are excluded using scalar products of
- induced characters. We take a suitable character 'chi' of 'a5xc3' and
- compute the norm of the induced character with respect to each possible
- map.
-
- | gap> chi:= a5xc3.irreducibles[5];
- [ 3, 3*E(3), 3*E(3)^2, -1, -E(3), -E(3)^2, 0, 0, 0, -E(5)-E(5)^4,
- -E(15)^2-E(15)^8, -E(15)^7-E(15)^13, -E(5)^2-E(5)^3,
- -E(15)^11-E(15)^14, -E(15)-E(15)^4 ]
- gap> List( fus, x -> List( Induced( a5xc3, split, [ chi ], x ),
- > y -> ScalarProduct( split, y, y ) )[1] );
- [ 8/15, -2/3*E(5)-11/15*E(5)^2-11/15*E(5)^3-2/3*E(5)^4,
- -2/3*E(5)-11/15*E(5)^2-11/15*E(5)^3-2/3*E(5)^4, 2/3,
- -11/15*E(5)-2/3*E(5)^2-2/3*E(5)^3-11/15*E(5)^4, 3/5, 1,
- -11/15*E(5)-2/3*E(5)^2-2/3*E(5)^3-11/15*E(5)^4,
- -11/15*E(5)-2/3*E(5)^2-2/3*E(5)^3-11/15*E(5)^4, 1, 3/5,
- -11/15*E(5)-2/3*E(5)^2-2/3*E(5)^3-11/15*E(5)^4, 2/3,
- -2/3*E(5)-11/15*E(5)^2-11/15*E(5)^3-2/3*E(5)^4,
- -2/3*E(5)-11/15*E(5)^2-11/15*E(5)^3-2/3*E(5)^4, 8/15 ]
- gap> Filtered( [ 1 .. Length( fus ) ], x -> IsInt( last[x] ) );
- [ 7, 10 ]|
-
- So only fusions 7 and 10 may be possible. They are equivalent (with
- respect to table automorphisms), and the list of induced characters
- contains the missing irreducibles of $G$\:
-
- | gap> Sublist( fus, last );
- [ [ 1, 2, 2, 3, 4, 4, 5, 6, 6, 7, 8, 9, 7, 9, 8 ],
- [ 1, 2, 2, 3, 4, 4, 5, 6, 6, 7, 9, 8, 7, 8, 9 ] ]
- gap> ind:= Induced( a5xc3, split, a5xc3.irreducibles, last[1] );;
- gap> Reduced( split, split.irreducibles, ind );
- rec(
- remainders := [ ],
- irreducibles :=
- [ [ 6, -3, -2, 1, 0, 0, 1, -E(15)-E(15)^2-E(15)^4-E(15)^8,
- -E(15)^7-E(15)^11-E(15)^13-E(15)^14, 0, 0, 0 ],
- [ 6, -3, -2, 1, 0, 0, 1, -E(15)^7-E(15)^11-E(15)^13-E(15)^14,
- -E(15)-E(15)^2-E(15)^4-E(15)^8, 0, 0, 0 ] ] )|
-
- \vspace{5mm}
- The following example is thought mainly for experts. It shall
- demonstrate how one can work together with {\GAP} and the
- {\ATLAS}~\cite{CCN85}, so better leave out the rest of this section if
- you are not familiar with the {\ATLAS}.
-
- %ignore
- \setlength{\unitlength}{0.1cm}
- \begin{minipage}[t]{90mm}
- We shall construct the character table of the group
- $G = A_6.2^2 \cong Aut( A_6 )$ from the tables of the normal subgroups
- $A_6.2_1 \cong S_6$, $A_6.2_2 \cong PGL(2,9)$ and $A_6.2_3 \cong M_{10}$.
-
- We regard $G$ as a downward extension of the Klein fourgroup $2^2$ with
- $A_6$. The set of classes of all preimages of cyclic subgroups of $2^2$
- covers the classes of $G$, but it may happen that some representatives
- are conjugate in $G$, i.e., the classes fuse.
-
- The {\ATLAS} denotes the character tables of $G$, $G.2_1$, $G.2_2$ and
- $G.2_3$ as follows\:
- \end{minipage} \ \ \begin{picture}(0,0)
- \put(5,-40){\begin{picture}(50,45)
- \put(25,5){\circle{1}}
- \put(25,20){\makebox(0,0){$A_6$}}
- \put(25,30){\makebox(0,0){$A_6.2_3$}}
- \put(15,30){\makebox(0,0){$A_6.2_1$}}
- \put(35,30){\makebox(0,0){$A_6.2_2$}}
- \put(25,40){\makebox(0,0){$G$}}
- \put(25,5){\line(0,1){12}}
- \put(23,22){\line(-1,1){6}}
- \put(27,22){\line(1,1){6}}
- \put(25,22){\line(0,1){6}}
- \put(23,38){\line(-1,-1){6}}
- \put(27,38){\line(1,-1){6}}
- \put(25,38){\line(0,-1){6}}
- \end{picture}}
- \end{picture}
-
- \vbox{
- \begin{picture}(0,0)\put(69.25,-76.25){\line(0,1){10}}\end{picture}
-
- | ; @ @ @ @ @ @ @ ; ; @ @ @ @ @
-
- 360 8 9 9 4 5 5 24 24 4 3 3
- p power A A A A A A A A A AB BC
- p|%
- \mbox{\tt\char13}%
- | part A A A A A A A A A AB BC
- ind 1A 2A 3A 3B 4A 5A B* fus ind 2B 2C 4B 6A 6B
-
- |$\chi_1$| + 1 1 1 1 1 1 1 : ++ 1 1 1 1 1
-
- |$\chi_2$| + 5 1 2 -1 -1 0 0 : ++ 3 -1 1 0 -1
-
- |$\chi_3$| + 5 1 -1 2 -1 0 0 : ++ -1 3 1 -1 0
-
- |$\chi_4$| + 8 0 -1 -1 0 -b5 * . + 0 0 0 0 0
-
- |$\chi_5$| + 8 0 -1 -1 0 * -b5 .
-
- |$\chi_6$| + 9 1 0 0 1 -1 -1 : ++ 3 3 -1 0 0
-
- |$\chi_7$| + 10 -2 1 1 0 0 0 : ++ 2 -2 0 -1 1|
- }
-
- \vbox{
- \begin{picture}(0,0)
- \put(6.5,-52){\line(0,1){8.5}}
- \put(56.25,-69){\line(0,1){9}}
- \put(56.25,-52){\line(0,1){8.5}}
- \end{picture}
-
- | ; ; @ @ @ @ @ ; ; @ @ @
-
- 10 4 4 5 5 2 4 4
- A A A BD AD A A A
- A A A AD BD A A A
- fus ind 2D 8A B* 10A B* fus ind 4C 8C D**
-
- : ++ 1 1 1 1 1 : ++ 1 1 1 |$\chi_1$|
-
- . + 0 0 0 0 0 . + 0 0 0 |$\chi_2$|
-
- . . |$\chi_3$|
-
- : ++ 2 0 0 b5 * . + 0 0 0 |$\chi_4$|
-
- : ++ 2 0 0 * b5 . |$\chi_5$|
-
- : ++ -1 1 1 -1 -1 : ++ 1 -1 -1 |$\chi_6$|
-
- : ++ 0 r2 -r2 0 0 : oo 0 i2 -i2 |$\chi_7$|
- |}
- %end
- %display
- %We shall construct the character G
- %table of the group G = A_6.2^2 . | .
- %which is isomorphic to Aut(A_6) . | .
- %from the tables of the normal . | .
- %subgroups A_6.2_1 (isom. to S_6), A_6.2_1 A_6.2_3 A_6.2_2
- %A_6.2_2 (isom. to PGL(2,9)), and . | .
- %A_6.2_3 (isom. to M_{10}). . | .
- % . | .
- %We regard G as a downward extension A_6
- %of the Klein fourgroup 2^2 with A_6. |
- %The set of classes of all preimages |
- %of cyclic subgroups of 2^2 covers |
- %the classes of G, but it may happen |
- %that some representatives are
- %conjugate in G, i.e., the classes fuse.
- %
- %The ATLAS denotes the character tables of G, G.2_1, G.2_2 and G.2_3
- %as follows:
- %
- % ; @ @ @ @ @ @ @ ; ; @ @ @ @ @
- %
- % 360 8 9 9 4 5 5 24 24 4 3 3
- % p power A A A A A A A A A AB BC
- % p' part A A A A A A A A A AB BC
- % ind 1A 2A 3A 3B 4A 5A B* fus ind 2B 2C 4B 6A 6B
- %
- %X1 + 1 1 1 1 1 1 1 : ++ 1 1 1 1 1
- %
- %X2 + 5 1 2 -1 -1 0 0 : ++ 3 -1 1 0 -1
- %
- %X3 + 5 1 -1 2 -1 0 0 : ++ -1 3 1 -1 0
- %
- %X4 + 8 0 -1 -1 0 -b5 * . + 0 0 0 0 0
- % |
- %X5 + 8 0 -1 -1 0 * -b5 .
- %
- %X6 + 9 1 0 0 1 -1 -1 : ++ 3 3 -1 0 0
- %
- %X7 + 10 -2 1 1 0 0 0 : ++ 2 -2 0 -1 1
- %
- %
- %
- % ; ; @ @ @ @ @ ; ; @ @ @
- %
- % 10 4 4 5 5 2 4 4
- % A A A BD AD A A A
- % A A A AD BD A A A
- % fus ind 2D 8A B* 10A B* fus ind 4C 8C D**
- %
- % : ++ 1 1 1 1 1 : ++ 1 1 1 X1
- %
- % . + 0 0 0 0 0 . + 0 0 0 X2
- % | |
- % . . X3
- %
- % : ++ 2 0 0 b5 * . + 0 0 0 X4
- % |
- % : ++ 2 0 0 * b5 . X5
- %
- % : ++ -1 1 1 -1 -1 : ++ 1 -1 -1 X6
- %
- % : ++ 0 r2 -r2 0 0 : oo 0 i2 -i2 X7
- %end
-
- First we construct a table whose classes are those of the three
- subgroups. Note that the exponent of $A_6$ is 60, so the representative
- orders could become at most 60 times the value in $2^2$.
-
- | gap> s1:= CharTable( "A6.2_1" );;
- gap> s2:= CharTable( "A6.2_2" );;
- gap> s3:= CharTable( "A6.2_3" );;
- gap> c2:= CharTable( "Cyclic", 2 );;
- gap> v4:= CharTableDirectProduct( c2, c2 );;
- &I CharTableDirectProduct: existing subgroup fusion on <tbl2> replaced
- &I by actual one
- 1513,1535c1559,1581
- gap> for tbl in [ s1, s2, s3 ] do
- > Print( tbl.irreducibles[2], "\n" );
- > od;
- [ 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1 ]
- [ 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1 ]
- [ 1, 1, 1, 1, 1, -1, -1, -1 ]
- gap> split:= CharTableSplitClasses( v4,
- > [1,1,1,1,1,2,2,2,2,2,3,3,3,3,3,4,4,4], 60 );
- rec( name := "Split(C2xC2,[ 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, \
- 3, 4, 4, 4 ])", order := 4, centralizers :=
- [ 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4 ], classes :=
- [ 1/5, 1/5, 1/5, 1/5, 1/5, 1/5, 1/5, 1/5, 1/5, 1/5, 1/5, 1/5, 1/5,
- 1/5, 1/5, 1/3, 1/3, 1/3 ], orders :=
- [ 1, [ 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, 60 ],
- [ 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, 60 ],
- [ 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, 60 ],
- [ 2, 3, 4, 5, 6, 10, 12, 15, 20, 30, 60 ],
- [ 2, 4, 6, 8, 10, 12, 20, 24, 30, 40, 60, 120 ],
- [ 2, 4, 6, 8, 10, 12, 20, 24, 30, 40, 60, 120 ],
- [ 2, 4, 6, 8, 10, 12, 20, 24, 30, 40, 60, 120 ],
- [ 2, 4, 6, 8, 10, 12, 20, 24, 30, 40, 60, 120 ],
- [ 2, 4, 6, 8, 10, 12, 20, 24, 30, 40, 60, 120 ],
- [ 2, 4, 6, 8, 10, 12, 20, 24, 30, 40, 60, 120 ],
- [ 2, 4, 6, 8, 10, 12, 20, 24, 30, 40, 60, 120 ],
- [ 2, 4, 6, 8, 10, 12, 20, 24, 30, 40, 60, 120 ],
- [ 2, 4, 6, 8, 10, 12, 20, 24, 30, 40, 60, 120 ],
- [ 2, 4, 6, 8, 10, 12, 20, 24, 30, 40, 60, 120 ],
- [ 2, 4, 6, 8, 10, 12, 20, 24, 30, 40, 60, 120 ],
- [ 2, 4, 6, 8, 10, 12, 20, 24, 30, 40, 60, 120 ],
- [ 2, 4, 6, 8, 10, 12, 20, 24, 30, 40, 60, 120 ] ], powermap :=
- [ , [ 1, [ 1, 2, 3, 4, 5 ], [ 1, 2, 3, 4, 5 ], [ 1, 2, 3, 4, 5 ],
- [ 1, 2, 3, 4, 5 ], [ 1, 2, 3, 4, 5 ], [ 1, 2, 3, 4, 5 ],
- [ 1, 2, 3, 4, 5 ], [ 1, 2, 3, 4, 5 ], [ 1, 2, 3, 4, 5 ],
- [ 1, 2, 3, 4, 5 ], [ 1, 2, 3, 4, 5 ], [ 1, 2, 3, 4, 5 ],
- [ 1, 2, 3, 4, 5 ], [ 1, 2, 3, 4, 5 ], [ 1, 2, 3, 4, 5 ],
- [ 1, 2, 3, 4, 5 ], [ 1, 2, 3, 4, 5 ] ] ], irreducibles :=
- [ [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ],
- [ 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, 1, 1, 1, 1, 1, -1, -1, -1 ],
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1 ],
- [ 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1 ]
- ], fusions := [ rec(
- name := "C2xC2",
- map := [ 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4 ]
- ) ], operations := CharTableOps )|
-
- Now we embed the subgroups and adjust the classlengths, order,
- centralizers, powermaps and thus the representative orders.
-
- | gap> StoreFusion( s1, split, [1,2,3,3,4,5,6,7,8,9,10]);
- gap> StoreFusion( s2, split, [1,2,3,4,5,5,11,12,13,14,15]);
- gap> StoreFusion( s3, split, [1,2,3,4,5,16,17,18]);
- gap> for tbl in [ s1, s2, s3 ] do
- > fus:= GetFusionMap( tbl, split );
- > for class in Difference( [ 1 .. Length( tbl.classes ) ],
- > KernelChar(tbl.irreducibles[2]) ) do
- > split.classes[ fus[ class ] ]:= tbl.classes[ class ];
- > od;
- > od;
- gap> for class in [ 1 .. 5 ] do
- > split.classes[ class ]:= s3.classes[ class ];
- > od;
- gap> split.classes;
- [ 1, 45, 80, 90, 144, 15, 15, 90, 120, 120, 36, 90, 90, 72, 72, 180,
- 90, 90 ]
- gap> split.order:= Sum( last );
- 1440
- gap> split.centralizers:= List( last2, x -> split.order / x );
- [ 1440, 32, 18, 16, 10, 96, 96, 16, 12, 12, 40, 16, 16, 20, 20, 8,
- 16, 16 ]
- gap> split.powermap[3]:= InitPowermap( split, 3 );;
- gap> split.powermap[5]:= InitPowermap( split, 5 );;
- gap> for tbl in [ s1, s2, s3 ] do
- > fus:= GetFusionMap( tbl, split );
- > for p in [ 2, 3, 5 ] do
- > TransferDiagram( tbl.powermap[p], fus, split.powermap[p] );
- > od;
- > od;
- gap> split.powermap;
- [ , [ 1, 1, 3, 2, 5, 1, 1, 2, 3, 3, 1, 4, 4, 5, 5, 2, 4, 4 ],
- [ 1, 2, 1, 4, 5, 6, 7, 8, 6, 7, 11, 13, 12, 15, 14, 16, 17, 18 ],,
- [ 1, 2, 3, 4, 1, 6, 7, 8, 9, 10, 11, 13, 12, 11, 11, 16, 18, 17 ] ]
- gap> split.orders:= ElementOrdersPowermap( split.powermap );
- [ 1, 2, 3, 4, 5, 2, 2, 4, 6, 6, 2, 8, 8, 10, 10, 4, 8, 8 ]|
-
- In order to decide which classes fuse in $G$, we look at the norms of
- suitable induced characters, first the + extension of $\chi_2$ to
- $A_6.2_1$.
-
- | gap> ind:= Induced( s1, split, [ s1.irreducibles[3] ] )[1];
- [ 10, 2, 1, -2, 0, 6, -2, 2, 0, -2, 0, 0, 0, 0, 0, 0, 0, 0 ]
- gap> ScalarProduct( split, ind, ind );
- 3/2|
-
- The inertia group of this character is $A_6.2_1$, thus the norm of the
- induced character must be 1. If the classes '2B' and '2C' fuse, the
- contribution of these classes is changed from $15\cdot 6^2+15\cdot(-2)^2$
- to $30 \cdot 2^2$, the difference is 480. But we have to subtract 720
- which is half the group order, so also '6A' and '6B' fuse. This is not
- surprising, since it reflects the action of the famous outer automorphism
- of $S_6$. Next we examine the + extension of $\chi_4$ to $A_6.2_2$.
-
- | gap> ind:= Induced( s2, split, [ s2.irreducibles[4] ] )[1];
- [ 16, 0, -2, 0, 1, 0, 0, 0, 0, 0, 4, 0, 0, 2*E(5)+2*E(5)^4,
- 2*E(5)^2+2*E(5)^3, 0, 0, 0 ]
- gap> ScalarProduct( split, ind, ind );
- 3/2|
-
- Again, the norm must be 1, '10A' and '10B' fuse.
-
- | gap> collaps:= CharTableCollapsedClasses( split,
- > [1,2,3,4,5,6,6,7,8,8,9,10,11,12,12,13,14,15] );
- rec( name := "Collapsed(Split(C2xC2,[ 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3,\
- 3, 3, 3, 3, 4, 4, 4 ]),[ 1, 2, 3, 4, 5, 6, 6, 7, 8, 8, 9, 10, 11, 12,\
- 12, 13, 14, 15 ])", order := 1440, centralizers :=
- [ 1440, 32, 18, 16, 10, 48, 16, 6, 40, 16, 16, 10, 8, 16, 16
- ], orders := [ 1, 2, 3, 4, 5, 2, 4, 6, 2, 8, 8, 10, 4, 8, 8
- ], powermap := [ , [ 1, 1, 3, 2, 5, 1, 2, 3, 1, 4, 4, 5, 2, 4, 4 ],
- [ 1, 2, 1, 4, 5, 6, 7, 6, 9, 11, 10, 12, 13, 14, 15 ],,
- [ 1, 2, 3, 4, 1, 6, 7, 8, 9, 11, 10, 9, 13, 15, 14 ]
- ], fusionsource :=
- [ "Split(C2xC2,[ 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4 \
- ])" ], irreducibles :=
- [ [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ],
- [ 1, 1, 1, 1, 1, -1, -1, -1, 1, 1, 1, 1, -1, -1, -1 ],
- [ 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1 ],
- [ 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, -1, 1, 1, 1 ]
- ], classes := [ 1, 45, 80, 90, 144, 30, 90, 240, 36, 90, 90, 144,
- 180, 90, 90 ], operations := CharTableOps )
- gap> split.fusions;
- [ rec(
- name := "C2xC2",
- map := [ 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3, 4, 4, 4 ]
- ), rec(
- name :=
- "Collapsed(Split(C2xC2,[ 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3,\
- 3, 3, 4, 4, 4 ]),[ 1, 2, 3, 4, 5, 6, 6, 7, 8, 8, 9, 10, 11, 12, 12, 1\
- 3, 14, 15 ])",
- map := [ 1, 2, 3, 4, 5, 6, 6, 7, 8, 8, 9, 10, 11, 12, 12, 13,
- 14, 15 ] ) ]
- gap> for tbl in [ s1, s2, s3 ] do
- > StoreFusion( tbl, collaps,
- > CompositionMaps( GetFusionMap( split, collaps ),
- > GetFusionMap( tbl, split ) ) );
- > od;
- gap> ind:= Induced( s1, collaps, [ s1.irreducibles[10] ] )[1];
- [ 20, -4, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ]
- gap> ScalarProduct( collaps, ind, ind );
- 1|
-
- This character must be equal to any induced character of an irreducible
- character of degree 10 of $A_6.2_2$ and $A_6.2_3$. That means, '8A'
- fuses with '8B', and '8C' with '8D'.
-
- | gap> a6v4:= CharTableCollapsedClasses( collaps,
- > [1,2,3,4,5,6,7,8,9,10,10,11,12,13,13] );
- rec( name := "Collapsed(Collapsed(Split(C2xC2,[ 1, 1, 1, 1, 1, 2, 2, 2\
- , 2, 2, 3, 3, 3, 3, 3, 4, 4, 4 ]),[ 1, 2, 3, 4, 5, 6, 6, 7, 8, 8, 9, 1\
- 0, 11, 12, 12, 13, 14, 15 ]),[ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 10, 11, \
- 12, 13, 13 ])", order := 1440, centralizers :=
- [ 1440, 32, 18, 16, 10, 48, 16, 6, 40, 8, 10, 8, 8 ], orders :=
- [ 1, 2, 3, 4, 5, 2, 4, 6, 2, 8, 10, 4, 8 ], powermap :=
- [ , [ 1, 1, 3, 2, 5, 1, 2, 3, 1, 4, 5, 2, 4 ],
- [ 1, 2, 1, 4, 5, 6, 7, 6, 9, 10, 11, 12, 13 ],,
- [ 1, 2, 3, 4, 1, 6, 7, 8, 9, 10, 9, 12, 13 ] ], fusionsource :=
- [ "Collapsed(Split(C2xC2,[ 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3, 3, 3, 3, 3\
- , 4, 4, 4 ]),[ 1, 2, 3, 4, 5, 6, 6, 7, 8, 8, 9, 10, 11, 12, 12, 13, 14\
- , 15 ])" ], irreducibles :=
- [ [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ],
- [ 1, 1, 1, 1, 1, -1, -1, -1, 1, 1, 1, -1, -1 ],
- [ 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1 ],
- [ 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 1, 1 ] ], classes :=
- [ 1, 45, 80, 90, 144, 30, 90, 240, 36, 180, 144, 180, 180
- ], operations := CharTableOps )
- gap> for tbl in [ s1, s2, s3 ] do
- > StoreFusion( tbl, a6v4,
- > CompositionMaps( GetFusionMap( collaps, a6v4 ),
- > GetFusionMap( tbl, collaps ) ) );
- > od;|
-
- Now the classes of $G$ are known, the only remaining work is to compute
- the irreducibles.
-
- | gap> a6v4.irreducibles;
- [ [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ],
- [ 1, 1, 1, 1, 1, -1, -1, -1, 1, 1, 1, -1, -1 ],
- [ 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1 ],
- [ 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 1, 1 ] ]
- gap> for tbl in [ s1, s2, s3 ] do
- > ind:= Set( Induced( tbl, a6v4, tbl.irreducibles ) );
- > Append( a6v4.irreducibles,
- > Filtered( ind, x -> ScalarProduct( a6v4,x,x ) = 1 ) );
- > od;
- gap> a6v4.irreducibles:= Set( a6v4.irreducibles );
- [ [ 1, 1, 1, 1, 1, -1, -1, -1, -1, -1, -1, 1, 1 ],
- [ 1, 1, 1, 1, 1, -1, -1, -1, 1, 1, 1, -1, -1 ],
- [ 1, 1, 1, 1, 1, 1, 1, 1, -1, -1, -1, -1, -1 ],
- [ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 ],
- [ 10, 2, 1, -2, 0, -2, -2, 1, 0, 0, 0, 0, 0 ],
- [ 10, 2, 1, -2, 0, 2, 2, -1, 0, 0, 0, 0, 0 ],
- [ 16, 0, -2, 0, 1, 0, 0, 0, -4, 0, 1, 0, 0 ],
- [ 16, 0, -2, 0, 1, 0, 0, 0, 4, 0, -1, 0, 0 ],
- [ 20, -4, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ]
- gap> sym:= Symmetrizations( a6v4, [ a6v4.irreducibles[5] ], 2 );
- [ [ 45, -3, 0, 1, 0, -3, 1, 0, -5, 1, 0, -1, 1 ],
- [ 55, 7, 1, 3, 0, 7, 3, 1, 5, -1, 0, 1, -1 ] ]
- gap> Reduced( a6v4, a6v4.irreducibles, sym );
- rec(
- remainders := [ [ 27, 3, 0, 3, -3, 3, -1, 0, 1, -1, 1, 1, -1 ] ],
- irreducibles := [ [ 9, 1, 0, 1, -1, -3, 1, 0, -1, 1, -1, -1, 1 ] ] )
- gap> Append( a6v4.irreducibles,
- > Tensored( last.irreducibles,
- > Sublist( a6v4.irreducibles, [ 1 .. 4 ] ) ) );
- gap> SortCharactersCharTable( a6v4,
- > (1,4)(2,3)(5,6)(7,8)(9,13,10,11,12) );;
- gap> a6v4.name:= "A6.2^2";;
- gap> DisplayCharTable( a6v4 );
- A6.2^2
-
- 2 5 5 1 4 1 4 4 1 3 3 1 3 3
- 3 2 . 2 . . 1 . 1 . . . . .
- 5 1 . . . 1 . . . 1 . 1 . .
-
- 1a 2a 3a 4a 5a 2b 4b 6a 2c 8a 10a 4c 8b
- 2P 1a 1a 3a 2a 5a 1a 2a 3a 1a 4a 5a 2a 4a
- 3P 1a 2a 1a 4a 5a 2b 4b 2b 2c 8a 10a 4c 8b
- 5P 1a 2a 3a 4a 1a 2b 4b 6a 2c 8a 2c 4c 8b
-
- X.1 1 1 1 1 1 1 1 1 1 1 1 1 1
- X.2 1 1 1 1 1 1 1 1 -1 -1 -1 -1 -1
- X.3 1 1 1 1 1 -1 -1 -1 1 1 1 -1 -1
- X.4 1 1 1 1 1 -1 -1 -1 -1 -1 -1 1 1
- X.5 10 2 1 -2 . 2 2 -1 . . . . .
- X.6 10 2 1 -2 . -2 -2 1 . . . . .
- X.7 16 . -2 . 1 . . . 4 . -1 . .
- X.8 16 . -2 . 1 . . . -4 . 1 . .
- X.9 9 1 . 1 -1 -3 1 . 1 -1 1 1 -1
- X.10 9 1 . 1 -1 -3 1 . -1 1 -1 -1 1
- X.11 9 1 . 1 -1 3 -1 . 1 -1 1 -1 1
- X.12 9 1 . 1 -1 3 -1 . -1 1 -1 1 -1
- X.13 20 -4 2 . . . . . . . . . .|
-
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \Section{About Group Libraries}
-
- When you start {\GAP} it already knows several groups. For example, some
- basic groups such as cyclic groups or symmetric groups, all primitive
- permutation groups of degree at most 50, and all 2-groups of size at most
- 256.
-
- Each of the sets above is called a *group library*. The set of all
- groups that {\GAP} knows initially is called the *collection of group
- libraries*.
-
- In this section we show you how you can access the groups in those
- libraries and how you can extract groups with certain properties from
- those libraries.
-
- Let us start with the basic groups, because they are not accessed in the
- same way as the groups in the other libraries.
-
- To access such a basic group you just call a function with an appropriate
- name, such as 'CyclicGroup' or 'SymmetricGroup'.
-
- | gap> c13 := CyclicGroup( 13 );
- Group( ( 1, 2, 3, 4, 5, 6, 7, 8, 9,10,11,12,13) )
- gap> Size( c13 );
- 13
- gap> s8 := SymmetricGroup( 8 );
- Group( (1,8), (2,8), (3,8), (4,8), (5,8), (6,8), (7,8) )
- gap> Size( s8 );
- 40320 |
-
- The functions above also accept an optional first argument that describes
- the type of group. For example you can pass 'AgWords' to 'CyclicGroup'
- to get a cyclic group as a finite polycyclic group (see "Finite
- Polycyclic Groups").
-
- | gap> c13 := CyclicGroup( AgWords, 13 );
- Group( c13 ) |
-
- Of course you cannot pass 'AgWords' to 'SymmetricGroup', because
- symmetric groups are in general not polycyclic.
-
- The default is to construct the groups as permutation groups, but you can
- also explicitly pass 'Permutations'. Other possible arguments are
- 'AgWords' for finite polycyclic groups, 'Words' for finitely presented
- groups, and 'Matrices' for matrix groups (however only 'Permutations' and
- 'AgWords' currently work).
-
- Let us now turn to the other libraries. They are all accessed in a
- uniform way. For a first example we will use the group library of
- primitive permutation groups.
-
- To extract a group from a group library you generally use the *extraction
- function*. In our example this function is called 'PrimitiveGroup'. It
- takes two arguments. The first is the degree of the primitive
- permutation group that you want and the second is an integer that
- specifies which of the primitive permutation groups of that degree you
- want.
-
- | gap> g := PrimitiveGroup( 12, 3 );
- M(11)
- gap> g.generators;
- [ ( 2, 6)( 3, 5)( 4, 7)( 9,10), ( 1, 5, 7)( 2, 9, 4)( 3, 8,10),
- ( 1,11)( 2, 7)( 3, 5)( 4, 6), ( 2, 5)( 3, 6)( 4, 7)(11,12) ]
- gap> Size( g );
- 7920
- gap> IsSimple( g );
- true
- gap> h := PrimitiveGroup( 16, 19 );
- 2^4.A(7)
- gap> Size( h );
- 40320 |
-
- The reason for the extraction function is as follows. A group library is
- usually not stored as a list of groups. Instead a more compact
- representation for the groups is used. For example the groups in the
- library of 2-groups are represented by 4 integers. The extraction
- function hides this representation from you, and allows you to access the
- group library as if it was a table of groups (two dimensional in the
- above example).
-
- What arguments the extraction function accepts, and how they are
- interpreted is described in the sections that describe the individual
- group libraries in chapter "Group Libraries". Those functions will of
- course signal an error when you pass illegal arguments.
-
- Suppose that you want to get a list of all primitive permutation groups
- that have a degree 10 and are simple but not cyclic. It would be very
- difficult to use the extraction function to extract all groups in the
- group library, and test each of those. It is much simpler to use the
- *selection function*. The name of the selection function always begins
- with 'All' and ends with 'Groups', in our example it is thus called
- 'AllPrimitiveGroups'.
-
- | gap> AllPrimitiveGroups( DegreeOperation, 10,
- > IsSimple, true,
- > IsCyclic, false );
- [ A(5), PSL(2,9), A(10) ] |
-
- 'AllPrimitiveGroups' takes a variable number of argument pairs consisting
- of a function (e.g. 'DegreeOperation') and a value (e.g. 10). To
- understand what 'AllPrimitiveGroups' does, imagine that the group library
- was stored as a long list of permutation groups. 'AllPrimitiveGroups'
- takes all those groups in turn. To each group it applies each function
- argument and compares the result with the corresponding value argument.
- It selects a group if and only if all the function results are equal to
- the corresponding value. So in our example 'AllPrimitiveGroups' selects
- those groups <g> for which 'DegreeOperation(<g>) = 10' *and*
- 'IsSimple(<g>) = true' *and* 'IsCyclic(<g>) = false'. Finally
- 'AllPrimitiveGroups' returns the list of the selected groups.
-
- Next suppose that you want all the primitive permutation groups that have
- degree *at most* 10, are simple but are not cyclic. You could obtain
- such a list with 10 calls to 'AllPrimitiveGroups' (i.e., one call for the
- degree 1 groups, another for the degree 2 groups and so on), but there is
- a simple way. Instead of specifying a single value that a function must
- return you can simply specify a list of such values.
-
- | gap> AllPrimitiveGroups( DegreeOperation, [1..10],
- > IsSimple, true,
- > IsCyclic, false );
- [ A(5), PSL(2,5), A(6), PSL(3,2), A(7), PSL(2,7), A(8), PSL(2,8),
- A(9), A(5), PSL(2,9), A(10) ] |
-
- Note that the list that you get contains 'A(5)' twice, first in its
- primitive presentation on 5 points and second in its primitive
- presentation on 10 points.
-
- Thus giving several argument pairs to the selection function allows you
- to express the logical *and* of properties that a group must have to be
- selected, and giving a list of values allows you to express a
- (restricted) logical *or* of properties that a group must have to be
- selected.
-
- There is no restriction on the functions that you can use. It is even
- possible to use functions that you have written yourself. Of course, the
- functions must be unary, i.e., accept only one argument, and must be able
- to deal with the groups.
-
- | gap> NumberConjugacyClasses := function ( g )
- > return Length( ConjugacyClasses( g ) );
- > end;
- function ( g ) ... end
- gap> AllPrimitiveGroups( DegreeOperation, [1..10],
- > IsSimple, true,
- > IsCyclic, false,
- > NumberConjugacyClasses, 9 );
- [ A(7), PSL(2,8) ] |
-
- Note that in some cases a selection function will issue a warning. For
- example if you call 'AllPrimitiveGroups' without specifying the degree,
- it will issue such a warning.
-
- | gap> AllPrimitiveGroups( Size, [100..400],
- > IsSimple, true,
- > IsCyclic, false );
- &W AllPrimitiveGroups: degree automatically restricted to [1..50]
- [ A(6), PSL(3,2), PSL(2,7), PSL(2,9), A(6) ] |
-
- If selection functions would really run over the list of all groups in a
- group library and apply the function arguments to each of those, they
- would be very inefficient. For example the 2-groups library contains
- 58760 groups. Simply creating all those groups would take a very long
- time.
-
- Instead selection functions recognize certain functions and handle them
- more efficiently. For example 'AllPrimitiveGroups' recognizes
- 'DegreeOperation'. If you pass 'DegreeOperation' to 'AllPrimitiveGroups'
- it does not create a group to apply 'DegreeOperation' to it. Instead it
- simply consults an index and quickly eliminates all groups that have a
- different degree. Other functions recognized by 'AllPrimitiveGroups' are
- 'IsSimple', 'Size', and 'Transitivity'.
-
- So in our examples 'AllPrimitiveGroups', recognizing 'DegreeOperation'
- and 'IsSimple', eliminates all but 16 groups. Then it creates those 16
- groups and applies 'IsCyclic' to them. This eliminates 4 more groups
- ('C(2)', 'C(3)', 'C(5)', and 'C(7)'). Then in our last example it
- applies 'NumberConjugacyClasses' to the remaining 12 groups and
- eliminates all but 'A(7)' and 'PSL(2,8)'.
-
- The catch is that the selection functions will take a large amount of
- time if they cannot recognize any special functions. For example the
- following selection will take a large amount of time, because only
- 'IsSimple' is recognized, and there are 116 simple groups in the
- primitive groups library.
-
- | AllPrimitiveGroups( IsSimple, true, NumberConjugacyClasses, 9 );|
-
- So you should specify as sufficiently large set of recognizable functions
- when you call a selection function. It is also advisable to put those
- functions first (though in some group libraries the selection function
- will automatically rearrange the argument pairs so that the recognized
- functions come first). The sections describing the individual group
- libraries in chapter "Group Libraries" tell you which functions are
- recognized by the selection function of that group library.
-
- There is another function, called the *example function* that behaves
- similar to the selection function. Instead of returning a list of all
- groups with a certain set of properties it only returns one such group.
- The name of the example function is obtained by replacing 'All' by 'One'
- and stripping the 's' at the end of the name of the selection function.
-
- | gap> OnePrimitiveGroup( DegreeOperation, [1..10],
- > IsSimple, true,
- > IsCyclic, false,
- > NumberConjugacyClasses, 9 );
- A(7) |
-
- The example function works just like the selection function. That means
- that all the above comments about the special functions that are
- recognized also apply to the example function.
-
- Let us now look at the 2-groups library. It is accessed in the same way
- as the primitive groups library. There is an extraction function
- 'TwoGroup', a selection function 'AllTwoGroups', and an example function
- 'OneTwoGroup'.
-
- | gap> g := TwoGroup( 128, 5 );
- Group( a1, a2, a3, a4, a5, a6, a7 )
- gap> Size( g );
- 128
- gap> NumberConjugacyClasses( g );
- 80 |
-
- The groups are all displayed as 'Group( a1, a2, ..., a<n> )', where $2^n$
- is the size of the group.
-
- | gap> AllTwoGroups( Size, 256,
- > Rank, 3,
- > pClass, 2 );
- [ Group( a1, a2, a3, a4, a5, a6, a7, a8 ),
- Group( a1, a2, a3, a4, a5, a6, a7, a8 ),
- Group( a1, a2, a3, a4, a5, a6, a7, a8 ),
- Group( a1, a2, a3, a4, a5, a6, a7, a8 ) ]
- gap> l := AllTwoGroups( Size, 256,
- > Rank, 3,
- > pClass, 5,
- > g -> Length( DerivedSeries( g ) ), 4 );;
- gap> Length( l );
- 28 |
-
- The selection and example function of the 2-groups library recognize
- 'Size', 'Rank', and 'pClass'. Note that 'Rank' and 'pClass' are
- functions that can in fact only be used in this context, i.e., they can
- not be applied to arbitrary groups.
-
- The following discussion is a bit technical and you can ignore it safely.
-
- For very big group libraries, such as the 2-groups library, the groups
- (or their compact representations) are not stored on a single file. This
- is because this file would be very large and loading it would take a long
- time and a lot of main memory.
-
- Instead the groups are stored on a small number of files (27 in the case
- of the 2-groups). The selection and example functions are careful to
- load only those files that may actually contain groups with the specified
- properties. For example in the above example the files containing the
- groups of size less than 256 are never loaded. In fact in the above
- example only one very small file is loaded.
-
- When a file is loaded the selection and example functions also unload the
- previously loaded file. That means that they forget all the groups in
- this file again (except those selected of course). Thus even if the
- selection or example functions have to search through the whole group
- library, only a small part of the library is held in main memory at any
- time. In principle it should be possible to search the whole 2-groups
- library with as little as 2 MByte of main memory.
-
- If you have sufficient main memory available you can explicitly load
- files from the 2-groups library with 'ReadTwo( <filename> )', e.g.,
- 'Read( \"twogp64\" )' to load the file with the groups of size 64. Those
- files will then not be unloaded again. This will take up more main
- memory, but the selection and example function will work faster, because
- they do not have to load those files again each time they are needed.
-
- In this section you have seen the basic groups library and the group
- libraries of primitive groups and 2-groups. You have seen how you can
- extract a single group from such a library with the extraction function.
- You have seen how you can select groups with certain properties with the
- selection and example function. Chapter "Group Libraries" tells you
- which other group libraries are available.
-
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \Section{About the Implementation of Domains}
-
- In this section we will open the black boxes and describe how all this
- works. This is complex and you do not need to understand it if you are
- content to use domains only as black boxes. So you may want to skip this
- section (and the remainder of this chapter).
-
- Domains are represented by records, which we will call *domain records*
- in the following. Which components have to be present, which may, and
- what those components hold, differs from category to category, and, to a
- smaller extent, from domain to domain. It is possible, though, to
- generally distinguish four types of components.
-
- The first type of components are called the *category components*. They
- determine to which category a domain belongs. A domain <D> in a category
- <Cat> has a component 'is<Cat>' with the value 'true'. For example, each
- group has the component 'isGroup'. Also each domain has the component
- 'isDomain' (again with the value 'true'). Finally a domain may also have
- components that describe the representation of this domain. For example,
- each permutation group has a component 'isPermGroup' (again with the
- value 'true'). Functions such as 'IsPermGroup' test whether such a
- component is present, and whether it has the value 'true'.
-
- The second type of components are called the *identification components*.
- They distinguish the domain from other domains in the same category. The
- identification components uniquely identify the domain. For example, for
- groups the identification components are 'generators', which holds a list
- of generators of the group, and 'identity', which holds the identity of
- the group (needed for the trivial group, for which the list of generators
- is empty).
-
- The third type of components are called *knowledge components*. They
- hold all the knowledge {\GAP} has about the domain. For example the size
- of the domain <D> is stored in the knowledge component '<D>.size', the
- commutator subgroup of a group is stored in the knowledge component
- '<D>.commutatorSubgroup', etc. Of course, the knowledge about a certain
- domain will usually increase as you work with a domain. For example, a
- group record may initially hold only the knowledge that the group is
- finite, but may later hold all kinds of knowledge, for example the
- derived series, the Sylow subgroups, etc.
-
- Finally each domain record contains an *operations record*. The
- operations record is discussed below.
-
- We want to emphasize that really all information that {\GAP} has about a
- domain is stored in the knowledge components. That means that you can
- access all this information, at least if you know where to look and how
- to interpret what you see. The chapters describing categories and
- domains will tell you what knowledge components a domain may have, and
- how the knowledge is represented in those components.
-
- For an example let us return to the permutation group 'a5' from section
- "About Domains and Categories". If we print the record using the
- function 'PrintRec' we see all the information. {\GAP} stores the
- stabilizer chain of 'a5' in the components 'orbit', 'transversal', and
- 'stabilizer'. It is not important that you understand what a stabilizer
- chain is (this is discussed in chapter "Permutation Groups"), the
- important point here is that it is the vital information that {\GAP}
- needs to work efficiently with 'a5' and that you can access it.
-
- | gap> a5 := Group( (1,2,3), (3,4,5) );
- Group( (1,2,3), (3,4,5) )
- gap> Size( a5 );
- 60
- gap> PrintRec( a5 ); Print( "\n" );
- rec(
- isDomain := true,
- isGroup := true,
- identity := (),
- generators := [ (1,2,3), (3,4,5) ],
- operations := ...,
- isPermGroup := true,
- isFinite := true,
- 1 := (1,2,3),
- 2 := (3,4,5),
- orbit := [ 1, 3, 2, 5, 4 ],
- transversal := [ (), (1,2,3), (1,2,3), (3,4,5), (3,4,5) ],
- stabilizer := rec(
- identity := (),
- generators := [ (3,4,5), (2,5,3) ],
- orbit := [ 2, 3, 5, 4 ],
- transversal := [ , (), (2,5,3), (3,4,5), (3,4,5) ],
- stabilizer := rec(
- identity := (),
- generators := [ (3,4,5) ],
- orbit := [ 3, 5, 4 ],
- transversal := [ ,, (), (3,4,5), (3,4,5) ],
- stabilizer := rec(
- identity := (),
- generators := [ ],
- operations := ... ),
- operations := ... ),
- operations := ... ),
- size := 60 ) |
-
- Note that you can not only read this information, you can also modify it.
- However, unless you truly understand what you are doing, we discourage
- you from playing around. All {\GAP} functions assume that the
- information in the domain record is in a consistent state, and everything
- will go wrong if it is not.
-
- | gap> a5.size := 120;
- 120
- gap> Size( ConjugacyClass( a5, (1,2,3,4,5) ) );
- 24 # this is of course wrong |
-
- As was mentioned above, each domain record has an operations record. We
- have already seen that functions such as 'Size' can be applied to various
- types of domains. It is clear that there is no general method that will
- compute the size of all domains efficiently. So 'Size' must somehow
- decide which method to apply to a given domain. The operations record
- makes this possible.
-
- The operations record of a domain <D> is the component with the name
- '<D>.operations', its value is a record. For each function that you can
- apply to <D> this record contains a function that will compute the
- required information (hopefully in an efficient way).
-
- To understand this let us take a look at what happens when we compute
- 'Size( a5 )'. Not much happens. 'Size' simply calls
- 'a5.operations.Size( a5 )'. 'a5.operations.Size' is a function written
- especially for permutation groups. It computes the size of 'a5' and
- returns it. Then 'Size' returns this value.
-
- Actually 'Size' does a little bit more than that. It first tests whether
- 'a5' has the knowledge component 'a5.size'. If this is the case, 'Size'
- simply returns that value. Otherwise it calls 'a5.operations.Size( a5 )'
- to compute the size. 'Size' remembers the result in the knowledge
- component 'a5.size' so that it is readily available the next time 'Size(
- a5 )' is called. The complete definition of 'Size' is as follows.
-
- | Size := function ( D )
- local size;
- if IsSet( D ) then
- size := Length( D );
- elif IsDomain( D ) and IsBound( D.size ) then
- size := D.size;
- elif IsDomain( D ) then
- D.size := D.operations.Size( D );
- size := D.size;
- else
- Error("<D> must be a domain or a set");
- fi;
- return size;
- end; |
-
- Because functions such as 'Size' only dispatch to the functions in the
- operations record, they are called *dispatcher functions*. Almost all
- functions that you call directly are dispatcher functions, and almost all
- functions that do the hard work are components in an operations record.
-
- Which function is called by a dispatcher obviously depends on the domain
- and its operations record (that is the whole point of having an
- operations record). In principle each domain could have its own 'Size'
- function. In practice however, this would require too many functions.
- So different domains share the functions in their operations records,
- usually all domains with the same representation share all their
- operations record functions. For example all permutation groups share
- the same 'Size' function. Because this shared 'Size' function must be
- able to access the information in the domain record to compute the
- correct result, the 'Size' dispatcher function (and all other dispatchers
- as well) pass the domain as first argument
-
- In fact the domains not only have the same functions in their operations
- record, they share the operations record. So for example all permutation
- groups share a common operations record, which is called 'PermGroupOps'.
- This means that changing a function in the operations record for a domain
- <D> in the following way '<D>.operations.<function> \:= <new-function>;'
- will also change this function for all domains of the same type, even
- those that do not yet exist at the moment of the assignment and will only
- be constructed later. This is usually not desirable, since supposedly
- <new-function> uses some special properties of the domain <D> to work
- more efficiently. We suggest therefore that you first make a copy of the
- operations record with '<D>.operations \:= Copy( <D>.operations );' and
- only afterwards do '<D>.operations.<function> \:= <new-function>;'.
-
- If a programmer that implements a new domain <D>, a new type of groups
- say, would have to write all functions applicable to <D>, this would
- require a lot of effort. For example, there are about 120 functions
- applicable to groups. Luckily many of those functions are independent of
- the particular type of groups. For example the following function will
- compute the commutator subgroup of any group, assuming that
- 'TrivialSubgroup', 'Closure', and 'NormalClosure' work. We say that this
- function is *generic*.
-
- | GroupOps.CommutatorSubgroup := function ( U, V )
- local C, u, v, c;
- C := TrivialSubgroup( U );
- for u in U.generators do
- for v in V.generators do
- c := Comm( u, v );
- if not c in C then
- C := Closure( C, c );
- fi;
- od;
- od;
- return NormalClosure( Closure( U, V ), C );
- end; |
-
- So it should be possible to use this function for the new type of groups.
- The mechanism to do this is called *inheritance*. How it works is
- described in "About Defining New Domains", but basically the programmer
- just copies the generic functions from the generic group operations
- record into the operations record for his new type of groups.
-
- The generic functions are also called *default functions*, because they
- are used by default, unless the programmer *overlaid* them for the new
- type of groups.
-
- There is another mechanism through which work can be simplified. It is
- called *delegation*. Suppose that a generic function works for the new
- type of groups, but that some special cases can be handled more
- efficiently for the new type of groups. Then it is possible to handle
- only those cases and delegate the general cases back to the generic
- function. An example of this is the function that computes the orbit of
- a point under a permutation group. If the point is an integer then the
- generic algorithm can be improved by keeping a second list that remembers
- which points have already been seen. The other cases (remember that
- 'Orbit' can also be used for other operations, e.g., the operation of a
- permutation group on pairs of points or the operations on subgroups by
- conjugation) are delegated back to the generic function. How this is
- done can be seen in the following definition.
-
- | PermGroupOps.Orbit := function ( G, d, opr )
- local orb, # orbit of <d> under <G>, result
- max, # largest point moved by the group <G>
- new, # boolean list indicating if a point is new
- gen, # one generator of the group <G>
- pnt, # one point in the orbit <orb>
- img; # image of <pnt> under <gen>
-
- # standard operation
- if opr = OnPoints and IsInt(d) then
-
- # get the largest point <max> moved by the group <G>
- max := 0;
- for gen in G.generators do
- if max < LargestMovedPointPerm(gen) then
- max := LargestMovedPointPerm(gen);
- fi;
- od;
-
- # handle fixpoints
- if not d in [1..max] then
- return [ d ];
- fi;
-
- # start with the singleton orbit
- orb := [ d ];
- new := BlistList( [1..max], [1..max] );
- new[d] := false;
-
- # loop over all points found
- for pnt in orb do
- for gen in G.generators do
- img := pnt ^ gen;
- if new[img] then
- Add( orb, img );
- new[img] := false;
- fi;
- od;
- od;
-
- # other operation, delegate back on default function
- else
- orb := GroupOps.Orbit( G, d, opr );
- fi;
-
- # return the orbit <orb>
- return orb;
- end; |
-
- Inheritance and delegation allow the programmer to implement a new type
- of groups by merely specifying how those groups differ from generic
- groups. This is far less work than having to implement all possible
- functions (apart from the problem that in this case it is very likely
- that the programmer would forget some of the more exotic functions).
-
- To make all this clearer let us look at an extended example to show you
- how a computation in a domain may use default and special functions to
- achieve its goal. Suppose you defined 'g', 'x', and 'y' as follows.
-
- | gap> g := SymmetricGroup( 8 );;
- gap> x := [ (2,7,4)(3,5), (1,2,6)(4,8) ];;
- gap> y := [ (2,5,7)(4,6), (1,5)(3,8,7) ];; |
-
- Now you ask for an element of 'g' that conjugates 'x' to 'y', i.e., a
- permutation on 8 points that takes '(2,7,4)(3,5)' to '(2,5,7)(4,6)' and
- '(1,2,6)(4,8)' to '(1,5)(3,8,7)'. This is done as follows (see
- "RepresentativeOperation" and "Other Operations").
-
- | gap> RepresentativeOperation( g, x, y, OnTuples );
- (1,8)(2,7)(3,4,5,6) |
-
- Now lets look at what happens step for step. First
- 'RepresentativeOperation' is called. After checking the arguments it
- calls the function 'g.operations.RepresentativeOperation', which is the
- function 'SymmetricGroupOps.RepresentativeOperation', passing the
- arguments 'g', 'x', 'y', and 'OnTuples'.
-
- 'SymmetricGroupOps.RepresentativeOperation' handles a lot of cases
- special, but the operation on tuples of permutations is not among them.
- Therefore it delegates this problem to the function that it overlays,
- which is 'PermGroupOps.RepresentativeOperation'.
-
- 'PermGroupOps.RepresentativeOperation' also does not handle this special
- case, and delegates the problem to the function that it overlays, which
- is the default function called 'GroupOps.RepresentativeOperation'.
-
- 'GroupOps.RepresentativeOperation' views this problem as a general tuples
- problem, i.e., it does not care whether the points in the tuples are
- integers or permutations, and decides to solve it one step at a time. So
- first it looks for an element taking '(2,7,4)(3,5)' to '(2,5,7)(4,6)' by
- calling 'RepresentativeOperation( g, (2,7,4)(3,5), (2,5,7)(4,6) )'.
-
- 'RepresentativeOperation' calls 'g.operations.RepresentativeOperation'
- next, which is the function 'SymmetricGroupOps.RepresentativeOperation',
- passing the arguments 'g', '(2,7,4)(3,5)', and '(2,5,7)(4,6)'.
-
- 'SymmetricGroupOps.RepresentativeOperation' can handle this case. It
- *knows* that 'g' contains every permutation on 8 points, so it contains
- '(3,4,7,5,6)', which obviously does what we want, namely it takes 'x[1]'
- to 'y[1]'. We will call this element 't'.
-
- Now 'GroupOps.RepresentativeOperation' (see above) looks for an 's' in
- the stabilizer of 'x[1]' taking 'x[2]' to 'y[2]\^(t\^-1)', since then for
- 'r=s\*t' we have 'x[1]\^r = (x[1]\^s)\^t = x[1]\^t = y[1]' and also
- 'x[2]\^r = (x[2]\^s)\^t = (y[2]\^(t\^-1))\^t = y[2]'. So the next step
- is to compute the stabilizer of 'x[1]' in 'g'. To do this it calls
- 'Stabilizer( g, (2,7,4)(3,5) )'.
-
- 'Stabilizer' calls 'g.operations.Stabilizer', which is
- 'SymmetricGroupOps.Stabilizer', passing the arguments 'g' and
- '(2,7,4)(3,5)'. 'SymmetricGroupOps.Stabilizer' detects that the second
- argument is a permutation, i.e., an element of the group, and calls
- 'Centralizer( g, (2,7,4)(3,5) )'. 'Centralizer' calls the function
- 'g.operations.Centralizer', which is 'SymmetricGroupOps.Centralizer',
- again passing the arguments 'g', '(2,7,4)(3,5)'.
-
- 'SymmetricGroupOps.Centralizer' again *knows* how centralizer in
- symmetric groups look, and after looking at the permutation
- '(2,7,4)(3,5)' sharply for a short while returns the centralizer as
- 'Subgroup( g, [ (1,6), (6,8), (2,7,4), (3,5) ] )', which we will call
- 'c'. Note that 'c' is of course not a symmetric group, therefore
- 'SymmetricGroupOps.Subgroup' gives it 'PermGroupOps' as operations record
- and not 'SymmetricGroupOps'.
-
- As explained above 'GroupOps.RepresentativeOperation' needs an element of
- 'c' taking 'x[2]' ('(1,2,6)(4,8)') to 'y[2]\^(t\^-1)' ('(1,7)(4,6,8)').
- So 'RepresentativeOperation( c, (1,2,6)(4,8), (1,7)(4,6,8) )' is called.
- 'RepresentativeOperation' in turn calls the function
- 'c.operations.RepresentativeOperation', which is, since 'c' is a
- permutation group, the function 'PermGroupOps.RepresentativeOperation',
- passing the arguments 'c', '(1,2,6)(4,8)', and '(1,7)(4,6,8)'.
-
- 'PermGroupOps.RepresentativeOperation' detects that the points are
- permutations and and performs a backtrack search through 'c'. It finds
- and returns '(1,8)(2,4,7)(3,5)', which we call 's'.
-
- Then 'GroupOps.RepresentativeOperation' returns 'r = s\*t =
- (1,8)(2,7)(3,6)(4,5)', and we are done.
-
- In this example you have seen how functions use the structure of their
- domain to solve a problem most efficiently, for example
- 'SymmetricGroupOps.RepresentativeOperation' but also the backtrack search
- in 'PermGroupOps.RepresentativeOperation', how they use other functions,
- for example 'SymmetricGroupOps.Stabilizer' called 'Centralizer', and how
- they delegate cases which they can not handle more efficiently back to
- the function they overlaid, for example
- 'SymmetricGroupOps.RepresentativeOperation' delegated to
- 'PermGroupOps.RepresentativeOperation', which in turn delegated to to the
- function 'GroupOps.RepresentativeOperation'.
-
- If you think this whole mechanism using dispatcher functions and the
- operations record is overly complex let us look at some of the
- alternatives. This is even more technical than the previous part of this
- section so you may want to skip the remainder of this section.
-
- One alternative would be to let the dispatcher know about the various
- types of domains, test which category a domain lies in, and dispatch to
- an appropriate function. Then we would not need an operations record.
- The dispatcher function 'CommutatorSubgroup' would then look as follows.
- Note this is *not* how 'CommutatorSubgroup' is implemented in {\GAP}.
-
- | CommutatorSubgroup := function ( G )
- local C;
- if IsAgGroup( G ) then
- C := CommutatorSubgroupAgGroup( G );
- elif IsMatGroup( G ) then
- C := CommutatorSubgroupMatGroup( G );
- elif IsPermGroup( G ) then
- C := CommutatorSubgroupPermGroup( G );
- elif IsFpGroup( G ) then
- C := CommutatorSubgroupFpGroup( G );
- elif IsFactorGroup( G ) then
- C := CommutatorSubgroupFactorGroup( G );
- elif IsDirectProduct( G ) then
- C := CommutatorSubgroupDirectProduct( G );
- elif IsDirectProductAgGroup( G ) then
- C := CommutatorSubgroupDirectProductAgGroup( G );
- elif IsSubdirectProduct( G ) then
- C := CommutatorSubgroupSubdirectProduct( G );
- elif IsSemidirectProduct( G ) then
- C := CommutatorSubgroupSemidirectProduct( G );
- elif IsWreathProduct( G ) then
- C := CommutatorSubgroupWreathProduct( G );
- elif IsGroup( G ) then
- C := CommutatorSubgroupGroup( G );
- else
- Error("<G> must be a group");
- fi;
- return C;
- end; |
-
- You already see one problem with this approach. The number of cases that
- the dispatcher functions would have to test is simply to large. It is
- even worse for set theoretic functions, because they would have to handle
- all different types of domains (currently about 30).
-
- The other problem arises when a programmer implements a new domain. Then
- he would have to rewrite all dispatchers and add a new case to each.
- Also the probability that the programmer forgets one dispatcher is very
- high.
-
- Another problem is that inheritance becomes more difficult. Instead of
- just copying one operations record the programmer would have to copy each
- function that should be inherited. Again the probability that he forgets
- one is very high.
-
- Another alternative would be to do completely without dispatchers. In
- this case there would be the functions 'CommutatorSugroupAgGroup',
- 'CommutatorSubgroupPermGroup', etc., and it would be your responsibility
- to call the right function. For example to compute the size of a
- permutation group you would call 'SizePermGroup' and to compute the size
- of a coset you would call 'SizeCoset' (or maybe even
- 'SizeCosetPermGroup').
-
- The most obvious problem with this approach is that it is much more
- cumbersome. You would always have to know what kind of domain you are
- working with and which function you would have to call.
-
- Another problem is that writing generic functions would be impossible.
- For example the above generic implementation of 'CommutatorSubgroup'
- could not work, because for a concrete group it would have to call
- 'ClosurePermGroup' or 'ClosureAgGroup' etc.
-
- If generic functions are impossible, inheritance and delegation can not
- be used. Thus for each type of domain all functions must be implemented.
- This is clearly a lot of work, more work than we are willing to do.
-
- So we argue that our mechanism is the easiest possible that serves the
- following two goals. It is reasonably convenient for you to use. It
- allows us to implement a large (and ever increasing) number of different
- types of domains.
-
- This may all sound a lot like object oriented programming to you. This
- is not surprising because we want to solve the same problems that object
- oriented programming tries to solve. Let us briefly discuss the
- similarities and differences to object oriented programming, taking C++
- as an example (because it is probably the widest known object oriented
- programming language nowadays). This discussion is very technical and
- again you may want to skip the remainder of this section.
-
- Let us first recall the problems that the {\GAP} mechanism wants to
- handle.
-
- 1: How can we represent domains in such a way that we can handle
- domains of different type in a common way?
-
- 2: How can we make it possible to allow functions that take domains
- of different type and perform the same operation for those
- domains (but using different methods)?
-
- 3: How can we make it possible that the implementation of a new type
- of domains only requires that one implements what distinguishes
- this new type of domains from domains of an old type (without the
- need to change any old code)?
-
- For object oriented programming the problems are the same, though the
- names used are different. We talk about domains, object oriented
- programming talks about objects, and we talk about categories, object
- oriented programming talks about classes.
-
- 1: How can we represent objects in such a way that we can handle
- objects of different classes in a common way (e.g., declare
- variables that can hold objects of different classes)?
-
- 2: How can we make it possible to allow functions that take objects
- of different classes (with a common base class) and perform the
- same operation for those objects (but using different methods)?
-
- 3: How can we make it possible that the implementation of a new
- class of objects only requires that one implements what
- distinguishes the objects of this new class from the objects of
- an old (base) class (without the need to change any old code)?
-
- In {\GAP} the first problem is solved by representing all domains using
- records. Actually because {\GAP} does not perform strong static type
- checking each variable can hold objects of arbitrary type, so it would
- even be possible to represent some domains using lists or something else.
- But then, where would we put the operations record?
-
- C++ does something similar. Objects are represented by 'struct'-s or
- pointers to structures. C++ then allows that a pointer to an object of a
- base class actually holds a pointer to an object of a derived class.
-
- In {\GAP} the second problem is solved by the dispatchers and the
- operations record. The operations record of a given domain holds the
- methods that should be applied to that domain, and the dispatcher does
- nothing but call this method.
-
- In C++ it is again very similar. The difference is that the dispatcher
- only exists conceptually. If the compiler can already decide which
- method will be executed by a given call to the dispatcher it directly
- calls this function. Otherwise (for virtual functions that may be
- overlaid in derived classes) it basically inlines the dispatcher. This
- inlined code then dispatches through the so--called *virtual method
- table* ('vmt'). Note that this virtual method table is the same as the
- operations record, except that it is a table and not a record.
-
- In {\GAP} the third problem is solved by inheritance and delegation. To
- inherit functions you simply copy them from the operations record of
- domains of the old category to the operations record of domains of the
- new category. Delegation to a method of a larger category is done by
- calling '<super-category-operations-record>.<function>'
-
- C++ also supports inheritance and delegation. If you derive a class from
- a base class, you copy the methods from the base class to the derived
- class. Again this copying is only done conceptually in C++. Delegation
- is done by calling a qualified function '<base-class>\:\:<function>'.
-
- Now that we have seen the similarities, let us discuss the differences.
-
- The first differences is that {\GAP} is not an object oriented
- programming language. We only programmed the library in an object
- oriented way using very few features of the language (basically all we
- need is that {\GAP} has no strong static type checking, that records can
- hold functions, and that records can grow dynamically). Following
- Stroustrup\'s convention we say that the {\GAP} language only *enables*
- object oriented programming, but does not *support* it.
-
- The second difference is that C++ adds a mechanism to support data
- hiding. That means that fields of a 'struct' can be private. Those
- fields can only be accessed by the functions belonging to this class (and
- 'friend' functions). This is not possible in {\GAP}. Every field of
- every domain is accessible. This means that you can also modify those
- fields, with probably catastrophic results.
-
- The final difference has to do with the relation between categories and
- their domains and classes and their objects. In {\GAP} a category is a
- set of domains, thus we say that a domain is an element of a category.
- In C++ (and most other object oriented programming languages) a class is
- a prototype for its objects, thus we say that an object is an instance of
- the class. We believe that {\GAP}\'s relation better resembles the
- mathematical model.
-
- In this section you have seen that domains are represented by domain
- records, and that you can therefore access all information that {\GAP}
- has about a certain domain. The following sections in this chapter
- discuss how new domains can be created (see "About Defining New Domains",
- and "About Defining New Parametrized Domains") and how you can even
- define a new type of elements (see "About Defining New Group Elements").
-
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \Section{About Defining New Domains}
-
- In this section we will show how one can add a new domain to {\GAP}. All
- domains are implemented in the library in this way. We will use the ring
- of Gaussian integers as our example.
-
- Note that everything defined here is already in the library file
- 'LIBNAME/\"gaussian.g\"', so there is no need for you to type it in. You
- may however like to make a copy of this file and modify it.
-
- The elements of this domain are already available, because Gaussian
- integers are just a special case of cyclotomic numbers. As is described
- in chapter "Cyclotomics" 'E(4)' is {\GAP}\'s name for the complex root of
- -1. So all Gaussian integers can be represented as '<a> + <b>\*E(4)',
- where <a> and <b> are ordinary integers.
-
- As was already mentioned each domain is represented by a record. So we
- create a record to represent the Gaussian integers, which we call
- 'GaussianIntegers'.
-
- | GaussianIntegers := rec(); |
-
- The first components that this record must have are those that identify
- this record as a record denoting a ring domain. Those components are
- called the *category components*.
-
- | GaussianIntegers.isDomain := true;
- GaussianIntegers.isRing := true; |
-
- The next components are those that uniquely identify this ring. For
- rings this must be 'generators', 'zero', and 'one'. Those components are
- called the *identification components* of the domain record. We also
- assign a *name component*. This name will be printed when the domain is
- printed.
-
- | GaussianIntegers.generators := [ 1, E(4) ];
- GaussianIntegers.zero := 0;
- GaussianIntegers.one := 1;
- GaussianIntegers.name := "GaussianIntegers"; |
-
- Next we enter some components that represent knowledge that we have about
- this domain. Those components are called the *knowledge components*. In
- our example we know that the Gaussian integers form a infinite,
- commutative, integral, Euclidean ring, which has an unique factorization
- property, with the four units 1, -1, 'E(4)', and '-E(4)'.
-
- | GaussianIntegers.size := "infinity";
- GaussianIntegers.isFinite := false;
- GaussianIntegers.isCommutativeRing := true;
- GaussianIntegers.isIntegralRing := true;
- GaussianIntegers.isUniqueFactorizationRing := true;
- GaussianIntegers.isEuclideanRing := true;
- GaussianIntegers.units := [1,-1,E(4),-E(4)]; |
-
- This was the easy part of this example. Now we have to add an
- *operations record* to the domain record. This operations record
- ('GaussianIntegers.operations') shall contain functions that implement
- all the functions mentioned in chapter "Rings", e.g., 'DefaultRing',
- 'IsCommutativeRing', 'Gcd', or 'Mod'.
-
- Luckily we do not have to implement all this functions. The first class
- of functions that we need not implement are those that can simply derive
- the result from the knowledge components. E.g., 'IsCommutativeRing'
- looks for the knowledge component 'isCommutativeRing', finds it and
- returns this value. So 'GaussianIntegers.operations.IsCommutativeRing'
- is never called.
-
- | gap> IsCommutativeRing( GaussianIntegers );
- true
- gap> Units( GaussianIntegers );
- [ 1, -1, E(4), -E(4) ] |
-
- The second class of functions that we need not implement are those for
- which there is a general algorithm that can be applied for all rings.
- For example once we can do a division with remainder (which we will have
- to implement) we can use the general Euclidean algorithm to compute the
- greatest common divisor of elements.
-
- So the question is, how do we get those general functions into our
- operations record. This is very simple, we just initialize the
- operations record as a copy of the record 'RingOps', which contains all
- those general functions. We say that 'GaussianIntegers.operations'
- *inherits* the general functions from 'RingOps'.
-
- | GaussianIntegersOps := Copy( RingOps );
- GaussianIntegers.operations := GaussianIntegersOps; |
-
- Now we can already test whether a Gaussian integer is a unit or not.
- This is because the default function inherited from 'RingOps' tests
- whether the knowledge component 'units' is present, and if it returns
- 'true' if the element is in that list and 'false' otherwise.
-
- | gap> IsUnit( GaussianIntegers, E(4) );
- true
- gap> IsUnit( GaussianIntegers, 1 + E(4) );
- false |
-
- So now we have to add those functions whose result can not (easily) be
- derived from the knowledge components and that we can not inherit from
- 'RingOps'.
-
- The first such function is the membership test. This function must test
- whether an object is an element of the domain 'GaussianIntegers'.
- 'IsCycInt(<x>)' tests whether <x> is a cyclotomic integer and
- 'NofCyc(<x>)' returns the smallest <n> such that the cyclotomic <x> can
- be written as a linear combination of powers of the primitive <n>-th root
- of unity 'E(<n>)'. If 'E(<x>)' returns 1, <x> is an ordinary rational
- number.
-
- | GaussianIntegersOps.'in' := function ( x, GaussInt )
- return IsCycInt( x ) and ((NofCyc( x ) = 1 or NofCyc( x ) = 4);
- end; |
-
- Note that the second argument 'GaussInt' is not used in the function.
- Whenever this function is called, the second argument must be
- 'GaussianIntegers', because 'GaussianIntegers' is the only domain that
- has this particular function in its operations record. This also happens
- for most other functions that we will write. This argument can not be
- dropped though, because there are other domains that share a common 'in'
- function, for example all permutation groups have the same 'in' function.
- If the operator 'in' would not pass the second argument, this function
- could not know for which permutation group it should perform the
- membership test.
-
- So now we can test whether a certain object is a Gaussian integer or not.
-
- | gap> E(4) in GaussianIntegers;
- true
- gap> 1/2 in GaussianIntegers;
- false
- gap> GaussianIntegers in GaussianIntegers;
- false |
-
- Another function that is just as easy is the function 'Random' that
- should return a random Gaussian integer.
-
- | GaussianIntegersOps.Random := function ( GaussInt )
- return Random( Integers ) + Random( Integers ) * E( 4 );
- end; |
-
- Note that actually a 'Random' function was inherited from 'RingOps'. But
- this function can not be used. It tries to construct the sorted list of
- all elements of the domain and then picks a random element from that
- list. Therefor this function is only applicable for finite domains, and
- can not be used for 'GaussianIntegers'. So we *overlay* this default
- function by simply putting another function in the operations record.
-
- Now we finally come to more interesting stuff. The function 'Quotient'
- should return the quotient of its two arguments <x> and <y>. If the
- quotient does not exist in the ring (i.e., if it is a proper Gaussian
- rational), it must return 'false'. (Without this last requirement we
- could do without the 'Quotient' function and always simply use the '/'
- operator.)
-
- | GaussianIntegersOps.Quotient := function ( GaussInt, x, y )
- local q;
- q := x / y;
- if not IsCycInt( q ) then
- q := false;
- fi;
- return q;
- end; |
-
- The next function is used to test if two elements are associate in the
- ring of Gaussian integers. In fact we need not implement this because
- the function that we inherit from 'RingOps' will do fine. The following
- function is a little bit faster though that the inherited one.
-
- | GaussianIntegersOps.IsAssociated := function ( GaussInt, x, y )
- return x = y or x = -y or x = E(4)*y or x = -E(4)*y;
- end; |
-
- We must however implement the function 'StandardAssociate'. It should
- return an associate that is in some way standard. That means, whenever
- we apply 'StandardAssociate' to two associated elements we must obtain
- the same value. For Gaussian integers we return that associate that lies
- in the first quadrant of the complex plane. That is, the result is that
- associated element that has positive real part and nonnegative imaginary
- part. 0 is its own standard associate of course. Note that this is a
- generalization of the absolute value function, which is
- 'StandardAssociate' for the integers. The reason that we must implement
- 'StandardAssociate' is of course that there is no general way to compute
- a standard associate for an arbitrary ring, there is not even a standard
- way to define this!
-
- | GaussianIntegersOps.StandardAssociate := function ( GaussInt, x )
- if IsRat(x) and 0 <= x then
- return x;
- elif IsRat(x) then
- return -x;
- elif 0 < COEFFSCYC(x)[1] and 0 <= COEFFSCYC(x)[2] then
- return x;
- elif COEFFSCYC(x)[1] <= 0 and 0 < COEFFSCYC(x)[2] then
- return - E(4) * x;
- elif COEFFSCYC(x)[1] < 0 and COEFFSCYC(x)[2] <= 0 then
- return - x;
- else
- return E(4) * x;
- fi;
- end; |
-
- Note that 'COEFFSCYC' is an internal function that returns the
- coefficients of a Gaussian integer (actually of an arbitrary cyclotomic)
- as a list.
-
- Now we have implemented all functions that are necessary to view the
- Gaussian integers plainly as a ring. Of course there is not much we can
- do with such a plain ring, we can compute with its elements and can do a
- few things that are related to the group of units.
-
- | gap> Quotient( GaussianIntegers, 2, 1+E(4) );
- 1-E(4)
- gap> Quotient( GaussianIntegers, 3, 1+E(4) );
- false
- gap> IsAssociated( GaussianIntegers, 1+E(4), 1-E(4) );
- true
- gap> StandardAssociate( GaussianIntegers, 3 - E(4) );
- 1+3*E(4) |
-
- The remaining functions are related to the fact that the Gaussian
- integers are an Euclidean ring (and thus also a unique factorization
- ring).
-
- The first such function is 'EuclideanDegree'. In our example the
- Euclidean degree of a Gaussian integer is of course simply its norm.
- Just as with 'StandardAssociate' we must implement this function because
- there is no general way to compute the Euclidean degree for an arbitrary
- Euclidean ring. The function itself is again very simple. The Euclidean
- degree of a Gaussian integer <x> is the product of <x> with its complex
- conjugate, which is denoted in {\GAP} by 'GaloisCyc( <x>, -1 )'.
-
- | GaussianIntegersOps.EuclideanDegree := function ( GaussInt, x )
- return x * GaloisCyc( x, -1 );
- end; |
-
- Once we have defined the Euclidean degree we now want to implement the
- 'Mod' function that gives us the remainder of a division.
-
- | GaussianIntegersOps.Mod := function ( GaussInt, x, y )
- return x - RoundCyc( x/y ) * y;
- end; |
-
- Note that in the definition of 'Mod' we must use the function 'RoundCyc',
- which views the Gaussian rational '<x>/<y>' as a point in the complex
- plane and returns the point of the lattice spanned by 1 and 'E(4)'
- *closest* to the point '<x>/<y>'. If we would truncate towards the
- origin instead (this is done by the function 'IntCyc') we could not
- guarantee that the result of 'Mod' always has Euclidean degree less than
- the Euclidean degree of <y> as the following example shows.
-
- | gap> x := 2 - E(4);; EuclideanDegree( GaussianIntegers, x );
- 5
- gap> y := 2 + E(4);; EuclideanDegree( GaussianIntegers, y );
- 5
- gap> q := x / y; q := IntCyc( q );
- 3/5-4/5*E(4)
- 0
- gap> EuclideanDegree( GaussianIntegers, x - q * y );
- 5 |
-
- Now that we have implemented the 'Mod' function we can compute greatest
- common divisors in the ring of Gaussian integers. This is because we
- have inherited from 'RingOps' the general function 'Gcd' that computes
- the greatest common divisor using Euclid\'s algorithm, which only uses
- 'Mod' (and 'StandardAssociate' to return the result in a normal form).
- Of course we can now also compute least common multiples, because that
- only uses 'Gcd'.
-
- | gap> Gcd( GaussianIntegers, 2, 5 - E(4) );
- 1+E(4)
- gap> Lcm( GaussianIntegers, 2, 5 - E(4) );
- 6+4*E(4) |
-
- Since the Gaussian integers are a Euclidean ring they are also a unique
- factorization ring. The next two functions implement the necessary
- operations. The first is the test for primality. A rational integer is
- a prime in the ring of Gaussian integers if and only if it is congruent
- to 3 modulo 4 (the other rational integer primes split into two
- irreducibles), and a Gaussian integer that is not a rational integer is a
- prime if its norm is a rational integer prime.
-
- | GaussianIntegersOps.IsPrime := function ( GaussInt, x )
- if IsInt( x ) then
- return x mod 4 = 3 and IsPrimeInt( x );
- else
- return IsPrimeInt( x * GaloisCyc( x, -1 ) );
- fi;
- end; |
-
- The factorization is based on the same observation. We compute the
- Euclidean degree of the number that we want to factor, and factor this
- rational integer. Then for every rational integer prime that is
- congruent to 3 modulo 4 we get one factor, and we split the other
- rational integer primes using the function 'TwoSquares' and test which
- irreducible divides.
-
- | GaussianIntegersOps.Factors := function ( GaussInt, x )
- local facs, # factors (result)
- prm, # prime factors of the norm
- tsq; # representation of prm as $x^2 + y^2$
-
- # handle trivial cases
- if x in [ 0, 1, -1, E(4), -E(4) ] then
- return [ x ];
- fi;
-
- # loop over all factors of the norm of x
- facs := [];
- for prm in Set( FactorsInt( EuclideanDegree( x ) ) ) do
-
- # $p=2$ and primes $p=1$ mod 4 split according to $p=x^2+y^2$
- if prm = 2 or prm mod 4 = 1 then
- tsq := TwoSquares( prm );
- while IsCycInt( x / (tsq[1]+tsq[2]*E(4)) ) do
- Add( facs, (tsq[1]+tsq[2]*E(4)) );
- x := x / (tsq[1]+tsq[2]*E(4));
- od;
- while IsCycInt( x / (tsq[2]+tsq[1]*E(4)) ) do
- Add( facs, (tsq[2]+tsq[1]*E(4)) );
- x := x / (tsq[2]+tsq[1]*E(4));
- od;
-
- # primes $p = 3$ mod 4 stay prime
- else
- while IsCycInt( x / prm ) do
- Add( facs, prm );
- x := x / prm;
- od;
- fi;
-
- od;
-
- # the first factor takes the unit
- facs[1] := x * facs[1];
-
- # return the result
- return facs;
- end; |
-
- So now we can factorize numbers in the ring of Gaussian integers.
-
- | gap> Factors( GaussianIntegers, 10 );
- [ -1-E(4), 1+E(4), 1+2*E(4), 2+E(4) ]
- gap> Factors( GaussianIntegers, 103 );
- [ 103 ] |
-
- Now we have written all the functions for the operations record that
- implement the operations. We would like one more thing however. Namely
- that we can simply write 'Gcd( 2, 5 - E(4) )' without having to specify
- 'GaussianIntegers' as first argument. 'Gcd' and the other functions
- should be clever enough to find out that the arguments are Gaussian
- integers and call 'GaussianIntegers.operations.Gcd' automatically.
-
- To do this we must first understand what happens when 'Gcd' is called
- without a ring as first argument. For an example suppose that we have
- called 'Gcd( 66, 123 )' (and want to compute the gcd over the integers).
-
- First 'Gcd' calls 'DefaultRing( [ 66, 123 ] )', to obtain a ring that
- contains 66 and 123. 'DefaultRing' then calls 'Domain( [ 66, 123 ] )' to
- obtain a domain, which need not be a ring, that contains 66 and 123.
- 'Domain' is the *only* function in the whole {\GAP} library that knows
- about the various types of elements. So it looks at its argument and
- decides to return the domain 'Integers' (which is in fact already a ring,
- but it could in principle also return 'Rationals'). 'DefaultRing' now
- calls 'Integers.operations.DefaultRing( [ 66, 123 ] )' and expects a ring
- in which the requested gcd computation can be performed.
- 'Integers.operations.DefaultRing( [ 66, 123 ] )' also returns 'Integers'.
- So 'DefaultRing' returns 'Integers' to 'Gcd' and 'Gcd' finally calls
- 'Integers.operations.Gcd( Integers, 66, 123 )'.
-
- So the first thing we must do is to tell 'Domain' about Gaussian
- integers. We do this by extending 'Domain' with the two lines
-
- | elif ForAll( elms, IsGaussInt ) then
- return GaussianIntegers; |
-
- so that it now looks as follows.
-
- | Domain := function ( elms )
- local elm;
- if ForAll( elms, IsInt ) then
- return Integers;
- elif ForAll( elms, IsRat ) then
- return Rationals;
- elif ForAll( elms, IsFFE ) then
- return FiniteFieldElements;
- elif ForAll( elms, IsPerm ) then
- return Permutations;
- elif ForAll( elms, IsMat ) then
- return Matrices;
- elif ForAll( elms, IsWord ) then
- return Words;
- elif ForAll( elms, IsAgWord ) then
- return AgWords;
- elif ForAll( elms, IsGaussInt ) then
- return GaussianIntegers;
- elif ForAll( elms, IsCyc ) then
- return Cyclotomics;
- else
- for elm in elms do
- if IsRec(elm) and IsBound(elm.domain)
- and ForAll( elms, l -> l in elm.domain )
- then
- return elm.domain;
- fi;
- od;
- Error("sorry, the elements lie in no common domain");
- fi;
- end; |
-
- Of course we must define a function 'IsGaussInt', otherwise this could
- not possibly work. This function is similar to the membership test we
- already defined above.
-
- | IsGaussInt := function ( x )
- return IsCycInt( x ) and ((NofCyc( x ) = 1 or NofCyc( x ) = 4);
- end; |
-
-
- Then we must define a function 'DefaultRing' for the Gaussian integers
- that does nothing but return 'GaussianIntegers'.
-
- | GaussianIntegersOps.DefaultRing := function ( elms )
- return GaussianIntegers;
- end; |
-
- Now we can call 'Gcd' with two Gaussian integers without having to pass
- 'GaussianIntegers' as first argument.
-
- | gap> Gcd( 2, 5 - E(4) );
- 1+E(4) |
-
- Of course {\GAP} can not read your mind. In the following example it
- assumes that you want to factor 10 over the ring of integers, not over
- the ring of Gaussian integers (because 'Integers' is the default ring
- containing 10). So if you want to factor a rational integer over the
- ring of Gaussian integers you must pass 'GaussianIntegers' as first
- argument.
-
- | gap> Factors( 10 );
- [ 2, 5 ]
- gap> Factors( GaussianIntegers, 10 );
- [ -1-E(4), 1+E(4), 1+2*E(4), 2+E(4) ] |
-
- This concludes our example. In the file 'LIBNAME/\"gaussian.g\"' you
- will also find the definition of the field of Gaussian rationals. It is
- so similar to the above definition that there is no point in discussing
- it here. The next section shows you what further considerations are
- necessary when implementing a type of parametrized domains (demonstrated
- by implementing full symmetric permutation groups). For further details
- see chapter "Gaussians" for a description of the Gaussian integers and
- rationals and chapter "Rings" for a list of all functions applicable to
- rings.
-
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \Section{About Defining New Parametrized Domains}
-
- In this section we will show you an example that is slightly more complex
- than the example in the previous section. Namely we will demonstrate how
- one can implement parametrized domains. As an example we will implement
- symmetric permutation groups. This works similar to the implementation
- of a single domain. Therefore we can be very brief. Of course you
- should have read the previous section.
-
- Note that everything defined here is already in the file
- 'GRPNAME/\"permgrp.grp\"', so there is no need for you to type it in.
- You may however like to make a copy of this file and modify it.
-
- In the example of the previous section we simply had a variable
- ('GaussianIntegers'), whose value was the domain. This can not work in
- this example, because there is not *one* symmetric permutation group.
- The solution is obvious. We simply define a function that takes the
- degree and returns the symmetric permutation group of this degree (as a
- domain).
-
- | SymmetricPermGroup := function ( n )
- local G; # symmetric group on <n> points, result
-
- # make the group generated by (1,n), (2,n), .., (n-1,n)
- G := Group( List( [1..n-1], i -> (i,n) ), () );
- G.degree := n;
-
- # give it the correct operations record
- G.operations := SymmetricPermGroupOps;
-
- # return the symmetric group
- return G;
- end; |
-
- The key is of course to give the domains returned by 'SymmetricPermGroup'
- a new operations record. This operations record will hold functions that
- are written especially for symmetric permutation groups. Note that all
- symmetric groups created by 'SymmetricPermGroup' share one operations
- record.
-
- Just as we inherited in the example in the previous section from the
- operations record 'RingOps', here we can inherit from the operations
- record 'PermGroupOps' (after all, each symmetric permutation group is
- also a permutation group).
-
- | SymmetricPermGroupOps := Copy( PermGroupOps ); |
-
- We will now overlay some of the functions in this operations record with
- new functions that make use of the fact that the domain is a full
- symmetric permutation group. The first function that does this is the
- membership test function.
-
- | SymmetricPermGroupOps.'in' := function ( g, G )
- return IsPerm( g )
- and ( g = ()
- or LargestMovedPointPerm( g ) <= G.degree);
- end; |
-
- The most important knowledge for a permutation group is a base and a
- strong generating set with respect to that base. It is not important
- that you understand at this point what this is mathematically. The
- important point here is that such a strong generating set with respect to
- an appropriate base is used by many generic permutation group functions,
- most of which we inherit for symmetric permutation groups. Therefore it
- is important that we are able to compute a strong generating set as fast
- as possible. Luckily it is possible to simply write down such a strong
- generating set for a full symmetric group. This is done by the following
- function.
-
- | SymmetricPermGroupOps.MakeStabChain := function ( G, base )
- local sgs, # strong generating system of <G> wrt. <base>
- last; # last point of the base
-
- # remove all unwanted points from the base
- base := Filtered( base, i -> i <= G.degree );
-
- # extend the base with those points not already in the base
- base := Concatenation( base, Difference( [1..G.degree], base ) );
-
- # take the last point
- last := base[ Length(base) ];
-
- # make the strong generating set
- sgs := List( [1..Length(base)-1], i -> ( base[i], last ) );
-
- # make the stabilizer chain
- MakeStabChainStrongGenerators( G, base, sgs );
- end; |
-
- One of the things that are very easy for symmetric groups is the
- computation of centralizers of elements. The next function does this.
- Again it is not important that you understand this mathematically. The
- centralizer of an element <g> in the symmetric group is generated by the
- cycles <c> of <g> and an element <x> for each pair of cycles of <g> of
- the same length that maps one cycle to the other.
-
- | SymmetricPermGroupOps.Centralizer := function ( G, g )
- local C, # centralizer of <g> in <G>, result
- sgs, # strong generating set of <C>
- gen, # one generator in <sgs>
- cycles, # cycles of <g>
- cycle, # one cycle from <cycles>
- lasts, # '<lasts>[<l>]' is the last cycle of length <l>
- last, # one cycle from <lasts>
- i; # loop variable
-
- # handle special case
- if IsPerm( g ) and g in G then
-
- # start with the empty strong generating system
- sgs := [];
-
- # compute the cycles and find for each length the last one
- cycles := Cycles( g, [1..G.degree] );
- lasts := [];
- for cycle in cycles do
- lasts[Length(cycle)] := cycle;
- od;
-
- # loop over the cycles
- for cycle in cycles do
-
- # add that cycle itself to the strong generators
- if Length( cycle ) <> 1 then
- gen := [1..G.degree];
- for i in [1..Length(cycle)-1] do
- gen[cycle[i]] := cycle[i+1];
- od;
- gen[cycle[Length(cycle)]] := cycle[1];
- gen := PermList( gen );
- Add( sgs, gen );
- fi;
-
- # and it can be mapped to the last cycle of this length
- if cycle <> lasts[ Length(cycle) ] then
- last := lasts[ Length(cycle) ];
- gen := [1..G.degree];
- for i in [1..Length(cycle)] do
- gen[cycle[i]] := last[i];
- gen[last[i]] := cycle[i];
- od;
- gen := PermList( gen );
- Add( sgs, gen );
- fi;
-
- od;
-
- # make the centralizer
- C := Subgroup( G, sgs );
-
- # make the stabilizer chain
- MakeStabChainStrongGenerators( C, [1..G.degree], sgs );
-
- # delegate general case
- else
- C := PermGroupOps.Centralizer( G, g );
- fi;
-
- # return the centralizer
- return C;
- end; |
-
- Note that the definition 'C \:= Subgroup( G, sgs );' defines a subgroup
- of a symmetric permutation group. But this subgroup is usually not a
- full symmetric permutation group itself. Thus 'C' must not have the
- operations record 'SymmetricPermGroupOps', instead it should have the
- operations record 'PermGroupOps'. And indeed 'C' will have this
- operations record. This is because 'Subgroup' calls
- '<G>.operations.Subgroup', and we inherited this function from
- 'PermGroupOps'.
-
- Note also that we only handle one special case in the function above.
- Namely the computation of a centralizer of a single element. This
- function can also be called to compute the centralizer of a whole
- subgroup. In this case 'SymmetricPermGroupOps.Centralizer' simply
- *delegates* the problem by calling 'PermGroupOps.Centralizer'.
-
- The next function computes the conjugacy classes of elements in a
- symmetric group. This is very easy, because two elements are conjugated
- in a symmetric group when they have the same cycle structure. Thus we
- can simply compute the partitions of the degree, and for each degree we
- get one conjugacy class.
-
- | SymmetricPermGroupOps.ConjugacyClasses := function ( G )
- local classes, # conjugacy classes of <G>, result
- prt, # partition of <G>
- sum, # partial sum of the entries in <prt>
- rep, # representative of a conjugacy class of <G>
- i; # loop variable
-
- # loop over the partitions
- classes := [];
- for prt in Partitions( G.degree ) do
-
- # compute the representative of the conjugacy class
- rep := [2..G.degree];
- sum := 1;
- for i in prt do
- rep[sum+i-1] := sum;
- sum := sum + i;
- od;
- rep := PermList( rep );
-
- # add the new class to the list of classes
- Add( classes, ConjugacyClass( G, rep ) );
-
- od;
-
- # return the classes
- return classes;
- end; |
-
- This concludes this example. You have seen that the implementation of a
- parametrized domain is not much more difficult than the implementation of
- a single domain. You have also seen how functions that overlay generic
- functions may delegate problems back to the generic function. The
- library file for symmetric permutation groups contain some more functions
- for symmetric permutation groups.
-
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- \Section{About Defining New Group Elements}
-
- In this section we will show how one can add a new type of group elements
- to {\GAP}. A lot of group elements in {\GAP} are implemented this way,
- for example elements of generic factor groups, or elements of generic
- direct products.
-
- We will use prime residue classes modulo an integer as our example. They
- have the advantage that the arithmetic is very simple, so that we can
- concentrate on the implementation without being carried away by
- mathematical details.
-
- Note that everything we define is already in the library in the file
- 'LIBNAME/\"numtheor.g\"', so there is no need for you to type it in. You
- may however like to make a copy of this file and modify it.
-
- We will represent residue classes by records. This is absolutely
- typical, all group elements not built into the {\GAP} kernel are realized
- by records.
-
- To distinguish records representing residue classes from other records we
- require that residue class records have a component with the name
- 'isResidueClass' and the value 'true'. We also require that they have a
- component with the name 'isGroupElement' and again the value 'true'.
- Those two components are called the tag components.
-
- Next each residue class record must of course have components that tell
- us which residue class this record represents. The component with the
- name 'representative' contains the smallest nonnegative element of the
- residue class. The component with the name 'modulus' contains the
- modulus. Those two components are called the identifying components.
-
- Finally each residue class record must have a component with the name
- 'operations' that contains an appropriate operations record (see below).
- In this way we can make use of the possibility to define operations for
- records (see "Comparisons of Records" and "Operations for Records").
-
- Below is an example of a residue class record.
-
- | r13mod43 := rec(
- isGroupElement := true,
- isResidueClass := true,
- representative := 13,
- modulus := 43,
- domain := GroupElements,
- operations := ResidueClassOps ) |
-
- The first function that we have to write is very simple. Its only task
- is to test whether an object is a residue class. It does this by testing
- for the tag component 'isResidueClass'.
-
- | IsResidueClass := function ( obj )
- return IsRec( obj )
- and IsBound( obj.isResidueClass )
- and obj.isResidueClass;
- end; |
-
- Our next function takes a representative and a modulus and constructs a
- new residue class. Again this is not very difficult.
-
- | ResidueClass := function ( representative, modulus )
- local res;
- res := rec();
- res.isGroupElement := true;
- res.isResidueClass := true;
- res.representative := representative mod modulus;
- res.modulus := modulus;
- res.domain := GroupElements;
- res.operations := ResidueClassOps;
- return res;
- end; |
-
- Now we have to define the operations record for residue classes.
- Remember that this record contains a function for each binary operation,
- which is called to evaluate such a binary operation (see "Comparisons of
- Records" and "Operations for Records"). The operations '=', '\<', '\*',
- '/', 'mod', '\^', 'Comm', and 'Order' are the ones that are applicable to
- all group elements. The meaning of those operations for group elements
- is described in "Comparisons of Group Elements" and "Operations for Group
- Elements".
-
- Luckily we do not have to define everything. Instead we can inherit a
- lot of those functions from generic group elements. For example, for all
- group elements '<g>/<h>' should be equivalent to '<g>\*<h>\^-1'. So the
- function for '/' could simply be 'function(g,h) return g\*h\^-1; end'.
- Note that this function can be applied to all group elements,
- independently of their type, because all the dependencies are in '\*' and
- '\^'.
-
- The operations record 'GroupElementOps' contains such functions that can
- be used by all types of group elements. Note that there is no element
- that has 'GroupElementsOps' as its operations record. This is
- impossible, because there is for example no generic method to multiply or
- invert group elements. Thus 'GroupElementsOps' is only used to inherit
- general methods as is done below.
-
- | ResidueClassOps := Copy( GroupElementOps ); |
-
- Note that the copy is necessary, otherwise the following assignments
- would not only change 'ResidueClassOps' but also 'GroupElementOps'.
-
- The first function we are implementing is the equality comparison. The
- required operation is described simply enough. '=' should evaluate to
- 'true' if the operands are equal and 'false' otherwise. Two residue
- classes are of course equal if they have the same representative and the
- same modulus. One complication is that when this function is called
- either operand may not be a residue class. Of course at least one must
- be a residue class otherwise this function would not have been called at
- all.
-
- | ResidueClassOps.'=' := function ( l, r )
- local isEql;
- if IsResidueClass( l ) then
- if IsResidueClass( r ) then
- isEql := l.representative = r.representative
- and l.modulus = r.modulus;
- else
- isEql := false;
- fi;
- else
- if IsResidueClass( r ) then
- isEql := false;
- else
- Error("panic, neither <l> nor <r> is a residue class");
- fi;
- fi;
- return isEql;
- end; |
-
- Note that the quotes around the equal sign '=' are necessary, otherwise
- it would not be taken as a record component name, as required, but as the
- symbol for equality, which must not appear at this place.
-
- Note that we do not have to implement a function for the inequality
- operator '\<>', because it is in the {\GAP} kernel implemented by the
- equivalence '<l> \<> <r>' is 'not <l> = <r>'.
-
- The next operation is the comparison. We define that one residue class
- is smaller than another residue class if either it has a smaller modulus
- or, if the moduli are equal, it has a smaller representative. We must
- also implement comparisons with other objects.
-
- | ResidueClassOps.'<' := function ( l, r )
- local isLess;
- if IsResidueClass( l ) then
- if IsResidueClass( r ) then
- isLess := l.representative < r.representative
- or (l.representative = r.representative
- and l.modulus < r.modulus);
- else
- isLess := not IsInt( r ) and not IsRat( r )
- and not IsCyc( r ) and not IsPerm( r )
- and not IsWord( r ) and not IsAgWord( r );
- fi;
- else
- if IsResidueClass( r ) then
- isLess := IsInt( l ) or IsRat( l )
- or IsCyc( l ) or IsPerm( l )
- or IsWord( l ) or IsAgWord( l );
- else
- Error("panic, neither <l> nor <r> is a residue class");
- fi;
- fi;
- return isLess;
- end; |
-
- The next operation that we must implement is the multiplication '\*'.
- This function is quite complex because it must handle several different
- tasks. To make its implementation easier to understand we will start
- with a very simple--minded one, which only multiplies residue classes,
- and extend it in the following paragraphs.
-
- | ResidueClassOps.'*' := function ( l, r )
- local prd; # product of <l> and <r>, result
- if IsResidueClass( l ) then
- if IsResidueClass( r ) then
- if l.modulus <> r.modulus then
- Error("<l> and <r> must have the same modulus");
- fi;
- prd := ResidueClass(
- l.representative * r.representative,
- l.modulus );
- else
- Error("product of <l> and <r> must be defined");
- fi;
- else
- if IsResidueClass( r ) then
- Error("product of <l> and <r> must be defined");
- else
- Error("panic, neither <l> nor <r> is a residue class");
- fi;
- fi;
- return prd;
- end; |
-
- This function correctly multiplies residue classes, but there are other
- products that must be implemented. First every group element can be
- multiplied with a list of group elements, and the result shall be the
- list of products (see "Operations for Group Elements" and "Operations for
- Lists"). In such a case the above function would only signal an error,
- which is not acceptable. Therefore we must extend this definition.
-
- | ResidueClassOps.'*' := function ( l, r )
- local prd; # product of <l> and <r>, result
- if IsResidueClass( l ) then
- if IsResidueClass( r ) then
- if l.modulus <> r.modulus then
- Error( "<l> and <r> must have the same modulus" );
- fi;
- prd := ResidueClass(
- l.representative * r.representative,
- l.modulus );
- elif IsList( r ) then
- prd := List( r, x -> l * x );
- else
- Error("product of <l> and <r> must be defined");
- fi;
- elif IsList( l ) then
- if IsResidueClass( r ) then
- prd := List( l, x -> x * r );
- else
- Error("panic: neither <l> nor <r> is a residue class");
- fi;
- else
- if IsResidueClass( r ) then
- Error( "product of <l> and <r> must be defined" );
- else
- Error("panic, neither <l> nor <r> is a residue class");
- fi;
- fi;
- return prd;
- end; |
-
- This function is almost complete. However it is also allowed to multiply
- a group element with a subgroup and the result shall be a coset (see
- "RightCoset"). The operations record of subgroups, which are of course
- also represented by records (see "Group Records"), contains a function
- that constructs such a coset. The problem is that in an expression like
- '<subgroup> \*\ <residue-class>', this function is not called. This is
- because the multiplication function in the operations record of the
- *right* operand is called if both operands have such a function (see
- "Operations for Records"). Now in the above case both operands have such
- a function. The left operand <subgroup> has the operations record
- 'GroupOps' (or some refinement thereof), the right operand
- <residue-class> has the operations record 'ResidueClassOps'. Thus
- 'ResidueClassOps.\*' is called. But it does not and also should not know
- how to construct a coset. The solution is simple. The multiplication
- function for residue classes detects this special case and simply calls
- the multiplication function of the left operand.
-
- | ResidueClassOps.'*' := function ( l, r )
- local prd; # product of <l> and <r>, result
- if IsResidueClass( l ) then
- if IsResidueClass( r ) then
- if l.modulus <> r.modulus then
- Error( "<l> and <r> must have the same modulus" );
- fi;
- prd := ResidueClass(
- l.representative * r.representative,
- l.modulus );
- elif IsList( r ) then
- prd := List( r, x -> l * x );
- else
- Error("product of <l> and <r> must be defined");
- fi;
- elif IsList( l ) then
- if IsResidueClass( r ) then
- prd := List( l, x -> x * r );
- else
- Error("panic: neither <l> nor <r> is a residue class");
- fi;
- else
- if IsResidueClass( r ) then
- if IsRec( l ) and IsBound( l.operations )
- and IsBound( l.operations.'*' )
- and l.operations.'*' <> ResidueClassOps.'*'
- then
- prd := l.operations.'*'( l, r );
- else
- Error("product of <l> and <r> must be defined");
- fi;
- else
- Error("panic, neither <l> nor <r> is a residue class");
- fi;
- fi;
- return prd;
- end; |
-
- Now we are done with the multiplication.
-
- Next is the powering operation '\^'. It is not very complicated. The
- 'PowerMod' function (see "PowerMod") does most of what we need,
- especially the inversion of elements with the Euclidean algorithm when
- the exponent is negative. Note however, that the definition of
- operations (see "Operations for Group Elements") requires that the
- conjugation is available as power of a residue class by another residue
- class. This is of course very easy since residue classes form an abelian
- group.
-
- | ResidueClassOps.'^' := function ( l, r )
- local pow;
- if IsResidueClass( l ) then
- if IsResidueClass( r ) then
- if l.modulus <> r.modulus then
- Error("<l> and <r> must have the same modulus");
- fi;
- if GcdInt( r.representative, r.modulus ) <> 1 then
- Error("<r> must be invertable");
- fi;
- pow := l;
- elif IsInt( r ) then
- pow := ResidueClass(
- PowerMod( l.representative, r, l.modulus ),
- l.modulus );
- else
- Error("power of <l> and <r> must be defined");
- fi;
- else
- if IsResidueClass( r ) then
- Error("power of <l> and <r> must be defined");
- else
- Error("panic, neither <l> nor <r> is a residue class");
- fi;
- fi;
- return pow;
- end; |
-
- The last function that we have to write is the printing function. This
- is called to print a residue class. It prints the residue class in the
- form 'ResidueClass( <representative>, <modulus> )'. It is fairly typical
- to print objects in such a form. This form has the advantage that it can
- be read back, resulting in exactly the same element, yet it is very
- concise.
-
- | ResidueClassOps.Print := function ( r )
- Print("ResidueClass( ",r.representative,", ",r.modulus," )");
- end; |
-
- Now we are done with the definition of residue classes as group elements.
- Try them. We can at this point actually create groups of such elements,
- and compute in them.
-
- However, we are not yet satisfied. There are two problems with the code
- we have implemented so far. Different people have different opinions
- about which of those problems is the graver one, but hopefully all agree
- that we should try to attack those problems.
-
- The first problem is that it is still possible to define objects via
- 'Group' (see "Group") that are not actually groups.
-
- | gap> G := Group( ResidueClass(13,43), ResidueClass(13,41) );
- Group( ResidueClass(13,43), ResidueClass(13,41) ) |
-
- The other problem is that groups of residue classes constructed with the
- code we have implemented so far are not handled very efficiently. This
- is because the generic group algorithms are used, since we have not
- implemented anything else. For example to test whether a residue class
- lies in a residue class group, all elements of the residue class group
- are computed by a Dimino algorithm, and then it is tested whether the
- residue class is an element of this proper set.
-
- To solve the first problem we must first understand what happens with the
- above code if we create a group with 'Group( <res1>, <res2>... )'.
- 'Group' tries to find a domain that contains all the elements <res1>,
- <res2>, etc. It first calls 'Domain( [ <res1>, <res2>... ] )' (see
- "Domain"). 'Domain' looks at the residue classes and sees that they all
- are records and that they all have a component with the name 'domain'.
- This is understood to be a domain in which the elements lie. And in fact
- '<res1> in GroupElements' is 'true', because 'GroupElements' accepts all
- records with tag 'isGroupElement'. So 'Domain' returns 'GroupElements'.
- 'Group' then calls\\
- 'GroupElements.operations.Group(GroupElements,[<res1>,<res2>...],<id>)',
- where <id> is the identity residue class, obtained by '<res1> \^\ 0', and
- returns the result.
-
- 'GroupElementsOps.Group' is the function that actually creates the group.
- It does this by simply creating a record with its second argument as
- generators list, its third argument as identity, and the generic
- 'GroupOps' as operations record. It ignores the first argument, which is
- passed only because convention dictates that a dispatcher passes the
- domain as first argument.
-
- So to solve the first problem we must achieve that another function
- instead of the generic function 'GroupElementsOps.Group' is called. This
- can be done by persuading 'Domain' to return a different domain. And
- this will happen if the residue classes hold this other domain in their
- 'domain' component.
-
- The obvious choice for such a domain is the (yet to be written) domain
- 'ResidueClasses'. So 'ResidueClass' must be slightly changed.
-
- | ResidueClass := function ( representative, modulus )
- local res;
- res := rec();
- res.isGroupElement := true;
- res.isResidueClass := true;
- res.representative := representative mod modulus;
- res.modulus := modulus;
- res.domain := ResidueClasses;
- res.operations := ResidueClassOps;
- return res;
- end; |
-
- The main purpose of the domain 'ResidueClasses' is to construct groups,
- so there is very little we have to do. And in fact most of that can be
- inherited from 'GroupElements'.
-
- | ResidueClasses := Copy( GroupElements );
- ResidueClasses.name := "ResidueClasses";
- ResidueClassesOps := Copy( GroupElementsOps );
- ResidueClasses.operations := ResidueClassesOps; |
-
- So now we must implement 'ResidueClassesOps.Group', which should check
- whether the passed elements do in fact form a group. After checking it
- simply delegates to the generic function 'GroupElementsOps.Group' to
- create the group as before.
-
- | ResidueClassesOps.Group := function ( ResidueClasses, gens, id )
- local g; # one generator from <gens>
- for g in gens do
- if g.modulus <> id.modulus then
- Error("the generators must all have the same modulus");
- fi;
- if GcdInt( g.representative, g.modulus ) <> 1 then
- Error("the generators must all be prime residue classes");
- fi;
- od;
- return GroupElementOps.Group( ResidueClasses, gens, id );
- end; |
-
- This solves the first problem. To solve the second problem, i.e., to
- make operations with residue class groups more efficient, we must extend
- the function 'ResidueClassesOps.Group'. It now enters a new operations
- record into the group. It also puts the modulus into the group record,
- so that it is easier to access.
-
- | ResidueClassesOps.Group := function ( ResidueClasses, gens, id )
- local G, # group <G>, result
- gen; # one generator from <gens>
- for gen in gens do
- if gen.modulus <> id.modulus then
- Error("the generators must all have the same modulus");
- fi;
- if GcdInt( gen.representative, gen.modulus ) <> 1 then
- Error("the generators must all be prime residue classes");
- fi;
- od;
- G := GroupElementsOps.Group( ResidueClasses, gens, id );
- G.modulus := id.modulus;
- G.operations := ResidueClassGroupOps;
- return G;
- end; |
-
- Of course now we must build such an operations record. Luckily we do not
- have to implement all functions, because we can inherit a lot of
- functions from 'GroupOps'. This is done by copying 'GroupOps' as we have
- done before for 'ResidueClassOps' and 'ResidueClassesOps'.
-
- | ResidueClassGroupOps := Copy( GroupOps ); |
-
- Now the first function that we must write is the 'Subgroup' function to
- ensure that not only groups constructed by 'Group' have the correct
- operations record, but also subgroups of those groups created by
- 'Subgroup'. As in 'Group' we only check the arguments and then leave the
- work to 'GroupOps.Subgroup'.
-
- | ResidueClassGroupOps.Subgroup := function ( G, gens )
- local S, # subgroup of <G>, result
- gen; # one generator from <gens>
- for gen in gens do
- if gen.modulus <> G.modulus then
- Error("the generators must all have the same modulus");
- fi;
- if GcdInt( gen.representative, gen.modulus ) <> 1 then
- Error("the generators must all be prime residue classes");
- fi;
- od;
- S := GroupOps.Subgroup( G, gens );
- S.modulus := G.modulus;
- S.operations := ResidueClassGroupOps;
- return S;
- end; |
-
- The first function that we write especially for residue class groups is
- 'SylowSubgroup'. Since residue class groups are abelian we can compute a
- Sylow subgroup of such a group by simply taking appropriate powers of the
- generators.
-
- | ResidueClassGroupOps.SylowSubgroup := function ( G, p )
- local S, # Sylow subgroup of <G>, result
- gen, # one generator of <G>
- ord, # order of <gen>
- gens; # generators of <S>
- gens := [];
- for gen in G.generators do
- ord := OrderMod( gen.representative, G.modulus );
- while ord mod p = 0 do ord := ord / p; od;
- Add( gens, gen ^ ord );
- od;
- S := Subgroup( Parent( G ), gens );
- return S;
- end; |
-
- To allow the other functions that are applicable to residue class groups
- to work efficiently we now want to make use of the fact that residue
- class groups are direct products of cyclic groups and that we know what
- those factors are and how we can project onto those factors.
-
- To do this we write 'ResidueClassGroupOps.MakeFactors' that adds the
- components 'facts', 'roots', 'sizes', and 'sgs' to the group record <G>.
- This information, detailed below, will enable other functions to work
- efficiently with such groups. Creating such information is a fairly
- typical thing, for example for permutation groups the corresponding
- information is the stabilizer chain computed by 'MakeStabChain'.
-
- '<G>.facts' will be the list of prime power factors of '<G>.modulus'.
- Actually this is a little bit more complicated, because the residue class
- group modulo the largest power <q> of 2 that divides '<G>.modulus' need
- not be cyclic. So if <q> is a multiple of 4, '<G>.facts[1]' will be 4,
- corresponding to the projection of <G> into $(Z / 4 Z)^\*$ (of size 2),
- furthermore if <q> is a multiple of 8, '<G>.facts[2]' will be <q>,
- corresponding to the projection of <G> into the subgroup generated by 5
- in $(Z / q Z)^\*$ (of size $q/4$).
-
- '<G>.roots' will be a list of primitive roots, i.e., of generators of the
- corresponding factors in '<G>.facts'. '<G>.sizes' will be a list of the
- sizes of the corresponding factors in '<G>.facts', i.e., '<G>.sizes[<i>]
- = Phi( <G>.facts[<i>] )'. (If '<G>.modulus' is a multiple of 8,
- '<G>.roots[2]' will be 5, and '<G>.sizes[2]' will be <q>/4.)
-
- Now we can represent each element <g> of the group <G> by a list <e>,
- called the exponent vector, of the length of '<G>.facts', where
- '<e>[<i>]' is the logarithm of '<g>.representative mod <G>.facts[<i>]'
- with respect to '<G>.roots[<i>]'. The multiplication of elements of <G>
- corresponds to the componentwise addition of their exponent vectors,
- where we add modulo '<G>.sizes[<i>]' in the <i>-th component. (Again
- special consideration are necessary if '<G>.modulus' is divisible by 8.)
-
- Next we compute the exponent vectors of all generators of <G>, and
- represent this information as a matrix. Then we bring this matrix into
- upper triangular form, with an algorithm that is very much like the
- ordinary Gaussian elimination, modified to account for the different
- sizes of the components. This upper triangular matrix of exponent
- vectors is the component '<G>.sgs'. This new matrix obviously still
- contains the exponent vectors of a generating system of <G>, but a much
- nicer one, which allows us to tackle problems one component at a time.
- (It is not necessary that you fully check this, the important thing here
- is not the mathematical side.)
-
- | ResidueClassGroupOps.MakeFactors := function ( G )
- local p, q, # prime factor of modulus and largest power
- r, s, # two rows of the standard generating system
- g, # extended gcd of leading entries in <r>, <s>
- x, y, # two entries in <r> and <s>
- i, k, l; # loop variables
-
- # find the factors of the direct product
- G.facts := [];
- G.roots := [];
- G.sizes := [];
- for p in Set( Factors( G.modulus ) ) do
- q := p;
- while G.modulus mod (p*q) = 0 do q := p*q; od;
- if q mod 4 = 0 then
- Add( G.facts, 4 );
- Add( G.roots, 3 );
- Add( G.sizes, 2 );
- fi;
- if q mod 8 = 0 then
- Add( G.facts, q );
- Add( G.roots, 5 );
- Add( G.sizes, q/4 );
- fi;
- if p <> 2 then
- Add( G.facts, q );
- Add( G.roots, PrimitiveRootMod( q ) );
- Add( G.sizes, (p-1)*q/p );
- fi;
- od;
-
- # represent each generator in this factorization
- G.sgs := [];
- for k in [ 1 .. Length( G.generators ) ] do
- G.sgs[k] := [];
- for i in [ 1 .. Length( G.facts ) ] do
- if G.facts[i] mod 8 = 0 then
- if G.generators[k].representative mod 4 = 1 then
- G.sgs[k][i] := LogMod(
- G.generators[k].representative,
- G.roots[i], G.facts[i] );
- else
- G.sgs[k][i] := LogMod(
- -G.generators[k].representative,
- G.roots[i], G.facts[i] );
- fi;
- else
- G.sgs[k][i] := LogMod(
- G.generators[k].representative,
- G.roots[i], G.facts[i] );
- fi;
- od;
- od;
- for i in [ Length( G.sgs ) + 1 .. Length( G.facts ) ] do
- G.sgs[i] := 0 * G.facts;
- od;
-
- # bring this matrix to diagonal form
- for i in [ 1 .. Length( G.facts ) ] do
- r := G.sgs[i];
- for k in [ i+1 .. Length( G.sgs ) ] do
- s := G.sgs[k];
- g := Gcdex( r[i], s[i] );
- for l in [ i .. Length( r ) ] do
- x := r[l]; y := s[l];
- r[l] := (g.coeff1 * x + g.coeff2 * y) mod G.sizes[l];
- s[l] := (g.coeff3 * x + g.coeff4 * y) mod G.sizes[l];
- od;
- od;
- s := [];
- x := G.sizes[i] / GcdInt( G.sizes[i], r[i] );
- for l in [ 1 .. Length( r ) ] do
- s[l] := (x * r[l]) mod G.sizes[l];
- od;
- Add( G.sgs, s );
- od;
-
- end; |
-
- With the information computed by 'MakeFactors' it is now of course very
- easy to compute the size of a residue class group. We just look at the
- '<G>.sgs', and multiply the orders of the leading exponents of the
- nonzero exponent vectors.
-
- | ResidueClassGroupOps.Size := function ( G )
- local s, # size of <G>, result
- i; # loop variable
- if not IsBound( G.facts ) then
- G.operations.MakeFactors( G );
- fi;
- s := 1;
- for i in [ 1 .. Length( G.facts ) ] do
- s := s * G.sizes[i] / GcdInt( G.sizes[i], G.sgs[i][i] );
- od;
- return s;
- end; |
-
- The membership test is a little bit more complicated. First we test that
- the first argument is really a residue class with the correct modulus.
- Then we compute the exponent vector of this residue class and reduce this
- exponent vector using the upper triangular matrix '<G>.sgs'.
-
- | ResidueClassGroupOps.'in' := function ( res, G )
- local s, # exponent vector of <res>
- g, # extended gcd
- x, y, # two entries in <s> and '<G>.sgs[i]'
- i, l; # loop variables
- if not IsResidueClass( res )
- or res.modulus <> G.modulus
- or GcdInt( res.representative, res.modulus ) <> 1
- then
- return false;
- fi;
- if not IsBound( G.facts ) then
- G.operations.MakeFactors( G );
- fi;
- s := [];
- for i in [ 1 .. Length( G.facts ) ] do
- if G.facts[i] mod 8 = 0 then
- if res.representative mod 4 = 1 then
- s[i] := LogMod( res.representative,
- G.roots[i], G.facts[i] );
- else
- s[i] := LogMod( -res.representative,
- G.roots[i], G.facts[i] );
- fi;
- else
- s[i] := LogMod( res.representative,
- G.roots[i], G.facts[i] );
- fi;
- od;
- for i in [ 1 .. Length( G.facts ) ] do
- if s[i] mod GcdInt( G.sizes[i], G.sgs[i][i] ) <> 0 then
- return false;
- fi;
- g := Gcdex( s[i], G.sgs[i][i] );
- for l in [ i .. Length( G.facts ) ] do
- x := s[l]; y := G.sgs[i][l];
- s[l] := (g.coeff3 * x + g.coeff4 * y) mod G.sizes[l];
- od;
- od;
- return true;
- end; |
-
- We also add a function 'Random' that works by creating a random exponent
- as a random linear combination of the exponent vectors in '<G>.sgs', and
- converts this exponent vector to a residue class. (The main purpose of
- this function is to allow you to create random test examples for the
- other functions.)
-
- | ResidueClassGroupOps.Random := function ( G )
- local s, # exponent vector of random element
- r, # vector of remainders in each factor
- i, k, l; # loop variables
- if not IsBound( G.facts ) then
- G.operations.MakeFactors( G );
- fi;
- s := 0 * G.facts;
- for i in [ 1 .. Length( G.facts ) ] do
- l := G.sizes[i] / GcdInt( G.sizes[i], G.sgs[i][i] );
- k := Random( [ 0 .. l-1 ] );
- for l in [ i .. Length( s ) ] do
- s[l] := (s[l] + k * G.sgs[i][l]) mod G.sizes[l];
- od;
- od;
- r := [];
- for l in [ 1 .. Length( s ) ] do
- r[l] := PowerModInt( G.roots[l], s[l], G.facts[l] );
- if G.facts[l] mod 8 = 0 and r[1] = 3 then
- r[l] := G.facts[l] - r[l];
- fi;
- od;
- return ResidueClass( ChineseRem( G.facts, r ), G.modulus );
- end; |
-
- There are a lot more functions that would benefit from being implemented
- especially for residue class groups. We do not show them here, because
- the above functions already displayed how such functions can be written.
-
- To round things up, we finally add a function that constructs the full
- residue class group given a modulus <m>. This function is totally
- independent of the implementation of residue classes and residue class
- groups. It only has to find a (minimal) system of generators of the full
- prime residue classes group, and to call 'Group' to construct this group.
- It also adds the information entry 'size' to the group record, of course
- with the value $\phi(n)$.
-
- | PrimeResidueClassGroup := function ( m )
- local G, # group $Z/mZ$, result
- gens, # generators of <G>
- p, q, # prime and prime power dividing <m>
- r, # primitive root modulo <q>
- g; # is = <r> mod <q> and = 1 mod <m> / <q>
-
- # add generators for each prime power factor <q> of <m>
- gens := [];
- for p in Set( Factors( m ) ) do
- q := p;
- while m mod (q * p) = 0 do q := q * p; od;
-
- # $(Z / 4Z)^\* = \< 3 > $
- if q = 4 then
- r := 3;
- g := r + q * (((1/q mod (m/q)) * (1 - r)) mod (m/q));
- Add( gens, ResidueClass( g, m ) );
-
- # $(Z / 8nZ)^\* = \< 5, -1 > $ is *not* cyclic
- elif q mod 8 = 0 then
- r := q-1;
- g := r + q * (((1/q mod (m/q)) * (1 - r)) mod (m/q));
- Add( gens, ResidueClass( g, m ) );
- r := 5;
- g := r + q * (((1/q mod (m/q)) * (1 - r)) mod (m/q));
- Add( gens, ResidueClass( g, m ) );
-
- # for odd <q> $(Z / qZ)^\*$ is cyclic
- elif q <> 2 then
- r := PrimitiveRootMod( q );
- g := r + q * (((1/q mod (m/q)) * (1 - r)) mod (m/q));
- Add( gens, ResidueClass( g, m ) );
- fi;
-
- od;
-
- # return the group generated by <gens>
- G := Group( gens, ResidueClass( 1, m ) );
- G.size := Phi( n );
- return G;
- end; |
-
- There is one more thing that we can learn from this example.
- Mathematically a residue class is not only a group element, but a set as
- well. We can reflect this in {\GAP} by turning residue classes into
- domains (see "Domains"). Section "About Defining New Domains" gives an
- example of how to implement a new domain, so we will here only show the
- code with few comments.
-
- First we must change the function that constructs a residue class, so
- that it enters the necessary fields to tag this record as a domain. It
- also adds the information that residue classes are infinite.
-
- | ResidueClass := function ( representative, modulus )
- local res;
- res := rec();
- res.isGroupElement := true;
- res.isDomain := true;
- res.isResidueClass := true;
- res.representative := representative mod modulus;
- res.modulus := modulus;
- res.isFinite := false;
- res.size := "infinity";
- res.domain := ResidueClasses;
- res.operations := ResidueClassOps;
- return res;
- end; |
-
- The initialization of the 'ResidueClassOps' record must be changed too,
- because now we want to inherit both from 'GroupElementsOps' and
- 'DomainOps'. This is done by the function 'MergedRecord', which takes
- two records and returns a new record that contains all components from
- either record.
-
- Note that the record returned by 'MergedRecord' does not have those
- components that appear in both arguments. This forces us to explicitly
- write down from which record we want to inherit those functions, or to
- define them anew. In our example the components common to
- 'GroupElementOps' and 'DomainOps' are only the equality and ordering
- functions, which we have to define anyhow. (This solution for the
- problem of which definition to choose in the case of multiple inheritance
- is also taken by C++.)
-
- With this function definition we can now initialize 'ResidueClassOps'.
-
- | ResidueClassOps := MergedRecord( GroupElementOps, DomainOps ); |
-
- Now we add all functions to this record as described above.
-
- Next we add a function to the operations record that tests whether a
- certain object is in a residue class.
-
- | ResidueClassOps.'in' := function ( element, class )
- if IsInt( element ) then
- return (element mod class.modulus = class.representative);
- else
- return false;
- fi;
- end; |
-
- Finally we add a function to compute the intersection of two residue
- classes.
-
- | ResidueClassOps.Intersection := function ( R, S )
- local I, # intersection of <R> and <S>, result
- gcd; # gcd of the moduli
- if IsResidueClass( R ) then
- if IsResidueClass( S ) then
- gcd := GcdInt( R.modulus, S.modulus );
- if R.representative mod gcd
- <> S.representative mod gcd
- then
- I := [];
- else
- I := ResidueClass(
- ChineseRem(
- [ R.representative, S.representative ],
- [ R.modulus, S.modulus ] ),
- Lcm( R.modulus, S.modulus ) );
- fi;
- else
- I := DomainOps.Intersection( R, S );
- fi;
- else
- I := DomainOps.Intersection( R, S );
- fi;
- return I;
- end; |
-
- There is one further thing that we have to do. When 'Group' is called
- with a single argument that is a domain, it assumes that you want to
- create a new group such that there is a bijection between the original
- domain and the new group. This is not what we want here. We want that
- in this case we get the cyclic group that is generated by the single
- residue class. (This overloading of 'Group' is probably a mistake, but
- so is the overloading of residue classes, which are both group elements
- and domains.) The following definition solves this problem.
-
- | ResidueClassOps.Group := function ( R )
- return ResidueClassesOps.Group( ResidueClasses, [R], R^0 );
- end; |
-
- This concludes our example. There are however several further things
- that you could do. One is to add functions for the quotient, the
- modulus, etc. Another is to fix the functions so that they do not hang
- if asked for the residue class group mod 1. Also you might try to
- implement residue class rings analogous to residue class groups. Finally
- it might be worthwhile to improve the speed of the multiplication of
- prime residue classes. This can be done by doing some precomputation in
- 'ResidueClass' and adding some information to the residue class record
- for prime residue classes (\cite{Mon85}).
-
- %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
- %%
- %E Emacs . . . . . . . . . . . . . . . . . . . . . local Emacs variables
- %%
- %% Local Variables:
- %% mode: outline
- %% outline-regexp: "\\\\Chapter\\|\\\\Section"
- %% fill-column: 73
- %% eval: (hide-body)
- %% End:
- %%
-