Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagesmc
Path: blob/master/src/sage/libs/gap/libgap.pyx
8815 views
1
"""
2
libGAP shared library Interface to GAP
3
4
This module implements a fast C library interface to GAP. To use
5
libGAP you simply call ``libgap`` (the parent of all
6
:class:`~sage.libs.gap.element.GapElement` instances) and use it to
7
convert Sage objects into GAP objects.
8
9
EXAMPLES::
10
11
sage: a = libgap(10)
12
sage: a
13
10
14
sage: type(a)
15
<type 'sage.libs.gap.element.GapElement_Integer'>
16
sage: a*a
17
100
18
sage: timeit('a*a') # random output
19
625 loops, best of 3: 898 ns per loop
20
21
Compared to the expect interface this is >1000 times faster::
22
23
sage: b = gap('10')
24
sage: timeit('b*b') # random output; long time
25
125 loops, best of 3: 2.05 ms per loop
26
27
If you want to evaluate GAP commands, use the :meth:`Gap.eval` method::
28
29
sage: libgap.eval('List([1..10], i->i^2)')
30
[ 1, 4, 9, 16, 25, 36, 49, 64, 81, 100 ]
31
32
not to be confused with the ``libgap`` call, which converts Sage
33
objects to GAP objects, for example strings to strings::
34
35
sage: libgap('List([1..10], i->i^2)')
36
"List([1..10], i->i^2)"
37
sage: type(_)
38
<type 'sage.libs.gap.element.GapElement_String'>
39
40
You can usually use the :meth:`~sage.libs.gap.element.GapElement.sage`
41
method to convert the resulting GAP element back to its Sage
42
equivalent::
43
44
sage: a.sage()
45
10
46
sage: type(_)
47
<type 'sage.rings.integer.Integer'>
48
49
sage: libgap.eval('5/3 + 7*E(3)').sage()
50
7*zeta3 + 5/3
51
52
sage: generators = libgap.AlternatingGroup(4).GeneratorsOfGroup().sage()
53
sage: generators # a Sage list of Sage permutations!
54
[(1,2,3), (2,3,4)]
55
sage: PermutationGroup(generators).cardinality() # computed in Sage
56
12
57
sage: libgap.AlternatingGroup(4).Size() # computed in GAP
58
12
59
60
So far, the following GAP data types can be directly converted to the
61
corresponding Sage datatype:
62
63
#. GAP booleans ``true`` / ``false`` to Sage booleans ``True`` /
64
``False``. The third GAP boolean value ``fail`` raises a
65
``ValueError``.
66
67
#. GAP integers to Sage integers.
68
69
#. GAP rational numbers to Sage rational numbers.
70
71
#. GAP cyclotomic numbers to Sage cyclotomic numbers.
72
73
#. GAP permutations to Sage permutations.
74
75
#. The GAP containers ``List`` and ``rec`` are converted to Sage
76
containers ``list`` and ``dict``. Furthermore, the
77
:meth:`~sage.libs.gap.element.GapElement.sage` method is applied
78
recursively to the entries.
79
80
Special support is available for the GAP container classes. GAP lists
81
can be used as follows::
82
83
sage: lst = libgap([1,5,7]); lst
84
[ 1, 5, 7 ]
85
sage: type(lst)
86
<type 'sage.libs.gap.element.GapElement_List'>
87
sage: len(lst)
88
3
89
sage: lst[0]
90
1
91
sage: [ x^2 for x in lst ]
92
[1, 25, 49]
93
sage: type(_[0])
94
<type 'sage.libs.gap.element.GapElement_Integer'>
95
96
Note that you can access the elements of GAP ``List`` objects as you
97
would expect from Python (with indexing starting at 0), but the
98
elements are still of type
99
:class:`~sage.libs.gap.element.GapElement`. The other GAP container
100
type are records, which are similar to Python dictionaries. You can
101
construct them directly from Python dictionaries::
102
103
sage: libgap({'a':123, 'b':456})
104
rec( a := 123, b := 456 )
105
106
Or get them as results of computations::
107
108
sage: rec = libgap.eval('rec(a:=123, b:=456, Sym3:=SymmetricGroup(3))')
109
sage: rec['Sym3']
110
Sym( [ 1 .. 3 ] )
111
sage: dict(rec)
112
{'a': 123, 'Sym3': Sym( [ 1 .. 3 ] ), 'b': 456}
113
114
The output is a Sage dictionary whose keys are Sage strings and whose
115
Values are instances of :meth:`~sage.libs.gap.element.GapElement`. So,
116
for example, ``rec['a']`` is not a Sage integer. To recursively
117
convert the entries into Sage objects, you should use the
118
:meth:`~sage.libs.gap.element.GapElement.sage` method::
119
120
sage: rec.sage()
121
{'a': 123,
122
'Sym3': NotImplementedError('cannot construct equivalent Sage object',),
123
'b': 456}
124
125
Now ``rec['a']`` is a Sage integer. We have not implemented the
126
conversion of the GAP symmetric group to the Sage symmetric group yet,
127
so you end up with a ``NotImplementedError`` exception object. The
128
exception is returned and not raised so that you can work with the
129
partial result.
130
131
While we don't directly support matrices yet, you can convert them to
132
Gap List of Lists. These lists are then easily converted into Sage
133
using the recursive expansion of the
134
:meth:`~sage.libs.gap.element.GapElement.sage` method::
135
136
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)')
137
sage: M
138
<block matrix of dimensions (2*2)x(2*2)>
139
sage: M.List() # returns a GAP List of Lists
140
[ [ 1, 2, 9, 10 ], [ 3, 4, 11, 12 ], [ 0, 0, 5, 6 ], [ 0, 0, 7, 8 ] ]
141
sage: M.List().sage() # returns a Sage list of lists
142
[[1, 2, 9, 10], [3, 4, 11, 12], [0, 0, 5, 6], [0, 0, 7, 8]]
143
sage: matrix(ZZ, _)
144
[ 1 2 9 10]
145
[ 3 4 11 12]
146
[ 0 0 5 6]
147
[ 0 0 7 8]
148
149
150
Using the libGAP C library from Cython
151
======================================
152
153
The lower-case ``libgap_foobar`` functions are ones that we added to
154
make the libGAP C shared library. The ``libGAP_foobar`` methods are
155
the original GAP methods simply prefixed with the string
156
``libGAP_``. The latter were originally not designed to be in a
157
library, so some care needs to be taken to call them.
158
159
In particular, you must call ``libgap_mark_stack_bottom()`` in every
160
function that calls into the libGAP C functions. The reason is that
161
the GAP memory manager will automatically keep objects alive that are
162
referenced in local (stack-allocated) variables. While convenient,
163
this requires to look through the stack to find anything that looks
164
like an address to a memory bag. But this requires vigilance against
165
the following pattern::
166
167
cdef f()
168
libgap_mark_stack_bottom()
169
libGAP_function()
170
171
cdef g()
172
libgap_mark_stack_bottom();
173
f() # f() changed the stack bottom marker
174
libGAP_function() # boom
175
176
The solution is to re-order ``g()`` to first call ``f()``. In order to
177
catch this error, it is recommended that you wrap calls into libGAP in
178
``libgap_enter`` / ``libgap_exit`` blocks and not call
179
``libgap_mark_stack_bottom`` manually. So instead, always write
180
181
cdef f()
182
libgap_enter()
183
libGAP_function()
184
libgap_exit()
185
186
cdef g()
187
f()
188
libgap_enter()
189
libGAP_function()
190
libgap_exit()
191
192
If you accidentally call ``libgap_enter()`` twice then an error
193
message is printed to help you debug this::
194
195
sage: from sage.libs.gap.util import error_enter_libgap_block_twice
196
sage: error_enter_libgap_block_twice()
197
Traceback (most recent call last):
198
...
199
RuntimeError: Entered a critical block twice
200
201
AUTHORS:
202
203
- William Stein, Robert Miller (2009-06-23): first version
204
- Volker Braun, Dmitrii Pasechnik, Ivan Andrus (2011-03-25, Sage Days 29):
205
almost complete rewrite; first usable version.
206
- Volker Braun (2012-08-28, GAP/Singular workshop): update to
207
gap-4.5.5, make it ready for public consumption.
208
"""
209
210
###############################################################################
211
# Copyright (C) 2009, William Stein <[email protected]>
212
# Copyright (C) 2012, Volker Braun <[email protected]>
213
#
214
# Distributed under the terms of the GNU General Public License (GPL)
215
# as published by the Free Software Foundation; either version 2 of
216
# the License, or (at your option) any later version.
217
# http://www.gnu.org/licenses/
218
###############################################################################
219
220
221
##############################################################################
222
#
223
# If you want to add support for converting some GAP datatype to its
224
# Sage equivalent, you have two options. Either
225
#
226
# 1. add an if-clause to GapElement.sage(). This is the easiest
227
# option and you should probably start with it first
228
#
229
# 2. Subclass GapElement to GapElement_Mydatatype. In that case you
230
# need to write the derived class, a factory function to
231
# instantiate it (GapElement cannot be instantiated by __init__),
232
# and add an if-clause in make_GapElement. See GapElement_List,
233
# for example. The advantage of this more complicated approach is
234
# that you can then add extra methods to your data type. For
235
# example, GapElement_List instances can access the individual
236
# elements with the usual gapelement[i] syntax.
237
#
238
# TODO
239
#
240
# Not all GAP types have their own GapElement. We should wrap more
241
# stuff. Talk to me (Volker) if you want to work on that.
242
#
243
##############################################################################
244
245
from gap_includes cimport *
246
247
from sage.structure.sage_object cimport SageObject
248
from sage.structure.parent cimport Parent
249
from sage.structure.element cimport ModuleElement, RingElement
250
from sage.rings.all import ZZ
251
from sage.misc.cachefunc import cached_method
252
from sage.libs.gap.element cimport *
253
254
255
############################################################################
256
### Debugging ##############################################################
257
############################################################################
258
259
cdef void report(libGAP_Obj bag):
260
print libGAP_TNAM_OBJ(bag), <int>libGAP_TNUM_BAG(bag), <int>libGAP_SIZE_BAG(bag)
261
262
263
cdef void print_gasman_objects():
264
libgap_enter()
265
libGAP_CallbackForAllBags(report)
266
libgap_exit()
267
268
269
270
271
from sage.misc.lazy_import import is_during_startup
272
if is_during_startup():
273
import sys, traceback
274
print 'Importing libgap during startup!'
275
traceback.print_stack(None, None, sys.stdout)
276
277
278
############################################################################
279
### Gap ###################################################################
280
############################################################################
281
# The libGap interpreter object Gap is the parent of the GapElements
282
283
284
class Gap(Parent):
285
r"""
286
The libgap interpreter object.
287
288
.. NOTE::
289
290
This object must be instantiated exactly once by the
291
libgap. Always use the provided ``libgap`` instance, and never
292
instantiate :class:`Gap` manually.
293
294
EXAMPLES::
295
296
sage: libgap.eval('SymmetricGroup(4)')
297
Sym( [ 1 .. 4 ] )
298
299
TESTS::
300
301
sage: TestSuite(libgap).run(skip=['_test_category', '_test_elements', '_test_pickling'])
302
"""
303
304
Element = GapElement
305
306
def _coerce_map_from_(self, S):
307
"""
308
Whether a coercion from `S` exists.
309
310
INPUT / OUTPUT:
311
312
See :mod:`sage.structure.parent`.
313
314
EXAMPLES::
315
316
sage: libgap.has_coerce_map_from(ZZ)
317
True
318
sage: libgap.has_coerce_map_from(CyclotomicField(5))
319
True
320
"""
321
from sage.rings.all import ZZ, QQ, is_CyclotomicField
322
if S in (ZZ, QQ) or is_CyclotomicField(S):
323
return True
324
325
def _element_constructor_(self, x):
326
r"""
327
Construct elements of this parent class.
328
329
INPUT:
330
331
- ``x`` -- anything that defines a GAP object.
332
333
OUTPUT:
334
335
A :class:`GapElement`.
336
337
EXAMPLES::
338
339
sage: libgap(0) # indirect doctest
340
0
341
sage: libgap(ZZ(0))
342
0
343
sage: libgap(int(0))
344
0
345
"""
346
if isinstance(x, GapElement):
347
return x
348
elif isinstance(x, (list, tuple)):
349
return make_GapElement_List(self, make_gap_list(x))
350
elif isinstance(x, dict):
351
return make_GapElement_Record(self, make_gap_record(x))
352
elif isinstance(x, bool):
353
# attention: must come before int
354
return make_GapElement_Boolean(self, libGAP_True if x else libGAP_False)
355
elif isinstance(x, int):
356
return make_GapElement_Integer(self, make_gap_integer(x))
357
elif isinstance(x, basestring):
358
return make_GapElement_String(self, make_gap_string(x))
359
else:
360
try:
361
return x._libgap_()
362
except AttributeError:
363
pass
364
x = str(x._gap_init_())
365
return make_any_gap_element(self, gap_eval(x))
366
raise ValueError('cannot represent '+str(x)+' as a GAP object')
367
368
def _construct_matrix(self, M):
369
"""
370
Construct a LibGAP matrix.
371
372
INPUT:
373
374
- ``M`` -- a matrix.
375
376
OUTPUT:
377
378
A GAP matrix, that is, a list of lists with entries over a
379
common ring.
380
381
EXAMPLES::
382
383
sage: libgap._construct_matrix(identity_matrix(ZZ,2))
384
[ [ 1, 0 ], [ 0, 1 ] ]
385
sage: libgap(identity_matrix(ZZ,2)) # syntactic sugar
386
[ [ 1, 0 ], [ 0, 1 ] ]
387
sage: libgap(matrix(GF(3),2,2,[4,5,6,7]))
388
[ [ Z(3)^0, Z(3) ], [ 0*Z(3), Z(3)^0 ] ]
389
"""
390
ring = M.base_ring()
391
try:
392
gap_ring = self(ring)
393
except ValueError:
394
raise TypeError('base ring is not supported by GAP')
395
M_list = map(list, M.rows())
396
return make_GapElement_List(self, make_gap_list(M_list))
397
398
def eval(self, gap_command):
399
"""
400
Evaluate a gap command and wrap the result.
401
402
INPUT:
403
404
- ``gap_command`` -- a string containing a valid gap command
405
without the trailing semicolon.
406
407
OUTPUT:
408
409
A :class:`GapElement`.
410
411
EXAMPLES::
412
413
sage: libgap.eval('0')
414
0
415
sage: libgap.eval('"string"')
416
"string"
417
"""
418
if not isinstance(gap_command, basestring):
419
gap_command = str(gap_command._gap_init_())
420
return make_any_gap_element(self, gap_eval(gap_command))
421
422
@cached_method
423
def function_factory(self, function_name):
424
"""
425
Return a GAP function wrapper
426
427
This is almost the same as calling
428
``libgap.eval(function_name)``, but faster and makes it
429
obvious in your code that you are wrapping a function.
430
431
INPUT:
432
433
- ``function_name`` -- string. The name of a GAP function.
434
435
OUTPUT:
436
437
A function wrapper
438
:class:`~sage.libs.gap.element.GapElement_Function` for the
439
GAP function. Calling it from Sage is equivalent to calling
440
the wrapped function from GAP.
441
442
EXAMPLES::
443
444
sage: libgap.function_factory('Print')
445
<Gap function "Print">
446
"""
447
return make_GapElement_Function(self, gap_eval(function_name))
448
449
def set_global(self, variable, value):
450
"""
451
Set a GAP global variable
452
453
INPUT:
454
455
- ``variable`` -- string. The variable name.
456
457
- ``value`` -- anything that defines a GAP object.
458
459
EXAMPLES::
460
461
sage: libgap.set_global('FooBar', 1)
462
sage: libgap.get_global('FooBar')
463
1
464
sage: libgap.unset_global('FooBar')
465
sage: libgap.get_global('FooBar')
466
Traceback (most recent call last):
467
...
468
ValueError: libGAP: Error, VAL_GVAR: No value bound to FooBar
469
"""
470
is_bound = self.function_factory('IsBoundGlobal')
471
bind_global = self.function_factory('BindGlobal')
472
if is_bound(variable):
473
self.unset_global(variable)
474
bind_global(variable, value)
475
476
def unset_global(self, variable):
477
"""
478
Remove a GAP global variable
479
480
INPUT:
481
482
- ``variable`` -- string. The variable name.
483
484
EXAMPLES::
485
486
sage: libgap.set_global('FooBar', 1)
487
sage: libgap.get_global('FooBar')
488
1
489
sage: libgap.unset_global('FooBar')
490
sage: libgap.get_global('FooBar')
491
Traceback (most recent call last):
492
...
493
ValueError: libGAP: Error, VAL_GVAR: No value bound to FooBar
494
"""
495
make_readwrite = self.function_factory('MakeReadWriteGlobal')
496
unbind_global = self.function_factory('UnbindGlobal')
497
make_readwrite(variable)
498
unbind_global(variable)
499
500
def get_global(self, variable):
501
"""
502
Get a GAP global variable
503
504
INPUT:
505
506
- ``variable`` -- string. The variable name.
507
508
OUTPUT:
509
510
A :class:`~sage.libs.gap.element.GapElement` wrapping the GAP
511
output. A ``ValueError`` is raised if there is no such
512
variable in GAP.
513
514
EXAMPLES::
515
516
sage: libgap.set_global('FooBar', 1)
517
sage: libgap.get_global('FooBar')
518
1
519
sage: libgap.unset_global('FooBar')
520
sage: libgap.get_global('FooBar')
521
Traceback (most recent call last):
522
...
523
ValueError: libGAP: Error, VAL_GVAR: No value bound to FooBar
524
"""
525
value_global = self.function_factory('ValueGlobal')
526
return value_global(variable)
527
528
def global_context(self, variable, value):
529
"""
530
Temporarily change a global variable
531
532
INPUT:
533
534
- ``variable`` -- string. The variable name.
535
536
- ``value`` -- anything that defines a GAP object.
537
538
OUTPUT:
539
540
A context manager that sets/reverts the given global variable.
541
542
EXAMPLES::
543
544
sage: libgap.set_global('FooBar', 1)
545
sage: with libgap.global_context('FooBar', 2):
546
....: print libgap.get_global('FooBar')
547
2
548
sage: libgap.get_global('FooBar')
549
1
550
"""
551
from sage.libs.gap.context_managers import GlobalVariableContext
552
return GlobalVariableContext(variable, value)
553
554
def _an_element_(self):
555
r"""
556
Return a :class:`GapElement`.
557
558
OUTPUT:
559
560
A :class:`GapElement`.
561
562
EXAMPLES::
563
564
sage: libgap.an_element() # indirect doctest
565
0
566
"""
567
return self(0)
568
569
def zero_element(self):
570
"""
571
Return (integer) zero in GAP.
572
573
OUTPUT:
574
575
A :class:`GapElement`.
576
577
EXAMPLES::
578
579
sage: libgap.zero_element()
580
0
581
"""
582
return self(0)
583
584
585
def __init__(self):
586
r"""
587
The Python constructor.
588
589
EXAMPLES::
590
591
sage: type(libgap)
592
<type 'sage.misc.lazy_import.LazyImport'>
593
sage: type(libgap._get_object())
594
<class 'sage.libs.gap.libgap.Gap'>
595
"""
596
initialize()
597
libgap_set_gasman_callback(gasman_callback)
598
from sage.rings.integer_ring import ZZ
599
Parent.__init__(self, base=ZZ)
600
601
602
def __repr__(self):
603
r"""
604
Return a string representation of ``self``.
605
606
OUTPUT:
607
608
String.
609
610
EXAMPLES::
611
612
sage: libgap
613
C library interface to GAP
614
"""
615
return 'C library interface to GAP'
616
617
618
def trait_names(self):
619
"""
620
Return all Gap function names.
621
622
OUTPUT:
623
624
A list of strings.
625
626
EXAMPLES::
627
628
sage: len(libgap.trait_names()) > 1000
629
True
630
"""
631
import gap_functions
632
return gap_functions.common_gap_functions
633
634
635
def __getattr__(self, name):
636
r"""
637
The attributes of the Gap object are the Gap functions.
638
639
INPUT:
640
641
- ``name`` -- string. The name of the GAP function you want to
642
call.
643
644
OUTPUT:
645
646
A :class:`GapElement_Function`. A ``AttributeError`` is raised
647
if there is no such function.
648
649
EXAMPLES::
650
651
sage: libgap.List
652
<Gap function "List">
653
"""
654
if name in self.trait_names():
655
f = make_GapElement_Function(self, gap_eval(str(name)))
656
assert f.is_function()
657
self.__dict__[name] = f
658
return f
659
else:
660
raise AttributeError, 'No such attribute: '+name+'.'
661
662
663
def show(self):
664
"""
665
Print statistics about the GAP owned object list
666
667
Slight complication is that we want to do it without accessing
668
libgap objects, so we don't create new GapElements as a side
669
effect.
670
671
EXAMPLES::
672
673
sage: a = libgap(123)
674
sage: b = libgap(456)
675
sage: c = libgap(789)
676
sage: del b
677
sage: libgap.show() # random output
678
11 LibGAP elements currently alive
679
rec( full := rec( cumulative := 122, deadbags := 9,
680
deadkb := 0, freekb := 7785, livebags := 304915,
681
livekb := 47367, time := 33, totalkb := 68608 ),
682
nfull := 3, npartial := 14 )
683
"""
684
print self.count_GAP_objects(), 'LibGAP elements currently alive'
685
print self.eval('GasmanStatistics()')
686
# print_gasman_objects()
687
688
689
def count_GAP_objects(self):
690
"""
691
Return the number of GAP objects that are being tracked by
692
libGAP
693
694
OUTPUT:
695
696
An integer
697
698
EXAMPLES::
699
700
sage: libgap.count_GAP_objects() # random output
701
5
702
"""
703
return sum([1 for obj in get_owned_objects()])
704
705
706
def mem(self):
707
"""
708
Return information about libGAP memory usage
709
710
The GAP workspace is partitioned into 5 pieces (see gasman.c
711
in the GAP sources for more details):
712
713
* The **masterpointer area** contains all the masterpointers of the bags.
714
715
* The **old bags area** contains the bodies of all the bags that survived at
716
least one garbage collection. This area is only scanned for dead bags
717
during a full garbage collection.
718
719
* The **young bags area** contains the bodies of all the bags that have been
720
allocated since the last garbage collection. This area is scanned for
721
dead bags during each garbage collection.
722
723
* The **allocation area** is the storage that is available for allocation of
724
new bags. When a new bag is allocated the storage for the body is taken
725
from the beginning of this area, and this area is correspondingly
726
reduced. If the body does not fit in the allocation area a garbage
727
collection is performed.
728
729
* The **unavailable area** is the free storage that is not available for
730
allocation.
731
732
OUTPUT:
733
734
This function returns a tuple containing 5 integers. Each is
735
the size (in bytes) of the five partitions of the
736
workspace. This will potentially change after each GAP garbage
737
collection.
738
739
EXAMPLES::
740
741
sage: libgap.collect()
742
sage: libgap.mem() # random output
743
(1048576, 6706782, 0, 960930, 0)
744
745
sage: libgap.FreeGroup(3)
746
<free group on the generators [ f1, f2, f3 ]>
747
sage: libgap.mem() # random output
748
(1048576, 6706782, 47571, 913359, 0)
749
750
sage: libgap.collect()
751
sage: libgap.mem() # random output
752
(1048576, 6734785, 0, 998463, 0)
753
"""
754
return memory_usage()
755
756
757
def collect(self):
758
"""
759
Manually run the garbage collector
760
761
EXAMPLES::
762
763
sage: a = libgap(123)
764
sage: del a
765
sage: libgap.collect()
766
"""
767
libgap_enter()
768
rc = libGAP_CollectBags(0,1)
769
libgap_exit()
770
if rc != 1:
771
raise RuntimeError('Garbage collection failed.')
772
773
774
775
libgap = Gap()
776
777
778