9

我在 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方法来测试这种等价性?它应该可以使用任意表达式。

4

5 回答 5

3

与其迭代所有可能的排列,不如假设一个存在并尝试构建它。我相信以正确的方式完成,算法的失败将意味着不存在排列。

以下是应用于上述表达式的想法的大纲:

让:

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

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

X1 = C
X2 = A

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

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 排列的组合(尽管您可能甚至不需要构造它,您只需要关心它是否存在)。

于 2011-05-27T22:18:58.283 回答
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

正如您所指出的,这在不考虑数学等价的情况下检查排列下的字符串等价,但这是成功的一半。如果您有数学表达式的规范形式,您可以在规范形式的两个表达式上使用这种方法。也许 sympy 的Simplify之一可以解决问题。

于 2011-05-28T10:28:59.263 回答
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)
于 2011-06-07T04:24:17.970 回答
1

我做过一次,在数学研究的一个模拟器中。好吧,就我而言,我知道将使用哪些变量。

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

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"

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

于 2011-05-27T17:31:47.017 回答
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()
于 2015-10-23T17:09:42.280 回答