serpiente
serpiente

Reputation: 359

Designing a symbolic equation solver

For a class I was assigned to a project to do a basic equation solver. It needs to solve linear equations. E.g. of some equations I should be able to solve:

I have looked and only found a handful of helpful suggestions. I tried to understand this implementation but failed to see where I put the arithmetic rules. I tried to read the Sympy source code but I don't know were to look for what I am looking for. I thought of defining certain rules in a txt files. For example u - v + v = u and tried look how to apply this rule to the equations.

Do any of you have an idea how I could design this?

Upvotes: 3

Views: 3580

Answers (3)

Joric
Joric

Reputation: 124

Nothing special really. You're just isolating x. An example in a few lines, extend if needed:

import ast

def solve(s):
    def isolate_x(l, r):
        while isinstance(l, ast.BinOp):
            op, inv_op = type(l.op), {ast.Add: ast.Sub, ast.Sub: ast.Add, ast.Mult: ast.Div, ast.Div: ast.Mult}[type(l.op)]()
            if 'x' in ast.unparse(l.left):
                l, r = l.left, ast.BinOp(r, inv_op, l.right)
            else:
                l, r = l.right, ast.BinOp(r, inv_op, l.left) if op in (ast.Add, ast.Mult) else ast.BinOp(l.left, l.op, r)
        return l, r
    a, b = s.split('=')
    return f'x = {ast.unparse(isolate_x(*map(lambda x: ast.parse(x.strip(), mode='eval').body, (a, b) if 'x' in a else (b, a)))[1])}'

print(solve('10 = (x + 50) * 10 - 15')) # the result is 'x = (10 + 15) / 10 - 50'

Custom TDOP expression parsers are also easy:

def parse(s):
    def parse_expr(min_p = 0):
        x = q.pop()
        node = (parse_expr(), q.pop())[0] if x=='(' else Node('number' if x.isdigit() else 'var', x)
        while q and (p := 1 + '+-*/'.find(q[-1]) // 2) > min_p:
            node = Node('op', q.pop(), node, parse_expr(p))
        return node
    q = s.replace(')', ' )').replace('(', '( ').split()[::-1]
    return parse_expr()

Upvotes: 0

scravy
scravy

Reputation: 12283

First, you should think about how to declare an equation (or polynomials) for that purpose. For example you could define

data Polynomial = Polynomial [Polynomial]
   | Sum Polynomial Polynomial
   | Ln Polynomial
   | Log10 Polynomial Polynomial
   | Var String -- a named Variable
   | ...

For easily dealing with Polynomials you could create instances for your data type Polynomial for Eq, Ord, Num, and so on, so that you could deal with a Polynomial just like with numbers.

instance Num Polynomial where
    a + b = ...

For creating these functions you can easily use Pattern Matching:

(Sum a b) + (Sum c d) = Sum (Sum a b) (Sum c d)

For an equation you could simply use tupels and create a new type for it:

type Equation = (Polynomial, Polynomial)

For solving it, a function like this could be used:

solve :: Equation -> String -> Polynomial

...where the String is the Name of a Variable.

Solve would than need to do the real work. Again, for this Pattern Matching could be used for the first steps:

solve ((Sum (Var a) b, e) x -- solves polynomials of type a + b = c + d
    | a == x = Sum e (negate b)
    | ...

Of course this is very basic and you could do it a lot smarter, by cutting down the number of possible cases using a normalization that e.g. combines "a + a + a" into "3 * a".

Upvotes: 2

GETah
GETah

Reputation: 21449

You have the choice to either writing your own math expression parser/interpreter or use an existing one. Jep is a good example of an open source math expression parser.

Otherwise, if you would like to know what compilers and syntactic analyzers do under the hood then you can write your own expression parser using jFlex and CUP

Also, here is an excellent tutorial on antlr that might help you get started.

Upvotes: 1

Related Questions