passagemath-fricas 10.5.32__cp39-cp39-musllinux_1_2_x86_64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,2186 @@
1
+ # sage_setup: distribution = sagemath-fricas
2
+ # sage.doctest: optional - fricas
3
+ r"""
4
+ Interface to FriCAS
5
+
6
+ .. TODO::
7
+
8
+ - some conversions in ``sage.functions`` are still missing and
9
+ all should be checked and tested
10
+
11
+ FriCAS is a free GPL-compatible (modified BSD license) general
12
+ purpose computer algebra system based on Axiom. The FriCAS
13
+ website can be found at http://fricas.sourceforge.net/.
14
+
15
+ AUTHORS:
16
+
17
+ - Mike Hansen (2009-02): Split off the FriCAS interface from
18
+ the Axiom interface.
19
+
20
+ - Martin Rubey, Bill Page (2016-08): Completely separate from Axiom,
21
+ implement more complete translation from FriCAS to SageMath types.
22
+
23
+ EXAMPLES::
24
+
25
+ sage: fricas('3 * 5')
26
+ 15
27
+ sage: a = fricas(3) * fricas(5); a
28
+ 15
29
+
30
+ The type of a is :class:`FriCASElement`, i.e., an element of the
31
+ FriCAS interpreter::
32
+
33
+ sage: type(a)
34
+ <class 'sage.interfaces.fricas.FriCASElement'>
35
+ sage: a.parent()
36
+ FriCAS
37
+
38
+ The underlying FriCAS type of a is also available, via the type
39
+ method::
40
+
41
+ sage: a.typeOf()
42
+ PositiveInteger
43
+
44
+ FriCAS objects are normally displayed using "ASCII art"::
45
+
46
+ sage: fricas(2/3)
47
+ 2
48
+ -
49
+ 3
50
+ sage: fricas('x^2 + 3/7')
51
+ 2 3
52
+ x + -
53
+ 7
54
+
55
+ Functions defined in FriCAS are available as methods of the :class:`fricas<FriCAS>` object::
56
+
57
+ sage: F = fricas.factor('x^5 - y^5'); F
58
+ 4 3 2 2 3 4
59
+ - (y - x)(y + x y + x y + x y + x )
60
+ sage: type(F)
61
+ <class 'sage.interfaces.fricas.FriCASElement'>
62
+ sage: F.typeOf()
63
+ Factored(Polynomial(Integer))
64
+
65
+ We can also create a FriCAS polynomial and apply the function
66
+ ``factor`` from FriCAS. The notation ``f.factor()`` is consistent
67
+ with how the rest of SageMath works::
68
+
69
+ sage: f = fricas('x^5 - y^5')
70
+ sage: f^2
71
+ 10 5 5 10
72
+ y - 2 x y + x
73
+ sage: f.factor()
74
+ 4 3 2 2 3 4
75
+ - (y - x)(y + x y + x y + x y + x )
76
+
77
+ For many FriCAS types, translation to an appropriate SageMath type is
78
+ available::
79
+
80
+ sage: f.factor().sage()
81
+ (y - x) * (y^4 + y^3*x + y^2*x^2 + y*x^3 + x^4)
82
+
83
+ Control-C interruption works well with the FriCAS interface. For
84
+ example, try the following sum but with a much bigger range, and hit
85
+ control-C::
86
+
87
+ sage: f = fricas('(x^5 - y^5)^10000') # not tested
88
+ Interrupting FriCAS...
89
+ ...
90
+ KeyboardInterrupt: Ctrl-c pressed while running FriCAS
91
+
92
+ Let us demonstrate some features of FriCAS. FriCAS can guess a
93
+ differential equation for the generating function for integer
94
+ partitions::
95
+
96
+ sage: fricas("guessADE([partition n for n in 0..40], homogeneous==4)")
97
+ [
98
+ [
99
+ n
100
+ [x ]f(x):
101
+ 2 3 (iv) 2 2 , 3 ,,,
102
+ x f(x) f (x) + (20 x f(x) f (x) + 5 x f(x) )f (x)
103
+ <BLANKLINE>
104
+ +
105
+ 2 2 ,, 2
106
+ - 39 x f(x) f (x)
107
+ <BLANKLINE>
108
+ +
109
+ 2 , 2 2 , 3 ,, 2 , 4
110
+ (12 x f(x)f (x) - 15 x f(x) f (x) + 4 f(x) )f (x) + 6 x f (x)
111
+ <BLANKLINE>
112
+ +
113
+ , 3 2 , 2
114
+ 10 x f(x)f (x) - 16 f(x) f (x)
115
+ <BLANKLINE>
116
+ =
117
+ 0
118
+ ,
119
+ 2 3 4
120
+ f(x) = 1 + x + 2 x + 3 x + O(x )]
121
+ ]
122
+
123
+ FriCAS can solve linear ordinary differential equations::
124
+
125
+ sage: fricas.set("y", "operator y")
126
+ sage: fricas.set("deq", "x^3*D(y x, x, 3) + x^2*D(y x, x, 2) - 2*x*D(y x, x) + 2*y x - 2*x^4")
127
+ sage: fricas.set("sol", "solve(deq, y, x)"); fricas("sol")
128
+ 5 3 2
129
+ x - 10 x + 20 x + 4
130
+ [particular = ----------------------,
131
+ 15 x
132
+ 3 2 3 3 2
133
+ 2 x - 3 x + 1 x - 1 x - 3 x - 1
134
+ basis = [---------------, ------, -------------]]
135
+ x x x
136
+
137
+ sage: fricas("sol.particular").sage()
138
+ 1/15*(x^5 - 10*x^3 + 20*x^2 + 4)/x
139
+ sage: fricas("sol.basis").sage()
140
+ [(2*x^3 - 3*x^2 + 1)/x, (x^3 - 1)/x, (x^3 - 3*x^2 - 1)/x]
141
+ sage: fricas.eval(")clear values y deq sol")
142
+ ''
143
+
144
+ FriCAS can expand expressions into series::
145
+
146
+ sage: x = var('x'); ex = sqrt(cos(x)); a = fricas(ex).series(x=0); a
147
+ 1 2 1 4 19 6 559 8 29161 10 11
148
+ 1 - - x - -- x - ---- x - ------ x - --------- x + O(x )
149
+ 4 96 5760 645120 116121600
150
+
151
+ sage: a.coefficients()[38].sage()
152
+ -29472026335337227150423659490832640468979/274214482066329363682430667508979749984665600000000
153
+
154
+ sage: ex = sqrt(atan(x)); a = fricas(ex).series(x=0); a
155
+ 1 5 9
156
+ - - -
157
+ 2 1 2 31 2 6
158
+ x - - x + --- x + O(x )
159
+ 6 360
160
+
161
+ sage: a.coefficient(9/2).sage()
162
+ 31/360
163
+
164
+ sage: x = fricas("x::TaylorSeries Fraction Integer")
165
+ sage: y = fricas("y::TaylorSeries Fraction Integer")
166
+ sage: 2*(1+2*x+sqrt(1-4*x)-2*x*y).recip()
167
+ 2 3 2 2 3 4 4 5
168
+ 1 + (x y + x ) + 2 x + (x y + 2 x y + 6 x ) + (4 x y + 18 x )
169
+ +
170
+ 3 3 4 2 5 6 5 2 6 7
171
+ (x y + 3 x y + 13 x y + 57 x ) + (6 x y + 40 x y + 186 x )
172
+ +
173
+ 4 4 5 3 6 2 7 8
174
+ (x y + 4 x y + 21 x y + 130 x y + 622 x )
175
+ +
176
+ 6 3 7 2 8 9
177
+ (8 x y + 66 x y + 432 x y + 2120 x )
178
+ +
179
+ 5 5 6 4 7 3 8 2 9 10
180
+ (x y + 5 x y + 30 x y + 220 x y + 1466 x y + 7338 x ) + O(11)
181
+
182
+ FriCAS does some limits right::
183
+
184
+ sage: x = var('x'); ex = x^2*exp(-x)*Ei(x) - x; fricas(ex).limit(x=oo)
185
+ 1
186
+ """
187
+
188
+ ###########################################################################
189
+ # Copyright (C) 2008 Mike Hansen <mhansen@gmail.com>
190
+ # 2007 Bill Page
191
+ # 2006 William Stein <wstein@gmail.com>
192
+ #
193
+ # Distributed under the terms of the GNU General Public License (GPL)
194
+ # The full text of the GPL is available at:
195
+ #
196
+ # https://www.gnu.org/licenses/
197
+ ###########################################################################
198
+
199
+ import re
200
+ import os
201
+
202
+ import sage.interfaces.abc
203
+
204
+ from sage.interfaces.tab_completion import ExtraTabCompletion
205
+ from sage.interfaces.expect import Expect, ExpectElement, FunctionElement, ExpectFunction
206
+ from sage.env import DOT_SAGE, LOCAL_IDENTIFIER
207
+ from sage.misc.instancedoc import instancedoc
208
+ from sage.rings.integer_ring import ZZ
209
+ from sage.rings.rational_field import QQ
210
+ from sage.misc.lazy_import import lazy_import
211
+ lazy_import('sage.symbolic.expression', ['symbol_table', 'register_symbol'])
212
+ lazy_import('sage.calculus.var', ['var', 'function'])
213
+ lazy_import('sage.symbolic.constants', ['I', 'e', 'pi'])
214
+
215
+ FRICAS_CONSTANTS = {'%i': I,
216
+ '%e': e,
217
+ '%pi': pi}
218
+
219
+ FRICAS_SINGLE_LINE_START = 3 # where output starts when it fits next to the line number
220
+ FRICAS_MULTI_LINE_START = 2 # and when it doesn't
221
+ FRICAS_LINE_LENGTH = 80 # length of a line, should match the line length in sage
222
+ # the following messages have, unfortunately, no markup.
223
+ FRICAS_WHAT_OPERATIONS_STRING = r"Operations whose names satisfy the above pattern\(s\):"
224
+ FRICAS_ERROR_IN_LIBRARY_CODE = ">> Error detected within library code:"
225
+
226
+ # only the last command should be necessary to make the interface
227
+ # work, the other are optimizations. Beware that lisp distinguishes
228
+ # between ' and ".
229
+ FRICAS_INIT_CODE = (
230
+ ")set functions compile on",
231
+ ")set message any on",
232
+ ")set message autoload off",
233
+ ")set message bottomup off",
234
+ ")set message dropmap off",
235
+ ")set message expose off",
236
+ ")set message file off",
237
+ ")set message frame off",
238
+ ")set message highlighting off",
239
+ ")set message instant off",
240
+ ")set message insteach off",
241
+ ")set message interponly off",
242
+ ")set message prompt step",
243
+ ")set message selection off",
244
+ ")set message set off",
245
+ ")set message startup on",
246
+ ")set message storage off",
247
+ ")set message testing off",
248
+ ")set message time off",
249
+ ")set message type off",
250
+ ")set message void off",
251
+ ")set output length " + str(FRICAS_LINE_LENGTH),
252
+ ")lisp (setf |$ioHook|"
253
+ " (lambda (x &optional args)"
254
+ " (when (member x '(|startAlgebraOutput| |endOfAlgebraOutput|"
255
+ " |startKeyedMsg| |endOfKeyedMsg|))"
256
+ " (prin1 x)"
257
+ " (princ #\\Newline))))")
258
+ # code (one-liners!) executed after having set up the prompt
259
+ FRICAS_HELPER_CODE = (
260
+ 'sageprint(x:InputForm):String == ' +
261
+ '(atom? x => (' +
262
+ 'float? x => return float(x)::String;' +
263
+ 'integer? x => return integer(x)::String;' +
264
+ 'string? x => return concat(["_"", string(x)::String, "_""])$String;' +
265
+ 'symbol? x => return string(symbol(x)));' +
266
+ 'S: List String := [sageprint y for y in destruct x];' +
267
+ 'R: String := new(1 + reduce(_+, [1 + #(s)$String for s in S], 0),' +
268
+ 'space()$Character);' +
269
+ 'copyInto!(R, "(", 1);' +
270
+ 'i := 2;' +
271
+ 'for s in S repeat'
272
+ '(copyInto!(R, s, i); i := i + 1 + #(s)$String);' +
273
+ 'copyInto!(R, ")", i-1);' +
274
+ 'return R)',)
275
+
276
+ FRICAS_LINENUMBER_OFF_CODE = ")lisp (setf |$IOindex| NIL)"
277
+ FRICAS_FIRST_PROMPT = r"\(1\) -> "
278
+ FRICAS_LINENUMBER_OFF_PROMPT = r"\(NIL\) -> "
279
+
280
+
281
+ class FriCAS(ExtraTabCompletion, Expect):
282
+ """
283
+ Interface to a FriCAS interpreter.
284
+ """
285
+ def __init__(self, name='fricas', command=None,
286
+ script_subdirectory=None, logfile=None,
287
+ server=None, server_tmpdir=None):
288
+ """
289
+ Create an instance of the FriCAS interpreter.
290
+
291
+ TESTS::
292
+
293
+ sage: fricas == loads(dumps(fricas))
294
+ True
295
+
296
+ Check that :issue:`25174` is fixed::
297
+
298
+ sage: fricas(I)
299
+ %i
300
+
301
+ sage: integrate(sin(x)*exp(I*x), x, -pi, 0, algorithm='fricas')
302
+ 1/2*I*pi
303
+
304
+ sage: fricas(I*sin(x)).sage()
305
+ I*sin(x)
306
+
307
+ sage: fricas(I*x).sage()
308
+ I*x
309
+ """
310
+ if command is None:
311
+ from sage.features.fricas import FriCAS
312
+ command = [FriCAS(), "-nosman"]
313
+
314
+ eval_using_file_cutoff = 4096 - 5 # magic number from Expect._eval_line (there might be a bug)
315
+ assert max(len(c) for c in FRICAS_INIT_CODE) < eval_using_file_cutoff
316
+ self.__eval_using_file_cutoff = eval_using_file_cutoff
317
+ self._COMMANDS_CACHE = '%s/%s_commandlist_cache.sobj' % (DOT_SAGE, name)
318
+ # we run the init code in _start to avoid spurious output
319
+ Expect.__init__(self,
320
+ name=name,
321
+ prompt=FRICAS_FIRST_PROMPT,
322
+ command=command,
323
+ script_subdirectory=script_subdirectory,
324
+ server=server,
325
+ server_tmpdir=server_tmpdir,
326
+ restart_on_ctrlc=False,
327
+ verbose_start=False,
328
+ init_code=[],
329
+ logfile=logfile,
330
+ eval_using_file_cutoff=eval_using_file_cutoff)
331
+
332
+ def _start(self):
333
+ """
334
+ Start the FriCAS interpreter and switch off the linenumbers.
335
+
336
+ EXAMPLES::
337
+
338
+ sage: a = FriCAS()
339
+ sage: a.is_running()
340
+ False
341
+ sage: a._start()
342
+ sage: a.is_running()
343
+ True
344
+ sage: a.quit()
345
+ """
346
+ # setting the prompt properly is necessary for restarting FriCAS
347
+ self._prompt = FRICAS_FIRST_PROMPT
348
+ Expect._start(self)
349
+ for line in FRICAS_INIT_CODE:
350
+ self.eval(line, reformat=False)
351
+ # switching off the line numbers also modified the prompt
352
+ self._prompt = FRICAS_LINENUMBER_OFF_PROMPT
353
+ self.eval(FRICAS_LINENUMBER_OFF_CODE, reformat=False)
354
+ for line in FRICAS_HELPER_CODE:
355
+ self.eval(line, reformat=False)
356
+ # register translations between SymbolicRing and FriCAS Expression
357
+ self._register_symbols()
358
+
359
+ def _install_hints(self):
360
+ """
361
+ Hints for installing FriCAS on your computer.
362
+
363
+ EXAMPLES::
364
+
365
+ sage: print(fricas._install_hints())
366
+ In order...
367
+ """
368
+ return r"""
369
+ In order to use the FriCAS interface you need to have FriCAS installed.
370
+ You can either run 'sage -i fricas' to install FriCAS as an optional
371
+ package within SageMath, or install FriCAS separately, see
372
+ http://fricas.sourceforge.net.
373
+ """
374
+
375
+ def _quit_string(self):
376
+ """
377
+ Return the string used to quit FriCAS.
378
+
379
+ EXAMPLES::
380
+
381
+ sage: fricas._quit_string()
382
+ ')quit'
383
+ sage: a = FriCAS()
384
+ sage: a.is_running()
385
+ False
386
+ sage: a._start()
387
+ sage: a.is_running()
388
+ True
389
+ sage: a.quit()
390
+ sage: a.is_running()
391
+ False
392
+
393
+ TESTS:
394
+
395
+ Ensure that a new process is started after ``quit()``::
396
+
397
+ sage: p = fricas.pid()
398
+ sage: fricas.quit()
399
+ sage: fricas.pid() == p
400
+ False
401
+ """
402
+ return ')quit'
403
+
404
+ def _commands(self):
405
+ """
406
+ Return a list of commands available. This is done by parsing the
407
+ result of the first section of the output of ')what things'.
408
+
409
+ EXAMPLES::
410
+
411
+ sage: cmds = fricas._commands()
412
+ sage: len(cmds) > 100
413
+ True
414
+ sage: '<' in cmds
415
+ True
416
+ sage: 'factor' in cmds
417
+ True
418
+ """
419
+ output = self.eval(")what operations", reformat=False)
420
+ m = re.search(FRICAS_WHAT_OPERATIONS_STRING + r"\n(.*)\n\|startKeyedMsg\|",
421
+ output, flags=re.DOTALL)
422
+ l = m.groups()[0].split()
423
+ return l
424
+
425
+ def _tab_completion(self, verbose=True, use_disk_cache=True):
426
+ """
427
+ Return a list of all the commands defined in Fricas and optionally
428
+ (per default) store them to disk.
429
+
430
+ EXAMPLES::
431
+
432
+ sage: c = fricas._tab_completion(use_disk_cache=False, verbose=False)
433
+ sage: len(c) > 100
434
+ True
435
+ sage: 'factor' in c
436
+ True
437
+ sage: '**' in c
438
+ False
439
+ sage: 'upperCase?' in c
440
+ False
441
+ sage: 'upperCase_q' in c
442
+ True
443
+ sage: 'upperCase_e' in c
444
+ True
445
+ """
446
+ try:
447
+ return self.__tab_completion
448
+ except AttributeError:
449
+ import sage.misc.persist
450
+ if use_disk_cache:
451
+ try:
452
+ self.__tab_completion = sage.misc.persist.load(self._COMMANDS_CACHE)
453
+ return self.__tab_completion
454
+ except OSError:
455
+ pass
456
+ if verbose:
457
+ print("\nBuilding %s command completion list (this takes" % self)
458
+ print("a few seconds only the first time you do it).")
459
+ print("To force rebuild later, delete %s." % self._COMMANDS_CACHE)
460
+ v = self._commands()
461
+
462
+ # process the commands to strip out things which are not
463
+ # valid Python identifiers
464
+ valid = re.compile('[^a-zA-Z0-9_]+')
465
+ names = [x for x in v if valid.search(x) is None]
466
+
467
+ # replace trailing ? with _q and trailing ! with _e
468
+ names += [x[:-1] + "_q" for x in v if x.endswith("?")]
469
+ names += [x[:-1] + "_e" for x in v if x.endswith("!")]
470
+
471
+ self.__tab_completion = names
472
+ if len(v) > 200:
473
+ # Fricas is actually installed.
474
+ sage.misc.persist.save(v, self._COMMANDS_CACHE)
475
+ return names
476
+
477
+ def _read_in_file_command(self, filename):
478
+ """
479
+ Return the FriCAS command to read the file ``filename``.
480
+
481
+ INPUT:
482
+
483
+ - ``filename`` -- string ending in '.input'.
484
+
485
+ OUTPUT: string with the command for reading filename without output
486
+
487
+ TESTS:
488
+
489
+ Evaluate a rather long line::
490
+
491
+ sage: len(fricas([i for i in range(600)])) # indirect doctest
492
+ 600
493
+ """
494
+ if not filename.endswith('.input'):
495
+ raise ValueError("the filename must end with .input")
496
+
497
+ return ')read %s )quiet' % filename
498
+
499
+ def _remote_tmpfile(self):
500
+ """
501
+ Return a remote tmpfile ending with ".input" used to buffer long
502
+ command lines sent to FriCAS.
503
+ """
504
+ try:
505
+ return self.__remote_tmpfile
506
+ except AttributeError:
507
+ self.__remote_tmpfile = self._remote_tmpdir() + "/interface_%s:%s.input" % (LOCAL_IDENTIFIER, self.pid())
508
+ return self.__remote_tmpfile
509
+
510
+ # what I expect from FriCAS:
511
+
512
+ # 1.) in set(self, var, value)
513
+ #
514
+ # no markers:
515
+ # there could be some "debugging" output, as in fricas("guessADE([1,1,1,1], debug==true)")
516
+ #
517
+ # startKeyedMsg: an error happened
518
+ #
519
+ # 2.) in get(self, var)
520
+ #
521
+ # |startAlgebraOutput\|...|endOfAlgebraOutput\|
522
+ #
523
+ # 3.) I also need a routine to send a system command and get its output.
524
+
525
+ def _check_errors(self, line, output):
526
+ """
527
+ Check whether output contains an error and, if so, raise it.
528
+
529
+ INPUT:
530
+
531
+ - ``line`` -- string that was sent to FriCAS
532
+
533
+ - ``output`` -- string returned by FriCAS
534
+
535
+ OUTPUT: none
536
+
537
+ TESTS::
538
+
539
+ sage: fricas.set("x", "[i fo83r i in 0..17]") # indirect doctest
540
+ Traceback (most recent call last):
541
+ ...
542
+ RuntimeError: An error occurred when FriCAS evaluated '[i fo83r i in 0..17]':
543
+ Line 1: x:=[i fo83r i in 0..17];
544
+ ...A..........B
545
+ Error A: Missing mate.
546
+ Error B: syntax error at top level
547
+ Error B: Possibly missing a ]
548
+ 3 error(s) parsing
549
+
550
+ sage: fricas.set("x", "something stupid") # indirect doctest
551
+ Traceback (most recent call last):
552
+ ...
553
+ RuntimeError: An error occurred when FriCAS evaluated 'something stupid':
554
+ There are no library operations named something
555
+ Use HyperDoc Browse or issue
556
+ )what op something
557
+ to learn if there is any operation containing " something " in its
558
+ name.
559
+ <BLANKLINE>
560
+ Cannot find a definition or applicable library operation named
561
+ something with argument type(s)
562
+ Variable(stupid)
563
+ <BLANKLINE>
564
+ Perhaps you should use "@" to indicate the required return type, or
565
+ "$" to specify which version of the function you need.
566
+ """
567
+ # otherwise there might be a message
568
+ m = re.search(r"\|startKeyedMsg\|\n(.*)\n\|endOfKeyedMsg\|",
569
+ output, flags=re.DOTALL)
570
+ if m:
571
+ replacements = [('|startKeyedMsg|\n', ''),
572
+ ('|endOfKeyedMsg|', '')]
573
+ for old, new in replacements:
574
+ output = output.replace(old, new)
575
+ raise RuntimeError("An error occurred when FriCAS evaluated '%s':\n%s" % (line, output))
576
+
577
+ # or even an error
578
+ if FRICAS_ERROR_IN_LIBRARY_CODE in output:
579
+ raise RuntimeError("An error occurred when FriCAS evaluated '%s':\n%s" % (line, output))
580
+
581
+ @staticmethod
582
+ def _register_symbols():
583
+ """
584
+ Register translations between elements of the ``SymbolicRing`` and FriCAS ``Expression`` domain.
585
+
586
+ This method is called from :meth:`_start`, to work around a
587
+ circular import problem involving ``pi``.
588
+ """
589
+ from sage.calculus.functional import diff
590
+ from sage.functions.log import dilog, lambert_w
591
+ from sage.functions.trig import sin, cos, tan, cot, sec, csc, asin
592
+ from sage.functions.hyperbolic import tanh, sinh, cosh, coth, sech, csch
593
+ from sage.functions.other import abs
594
+ from sage.functions.gamma import gamma
595
+ from sage.functions.special import elliptic_e, elliptic_f
596
+ from sage.misc.functional import symbolic_sum, symbolic_prod
597
+ from sage.rings.infinity import infinity
598
+ register_symbol(pi, {'fricas': 'pi'}, 0) # %pi::INFORM is %pi, but (pi) also exists
599
+ register_symbol(lambda: infinity, {'fricas': 'infinity'}, 0) # %infinity::INFORM is (infinity)
600
+ register_symbol(lambda: infinity, {'fricas': 'plusInfinity'}, 0) # %plusInfinity::INFORM is (plusInfinity)
601
+ register_symbol(lambda: -infinity, {'fricas': 'minusInfinity'}, 0) # %minusInfinity::INFORM is (minusInfinity)
602
+ register_symbol(cos, {'fricas': 'cos'})
603
+ register_symbol(sin, {'fricas': 'sin'})
604
+ register_symbol(tan, {'fricas': 'tan'})
605
+ register_symbol(cot, {'fricas': 'cot'})
606
+ register_symbol(sec, {'fricas': 'sec'})
607
+ register_symbol(csc, {'fricas': 'csc'})
608
+ register_symbol(tanh, {'fricas': 'tanh'})
609
+ register_symbol(sinh, {'fricas': 'sinh'})
610
+ register_symbol(cosh, {'fricas': 'cosh'})
611
+ register_symbol(coth, {'fricas': 'coth'})
612
+ register_symbol(sech, {'fricas': 'sech'})
613
+ register_symbol(csch, {'fricas': 'csch'})
614
+ register_symbol(gamma, {'fricas': 'Gamma'}, 1)
615
+ register_symbol(gamma, {'fricas': 'Gamma'}, 2)
616
+ register_symbol(lambda x, y: elliptic_e(asin(x), y), {'fricas': 'ellipticE'}, 2)
617
+ register_symbol(lambda x, y: elliptic_f(asin(x), y), {'fricas': 'ellipticF'}, 2)
618
+ register_symbol(lambda x, y: x + y, {'fricas': '+'}, 2)
619
+ register_symbol(lambda x, y: x - y, {'fricas': '-'}, 2)
620
+ register_symbol(lambda x, y: x * y, {'fricas': '*'}, 2)
621
+ register_symbol(lambda x, y: x / y, {'fricas': '/'}, 2)
622
+ register_symbol(lambda x, y: x ** y, {'fricas': '^'}, 2)
623
+ register_symbol(lambda f, x: diff(f, x), {'fricas': 'D'}, 2)
624
+ register_symbol(lambda x, y: x + y * I, {'fricas': 'complex'}, 2)
625
+ register_symbol(lambda x: dilog(1 - x), {'fricas': 'dilog'}, 1)
626
+ register_symbol(lambda z: lambert_w(z), {'fricas': 'lambertW'}, 1)
627
+ register_symbol(abs, {'fricas': 'abs'}, 1)
628
+ # construct occurs in the InputForm of hypergeometricF
629
+ register_symbol(lambda *x: x, {'fricas': 'construct'}, -1)
630
+ # the following is a hack to deal with
631
+ # integrate(sin((x^2+1)/x),x)::INFORM giving
632
+ # (integral (sin (/ (+ (^ x 2) 1) x)) (:: x Symbol))
633
+ register_symbol(lambda x, y: x, {'fricas': '::'}, 2)
634
+
635
+ def _convert_eval(f, a, b):
636
+ # it might be that FriCAS also returns a two-argument
637
+ # eval, where the second argument is a list of equations,
638
+ # in which case this function needs to be adapted
639
+ return f.subs({a: b})
640
+
641
+ register_symbol(_convert_eval, {'fricas': 'eval'}, 3)
642
+
643
+ def _convert_sum(x, y):
644
+ v, seg = y.operands()
645
+ a, b = seg.operands()
646
+ return symbolic_sum(x, v, a, b)
647
+
648
+ def _convert_prod(x, y):
649
+ v, seg = y.operands()
650
+ a, b = seg.operands()
651
+ return symbolic_prod(x, v, a, b)
652
+
653
+ register_symbol(_convert_sum, {'fricas': 'sum'}, 2)
654
+ register_symbol(_convert_prod, {'fricas': 'product'}, 2)
655
+
656
+ def explicitly_not_implemented(*args):
657
+ raise NotImplementedError("the translation of the FriCAS Expression '%s' to sage is not yet implemented" % args)
658
+
659
+ register_symbol(lambda *args: explicitly_not_implemented("rootOfADE"), {'fricas': 'rootOfADE'}, 2) # to be removed once we fully on FriCAS 1.3.10+
660
+ register_symbol(lambda *args: explicitly_not_implemented("FEseries"), {'fricas': 'FEseries'}, 2)
661
+ register_symbol(lambda *args: explicitly_not_implemented("rootOfRec"), {'fricas': 'rootOfRec'}, 2)
662
+
663
+ def set(self, var, value):
664
+ """
665
+ Set a variable to a value in FriCAS.
666
+
667
+ INPUT:
668
+
669
+ - ``var``, ``value`` -- strings; the first representing a valid
670
+ FriCAS variable identifier, the second a FriCAS expression
671
+
672
+ OUTPUT: none
673
+
674
+ EXAMPLES::
675
+
676
+ sage: fricas.set('xx', '2')
677
+ sage: fricas.get('xx')
678
+ '2'
679
+ """
680
+ cmd = '%s%s%s;' % (var, self._assign_symbol(), value)
681
+ output = self.eval(cmd, reformat=False)
682
+ self._check_errors(value, output)
683
+
684
+ def get(self, var):
685
+ r"""
686
+ Get the string representation of the value (more precisely, the
687
+ OutputForm) of a variable or expression in FriCAS.
688
+
689
+ If FriCAS cannot evaluate `var` an error is raised.
690
+
691
+ EXAMPLES::
692
+
693
+ sage: fricas.set('xx', '2')
694
+ sage: fricas.get('xx')
695
+ '2'
696
+ sage: a = fricas('(1 + sqrt(2))^5')
697
+ sage: fricas.get(a.name())
698
+ ' +-+\n29 \\|2 + 41'
699
+ sage: fricas.get('(1 + sqrt(2))^5')
700
+ ' +-+\n29 \\|2 + 41'
701
+ sage: fricas.new('(1 + sqrt(2))^5')
702
+ +-+
703
+ 29 \|2 + 41
704
+ """
705
+ output = self.eval(str(var), reformat=False)
706
+ # if there is AlgebraOutput we ask no more
707
+ m = re.search(r"\|startAlgebraOutput\|\n(.*)\n\|endOfAlgebraOutput\|",
708
+ output, flags=re.DOTALL)
709
+ if m:
710
+ lines = m.groups()[0].split("\n")
711
+ if max(len(line) for line in lines) < FRICAS_LINE_LENGTH:
712
+ return "\n".join(line[FRICAS_SINGLE_LINE_START:] for line in lines)
713
+ else:
714
+ return "\n".join(line[FRICAS_MULTI_LINE_START:] for line in lines)
715
+
716
+ self._check_errors(var, output)
717
+
718
+ def get_string(self, var):
719
+ """
720
+ Return the value of a FriCAS string as a string, without checking
721
+ that it is a string.
722
+
723
+ TESTS:
724
+
725
+ We test that strings are returned properly::
726
+
727
+ sage: r = fricas.get_string('concat([concat(string(i)," ") for i in 0..299])')
728
+ sage: r == " ".join(str(i) for i in range(300)) + ' '
729
+ True
730
+
731
+ sage: fricas.get_string('concat([string(1) for i in 1..5])') == "1"*5
732
+ True
733
+
734
+ sage: fricas.get_string('concat([string(1) for i in 1..10000])') == "1"*10000
735
+ True
736
+
737
+ A problem with leading space::
738
+
739
+ sage: s = "unparse((-1234567890123456789012345678901234567890123456789012345678901234567890*n::EXPR INT)::INFORM)"
740
+ sage: fricas.get_string(s)
741
+ '(-1234567890123456789012345678901234567890123456789012345678901234567890)*n'
742
+
743
+ Check that :issue:`25628` is fixed::
744
+
745
+ sage: var("a b"); f = 1/(1+a*cos(x))
746
+ (a, b)
747
+ sage: lF = integrate(f, x, algorithm='fricas')
748
+ sage: (diff(lF[0], x) - f).simplify_trig()
749
+ 0
750
+ sage: (diff(lF[1], x) - f).simplify_trig()
751
+ 0
752
+ sage: f = 1/(b*x^2+a); lF = integrate(f, x, algorithm='fricas'); lF
753
+ [1/2*log((2*a*b*x + (b*x^2 - a)*sqrt(-a*b))/(b*x^2 + a))/sqrt(-a*b),
754
+ arctan(sqrt(a*b)*x/a)/sqrt(a*b)]
755
+ sage: (diff(lF[0], x) - f).simplify_trig()
756
+ 0
757
+ sage: (diff(lF[1], x) - f).simplify_trig()
758
+ 0
759
+ """
760
+ # strip removes leading and trailing whitespace, after that
761
+ # we can assume that the first and the last character are
762
+ # double quotes
763
+ return self.get(str(var)).replace("\n", "").strip()[1:-1]
764
+
765
+ def get_integer(self, var):
766
+ """
767
+ Return the value of a FriCAS integer as an integer, without
768
+ checking that it is an integer.
769
+
770
+ TESTS::
771
+
772
+ sage: fricas.get_integer('factorial 1111') == factorial(1111)
773
+ True
774
+ """
775
+ return int(self.get_unparsed_InputForm(str(var)))
776
+
777
+ def get_boolean(self, var):
778
+ """
779
+ Return the value of a FriCAS boolean as a boolean, without checking
780
+ that it is a boolean.
781
+
782
+ TESTS::
783
+
784
+ sage: fricas.get_boolean('(1=1)::Boolean') == True
785
+ True
786
+
787
+ sage: fricas.get_boolean('(1=2)::Boolean') == False
788
+ True
789
+ """
790
+ return self.get(str(var)).replace("\n", "") == "true"
791
+
792
+ def get_unparsed_InputForm(self, var):
793
+ """
794
+ Return the unparsed ``InputForm`` as a string.
795
+
796
+ .. TODO::
797
+
798
+ - catch errors, especially when InputForm is not available:
799
+
800
+ - for example when integration returns ``'failed'``
801
+
802
+ - ``UnivariatePolynomial``
803
+
804
+ - should we provide workarounds, too?
805
+
806
+ TESTS::
807
+
808
+ sage: fricas.get_unparsed_InputForm('1..3')
809
+ '(1..3)$Segment(PositiveInteger())'
810
+ """
811
+ return self.get_string('unparse((%s)::InputForm)' % var)
812
+
813
+ def get_InputForm(self, var):
814
+ """
815
+ Return the ``InputForm`` as a string.
816
+
817
+ TESTS::
818
+
819
+ sage: fricas.get_InputForm('1..3')
820
+ '(($elt (Segment (PositiveInteger)) SEGMENT) 1 3)'
821
+ """
822
+ return self.get_string('sageprint((%s)::InputForm)' % str(var))
823
+
824
+ def _assign_symbol(self):
825
+ """
826
+ Return the symbol used for setting a variable in FriCAS.
827
+
828
+ EXAMPLES::
829
+
830
+ sage: fricas.set("x", "1"); # indirect doctest
831
+ sage: fricas.get("x")
832
+ '1'
833
+ sage: fricas.eval(")cl val x")
834
+ ''
835
+ """
836
+ return ":="
837
+
838
+ def _equality_symbol(self):
839
+ """
840
+ Return the equality testing logical symbol in FriCAS.
841
+
842
+ EXAMPLES::
843
+
844
+ sage: a = fricas(x==6); a # indirect doctest
845
+ x = 6
846
+
847
+ A warning::
848
+
849
+ sage: fricas.set("x", 2);
850
+ sage: a = fricas(x==6); a
851
+ 2 = 6
852
+ sage: fricas.eval(")cl val x")
853
+ ''
854
+ """
855
+ return "="
856
+
857
+ def _true_symbol(self):
858
+ """
859
+ Return the string used for ``True`` in FriCAS.
860
+
861
+ EXAMPLES::
862
+
863
+ sage: str(fricas("(1=1)@Boolean")) == fricas._true_symbol()
864
+ True
865
+ """
866
+ return "true"
867
+
868
+ def _false_symbol(self):
869
+ """
870
+ Return the string used for ``False`` in FriCAS.
871
+
872
+ EXAMPLES::
873
+
874
+ sage: str(fricas("(1~=1)@Boolean")) == fricas._false_symbol()
875
+ True
876
+ """
877
+ return "false"
878
+
879
+ def _inequality_symbol(self):
880
+ """
881
+ Return the string used for False in FriCAS.
882
+
883
+ EXAMPLES::
884
+
885
+ sage: fricas(x!=0) # indirect doctest
886
+ true
887
+ """
888
+ return '~='
889
+
890
+ def _repr_(self):
891
+ """
892
+ EXAMPLES::
893
+
894
+ sage: fricas # indirect doctest
895
+ FriCAS
896
+ """
897
+ return "FriCAS"
898
+
899
+ def __reduce__(self):
900
+ """
901
+ EXAMPLES::
902
+
903
+ sage: FriCAS().__reduce__()
904
+ (<function reduce_load_fricas at 0x...>, ())
905
+ sage: f, args = _
906
+ sage: f(*args)
907
+ FriCAS
908
+ """
909
+ return reduce_load_fricas, tuple([])
910
+
911
+ def eval(self, code, strip=True, synchronize=False, locals=None, allow_use_file=True,
912
+ split_lines='nofile', reformat=True, **kwds):
913
+ """
914
+ Evaluate ``code`` using FriCAS.
915
+
916
+ Except ``reformat``, all arguments are passed to
917
+ :meth:`sage.interfaces.expect.Expect.eval`.
918
+
919
+ INPUT:
920
+
921
+ - ``reformat`` -- boolean; remove the output markers when True
922
+
923
+ This can also be used to pass system commands to FriCAS.
924
+
925
+ EXAMPLES::
926
+
927
+ sage: fricas.set("x", "1783"); fricas("x")
928
+ 1783
929
+ sage: fricas.eval(")cl val x");
930
+ ''
931
+ sage: fricas("x")
932
+ x
933
+ """
934
+ output = Expect.eval(self, code, strip=strip,
935
+ synchronize=synchronize, locals=locals,
936
+ allow_use_file=allow_use_file, split_lines=split_lines,
937
+ **kwds)
938
+ # we remove carriage returns (\r) to make parsing easier
939
+ # they are sent depending on how fricas was invoked:
940
+ # on linux, "fricas -nox -noclef" sends "\r\n" and "fricas -nosman" sends "\n"
941
+ output = output.replace('\r', '')
942
+ if reformat:
943
+ replacements = [('|startAlgebraOutput|\n', ''),
944
+ ('|endOfAlgebraOutput|', ''),
945
+ ('|startKeyedMsg|\n', ''),
946
+ ('|endOfKeyedMsg|', '')]
947
+ for old, new in replacements:
948
+ output = output.replace(old, new)
949
+
950
+ return output
951
+
952
+ def _function_class(self):
953
+ """
954
+ Return the FriCASExpectFunction class.
955
+
956
+ EXAMPLES::
957
+
958
+ sage: fricas._function_class()
959
+ <class 'sage.interfaces.fricas.FriCASExpectFunction'>
960
+ sage: type(fricas.gcd)
961
+ <class 'sage.interfaces.fricas.FriCASExpectFunction'>
962
+ """
963
+ return FriCASExpectFunction
964
+
965
+ def _object_class(self):
966
+ """
967
+ EXAMPLES::
968
+
969
+ sage: fricas._object_class()
970
+ <class 'sage.interfaces.fricas.FriCASElement'>
971
+ sage: type(fricas(2))
972
+ <class 'sage.interfaces.fricas.FriCASElement'>
973
+ """
974
+ return FriCASElement
975
+
976
+ def _function_element_class(self):
977
+ """
978
+ Return the FriCAS function element class.
979
+
980
+ EXAMPLES::
981
+
982
+ sage: fricas._function_element_class()
983
+ <class 'sage.interfaces.fricas.FriCASFunctionElement'>
984
+ sage: type(fricas(2).gcd)
985
+ <class 'sage.interfaces.fricas.FriCASFunctionElement'>
986
+ """
987
+ return FriCASFunctionElement
988
+
989
+ def console(self):
990
+ """
991
+ Spawn a new FriCAS command-line session.
992
+
993
+ EXAMPLES::
994
+
995
+ sage: fricas.console() # not tested
996
+ FriCAS (AXIOM fork) Computer Algebra System
997
+ Version: FriCAS 1.0.5
998
+ Timestamp: Thursday February 19, 2009 at 06:57:33
999
+ -----------------------------------------------------------------------------
1000
+ Issue )copyright to view copyright notices.
1001
+ Issue )summary for a summary of useful system commands.
1002
+ Issue )quit to leave AXIOM and return to shell.
1003
+ -----------------------------------------------------------------------------
1004
+ """
1005
+ fricas_console()
1006
+
1007
+
1008
+ @instancedoc
1009
+ class FriCASElement(ExpectElement, sage.interfaces.abc.FriCASElement):
1010
+ """
1011
+ Instances of this class represent objects in FriCAS.
1012
+
1013
+ Using the method :meth:`sage` we can translate some of them to
1014
+ SageMath objects:
1015
+
1016
+ .. automethod:: _sage_
1017
+ """
1018
+ def __len__(self):
1019
+ """
1020
+ Return the length of a list.
1021
+
1022
+ EXAMPLES::
1023
+
1024
+ sage: v = fricas('[x^i for i in 0..5]')
1025
+ sage: len(v)
1026
+ 6
1027
+
1028
+ TESTS:
1029
+
1030
+ Streams are not handled yet::
1031
+
1032
+ sage: oh = fricas('[i for i in 1..]')
1033
+ sage: len(oh)
1034
+ Traceback (most recent call last):
1035
+ ...
1036
+ TypeError: ...
1037
+ """
1038
+ P = self._check_valid()
1039
+ l = P('#(%s)' % self._name)
1040
+ return l.sage()
1041
+
1042
+ def __iter__(self):
1043
+ """
1044
+ Return an iterator over ``self``.
1045
+
1046
+ EXAMPLES::
1047
+
1048
+ sage: L = fricas([4,5,6])
1049
+ sage: list(L)
1050
+ [4, 5, 6]
1051
+
1052
+ TESTS:
1053
+
1054
+ Streams are not handled yet::
1055
+
1056
+ sage: oh = fricas('[i for i in 1..]')
1057
+ sage: next(iter(oh)) # known bug
1058
+ """
1059
+ for i in range(len(self)):
1060
+ yield self[i]
1061
+
1062
+ def __getitem__(self, n):
1063
+ """
1064
+ We implement the sage conventions here, translating to 0-based iterables.
1065
+
1066
+ We do not check validity, since many objects in FriCAS are
1067
+ iterable, in particular Streams
1068
+
1069
+ TESTS::
1070
+
1071
+ sage: fricas("[1,2,3]")[0]
1072
+ 1
1073
+
1074
+ Negative indices do work::
1075
+
1076
+ sage: fricas("[1,2,3]")[-1]
1077
+ 3
1078
+
1079
+ sage: fricas("[1,2,3]")[-2]
1080
+ 2
1081
+
1082
+ Invalid indices raise exceptions::
1083
+
1084
+ sage: fricas("[1,2,3]")[3]
1085
+ Traceback (most recent call last):
1086
+ ...
1087
+ TypeError: An error occurred when FriCAS evaluated 'elt(...,...)':
1088
+ <BLANKLINE>
1089
+ >> Error detected within library code:
1090
+ index out of range
1091
+
1092
+ And streams are ok too::
1093
+
1094
+ sage: oh = fricas('[i for i in 1..]')
1095
+ sage: oh[4]
1096
+ 5
1097
+ """
1098
+ n = int(n)
1099
+ P = self._check_valid()
1100
+ if n < -1:
1101
+ try:
1102
+ l = len(self)
1103
+ except TypeError:
1104
+ raise
1105
+ else:
1106
+ n += l
1107
+ if not (0 <= n < l):
1108
+ raise IndexError("index out of range")
1109
+ # use "elt" instead of "." here because then the error
1110
+ # message is clearer
1111
+ if n == -1:
1112
+ return P.new("elt(%s,last)" % (self._name))
1113
+ return P.new("elt(%s,%s)" % (self._name, n + 1))
1114
+
1115
+ def __int__(self):
1116
+ """
1117
+ TESTS::
1118
+
1119
+ sage: int(fricas(2))
1120
+ 2
1121
+ """
1122
+ return int(self.sage())
1123
+
1124
+ def bool(self):
1125
+ """
1126
+ Coerce the expression into a boolean.
1127
+
1128
+ EXAMPLES::
1129
+
1130
+ sage: fricas("1=1").bool()
1131
+ True
1132
+ sage: fricas("1~=1").bool()
1133
+ False
1134
+ """
1135
+ P = self._check_valid()
1136
+ return P.new(self._name + "::Boolean").sage()
1137
+
1138
+ def __bool__(self):
1139
+ """
1140
+ Check whether the expression is different from zero.
1141
+
1142
+ EXAMPLES::
1143
+
1144
+ sage: fricas(0).is_zero() # indirect doctest
1145
+ True
1146
+ """
1147
+ P = self._check_valid()
1148
+ return not P.new("zero?(%s)" % self._name).sage()
1149
+
1150
+ def __float__(self):
1151
+ """
1152
+ TESTS::
1153
+
1154
+ sage: float(fricas(2))
1155
+ 2.0
1156
+ """
1157
+ return float(self.sage())
1158
+
1159
+ def _integer_(self, ZZ=None):
1160
+ """
1161
+ EXAMPLES::
1162
+
1163
+ sage: ZZ(fricas('1'))
1164
+ 1
1165
+ """
1166
+ return ZZ(self.sage())
1167
+
1168
+ def _rational_(self):
1169
+ """
1170
+ EXAMPLES::
1171
+
1172
+ sage: QQ(fricas('-1/2'))
1173
+ -1/2
1174
+ """
1175
+ return QQ(self.sage())
1176
+
1177
+ def gen(self, n):
1178
+ """
1179
+ Return an error, since the n-th generator in FriCAS is not well defined.
1180
+ """
1181
+ raise NotImplementedError
1182
+
1183
+ def _latex_(self):
1184
+ r"""
1185
+ EXAMPLES::
1186
+
1187
+ sage: latex(fricas("sin(x+y)/exp(z)*log(1+%e)"))
1188
+ \frac{{{\log \left( {{e+1}} \right)} \ {\sin \left( {{y+x}} \right)}}}{{{e} ^{z}}}
1189
+
1190
+ sage: latex(fricas("matrix([[1,2],[3,4]])"))
1191
+ \left[ \begin{array}{cc} 1 & 2 \\ 3 & 4...\end{array}...\right]
1192
+
1193
+ sage: latex(fricas("integrate(sin(x+1/x),x)"))
1194
+ \int ^{\displaystyle x} {{\sin \left( {{\frac{{{{ \%...} ^{2}}+1}}{ \%...}}} \right)} \ {d \%...}}
1195
+ """
1196
+ replacements = [(r'\sp ', '^'),
1197
+ (r'\sp{', '^{'),
1198
+ (r'\sb ', '_'),
1199
+ (r'\sb{', '_{')]
1200
+ P = self._check_valid()
1201
+ s = P.get_string("latex(%s)" % self._name)
1202
+ for old, new in replacements:
1203
+ s = s.replace(old, new)
1204
+ return s
1205
+
1206
+ def _get_sage_type(self, domain):
1207
+ """
1208
+ INPUT:
1209
+
1210
+ - ``domain`` -- a FriCAS SExpression
1211
+
1212
+ OUTPUT: a corresponding Sage type
1213
+
1214
+ EXAMPLES::
1215
+
1216
+ sage: m = fricas("dom(1/2)::Any")
1217
+ sage: fricas(0)._get_sage_type(m)
1218
+ Rational Field
1219
+
1220
+ TESTS::
1221
+
1222
+ sage: m = fricas("UP(y, UP(x, AN))::INFORM")
1223
+ sage: fricas(0)._get_sage_type(m)
1224
+ Univariate Polynomial Ring in y over Univariate Polynomial Ring in x over Algebraic Field
1225
+ """
1226
+ from sage.rings.qqbar import QQbar
1227
+ from sage.rings.real_double import RDF
1228
+ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
1229
+ from sage.rings.fraction_field import FractionField
1230
+ from sage.rings.finite_rings.integer_mod_ring import Integers
1231
+ from sage.rings.finite_rings.finite_field_constructor import FiniteField
1232
+ from sage.rings.real_mpfr import RealField
1233
+ from sage.symbolic.ring import SR
1234
+
1235
+ # first implement domains without arguments
1236
+ head = str(domain.car())
1237
+ if head in ["Integer", "NonNegativeInteger", "PositiveInteger"]:
1238
+ return ZZ
1239
+ if head == "String":
1240
+ return str
1241
+ if head == "Float":
1242
+ P = self._check_valid()
1243
+ prec = max(P.new("length mantissa(%s)" % self._name).sage(), 53)
1244
+ return RealField(prec)
1245
+ if head == "DoubleFloat":
1246
+ return RDF
1247
+ if head == "AlgebraicNumber":
1248
+ return QQbar
1249
+
1250
+ # now implement "functorial" types
1251
+ if head == "OrderedCompletion" or head == "Complex":
1252
+ # this is a workaround, I don't know how translate this
1253
+ return SR
1254
+
1255
+ if head == "IntegerMod":
1256
+ return Integers(domain[1].integer().sage())
1257
+
1258
+ if head == "PrimeField":
1259
+ return FiniteField(domain[1].integer().sage())
1260
+
1261
+ if head == "Fraction":
1262
+ return FractionField(self._get_sage_type(domain[1]))
1263
+
1264
+ if head == "Expression":
1265
+ return SR
1266
+
1267
+ if head == "Polynomial":
1268
+ # this is a workaround, since in sage we always have to specify the variables
1269
+ return SR
1270
+
1271
+ if head == "UnivariatePolynomial":
1272
+ var = str(domain[1])
1273
+ return PolynomialRing(self._get_sage_type(domain[2]), var)
1274
+
1275
+ raise NotImplementedError("the translation of FriCAS type %s to sage is not yet implemented" % domain)
1276
+
1277
+ _WHITESPACE = " "
1278
+ _LEFTBRACKET = "("
1279
+ _RIGHTBRACKET = ")"
1280
+ _STRINGMARKER = '"'
1281
+ _ESCAPEMARKER = '_' # STRINGMARKER must be escaped in strings
1282
+
1283
+ @staticmethod
1284
+ def _parse_and_eval(s, start=0):
1285
+ """
1286
+ Parse and evaluate the string.
1287
+
1288
+ INPUT:
1289
+
1290
+ - ``s`` -- string
1291
+ - ``start`` -- integer; specifies where to start parsing
1292
+
1293
+ OUTPUT:
1294
+
1295
+ - a pair ``(L, end)``, where ``L`` is the parsed list and
1296
+ ``end`` is the position of the last parsed letter.
1297
+
1298
+ TESTS::
1299
+
1300
+ sage: from sage.interfaces.fricas import FriCASElement
1301
+ sage: FriCASElement._parse_and_eval("abc")
1302
+ (abc, 2)
1303
+
1304
+ sage: FriCASElement._parse_and_eval("(asin c)")
1305
+ (arcsin(c), 7)
1306
+
1307
+ sage: N(FriCASElement._parse_and_eval("(pi)")[0])
1308
+ 3.14159265358979
1309
+
1310
+ sage: FriCASElement._parse_and_eval('(a "(b c)")')
1311
+ Traceback (most recent call last):
1312
+ ...
1313
+ TypeError: cannot coerce arguments: no canonical coercion from <class 'str'> to Symbolic Ring
1314
+ """
1315
+ a = start
1316
+ while s[a] in FriCASElement._WHITESPACE:
1317
+ a += 1
1318
+
1319
+ if s[a] == FriCASElement._LEFTBRACKET:
1320
+ return FriCASElement._parse_list(s, start=a)
1321
+ elif s[a] == FriCASElement._STRINGMARKER:
1322
+ return FriCASElement._parse_string(s, start=a)
1323
+ else:
1324
+ return FriCASElement._parse_other(s, start=a)
1325
+
1326
+ @staticmethod
1327
+ def _parse_list(s, start=0):
1328
+ """
1329
+ Parse the initial part of a string, assuming that it is a
1330
+ whitespace separated list, treating its first element as
1331
+ function and the rest as arguments.
1332
+
1333
+ INPUT:
1334
+
1335
+ - ``s`` -- string
1336
+ - ``start`` -- integer; specifies the position of the left
1337
+ bracket
1338
+
1339
+ TESTS::
1340
+
1341
+ sage: from sage.interfaces.fricas import FriCASElement
1342
+ sage: FriCASElement._parse_list("()")
1343
+ Traceback (most recent call last):
1344
+ ...
1345
+ TypeError: 'tuple' object is not callable
1346
+
1347
+ sage: FriCASElement._parse_list("(a b c)")
1348
+ (a(b, c), 6)
1349
+
1350
+ sage: FriCASElement._parse_list('(bcd)')
1351
+ (bcd(), 4)
1352
+ """
1353
+ a = start
1354
+ assert s[a] == FriCASElement._LEFTBRACKET
1355
+ a += 1
1356
+ # the first element of a list must be a function call
1357
+ fun_string, a = FriCASElement._parse_other(s, start=a, make_fun=True)
1358
+ a += 1
1359
+ # now parse the arguments
1360
+ args = []
1361
+ while s[a] != FriCASElement._RIGHTBRACKET:
1362
+ e, a = FriCASElement._parse_and_eval(s, start=a)
1363
+ args.append(e)
1364
+ a += 1
1365
+ # then lookup for the function with the given number of arguments
1366
+ try:
1367
+ fun = symbol_table["fricas"][(fun_string, len(args))]
1368
+ except KeyError:
1369
+ try:
1370
+ # to handle the case of "construct"
1371
+ fun = symbol_table["fricas"][(fun_string, -1)]
1372
+ except KeyError:
1373
+ fun = function(fun_string)
1374
+ return fun(*args), a
1375
+
1376
+ @staticmethod
1377
+ def _parse_other(s, start=0, make_fun=False):
1378
+ """
1379
+ Parse the initial part of a string, assuming that it is an
1380
+ atom, but not a string.
1381
+
1382
+ Symbols and numbers must not contain ``FriCASElement._WHITESPACE`` and
1383
+ ``FriCASElement._RIGHTBRACKET``.
1384
+
1385
+ INPUT:
1386
+
1387
+ - ``s`` -- string
1388
+ - ``start`` -- integer; specifies where the symbol begins
1389
+ - ``make_fun`` -- boolean (default: ``False``); whether the atom should
1390
+ be interpreted as a function call
1391
+
1392
+ TESTS::
1393
+
1394
+ sage: from sage.interfaces.fricas import FriCASElement
1395
+ sage: FriCASElement._parse_other("abc")
1396
+ (abc, 2)
1397
+ sage: FriCASElement._parse_other("123 xyz")
1398
+ (123, 2)
1399
+ sage: FriCASElement._parse_other("abc -1.23", 4)
1400
+ (-1.23, 8)
1401
+
1402
+ This function cannot use the symbol table to translate
1403
+ symbols which are not function calls, as :issue:`31849` shows
1404
+ ``D`` would erroneously be interpreted as differential
1405
+ then::
1406
+
1407
+ sage: var("D")
1408
+ D
1409
+ sage: integrate(D/x, x, algorithm='fricas')
1410
+ D*log(x)
1411
+
1412
+ However, it does have to check for constants, for example
1413
+ ``%pi``::
1414
+
1415
+ sage: FriCASElement._parse_other("%pi")
1416
+ (pi, 2)
1417
+ """
1418
+ a = start
1419
+ b = len(s)
1420
+ while a < b and s[a] not in FriCASElement._WHITESPACE and s[a] != FriCASElement._RIGHTBRACKET:
1421
+ a += 1
1422
+
1423
+ e = s[start:a]
1424
+ if make_fun:
1425
+ pass
1426
+ else:
1427
+ try:
1428
+ e = ZZ(e)
1429
+ except TypeError:
1430
+ try:
1431
+ e = float(e)
1432
+ except ValueError:
1433
+ try:
1434
+ e = FRICAS_CONSTANTS[e]
1435
+ except KeyError:
1436
+ e = var(e.replace("%", "_"))
1437
+ return e, a - 1
1438
+
1439
+ @staticmethod
1440
+ def _parse_string(s, start=0):
1441
+ r"""
1442
+ Parse the initial part of a string, assuming that it represents a
1443
+ string.
1444
+
1445
+ INPUT:
1446
+
1447
+ - ``s`` -- string
1448
+ - ``start`` -- integer; specifies the position of the left
1449
+ quote
1450
+
1451
+ TESTS::
1452
+
1453
+ sage: from sage.interfaces.fricas import FriCASElement
1454
+ sage: FriCASElement._parse_string('"abc" 123')
1455
+ ('abc', 4)
1456
+
1457
+ sage: FriCASElement._parse_string('"" 123')
1458
+ ('', 1)
1459
+
1460
+ sage: FriCASElement._parse_string('"____" 123')
1461
+ ('__', 5)
1462
+
1463
+ sage: FriCASElement._parse_string('"_a" 123')
1464
+ ('a', 3)
1465
+
1466
+ sage: FriCASElement._parse_string('"_" _"" 123')
1467
+ ('" "', 6)
1468
+
1469
+ sage: FriCASElement._parse_string('"(b c)"')
1470
+ ('(b c)', 6)
1471
+ """
1472
+ a = start
1473
+ assert s[a] == FriCASElement._STRINGMARKER
1474
+ b = a + 1
1475
+ a = b
1476
+ S = []
1477
+ while s[b] != FriCASElement._STRINGMARKER:
1478
+ if s[b] == FriCASElement._ESCAPEMARKER:
1479
+ S.append(s[a:b])
1480
+ b += 1
1481
+ a = b
1482
+ b += 1
1483
+ S.append(s[a:b])
1484
+ return "".join(S), b
1485
+
1486
+ @staticmethod
1487
+ def _sage_expression(fricas_InputForm):
1488
+ r"""
1489
+ Convert an expression to an element of the Symbolic Ring.
1490
+
1491
+ INPUT:
1492
+
1493
+ - fricas_InputForm, a string, the InputForm of a FriCAS expression.
1494
+
1495
+ TESTS::
1496
+
1497
+ sage: f = fricas('integrate(sin(x^2), x)'); f
1498
+ +---+
1499
+ | 2
1500
+ fresnelS(x |--- )
1501
+ \|%pi
1502
+ -----------------
1503
+ +---+
1504
+ | 2
1505
+ |---
1506
+ \|%pi
1507
+ sage: s = fricas.get_InputForm(f._name); s
1508
+ '(/ (fresnelS (* x (^ (/ 2 (pi)) (/ 1 2)))) (^ (/ 2 (pi)) (/ 1 2)))'
1509
+ sage: from sage.interfaces.fricas import FriCASElement
1510
+ sage: FriCASElement._sage_expression(s)
1511
+ 1/2*sqrt(2)*sqrt(pi)*fresnel_sin(sqrt(2)*x/sqrt(pi))
1512
+
1513
+ Check that :issue:`22525` is fixed::
1514
+
1515
+ sage: l = [sin, cos, sec, csc, cot, tan, asin, acos, atan, acot, acsc, asec, arcsin, arccos, arctan, arccot, arccsc, arcsec]
1516
+ sage: [f(x)._fricas_().sage().subs(x=0.9) for f in l]
1517
+ [0.783326909627483,
1518
+ 0.621609968270664,
1519
+ 1.60872581046605,
1520
+ 1.27660621345890,
1521
+ 0.793551147842317,
1522
+ 1.26015821755034,
1523
+ 1.11976951499863,
1524
+ 0.451026811796262,
1525
+ 0.732815101786507,
1526
+ 0.837981225008390,
1527
+ 1.57079632679490 - 0.467145308103262*I,
1528
+ 0.467145308103262*I,
1529
+ 1.11976951499863,
1530
+ 0.451026811796262,
1531
+ 0.732815101786507,
1532
+ 0.837981225008390,
1533
+ 1.57079632679490 - 0.467145308103262*I,
1534
+ 0.467145308103262*I]
1535
+ sage: l = [tanh, sinh, cosh, coth, sech, csch, asinh, acosh, atanh, acoth, asech, acsch, arcsinh, arccosh, arctanh, arccoth, arcsech, arccsch]
1536
+ sage: [f(x)._fricas_().sage().subs(x=0.9) for f in l]
1537
+ [0.716297870199024,
1538
+ 1.02651672570818,
1539
+ 1.43308638544877,
1540
+ 1.39606725303001,
1541
+ 0.697794641100332,
1542
+ 0.974168247780004,
1543
+ 0.808866935652782,
1544
+ 0.451026811796262*I,
1545
+ 1.47221948958322,
1546
+ 1.47221948958322 - 1.57079632679490*I,
1547
+ 0.467145308103262,
1548
+ 0.957800449200672,
1549
+ 0.808866935652782,
1550
+ 0.451026811796262*I,
1551
+ 1.47221948958322,
1552
+ 1.47221948958322 - 1.57079632679490*I,
1553
+ 0.467145308103262,
1554
+ 0.957800449200672]
1555
+
1556
+ Check that :issue:`23782` is fixed::
1557
+
1558
+ sage: s = '((3*n^10-25*n^9+50*n^8+62*n^7-229*n^6-25*n^5+320*n^4-12*n^3-144*n^2)/11520)::EXPR INT'
1559
+ sage: fricas(s).sage()
1560
+ 1/3840*n^10 - 5/2304*n^9 + 5/1152*n^8 + 31/5760*n^7 - 229/11520*n^6 - 5/2304*n^5 + 1/36*n^4 - 1/960*n^3 - 1/80*n^2
1561
+
1562
+ Some checks for digamma and polygamma (:issue:`31853`)::
1563
+
1564
+ sage: fricas.digamma(1.0)
1565
+ - 0.5772156649_0153286061
1566
+ sage: psi(1.0)
1567
+ -0.577215664901533
1568
+ sage: fricas.polygamma(1, 1.0)
1569
+ 1.644934066848226...
1570
+ sage: psi(1, 1).n()
1571
+ 1.64493406684823
1572
+
1573
+ sage: var("w")
1574
+ w
1575
+ sage: fricas.laplace(log(x), x, w).sage()
1576
+ -(euler_gamma + log(w))/w
1577
+ sage: fricas(laplace(log(x), x, w)).sage()
1578
+ -(euler_gamma + log(w))/w
1579
+
1580
+ Check that :issue:`25224` is fixed::
1581
+
1582
+ sage: integrate(log(x)/(1-x),x,algorithm='fricas')
1583
+ dilog(-x + 1)
1584
+ sage: fricas(dilog(-x + 1))
1585
+ dilog(x)
1586
+ sage: dilog._fricas_()(1.0)
1587
+ 1.6449340668_4822643647_24152
1588
+ sage: dilog(1.0)
1589
+ 1.64493406684823
1590
+
1591
+ Check that :issue:`25987` is fixed::
1592
+
1593
+ sage: integrate(lambert_w(x), x, algorithm='fricas')
1594
+ (x*lambert_w(x)^2 - x*lambert_w(x) + x)/lambert_w(x)
1595
+
1596
+ Check that :issue:`25838` is fixed::
1597
+
1598
+ sage: F = function('f'); f = SR.var('f')
1599
+ sage: FF = fricas(F(f)); FF
1600
+ f(f)
1601
+ sage: FF.D(f).sage()
1602
+ diff(f(f), f)
1603
+ sage: bool(FF.D(f).integrate(f).sage() == F(f))
1604
+ True
1605
+
1606
+ Check that :issue:`25602` is fixed::
1607
+
1608
+ sage: r = fricas.integrate(72000/(1+x^5), x).sage()
1609
+ sage: abs(n(r.subs(x=5) - r.subs(x=3)) - 193.020947266210) <= 0.1
1610
+ True
1611
+
1612
+ sage: var("a"); r = fricas.integrate(72000*a^8/(a^5+x^5), x).sage()
1613
+ a
1614
+ sage: abs(n(r.subs(a=1, x=5) - r.subs(a=1, x=3)) - 193.020947266268) <= 0.1
1615
+ True
1616
+
1617
+ Check conversions of sums and products::
1618
+
1619
+ sage: var("k, m, n")
1620
+ (k, m, n)
1621
+ sage: fricas("sum(1/factorial(k), k=1..n)").sage()
1622
+ sum(1/factorial(_...), _..., 1, n)
1623
+ sage: fricas("eval(sum(x/k, k=1..n), x=k)").sage()
1624
+ k*harmonic_number(n)
1625
+ sage: fricas("product(1/factorial(k), k=1..n)").sage()
1626
+ 1/product(factorial(_...), _..., 1, n)
1627
+
1628
+ sage: f = fricas.guess([sum(1/k, k,1,n) for n in range(10)])[0]; f
1629
+ n - 1
1630
+ --+ 1
1631
+ > -------
1632
+ --+ s + 1
1633
+ s = 0 10
1634
+ 10
1635
+
1636
+ sage: f.sage()
1637
+ harmonic_number(n)
1638
+
1639
+ sage: f = fricas.guess([0, 1, 3, 9, 33])[0]; f
1640
+ s - 1
1641
+ n - 1 5
1642
+ --+ ++-++
1643
+ > | | p + 2
1644
+ --+ | | 4
1645
+ s = 0 p = 0
1646
+ 5 4
1647
+
1648
+ sage: f.sage()
1649
+ sum(factorial(_... + 1), _..., 0, n - 1)
1650
+
1651
+ Check that :issue:`26746` is fixed::
1652
+
1653
+ sage: _ = var('x, y, z')
1654
+ sage: f = sin(x^2) + y^z
1655
+ sage: f.integrate(x, algorithm='fricas')
1656
+ 1/2*sqrt(2)*sqrt(pi)*(sqrt(2)*x*y^z/sqrt(pi) + fresnel_sin(sqrt(2)*x/sqrt(pi)))
1657
+
1658
+ sage: fricas(fresnel_sin(1))
1659
+ fresnelS(1)
1660
+ sage: fricas("fresnelS(1.0)")
1661
+ 0.4382591473_9035476607_676
1662
+
1663
+ sage: fricas(fresnel_cos(1))
1664
+ fresnelC(1)
1665
+ sage: fricas("fresnelC(1.0)")
1666
+ 0.7798934003_7682282947_42
1667
+
1668
+ Check that :issue:`17908` is fixed::
1669
+
1670
+ sage: fricas(abs(x)).sage().subs(x=-1783)
1671
+ 1783
1672
+
1673
+ Check that :issue:`27310` is fixed::
1674
+
1675
+ sage: fricas.set("F", "operator 'f")
1676
+ sage: fricas("eval(D(F(x,y), [x, y], [2, 1]), x=x+y)").sage()
1677
+ D[0, 0, 1](f)(x + y, y)
1678
+
1679
+ Conversion of hypergeometric functions (:issue:`31298`)::
1680
+
1681
+ sage: a,b,c = var("a b c")
1682
+ sage: A = hypergeometric([a, b], [c], x)
1683
+ sage: fricas(A).sage() - A
1684
+ 0
1685
+ sage: fricas(A).D(x).sage() - diff(A, x)
1686
+ 0
1687
+
1688
+ Check that :issue:`31858` is fixed::
1689
+
1690
+ sage: fricas.Gamma(3/2).sage()
1691
+ 1/2*sqrt(pi)
1692
+ sage: fricas.Gamma(3/4).sage()
1693
+ gamma(3/4)
1694
+ sage: fricas.Gamma(3, 2).sage()
1695
+ gamma(3, 2)
1696
+
1697
+
1698
+ Check that :issue:`32133` is fixed::
1699
+
1700
+ sage: var("y")
1701
+ y
1702
+ sage: f = fricas.zerosOf(y^4 + y + 1, y); f
1703
+ +-----------------------------+
1704
+ | 2 2
1705
+ \|- 3 %y1 - 2 %y0 %y1 - 3 %y0 - %y1 - %y0
1706
+ [%y0, %y1, --------------------------------------------,
1707
+ 2
1708
+ +-----------------------------+
1709
+ | 2 2
1710
+ - \|- 3 %y1 - 2 %y0 %y1 - 3 %y0 - %y1 - %y0
1711
+ ----------------------------------------------]
1712
+ 2
1713
+
1714
+ sage: f[1].sage()
1715
+ -1/2*sqrt(1/3)*sqrt((3*(1/18*I*sqrt(229)*sqrt(3) + 1/2)^(2/3) + 4)/(1/18*I*sqrt(229)*sqrt(3) + 1/2)^(1/3)) + 1/2*sqrt(-(1/18*I*sqrt(229)*sqrt(3) + 1/2)^(1/3) + 6*sqrt(1/3)/sqrt((3*(1/18*I*sqrt(229)*sqrt(3) + 1/2)^(2/3) + 4)/(1/18*I*sqrt(229)*sqrt(3) + 1/2)^(1/3)) - 4/3/(1/18*I*sqrt(229)*sqrt(3) + 1/2)^(1/3))
1716
+ """
1717
+ # a FriCAS expressions may contain implicit references to a
1718
+ # rootOf expression within itself, as for example in the
1719
+ # result of integrate(1/(1+x^5), x). Each algebraic number
1720
+ # appearing in the expression is only introduced once and
1721
+ # assigned a variable (usually of the form %%...).
1722
+ rootOf = dict() # (variable, polynomial)
1723
+ rootOf_ev = dict() # variable -> (complex) algebraic number
1724
+
1725
+ def convert_rootOf(x, y):
1726
+ if y in rootOf:
1727
+ assert rootOf[y] == x
1728
+ else:
1729
+ rootOf[y] = x
1730
+ return y
1731
+
1732
+ register_symbol(convert_rootOf, {'fricas': 'rootOf'}, 2)
1733
+
1734
+ ex, _ = FriCASElement._parse_and_eval(fricas_InputForm)
1735
+ # postprocessing of rootOf
1736
+ from sage.rings.qqbar import QQbar
1737
+ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
1738
+ while rootOf:
1739
+ for var, poly in rootOf.items():
1740
+ pvars = poly.variables()
1741
+ rvars = [v for v in pvars if v not in rootOf_ev] # remaining variables
1742
+ uvars = [v for v in rvars if v in rootOf] # variables to evaluate
1743
+ if len(uvars) == 1:
1744
+ assert uvars[0] == var, "the only variable in uvars should be %s but is %s" % (var, uvars[0])
1745
+ break
1746
+ else:
1747
+ assert False, "circular dependency in rootOf expression"
1748
+ # substitute known roots
1749
+ poly = poly.subs(rootOf_ev)
1750
+ evars = set(poly.variables()).difference([var])
1751
+ del rootOf[var]
1752
+ if evars:
1753
+ # we just need any root per FriCAS specification -
1754
+ # however, if there are extra variables, we cannot
1755
+ # use QQbar.any_root
1756
+ rootOf_ev[var] = poly.roots(var, multiplicities=False)[0]
1757
+ else:
1758
+ R = PolynomialRing(QQbar, "x")
1759
+ # PolynomialRing does not accept variable names with
1760
+ # leading underscores
1761
+ poly = R(poly.subs({var: R.gen()}))
1762
+ # we just need any root per FriCAS specification
1763
+ rootOf_ev[var] = poly.any_root()
1764
+
1765
+ return ex.subs({var: (val.radical_expression()
1766
+ if val.parent() is QQbar else val)
1767
+ for var, val in rootOf_ev.items()})
1768
+
1769
+ def _sage_(self):
1770
+ r"""
1771
+ Convert ``self`` to a Sage object.
1772
+
1773
+ EXAMPLES:
1774
+
1775
+ Floats::
1776
+
1777
+ sage: fricas(2.1234).sage()
1778
+ 2.12340000000000
1779
+ sage: _.parent()
1780
+ Real Field with 53 bits of precision
1781
+ sage: a = RealField(100)(pi)
1782
+ sage: fricas(a).sage()
1783
+ 3.1415926535897932384626433833
1784
+ sage: _.parent()
1785
+ Real Field with 100 bits of precision
1786
+ sage: fricas(a).sage() == a
1787
+ True
1788
+ sage: fricas(2.0).sage()
1789
+ 2.00000000000000
1790
+ sage: _.parent()
1791
+ Real Field with 53 bits of precision
1792
+
1793
+ Algebraic numbers::
1794
+
1795
+ sage: a = fricas('(1 + sqrt(2))^5'); a
1796
+ +-+
1797
+ 29 \|2 + 41
1798
+ sage: b = a.sage(); b
1799
+ 82.0121933088198?
1800
+ sage: b.radical_expression()
1801
+ 29*sqrt(2) + 41
1802
+
1803
+ Integers modulo n::
1804
+
1805
+ sage: fricas("((42^17)^1783)::IntegerMod(5^(5^5))").sage() == Integers(5^(5^5))((42^17)^1783)
1806
+ True
1807
+
1808
+ Matrices over a prime field::
1809
+
1810
+ sage: fricas("matrix [[1::PF 3, 2],[2, 0]]").sage().parent()
1811
+ Full MatrixSpace of 2 by 2 dense matrices over Finite Field of size 3
1812
+
1813
+ We can also convert FriCAS's polynomials to Sage polynomials::
1814
+
1815
+ sage: a = fricas("x^2 + 1"); a.typeOf()
1816
+ Polynomial(Integer)
1817
+ sage: a.sage()
1818
+ x^2 + 1
1819
+ sage: _.parent()
1820
+ Univariate Polynomial Ring in x over Integer Ring
1821
+ sage: fricas('x^2 + y^2 + 1/2').sage()
1822
+ y^2 + x^2 + 1/2
1823
+ sage: _.parent()
1824
+ Multivariate Polynomial Ring in y, x over Rational Field
1825
+
1826
+ sage: fricas("1$Polynomial Integer").sage()
1827
+ 1
1828
+
1829
+ sage: fricas("x^2/2").sage()
1830
+ 1/2*x^2
1831
+
1832
+ sage: x = polygen(QQ, 'x')
1833
+ sage: fricas(x+3).sage()
1834
+ x + 3
1835
+ sage: fricas(x+3).domainOf()
1836
+ Polynomial(Integer...)
1837
+
1838
+ sage: fricas(matrix([[2,3],[4,x+5]])).diagonal().sage()
1839
+ (2, x + 5)
1840
+
1841
+ sage: f = fricas("(y^2+3)::UP(y, INT)").sage(); f
1842
+ y^2 + 3
1843
+ sage: f.parent()
1844
+ Univariate Polynomial Ring in y over Integer Ring
1845
+
1846
+ sage: fricas("(y^2+sqrt 3)::UP(y, AN)").sage()
1847
+ y^2 + 1.732050807568878?
1848
+
1849
+ Rational functions::
1850
+
1851
+ sage: fricas("x^2 + 1/z").sage()
1852
+ x^2 + 1/z
1853
+
1854
+ Expressions::
1855
+
1856
+ sage: fricas(pi).sage()
1857
+ pi
1858
+
1859
+ sage: fricas("sin(x+y)/exp(z)*log(1+%e)").sage()
1860
+ e^(-z)*log(e + 1)*sin(x + y)
1861
+
1862
+ sage: fricas("factorial(n)").sage()
1863
+ factorial(n)
1864
+
1865
+ sage: fricas("integrate(sin(x+y), x=0..1)").sage()
1866
+ -cos(y + 1) + cos(y)
1867
+
1868
+ sage: fricas("integrate(x*sin(1/x), x=0..1)").sage()
1869
+ 'failed'
1870
+
1871
+ sage: fricas("integrate(sin((x^2+1)/x),x)").sage()
1872
+ integral(sin((x^2 + 1)/x), x)
1873
+
1874
+ .. TODO::
1875
+
1876
+ - Converting matrices and lists takes much too long.
1877
+
1878
+ Matrices::
1879
+
1880
+ sage: fricas("matrix [[x^n/2^m for n in 0..5] for m in 0..3]").sage() # long time
1881
+ [ 1 x x^2 x^3 x^4 x^5]
1882
+ [ 1/2 1/2*x 1/2*x^2 1/2*x^3 1/2*x^4 1/2*x^5]
1883
+ [ 1/4 1/4*x 1/4*x^2 1/4*x^3 1/4*x^4 1/4*x^5]
1884
+ [ 1/8 1/8*x 1/8*x^2 1/8*x^3 1/8*x^4 1/8*x^5]
1885
+
1886
+ Lists::
1887
+
1888
+ sage: fricas("[2^n/x^n for n in 0..5]").sage() # long time
1889
+ [1, 2/x, 4/x^2, 8/x^3, 16/x^4, 32/x^5]
1890
+
1891
+ sage: fricas("[matrix [[i for i in 1..n]] for n in 0..5]").sage() # long time
1892
+ [[], [1], [1 2], [1 2 3], [1 2 3 4], [1 2 3 4 5]]
1893
+
1894
+ Error handling::
1895
+
1896
+ sage: s = fricas.guessPade("[fibonacci i for i in 0..10]"); s
1897
+ n x
1898
+ [[[x ]- ----------]]
1899
+ 2
1900
+ x + x - 1
1901
+ sage: s.sage()
1902
+ Traceback (most recent call last):
1903
+ ...
1904
+ NotImplementedError: the translation of the FriCAS Expression 'FEseries' to sage is not yet implemented
1905
+
1906
+ sage: s = fricas("series(sqrt(1+x), x=0)"); s
1907
+ 1 1 2 1 3 5 4 7 5 21 6 33 7 429 8
1908
+ 1 + - x - - x + -- x - --- x + --- x - ---- x + ---- x - ----- x
1909
+ 2 8 16 128 256 1024 2048 32768
1910
+ +
1911
+ 715 9 2431 10 11
1912
+ ----- x - ------ x + O(x )
1913
+ 65536 262144
1914
+
1915
+ sage: s.sage()
1916
+ Traceback (most recent call last):
1917
+ ...
1918
+ NotImplementedError: the translation of the FriCAS object
1919
+ <BLANKLINE>
1920
+ 1 1 2 1 3 5 4 7 5 21 6 33 7 429 8
1921
+ 1 + - x - - x + -- x - --- x + --- x - ---- x + ---- x - ----- x
1922
+ 2 8 16 128 256 1024 2048 32768
1923
+ +
1924
+ 715 9 2431 10 11
1925
+ ----- x - ------ x + O(x )
1926
+ 65536 262144
1927
+ <BLANKLINE>
1928
+ to sage is not yet implemented:
1929
+ An error occurred when FriCAS evaluated 'unparse(...::InputForm)':
1930
+ <BLANKLINE>
1931
+ Cannot convert the value from type Any to InputForm .
1932
+ """
1933
+ from sage.rings.polynomial.polynomial_ring_constructor import PolynomialRing
1934
+ from sage.rings.real_double import RDF
1935
+ from sage.rings.imaginary_unit import I
1936
+ from sage.rings.real_mpfr import RealField
1937
+ from sage.symbolic.ring import SR
1938
+ from sage.matrix.constructor import matrix
1939
+ from sage.modules.free_module_element import vector
1940
+ from sage.structure.factorization import Factorization
1941
+ from sage.misc.sage_eval import sage_eval
1942
+
1943
+ # TODO: perhaps we should translate the type first?
1944
+ # TODO: perhaps we should get the InputForm as SExpression?
1945
+
1946
+ # remember: fricas.new gives a FriCASElement
1947
+
1948
+ # the coercion to Any gets rid of the Union domain
1949
+ P = self._check_valid()
1950
+ domain = P.new("dom((%s)::Any)" % self._name) # domain is now a fricas SExpression
1951
+
1952
+ # first translate dummy domains such as "failed". We must
1953
+ # not recurse here!
1954
+ if P.get_boolean("string?(%s)" % domain._name):
1955
+ return P.get_string("string(%s)" % domain._name)
1956
+
1957
+ # now translate domains which cannot be coerced to InputForm,
1958
+ # or where we do not need it.
1959
+ head = str(domain.car())
1960
+ if head == "Record":
1961
+ fields = fricas("[string symbol(e.2) for e in rest destruct %s]" % domain._name).sage()
1962
+ return {field: self.elt(field).sage() for field in fields}
1963
+
1964
+ if head == "List":
1965
+ n = P.get_integer('#(%s)' % self._name)
1966
+ return [self.elt(k).sage() for k in range(1, n + 1)]
1967
+
1968
+ if head == "Vector" or head == "DirectProduct":
1969
+ n = P.get_integer('#(%s)' % self._name)
1970
+ return vector([self.elt(k).sage() for k in range(1, n + 1)])
1971
+
1972
+ if head == "Matrix":
1973
+ base_ring = self._get_sage_type(domain[1])
1974
+ rows = self.listOfLists().sage()
1975
+ return matrix(base_ring, rows)
1976
+
1977
+ if head == "Fraction":
1978
+ return self.numer().sage() / self.denom().sage()
1979
+
1980
+ if head == "Complex":
1981
+ return self.real().sage() + self.imag().sage() * I
1982
+
1983
+ if head == "Factored":
1984
+ l = P.new('[[f.factor, f.exponent] for f in factors(%s)]' % self._name).sage()
1985
+ return Factorization(list(l))
1986
+
1987
+ if head == "UnivariatePolynomial":
1988
+ base_ring = self._get_sage_type(domain[2])
1989
+ vars = str(domain[1])
1990
+ R = PolynomialRing(base_ring, vars)
1991
+ return R([self.coefficient(i).sage()
1992
+ for i in range(ZZ(self.degree()) + 1)])
1993
+
1994
+ # finally translate domains with InputForm - we do this
1995
+ # lazily, because sometimes we can use unparse, sometimes we
1996
+ # need our custom sageprint
1997
+
1998
+ def unparsed_InputForm():
1999
+ try:
2000
+ return P.get_unparsed_InputForm(self._name)
2001
+ except RuntimeError as error:
2002
+ raise NotImplementedError("the translation of the FriCAS object\n\n%s\n\nto sage is not yet implemented:\n%s" % (self, error))
2003
+
2004
+ if head == "Boolean":
2005
+ return unparsed_InputForm() == "true"
2006
+
2007
+ if head in ["Integer", "NonNegativeInteger", "PositiveInteger"]:
2008
+ return ZZ(unparsed_InputForm())
2009
+
2010
+ if head == "String":
2011
+ return unparsed_InputForm()
2012
+
2013
+ if head == "Float":
2014
+ # Warning: precision$Float gives the current precision,
2015
+ # whereas length(mantissa(self)) gives the precision of
2016
+ # self.
2017
+ prec = max(P.new("length mantissa(%s)" % self._name).sage(), 53)
2018
+ R = RealField(prec)
2019
+ x, e, b = unparsed_InputForm().lstrip('float(').rstrip(')').split(',')
2020
+ return R(ZZ(x) * ZZ(b)**ZZ(e))
2021
+
2022
+ if head == "DoubleFloat":
2023
+ return RDF(unparsed_InputForm())
2024
+
2025
+ if head == "AlgebraicNumber":
2026
+ s = unparsed_InputForm()[:-len("::AlgebraicNumber()")]
2027
+ return sage_eval("QQbar(" + s + ")")
2028
+
2029
+ if head == "IntegerMod" or head == "PrimeField":
2030
+ # one might be tempted not to go via InputForm here, but
2031
+ # it turns out to be safer to do it.
2032
+ s = unparsed_InputForm()[len("index("):]
2033
+ s = s[:s.find(")")]
2034
+ return self._get_sage_type(domain)(s)
2035
+
2036
+ if head == 'DistributedMultivariatePolynomial':
2037
+ base_ring = self._get_sage_type(domain[2])
2038
+ vars = domain[1].car()
2039
+ R = PolynomialRing(base_ring, vars)
2040
+ return R(unparsed_InputForm())
2041
+
2042
+ if head == "Polynomial":
2043
+ base_ring = self._get_sage_type(domain[1])
2044
+ # Polynomial Complex is translated into SR
2045
+ if base_ring is SR:
2046
+ return FriCASElement._sage_expression(P.get_InputForm(self._name))
2047
+
2048
+ # the following is a bad hack, we should be getting a list here
2049
+ vars = P.get_unparsed_InputForm("variables(%s)" % self._name)[1:-1]
2050
+ s = unparsed_InputForm()
2051
+ if vars == "":
2052
+ return base_ring(s)
2053
+
2054
+ R = PolynomialRing(base_ring, vars)
2055
+ return R(s)
2056
+
2057
+ if head in ["OrderedCompletion", "OnePointCompletion"]:
2058
+ # it would be more correct to get the type parameter
2059
+ # (which might not be Expression Integer) and recurse
2060
+ return FriCASElement._sage_expression(P.get_InputForm(self._name))
2061
+
2062
+ if head == "Expression" or head == "Pi" or head == "PiDomain":
2063
+ # we treat Expression Integer and Expression Complex
2064
+ # Integer just the same
2065
+ return FriCASElement._sage_expression(P.get_InputForm(self._name))
2066
+
2067
+ raise NotImplementedError("the translation of the FriCAS object %s to sage is not yet implemented" % (unparsed_InputForm()))
2068
+
2069
+
2070
+ @instancedoc
2071
+ class FriCASFunctionElement(FunctionElement):
2072
+ def __init__(self, object, name):
2073
+ """
2074
+ Make FriCAS operation names valid python function identifiers.
2075
+
2076
+ TESTS::
2077
+
2078
+ sage: a = fricas('"Hello"')
2079
+ sage: a.upperCase_q
2080
+ upperCase?
2081
+ sage: a.upperCase_e
2082
+ upperCase!
2083
+ sage: a.upperCase_e()
2084
+ "HELLO"
2085
+ """
2086
+ if name.endswith("_q"):
2087
+ name = name[:-2] + "?"
2088
+ elif name.endswith("_e"):
2089
+ name = name[:-2] + "!"
2090
+ FunctionElement.__init__(self, object, name)
2091
+
2092
+
2093
+ @instancedoc
2094
+ class FriCASExpectFunction(ExpectFunction):
2095
+ def __init__(self, parent, name):
2096
+ """
2097
+ Translate the pythonized function identifier back to a FriCAS
2098
+ operation name.
2099
+
2100
+ TESTS::
2101
+
2102
+ sage: fricas.upperCase_q
2103
+ upperCase?
2104
+ sage: fricas.upperCase_e
2105
+ upperCase!
2106
+ """
2107
+ if name.endswith("_q"):
2108
+ name = name[:-2] + "?"
2109
+ elif name.endswith("_e"):
2110
+ name = name[:-2] + "!"
2111
+ ExpectFunction.__init__(self, parent, name)
2112
+
2113
+
2114
+ def is_FriCASElement(x):
2115
+ """
2116
+ Return ``True`` if ``x`` is of type :class:`FriCASElement`.
2117
+
2118
+ EXAMPLES::
2119
+
2120
+ sage: from sage.interfaces.fricas import is_FriCASElement
2121
+ sage: is_FriCASElement(2)
2122
+ doctest:...: DeprecationWarning: the function is_FriCASElement is deprecated; use isinstance(x, sage.interfaces.abc.FriCASElement) instead
2123
+ See https://github.com/sagemath/sage/issues/34804 for details.
2124
+ False
2125
+ sage: is_FriCASElement(fricas(2))
2126
+ True
2127
+ """
2128
+ from sage.misc.superseded import deprecation
2129
+ deprecation(34804, "the function is_FriCASElement is deprecated; use isinstance(x, sage.interfaces.abc.FriCASElement) instead")
2130
+
2131
+ return isinstance(x, FriCASElement)
2132
+
2133
+
2134
+ fricas = FriCAS()
2135
+
2136
+
2137
+ def reduce_load_fricas():
2138
+ """
2139
+ Return the FriCAS interface object defined in :mod:`sage.interfaces.fricas`.
2140
+
2141
+ EXAMPLES::
2142
+
2143
+ sage: from sage.interfaces.fricas import reduce_load_fricas
2144
+ sage: reduce_load_fricas()
2145
+ FriCAS
2146
+ """
2147
+ return fricas
2148
+
2149
+
2150
+ def fricas_console():
2151
+ """
2152
+ Spawn a new FriCAS command-line session.
2153
+
2154
+ EXAMPLES::
2155
+
2156
+ sage: fricas_console() # not tested
2157
+ FriCAS (AXIOM fork) Computer Algebra System
2158
+ Version: FriCAS 1.0.5
2159
+ Timestamp: Thursday February 19, 2009 at 06:57:33
2160
+ -----------------------------------------------------------------------------
2161
+ Issue )copyright to view copyright notices.
2162
+ Issue )summary for a summary of useful system commands.
2163
+ Issue )quit to leave AXIOM and return to shell.
2164
+ -----------------------------------------------------------------------------
2165
+ """
2166
+ from sage.repl.rich_output.display_manager import get_display_manager
2167
+ if not get_display_manager().is_in_terminal():
2168
+ raise RuntimeError('Can use the console only in the terminal. Try %%fricas magics instead.')
2169
+ os.system('fricas -nox')
2170
+
2171
+
2172
+ def __doctest_cleanup():
2173
+ """
2174
+ EXAMPLES::
2175
+
2176
+ sage: from sage.interfaces.fricas import __doctest_cleanup
2177
+ sage: a = FriCAS()
2178
+ sage: two = a(2)
2179
+ sage: a.is_running()
2180
+ True
2181
+ sage: __doctest_cleanup()
2182
+ sage: a.is_running()
2183
+ False
2184
+ """
2185
+ import sage.interfaces.quit
2186
+ sage.interfaces.quit.expect_quitall()