Goto Chapter: Top 1 2 3 Ind
 [Top of Book]  [Contents]   [Previous Chapter]   [Next Chapter] 

3 Conversions between GAP and Julia
 3.1 Conversion rules
 3.2 Conversion functions
 3.3 Using Julia random number generators in GAP
 3.4 Open items

3 Conversions between GAP and Julia

3.1 Conversion rules

3.1-1 Guiding principles
Avoid conversions, use wrapper objects instead.

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 GAP.GapObj. The exception to that rule are immediate GAP objects, more on that in the next section.

Perform automatic conversions only if absolutely necessary, or if unambiguous and free.

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.

Provide explicit conversion functions for as many data types as possible.

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.

Conversion round trip fidelity.

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.

3.1-2 Automatic (implicit) conversions

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 GAP.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 Int64objects 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*).

The following conversions are performed by gap_julia (from Julia's jl_value_t* to GAP's Obj).

3.1-3 Manual (explicit) conversions

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 julia_to_gap.

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.

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 julia_to_gap (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

3.2 Conversion functions

3.2-1 Julia.GAP.Obj
‣ 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.

3.2-2 JuliaToGAP
‣ 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.

3.2-3 GAPToJulia
‣ 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 ]

3.3 Using Julia random number generators in GAP

3.3-1 IsRandomSourceJulia
‣ 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

3.4 Open items

 [Top of Book]  [Contents]   [Previous Chapter]   [Next Chapter] 
Goto Chapter: Top 1 2 3 Ind

generated by GAPDoc2HTML