GAP Integration
This section explains how OSCAR interacts with GAP.
The Julia package GAP.jl
This package provides a bidirectional interface between GAP and Julia. Its documentation describes how to call GAP functions in Julia code and vice versa, and how low level Julia objects can be converted to GAP objects and vice versa.
When one works interactively in an OSCAR session, calling GAP.prompt() opens a GAP session which has access to the variables in the Julia session, in particular to all OSCAR functions and objects; one can return to the Julia prompt by entering quit; in the GAP session.
Using GAP from OSCAR
Calling GAP directly is an escape hatch for functionality that OSCAR does not provide yet. Prefer OSCAR's own functions whenever they exist, and please open an issue for what is missing, so that it can be added to OSCAR itself.
Global GAP variables and functions
Every global GAP variable is available as GAP.Globals.<name>, and GAP functions can be called like Julia functions. Julia values of type Int64 and Bool can be passed as arguments directly; integers of other types, and all other arguments, must be converted with GapObj first, see below. Small integers, booleans, and elements of small finite fields that GAP returns are converted to Julia automatically. Most other results are GapObjs and get printed with the prefix GAP: (but a GAP function may also return any Julia object, for example one that was passed to it as an argument).
julia> GAP.Globals.Factorial(5)
120
julia> GAP.Globals.IsPrimeInt(97)
true
julia> x = GAP.Globals.Factorial(30)
GAP: 265252859812191058636308480000000Converting GAP objects to OSCAR objects
For numbers, lists, and strings, use the constructor of the Julia type that you want to get. Nested lists need the recursive keyword argument, and OSCAR matrices are constructed from GAP matrices with matrix.
julia> ZZ(GAP.Globals.Factorial(30))
265252859812191058636308480000000
julia> Vector{Int}(GAP.Globals.DivisorsInt(12))
6-element Vector{Int64}:
1
2
3
4
6
12
julia> Vector{Vector{Int}}(GAP.Globals.Partitions(4))
5-element Vector{Vector{Int64}}:
[1, 1, 1, 1]
[2, 1, 1]
[2, 2]
[3, 1]
[4]
julia> String(GAP.Globals.StructureDescription(GAP.Globals.SymmetricGroup(4)))
"S4"
julia> matrix(ZZ, GAP.Globals.IdentityMat(2))
[1 0]
[0 1]For example, this is how to call GAP's Derangements.
julia> x = GAP.Globals.Derangements(GapObj(1:4))
GAP: [ [ 2, 1, 4, 3 ], [ 2, 3, 4, 1 ], [ 2, 4, 1, 3 ], [ 3, 1, 4, 2 ], [ 3, 4, 1, 2 ], [ 3, 4, 2, 1 ], [ 4, 1, 2, 3 ], [ 4, 3, 1, 2 ], [ 4, 3, 2, 1 ] ]
julia> Vector{Vector{Int}}(x)
9-element Vector{Vector{Int64}}:
[2, 1, 4, 3]
[2, 3, 4, 1]
[2, 4, 1, 3]
[3, 1, 4, 2]
[3, 4, 1, 2]
[3, 4, 2, 1]
[4, 1, 2, 3]
[4, 3, 1, 2]
[4, 3, 2, 1]GAP groups are turned into OSCAR groups by the constructors PermGroup, PcGroup, FPGroup, and matrix_group.
julia> G = PermGroup(GAP.Globals.MathieuGroup(11))
Permutation group of degree 11 and order 7920
julia> H = PcGroup(GAP.Globals.SmallGroup(8, 3))
Pc group of order 8Elements of GAP's finite fields and cyclotomic fields are converted by calling the OSCAR field on them, and the same works for matrices.
julia> F = GF(3);
julia> F(GAP.Globals.Z(3))
2
julia> K, z = cyclotomic_field(5);
julia> K(GAP.Globals.E(5))
z_5
julia> matrix(K, GAP.evalstr("[[E(5), 1], [0, E(5)^2]]"))
[z_5 1]
[ 0 z_5^2]For more complex situations, Oscar.iso_gap_oscar and Oscar.iso_oscar_gap construct an isomorphism between a GAP ring or field and its OSCAR counterpart, see the section on GAP Integration. More conversions are described in the GAP.jl manual.
Passing OSCAR objects to GAP
The function GapObj converts Julia and OSCAR objects into GAP objects. Not every OSCAR group is based on a GAP group, but permutation groups, pc groups, finitely presented groups, and matrix groups are; for those, GapObj(G) returns the underlying GAP group. The same holds for group elements, character tables, and other objects that are backed by GAP.
julia> G = symmetric_group(4);
julia> GAP.Globals.StructureDescription(GapObj(G))
GAP: "S4"
julia> GAP.Globals.IsAbelian(GapObj(G))
false
julia> GapObj(ZZ(2)^100)
GAP: 1267650600228229401496703205376
julia> GapObj([1, 2, 3])
GAP: [ 1, 2, 3 ]
julia> GapObj([[1, 2], [3, 4]]; recursive = true)
GAP: [ [ 1, 2 ], [ 3, 4 ] ]
julia> GapObj(matrix(QQ, [1 2; 3 4]))
GAP: [ [ 1, 2 ], [ 3, 4 ] ]Julia functions can be passed to GAP functions that expect a function argument.
julia> GAP.Globals.Filtered(GapObj(1:10), is_prime)
GAP: [ 2, 3, 5, 7 ]Running GAP code
GAP.evalstr evaluates a string containing GAP code and returns the result. The string has to be a self-contained piece of GAP code; GAP code that is stored in a file can be read with GAP.Globals.Read.
julia> GAP.evalstr("List([1..5], x -> x^2)")
GAP: [ 1, 4, 9, 16, 25 ]
julia> r = GAP.evalstr("rec(a := 1, b := [1, 2])");
julia> r.a
1GAP.prompt() opens a GAP prompt inside the Julia session. In it, the variables of the Julia session are available via Julia.<name>, and the OSCAR functions via Oscar_jl.<name>. Enter quit; to get back to the Julia prompt. GAP's help system is available at the Julia prompt via ?GAP.Globals.Size or GAP.show_gap_help("Size", true).
GAP packages that are not loaded automatically can be loaded with GAP.Packages.load, and packages that are not shipped with OSCAR can be installed with GAP.Packages.install.
julia> GAP.Packages.load("ctbllib")
trueWhen OSCAR does not have the function you need
If you cannot find a counterpart of a GAP function in OSCAR (the tables in the Notes for GAP users may help), calling the GAP function as described above will usually work, as long as the objects involved can be converted. In this case, please open an issue to request a proper OSCAR interface for it.
Note that Oscar.GAPWrap, described below, is intended for OSCAR's own code, not for interactive use.
Interface functionalities beyond GAP.jl
For code involving Julia types that are defined in OSCAR, GAP.jl cannot provide utility functions such as conversions to and from GAP.
The GAP package OscarInterface (at
gap/OscarInterface) is intended to contain the GAP code in question, for example the declarations of new filters and the installation of new methods.Note that such code must be loaded at runtime into the GAP session that is started by Julia, and the OscarInterface package gets loaded in OSCAR's
__init__function.The files in the directory
src/GAPare intended to contain the Julia code in question, for example conversions from GAP toZZRingElem,QQFieldElem,FinFieldElem, etc., and the construction of isomorphisms between algebraic structures such as rings and fields in GAP and OSCAR, viaOscar.iso_oscar_gapandOscar.iso_gap_oscar.In OSCAR code, global GAP variables can be accessed as members of
GAP.Globals, but for the case of GAP functions, it is more efficient to useOscar.GAPWrapinstead.For example, if one wants to call GAP's
IsFinitethen it is recommended to replace the callGAP.Globals.IsFinite(x)::Bool, for some GAP objectx(a group or a ring or a list, etc.), byOscar.GAPWrap.IsFinite(x). This works only if the method in question gets defined insrc/GAP/wrappers.jl, thus methods with the required signatures should be added to this file when they turn out to be needed.(The reason why we collect the
GAP.@wraplines in an OSCAR file and not inside GAP.jl is that we can extend the list without waiting for releases of GAP.jl.)Note that
Oscar.GAPWrapis intended only for calling the GAP function in question. In situations where a GAP function is used for other purposes, usually as an argument in a function call, one should access it viaGAP.Globals.In GAP code, global Julia variables can be accessed as members of
Julia, relative to itsMainmodule. For example, one can callJulia.sqrtandJulia.typeof(orJulia.Base.sqrtandJulia.Core.typeof) in GAP code.In order to access variables from the
Oscarmodule, it is not safe to useJulia.Oscarbecause the moduleOscaris not always defined inMain. Instead, there is the global GAP variableOscar_jl.
iso_oscar_gap — Function
Oscar.iso_oscar_gap(R::T) -> Map{T, GapObj}Return an isomorphism f with domain R and codomain a GAP object S.
Elements x of R are mapped to S via f(x), and elements y of S are mapped to R via preimage(f, y).
Matrices m over R are mapped to matrices over S via map_entries(f, m), and matrices n over S are mapped to matrices over R via Oscar.preimage_matrix(f, n).
Admissible values of R and the corresponding S are currently as follows.
R | S (in GAP.Globals) |
|---|---|
ZZ | Integers |
QQ | Rationals |
residue_ring(ZZ, n)[1] | mod(Integers, n) |
finite_field(p, d)[1] | GF(p, d) |
cyclotomic_field(n)[1] | CF(n) |
number_field(f::QQPolyRingElem)[1] | AlgebraicExtension(Rationals, g) |
abelian_closure(QQ)[1] | Cyclotomics |
polynomial_ring(F)[1] | PolynomialRing(G) |
polynomial_ring(F, n)[1] | PolynomialRing(G, n) |
(Here g is the polynomial over GAP.Globals.Rationals that corresponds to f, and G is equal to Oscar.iso_oscar_gap(F).)
Examples
julia> f = Oscar.iso_oscar_gap(ZZ);
julia> x = ZZ(2)^100; y = f(x)
GAP: 1267650600228229401496703205376
julia> preimage(f, y) == x
true
julia> m = matrix(ZZ, 2, 3, [1, 2, 3, 4, 5, 6]);
julia> n = map_entries(f, m)
GAP: [ [ 1, 2, 3 ], [ 4, 5, 6 ] ]
julia> Oscar.preimage_matrix(f, n) == m
true
julia> R, x = polynomial_ring(QQ);
julia> f = Oscar.iso_oscar_gap(R);
julia> pol = x^2 + x - 1;
julia> y = f(pol)
GAP: x_1^2+x_1-1
julia> preimage(f, y) == pol
trueThe functions Oscar.iso_oscar_gap and Oscar.iso_gap_oscar are not injective. Due to caching, it may happen that S stores an attribute value of Oscar.iso_gap_oscar(S), but that the codomain of this map is not identical with or even not equal to the given R.
Note also that R and S may differ w.r.t. some structural properties because GAP does not support all kinds of constructions that are possible in Oscar. For example, if R is a non-simple number field then S will be a simple extension because GAP knows only simple field extensions. Thus using Oscar.iso_oscar_gap(R) for objects R whose recursive structure is not fully supported in GAP will likely cause overhead at runtime.
iso_gap_oscar — Function
Oscar.iso_gap_oscar(R) -> Map{GapObj, T}Return an isomorphism f with domain the GAP object R and codomain an Oscar object S.
Elements x of R are mapped to S via f(x), and elements y of S are mapped to R via preimage(f, y).
Matrices m over R are mapped to matrices over S via map_entries(f, m), and matrices n over S are mapped to matrices over R via Oscar.preimage_matrix(f, n).
Admissible values of R and the corresponding S are currently as follows.
S (in GAP.Globals) | R |
|---|---|
Integers | ZZ |
Rationals | QQ |
mod(Integers, n) | residue_ring(ZZ, n)[1] |
GF(p, d) | finite_field(p, d)[1] |
CF(n) | cyclotomic_field(n)[1] |
AlgebraicExtension(Rationals, f) | number_field(g)[1] |
Cyclotomics | abelian_closure(QQ)[1] |
PolynomialRing(F) | polynomial_ring(G)[1] |
PolynomialRing(F, n) | polynomial_ring(G, n)[1] |
(Here g is the polynomial over QQ that corresponds to the polynomial f, and G is equal to Oscar.iso_gap_oscar(F).)
Examples
julia> f = Oscar.iso_gap_oscar(GAP.Globals.Integers);
julia> x = ZZ(2)^100; y = preimage(f, x)
GAP: 1267650600228229401496703205376
julia> f(y) == x
true
julia> m = matrix(ZZ, 2, 3, [1, 2, 3, 4, 5, 6]);
julia> n = Oscar.preimage_matrix(f, m)
GAP: [ [ 1, 2, 3 ], [ 4, 5, 6 ] ]
julia> map_entries(f, n) == m
true
julia> R = GAP.Globals.PolynomialRing(GAP.Globals.Rationals);
julia> f = Oscar.iso_gap_oscar(R);
julia> x = gen(codomain(f));
julia> pol = x^2 + x + 1;
julia> y = preimage(f, pol)
GAP: x_1^2+x_1+1
julia> f(y) == pol
trueThe functions Oscar.iso_gap_oscar and Oscar.iso_oscar_gap are not injective. Due to caching, it may happen that S stores an attribute value of Oscar.iso_oscar_gap(S), but that the codomain of this map is not identical with or even not equal to the given R.