mailing-list for TeXmacs Users

Text archives Help


[TeXmacs] Update: Modifications to Texmacs Python Plugin To Accommodate SymPy LaTeX Output


Chronological Thread 
  • From: "David E. Miller" <address@hidden>
  • To: François Poulain <address@hidden>, Bill Eaton <address@hidden>, address@hidden, address@hidden
  • Subject: [TeXmacs] Update: Modifications to Texmacs Python Plugin To Accommodate SymPy LaTeX Output
  • Date: Sun, 19 May 2013 04:07:37 -0400
  • Authentication-results: smtp03.embarq.synacor.com smtp.user=address@hidden; auth=pass (LOGIN)
  • X_cmae_category: 0,0 Undefined,Undefined

All:

Attached is a TeXmacs file that documents the latest efforts of the required modifications to the existing Python plugin required to adapt this plugin to a dedicated SymPy plugin. Also attached is the existing Python plugin file tm_python which I have modified to accommodate SymPy LaTeX output to TeXmacs.

It might also be possible to use what I have done so far and merely modify the existing Python plugin to accommodate SymPy LaTeX output which is what I have accomplished here while preserving as much of the original Python plugin code as possible.

TeXmacs user Bill Eaton first brought the attention of users to this issue and several replies seemed to favor some accommodation for his goal. Personally I am not sure I will ever use this feature if it is brought to fruition, but the challenge sort of got under my skin, so I gave it a go and the results of what I was able to accomplish so far are attached. There are probably some sharp Pythonists out there that can see this through from here. Sorry, but I am not in that category, as my Python programming skills are not that sharp.

My input to those that follow is that the best solution is to modify the existing Python plugin instead of creating a new one dedicated to SymPy. If someone can figure out from what I have done how to distinguish SymPy LaTeX text string objects and plain old Python text string objects when the output to TeXmacs is composed, then I think this is all that stands in the way. This is clear if you read the attached TeXmacs file with the included Python/SymPy session. The output is consistent with what we expect when using TeXmacs.

David Miller



<TeXmacs|1.0.7.15>

<style|generic>

<\body>
UPDATED: 19 May 2013

\;

From: David E. Miller

Subject: Modifying <name|Python> plugin for feasibility of <name|SymPy>
plugin.

\;

In the <TeXmacs> <name|Python> plugin file t<verbatim|m_python>:

Add to plugin file t<verbatim|m_python> import statement to import
<verbatim|sympy latex> method:

<\framed>
<\verbatim-code>
\;

import os

import traceback

import keyword

import re

import string

from sympy import latex # added to import sympy latex method

DATA_BEGIN = chr(2)

DATA_END = chr(5)

DATA_ESCAPE = chr(27)

DATA_COMMAND = chr(16)

\;
</verbatim-code>
</framed>

<\warning*>
If <name|SymPy> module is not accessible, then an error is likely to be
returned here. Probably need to add a condition that checks for the
module and returns a ``nice'' message that <name|SymPy> module is not
installed or not accessible.
</warning*>

Edit/replace the <verbatim|compose_out(data)> method as follows

<\framed>
<\verbatim-code>
\;

def compose_output(data):

\ \ \ \ \ """Do some parsing on the output according to its type."""

\ \ \ \ \ if isinstance(data, str) and data == "":

\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ return 'verbatim: %s' % str(data)

\ \ \ \ \ if isinstance(data, str) and data.find('Traceback') != -1:

\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ return 'verbatim: %s' % str(data)

\ \ \ \ \ if isinstance(data, str) and data.find('Error:') != -1:

\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ return 'verbatim: %s' % str(data)

\ \ \ \ \ if isinstance(data, int):

\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ return 'verbatim: %d' % data

\ \ \ \ \ if isinstance(data, float):

\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ return 'verbatim: %f' % data

\ \ \ \ \ if isinstance(data, unicode):

\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ data2=r''

\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ for c in data:

\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ if c not in
string.printable:

\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \
data2+='\\x%x'
% ord(c)

\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ else:

\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ data2+=c

\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ data=data2

\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ return 'verbatim: %s' % str(data)

\ \ \ \ \ \ data = latex(data,mode='inline')

\ \ \ \ \ \ return 'latex: %s' % str(data)

\;
</verbatim-code>
</framed>

<\note*>
The <verbatim|latex> method uses the <verbatim|inline> mode when used in
this way.
</note*>

Then a <TeXmacs> <name|Python> session will return <LaTeX> formatted output
as shown below:

<\session|python|default>
<\output>
Python plugin for TeXmacs.

Please see the documentation in Help -\<gtr\> plugins -\<gtr\> Python
</output>

<\input|Python] >
from __future__ import division
</input>

<\input|Python] >
from sympy import *
</input>

<\input|Python] >
x, y, z, t = symbols('x y z t')
</input>

<\input|Python] >
k, m, n = symbols('k m n', integer=True)
</input>

<\input|Python] >
f, g, h = symbols('f g h', cls=Function)
</input>

<\input|Python] >
from sympy import Integral, latex
</input>

<\input|Python] >
from sympy.abc import x
</input>

<\unfolded-io|Python] >
189856
<|unfolded-io>
\ 189856
</unfolded-io>

<\unfolded-io|Python] >
3.14159
<|unfolded-io>
\ 3.141590
</unfolded-io>

<\unfolded-io|Python] >
"a string"
<|unfolded-io>
<math|a*s*t*r*i*n*g>
</unfolded-io>

<\textput>
This looks like a problem. Strings are being formatted as <LaTeX>
variables.

\;
</textput>

<\unfolded-io|Python] >
\\text{a string}
<|unfolded-io>
\ Traceback (most recent call last):

\ \ File "tm_python", line 1

\ \ \ \ \\text{a string}

\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ ^

SyntaxError: unexpected character after line continuation character
</unfolded-io>

<\textput>
This does not work either. <name|Python> system error messages are also
affected. So I added some code to catch exceptions in
<verbatim|compose_out> method to return exception and error messages
verbatim. Otherwise these would be returned as <LaTeX> and these
messages do not display well if not verbatim. The question that remains
if you want to output strings is how to detect what is <LaTeX> and what
is not. What is not (non-<LaTeX> strings), needs to be returned
<verbatim|verbatim> formatted as a string. It looks like everything
else is okay. Or you should merely not use strings with <name|SymPy>.
</textput>

<\textput>
\;
</textput>

<\unfolded-io|Python] >
while True print 'Hello world'
<|unfolded-io>
\ Traceback (most recent call last):

\ \ File "tm_python", line 1

\ \ \ \ while True print 'Hello world'

\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ ^

SyntaxError: invalid syntax
</unfolded-io>

<\textput>
Same issue here, but the error type is different. Again some code was
added to catch this error and format it as <verbatim|verbatim:> instead
of <verbatim|latex:> to avoid the display issue.

\;
</textput>

<\unfolded-io|Python] >
x**2
<|unfolded-io>
<math|x<rsup|2>>
</unfolded-io>

<\unfolded-io|Python] >
1/x
<|unfolded-io>
<math|<frac|1|x>>
</unfolded-io>

<\unfolded-io|Python] >
Integral(x**2, x)
<|unfolded-io>
<math|<big|int>x<rsup|2>*<space|0.25spc>d*x>
</unfolded-io>

\;

<\unfolded-io|Python] >
(1/cos(x)).series(x, 0, 10)
<|unfolded-io>

<math|1+<frac|1|2>*x<rsup|2>+<frac|5|24>*x<rsup|4>+<frac|61|720>*x<rsup|6>+<frac|277|8064>*x<rsup|8>+<with|math-font-family|rm|math-font|cal|O><around*|(|x<rsup|10>|)>>
</unfolded-io>

<\input|Python] >
from sympy import Rational
</input>

<\input|Python] >
a = Rational(1,2)
</input>

<\unfolded-io|Python] >
a
<|unfolded-io>
<math|<frac|1|2>>
</unfolded-io>

<\unfolded-io|Python] >
a**2
<|unfolded-io>
<math|<frac|1|4>>
</unfolded-io>

<\unfolded-io|Python] >
Rational(2)**50/Rational(10)**50
<|unfolded-io>
<math|<frac|1|88817841970012523233890533447265625>>
</unfolded-io>

<\unfolded-io|Python] >
1/2
<|unfolded-io>
\ 0
</unfolded-io>

<\unfolded-io|Python] >
\ x+y+x-y
<|unfolded-io>
<math|2*x>
</unfolded-io>

<\unfolded-io|Python] >
(x+y)**2
<|unfolded-io>
<math|<around*|(|x+y|)><rsup|2>>
</unfolded-io>

<\unfolded-io|Python] >
((x+y)**2).expand()
<|unfolded-io>
<math|x<rsup|2>+2*x*y+y<rsup|2>>
</unfolded-io>

<\input|Python] >
from sympy import apart
</input>

<\unfolded-io|Python] >
1/( (x+2)*(x+1) )
<|unfolded-io>
<math|<frac|1|<around*|(|x+1|)>*<around*|(|x+2|)>>>
</unfolded-io>

<\unfolded-io|Python] >
apart(1/( (x+2)*(x+1) ), x)
<|unfolded-io>
<math|-<frac|1|x+2>+<frac|1|x+1>>
</unfolded-io>

<\input|Python] >
from sympy import limit, Symbol, sin, oo
</input>

<\input|Python] >
x = Symbol("x")
</input>

<\unfolded-io|Python] >
limit(sin(x)/x, x, 0)
<|unfolded-io>
<math|1>
</unfolded-io>

<\input|Python] >
from sympy import diff, Symbol, sin, tan
</input>

<\unfolded-io|Python] >
diff(sin(x), x)
<|unfolded-io>
<math|<math-up|cos><around*|(|x|)>>
</unfolded-io>

<\unfolded-io|Python] >
diff(sin(2*x), x)
<|unfolded-io>
<math|2<math-up|cos><around*|(|2*x|)>>
</unfolded-io>

<\unfolded-io|Python] >
diff(tan(x), x)
<|unfolded-io>
<math|<math-up|tan><rsup|2><around*|(|x|)>+1>
</unfolded-io>

<\input|Python] >
from sympy import Matrix
</input>

<\unfolded-io|Python] >
Matrix([[1,0], [0,1]])
<|unfolded-io>

<math|<around*|(|<matrix*|<tformat|<table|<row|<cell|1>|<cell|0>>|<row|<cell|0>|<cell|1>>>>>|)>>
</unfolded-io>

<\unfolded-io|Python] >
Matrix([[1,0,3], [5,4,3],[0,1,8]])
<|unfolded-io>

<math|<around*|(|<matrix*|<tformat|<table|<row|<cell|1>|<cell|0>|<cell|3>>|<row|<cell|5>|<cell|4>|<cell|3>>|<row|<cell|0>|<cell|1>|<cell|8>>>>>|)>>
</unfolded-io>

\;

<\input|Python] >
\;
</input>
</session>

This is as much code as I have tried using this latest version.
</body>

<\initial>
<\collection>
<associate|language|american>
<associate|page-type|letter>
</collection>
</initial>#!/usr/bin/env python
#####################################################################
##
## MODULE : tm_python.scm
## DESCRIPTION : Initialize python plugin
## COPYRIGHT : (C) 2004 Ero Carrera, address@hidden
## (C) 2012 Adrian Soto
## This software falls under the GNU general public license version 3 or
later.
## It comes WITHOUT ANY WARRANTY WHATSOEVER. For details, see the file LICENSE
## in the root directory or <http://www.gnu.org/licenses/gpl-3.0.html>.

import os
import traceback
import keyword
import re
import string
from sympy import latex # added to import sympy latex method
DATA_BEGIN = chr(2)
DATA_END = chr(5)
DATA_ESCAPE = chr(27)
DATA_COMMAND = chr(16)

__version__='1.0'
__author__='Ero Carrera, Adrian Soto'

class Capture:
"""Capture python output.

Class in charge of recording the output of the
statements/expressions entered in the TeXmacs
session and executed in Python.
"""
def __init__(self):
self.text = ''
def write(self, str):
self.text += str
def getOutput(self):
return self.text
def flush(self):
os.sys.stdout.flush()#Needed?
self.text = ''

def data_begin():
"""Signal the beginning of data to TeXmacs."""
os.sys.stdout.write(chr(2))

def data_end():
"""Signal the end of data to TeXmacs."""
os.sys.stdout.write(chr(5))
os.sys.stdout.flush()

def texmacs_out(out_str):
"""Feed data back to TeXmacs.

Output results back to TeXmacs, with the DATA_BEGIN,
DATA_END control characters."""
data_begin()
os.sys.stdout.write(out_str)
data_end()
os.sys.stdout.flush()

def ps_out(ps_file):
"""Outputs Postscript within TeXmacs.

According the the type of the argument the following
scenarios can take place:

If the argument is a string and has more than one line, it
will be processed as raw Postscript data.

If the argument is a string, it's supposed to contain the
filename of a Postscript file which will be read ( if the
file has no extension, the defaults .ps and .eps will be
tried.)

If the argument is a file or other object which provides a
'read' method, data will be obtained by calling such
method.


Implemented from suggestion by Alvaro Tejero Cantero.
Implementation partially based on information provided
by Mark Arrasmith.
"""

if 'read' in dir(ps_file):
data = ps_file.read()
return chr(2)+'ps:'+data+chr(5)

if ps_file.find('\n')>0:
return chr(2)+'ps:'+ps_file+chr(5)

ext_list = ['', '.eps', '.ps']
if isinstance(ps_file, str):
for ext in ext_list:
if os.path.exists(ps_file+ext):
ps_fd = file(ps_file+ext, 'r')
data = ps_fd.read()
ps_fd.close()
break
else:
raise IOError('File \''+ps_file+'+'+str(ext_list)+'\'
not found.')
return chr(2)+'ps:'+data+chr(5)

def compose_output(data):
"""Do some parsing on the output according to its type."""
if isinstance(data, str) and data == "":
return 'verbatim: %s' % str(data)
if isinstance(data, str) and data.find('Traceback') != -1:
return 'verbatim: %s' % str(data)
if isinstance(data, str) and data.find('Error:') != -1:
return 'verbatim: %s' % str(data)
if isinstance(data, int):
return 'verbatim: %d' % data
if isinstance(data, float):
return 'verbatim: %f' % data
if isinstance(data, unicode):
data2=r''
for c in data:
if c not in string.printable:
data2+='\\x%x' % ord(c)
else:
data2+=c
data=data2
return 'verbatim: %s' % str(data)

data = latex(data,mode='inline')
return 'latex: %s' % str(data)



def do_module_hierarchy(mod, attr):
"""Explore an object's hierarchy.

Go through the objects hierarchy looking for
attributes/methods to provide as autocompletion
options.
"""
dot = attr.find('.')
if dot>0:
if hasattr(mod, attr[:dot]):
next = getattr(mod, attr[:dot])
return do_module_hierarchy(next, attr[dot+1:])
if isinstance(mod, dict):
return dir(mod)
else:
return dir(mod)

def find_completion_candidates(cmpl_str, my_globals):
"""Harvest candidates to provide as autocompletion options."""

haystack =
my_globals.keys()+dir(my_globals['__builtins__'])+keyword.kwlist
dot = cmpl_str.rfind('.')
offset = None
if dot>0:
offset = len(cmpl_str[dot+1:])
first_dot = cmpl_str[:dot].find('.')
if first_dot<0:
mod_name = cmpl_str[:dot]
r_str = cmpl_str[dot+1:]
else:
mod_name = cmpl_str[:first_dot]
r_str = cmpl_str[first_dot+1:]
if mod_name in keyword.kwlist:
return None, []
if os.sys.modules.has_key(mod_name):
haystack =
do_module_hierarchy(os.sys.modules[mod_name], r_str)
elif mod_name in my_globals.keys():
haystack = do_module_hierarchy(my_globals[mod_name],
r_str)
else:
haystack = do_module_hierarchy(type(mod_name), r_str)

return offset, filter(lambda x:x.find(cmpl_str[dot+1:]) == 0,
haystack)

def name_char(c):
"""Check whether a character is a valid symbol."""
if c in '+-*/%<>&|^~ = !,:()[]{}':
return ' '
else:
return c

def complete(cmd, my_globals):
"""Parse autocomplete command.

Parse the command and return a suitable answer to
give back to TeXmacs.
"""

# Parse Texmacs command and extract string to
# complete and offset to complete from.
cmd = cmd.strip()[:-1]
cmd_re = re.compile(r'"(.*)"\s+(\d+)')
res = cmd_re.match(cmd)

# if we don't match anything we return
# no completion possibilities.
if res is None:
return 'scheme:(tuple "" "")'

cmpl_str = res.group(1)
pos_str = int(res.group(2))

cmpl_str = cmpl_str[:pos_str]
if len(cmpl_str) == 0:
return 'scheme:(tuple "" "")'

# We get the string after the last space character.
# no completion is done for strings with spaces
# within
cmpl_str = str().join(map(name_char, cmpl_str))
cmpl_str = cmpl_str.split()[-1]
pos = len(cmpl_str)

# no string after last space? return empty
# completion
if len(cmpl_str) == 0:
return 'scheme:(tuple "" "")'

# Find completion candidates and form a suitable
# answer to Texmacs
offset, cand = find_completion_candidates(cmpl_str, my_globals)
if len(cand) == 0:
res = '""'
else:
res = ''
for c in cand:
if offset is not None:
pos = offset
res += '"%s" ' % c[pos:]
return 'scheme:(tuple "'+cmpl_str+'" '+res+')'

##FIXME: It should display the Python version, just as when one starts
python.
texmacs_out("""verbatim:Python plugin for TeXmacs.
Please see the documentation in Help -> plugins -> Python
""")

my_globals = {}
# We insert into the session's namespace the 'ps_out' method.
my_globals['ps_out'] = ps_out

# As well as some documentation.
my_globals['__doc__'] = """Python plugin for TeXmacs. It provides
autocompletion, and can insert postscript files into TeXmacs.
"""

capt = Capture()
stdout_saved, os.sys.stdout = os.sys.stdout, capt
co = compile('import __builtin__ as __builtins__', 'tm_python', 'exec')
eval(co, my_globals)
os.sys.stdout = stdout_saved

# Main session loop.
while 1:
line=raw_input();
if line[0] == DATA_COMMAND:
if line[1:].find('(complete ') == 0:
#The replace is due to texmacs sending " as two
characters
texmacs_out(complete(line[11:].replace("\\\"","\""),
my_globals))
continue
lines=[line]
while line<>"<EOF>":
line=raw_input()
if line == '': continue
lines.append(line)
text='\n'.join(lines[:-1])
capt=Capture()
try:
out = eval(text, my_globals)
result = out
except:
try:
stdout_saved, os.sys.stdout = os.sys.stdout, capt
co = compile(text, 'tm_python', 'exec')
eval(co, my_globals)
os.sys.stdout = stdout_saved
result = capt.getOutput()
except Exception:
traceback.print_exc(file = os.sys.stdout, limit = 0)
os.sys.stdout = stdout_saved
result = capt.getOutput()
del capt

out = compose_output(result)
texmacs_out(out.strip())


  • [TeXmacs] Update: Modifications to Texmacs Python Plugin To Accommodate SymPy LaTeX Output, David E. Miller, 05/19/2013

Archive powered by MHonArc 2.6.19.

Top of page