Skip to content

Commit

Permalink
sagemathgh-35838: FriCAS spkg-configure and Feature
Browse files Browse the repository at this point in the history
    
As FriCAS is only used as an executable, this is straightforward; quite
a number of systems has Fricas 1.3.8, so this is useful, too.

The FriCAS pexpect interface now uses the new `Executable` feature to
determine the absolute pathname of the fricas executable (unless
executed remotely).
This is made possible by a simple refactor of the
`sage.interfaces.expect.Expect` class: Computing the effective command
line is no longer done in `set_server_and_command` (called by
`__init__`); it is delayed until an interface is started and needs the
command line.

- Fixes sagemath#35837
- Fixes sagemath#33575

Dependencies: The changes in `sage.interfaces` outside of `.expect` and
`.fricas` are all from the following PR and do not need review.

- Depends on sagemath#36656 (merged here)
    
URL: sagemath#35838
Reported by: Dima Pasechnik
Reviewer(s): Dima Pasechnik, François Bissey, Matthias Köppe
  • Loading branch information
Release Manager committed Dec 3, 2023
2 parents fef52de + 870fa22 commit b84c6b1
Show file tree
Hide file tree
Showing 38 changed files with 1,685 additions and 1,367 deletions.
1 change: 1 addition & 0 deletions build/pkgs/fricas/distros/debian.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fricas
1 change: 1 addition & 0 deletions build/pkgs/fricas/distros/freebsd.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
math/fricas
1 change: 1 addition & 0 deletions build/pkgs/fricas/distros/gentoo.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
sci-mathematics/fricas
1 change: 1 addition & 0 deletions build/pkgs/fricas/distros/opensuse.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
fricas
16 changes: 16 additions & 0 deletions build/pkgs/fricas/spkg-configure.m4
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
SAGE_SPKG_CONFIGURE(
[fricas], [
AC_CACHE_CHECK([for FriCAS >= 1.3.8], [ac_cv_path_FRICAS], [
AC_PATH_PROGS_FEATURE_CHECK([FRICAS], [fricas], [
fricas_version=`echo ")quit" | $ac_path_FRICAS -nox -noclef | grep Version | tail -1 2>&1 \
| $SED -n -e 's/.* Version: FriCAS //p'`
AS_IF([test -n "$fricas_version"], [
AX_COMPARE_VERSION([$fricas_version], [ge], [1.3.8], [
ac_cv_path_FRICAS="$ac_path_FRICAS"
ac_path_FRICAS_found=:
])
])
])
])
AS_IF([test -z "$ac_cv_path_FRICAS"], [sage_spkg_install_fricas=yes])
])
65 changes: 65 additions & 0 deletions src/sage/features/fricas.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
r"""
Features for testing the presence of ``fricas``
"""

# *****************************************************************************
# Copyright (C) 2023 Dima Pasechnik
#
# Distributed under the terms of the GNU General Public License (GPL)
# as published by the Free Software Foundation; either version 2 of
# the License, or (at your option) any later version.
# https://www.gnu.org/licenses/
# *****************************************************************************

import os
import subprocess
from . import Executable, FeatureTestResult

class FriCAS(Executable):
r"""
A :class:`~sage.features.Feature` which checks for the :ref:`fricas <fricas>` binary.
EXAMPLES::
sage: from sage.features.fricas import FriCAS
sage: FriCAS().is_present() # optional - fricas
FeatureTestResult('fricas', True)
"""
def __init__(self):
r"""
TESTS::
sage: from sage.features.fricas import FriCAS
sage: isinstance(FriCAS(), FriCAS)
True
"""
Executable.__init__(self, name="fricas", spkg="fricas",
executable="fricas",
url="https://fricas.github.io")

def is_functional(self):
r"""
Check whether ``fricas`` works on trivial input.
EXAMPLES::
sage: from sage.features.fricas import FriCAS
sage: FriCAS().is_functional() # optional - fricas
FeatureTestResult('fricas', True)
"""
command = ['fricas -nosman -eval ")quit"']
try:
lines = subprocess.check_output(command, stderr=subprocess.STDOUT, shell=True)
except subprocess.CalledProcessError as e:
return FeatureTestResult(self, False,
reason="Call `{command}` failed with exit code {e.returncode}".format(command=" ".join(command), e=e))

expected = b"FriCAS"
if lines.find(expected) == -1:
return FeatureTestResult(self, False,
reason="Call `{command}` did not produce output which contains `{expected}`".format(command=" ".join(command), expected=expected))

return FeatureTestResult(self, True)

def all_features():
return [FriCAS()]
8 changes: 4 additions & 4 deletions src/sage/functions/special.py
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,7 @@ def __init__(self):
Check that :trac:`34085` is fixed::
sage: _ = var("x y") # needs sage.symbolic
sage: fricas(elliptic_e(x, y)) # optional - fricas, needs sage.symbolic
sage: fricas(elliptic_e(x, y)) # optional - fricas, needs sage.symbolic
ellipticE(sin(x),y)
However, the conversion is only correct in the interval
Expand All @@ -525,7 +525,7 @@ def __init__(self):
sage: f = lambda x, y: elliptic_e(arcsin(x), y).subs(x=x, y=y)
sage: g = lambda x, y: fricas.ellipticE(x, y).sage()
sage: d = lambda x, y: f(x, y) - g(x, y)
sage: [d(N(-pi/2 + x), y) # tol 1e-8 # optional - fricas, needs sage.symbolic
sage: [d(N(-pi/2 + x), y) # abs tol 1e-8 # optional - fricas, needs sage.symbolic
....: for x in range(1, 3) for y in range(-2, 2)]
[0.000000000000000,
0.000000000000000,
Expand Down Expand Up @@ -877,7 +877,7 @@ def __init__(self):
Check that :trac:`34186` is fixed::
sage: _ = var("x y") # needs sage.symbolic
sage: fricas(elliptic_f(x, y)) # optional - fricas, needs sage.symbolic
sage: fricas(elliptic_f(x, y)) # optional - fricas, needs sage.symbolic
ellipticF(sin(x),y)
However, the conversion is only correct in the interval
Expand All @@ -891,7 +891,7 @@ def __init__(self):
sage: f = lambda x, y: elliptic_f(arcsin(x), y).subs(x=x, y=y)
sage: g = lambda x, y: fricas.ellipticF(x, y).sage()
sage: d = lambda x, y: f(x, y) - g(x, y)
sage: [d(N(-pi/2 + x), y) # tol 1e-8 # optional - fricas, needs sage.symbolic
sage: [d(N(-pi/2 + x), y) # abs tol 1e-8 # optional - fricas, needs sage.symbolic
....: for x in range(1, 3) for y in range(-2,2)]
[0.000000000000000,
0.000000000000000,
Expand Down
105 changes: 57 additions & 48 deletions src/sage/interfaces/axiom.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,12 +304,13 @@ def _commands(self):
EXAMPLES::
sage: cmds = axiom._commands() #optional - axiom
sage: len(cmds) > 100 #optional - axiom
sage: # optional - axiom
sage: cmds = axiom._commands()
sage: len(cmds) > 100
True
sage: '<' in cmds #optional - axiom
sage: '<' in cmds
True
sage: 'factor' in cmds #optional - axiom
sage: 'factor' in cmds
True
"""
s = self.eval(")what things")
Expand All @@ -327,18 +328,19 @@ def _tab_completion(self, verbose=True, use_disk_cache=True):
EXAMPLES::
sage: c = axiom._tab_completion(use_disk_cache=False, verbose=False) #optional - axiom
sage: len(c) > 100 #optional - axiom
sage: # optional - axiom
sage: c = axiom._tab_completion(use_disk_cache=False, verbose=False)
sage: len(c) > 100
True
sage: 'factor' in c #optional - axiom
sage: 'factor' in c
True
sage: '**' in c #optional - axiom
sage: '**' in c
False
sage: 'upperCase?' in c #optional - axiom
sage: 'upperCase?' in c
False
sage: 'upperCase_q' in c #optional - axiom
sage: 'upperCase_q' in c
True
sage: 'upperCase_e' in c #optional - axiom
sage: 'upperCase_e' in c
True
"""
try:
Expand Down Expand Up @@ -396,11 +398,12 @@ def get(self, var):
EXAMPLES::
sage: axiom.set('xx', '2') #optional - axiom
sage: axiom.get('xx') #optional - axiom
sage: # optional - axiom
sage: axiom.set('xx', '2')
sage: axiom.get('xx')
'2'
sage: a = axiom('(1 + sqrt(2))^5') #optional - axiom
sage: axiom.get(a.name()) #optional - axiom
sage: a = axiom('(1 + sqrt(2))^5')
sage: axiom.get(a.name())
' +-+\r\r\n 29\\|2 + 41'
"""
s = self._eval_line(str(var))
Expand Down Expand Up @@ -571,26 +574,28 @@ def _richcmp_(self, other, op):
"""
EXAMPLES::
sage: two = axiom(2) #optional - axiom
sage: two == 2 #optional - axiom
sage: # optional - axiom
sage: two = axiom(2)
sage: two == 2
True
sage: two == 3 #optional - axiom
sage: two == 3
False
sage: two < 3 #optional - axiom
sage: two < 3
True
sage: two > 1 #optional - axiom
sage: two > 1
True
sage: a = axiom(1); b = axiom(2) #optional - axiom
sage: a == b #optional - axiom
sage: # optional - axiom
sage: a = axiom(1); b = axiom(2)
sage: a == b
False
sage: a < b #optional - axiom
sage: a < b
True
sage: a > b #optional - axiom
sage: a > b
False
sage: b < a #optional - axiom
sage: b < a
False
sage: b > a #optional - axiom
sage: b > a
True
We can also compare more complicated object such as functions::
Expand Down Expand Up @@ -649,15 +654,16 @@ def __getitem__(self, n):
EXAMPLES::
sage: v = axiom('[i*x^i for i in 0..5]'); v # optional - axiom
sage: # optional - axiom
sage: v = axiom('[i*x^i for i in 0..5]'); v
2 3 4 5
[0,x,2x ,3x ,4x ,5x ]
sage: v[4] # optional - axiom
sage: v[4]
3
3x
sage: v[1] # optional - axiom
sage: v[1]
0
sage: v[10] # optional - axiom
sage: v[10]
Traceback (most recent call last):
...
IndexError: index out of range
Expand All @@ -677,12 +683,13 @@ def comma(self, *args):
EXAMPLES::
sage: two = axiom(2) #optional - axiom
sage: two.comma(3) #optional - axiom
sage: # optional - axiom
sage: two = axiom(2)
sage: two.comma(3)
[2,3]
sage: two.comma(3,4) #optional - axiom
sage: two.comma(3,4)
[2,3,4]
sage: _.type() #optional - axiom
sage: _.type()
Tuple PositiveInteger
"""
P = self._check_valid()
Expand Down Expand Up @@ -796,12 +803,12 @@ def _sage_(self):
2.12340000000000
sage: _.parent() #optional - axiom
Real Field with 53 bits of precision
sage: a = RealField(100)(pi)
sage: axiom(a)._sage_() #optional - axiom
sage: a = RealField(100)(pi) # needs sage.symbolic
sage: axiom(a)._sage_() # optional - axiom # needs sage.symbolic
3.1415926535897932384626433833
sage: _.parent() #optional - axiom
sage: _.parent() # optional - axiom # needs sage.symbolic
Real Field with 100 bits of precision
sage: axiom(a)._sage_() == a #optional - axiom
sage: axiom(a)._sage_() == a # optional - axiom # needs sage.symbolic
True
sage: axiom(2.0)._sage_() #optional - axiom
2.00000000000000
Expand All @@ -810,16 +817,17 @@ def _sage_(self):
We can also convert Axiom's polynomials to Sage polynomials.
sage: a = axiom(x^2 + 1) #optional - axiom
sage: a.type() #optional - axiom
sage: # optional - axiom, needs sage.symbolic
sage: a = axiom(x^2 + 1)
sage: a.type()
Polynomial Integer
sage: a.sage() #optional - axiom
sage: a.sage()
x^2 + 1
sage: _.parent() #optional - axiom
sage: _.parent()
Univariate Polynomial Ring in x over Integer Ring
sage: axiom('x^2 + y^2 + 1/2').sage() #optional - axiom
sage: axiom('x^2 + y^2 + 1/2').sage()
y^2 + x^2 + 1/2
sage: _.parent() #optional - axiom
sage: _.parent()
Multivariate Polynomial Ring in y, x over Rational Field
Expand Down Expand Up @@ -902,12 +910,13 @@ def __init__(self, object, name):
"""
TESTS::
sage: a = axiom('"Hello"') #optional - axiom
sage: a.upperCase_q #optional - axiom
sage: # optional - axiom
sage: a = axiom('"Hello"')
sage: a.upperCase_q
upperCase?
sage: a.upperCase_e #optional - axiom
sage: a.upperCase_e
upperCase!
sage: a.upperCase_e() #optional - axiom
sage: a.upperCase_e()
"HELLO"
"""
if name.endswith("_q"):
Expand Down
Loading

0 comments on commit b84c6b1

Please sign in to comment.