| 1 | r""" |
|---|
| 2 | Interface to Magma |
|---|
| 3 | |
|---|
| 4 | \note{You must have \code{magma} installed on your computer |
|---|
| 5 | for this interface to work. Magma is not free, so it is |
|---|
| 6 | not included with \sage, but you can obtain it from |
|---|
| 7 | \url{http://magma.maths.usyd.edu.au/}.} You do not |
|---|
| 8 | have to install any optional \sage packages. |
|---|
| 9 | |
|---|
| 10 | |
|---|
| 11 | SAGE provides an interface to the Magma computational algebra |
|---|
| 12 | system. This system provides extensive functionality for |
|---|
| 13 | number theory, group theory, combinatorics and algebra. |
|---|
| 14 | |
|---|
| 15 | The Magma interface offers three pieces of functionality: |
|---|
| 16 | \begin{enumerate} |
|---|
| 17 | |
|---|
| 18 | \item \code{magma_console()} -- A function that dumps you |
|---|
| 19 | into an interactive command-line Magma session. |
|---|
| 20 | |
|---|
| 21 | \item \code{magma(expr)} -- Evaluation of arbitrary Magma |
|---|
| 22 | expressions, with the result returned as a string. |
|---|
| 23 | |
|---|
| 24 | \item \code{magma.new(expr)} -- Creation of a SAGE object that wraps a |
|---|
| 25 | Magma object. This provides a Pythonic interface to Magma. For example, |
|---|
| 26 | if \code{f=magma.new(10)}, then \code{f.Factors()} returns the prime |
|---|
| 27 | factorization of $10$ computed using Magma. |
|---|
| 28 | |
|---|
| 29 | \end{enumerate} |
|---|
| 30 | |
|---|
| 31 | |
|---|
| 32 | |
|---|
| 33 | \subsection{Parameters} |
|---|
| 34 | Some Magma functions have optional ``parameters'', which |
|---|
| 35 | are arguments that in Magma go after a colon. In SAGE, |
|---|
| 36 | you pass these using named function arguments. For example, |
|---|
| 37 | |
|---|
| 38 | sage: E = magma.new('EllipticCurve([0,1,1,-1,0])') |
|---|
| 39 | sage: E.Rank(Bound = 5) |
|---|
| 40 | 0 |
|---|
| 41 | |
|---|
| 42 | \subsection{Multiple Return Values} |
|---|
| 43 | |
|---|
| 44 | Some Magma functions return more than one value. |
|---|
| 45 | You can control how many you get using the \code{nvals} |
|---|
| 46 | named parameter to a function call: |
|---|
| 47 | |
|---|
| 48 | sage: n = magma.new(100) |
|---|
| 49 | sage: n.IsSquare(nvals = 1) |
|---|
| 50 | true |
|---|
| 51 | sage: n.IsSquare(nvals = 2) |
|---|
| 52 | (true, 10) |
|---|
| 53 | |
|---|
| 54 | \subsection{Long Input} |
|---|
| 55 | The Magma interface reads in even very long input (using files) in a |
|---|
| 56 | robust manner. |
|---|
| 57 | |
|---|
| 58 | sage: t = '"%s"'%10^10000 # ten thousand character string. |
|---|
| 59 | sage: a = magma.eval(t) |
|---|
| 60 | sage: a = magma(t) |
|---|
| 61 | """ |
|---|
| 62 | |
|---|
| 63 | #***************************************************************************** |
|---|
| 64 | # Copyright (C) 2005 William Stein <wstein@ucsd.edu> |
|---|
| 65 | # |
|---|
| 66 | # Distributed under the terms of the GNU General Public License (GPL) |
|---|
| 67 | # |
|---|
| 68 | # This code is distributed in the hope that it will be useful, |
|---|
| 69 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
|---|
| 70 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|---|
| 71 | # General Public License for more details. |
|---|
| 72 | # |
|---|
| 73 | # The full text of the GPL is available at: |
|---|
| 74 | # |
|---|
| 75 | # http://www.gnu.org/licenses/ |
|---|
| 76 | #***************************************************************************** |
|---|
| 77 | |
|---|
| 78 | from sage.structure.element import RingElement |
|---|
| 79 | from expect import console, Expect, ExpectElement, FunctionElement |
|---|
| 80 | PROMPT = ">>>" |
|---|
| 81 | |
|---|
| 82 | class Magma(Expect): |
|---|
| 83 | """ |
|---|
| 84 | Interface to the Magma interpreter. |
|---|
| 85 | """ |
|---|
| 86 | def __init__(self, maxread=10000, script_subdirectory="user", logfile=None, server=None): |
|---|
| 87 | Expect.__init__(self, |
|---|
| 88 | name = "magma", |
|---|
| 89 | prompt = ">", |
|---|
| 90 | command = "magma -b -n", |
|---|
| 91 | maxread = maxread, |
|---|
| 92 | server = server, |
|---|
| 93 | script_subdirectory = script_subdirectory, |
|---|
| 94 | restart_on_ctrlc = True, |
|---|
| 95 | logfile = logfile, |
|---|
| 96 | eval_using_file_cutoff=100) |
|---|
| 97 | # We use "-n" above in the Magma startup command so |
|---|
| 98 | # local user startup changes are not read. |
|---|
| 99 | |
|---|
| 100 | self.__seq = 0 |
|---|
| 101 | |
|---|
| 102 | def __reduce__(self): |
|---|
| 103 | return reduce_load_Magma, tuple([]) |
|---|
| 104 | |
|---|
| 105 | def _read_in_file_command(self, filename): |
|---|
| 106 | return 'load "%s";'%filename |
|---|
| 107 | |
|---|
| 108 | def eval(self, x): |
|---|
| 109 | x = str(x).rstrip() |
|---|
| 110 | if len(x) == 0 or x[len(x) - 1] != ';': |
|---|
| 111 | x += ';' |
|---|
| 112 | ans = Expect.eval(self, x).replace('\\\n','') |
|---|
| 113 | if ans.find("Runtime error") != -1: |
|---|
| 114 | raise RuntimeError, "Error evaluation Magma code.\nIN:%s\nOUT:%s"%(x, ans) |
|---|
| 115 | return ans |
|---|
| 116 | |
|---|
| 117 | def _start(self): |
|---|
| 118 | self._change_prompt('>') |
|---|
| 119 | Expect._start(self) |
|---|
| 120 | self.eval('SetPrompt("%s"); SetLineEditor(false); SetColumns(0);'%PROMPT) |
|---|
| 121 | self._change_prompt(PROMPT) |
|---|
| 122 | self.expect().expect(PROMPT) |
|---|
| 123 | self.expect().expect(PROMPT) |
|---|
| 124 | |
|---|
| 125 | def set(self, var, value): |
|---|
| 126 | """ |
|---|
| 127 | Set the variable var to the given value. |
|---|
| 128 | """ |
|---|
| 129 | out = self.eval("%s := %s"%(var, value)) |
|---|
| 130 | if out.lower().find("error") != -1: |
|---|
| 131 | raise TypeError, "Error executing Magma code:\n%s"%out |
|---|
| 132 | |
|---|
| 133 | def get(self, var): |
|---|
| 134 | """ |
|---|
| 135 | Get the value of the variable var. |
|---|
| 136 | """ |
|---|
| 137 | return self.eval("%s"%var) |
|---|
| 138 | |
|---|
| 139 | #def clear(self, var): |
|---|
| 140 | # """ |
|---|
| 141 | # Clear the variable named var. |
|---|
| 142 | # """ |
|---|
| 143 | #self.eval("delete %s"%var) |
|---|
| 144 | # self.eval("%s:=0"%var) |
|---|
| 145 | |
|---|
| 146 | def attach(self, filename): |
|---|
| 147 | self.eval('Attach("%s")'%filename) |
|---|
| 148 | |
|---|
| 149 | def _next_var_name(self): |
|---|
| 150 | if self.__seq == 0: |
|---|
| 151 | self.eval('sage := [* *];') |
|---|
| 152 | else: |
|---|
| 153 | self.eval('Append(~sage, 0);') |
|---|
| 154 | self.__seq += 1 |
|---|
| 155 | return 'sage[%s]'%self.__seq |
|---|
| 156 | |
|---|
| 157 | def function_call(self, function, args=[], params={}, nvals=1): |
|---|
| 158 | if not isinstance(args, list): |
|---|
| 159 | args = [args] |
|---|
| 160 | for i in range(len(args)): |
|---|
| 161 | if not isinstance(args[i], ExpectElement): |
|---|
| 162 | args[i] = self(args[i]) |
|---|
| 163 | nvals = int(nvals) |
|---|
| 164 | if len(params) == 0: |
|---|
| 165 | par = '' |
|---|
| 166 | else: |
|---|
| 167 | par = ' : ' + ','.join(['%s:=%s'%(a,b) for a,b in params.items()]) |
|---|
| 168 | |
|---|
| 169 | fun = "%s(%s%s)"%(function, ",".join([s.name() for s in args]), par) |
|---|
| 170 | if nvals <= 0: |
|---|
| 171 | out = self.eval(fun) |
|---|
| 172 | ans = None |
|---|
| 173 | elif nvals == 1: |
|---|
| 174 | return self(fun) |
|---|
| 175 | else: |
|---|
| 176 | v = [self._next_var_name() for _ in range(nvals)] |
|---|
| 177 | vars = ", ".join(v) |
|---|
| 178 | cmd = "%s := %s;"%(vars, fun) |
|---|
| 179 | out = self.eval(cmd) |
|---|
| 180 | ans = tuple([MagmaElement(self, x, is_name = True) for x in v]) |
|---|
| 181 | |
|---|
| 182 | if out.lower().find("error") != -1: |
|---|
| 183 | raise TypeError, "Error executing Magma code:\n%s"%out |
|---|
| 184 | return ans |
|---|
| 185 | |
|---|
| 186 | #def new(self, x): |
|---|
| 187 | # if isinstance(x, MagmaElement) and x.parent() == self: |
|---|
| 188 | # return x |
|---|
| 189 | # return MagmaElement(self, x) |
|---|
| 190 | |
|---|
| 191 | def _object_class(self): |
|---|
| 192 | return MagmaElement |
|---|
| 193 | |
|---|
| 194 | def _left_list_delim(self): |
|---|
| 195 | return "[*" |
|---|
| 196 | |
|---|
| 197 | def _right_list_delim(self): |
|---|
| 198 | return "*]" |
|---|
| 199 | |
|---|
| 200 | def console(self): |
|---|
| 201 | magma_console() |
|---|
| 202 | |
|---|
| 203 | def version(self): |
|---|
| 204 | return magma_version() |
|---|
| 205 | |
|---|
| 206 | class MagmaFunctionElement(FunctionElement): |
|---|
| 207 | def __call__(self, *args, **kwds): |
|---|
| 208 | nvals = 1 |
|---|
| 209 | if len(kwds) > 0: |
|---|
| 210 | if kwds.has_key('nvals'): |
|---|
| 211 | nvals = kwds['nvals'] |
|---|
| 212 | del kwds['nvals'] |
|---|
| 213 | M = self._obj.parent() |
|---|
| 214 | return M.function_call(self._name, |
|---|
| 215 | [self._obj.name()] + list(args), |
|---|
| 216 | params = kwds, |
|---|
| 217 | nvals = nvals) |
|---|
| 218 | |
|---|
| 219 | def help(self): |
|---|
| 220 | M = self._obj.parent() |
|---|
| 221 | t = str(self.Type()) |
|---|
| 222 | return M(self._name) |
|---|
| 223 | |
|---|
| 224 | |
|---|
| 225 | class MagmaElement(ExpectElement): |
|---|
| 226 | def __getattr__(self, attrname): |
|---|
| 227 | return MagmaFunctionElement(self, attrname) |
|---|
| 228 | |
|---|
| 229 | def methods(self): |
|---|
| 230 | return self.parent()('ListSignatures(%s)'%self.Type()) |
|---|
| 231 | |
|---|
| 232 | |
|---|
| 233 | |
|---|
| 234 | |
|---|
| 235 | magma = Magma() |
|---|
| 236 | |
|---|
| 237 | def reduce_load_Magma(): |
|---|
| 238 | return magma |
|---|
| 239 | |
|---|
| 240 | def magma_console(): |
|---|
| 241 | console('magma') |
|---|
| 242 | |
|---|
| 243 | def magma_version(): |
|---|
| 244 | t = tuple([int(n) for n in magma.eval('GetVersion()').split()]) |
|---|
| 245 | return t, 'V%s.%s-%s'%t |
|---|