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
a = libgap(10)
a
type(a)
a*a
timeit('a*a') # random output
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
b = gap('10')
timeit('b*b') # random output; long time
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 ]
libgap.eval('List([1..10], i->i^2)')
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'>
libgap('List([1..10], i->i^2)')
type(_)
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
a.sage()
type(_)
libgap.eval('5/3 + 7*E(3)').sage() # needs sage.rings.number_field
gens_of_group = libgap.AlternatingGroup(4).GeneratorsOfGroup()
generators = gens_of_group.sage()
generators # a Sage list of Sage permutations!
PermutationGroup(generators).cardinality() # computed in Sage
libgap.AlternatingGroup(4).Size() # computed in GAP
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
A4 = groups.permutation.Alternating(4) generators = gens_of_group.sage(parent=A4); generators all(gen.parent() is A4 for gen in generators)
So far, the following GAP data types can be directly converted to the corresponding Sage datatype:
GAP booleans
true/falseto Sage booleansTrue/False. The third GAP boolean valuefailraises 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
Listandrecare converted to Sage containerslistanddict. 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'>
lst = libgap([1,5,7]); lst type(lst) len(lst) lst[0] [ x^2 for x in lst ] type(_[0])
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 )
libgap({'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}
rec = libgap.eval('rec(a:=123, b:=456, Sym3:=SymmetricGroup(3))')
rec['Sym3']
dict(rec)
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}
rec.sage()
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, _) # needs sage.modules
[ 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, _) # needs sage.modules
[ 1 2 9 10]
[ 3 4 11 12]
[ 0 0 5 6]
[ 0 0 7 8]
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
M.List() # returns a GAP List of Lists
M.List().sage() # returns a Sage list of lists
matrix(ZZ, _) # needs sage.modules
Using the GAP C library from Cython¶
We are using the GAP API provided by the GAP project since GAP 4.10.
Calls to the GAP C library (functions declared in libgap-api.h)
should be sandwiched between calls to GAP_Enter() and
GAP_Leave(). These are macros defined in libgap-api.h and must
be used carefully because GAP_Enter() is defined as two function
calls in succession without braces. The first thing that
GAP_Enter() does is a setjmp() which plays an important role
in handling errors. The return value from GAP_Enter() is non-zero
(success) the first time around, and if an error occurs, execution
“jumps” back to GAP_Enter(), this time with a return value of zero
(failure). Due to these quirks, naive attempts to handle the return
value of GAP_Enter() are doomed to fail. The correct pattern to
use is:
try:
GAP_Enter()
# further calls to libgap
finally:
GAP_Leave()
How this works is subtle. When GAP is initialized, we install an
error_handler() callback that GAP invokes on error. This function
sets a python exception using PyErr_Restore(), but so long as we
remain in C, this exception will not actually be raised. When
error_handler() finishes executing, control returns to GAP which
then jumps back to the previous GAP_Enter(). It is at this point
that we need to raise the (already set) exception, to prevent
re-executing the code that caused an error. To facilitate this,
GAP_Enter() is wrapped by Cython, and the wrapper is qualified
with except 0. This tells Cython to treat a return value of zero
as an error, and raise an exception if an exception is set. (One will
be set if there was an error because our error_handler() sets
it). Here is a real example:
cpdef void crash_and_burn() except *:
x = libgap({'a': 1, 'b': 2})
cdef unsigned int xlen
try:
GAP_Enter()
xlen = GAP_LenList((<GapElement>x).value)
finally:
GAP_Leave()
print(xlen)
The call to GAP_LenList() is an error in this case, because
x.value is a GAP record, not a GAP list. In any case, what happens
is,
We call the
GAP_Enter()Cython wrapper, which invokes the macro, and additionally generates some C code to raise an exception if that return value is zero (error). But this is the first pass, so for now the macro returns a non-zero (success) value.We call
GAP_LenList(x.value), which is an error.GAP invokes our
error_handler(), which creates asage.libs.gap.util.GAPError, and sets it active.Control returns to GAP.
GAP jumps back to
GAP_Enter().The error branch of
GAP_Enter()is executed. In other words we proceed fromGAP_Enter()as if it returned zero (error).An exception is raised, because the
except 0qualifier on the Cython wrapper forGAP_Enter()specifically checks for zero and raises any exceptions in that case.Finally,
GAP_Leave()is called to clean up. In a more realistic example where failure is not guaranteed, this would also have been run to clean up if no errors were raised.
Another unusual aspect of the libgap interface is its signal
handling. Typically, cysignals’ sig_on() and sig_off()
functions are used to wrap code that may take a long time, and as a
result, may need to be interrupted with Ctrl-C. However, it is
possible that interrupting a function execution at an arbitrary
location will lead to inconsistent state. Internally, GAP provides a
mechanism using InterruptExecStat, which sets a flag that tells
GAP to gracefully exit with an error as early as possible. We make use
of this internal mechanism to prevent segmentation faults when GAP
functions are interrupted.
Specifically, we install GAP’s own SIGINT handler (to catch
Ctrl-C) before executing any long-running GAP code, and then later
reinstall the original handler when the GAP code has finished. This is
accomplished using the suggestively-named gap_sig_on() and
gap_sig_off() functions. After you have called gap_sig_on(),
if GAP receives Ctrl-C, it will invoke our custom error_handler()
that will set a KeyboardInterrupt containing the phrase “user
interrupt”. Eventually (as explained in the preceding paragraphs),
control will jump back to the Cython wrapper for GAP_Enter(), and
this exception will be raised.
The safest pattern to use for interruptible libgap code is:
try:
gap_sig_on()
GAP_Enter()
# further calls to libgap
finally:
GAP_Leave()
gap_sig_off()
Before you attempt to change any of this, please make sure that you understand the issues that it is intended to fix, e.g.
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:
ParentThe libgap interpreter object.
Note
This object must be instantiated exactly once by the libgap. Always use the provided
libgapinstance, and never instantiateGapmanually.EXAMPLES:
sage: libgap.eval('SymmetricGroup(4)') Sym( [ 1 .. 4 ] )
>>> from sage.all import * >>> libgap.eval('SymmetricGroup(4)') Sym( [ 1 .. 4 ] )
libgap.eval('SymmetricGroup(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()
a = libgap(123) del a libgap.collect()
- count_GAP_objects()[source]¶
Return the number of GAP objects that are being tracked by GAP.
OUTPUT: integer
EXAMPLES:
sage: libgap.count_GAP_objects() # random output 5
>>> from sage.all import * >>> libgap.count_GAP_objects() # random output 5
libgap.count_GAP_objects() # random output
- eval(gap_command)[source]¶
Evaluate a gap command and wrap the result.
INPUT:
gap_command– string containing a valid gap command without the trailing semicolon
OUTPUT: a
GapElementEXAMPLES:
sage: libgap.eval('0') 0 sage: libgap.eval('"string"') "string"
>>> from sage.all import * >>> libgap.eval('0') 0 >>> libgap.eval('"string"') "string"
libgap.eval('0') libgap.eval('"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_Functionfor 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">
libgap.function_factory('Print')
- get_global(variable)[source]¶
Get a GAP global variable.
INPUT:
variable– string; the variable name
OUTPUT:
A
GapElementwrapping the GAP output. AValueErroris 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
libgap.set_global('FooBar', 1) libgap.get_global('FooBar') libgap.unset_global('FooBar') libgap.get_global('FooBar')
- global_context(variable, value)[source]¶
Temporarily change a global variable.
INPUT:
variable– string; the variable namevalue– 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
libgap.set_global('FooBar', 1) with libgap.global_context('FooBar', 2): print(libgap.get_global('FooBar')) libgap.get_global('FooBar')
- load_package(pkg)[source]¶
If loading fails, raise a
RuntimeErrorexception.
- 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
libgap.one() parent(_)
- set_global(variable, value)[source]¶
Set a GAP global variable.
INPUT:
variable– string; the variable namevalue– 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
libgap.set_global('FooBar', 1) libgap.get_global('FooBar') libgap.unset_global('FooBar') libgap.get_global('FooBar')
- 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]
libgap.set_seed(0) [libgap.Random(1, 10) for i in range(5)]
- 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 asGapElementinstances.The value
livekb + deadkbwill 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}
a = libgap(123) b = libgap(456) c = libgap(789) del b libgap.collect() libgap.show() # random output
- 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
libgap.set_global('FooBar', 1) libgap.get_global('FooBar') libgap.unset_global('FooBar') libgap.get_global('FooBar')