def full_compare(answer, response, vars=[], exclude=[]): """ Performs a full symbolic comparison between two expressions. This method will perform a full comparison between the two expressions passed to it. This will involve fully simplifying each and thencomparing to see if the result is the same. A list of the variables used must also be passed. Optionally a list of excluded expressions can also be passed. If the response matches one of the excluded expressions, using the simple compare method, then the comparison will return false. This is to allow questions such as "expand (x+1)^2" to stop the student just entering "(x+1)^2" and have SAGE accept it as a match to the correct answer. @type answer: string @param answer: the answer expression to compare against @type response: string @param response: the response expression entered by the student @type vars: array @param vars: an array of variable names used in the expressions @type exclude: array @param exclude: an array of expressions to exclude as invalid @rtype: bool @return: true if the expressions are equal, false otherwise """ web.debug( "Full compare of '%s' to '%s' with variables '%s' excluding '%s'" % (answer, response, ','.join(vars), ';'.join(exclude))) try: answer = replace_variables(answer, vars) response = replace_variables(response, vars) answer = algebra.convert_latex(answer) response = algebra.convert_latex(response) sage.misc.preparser.implicit_multiplication(10) web.debug("Answer: " + answer) web.debug("Response: " + response) web.debug("Level: " + str(sage.misc.preparser.implicit_multiplication())) answer = preparse(answer).replace('Integer', '').replace('RealNumber', '') response = preparse(response).replace('Integer', '').replace('RealNumber', '') web.debug("Answer: " + answer) web.debug("Response: " + response) answer_expr = symbolic_expression_from_string(answer) response_expr = symbolic_expression_from_string(response) except SyntaxError, e: web.debug("Error parsing answer and response expressions: %s" % e) f = "Error parsing answer and response expressions: %s" % e return f
def simple_compare(expr1, expr2, vars=[]): """ Performs a simple symbolic comparison between two expressions. This method will perform a very simple comparision between two algebraic expressions. No expansion or factorization is performed hence expressions like "(x+1)^2" and "(1+x)^2" will evaluate as the same but expressions like "(x+1)^2" and "x^2+2*x+1" will not. @type expr1: string @param expr1: the first expression to compare @type expr2: string @param expr2: the second expression to compare @type vars: array @param vars: an array of variable names used in the expressions @rtype: bool @return: true if the expressions are equal, false otherwise """ web.debug("Simple compare of '%s' to '%s' with variables %s" % (expr1, expr2, vars)) expr1 = replace_variables(expr1, vars) expr2 = replace_variables(expr2, vars) expr1 = algebra.convert_latex(expr1) expr2 = algebra.convert_latex(expr2) sage.misc.preparser.implicit_multiplication(10) web.debug("Expr1: " + expr1) web.debug("Expr2: " + expr2) web.debug("Level: " + str(sage.misc.preparser.implicit_multiplication())) expr1 = preparse(expr1).replace('Integer', '').replace('RealNumber', '') expr2 = preparse(expr2).replace('Integer', '').replace('RealNumber', '') web.debug("Expr1: " + expr1) web.debug("Expr2: " + expr2) f = symbolic_expression_from_string("(%s)-(%s)" % (expr1, expr2)) # Simply check to see if the representation of the expression is # a string containing the single character '0'. result = {'expr1': expr1, 'expr2': expr2, 'result': (f.__repr__() == '0')} return result
def _sage_(self, locals={}): r""" Attempt to return a Sage version of this object. This method works successfully when Mathematica returns a result or list of results that consist only of: - numbers, i.e. integers, floats, complex numbers; - functions and named constants also present in Sage, where: - Sage knows how to translate the function or constant's name from Mathematica's naming scheme, or - you provide a translation dictionary `locals`, or - the Sage name for the function or constant is simply the Mathematica name in lower case; - symbolic variables whose names don't pathologically overlap with objects already defined in Sage. This method will not work when Mathematica's output includes: - strings; - functions unknown to Sage that are not specified in `locals`; - Mathematica functions with different parameters/parameter order to the Sage equivalent. In this case, define a function to do the parameter conversion, and pass it in via the locals dictionary. EXAMPLES: Mathematica lists of numbers/constants become Sage lists of numbers/constants:: sage: m = mathematica('{{1., 4}, Pi, 3.2e100, I}') # optional - mathematica sage: s = m.sage(); s # optional - mathematica [[1.0, 4], pi, 3.2*e100, I] sage: s[1].n() # optional - mathematica 3.14159265358979 sage: s[3]^2 # optional - mathematica -1 :: sage: m = mathematica('x^2 + 5*y') # optional - mathematica sage: m.sage() # optional - mathematica x^2 + 5*y :: sage: m = mathematica('Sin[Sqrt[1-x^2]] * (1 - Cos[1/x])^2') # optional - mathematica sage: m.sage() # optional - mathematica (cos(1/x) - 1)^2*sin(sqrt(-x^2 + 1)) :: sage: m = mathematica('NewFn[x]') # optional - mathematica sage: m._sage_(locals={'NewFn': sin}) # optional - mathematica sin(x) :: sage: var('bla') # optional - mathematica bla sage: m = mathematica('bla^2') # optional - mathematica sage: bla^2 - m.sage() # optional - mathematica 0 :: sage: m = mathematica('bla^2') # optional - mathematica sage: mb = m.sage() # optional - mathematica sage: var('bla') # optional - mathematica bla sage: bla^2 - mb # optional - mathematica 0 AUTHORS: - Felix Lawrence (2010-11-03): Major rewrite to use ._sage_repr() and sage.calculus.calculus.symbolic_expression_from_string() for greater compatibility, while still supporting conversion of symbolic expressions. """ from sage.symbolic.pynac import symbol_table from sage.symbolic.constants import constants_name_table as constants from sage.calculus.calculus import symbolic_expression_from_string from sage.calculus.calculus import _find_func as find_func # Get Mathematica's output and perform preliminary formatting res = self._sage_repr() if '"' in res: raise NotImplementedError, "String conversion from Mathematica \ does not work. Mathematica's output was: %s" % res # Find all the mathematica functions, constants and symbolic variables # present in `res`. Convert MMA functions and constants to their # Sage equivalents (if possible), using `locals` and # `sage.symbolic.pynac.symbol_table['mathematica']` as translation # dictionaries. If a MMA function or constant is not either # dictionary, then we use a variety of tactics listed in `autotrans`. # If a MMA variable is not in any dictionary, then create an # identically named Sage equivalent. # Merge the user-specified locals dictionary and the symbol_table # (locals takes priority) lsymbols = symbol_table['mathematica'].copy() lsymbols.update(locals) # Strategies for translating unknown functions/constants: autotrans = [ str.lower, # Try it in lower case _un_camel, # Convert `CamelCase` to `camel_case` lambda x: x # Try the original name ] # Find the MMA funcs/vars/constants - they start with a letter. # Exclude exponents (e.g. 'e8' from 4.e8) p = re.compile('(?<!\.)[a-zA-Z]\w*') for m in p.finditer(res): # If the function, variable or constant is already in the # translation dictionary, then just move on. if m.group() in lsymbols: pass # Now try to translate all other functions -- try each strategy # in `autotrans` and check if the function exists in Sage elif m.end() < len(res) and res[m.end()] == '(': for t in autotrans: f = find_func(t(m.group()), create_when_missing = False) if f != None: lsymbols[m.group()] = f break else: raise NotImplementedError, "Don't know a Sage equivalent \ for Mathematica function '%s'. Please specify one \ manually using the 'locals' dictionary" % m.group() # Check if Sage has an equivalent constant else: for t in autotrans: if t(m.group()) in constants: lsymbols[m.group()] = constants[t(m.group())] break # If Sage has never heard of the variable, then # symbolic_expression_from_string will automatically create it try: return symbolic_expression_from_string(res, lsymbols, accept_sequence=True) except StandardError: raise NotImplementedError, "Unable to parse Mathematica \ output: %s" % res
def symbolic_expression_from_mathematica_string(mexpr): r""" Translate a mathematica string into a symbolic expression INPUT: - ``mexpr`` -- string OUTPUT: symbolic expression EXAMPLES:: sage: from sage.symbolic.integration.external import symbolic_expression_from_mathematica_string sage: symbolic_expression_from_mathematica_string(u'-Cos[x]') -cos(x) """ import re from sage.libs.pynac.pynac import symbol_table from sage.interfaces.mathematica import _un_camel as un_camel from sage.symbolic.constants import constants_name_table as constants from sage.calculus.calculus import symbolic_expression_from_string from sage.calculus.calculus import _find_func as find_func expr = mexpr.replace('\n', ' ').replace('\r', '') expr = expr.replace('[', '(').replace(']', ')') expr = expr.replace('{', '[').replace('}', ']') lsymbols = symbol_table['mathematica'].copy() autotrans = [ lambda x: x.lower(), # Try it in lower case un_camel, # Convert `CamelCase` to `camel_case` lambda x: x ] # Try the original name # Find the MMA funcs/vars/constants - they start with a letter. # Exclude exponents (e.g. 'e8' from 4.e8) p = re.compile(r'(?<!\.)[a-zA-Z]\w*') for m in p.finditer(expr): # If the function, variable or constant is already in the # translation dictionary, then just move on. if m.group() in lsymbols: pass # Now try to translate all other functions -- try each strategy # in `autotrans` and check if the function exists in Sage elif m.end() < len(expr) and expr[m.end()] == '(': for t in autotrans: f = find_func(t(m.group()), create_when_missing=False) if f is not None: lsymbols[m.group()] = f break else: raise NotImplementedError( "Don't know a Sage equivalent for Mathematica function '%s'." % m.group()) # Check if Sage has an equivalent constant else: for t in autotrans: if t(m.group()) in constants: lsymbols[m.group()] = constants[t(m.group())] break return symbolic_expression_from_string(expr, lsymbols, accept_sequence=True)
def _sage_(self, locals={}): r""" Attempt to return a Sage version of this object. This method works successfully when Mathematica returns a result or list of results that consist only of: - numbers, i.e. integers, floats, complex numbers; - functions and named constants also present in Sage, where: - Sage knows how to translate the function or constant's name from Mathematica's naming scheme, or - you provide a translation dictionary `locals`, or - the Sage name for the function or constant is simply the Mathematica name in lower case; - symbolic variables whose names don't pathologically overlap with objects already defined in Sage. This method will not work when Mathematica's output includes: - strings; - functions unknown to Sage that are not specified in `locals`; - Mathematica functions with different parameters/parameter order to the Sage equivalent. In this case, define a function to do the parameter conversion, and pass it in via the locals dictionary. EXAMPLES: Mathematica lists of numbers/constants become Sage lists of numbers/constants:: sage: m = mathematica('{{1., 4}, Pi, 3.2e100, I}') # optional - mathematica sage: s = m.sage(); s # optional - mathematica [[1.0, 4], pi, 3.2*e100, I] sage: s[1].n() # optional - mathematica 3.14159265358979 sage: s[3]^2 # optional - mathematica -1 :: sage: m = mathematica('x^2 + 5*y') # optional - mathematica sage: m.sage() # optional - mathematica x^2 + 5*y :: sage: m = mathematica('Sin[Sqrt[1-x^2]] * (1 - Cos[1/x])^2') # optional - mathematica sage: m.sage() # optional - mathematica (cos(1/x) - 1)^2*sin(sqrt(-x^2 + 1)) :: sage: m = mathematica('NewFn[x]') # optional - mathematica sage: m._sage_(locals={'NewFn': sin}) # optional - mathematica sin(x) :: sage: var('bla') # optional - mathematica bla sage: m = mathematica('bla^2') # optional - mathematica sage: bla^2 - m.sage() # optional - mathematica 0 :: sage: m = mathematica('bla^2') # optional - mathematica sage: mb = m.sage() # optional - mathematica sage: var('bla') # optional - mathematica bla sage: bla^2 - mb # optional - mathematica 0 AUTHORS: - Felix Lawrence (2010-11-03): Major rewrite to use ._sage_repr() and sage.calculus.calculus.symbolic_expression_from_string() for greater compatibility, while still supporting conversion of symbolic expressions. """ from sage.symbolic.pynac import symbol_table from sage.symbolic.constants import constants_name_table as constants from sage.calculus.calculus import symbolic_expression_from_string from sage.calculus.calculus import _find_func as find_func # Get Mathematica's output and perform preliminary formatting res = self._sage_repr() if '"' in res: raise NotImplementedError("String conversion from Mathematica \ does not work. Mathematica's output was: %s" % res) # Find all the mathematica functions, constants and symbolic variables # present in `res`. Convert MMA functions and constants to their # Sage equivalents (if possible), using `locals` and # `sage.symbolic.pynac.symbol_table['mathematica']` as translation # dictionaries. If a MMA function or constant is not either # dictionary, then we use a variety of tactics listed in `autotrans`. # If a MMA variable is not in any dictionary, then create an # identically named Sage equivalent. # Merge the user-specified locals dictionary and the symbol_table # (locals takes priority) lsymbols = symbol_table['mathematica'].copy() lsymbols.update(locals) # Strategies for translating unknown functions/constants: autotrans = [ str.lower, # Try it in lower case _un_camel, # Convert `CamelCase` to `camel_case` lambda x: x # Try the original name ] # Find the MMA funcs/vars/constants - they start with a letter. # Exclude exponents (e.g. 'e8' from 4.e8) p = re.compile('(?<!\.)[a-zA-Z]\w*') for m in p.finditer(res): # If the function, variable or constant is already in the # translation dictionary, then just move on. if m.group() in lsymbols: pass # Now try to translate all other functions -- try each strategy # in `autotrans` and check if the function exists in Sage elif m.end() < len(res) and res[m.end()] == '(': for t in autotrans: f = find_func(t(m.group()), create_when_missing=False) if f is not None: lsymbols[m.group()] = f break else: raise NotImplementedError("Don't know a Sage equivalent \ for Mathematica function '%s'. Please specify one \ manually using the 'locals' dictionary" % m.group()) # Check if Sage has an equivalent constant else: for t in autotrans: if t(m.group()) in constants: lsymbols[m.group()] = constants[t(m.group())] break # If Sage has never heard of the variable, then # symbolic_expression_from_string will automatically create it try: return symbolic_expression_from_string(res, lsymbols, accept_sequence=True) except Exception: raise NotImplementedError("Unable to parse Mathematica \ output: %s" % res)
def _sage_(self, locals={}): r""" Convert a giac expression back to a Sage expression, if possible. NOTES: This method works successfully when Giac returns a result or list of results that consist only of: - numbers, i.e. integers, floats, complex numbers; - functions and named constants also present in Sage, where: - Sage knows how to translate the function or constant's name from Giac's naming scheme through the symbols_table, or - you provide a translation dictionary ``locals``. New conversions can be added using Pynac's ``register_symbol``. This is the recommended approach for library code. .. WARNING:: List conversion is slow. EXAMPLES:: sage: m = giac('x^2 + 5*y') sage: m.sage() x^2 + 5*y :: sage: m = giac('sin(2*sqrt(1-x^2)) * (1 - cos(1/x))^2') sage: m.trigexpand().sage() 2*cos(sqrt(-x^2 + 1))*cos(1/x)^2*sin(sqrt(-x^2 + 1)) - 4*cos(sqrt(-x^2 + 1))*cos(1/x)*sin(sqrt(-x^2 + 1)) + 2*cos(sqrt(-x^2 + 1))*sin(sqrt(-x^2 + 1)) Converting a custom name using the ``locals`` dictionary:: sage: ex = giac('myFun(x)') sage: ex._sage_({'myFun': sin}) sin(x) Same but by adding a new entry to the ``symbols_table``:: sage: ex = giac('myFun(x)') sage: sage.libs.pynac.pynac.register_symbol(sin, {'giac':'myFun'}) sage: ex._sage_() sin(x) Conversion of lists:: sage: L = giac('solve((2/3)^x-2, x)'); L list[ln(2)/(ln(2)-ln(3))] sage: L.sage() [-ln(2)/(ln(3) - ln(2))] """ from sage.libs.pynac.pynac import symbol_table from sage.calculus.calculus import symbolic_expression_from_string result = repr(self) # string representation if str(self.type()) not in ['DOM_LIST', 'vector', 'vecteur']: # Merge the user-specified locals dictionary and the symbol_table # (locals takes priority) lsymbols = symbol_table['giac'].copy() lsymbols.update(locals) try: return symbolic_expression_from_string(result, lsymbols, accept_sequence=True) except Exception: raise NotImplementedError("Unable to parse Giac output: %s" % result) else: return [entry.sage() for entry in self]
def mma_free_integrator(expression, v, a=None, b=None): """ Integration using Mathematica's online integrator EXAMPLES:: sage: from sage.symbolic.integration.external import mma_free_integrator sage: mma_free_integrator(sin(x), x) # optional - internet -cos(x) TESTS: Check that :trac:`18212` is resolved:: sage: var('y') # optional - internet y sage: integral(sin(y)^2, y, algorithm='mathematica_free') # optional - internet -1/2*cos(y)*sin(y) + 1/2*y sage: mma_free_integrator(exp(-x^2)*log(x), x) # optional - internet 1/2*sqrt(pi)*erf(x)*log(x) - x*hypergeometric((1/2, 1/2), (3/2, 3/2), -x^2) """ import re # import compatible with py2 and py3 from six.moves.urllib.request import urlopen from six.moves.urllib.parse import urlencode # We need to integrate against x vars = [str(x) for x in expression.variables()] if any(len(x)>1 for x in vars): raise NotImplementedError("Mathematica online integrator can only handle single letter variables.") x = SR.var('x') if repr(v) != 'x': for i in range(ord('a'), ord('z')+1): if chr(i) not in vars: shadow_x = SR.var(chr(i)) break expression = expression.subs({x:shadow_x}).subs({v: x}) params = urlencode({'expr': expression._mathematica_init_(), 'random': 'false'}) page = urlopen("http://integrals.wolfram.com/home.jsp", params).read() page = page[page.index('"inputForm"'):page.index('"outputForm"')] page = re.sub("\s", "", page) mexpr = re.match(r".*Integrate.*==</em><br/>(.*)</p>", page).groups()[0] try: from sage.libs.pynac.pynac import symbol_table from sage.interfaces.mathematica import _un_camel as un_camel from sage.symbolic.constants import constants_name_table as constants from sage.calculus.calculus import symbolic_expression_from_string from sage.calculus.calculus import _find_func as find_func expr = mexpr.replace('\n',' ').replace('\r', '') expr = expr.replace('[', '(').replace(']', ')') expr = expr.replace('{', '[').replace('}', ']') lsymbols = symbol_table['mathematica'].copy() autotrans = [str.lower, # Try it in lower case un_camel, # Convert `CamelCase` to `camel_case` lambda x: x # Try the original name ] # Find the MMA funcs/vars/constants - they start with a letter. # Exclude exponents (e.g. 'e8' from 4.e8) p = re.compile('(?<!\.)[a-zA-Z]\w*') for m in p.finditer(expr): # If the function, variable or constant is already in the # translation dictionary, then just move on. if m.group() in lsymbols: pass # Now try to translate all other functions -- try each strategy # in `autotrans` and check if the function exists in Sage elif m.end() < len(expr) and expr[m.end()] == '(': for t in autotrans: f = find_func(t(m.group()), create_when_missing = False) if f is not None: lsymbols[m.group()] = f break else: raise NotImplementedError("Don't know a Sage equivalent for Mathematica function '%s'." % m.group()) # Check if Sage has an equivalent constant else: for t in autotrans: if t(m.group()) in constants: lsymbols[m.group()] = constants[t(m.group())] break ans = symbolic_expression_from_string(expr, lsymbols, accept_sequence=True) if repr(v) != 'x': ans = ans.subs({x:v}).subs({shadow_x:x}) return ans except TypeError: raise ValueError("Unable to parse: %s" % mexpr)
def evaluate_expression(expr): f = symbolic_expression_from_string(expr) return f.__repr__()
response = preparse(response).replace('Integer', '').replace('RealNumber', '') web.debug("Answer: " + answer) web.debug("Response: " + response) answer_expr = symbolic_expression_from_string(answer) response_expr = symbolic_expression_from_string(response) except SyntaxError, e: web.debug("Error parsing answer and response expressions: %s" % e) f = "Error parsing answer and response expressions: %s" % e return f # First check for exlcuded responses for exc in exclude: exc = algebra.convert_latex(exc) exc = preparse(exc) # Create and expression from the excluded string expr = symbolic_expression_from_string(exc) # Take a difference between the excluded expression and the # response provided by the student diff = response_expr - expr # See if the difference has a representation of zero. If so it # matches with a simple comparison and so should be exlcuded. web.debug(diff) if diff.__repr__() == '0': # Response is excluded so immediately return false return {'expr1': answer, 'expr2': response, 'result': False} # Create an expression that is the difference of the answer and response web.debug('FINAL %s - %s' % (answer_expr, response_expr)) f = (answer_expr) - (response_expr) web.debug('RESULT %s' % f) web.debug('RESULT %s' % f.simplify_full()) # Simply use the 'is_zero' method to determine if the expressions are
def _sage_(self, locals={}): r""" Convert a giac expression back to a Sage expression, if possible. NOTES: This method works successfully when Giac returns a result or list of results that consist only of: - numbers, i.e. integers, floats, complex numbers; - functions and named constants also present in Sage, where: - Sage knows how to translate the function or constant's name from Giac's naming scheme through the symbols_table, or - you provide a translation dictionary ``locals``. New conversions can be added using Pynac's ``register_symbol``. This is the recommended approach for library code. Warning: List conversion is slow. EXAMPLES:: sage: m = giac('x^2 + 5*y') sage: m.sage() x^2 + 5*y :: sage: m = giac('sin(2*sqrt(1-x^2)) * (1 - cos(1/x))^2') sage: m.trigexpand().sage() 2*cos(sqrt(-x^2 + 1))*cos(1/x)^2*sin(sqrt(-x^2 + 1)) - 4*cos(sqrt(-x^2 + 1))*cos(1/x)*sin(sqrt(-x^2 + 1)) + 2*cos(sqrt(-x^2 + 1))*sin(sqrt(-x^2 + 1)) Converting a custom name using the ``locals`` dictionary:: sage: ex = giac('myFun(x)') sage: ex._sage_({'myFun': sin}) sin(x) Same but by adding a new entry to the ``symbols_table``:: sage: ex = giac('myFun(x)') sage: sage.libs.pynac.pynac.register_symbol(sin, {'giac':'myFun'}) sage: ex._sage_() sin(x) """ from sage.libs.pynac.pynac import symbol_table from sage.calculus.calculus import symbolic_expression_from_string result = repr(self) # string representation if str(self.type()) != 'DOM_LIST' : # Merge the user-specified locals dictionary and the symbol_table # (locals takes priority) lsymbols = symbol_table['giac'].copy() lsymbols.update(locals) try: return symbolic_expression_from_string(result, lsymbols, accept_sequence=True) except Exception: raise NotImplementedError("Unable to parse Giac output: %s" % result) else: return [entry.sage() for entry in self]