|
import math |
|
from abc import ABC, abstractmethod |
|
|
|
def magic(k): |
|
if isinstance(k, Gradable): |
|
return k |
|
elif type(k) in [int, float]: |
|
return Number(k) |
|
elif type(k) == str: |
|
return Variable(k) |
|
else: |
|
print(k, type(k)) |
|
raise TypeError("Not Appropriate") |
|
|
|
class Gradable(ABC): |
|
def __init__(self): |
|
pass |
|
|
|
def __repr__(self): |
|
if isinstance(self, Number): |
|
return f"<autograd: Number {self.value}>" |
|
elif isinstance(self, Variable): |
|
return f"<autograd: Variable {self.alias}>" |
|
else: |
|
return "<autograd: Gradable expression>" |
|
def __call__(self, x): |
|
return self.calc(x) |
|
|
|
@abstractmethod |
|
def calc(self): |
|
pass |
|
|
|
@property |
|
@abstractmethod |
|
def grad(self): |
|
pass |
|
|
|
def __add__(left, right): |
|
return Add(left, right) |
|
def __sub__(left, right): |
|
return Sub(left, right) |
|
def __mul__(left, right): |
|
return Mul(left, right) |
|
def __truediv__(left, right): |
|
return Div(left, right) |
|
def __pow__(left, right): |
|
return Pow(left, right) |
|
|
|
def __radd__(right, left): |
|
return Add(left, right) |
|
def __rsub__(right, left): |
|
return Sub(left, right) |
|
def __rmul__(right, left): |
|
return Mul(left, right) |
|
def __rtruediv__(right, left): |
|
return Div(left, right) |
|
def __rpow__(right, left): |
|
return Pow(left, right) |
|
|
|
class Number(Gradable): |
|
def __init__(self, a): |
|
self.value = a |
|
|
|
def calc(self, x): |
|
return self.value |
|
|
|
@property |
|
def grad(self): |
|
return Number(0) |
|
|
|
class Variable(Gradable): |
|
def __init__(self, a): |
|
self.alias = a |
|
|
|
def calc(self, x): |
|
return x |
|
|
|
@property |
|
def grad(self): |
|
return Number(1) |
|
|
|
class Add(Gradable): |
|
def __init__(self, a, b): |
|
self.a = magic(a) |
|
self.b = magic(b) |
|
|
|
def calc(self, x): |
|
return self.a(x) + self.b(x) |
|
|
|
@property |
|
def grad(self): |
|
return self.a.grad + self.b.grad |
|
|
|
class Sub(Gradable): |
|
def __init__(self, a, b): |
|
self.a = magic(a) |
|
self.b = magic(b) |
|
|
|
def calc(self, x): |
|
return self.a(x) - self.b(x) |
|
|
|
@property |
|
def grad(self): |
|
return self.a.grad - self.b.grad |
|
|
|
class Mul(Gradable): |
|
def __init__(self, a, b): |
|
self.a = magic(a) |
|
self.b = magic(b) |
|
|
|
def calc(self, x): |
|
return self.a(x) * self.b(x) |
|
|
|
@property |
|
def grad(self): |
|
return self.a.grad * self.b + self.a * self.b.grad |
|
|
|
class Div(Gradable): |
|
def __init__(self, a, b): |
|
self.a = magic(a) |
|
self.b = magic(b) |
|
|
|
def calc(self, x): |
|
return self.a(x) / self.b(x) |
|
|
|
@property |
|
def grad(self): |
|
return (self.a.grad * self.b - self.a * self.b.grad) / (self.b ** 2) |
|
|
|
class Pow(Gradable): |
|
def __init__(self, a, b): |
|
self.a = magic(a) |
|
self.b = magic(b) |
|
|
|
def calc(self, x): |
|
return math.pow(self.a(x), self.b(x)) |
|
|
|
@property |
|
def grad(self): |
|
return Pow(self.a, self.b) * self.b.grad * Log(self.a) + Pow(self.a, self.b - 1) * self.b * self.a.grad |
|
|
|
class Log(Gradable): |
|
def __init__(self, *args): |
|
if not 1 <= len(args) <= 2: |
|
raise TypeError(f"Log() takes 1 or 2 positional arguments but {len(args)} were given") |
|
self.a = magic(args[0]) |
|
self.b = magic(args[1]) if len(args) == 2 else Number(math.e) |
|
|
|
def calc(self, x): |
|
return math.log(self.a(x), self.b(x)) |
|
|
|
@property |
|
def grad(self): |
|
return self.a.grad / (self.a * Log(self.b)) - (Log(self.a) * self.b.grad) / (self.b * Log(self.b) ** 2) |