Other stuff
Macros
GAP.@gap
— Macro@gap <expr>
@gap(<expr>)
Execute <expr> directly in GAP, as if GAP.evalstr("<expr>")
was called. This can be used for creating GAP literals directly from Julia.
Examples
julia> @gap [1,2,3]
GAP: [ 1, 2, 3 ]
julia> @gap SymmetricGroup(3)
GAP: Sym( [ 1 .. 3 ] )
julia> @gap(SymmetricGroup)(3)
GAP: Sym( [ 1 .. 3 ] )
Note that the last two examples have a slight syntactical, and therefore also a semantical difference. The first one executes the string SymmetricGroup(3)
directly inside GAP. The second example returns the function SymmetricGroup
via @gap(SymmetricGroup)
, then calls that function with the argument 3
.
Due to Julia's way of handing over arguments into the code of macros, not all expressions representing valid GAP code can be processed. For example, the GAP syntax of permutations consisting of more than one cycle cause problems, as well as the GAP syntax of non-dense lists.
julia> @gap (1,2,3)
GAP: (1,2,3)
julia> @gap (1,2)(3,4)
ERROR: LoadError: Error thrown by GAP: Error, no method found! For debugging hints type ?Recovery from NoMethodFound
[...]
julia> @gap [ 1,, 2 ]
ERROR: syntax: unexpected ","
[...]
Note also that a string argument gets evaluated with GAP.evalstr
.
julia> @gap "\"abc\""
GAP: "abc"
julia> @gap "[1,,2]"
GAP: [ 1,, 2 ]
julia> @gap "(1,2)(3,4)"
GAP: (1,2)(3,4)
GAP.@g_str
— Macro@g_str
Create a GAP string by typing g"content"
.
Examples
julia> g"foo"
GAP: "foo"
julia> g"ab\ncd\"ef\\gh" # special characters are handled as in GAP
GAP: "ab\ncd\"ef\\gh"
Due to Julia's way of handing over arguments into the code of macros, not all strings representing valid GAP strings can be processed.
julia> g"\\"
ERROR: Error thrown by GAP: Syntax error: String must end with " before end of file in stream:1
[...]
Conversely, there are valid arguments for the macro that are not valid Julia strings.
julia> g"\c"
GAP: "\c"
GAP.@gapwrap
— Macro@gapwrap
When applied to a method definition that involves access to entries of GAP.Globals
, this macro rewrites the code such that the relevant GAP globals are cached, and need not be fetched again and again.
Examples
julia> @gapwrap isevenint(x) = GAP.Globals.IsEvenInt(x)::Bool;
julia> isevenint(1)
false
julia> isevenint(2)
true
GAP.@gapattribute
— Macro@gapattribute
This macro is intended to be applied to a method definition for a unary function called attr
, say, where the argument has the type T
, say, the code contains exactly one call of the form GAP.Globals.Something(X)
, where Something
is a GAP attribute such as Centre
or IsSolvableGroup
, and attr
returns the corresponding attribute value for its argument.
The macro defines three functions attr
, has_attr
, and set_attr
, where attr
takes an argument of type T
and returns what the given method definition says, has_attr
takes an argument of type T
and returns the result of GAP.Globals.HasSomething(X)
(which is either true
or false
), set_attr
takes an argument of type T
and an object obj
and calls GAP.Globals.SetSomething(X, obj)
.
In order to avoid runtime access via GAP.Globals.Something
etc., the same modifications are applied in the construction of the three functions that are applied by @gapwrap
.
The variables that are created by the macro belong to the Julia module in whose scope the macro is called.
Examples
julia> @gapattribute isstrictlysortedlist(obj::GapObj) = GAP.Globals.IsSSortedList(obj)::Bool;
julia> l = GapObj([ 1, 3, 7 ]);
julia> has_isstrictlysortedlist( l )
false
julia> isstrictlysortedlist( l )
true
julia> has_isstrictlysortedlist( l )
true
julia> l = GapObj([ 1, 3, 7 ]);
julia> has_isstrictlysortedlist( l )
false
julia> set_isstrictlysortedlist( l, true )
julia> has_isstrictlysortedlist( l )
true
julia> isstrictlysortedlist( l )
true
GAP.@wrap
— Macro@wrap funcdecl
When applied to a function declaration of the form NAME(a::T)
or NAME(a::T)::S
, this macro generates a function which behaves equivalently to NAME(a::T) = GAP.Globals.NAME(a)
resp. NAME(a::T) = GAP.Globals.NAME(a)::S
, assuming that GAP.Globals.NAME
references a GAP function. Function declarations with more than one argument or zero arguments are also supported.
However, the generated function actually caches the GAP object GAP.Globals.NAME
. This minimizes the call overhead. So @wrap typically is used to provide an optimized way to call certain GAP functions.
If an argument is annotated as ::GapObj
(respectively ::GAP.Obj
or ::GapInt
), the resulting function accepts arguments of any type and attempts to convert them to GapObj
(respectively GAP.Obj
or GapInt
) before passing them to the GAP function.
Another use case for this macro is to improve type stability of code calling into GAP, via the type annotations for the arguments and return value contained in the function declaration.
Be advised, though, that if the value of GAP.Globals.NAME
is changed later on, the function generated by this macro will not be updated, i.e., it will still reference the original GAP object.
Examples
julia> GAP.@wrap IsString(x::GapObj)::Bool
IsString (generic function with 1 method)
julia> IsString("abc")
true
julia> GAP.@wrap Jacobi(x::GapInt, y::GapInt)::Int
Jacobi (generic function with 1 method)
julia> Jacobi(11,35)
1
julia> Jacobi(big(35)^100+11, 35)
1
GAP.@install
— Macro@install
When applied to a unary method definition for the function GapObj
, with argument of type T
, this macro installs instead a three argument method for GAP.GapObj_internal
, with second argument of type GAP.GapCacheDict
and third argument of type Bool
.
This way, the intended GapObj(x::T)
method becomes available, and additionally its code is applicable in recursive calls, for example when GapObj
is called with a vector of objects of type T
.
Since the GapObj
method does not support a dictionary for tracking identical subobjects, the type T
is marked as "not needing recursion", by automatically installing a method for _needs_tracking_julia_to_gap
that returns false
.
The calls of the macro have the form GAP.@install GapObj(x::T) = f(x)
or GAP.@install function GapObj(x::T) ... end
.
Convenience adapters
This section describes how one can manipulate GAP objects from the Julia side, using Julia syntax features.
In particular, the following is available on the Julia side in order to support special GAP syntax beyond function calls with arguments.
Call functions with global options via
call_gap_func
or using Julia's keyword argument syntax. For example,Cyc(1.41421356 : bits:=20)
in GAP translates toGAP.Globals.Cyc(GAP.Obj(1.41421356); bits=20)
in Julia.Access list/matrix entries via
getindex
andsetindex!
respectively the corresponding Julia syntax (described there).Access record components via
getproperty
andsetproperty!
respectively the corresponding Julia syntax (described there).Check for bound record components via
hasproperty
.Access entries of a positional object via
getbangindex
, equivalent to GAP's![]
operator.Access components of a component object via
getbangproperty
, equivalent to GAP's!.
operator.Iterate over a GAP list or collection (such as a GAP domain)
obj
viafor x in obj
, ormap(f, obj)
for a unary functionf
, or[f(x) for x in obj]
. Ifobj
is not a list then a GAP iterator forobj
gets constructed.
julia> l = [1, 2, 3]; gl = GapObj(l)
GAP: [ 1, 2, 3 ]
julia> ll = []; for x in gl push!(ll, x); end; ll == l
true
julia> map(x -> x, gl) == l
true
julia> [x for x in gl] == l
true
- Iterating in Julia over a GAP list
obj
skips the unbound entries inobj
, like iterating in GAP does.
julia> gl = GAP.evalstr("[1,, 3]")
GAP: [ 1,, 3 ]
julia> ll = []; for x in gl push!(ll, x); end; ll
2-element Vector{Any}:
1
3
- Note that iterating in Julia over a GAP iterator object
obj
does not changeobj
, whereas iterating in GAP overobj
changesobj
.
julia> g = GAP.Globals.SymmetricGroup(3)
GAP: Sym( [ 1 .. 3 ] )
julia> iter = GAP.Globals.Iterator(g)
GAP: <iterator>
julia> [x for x in iter] == collect(g)
true
julia> [x for x in iter] == collect(g)
true
julia> f = GAP.evalstr("function(itr) local res, i; res:= [];" *
"for i in itr do Add( res, i ); od; return res; end")
GAP: function( itr ) ... end
julia> f( iter )
GAP: [ (), (2,3), (1,3), (1,3,2), (1,2,3), (1,2) ]
julia> f( iter )
GAP: [ ]
GAP.call_gap_func
— Functioncall_gap_func(func::GapObj, args...; kwargs...)
Call the GAP object func
as a function, with arguments args...
and global GAP options kwargs...
, and return the result if there is one, and nothing
otherwise.
There is no argument number checking here, all checks on the arguments are done by GAP itself.
For convenience, one can use the syntax func(args...; kwargs...)
.
Examples
julia> GAP.Globals.Factors( 12 )
GAP: [ 2, 2, 3 ]
julia> GAP.Globals.Cyc(GAP.Obj(1.41421356))
GAP: 35355339/25000000
julia> GAP.Globals.Cyc(GAP.Obj(1.41421356); bits=20)
GAP: E(8)-E(8)^3
GAP.call_with_catch
— Functioncall_with_catch(func, args::Vector)
call_with_catch(func, args::Vector, kwargs::Dict{Symbol,T}) where T
Return a tuple (ok, val)
where ok
is either true
, meaning that calling func
with arguments args
(and optionally with keyword arguments given by the keys and values of kwargs
) returns the value val
, or false
, meaning that the function call runs into an error; in the latter case, val
is set to the string of the error message.
This function is used on the GAP side.
Examples
julia> GAP.call_with_catch(sqrt, [2])
(true, 1.4142135623730951)
julia> flag, res = GAP.call_with_catch(sqrt, [-2]);
julia> flag
false
julia> startswith(res, "DomainError")
true
julia> GAP.call_with_catch(range, [2, 10], Dict(:step => 2))
(true, 2:2:10)
julia> flag, res = GAP.call_with_catch(range, [2, 10], Dict(:step => "a"));
julia> flag
false
julia> startswith(res, "MethodError")
true
Base.getindex
— Functiongetindex(x::GapObj, i::Int64)
getindex(x::GapObj, i::Int64, j::Int64)
getindex(x::GapObj, l::Union{Vector{T},AbstractRange{T}}) where {T<:Integer}
Return the entry at position i
or at position (i,j)
in x
, or the list of entries in x
at the positions described by l
, provided that x
is a GAP object supporting this, such as a GAP list or matrix object.
Examples
julia> l = GapObj([ 1, 2, 3, 5, 8, 13 ])
GAP: [ 1, 2, 3, 5, 8, 13 ]
julia> l[4]
5
julia> l[end]
13
julia> l[2:4]
GAP: [ 2, 3, 5 ]
julia> l[[1,4,4]]
GAP: [ 1, 5, 5 ]
julia> m = GapObj([ 1 2 ; 3 4 ])
GAP: [ [ 1, 2 ], [ 3, 4 ] ]
julia> m[1,1]
1
julia> m[1,2]
2
julia> m[2,1]
3
Base.setindex!
— Functionsetindex!(x::GapObj, v::Any, i::Int64)
setindex!(x::GapObj, v::Any, i::Int64, j::Int64)
setindex!(x::GapObj, v::Any, l::Union{Vector{T},AbstractRange{T}}) where {T<:Integer}
Set the entry at position i
or (i,j)
in x
to v
, or set the entries at the positions in x
that are described by l
to the entries in v
, provided that x
is a GAP object supporting this, such as a GAP list or matrix object.
Examples
julia> l = GapObj([ 1, 2, 3, 5, 8, 13 ])
GAP: [ 1, 2, 3, 5, 8, 13 ]
julia> l[1] = 0
0
julia> l[8] = -1
-1
julia> l[2:4] = [ 7, 7, 7 ]
3-element Vector{Int64}:
7
7
7
julia> l
GAP: [ 0, 7, 7, 7, 8, 13,, -1 ]
julia> m = GapObj([ 1 2 ; 3 4 ])
GAP: [ [ 1, 2 ], [ 3, 4 ] ]
julia> m[1,2] = 0
0
julia> m
GAP: [ [ 1, 0 ], [ 3, 4 ] ]
GAP.getbangindex
— Functiongetbangindex(x::GapObj, i::Int64)
Return the entry at position i
in the positional object x
.
Examples
julia> x = GAP.Globals.ZmodnZObj(1, 6)
GAP: ZmodnZObj( 1, 6 )
julia> GAP.Globals.IsPositionalObjectRep(x)
true
julia> getbangindex(x, 1)
1
GAP.hasbangindex
— Functionhasbangindex(x::GapObj, i::Int64)
Return whether the entry at position i
exists in the positional object x
.
Examples
julia> x = GAP.Globals.ZmodnZObj(1, 6)
GAP: ZmodnZObj( 1, 6 )
julia> GAP.Globals.IsPositionalObjectRep(x)
true
julia> hasbangindex(x, 1)
true
julia> hasbangindex(x, 2)
false
GAP.setbangindex!
— Functionsetbangindex!(x::GapObj, v::Any, i::Int64)
Set the entry at position i
in the positional object x
to v
, and return x
.
Examples
julia> x = GAP.Globals.ZmodnZObj(1, 6)
GAP: ZmodnZObj( 1, 6 )
julia> GAP.Globals.IsPositionalObjectRep(x)
true
julia> setbangindex!(x, 0, 1)
GAP: ZmodnZObj( 0, 6 )
Base.getproperty
— Functiongetproperty(x::GapObj, f::Symbol)
getproperty(x::GapObj, f::Union{AbstractString,Int64})
Return the record component of the GAP record x
that is described by f
.
Examples
julia> r = GapObj(Dict(:a => 1))
GAP: rec( a := 1 )
julia> r.a
1
Base.setproperty!
— Functionsetproperty!(x::GapObj, f::Symbol, v)
setproperty!(x::GapObj, f::Union{AbstractString,Int64}, v)
Set the record component of the GAP record x
that is described by f
to the value v
.
Examples
julia> r = GapObj(Dict(:a => 1))
GAP: rec( a := 1 )
julia> r.b = 0
0
julia> r
GAP: rec( a := 1, b := 0 )
Base.hasproperty
— Functionhasproperty(x::GapObj, f::Symbol)
hasproperty(x::GapObj, f::Union{AbstractString,Int64})
Return true
if the GAP record x
has a component that is described by f
, and false
otherwise.
Examples
julia> r = GapObj(Dict(:a => 1))
GAP: rec( a := 1 )
julia> hasproperty( r, :a )
true
julia> hasproperty( r, :b )
false
julia> r.b = 2
2
julia> hasproperty( r, :b )
true
julia> r
GAP: rec( a := 1, b := 2 )
GAP.getbangproperty
— Functiongetbangproperty(x::GapObj, f::Union{AbstractString,Int64,Symbol})
Return the value of the component f
in the component object x
.
Examples
julia> x = GAP.Globals.Iterator(GAP.Globals.Integers)
GAP: <iterator of Integers at 0>
julia> GAP.Globals.IsComponentObjectRep(x)
true
julia> getbangproperty(x, :counter)
0
GAP.hasbangproperty
— Functionhasbangproperty(x::GapObj, f::Union{AbstractString,Int64,Symbol})
Return whether the component object x
has the component f
.
Examples
julia> x = GAP.Globals.Iterator(GAP.Globals.Integers)
GAP: <iterator of Integers at 0>
julia> GAP.Globals.IsComponentObjectRep(x)
true
julia> hasbangproperty(x, :counter)
true
julia> hasbangproperty(x, :x)
false
GAP.setbangproperty!
— Functionsetbangproperty!(x::GapObj, f::Union{AbstractString,Int64,Symbol}, v)
Set the value of the component f
in the component object x
to v
, and return x
.
Examples
julia> x = GAP.Globals.Iterator(GAP.Globals.Integers)
GAP: <iterator of Integers at 0>
julia> GAP.Globals.IsComponentObjectRep(x)
true
julia> setbangproperty!(x, :counter, 3)
GAP: <iterator of Integers at -1>
julia> getbangproperty(x, :counter)
3
GAP.wrap_rng
— Functionwrap_rng(rng::Random.AbstractRNG)
Return a GAP object in the filter IsRandomSource
that uses rng
in calls to GAP's Random
function. The idea is that GAP's Random
methods for high level objects will just hand over the given random source to subfunctions until Random
gets called for a list or the bounds of a range, and then Base.rand
gets called with rng
.
Examples
julia> rng1 = Random.default_rng();
julia> rng2 = copy(rng1);
julia> rng1 == rng2
true
julia> rng1 === rng2
false
julia> gap_rng1 = GAP.wrap_rng(rng1)
GAP: <RandomSource in IsRandomSourceJulia>
julia> gap_rng2 = GAP.wrap_rng(rng2)
GAP: <RandomSource in IsRandomSourceJulia>
julia> res1 = GAP.Globals.Random(gap_rng1, 1, 10);
julia> rng1 == rng2 # the two rngs have diverged
false
julia> res1 == GAP.Globals.Random(gap_rng2, GapObj(1:10))
true
julia> rng1 == rng2 # now the two rngs are again in sync
true
julia> g = GAP.Globals.SymmetricGroup(10);
julia> p = GAP.Globals.Random(gap_rng1, g);
julia> p in g
true
julia> GAP.Globals.Random(gap_rng1, GAP.Globals.GF(2)^10)
GAP: <a GF2 vector of length 10>
GAP.randseed!
— FunctionGAP.randseed!([seed::Integer])
Reseed GAP's global RNG with seed
.
The given seed
must be a non-negative integer. When seed
is not specified, a random seed is generated from Julia's global RNG.
For a fixed seed, the stream of generated numbers is allowed to change between different versions of GAP.
For the following Julia functions, methods are provided that deal with the case that the arguments are GAP objects; they delegate to the corresponding GAP operations.
Julia | GAP |
---|---|
length | Length |
in | \in |
zero | ZeroSameMutability |
one | OneSameMutability |
- (unary) | AdditiveInverseSameMutability |
inv | InverseSameMutability |
+ | SUM |
- (binary) | DIFF |
* | PROD |
/ | QUO |
\ | LQUO |
^ | POW |
mod | MOD |
< | LT |
== | EQ |
The reason why four SameMutability
operations are chosen in this list is as follows. In GAP, binary arithmetic operations return immutable results if and only if the two arguments are immutable. Thus it is consistent if unary arithmetic operations return a result with the same mutability as the argument. Note that GAP provides several variants of these unary operations, regarding the mutability of the result (ZeroMutable
, ZeroImmutable
, ZeroSameMutability
, etc.), but here we have to choose one behaviour for the Julia function.
julia> l = GapObj( [ 1, 3, 7, 15 ] )
GAP: [ 1, 3, 7, 15 ]
julia> m = GapObj( [ 1 2; 3 4 ] )
GAP: [ [ 1, 2 ], [ 3, 4 ] ]
julia> length( l )
4
julia> length( m ) # different from Julia's behaviour
2
julia> 1 in l
true
julia> 2 in l
false
julia> zero( l )
GAP: [ 0, 0, 0, 0 ]
julia> one( m )
GAP: [ [ 1, 0 ], [ 0, 1 ] ]
julia> - l
GAP: [ -1, -3, -7, -15 ]
julia> l + 1
GAP: [ 2, 4, 8, 16 ]
julia> l + l
GAP: [ 2, 6, 14, 30 ]
julia> m + m
GAP: [ [ 2, 4 ], [ 6, 8 ] ]
julia> 1 - m
GAP: [ [ 0, -1 ], [ -2, -3 ] ]
julia> l * l
284
julia> l * m
GAP: [ 10, 14 ]
julia> m * m
GAP: [ [ 7, 10 ], [ 15, 22 ] ]
julia> 1 / m
GAP: [ [ -2, 1 ], [ 3/2, -1/2 ] ]
julia> m / 2
GAP: [ [ 1/2, 1 ], [ 3/2, 2 ] ]
julia> 2 \ m
GAP: [ [ 1/2, 1 ], [ 3/2, 2 ] ]
julia> m ^ 2
GAP: [ [ 7, 10 ], [ 15, 22 ] ]
julia> m ^ -1
GAP: [ [ -2, 1 ], [ 3/2, -1/2 ] ]
julia> mod( l, 3 )
GAP: [ 1, 0, 1, 0 ]
julia> m < 2 * m
true
julia> m^2 - 5 * m == 2 * one( m )
true
Access to the GAP help system
GAP.show_gap_help
— Functionshow_gap_help(topic::String, onlyexact::Bool = false)
Print the information from the GAP help system about topic
to the screen. If onlyexact
is true
then only exact matches are shown, otherwise all matches. For example, GAP.show_gap_help("Size")
shows also documentation for SizeScreen
and SizesPerfectGroups
, whereas GAP.show_gap_help("Size", true)
shows only documentation for Size
.
For the variant showing all matches, one can also enter ?GAP.Globals.Size
at the Julia prompt instead of calling show_gap_help
.
Examples
julia> GAP.show_gap_help( "Size" )
[...] # more than 50 entries from GAP manuals
help?> GAP.Globals.Size
[...] # the same
julia> GAP.show_gap_help( "Size", true )
[...] # about 15 entries from GAP manuals