| 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 |
|---|
| 9 | \sage. The interface offers three pieces of functionality: |
|---|
| 10 | \begin{enumerate} |
|---|
| 11 | \item \code{gap_console()} -- A function that dumps you |
|---|
| 12 | into an interactive command-line GAP session. |
|---|
| 13 | |
|---|
| 14 | \item \code{gap(expr)} -- Evaluation of arbitrary GAP |
|---|
| 15 | expressions, with the result returned as a string. |
|---|
| 16 | |
|---|
| 17 | \item \code{gap.new(expr)} -- Creation of a \sage object that wraps a |
|---|
| 18 | GAP object. This provides a Pythonic interface to GAP. For example, |
|---|
| 19 | if \code{f=gap.new(10)}, then \code{f.Factors()} returns the prime |
|---|
| 20 | factorization of $10$ computed using GAP. |
|---|
| 21 | |
|---|
| 22 | \end{enumerate} |
|---|
| 23 | |
|---|
| 24 | \subsection{First Examples} |
|---|
| 25 | |
|---|
| 26 | We 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} |
|---|
| 40 | This example illustrates conversion between Singular and GAP via \sage |
|---|
| 41 | as 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 | |
|---|
| 57 | Next we convert the factor $-x^5+y^2$ to a \sage multivariate |
|---|
| 58 | polynomial. Note that it is important to let $x$ and $y$ be the |
|---|
| 59 | generators 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 | |
|---|
| 65 | Next 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 | |
|---|
| 72 | In 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 |
|---|
| 74 | one 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 | |
|---|
| 94 | \subsection{Saving and loading objects} Saving and loading GAP objects |
|---|
| 95 | (using the dumps method, etc.) is \emph{not} supported, since the |
|---|
| 96 | output string representation of Gap objects is sometimes not valid |
|---|
| 97 | input to GAP. Creating classes that wrap GAP objects \emph{is} |
|---|
| 98 | supported, via simply defining the a _gap_init_ member function that |
|---|
| 99 | returns a string that when evaluated in GAP constructs the object. |
|---|
| 100 | See \code{groups/permutation_group.py} for a nontrivial example of |
|---|
| 101 | this. |
|---|
| 102 | |
|---|
| 103 | \subsection{Long Input} |
|---|
| 104 | The GAP interface reads in even very long input (using files) in a |
|---|
| 105 | robust manner, as long as you are creating a new object. |
|---|
| 106 | \note{Using \code{gap.eval} for long input |
|---|
| 107 | is much less robust, and is not recommended.} |
|---|
| 108 | |
|---|
| 109 | sage: t = '"%s"'%10^10000 # ten thousand character string. |
|---|
| 110 | sage: a = gap(t) |
|---|
| 111 | |
|---|
| 112 | AUTHORS: |
|---|
| 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 | # |
|---|
| 122 | # Distributed under the terms of the GNU General Public License (GPL) |
|---|
| 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 | # |
|---|
| 131 | # http://www.gnu.org/licenses/ |
|---|
| 132 | #***************************************************************************** |
|---|
| 133 | |
|---|
| 134 | from expect import Expect, ExpectElement |
|---|
| 135 | from sage.misc.misc import SAGE_ROOT, is_64_bit |
|---|
| 136 | import os |
|---|
| 137 | DB_HOME = "%s/data/"%SAGE_ROOT |
|---|
| 138 | WORKSPACE = "%s/tmp/gap-workspace"%SAGE_ROOT |
|---|
| 139 | |
|---|
| 140 | def 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 | |
|---|
| 147 | class 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, |
|---|
| 154 | maxread=100000, script_subdirectory="user", |
|---|
| 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, |
|---|
| 174 | maxread = maxread, |
|---|
| 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): |
|---|
| 185 | return reduce_load_GAP, tuple([]) |
|---|
| 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 | |
|---|
| 200 | #def _read_in_file_command(self, filename): |
|---|
| 201 | # return 'EvalString(ReadAll(InputTextFile("%s")));'%filename |
|---|
| 202 | #return 'Read(InputTextFile("%s"));'%filename |
|---|
| 203 | |
|---|
| 204 | def _eval_line_using_file(self, line, tmp): |
|---|
| 205 | F = open(tmp, 'w') |
|---|
| 206 | F.write(line) |
|---|
| 207 | F.close() |
|---|
| 208 | return self._eval_line('Read(InputTextFile("%s"));'%tmp, |
|---|
| 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 | |
|---|
| 221 | def load_package(self, pkg, verbose=False): |
|---|
| 222 | """ |
|---|
| 223 | Load the Gap package with the given name. |
|---|
| 224 | """ |
|---|
| 225 | if verbose: |
|---|
| 226 | "Loading GAP package %s"%pkg |
|---|
| 227 | self.eval('LoadPackage("%s")'%pkg) |
|---|
| 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 | |
|---|
| 311 | def 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 |
|---|
| 325 | gap.load_package("laguna") |
|---|
| 326 | """ |
|---|
| 327 | g = Gap(use_workspace_cache=False, max_workspace_size=None) |
|---|
| 328 | g.eval('SaveWorkspace("%s");'%WORKSPACE) |
|---|
| 329 | |
|---|
| 330 | |
|---|
| 331 | class 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 | |
|---|
| 412 | def is_GapElement(x): |
|---|
| 413 | return isinstance(x, GapElement) |
|---|
| 414 | |
|---|
| 415 | ########### |
|---|
| 416 | |
|---|
| 417 | ############# |
|---|
| 418 | |
|---|
| 419 | gap = Gap() |
|---|
| 420 | |
|---|
| 421 | def reduce_load_GAP(): |
|---|
| 422 | return gap |
|---|
| 423 | |
|---|
| 424 | def reduce_load(): |
|---|
| 425 | return GapElement(None, None) |
|---|
| 426 | |
|---|
| 427 | import os |
|---|
| 428 | def gap_console(use_workspace_cache=True): |
|---|
| 429 | cmd, _ = gap_command(use_workspace_cache=use_workspace_cache) |
|---|
| 430 | os.system(cmd) |
|---|
| 431 | |
|---|
| 432 | def gap_version(): |
|---|
| 433 | return gap.eval('VERSION')[1:-1] |
|---|
| 434 | |
|---|
| 435 | |
|---|