Naively, one may think that in order to use Julia functionality from GAP, one has to convert all data to a format usable by Julia, then call Julia functions on that data, and finally convert it back; rinse and repeat. While this is certainly sometimes so, in many cases, things are a bit different: Some initial (usually very small) data may need to be converted. But afterwards, the output of one Julia function is used as input of the next one, and so on. Converting the data to GAP format and back then is needlessly wasteful. It is much better to not perform any conversion here. Instead, we create special wrapper
objects on the GAP side, which wraps a given Julia object without converting it. This operation is thus very cheap, both in terms of performance and in memory usage. Such a wrapped object can then be transparently used as input for Julia functions.
On the GAP C kernel level, the internal functions used for this are NewJuliaObj
, IS_JULIA_OBJ
, GET_JULIA_OBJ
. On the GAP language level, this is IsJuliaObject
(2.1-1). On the Julia side, there is usually no need for a wrapper, as (thanks to the shared garbage collector) most GAP objects are valid Julia objects of type GapObj
. The exception to that rule are immediate GAP objects, more on that in the next section.
Any conversion which the user cannot prevent, and which has some cost or choice involved, may cause several problems. The added overhead may turn an otherwise reasonable computation into an infeasible one (think about a conversion triggered several million times). And the conversion can add extra complications if one wants to detect and undo it.
While users should not be forced into conversions, it nevertheless should be possible to perform sensible conversions. The simpler it is to do so, the easier it is to use the interface.
If an object is converted from Julia to GAP and back to Julia (or conversely, from GAP to Julia and back to GAP), then ideally the result should be equal and of equal type to the original value. At the very least, the automatic conversions should follow this principle. This is not always possible, due to mismatches in existing types, but we strive to get as close as possible.
GAP has a notion of immediate
objects, whose values are stored inside the pointer
referencing them. GAP uses this to store small integers and elements of small finite fields, see for example the beginning of Chapter Reference: Integers in the GAP Reference Manual. Since these are not valid pointers, Julia cannot treat them like other GAP objects, which are simply Julia objects of type GapObj
. Instead, a conversion is unavoidable, at least when immediate objects are passed as stand-alone arguments to a function.
To this end, the interface converts GAP immediate integers into Julia Int64
objects, and vice versa. However, GAP immediate integers on a 64 bit system can only store 61 bits, so not all Int64
objects can be converted into immediate integers; integers exceeding the 61 bits limit are therefore wrapped like any other Julia object. Other Julia integer types, like UInt64
, Int32
, are also wrapped by default, in order to ensure that conversion round trips do not arbitrary change object types.
All automatic conversions and wrappings are handled on the C functions julia_gap
and gap_julia
in JuliaInterface.
The following conversions are performed by julia_gap
(from GAP's Obj
to Julia's jl_value_t*
).
NULL
to jl_nothing
,
immediate integer to Int64
,
immediate FFE to the GapFFE
Julia type,
GAP true
to Julia true
,
GAP false
to Julia false
,
Julia object wrapper to Julia object,
Julia function wrapper to Julia function,
other GAP objects to GapObj
.
The following conversions are performed by gap_julia
(from Julia's jl_value_t*
to GAP's Obj
).
Int64
to immediate integer when it fits, otherwise to a GAP large integer,
GapFFE
to immediate FFE,
Julia true
to GAP true
,
Julia false
to GAP false
,
GapObj
to Obj
,
other Julia objects to Julia object wrapper.
Manual conversion in GAP is done via the functions GAPToJulia
(3.2-3) and JuliaToGAP
(3.2-2). In Julia, conversion is done via gap_to_julia
and GapObj
.
Conversion from GAP to Julia
In GAP, the function GAPToJulia
(3.2-3) calls (after automatic conversion of the GAP object if applicable) the Julia function gap_to_julia
; If a Julia type has been entered as the first argument of GAPToJulia
(3.2-3) then this is the type to which the GAP object shall be converted, and if such a conversion is implemented then a Julia object of this type is returned, otherwise an ArgumentError
is thrown.
IsBool
to Bool
,
IsFFE and IsInternalRep
to GapFFE
,
IsInt and IsSmallIntRep
to Int8
, Int16
, Int32
, Int64
(default), Int128
, UInt8
, UInt16
, UInt32
, UInt64
, UInt128
, BigInt
, or Rational{T} where T <: Integer
,
GapObj and IsInt
to BigInt
(default), or Rational{T} where T <: Integer
,
GapObj and IsRat
to Rational{BigInt}
(default), or Rational{T} where T <: Integer
,
IsFloat
to Float16
, Float32
, Float64
(default), or BigFloat
,
IsChar
to Cuchar
,
IsString
to AbstractString
(default), String
, Symbol
, Vector{UInt8}
, or types available for IsList
,
IsRange
to StepRange{Int64,Int64}
(default), StepRange{T1,T2}
, UnitRange{T}
, or types available for IsList
,
IsBlist
to BitVector
(default), or types available for IsList
,
IsList
to Vector{Union{Any,Nothing}}
(default), Vector{T}
, Matrix{T}
, or T <: Tuple
,
IsRecord
to Dict{Symbol,Any}
(default) or Dict{Symbol,T}
.
If no Julia type is specified then a Julia type is chosen, based on the filters of the GAP object, see the (default)
markers in the above list. Note that this might include checking various filters and will be, in almost all cases, slower than the typed version.
Conversion from Julia to GAP
There are two alternatives for this direction, the Julia constructor Julia.GAP.Obj
(3.2-1) and the GAP constructor JuliaToGAP
(3.2-2).
Julia.GAP.Obj
(3.2-1) is a Julia function that takes one or two arguments, the object to be converted and optionally the value true
indicating recursive conversion of nested objects. The chosen method depends on the Julia type of the first argument.
The function JuliaToGAP
(3.2-2). takes two or three arguments, a GAP filter and an object to be converted, and optionally the value true
indicating recursive conversion of nested objects. Various methods for this constructor then take care of input validation and the actual conversion, either by delegating to the Julia function GapObj
(which takes just one or two arguments and chooses the GAP filters of its result depending on the Julia type), or by automatic conversion.
The supported Julia types of the second argument of JuliaToGAP
(3.2-2) are as follows; more Julia types may be supported for Julia.GAP.Obj
(3.2-1).
Julia type | GAP filter | comment |
Int64 , GapObj , GapFFE , and Bool |
automatic conversion | |
other integers, including BigInt |
IsInt |
integers |
Rational{T} |
IsRat |
rationals |
Float16 , Float32 , Float64 |
IsFloat |
machine floats |
AbstractString |
IsString |
strings |
Symbol |
IsString |
strings |
Vector{T} |
IsList |
plain lists |
Vector{Bool} , BitVector |
IsBList |
bit lists |
Tuple{T} |
IsList |
plain lists |
Dict{String,T} , Dict{Symbol,T} |
IsRecord |
records |
UnitRange{T} , StepRange{T} |
IsRange |
ranges |
‣ Julia.GAP.Obj ( juliaobj[, recursive] ) | ( function ) |
‣ Julia.GAP.GapObj ( juliaobj[, recursive] ) | ( function ) |
Returns: a GAP object
The Julia constructor Julia.GAP.Obj
takes an object juliaobj and chooses a method depending on its Julia type for computing a GAP object corresponding to juliaobj. If recursive is true
then nested objects are converted recursively.
gap> Julia.GAP.Obj( 42 ); 42 gap> m:= GAPToJulia( [ [ 1, 2 ], [ 3, 4 ] ] ); <Julia: Any[Any[1, 2], Any[3, 4]]> gap> Julia.GAP.Obj( m ); [ <Julia: Any[1, 2]>, <Julia: Any[3, 4]> ] gap> Julia.GAP.Obj( m, true ); [ [ 1, 2 ], [ 3, 4 ] ]
One advantage of Julia.GAP.Obj
compared to the GAP constructor JuliaToGAP
(3.2-2) is that it is easy to extend the scope of Julia.GAP.Obj
on the Julia side, whereas extending JuliaToGAP
(3.2-2) would require changing its methods. For example, the Oscar system provides Julia types of matrices for which conversions to GAP matrices are installed, via suitable methods for Julia.GAP.Obj
.
If one is sure that the result of the conversion to GAP is not an immediate GAP object then one can call Julia.GAP.GapObj
instead of Julia.GAP.Obj
.
‣ JuliaToGAP ( filt, juliaobj[, recursive] ) | ( constructor ) |
Returns: a GAP object in the filter filt
Let juliaobj be a Julia object in for which a conversion to GAP is provided, in the sense of Section 3.1, such that the corresponding GAP object is in the filter filt. Then JuliaToGAP
returns this GAP object.
gap> s:= GAPToJulia( "abc" ); <Julia: "abc"> gap> JuliaToGAP( IsString, s ); "abc" gap> l:= GAPToJulia( [ 1, 2, 4 ] ); <Julia: Any[1, 2, 4]> gap> JuliaToGAP( IsList, l ); [ 1, 2, 4 ]
For recursive structures (GAP lists and records), only the outermost level is converted except if the optional argument recursive is given and has the value true
, in this case all layers are converted recursively.
Note that this default is different from the default in the other direction (see GAPToJulia
(3.2-3)). The idea behind this choice is that from the viewpoint of a GAP session, it is more likely to use plain Julia objects for computations on the Julia side than Julia objects that contain GAP subobjects, whereas shallow conversion
of Julia objects to GAP yields something useful on the GAP side.
gap> m:= GAPToJulia( [ [ 1, 2 ], [ 3, 4 ] ] ); <Julia: Any[Any[1, 2], Any[3, 4]]> gap> JuliaToGAP( IsList, m ); [ <Julia: Any[1, 2]>, <Julia: Any[3, 4]> ] gap> JuliaToGAP( IsList, m, true ); [ [ 1, 2 ], [ 3, 4 ] ]
The following values for filt are supported. IsInt
(Reference: IsInt), IsRat
(Reference: IsRat), IsFFE
(Reference: IsFFE), IsFloat
(see Reference: Floats), IsBool
(Reference: IsBool), IsChar
(Reference: IsChar), IsRecord
(Reference: IsRecord), IsString
(Reference: IsString), IsRange
(Reference: IsRange), IsBlist
(Reference: IsBlist), IsList
(Reference: IsList). See Section 3.1 for the admissible types of juliaobj in these cases.
‣ GAPToJulia ( [juliatype, ]gapobj[, recursive] ) | ( function ) |
Returns: a Julia object
Let gapobj be an object for which a conversion to Julia is provided, in the sense of Section 3.1, such that a corresponding Julia object with type juliatype can be constructed. Then GAPToJulia
returns this Julia object.
If juliatype is not given then a default type is chosen. The function is implemented via the Julia function GAP.gap_to_julia
.
gap> GAPToJulia( 1 ); 1 gap> GAPToJulia( JuliaEvalString( "Rational{Int64}" ), 1 ); <Julia: 1//1> gap> l:= [ 1, 3, 4 ];; gap> GAPToJulia( l ); <Julia: Any[1, 3, 4]> gap> GAPToJulia( JuliaEvalString( "Vector{Int}" ), l ); <Julia: [1, 3, 4]> gap> m:= [ [ 1, 2 ], [ 3, 4 ] ];; gap> GAPToJulia( m ); <Julia: Any[Any[1, 2], Any[3, 4]]> gap> GAPToJulia( JuliaEvalString( "Matrix{Int}" ), m ); <Julia: [1 2; 3 4]> gap> r:= rec( a:= 1, b:= [ 1, 2, 3 ] );; gap> GAPToJulia( r ); <Julia: Dict{Symbol,Any}(:a => 1,:b => Any[1, 2, 3])>
If gapobj is a list or a record, one may want that its subobjects are also converted to Julia or that they are kept as they are, which can be decided by entering true
or false
as the value of the optional argument recursive; the default is true
, that is, the subobjects of gapobj are converted recursively.
Note that this default is different from the default in the other direction, see the description of JuliaToGAP
(3.2-2).
gap> jl:= GAPToJulia( m, false ); <Julia: Any[GAP: [ 1, 2 ], GAP: [ 3, 4 ]]> gap> jl[1]; [ 1, 2 ] gap> jr:= GAPToJulia( r, false ); <Julia: Dict{Symbol,Any}(:a => 1,:b => GAP: [ 1, 2, 3 ])> gap> Julia.Base.get( jr, JuliaSymbol( "b" ), fail ); [ 1, 2, 3 ]
‣ IsRandomSourceJulia ( obj ) | ( filter ) |
Returns: true
or false
This filter allows one to use Julia's random number generators in GAP, see Reference: Random Sources for the background. Calling RandomSource
(Reference: RandomSource) with only argument IsRandomSourceJulia
yields a GAP random source that uses a copy of Julia's default random number generator Julia.Random.default_rng()
. Note that different calls with only argument IsRandomSourceJulia
yield different random sources.
Called with IsRandomSourceJulia
and a positive integer, RandomSource
(Reference: RandomSource) returns a random source that is based on a copy of Julia.Random.default_rng()
but got initialized with the given integer as a seed.
Called with IsRandomSourceJulia
and a Julia random number generator, RandomSource
(Reference: RandomSource) returns a random source that uses this random number generator. Note that we do not make a copy of the second argument, in order to be able to use the given random number generator both on the GAP side and the Julia side.
State
(Reference: State) for random sources in IsRandomSourceJulia
returns a copy of the underlying Julia random number generator.
gap> rs1:= RandomSource( IsRandomSourceJulia ); <RandomSource in IsRandomSourceJulia> gap> rs2:= RandomSource( IsRandomSourceJulia, > Julia.Random.default_rng() ); <RandomSource in IsRandomSourceJulia> gap> repeat > x:= Random( rs1, [ 1 .. 100 ] ); > y:= Random( rs2, [ 1 .. 100 ] ); > until x <> y; gap> Random( rs1, 1, 100 ) in [ 1 .. 100 ]; true gap> from:= 2^70;; to:= from + 100;; gap> x:= Random( rs1, from, to );; gap> from <= x and x <= to; true gap> g:= SymmetricGroup( 10 );; gap> Random( rs1, g ) in g; true gap> State( rs1 ) = JuliaPointer( rs1 ); true
Discuss/add more dedicated conversion functions and/or special wrapper kinds, e.g.:
There could be a Julia type hierarchy of wrappers, e.g., GAPInt <: GAPRat <: GAPCyc
; those types would wrap the corresponding GAP objects, i.e., they would simply wrap a Union{GapObj,Int64}
, but perhaps provided nicer integration with the rest of Julia, like methods for gcd
, say, which are properly type restricted; or nicer printing (w/o the GAP:
prefix even?). Not really sure whether this is useful, though.
Should we allow the three argument case of JuliaToGAP
(3.2-2) in all cases, e.g., JuliaToGAP( IsInt, 1, true )
?
Many tests of conversions are missing.
generated by GAPDoc2HTML