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:

  1. GAP booleans true / false to Sage booleans True / False. The third GAP boolean value fail raises a ValueError.

  2. GAP integers to Sage integers.

  3. GAP rational numbers to Sage rational numbers.

  4. GAP cyclotomic numbers to Sage cyclotomic numbers.

  5. GAP permutations to Sage permutations.

  6. The GAP containers List and rec are converted to Sage containers list and dict. Furthermore, the sage() 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 instantiate Gap 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. A ValueError 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() if seed=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 as GapElement instances.

The value livekb + deadkb will roughly equal the total memory allocated for GAP objects (see libgap.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
zero()[source]#

Return (integer) zero in GAP.

OUTPUT:

A GapElement.

EXAMPLES:

sage: libgap.zero()
0
>>> from sage.all import *
>>> libgap.zero()
0