Python中的数学方程操作

16 投票
5 回答
18569 浏览
提问于 2025-04-15 12:19

我想开发一个图形界面应用程序,用来显示一个数学方程。当你点击方程中的某个变量,表示这个变量是未知数,也就是需要计算的内容时,方程会自动调整,来计算这个未知数。

举个例子:


a = (b+c*d)/e

假设我点击了“d”,表示它是未知数。那么方程应该重新调整为:

d = (a*e - b)/c

目前,我只想知道如何根据用户的输入来重新排列这个方程。我哥哥给我的一个建议是,在后台使用前缀/后缀表示法来进行计算。

这是不是唯一的方法,还是有其他更简单的建议呢?另外,我不仅会使用基本的数学函数,还会用到三角函数和微积分(我觉得是基础的,不涉及偏微分之类的)。我觉得前缀/后缀表示法可能在处理更复杂的数学函数时不太有用。

不过这只是我的看法,如果我错了请指出来。另外,我会使用SymPy来进行数学计算,所以计算一个给定的数学方程不是问题,我主要的问题是如何从一个通用的方程创建一个特定的方程。

5 个回答

7

自从2009年以来,事情确实发生了很大变化。我不知道你的图形用户界面(GUI)应用程序进展得怎么样,但现在在IPython qtconsole中可以直接做到这一点(你可以把它嵌入到自定义的PyQt/PySide应用程序中,并跟踪所有定义的符号,以便在一个单独的列表框中进行GUI交互等等)。

在这里输入图片描述

(使用了IPython的 sympyprt 扩展)

8

如果你想要不依赖任何库,直接实现这个功能,我觉得你遇到的问题可能和Python本身没有关系。如果你想找到这样的方程,你需要描述解决这些方程所需的经验法则。

首先,你需要表示你的方程。可以考虑把它分开:

  • 操作数:
    • 符号操作数(比如a, b)
    • 数字操作数(比如1, 2)
  • 运算符:
    • 一元运算符(比如负号-,三角函数)
    • 二元运算符(比如加法+,减法-,乘法*,除法/)

一元运算符显然只需要一个操作数,而二元运算符则需要两个。

那类型呢?

我觉得所有这些组成部分应该都来自一个共同的expression类型。这个类应该有一个getsymbols方法,用来快速找到你表达式中的符号。

然后区分一元和二元运算符,添加一些基本的补充/重排序的功能……

大概是这样的:

class expression(object):
    def symbols(self):
        if not hasattr(self, '_symbols'):
            self._symbols = self._getsymbols()
        return self._symbols
    def _getsymbols(self):
        """
        return type: list of strings
        """
        raise NotImplementedError

class operand(expression): pass

class symbolicoperand(operand):
    def __init__(self, name):
        self.name = name
    def _getsymbols(self):
        return [self.name]
    def __str__(self):
        return self.name

class numericoperand(operand):
    def __init__(self, value):
        self.value = value
    def _getsymbols(self):
        return []
    def __str__(self):
        return str(self.value)

class operator(expression): pass

class binaryoperator(operator):
    def __init__(self, lop, rop):
        """
        @type lop, rop: expression
        """
        self.lop = lop
        self.rop = rop
    def _getsymbols(self):
        return self.lop._getsymbols() + self.rop._getsymbols()
    @staticmethod
    def complementop():
        """
        Return complement operator:
         op.complementop()(op(a,b), b) = a
        """
        raise NotImplementedError
    def reorder():
        """
        for op1(a,b) return op2(f(b),g(a)) such as op1(a,b) = op2(f(a),g(b))
        """
        raise NotImplementedError
    def _getstr(self):
        """
        string representing the operator alone
        """
        raise NotImplementedError
    def __str__(self):
        lop = str(self.lop)
        if isinstance(self.lop, operator):
            lop = '(%s)' % lop
        rop = str(self.rop)
        if isinstance(self.rop, operator):
            rop = '(%s)' % rop
        return '%s%s%s' % (lop, self._getstr(), rop)


class symetricoperator(binaryoperator): 
    def reorder(self):
        return self.__class__(self.rop, self.lop)

class asymetricoperator(binaryoperator):
    @staticmethod
    def _invert(operand):
        """
        div._invert(a) -> 1/a
        sub._invert(a) -> -a
        """
        raise NotImplementedError

    def reorder(self):
        return self.complementop()(self._invert(self.rop), self.lop)


class div(asymetricoperator):
    @staticmethod
    def _invert(operand):
        if isinstance(operand, div):
            return div(self.rop, self.lop)
        else:
            return div(numericoperand(1), operand)
    @staticmethod
    def complementop():
        return mul
    def _getstr(self):
        return '/'

class mul(symetricoperator):
    @staticmethod
    def complementop():
        return div
    def _getstr(self):
        return '*'

class add(symetricoperator):
    @staticmethod
    def complementop():
        return sub
    def _getstr(self):
        return '+'

class sub(asymetricoperator):
    @staticmethod
    def _invert(operand):
        if isinstance(operand, min):
            return operand.op
        else:
            return min(operand)
    @staticmethod
    def complementop():
        return add
    def _getstr(self):
        return '-'

class unaryoperator(operator):
    def __init__(self, op):
        """
        @type op: expression
        """
        self.op = op
    @staticmethod
    def complement(expression):
        raise NotImplementedError

    def _getsymbols(self):
        return self.op._getsymbols()

class min(unaryoperator):
    @staticmethod
    def complement(expression):
        if isinstance(expression, min):
            return expression.op
        else:
            return min(expression) 
    def __str__(self):
        return '-' + str(self.op)

有了这个基本结构后,你应该能够描述一个简单的经验法则来解决非常简单的方程。想想你学过的解方程的简单规则,把它们写下来。这样应该就能行 :)

然后是一个非常简单的求解器:

def solve(left, right, symbol):
    """
    @type left, right: expression
    @type symbol: string
    """
    if symbol not in left.symbols():
        if symbol not in right.symbols():
            raise ValueError('%s not in expressions' % symbol)
        left, right = right, left

    solved = False
    while not solved:
        if isinstance(left, operator):
            if isinstance(left, unaryoperator):
                complementor = left.complement
                right = complementor(right)
                left = complementor(left)
            elif isinstance(left, binaryoperator):
                if symbol in left.rop.symbols():
                    left = left.reorder()
                else:
                    right = left.complementop()(right, left.rop)
                    left = left.lop
        elif isinstance(left, operand): 
            assert isinstance(left, symbolicoperand)
            assert symbol==left.name
            solved = True

    print symbol,'=',right

a,b,c,d,e = map(symbolicoperand, 'abcde')

solve(a, div(add(b,mul(c,d)),e), 'd') # d = ((a*e)-b)/c
solve(numericoperand(1), min(min(a)), 'a') # a = 1
28

使用SymPy,你的例子可以这样写:

>>> import sympy
>>> a,b,c,d,e = sympy.symbols('abcde')
>>> r = (b+c*d)/e
>>> l = a
>>> r = sympy.solve(l-r,d)
>>> l = d
>>> r
[(-b + a*e)/c]
>>> 

这似乎也适用于三角函数:

>>> l = a
>>> r = b*sympy.sin(c)
>>> sympy.solve(l-r,c)
[asin(a/b)]
>>> 

而且因为你在使用图形用户界面(GUI),你可能想要在字符串和表达式之间来回转换:

>>> r = '(b+c*d)/e'
>>> sympy.sympify(r)
(b + c*d)/e
>>> sympy.sstr(_)
'(b + c*d)/e'
>>> 

或者你可能更喜欢将它们以渲染后的LaTeX或MathML格式显示。

撰写回答