在Python中测试数学表达式的等价性

时间:2011-05-27 16:10:47

标签: python equality sympy mathematical-expressions

我在Python中有两个字符串,

A m * B s / (A m + C m)

C m * B s / (C m + A m)

既是无序集合(A,C)和无序集合(B)的等价函数。 m和s表示可以在相同但不与另一个单位交换的单位。

到目前为止,我正在对A,B和C进行排列,并使用eval和SymPy的==运算符对它们进行测试。这有许多缺点:

  • 对于更复杂的表达式,我必须生成大量的排列(在我的例子中,8个嵌套for循环)
  • 我需要将A,B,C定义为符号,当我不知道我将拥有哪些参数时,这不是最佳的(所以我必须生成所有这些参数 - >非常低效并弄乱我的变量名称空间)

有没有pythonian方法来测试这种等价?它应该是一个任意的表达式。

5 个答案:

答案 0 :(得分:3)

不是迭代所有可能的排列,而是假设存在并尝试构造它。我相信以正确的方式完成,算法的失败意味着排列不存在。

以下是应用于上述表达式的想法概要:

令:

str1 = "A m * B s / (A m + C m)"
str2 = "C m * B s / (C m + A m)"

我们正在寻找使表达式相同的集合(A,C)的排列。根据它们在str2中首次出现的顺序,重新标记A和C为X1和X2,所以:

X1 = C
X2 = A

因为C出现在str2中的A之前。接下来,创建数组Y,使得y [i]是第一次出现在str1中的第i个符号A或C.所以:

Y[1] = A
Y[2] = C

因为A出现在str1中的C之前。

现在通过用X1和X2替换A和C来构造str2的str3:

str3 = "X1 m * B s / (X1 m + X2 m)"

然后开始用Xi代替Y [i]。首先,X1变为Y [1] = A:

str3_1 = "A m * Bs / (A m + X2 m)"

在这个阶段,将str3_1和str1比较为任何Xi的第一次出现,在本例中为X2,因为这两个字符串是相等的:

str3_1[:18] = "A m * B s / (A m + " 
str1[:18] = "A m * B s / (A m + "

你有机会构建排列。如果它们是不相等的,那么你已经证明没有合适的排列存在(因为任何排列都必须至少进行替换)并且可以推断出不等价。但它们是相同的,所以你继续下一步,用X2代替Y [2] = C:

str3_2 = "A m * B s / (A m + C m)"

这等于str1,所以你有你的排列(A-> C,C-> A),并且已经显示了表达式的等价性。

这只是针对特定情况的算法演示,但它应该概括。不确定你能得到的最低顺序是什么,但它应该比n更快!在n个变量上生成所有排列。

如果我正确理解单位的重要性,它们会限制哪些变量可以通过排列交换哪些变量。所以在上面的表达式中A可以用C代替,因为它们都有'm'个单位,而不是有's'单位的B。您可以通过以下方式处理此问题:

通过删除所有不具有m个单位的符号,从str1和str2构造表达式str1_m和str2_m,然后对str1_m和str2_m执行上述算法。如果施工失败,则不存在排列。如果构造成功,保持该排列(称之为m-置换)并通过删除所有没有s单位的符号从str1和str2构造str1_s和str2_s,然后再次为str1_s和str2_s执行算法。如果施工失败,它们就不相同。如果成功,最终的排列将是m-排列和s-排列的组合(尽管你可能甚至不需要构造它,你只关心它存在)。

答案 1 :(得分:3)

这是基于我之前的答案的简化方法。

这个想法是,如果两个表达式在排列下是等价的,那么携带一个到另一个的排列必须将第一个字符串中的第i个符号(按第一次出现的索引排序)映射到第二个字符串中的第i个符号(再次排序)按第一次出现的指数)。这个原则可以用来构造一个排列,将它应用到第一个字符串,然后检查与第二个字符串的相等性 - 如果它们是相等的,它们是等价的,否则它们不是。

以下是一种可能的实施方式:

import re

# Unique-ify list, preserving order
def uniquify(l):
    return reduce(lambda s, e: s + ([] if e in s else [e]), l, [])

# Replace all keys in replacements with corresponding values in str
def replace_all(str, replacements):
    for old, new in replacements.iteritems():
        str = str.replace(old, new)
    return str

class Expression:
    units = ["m", "s"]

    def __init__(self, exp):
        self.exp = exp

    # Returns a list of symbols in the expression that are preceded
    # by the given unit, ordered by first appearance. Assumes the
    # symbol and unit are separated by a space. For example:
    # Expression("A m * B s / (A m + C m)").symbols_for_unit("m")
    # returns ['A', 'C']
    def symbols_for_unit(self, unit):
        sym_re = re.compile("(.) %s" % unit)
        symbols = sym_re.findall(self.exp)
        return uniquify(symbols)

    # Returns a string with all symbols that have units other than
    # unit "muted", that is replaced with the empty string. Example:
    # Expression("A m * B s / (A m + C m)").mute_symbols_for_other_units("m")
    # returns "A m *  s / (A m + C m)"
    def mute_symbols_for_other_units(self, unit):
        other_units = "".join(set(self.units) - set(unit))
        return re.sub("(.) ([%s])" % "".join(other_units), " \g<2>", self.exp)

    # Returns a string with all symbols that have the given unit
    # replaced with tokens of the form $0, $1, ..., by order of their
    # first appearance in the string, and all other symbols muted. 
    # For example:
    # Expression("A m * B s / (A m + C m)").canonical_form("m")
    # returns "$0 m *  s / ($0 m + $1 m)"
    def canonical_form(self, unit):
        symbols = self.symbols_for_unit(unit)
        muted_self = self.mute_symbols_for_other_units(unit)
        for i, sym in enumerate(symbols):
            muted_self = muted_self.replace("%s %s" % (sym, unit), "$%s %s" % (i, unit))
        return muted_self

    # Define a permutation, represented as a dictionary, according to
    # the following rule: replace $i with the ith distinct symbol
    # occurring in the expression with the given unit. For example:
    # Expression("C m * B s / (C m + A m)").permutation("m")
    # returns {'$0':'C', '$1':'A'}
    def permutation(self, unit):
        enum = enumerate(self.symbols_for_unit(unit))
        return dict(("$%s" % i, sym) for i, sym in enum)

    # Return a string produced from the expression by first converting it
    # into canonical form, and then performing the replacements defined
    # by the given permutation. For example:
    # Expression("A m * B s / (A m + C m)").permute("m", {"$0":"C", "$1":"A"})
    # returns "C m *  s / (C m + A m)"
    def permute(self, unit, permutation):
        new_exp = self.canonical_form(unit)
        return replace_all(new_exp, permutation) 

    # Test for equality under permutation and muting of all other symbols 
    # than the unit provided. 
    def eq_under_permutation(self, unit, other_exp):
        muted_self = self.mute_symbols_for_other_units(unit)        
        other_permuted_str = other_exp.permute(unit, self.permutation(unit))
        return muted_self == other_permuted_str    

    # Test for equality under permutation. This is done for each of
    # the possible units using eq_under_permutation
    def __eq__(self, other):
        return all([self.eq_under_permutation(unit, other) for unit in self.units])

e1 = Expression("A m * B s / (A m + C m)")
e2 = Expression("C m * B s / (C m + A m)")
e3 = Expression("A s * B s / (A m + C m)")

f1 = Expression("A s * (B s + D s) / (A m + C m)")
f2 = Expression("A s * (D s + B s) / (C m + A m)")
f3 = Expression("D s")

print "e1 == e2: ", e1 == e2 # True
print "e1 == e3: ", e1 == e3 # False
print "e2 == e3: ", e2 == e3 # False

print "f1 == f2: ", f1 == f2 # True
print "f1 == f3: ", f1 == f3 # False

正如你所指出的那样,这会检查排列下的字符串等价,而不考虑数学等价,但这只是成功的一半。如果你有一个数学表达式的规范形式,你可以在规范形式的两个表达式上使用这种方法。也许有一个同情的Simplify可以做到这一点。

答案 2 :(得分:2)

如果您将字符串传递给SymPy的sympify()函数,它将自动为您创建符号(无需全部定义)。

>>> from sympy import *
>>> x
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
NameError: name 'x' is not defined
>>> sympify("x**2 + cos(x)")
x**2 + cos(x)
>>> sympify("diff(x**2 + cos(x), x)")
2*x - sin(x)

答案 3 :(得分:1)

我做了一次,在一个mathemathics estudies的模拟器.. 好吧,就我而言,我知道将使用的变量是什么。

所以,我测试了将值放在变量中的结果。

A = 10
B = 20
C = 30
m = Math.e
s = Math.pi

所以,我们解决了:

s1 = 'A m * B s / (A m + C m)'
s2 = 'C m * B s / (C m + A m)'

如果s1!= s2,证明没有等价

用这种方法说两个表达式是等价的, 但你可以说两者都不等同

if s1 != s2:
   print "Not equivalent"
else:
   print "Try with another sample"

嗯..我希望这可以帮到你。

答案 4 :(得分:0)

与所有其他日期答案一样,这并不是问题的有力解决方案,而是包含更多有用的信息,供我们未来一丝不苟的朋友解决。

我使用欧拉公式https://en.wikipedia.org/wiki/Euler%27s_formula

提供了一个难题

我确信在我的例子中,所有其他溢出的答案都不会成功。

我表示,对于sympy网站的所有建议也都失败了。 (https://github.com/sympy/sympy/wiki/Faq

#SOURCE FOR HELPERS:    https://github.com/sympy/sympy/wiki/Faq 
import sympy
import sympy.parsing.sympy_parser
ExampleExpressionString1 = 'exp( i*( (x0 - 1)*(x0 + 2) ) )'
ExampleExpressionSympy1 = sympy.parsing.sympy_parser.parse_expr(ExampleExpressionString1)

ExampleExpressionString2 = 'i*sin( (x0 - 1)*(x0 + 2) ) + cos( (x0 - 1)*(x0 + 2) )'
ExampleExpressionSympy2 = sympy.parsing.sympy_parser.parse_expr(ExampleExpressionString2)

print '(ExampleExpressionSympy1 == ExampleExpressionSympy2):'
print ' ', (ExampleExpressionSympy1 == ExampleExpressionSympy2)

print '(ExampleExpressionSympy1.simplify() == ExampleExpressionSympy2.simplify()):'
print ' ', (ExampleExpressionSympy1.simplify() == ExampleExpressionSympy2.simplify())

print '(ExampleExpressionSympy1.expand() == ExampleExpressionSympy2.expand()):'
print ' ', (ExampleExpressionSympy1.trigsimp() == ExampleExpressionSympy2.trigsimp())

print '(ExampleExpressionSympy1.trigsimp() == ExampleExpressionSympy2.trigsimp()):'
print ' ', (ExampleExpressionSympy1.trigsimp() == ExampleExpressionSympy2.trigsimp())

print '(ExampleExpressionSympy1.simplify().expand().trigsimp() == ExampleExpressionSympy2.simplify().expand().trigsimp()):'
print ' ', (ExampleExpressionSympy1.simplify().expand().trigsimp() == ExampleExpressionSympy2.simplify().expand().trigsimp())

更多说明:

我怀疑这是一个很难解决的问题。要正确检查数学等价,你不仅要尝试排序,而且还必须有一个数学等价变换库,并尝试所有这些排列。

然而,我确实认为这可能是一个可以解决的问题,因为Wolfram Alpha似乎有一个“替代表达式”。这一部分似乎可以解决使用这些等价物在任意表达式上提供所有排列的技巧。

总结:

我建议以下内容,期望它会破裂:

import sympy
import sympy.parsing.sympy_parser
Expression.simplify().expand().trigsimp()