Проверка эквивалентности математических выражений в Python

у меня есть две строки в Python,

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

и

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

которые являются эквивалентными функциями неупорядоченного множества (A, C) и неупорядоченного множества (B). m и s указывают единицы, которые могут быть заменены между собой, но не с другой единицей.

до сих пор, я делаю перестановки, B и C и тестирование их с помощью eval и SymPy в == оператора. Это имеет несколько недостатков:

  • для более сложных выражений, я должен генерировать большое количество перестановок (в моем случае 8 вложенных циклов)
  • мне нужно определить A, B, C как символы, что не является оптимальным, когда я не знаю, какие параметры у меня будут (поэтому я должен генерировать все из них -> ужасно неэффективно и испортить мое пространство имен переменных)

есть ли пифонический способ проверить этот вид эквивалентности? Он должен работать произвольными выражениями.

5 ответов


вот упрощенный подход, основанный на моем предыдущем ответе.

идея заключается в том, что если два выражения эквивалентны при перестановках, перестановка, несущая одно к другому, должна сопоставлять 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 , , ..., 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 " m *  s / ( m +  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 {'':'C', '':'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", {"":"C", "":"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 это упростить может сделать трюк.


вместо того, чтобы перебирать все возможные перестановки, предположим, что он существует и пытается его построить. Я считаю, что, сделав все правильно, сбой алгоритма будет означать отсутствие перестановки.

вот набросок идеи, примененной к выражениям выше:

допустим:

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

мы ищем перестановку множества (A, C), которая сделает выражения идентичными. Relabel A и C как X1 и X2 согласно порядку их первое появление в str2, так:

X1 = C
X2 = A

потому что C появляется перед A в str2. Затем создайте массив Y таким образом, чтобы y[i] был i-м символом A или C в порядке первого появления в str1. Итак:

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

потому что A появляется перед C в str1.

теперь постройте str3 из str2, заменив A и C на X1 и X2:

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", но не с B, который имеет единицы "s". Вы можете справиться с этим следующим образом:

создайте выражения str1_m и str2_m из str1 и str2, удалив все символы, которые не имеют M единиц, а затем выполните приведенный выше алгоритм для str1_m и str2_m. Если конструкция терпит неудачу, перестановки не существует. Если построение успешно, сохраните эту перестановку (назовите ее m-перестановкой) и построить str1_s и str2_s из str1 и str2, удалив все символы, которые не имеют единиц s, а затем снова выполнить алгоритм для str1_s и str2_s. Если строительство не удается, они не эквивалентны. Если это удастся, окончательная перестановка будет комбинацией m-перестановки и s-перестановки (хотя вам, вероятно, даже не нужно ее строить, вам просто нужно, чтобы она существовала).


Если вы передаете строку в 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)

Я сделал это однажды, в одном симуляторе mathemathics estudies.. Ну, в моем случае я знал, какие переменные будут использоваться.

Итак, я проверил результат, поместив значения внутри vars.

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"

хорошо.. Я надеюсь, это поможет вам.


Это, как и все другие ответы на сегодняшний день не является надежным решением проблемы, но вместо этого содержит больше полезной информации для нашей дальнейшей тщательной друга, чтобы решить.

я обеспечиваю сложный пример с использованием формулы 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())

ДОПОЛНИТЕЛЬНЫЕ ПРИМЕЧАНИЯ:

Я подозреваю, что это сложная проблема для решения в целом и надежно. Чтобы правильно проверить математическую эквивалентность, вам нужно не только попробовать перестановки порядка, но и иметь библиотеку математических эквивалентных преобразований и попробовать все эти перестановки.

Я, однако, считаю, что это может быть разрешимой проблемой, потому что вольфрам Альфа, похоже, имеет раздел "альтернативное выражение", который, похоже, делает трюк обеспечения всех перестановок большую часть времени на произвольных выражениях, использующих эти виды эквивалентностей.

В СУММЕ:

Я предлагаю следующее с ожиданием, что он сломается:

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