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)
<type '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

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

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 ]

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(_)
<type '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(_)
<type 'sage.rings.integer.Integer'>

sage: libgap.eval('5/3 + 7*E(3)').sage()
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

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

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)
<type '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])
<type '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 )

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}

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}

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]

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

Bases: sage.structure.parent.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 ] )
Element

alias of sage.libs.gap.element.GapElement

collect()

Manually run the garbage collector

EXAMPLES:

sage: a = libgap(123)
sage: del a
sage: libgap.collect()
count_GAP_objects()

Return the number of GAP objects that are being tracked by GAP.

OUTPUT:

An integer

EXAMPLES:

sage: libgap.count_GAP_objects()   # random output
5
eval(gap_command)

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"
function_factory(function_name)

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">
get_global(variable)

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')
Traceback (most recent call last):
...
GAPError: Error, VAL_GVAR: No value bound to FooBar
global_context(variable, value)

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
load_package(pkg)

If loading fails, raise a RuntimeError exception.

mem()

Return information about GAP memory usage

This method is deprecated and is a no-op. Use Gap.show() to display memory-usage and bag count statistics from GASMAN.

one()

Return (integer) one in GAP.

EXAMPLES:

sage: libgap.one()
1
sage: parent(_)
C library interface to GAP
set_global(variable, value)

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')
Traceback (most recent call last):
...
GAPError: Error, VAL_GVAR: No value bound to FooBar
set_seed(seed=None)

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]
show()

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}
unset_global(variable)

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')
Traceback (most recent call last):
...
GAPError: Error, VAL_GVAR: No value bound to FooBar
zero()

Return (integer) zero in GAP.

OUTPUT:

A GapElement.

EXAMPLES:

sage: libgap.zero()
0