2
\$\begingroup\$

I am newbie in the field of programming, but I have tried to make relatively "huge" project. My "app" would derivate a mathematical expression, after that simplify the output expression.

How could I improve the expression simplify function to further simplify the output expression?

I know about simpy, I want to implement the simplifier myself using python.

from abc import ABC, abstractmethod
import numpy as np
import math
class derivateSymb(ABC):
 @abstractmethod
 def df(self, var): pass
 @abstractmethod
 def compute(self): pass
class Const(derivateSymb):
 def __init__(self, value): self.value = value 
 def df(self, var): return Const(0)
 def compute(self): return self.value
 def __repr__(self): return str(self.value)
class Var(derivateSymb):
 def __init__(self, name, value=None): self.name, self.value = name, value
 def df(self, var): return Const(1) if self == var else Const(0)
 def compute(self):
 if self.value is None:
 raise ValueError('unassigned variable')
 return self.value
 def __repr__(self): return f'{self.name}'
class Add(derivateSymb):
 def __init__(self, x, y): self.x, self.y = x, y
 def df(self, var): return Add(self.x.df(var), self.y.df(var))
 def compute(self): return self.x.compute() + self.y.compute()
 def __repr__(self): return f'({self.x} + {self.y})'
class Sub(derivateSymb):
 def __init__(self, x, y): self.x, self.y = x, y
 def df(self, var): return Sub(self.x.df(var), self.y.df(var))
 def compute(self): return self.x.compute() - self.y.compute()
 def __repr__(self): return f'({self.x} - {self.y})'
class Mult(derivateSymb):
 def __init__(self, x, y): self.x, self.y = x, y
 def df(self, var): return Add( Mult(self.x.df(var), self.y), Mult(self.x, self.y.df(var)) )
 def compute(self): return self.x.compute() * self.y.compute()
 def __repr__(self): return f'({self.x} * {self.y})'
class Div(derivateSymb):
 def __init__(self, x, y): self.x, self.y = x, y
 def df(self, var): return Div( Sub( Mult(self.x.df(var), self.y), Mult(self.x, self.y.df(var)) ), Mult(self.y, self.y) )
 def compute(self): return self.x.compute() / self.y.compute()
 def __repr__(self): return f'({self.x} / {self.y})'
class Pow(derivateSymb):
 def __init__(self, base, power): self.base, self.power = base, power
 def df(self, var): return Mult( Mult( Const(self.power), Pow(self.base, self.power-1) ), self.base.df(var) )
 def compute(self): return math.pow(self.base.compute(), self.power)
 def __repr__(self): return f'({self.base}^{self.power})'
class Sin(derivateSymb):
 def __init__(self, x): self.x = x
 def df(self, var): return Mult( Cos(self.x), self.x.df(var) )
 def compute(self): return math.sin(self.x.compute())
 def __repr__(self): return f'(sin{self.x})'
class Cos(derivateSymb):
 def __init__(self, x): self.x = x
 def df(self, var): return Mult( Const(-1), Mult( Cos(self.x), self.x.df(var) ) )
 def compute(self): return math.cos(self.x.compute())
 def __repr__(self): return f'(cos{self.x})'
 
class Nodes:
 @staticmethod
 def _to_symbolic(x):
 if not isinstance(x, derivateSymb): return Const(x)
 else: return x
 def __add__(self, other): return ErgonomicAdd(self, self._to_symbolic(other))
 def __sub__(self, other): return ErgonomicSub(self, self._to_symbolic(other))
 def __mul__(self, other): return ErgonomicMul(self, self._to_symbolic(other))
 def __truediv__(self, other): return ErgonomicDiv(self, self._to_symbolic(other))
 def __neg__(self): return ErgonomicMul(Const(-1), self)
class ErgonomicVar(Var, Nodes): pass
class ErgonomicAdd(Add, Nodes): pass
class ErgonomicSub(Sub, Nodes): pass
class ErgonomicMul(Mult, Nodes): pass
class ErgonomicDiv(Div, Nodes): pass
def simplify(node):
 if isinstance(node, Add): return simplifyAdd(node)
 elif isinstance(node, Sub): return simplifySub(node)
 elif isinstance(node, Mult): return simplifyMult(node)
 else: return node
def simplifyAdd(node):
 x, y = simplify(node.x), simplify(node.y)
 x_const, y_const = isinstance(x, Const), isinstance(y, Const)
 if x_const and y_const: return Const(x.value + y.value)
 elif x_const and x.value == 0: return y
 elif y_const and y.value == 0: return x
 else: return Add(x, y)
def simplifySub(node):
 x, y = simplify(node.x), simplify(node.y)
 x_const, y_const = isinstance(x, Const), isinstance(y, Const)
 if x_const and y_const: return Const(x.value - y.value)
 elif x_const and x.value == 0: return y
 elif y_const and y.value == 0: return x
 else: return Sub(x, y)
def simplifyMult(node):
 x, y = simplify(node.x), simplify(node.y)
 x_const, y_const = isinstance(x, Const), isinstance(y, Const)
 if x_const and y_const: return Const(x.value * y.value)
 elif x_const and x.value == 0: return Const(0)
 elif x_const and x.value == 1: return y
 elif y_const and y.value == 0: return Const(0)
 elif y_const and y.value == 1: return x
 else: return Mult(x, y)
x = ErgonomicVar('x', 3)
y = ErgonomicVar('y', 3)
z = Pow(x*3+y*y*y, 3)
#z = Sin(x*3+y*y)
print(z)
print(z.compute())
print(simplify(z.df(x)))
print(z.df(x).compute())
asked Sep 20, 2022 at 10:32
\$\endgroup\$
3
  • 1
    \$\begingroup\$ What is ABC supposed to be? And do you have any import statements that are not included in this code sample? \$\endgroup\$ Commented Sep 20, 2022 at 11:43
  • 1
    \$\begingroup\$ @AaronMeese I have just included the import statements. \$\endgroup\$ Commented Sep 20, 2022 at 11:48
  • \$\begingroup\$ (taking passes, eh?) \$\endgroup\$ Commented Dec 18, 2022 at 10:54

1 Answer 1

3
\$\begingroup\$

Firstly I recommend using some form of standard Python formatter, so your code is standardized and will allow others to more easily assist you. In my answer I use the black formatter, but you can choose from alternatives.

I would recommend is adding support for division, exponents, sin, and cosin, since you already have the classes for them:

def simplify(node):
 if isinstance(node, Add):
 return simplifyAdd(node)
 elif isinstance(node, Sub):
 return simplifySub(node)
 elif isinstance(node, Mult):
 return simplifyMult(node)
 elif isinstance(node, Div):
 return simplifyDiv(node)
 elif isinstance(node, Pow):
 return simplifyPow(node)
 elif isinstance(node, Sin):
 return simplifySin(node)
 else:
 return node
def simplifyAdd(node):
 x, y = simplify(node.x), simplify(node.y)
 x_const, y_const = isinstance(x, Const), isinstance(y, Const)
 if x_const and y_const:
 return Const(x.value + y.value)
 elif x_const and x.value == 0:
 return y
 elif y_const and y.value == 0:
 return x
 else:
 return Add(x, y)
def simplifySub(node):
 x, y = simplify(node.x), simplify(node.y)
 x_const, y_const = isinstance(x, Const), isinstance(y, Const)
 if x_const and y_const:
 return Const(x.value - y.value)
 elif x_const and x.value == 0:
 return y
 elif y_const and y.value == 0:
 return x
 else:
 return Sub(x, y)
def simplifyMult(node):
 x, y = simplify(node.x), simplify(node.y)
 x_const, y_const = isinstance(x, Const), isinstance(y, Const)
 if x_const and y_const:
 return Const(x.value * y.value)
 elif x_const and x.value == 0:
 return Const(0)
 elif x_const and x.value == 1:
 return y
 elif y_const and y.value == 0:
 return Const(0)
 elif y_const and y.value == 1:
 return x
 else:
 return Mult(x, y)
def simplifyDiv(node):
 x, y = simplify(node.x), simplify(node.y)
 x_const, y_const = isinstance(x, Const), isinstance(y, Const)
 if x_const and y_const:
 return Const(x.value / y.value)
 elif x_const and x.value == 0:
 return Const(0)
 elif y_const and y.value == 1:
 return x
 else:
 return Div(x, y)
def simplifyPow(node):
 x, y = simplify(node.base), simplify(node.power)
 x_const, y_const = isinstance(x, Const), isinstance(y, Const)
 if x_const and y_const:
 return Const(math.pow(x.value, y.value))
 elif y_const and y.value == 0:
 return Const(1)
 elif y_const and y.value == 1:
 return x
 else:
 return Pow(x, y)
def simplifySin(node):
 x = simplify(node.x)
 x_const = isinstance(x, Const)
 if x_const:
 return Const(math.sin(x.value))
 else:
 return Sin(x)
def simplifyCos(node):
 x = simplify(node.x)
 x_const = isinstance(x, Const)
 if x_const:
 return Const(math.cos(x.value))
 else:
 return Cos(x)

This can be complemented by adding __pow__ to the Nodes class:

def __pow__(self, other):
 return ErgonomicPow(self, self._to_symbolic(other))

And by adding in your empty ErgonomicPow class:

class ErgonomicPow(Pow, Nodes):
 pass
answered Sep 20, 2022 at 12:40
\$\endgroup\$
2
  • 2
    \$\begingroup\$ Does the code follow PEP8? That would be one thing to review. Welcome to the Code Review Community. While this might be a good answer on stackoverflow, it is not a good answer on Code Review. A good answer on Code Review contains at least one insightful observation about the code. Alternate code only solutions are considered poor answers and may be down voted or deleted by the community. Please read How do I write a good answer. \$\endgroup\$ Commented Sep 20, 2022 at 13:43
  • \$\begingroup\$ Answer has been edited to conform with your suggestions (I think) @pacmaninbw Thanks for the feedback, I'm very new to this forum! \$\endgroup\$ Commented Sep 20, 2022 at 19:26

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.