Library Interface to GAP#
This module implements a fast C library interface to GAP.
To use it, you simply call libgap
(the parent of all
GapElement
instances) and use it to
convert Sage objects into GAP objects.
EXAMPLES:
sage: a = libgap(10)
sage: a
10
sage: type(a)
<class 'sage.libs.gap.element.GapElement_Integer'>
sage: a*a
100
sage: timeit('a*a') # random output
625 loops, best of 3: 898 ns per loop
>>> from sage.all import *
>>> a = libgap(Integer(10))
>>> a
10
>>> type(a)
<class 'sage.libs.gap.element.GapElement_Integer'>
>>> a*a
100
>>> timeit('a*a') # random output
625 loops, best of 3: 898 ns per loop
Compared to the expect interface this is >1000 times faster:
sage: b = gap('10')
sage: timeit('b*b') # random output; long time
125 loops, best of 3: 2.05 ms per loop
>>> from sage.all import *
>>> b = gap('10')
>>> timeit('b*b') # random output; long time
125 loops, best of 3: 2.05 ms per loop
If you want to evaluate GAP commands, use the Gap.eval()
method:
sage: libgap.eval('List([1..10], i->i^2)')
[ 1, 4, 9, 16, 25, 36, 49, 64, 81, 100 ]
>>> from sage.all import *
>>> libgap.eval('List([1..10], i->i^2)')
[ 1, 4, 9, 16, 25, 36, 49, 64, 81, 100 ]
not to be confused with the libgap
call, which converts Sage
objects to GAP objects, for example strings to strings:
sage: libgap('List([1..10], i->i^2)')
"List([1..10], i->i^2)"
sage: type(_)
<class 'sage.libs.gap.element.GapElement_String'>
>>> from sage.all import *
>>> libgap('List([1..10], i->i^2)')
"List([1..10], i->i^2)"
>>> type(_)
<class 'sage.libs.gap.element.GapElement_String'>
You can usually use the sage()
method to convert the resulting GAP element back to its Sage
equivalent:
sage: a.sage()
10
sage: type(_)
<class 'sage.rings.integer.Integer'>
sage: libgap.eval('5/3 + 7*E(3)').sage() # needs sage.rings.number_field
7*zeta3 + 5/3
sage: gens_of_group = libgap.AlternatingGroup(4).GeneratorsOfGroup()
sage: generators = gens_of_group.sage()
sage: generators # a Sage list of Sage permutations!
[[2, 3, 1], [1, 3, 4, 2]]
sage: PermutationGroup(generators).cardinality() # computed in Sage
12
sage: libgap.AlternatingGroup(4).Size() # computed in GAP
12
>>> from sage.all import *
>>> a.sage()
10
>>> type(_)
<class 'sage.rings.integer.Integer'>
>>> libgap.eval('5/3 + 7*E(3)').sage() # needs sage.rings.number_field
7*zeta3 + 5/3
>>> gens_of_group = libgap.AlternatingGroup(Integer(4)).GeneratorsOfGroup()
>>> generators = gens_of_group.sage()
>>> generators # a Sage list of Sage permutations!
[[2, 3, 1], [1, 3, 4, 2]]
>>> PermutationGroup(generators).cardinality() # computed in Sage
12
>>> libgap.AlternatingGroup(Integer(4)).Size() # computed in GAP
12
We can also specify which group in Sage the permutations should consider themselves as elements of when converted to Sage:
sage: A4 = groups.permutation.Alternating(4)
sage: generators = gens_of_group.sage(parent=A4); generators
[(1,2,3), (2,3,4)]
sage: all(gen.parent() is A4 for gen in generators)
True
>>> from sage.all import *
>>> A4 = groups.permutation.Alternating(Integer(4))
>>> generators = gens_of_group.sage(parent=A4); generators
[(1,2,3), (2,3,4)]
>>> all(gen.parent() is A4 for gen in generators)
True
So far, the following GAP data types can be directly converted to the corresponding Sage datatype:
GAP booleans
true
/false
to Sage booleansTrue
/False
. The third GAP boolean valuefail
raises aValueError
.GAP integers to Sage integers.
GAP rational numbers to Sage rational numbers.
GAP cyclotomic numbers to Sage cyclotomic numbers.
GAP permutations to Sage permutations.
The GAP containers
List
andrec
are converted to Sage containerslist
anddict
. Furthermore, thesage()
method is applied recursively to the entries.
Special support is available for the GAP container classes. GAP lists can be used as follows:
sage: lst = libgap([1,5,7]); lst
[ 1, 5, 7 ]
sage: type(lst)
<class 'sage.libs.gap.element.GapElement_List'>
sage: len(lst)
3
sage: lst[0]
1
sage: [ x^2 for x in lst ]
[1, 25, 49]
sage: type(_[0])
<class 'sage.libs.gap.element.GapElement_Integer'>
>>> from sage.all import *
>>> lst = libgap([Integer(1),Integer(5),Integer(7)]); lst
[ 1, 5, 7 ]
>>> type(lst)
<class 'sage.libs.gap.element.GapElement_List'>
>>> len(lst)
3
>>> lst[Integer(0)]
1
>>> [ x**Integer(2) for x in lst ]
[1, 25, 49]
>>> type(_[Integer(0)])
<class 'sage.libs.gap.element.GapElement_Integer'>
Note that you can access the elements of GAP List
objects as you
would expect from Python (with indexing starting at 0), but the
elements are still of type
GapElement
. The other GAP container
type are records, which are similar to Python dictionaries. You can
construct them directly from Python dictionaries:
sage: libgap({'a':123, 'b':456})
rec( a := 123, b := 456 )
>>> from sage.all import *
>>> libgap({'a':Integer(123), 'b':Integer(456)})
rec( a := 123, b := 456 )
Or get them as results of computations:
sage: rec = libgap.eval('rec(a:=123, b:=456, Sym3:=SymmetricGroup(3))')
sage: rec['Sym3']
Sym( [ 1 .. 3 ] )
sage: dict(rec)
{'Sym3': Sym( [ 1 .. 3 ] ), 'a': 123, 'b': 456}
>>> from sage.all import *
>>> rec = libgap.eval('rec(a:=123, b:=456, Sym3:=SymmetricGroup(3))')
>>> rec['Sym3']
Sym( [ 1 .. 3 ] )
>>> dict(rec)
{'Sym3': Sym( [ 1 .. 3 ] ), 'a': 123, 'b': 456}
The output is a Sage dictionary whose keys are Sage strings and whose
Values are instances of GapElement()
. So,
for example, rec['a']
is not a Sage integer. To recursively
convert the entries into Sage objects, you should use the
sage()
method:
sage: rec.sage()
{'Sym3': NotImplementedError('cannot construct equivalent Sage object'...),
'a': 123,
'b': 456}
>>> from sage.all import *
>>> rec.sage()
{'Sym3': NotImplementedError('cannot construct equivalent Sage object'...),
'a': 123,
'b': 456}
Now rec['a']
is a Sage integer. We have not implemented the
conversion of the GAP symmetric group to the Sage symmetric group yet,
so you end up with a NotImplementedError
exception object. The
exception is returned and not raised so that you can work with the
partial result.
While we don’t directly support matrices yet, you can convert them to
Gap List of Lists. These lists are then easily converted into Sage
using the recursive expansion of the
sage()
method:
sage: M = libgap.eval('BlockMatrix([[1,1,[[1, 2],[ 3, 4]]], [1,2,[[9,10],[11,12]]], [2,2,[[5, 6],[ 7, 8]]]],2,2)')
sage: M
<block matrix of dimensions (2*2)x(2*2)>
sage: M.List() # returns a GAP List of Lists
[ [ 1, 2, 9, 10 ], [ 3, 4, 11, 12 ], [ 0, 0, 5, 6 ], [ 0, 0, 7, 8 ] ]
sage: M.List().sage() # returns a Sage list of lists
[[1, 2, 9, 10], [3, 4, 11, 12], [0, 0, 5, 6], [0, 0, 7, 8]]
sage: matrix(ZZ, _)
[ 1 2 9 10]
[ 3 4 11 12]
[ 0 0 5 6]
[ 0 0 7 8]
>>> from sage.all import *
>>> M = libgap.eval('BlockMatrix([[1,1,[[1, 2],[ 3, 4]]], [1,2,[[9,10],[11,12]]], [2,2,[[5, 6],[ 7, 8]]]],2,2)')
>>> M
<block matrix of dimensions (2*2)x(2*2)>
>>> M.List() # returns a GAP List of Lists
[ [ 1, 2, 9, 10 ], [ 3, 4, 11, 12 ], [ 0, 0, 5, 6 ], [ 0, 0, 7, 8 ] ]
>>> M.List().sage() # returns a Sage list of lists
[[1, 2, 9, 10], [3, 4, 11, 12], [0, 0, 5, 6], [0, 0, 7, 8]]
>>> matrix(ZZ, _)
[ 1 2 9 10]
[ 3 4 11 12]
[ 0 0 5 6]
[ 0 0 7 8]
Using the GAP C library from Cython#
Todo
Expand the following text
We are using the GAP API provided by the GAP project since GAP 4.10.
AUTHORS:
William Stein, Robert Miller (2009-06-23): first version
Volker Braun, Dmitrii Pasechnik, Ivan Andrus (2011-03-25, Sage Days 29): almost complete rewrite; first usable version.
Volker Braun (2012-08-28, GAP/Singular workshop): update to gap-4.5.5, make it ready for public consumption.
Dima Pasechnik (2018-09-18, GAP Days): started the port to native libgap API
- class sage.libs.gap.libgap.Gap[source]#
Bases:
Parent
The libgap interpreter object.
Note
This object must be instantiated exactly once by the libgap. Always use the provided
libgap
instance, and never instantiateGap
manually.EXAMPLES:
sage: libgap.eval('SymmetricGroup(4)') Sym( [ 1 .. 4 ] )
>>> from sage.all import * >>> libgap.eval('SymmetricGroup(4)') Sym( [ 1 .. 4 ] )
- Element[source]#
alias of
GapElement
- collect()[source]#
Manually run the garbage collector
EXAMPLES:
sage: a = libgap(123) sage: del a sage: libgap.collect()
>>> from sage.all import * >>> a = libgap(Integer(123)) >>> del a >>> libgap.collect()
- count_GAP_objects()[source]#
Return the number of GAP objects that are being tracked by GAP.
OUTPUT:
An integer
EXAMPLES:
sage: libgap.count_GAP_objects() # random output 5
>>> from sage.all import * >>> libgap.count_GAP_objects() # random output 5
- eval(gap_command)[source]#
Evaluate a gap command and wrap the result.
INPUT:
gap_command
– a string containing a valid gap command without the trailing semicolon.
OUTPUT:
A
GapElement
.EXAMPLES:
sage: libgap.eval('0') 0 sage: libgap.eval('"string"') "string"
>>> from sage.all import * >>> libgap.eval('0') 0 >>> libgap.eval('"string"') "string"
- function_factory(function_name)[source]#
Return a GAP function wrapper
This is almost the same as calling
libgap.eval(function_name)
, but faster and makes it obvious in your code that you are wrapping a function.INPUT:
function_name
– string. The name of a GAP function.
OUTPUT:
A function wrapper
GapElement_Function
for the GAP function. Calling it from Sage is equivalent to calling the wrapped function from GAP.EXAMPLES:
sage: libgap.function_factory('Print') <Gap function "Print">
>>> from sage.all import * >>> libgap.function_factory('Print') <Gap function "Print">
- get_global(variable)[source]#
Get a GAP global variable
INPUT:
variable
– string. The variable name.
OUTPUT:
A
GapElement
wrapping the GAP output. AValueError
is raised if there is no such variable in GAP.EXAMPLES:
sage: libgap.set_global('FooBar', 1) sage: libgap.get_global('FooBar') 1 sage: libgap.unset_global('FooBar') sage: libgap.get_global('FooBar') NULL
>>> from sage.all import * >>> libgap.set_global('FooBar', Integer(1)) >>> libgap.get_global('FooBar') 1 >>> libgap.unset_global('FooBar') >>> libgap.get_global('FooBar') NULL
- global_context(variable, value)[source]#
Temporarily change a global variable
INPUT:
variable
– string. The variable name.value
– anything that defines a GAP object.
OUTPUT:
A context manager that sets/reverts the given global variable.
EXAMPLES:
sage: libgap.set_global('FooBar', 1) sage: with libgap.global_context('FooBar', 2): ....: print(libgap.get_global('FooBar')) 2 sage: libgap.get_global('FooBar') 1
>>> from sage.all import * >>> libgap.set_global('FooBar', Integer(1)) >>> with libgap.global_context('FooBar', Integer(2)): ... print(libgap.get_global('FooBar')) 2 >>> libgap.get_global('FooBar') 1
- load_package(pkg)[source]#
If loading fails, raise a
RuntimeError
exception.
- one()[source]#
Return (integer) one in GAP.
EXAMPLES:
sage: libgap.one() 1 sage: parent(_) C library interface to GAP
>>> from sage.all import * >>> libgap.one() 1 >>> parent(_) C library interface to GAP
- set_global(variable, value)[source]#
Set a GAP global variable
INPUT:
variable
– string. The variable name.value
– anything that defines a GAP object.
EXAMPLES:
sage: libgap.set_global('FooBar', 1) sage: libgap.get_global('FooBar') 1 sage: libgap.unset_global('FooBar') sage: libgap.get_global('FooBar') NULL
>>> from sage.all import * >>> libgap.set_global('FooBar', Integer(1)) >>> libgap.get_global('FooBar') 1 >>> libgap.unset_global('FooBar') >>> libgap.get_global('FooBar') NULL
- set_seed(seed=None)[source]#
Reseed the standard GAP pseudo-random sources with the given seed.
Uses a random seed given by
current_randstate().ZZ_seed()
ifseed=None
. Otherwise the seed should be an integer.EXAMPLES:
sage: libgap.set_seed(0) 0 sage: [libgap.Random(1, 10) for i in range(5)] [2, 3, 3, 4, 2]
>>> from sage.all import * >>> libgap.set_seed(Integer(0)) 0 >>> [libgap.Random(Integer(1), Integer(10)) for i in range(Integer(5))] [2, 3, 3, 4, 2]
- show()[source]#
Return statistics about the GAP owned object list
This includes the total memory allocated by GAP as returned by
libgap.eval('TotalMemoryAllocated()'), as well as garbage collection / object count statistics as returned by ``libgap.eval('GasmanStatistics')
, and finally the total number of GAP objects held by Sage asGapElement
instances.The value
livekb + deadkb
will roughly equal the total memory allocated for GAP objects (seelibgap.eval('TotalMemoryAllocated()')
).Note
Slight complication is that we want to do it without accessing libgap objects, so we don’t create new GapElements as a side effect.
EXAMPLES:
sage: a = libgap(123) sage: b = libgap(456) sage: c = libgap(789) sage: del b sage: libgap.collect() sage: libgap.show() # random output {'gasman_stats': {'full': {'cumulative': 110, 'deadbags': 321400, 'deadkb': 12967, 'freekb': 15492, 'livebags': 396645, 'livekb': 37730, 'time': 110, 'totalkb': 65536}, 'nfull': 1, 'npartial': 1}, 'nelements': 23123, 'total_alloc': 3234234}
>>> from sage.all import * >>> a = libgap(Integer(123)) >>> b = libgap(Integer(456)) >>> c = libgap(Integer(789)) >>> del b >>> libgap.collect() >>> libgap.show() # random output {'gasman_stats': {'full': {'cumulative': 110, 'deadbags': 321400, 'deadkb': 12967, 'freekb': 15492, 'livebags': 396645, 'livekb': 37730, 'time': 110, 'totalkb': 65536}, 'nfull': 1, 'npartial': 1}, 'nelements': 23123, 'total_alloc': 3234234}
- unset_global(variable)[source]#
Remove a GAP global variable
INPUT:
variable
– string. The variable name.
EXAMPLES:
sage: libgap.set_global('FooBar', 1) sage: libgap.get_global('FooBar') 1 sage: libgap.unset_global('FooBar') sage: libgap.get_global('FooBar') NULL
>>> from sage.all import * >>> libgap.set_global('FooBar', Integer(1)) >>> libgap.get_global('FooBar') 1 >>> libgap.unset_global('FooBar') >>> libgap.get_global('FooBar') NULL