Skip to content

Instantly share code, notes, and snippets.

@timokau
Created November 22, 2018 16:50
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save timokau/144262860a5d777fa1700d25344fc5f2 to your computer and use it in GitHub Desktop.
Save timokau/144262860a5d777fa1700d25344fc5f2 to your computer and use it in GitHub Desktop.
diff --git a/src/sage/doctest/test.py b/src/sage/doctest/test.py
index 9f22451094..cb6b59a9d1 100644
--- a/src/sage/doctest/test.py
+++ b/src/sage/doctest/test.py
@@ -497,7 +497,8 @@ Test ``atexit`` support in the doctesting framework::
....: pass
Test the ``--memlimit`` option and ``# optional - memlimit``
-(but only on Linux)::
+(but only on Linux). If this test fails, the memory needed to
+run it may have increased. Try increasing the limit. ::
sage: from platform import system
sage: ok = True
diff --git a/src/sage/interfaces/expect.py b/src/sage/interfaces/expect.py
index 4c934c18c2..789c7d1363 100644
--- a/src/sage/interfaces/expect.py
+++ b/src/sage/interfaces/expect.py
@@ -1122,10 +1122,15 @@ If this all works, you can then make calls like:
EXAMPLES:
- We test all of this using the R interface. First we put
+ We test all of this using the Singular interface. First we put
10 + 15 in the input stream::
- sage: r._sendstr('abc <- 10 +15;\n')
+ sage: singular._sendstr('def abc = 10 + 15;\n')
+
+ Then we tell singular to print 10, which is an arbitrary number
+ different from the expected result 35.
+
+ sage: singular._sendstr('10;\n')
Here an exception is raised because 25 hasn't appeared yet in the
output stream. The key thing is that this doesn't lock, but instead
@@ -1135,7 +1140,7 @@ If this all works, you can then make calls like:
sage: t = walltime()
sage: try:
- ....: r._expect_expr('25', timeout=0.5)
+ ....: singular._expect_expr('25', timeout=0.5)
....: except Exception:
....: print('Did not get expression')
Did not get expression
@@ -1145,24 +1150,24 @@ If this all works, you can then make calls like:
sage: w = walltime(t); w > 0.4 and w < 10
True
- We tell R to print abc, which equals 25.
+ We tell Singular to print abc, which equals 25.
::
- sage: r._sendstr('abc;\n')
+ sage: singular._sendstr('abc;\n')
Now 25 is in the output stream, so we can wait for it.
::
- sage: r._expect_expr('25')
+ sage: singular._expect_expr('25')
- This gives us everything before the 25.
+ This gives us everything before the 25, including the 10 we printed earlier.
::
- sage: r._expect.before
- '...abc;\r\n[1] '
+ sage: singular._expect.before.decode('ascii')
+ u'...10\r\n> '
We test interrupting ``_expect_expr`` using the GP interface,
see :trac:`6661`. Unfortunately, this test doesn't work reliably using
@@ -1203,14 +1208,7 @@ If this all works, you can then make calls like:
- ``string`` -- a string
- EXAMPLES: We illustrate this function using the R interface::
-
- sage: r._synchronize()
- sage: r._sendstr('a <- 10;\n')
- sage: r.eval('a')
- '[1] 10'
-
- We illustrate using the singular interface::
+ EXAMPLES: We illustrate this function using the Singular interface::
sage: singular._synchronize()
sage: singular._sendstr('int i = 5;')
@@ -1260,7 +1258,7 @@ If this all works, you can then make calls like:
EXAMPLES: We observe nothing, just as it should be::
- sage: r._synchronize()
+ sage: singular._synchronize()
TESTS:
diff --git a/src/sage/interfaces/gp.py b/src/sage/interfaces/gp.py
index 86f401571a..f3c6120ddc 100644
--- a/src/sage/interfaces/gp.py
+++ b/src/sage/interfaces/gp.py
@@ -254,6 +254,7 @@ class Gp(ExtraTabCompletion, Expect):
self._eval_line('default(help, "gphelp -detex");')
# logfile disabled since Expect already logs
self._eval_line('default(log,0);')
+ self._eval_line("default(nbthreads,1);")
# set random seed
self.set_seed(self._seed)
diff --git a/src/sage/interfaces/interface.py b/src/sage/interfaces/interface.py
index c4ec96c02b..cc9735d5eb 100644
--- a/src/sage/interfaces/interface.py
+++ b/src/sage/interfaces/interface.py
@@ -817,6 +817,21 @@ class InterfaceElement(Element):
sage: singular('1')._reduce()
1
+ TESTS:
+
+ Special care has to be taken with strings. Since for example `r("abc")` will be
+ interpreted as the R-command abc (not a string in R), we have to reduce to
+ `"'abc'"` instead. That is dependant on the Elements `is_string` function to
+ be implemented correctly. This has gone wrong in the past and remained uncaught
+ by the doctests because the original identifier was reused. This test makes sure
+ that does not happen again:
+
+ sage: a = r("'abc'")
+ sage: b = dumps(a)
+ sage: r.set(a.name(), 0) # make identifier reuse doesn't accidentally lead to success
+ sage: loads(b)
+ [1] "abc"
+
"""
if self.is_string():
return repr(self.sage())
@@ -1339,7 +1354,7 @@ class InterfaceElement(Element):
sage: x = r([1,2,3]); x
[1] 1 2 3
sage: x.name()
- 'sage3'
+ 'sage...'
sage: x = r([1,2,3]).name('x'); x
[1] 1 2 3
sage: x.name()
diff --git a/src/sage/interfaces/r.py b/src/sage/interfaces/r.py
index d4d3aa4d67..4e6b61a794 100644
--- a/src/sage/interfaces/r.py
+++ b/src/sage/interfaces/r.py
@@ -158,7 +158,7 @@ Distributions::
sage: r.options(width="60")
$width
- [1] 100
+ [1] 80
sage: rr = r.dnorm(r.seq(-3,3,0.1))
sage: rr
@@ -268,18 +268,21 @@ from __future__ import print_function, absolute_import
from six.moves import range
import six
-from .expect import Expect, ExpectElement, ExpectFunction, FunctionElement
+from .interface import Interface, InterfaceElement, InterfaceFunction, InterfaceFunctionElement
from sage.env import DOT_SAGE
import re
-import sage.rings.integer
from sage.structure.element import parent
from sage.misc.cachefunc import cached_method
from sage.interfaces.tab_completion import ExtraTabCompletion
from sage.docs.instancedoc import instancedoc
+# see the _lazy_init for some reasoning behind the lazy imports
+from sage.misc.lazy_import import lazy_import
+lazy_import("rpy2", "robjects")
+lazy_import("rpy2.robjects", "packages", "rpy2_packages")
+lazy_import("rpy2.robjects.conversion", "localconverter")
+
COMMANDS_CACHE = '%s/r_commandlist.sobj'%DOT_SAGE
-PROMPT = '__SAGE__R__PROMPT__> '
-prompt_re = re.compile("^>", re.M)
#there is a mirror network, but lets take #1 for now
RRepositoryURL = "http://cran.r-project.org/"
@@ -289,12 +292,161 @@ RFilteredPackages = ['.GlobalEnv']
# but package:base should cover this. i think.
RBaseCommands = ['c', "NULL", "NA", "True", "False", "Inf", "NaN"]
-class R(ExtraTabCompletion, Expect):
+def _setup_r_to_sage_converter():
+ """
+ Set up a the converter used to convert from rpy2's
+ representation of R objects to the one sage expects.
+
+ EXAMPLES::
+
+ Test
+
+ Simple numeric values are represented as vectors in R. So `1` would actually
+ be an array of length 1. We convert all vectors of length 1 to simple values,
+ whether or not they "originally" were simple values or not:
+
+ sage: r([42]).sage()
+ 42
+
+ sage: r(42).sage()
+ 42
+
+ sage: r('c("foo")').sage()
+ 'foo'
+
+ Arrays of length greater than one are treated normally:
+
+ sage: r([42, 43]).sage()
+ [42, 43]
+
+ We also convert all numeric values to integers if that is possible without
+ loss of precision:
+
+ sage: type(r([1.0]).sage()) == int
+ True
+
+ sage: r([1.0, 42.5]).sage()
+ [1, 42.5]
+
+ Matrices are converted to sage matrices:
+
+ sage: r('matrix(c(2,4,3,1,5,7), nrow=2, ncol=3)').sage()
+ [2 3 5]
+ [4 1 7]
+
+ More complex r structures are represented by dictionaries:
+
+ sage: r.summary(1).sage()
+ {'DATA': [1, 1, 1, 1, 1, 1],
+ '_Names': ['Min.', '1st Qu.', 'Median', 'Mean', '3rd Qu.', 'Max.'],
+ '_r_class': ['summaryDefault', 'table']}
+
+ sage: r.options(width="60").sage()
+ {'DATA': {'width': 60}, '_Names': 'width'}
+
+ The conversion can handle "not a number", infintiy, imaginary values and
+ missing values:
+
+ sage: r(-17).sqrt().sage()
+ nan
+ sage: r('-17+0i').sqrt().sage()
+ 4.123105625617661j
+ sage: r('NA').sage()
+ NA
+ sage: inf = r('Inf'); inf.sage()
+ inf
+
+
+ Character Vectors are represented by regular python arrays:
+
+ sage: labs = r.paste('c("X","Y")', '1:10', sep='""'); labs.sage()
+ ['X1', 'Y2', 'X3', 'Y4', 'X5', 'Y6', 'X7', 'Y8', 'X9', 'Y10']
+ """
+ from rpy2.rinterface import SexpVector, ListSexpVector, FloatSexpVector
+ from rpy2.robjects.conversion import Converter
+
+ # convert rpy2's representation of r objects to the one sage expects (as defined by the old
+ # expect interface)
+ cv = Converter('r to sage converter')
+
+ # fallback
+ cv.ri2py.register(object, lambda obj: obj)
+
+ def float_to_int_if_possible(f):
+ # First, round the float to at most 15 significant places.
+ # This is what R does by default when using `dput`. It prevents
+ # platform-specific fluctuations.
+ f = float('%.15g' % f)
+ # Preserve the behaviour of the old r parser, e.g. return 1 instead of 1.0
+ float_or_int = int(f) if isinstance(f, int) or f.is_integer() else f
+ return float_or_int
+ cv.ri2py.register(float, float_to_int_if_possible)
+
+ def list_to_singleton_if_possible(l):
+ if len(l) == 1:
+ return l[0]
+ else:
+ return l
+
+ def _vector(vec):
+ attrs = vec.list_attrs()
+ # Recursive calls have to be made explicitly
+ # https://bitbucket.org/rpy2/rpy2/issues/363/custom-converters-are-not-applied
+ data = list_to_singleton_if_possible([ cv.ri2py(val) for val in vec ])
+ rclass = list(vec.do_slot('class')) if 'class' in attrs else vec.rclass
+
+ if 'names' in attrs:
+ # seperate names and values, call ri2py recursively to convert elements
+ names = list_to_singleton_if_possible(list(vec.do_slot('names')))
+ return {
+ 'DATA': data,
+ '_Names': names,
+ '_r_class': rclass,
+ }
+ else:
+ # if no names are present, convert to a normal list or a single value
+ return data
+ cv.ri2py.register(SexpVector, _vector)
+
+ def _matrix(mat):
+ if 'dim' in mat.list_attrs():
+ try:
+ from sage.matrix.constructor import matrix
+ dimensions = mat.do_slot("dim")
+ if len(dimensions) != 2:
+ raise NotImplementedError("Higher-dimension matrices are currently not supported")
+ (nrow, ncol) = dimensions
+ # Since R does it the other way round, we assign transposed and
+ # then transpose the matrix :)
+ m = matrix(ncol, nrow, [cv.ri2py(i) for i in mat])
+ return m.transpose()
+ except TypeError:
+ pass
+ else:
+ return _vector(mat)
+ cv.ri2py.register(FloatSexpVector, _matrix)
+
+ def _list_vector(vec):
+ # we have a R list (vector of arbitrary elements)
+ attrs = vec.list_attrs()
+ names = vec.do_slot('names')
+ values = [ cv.ri2py(val) for val in vec ]
+ rclass = list(vec.do_slot('class')) if 'class' in attrs else vec.rclass
+ data = zip(names, values)
+ return {
+ 'DATA': dict(data),
+ '_Names': cv.ri2py(names),
+ # We don't give the rclass here because the old expect interface
+ # didn't do that either and we want to maintain compatibility.
+ };
+ cv.ri2py.register(ListSexpVector, _list_vector)
+
+ return cv
+
+class R(ExtraTabCompletion, Interface):
def __init__(self,
- maxread=None, script_subdirectory=None,
- server_tmpdir = None,
+ maxread=None,
logfile=None,
- server=None,
init_list_length=1024,
seed=None):
"""
@@ -320,46 +472,69 @@ class R(ExtraTabCompletion, Expect):
sage: r == loads(dumps(r))
True
"""
- Expect.__init__(self,
- # The capitalized version of this is used for printing.
- name = 'r',
+ Interface.__init__(
+ self,
+ name = 'r', # The capitalized version of this is used for printing.
+ )
+ self._seed = seed
+ self._initialized = False # done lazily
+
+
+ def _lazy_init(self):
+ """
+ Initialize the R interpreter. This will set the initial options and
+ implicitly (through lazy_import) import rpy2 if it is not alreay
+ imported.
+
+ Importing rpy2 takes something in the order of hundreds of milliseconds.
+ It also takes tens of megabytes of RAM. Since an instance of R is
+ assigned to the global variable `r` at sage startup, it is important to
+ be as lazy as possible here.
+ For some discussion, see https://bitbucket.org/rpy2/rpy2/issues/490.
- # This is regexp of the input prompt. If you can change
- # it to be very obfuscated that would be better. Even
- # better is to use sequence numbers.
- # options(prompt=\"<prompt> \")
- prompt = '> ', #default, later comes the change
+ Also, importing rpy2 too early (e.g. before numpy) can cause issues with
+ the blas implementation that is used.
+ For details, see https://bitbucket.org/rpy2/rpy2/issues/491.
- # This is the command that starts up your program
- # See #25806 for the --no-readline switch which fixes hangs for some
- command = "R --no-readline --vanilla --quiet",
+ TESTS::
- server=server,
- server_tmpdir=server_tmpdir,
+ Initialization happens on eval:
- script_subdirectory = script_subdirectory,
+ sage: my_r = R()
+ sage: my_r._initialized
+ False
+ sage: my_r(42) # indirect doctest
+ [1] 42
+ sage: my_r._initialized
+ True
- # If this is true, then whenever the user presses Control-C to
- # interrupt a calculation, the whole interface is restarted.
- restart_on_ctrlc = False,
+ And on package import:
- # If true, print out a message when starting
- # up the command when you first send a command
- # to this interface.
- verbose_start = False,
+ sage: my_r = R()
+ sage: my_r._initialized
+ False
+ sage: my_r.library('grid')
+ sage: my_r._initialized
+ True
- logfile=logfile,
+ And when fetching help pages:
+
+ sage: my_r = R()
+ sage: my_r._initialized
+ False
+ sage: _ = my_r.help('c')
+ sage: my_r._initialized
+ True
+ """
+ if not self._initialized:
+ # Set this to True *before* the call to start, since that will call eval() which will in turn call this function.
+ # Setting this to True early prevents infinite recursion.
+ self._initialized = True
+ self._r_to_sage_converter = _setup_r_to_sage_converter()
+ self._start()
- # If an input is longer than this number of characters, then
- # try to switch to outputting to a file.
- eval_using_file_cutoff=1024)
- self.__seq = 0
- self.__var_store_len = 0
- self.__init_list_length = init_list_length
- self._prompt_wait = [self._prompt]
- self._seed = seed
def set_seed(self, seed=None):
"""
@@ -392,14 +567,9 @@ class R(ExtraTabCompletion, Expect):
sage: r = R()
sage: r._start()
"""
- Expect._start(self)
-
- # width is line width, what's a good value? maximum is 10000!
# pager needed to replace help view from less to printout
# option device= is for plotting, is set to x11, NULL would be better?
- self._change_prompt(PROMPT)
- self.eval('options(prompt=\"%s\",continue=\"%s\", width=100,pager="cat",device="png")'%(PROMPT, PROMPT))
- self.expect().expect(PROMPT)
+ self.eval('options(pager="cat",device="png")')
self.eval('options(repos="%s")'%RRepositoryURL)
self.eval('options(CRAN="%s")'%RRepositoryURL)
@@ -444,7 +614,7 @@ class R(ExtraTabCompletion, Expect):
"""
#Check to see if R has PNG support
s = self.eval('capabilities("png")')
- t = r.eval('capabilities("aqua")')
+ t = self.eval('capabilities("aqua")')
if "TRUE" not in s+t:
raise RuntimeError("R was not compiled with PNG support")
@@ -471,12 +641,10 @@ class R(ExtraTabCompletion, Expect):
'Autoloads',
'package:base']
"""
- pl = []
- l = "".join(l.split("\n"))
- l = l[2:len(l)-1]
- for l in l.split(","):
- pl.append(l.strip().strip('"'))
- return pl
+ # This function is only kept for legacy reasons. It was used internally
+ # in the old expect based interface and for some reason was made part
+ # of the public api.
+ return self(l).sage()
def install_packages(self, package_name):
"""
@@ -532,22 +700,17 @@ class R(ExtraTabCompletion, Expect):
sage: type(c)
<class 'sage.interfaces.r.RFunction'>
"""
- if attrname[:1] == "_":
- raise AttributeError
- return RFunction(self, attrname)
-
-
- def _quit_string(self):
- r"""
- Return the string that when typed into R causes the R
- interpreter to exit.
-
- EXAMPLES::
+ try:
+ # First try to get a regular python attribute. This makes it
+ # possible to still use attributes like _r_to_sage_converter
+ # internally.
+ self.__getattribute__(attrname)
+ except AttributeError:
+ # if there is no such attribute, get the r attribute
+ if attrname[:1] == "_":
+ raise AttributeError("Attribute {} is not allowed to start with an underscore.".format(attrname))
+ return RFunction(self, attrname)
- sage: r._quit_string()
- 'quit(save="no")'
- """
- return 'quit(save="no")'
def _read_in_file_command(self, filename):
r"""
@@ -670,21 +833,18 @@ class R(ExtraTabCompletion, Expect):
...
ImportError: ...
"""
- ret = self.eval('require("%s")' % library_name)
- try:
- ret = ret.decode('utf-8')
- except UnicodeDecodeError:
- ret = ret.decode('latin-1')
- # try hard to parse the message string in a locale-independent way
- if ' library(' in ret: # locale-independent key-word
- raise ImportError("%s"%ret)
+ self._lazy_init()
+ if rpy2_packages.isinstalled(library_name):
+ rpy2_packages.importr(library_name)
else:
- try:
- # We need to rebuild keywords!
- del self.__tab_completion
- except AttributeError:
- pass
- self._tab_completion(verbose=False, use_disk_cache=False)
+ raise ImportError("R library {} not installed".format(library_name))
+
+ try:
+ # We need to rebuild keywords!
+ del self.__tab_completion
+ except AttributeError:
+ pass
+ self._tab_completion(verbose=False, use_disk_cache=False)
require = library #overwrites require
@@ -740,13 +900,6 @@ class R(ExtraTabCompletion, Expect):
sage: r._true_symbol()
'[1] TRUE'
-
- This is used behinds the scenes to implement comparison::
-
- sage: r('1') == r('1')
- [1] TRUE
- sage: r('1') == r('2')
- [1] FALSE
"""
# return the string rep of truth, i.e., what the system outputs
# when you type 1==1.
@@ -789,17 +942,33 @@ class R(ExtraTabCompletion, Expect):
EXAMPLES::
sage: r.help('c')
- c package:base R Documentation
+ title
+ -----
+ <BLANKLINE>
+ Combine Values into a Vector or List
+ <BLANKLINE>
+ name
+ ----
+ <BLANKLINE>
+ c
...
+ """
+ self._lazy_init()
+ # This is looking for the topic in all existing namespaces.
+ # Theoretically, there may be multiple options. We ignore that.
+ pages_for_topic = robjects.help.pages(command)
+ if len(pages_for_topic) == 0:
+ raise ValueError("There is no help page for the given topic")
- .. note::
+ s = pages_for_topic[0].to_docstring()
- This is similar to typing r.command?.
- """
- s = self.eval('help("%s")'%command).strip() # ?cmd is only an unsafe shortcut
+ # Maybe this can be removed now (it is a leftover from the old expect
+ # interface). Since I don't understand why it was needed in the first
+ # place, I'm keeping it for now.
import sage.plot.plot
if sage.plot.plot.EMBEDDED_MODE:
s = s.replace('_\x08','')
+
return HelpExpression(s)
def _assign_symbol(self):
@@ -912,11 +1081,10 @@ class R(ExtraTabCompletion, Expect):
sage: r.set('a', '2 + 3')
sage: r.get('a')
'[1] 5'
+
"""
cmd = '%s <- %s'%(var,value)
out = self.eval(cmd)
- if out.find("error") != -1:
- raise TypeError("Error executing code in R\nCODE:\n\t%s\nR ERROR:\n\t%s"%(cmd, out))
def get(self, var):
"""
@@ -934,9 +1102,7 @@ class R(ExtraTabCompletion, Expect):
sage: r.get('a')
'[1] 2'
"""
- s = self.eval('%s'%var)
- #return self._remove_indices_re.sub("", s).strip()
- return s
+ return self.eval('%s'%var)
def na(self):
"""
@@ -965,8 +1131,8 @@ class R(ExtraTabCompletion, Expect):
EXAMPLES::
sage: dummy = r._tab_completion(use_disk_cache=False) #clean doctest
- sage: r.completions('tes')
- ['testInheritedMethods', 'testPlatformEquivalence', 'testVirtual']
+ sage: 'testInheritedMethods' in r.completions('tes')
+ True
"""
return [name for name in self._tab_completion() if name[:len(s)] == s]
@@ -986,8 +1152,7 @@ class R(ExtraTabCompletion, Expect):
"""
v = RBaseCommands
- ll = self.eval('dput(search())') # loaded libs
- ll = self.convert_r_list(ll)
+ ll = self('search()')._sage_() # loaded libs
for lib in ll:
if lib in RFilteredPackages:
@@ -996,9 +1161,7 @@ class R(ExtraTabCompletion, Expect):
if lib.find("package:") != 0:
continue #only packages
- raw = self.eval('dput(objects("%s"))'%lib)
- raw = self.convert_r_list(raw)
- raw = [x.replace(".","_") for x in raw]
+ raw = self('objects("%s")'%lib)._sage_()
#TODO are there others? many of them are shortcuts or
#should be done on another level, like selections in lists
@@ -1123,25 +1286,7 @@ class R(ExtraTabCompletion, Expect):
RFunction(self, 'plot')(*args, **kwds)
return RFunction(self, 'dev.off')()
- def _strip_prompt(self, code):
- """
- Remove the standard R prompt from the beginning of lines in code.
-
- INPUT:
-
- - code -- a string
-
- OUTPUT: a string
-
- EXAMPLES::
-
- sage: s = '> a <- 2\n> b <- 3'
- sage: r._strip_prompt(s)
- ' a <- 2\n b <- 3'
- """
- return prompt_re.sub("", code)
-
- def eval(self, code, globals=None, locals=None, synchronize=True, *args, **kwds):
+ def eval(self, code, *args, **kwds):
"""
Evaluates a command inside the R interpreter and returns the output
as a string.
@@ -1151,9 +1296,9 @@ class R(ExtraTabCompletion, Expect):
sage: r.eval('1+1')
'[1] 2'
"""
- # TODO split code at ";" outside of quotes and send them as individual
- # lines without ";".
- return Expect.eval(self, code, synchronize=synchronize, *args, **kwds)
+ self._lazy_init()
+ return str(robjects.r(code)).rstrip()
+
def _r_to_sage_name(self, s):
"""
@@ -1255,16 +1400,8 @@ class R(ExtraTabCompletion, Expect):
self.execute('setwd(%r)' % dir)
-# patterns for _sage_()
-rel_re_param = re.compile(r'\s([\w\.]+)\s=')
-rel_re_range = re.compile(r'([\d]+):([\d]+)')
-rel_re_integer = re.compile(r'([^\d])([\d]+)L')
-rel_re_terms = re.compile(r'terms\s*=\s*(.*?),')
-rel_re_call = re.compile(r'call\s*=\s*(.*?)\),')
-
-
@instancedoc
-class RElement(ExtraTabCompletion, ExpectElement):
+class RElement(ExtraTabCompletion, InterfaceElement):
def _tab_completion(self):
"""
@@ -1303,6 +1440,20 @@ class RElement(ExtraTabCompletion, ExpectElement):
stat_model = tilde
+ def is_string(self):
+ """
+ Tell whether this element is a string.
+
+ EXAMPLES::
+
+ sage: r('"abc"').is_string()
+ True
+ sage: r([1,2,3]).is_string()
+ False
+
+ """
+ return isinstance(self.sage(), str)
+
def __len__(self):
"""
Return the length of this object.
@@ -1315,7 +1466,7 @@ class RElement(ExtraTabCompletion, ExpectElement):
sage: len(x)
5
"""
- return int(self.parent().eval('dput(length(%s))'%self.name())[:-1] )
+ return self.parent()('length(%s)'%self.name()).sage()
def __getattr__(self, attrname):
"""
@@ -1337,10 +1488,16 @@ class RElement(ExtraTabCompletion, ExpectElement):
sage: length()
[1] 3
"""
- self._check_valid()
- if attrname[:1] == "_":
- raise AttributeError
- return RFunctionElement(self, attrname)
+ try:
+ # First try to get a regular python attribute. This makes it
+ # possible to still use attributes like _r_to_sage_converter
+ # internally.
+ self.__getattribute__(attrname)
+ except AttributeError:
+ self._check_valid()
+ if attrname[:1] == "_":
+ raise AttributeError("Attribute {} is not allowed to start with an underscore.".format(attrname))
+ return RFunctionElement(self, attrname)
def __getitem__(self, n):
"""
@@ -1593,166 +1750,6 @@ class RElement(ExtraTabCompletion, ExpectElement):
# the R operator is %*% for matrix multiplication
return P('%s %%*%% %s'%(self.name(), Q.name()))
- def _subs_dots(self, x):
- r"""
- Replace dots by underscores; used internally to implement
- conversation from R expression to Sage objects.
-
- INPUT:
-
- - x -- regular expression match: ``<type '_sre.SRE_Match'>``
-
- OUTPUT: string
-
- EXAMPLES::
-
- sage: import re
- sage: a = r([1,2,3])
- sage: rel_re_param = re.compile(r'\s([\w\.]+)\s=')
- sage: rel_re_param.sub(a._subs_dots, ' test.test =')
- ' test_test ='
- """
- return x.group().replace('.','_')
-
- def _subs_range(self, x):
- r"""
- Change endpoints of ranges. This is used internally in the
- code for converting R expressions to Sage objects.
-
- INPUT:
-
- - x -- regular expression match: ``<type '_sre.SRE_Match'>``
-
- OUTPUT: string
-
- EXAMPLES::
-
- sage: import re
- sage: a = r([1,2,3])
- sage: rel_re_range = re.compile(r'([\d]+):([\d]+)')
- sage: rel_re_range.sub(a._subs_range, ' 1:10')
- ' range(1,11)'
- """
- g = x.groups()
- g1 = int(g[1]) + 1
- return 'range(%s,%s)' % (g[0], g1)
-
- def _subs_integer(self, x):
- r"""
- Replaces strings like 'dL' with 'Integer(d)' where d is some
- integer. This is used internally in the code for converting R
- expressions to Sage objects.
-
- EXAMPLES::
-
- sage: import re
- sage: a = r([1,2,3])
- sage: rel_re_integer = re.compile(r'([^\d])([\d]+)L')
- sage: rel_re_integer.sub(a._subs_integer, ' 1L 2L')
- ' Integer(1) Integer(2)'
- sage: rel_re_integer.sub(a._subs_integer, '1L 2L')
- '1L Integer(2)'
-
- """
- return '%sInteger(%s)'%x.groups()
-
- def _convert_nested_r_list(self, exp):
- """
- Converts a string representing a (possibly) nested list in R
- to a (possibly) nested Python list. This is used internally
- in the code for converting R expressions to Sage objects.
-
- INPUT:
-
- - exp -- a string
-
- OUTPUT: a string
-
- EXAMPLES::
-
- sage: a = r([1,2,3])
- sage: s = 'c(1, 2, 3)'
- sage: a._convert_nested_r_list(s)
- '[1, 2, 3]'
- """
- from re import compile as re_compile
- from re import split as re_split
- splt = re_compile(r'(c\(|\(|\))') # c( or ( or )
- lvl = 0
- ret = []
- for token in re_split(splt, exp):
- if token == 'c(':
- ret.append('[')
- lvl += 1
- elif token == '(':
- ret.append(token)
- if lvl > 0 : lvl += 1
- elif token == ')':
- if lvl == 1:
- ret.append(']')
- lvl -= 1
- else:
- ret.append(token)
- if lvl > 0:
- lvl -= 1
- else:
- ret.append(token)
-
- return ''.join(ret)
-
- def _r_list(self, *args, **kwds):
- """
- This is used internally in the code for converting R
- expressions to Sage objects.
-
- EXAMPLES::
-
- sage: a = r([1,2,3])
- sage: sorted(a._r_list(1,2,3,k=5).items())
- [('#0', 1), ('#1', 2), ('#2', 3), ('k', 5)]
- """
- ret = dict(kwds)
- i = 0
- for k in args:
- ret['#%s' % i] = k
- i += 1
- return ret
-
- def _r_structure(self, __DATA__, **kwds):
- """
- This is used internally in the code for converting R
- expressions to Sage objects.
-
- EXAMPLES::
-
- sage: a = r([1,2,3])
- sage: d = a._r_structure('data', a=1, b=2)
- sage: sorted(d.items())
- [('DATA', 'data'), ('a', 1), ('b', 2)]
- sage: a._r_structure([1,2,3,4], _Dim=(2,2))
- [1 3]
- [2 4]
-
- """
- if '_Dim' in kwds: #we have a matrix
- # TODO what about more than 2 dimensions?
- # additional checks!!
- try:
- from sage.matrix.constructor import matrix
- d = kwds.get('_Dim')
- # TODO: higher dimensions? happens often in statistics
- if len(d) != 2:
- raise TypeError
- #since R does it the other way round, we assign
- #transposed and then transpose the matrix :)
- m = matrix(d[1], d[0], [i for i in __DATA__])
- return m.transpose()
- except TypeError:
- pass
- d = dict(DATA=__DATA__)
- d.update(kwds)
- return d
-
def _sage_(self):
r"""
Returns Sage representation of the R object.
@@ -1777,95 +1774,9 @@ class RElement(ExtraTabCompletion, ExpectElement):
self._check_valid()
P = self.parent()
- # This is the core of the trick: using dput
- # dput prints out the internal structure of R's data elements
- # options via .deparseOpts(control=...)
- # TODO: dput also works with a file, if things get huge!
- # [[However, using a file for output often isn't necessary
- # since pipe buffering works pretty well for output.
- # That said, benchmark this. -- William Stein]]
- exp = P.eval('dput(%s)'%self.name())
-
- # Preprocess expression
- # example what this could be:
- # structure(list(statistic = structure(0.233549683248457, .Names = "t"),
- # parameter = structure(5.58461538461538, .Names = "df"), p.value = 0.823657802106985,
- # conf.int = structure(c(-2.41722062247400, 2.91722062247400
- # ), conf.level = 0.95), estimate = structure(c(2.75, 2.5), .Names = c("mean of x",
- # "mean of y")), null.value = structure(0, .Names = "difference in means"),
- # alternative = "two.sided", method = "Welch Two Sample t-test",
- # data.name = "c(1, 2, 3, 5) and c(1, 2, 3, 4)"), .Names = c("statistic",
- # "parameter", "p.value", "conf.int", "estimate", "null.value",
- # "alternative", "method", "data.name"), class = "htest")
-
- # R's structure (from help):
- # structure(.Data, ...)
- # .Data: an object which will have various attributes attached to it.
- # ...: attributes, specified in 'tag=value' form, which will be
- # attached to data.
- #For historical reasons (these names are used when deparsing),
- # attributes '".Dim"', '".Dimnames"', '".Names"', '".Tsp"' and
- # '".Label"' are renamed to '"dim"', '"dimnames"', '"names"',
- # '"tsp"' and '"levels"'.
-
-
-
- # we want this in a single line
- exp.replace('\n','')
- exp = "".join(exp.split("\n"))
-
- # python compatible parameters
- exp = rel_re_param.sub(self._subs_dots, exp)
-
- # Rename class since it is a Python keyword
- exp = re.sub(' class = ', ' _r_class = ',exp)
-
- # Change 'structure' to '_r_structure'
- # TODO: check that we are outside of quotes ""
- exp = re.sub(r' structure\(', ' _r_structure(', exp)
- exp = re.sub(r'^structure\(', '_r_structure(', exp) # special case
-
- # Change 'list' to '_r_list'
- exp = re.sub(r' list\(', ' _r_list(', exp)
- exp = re.sub(r'\(list\(', '(_r_list(', exp)
-
- # Change 'a:b' to 'range(a,b+1)'
- exp = rel_re_range.sub(self._subs_range, exp)
-
- # Change 'dL' to 'Integer(d)'
- exp = rel_re_integer.sub(self._subs_integer, exp)
-
- # Wrap the right hand side of terms = ... in quotes since it
- # has a ~ in it.
- exp = rel_re_terms.sub(r'terms = "\1",', exp)
-
-
- # Change call = ..., to call = "...",
- exp = rel_re_call.sub(r'call = "\1",', exp)
-
- # seems to work for
- # rr = r.summary(r.princomp(r.matrix(r.c(1,2,3,4,3,4,1,2,2),4)))
- # rr._sage_()
- # but the call expression get's evaluated. why?!? TODO
-
-
- # translation:
- # c is an ordered list
- # list is a dictionary (where _Names give the entries names.
- # map entries in names to (value, name) in each entry?
- # structure is .. see above .. structure(DATA,**kw)
- # TODO: thinking of just replacing c( with ( to get a long tuple?
-
-
- exp = self._convert_nested_r_list(exp)
-
- # Set up the globals
- globs = {'NA':None, 'NULL':None, 'FALSE':False, 'TRUE':True,
- '_r_list':self._r_list, '_r_structure':self._r_structure,
- 'Integer':sage.rings.integer.Integer,
- 'character':str}
-
- return eval(exp, globs, globs)
+ with localconverter(P._r_to_sage_converter) as cv:
+ parsed = robjects.r(self.name())
+ return parsed
def _latex_(self):
@@ -1893,7 +1804,7 @@ class RElement(ExtraTabCompletion, ExpectElement):
@instancedoc
-class RFunctionElement(FunctionElement):
+class RFunctionElement(InterfaceFunctionElement):
def __reduce__(self):
"""
EXAMPLES::
@@ -1917,9 +1828,16 @@ class RFunctionElement(FunctionElement):
sage: a = r([1,2,3])
sage: length = a.length
sage: print(length.__doc__)
- length package:base R Documentation
- ...
+ title
+ -----
<BLANKLINE>
+ Length of an Object
+ <BLANKLINE>
+ name
+ ----
+ <BLANKLINE>
+ length
+ ...
"""
M = self._obj.parent()
return M.help(self._name)
@@ -1951,7 +1869,7 @@ class RFunctionElement(FunctionElement):
@instancedoc
-class RFunction(ExpectFunction):
+class RFunction(InterfaceFunction):
def __init__(self, parent, name, r_name=None):
"""
A Function in the R interface.
@@ -2007,9 +1925,16 @@ class RFunction(ExpectFunction):
sage: length = r.length
sage: print(length.__doc__)
- length package:base R Documentation
- ...
+ title
+ -----
+ <BLANKLINE>
+ Length of an Object
<BLANKLINE>
+ name
+ ----
+ <BLANKLINE>
+ length
+ ...
"""
M = self._parent
return M.help(self._name)
diff --git a/src/sage/rings/function_field/function_field.py b/src/sage/rings/function_field/function_field.py
index 9ddf8aca49..1905f4bbee 100644
--- a/src/sage/rings/function_field/function_field.py
+++ b/src/sage/rings/function_field/function_field.py
@@ -1692,13 +1692,13 @@ class FunctionField_polymod(FunctionField):
You can also specify a morphism on the base::
- sage: R1.<r> = K[]
- sage: L1.<r> = K.extension(r^2 - (x+1)^3 - 1)
- sage: L.hom(r, base_morphism=phi)
+ sage: R1.<q> = K[]
+ sage: L1.<q> = K.extension(q^2 - (x+1)^3 - 1)
+ sage: L.hom(q, base_morphism=phi)
Function Field morphism:
From: Function field in y defined by y^2 - x^3 - 1
- To: Function field in r defined by r^2 - x^3 - 3*x^2 - 3*x - 2
- Defn: y |--> r
+ To: Function field in q defined by q^2 - x^3 - 3*x^2 - 3*x - 2
+ Defn: y |--> q
x |--> x + 1
We make another extension of a rational function field::
diff --git a/src/sage/stats/r.py b/src/sage/stats/r.py
index cd2002559b..8a2f243901 100644
--- a/src/sage/stats/r.py
+++ b/src/sage/stats/r.py
@@ -39,12 +39,12 @@ def ttest(x,y,conf_level = 0.95, **kw):
Example::
- sage: a, b = ttest([1,2,3,4,5],[1,2,3,3.5,5.121]); a
- 0.941026372027427
+ sage: a, b = ttest([1,2,3,4,5],[1,2,3,3.5,5.121]); a # abs tol 1e-12
+ 0.9410263720274274
"""
if len(x) != len(y):
raise AttributeError("vectors x and y must be of same length")
test = myR.t_test(x,y,conf_level = conf_level, **kw)._sage_()
- t = test.get('DATA').get('p_value')
+ t = test.get('DATA').get('p.value')
return t, test
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment