Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/interfaces/gap.py
4045 views
1
r"""
2
Interface to GAP
3
4
Sage provides an interface to the GAP system. This system provides
5
extensive group theory, combinatorics, etc.
6
7
The GAP interface will only work if GAP is installed on your
8
computer; this should be the case, since GAP is included with Sage.
9
The interface offers three pieces of functionality:
10
11
12
#. ``gap_console()`` - A function that dumps you into
13
an interactive command-line GAP session.
14
15
#. ``gap(expr)`` - Evaluation of arbitrary GAP
16
expressions, with the result returned as a string.
17
18
#. ``gap.new(expr)`` - Creation of a Sage object that
19
wraps a AP object. This provides a Pythonic interface to GAP. For
20
example, if ``f=gap.new(10)``, then
21
``f.Factors()`` returns the prime factorization of
22
`10` computed using GAP.
23
24
25
First Examples
26
--------------
27
28
We factor an integer using GAP::
29
30
sage: n = gap(20062006); n
31
20062006
32
sage: n.parent()
33
Gap
34
sage: fac = n.Factors(); fac
35
[ 2, 17, 59, 73, 137 ]
36
sage: fac.parent()
37
Gap
38
sage: fac[1]
39
2
40
41
GAP and Singular
42
----------------
43
44
This example illustrates conversion between Singular and GAP via
45
Sage as an intermediate step. First we create and factor a Singular
46
polynomial.
47
48
::
49
50
sage: singular(389)
51
389
52
sage: R1 = singular.ring(0, '(x,y)', 'dp')
53
sage: f = singular('9*x^16-18*x^13*y^2-9*x^12*y^3+9*x^10*y^4-18*x^11*y^2+36*x^8*y^4+18*x^7*y^5-18*x^5*y^6+9*x^6*y^4-18*x^3*y^6-9*x^2*y^7+9*y^8')
54
sage: F = f.factorize()
55
sage: print F
56
[1]:
57
_[1]=9
58
_[2]=x^6-2*x^3*y^2-x^2*y^3+y^4
59
_[3]=-x^5+y^2
60
[2]:
61
1,1,2
62
63
Next we convert the factor `-x^5+y^2` to a Sage
64
multivariate polynomial. Note that it is important to let
65
`x` and `y` be the generators of a polynomial ring,
66
so the eval command works.
67
68
::
69
70
sage: R.<x,y> = PolynomialRing(QQ,2)
71
sage: s = F[1][3].sage_polystring(); s
72
'-x**5+y**2'
73
sage: g = eval(s); g
74
-x^5 + y^2
75
76
Next we create a polynomial ring in GAP and obtain its
77
indeterminates::
78
79
sage: R = gap.PolynomialRing('Rationals', 2); R
80
PolynomialRing( Rationals, ["x_1", "x_2"] )
81
sage: I = R.IndeterminatesOfPolynomialRing(); I
82
[ x_1, x_2 ]
83
84
In order to eval `g` in GAP, we need to tell GAP to view
85
the variables ``x0`` and ``x1`` as the two
86
generators of `R`. This is the one tricky part. In the GAP
87
interpreter the object ``I`` has its own name (which
88
isn't ``I``). We can access its name using
89
``I.name()``.
90
91
::
92
93
sage: _ = gap.eval("x := %s[1];; y := %s[2];;"%(I.name(), I.name()))
94
95
Now `x_0` and `x_1` are defined, so we can
96
construct the GAP polynomial `f` corresponding to
97
`g`::
98
99
sage: R.<x,y> = PolynomialRing(QQ,2)
100
sage: f = gap(str(g)); f
101
-x_1^5+x_2^2
102
103
We can call GAP functions on `f`. For example, we evaluate
104
the GAP ``Value`` function, which evaluates `f`
105
at the point `(1,2)`.
106
107
::
108
109
sage: f.Value(I, [1,2])
110
3
111
sage: g(1,2) # agrees
112
3
113
114
Saving and loading objects
115
--------------------------
116
117
Saving and loading GAP objects (using the dumps method, etc.) is
118
*not* supported, since the output string representation of Gap
119
objects is sometimes not valid input to GAP. Creating classes that
120
wrap GAP objects *is* supported, via simply defining the a
121
_gap_init_ member function that returns a string that when
122
evaluated in GAP constructs the object. See
123
``groups/permutation_group.py`` for a nontrivial
124
example of this.
125
126
Long Input
127
----------
128
129
The GAP interface reads in even very long input (using files) in a
130
robust manner, as long as you are creating a new object.
131
132
.. note::
133
134
Using ``gap.eval`` for long input is much less robust, and is not
135
recommended.
136
137
::
138
139
sage: t = '"%s"'%10^10000 # ten thousand character string.
140
sage: a = gap(t)
141
142
Changing which GAP is used
143
--------------------------
144
145
Use this code to change which GAP interpreter is run. E.g.,
146
147
::
148
149
import sage.interfaces.gap
150
sage.interfaces.gap.gap_cmd = "/usr/local/bin/gap"
151
152
AUTHORS:
153
154
- David Joyner and William Stein: initial version(s)
155
156
- William Stein (2006-02-01): modified gap_console command so it uses
157
exactly the same startup command as Gap.__init__.
158
159
- William Stein (2006-03-02): added tab completions: gap.[tab], x =
160
gap(...), x.[tab], and docs, e.g., gap.function? and x.function?
161
"""
162
163
#*****************************************************************************
164
# Copyright (C) 2005 William Stein <[email protected]>
165
#
166
# Distributed under the terms of the GNU General Public License (GPL)
167
#
168
# This code is distributed in the hope that it will be useful,
169
# but WITHOUT ANY WARRANTY; without even the implied warranty of
170
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
171
# General Public License for more details.
172
#
173
# The full text of the GPL is available at:
174
#
175
# http://www.gnu.org/licenses/
176
#*****************************************************************************
177
178
import expect
179
from expect import Expect, ExpectElement, FunctionElement, ExpectFunction
180
from sage.misc.misc import SAGE_ROOT, SAGE_DATA, DOT_SAGE, is_64_bit, is_in_string
181
from IPython.genutils import page
182
import re
183
import os
184
import pexpect
185
import time
186
187
WORKSPACE = "%s/gap/workspace-%s"%(DOT_SAGE, abs(hash(SAGE_ROOT)))
188
189
GAP_STAMP = '%s/local/bin/gap_stamp'%SAGE_ROOT
190
if not os.path.exists(GAP_STAMP):
191
open(GAP_STAMP,'w').close()
192
193
GAP_DIR = '%s/gap/'%DOT_SAGE
194
195
first_try = True
196
197
try:
198
os.makedirs(GAP_DIR)
199
open('%s/gap/README.txt'%DOT_SAGE, 'w').write("It is OK to delete all these cache files. They will be recreated as needed.")
200
except OSError:
201
if not os.path.isdir(GAP_DIR):
202
raise
203
204
gap_cmd = "gap -r"
205
206
def gap_command(use_workspace_cache=True, local=True):
207
if use_workspace_cache:
208
if local:
209
return "%s -L %s"%(gap_cmd, WORKSPACE), False
210
else:
211
# TO DO: Use remote workspace
212
return gap_cmd, False
213
else:
214
return gap_cmd, True
215
216
############ Classes with methods for both the GAP3 and GAP4 interface
217
218
class Gap_generic(Expect):
219
r"""
220
Generic interface to the GAP3/GAP4 interpreters.
221
222
AUTHORS:
223
224
- William Stein and David Joyner (interface for GAP4)
225
226
- Franco Saliola (Feb 2010): refactored to separate out the generic
227
code
228
229
"""
230
def _assign_symbol(self):
231
r"""
232
Return the assign symbol in GAP.
233
234
TESTS::
235
236
sage: gap = Gap()
237
sage: print gap._assign_symbol()
238
:=
239
240
"""
241
return ":="
242
243
def _quit_string(self):
244
"""
245
Returns the string used to quit GAP.
246
247
EXAMPLES::
248
249
sage: gap._quit_string()
250
'quit'
251
252
::
253
254
sage: g = Gap()
255
sage: a = g(2); g.is_running()
256
True
257
sage: g.quit()
258
sage: g.is_running()
259
False
260
"""
261
return 'quit'
262
263
def _read_in_file_command(self, filename):
264
r"""
265
Returns the command use to read in a file in GAP.
266
267
EXAMPLES::
268
269
sage: gap._read_in_file_command('test')
270
'Read("test");'
271
272
::
273
274
sage: filename = tmp_filename()
275
sage: f = open(filename, 'w')
276
sage: f.write('xx := 22;\n')
277
sage: f.close()
278
sage: gap.read(filename)
279
sage: gap.get('xx').strip()
280
'22'
281
"""
282
return 'Read("%s");'%filename
283
284
def _continuation_prompt(self):
285
"""
286
Returns the continuation prompt in GAP.
287
288
EXAMPLES::
289
290
sage: gap._continuation_prompt()
291
'> '
292
"""
293
return '> '
294
295
def load_package(self, pkg, verbose=False):
296
"""
297
Load the Gap package with the given name.
298
299
If loading fails, raise a RuntimeError exception.
300
301
TESTS::
302
303
sage: gap.load_package("chevie")
304
Traceback (most recent call last):
305
...
306
RuntimeError: Error loading Gap package chevie
307
308
"""
309
if verbose:
310
print "Loading GAP package %s"%pkg
311
x = self.eval('LoadPackage("%s")'%pkg)
312
if x == 'fail':
313
raise RuntimeError, 'Error loading Gap package %s'%pkg
314
315
def eval(self, x, newlines=False, strip=True, split_lines=True, **kwds):
316
r"""
317
Send the code in the string s to the GAP interpreter and return the
318
output as a string.
319
320
INPUT:
321
322
323
- ``s`` - string containing GAP code.
324
325
- ``newlines`` - bool (default: True); if False,
326
remove all backslash-newlines inserted by the GAP output
327
formatter.
328
329
- ``strip`` - ignored
330
331
- ``split_lines`` -- bool (default: True); if True then each
332
line is evaluated separately. If False, then the whole
333
block of code is evaluated all at once.
334
335
EXAMPLES::
336
337
sage: gap.eval('2+2')
338
'4'
339
sage: gap.eval('Print(4); #test\n Print(6);')
340
'46'
341
sage: gap.eval('Print("#"); Print(6);')
342
'#6'
343
sage: gap.eval('4; \n 6;')
344
'4\n6'
345
sage: gap.eval('if 3>2 then\nPrint("hi");\nfi;')
346
'hi'
347
sage: gap.eval('## this is a test\nPrint("OK")')
348
'OK'
349
sage: gap.eval('Print("This is a test. Oh no, a #");# but this is a comment\nPrint("OK")')
350
'This is a test. Oh no, a #OK'
351
sage: gap.eval('if 4>3 then')
352
''
353
sage: gap.eval('Print("Hi how are you?")')
354
'Hi how are you?'
355
sage: gap.eval('fi')
356
''
357
"""
358
# '"
359
#We remove all of the comments: On each line, we try
360
#to find a pound sign. If we find it, we check to see if
361
#it is occurring in a string. If it is not in a string, we
362
#strip off the comment.
363
if not split_lines:
364
input_line=str(x)
365
else:
366
input_line = ""
367
for line in str(x).rstrip().split('\n'):
368
pound_position = line.find('#')
369
while pound_position != -1:
370
if not is_in_string(line, pound_position):
371
line = line[:pound_position]
372
pound_position = line.find('#',pound_position+1)
373
input_line += " "+line
374
if not input_line.endswith(';'):
375
input_line += ';'
376
result = Expect.eval(self, input_line, **kwds)
377
if not newlines:
378
result = result.replace("\\\n","")
379
return result.strip()
380
381
382
def _execute_line(self, line, wait_for_prompt=True, expect_eof=False):
383
if self._expect is None: # interface is down
384
self._start()
385
E = self._expect
386
387
try:
388
if len(line) > 4095:
389
raise RuntimeError("Passing commands this long to gap would hang")
390
E.sendline(line)
391
except OSError:
392
raise RuntimeError("Error evaluating %s in %s"%(line, self))
393
if wait_for_prompt == False:
394
return ('','')
395
if len(line)==0:
396
return ('','')
397
try:
398
E.expect("\r\n") # seems to be necessary to skip TWO echoes
399
E.expect("\r\n") # one from the pty and one from GAP, I guess
400
normal_outputs = []
401
error_outputs = []
402
current_outputs = normal_outputs
403
while True:
404
x = E.expect_list(self._compiled_full_pattern)
405
current_outputs.append(E.before)
406
if x == 0: # @p
407
if E.after != '@p1.':
408
print "Warning: possibly wrong version of GAP package interface\n"
409
print "Crossing fingers and continuing\n"
410
elif x == 1: #@@
411
current_outputs.append('@')
412
elif x == 2: #special char
413
current_outputs.append(chr(ord(E.after[1:2])-ord('A')+1))
414
elif x == 3: # garbage collection info, ignore
415
pass
416
elif x == 4: # @e -- break loop
417
E.sendline("quit;")
418
elif x == 5: # @c completion, doesn't seem to happen when -p is in use
419
print "I didn't think GAP could do this\n"
420
elif x == 6: # @f GAP error message
421
current_outputs = error_outputs;
422
elif x == 7: # @h help text, but this stopped happening with new help
423
print "I didn't think GAP could do this"
424
elif x == 8: # @i awaiting normal input
425
break;
426
elif x == 9: # @m finished running a child
427
pass # there is no need to do anything
428
elif x==10: #@n normal output line
429
current_outputs = normal_outputs;
430
elif x==11: #@r echoing input
431
E.expect_list(self._compiled_small_pattern)
432
elif x==12: #@sN shouldn't happen
433
print "Warning: this should never happen"
434
elif x==13: #@w GAP is trying to send a Window command
435
print "Warning: this should never happen"
436
elif x ==14: #@x seems to be safely ignorable
437
pass
438
elif x == 15:#@z GAP starting a subprocess
439
pass # there is no need to do anything
440
except pexpect.EOF:
441
if not expect_eof:
442
raise RuntimeError("Unexpected EOF from %s executing %s"%(self,line))
443
except IOError:
444
raise RuntimeError("IO Error from %s executing %s"%(self,line))
445
return ("".join(normal_outputs),"".join(error_outputs))
446
447
def _keyboard_interrupt(self):
448
"""
449
TESTS:
450
451
We check that the gap interface behaves correctly after an
452
interrupt::
453
454
sage: gap(2)
455
2
456
sage: import sage.tests.interrupt
457
sage: try:
458
... sage.tests.interrupt.interrupt_after_delay()
459
... while True: SymmetricGroup(8).conjugacy_classes_subgroups()
460
... except KeyboardInterrupt:
461
... pass
462
Interrupting Gap...
463
sage: gap(2)
464
2
465
"""
466
print "Interrupting %s..."%self
467
self.quit()
468
raise KeyboardInterrupt, "Ctrl-c pressed while running %s"%self
469
470
def _eval_line(self, line, allow_use_file=True, wait_for_prompt=True, restart_if_needed=True):
471
"""
472
Evaluate a line of commands.
473
474
REMARK:
475
476
By default, a long command (length exceeding ``self._eval_using_file_cutoff``)
477
is evaluated using :meth:`_eval_line_using_file`.
478
479
If the command can not be evaluated since the interface
480
has crashed, it is automatically restarted and tried
481
again *once*.
482
483
If the optional ``wait_for_prompt`` is ``False`` then even a very long line
484
will not be evaluated by :meth:`_eval_line_using_file`, since this does not
485
support the ``wait_for_prompt`` option.
486
487
INPUT:
488
489
- ``line`` -- (string) a command.
490
- ``allow_use_file`` (optional bool, default ``True``) --
491
allow to evaluate long commands using :meth:`_eval_line_using_file`.
492
- ``wait_for_prompt`` (optional bool, default ``True``) --
493
wait until the prompt appears in the sub-process' output.
494
- ``restart_if_needed`` (optional bool, default ``True``) --
495
If it is ``True``, the command evaluation is evaluated
496
a second time after restarting the interface, if an
497
``EOFError`` occured.
498
499
TESTS::
500
501
sage: gap._eval_line('2+2;')
502
'4'
503
504
We test the ``wait_for_prompt`` option by sending a command that
505
creates an infinite loop in the GAP sub-process. But if we don't
506
wait for the prompt to appear in the output, we can interrupt
507
the loop without raising a KeyboardInterrupt. At the same time,
508
we test that the line is not forwarded to :meth:`_eval_line_using_file`,
509
since that method would not support the ``wait_for_prompt`` option::
510
511
sage: cutoff = gap._eval_using_file_cutoff
512
sage: gap._eval_using_file_cutoff = 4
513
sage: gap._eval_line('while(1=1) do i:=1;; od;', wait_for_prompt=False)
514
''
515
sage: gap.interrupt(timeout=1) is not None
516
True
517
sage: gap._eval_using_file_cutoff = cutoff
518
519
The following tests against a bug fixed at trac ticket #10296:
520
521
sage: a = gap(3)
522
sage: gap.eval('quit;')
523
''
524
sage: a = gap(3)
525
** Gap crashed or quit executing '$sage...:=3;;' **
526
Restarting Gap and trying again
527
sage: a
528
3
529
530
"""
531
#if line.find('\n') != -1:
532
# raise ValueError, "line must not contain any newlines"
533
E = None
534
try:
535
if self._expect is None:
536
self._start()
537
E = self._expect
538
#import pdb; pdb.set_trace()
539
if allow_use_file and wait_for_prompt and len(line) > self._eval_using_file_cutoff:
540
return self._eval_line_using_file(line)
541
(normal, error) = self._execute_line(line, wait_for_prompt=wait_for_prompt,
542
expect_eof= (self._quit_string() in line))
543
544
if len(error)> 0:
545
if 'Error, Rebuild completion files!' in error:
546
error += "\nRunning gap_reset_workspace()..."
547
self.quit()
548
gap_reset_workspace()
549
error = error.replace('\r','')
550
raise RuntimeError, "%s produced error output\n%s\n executing %s"%(self, error,line)
551
if len(normal) == 0:
552
return ''
553
554
if isinstance(wait_for_prompt, str) and normal.ends_with(wait_for_prompt):
555
n = len(wait_for_prompt)
556
elif normal.endswith(self._prompt):
557
n = len(self._prompt)
558
elif normal.endswith(self._continuation_prompt()):
559
n = len(self._continuation_prompt())
560
else:
561
n = 0
562
out = normal[:-n]
563
if len(out) > 0 and out[-1] == "\n":
564
out = out[:-1]
565
return out
566
567
except (RuntimeError,TypeError),message:
568
if 'EOF' in message[0] or E is None or not E.isalive():
569
print "** %s crashed or quit executing '%s' **"%(self, line)
570
print "Restarting %s and trying again"%self
571
self._start()
572
if line != '':
573
return self._eval_line(line, allow_use_file=allow_use_file)
574
else:
575
return ''
576
else:
577
raise RuntimeError, message
578
579
except KeyboardInterrupt:
580
self._keyboard_interrupt()
581
raise KeyboardInterrupt, "Ctrl-c pressed while running %s"%self
582
583
def unbind(self, var):
584
"""
585
Clear the variable named var.
586
587
EXAMPLES::
588
589
sage: gap.set('x', '2')
590
sage: gap.get('x')
591
'2'
592
sage: gap.unbind('x')
593
sage: gap.get('x')
594
Traceback (most recent call last):
595
...
596
RuntimeError: Gap produced error output
597
Variable: 'x' must have a value
598
...
599
"""
600
self.eval('Unbind(%s)'%var)
601
self.clear(var)
602
603
def _contains(self, v1, v2):
604
"""
605
EXAMPLES::
606
607
sage: Integers = gap('Integers')
608
sage: two = gap(2)
609
sage: gap._contains(two.name(), Integers.name())
610
True
611
612
::
613
614
sage: 2 in gap('Integers')
615
True
616
"""
617
return self.eval('%s in %s'%(v1,v2)) == "true"
618
619
def _true_symbol(self):
620
"""
621
Returns the symbol for truth in GAP.
622
623
EXAMPLES::
624
625
sage: gap._true_symbol()
626
'true'
627
sage: gap(2) == gap(2)
628
True
629
"""
630
return "true"
631
632
def _false_symbol(self):
633
"""
634
Returns the symbol for falsity in GAP.
635
636
EXAMPLES::
637
638
sage: gap._false_symbol()
639
'false'
640
sage: gap(2) == gap(3)
641
False
642
"""
643
return "false"
644
645
def _equality_symbol(self):
646
"""
647
Returns the symbol for equality in GAP.
648
649
EXAMPLES::
650
651
sage: gap._equality_symbol()
652
'='
653
sage: gap(2) == gap(3)
654
False
655
sage: gap(2) == gap(2)
656
True
657
"""
658
return "="
659
660
def version(self):
661
"""
662
Returns the version of GAP being used.
663
664
EXAMPLES::
665
666
sage: gap.version()
667
'4.4.12'
668
"""
669
return self.eval('VERSION')[1:-1]
670
671
def function_call(self, function, args=None, kwds=None):
672
"""
673
Calls the GAP function with args and kwds.
674
675
EXAMPLES::
676
677
sage: gap.function_call('SymmetricGroup', [5])
678
SymmetricGroup( [ 1 .. 5 ] )
679
680
If the GAP function does not return a value, but prints something
681
to the screen, then a string of the printed output is returned.
682
683
::
684
685
sage: s = gap.function_call('Display', [gap.SymmetricGroup(5).CharacterTable()])
686
sage: type(s)
687
<class 'sage.interfaces.interface.AsciiArtString'>
688
sage: s.startswith('CT')
689
True
690
"""
691
args, kwds = self._convert_args_kwds(args, kwds)
692
self._check_valid_function_name(function)
693
694
#Here we have to do some magic because not all GAP
695
#functions return a value. If you try to store their
696
#results to a variable, then GAP will complain. Thus, before
697
#we evaluate the function, we make it so that the marker string
698
#is in the 'last' variable in GAP. If the function returns a
699
#value, then that value will be in 'last', otherwise it will
700
#be the marker.
701
marker = '"__SAGE_LAST__"'
702
self.eval('__SAGE_LAST__ := %s;;'%marker)
703
res = self.eval("%s(%s)"%(function, ",".join([s.name() for s in args]+
704
['%s=%s'%(key,value.name()) for key, value in kwds.items()])))
705
if self.eval('last') != marker:
706
return self.new('last')
707
else:
708
if res.strip():
709
from sage.interfaces.expect import AsciiArtString
710
return AsciiArtString(res)
711
712
def trait_names(self):
713
"""
714
EXAMPLES::
715
716
sage: c = gap.trait_names()
717
sage: len(c) > 100
718
True
719
sage: 'Order' in c
720
True
721
"""
722
return []
723
724
def get_record_element(self, record, name):
725
r"""
726
Return the element of a GAP record identified by ``name``.
727
728
INPUT:
729
730
- ``record`` -- a GAP record
731
- ``name`` -- str
732
733
OUTPUT:
734
735
- :class:`GapElement`
736
737
EXAMPLES::
738
739
sage: rec = gap('rec( a := 1, b := "2" )')
740
sage: gap.get_record_element(rec, 'a')
741
1
742
sage: gap.get_record_element(rec, 'b')
743
2
744
745
TESTS::
746
747
sage: rec = gap('rec( a := 1, b := "2" )')
748
sage: type(gap.get_record_element(rec, 'a'))
749
<class 'sage.interfaces.gap.GapElement'>
750
"""
751
return self('%s.%s' % (record.name(), name))
752
753
754
class GapElement_generic(ExpectElement):
755
r"""
756
Generic interface to the GAP3/GAP4 interpreters.
757
758
AUTHORS:
759
760
- William Stein and David Joyner (interface for GAP4)
761
762
- Franco Saliola (Feb 2010): refactored to separate out the generic
763
code
764
765
"""
766
def __repr__(self):
767
"""
768
EXAMPLES::
769
770
sage: gap(2)
771
2
772
"""
773
s = ExpectElement.__repr__(self)
774
if s.find('must have a value') != -1:
775
raise RuntimeError, "An error occurred creating an object in %s from:\n'%s'\n%s"%(self.parent().name(), self._create, s)
776
return s
777
778
def bool(self):
779
"""
780
EXAMPLES::
781
782
sage: bool(gap(2))
783
True
784
sage: gap(0).bool()
785
False
786
sage: gap('false').bool()
787
False
788
"""
789
P = self._check_valid()
790
return self != P(0) and repr(self) != 'false'
791
792
793
def __len__(self):
794
"""
795
EXAMPLES::
796
797
sage: v = gap('[1,2,3]'); v
798
[ 1, 2, 3 ]
799
sage: len(v)
800
3
801
802
len is also called implicitly by if::
803
804
sage: if gap('1+1 = 2'):
805
... print "1 plus 1 does equal 2"
806
1 plus 1 does equal 2
807
808
::
809
810
sage: if gap('1+1 = 3'):
811
... print "it is true"
812
... else:
813
... print "it is false"
814
it is false
815
"""
816
P = self.parent()
817
if P.eval('%s = true'%self.name()) == 'true':
818
return 1
819
elif P.eval('%s = false'%self.name()) == 'true':
820
return 0
821
else:
822
return int(self.Length())
823
824
def _matrix_(self, R):
825
r"""
826
Return matrix over the (Sage) ring R determined by self, where self
827
should be a Gap matrix.
828
829
EXAMPLES::
830
831
sage: s = gap("(Z(7)^0)*[[1,2,3],[4,5,6]]"); s
832
[ [ Z(7)^0, Z(7)^2, Z(7) ], [ Z(7)^4, Z(7)^5, Z(7)^3 ] ]
833
sage: s._matrix_(GF(7))
834
[1 2 3]
835
[4 5 6]
836
837
::
838
839
sage: s = gap("[[1,2], [3/4, 5/6]]"); s
840
[ [ 1, 2 ], [ 3/4, 5/6 ] ]
841
sage: m = s._matrix_(QQ); m
842
[ 1 2]
843
[3/4 5/6]
844
sage: parent(m)
845
Full MatrixSpace of 2 by 2 dense matrices over Rational Field
846
847
::
848
849
sage: s = gap('[[Z(16),Z(16)^2],[Z(16)^3,Z(16)]]')
850
sage: s._matrix_(GF(16,'a'))
851
[ a a^2]
852
[a^3 a]
853
"""
854
P = self.parent()
855
v = self.DimensionsMat()
856
n = int(v[1])
857
m = int(v[2])
858
859
from sage.matrix.matrix_space import MatrixSpace
860
M = MatrixSpace(R, n, m)
861
entries = [[R(self[r,c]) for c in range(1,m+1)] for r in range(1,n+1)]
862
return M(entries)
863
864
############
865
866
class Gap(Gap_generic):
867
r"""
868
Interface to the GAP interpreter.
869
870
AUTHORS:
871
872
- William Stein and David Joyner
873
"""
874
def __init__(self, max_workspace_size=None,
875
maxread=100000, script_subdirectory=None,
876
use_workspace_cache = True,
877
server=None,
878
server_tmpdir=None,
879
logfile = None):
880
"""
881
EXAMPLES::
882
883
sage: gap == loads(dumps(gap))
884
True
885
"""
886
self.__use_workspace_cache = use_workspace_cache
887
cmd, self.__make_workspace = gap_command(use_workspace_cache, server is None)
888
cmd += " -b -p -T"
889
if max_workspace_size != None:
890
cmd += " -o %s"%int(max_workspace_size)
891
else: # unlimited
892
if is_64_bit:
893
cmd += " -o 9999G"
894
else:
895
cmd += " -o 3900m"
896
cmd += " %s/extcode/gap/sage.g"%SAGE_DATA
897
Expect.__init__(self,
898
name = 'gap',
899
prompt = 'gap> ',
900
command = cmd,
901
maxread = maxread,
902
server = server,
903
server_tmpdir = server_tmpdir,
904
script_subdirectory = script_subdirectory,
905
restart_on_ctrlc = True,
906
verbose_start = False,
907
logfile = logfile,
908
eval_using_file_cutoff=100)
909
self.__seq = 0
910
911
def __reduce__(self):
912
"""
913
EXAMPLES::
914
915
sage: gap.__reduce__()
916
(<function reduce_load_GAP at 0x...>, ())
917
sage: f, args = _
918
sage: f(*args)
919
Gap
920
"""
921
return reduce_load_GAP, tuple([])
922
923
def _next_var_name(self):
924
"""
925
Returns the next unused variable name.
926
927
EXAMPLES::
928
929
sage: g = Gap()
930
sage: g._next_var_name()
931
'$sage1'
932
sage: g(2)^2
933
4
934
sage: g._next_var_name()
935
'$sage...'
936
"""
937
if len(self._available_vars) != 0:
938
v = self._available_vars[0]
939
del self._available_vars[0]
940
return v
941
self.__seq += 1
942
return '$sage%s'%self.__seq
943
944
def _start(self):
945
"""
946
EXAMPLES::
947
948
sage: g = Gap()
949
sage: g.is_running()
950
False
951
sage: g._start()
952
sage: g.is_running()
953
True
954
sage: g.quit()
955
"""
956
if self.__use_workspace_cache and not os.path.exists(WORKSPACE):
957
gap_reset_workspace()
958
global first_try
959
n = self._session_number
960
try:
961
Expect._start(self, "Failed to start GAP.")
962
except Exception, msg:
963
if self.__use_workspace_cache and first_try:
964
print "A workspace appears to have been corrupted... automatically rebuilding (this is harmless)."
965
first_try = False
966
self._expect = None
967
expect.failed_to_start.remove(self.name())
968
gap_reset_workspace(verbose=False)
969
Expect._start(self, "Failed to start GAP.")
970
self._session_number = n
971
return
972
raise RuntimeError, msg
973
974
if self.__use_workspace_cache and self.__make_workspace:
975
self.save_workspace()
976
# Now, as self._expect exists, we can compile some useful pattern:
977
self._compiled_full_pattern = self._expect.compile_pattern_list(['@p\d+\.','@@','@[A-Z]','@[123456!"#$%&][^+]*\+',
978
'@e','@c','@f','@h','@i','@m','@n','@r','@s\d','@w.*\+','@x','@z'])
979
self._compiled_small_pattern = self._expect.compile_pattern_list('@J')
980
981
def _function_class(self):
982
"""
983
Returns the GapFunction class.
984
985
EXAMPLES::
986
987
sage: gap._function_class()
988
<class 'sage.interfaces.gap.GapFunction'>
989
990
::
991
992
sage: type(gap.Order)
993
<class 'sage.interfaces.gap.GapFunction'>
994
"""
995
return GapFunction
996
997
998
def cputime(self, t=None):
999
r"""
1000
Returns the amount of CPU time that the GAP session has used. If
1001
``t`` is not None, then it returns the difference
1002
between the current CPU time and ``t``.
1003
1004
EXAMPLES::
1005
1006
sage: t = gap.cputime()
1007
sage: t #random
1008
0.13600000000000001
1009
sage: gap.Order(gap.SymmetricGroup(5))
1010
120
1011
sage: gap.cputime(t) #random
1012
0.059999999999999998
1013
"""
1014
if t is not None:
1015
return self.cputime() - t
1016
else:
1017
self.eval('_r_ := Runtimes();')
1018
r = sum(eval(self.eval('[_r_.user_time, _r_.system_time, _r_.user_time_children, _r_.system_time_children]')))
1019
return r/1000.0
1020
1021
def save_workspace(self):
1022
r"""
1023
Save the GAP workspace.
1024
1025
TESTS:
1026
1027
We make sure that #9938 (GAP does not start if the path to the GAP
1028
workspace file contains more than 82 characters) is fixed::
1029
1030
sage: ORIGINAL_WORKSPACE = sage.interfaces.gap.WORKSPACE
1031
sage: sage.interfaces.gap.WORKSPACE = SAGE_TMP + "gap" + "0"*(80-len(SAGE_TMP))
1032
sage: gap = Gap()
1033
sage: gap('3+2')
1034
5
1035
sage: sage.interfaces.gap.WORKSPACE = ORIGINAL_WORKSPACE
1036
"""
1037
# According to the GAP Reference Manual,
1038
# [http://www.gap-system.org/Manuals/doc/htm/ref/CHAP003.htm#SSEC011.1]
1039
# SaveWorkspace can only be used at the main gap> prompt. It cannot
1040
# be included in the body of a loop or function, or called from a
1041
# break loop.
1042
self.eval('SaveWorkspace("%s");'%WORKSPACE, allow_use_file=False)
1043
1044
# Todo -- this -- but there is a tricky "when does it end" issue!
1045
# Maybe do via a file somehow?
1046
def help(self, s, pager=True):
1047
"""
1048
Print help on a given topic.
1049
1050
EXAMPLES::
1051
1052
sage: print gap.help('SymmetricGroup', pager=False)
1053
Basic Groups _____________________________________________ Group Libraries
1054
...
1055
"""
1056
tmp_to_use = self._local_tmpfile()
1057
if self.is_remote():
1058
tmp_to_use = self._remote_tmpfile()
1059
else:
1060
tmp_to_use = self._local_tmpfile()
1061
self.eval('$SAGE.tempfile := "%s";'%tmp_to_use)
1062
line = Expect.eval(self, "? %s"%s)
1063
match = re.search("Page from (\d+)", line)
1064
if match == None:
1065
print line
1066
else:
1067
(sline,) = match.groups()
1068
if self.is_remote():
1069
self._get_tmpfile()
1070
F = open(self._local_tmpfile(),"r")
1071
if pager:
1072
page(F.read(), start = int(sline)-1)
1073
else:
1074
return F.read()
1075
1076
def set(self, var, value):
1077
"""
1078
Set the variable var to the given value.
1079
1080
EXAMPLES::
1081
1082
sage: gap.set('x', '2')
1083
sage: gap.get('x')
1084
'2'
1085
"""
1086
cmd = ('%s:=%s;;'%(var,value)).replace('\n','')
1087
out = self._eval_line(cmd, allow_use_file=True)
1088
1089
def get(self, var, use_file=False):
1090
"""
1091
Get the string representation of the variable var.
1092
1093
EXAMPLES::
1094
1095
sage: gap.set('x', '2')
1096
sage: gap.get('x')
1097
'2'
1098
"""
1099
if use_file:
1100
tmp = self._local_tmpfile()
1101
if os.path.exists(tmp):
1102
os.unlink(tmp)
1103
self.eval('PrintTo("%s", %s);'%(tmp,var), strip=False)
1104
r = open(tmp).read()
1105
r = r.strip().replace("\\\n","")
1106
os.unlink(tmp)
1107
return r
1108
else:
1109
return self.eval('Print(%s);'%var, newlines=False)
1110
1111
def _pre_interact(self):
1112
"""
1113
EXAMPLES::
1114
1115
sage: gap._pre_interact()
1116
sage: gap._post_interact()
1117
"""
1118
self._eval_line("$SAGE.StartInteract();")
1119
1120
def _post_interact(self):
1121
"""
1122
EXAMPLES::
1123
1124
sage: gap._pre_interact()
1125
sage: gap._post_interact()
1126
"""
1127
self._eval_line("$SAGE.StopInteract();")
1128
1129
def _eval_line_using_file(self, line):
1130
i = line.find(':=')
1131
if i != -1:
1132
j = line.find('"')
1133
if j >= 0 and j < i:
1134
i = -1
1135
if i == -1:
1136
line0 = 'Print( %s );'%line.rstrip().rstrip(';')
1137
try: # this is necessary, since Print requires something as input, and some functions (e.g., Read) return nothing.
1138
return Expect._eval_line_using_file(self, line0)
1139
except RuntimeError, msg:
1140
#if not ("Function call: <func> must return a value" in msg):
1141
# raise RuntimeError, msg
1142
return ''
1143
return Expect._eval_line_using_file(self, line)
1144
1145
def console(self):
1146
"""
1147
Spawn a new GAP command-line session.
1148
1149
EXAMPLES::
1150
1151
sage: gap.console() #not tested
1152
GAP4, Version: 4.4.12 of 17-Dec-2008, powerpc-apple-darwin9.8.0-gcc
1153
gap>
1154
"""
1155
gap_console()
1156
1157
def _object_class(self):
1158
"""
1159
Returns the GapElement class.
1160
1161
EXAMPLES::
1162
1163
sage: gap._object_class()
1164
<class 'sage.interfaces.gap.GapElement'>
1165
sage: type(gap(2))
1166
<class 'sage.interfaces.gap.GapElement'>
1167
"""
1168
return GapElement
1169
1170
def _function_element_class(self):
1171
"""
1172
Returns the GapFunctionElement class.
1173
1174
EXAMPLES::
1175
1176
sage: gap._function_element_class()
1177
<class 'sage.interfaces.gap.GapFunctionElement'>
1178
sage: type(gap.SymmetricGroup(4).Order)
1179
<class 'sage.interfaces.gap.GapFunctionElement'>
1180
"""
1181
return GapFunctionElement
1182
def trait_names(self):
1183
"""
1184
EXAMPLES::
1185
1186
sage: c = gap.trait_names()
1187
sage: len(c) > 100
1188
True
1189
sage: 'Order' in c
1190
True
1191
"""
1192
try:
1193
return self.__trait_names
1194
except AttributeError:
1195
self.__trait_names = eval(self.eval('NamesSystemGVars()')) + \
1196
eval(self.eval('NamesUserGVars()'))
1197
return self.__trait_names
1198
1199
1200
############
1201
1202
def gap_reset_workspace(max_workspace_size=None, verbose=False):
1203
r"""
1204
Call this to completely reset the GAP workspace, which is used by
1205
default when Sage first starts GAP.
1206
1207
The first time you start GAP from Sage, it saves the startup state
1208
of GAP in the file
1209
1210
::
1211
1212
$HOME/.sage/gap-workspace
1213
1214
1215
This is useful, since then subsequent startup of GAP is at least 10
1216
times as fast. Unfortunately, if you install any new code for GAP,
1217
it won't be noticed unless you explicitly load it, e.g., with
1218
gap.load_package("my_package")
1219
1220
The packages sonata, guava, factint, gapdoc, grape, design, toric,
1221
and laguna are loaded in all cases before the workspace is saved,
1222
if they are available.
1223
"""
1224
if os.path.exists(WORKSPACE):
1225
os.unlink(WORKSPACE)
1226
1227
g = Gap(use_workspace_cache=False, max_workspace_size=None)
1228
for pkg in ['ctbllib', 'sonata', 'guava', 'factint', \
1229
'gapdoc', 'grape', 'design', \
1230
'toric', 'laguna', 'braid']: # NOTE: Do *not* autoload hap - it screws up PolynomialRing(Rationals,2)
1231
try:
1232
g.load_package(pkg, verbose=verbose)
1233
except RuntimeError, msg:
1234
if verbose:
1235
print '*** %s'%msg
1236
pass
1237
# end for
1238
g.save_workspace()
1239
1240
1241
# Check to see if we need to auto-regenerate the gap workspace, i.e.,
1242
# if the modification time of the gap link has changed (which signals
1243
# that gap has been somehow upgraded).
1244
if not os.path.exists(WORKSPACE) or os.path.getmtime(WORKSPACE) < os.path.getmtime(GAP_STAMP):
1245
#print "Automatically updating the cached Gap workspace:"
1246
#print WORKSPACE
1247
gap_reset_workspace(verbose=False)
1248
1249
# Delete all gap workspaces that haven't been used in at least 1
1250
# week, to avoid needless cruft. I had an install on sage.math
1251
# with 90 of these, since I run a lot of different versions of
1252
# Sage, and it totalled 1.3GB of wasted space! See trac #4936.
1253
# We only do this after creating a new workspace, since this cruft
1254
# issue is only a problem if workspaces get created every so
1255
# often. We don't want to have to do this on every startup.
1256
now = time.time()
1257
for F in os.listdir(GAP_DIR):
1258
if F.startswith('workspace'):
1259
age = now - os.path.getatime(GAP_DIR + '/' + F)
1260
if age >= 604800: # = 168*3600 = 2 weeks in seconds
1261
os.unlink(GAP_DIR + '/' + F)
1262
1263
1264
class GapElement(GapElement_generic):
1265
def __getitem__(self, n):
1266
"""
1267
EXAMPLES::
1268
1269
sage: a = gap([1,2,3])
1270
sage: a[1]
1271
1
1272
"""
1273
self._check_valid()
1274
if not isinstance(n, tuple):
1275
return self.parent().new('%s[%s]'%(self._name, n))
1276
else:
1277
return self.parent().new('%s%s'%(self._name, ''.join(['[%s]'%x for x in n])))
1278
1279
def __reduce__(self):
1280
"""
1281
Note that GAP elements cannot be pickled.
1282
1283
EXAMPLES::
1284
1285
sage: gap(2).__reduce__()
1286
(<function reduce_load at 0x...>, ())
1287
sage: f, args = _
1288
sage: f(*args)
1289
Traceback (most recent call last):
1290
...
1291
ValueError: The session in which this object was defined is no longer running.
1292
"""
1293
return reduce_load, () # default is an invalid object
1294
1295
def str(self, use_file=False):
1296
"""
1297
EXAMPLES::
1298
1299
sage: print gap(2)
1300
2
1301
"""
1302
if use_file:
1303
P = self._check_valid()
1304
return P.get(self.name(), use_file=True)
1305
else:
1306
return self.__repr__()
1307
1308
def _latex_(self):
1309
r"""
1310
EXAMPLES::
1311
1312
sage: s = gap("[[1,2], [3/4, 5/6]]")
1313
sage: latex(s)
1314
\left(\begin{array}{rr} 1&2\\ 3/4&\frac{5}{6}\\ \end{array}\right)
1315
"""
1316
P = self._check_valid()
1317
try:
1318
s = P.eval('LaTeXObj(%s)'%self.name())
1319
s = s.replace('\\\\','\\').replace('"','')
1320
s = s.replace('%\\n',' ')
1321
return s
1322
except RuntimeError:
1323
return str(self)
1324
1325
def trait_names(self):
1326
"""
1327
EXAMPLES::
1328
1329
sage: s5 = gap.SymmetricGroup(5)
1330
sage: 'Centralizer' in s5.trait_names()
1331
True
1332
"""
1333
if '__trait_names' in self.__dict__:
1334
return self.__trait_names
1335
P = self.parent()
1336
v = P.eval('$SAGE.OperationsAdmittingFirstArgument(%s)'%self.name())
1337
v = v.replace('Tester(','').replace('Setter(','').replace('<Operation ','').replace('>','').replace(')','')
1338
v = eval(v)
1339
v = list(set(v))
1340
v.sort()
1341
self.__trait_names = v
1342
return v
1343
1344
1345
1346
class GapFunctionElement(FunctionElement):
1347
def _sage_doc_(self):
1348
"""
1349
EXAMPLES::
1350
1351
sage: print gap(4).SymmetricGroup._sage_doc_()
1352
Basic Groups _____________________________________________ Group Libraries
1353
...
1354
"""
1355
M = self._obj.parent()
1356
return M.help(self._name, pager=False)
1357
1358
1359
class GapFunction(ExpectFunction):
1360
def _sage_doc_(self):
1361
"""
1362
EXAMPLES::
1363
1364
sage: print gap.SymmetricGroup._sage_doc_()
1365
Basic Groups _____________________________________________ Group Libraries
1366
...
1367
"""
1368
M = self._parent
1369
return M.help(self._name, pager=False)
1370
1371
1372
def is_GapElement(x):
1373
"""
1374
Returns True if x is a GapElement.
1375
1376
EXAMPLES::
1377
1378
sage: from sage.interfaces.gap import is_GapElement
1379
sage: is_GapElement(gap(2))
1380
True
1381
sage: is_GapElement(2)
1382
False
1383
"""
1384
return isinstance(x, GapElement)
1385
1386
def gfq_gap_to_sage(x, F):
1387
"""
1388
INPUT:
1389
1390
1391
- ``x`` - gap finite field element
1392
1393
- ``F`` - Sage finite field
1394
1395
1396
OUTPUT: element of F
1397
1398
EXAMPLES::
1399
1400
sage: x = gap('Z(13)')
1401
sage: F = GF(13, 'a')
1402
sage: F(x)
1403
2
1404
sage: F(gap('0*Z(13)'))
1405
0
1406
sage: F = GF(13^2, 'a')
1407
sage: x = gap('Z(13)')
1408
sage: F(x)
1409
2
1410
sage: x = gap('Z(13^2)^3')
1411
sage: F(x)
1412
12*a + 11
1413
sage: F.multiplicative_generator()^3
1414
12*a + 11
1415
1416
AUTHOR:
1417
1418
- David Joyner and William Stein
1419
"""
1420
from sage.rings.finite_rings.constructor import FiniteField
1421
1422
s = str(x)
1423
if s[:2] == '0*':
1424
return F(0)
1425
i1 = s.index("(")
1426
i2 = s.index(")")
1427
q = eval(s[i1+1:i2].replace('^','**'))
1428
if q == F.order():
1429
K = F
1430
else:
1431
K = FiniteField(q, F.variable_name())
1432
if s.find(')^') == -1:
1433
e = 1
1434
else:
1435
e = int(s[i2+2:])
1436
if F.degree() == 1:
1437
g = int(gap.eval('Int(Z(%s))'%q))
1438
else:
1439
g = K.multiplicative_generator()
1440
return F(K(g**e))
1441
1442
def intmod_gap_to_sage(x):
1443
r"""
1444
INPUT:
1445
1446
- x -- Gap integer mod ring element
1447
1448
EXAMPLES::
1449
1450
sage: a = gap(Mod(3, 18)); a
1451
ZmodnZObj( 3, 18 )
1452
sage: b = sage.interfaces.gap.intmod_gap_to_sage(a); b
1453
3
1454
sage: b.parent()
1455
Ring of integers modulo 18
1456
1457
sage: a = gap(Mod(3, 17)); a
1458
Z(17)
1459
sage: b = sage.interfaces.gap.intmod_gap_to_sage(a); b
1460
3
1461
sage: b.parent()
1462
Ring of integers modulo 17
1463
1464
sage: a = gap(Mod(0, 17)); a
1465
0*Z(17)
1466
sage: b = sage.interfaces.gap.intmod_gap_to_sage(a); b
1467
0
1468
sage: b.parent()
1469
Ring of integers modulo 17
1470
1471
sage: a = gap(Mod(3, 65537)); a
1472
ZmodpZObj( 3, 65537 )
1473
sage: b = sage.interfaces.gap.intmod_gap_to_sage(a); b
1474
3
1475
sage: b.parent()
1476
Ring of integers modulo 65537
1477
"""
1478
from sage.rings.finite_rings.integer_mod import Mod
1479
from sage.rings.finite_rings.integer_mod_ring import Zmod
1480
s = str(x)
1481
m = re.search(r'Z\(([0-9]*)\)', s)
1482
if m:
1483
return gfq_gap_to_sage(x, Zmod(m.group(1)))
1484
m = re.match(r'Zmod[np]ZObj\( ([0-9]*), ([0-9]*) \)', s)
1485
if m:
1486
return Mod(m.group(1), m.group(2))
1487
raise ValueError, "Unable to convert Gap element '%s'" % s
1488
1489
#############
1490
1491
gap = Gap()
1492
1493
def reduce_load_GAP():
1494
"""
1495
Returns the GAP interface object defined in sage.interfaces.gap.
1496
1497
EXAMPLES::
1498
1499
sage: from sage.interfaces.gap import reduce_load_GAP
1500
sage: reduce_load_GAP()
1501
Gap
1502
"""
1503
return gap
1504
1505
def reduce_load():
1506
"""
1507
Returns an invalid GAP element. Note that this is the object
1508
returned when a GAP element is unpickled.
1509
1510
EXAMPLES::
1511
1512
sage: from sage.interfaces.gap import reduce_load
1513
sage: reduce_load()
1514
Traceback (most recent call last):
1515
...
1516
ValueError: The session in which this object was defined is no longer running.
1517
sage: loads(dumps(gap(2)))
1518
Traceback (most recent call last):
1519
...
1520
ValueError: The session in which this object was defined is no longer running.
1521
"""
1522
return GapElement(None, None)
1523
1524
import os
1525
def gap_console(use_workspace_cache=True):
1526
"""
1527
Spawn a new GAP command-line session.
1528
1529
EXAMPLES::
1530
1531
sage: gap.console() #not tested
1532
GAP4, Version: 4.4.12 of 17-Dec-2008, powerpc-apple-darwin9.8.0-gcc
1533
gap>
1534
"""
1535
cmd, _ = gap_command(use_workspace_cache=use_workspace_cache)
1536
os.system(cmd)
1537
1538
def gap_version():
1539
"""
1540
Returns the version of GAP being used.
1541
1542
EXAMPLES::
1543
1544
sage: gap_version()
1545
'4.4.12'
1546
"""
1547
return gap.eval('VERSION')[1:-1]
1548
1549
1550
1551