|
#############################################################################
##
## This file is part of GAP, a system for computational discrete algebra.
## This file's authors include Thomas Breuer.
##
## Copyright of GAP belongs to its developers, whose names are too numerous
## to list here. Please refer to the COPYRIGHT file for details.
##
## SPDX-License-Identifier: GPL-2.0-or-later
##
## This file is being maintained by Thomas Breuer.
## Please do not make any changes without consulting him.
## (This holds also for minor changes such as the removal of whitespace or
## the correction of typos.)
##
## This file contains methods for cyclotomics.
##
#############################################################################
##
#V Cyclotomics . . . . . . . . . . . . . . . . . . field of all cyclotomics
##
BindGlobal( "Cyclotomics", Objectify( NewType(
CollectionsFamily( CyclotomicsFamily ),
IsField and IsAttributeStoringRep ),
rec() ) );
SetName( Cyclotomics, "Cyclotomics" );
SetLeftActingDomain( Cyclotomics, Rationals );
SetIsFiniteDimensional( Cyclotomics, false );
SetIsFinite( Cyclotomics, false );
SetIsWholeFamily( Cyclotomics, true );
SetDegreeOverPrimeField( Cyclotomics, infinity );
SetDimension( Cyclotomics, infinity );
SetRepresentative(Cyclotomics, 0);
#############################################################################
##
#M Conductor( <list> ) . . . . . . . . . . . . . . . . . . . . . for a list
##
## (This works not only for lists of cyclotomics but also for lists of
## lists of cyclotomics etc.)
##
InstallOtherMethod( Conductor,
"for a list",
[ IsList ],
function( list )
local n, entry;
n:= 1;
for entry in list do
n:= LcmInt( n, Conductor( entry ) );
od;
return n;
end );
#############################################################################
##
#M RoundCyc( <cyc> ) . . . . . . . . . . cyclotomic integer near to <cyc>
##
InstallMethod( RoundCyc, "general cyclotomic", [ IsCyclotomic ],
function ( x )
local n, cfs, int, i, c;
n:= Conductor( x );
cfs:= ExtRepOfObj( x );
int:= EmptyPlist( n );
for i in [ 1 .. n ] do
c:= cfs[i];
if IsInt( c ) then
int[i]:= c;
elif c < 0 then
int[i]:= Int( c - 1/2 );
else
int[i]:= Int( c + 1/2 );
fi;
od;
return CycList( int );
end );
#############################################################################
##
#M RoundCycDown( <cyc> ) . . . . . . . . . cyclotomic integer near to <cyc>
## rounding halves down
##
InstallMethod( RoundCycDown, "general cyclotomic", [ IsCyclotomic ],
x -> CycList( List( ExtRepOfObj( x ), RoundCycDown ) ) );
#############################################################################
##
#M ComplexConjugate( <cyc> )
#M ComplexConjugate( <list> )
##
InstallMethod( ComplexConjugate,
"for a cyclotomic",
[ IsCyc ],
cyc -> GaloisCyc( cyc, -1 ) );
InstallMethod( ComplexConjugate,
"for a list",
[ IsList ],
function( list )
local result, i;
result:= [];
for i in [ 1 .. Length( list ) ] do
if IsBound( list[i] ) then
result[i]:= ComplexConjugate( list[i] );
fi;
od;
return result;
end );
#############################################################################
##
#M RealPart( <z> )
#M RealPart( <list> )
##
InstallMethod( RealPart,
"for a scalar",
[ IsScalar ],
z -> ( z + ComplexConjugate( z ) ) / 2 );
InstallMethod( RealPart,
"for a list",
[ IsList ],
function( list )
local result, i;
result:= [];
for i in [ 1 .. Length( list ) ] do
if IsBound( list[i] ) then
result[i]:= RealPart( list[i] );
fi;
od;
return result;
end );
#############################################################################
##
#M ImaginaryPart( <z> )
#M ImaginaryPart( <list> )
##
InstallMethod( ImaginaryPart,
"for a cyclotomic",
[ IsCyc ],
z -> E(4) * ( ComplexConjugate( z ) - z ) / 2 );
InstallMethod( ImaginaryPart,
"for a list",
[ IsList ],
function( list )
local result, i;
result:= [];
for i in [ 1 .. Length( list ) ] do
if IsBound( list[i] ) then
result[i]:= ImaginaryPart( list[i] );
fi;
od;
return result;
end );
#############################################################################
##
#M ExtRepOfObj( <cyc> )
##
## <#GAPDoc Label="ExtRepOfObj:cyclotomics">
## <ManSection>
## <Meth Name="ExtRepOfObj" Arg='cyc' Label="for a cyclotomic"/>
##
## <Description>
## The external representation of a cyclotomic <A>cyc</A> with conductor
## <M>n</M> (see <Ref Attr="Conductor" Label="for a cyclotomic"/> is
## the list returned by <Ref Func="CoeffsCyc"/>,
## called with <A>cyc</A> and <M>n</M>.
## <P/>
## <Example><![CDATA[
## gap> ExtRepOfObj( E(5) ); CoeffsCyc( E(5), 5 );
## [ 0, 1, 0, 0, 0 ]
## [ 0, 1, 0, 0, 0 ]
## gap> CoeffsCyc( E(5), 15 );
## [ 0, 0, 0, 0, 0, 0, 0, 0, -1, 0, 0, 0, 0, -1, 0 ]
## ]]></Example>
## </Description>
## </ManSection>
## <#/GAPDoc>
##
InstallMethod( ExtRepOfObj,
"for an internal cyclotomic",
[ IsCyc and IsInternalRep ],
COEFFS_CYC );
#############################################################################
##
#F CoeffsCyc( <z>, <N> )
##
InstallGlobalFunction( CoeffsCyc, function( z, N )
local coeffs, # coefficients list (also intermediately)
n, # length of `coeffs'
quo, # factor by that we have to blow up
s, # denominator of `quo' (we have to reduce)
factors, #
second, # product of primes in second reduction step
first,
val,
kk,
pos,
j,
k,
p,
nn,
newcoeffs;
if IsCyc( z ) then
# `z' is an internal cyclotomic, and therefore it is represented
# in the smallest possible cyclotomic field.
coeffs:= ExtRepOfObj( z ); # returns `CoeffsCyc( z, Conductor( z ) )'
n:= Length( coeffs );
quo:= N / n;
if not IsInt( quo ) then
return fail;
fi;
else
# `z' is already a coefficients list
coeffs:= z;
n:= Length( coeffs );
quo:= N / n;
if not IsInt( quo ) then
# Maybe `z' was not represented with respect to
# the smallest possible cyclotomic field.
# Try to reduce `z' until the denominator $s$ disappears.
s:= DenominatorRat( quo );
# step 1.
# First we get rid of
# - the $p$-parts for those primes $p$ that divide both $s$
# and $n / s$, and hence remain in the reduced expression,
# - all primes but the squarefree part of the rest.
factors:= PrimeDivisors( s );
second:= 1;
for p in factors do
if s mod p <> 0 or ( n / s ) mod p <> 0 then
second:= second * p;
fi;
od;
if second mod 2 = 0 then
second:= second / 2;
fi;
first:= s / second;
if first > 1 then
newcoeffs:= ListWithIdenticalEntries( n / first, 0 );
for k in [ 1 .. n ] do
if coeffs[k] <> 0 then
pos:= ( k - 1 ) / first + 1;
if not IsInt( pos ) then
return fail;
fi;
newcoeffs[ pos ]:= coeffs[k];
fi;
od;
n:= n / first;
coeffs:= newcoeffs;
fi;
# step 2.
# Now we process those primes that shall disappear
# in the reduced form.
# Note that $p-1$ of the coefficients congruent modulo $n/p$
# must be equal, and the negative of this value is put at the
# position of the $p$-th element of this congruence class.
if second > 1 then
for p in Factors(Integers, second ) do
nn:= n / p;
newcoeffs:= ListWithIdenticalEntries( nn, 0 );
for k in [ 1 .. n ] do
pos:= 0;
if coeffs[k] <> 0 then
val:= coeffs[k];
for j in [ 1 .. p-1 ] do
kk:= ( ( k - 1 + j*nn ) mod n ) + 1;
if coeffs[ kk ] = val then
coeffs[ kk ]:= 0;
elif pos <> 0 or coeffs[kk] <> 0 or kk mod p <> 1 then
return fail;
else
pos:= ( kk - 1 ) / p + 1;
fi;
od;
newcoeffs[ pos ]:= - val;
fi;
od;
n:= nn;
coeffs:= newcoeffs;
od;
fi;
quo:= NumeratorRat( quo );
fi;
fi;
# If necessary then blow up the representation in two steps.
# step 1.
# For each prime `p' not dividing `n' we replace
# `E(n)^k' by $ - \sum_{j=1}^{p-1} `E(n*p)^(p*k+j*n)'$.
if quo <> 1 then
for p in PrimeDivisors( quo ) do
if p <> 2 and n mod p <> 0 then
nn := n * p;
quo := quo / p;
newcoeffs:= ListWithIdenticalEntries( nn, 0 );
for k in [ 1 .. n ] do
if coeffs[k] <> 0 then
for j in [ 1 .. p-1 ] do
newcoeffs[ ( ( (k-1)*p + j*n ) mod nn ) + 1 ]:= -coeffs[k];
od;
fi;
od;
coeffs:= newcoeffs;
n:= nn;
fi;
od;
fi;
# step 2.
# For the remaining divisors of `quo' we have
# `E(n*p)^(k*p)' in the basis for each basis element `E(n)^k'.
if quo <> 1 then
n:= Length( coeffs );
newcoeffs:= ListWithIdenticalEntries( quo*n, 0 );
for k in [ 1 .. Length( coeffs ) ] do
if coeffs[k] <> 0 then
newcoeffs[ (k-1)*quo + 1 ]:= coeffs[k];
fi;
od;
coeffs:= newcoeffs;
fi;
# Return the coefficients list.
return coeffs;
end );
#############################################################################
##
#F IsGaussInt(<x>) . . . . . . . . . test if an object is a Gaussian integer
##
InstallGlobalFunction( IsGaussInt,
x -> IsCycInt( x ) and (Conductor( x ) = 1 or Conductor( x ) = 4) );
#############################################################################
##
#F IsGaussRat( <x> ) . . . . . . . test if an object is a Gaussian rational
##
InstallGlobalFunction( IsGaussRat,
x -> IsCyc( x ) and (Conductor( x ) = 1 or Conductor( x ) = 4) );
##############################################################################
##
#F DescriptionOfRootOfUnity( <root> )
##
## Let $\zeta$ denote the primitive $n$-th root of unity $`E'(n)$,
## and suppose that we know the coefficients of $\pm\zeta^i$ w.r.t.
## the $n$-th Zumbroich basis (see~"ZumbroichBasis").
## These coefficients are either all $1$ or all $-1$.
## More precisely, they arise in the base conversion from (formally)
## successively multiplying $\pm\zeta^i$ by
## $1 = - \sum_{j=1}^{p-1} \zeta^{jn/p}$,
## for suitable prime diviors $p$ of $n$,
## and then treating the summands $\pm\zeta^{i + jn/p}$ in the same way
## until roots in the basis are reached.
## It should be noted that all roots obtained this way are distinct.
##
## Suppose the above procedure must be applied for the primes
## $p_1$, $p_2$, \ldots, $p_r$.
## Then $\zeta^i$ is equal to
## $(-1)^r \sum_{j_1=1}^{p_1-1} \cdots \sum_{j_r=1}^{p_r-1}
## \zeta^{i + \sum_{k=1}^r j_k n/p_k}$.
## The number of summands is $m = \prod_{k=1}^r (p_k-1)$.
## Since these roots are all distinct, we can compute the sum $s$ of their
## exponents modulo $n$ from the known coefficients of $\zeta^i$,
## and we get $s \equiv m ( i + r n/2 ) \pmod{n}$.
## Either $m = 1$ or $m$ is even,
## hence this congruence determines $\zeta^i$ at most up to its sign.
##
## Now suppose that $g = \gcd( m, n )$ is nontrivial.
## Then $i$ is determined only modulo $n/g$, and we have to decide which
## of the $g$ possible values $i$ is.
## This could be done by computing the values $j_{0,p}$ for one
## candidate $i$, where $p$ runs over the prime divisors $p$ of $n$
## for which $m$ is the product of $(p-1)$.
##
## (Recall that each $n$-th root of unity is of the form
## $\prod_{p\in P} \prod_{k_p=1}^{\nu_p-1} \zeta^{j_{k,p}n/p^{k_p+1}}$,
## with $j_{0,p} \in \{ 0, 1, \ldots, p-1 \}$, $j_{k,2} \in \{ 0, 1 \}$
## for $k > 0$, and $j_{k,p} \in \{ -(p-1)/2, \ldots, (p-1)/2 \}$ for
## $p > 2$.
## The root is in the $n$-th Zumbroich basis if and only if $j_{0,2} = 0$
## and $j_{0,p} \not= 0$ for $p > 2$.)
##
## But note that we do not have an easy access to the decomposition
## of $m$ into factors $(p-1)$,
## although in fact this decomposition is unique for $n \leq 65000$.
##
## So the exponent is identified by dividing off one of the candidates
## and then identifying the quotient, which is a $g$-th or $2 g$-th
## root of unity.
## (Note that $g$ is small compared to $n$.
## $m$ divides the product of $(p-1)$ for prime divisors $p$
## that divide $n$ at least with exponent $2$.
## The maximum of $g$ for $n \leq 65000$ is $40$, so $2$ steps suffice
## for these values of $n$.)
##
InstallGlobalFunction( DescriptionOfRootOfUnity, function( root )
local coeffs, # Zumbroich basis coefficients of `root'
n, # conductor of `n'
sum, # sum of exponents with nonzero coefficients
num, # number of nonzero coefficients
i, # loop variable
val, # one coefficient
coeff, # one nonzero coefficient (either `1' or `-1')
exp, # candidate for the exponent
g, # `Gcd( n, num )'
groot; # root in recursion
# Handle the trivial cases that `root' is an integer.
if root = 1 then
return [ 1, 1 ];
elif root = -1 then
return [ 2, 1 ];
fi;
Assert( 1, not IsRat( root ) );
# Compute the Zumbroich basis coefficients,
# and number and sum of exponents with nonzero coefficient (mod `n').
coeffs:= ExtRepOfObj( root );
n:= Length( coeffs );
sum:= 0;
num:= 0;
for i in [ 1 .. n ] do
val:= coeffs[i];
if val <> 0 then
sum:= sum + i;
num:= num + 1;
coeff:= val;
fi;
od;
sum:= sum - num;
# `num' is equal to `1' if and only if `root' or its negative
# belongs to the basis.
# (The coefficient is equal to `-1' if and only if either
# `n' is a multiple of $4$ or
# `n' is odd and `root' is a primitive $2 `n'$-th root of unity.)
if num = 1 then
if coeff < 0 then
if n mod 2 = 0 then
sum:= sum + n/2;
else
sum:= 2*sum + n;
n:= 2*n;
fi;
fi;
Assert( 1, root = E(n)^sum );
return [ n, sum mod n ];
fi;
# Let $N$ be `n' if `n' is even, and equal to $2 `n'$ otherwise.
# The exponent is determined modulo $N / \gcd( N, `num' )$.
g:= GcdInt( n, num );
if g = 1 then
# If `n' and `num' are coprime then `root' is identified up to its sign.
exp:= ( sum / num ) mod n;
if root <> E(n)^exp then
exp:= 2*exp + n;
n:= 2*n;
fi;
elif g = 2 then
# `n' is even, and again `root' is determined up to its sign.
exp:= ( sum / num ) mod ( n / 2 );
if root <> E(n)^exp then
exp:= exp + n / 2;
fi;
else
# Divide off one of the candidates.
# The quotient is a `g'-th or $2 `g'$-th root of unity,
# which can be identified by recursion.
exp:= ( sum / num ) mod ( n / g );
groot:= DescriptionOfRootOfUnity( root * E(n)^(n-exp) );
if n mod 2 = 1 and groot[1] mod 2 = 0 then
exp:= 2*exp;
n:= 2*n;
fi;
exp:= exp + groot[2] * n / groot[1];
fi;
# Return the result.
Assert( 1, root = E(n)^exp );
return [ n, exp mod n ];
end );
#############################################################################
##
#F Atlas1( <n>, <i> ) . . . . . . . . . . . . . . . utility for EB, ..., EH
##
## is the value $\frac{1}{i} \sum{j=1}^{n-1} z_n^{j^i}$
## for $2 \leq i \leq 8$ and $<n> \equiv 1 \pmod{i}$;
## if $i > 2$, <n> should be a prime to get sure that the result is
## well-defined;
## `Atlas1' returns the value given above if it is a cyclotomic integer.
## (see: Conway et al, ATLAS of finite groups, Oxford University Press 1985,
## Chapter 7, Section 10)
##
BindGlobal( "Atlas1", function( n, i )
local atlas, k, pos;
if not IsInt( n ) or n < 1 then
Error( "usage: EB(<n>), EC(<n>), ..., EH(<n>) with appropriate ",
"integer <n>" );
elif n mod i <> 1 then
Error( "<n> not congruent 1 mod ", i );
fi;
if n = 1 then
return 0;
fi;
atlas:= [ 1 .. n ] * 0;
if i mod 2 = 0 then
# Note that `n' is odd in this case.
for k in [ 1 .. QuoInt( n-1, 2 ) ] do
# summand `2 * E(n)^(k^i)'
pos:= ( (k^i) mod n ) + 1;
atlas[ pos ]:= atlas[ pos ] + 2;
od;
else
for k in [ 1 .. QuoInt( n-1, 2 ) ] do
# summand `E(n)^(k^i) + E(n)^(n - k^i)'
pos:= ( (k^i) mod n ) + 1;
atlas[ pos ]:= atlas[ pos ] + 1;
pos:= ( n - pos + 1 ) mod n + 1;
atlas[ pos ]:= atlas[ pos ] + 1;
od;
if n mod 2 = 0 then
# summand `E(n)^( (n/2)^i )'
pos:= ( ( (n/2)^i ) mod n ) + 1;
atlas[ pos ]:= atlas[ pos ] + 1;
fi;
fi;
atlas:= CycList( atlas / i );
if not IsCycInt( atlas ) then
Error( "result divided by ", i, " is not a cyclotomic integer" );
fi;
return atlas;
end );
#############################################################################
##
#F EB( <n> ), EC( <n> ), \ldots, EH( <n> ) . . . some ATLAS irrationalities
##
InstallGlobalFunction( EB, n -> Atlas1( n, 2 ) );
InstallGlobalFunction( EC, n -> Atlas1( n, 3 ) );
InstallGlobalFunction( ED, n -> Atlas1( n, 4 ) );
InstallGlobalFunction( EE, n -> Atlas1( n, 5 ) );
InstallGlobalFunction( EF, n -> Atlas1( n, 6 ) );
InstallGlobalFunction( EG, n -> Atlas1( n, 7 ) );
InstallGlobalFunction( EH, n -> Atlas1( n, 8 ) );
#############################################################################
##
#F NK( <n>, <k>, <d> ) . . . . . . . . . . utility for ATLAS irrationalities
##
## Find the (<d>+1)-st automorphism *nk of order <k> on primitive <n>-th
## roots of unity.
##
## Optimization steps:
## - Since <k> is very small, `PowerModInt' should be avoided, compared to
## powering and then reducing mod <n>.
## - Do not call `Phi' but look at the prime factors of <n> directly.
## - Use multiplication instead of powering for computing squares.
##
InstallGlobalFunction( NK, function( n, k, deriv )
local nk,
nkn, # nk mod n
n1, # n-1
pow, pow2, pow3;
if n <= 2 then
return fail;
fi;
nk:= 1;
nkn:= 1;
n1:= n-1;
if k = 2 then
# We have always `Phi( n ) mod k = 0'.
while true do
if nkn <> 1 then
# `*nk' has order larger than 1.
pow:= (nkn * nkn) mod n;
if pow = 1 then
# `*nk' has order 2.
if deriv = 0 then return nk; fi;
deriv:= deriv - 1;
if nkn <> n1 then
# `**nk' has order larger than 1 and thus equal to 2.
if deriv = 0 then return -nk; fi;
deriv:= deriv - 1;
fi;
fi;
elif nkn <> n1 then
# `**nk' has order larger than 1.
pow:= (nkn * nkn) mod n;
if pow = 1 then
# `**nk' has order dividing 2.
if deriv = 0 then return -nk; fi;
deriv:= deriv - 1;
fi;
fi;
nk:= nk + 1;
nkn:= nk mod n;
od;
elif k in [ 3, 5, 7 ] then # for odd primes
if ( n mod ( k*k ) <> 0 ) and
ForAll( PrimeDivisors( n ), p -> (p-1) mod k <> 0 ) then
return fail;
fi;
while true do
if nkn <> 1 then
# `*nk' has order larger than 1.
pow:= (nkn ^ k) mod n;
if pow = 1 then
# `*nk' has order dividing `k'.
if deriv = 0 then return nk; fi;
deriv:= deriv - 1;
fi;
if nkn <> n1 and pow = n1 then
# `**nk' has order larger than 1 and dividing `k'.
if deriv = 0 then return -nk; fi;
deriv:= deriv - 1;
fi;
elif nkn <> n1 then
# `**nk' has order larger than 1.
pow:= (nkn ^ k) mod n;
if pow = n1 then
# `**nk' has order dividing `k'.
if deriv = 0 then return -nk; fi;
deriv:= deriv - 1;
fi;
fi;
nk:= nk + 1;
nkn:= nk mod n;
od;
elif k = 4 then
# An automorphism of order 4 exists if 4 divides $p-1$ for an odd
# prime divisor $p$ of `n', or if 16 divides `n'.
if ForAll( PrimeDivisors( n ), p -> (p-1) mod k <> 0 )
and n mod 16 <> 0 then
return fail;
fi;
while true do
if nkn <> 1 and nkn <> n1 then
# `*nk' and `**nk' have order at least 2.
pow:= (nkn * nkn) mod n;
if pow <> 1 and ( pow * pow ) mod n = 1 then
# `*nk' (and thus also `**nk') has order 4.
if deriv = 0 then
return nk;
elif deriv = 1 then
return -nk;
fi;
deriv:= deriv - 2;
fi;
fi;
nk:= nk + 1;
nkn:= nk mod n;
od;
elif k = 6 then
# An automorphism of order 6 exists if automorphisms of the orders
# 2 and 3 exist; the former is always true.
if ( n mod 9 <> 0 ) and
ForAll( PrimeDivisors( n ), p -> (p-1) mod 3 <> 0 ) then
return fail;
fi;
while true do
if nkn <> 1 and nkn <> n1 then
# `*nk' and `**nk' have order at least 2.
pow2:= (nkn * nkn) mod n;
if pow2 <> 1 then
# `*nk' (and thus `**nk') has order larger than 2.
pow:= (pow2 ^ 3) mod n;
if pow = 1 then
# `*nk' (and thus `**nk') has order dividing 6.
pow3:= (nkn * pow2) mod n;
if pow3 <> 1 then
if deriv = 0 then return nk; fi;
deriv:= deriv - 1;
fi;
if pow3 <> n1 then
if deriv = 0 then return -nk; fi;
deriv:= deriv - 1;
fi;
fi;
fi;
fi;
nk:= nk + 1;
nkn:= nk mod n;
od;
elif k = 8 then
# An automorphism of order 8 exists if 8 divides $p-1$ for an odd
# prime divisor $p$ of `n', or if 32 divides `n'.
if ForAll( PrimeDivisors( n ), p -> (p-1) mod k <> 0 )
and n mod 32 <> 0 then
return fail;
fi;
while true do
if nkn <> 1 and nkn <> n1 then
# `*nk' and `**nk' have order at least 2.
pow:= (nkn ^ 4) mod n;
if pow <> 1 and ( pow * pow ) mod n = 1 then
# `*nk' (and thus also `**nk') has order 8.
if deriv = 0 then
return nk;
elif deriv = 1 then
return -nk;
fi;
deriv:= deriv - 2;
fi;
fi;
nk:= nk + 1;
nkn:= nk mod n;
od;
fi;
# We have `k < 2' or `k > 8'.
return fail;
end );
#############################################################################
##
#F Atlas2( <n>, <k>, <deriv> ) . . . . . . utility for ATLAS irrationalities
##
BindGlobal( "Atlas2", function( n, k, deriv )
local nk, result, i, pos;
if not ( IsInt( n ) and IsInt( k ) and IsInt( deriv ) ) then
Error( "usage: ATLAS irrationalities require integral arguments" );
fi;
nk:= NK( n, k, deriv );
if nk = fail then
return fail;
fi;
result:= [ 1 .. n ] * 0;
for i in [ 0 .. k-1 ] do
# summand `E(n)^(nk^i);
pos:= ( (nk^i) mod n ) + 1;
result[ pos ]:= result[ pos ] + 1;
od;
return CycList( result );
end );
#############################################################################
##
#F EY(<n>), EY(<n>,<deriv>) . . . . . . . ATLAS irrationalities $y_n$ resp.
#F $y_n^{<deriv>}$
#F ... ES(<n>), ES(<n>,<deriv>) ... $s_n$ resp. $s_n^{<deriv>}$
##
InstallGlobalFunction( EY, function(arg)
if Length(arg)=1 then return Atlas2(arg[1],2,0);
elif Length(arg)=2 then return Atlas2(arg[1],2,arg[2]);
else Error( "usage: EY(n) resp. EY(n,deriv)" ); fi;
end );
InstallGlobalFunction( EX, function(arg)
if Length(arg)=1 then return Atlas2(arg[1],3,0);
elif Length(arg)=2 then return Atlas2(arg[1],3,arg[2]);
else Error( "usage: EX(n) resp. EX(n,deriv)" ); fi;
end );
InstallGlobalFunction( EW, function(arg)
if Length(arg)=1 then return Atlas2(arg[1],4,0);
elif Length(arg)=2 then return Atlas2(arg[1],4,arg[2]);
else Error( "usage: EW(n) resp. EW(n,deriv)" ); fi;
end );
InstallGlobalFunction( EV, function(arg)
if Length(arg)=1 then return Atlas2(arg[1],5,0);
elif Length(arg)=2 then return Atlas2(arg[1],5,arg[2]);
else Error( "usage: EV(n) resp. EV(n,deriv)" ); fi;
end );
InstallGlobalFunction( EU, function(arg)
if Length(arg)=1 then return Atlas2(arg[1],6,0);
elif Length(arg)=2 then return Atlas2(arg[1],6,arg[2]);
else Error( "usage: EU(n) resp. EU(n,deriv)" ); fi;
end );
InstallGlobalFunction( ET, function(arg)
if Length(arg)=1 then return Atlas2(arg[1],7,0);
elif Length(arg)=2 then return Atlas2(arg[1],7,arg[2]);
else Error( "usage: ET(n) resp. ET(n,deriv)" ); fi;
end );
InstallGlobalFunction( ES, function(arg)
if Length(arg)=1 then return Atlas2(arg[1],8,0);
elif Length(arg)=2 then return Atlas2(arg[1],8,arg[2]);
else Error( "usage: ES(n) resp. ES(n,deriv)" ); fi;
end );
#############################################################################
##
#F Atlas3( <n>, <k>, <deriv> ) . . . . . . utility for ATLAS irrationalities
##
BindGlobal( "Atlas3", function( n, k, deriv )
local nk, l, pow, val, i;
nk:= NK( n, k, deriv );
if nk = fail then
return fail;
fi;
l:= 0 * [ 1 .. n ];
pow:= 1;
val:= 1;
for i in [ 1 .. k ] do
l[ pow + 1 ]:= val;
pow:= (nk * pow) mod n;
val:= -val;
od;
return CycList( l );
end );
#############################################################################
##
#F EM( <n>[, <deriv>] ) . . . . . . . . ATLAS irrationality $m_{<n>}$ resp.
## $m_{<n>}^{<deriv>}$
InstallGlobalFunction( EM, function( arg )
local n;
n:= arg[1];
if 2 < Length( arg ) or not IsInt( n ) or n < 3 then
Error( "usage: EM( <n>[, <deriv>] )" );
elif Length( arg ) = 1 then
return Atlas3( n, 2, 0 );
else
return Atlas3( n, 2, arg[2] );
fi;
end );
#############################################################################
##
#F EL( <n>[, <deriv>] ) . . . . . . . . ATLAS irrationality $l_{<n>}$ resp.
## $l_{<n>}^{<deriv>}$
InstallGlobalFunction( EL, function( arg )
local n;
n:= arg[1];
if 2 < Length( arg ) or not IsInt( n ) or n < 3 then
Error( "usage: EL( <n>[, <deriv>] )" );
elif Length( arg ) = 1 then
return Atlas3( n, 4, 0 );
else
return Atlas3( n, 4, arg[2] );
fi;
end );
#############################################################################
##
#F EK( <n>[, <deriv>] ) . . . . . . . . ATLAS irrationality $k_{<n>}$ resp.
## $k_{<n>}^{<deriv>}$
InstallGlobalFunction( EK, function( arg )
local n;
n:= arg[1];
if 2 < Length( arg ) or not IsInt( n ) or n < 3 then
Error( "usage: EK( <n>[, <deriv>] )" );
elif Length( arg ) = 1 then
return Atlas3( n, 6, 0 );
else
return Atlas3( n, 6, arg[2] );
fi;
end );
#############################################################################
##
#F EJ( <n>[, <deriv>] ) . . . . . . . . ATLAS irrationality $j_{<n>}$ resp.
## $j_{<n>}^{<deriv>}$
InstallGlobalFunction( EJ, function( arg )
local n;
n:= arg[1];
if 2 < Length( arg ) or not IsInt( n ) or n < 3 then
Error( "usage: EJ( <n>[, <deriv>] )" );
elif Length( arg ) = 1 then
return Atlas3( n, 8, 0 );
else
return Atlas3( n, 8, arg[2] );
fi;
end );
#############################################################################
##
#F ER( <n> ) . . . . ATLAS irrationality $r_{<n>}$ (pos. square root of <n>)
##
## Different from other {\ATLAS} irrationalities,
## `ER' (and its synonym `Sqrt') can be applied also to noninteger
## rationals.
##
InstallGlobalFunction( ER, function( n )
local factor, factors, pair;
if IsInt( n ) then
if n = 0 then
return 0;
elif n < 0 then
factor:= E(4);
n:= -n;
else
factor:= 1;
fi;
# Split `n' into the product of a square and a squarefree number.
factors:= Collected( Factors( n ) );
n:= 1;
for pair in factors do
if pair[2] mod 2 = 0 then
factor:= factor * pair[1]^(pair[2]/2);
else
n:= n * pair[1];
if pair[2] <> 1 then
factor:= factor * pair[1]^((pair[2]-1)/2);
fi;
fi;
od;
if n mod 4 = 1 then
return factor * ( 2 * EB(n) + 1 );
elif n mod 4 = 2 then
return factor * ( E(8) - E(8)^3 ) * ER( n / 2 );
elif n mod 4 = 3 then
return factor * (-E(4)) * ( 2 * EB(n) + 1 );
fi;
elif IsRat( n ) then
factor:= DenominatorRat( n );
return ER( NumeratorRat( n ) * factor ) / factor;
else
Error( "argument must be rational" );
fi;
end );
#############################################################################
##
#F EI( <n> ) . . . . ATLAS irrationality $i_{<n>}$ (the square root of -<n>)
##
InstallGlobalFunction( EI, n -> E(4) * ER(n) );
#############################################################################
##
#M Sqrt( <rat> ) . . . . . . . . . . . . . . . . . square root of rationals
##
InstallMethod( Sqrt,
"for a rational",
[ IsRat ],
ER );
#############################################################################
##
#F StarCyc( <cyc> ) . . . . the unique nontrivial Galois conjugate of <cyc>
##
InstallGlobalFunction( StarCyc, function( cyc )
local n, gens, cand, exp, img;
n:= Conductor( cyc );
if n = 1 then
return fail;
fi;
gens:= Flat( GeneratorsPrimeResidues( n ).generators );
cand:= fail;
for exp in gens do
img:= GaloisCyc( cyc, exp );
if img <> cyc then
if cand = fail then
cand:= img;
elif cand <> img then
return fail;
fi;
fi;
od;
for exp in gens do
img:= GaloisCyc( cand, exp );
if img <> cyc and img <> cand then
return fail;
fi;
od;
return cand;
end );
#############################################################################
##
#F AtlasIrrationality( <irratname> )
##
InstallGlobalFunction( AtlasIrrationality, function( irratname )
local len, pos, sign, pos2, coeff, letts, funcs, recurse, lpos, N,
dashes, irrat, qN, gal, oldpos;
# Check the argument.
if not IsString( irratname ) or IsEmpty( irratname ) then
return fail;
fi;
len:= Length( irratname );
# Get the first sign.
pos:= 1;
sign:= 1;
if irratname[1] = '-' then
sign:= -1;
pos:= 2;
elif irratname[1] = '+' then
pos:= 2;
fi;
# Get the first coefficient.
if pos <= len and IsDigitChar( irratname[ pos ] ) then
pos2:= pos;
while pos2 <= len and IsDigitChar( irratname[ pos2 ] ) do
pos2:= pos2+1;
od;
coeff:= sign * Int( irratname{ [ pos .. pos2-1 ] } );
pos:= pos2;
else
coeff:= sign;
fi;
if len < pos then
return coeff;
fi;
# Get the first atomic irrationality (with dashes).
letts:= "bcdefghijklmrstuvwxyz";
funcs:= List( "BCDEFGHIJKLMRSTUVWXY", x -> ValueGlobal( [ 'E', x ] ) );
Add( funcs, E );
# Is the coefficient an integer summand?
if irratname[ pos ] in "+-" then
recurse:= AtlasIrrationality( irratname{ [ pos ..
Length( irratname ) ] } );
if recurse = fail then
return fail;
else
return coeff + recurse;
fi;
fi;
lpos:= Position( letts, irratname[ pos ] );
if lpos = fail then
return fail;
fi;
pos:= pos + 1;
dashes:= 0;
while pos <= len and irratname[ pos ] in "\'\"" do
dashes:= dashes + 1;
if irratname[ pos ] = '\"' then
dashes:= dashes + 1;
fi;
pos:= pos + 1;
od;
pos2:= pos;
while pos2 <= len and IsDigitChar( irratname[ pos2 ] ) do
pos2:= pos2+1;
od;
if pos2 = pos and lpos = 8 then
N:= 1;
else
N:= Int( irratname{ [ pos .. pos2 - 1 ] } );
fi;
if dashes = 0 then
qN:= funcs[ lpos ]( N );
else
qN:= funcs[ lpos ]( N, dashes );
fi;
pos:= pos2;
irrat:= coeff * qN;
if len < pos then
return irrat;
fi;
# Get the Galois automorphism.
if irratname[ pos ] = '*' then
pos:= pos + 1;
if pos <= len and irratname[ pos ] = '*' then
pos:= pos + 1;
pos2:= pos;
while pos2 <= len and IsDigitChar( irratname[ pos2 ] ) do
pos2:= pos2 + 1;
od;
gal:= Int( irratname{ [ pos .. pos2-1 ] } );
if gal = 0 then
irrat:= ComplexConjugate( irrat );
else
irrat:= GaloisCyc( irrat, -gal );
fi;
pos:= pos2;
elif len < pos or irratname[ pos ] in "+-&" then
irrat:= StarCyc( irrat );
else
pos2:= pos;
while pos2 <= len and IsDigitChar( irratname[ pos2 ] ) do
pos2:= pos2 + 1;
od;
gal:= Int( irratname{ [ pos .. pos2-1 ] } );
irrat:= GaloisCyc( irrat, gal );
pos:= pos2;
fi;
fi;
while pos <= len do
# Get ampersand summands.
if irratname[ pos ] = '&' then
pos2:= pos + 1;
while pos2 <= len and IsDigitChar( irratname[ pos2 ] ) do
pos2:= pos2 + 1;
od;
gal:= Int( irratname{ [ pos+1 .. pos2-1 ] } );
irrat:= irrat + coeff * GaloisCyc( qN, gal );
pos:= pos2;
elif irratname[ pos ] in "+-" then
if irratname[ pos ] = '+' then
sign:= 1;
oldpos:= pos+1;
else
sign:= -1;
oldpos:= pos;
fi;
pos2:= pos + 1;
while pos2 <= len and IsDigitChar( irratname[ pos2 ] ) do
pos2:= pos2 + 1;
od;
if pos2 = pos + 1 then
coeff:= sign;
else
coeff:= sign * Int( irratname{ [ pos+1 .. pos2-1 ] } );
fi;
pos:= pos2;
if pos <= len then
if irratname[ pos ] = '&' then
pos2:= pos + 1;
while pos2 <= len and IsDigitChar( irratname[ pos2 ] ) do
pos2:= pos2 + 1;
od;
gal:= Int( irratname{ [ pos+1 .. pos2-1 ] } );
irrat:= irrat + coeff * GaloisCyc( qN, gal );
pos:= pos2;
else
recurse:= AtlasIrrationality( irratname{ [ oldpos .. len ] } );
if recurse = fail then
return fail;
fi;
irrat:= irrat + recurse;
pos:= len+1;
fi;
else
irrat:= irrat + coeff;
fi;
else
return fail;
fi;
od;
# Return the result.
return irrat;
end );
#############################################################################
##
#F Quadratic( <cyc>[, <rat>] ) . information about quadratic irrationalities
##
InstallGlobalFunction( Quadratic, function( arg )
local cyc,
rat,
denom,
coeffs, # Zumbroich basis coefficients of `cyc'
facts, # factors of conductor of `cyc'
factsset, # set of `facts'
two_part, # 2-part of the conductor of `cyc'
root, # `root' component of the result
a, # `a' component of the result
b, # `b' component of the result
d, # `d' component of the result
ATLAS, # string that expresses `cyc' in {\sf ATLAS} format
ATLAS2, # another string, maybe shorter ...
display; # string that shows a way to input `cyc'
cyc:= arg[1];
rat:= Length( arg ) = 2 and arg[2] = true;
if not IsCyc( cyc ) then
return fail;
fi;
denom:= DenominatorCyc( cyc );
if not ( rat or denom = 1 ) then
return fail;
fi;
if IsRat( cyc ) then
return rec(
a := NumeratorRat( cyc ),
b := 0,
root := 1,
d := denom,
ATLAS := String( cyc ),
display := String( cyc )
);
fi;
if denom <> 1 then
cyc:= cyc * denom;
fi;
coeffs:= ExtRepOfObj( cyc );
facts:= Factors(Integers, Length( coeffs ) );
factsset:= Set( facts );
two_part:= Number( facts, x -> x = 2 );
# Compute candidates for `a', `b', `root', `d'.
if two_part = 0 and Length( facts ) = Length( factsset ) then
root:= Length( coeffs );
if root mod 4 = 3 then
root:= -root;
fi;
a:= StarCyc( cyc );
if a = fail then
return fail;
fi;
# Set `a' to the trace of `cyc' over the rationals.
a:= cyc + a;
if Length( factsset ) mod 2 = 0 then
b:= 2 * coeffs[2] - a;
else
b:= 2 * coeffs[2] + a;
fi;
if a mod 2 = 0 and b mod 2 = 0 then
a:= a / 2;
b:= b / 2;
d:= 1;
else
d:= 2;
fi;
elif two_part = 2 and Length( facts ) = Length( factsset ) + 1 then
root:= Length( coeffs ) / 4;
if root = 1 then
a:= coeffs[1];
b:= - coeffs[2];
else
a:= coeffs[5];
if Length( factsset ) mod 2 = 0 then a:= -a; fi;
b:= - coeffs[ root + 5 ];
fi;
if root mod 4 = 1 then
root:= -root;
b:= -b;
fi;
d:= 1;
elif two_part = 3 then
root:= Length( coeffs ) / 4;
if root = 2 then
a:= coeffs[1];
b:= coeffs[2];
if b = coeffs[4] then
root:= -2;
fi;
else
a:= coeffs[9];
if Length( factsset ) mod 2 = 0 then a:= -a; fi;
b:= coeffs[ root / 2 + 9 ];
if b <> - coeffs[ 3 * root / 2 - 7 ] then
root:= -root;
elif ( root / 2 ) mod 4 = 3 then
b:= -b;
fi;
fi;
d:= 1;
else
return fail;
fi;
# Check whether the candidates `a', `b', `d', `root' are correct.
if d * cyc <> a + b * ER( root ) then
return fail;
fi;
# Compute a string for the irrationality in {\ATLAS} format.
if d = 2 then
# Necessarily `root' is congruent 1 mod 4, only $b_{'root'}$ possible.
# We have $'cyc' = ('a' + `b') / 2 + `b' b_{'root'}$.
if a + b = 0 then
if b = 1 then
ATLAS:= "";
elif b = -1 then
ATLAS:= "-";
else
ATLAS:= ShallowCopy( String( b ) );
fi;
elif b = 1 then
ATLAS:= Concatenation( String( ( a + b ) / 2 ), "+" );
elif b = -1 then
ATLAS:= Concatenation( String( ( a + b ) / 2 ), "-" );
elif 0 < b then
ATLAS:= Concatenation( String( ( a + b ) / 2 ), "+", String( b ) );
else
ATLAS:= Concatenation( String( ( a + b ) / 2 ), String( b ) );
fi;
Append( ATLAS, "b" );
if 0 < root then
Append( ATLAS, String( root ) );
else
Append( ATLAS, String( -root ) );
fi;
else
# `d' = 1, so we may use $i_{'root'}$ and $r_{'root'}$.
if a = 0 then
ATLAS:= "";
else
ATLAS:= ShallowCopy( String( a ) );
fi;
if a <> 0 and b > 0 then Append( ATLAS, "+" ); fi;
if b = -1 then
Append( ATLAS, "-" );
elif b <> 1 then
Append( ATLAS, String( b ) );
fi;
if root > 0 then
ATLAS:= Concatenation( ATLAS, "r", String( root ) );
elif root = -1 then
Append( ATLAS, "i" );
else
ATLAS:= Concatenation( ATLAS, "i", String( -root ) );
fi;
if ( root - 1 ) mod 4 = 0 then
# In this case, also $b_{|'root'|}$ is possible.
# Note that here the coefficients are never equal to $\pm 1$.
if a = -b then
ATLAS2:= String( 2 * b );
else
ATLAS2:= Concatenation( String( a+b ), "+", String( 2*b ) );
fi;
if root > 0 then
ATLAS2:= Concatenation( ATLAS2, "b", String( root ) );
else
ATLAS2:= Concatenation( ATLAS2, "b", String( -root ) );
fi;
if Length( ATLAS2 ) < Length( ATLAS ) then
ATLAS:= ATLAS2;
fi;
fi;
fi;
if denom <> 1 then
ATLAS:= Concatenation( "(", ATLAS, ")/", String( denom ) );
fi;
ConvertToStringRep( ATLAS );
# Compute a string used by the `Display' function for character tables.
if a = 0 then
if b = 1 then
display:= "";
elif b = -1 then
display:= "-";
else
display:= Concatenation( String( b ), "*" );
fi;
elif b = 1 then
display:= Concatenation( String( a ), "+" );
elif b = -1 then
display:= Concatenation( String( a ), "-" );
elif 0 < b then
display:= Concatenation( String( a ), "+", String( b ), "*" );
else
display:= Concatenation( String( a ), String( b ), "*" );
fi;
Append( display, Concatenation( "Sqrt(", String( root ), ")" ) );
d:= d * denom;
if d <> 1 then
if a <> 0 then
display:= Concatenation( "(", display, ")" );
fi;
display:= Concatenation( display, "/", String( d ) );
fi;
ConvertToStringRep( display );
# Return the result.
return rec(
a := a,
b := b,
root := root,
d := d,
ATLAS := ATLAS,
display := display
);
end );
#############################################################################
##
#M GaloisMat( <mat> ) . . . . . . . . . . . . . for a matrix of cyclotomics
##
## Note that we must not mix up plain lists and class functions, since
## these objects lie in different families.
##
InstallMethod( GaloisMat,
"for a matrix of cyclotomics",
[ IsMatrix and IsCyclotomicCollColl ],
function( mat )
local warned, # at most one warning will be printed if `mat' grows
ncha, # number of rows in `mat'
nccl, # number of columns in `mat'
galoisfams, # list with information about conjugate characters:
# 1 means rational character,
# -1 means character with undefs,
# 0 means dependent irrational character,
# [ .. ] means leading irrational character.
n, # conductor of irrationalities in `mat'
genexp, # generators of prime residues mod `n'
generators, # permutation of `mat' induced by elements in `genexp'
X, # one row of `mat'
i, j, # loop over rows of `mat'
irrats, # set of irrationalities in `X'
fusion, # positions of `irrats' in `X'
k, l, m, # loop variables
generator,
irratsimages,
automs,
family,
orders,
exp,
image,
oldorder,
cosets,
auto,
conj,
blocklength,
innerlength;
warned := false;
ncha := Length( mat );
mat := ShallowCopy( mat );
# Step 1:
# Find the minimal cyclotomic field $Q_n$ containing all irrational
# entries of <mat>.
galoisfams:= [];
n:= 1;
for i in [ 1 .. ncha ] do
if ForAny( mat[i], IsUnknown ) then
galoisfams[i]:= -1;
elif ForAll( mat[i], IsRat ) then
galoisfams[i]:= 1;
else
n:= LcmInt( n, Conductor( mat[i] ) );
fi;
od;
# Step 2:
# Compute generators for Aut( Q(n):Q ), that is,
# compute generators for (Z/nZ)* and convert them to exponents.
if 1 < n then
# Each Galois automorphism induces a permutation of rows.
# Compute the permutations for each generating automorphism.
# (Initialize with the identity permutation.)
genexp:= Flat( GeneratorsPrimeResidues( n ).generators );
generators:= List( genexp, x -> [ 1 .. ncha ] );
else
# The matrix is rational.
generators:= [];
fi;
# Step 3:
# For each character X, find and complete the family of conjugates.
if 0 < ncha then
nccl:= Length( mat[1] );
fi;
for i in [ 1 .. ncha ] do
if not IsBound( galoisfams[i] ) then
# We have found an independent character that is not integral
# and contains no unknowns.
X:= mat[i];
for j in [ i+1 .. ncha ] do
if mat[j] = X then
galoisfams[j]:= Unknown();
Info( InfoWarning, 1,
"GaloisMat: row ", i, " is equal to row ", j );
fi;
od;
# Initialize the list of distinct irrationalities of `X'
# (not ordered).
# Each Galois automorphism induces a permutation of that list
# rather than of the entries of `X' themselves.
irrats:= [];
# Store how to distribute the entries of irrats to `X'.
fusion:= [];
for j in [ 1 .. nccl ] do
if IsCyc( X[j] ) and not IsRat( X[j] ) then
k:= 1;
while k <= Length( irrats ) and X[j] <> irrats[k] do
k:= k+1;
od;
if k > Length( irrats ) then
# This is the first appearance of `X[j]' in `X'.
irrats[k]:= X[j];
fi;
# Store the position in `irrats'.
fusion[j]:= k;
else
fusion[j]:= 0;
fi;
od;
irratsimages:= [ irrats ];
automs:= [ 1 ];
family:= [ i ]; # indices of family members (same ordering as automs)
orders:= []; # orders[k] will be the order of the k-th generator
for j in [ 1 .. Length( genexp ) ] do
exp:= genexp[j];
image:= List( irrats, x -> GaloisCyc( x, exp ) );
oldorder:= Length( automs ); # group order up to now
cosets:= [];
orders[j]:= 1;
while not image in irratsimages do
orders[j]:= orders[j] + 1;
for k in [ 1 .. oldorder ] do
auto:= ( automs[k] * exp ) mod n;
image:= List( irrats, x -> GaloisCyc( x, auto ) );
conj:= []; # the conjugate character
for l in [ 1 .. nccl ] do
if fusion[l] = 0 then
conj[l]:= X[l];
else
conj[l]:= image[ fusion[l] ];
fi;
od;
l:= Position( mat, conj, i );
if l <> fail then
galoisfams[l]:= 0;
Add( family, l );
for m in [ l+1 .. ncha ] do
if mat[m] = conj then galoisfams[m]:= 0; fi;
od;
else
if not warned and 500 < Length( mat ) then
Info( InfoWarning, 1,
"GaloisMat: completion of <mat> will have",
" more than 500 rows" );
warned:= true;
fi;
Add( mat, conj );
galoisfams[ Length( mat ) ]:= 0;
Add( family, Length( mat ) );
fi;
Add( automs, auto );
Add( cosets, image );
od;
exp:= exp * genexp[j];
image:= List( irrats, x -> GaloisCyc( x, exp ) );
od;
irratsimages:= Concatenation( irratsimages, cosets );
od;
# Store the conjugates and automorphisms.
galoisfams[i]:= [ family, automs ];
# Now the length of `family' is the size of the Galois family of the
# row `X'.
# Compute the permutation operation of the generators on the set of
# rows in `family'.
blocklength:= 1;
for j in [ 1 .. Length( genexp ) ] do
innerlength:= blocklength;
blocklength:= blocklength * orders[j];
generator:= [ 1 .. blocklength ];
# `genexp[j]' maps the conjugates under the action of
# $\langle `genexp[1]', \ldots, `genexp[j-1]' \rangle$
# (a set of length `innerlength') as one block to their images,
# preserving the order of succession.
for l in [ 1 .. blocklength - innerlength ] do
generator[l]:= l + innerlength;
od;
# Compute how a power of `genexp[j]' maps back to the block.
exp:= ( genexp[j] ^ orders[j] ) mod n;
for l in [ 1 .. innerlength ] do
generator[ l + blocklength - innerlength ]:=
Position( irratsimages, List( irrats,
x -> GaloisCyc( x, exp*automs[l] ) ) );
od;
# Transfer this operation to the cosets under the operation of
# $\langle `genexp[j+1]',\ldots,`genexp[Length(genexp)]' \rangle$,
# and transfer this to <mat> via `family'.
for k in [ 0 .. Length( family ) / blocklength - 1 ] do
for l in [ 1 .. blocklength ] do
generators[j][ family[ l + k*blocklength ] ]:=
family[ generator[ l ] + k*blocklength ];
od;
od;
od;
fi;
od;
# Convert the `generators' component to a set of generating permutations.
generators:= Set( generators, PermList );
RemoveSet( generators, () ); # `generators' arose from `PermList'
if IsEmpty( generators ) then
generators:= [ () ]; # `generators' arose from `PermList'
fi;
# Return the result.
return rec(
mat := mat,
galoisfams := galoisfams,
generators := generators
);
end );
#############################################################################
##
#M RationalizedMat( <mat> ) . . . . . . list of rationalized rows of <mat>
##
InstallMethod( RationalizedMat,
"for a matrix",
[ IsMatrix ],
function( mat )
local i, rationalizedmat, rationalfams;
rationalfams:= GaloisMat( mat );
mat:= rationalfams.mat;
rationalfams:= rationalfams.galoisfams;
rationalizedmat:= [];
for i in [ 1 .. Length( mat ) ] do
if rationalfams[i] = 1 or rationalfams[i] = -1 then
# The row is rational or contains unknowns.
Add( rationalizedmat, mat[i] );
elif IsList( rationalfams[i] ) then
# The row is a leading character of a family.
Add( rationalizedmat, Sum( mat{ rationalfams[i][1] } ) );
fi;
od;
return rationalizedmat;
end );
InstallOtherMethod( RationalizedMat,
"for an empty list",
[ IsList and IsEmpty ],
empty -> [] );
#############################################################################
##
#M IsGeneratorsOfMagmaWithInverses( <cycs> ) . . for a coll. of cyclotomics
##
## Disallow to create groups of cyclotomics because the `\^' operator has
## a meaning for cyclotomics that makes it not compatible with that for
## groups:
## `E(4)^-1' is the *inverse* of `E(4)',
## but in the group generated by `E(4)',
## {\GAP} would use this term for the *conjugate* of `E(4)' by `-1'.
##
InstallMethod( IsGeneratorsOfMagmaWithInverses,
"for a collection of cyclotomics (return false)",
[ IsCyclotomicCollection ],
SUM_FLAGS, # override everything else
function( gens )
Info( InfoWarning, 1,
"no groups of cyclotomics allowed because of incompatible ^" );
return false;
end );
#############################################################################
##
## Functions for factorizing polynomials over fields of cyclotomics
## (For arbitrary number fields, the explicit embedding of the field of
## rationals may cause that the functions below do not work.)
##
#############################################################################
##
#M FactorsSquarefree( <R>, <cycpol>, <opt> )
##
## The function uses Algorithm~3.6.4 in~\cite{Coh93}.
## (The record <opt> is ignored.)
##
InstallMethod( FactorsSquarefree,
"for a polynomial over a field of cyclotomics",
IsCollsElmsX,
[ IsAbelianNumberFieldPolynomialRing, IsUnivariatePolynomial, IsRecord ],
function( R, U, opt )
local coeffring, theta, xind, yind, x, y, T, coeffs, G, powers, pow, i,
B, c, val, j, k, N, factors;
if IsRationalsPolynomialRing( R ) then
TryNextMethod();
fi;
# Let $K = \Q(\theta)$ be a number field,
# $T \in \Q[X]$ the minimal monic polynomial of $\theta$.
# Let $U(X) be a monic squarefree polynomial in $K[x]$.
coeffring:= CoefficientsRing( R );
theta:= PrimitiveElement( coeffring );
xind:= IndeterminateNumberOfUnivariateRationalFunction( U );
if xind = 1 then
yind:= 2;
else
yind:= 1;
fi;
x:= Indeterminate( Rationals, xind );
y:= Indeterminate( Rationals, yind );
# Let $U(X) = \sum_{i=0}^m u_i X^i$ and write $u_i = g_i(\theta)$
# for some polynomial $g_i \in \Q[X]$.
# Set $G(X,Y) = \sum_{i=0}^m g_i(Y) X^i \in \Q[X,Y]$.
coeffs:= CoefficientsOfUnivariatePolynomial( U );
if ForAll( coeffs, IsRat ) then
G:= U;
else
powers:= [ 1 ];
pow:= 1;
for i in [ 2 .. DegreeOverPrimeField( coeffring ) ] do
pow:= pow * theta;
powers[i]:= pow;
od;
B:= Basis( coeffring, powers );
G:= Zero( U );
for i in [ 1 .. Length( coeffs ) ] do
if IsRat( coeffs[i] ) then
G:= G + coeffs[i] * x^i;
else
c:= Coefficients( B, coeffs[i] );
val:= c[1];
for j in [ 2 .. Length( c ) ] do
val:= val + c[j] * y^(j-1);
od;
G:= G + val * x^i;
fi;
od;
fi;
# Set $k = 0$.
k:= 0;
# Compute $N(X) = R_Y( T(Y), G(X - kY,Y) )$
# where $R_Y$ denotes the resultant with respect to the variable $Y$.
# If $N(X)$ is not squarefree, increase $k$.
T:= MinimalPolynomial( Rationals, theta, yind );
repeat
k:= k+1;
N:= Resultant( T, Value( G, [ x, y ], [ x-k*y, y ] ), y );
until DegreeOfUnivariateLaurentPolynomial( Gcd( N, Derivative(N) ) ) = 0;
# Let $N = \prod_{i=1}^g N_i$ be a factorization of $N$.
# For $1 \leq i \leq g$, set $A_i(X) = \gcd( U(X), N_i(X + k \theta) )$.
# The desired factorization of $U(X)$ is $\prod_{i=1}^g A_i$.
factors:= List( Factors( PolynomialRing( Rationals, [ xind ] ), N ),
f -> Gcd( R, U, Value( f, x + k*theta ) ) );
return Filtered( factors,
x -> DegreeOfUnivariateLaurentPolynomial( x ) <> 0 );
end );
#############################################################################
##
#M Factors( <R>, <cycpol> ) . for a polynomial over a field of cyclotomics
##
InstallMethod( Factors,
"for a polynomial over a field of cyclotomics",
IsCollsElms,
[ IsAbelianNumberFieldPolynomialRing, IsUnivariatePolynomial ],
function( R, pol )
local irrfacs, coeffring, i, factors, ind, coeffs, val,
lc, der, g, factor, q;
if IsRationalsPolynomialRing( R ) then
TryNextMethod();
fi;
# Check whether the desired factorization is already stored.
irrfacs:= IrrFacsPol( pol );
coeffring:= CoefficientsRing( R );
i:= PositionProperty( irrfacs, pair -> pair[1] = coeffring );
if i <> fail then
return ShallowCopy(irrfacs[i][2]);
fi;
# Handle (at most) linear polynomials.
if DegreeOfLaurentPolynomial( pol ) < 2 then
factors:= [ pol ];
StoreFactorsPol( coeffring, pol, factors );
return factors;
fi;
# Compute the valuation, split off the indeterminate as a zero.
ind:= IndeterminateNumberOfLaurentPolynomial( pol );
coeffs:= CoefficientsOfLaurentPolynomial( pol );
val:= coeffs[2];
coeffs:= coeffs[1];
factors:= ListWithIdenticalEntries( val,
IndeterminateOfUnivariateRationalFunction( pol ) );
if Length( coeffs ) = 1 then
# The polynomial is a power of the indeterminate.
factors[1]:= coeffs[1] * factors[1];
StoreFactorsPol( coeffring, pol, factors );
return factors;
elif Length( coeffs ) = 2 then
# The polynomial is a linear polynomial times a power of the indet.
factors[1]:= coeffs[2] * factors[1];
factors[ val+1 ]:= LaurentPolynomialByExtRepNC( FamilyObj( pol ),
[ coeffs[1] / coeffs[2], 1 ], 0, ind );
StoreFactorsPol( coeffring, pol, factors );
return factors;
fi;
# We really have to compute the factorization.
# First split the polynomial into leading coefficient and monic part.
lc:= Last(coeffs);
if not IsOne( lc ) then
coeffs:= coeffs / lc;
fi;
if val = 0 then
pol:= pol / lc;
else
pol:= LaurentPolynomialByExtRepNC( FamilyObj( pol ), coeffs, 0, ind );
fi;
# Now compute the quotient of `pol' by the g.c.d. with its derivative,
# and factorize the squarefree part.
der:= Derivative( pol );
g:= Gcd( R, pol, der );
if DegreeOfLaurentPolynomial( g ) = 0 then
Append( factors, FactorsSquarefree( R, pol, rec() ) );
else
for factor in FactorsSquarefree( R, Quotient( R, pol, g ), rec() ) do
Add( factors, factor );
q:= Quotient( R, g, factor );
while q <> fail do
Add( factors, factor );
g:= q;
q:= Quotient( R, g, factor );
od;
od;
fi;
# Sort the factorization.
Sort( factors );
# Adjust the first factor by the constant term.
Assert( 2, DegreeOfLaurentPolynomial(g) = 0 );
if not IsOne( g ) then
lc:= g * lc;
fi;
if not IsOne( lc ) then
factors[1]:= lc * factors[1];
fi;
# Store the factorization.
Assert( 2, Product( factors ) = lc * pol * IndeterminateOfUnivariateRationalFunction( pol )^val );
StoreFactorsPol( coeffring, pol, factors );
# Return the factorization.
return factors;
end );
#############################################################################
##
#F DenominatorCyc( <cyc> )
##
InstallGlobalFunction( DenominatorCyc, function( cyc )
if IsRat( cyc ) then
return DenominatorRat( cyc );
else
return Lcm( List( COEFFS_CYC( cyc ), DenominatorRat ) );
fi;
end );
#############################################################################
#
# The following code is meant to allow comparisons between some select
# cyclotomics domains. So you can do things like
# Integers = GaussianRationals;
# IsSubset(Rationals, PositiveIntegers);
# without GAP running into an error. However, this code currently only
# works for a small fixed set of domains. It will not, for example, work
# with cyclotomic field extensions, or manually defined rings over the
# integers such as ClosureRing(1, E(3)).
# It would be nice if we eventually, were able to compare and intersect
# such objects, too.
#
# The following are three lists of equal length. At position i of the first
# list is a certain known cyclotomic (semi)ring. At position i of the second
# list is the corresponding filter. At position i of the third list is a
# finite list which can act as a "proxy" for the corresponding semiring when
# it comes to comparing it for inclusion resp. computing intersections with
# any of the other semirings.
# To simplify the code using these lists, the final entry of each list is
# fail, resp. the trivial filter IsObject.
BindGlobal("CompareCyclotomicCollectionHelper_Semirings", MakeImmutable( [
PositiveIntegers, NonnegativeIntegers,
Integers, GaussianIntegers,
Rationals, GaussianRationals,
Cyclotomics, fail
] ) );
BindGlobal("CompareCyclotomicCollectionHelper_Filters", MakeImmutable( [
IsPositiveIntegers, IsNonnegativeIntegers,
IsIntegers, IsGaussianIntegers,
IsRationals, IsGaussianRationals,
HasIsWholeFamily and IsWholeFamily, IsObject
] ) );
BindGlobal("CompareCyclotomicCollectionHelper_Proxies", MakeImmutable( [
[ 1 ], [ 0, 1 ],
[ -1, 0, 1 ], [ -1, 0, 1, E(4) ],
[ -1, 0, 1/2, 1 ], [ -1, 0, 1/2, 1, E(4), 1/2+E(4) ],
[ -1, 0, 1/2, 1, E(4), 1/2+E(4), E(9) ], fail
] ) );
ForAll(CompareCyclotomicCollectionHelper_Proxies, IsSet);
if IsHPCGAP then
MakeReadOnlySingleObj(CompareCyclotomicCollectionHelper_Semirings);
MakeReadOnlyObj(CompareCyclotomicCollectionHelper_Filters);
fi;
BindGlobal("CompareCyclotomicCollectionHelper", function (A, B)
local a, b;
a := PositionProperty( CompareCyclotomicCollectionHelper_Filters, p -> p(A) );
b := PositionProperty( CompareCyclotomicCollectionHelper_Filters, p -> p(B) );
return CompareCyclotomicCollectionHelper_Proxies{[a,b]};
end );
InstallMethod( \=, "for certain cyclotomic semirings",
[IsCyclotomicCollection and IsSemiringWithOne,
IsCyclotomicCollection and IsSemiringWithOne],
function (A,B)
local ab;
ab := CompareCyclotomicCollectionHelper(A, B);
# It suffices if we "recognize" at least on of A and B; but if we
# recognize neither, we give up.
if ab = [fail,fail] then TryNextMethod(); fi;
return ab[1] = ab[2];
end );
InstallMethod( IsSubset, "for certain cyclotomic semirings",
[IsCyclotomicCollection and IsSemiringWithOne,
IsCyclotomicCollection and IsSemiringWithOne],
function (A,B)
local ab;
ab := CompareCyclotomicCollectionHelper(A, B);
# Verify that we recognized both A and B, otherwise give up.
if fail in ab then TryNextMethod(); fi;
return IsSubset(ab[1], ab[2]);
end );
InstallMethod( Intersection2, "for certain cyclotomic semirings",
[IsCyclotomicCollection and IsSemiringWithOne,
IsCyclotomicCollection and IsSemiringWithOne],
function (A,B)
local ab, i;
ab := CompareCyclotomicCollectionHelper(A, B);
# Verify that we recognized both A and B, otherwise give up.
if fail in ab then TryNextMethod(); fi;
i := Position( CompareCyclotomicCollectionHelper_Proxies, Intersection2( ab[1], ab[2] ) );
return CompareCyclotomicCollectionHelper_Semirings[i];
end );
InstallMethod( Union2, "for certain cyclotomic semirings",
[IsCyclotomicCollection and IsSemiringWithOne,
IsCyclotomicCollection and IsSemiringWithOne],
function (A,B)
local ab, i;
ab := CompareCyclotomicCollectionHelper(A, B);
# Verify that we recognized both A and B, otherwise give up.
if fail in ab then TryNextMethod(); fi;
i := Position( CompareCyclotomicCollectionHelper_Proxies, Union2( ab[1], ab[2] ) );
if i = fail then TryNextMethod(); fi;
return CompareCyclotomicCollectionHelper_Semirings[i];
end );
[ Dauer der Verarbeitung: 0.28 Sekunden
(vorverarbeitet)
]
|