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())
\$\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\$
    – dobok
    Commented Sep 20, 2022 at 11:48
  • \$\begingroup\$ (taking passes, eh?) \$\endgroup\$
    – greybeard
    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
\$\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\$
    – pacmaninbw
    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

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

Not the answer you're looking for? Browse other questions tagged or ask your own question.