import numpy as np

class SoftPlus():
    """
    smooth, y-range [0, inf], vanishing gradient for x<<0 with exponential decay, moderate computationally expensive
    """
    @staticmethod
    def f(x):
        return np.where(
            x > 20,               # threshold chosen to avoid overflow in exp(x)
            x,                    # for large x, softplus(x) ≈ x
            np.log1p(np.exp(-np.abs(x))) + np.maximum(x, 0)
        )
        # return np.log1p(np.exp(x))  # use the above one for numerical stability
    @staticmethod
    def df(x, order=1):
        exp_neg_x = np.exp(-x)
        match order:
            case 1:
                return 1 / (1 + exp_neg_x) # sigmoid
            case 2:
                return exp_neg_x / (1 + exp_neg_x)**2
            case _:
                raise NotImplementedError(f"SoftPlus gradient order {order} is not implemented yet")
