GAP 4.8.9 installation with standard packages -- copy to your CoCalc project to get it
############################################################################# ## #W access.gi GAP 4 package AtlasRep Thomas Breuer ## #Y Copyright (C) 2001, Lehrstuhl D fuer Mathematik, RWTH Aachen, Germany ## ## This file contains functions for accessing data from the ATLAS of Group ## Representations. ## ############################################################################# ## #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; str:= StringFile( filename ); if IsString( str ) then str:= ReplacedString( str, "\r", "" ); fi; return str; end; ############################################################################# ## #V AGR.ExtensionInfoCharacterTable #V AGR.HasExtensionInfoCharacterTable #V AGR.LibInfoCharacterTable ## 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 ); ############################################################################# ## ## If the IO package is not installed then error messages are avoided ## via the following assignments. ## if not IsBound( SingleHTTPRequest ) then SingleHTTPRequest:= "dummy"; fi; if not IsBound( IO_stat ) then IO_stat:= "dummy"; fi; ############################################################################# ## #F AtlasOfGroupRepresentationsTransferFile( <server>, <srvfile>, <dstfile> ) ## ## This function encapsulates the access to a file either via <C>wget</C> ## or via the <Package>IO</Package> package ## <Cite Key="IO"/><Index>IO package</Index>. ## <P/> ## The source file is described by the server name <A>server</A> and the ## path <A>srvfile</A>. ## The file is written to the filename <A>dstfile</A>. ## ## <#GAPDoc Label="IO_or_wget"> ## When access to remote data is enabled ## (see Section <Ref Subsect="subsect:Local or remote access"/>) ## then one needs either the &GAP; package <Package>IO</Package> ## <Cite Key="IO"/><Index>IO package</Index> ## or the external program <F>wget</F><Index Key="wget"><F>wget</F></Index> ## for accessing data files. ## <P/> ## The chosen alternative is given by the value of the <C>wget</C> component ## of the global variable <Ref Var="AtlasOfGroupRepresentationsInfo"/>. ## <P/> ## If this component has the value <K>true</K> then only <F>wget</F> ## is tried, ## if the value is <K>false</K> then only the <Package>IO</Package> package ## is used. ## If this component is not bound or bound to another value than <K>true</K> ## or <K>false</K> (this is also the default) ## then the <Package>IO</Package> package <Index>IO package</Index> ## is preferred to <F>wget</F><Index Key="wget"><F>wget</F></Index> ## if this package is available, and otherwise <F>wget</F> is tried. ## <P/> ## Note that the system program <F>wget</F> may be not available, ## and that it may require some work to install it; ## hints for that can be found on the home page of the ## <Package>AtlasRep</Package> package (see ## Section <Ref Sect="sect:Web Services for the AtlasRep Package"/>). ## <#/GAPDoc> ## ## If the access worked then <K>true</K> is returned, ## otherwise <K>false</K>. ## BindGlobal( "AtlasOfGroupRepresentationsTransferFile", function( server, srvfile, dstfile ) local pos, dstdir, wget, io, result; # Check whether 'dstfile' can be written. pos:= Positions( dstfile, '/' ); if pos = [] then dstdir:= "."; else dstdir:= dstfile{ [ 1 .. pos[ Length( pos ) ] - 1 ] }; fi; if not IsWritableFile( dstdir ) then Info( InfoWarning, 1, "Package AtlasRep:\n", "#I cannot write to the directory '", dstdir, "',\n", "#I perhaps change the AtlasRep data directories using\n", "#I 'SetUserPreference( \"AtlasRep\",", " \"AtlasRepDataDirectory\", ... )'" ); return false; fi; # Determine admissible alternatives. wget:= true; io:= true; if IsBound( AtlasOfGroupRepresentationsInfo.wget ) then if AtlasOfGroupRepresentationsInfo.wget = true then io:= false; elif AtlasOfGroupRepresentationsInfo.wget = false then wget:= false; fi; fi; srvfile:= Concatenation( "/", srvfile); # Try the IO package if it is admissible. if io and LoadPackage( "io" ) = true then Info( InfoAtlasRep, 2, "calling SingleHTTPRequest to get ", server, srvfile ); result:= SingleHTTPRequest( server, 80, "GET", srvfile, rec(), false, dstfile ); if result.statuscode <> 200 then Info( InfoAtlasRep, 2, "SingleHTTPRequest failed with status\n#I ", result.status ); else return true; fi; fi; # Try wget if it is admissible. if wget then wget:= Filename( DirectoriesSystemPrograms(), "wget" ); if wget = fail then Info( InfoAtlasRep, 1, "no `wget' executable found" ); else Info( InfoAtlasRep, 2, "calling `wget' to get `", server, srvfile, "'" ); result:= Process( DirectoryCurrent(), wget, InputTextNone(), OutputTextNone(), [ "-q", "-O", dstfile, Concatenation( "http://", server, srvfile ) ] ); if result <> 0 then Info( InfoAtlasRep, 2, "`wget' failed to fetch `", Concatenation( "http://", server, srvfile ), "'" ); RemoveFile( dstfile ); else return true; fi; fi; fi; # No admissible alternative was successful. return false; 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:= "default functions (read text files)", active:= true, location:= function( filename, groupname, dirname, type ) local pref, datadirs, info, name, namegz, names, fname; pref:= UserPreference( "AtlasRep", "AtlasRepDataDirectory" ); if dirname in [ "datagens", "dataword" ] then datadirs:= [ Directory( Concatenation( pref, dirname ) ) ]; else for info in AtlasOfGroupRepresentationsInfo.private do if dirname = info[2] then datadirs:= [ Directory( info[1] ) ]; break; fi; od; if not IsBound( datadirs ) then Error( "no private directory with identifier `", dirname, "'" ); fi; fi; # There may be an uncompressed or a compressed version. # If both are available then prefer the uncompressed version. if IsString( filename ) then name:= Filename( datadirs, filename ); if name = fail or not IsExistingFile( name ) then namegz:= Filename( datadirs, Concatenation( filename, ".gz" ) ); if namegz = fail then # No version is available yet. return Filename( datadirs[1], filename ); else return namegz; fi; else return name; fi; else # Treat the list entries separately. names:= []; for fname in filename do name:= Filename( datadirs, fname ); if name = fail or not IsExistingFile( name ) then namegz:= Filename( datadirs, Concatenation( fname, ".gz" ) ); if namegz = fail then # No version is available yet. Add( names, Filename( datadirs[1], fname ) ); else Add( names, namegz ); fi; else Add( names, name ); fi; od; return names; fi; end, fetch:= function( filepath, filename, groupname, dirname, type ) local triple, info, dirnam, result, gzip; # Get the group name info. triple:= First( AtlasOfGroupRepresentationsInfo.groupnames, x -> x[3] = groupname ); if triple = fail then Error( "illegal value of <groupname>" ); fi; # Try to fetch the remote file. result:= fail; for info in AtlasOfGroupRepresentationsInfo.servers do dirnam:= Concatenation( info[2], triple[1], "/", triple[2] ); # Compose the name of the directory on the server. if dirname = "dataword" then Append( dirnam, "/words/" ); elif filename[ Length( filename ) ] = 'g' then Append( dirnam, "/gap0/" ); else Append( dirnam, "/mtx/" ); fi; # Fetch the file if possible. result:= AtlasOfGroupRepresentationsTransferFile( info[1], Concatenation( dirnam, filename ), filepath ); if result = false then Info( InfoAtlasRep, 2, "cannot transfer file from '", info[1], "'\n", "#I to '", filepath, "'" ); else break; fi; od; if result = false then Info( InfoAtlasRep, 1, "file '", filename, "' not fetched" ); return false; fi; # The file has just been fetched, perform postprocessing. # (For MeatAxe format only: If wanted then compress the new file.) if AtlasOfGroupRepresentationsInfo.compress = true and dirnam[ Length( dirnam ) - 1 ] = 'x' then gzip:= Filename( DirectoriesSystemPrograms(), "gzip" ); if gzip = fail or not IsExecutableFile( gzip ) then Info( InfoAtlasRep, 1, "no `gzip' executable found" ); else result:= Process( DirectoryCurrent(), gzip, InputTextNone(), OutputTextNone(), [ filepath ] ); if result = fail then Info( InfoAtlasRep, 2, "impossible to compress file `", filepath, "'" ); fi; fi; fi; return true; end, contents:= function( filepath, filename, groupname, dirname, type ) local len, i; if IsString( filepath ) then len:= Length( filepath ); if 3 < len and filepath{ [ len-2 .. len ] } = ".gz" then filepath:= filepath{ [ 1 .. len-3 ] }; fi; else filepath:= ShallowCopy( filepath ); for i in [ 1 .. Length( filepath ) ] do len:= Length( filepath[i] ); if 3 < len and filepath[i]{ [ len-2 .. len ] } = ".gz" then filepath[i]:= filepath[i]{ [ 1 .. len-3 ] }; fi; od; fi; return type[2].ReadAndInterpretDefault( filepath ); end, ), rec( description:= "read MeatAxe binary not text format", active:= false, location:= function( filename, groupname, dirname, type ) local pref, datadirs, info, names, fname, name; if not type[1] in [ "perm", "matff" ] then return fail; fi; pref:= UserPreference( "AtlasRep", "AtlasRepDataDirectory" ); if dirname = "datagens" then datadirs:= [ Directory( Concatenation( pref, dirname ) ) ]; else for info in AtlasOfGroupRepresentationsInfo.private do if dirname = info[2] then datadirs:= [ Directory( info[1] ) ]; break; fi; od; if not IsBound( datadirs ) then Error( "no private directory with identifier `", dirname, "'" ); fi; fi; # A list of file names is given, and the files are not compressed. # Replace the text format names by binary format names. filename:= List( filename, nam -> ReplacedString( nam, ".m", ".b" ) ); names:= []; for fname in filename do name:= Filename( datadirs, fname ); if name = fail then # No version is available yet. Add( names, Filename( datadirs[1], fname ) ); else Add( names, name ); fi; od; return names; end, fetch:= function( filepath, filename, groupname, dirname, type ) local triple, info, dirnam, result; # Get the group name info. triple:= First( AtlasOfGroupRepresentationsInfo.groupnames, x -> x[3] = groupname ); if triple = fail then Error( "illegal value of <groupname>" ); fi; # Try to fetch the remote file. result:= fail; filename:= ReplacedString( filename, ".m", ".b" ); for info in AtlasOfGroupRepresentationsInfo.servers do # Fetch the file if possible. result:= AtlasOfGroupRepresentationsTransferFile( info[1], Concatenation( info[2], triple[1], "/", triple[2], "/bin/", filename ), filepath ); if result = false then Info( InfoAtlasRep, 2, "no connection to AtlasRep server ", info[1] ); else break; fi; od; if result = false then Info( InfoAtlasRep, 1, "no file `", filename, "' found on the servers" ); return false; fi; # (Do not compress the new file, it is in binary format.) return true; end, contents:= function( filepath, filename, groupname, dirname, type ) # This function is called only for the types "perm" and "matff", # and binary format files are *not* compressed. return List( filepath, FFMatOrPermCMtxBinary ); end, ), #T The following is currently useless because of an unlucky files format. # rec( # description:= "read GAP format not MeatAxe format", # # active:= false, # # location:= function( filename, groupname, dirname, type ) # local datadirs, info, names, fname, name; # # # (Does the same as the `location' function for MeatAxe binary format, # # except that we replace the suffix of the filename by `.g' not `.b'.) # if not type[1] in [ "perm", "matff" ] then # return fail; # fi; # if dirname = "datagens" then # datadirs:= DirectoriesPackageLibrary( "atlasrep", dirname ); # else # for info in AtlasOfGroupRepresentationsInfo.private do # if dirname = info[2] then # datadirs:= [ Directory( info[1] ) ]; # break; # fi; # od; # if not IsBound( datadirs ) then # Error( "no private directory with identifier `", dirname, "'" ); # fi; # fi; # # # A list of file names is given, and the files are not compressed. # # Replace the text format names by binary format names. # filename:= List( filename, nam -> ReplacedString( nam, ".m", ".g" ) ); # names:= []; # for fname in filename do # name:= Filename( datadirs, fname ); # if name = fail then # # No version is available yet. # Add( names, Filename( datadirs[1], fname ) ); # else # Add( names, name ); # fi; # od; # return names; ## alternative for ONE file with SEVERAL generators: ## # Replace the list of text format names by one GAP format name. ## filename:= ReplacedString( filename[1], ".m1", ".g" ); ## name:= Filename( datadirs, filename ); ## if name = fail then ## # No version is available yet. ## return Filename( datadirs[1], filename ); ## else ## return name; ## fi; # end, # # fetch:= function( filepath, filename, groupname, dirname, type ) # local triple, info, dirnam, result; # # # (Does the same as the `fetch' function for MeatAxe binary format, # # except that the source file is expected in `gap' not `bin'.) # # Get the group name info. # triple:= First( AtlasOfGroupRepresentationsInfo.groupnames, # x -> x[3] = groupname ); # if triple = fail then # Error( "illegal value of <groupname>" ); # fi; # # # Try to fetch the remote file. # result:= fail; # filename:= ReplacedString( filename, ".m", ".g" ); # for info in AtlasOfGroupRepresentationsInfo.servers do # # # Fetch the file if possible. # result:= AtlasOfGroupRepresentationsTransferFile( info[1], # Concatenation( info[2], triple[1], "/", triple[2], # "/gap/", filename ), # filepath ); # if result = false then # Info( InfoAtlasRep, 2, # "no connection to AtlasRep server ", info[1] ); # else # break; # fi; # # od; # if result = false then # Info( InfoAtlasRep, 1, # "no file `", filename, "' found on the servers" ); # return false; # fi; # # # (Do not compress the new file, it is not in MeatAxe text format.) # return true; # end, # # contents:= function( filepath, filename, groupname, dirname, type ) # # This function is called only for the types "perm" and "matff", # # and GAP format files are *not* compressed. # return List( filepath, AtlasDataGAPFormatFile ); ## alternative for ONE file with SEVERAL generators: ## return AtlasDataGAPFormatFile( filepath ); # end, # ), rec( description:= "direct access to a local server", active:= false, location:= function( filename, groupname, dirname, type ) local triple, dirnam, name, names, fname; # This is meaningful only for official data # and if there is a local server. if not ( dirname in [ "datagens", "dataword" ] and IsBound( AtlasOfGroupRepresentationsInfo.localserver ) ) then return fail; fi; # Get the group name info. triple:= First( AtlasOfGroupRepresentationsInfo.groupnames, x -> x[3] = groupname ); if triple = fail then Error( "illegal value of <groupname>" ); fi; # Compose the name of the directory on the server. dirnam:= Concatenation( AtlasOfGroupRepresentationsInfo.localserver, triple[1], "/", triple[2] ); if dirname = "dataword" then Append( dirnam, "/words/" ); elif filename[ Length( filename ) ] = 'g' then Append( dirnam, "/gap0/" ); else Append( dirnam, "/mtx/" ); fi; # Check whether the file(s) exist(s). if IsString( filename ) then name:= Concatenation( dirnam, filename ); if IsExistingFile( name ) then return name; fi; return fail; else names:= []; for fname in filename do name:= Concatenation( dirnam, fname ); if IsExistingFile( name ) then Add( names, name ); else return fail; fi; od; return names; fi; end, fetch:= function( filepath, filename, groupname, dirname, type ) # The `location' function has checked that the file exists. return true; end, contents:= function( filepath, filename, groupname, dirname, type ) # We need not care about compressed files. return type[2].ReadAndInterpretDefault( filepath ); end, ), ] ); ############################################################################# ## #F AGR.CrcFileFits( <filename>, <path> ) ## AGR.CrcFileFits:= function( filename, path ) local crc, len; crc:= First( AtlasOfGroupRepresentationsInfo.filenames, p -> p[1] = filename ); if crc = fail then return false; fi; len:= Length( path ); if path{ [ len - 2 .. len ] } = ".gz" then path:= path{ [ 1 .. len - 3 ] }; fi; if crc[2] = CrcFile( path ) then return true; else Info( InfoWarning, 1, "CrcFile value of\n", "#I '", path, "'\n", "#I does not match, ignoring this file" ); return false; fi; end; ############################################################################# ## #F AtlasOfGroupRepresentationsLocalFilename( <dirname>, <groupname>, #F <filename>, <type> ) ## InstallGlobalFunction( AtlasOfGroupRepresentationsLocalFilename, function( dirname, groupname, filename, type ) local cand, r, path, i; cand:= []; for r in Reversed( AtlasOfGroupRepresentationsInfo.accessFunctions ) do if r.active = true then path:= r.location( filename, groupname, dirname, type ); if path <> fail then # Check whether the CRC values fit. if IsString( path ) then path:= [ path ]; filename:= [ filename ]; fi; for i in [ 1 .. Length( filename ) ] do path[i]:= [ path[i], IsExistingFile( path[i] ) ]; od; if ForAll( path, x -> x[2] ) then # This has priority, do not consider other sources. cand:= [ [ r, path ] ]; break; else Add( cand, [ r, path ] ); fi; fi; fi; od; return cand; end ); ############################################################################# ## #F AtlasOfGroupRepresentationsLocalFilenameTransfer( <dirname>, <groupname>, #F <filename>, <type> ) ## InstallGlobalFunction( AtlasOfGroupRepresentationsLocalFilenameTransfer, function( dirname, groupname, filename, type ) local cand, list, filenamex, result, ok, i; # Determine the local directory where to look for the file, # and the functions that claim to be applicable. cand:= AtlasOfGroupRepresentationsLocalFilename( dirname, groupname, filename, type ); if IsString( filename ) then filenamex:= [ filename ]; else filenamex:= filename; fi; for list in cand do if Length( list[2] ) = Length( filenamex ) then # This is the situation we can handle. if IsString( filename ) then result:= [ list[2][1][1], list[1] ]; else result:= [ List( list[2], x -> x[1] ), list[1] ]; fi; ok:= true; for i in [ 1 .. Length( list[2] ) ] do if list[2][i][2] then # This file is already available. if dirname in [ "datagens", "dataword" ] then # Check its crc value. if not AGR.CrcFileFits( filenamex[i], list[2][i][1] ) then return fail; fi; fi; elif AtlasOfGroupRepresentationsInfo.remote = true and dirname in [ "datagens", "dataword" ] and list[1].fetch( list[2][i][1], filenamex[i], groupname, dirname, type ) then # We have created a new local file. # Check its crc value, if not AGR.CrcFileFits( filenamex[i], list[2][i][1] ) then return fail; fi; else # We cannot fetch the file. ok:= false; break; fi; od; if ok then # Return path(s) and access functions. return result; fi; fi; od; # Not all files can be made available, or not all crc values fit. Info( InfoAtlasRep, 1, "no file(s) `", filename, "' found in the local directories" ); return fail; end ); ############################################################################# ## #F AtlasOfGroupRepresentationsTestTableOfContentsRemoteUpdates() ## InstallGlobalFunction( AtlasOfGroupRepresentationsTestTableOfContentsRemoteUpdates, function() local version, inforec, home, server, path, dstfilename, result, lines, pref, datadirs, line, pos, pos2, filename, filenames, localfile, servdate, stat; if LoadPackage( "io" ) <> true then Info( InfoAtlasRep, 1, "the package IO is not available" ); return fail; fi; # Download the file that lists the changes. version:= InstalledPackageVersion( "atlasrep" ); inforec:= First( PackageInfo( "atlasrep" ), r -> r.Version = version ); home:= inforec.PackageWWWHome; if home{ [ 1 .. 7 ] } = "http://" then home:= home{ [ 8 .. Length( home ) ] }; fi; server:= home{ [ 1 .. Position( home, '/' ) - 1 ] }; path:= home{ [ Position( home, '/' ) + 1 .. Length( home ) ] }; dstfilename:= Filename( DirectoryTemporary(), "changes.htm" ); result:= []; if AtlasOfGroupRepresentationsTransferFile( server, Concatenation( path, "/htm/data/changes.htm" ), dstfilename ) then lines:= SplitString( AGR.StringFile( dstfilename ), "\n" ); lines:= Filtered( lines, x -> 20 < Length( x ) and x{ [ 1 .. 4 ] } = "<tr>" and x{ [ -3 .. 0 ] + Length( x ) } = " -->" ); pref:= UserPreference( "AtlasRep", "AtlasRepDataDirectory" ); 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 ] }; if PositionSublist( filename, "<it>i</it>" ) <> fail then filenames:= List( [ "1", "2" ], i -> ReplacedString( filename, "<it>i</it>", i ) ); else filenames:= [ filename ]; fi; for filename in filenames do localfile:= Filename( datadirs, filename ); if localfile <> fail then if not IsExistingFile( localfile ) then localfile:= Concatenation( localfile, ".gz" ); fi; if IsExistingFile( 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; od; fi; od; fi; return result; end ); ############################################################################# ## #F AGR.FileContents( <dirname>, <groupname>, <filename>, <type> ) ## AGR.FileContents:= function( dirname, groupname, filename, type ) local result; if not ( IsString( filename ) or ( IsList( filename ) and ForAll( filename, IsString ) ) ) then Error( "<file> must be a string or a list of strings" ); fi; result:= AtlasOfGroupRepresentationsLocalFilenameTransfer( dirname, groupname, filename, type ); if result = fail then return fail; fi; # 3. We have the local file(s). Try to extract the contents. return result[2].contents( result[1], filename, groupname, dirname, type ); end; ############################################################################# ## #F FilenameAtlas( <dirname>, <groupname>, <filename> ) ## InstallGlobalFunction( FilenameAtlas, function( dirname, groupname, filename ) local type; if not IsBound( AtlasOfGroupRepresentationsInfo.WarnedFilenameAtlas ) then AtlasOfGroupRepresentationsInfo.WarnedFilenameAtlas:= true; Info( InfoWarning, 1, "FilenameAtlas is deprecated,\n#I use ", "`AtlasOfGroupRepresentationsLocalFilenameTransfer' instead" ); fi; for type in AGR.DataTypes( "rep", "prg" ) do if AGR.ParseFilenameFormat( filename, type[2].FilenameFormat ) <> fail then return AtlasOfGroupRepresentationsLocalFilenameTransfer( dirname, groupname, filename, type )[1]; fi; od; return fail; 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>. ## 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 alreay 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.GNAN( <gapname>, <atlasname> ) ## ## <#GAPDoc Label="AGR.GNAN"> ## <Mark><C>AGR.GNAN( <A>gapname</A>, <A>atlasname</A> )</C></Mark> ## <Item> ## Called with two strings <A>gapname</A> (the &GAP; name of the group) ## and <A>atlasname</A> (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 <Package>ATLAS</Package> ## names and &GAP; names of the groups. ## <P/> ## This function may be used also for private extensions of the database. ## <P/> ## An example of a valid call is ## <C>AGR.GNAN("A5.2","S5")</C>. ## </Item> ## <#/GAPDoc> ## AGR.GNAN:= function( gapname, atlasname ) local value, r, pos; 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() ]; AddSet( AtlasOfGroupRepresentationsInfo.GAPnames, value ); AGR.GAPnamesRec.( gapname ):= value; end; ############################################################################# ## #F AGR.GRP( <dirname>, <simpname>, <groupname> ) ## ## <#GAPDoc Label="AGR.GRP"> ## <Mark><C>AGR.GRP( <A>dirname</A>, <A>simpname</A>, <A>groupname</A>)</C></Mark> ## <Item> ## Called with three strings, <C>AGR.GRP</C> stores in the ## <C>groupname</C> component of ## <Ref Var="AtlasOfGroupRepresentationsInfo"/> in which path on the ## servers the data about the group with &ATLAS; name <A>groupname</A> ## can be found. ## <P/> ## This function is <E>not</E> intended for private extensions of the ## database. ## <P/> ## An example of a valid call is ## <C>AGR.GRP("alt","A5","S5")</C>. ## </Item> ## <#/GAPDoc> ## AGR.GRP:= function( dirname, simpname, groupname ) local entry; if ForAll( AtlasOfGroupRepresentationsInfo.GAPnames, pair -> pair[2] <> groupname ) then # There is no corresponding GAP name. AddSet( AtlasOfGroupRepresentationsInfo.GAPnames, Immutable( [ groupname, groupname ] ) ); AGR.SetGAPnamesSortDisp(); Info( InfoAtlasRep, 1, "no GAP name known for `", groupname, "'" ); fi; if IsBound( AtlasOfGroupRepresentationsInfo.checkData ) then entry:= First( AtlasOfGroupRepresentationsInfo.groupnames, l -> l[3] = groupname ); if entry <> fail then # Check whether the group is already notified. if entry[1] = dirname then Info( InfoAtlasRep, 1, "group with Atlas name `", groupname, "' already notified" ); else Error( "group with Atlas name `", groupname, "' notified for different directories!" ); fi; if entry[2] <> simpname then Error( "group with Atlas name `", groupname, "' notified for different simple groups!" ); fi; return; fi; fi; # Notify the group. Add( AtlasOfGroupRepresentationsInfo.groupnames, [ dirname, simpname, groupname ] ); end; ############################################################################# ## #F AGR.TOC( <typename>, <filename>, <crc> ) ## ## <#GAPDoc Label="AGR.TOC"> ## <Mark><C>AGR.TOC( <A>typename</A>, <A>filename</A>, <A>crcfile</A> )</C></Mark> ## <Item> ## Called with two strings <A>typename</A> and <A>filename</A>, ## and a list <A>crc</A> of integers, ## <C>AGR.TOC</C> notifies an entry to the <C>TableOfContents.remote</C> ## component of <Ref Var="AtlasOfGroupRepresentationsInfo"/>, ## where <A>typename</A> must be the name of the data type to which ## the entry belongs, ## <A>filename</A> must be the prefix of the data file(s), ## and <A>crc</A> must be the list of <Ref BookName="ref" Func="CrcFile"/> ## values of the file(s). ## <P/> ## This function is <E>not</E> intended for private extensions of the ## database. ## <P/> ## An example of a valid call is ## <C>AGR.TOC("perm","S5G1-p5B0.m",[-3581724,115937465])</C>. ## </Item> ## <#/GAPDoc> ## AGR.TOC:= function( type, string, crc ) local n, t, stringx, record, entry, groupname, added, j; n:= Length( crc ); # Parse the filename with the given format info. # type:= First( AGR.DataTypes( "rep", "prg" ), x -> x[1] = type ); for t in AGR.DataTypes( "rep", "prg" ) do if t[1] = type then type:= t; break; fi; od; if 1 < n then stringx:= Concatenation( string, "1" ); else stringx:= string; fi; record:= AtlasTableOfContents( "remote" ).TableOfContents; entry:= AGR.ParseFilenameFormat( stringx, type[2].FilenameFormat ); if entry = fail then Info( InfoAtlasRep, 1, "`", [ type, string, crc ], "' is not a valid t.o.c. entry" ); return; fi; # Get the list for the data in the record for the group name. groupname:= entry[1]; if IsBound( AtlasOfGroupRepresentationsInfo.checkData ) and ForAll( AtlasOfGroupRepresentationsInfo.groupnames, x -> x[3] <> 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, stringx ); Add( AtlasOfGroupRepresentationsInfo.filenames, Immutable( [ stringx, crc[1] ] ) ); # Add the other filenames if necessary. if added then for j in [ 2 .. n ] do entry[ Length( entry ) ]:= j; stringx:= Concatenation( string, String( j ) ); added:= type[2].AddFileInfo( record.( type[1] ), entry, stringx ) and added; Add( AtlasOfGroupRepresentationsInfo.filenames, Immutable( [ stringx, crc[j] ] ) ); od; fi; if not added then Info( InfoAtlasRep, 1, "`", [ type, string, crc ], "' is not a valid t.o.c. entry" ); fi; end; ############################################################################# ## #F AGR.GRS( <gapname>, <size> ) ## ## <#GAPDoc Label="AGR.GRS"> ## <Mark><C>AGR.GRS( <A>gapname</A>, <A>size</A> )</C></Mark> ## <Item> ## Called with the string <A>gapname</A> (the &GAP; name of the group) ## and the integer <A>size</A> (the order of the group), ## <C>AGR.GRS</C> stores this information 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 ) if IsBound( AtlasOfGroupRepresentationsInfo.checkData ) then AGR.TST( gapname, size, "size", IsPosInt, "positive integer" ); fi; AGR.GAPnamesRec.( gapname )[3].size:= size; end; ############################################################################# ## #F AGR.MXN( <gapname>, <nrMaxes> ) ## ## <#GAPDoc Label="AGR.MXN"> ## <Mark><C>AGR.MXN( <A>gapname</A>, <A>nrMaxes</A> )</C></Mark> ## <Item> ## Called with the string <A>gapname</A> (the &GAP; name of the group) ## and the integer <A>nrMaxes</A> (the number of classes of maximal ## subgroups of the group), ## <C>AGR.MXN</C> stores the information 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 ) if IsBound( AtlasOfGroupRepresentationsInfo.checkData ) then AGR.TST( gapname, nrMaxes, "nrMaxes", IsPosInt, "positive integer" ); fi; AGR.GAPnamesRec.( gapname )[3].nrMaxes:= nrMaxes; end; ############################################################################# ## #F AGR.MXO( <gapname>, <sizesMaxes> ) ## ## <#GAPDoc Label="AGR.MXO"> ## <Mark><C>AGR.MXO( <A>gapname</A>, <A>sizesMaxes</A> )</C></Mark> ## <Item> ## Called with the string <A>gapname</A> (the &GAP; name of the group) ## and the list <A>sizesMaxes</A> (of subgroup orders of the classes of ## maximal subgroups of the group, not necessarily dense, ## in non-increasing order), ## <C>AGR.MXO</C> stores the information 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 ) 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; end; ############################################################################# ## #F AGR.MXS( <gapname>, <structureMaxes> ) ## ## <#GAPDoc Label="AGR.MXS"> ## <Mark><C>AGR.MXS( <A>gapname</A>, <A>structureMaxes</A> )</C></Mark> ## <Item> ## Called with the string <A>gapname</A> (the &GAP; name of the group) ## and the list <A>structureMaxes</A> (of strings describing the ## structures of the maximal subgroups of the group, not necessarily dense), ## <C>AGR.MXS</C> stores the information 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 ) 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; end; ############################################################################# ## ## AGR.KERPRG( <gapname>, <kernelProgram> ) ## ## <#GAPDoc Label="AGR.KERPRG"> ## <Mark><C>AGR.KERPRG( <A>gapname</A>, <A>kernelProgram</A> )</C></Mark> ## <Item> ## Called with the string <A>gapname</A> (the &GAP; name of the group) ## and the list <A>kernelProgram</A> (with entries the standardization of ## the group, the &GAP; name of a factor group, and the list of lines of a ## straight line program that computes generators of the kernel of the ## epimorphism from the group to the factor group), ## <C>AGR.KERPRG</C> stores the information in ## <C>AtlasOfGroupRepresentationsInfo.GAPnames</C>. ## <P/> ## An example of a valid call is ## <C>AGR.KERPRG("2.J2",[1,"J2",[[[1,2]]]])</C>. ## </Item> ## <#/GAPDoc> ## AGR.KERPRG:= function( gapname, kernelProgram ) if IsBound( AtlasOfGroupRepresentationsInfo.checkData ) and not ( IsList( kernelProgram ) and Length( kernelProgram ) = 3 and IsPosInt( kernelProgram[1] ) and IsString( kernelProgram[2] ) and IsDenseList( kernelProgram[3] ) ) then Error( "<kernelProgram> must be a suitable list" ); fi; if not IsBound( AGR.GAPnamesRec.( gapname )[3].kernelPrograms ) then AGR.GAPnamesRec.( gapname )[3].kernelPrograms:= []; fi; Add( AGR.GAPnamesRec.( gapname )[3].kernelPrograms, kernelProgram ); end; ############################################################################# ## #F AGR.STDCOMP( <gapname>, <factorCompatibility> ) ## ## <#GAPDoc Label="AGR.STDCOMP"> ## <Mark><C>AGR.STDCOMP</C></Mark> ## <Item> ## Called with the string <A>gapname</A> (the &GAP; name of the group) ## and the list <A>factorCompatibility</A> (with entries ## the standardization of the group, 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 <A>gapname</A> to those of <A>factgapname</A> defines an ## epimorphism), ## <C>AGR.STDCOMP</C> stores the information 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 ) 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, factorCompatibility ); end; ############################################################################# ## #F AGR.RNG( <repname>, <descr> ) ## ## <#GAPDoc Label="AGR.RNG"> ## <Mark><C>AGR.RNG( <A>repname</A>, <A>descr</A> )</C></Mark> ## <Item> ## Called with two strings <A>repname</A> (denoting the name ## of a file containing the generators of a matrix representation over a ## ring that is not determined by the filename) ## and <A>descr</A> (describing this ring <M>R</M>, say), ## <C>AGR.RNG</C> adds the triple ## <M>[ <A>repname</A>, <A>descr</A>, 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 ) local triple; if IsBound( AtlasOfGroupRepresentationsInfo.checkData ) then # Check that this representation really exists. if not AGR.IsRepNameAvailable( repname ) then return; fi; fi; triple:= [ repname, descr, EvalString( descr ) ]; if triple in AtlasOfGroupRepresentationsInfo.ringinfo then Info( InfoAtlasRep, 1, "triple `", triple, "' cannot be notified more than once" ); else Add( AtlasOfGroupRepresentationsInfo.ringinfo, triple ); fi; end; ############################################################################# ## #F AGR.TOCEXT( <atlasname>, <std>, <maxnr>, <files> ) ## ## <#GAPDoc Label="AGR.TOCEXT"> ## <Mark><C>AGR.TOCEXT( <A>atlasname</A>, <A>std</A>, <A>maxnr</A>, <A>files</A> )</C></Mark> ## <Item> ## Called with the string <A>atlasname</A> (the &ATLAS; name of the ## group), the positive integers <A>std</A> (the standardization) and ## <A>maxnr</A> (the number of the class of maximal subgroups), and ## the list <A>files</A> (of filenames of straight line programs for ## computing generators of the <A>maxnr</A>-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 the <C>maxext</C> component ## of the <A>atlasname</A> component of the <C>"remote"</C> ## table of contents. ## <P/> ## An example of a valid call is ## <C>AGR.TOCEXT("2A5",1,3,["A5G1-max3W1"])</C>. ## </Item> ## <#/GAPDoc> ## AGR.TOCEXT:= function( atlasname, std, maxnr, files ) local r, info; if IsBound( AtlasOfGroupRepresentationsInfo.checkData ) then if not ( IsString( atlasname ) 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] <> atlasname ) then Error( "`", atlasname, "' is not a valid group name" ); fi; # Check that the required programs really exist. if not AGR.IsPrgNameAvailable( files[1] ) then # The program for the max. subgroup of the factor is not available. return; elif IsBound( files[2] ) then # Check whether the required program for computing kernel generators # is available. info:= First( AtlasOfGroupRepresentationsInfo.GAPnames, x -> x[2] = atlasname )[3]; if not ( IsBound( info.kernelPrograms ) and ForAny( info.kernelPrograms, x -> x[2] = files[2] ) ) then Print( "#E kernel program required by `", atlasname, "' and `", files, "' not available\n" ); return; fi; fi; fi; r:= AtlasTableOfContents( "remote" ).TableOfContents; if not IsBound( r.( atlasname ) ) then r.( atlasname ):= rec(); fi; r:= r.( atlasname ); if not IsBound( r.maxext ) then r.maxext:= []; fi; Add( r.maxext, [ std, maxnr, files ] ); end; ############################################################################# ## #F AGR.API( <repname>, <info> ) ## ## <#GAPDoc Label="AGR.API"> ## <Mark><C>AGR.API( <A>repname</A>, <A>info</A> )</C></Mark> ## <Item> ## Called with the string <A>repname</A> (denoting the name of a ## permutation representation) ## and the list <A>info</A> (describing the point stabilizer of this ## representation), ## <C>AGR.API</C> binds the component <A>repname</A> of the record ## <C>AtlasOfGroupRepresentationsInfo.permrepinfo</C> to <A>info</A>. ## <P/> ## <A>info</A> has the following entries. ## <List> ## <Item> ## At position <M>1</M>, the transitivity is stored. ## </Item> ## <Item> ## If the transitivity is zero then the second entry is the list of ## orbit lengths. ## </Item> ## <Item> ## If the transitivity is positive then 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 a string ## describing 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 ## <C>"???"</C> ## or it denotes 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 ) local r; 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; r:= rec( transitivity:= info[1] ); if info[1] = 0 then r.orbits:= info[2]; 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( <groupname>, <repname>, <char>, <pos>[, <charname>] ) ## ## <#GAPDoc Label="AGR.CHAR"> ## <Mark><C>AGR.CHAR( <A>groupname</A>, <A>repname</A>, <A>char</A>, <A>pos</A>[, <A>charname</A>] )</C></Mark> ## <Item> ## Called with the strings <A>groupname</A> (the &GAP; name of the group) ## and <A>repname</A> (denoting the name of the representation), ## the integer <A>char</A> (the characteristic of the representation), ## and <A>pos</A> (the position or list of positions of the irreducible ## constituent(s)), ## <C>AGR.CHAR</C> stores the information in ## <C>AtlasOfGroupRepresentationsInfo.characterinfo</C>. ## A string describing the character can be entered as <A>charname</A>. ## <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( arg ) local map, groupname, repname, char, pos; map:= AtlasOfGroupRepresentationsInfo.characterinfo; groupname:= arg[1]; repname:= arg[2]; char:= arg[3]; pos:= arg[4]; 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 ); if Length( arg ) = 5 then Add( map[3], arg[5] ); else Add( map[3], fail ); fi; 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> ) ## InstallGlobalFunction( AtlasDataGAPFormatFile, function( filename ) local record; InfoRead1( "#I reading `", filename, "' started\n" ); record:= ReadAsFunction( filename ); InfoRead1( "#I reading `", filename, "' done\n" ); if record = fail then Info( InfoAtlasRep, 1, "problem reading `", filename, "' as function\n" ); else record:= record(); fi; return record; end ); ############################################################################# ## #F AtlasStringOfFieldOfMatrixEntries( <mats> ) #F AtlasStringOfFieldOfMatrixEntries( <filename> ) ## InstallGlobalFunction( AtlasStringOfFieldOfMatrixEntries, function( mats ) local F, n, str; if IsString( mats ) then mats:= AtlasDataGAPFormatFile( mats ).generators; fi; if IsCyclotomicCollCollColl( mats ) then F:= Field( Rationals, Flat( mats ) ); elif ForAll( mats, IsQuaternionCollColl ) then F:= Field( Flat( List( Flat( mats ), ExtRepOfObj ) ) ); else Error( "<mats> must be a matrix list of cyclotomics or quaternions" ); fi; n:= Conductor( F ); if DegreeOverPrimeField( F ) = 2 then # The field is quadratic, # so it is generated by `Sqrt(n)' if $`n' \equiv 1 \pmod{4}$, # by `Sqrt(-n)' if $`n' \equiv 3 \pmod{4}$, # and by one of `Sqrt(n/4)', `Sqrt(-n/4)' otherwise. if n mod 4 = 1 then str:= Concatenation( "[Sqrt(", String( n ), ")]" ); elif n mod 4 = 3 then str:= Concatenation( "[Sqrt(-", String( n ), ")]" ); elif Sqrt( -n/4 ) in F then str:= Concatenation( "[Sqrt(-", String( n/4 ), ")]" ); else str:= Concatenation( "[Sqrt(", String( n/4 ), ")]" ); fi; elif IsCyclotomicField( F ) then # The field is not quadratic but cyclotomic. str:= Concatenation( "[E(", String( n ), ")]" ); else str:= ""; fi; if IsCyclotomicCollCollColl( mats ) then if str = "" then str:= String( F ); else str:= Concatenation( "Field(", str, ")" ); fi; elif F = Rationals then str:= "QuaternionAlgebra(Rationals)"; elif str = "" then str:= Concatenation( "QuaternionAlgebra(", String( F ), ")" ); else str:= Concatenation( "QuaternionAlgebra(", str, ")" ); fi; return [ F, str ]; end ); ############################################################################# ## #F AtlasOfGroupRepresentationsScanFilename( <name>, <result> ) ## BindGlobal( "AtlasOfGroupRepresentationsScanFilename", function( name, result ) local filename, pos, type, format, groupname; filename:= name; pos:= Position( filename, '/' ); while pos <> fail do filename:= filename{ [ pos+1 .. Length( filename ) ] }; pos:= Position( filename, '/' ); od; for type in AGR.DataTypes( "rep", "prg" ) do format:= AGR.ParseFilenameFormat( filename, type[2].FilenameFormat ); if format <> fail then groupname:= format[1]; if not IsBound( result.( groupname ) ) then result.( groupname ):= rec(); fi; if not IsBound( result.( groupname ).( type[1] ) ) then result.( groupname ).( type[1] ):= []; fi; return type[2].AddFileInfo( result.( groupname ).( type[1] ), format, name ); fi; od; return false; end ); ############################################################################# ## #F AtlasOfGroupRepresentationsUpdateTableOfContentsFile( <dstfilename> ) ## ## Fetch the current table of contents from the package's home directory, ## and write it to the file <A>dstfilename</A>, ## which is interpreted as an absolute path. ## ## This function is used by the script <F>etc/maketoc</F> and in the call ## <C>AtlasTableOfContents( "remote" )</C>. ## BindGlobal( "AtlasOfGroupRepresentationsUpdateTableOfContentsFile", function( dstfilename ) local version, inforec, home, server, path; version:= InstalledPackageVersion( "atlasrep" ); inforec:= First( PackageInfo( "atlasrep" ), r -> r.Version = version ); home:= inforec.PackageWWWHome; if home{ [ 1 .. 7 ] } = "http://" then home:= home{ [ 8 .. Length( home ) ] }; fi; server:= home{ [ 1 .. Position( home, '/' ) - 1 ] }; path:= home{ [ Position( home, '/' ) + 1 .. Length( home ) ] }; # Fetch the file if possible. return AtlasOfGroupRepresentationsTransferFile( server, Concatenation( path, "/atlasprm.g" ), dstfilename ); end ); ############################################################################# ## #F AtlasOfGroupRepresentationsComposeTableOfContents( <filelist>, #F <groupnames> ) ## ## This code is used by `AtlasTableOfContents'. ## BindGlobal( "AtlasOfGroupRepresentationsComposeTableOfContents", function( filelist, groupnames ) local result, name, len, groupname, record, type, listtosort; # Initialize the result record. result:= rec( otherfiles:= [] ); # Deal with the case of `gzip'ped files, and omit obvious garbage. for name in Set( filelist ) do len:= Length( name ); if 3 <= len and name{ [ len-2 .. len ] } = ".gz" then name:= name{ [ 1 .. len-3 ] }; fi; if AtlasOfGroupRepresentationsScanFilename( name, result ) = false then if not ( name in [ "dummy", ".", "..", "CVS", "CVS:", "./CVS:", "Entries", "Repository", "Root", "toc.g", ".cvsignore" ] or name[ Length( name ) ] = '%' or ( 3 <= Length( name ) and name{ Length( name ) + [ - 2 .. 0 ] } = "BAK" ) ) then Info( InfoAtlasRep, 3, "t.o.c. construction: ignoring name `", name, "'" ); AddSet( result.otherfiles, name ); fi; fi; od; # Postprocessing, # and *sort* the representations as given in the type definition. for groupname in List( groupnames, x -> x[3] ) do if IsBound( result.( groupname ) ) then record:= result.( groupname ); for type in AGR.DataTypes( "rep", "prg" ) do if IsBound( record.( type[1] ) ) then type[2].PostprocessFileInfo( result, record ); # Sort the data of the given type as defined. if IsBound( type[2].SortTOCEntries ) then listtosort:= List( record.( type[1] ), type[2].SortTOCEntries ); SortParallel( listtosort, record.( type[1] ) ); fi; fi; od; fi; od; # Store the current date in Coordinated Universal Time # (Greenwich Mean Time). result.lastupdated:= CurrentDateTimeString(); return result; end ); ############################################################################# ## #F AtlasTableOfContents( <dirname> ) ## InstallGlobalFunction( AtlasTableOfContents, function( string ) local toc, groupnames, prefix, filenames, dstdir, dstfile, tocremote, typeinfo, groupname, r, pair, type, entry, fileinfo, filename, loc, dirname, privdir, dir, result; toc:= AtlasOfGroupRepresentationsInfo.TableOfContents; # Take the stored version if it is already available. groupnames:= AtlasOfGroupRepresentationsInfo.groupnames; if IsBound( toc.( string ) ) then return rec( groupnames := groupnames, TableOfContents := toc.( string ) ); fi; prefix:= ""; filenames:= []; if string = "remote" then # Someone has unbound the value, we interpret this as the wish # to transfer the current file `atlasprm.g' from the package homepage, # and to read it. # If we are allowed to overwrite the file from the installation # then we do this, otherwise we use a temporary file. dstdir:= DirectoriesPackageLibrary( "atlasrep", "gap" ); dstfile:= Filename( dstdir, "atlasprm.g" ); if not IsWritableFile( dstfile ) then dstfile:= Filename( DirectoryTemporary(), "atlasprm.g" ); fi; if not AtlasOfGroupRepresentationsUpdateTableOfContentsFile( dstfile ) then return fail; fi; ReplaceAtlasTableOfContents( dstfile ); return rec( groupnames := AtlasOfGroupRepresentationsInfo.groupnames, TableOfContents := toc.remote ); elif string = "local" then # List the information that is locally available, # by testing which of the files in the remote t.o.c. are stored. tocremote:= AtlasTableOfContents( "remote" ).TableOfContents; typeinfo:= Concatenation( List( AGR.DataTypes( "rep" ), x -> [ "datagens", x ] ), List( AGR.DataTypes( "prg" ), x -> [ "dataword", x ] ) ); for groupname in RecNames( tocremote ) do r:= tocremote.( groupname ); if IsRecord( r ) then for pair in typeinfo do type:= pair[2]; if IsBound( r.( type[1] ) ) then for entry in r.( type[1] ) do fileinfo:= entry[ Length( entry ) ]; if IsString( fileinfo ) then fileinfo:= [ fileinfo ]; fi; for filename in fileinfo do loc:= AtlasOfGroupRepresentationsLocalFilename( pair[1], groupname, filename, type ); if not IsEmpty( loc ) and ForAll( loc[1][2], x -> x[2] ) then Add( filenames, filename ); fi; od; od; fi; od; fi; od; else # List the contents of the private data directories. dirname:= First( AtlasOfGroupRepresentationsInfo.private, pair -> pair[2] = string )[1]; if IsExistingFile( dirname ) then # List the information available in the given private directory. # (Up to one directory layer above the data files is supported.) filenames:= []; privdir:= Directory( dirname ); for dir in Difference( DirectoryContents( dirname ), [ ".", ".." ] ) do dstfile:= Filename( privdir, dir ); if IsDirectoryPath( dstfile ) then Append( filenames, List( DirectoryContents( dstfile ), x -> Concatenation( dir, "/", x ) ) ); else Add( filenames, dir ); fi; od; fi; fi; # Compose the result record. result:= AtlasOfGroupRepresentationsComposeTableOfContents( filenames, groupnames ); result.diridPrivate:= string; # Store the newly computed table of contents. toc.( string ):= result; # Return the result record. return rec( groupnames := groupnames, TableOfContents := result ); end ); ############################################################################# ## #F ReloadAtlasTableOfContents( \"local\" ) #F ReloadAtlasTableOfContents( \"remote\" ) ## InstallGlobalFunction( ReloadAtlasTableOfContents, function( string ) local toc, old, new; toc:= AtlasOfGroupRepresentationsInfo.TableOfContents; if IsBound( toc.( string ) ) then old:= toc.( string ); Unbind( toc.( string ) ); fi; new:= AtlasTableOfContents( string ); if new = fail then if IsBound( old ) then toc.( string ):= old; fi; Info( InfoAtlasRep, 1, "could not reload ", string, " table of contents" ); return fail; fi; toc.( string ):= new.TableOfContents; if string = "remote" then AtlasOfGroupRepresentationsInfo.groupnames:= new.groupnames; fi; return true; end ); ############################################################################# ## #F StoreAtlasTableOfContents( <filename> ) ## ## Note that we must put everything into a single file because this is the ## one that can be fetched automatically by users. ## InstallGlobalFunction( StoreAtlasTableOfContents, function( filename ) if IsExistingFile( filename ) and not IsWritableFile( filename ) then Error( "<filename> must be writable if exists" ); elif not IsBound( AtlasOfGroupRepresentationsInfo.TableOfContents.( "remote" ) ) then Error( "no remote t.o.c. exists" ); fi; # Add the data. FileString( filename, StringOfAtlasTableOfContents( "remote" ) ); end ); ############################################################################# ## #F ReplaceAtlasTableOfContents( <filename> ) ## InstallGlobalFunction( ReplaceAtlasTableOfContents, function( filename ) local priv, pair; if not IsReadableFile( filename ) then Error( "<filename> must be the name of a readable file" ); fi; # Remove the information we are going to add by reading the file. AtlasOfGroupRepresentationsInfo.TableOfContents.( "remote" ):= rec(); priv:= AtlasOfGroupRepresentationsInfo.private; AtlasOfGroupRepresentationsInfo.GAPnames := []; AtlasOfGroupRepresentationsInfo.GAPnamesSortDisp:= []; AtlasOfGroupRepresentationsInfo.groupnames := []; AtlasOfGroupRepresentationsInfo.ringinfo := []; AtlasOfGroupRepresentationsInfo.private := []; AtlasOfGroupRepresentationsInfo.characterinfo:= rec(); AtlasOfGroupRepresentationsInfo.permrepinfo:= rec(); AGR.MapNameToGAPName:= [ [], [] ]; AGR.GAPnamesRec:= rec(); # Replace the old version by the new one. AtlasOfGroupRepresentationsInfo.checkData:= true; Reread( filename ); Unbind( AtlasOfGroupRepresentationsInfo.checkData ); # Reinsert the information from private directories. for pair in priv do AtlasOfGroupRepresentationsNotifyPrivateDirectory( pair[1], pair[2] ); od; end ); ############################################################################# ## #F StringOfAtlasTableOfContents( <string> ) ## InstallGlobalFunction( StringOfAtlasTableOfContents, function( string ) local str, pos, toc, sepline, triple, groupname, reps, type, entry, lines, nam, line, r, map, i, j; # Currently only the argument `"remote"' is supported. if string <> "remote" then Error( "sorry, private tables of contents are not yet supported here" ); fi; # Copy the constant part of the data to the desired file. str:= StringFile( Filename( DirectoriesPackageLibrary( "atlasrep", "gap" ), "atlasprm.g" ) ); pos:= PositionSublist( str, "AGR.SetGAPnamesSortDisp();" ); pos:= Position( str, '\n', pos ); str:= str{ [ 1 .. pos ] }; toc:= AtlasTableOfContents( string ).TableOfContents; sepline:= RepeatedString( "#", 77 ); Append( str, "\n\n" ); Append( str, sepline ); Append( str, "\n##\n## " ); Append( str, "Store group orders.\n" ); Append( str, "##\n" ); lines:= []; for nam in RecNames( AGR.GAPnamesRec ) do if IsBound( AGR.GAPnamesRec.( nam )[3].size ) then entry:= AGR.GAPnamesRec.( nam )[3].size; Add( lines, Concatenation( "AGR.GRS(\"", nam, "\",", String( entry ), ");\n" ) ); fi; od; Sort( lines ); for line in lines do Append( str, line ); od; Append( str, "\n\n" ); Append( str, sepline ); Append( str, "\n##\n## " ); Append( str, "Store numbers of classes of maximal subgroups.\n" ); Append( str, "##\n" ); lines:= []; for nam in RecNames( AGR.GAPnamesRec ) do if IsBound( AGR.GAPnamesRec.( nam )[3].nrMaxes ) then entry:= AGR.GAPnamesRec.( nam )[3].nrMaxes; Add( lines, Concatenation( "AGR.MXN(\"", nam, "\",", String( entry ), ");\n" ) ); fi; od; Sort( lines ); for line in lines do Append( str, line ); od; Append( str, "\n\n" ); Append( str, sepline ); Append( str, "\n##\n## " ); Append( str, "Store orders of maximal subgroups.\n" ); Append( str, "##\n" ); lines:= []; for nam in RecNames( AGR.GAPnamesRec ) do if IsBound( AGR.GAPnamesRec.( nam )[3].sizesMaxes ) then entry:= AGR.GAPnamesRec.( nam )[3].sizesMaxes; Add( lines, Concatenation( "AGR.MXO(\"", nam, "\",", ReplacedString( String( entry ), " ", "" ), ");\n" ) ); fi; od; Sort( lines ); for line in lines do Append( str, line ); od; Append( str, "\n\n" ); Append( str, sepline ); Append( str, "\n##\n## " ); Append( str, "Store structures of maximal subgroups.\n" ); Append( str, "##\n" ); lines:= []; for nam in RecNames( AGR.GAPnamesRec ) do if IsBound( AGR.GAPnamesRec.( nam )[3].structureMaxes ) then entry:= AGR.GAPnamesRec.( nam )[3].structureMaxes; Add( lines, Concatenation( "AGR.MXS(\"", nam, "\",", ReplacedString( String( entry ), " ", "" ), ");\n" ) ); fi; od; Sort( lines ); for line in lines do Append( str, line ); od; Append( str, "\n\n" ); Append( str, sepline ); Append( str, "\n##\n## " ); Append( str, "Store information about generators of kernels.\n" ); Append( str, "##\n" ); lines:= []; for nam in RecNames( AGR.GAPnamesRec ) do if IsBound( AGR.GAPnamesRec.( nam )[3].kernelPrograms ) then for entry in AGR.GAPnamesRec.( nam )[3].kernelPrograms do Add( lines, Concatenation( "AGR.KERPRG(\"", nam, "\",", ReplacedString( String( entry ), " ", "" ), ");\n" ) ); od; fi; od; Sort( lines ); for line in lines do Append( str, line ); od; Append( str, "\n\n" ); Append( str, sepline ); Append( str, "\n##\n## " ); Append( str, "In the following, the table of contents is built\n" ); Append( str, "## using `AGR.GRP' and `AGR.TOC'.\n" ); Append( str, "## This part of the file is created by the function\n" ); Append( str, "## `StringOfAtlasTableOfContents',\n" ); Append( str, "## do not edit below this line!\n" ); Append( str, "##\n\n" ); for triple in AtlasOfGroupRepresentationsInfo.groupnames do # Start with the notification of the group. Append( str, "# " ); Append( str, triple[3] ); Append( str, "\n" ); Append( str, "AGR.GRP(\"" ); Append( str, triple[1] ); Append( str, "\",\"" ); Append( str, triple[2] ); Append( str, "\",\"" ); Append( str, triple[3] ); Append( str, "\");\n" ); # Append the notifications of data entries for the group. groupname:= triple[3]; if IsBound( toc.( groupname ) ) then reps:= toc.( groupname ); for type in AGR.DataTypes( "rep", "prg" ) do if IsBound( reps.( type[1] ) ) then for entry in reps.( type[1] ) do if IsBound( type[2].TOCEntryString ) then Append( str, type[2].TOCEntryString( type[1], entry ) ); else Info( InfoAtlasRep, 1, "no component `TOCEntryString' for data type `", type[1], "'" ); fi; od; fi; od; fi; Append( str, "\n" ); od; Append( str, "\n" ); Append( str, sepline ); Append( str, "\n##\n## " ); Append( str, "What follows now are the representation dependent additional data.\n" ); Append( str, "## They must be read after the notification of the representations,\n" ); Append( str, "## in order to give us a chance to check the existence of the underlying\n"); Append( str, "## representation.\n" ); Append( str, "##\n" ); # Append the compatibility information w.r.t. factors. Append( str, "\n\n" ); Append( str, sepline ); Append( str, "\n##\n## " ); Append( str, "Store info about compatibility of generators with those of factor groups.\n" ); Append( str, "##\n" ); lines:= []; for nam in RecNames( AGR.GAPnamesRec ) do if IsBound( AGR.GAPnamesRec.( nam )[3].factorCompatibility ) then for entry in AGR.GAPnamesRec.( nam )[3].factorCompatibility do Add( lines, Concatenation( "AGR.STDCOMP(\"", nam, "\",", ReplacedString( String( entry ), " ", "" ), ");\n" ) ); od; fi; od; Sort( lines ); for line in lines do Append( str, line ); od; # Append the information about the base rings of char. zero repres. Append( str, "\n\n" ); Append( str, sepline ); Append( str, "\n##\n## " ); Append( str, "Store information about the rings over which characteristic zero\n" ); Append( str, "## representations are written if known.\n" ); Append( str, "## Note that the filenames do not contain this information,\n" ); Append( str, "## so it has to be stored explicitly.\n" ); Append( str, "##\n" ); lines:= []; for entry in AtlasOfGroupRepresentationsInfo.ringinfo do Add( lines, Concatenation( "AGR.RNG(\"", entry[1], "\",\"", entry[2], "\");\n" ) ); od; Sort( lines ); for line in lines do Append( str, line ); od; # Append the information about compatibility of maxes scripts. Append( str, "\n\n" ); Append( str, sepline ); Append( str, "\n##\n## " ); Append( str, "Store information which straight line programs for restricting to maximal\n" ); Append( str, "## subgroups of a group can be used also for restricting to maximal\n" ); Append( str, "## subgroups of downward extensions.\n" ); Append( str, "##\n" ); lines:= []; for nam in RecNames( toc ) do if IsRecord( toc.( nam ) ) and IsBound( toc.( nam ).maxext ) then for entry in toc.( nam ).maxext do Add( lines, Concatenation( "AGR.TOCEXT(\"", nam, "\",", String( entry[1] ), ",", String( entry[2] ), ",", ReplacedString( String( entry[3] ), " ", "" ), ");\n" ) ); od; fi; od; Sort( lines ); for line in lines do Append( str, line ); od; # Append the primitivity information. Append( str, "\n\n" ); Append( str, sepline ); Append( str, "\n##\n## " ); Append( str, "Store information about the point stabilizers of permutation\n" ); Append( str, "## representations if known.\n" ); Append( str, "## Note that the filenames do not contain this information,\n" ); Append( str, "## so it has to be stored explicitly.\n" ); Append( str, "##\n" ); lines:= []; for nam in RecNames( AtlasOfGroupRepresentationsInfo.permrepinfo ) do r:= AtlasOfGroupRepresentationsInfo.permrepinfo.( nam ); if not IsBound( r.isPrivate ) then line:= Concatenation( "AGR.API(\"", nam, "\",[" ); Append( line, String( r.transitivity ) ); Append( line, "," ); if r.transitivity = 0 then Append( line, ReplacedString( String( r.orbits ), " ", "" ) ); Append( line, "]);\n" ); else Append( line, String( r.rankAction ) ); Append( line, "," ); if r.isPrimitive then Append( line, "\"prim\"" ); else Append( line, "\"imprim\"" ); fi; Append( line, ",\"" ); Append( line, r.stabilizer ); Append( line, "\"" ); if r.isPrimitive then Append( line, "," ); if IsInt( r.maxnr ) then Append( line, String( r.maxnr ) ); else Append( line, "\"" ); Append( line, String( r.maxnr ) ); Append( line, "\"" ); fi; fi; Append( line, "]);\n" ); fi; Add( lines, line ); fi; od; Sort( lines ); for line in lines do Append( str, line ); od; # Append the character information. Append( str, "\n\n" ); Append( str, sepline ); Append( str, "\n##\n## precomputed character data\n" ); Append( str, "##\n" ); lines:= []; for nam in RecNames( AtlasOfGroupRepresentationsInfo.characterinfo ) do map:= AtlasOfGroupRepresentationsInfo.characterinfo.( nam ); for i in [ 1 .. Length( map ) ] do if IsBound( map[i] ) then for j in [ 1 .. Length( map[i][1] ) ] do line:= Concatenation( "AGR.CHAR(\"", nam, "\",\"", map[i][2][j], "\"," ); #T only for the non-private ones! if i = 1 then Append( line, "0" ); else Append( line, String( i ) ); fi; Append( line, "," ); Append( line, ReplacedString( String( map[i][1][j] ), " ", "" ) ); if map[i][3][j] <> fail then Append( line, ",\"" ); Append( line, map[i][3][j] ); Append( line, "\"" ); fi; Append( line, ");\n" ); Add( lines, line ); od; fi; od; od; Sort( lines ); for line in lines do Append( str, line ); od; # Finally, append the current date and time. Append( str, "\n\n" ); Append( str, "AtlasOfGroupRepresentationsInfo.TableOfContents.( \"" ); Append( str, string ); Append( str, "\" ).lastupdated:=\n \"" ); Append( str, toc.lastupdated ); Append( str, "\";\n\n" ); Append( str, sepline ); Append( str, "\n##\n#E\n\n" ); return str; end ); ############################################################################# ## #F AGR.TablesOfContents( <descr> ) ## ## Admissible arguments are ## 1 the string "all", ## 2 the string "public", ## 3 a string describing a private table of contents, ## (which occurs as the second component of an entry in ## `AtlasOfGroupRepresentationsInfo.private') or ## 4 a list of conditions such as [ <std>, "contents", <...> ] ## 5 a list of strings as 1-3. ## AGR.TablesOfContents:= function( descr ) local pos, tocid, i, label, tocs, flag, toc, pair; if descr = [] then descr:= [ "all" ]; elif IsString( descr ) then descr:= [ descr ]; elif not IsList( descr ) then Error( "<descr> must be a string or a list of strings/conditions" ); fi; pos:= Position( descr, "contents" ); if pos <> fail then # `descr' is a list of conditions. # Evaluate its "contents" part, # i. e., restrict the tables of contents, and remove this condition. tocid:= descr[ pos+1 ]; for i in [ pos .. Length( descr ) - 2 ] do descr[i]:= descr[ i+2 ]; od; Unbind( descr[ Length( descr ) ] ); Unbind( descr[ Length( descr ) ] ); if IsString( tocid ) then descr:= [ tocid ]; else descr:= tocid; fi; elif ForAny( descr, x -> x <> "all" and x <> "public" and ForAll( AtlasOfGroupRepresentationsInfo.private, pair -> x <> pair[2] ) ) then # `descr' is a list of conditions that does not restrict the # table of contents. descr:= [ "all" ]; fi; # Now `descr' is a list of identifiers of tables of contents. label:= JoinStringsWithSeparator( SortedList( descr ), "|" ); if not IsBound( AtlasOfGroupRepresentationsInfo.TOC_Cache.( label ) ) then tocs:= []; if descr = "all" or descr = "public" or "all" in descr or "public" in descr then flag:= String( AtlasOfGroupRepresentationsInfo.remote ); if not IsBound( AtlasOfGroupRepresentationsInfo.TOC_Cache.( flag ) ) then if flag = "true" then toc:= AtlasTableOfContents( "remote" ).TableOfContents; else toc:= AtlasTableOfContents( "local" ).TableOfContents; fi; AtlasOfGroupRepresentationsInfo.TOC_Cache.( flag ):= toc; fi; Add( tocs, AtlasOfGroupRepresentationsInfo.TOC_Cache.( flag ) ); fi; for pair in AtlasOfGroupRepresentationsInfo.private do if descr = "all" or "all" in descr or descr = pair[2] or pair[2] in descr then Add( tocs, AtlasOfGroupRepresentationsInfo.TableOfContents.( pair[2] ) ); fi; od; AtlasOfGroupRepresentationsInfo.TOC_Cache.( label ):= tocs; fi; return AtlasOfGroupRepresentationsInfo.TOC_Cache.( label ); end; ############################################################################# ## #F AtlasOfGroupRepresentationsNotifyPrivateDirectory( <dir>[, <dirid>] #F [, <test>] ) ## InstallGlobalFunction( AtlasOfGroupRepresentationsNotifyPrivateDirectory, function( arg ) local dirname, dirid, test, oldtest, olddata, dir, prm, toc, allfilenames, RemovedDirectories, groupname, record, type, entry, name, tocs, ok, oldtoc, names, unknown, nam; # Get and check the arguments. if 1 <= Length( arg ) and Length( arg ) <= 3 and ( IsString( arg[1] ) or IsDirectory( arg[1] ) ) and ( Length( arg ) = 1 or ( IsString( arg[2] ) or IsBool( arg[2] ) ) ) and ( Length( arg ) < 3 or ( IsString( arg[2] ) and IsBool( arg[3] ) ) ) then if IsString( arg[1] ) then dirname:= ShallowCopy( arg[1] ); else dir:= arg[1]; dirname:= ShallowCopy( dir![1] ); #T this is not clean! #T (how to call DirectoryContents for a directory given as an object?) fi; if Length( arg ) = 1 then dirid:= dirname; elif IsString( arg[2] ) or Length( arg ) = 3 then dirid:= arg[2]; else dirid:= dirname; fi; if Length( arg ) = 1 then test:= false; elif IsBool( arg[2] ) then test:= arg[2]; elif Length( arg ) = 3 then test:= arg[3]; else test:= false; fi; else Error( "usage: AtlasOfGroupRepresentationsNotifyPrivateDirectory(\n", " <dirname>[,<dirid>][,<test>])" ); fi; # Add the directory name. if IsEmpty( dirname ) then Error( "<dirname> must not be empty" ); elif dirname[ Length( dirname ) ] <> '/' then Add( dirname, '/' ); fi; if ForAny( AtlasOfGroupRepresentationsInfo.private, pair -> pair[2] = dirid and pair[1] <> dirname ) then Error( dirid, " is already the identifier of another private directory" ); fi; AddSet( AtlasOfGroupRepresentationsInfo.private, [ dirname, dirid ] ); # Read the primary file (for group declarations and describing data). # The file may contain calls of `AGR.GNAN' (which must come *before* the # data for the groups in question can be notified) and of `AGR.API' and # `AGR.CHAR' (which must come *afterwards*). # So we must postpone the calls of `AGR.IsRepNameAvailable'. oldtest:= IsBound( AtlasOfGroupRepresentationsInfo.checkData ); Unbind( AtlasOfGroupRepresentationsInfo.checkData ); olddata:= rec( permrepinfo:= RecNames( AtlasOfGroupRepresentationsInfo.permrepinfo ), ); #T same for characters! prm:= Filename( dir, "toc.g" ); if IsExistingFile( prm ) = true then Read( prm ); fi; # Set up a table of contents for the private directory. toc:= AtlasTableOfContents( dirid ).TableOfContents; Unbind( toc.otherfiles ); # Check that no filename of this table of contents exists already in # other tables of contents. # 1. Compute the list of all filenames. allfilenames:= []; RemovedDirectories:= function( string ) string:= SplitString( string, "/" ); return string[ Length( string ) ]; end; for groupname in RecNames( toc ) do record:= toc.( groupname ); if IsRecord( record ) then for type in RecNames( record ) do for entry in record.( type ) do name:= entry[ Length( entry ) ]; if IsString( name ) then Add( allfilenames, RemovedDirectories( name ) ); else Append( allfilenames, List( name, RemovedDirectories ) ); fi; od; od; fi; od; # 2. Check whether the list is disjoint to the other tables of contents. #T this is expensive if AtlasOfGroupRepresentationsInfo.remote = true then tocs:= [ AtlasTableOfContents( "remote" ).TableOfContents ]; else tocs:= [ AtlasTableOfContents( "local" ).TableOfContents ]; fi; Append( tocs, List( Filtered( AtlasOfGroupRepresentationsInfo.private, pair -> pair[2] <> dirid ), x -> AtlasOfGroupRepresentationsInfo.TableOfContents.( x[2] ) ) ); ok:= true; for oldtoc in tocs do for groupname in RecNames( toc ) do if IsBound( oldtoc.( groupname ) ) then record:= oldtoc.( groupname ); if IsRecord( record ) then for type in RecNames( record ) do for entry in record.( type ) do names:= entry[ Length( entry ) ]; if IsString( names ) then names:= [ names ]; fi; for name in names do if name in allfilenames then ok:= false; Info( InfoAtlasRep, 1, "file `", name, "' was already in another t.o.c." ); #T better remove the entry for this file fi; od; od; od; fi; fi; od; od; # Add group names that were not notified. unknown:= Set( Filtered( RecNames( toc ), x -> ForAll( AtlasOfGroupRepresentationsInfo.GAPnames, pair -> x <> pair[2] ) ) ); RemoveSet( unknown, "diridPrivate" ); RemoveSet( unknown, "lastupdated" ); if not IsEmpty( unknown ) then ok:= false; Info( InfoAtlasRep, 1, "no GAP names defined for ", unknown ); UniteSet( AtlasOfGroupRepresentationsInfo.GAPnames, List( unknown, x -> [ x, x ] ) ); fi; AGR.SetGAPnamesSortDisp(); AtlasOfGroupRepresentationsInfo.TOC_Cache:= rec(); AtlasOfGroupRepresentationsInfo.TableOfContents.merged:= rec(); # Run the postponed tests. if test then AtlasOfGroupRepresentationsInfo.checkData:= true; for nam in Difference( RecNames( AtlasOfGroupRepresentationsInfo.permrepinfo ), olddata.permrepinfo ) do AtlasOfGroupRepresentationsInfo.permrepinfo.( nam ).isPrivate:= true; ok:= AGR.IsRepNameAvailable( nam ) and ok; od; #T check also the characters! fi; # Restore the original flag. if not oldtest then Unbind( AtlasOfGroupRepresentationsInfo.checkData ); fi; # Return the flag. return ok; end ); ############################################################################# ## #F AtlasOfGroupRepresentationsForgetPrivateDirectory( <dirid> ) ## InstallGlobalFunction( AtlasOfGroupRepresentationsForgetPrivateDirectory, function( dirid ) local private, i, dirname, dir, prm, str, file, GAPnames, pos, pos2, gapname, j; Unbind( AtlasOfGroupRepresentationsInfo.TableOfContents.( dirid ) ); GAPnames:= AtlasOfGroupRepresentationsInfo.GAPnames; private:= AtlasOfGroupRepresentationsInfo.private; for i in [ 1 .. Length( private ) ] do if private[i][2] = dirid then # Remove the group declarations. dirname:= private[i][1]; dir:= Directory( dirname ); prm:= Filename( dir, "toc.g" ); if IsExistingFile( prm ) = true then str:= StringFile( prm ); pos:= PositionSublist( str, "GNAN" ); while pos <> fail do while pos <= Length( str ) and str[ pos ] <> '"' do pos:= pos + 1; pos2:= Position( str, '"', pos ); if pos2 <> fail then gapname:= str{ [ pos+1 .. pos2-1 ] }; for j in [ 1 .. Length( GAPnames ) ] do if IsBound( GAPnames[j] ) and GAPnames[j][1] = gapname then Unbind( GAPnames[j] ); Unbind( AGR.GAPnamesRec.( gapname ) ); break; fi; od; fi; od; pos:= PositionSublist( str, "GNAN", pos2 ); od; fi; # Remove the information concerning the private directory. Unbind( private[i] ); AtlasOfGroupRepresentationsInfo.private:= Compacted( private ); break; fi; od; AtlasOfGroupRepresentationsInfo.GAPnames:= Compacted( GAPnames ); AGR.SetGAPnamesSortDisp(); AtlasOfGroupRepresentationsInfo.TOC_Cache:= rec(); AtlasOfGroupRepresentationsInfo.TableOfContents.merged:= rec(); end ); if IsString( SingleHTTPRequest ) then Unbind( SingleHTTPRequest ); fi; if IsString( IO_stat ) then Unbind( IO_stat ); fi; ############################################################################# ## #E