|
#############################################################################
##
#W access.gi GAP 4 package AtlasRep Thomas Breuer
##
## This file contains functions for accessing data from the ATLAS of Group
## Representations.
##
#############################################################################
##
#F AGR.InfoRead( <str1>, <str2>, ... )
##
AGR.InfoRead:= function( arg )
local str;
if UserPreference( "AtlasRep", "DebugFileLoading" ) = true then
for str in arg do
Print( str );
od;
fi;
end;
#############################################################################
##
#F AGR.StringFile( <filename> )
##
## In unfortunate cases, files may contain line breaks of the form "\r\n"
## instead of "\n".
## 'Read' would recognize this situation, and would silently replace these
## line breaks, but 'StringFile' keeps the file contents.
## Therefore we remove the '\r' characters.
##
AGR.StringFile:= function( filename )
local str;
AGR.InfoRead( "#I reading `", filename, "' started\n" );
str:= StringFile( filename );
AGR.InfoRead( "#I reading `", filename, "' done\n" );
if IsString( str ) then
str:= ReplacedString( str, "\r", "" );
fi;
return str;
end;
#############################################################################
##
#F AGR.ExtensionInfoCharacterTable
#F AGR.HasExtensionInfoCharacterTable
#F AGR.LibInfoCharacterTable
##
## If the CTblLib package is not available then we cannot use these
## functions.
##
if IsBound( ExtensionInfoCharacterTable ) then
AGR.ExtensionInfoCharacterTable:= ExtensionInfoCharacterTable;
AGR.HasExtensionInfoCharacterTable:= HasExtensionInfoCharacterTable;
AGR.LibInfoCharacterTable:= LibInfoCharacterTable;
fi;
#############################################################################
##
#F AGR.IsLowerAlphaOrDigitChar( <char> )
##
AGR.IsLowerAlphaOrDigitChar:=
char -> IsLowerAlphaChar( char ) or IsDigitChar( char );
#############################################################################
##
#F AGR_ChecksumFits( <string>, <checksum> )
##
BindGlobal( "AGR_ChecksumFits", function( string, checksum )
if checksum = fail then
# We cannot check anything.
return true;
elif IsString( checksum ) then
# This is a 'SHA256' format string.
return checksum = ValueGlobal( "HexSHA256" )( string );
elif IsInt( checksum ) then
# This is a 'CrcString' value.
return checksum = CrcString( string );
else
Error( "<chcksum> must be a string or an integer" );
fi;
end );
#############################################################################
##
## If the IO package is not available then the following assignments have
## the effect that no warnings about unbound variables are printed when this
## file gets read.
##
if not IsBound( IO_mkdir ) then
IO_mkdir:= "dummy";
fi;
if not IsBound( IO_stat ) then
IO_stat:= "dummy";
fi;
if not IsBound( IO_chmod ) then
IO_chmod:= "dummy";
fi;
#############################################################################
##
#F AtlasOfGroupRepresentationsTransferFile( <url>, <localpath>, <crc> )
##
## This function encapsulates the access to the remote file at the address
## <url>.
## <P/>
## If the access failed then <K>false</K> is returned, otherwise
## either the data are written to the local file with filename
## <A>localpath</A> (if this is a string and the user preference
## <C>AtlasRepDataDirectory</C> is nonempty),
## or a string with the contents of the file is returned.
##
BindGlobal( "AtlasOfGroupRepresentationsTransferFile",
function( url, localpath, crc )
local savetofile, pref, result, str, out;
# Save the contents of the target file to a local file?
# (The first two conditions mean that we *want* to avoid saving,
# the third deals with the situation that the intended path does not
# exist, probably due to missing write permissions.)
savetofile:= not ( localpath = fail or
IsEmpty( UserPreference( "AtlasRep",
"AtlasRepDataDirectory" ) ) or
not IsWritableFile( localpath{ [ 1 .. Last( Positions(
localpath, '/' ) ) - 1 ] } ) );
Info( InfoAtlasRep, 2,
"calling 'Download' with url '", url, "'" );
if savetofile then
result:= Download( url, rec( target:= localpath ) );
elif EndsWith( url, ".gz" ) then
# We can only download the compressed file and then load it.
if not IsBound( AGR.TmpDir ) then
AGR.TmpDir:= DirectoryTemporary();
fi;
if AGR.TmpDir = fail then
return false;
fi;
localpath:= Filename( AGR.TmpDir, "currentfile" );
result:= Download( url, rec( target:= localpath ) );
if result.success <> true then
Info( InfoAtlasRep, 2,
"Download failed" );
RemoveFile( localpath );
return false;
fi;
# Uncompress and load the contents.
str:= StringFile( localpath );
RemoveFile( localpath );
if not AGR_ChecksumFits( str, crc ) then
Info( InfoWarning, 1,
"download of file '", url,
"' does not yield a string with the expected crc value '",
crc, "'" );
return false;
fi;
return str;
else
# Transfer the file into the GAP session.
result:= Download( url, rec() );
fi;
if result.success <> true then
Info( InfoAtlasRep, 2,
"Download failed with message\n#I ", result.error );
if savetofile and IsExistingFile( localpath ) then
# This should not happen, 'Download' should have removed the file.
if RemoveFile( localpath ) <> true then
Error( "cannot remove corruped file '", localpath, "'" );
fi;
fi;
elif savetofile and not AGR_ChecksumFits( StringFile( localpath ), crc ) then
Info( InfoWarning, 1,
"download of file '", url, "' to '", localpath,
"' does not yield a file with the expected crc value '",
crc, "'" );
if RemoveFile( localpath ) <> true then
Error( "cannot remove corruped file '", localpath, "'" );
fi;
elif not savetofile and not AGR_ChecksumFits( result.result, crc ) then
Info( InfoWarning, 1,
"download of file '", url,
"' does not yield a string with the expected crc value '",
crc, "'" );
elif savetofile then
# The file has been downloaded and stored and seems to be o.k.
return true;
else
# The contents has been downloaded and seems to be o.k.
return result.result;
fi;
return false;
end );
#############################################################################
##
#F AGR.AccessFilesLocation( <files>, <type>, <replace>, <compressed> )
##
AGR.AccessFilesLocation:= function( files, type, replace, compressed )
#T type is not used at all!
local names, pref, pair, dirname, filename, datadirs, info, entry,
prefjson, name, namegz;
names:= [];
pref:= UserPreference( "AtlasRep", "AtlasRepDataDirectory" );
if pref <> "" and not EndsWith( pref, "/" ) then
pref:= Concatenation( pref, "/" );
fi;
for pair in files do
dirname:= pair[1];
filename:= pair[2];
if dirname in [ "datagens", "dataword" ] then
datadirs:= [ Directory( Concatenation( pref, dirname ) ) ];
else
datadirs:= fail;
for info in AtlasOfGroupRepresentationsInfo.notified do
if dirname = info.ID then
if StartsWith( info.DataURL, "http" ) then
# local directory of a remote data extension
datadirs:= [ Directory(
Concatenation( pref, "dataext/", info.ID ) ) ];
else
# local data extension
datadirs:= [ Directory( info.DataURL ) ];
entry:= First( AtlasOfGroupRepresentationsInfo.filenames,
x -> x[1] = filename );
if entry = fail then
Error( "do not know about <filename>" );
fi;
filename:= entry[2];
fi;
break;
fi;
od;
if datadirs = fail then
Error( "no data extension with identifier '", dirname, "'" );
fi;
fi;
if replace <> fail then
filename:= ReplacedString( filename, replace[1], replace[2] );
fi;
# Hack/experimental:
# If wanted then switch to a JSON format alternative of
# characteristic zero matrices (supported only for "datagens").
if dirname = "datagens" and
( PositionSublist( filename, "-Ar" ) <> fail or
PositionSublist( filename, "-Zr" ) <> fail ) then
prefjson:= UserPreference( "AtlasRep", "AtlasRepJsonFilesAddresses" );
if prefjson <> fail then
# Use Json format files of characteristic zero
# matrix representations instead of the GAP format files.
datadirs:= [ Directory( prefjson[2] ) ];
filename:= ReplacedString( filename, ".g", ".json" );
fi;
fi;
# There may be an uncompressed or a compressed version.
# If both are available then prefer the uncompressed version.
# Take the compressed version only if the program 'gunzip'
# is available.
name:= Filename( datadirs, filename );
if name = fail or not IsReadableFile( name ) then
if compressed and
Filename( DirectoriesSystemPrograms(), "gunzip" ) <> fail then
namegz:= Filename( datadirs, Concatenation( filename, ".gz" ) );
if namegz = fail then
# No version is available yet.
Add( names, Filename( datadirs[1], filename ) );
else
Add( names, namegz );
fi;
else
# No version is available yet.
Add( names, Filename( datadirs[1], filename ) );
fi;
else
Add( names, name );
fi;
od;
return names;
end;
#############################################################################
##
#F AGR.AccessFilesFetch( <filepath>, <filename>, <dirname>
#F <type>, <compressed>, <crc> )
##
## We assume that the local file <filepath> is not yet available,
## and that we have to download the file.
##
## <filepath> is the local path where the file shall be stored if local
## directories are writable (otherwise the content just gets downloaded),
## <filename> is the name part of the file in question.
## <dirname> is one of "datagens", "dataword", or a private id.
## <type> is a type record.
## <compressed> is 'true' or 'false'.
## <crc> is either the expected crc value of the file or 'fail'.
##
## The function returns 'false' if the access failed,
## 'true' if the remote file was copied to a local file,
## and a string containing the contents of the file otherwise.
##
AGR.AccessFilesFetch:= function( filepath, filename, dirname,
type, compressed, crc )
local result, iscompressed, info, datadirs, pref, url, pos,
gzip, gunzip;
# Try to fetch the remote file.
result:= fail;
iscompressed:= false;
if dirname in [ "datagens", "dataword" ] then
# This is an 'official' file.
dirname:= "core";
fi;
# The domain is described by the 'notified' list.
# We are in the case of a remote extension.
datadirs:= fail;
for info in AtlasOfGroupRepresentationsInfo.notified do
if dirname = info.ID then
if not IsBound( info.data ) then
# This should happen only for pure local extension,
Error( "non-available file <filepath> of a local extension?" );
fi;
# Fetch the file if possible.
if EndsWith( filepath, ".json" ) and EndsWith( filename, ".g" ) then
# Fetch the file from the address given by the user preference
# 'AtlasRepJsonFilesAddresses'.
filename:= filepath{ [ Last( Positions( filepath, '/' ) )+1
.. Length( filepath ) ] };
crc:= fail;
pref:= UserPreference( "AtlasRep", "AtlasRepJsonFilesAddresses" );
url:= pref[1];
else
# Use the standard addresses.
url:= info.DataURL;
fi;
if not EndsWith( url, "/" ) then
url:= Concatenation( url, "/" );
fi;
url:= Concatenation( url, filename );
# First look for an uncompressed file.
result:= AtlasOfGroupRepresentationsTransferFile( url,
filepath, crc );
# In case of private MeatAxe text files
# and if 'gunzip' is available,
# look for a compressed version of the file.
# (This is not supported for "core".)
if result = false and compressed and dirname <> "core" then
gunzip:= Filename( DirectoriesSystemPrograms(), "gunzip" );
if gunzip <> fail and not IsExecutableFile( gunzip ) then
gunzip:= fail;
fi;
if gunzip <> fail then
result:= AtlasOfGroupRepresentationsTransferFile(
Concatenation( url, ".gz" ),
Concatenation( filepath, ".gz" ), fail );
# If the file has been stored locally then it is compressed.
# If the contents is stored in 'result' then it is *uncompressed*.
if result = true then
iscompressed:= true;
fi;
fi;
fi;
if result = false then
Info( InfoAtlasRep, 1,
"failed to transfer file '", url, "'" );
return false;
fi;
break;
fi;
od;
if dirname <> info.ID then
Error( "no data extension with identifier '", dirname, "'" );
fi;
if result = true then
# The contents has just been stored in a local file.
# For MeatAxe text files, perform postprocessing:
# If wanted and if the file is not yet compressed then compress it.
if compressed and
( iscompressed = false ) and
type[1] in [ "perm", "matff" ] and
UserPreference( "AtlasRep", "CompressDownloadedMeatAxeFiles" ) = true
then
gzip:= Filename( DirectoriesSystemPrograms(), "gzip" );
if gzip = fail or not IsExecutableFile( gzip ) then
Info( InfoAtlasRep, 1, "no 'gzip' executable found" );
else
if not IsBound( gunzip ) then
gunzip:= Filename( DirectoriesSystemPrograms(), "gunzip" );
if gunzip <> fail and not IsExecutableFile( gunzip ) then
gunzip:= fail;
fi;
fi;
if gunzip <> fail then
result:= Process( DirectoryCurrent(), gzip,
InputTextNone(), OutputTextNone(), [ filepath ] );
if result = fail then
Info( InfoAtlasRep, 2,
"impossible to compress file '", filepath, "'" );
fi;
fi;
fi;
fi;
fi;
return result;
end;
#############################################################################
##
#F AGR.AtlasDataGAPFormatFile2( <filename>[, "string"] )
##
## This function is used for reading a GAP format file containing
## a permutation or a matrix over a finite field.
## The assignment to a global variable is avoided by reading a modified
## version of the file.
##
AGR.AtlasDataGAPFormatFile2:= function( filename, string... )
local str, pos, i;
if Length( string ) = 0 then
str:= AGR.StringFile( filename );
else
str:= filename;
fi;
pos:= PositionSublist( str, ":=" );
if pos <> fail then
str:= str{ [ pos + 2 .. Length( str ) ] };
fi;
i := InputTextString( Concatenation( "return ", str ) );
i:= ReadAsFunction( i );
if i <> fail then
i:= i();
fi;
return i;
end;
#############################################################################
##
#V AtlasOfGroupRepresentationsAccessFunctionsDefault
##
## several functions may be provided; return value 'fail' means that
## the next function is tried, otherwise the result counts
##
InstallValue( AtlasOfGroupRepresentationsAccessFunctionsDefault, [
rec(
description:= "download/read MeatAxe text files (default)",
location:= function( files, type )
return AGR.AccessFilesLocation( files, type, fail, true );
end,
fetch:= function( filepath, filename, dirname, type, crc )
return AGR.AccessFilesFetch( filepath, filename, dirname, type, true, crc );
end,
contents:= function( files, type, filepaths )
local i;
if not ( IsExistingFile( filepaths[1] ) or
IsExistingFile( Concatenation( filepaths[1], ".gz" ) ) ) then
# We have the file contents.
return type[2].InterpretDefault( filepaths );
else
# We have the local filenames.
filepaths:= ShallowCopy( filepaths );
for i in [ 1 .. Length( filepaths ) ] do
if EndsWith( filepaths[i], ".gz" ) then
filepaths[i]:= filepaths[i]{ [ 1 .. Length( filepaths[i] )-3 ] };
fi;
od;
return type[2].ReadAndInterpretDefault( filepaths );
fi;
end,
),
rec(
description:= "prefer downloading/reading MeatAxe binary files",
location:= function( files, type )
if ( not type[1] in [ "perm", "matff" ] ) or
IsEmpty( UserPreference( "AtlasRep", "AtlasRepDataDirectory" ) ) then
return fail;
fi;
# A list of file names is given, and the files are not compressed.
# Replace the text format names by binary format names.
return AGR.AccessFilesLocation( files, type, [ ".m", ".b" ], false );
end,
fetch:= function( filepath, filename, dirname, type, crc )
# Replace the filename by that of the binary file.
filename:= ReplacedString( filename, ".m", ".b" );
filename:= ReplacedString( filename, "/mtx/", "/bin/" );
return AGR.AccessFilesFetch( filepath, filename, dirname,
type, false, fail );
end,
contents:= function( files, type, filepaths )
# This function is called only for the types "perm" and "matff",
# binary format files are *not* compressed,
# and we are sure that we have the filenames not file contents.
return List( filepaths, FFMatOrPermCMtxBinary );
end,
),
# GAP format files means:
# one generator per file,
# the first line containing an assignment to a global variable,
# the last character being a semicolon
rec(
description:= "prefer downloading/reading GAP format files",
location:= function( files, type )
if not type[1] in [ "perm", "matff" ] then
return fail;
fi;
# A list of file names is given, and the files are not compressed.
# Replace the text format names by GAP format names.
return AGR.AccessFilesLocation( files, type, [ ".m", ".g" ], false );
end,
fetch:= function( filepath, filename, dirname, type, crc )
# Replace the filename by that of the GAP format file.
filename:= ReplacedString( filename, ".m", ".g" );
filename:= ReplacedString( filename, "/mtx/", "/gap/" );
return AGR.AccessFilesFetch( filepath, filename, dirname,
type, false, fail );
end,
contents:= function( files, type, filepaths )
# This function is called only for the types "perm" and "matff",
# and GAP format files are *not* compressed.
if not ( IsExistingFile( filepaths[1] ) or
IsExistingFile( Concatenation( filepaths[1], ".gz" ) ) ) then
# We have the file contents.
return List( filepaths,
str -> AGR.AtlasDataGAPFormatFile2( str, "string" ) );
else
# We have the local filenames.
return List( filepaths, AGR.AtlasDataGAPFormatFile2 );
fi;
end,
),
rec(
# This applies only to the "core" data, not to extensions.
description:= "prefer reading files available from a local server",
location:= function( files, type )
local localserverpath, names, pair, filename, info, name;
# This is meaningful only for official data
# and if there is a local server.
localserverpath:= UserPreference( "AtlasRep",
"AtlasRepLocalServerPath" );
if localserverpath = "" then
return fail;
fi;
names:= [];
for pair in files do
# Compose the remote filename.
if not pair[1] in [ "datagens", "dataword" ] then
return fail;
fi;
filename:= pair[2];
info:= First( AtlasOfGroupRepresentationsInfo.filenames,
x -> x[1] = filename );
if info = fail then
Error( "do not know about <filename>" );
fi;
filename:= info[2];
# Check whether the file(s) exist(s).
name:= Concatenation( localserverpath, filename );
if IsReadableFile( name ) then
Add( names, name );
else
return fail;
fi;
od;
return names;
end,
fetch:= function( filepath, filename, dirname, type, crc )
# The 'location' function has checked that the file exists.
return true;
end,
contents:= function( files, type, filepaths )
# We need not care about compressed files,
# and we know that we get filenames not file contents.
return type[2].ReadAndInterpretDefault( filepaths );
end,
),
] );
#############################################################################
##
#F AtlasOfGroupRepresentationsLocalFilename( <files>, <type> )
##
InstallGlobalFunction( AtlasOfGroupRepresentationsLocalFilename,
function( files, type )
local pref, cand, r, paths;
pref:= UserPreference( "AtlasRep", "FileAccessFunctions" );
cand:= [];
for r in Reversed( AtlasOfGroupRepresentationsInfo.accessFunctions ) do
if r.description in pref then
paths:= r.location( files, type );
if paths <> fail then
if ForAll( paths, IsReadableFile ) then
# This has priority, do not consider other sources.
cand:= [ [ r, List( paths, x -> [ x, true ] ) ] ];
break;
else
Add( cand, [ r, List( paths, x -> [ x, IsReadableFile( x ) ] ) ] );
fi;
fi;
fi;
od;
return cand;
end );
#############################################################################
##
#F AtlasOfGroupRepresentationsLocalFilenameTransfer( <files>, <type> )
##
InstallGlobalFunction( AtlasOfGroupRepresentationsLocalFilenameTransfer,
function( files, type )
local cand, list, ok, result, fetchfun, i, filepath, filename, info,
dirname, crc, res;
# 1. Determine the local directory where to look for the file,
# and the functions that claim to be applicable.
cand:= AtlasOfGroupRepresentationsLocalFilename( files, type );
# 2. Check whether the files are already stored.
# (If yes then 'cand' has length 1.)
if Length( cand ) = 1 and ForAll( cand[1][2], x -> x[2] ) then
# 3. We have the local files. Return paths and access functions.
return [ List( cand[1][2], x -> x[1] ), cand[1][1] ];
elif UserPreference( "AtlasRep", "AtlasRepAccessRemoteFiles" ) = true then
# Try to fetch the remote files,
# using the applicable methods.
for list in cand do
if Length( list[2] ) = Length( files ) then
ok:= true;
result:= [];
fetchfun:= list[1].fetch;
for i in [ 1 .. Length( files ) ] do
if not list[2][i][2] then
filepath:= list[2][i][1];
filename:= files[i][2];
info:= First( AtlasOfGroupRepresentationsInfo.filenames,
#T the list is ssorted; cheaper way!
x -> x[1] = filename );
if info = fail then
Error( "do not know about <filename>" );
fi;
filename:= info[2];
dirname:= files[i][1];
if IsBound( info[4] ) then
crc:= info[4];
else
crc:= fail;
fi;
res:= fetchfun( filepath, filename, dirname, type, crc );
if res = false then
ok:= false;
fi;
Add( result, res );
fi;
od;
if ok then
# 3. We have either the local files or their contents.
if result[1] = true then
# Return paths and the relevant record of access functions.
return [ List( list[2], x -> x[1] ), list[1] ];
else
# Return contents and the relevant record of access functions.
return [ result, list[1] ];
fi;
fi;
fi;
od;
fi;
# The file cannot be made available.
Info( InfoAtlasRep, 1,
"no files '", files, "' found in the local directories" );
return fail;
end );
#############################################################################
##
#F AtlasOfGroupRepresentationsTestTableOfContentsRemoteUpdates()
##
InstallGlobalFunction(
AtlasOfGroupRepresentationsTestTableOfContentsRemoteUpdates, function()
local pref, version, inforec, home, result, lines,
datadirs, line, pos, pos2, filename, localfile, servdate,
stat;
if not IsPackageMarkedForLoading( "io", "" ) then
Info( InfoAtlasRep, 1, "the package IO is not available" );
return fail;
fi;
# If the data directories do not yet exist then nothing is to do.
pref:= UserPreference( "AtlasRep", "AtlasRepDataDirectory" );
if not IsDirectoryPath( pref ) then
return [];
fi;
# Download the file that lists the changes.
version:= InstalledPackageVersion( "atlasrep" );
inforec:= First( PackageInfo( "atlasrep" ), r -> r.Version = version );
home:= inforec.PackageWWWHome;
result:= AtlasOfGroupRepresentationsTransferFile(
Concatenation( home, "/htm/data/changes.htm" ), fail, fail );
if result <> false then
lines:= SplitString( result, "\n" );
result:= [];
lines:= Filtered( lines,
x -> 20 < Length( x ) and x{ [ 1 .. 4 ] } = "<tr>"
and x{ [ -3 .. 0 ] + Length( x ) } = " -->" );
if pref <> "" and not EndsWith( pref, "/" ) then
pref:= Concatenation( pref, "/" );
fi;
datadirs:= [ Directory( Concatenation( pref, "datagens" ) ),
Directory( Concatenation( pref, "dataword" ) ) ];
for line in lines do
pos:= PositionSublist( line, "</td><td>" );
if pos <> fail then
pos2:= PositionSublist( line, "</td><td>", pos );
filename:= line{ [ pos+9 .. pos2-1 ] };
localfile:= Filename( datadirs, filename );
if localfile <> fail then
if not IsReadableFile( localfile ) then
localfile:= Concatenation( localfile, ".gz" );
fi;
if IsReadableFile( localfile ) then
# There is something to compare.
pos:= PositionSublist( line, "<!-- " );
if pos <> fail then
servdate:= Int( line{ [ pos+5 .. Length( line )-4 ] } );
stat:= IO_stat( localfile );
if stat <> fail then
if stat.mtime < servdate then
Add( result, localfile );
fi;
fi;
fi;
fi;
fi;
fi;
od;
return result;
fi;
return [];
end );
#############################################################################
##
#F AGR.FileContents( <files>, <type> )
##
## <files> must be a list of [ <dirname>, <filename> ] pairs,
## where <dirname> is one of "dataword" or "datagens" or the ID of a
## data extension.
##
AGR.FileContents:= function( files, type )
local result;
if not ( IsList( files ) and
ForAll( files, l -> IsList( l ) and Length( l ) = 2
and ForAll( l, IsString ) ) ) then
Error( "<files> must be a list of [ <dirname>, <filename> ] pairs" );
fi;
# Perhaps the method in question rearranges the distribution into
# one or several files.
result:= AtlasOfGroupRepresentationsLocalFilenameTransfer( files, type );
if result = fail then
return fail;
else
# We have the local files or the contents of the files.
# Extract and process the contents.
return result[2].contents( files, type, result[1] );
fi;
end;
#############################################################################
##
#F AGR.InfoForName( <gapname> )
##
AGR.InfoForName:= function( gapname )
local pos;
gapname:= AGR.GAPName( gapname );
pos:= PositionSorted( AtlasOfGroupRepresentationsInfo.GAPnames,
[ gapname ] );
if pos <= Length( AtlasOfGroupRepresentationsInfo.GAPnames ) and
AtlasOfGroupRepresentationsInfo.GAPnames[ pos ][1] = gapname then
return AtlasOfGroupRepresentationsInfo.GAPnames[ pos ];
else
return fail;
fi;
end;
#############################################################################
##
## auxiliary function
##
AGR.TST:= function( gapname, value, compname, testfun, msg )
if not IsBound( AGR.GAPnamesRec.( gapname ) ) then
Error( "AGR.GAPnamesRec.( \"", gapname, "\" ) is not bound" );
elif not IsBound( AGR.GAPnamesRec.( gapname )[3] ) then
Error( "AGR.GAPnamesRec.( \"", gapname, "\" )[3] is not bound" );
elif IsBound( AGR.GAPnamesRec.( gapname )[3].( compname ) ) then
Error( "AGR.GAPnamesRec.( \"", gapname, "\" )[3].", compname,
" is bound" );
elif not testfun( value ) then
Error( "<", compname, "> must be a ", msg );
fi;
end;
#############################################################################
##
#F AGR.IsRepNameAvailable( <repname> )
##
## If 'AtlasOfGroupRepresentationsInfo.checkData' is bound then this
## function is called when additional data are added that refer to the
## representation <repname>.
##
## The data files themselves are *not* read by the function,
## only the format of the filenames and the access with
## 'AllAtlasGeneratingSetInfos' are checked.
##
AGR.IsRepNameAvailable:= function( repname )
local filenames, type, parsed, groupname, gapname;
filenames:= [ Concatenation( repname, ".m1" ),
Concatenation( repname, ".g" ) ];
for type in AGR.DataTypes( "rep" ) do
parsed:= List( filenames,
x -> AGR.ParseFilenameFormat( x, type[2].FilenameFormat ) );
if ForAny( parsed, IsList ) then
break;
fi;
od;
if ForAll( parsed, IsBool ) then
Print( "#E wrong format of '", repname, "'\n" );
return false;
fi;
groupname:= First( parsed, IsList )[1];
gapname:= First( AtlasOfGroupRepresentationsInfo.GAPnames,
pair -> pair[2] = groupname );
if gapname = fail then
Print( "#E no group name '", groupname, "' for '", repname, "'\n" );
return false;
elif ForAll( AllAtlasGeneratingSetInfos( gapname[1] ),
x -> x.repname <> repname ) then
Print( "#E no representation '", repname, "' available\n" );
return false;
fi;
return true;
end;
#############################################################################
##
#F AGR.IsPrgNameAvailable( <prgname> )
##
## If 'AtlasOfGroupRepresentationsInfo.checkData' is bound then this
## function is called when additional data are added that refer to the
## program <prgname>.
##
AGR.IsPrgNameAvailable:= function( prgname )
local type, parsed, groupname;
for type in AGR.DataTypes( "prg" ) do
parsed:= AGR.ParseFilenameFormat( prgname, type[2].FilenameFormat );
if IsList( parsed ) then
break;
fi;
od;
if parsed = fail then
Print( "#E wrong format of '", prgname, "'\n" );
return false;
fi;
groupname:= parsed[1];
if ForAny( AGR.TablesOfContents( "all" ),
toc -> IsBound( toc.( groupname ) ) and
ForAny( RecNames( toc.( groupname ) ),
nam -> ForAny( toc.( groupname ).( nam ),
l -> l[ Length( l ) ] = prgname ) ) ) then
return true;
else
Print( "#E no program '", prgname, "' available\n" );
return false;
fi;
end;
#############################################################################
##
#V AGR.MapNameToGAPName
#F AGR.GAPName( <name> )
##
## Let <name> be a string.
## If 'LowercaseString( <name> )' is the lower case version of the GAP name
## of an ATLAS group then 'AGR.GAPName' returns this GAP name.
## If <name> is an admissible name of a GAP character table with identifier
## <id> (this condition is already case insensitive) then 'AGR.GAPName'
## returns 'AGR.GAPName( <id> )'.
##
## These two conditions are forced to be consistent, as follows.
## Whenever a GAP name <nam>, say, of an ATLAS group is notified with
## 'AGR.GNAN', we compute 'LibInfoCharacterTable( <nam> )'.
## If this is 'fail' then there is no danger of an inconsistency,
## and if the result is a record <r> then we have the condition
## 'AGR.GAPName( <r>.firstName ) = <nam>'.
##
## So a case insensitive partial mapping from character table identifiers
## to GAP names of ATLAS groups is built in 'AGR.GNAN',
## and is used in 'AGR.GAPName'
##
## Examples of different names for a group are '"F3+"' vs. '"Fi24'"'
## and '"S6"' vs. '"A6.2_1"'.
##
AGR.MapNameToGAPName:= [ [], [] ];
AGR.GAPName:= function( name )
local r, nname, pos;
# Make sure that the file 'gap/types.g' is already loaded.
IsRecord( AtlasOfGroupRepresentationsInfo );
if IsBound( AGR.LibInfoCharacterTable ) then
r:= AGR.LibInfoCharacterTable( name );
else
r:= fail;
fi;
if r = fail then
nname:= LowercaseString( name );
else
nname:= r.firstName;
fi;
pos:= Position( AGR.MapNameToGAPName[1], nname );
if pos = fail then
return name;
fi;
return AGR.MapNameToGAPName[2][ pos ];
end;
#############################################################################
##
#F AGR.GAPNameAtlasName( <atlasname> )
##
## Map the Atlas name <atlasname> to the corresponding GAP name.
##
AGR.GAPNameAtlasName:= function( atlasname )
local entry;
entry:= First( AtlasOfGroupRepresentationsInfo.GAPnames,
x -> x[2] = atlasname );
if entry = fail then
return fail;
fi;
return entry[1];
end;
#############################################################################
##
#F AGR.GNAN( <gapname>, <atlasname>[, <dirid>] )
##
## <#GAPDoc Label="AGR.GNAN">
## <Mark><C>AGR.GNAN( </C><M>gapname, atlasname[, dirid]</M><C> )</C></Mark>
## <Item>
## Called with two strings <M>gapname</M> (the &GAP; name of the group)
## and <M>atlasname</M> (the &ATLAS; name of the group),
## <C>AGR.GNAN</C> stores the information in the list
## <C>AtlasOfGroupRepresentationsInfo.GAPnames</C>,
## which defines the name mapping between the &ATLAS;
## names and &GAP; names of the groups.
## <P/>
## An example of a valid call is
## <C>AGR.GNAN("A5.2","S5")</C>.
## </Item>
## <#/GAPDoc>
##
AGR.GNAN:= function( gapname, atlasname, dirid... )
local value, r, pos;
# Get the arguments.
if Length( dirid ) = 1 and IsString( dirid[1] ) then
dirid:= dirid[1];
elif IsBound( AGR.DIRID ) then
dirid:= AGR.DIRID;
else
Info( InfoAtlasRep, 1, "'AGR.DIRID' is not bound" );
return;
fi;
if IsBound( AtlasOfGroupRepresentationsInfo.checkData ) then
if ForAny( AtlasOfGroupRepresentationsInfo.GAPnames,
pair -> gapname = pair[1] ) then
Error( "cannot notify '", gapname, "' more than once" );
elif ForAny( AtlasOfGroupRepresentationsInfo.GAPnames,
pair -> atlasname = pair[2] ) then
Error( "ambiguous GAP names for ATLAS name '", atlasname, "'" );
fi;
fi;
# Make the character table names admissible.
if IsBound( AGR.LibInfoCharacterTable ) then
r:= AGR.LibInfoCharacterTable( gapname );
else
r:= fail;
fi;
if r = fail then
# Store the lowercase name.
Add( AGR.MapNameToGAPName[1], LowercaseString( gapname ) );
Add( AGR.MapNameToGAPName[2], gapname );
elif not r.firstName in AGR.MapNameToGAPName[1] then
Add( AGR.MapNameToGAPName[1], r.firstName );
Add( AGR.MapNameToGAPName[2], gapname );
else
Error( "<gapname> is not compatible with CTblLib" );
fi;
value:= [ gapname, atlasname,
rec(),
rec( GNAN:= dirid ) ];
AddSet( AtlasOfGroupRepresentationsInfo.GAPnames, value );
AGR.GAPnamesRec.( gapname ):= value;
end;
#############################################################################
##
#F AGR.TOC( <typename>, <filename>, <crc>[, <dirid>] )
##
## <#GAPDoc Label="AGR.TOC">
## <Mark><C>AGR.TOC( </C><M>typename, filename, crc[, dirid]</M><C> )</C></Mark>
## <Item>
## <C>AGR.TOC</C> notifies an entry to the
## <C>TableOfContents.( </C><M>dirid</M><C> )</C>
## component of <Ref Var="AtlasOfGroupRepresentationsInfo"/>.
## The string <M>typename</M> must be the name of the data type
## to which the entry belongs,
## the string <M>filename</M> must be the prefix of the data file(s), and
## <M>crc</M> must be a list that contains the checksums of the data files,
## which are either integers (see <Ref BookName="ref" Func="CrcFile"/>)
## or strings (see <C>HexSHA256</C>).
## In particular, the number of files that are described by the entry
## equals the length of <M>crc</M>.
## <P/>
## The optional argument <M>dirid</M> is equal to the argument with the
## same name in the corresponding call of
## <Ref Func="AtlasOfGroupRepresentationsNotifyData"
## Label="for a local file describing private data"/>.
## If no <M>dirid</M> argument is given then the current value of
## <C>AGR.DIRID</C> is taken as the default;
## this value is set automatically before a <F>toc.json</F> file
## gets evaluated by
## <Ref Func="AtlasOfGroupRepresentationsNotifyData"
## Label="for a local file describing private data"/>,
## and is reset afterwards.
## If <C>AGR.DIRID</C> is not bound and <M>dirid</M> is not given then
## this function has no effect.
## <P/>
## An example of a valid call is
## <C>AGR.TOC("perm","alt/A5/mtx/S5G1-p5B0.m", [-3581724,115937465])</C>.
## </Item>
## <#/GAPDoc>
##
AGR.TOC:= function( arg )
local type, crc, n, string, dirid, t, record, filename, pos, entry,
groupname, added, j, filenamex, stringx;
# Get the arguments.
type:= arg[1];
crc:= arg[3];
n:= Length( crc );
string:= arg[2];
if Length( arg ) = 4 and IsString( arg[4] ) then
dirid:= arg[4];
elif IsBound( AGR.DIRID ) then
dirid:= AGR.DIRID;
else
Info( InfoAtlasRep, 1, "'AGR.DIRID' is not bound" );
return;
fi;
# Parse the filename with the given format info.
type:= First( AGR.DataTypes( "rep", "prg" ), x -> x[1] = type );
record:= AtlasTableOfContents( dirid, "all" );
# Split the name into path and filename.
filename:= string;
pos:= Position( filename, '/' );
while pos <> fail do
filename:= filename{ [ pos+1 .. Length( filename ) ] };
pos:= Position( filename, '/' );
od;
filenamex:= filename;
stringx:= string;
if 1 < n then
filenamex:= Concatenation( filename, "1" );
stringx:= Concatenation( string, "1" );
fi;
entry:= AGR.ParseFilenameFormat( filenamex, type[2].FilenameFormat );
if entry = fail then
Info( InfoAtlasRep, 1, "'", arg, "' is not a valid t.o.c. entry" );
return;
fi;
Add( AtlasOfGroupRepresentationsInfo.newfilenames,
Immutable( [ filenamex, stringx, dirid, crc[1] ] ) );
# Get the list for the data in the record for the group name.
groupname:= entry[1];
if IsBound( AtlasOfGroupRepresentationsInfo.checkData ) and
ForAll( AtlasOfGroupRepresentationsInfo.GAPnames,
x -> x[2] <> groupname ) then
Error( "'", groupname, "' is not a valid group name" );
fi;
if not IsBound( record.( groupname ) ) then
record.( groupname ):= rec();
fi;
record:= record.( groupname );
if not IsBound( record.( type[1] ) ) then
record.( type[1] ):= [];
fi;
# Add the first filename.
added:= type[2].AddFileInfo( record.( type[1] ), entry, filenamex );
# Add the other filenames if necessary.
if added then
for j in [ 2 .. n ] do
entry[ Length( entry ) ]:= j;
filenamex:= Concatenation( filename, String( j ) );
stringx:= Concatenation( string, String( j ) );
added:= type[2].AddFileInfo( record.( type[1] ), entry, filenamex )
and added;
Add( AtlasOfGroupRepresentationsInfo.newfilenames,
Immutable( [ filenamex, stringx, dirid, crc[j] ] ) );
od;
fi;
if not added then
Info( InfoAtlasRep, 1, "'", arg, "' is not a valid t.o.c. entry" );
fi;
end;
#############################################################################
##
#F AGR.GRS( <gapname>, <size>[, <dirid>] )
##
## <#GAPDoc Label="AGR.GRS">
## <Mark><C>AGR.GRS( </C><M>gapname, size[, dirid]</M><C> )</C></Mark>
## <Item>
## The integer <M>size</M> is stored as the order of the group with
## &GAP; name <M>gapname</M>,
## in <C>AtlasOfGroupRepresentationsInfo.GAPnames</C>.
## <P/>
## An example of a valid call is
## <C>AGR.GRS("A5.2",120)</C>.
## </Item>
## <#/GAPDoc>
##
AGR.GRS:= function( gapname, size, dirid... )
# Get the arguments.
if Length( dirid ) = 1 and IsString( dirid[1] ) then
dirid:= dirid[1];
elif IsBound( AGR.DIRID ) then
dirid:= AGR.DIRID;
else
Info( InfoAtlasRep, 1, "'AGR.DIRID' is not bound" );
return;
fi;
if IsBound( AtlasOfGroupRepresentationsInfo.checkData ) then
AGR.TST( gapname, size, "size", IsPosInt, "positive integer" );
fi;
AGR.GAPnamesRec.( gapname )[3].size:= size;
AGR.GAPnamesRec.( gapname )[4].GRS:= dirid;
end;
#############################################################################
##
#F AGR.MXN( <gapname>, <nrMaxes>[, <dirid>] )
##
## <#GAPDoc Label="AGR.MXN">
## <Mark><C>AGR.MXN( </C><M>gapname, nrMaxes[, dirid]</M><C> )</C></Mark>
## <Item>
## The integer <M>nrMaxes</M> is stored as the number of classes of
## maximal subgroups of the group with &GAP; name <M>gapname</M>,
## in <C>AtlasOfGroupRepresentationsInfo.GAPnames</C>.
## <P/>
## An example of a valid call is
## <C>AGR.MXN("A5.2",4)</C>.
## </Item>
## <#/GAPDoc>
##
AGR.MXN:= function( gapname, nrMaxes, dirid... )
# Get the arguments.
if Length( dirid ) = 1 and IsString( dirid[1] ) then
dirid:= dirid[1];
elif IsBound( AGR.DIRID ) then
dirid:= AGR.DIRID;
else
Info( InfoAtlasRep, 1, "'AGR.DIRID' is not bound" );
return;
fi;
if IsBound( AtlasOfGroupRepresentationsInfo.checkData ) then
AGR.TST( gapname, nrMaxes, "nrMaxes", IsPosInt, "positive integer" );
fi;
AGR.GAPnamesRec.( gapname )[3].nrMaxes:= nrMaxes;
AGR.GAPnamesRec.( gapname )[4].MXN:= dirid;
end;
#############################################################################
##
#F AGR.MXO( <gapname>, <sizesMaxes>[, <dirid>] )
##
## <#GAPDoc Label="AGR.MXO">
## <Mark><C>AGR.MXO( </C><M>gapname, sizesMaxes[, dirid]</M><C> )</C></Mark>
## <Item>
## The list <M>sizesMaxes</M> of subgroup orders of the classes of
## maximal subgroups of the group with &GAP; name <M>gapname</M>
## (not necessarily dense, in non-increasing order) is stored
## in <C>AtlasOfGroupRepresentationsInfo.GAPnames</C>.
## <P/>
## An example of a valid call is
## <C>AGR.MXO("A5.2",[60,24,20,12])</C>.
## </Item>
## <#/GAPDoc>
##
AGR.MXO:= function( gapname, sizesMaxes, dirid... )
local i;
# Get the arguments.
if Length( dirid ) = 1 and IsString( dirid[1] ) then
dirid:= dirid[1];
elif IsBound( AGR.DIRID ) then
dirid:= AGR.DIRID;
else
Info( InfoAtlasRep, 1, "'AGR.DIRID' is not bound" );
return;
fi;
# Entries of the form '0' mean unknown values.
for i in [ 1 .. Length( sizesMaxes ) ] do
if IsBound( sizesMaxes[i] ) and sizesMaxes[i] = 0 then
Unbind( sizesMaxes[i] );
fi;
od;
if IsBound( AtlasOfGroupRepresentationsInfo.checkData ) then
AGR.TST( gapname, sizesMaxes, "sizesMaxes",
x -> IsList( x ) and ForAll( x, IsPosInt )
and IsSortedList( Reversed( Compacted( x ) ) ),
"list of non-increasing pos. integers" );
fi;
AGR.GAPnamesRec.( gapname )[3].sizesMaxes:= sizesMaxes;
AGR.GAPnamesRec.( gapname )[4].MXO:= dirid;
end;
#############################################################################
##
#F AGR.MXS( <gapname>, <structureMaxes>[, <dirid>] )
##
## <#GAPDoc Label="AGR.MXS">
## <Mark><C>AGR.MXS( </C><M>gapname, structureMaxes[, dirid]</M><C> )</C></Mark>
## <Item>
## The list <M>structureMaxes</M> of strings describing the
## structures of the maximal subgroups of the group with &GAP; name
## <M>gapname</M> (not necessarily dense), is stored
## in <C>AtlasOfGroupRepresentationsInfo.GAPnames</C>.
## <P/>
## An example of a valid call is
## <C>AGR.MXS("A5.2",["A5","S4","5:4","S3x2"])</C>.
## </Item>
## <#/GAPDoc>
##
AGR.MXS:= function( gapname, structureMaxes, dirid... )
local i;
# Get the arguments.
if Length( dirid ) = 1 and IsString( dirid[1] ) then
dirid:= dirid[1];
elif IsBound( AGR.DIRID ) then
dirid:= AGR.DIRID;
else
Info( InfoAtlasRep, 1, "'AGR.DIRID' is not bound" );
return;
fi;
# Entries of the form '""' mean unknown values.
for i in [ 1 .. Length( structureMaxes ) ] do
if IsBound( structureMaxes[i] ) and structureMaxes[i] = "" then
Unbind( structureMaxes[i] );
fi;
od;
if IsBound( AtlasOfGroupRepresentationsInfo.checkData ) then
AGR.TST( gapname, structureMaxes, "structureMaxes",
x -> IsList( x ) and ForAll( x, IsString ),
"list of strings" );
fi;
AGR.GAPnamesRec.( gapname )[3].structureMaxes:= structureMaxes;
AGR.GAPnamesRec.( gapname )[4].MXS:= dirid;
end;
#############################################################################
##
#F AGR.STDCOMP( <gapname>, <factorCompatibility>[, <dirid>] )
##
## <#GAPDoc Label="AGR.STDCOMP">
## <Mark><C>AGR.STDCOMP( </C><M>gapname, factorCompatibility[, dirid]</M><C> )</C></Mark>
## <Item>
## The list <M>factorCompatibility</M> (with entries
## the standardization of the group with &GAP; name <M>gapname</M> ,
## the &GAP; name of a factor group,
## the standardization of this factor group, and
## <K>true</K> or <K>false</K>, indicating whether mapping the standard
## generators for <M>gapname</M> to those of <M>factgapname</M> defines an
## epimorphism) is stored
## in <C>AtlasOfGroupRepresentationsInfo.GAPnames</C>.
## <P/>
## An example of a valid call is
## <C>AGR.STDCOMP("2.A5.2",[1,"A5.2",1,true])</C>.
## </Item>
## <#/GAPDoc>
##
AGR.STDCOMP:= function( gapname, factorCompatibility, dirid... )
# Get the arguments.
if Length( dirid ) = 1 and IsString( dirid[1] ) then
dirid:= dirid[1];
elif IsBound( AGR.DIRID ) then
dirid:= AGR.DIRID;
else
Info( InfoAtlasRep, 1, "'AGR.DIRID' is not bound" );
return;
fi;
if IsBound( AtlasOfGroupRepresentationsInfo.checkData ) and
not ( IsList( factorCompatibility ) and
Length( factorCompatibility ) = 4 and
IsPosInt( factorCompatibility[1] ) and
IsString( factorCompatibility[2] ) and
IsPosInt( factorCompatibility[3] ) and
IsBool( factorCompatibility[4] ) ) then
Error( "<factorCompatibility> must be a suitable list" );
fi;
if not IsBound( AGR.GAPnamesRec.( gapname )[3].factorCompatibility ) then
AGR.GAPnamesRec.( gapname )[3].factorCompatibility:= [];
fi;
Add( AGR.GAPnamesRec.( gapname )[3].factorCompatibility,
Concatenation( factorCompatibility, [ dirid ] ) );
end;
#############################################################################
##
#F AGR.RNG( <repname>, <descr>[, <dirid>] )
##
## <#GAPDoc Label="AGR.RNG">
## <Mark><C>AGR.RNG( </C><M>repname, descr[, dirid]</M><C> )</C></Mark>
## <Item>
## Called with two strings <M>repname</M> (denoting the name
## of a file containing the generators of a matrix representation over a
## ring that is not determined by the filename)
## and <M>descr</M> (describing this ring <M>R</M>, say),
## <C>AGR.RNG</C> adds the triple
## <M>[ repname, descr, R ]</M>
## to the list stored in the <C>ringinfo</C> component of
## <Ref Var="AtlasOfGroupRepresentationsInfo"/>.
## <P/>
## An example of a valid call is
## <C>AGR.RNG("A5G1-Ar3aB0","Field([Sqrt(5)])")</C>.
## </Item>
## <#/GAPDoc>
##
AGR.RNG:= function( repname, descr, args... )
local len, dirid, data;
# Get the arguments.
len:= Length( args );
if len in [ 1, 3 ] and IsString( args[ len ] ) then
dirid:= args[ len ];
args:= args{ [ 1 .. len-1 ] };
elif IsBound( AGR.DIRID ) then
dirid:= AGR.DIRID;
else
Info( InfoAtlasRep, 1, "'AGR.DIRID' is not bound" );
return;
fi;
if IsBound( AtlasOfGroupRepresentationsInfo.checkData ) then
# Check that this representation really exists.
if not AGR.IsRepNameAvailable( repname ) then
return;
fi;
fi;
data:= [ repname, descr, EvalString( descr ) ];
Append( data, args );
Add( data, dirid );
if ForAny( AtlasOfGroupRepresentationsInfo.ringinfo,
entry -> repname = entry[1] ) then
Info( InfoAtlasRep, 1,
"data '", data, "' cannot be notified more than once" );
else
Add( AtlasOfGroupRepresentationsInfo.ringinfo, data );
fi;
end;
#############################################################################
##
#F AGR.TOCEXT( <atlasname>, <std>, <maxnr>, <files>[, <dirid>] )
##
## <#GAPDoc Label="AGR.TOCEXT">
## <Mark><C>AGR.TOCEXT( </C><M>atlasname, std, maxnr, files[, dirid]</M><C> )</C></Mark>
## <Item>
## Called with <M>atlasname</M>,
## the positive integers <M>std</M> (the standardization) and
## <M>maxnr</M> (the number of the class of maximal subgroups), and
## the list <M>files</M> (of filenames of straight line programs for
## computing generators of the <M>maxnr</M>-th maximal subgroup, using
## a straight line program for a factor group plus perhaps some straight
## line program for computing kernel generators),
## <C>AGR.TOCEXT</C> stores the information in
## <C>AtlasOfGroupRepresentationsInfo.GAPnames</C>.
## <P/>
## An example of a valid call is
## <C>AGR.TOCEXT("2A5",1,3,["A5G1-max3W1"])</C>.
## </Item>
## <#/GAPDoc>
##
AGR.TOCEXT:= function( groupname, std, maxnr, files, dirid... )
local info;
# Get the arguments.
if Length( dirid ) = 1 and IsString( dirid[1] ) then
dirid:= dirid[1];
elif IsBound( AGR.DIRID ) then
dirid:= AGR.DIRID;
else
Info( InfoAtlasRep, 1, "'AGR.DIRID' is not bound" );
return;
fi;
if IsBound( AtlasOfGroupRepresentationsInfo.checkData ) then
if not ( IsString( groupname ) and IsPosInt( std )
and IsPosInt( maxnr )
and IsList( files )
and ForAll( files, IsString ) ) then
Error( "not a valid t.o.c.ext entry" );
elif ForAll( AtlasOfGroupRepresentationsInfo.GAPnames,
x -> x[2] <> groupname ) then
Error( "'", groupname, "' is not a valid group name" );
fi;
# Check that the required programs really exist.
# (We cannot check the availability of the required program for
# computing kernel generators, since these programs will be notified
# *after* the current call, in another directory.)
if not AGR.IsPrgNameAvailable( files[1] ) then
# The program for the max. subgroup of the factor is not available.
Print( "#E factor program required by '", groupname, "' and '",
files, "' not available\n" );
return;
fi;
fi;
info:= First( AtlasOfGroupRepresentationsInfo.GAPnames,
x -> x[2] = groupname );
if not IsBound( info[3].maxext ) then
info[3].maxext:= [];
fi;
Add( info[3].maxext, [ std, maxnr, files, dirid ] );
end;
#############################################################################
##
#F AGR.API( <repname>, <info>[, <dirid>] )
##
## <#GAPDoc Label="AGR.API">
## <Mark><C>AGR.API( </C><M>repname, info[, dirid]</M><C> )</C></Mark>
## <Item>
## Called with the string <M>repname</M> (denoting the name of a
## permutation representation)
## and the list <M>info</M> (describing the point stabilizer of this
## representation),
## <C>AGR.API</C> binds the component <M>repname</M> of the record
## <C>AtlasOfGroupRepresentationsInfo.permrepinfo</C> to a record that
## describes the contents of <M>info</M>.
## <P/>
## <M>info</M> has the following entries.
## <List>
## <Item>
## At position <M>1</M>, the transitivity is stored.
## </Item>
## <Item>
## If the transitivity is zero then <M>info</M> has length two,
## and the second entry is the list of orbit lengths.
## </Item>
## <Item>
## If the transitivity is positive then <M>info</M> has length
## four or five, and the second entry is the rank of the action.
## </Item>
## <Item>
## If the transitivity is positive then the third entry is one of the
## strings <C>"prim"</C>, <C>"imprim"</C>, denoting primitivity or not.
## </Item>
## <Item>
## If the transitivity is positive then the fourth entry is either
## the string <C>"???"</C> or a string that describes the structure of
## the point stabilizer.
## If the third entry is <C>"imprim"</C> then this description consists
## of a subgroup part and a maximal subgroup part, separated by
## <C>" < "</C>.
## </Item>
## <Item>
## If the third entry is <C>"prim"</C> then the fifth entry is either
## the string <C>"???"</C>
## or the number of the class of maximal subgroups
## that are the point stabilizers.
## </Item>
## </List>
## <P/>
## An example of a valid call is
## <C>AGR.API("A5G1-p5B0",[3,2,"prim","A4",1])</C>.
## </Item>
## <#/GAPDoc>
##
AGR.API:= function( repname, info, dirid... )
local r;
# Get the arguments.
if Length( dirid ) = 1 and IsString( dirid[1] ) then
dirid:= dirid[1];
elif IsBound( AGR.DIRID ) then
dirid:= AGR.DIRID;
else
Info( InfoAtlasRep, 1, "'AGR.DIRID' is not bound" );
return;
fi;
if IsBound( AtlasOfGroupRepresentationsInfo.checkData ) then
if IsBound( AtlasOfGroupRepresentationsInfo.permrepinfo.( repname ) ) then
Error( "cannot notify '", repname, "' more than once" );
fi;
# Check that this representation really exists.
if not AGR.IsRepNameAvailable( repname ) then
return;
fi;
fi;
# The component 'dirid' is used in 'StringOfAtlasTableOfContents'.
r:= rec( transitivity:= info[1], dirid:= dirid );
if info[1] = 0 then
r.orbits:= info[2];
r.isPrimitive:= false;
else
r.rankAction:= info[2];
r.isPrimitive:= ( info[3] = "prim" );
r.stabilizer:= info[4];
if r.isPrimitive then
r.maxnr:= info[5];
fi;
fi;
AtlasOfGroupRepresentationsInfo.permrepinfo.( repname ):= r;
end;
#############################################################################
##
#F AGR.CHAR( <gapname>, <repname>, <char>, <pos>[, <charname>][, <dirid>] )
##
## <#GAPDoc Label="AGR.CHAR">
## <Mark><C>AGR.CHAR( </C><M>gapname, repname, char, pos[, charname[, dirid]]</M><C> )</C></Mark>
## <Item>
## Called with the strings <M>gapname</M>
## and <M>repname</M> (denoting the name of the representation),
## the integer <M>char</M> (the characteristic of the representation),
## and <M>pos</M> (the position or list of positions of the irreducible
## constituent(s)),
## <C>AGR.CHAR</C> stores the information in
## <C>AtlasOfGroupRepresentationsInfo.characterinfo</C>.
## <P/>
## A string describing the character can be entered as <M>charname</M>.
## <P/>
## If <M>dirid</M> is given but no <M>charname</M> is known then one can
## enter <K>fail</K> as the fifth argument.
## <P/>
## An example of a valid call is
## <C>AGR.CHAR("M11","M11G1-p11B0",0,[1,2],"1a+10a")</C>.
## </Item>
## <#/GAPDoc>
##
AGR.CHAR:= function( groupname, repname, char, pos, arg... )
local charname, dirid, map;
# Get the arguments.
if Length( arg ) >= 1 then
charname:= arg[1];
else
charname:= fail;
fi;
if Length( arg ) = 2 then
dirid:= arg[2];
elif IsBound( AGR.DIRID ) then
dirid:= AGR.DIRID;
else
Info( InfoAtlasRep, 1, "'AGR.DIRID' is not bound" );
return;
fi;
map:= AtlasOfGroupRepresentationsInfo.characterinfo;
if not IsBound( map.( groupname ) ) then
map.( groupname ):= [];
fi;
map:= map.( groupname );
if char = 0 then
char:= 1;
fi;
if not IsBound( map[ char ] ) then
map[ char ]:= [ [], [], [], [] ];
fi;
map:= map[ char ];
if IsBound( AtlasOfGroupRepresentationsInfo.checkData ) then
# Check whether we have already a character for this representation.
# (Two different representations with the same character are allowed.)
if arg[2] in map[2] and map[1][ Position( map[2], repname ) ] <> pos then
Error( "attempt to enter two different characters for ", arg[2] );
fi;
# Check that this representation really exists.
if not AGR.IsRepNameAvailable( repname ) then
return;
fi;
fi;
Add( map[1], pos );
Add( map[2], repname );
Add( map[3], charname );
Add( map[4], dirid );
# The character information forms one global object.
# It may belong to any t.o.c., and we would not consider the new entry
# if the cached t.o.c. would be taken.
AtlasOfGroupRepresentationsInfo.TOC_Cache:= rec();
AtlasOfGroupRepresentationsInfo.TableOfContents.merged:= rec();
end;
#############################################################################
##
#F AGR.CompareAsNumbersAndNonnumbers( <nam1>, <nam2> )
##
## This function is available as 'BrowseData.CompareAsNumbersAndNonnumbers'
## if the Browse package is available.
## But we must deal also with the case that this package is not available.
##
AGR.CompareAsNumbersAndNonnumbers:= function( nam1, nam2 )
local len1, len2, len, digit, comparenumber, i;
len1:= Length( nam1 );
len2:= Length( nam2 );
len:= len1;
if len2 < len then
len:= len2;
fi;
digit:= false;
comparenumber:= 0;
for i in [ 1 .. len ] do
if nam1[i] in DIGITS then
if nam2[i] in DIGITS then
digit:= true;
if comparenumber = 0 then
# first digit of a number, or previous digits were equal
if nam1[i] < nam2[i] then
comparenumber:= 1;
elif nam1[i] <> nam2[i] then
comparenumber:= -1;
fi;
fi;
else
# if digit then the current number in 'nam2' is shorter,
# so 'nam2' is smaller;
# if not digit then a number starts in 'nam1' but not in 'nam2',
# so 'nam1' is smaller
return not digit;
fi;
elif nam2[i] in DIGITS then
# if digit then the current number in 'nam1' is shorter,
# so 'nam1' is smaller;
# if not digit then a number starts in 'nam2' but not in 'nam1',
# so 'nam2' is smaller
return digit;
else
# both characters are non-digits
if digit then
# first evaluate the current numbers (which have the same length)
if comparenumber = 1 then
# nam1 is smaller
return true;
elif comparenumber = -1 then
# nam2 is smaller
return false;
fi;
digit:= false;
fi;
# now compare the non-digits
if nam1[i] <> nam2[i] then
return nam1[i] < nam2[i];
fi;
fi;
od;
if digit then
# The suffix of the shorter string is a number.
# If the longer string continues with a digit then it is larger,
# otherwise the first digits of the number decide.
if len < len1 and nam1[ len+1 ] in DIGITS then
# nam2 is smaller
return false;
elif len < len2 and nam2[ len+1 ] in DIGITS then
# nam1 is smaller
return true;
elif comparenumber = 1 then
# nam1 is smaller
return true;
elif comparenumber = -1 then
# nam2 is smaller
return false;
fi;
fi;
# Now the longer string is larger.
return len1 < len2;
end;
#############################################################################
##
#F AGR.SetGAPnamesSortDisp()
##
## Bind the component 'AtlasOfGroupRepresentationsInfo.GAPnamesSortDisp'.
##
AGR.SetGAPnamesSortDisp:= function()
local list;
list:= ShallowCopy( AtlasOfGroupRepresentationsInfo.GAPnames );
SortParallel( List( list, x -> x[1] ), list,
AGR.CompareAsNumbersAndNonnumbers );
AtlasOfGroupRepresentationsInfo.GAPnamesSortDisp:= list;
end;
#############################################################################
##
#F AGR.ParseFilenameFormat( <string>, <format> )
##
AGR.ParseFilenameFormat:= function( string, format )
local result, i, res;
string:= SplitString( string, "-" );
if Length( string ) <> Length( format[1] ) then
return fail;
fi;
result:= [];
for i in [ 1 .. Length( string ) ] do
# Loop over the '-' separated components.
res:= format[2][i]( string[i], format[1][i] );
if res = fail then
return fail;
fi;
Append( result, res );
od;
return result;
end;
#############################################################################
##
#F AtlasDataGAPFormatFile( <filename>[, "string"] )
##
## <ManSection>
## <Func Name="AtlasDataGAPFormatFile" Arg='filename[, "string"]'/>
##
## <Description>
## Let <A>filename</A> be the name of a file containing the generators of a
## representation in characteristic zero such that reading the file via
## <Ref Func="ReadAsFunction" BookName="ref"</C> yields a record
## containing the list of the generators and additional information.
## Then <Ref Func="AtlasDataGAPFormatFile"/> returns this record.
## </Description>
## </ManSection>
##
BindGlobal( "AtlasDataGAPFormatFile", function( filename, string... )
local fun;
AGR.InfoRead( "#I reading '", filename, "' started\n" );
if Length( string ) = 0 then
fun:= ReadAsFunction( filename );
else
fun:= ReadAsFunction( InputTextString( filename ) );
fi;
AGR.InfoRead( "#I reading '", filename, "' done\n" );
if fun = fail then
Info( InfoAtlasRep, 1,
"problem reading '", filename, "' as function\n" );
else
fun:= fun();
fi;
return fun;
end );
#############################################################################
##
#F AtlasDataJsonFormatFile( <filename>[, "string"] )
##
## Evaluate the contents of a Json format file that describes matrices
## in characteristic zero.
##
## Admit a prescribed ring for the matrix entries,
## given by the 'givenRing' component of the info record stored in the
## global option 'inforecord'.
##
BindGlobal( "AtlasDataJsonFormatFile", function( filename, string... )
local obj, arec, givenF, info, F, pol, facts, gen, roots, ecoeffs, pos,
found, N, B, nrows, ncols, mats, d, mat, i, j;
if Length( string ) = 0 then
obj:= AGR.GapObjectOfJsonText( StringFile( filename ) );
else
obj:= AGR.GapObjectOfJsonText( filename );
fi;
if obj.status = false then
if Length( string ) = 0 then
Info( InfoAtlasRep, 1,
"file ", filename, " does not contain valid Json" );
else
Info( InfoAtlasRep, 1,
"string <filename> does not contain valid Json" );
fi;
return fail;
fi;
arec:= obj.value;
givenF:= ValueOption( "inforecord" );
if givenF <> fail then
if IsBound( givenF.givenRing ) then
--> --------------------
--> maximum size reached
--> --------------------
[ Dauer der Verarbeitung: 0.26 Sekunden
(vorverarbeitet)
]
|