import numpy as np


class Activation:
    """Base class for activation functions"""

    def forward(self, x):
        raise NotImplementedError

    def backward(self, grad):
        raise NotImplementedError



# 1. Sigmoid

class Sigmoid(Activation):

    def forward(self, x):
        x = np.clip(x, -500, 500)
        self.out = 1 / (1 + np.exp(-x))
        return self.out

    def backward(self, grad):
        return grad * self.out * (1 - self.out)



# 2. Tanh

class Tanh(Activation):

    def forward(self, x):
        self.out = np.tanh(x)
        return self.out

    def backward(self, grad):
        return grad * (1 - self.out ** 2)



# 3. ReLU

class ReLU(Activation):

    def forward(self, x):
        self.x = x
        return np.maximum(0, x)

    def backward(self, grad):
        dx = grad.copy()
        dx[self.x <= 0] = 0
        return dx



# 4. Leaky ReLU

class LeakyReLU(Activation):

    def __init__(self, alpha=0.01):
        self.alpha = alpha

    def forward(self, x):
        self.x = x
        return np.where(x > 0, x, self.alpha * x)

    def backward(self, grad):
        dx = grad.copy()
        dx[self.x < 0] *= self.alpha
        return dx



# 5. Parametric ReLU (PReLU)

class PReLU(Activation):

    def __init__(self, alpha=0.25):
        self.alpha = alpha
        self.dalpha = 0

    def forward(self, x):
        self.x = x
        return np.where(x > 0, x, self.alpha * x)

    def backward(self, grad):

        dx = grad.copy()
        dx[self.x < 0] *= self.alpha

        # gradient for learnable parameter alpha
        self.dalpha = np.sum(grad[self.x < 0] * self.x[self.x < 0])

        return dx



# 6. Exponential Linear Unit (ELU)

class ELU(Activation):

    def __init__(self, alpha=1.0):
        self.alpha = alpha

    def forward(self, x):
        self.x = x
        return np.where(x > 0, x, self.alpha * (np.exp(x) - 1))

    def backward(self, grad):

        dx = grad.copy()

        mask = self.x <= 0
        dx[mask] *= self.alpha * np.exp(self.x[mask])

        return dx
    
    