formula.py 24.2 KB
Newer Older
1 2 3 4
#!/usr/bin/python
# -*- coding: utf-8 -*-
#
# File:   formula.py
5
# Date:   04-May-12 (creation)
6 7 8 9 10 11
# Author: I. Chuang <ichuang@mit.edu>
#
# flexible python representation of a symbolic mathematical formula.
# Acceptes Presentation MathML, Content MathML (and could also do OpenMath)
# Provides sympy representation.

12 13 14 15
import os
import sys
import string
import re
16
import logging
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
import operator
import sympy
from sympy.printing.latex import LatexPrinter
from sympy.printing.str import StrPrinter
from sympy import latex, sympify
from sympy.physics.quantum.qubit import *
from sympy.physics.quantum.state import *
# from sympy import exp, pi, I
# from sympy.core.operations import LatticeOp
# import sympy.physics.quantum.qubit

import urllib
from xml.sax.saxutils import escape, unescape
import sympy
import unicodedata
from lxml import etree
#import subprocess
import requests
from copy import deepcopy

37 38 39
log = logging.getLogger(__name__)

log.warning("Dark code. Needs review before enabling in prod.")
40 41 42 43 44

os.environ['PYTHONIOENCODING'] = 'utf-8'

#-----------------------------------------------------------------------------

45 46

class dot(sympy.operations.LatticeOp):	 # my dot product
47 48 49 50 51 52
    zero = sympy.Symbol('dotzero')
    identity = sympy.Symbol('dotidentity')

#class dot(sympy.Mul):	# my dot product
#    is_Mul = False

53 54 55

def _print_dot(self, expr):
    return '{((%s) \cdot (%s))}' % (expr.args[0], expr.args[1])
56 57 58 59 60 61

LatexPrinter._print_dot = _print_dot

#-----------------------------------------------------------------------------
# unit vectors (for 8.02)

62 63

def _print_hat(self, expr): return '\\hat{%s}' % str(expr.args[0]).lower()
64 65 66 67 68 69 70

LatexPrinter._print_hat = _print_hat
StrPrinter._print_hat = _print_hat

#-----------------------------------------------------------------------------
# helper routines

71

72
def to_latex(x):
73
    if x is None: return ''
74 75
    # LatexPrinter._print_dot = _print_dot
    xs = latex(x)
76
    xs = xs.replace(r'\XI', 'XI')	 # workaround for strange greek
Peter Baratta committed
77 78 79 80 81 82 83 84 85

    # substitute back into latex form for scripts
    # literally something of the form
    # 'scriptN' becomes '\\mathcal{N}'
    # note: can't use something akin to the _print_hat method above because we sometimes get 'script(N)__B' or more complicated terms
    xs = re.sub(r'script([a-zA-Z0-9]+)',
                '\\mathcal{\\1}',
                xs)

86
    #return '<math>%s{}{}</math>' % (xs[1:-1])
87 88
    if xs[0] == '$':
        return '[mathjax]%s[/mathjax]<br>' % (xs[1:-1])	 # for sympy v6
89 90
    return '[mathjax]%s[/mathjax]<br>' % (xs)		# for sympy v7

91 92 93

def my_evalf(expr, chop=False):
    if type(expr) == list:
94 95 96 97 98 99 100 101 102 103 104 105
        try:
            return [x.evalf(chop=chop) for x in expr]
        except:
            return expr
    try:
        return expr.evalf(chop=chop)
    except:
        return expr

#-----------------------------------------------------------------------------
# my version of sympify to import expression into sympy

106 107

def my_sympify(expr, normphase=False, matrix=False, abcsym=False, do_qubit=False, symtab=None):
108 109 110 111
    # make all lowercase real?
    if symtab:
        varset = symtab
    else:
112 113 114 115 116
        varset = {'p': sympy.Symbol('p'),
                  'g': sympy.Symbol('g'),
                  'e': sympy.E,			# for exp
                  'i': sympy.I,			# lowercase i is also sqrt(-1)
                  'Q': sympy.Symbol('Q'),	 # otherwise it is a sympy "ask key"
117
                  'I': sympy.Symbol('I'),	 # otherwise it is sqrt(-1)
Peter Baratta committed
118
                  'N': sympy.Symbol('N'),	 # or it is some kind of sympy function
119 120 121
                  #'X':sympy.sympify('Matrix([[0,1],[1,0]])'),
                  #'Y':sympy.sympify('Matrix([[0,-I],[I,0]])'),
                  #'Z':sympy.sympify('Matrix([[1,0],[0,-1]])'),
122 123 124
                  'ZZ': sympy.Symbol('ZZ'),	 # otherwise it is the PythonIntegerRing
                  'XI': sympy.Symbol('XI'),	 # otherwise it is the capital \XI
                  'hat': sympy.Function('hat'),	 # for unit vectors (8.02)
125 126
                  }
    if do_qubit:		# turn qubit(...) into Qubit instance
127 128 129 130
        varset.update({'qubit': sympy.physics.quantum.qubit.Qubit,
                       'Ket': sympy.physics.quantum.state.Ket,
                       'dot': dot,
                       'bit': sympy.Function('bit'),
131 132 133
                       })
    if abcsym:			# consider all lowercase letters as real symbols, in the parsing
        for letter in string.lowercase:
134
            if letter in varset:	 # exclude those already done
135
                continue
136
            varset.update({letter: sympy.Symbol(letter, real=True)})
137

138 139 140
    sexpr = sympify(expr, locals=varset)
    if normphase:	 # remove overall phase if sexpr is a list
        if type(sexpr) == list:
141 142
            if sexpr[0].is_number:
                ophase = sympy.sympify('exp(-I*arg(%s))' % sexpr[0])
143
                sexpr = [sympy.Mul(x, ophase) for x in sexpr]
144 145

    def to_matrix(x):		# if x is a list of lists, and is rectangular, then return Matrix(x)
146
        if not type(x) == list:
147 148
            return x
        for row in x:
149
            if (not type(row) == list):
150 151 152
                return x
        rdim = len(x[0])
        for row in x:
153
            if not len(row) == rdim:
154 155 156 157 158 159 160 161 162 163
                return x
        return sympy.Matrix(x)

    if matrix:
        sexpr = to_matrix(sexpr)
    return sexpr

#-----------------------------------------------------------------------------
# class for symbolic mathematical formulas

164

165 166
class formula(object):
    '''
167 168 169
    Representation of a mathematical formula object.  Accepts mathml math expression
    for constructing, and can produce sympy translation.  The formula may or may not
    include an assignment (=).
170
    '''
171
    def __init__(self, expr, asciimath='', options=None):
172 173 174 175
        self.expr = expr.strip()
        self.asciimath = asciimath
        self.the_cmathml = None
        self.the_sympy = None
176
        self.options = options
177 178 179 180 181 182 183

    def is_presentation_mathml(self):
        return '<mstyle' in self.expr

    def is_mathml(self):
        return '<math ' in self.expr

184
    def fix_greek_in_mathml(self, xml):
185
        def gettag(x):
186
            return re.sub('{http://[^}]+}', '', x.tag)
187 188 189

        for k in xml:
            tag = gettag(k)
190
            if tag == 'mi' or tag == 'ci':
191 192 193
                usym = unicode(k.text)
                try:
                    udata = unicodedata.name(usym)
194 195
                except Exception, err:
                    udata = None
196 197 198
                #print "usym = %s, udata=%s" % (usym,udata)
                if udata:			# eg "GREEK SMALL LETTER BETA"
                    if 'GREEK' in udata:
199
                        usym = udata.split(' ')[-1]
200 201 202 203 204 205
                        if 'SMALL' in udata: usym = usym.lower()
                        #print "greek: ",usym
                k.text = usym
            self.fix_greek_in_mathml(k)
        return xml

206
    def preprocess_pmathml(self, xml):
207
        '''
208 209 210 211 212 213
        Pre-process presentation MathML from ASCIIMathML to make it more
        acceptable for SnuggleTeX, and also to accomodate some sympy
        conventions (eg hat(i) for \hat{i}).

        This method would be a good spot to look for an integral and convert
        it, if possible...
214 215
        '''

216
        if type(xml) == str or type(xml) == unicode:
217 218
            xml = etree.fromstring(xml)		# TODO: wrap in try

219
        xml = self.fix_greek_in_mathml(xml)	 # convert greek utf letters to greek spelled out in ascii
220 221

        def gettag(x):
222
            return re.sub('{http://[^}]+}', '', x.tag)
223 224 225 226 227 228 229

        # f and g are processed as functions by asciimathml, eg  "f-2" turns into "<mrow><mi>f</mi><mo>-</mo></mrow><mn>2</mn>"
        # this is really terrible for turning into cmathml.
        # undo this here.
        def fix_pmathml(xml):
            for k in xml:
                tag = gettag(k)
230 231 232
                if tag == 'mrow':
                    if len(k) == 2:
                        if gettag(k[0]) == 'mi' and k[0].text in ['f', 'g'] and gettag(k[1]) == 'mo':
233
                            idx = xml.index(k)
234 235
                            xml.insert(idx, deepcopy(k[0]))	 # drop the <mrow> container
                            xml.insert(idx + 1, deepcopy(k[1]))
236 237 238 239 240 241 242 243 244 245 246
                            xml.remove(k)
                fix_pmathml(k)

        fix_pmathml(xml)

        # hat i is turned into <mover><mi>i</mi><mo>^</mo></mover> ; mangle this into <mi>hat(f)</mi>
        # hat i also somtimes turned into <mover><mrow> <mi>j</mi> </mrow><mo>^</mo></mover>

        def fix_hat(xml):
            for k in xml:
                tag = gettag(k)
247 248 249
                if tag == 'mover':
                    if len(k) == 2:
                        if gettag(k[0]) == 'mi' and gettag(k[1]) == 'mo' and str(k[1].text) == '^':
250 251
                            newk = etree.Element('mi')
                            newk.text = 'hat(%s)' % k[0].text
252 253
                            xml.replace(k, newk)
                        if gettag(k[0]) == 'mrow' and gettag(k[0][0]) == 'mi' and gettag(k[1]) == 'mo' and str(k[1].text) == '^':
254 255
                            newk = etree.Element('mi')
                            newk.text = 'hat(%s)' % k[0][0].text
256
                            xml.replace(k, newk)
257 258 259
                fix_hat(k)
        fix_hat(xml)

260
        def flatten_pmathml(xml):
261 262 263 264 265 266 267 268 269 270 271
            ''' Give the text version of certain PMathML elements

            Sometimes MathML will be given with each letter separated (it
            doesn't know if its implicit multiplication or what). From an xml
            node, find the (text only) variable name it represents. So it takes
            <mrow>
              <mi>m</mi>
              <mi>a</mi>
              <mi>x</mi>
            </mrow>
            and returns 'max', for easier use later on.
272 273 274 275 276 277 278
            '''
            tag = gettag(xml)
            if tag == 'mn': return xml.text
            elif tag == 'mi': return xml.text
            elif tag == 'mrow': return ''.join([flatten_pmathml(y) for y in xml])
            raise Exception, '[flatten_pmathml] unknown tag %s' % tag

Peter Baratta committed
279 280 281 282 283 284 285 286 287 288 289 290 291 292 293
        def fix_mathvariant(parent):
            '''Fix certain kinds of math variants

            Literally replace <mstyle mathvariant="script"><mi>N</mi></mstyle>
            with 'scriptN'. There have been problems using script_N or script(N)
            '''
            for child in parent:
                if (gettag(child) == 'mstyle' and child.get('mathvariant') == 'script'):
                    newchild = etree.Element('mi')
                    newchild.text = 'script%s' % flatten_pmathml(child[0])
                    parent.replace(child, newchild)
                fix_mathvariant(child)
        fix_mathvariant(xml)


294 295 296 297
        # find "tagged" superscripts
        # they have the character \u200b in the superscript
        # replace them with a__b so snuggle doesn't get confused
        def fix_superscripts(xml):
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330
            ''' Look for and replace sup elements with 'X__Y' or 'X_Y__Z'

            In the javascript, variables with '__X' in them had an invisible
            character inserted into the sup (to distinguish from powers)
            E.g. normal:
            <msubsup>
              <mi>a</mi>
              <mi>b</mi>
              <mi>c</mi>
            </msubsup>
            to be interpreted '(a_b)^c' (nothing done by this method)

            And modified:
            <msubsup>
              <mi>b</mi>
              <mi>x</mi>
              <mrow>
                <mo>&#x200B;</mo>
                <mi>d</mi>
              </mrow>
            </msubsup>
            to be interpreted 'a_b__c'

            also:
            <msup>
              <mi>x</mi>
              <mrow>
                <mo>&#x200B;</mo>
                <mi>B</mi>
              </mrow>
            </msup>
            to be 'x__B'
            '''
331 332 333
            for k in xml:
                tag = gettag(k)

334 335 336
                # match things like the last example--
                # the second item in msub is an mrow with the first
                # character equal to \u200b
337 338 339 340
                if (tag == 'msup' and
                    len(k) == 2 and gettag(k[1]) == 'mrow' and
                    gettag(k[1][0]) == 'mo' and k[1][0].text == u'\u200b'): # whew

341
                    # replace the msup with 'X__Y'
342 343 344 345 346
                    k[1].remove(k[1][0])
                    newk = etree.Element('mi')
                    newk.text = '%s__%s' % (flatten_pmathml(k[0]), flatten_pmathml(k[1]))
                    xml.replace(k, newk)

347 348 349
                # match things like the middle example-
                # the third item in msubsup is an mrow with the first
                # character equal to \u200b
350 351 352 353
                if (tag == 'msubsup' and
                    len(k) == 3 and gettag(k[2]) == 'mrow' and
                    gettag(k[2][0]) == 'mo' and k[2][0].text == u'\u200b'): # whew

354
                    # replace the msubsup with 'X_Y__Z'
355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380
                    k[2].remove(k[2][0])
                    newk = etree.Element('mi')
                    newk.text = '%s_%s__%s' % (flatten_pmathml(k[0]), flatten_pmathml(k[1]), flatten_pmathml(k[2]))
                    xml.replace(k, newk)

                fix_superscripts(k)
        fix_superscripts(xml)

        # Snuggle returns an error when it sees an <msubsup>
        # replace such elements with an <msup>, except the first element is of
        # the form a_b. I.e. map a_b^c => (a_b)^c
        def fix_msubsup(parent):
            for child in parent:
                # fix msubsup
                if (gettag(child) == 'msubsup' and len(child) == 3):
                    newchild = etree.Element('msup')
                    newbase = etree.Element('mi')
                    newbase.text = '%s_%s' % (flatten_pmathml(child[0]), flatten_pmathml(child[1]))
                    newexp = child[2]
                    newchild.append(newbase)
                    newchild.append(newexp)
                    parent.replace(child, newchild)

                fix_msubsup(child)
        fix_msubsup(xml)

381 382 383 384 385 386 387
        self.xml = xml
        return self.xml

    def get_content_mathml(self):
        if self.the_cmathml: return self.the_cmathml

        # pre-process the presentation mathml before sending it to snuggletex to convert to content mathml
388 389
        try:
            xml = self.preprocess_pmathml(self.expr)
390
        except Exception, err:
391
            log.warning('Err %s while preprocessing; expr=%s' % (err, self.expr))
392
            return "<html>Error! Cannot process pmathml</html>"
393
        pmathml = etree.tostring(xml, pretty_print=True)
394 395 396
        self.the_pmathml = pmathml

        # convert to cmathml
397
        self.the_cmathml = self.GetContentMathML(self.asciimath, pmathml)
398 399
        return self.the_cmathml

400
    cmathml = property(get_content_mathml, None, None, 'content MathML representation')
401

402
    def make_sympy(self, xml=None):
403
        '''
404 405
        Return sympy expression for the math formula.
        The math formula is converted to Content MathML then that is parsed.
406 407 408

        This is a recursive function, called on every CMML node. Support for
        more functions can be added by modifying opdict, abould halfway down
409 410 411 412
        '''

        if self.the_sympy: return self.the_sympy

413
        if xml is None:	 # root
414 415 416
            if not self.is_mathml():
                return my_sympify(self.expr)
            if self.is_presentation_mathml():
417
                cmml = None
418 419 420
                try:
                    cmml = self.cmathml
                    xml = etree.fromstring(str(cmml))
421
                except Exception, err:
422 423 424 425 426
                    if 'conversion from Presentation MathML to Content MathML was not successful' in cmml:
                        msg = "Illegal math expression"
                    else:
                        msg = 'Err %s while converting cmathml to xml; cmml=%s' % (err, cmml)
                    raise Exception, msg
427 428 429 430 431 432 433 434 435
                xml = self.fix_greek_in_mathml(xml)
                self.the_sympy = self.make_sympy(xml[0])
            else:
                xml = etree.fromstring(self.expr)
                xml = self.fix_greek_in_mathml(xml)
                self.the_sympy = self.make_sympy(xml[0])
            return self.the_sympy

        def gettag(x):
436
            return re.sub('{http://[^}]+}', '', x.tag)
437 438 439

        # simple math
        def op_divide(*args):
440 441
            if not len(args) == 2:
                raise Exception, 'divide given wrong number of arguments!'
442
            # print "divide: arg0=%s, arg1=%s" % (args[0],args[1])
443 444 445
            return sympy.Mul(args[0], sympy.Pow(args[1], -1))

        def op_plus(*args): return args[0] if len(args) == 1 else op_plus(*args[:-1]) + args[-1]
446

447
        def op_times(*args): return reduce(operator.mul, args)
448 449

        def op_minus(*args):
450
            if len(args) == 1:
451
                return -args[0]
452 453
            if not len(args) == 2:
                raise Exception, 'minus given wrong number of arguments!'
454
            #return sympy.Add(args[0],-args[1])
455
            return args[0] - args[1]
456

457 458 459 460
        opdict = {'plus': op_plus,
                  'divide': operator.div,
                  'times': op_times,
                  'minus': op_minus,
461 462 463
                  #'plus': sympy.Add,
                  #'divide' : op_divide,
                  #'times' : sympy.Mul,
464 465 466
                  'minus': op_minus,
                  'root': sympy.sqrt,
                  'power': sympy.Pow,
467 468
                  'sin': sympy.sin,
                  'cos': sympy.cos,
469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486
                  'tan': sympy.tan,
                  'cot': sympy.cot,
                  'sinh': sympy.sinh,
                  'cosh': sympy.cosh,
                  'coth': sympy.coth,
                  'tanh': sympy.tanh,
                  'asin': sympy.asin,
                  'acos': sympy.acos,
                  'atan': sympy.atan,
                  'atan2': sympy.atan2,
                  'acot': sympy.acot,
                  'asinh': sympy.asinh,
                  'acosh': sympy.acosh,
                  'atanh': sympy.atanh,
                  'acoth': sympy.acoth,
                  'exp': sympy.exp,
                  'log': sympy.log,
                  'ln': sympy.ln,
487 488 489 490 491 492 493 494 495 496 497
                   }

        # simple sumbols
        nums1dict = {'pi': sympy.pi,
                     }

        def parsePresentationMathMLSymbol(xml):
            '''
            Parse <msub>, <msup>, <mi>, and <mn>
            '''
            tag = gettag(xml)
498 499 500 501 502
            if tag == 'mn': return xml.text
            elif tag == 'mi': return xml.text
            elif tag == 'msub': return '_'.join([parsePresentationMathMLSymbol(y) for y in xml])
            elif tag == 'msup': return '^'.join([parsePresentationMathMLSymbol(y) for y in xml])
            raise Exception, '[parsePresentationMathMLSymbol] unknown tag %s' % tag
503

504
        # parser tree for Content MathML
505
        tag = gettag(xml)
506
        # print "tag = ",tag
507 508 509

        # first do compound objects

510
        if tag == 'apply':		# apply operator
511 512 513
            opstr = gettag(xml[0])
            if opstr in opdict:
                op = opdict[opstr]
514
                args = [self.make_sympy(x) for x in xml[1:]]
515 516
                try:
                    res = op(*args)
517
                except Exception, err:
518 519
                    self.args = args
                    self.op = op
520
                    raise Exception, '[formula] error=%s failed to apply %s to args=%s' % (err, opstr, args)
521
                return res
522
            else:
523
                raise Exception, '[formula]: unknown operator tag %s' % (opstr)
524

525 526
        elif tag == 'list':		# square bracket list
            if gettag(xml[0]) == 'matrix':
527 528
                return self.make_sympy(xml[0])
            else:
529
                return [self.make_sympy(x) for x in xml]
530

531 532
        elif tag == 'matrix':
            return sympy.Matrix([self.make_sympy(x) for x in xml])
533

534 535
        elif tag == 'vector':
            return [self.make_sympy(x) for x in xml]
536 537 538

        # atoms are below

539
        elif tag == 'cn':			# number
540 541 542
            return sympy.sympify(xml.text)
            return float(xml.text)

543 544
        elif tag == 'ci':			# variable (symbol)
            if len(xml) > 0 and (gettag(xml[0]) == 'msub' or gettag(xml[0]) == 'msup'):	 # subscript or superscript
545 546 547 548 549 550 551
                usym = parsePresentationMathMLSymbol(xml[0])
                sym = sympy.Symbol(str(usym))
            else:
                usym = unicode(xml.text)
                if 'hat' in usym:
                    sym = my_sympify(usym)
                else:
552
                    if usym == 'i' and self.options is not None and 'imaginary' in self.options:	 # i = sqrt(-1)
553
                        sym = sympy.I
554 555
                    else:
                        sym = sympy.Symbol(str(usym))
556 557 558
            return sym

        else:				# unknown tag
559
            raise Exception, '[formula] unknown tag %s' % tag
560

561
    sympy = property(make_sympy, None, None, 'sympy representation')
562

563
    def GetContentMathML(self, asciimath, mathml):
564
        # URL = 'http://192.168.1.2:8080/snuggletex-webapp-1.2.2/ASCIIMathMLUpConversionDemo'
565
        # URL = 'http://127.0.0.1:8080/snuggletex-webapp-1.2.2/ASCIIMathMLUpConversionDemo'
566
        URL = 'https://math-xserver.mitx.mit.edu/snuggletex-webapp-1.2.2/ASCIIMathMLUpConversionDemo'
567 568

        if 1:
569 570
            payload = {'asciiMathInput': asciimath,
                       'asciiMathML': mathml,
571 572
                       #'asciiMathML':unicode(mathml).encode('utf-8'),
                       }
573
            headers = {'User-Agent': "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.13) Gecko/20080311 Firefox/2.0.0.13"}
574
            r = requests.post(URL, data=payload, headers=headers, verify=False)
575 576 577 578 579
            r.encoding = 'utf-8'
            ret = r.text
            #print "encoding: ",r.encoding

        # return ret
580

581 582 583 584 585 586
        mode = 0
        cmathml = []
        for k in ret.split('\n'):
            if 'conversion to Content MathML' in k:
                mode = 1
                continue
587
            if mode == 1:
588 589 590 591 592 593 594 595 596 597
                if '<h3>Maxima Input Form</h3>' in k:
                    mode = 0
                    continue
                cmathml.append(k)
        # return '\n'.join(cmathml)
        cmathml = '\n'.join(cmathml[2:])
        cmathml = '<math xmlns="http://www.w3.org/1998/Math/MathML">\n' + unescape(cmathml) + '\n</math>'
        # print cmathml
        #return unicode(cmathml)
        return cmathml
598

599 600
#-----------------------------------------------------------------------------

601

602 603 604 605 606 607 608 609 610 611 612 613
def test1():
    xmlstr = '''
<math xmlns="http://www.w3.org/1998/Math/MathML">
   <apply>
      <plus/>
      <cn>1</cn>
      <cn>2</cn>
   </apply>
</math>
    '''
    return formula(xmlstr)

614

615 616 617 618 619 620 621 622 623
def test2():
    xmlstr = u'''
<math xmlns="http://www.w3.org/1998/Math/MathML">
   <apply>
      <plus/>
      <cn>1</cn>
      <apply>
         <times/>
         <cn>2</cn>
624
     <ci>α</ci>
625 626 627 628 629 630
      </apply>
   </apply>
</math>
    '''
    return formula(xmlstr)

631

632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647
def test3():
    xmlstr = '''
<math xmlns="http://www.w3.org/1998/Math/MathML">
   <apply>
      <divide/>
      <cn>1</cn>
      <apply>
         <plus/>
         <cn>2</cn>
         <ci>γ</ci>
      </apply>
   </apply>
</math>
    '''
    return formula(xmlstr)

648

649 650 651 652 653 654 655 656 657 658 659 660 661 662
def test4():
    xmlstr = u'''
<math xmlns="http://www.w3.org/1998/Math/MathML">
  <mstyle displaystyle="true">
    <mn>1</mn>
    <mo>+</mo>
    <mfrac>
      <mn>2</mn>
      <mi>α</mi>
    </mfrac>
  </mstyle>
</math>
'''
    return formula(xmlstr)
663

664

665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727
def test5():		# sum of two matrices
    xmlstr = u'''
<math xmlns="http://www.w3.org/1998/Math/MathML">
  <mstyle displaystyle="true">
    <mrow>
      <mi>cos</mi>
      <mrow>
        <mo>(</mo>
        <mi>&#x3B8;</mi>
        <mo>)</mo>
      </mrow>
    </mrow>
    <mo>&#x22C5;</mo>
    <mrow>
      <mo>[</mo>
      <mtable>
        <mtr>
          <mtd>
            <mn>1</mn>
          </mtd>
          <mtd>
            <mn>0</mn>
          </mtd>
        </mtr>
        <mtr>
          <mtd>
            <mn>0</mn>
          </mtd>
          <mtd>
            <mn>1</mn>
          </mtd>
        </mtr>
      </mtable>
      <mo>]</mo>
    </mrow>
    <mo>+</mo>
    <mrow>
      <mo>[</mo>
      <mtable>
        <mtr>
          <mtd>
            <mn>0</mn>
          </mtd>
          <mtd>
            <mn>1</mn>
          </mtd>
        </mtr>
        <mtr>
          <mtd>
            <mn>1</mn>
          </mtd>
          <mtd>
            <mn>0</mn>
          </mtd>
        </mtr>
      </mtable>
      <mo>]</mo>
    </mrow>
  </mstyle>
</math>
'''
    return formula(xmlstr)

728

729 730 731 732 733 734 735 736 737 738
def test6():		# imaginary numbers
    xmlstr = u'''
<math xmlns="http://www.w3.org/1998/Math/MathML">
  <mstyle displaystyle="true">
    <mn>1</mn>
    <mo>+</mo>
    <mi>i</mi>
  </mstyle>
</math>
'''
739
    return formula(xmlstr, options='imaginaryi')