views:

925

answers:

6

Hi,

Actually I want to develop a GUI app which displays a given mathematical equation. And when you click upon a particular variable in the equation to signify that it is the unknown variable ie., to be calculated. The equation transforms itself to evaluate the required unknown variable.

for eg:


a = (b+c*d)/e

Let us suppose that I click upon "d" to signify that it is the unknown variable. Then the equation should be re-structured to:

d = (a*e - b)/c


As of now, I just want to know how I can go about rearranging the given equation based on user input. One suggestion I got from my brother was to use pre-fix/post-fix notational representation in back end to evaluate it.

Is that the only way to go or is there any simpler suggestion? Also, I will be using not only basic mathematical functions but Trignometric & Calculus (Basic I think. No partial differential calculus and all that) as well. I think that the pre/post-fix notation evaluation might not be helpful in evaluation higher mathematical functions.

But that is just my opinion so please point out if I am wrong. Also, I will be using SymPy for mathematical evaluation so evaluation of a given mathematical equation is not a problem, creating a specific equation from a given generic one is my main problem.

Thanks in Advance.

+4  A: 

Sage has support for symbolic math. You could just use some of the equation manipulating functions built-in:

http://sagemath.org/

Dan Lorenc
IMHO it's much better to point to SymPy here, instead of SAGE which is a huge conglomeration of packages that doesn't even run on Windows (except in a virtual machine, but this doesn't count).
Eli Bendersky
+3  A: 

What you want to do isn't easy. Some equations are quite straight forward to rearrange (like make b the subject of a = b*c+d, which is b = (a-d)/c), while others are not so obvious (like make x the subject of y = x*x + 4*x + 4), while others are not possible (especially when you trigonometric functions and other complications).

As other people have said, check out Sage. It does what you want:

You can solve equations for one variable in terms of others:

sage: x, b, c = var('x b c')
sage: solve([x^2 + b*x + c == 0],x)
[x == -1/2*b - 1/2*sqrt(b^2 - 4*c), x == -1/2*b + 1/2*sqrt(b^2 - 4*c)]
David Johnstone
+7  A: 

Using sympy, your example would go something like this:

>>> 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]
>>>

seems to work for trig too:

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

and since you are working with a gui, you'll (probably) want to convert back and forth from strings to expressions:

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

or you may prefer to display them as rendered LaTeX

bpowah
+1 for giving possibly helpful examples from SymPy itself instead of immediately bringing up Sage (which, incidentally, includes SymPy).
John Y
+1  A: 

If you want to do this out of the box, without relying on librairies, I think that the problems you will find are not Python related. If you want to find such equations, you have to describe the heuristics necessary to solve these equations.

First, you have to represent your equation. What about separating:

  • operands:
    • symbolic operands (a,b)
    • numeric operands (1,2)
  • operators:
    • unary operators (-, trig functions)
    • binary operators (+,-,*,/)

Unary operators will obviously enclose one operand, binary ops will enclose two.

What about types?

I think that all of these components should derivate from a single common expression type. And this class would have a getsymbols method to locate quickly symbols in your expressions.

And then distinguish between unary and binary operators, add a few basic complement/reorder primitives...

Something like:

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)

With this basic structure set up, you should be able to describe a simple heuristic to solve very simple equations. Just think of the simple rules you learned to solve equations, and write them down. That should work :)

And then a very naive solver:

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
NicDumZ
A: 
  1. To display pretty equations in a GUI, look at MathML.
  2. This was all done 40 years ago with computer algebra systems. Lookup up Macsyma. Pick up an intro to AI book.
projectshave
A: 

Thanks nicdumz & bpwoah.

I haven't looked at it in detail but I guess that is more or less what I am looking for.

Leafy