import numpy as np

def logsig(x):
    """
    Numerically stable implementation of log sigmoid function

    ..math::
        logsig(x) = \log\frac{1}{1+\exp(-x)}

    See http://fa.bianp.net/blog/2019/evaluate_logistic/
    """
    out = np.zeros_like(x)
    idx0 = x < -33
    out[idx0] = x[idx0]
    idx1 = (x >= -33) & (x < -18)
    out[idx1] = x[idx1] - np.exp(x[idx1])
    idx2 = (x >= -18) & (x < 37)
    out[idx2] = -np.log1p(np.exp(-x[idx2]))
    idx3 = x >= 37
    out[idx3] = -np.exp(-x[idx3])
    return out

def logistic_loss(x, A, b):
    """
    x : variable (n,)
    A : row piled matrix  [a1; a2; ...; a_k] (k, n)
    b : sign vector (k, )
    """
    return -logsig(b * np.dot(A, x)).mean()

def logistic_loss_grad(x, a, b):
    """
    gradient coefficient
    """
    t = b * np.dot(a, x)
    if t > 0:
        coeff = np.exp(-t) / (1 + np.exp(-t))
    else:
        coeff = 1 / (1 + np.exp(t))
    return -coeff * b * a

def hinge_loss(x, A, b):
    """
    compute hinge loss
     x : input vector (n,)
     A : feature matrix (k, n)
     b : label vector (k, )
    """
    return np.maximum(0, 1 -b * np.dot(A, x)).mean()

def hinge_loss_grad(x, a, b):
    if hinge_loss(x, a, b) > 0:
        return -b * a
    else:
        return np.zeros_like(x)