import math

class Dual:
    def __init__(self, a, b):
        self.a = a
        self.b = b
    
    def __repr__(self):
        return str(self.a) + (" + " + str(self.b) if self.b>=0 else " - " + str(-self.b)) + "d"

    def __str__(self):
        return self.__repr__()
    
    def __add__(self, y):
        if type(y) == int or type(y) == float:
            return Dual(self.a + y, self.b)
        else:
            return Dual(y.a+self.a, y.b+self.b)

    def __radd__(self, y):
        return self.__add__(y)

    def __sub__(self, y):
        if type(y) == int or type(y) == float:
            return Dual(self.a-y, self.b)
        else:
            return Dual(self.a-y.a, self.b-y.b)

    def __rsub__(self, y):
        if type(y) == int or type(y) == float:
            return Dual(y-self.a, -self.b)
    
    def __mul__(self, y):
        if type(y) == int or type(y) == float:
            return Dual(self.a*y, self.b*y)
        else:
            return Dual(y.a*self.a, y.b*self.a + y.a*self.b)

    def __rmul__(self, y):
        return self.__mul__(y)
        
    def __pow__(self, e):
        return Dual(self.a ** e, self.b*e*self.a ** (e-1))

    def __truediv__(self, y):
        if type(y) == int or type(y) == float:
            return Dual(self.a/y, self.b/y)
        else:
            return Dual(self.a/y.a, (self.b*y.a-self.a*y.b)/(y.a*y.a))

    def __rtruediv__(self, y):
        if type(y) == int or type(y) == float:
            return Dual(y/self.a, -y*self.b/(self.a*self.a))
        

#f(x+d) = f(x) + f'(x)d
def create_func(f, deriv):
    return lambda D: Dual(f(D.a), D.b*deriv(D.a)) if type(D)==Dual else f(D)

def autoderiv_nostring(f,x):
    return (f(Dual(x,1))-f(Dual(x,0))).b

def autoderiv(s, x):
    f = eval('lambda x: ' + s.replace("^", "**"))
    return (f(Dual(x,1))-f(Dual(x,0))).b

sin = create_func(math.sin, math.cos)
cos = create_func(math.cos, lambda x:-math.sin(x))
tan = create_func(math.tan, lambda x:1/math.cos(x)/math.cos(x))
sec = create_func(lambda x:1/math.cos(x), lambda x:math.sin(x)/math.cos(x)/math.cos(x))
csc = create_func(lambda x:1/math.sin(x), lambda x:-math.cos(x)/math.sin(x)/math.sin(x))
cot = create_func(lambda x:1/math.tan(x), lambda x:-1/math.sin(x)/math.sin(x))
arcsin = create_func(lambda x:math.asin(x), lambda x:1/math.sqrt(1-x*x))
arccos = create_func(lambda x:math.acos(x), lambda x:-1/math.sqrt(1-x*x))
arctan = create_func(lambda x:math.atan(x), lambda x:1/(1+x*x))

exp = create_func(math.exp, math.exp)
ln = create_func(math.log, lambda x:1/x)

print(autoderiv("sin(1/x)", 2))
print(autoderiv("arctan(1+sin(x)**2)/(2-3/x**2 + sin(x)*exp(x)-ln(x))", 3))

