# source:sage/interfaces/gap.py@1:bf5417bc8931

Revision 1:bf5417bc8931, 13.4 KB checked in by tornaria@…, 7 years ago (diff)

[project @ patch to sage-0.10.13]

Line
1r"""
2Interface to GAP
3
4\sage provides an interface to the GAP system.  This system provides
5extensive group theory, combinatorics, etc.
6
7The GAP interface will only work if GAP is installed on your
8computer; this should be the case, since GAP is included with
9\sage.  The interface offers three pieces of functionality:
10\begin{enumerate}
11\item \code{gap_console()} -- A function that dumps you
12into an interactive command-line GAP session.
13
14\item \code{gap(expr)} -- Evaluation of arbitrary GAP
15expressions, with the result returned as a string.
16
17\item \code{gap.new(expr)} -- Creation of a \sage object that wraps a
18GAP object.  This provides a Pythonic interface to GAP.  For example,
19if \code{f=gap.new(10)}, then \code{f.Factors()} returns the prime
20factorization of $10$ computed using GAP.
21
22\end{enumerate}
23
24\subsection{First Examples}
25
26We factor an integer using GAP:
27
28    sage: n = gap(20062006); n
29    20062006
30    sage: n.parent()
31    Gap
32    sage: fac = n.Factors(); fac
33    [ 2, 17, 59, 73, 137 ]
34    sage: fac.parent()
35    Gap
36    sage: fac[1]
37    2
38
39\subsection{GAP and Singular}
40This example illustrates conversion between Singular and GAP via \sage
41as an intermediate step.  First we create and factor a Singular polynomial.
42
43    sage: singular(389)
44    389
45    sage: R1 = singular.ring(0, '(x,y)', 'dp')
46    sage: R1 = singular.ring(0, '(x,y)', 'dp')
47    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')
48    sage: F = f.factorize()
49    sage: print F
50    [1]:
51       _[1]=9
52       _[2]=x^6-2*x^3*y^2-x^2*y^3+y^4
53       _[3]=-x^5+y^2
54    [2]:
55       1,1,2
56
57Next we convert the factor $-x^5+y^2$ to a \sage multivariate
58polynomial.  Note that it is important to let $x$ and $y$ be the
59generators of a polynomial ring, so the eval command works.
60
61    sage: x, y = MPolynomialRing(RationalField(), 2).gens()
62    sage: g = eval(F[1][3].sage_polystring()); g
63    x_1^2 - x_0^5
64
65Next we create a polynomial ring in GAP and obtain its indeterminates:
66
67    sage: R = gap.PolynomialRing('Rationals', 2); R
68    PolynomialRing(..., [ x_1, x_2 ])
69    sage: I = R.IndeterminatesOfPolynomialRing(); I
70    [ x_1, x_2 ]
71
72In order to eval $g$ in GAP, we need to tell GAP to view the variables
73\code{x_0} and \code{x_1} as the two generators of $R$.  This is the
74one tricky part.  In the GAP interpreter the object \code{I} has its
75    own name (which isn't \code{I}).  We can access its name using
76    \code{I.name()}.
77
78        sage: _ = gap.eval("x_0 := %s[1];; x_1 := %s[2];;"%(I.name(), I.name()))
79
80    Now $x_0$ and $x_1$ are defined, so we can construct the GAP polynomial $f$
81    corresponding to $g$:
82
83        sage: f = gap(g); f
84        -x_1^5+x_2^2
85
86    We can call GAP functions on $f$.  For example, we evaluate
87    the GAP \code{Value} function, which evaluates $f$ at the point $(1,2)$.
88
89        sage: f.Value(I, [1,2])
90        3
91        sage: g(1,2)        # agrees
92        3
93
95(using the dumps method, etc.)  is \emph{not} supported, since the
96output string representation of Gap objects is sometimes not valid
97input to GAP.  Creating classes that wrap GAP objects \emph{is}
98supported, via simply defining the a _gap_init_ member function that
99returns a string that when evaluated in GAP constructs the object.
100See \code{groups/permutation_group.py} for a nontrivial example of
101this.
102
103\subsection{Long Input}
104The GAP interface reads in even very long input (using files) in a
105robust manner, as long as you are creating a new object.
106\note{Using \code{gap.eval} for long input
107is much less robust, and is not recommended.}
108
109    sage: t = '"%s"'%10^10000   # ten thousand character string.
110    sage: a = gap(t)
111
112AUTHORS:
113    -- David Joyner and William Stein; initial version(s)
114    -- William Stein (2006-02-01): modified gap_console command
115       so it uses exactly the same startup command as Gap.__init__.
116
117"""
118
119#*****************************************************************************
120#       Copyright (C) 2005 William Stein <wstein@ucsd.edu>
121#
123#
124#    This code is distributed in the hope that it will be useful,
125#    but WITHOUT ANY WARRANTY; without even the implied warranty of
126#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
127#    General Public License for more details.
128#
129#  The full text of the GPL is available at:
130#
132#*****************************************************************************
133
134from expect import Expect, ExpectElement
135from sage.misc.misc import SAGE_ROOT, is_64_bit
136import os
137DB_HOME = "%s/data/"%SAGE_ROOT
138WORKSPACE = "%s/tmp/gap-workspace"%SAGE_ROOT
139
140def gap_command(use_workspace_cache=True):
141    if use_workspace_cache and os.path.exists(WORKSPACE):
142        return "gap -L %s"%WORKSPACE, False
143    else:
144        return "gap ", True
145
146
147class Gap(Expect):
148    r"""
149    Interface to the GAP interpreter.
150
151    AUTHORS: William Stein and David Joyner
152    """
153    def __init__(self, max_workspace_size=None,
155                 use_workspace_cache = True,
156                 server=None,
157                 logfile = None):
158
159        self.__use_workspace_cache = use_workspace_cache
160        cmd, self.__make_workspace = gap_command(use_workspace_cache)
161        cmd += ' -T -n -b '
162        if max_workspace_size != None:
163            cmd += " -o %s"%int(max_workspace_size)
164        else: # unlimited
165            if is_64_bit:
166                cmd += " -o 9999G"
167            else:
168                cmd += " -o 3900m"
169
170        Expect.__init__(self,
171                        name = 'gap',
172                        prompt = 'gap> ',
173                        command = cmd,
175                        server = server,
176                        script_subdirectory = script_subdirectory,
177                        restart_on_ctrlc = True,
178                        verbose_start = False,
179                        logfile = logfile,
180                        eval_using_file_cutoff=100)
181
182        self.__seq = 0
183
184    def __reduce__(self):
186
187    def _quit_string(self):
188        return 'quit'
189
190    def _next_var_name(self):
191        if len(self._available_vars) != 0:
192            v = self._available_vars[0]
193            del self._available_vars[0]
194            return v
195        if self.__seq == 0:
196            self.eval('sage := [ ];')
197        self.__seq += 1
198        return 'sage[%s]'%self.__seq
199
203
204    def _eval_line_using_file(self, line, tmp):
205        F = open(tmp, 'w')
206        F.write(line)
207        F.close()
209                               allow_use_file=False)
210
211    # Change the default for Gap, since eval using a file doesn't
212    # work except for setting variables.
213    def _eval_line(self, line, allow_use_file=False):
214        return Expect._eval_line(self, line, allow_use_file=allow_use_file)
215
216    def _start(self):
217        Expect._start(self, "Failed to start GAP.  One possible reason for this is that your gap workspace may be corrupted.  Perhaps remove %s/tmp/gap-workspace"%SAGE_ROOT)
218        if self.__use_workspace_cache and self.__make_workspace:
219            self.eval('SaveWorkspace("%s");'%WORKSPACE)
220
222        """
223        Load the Gap package with the given name.
224        """
225        if verbose:
228
229    def save_workspace(self):
230        self.eval('SaveWorkspace("%s");'%WORKSPACE)
231
232    def eval(self, x, newlines=False):
233        r"""
234        Send the code in the string s to the GAP interpreter and return
235        the output as a string.
236
237        INPUT:
238            s -- string containing GAP code.
239            newlines -- bool (default: True); if False, remove all
240                      backslash-newlines inserted by the GAP output formatter.
241        """
242        # newlines cause hang (i.e., error but no gap> prompt!)
243        x = str(x).rstrip().replace('\n','')
244        if len(x) == 0 or x[len(x) - 1] != ';':
245            x += ';'
246        s = Expect.eval(self, x)
247        if newlines:
248            return s
249        else:
250            return s.replace("\\\n","")
251
252    # Todo -- this -- but there is a tricky "when does it end" issue!
253    # Maybe do via a file somehow?
254    #def help(self, s):
255    #    """
256    #    Print help on a given topic.#
257    #    """
258    #    print Expect.eval(self, "? %s"%s)
259
260    def set(self, var, value):
261        """
262        Set the variable var to the given value.
263        """
264        cmd = ('%s:=%s;;'%(var,value)).replace('\n','')
265        #out = self.eval(cmd)
266        out = self._eval_line(cmd, allow_use_file=True)
267        if out.lower().find('error') != -1:
268            raise TypeError, "Error executing code in GAP\nCODE:\n\t%s\nGAP ERROR:\n\t%s"%(cmd, out)
269
270
271    def get(self, var):
272        """
273        Get the value of the variable var.
274        """
275        return self.eval('%s;'%var, newlines=False)
276
277    #def clear(self, var):
278        #"""
279        #Clear the variable named var.
280        #"""
281        #self.eval('Unbind(%s)'%var)
282        #self._available_vars.append(var)
283
284    def _contains(self, v1, v2):
285        return self.eval('%s in %s'%(v1,v2))
286
287    def _is_true_string(self, t):
288        return t == "true"
289
290    def _true_symbol(self):
291        return "true"
292
293    def _false_symbol(self):
294        return "false"
295
296    def _equality_symbol(self):
297        return "="
298
299    def console(self):
300        gap_console()
301
302    def version(self):
303        return gap_version()
304
305    def _object_class(self):
306        return GapElement
307
308
309############
310
311def gap_reset_workspace(max_workspace_size=None):
312    """
313    Call this to completely reset the GAP workspace, which
314    is used by default when SAGE first starts GAP.
315
316    The first time you start GAP from SAGE, it saves the
317    startup state of GAP in the file
318
319        tmp/gap-workspace
320
321    This is useful, since then subsequent startup of GAP
322    is at least 10 times as fast.  Unfortunately, if you
323    install any new code for GAP, it won't be noticed unless
324    you explicitly load it, e.g., with
326    """
327    g = Gap(use_workspace_cache=False, max_workspace_size=None)
328    g.eval('SaveWorkspace("%s");'%WORKSPACE)
329
330
331class GapElement(ExpectElement):
332    def __getitem__(self, n):
333        self._check_valid()
334        if not isinstance(n, tuple):
335            return self.parent().new('%s[%s]'%(self._name, n))
336        else:
337            return self.parent().new('%s%s'%(self._name, ''.join(['[%s]'%x for x in n])))
338
339    def __reduce__(self):
340        return reduce_load, ()  # default is an invalid object
341
342    def __repr__(self):
343        s = ExpectElement.__repr__(self)
344        if s.find('must have a value') != -1:
345            raise RuntimeError, "An error occured creating an object in %s from:\n'%s'\n%s"%(self.parent().name(), self._create, s)
346        return s
347
348    def __len__(self):
349        return int(self.Length())
350
351    def _matrix_(self, R):
352        r"""
353        Return matrix over the (\sage) ring R determined by self, where self
354        should be a Gap matrix.
355
356            sage: s = gap("Z(3)*[[1,2,3],[3,4,5]]"); s
357            [ [ Z(3), Z(3)^0, 0*Z(3) ], [ 0*Z(3), Z(3), Z(3)^0 ] ]
358            sage: matrix(s, GF(3))
359            [2 0 1]
360            [2 0 1]
361
362            sage: s = gap("[[1,2], [3/4, 5/6]]"); s
363            [ [ 1, 2 ], [ 3/4, 5/6 ] ]
364            sage: m = matrix(s, QQ); m
365            [  1 3/4]
366            [  2 5/6]
367            sage: parent(m)
368            Full MatrixSpace of 2 by 2 dense matrices over Rational Field
369
370            sage: s = gap('[[Z(16),Z(16)^2],[Z(16)^3,Z(16)]]')
371            sage: matrix(s, GF(16))
372            [  a a^3]
373            [a^2   a]
374        """
375        P = self.parent()
376        v = self.DimensionsMat()
377        n = int(v[1])
378        m = int(v[2])
379
380        from sage.matrix.matrix_space import MatrixSpace
381        M = MatrixSpace(R, n, m)
382        entries = [[R(self[i,j]) for i in range(1,n+1)] for j in range(1,m+1)]
383
384        return M(entries)
385
386        mat = copy.copy(s)
387        mat = mat.replace("\n","")
388        mat = mat.replace("] ]","")
389        mat = mat.replace("[ [","")
390        mat = mat.split("],")
391        rows_str = mat
392        rowss = []
393        for x in rows_str:
394            rowss.append(x.split(","))
395        rows_mat = [[] for i in range(len(rowss))]
396        for i in range(len(rowss)):
397            for x in rowss[i]:
398                x = x.replace("]","")
399                x = x.replace("[","")
400                rows_mat[i].append(x)
401        M = []
402        for i in range(m):
403            rows = []
404            for x in rows_mat[i]:
405                rows.append(gap2sage_finite_field(x,F))
406            M.append(rows)
407        MS = MatrixSpace(F,m,n)
408        return MS(M)
409
410
411
412def is_GapElement(x):
413    return isinstance(x, GapElement)
414
415###########
416
417#############
418
419gap = Gap()
420
422    return gap
423