//  [python]
def project_ball(array, epsilon=1, ord=2):
    """
    Compute the orthogonal projection of the input tensor (as vector) onto the L_ord epsilon-ball.

    **Assumes the first dimension to be batch dimension, which is preserved.**

    :param array: array
    :type array: numpy.ndarray
    :param epsilon: radius of ball.
    :type epsilon: float
    :param ord: order of norm
    :type ord: int
    :return: projected vector
    :rtype: torch.autograd.Variable or torch.Tensor
    """

    assert isinstance(array, numpy.ndarray), 'given tensor should be numpy.ndarray'

    if ord == 0:
        assert epsilon >= 1
        size = array.shape
        flattened_size = n.prod(n.array(size[1:]))

        array = array.reshape(-1, flattened_size)
        sorted = n.sort(array, axis=1)

        k = int(math.ceil(epsilon))
        thresholds = sorted[:, -k]

        mask = (array >= expand_as(thresholds, array)).astype(float)
        array *= mask
    elif ord == 1:
        size = array.shape
        flattened_size = n.prod(n.array(size[1:]))

        array = array.reshape(-1, flattened_size)

        if False: #  Version 1
            # https://github.com/ftramer/MultiRobustness/blob/master/pgd_attack.py
            l1 = n.sum(n.abs(array), axis=1)
            epsilons = n.ones((array.shape[0]))*epsilon
            to_project = l1 > epsilons
            if n.any(to_project):
                n = n.sum(to_project)
                d = array[to_project]#.reshape(n, -1)  # n * N (N=h*w*ch)
                abs_d = n.abs(d)  # n * N
                mu = -n.sort(-abs_d, axis=-1)  # n * N
                cumsums = mu.cumsum(axis=-1)  # n * N
                eps_d = epsilons[to_project]
                js = 1.0 / n.arange(1, array.shape[1] + 1)
                temp = mu - js * (cumsums - n.expand_dims(eps_d, -1))
                rho = n.argmin(temp > 0, axis=-1)
                theta = 1.0 / (1 + rho) * (cumsums[range(n), rho] - eps_d)
                sgn = n.sign(d)
                d = sgn * n.maximum(abs_d - n.expand_dims(theta, -1), 0)
                array[to_project] = d.reshape(-1, array.shape[1])

        if True: # Version 2
            for i in range(array.shape[0]):
                # compute the vector of absolute values
                u = n.abs(array[i])
                # check if v is already a solution
                if u.sum() <= epsilon:
                    # L1-norm is <= s
                    continue
                # v is not already a solution: optimum lies on the boundary (norm == s)
                # project *u* on the simplex
                #w = project_simplex(u, s=epsilon)
                w = projection_simplex_sort(u, z=epsilon)
                # compute the solution to the original problem on v
                w *= n.sign(array[i])
                array[i] = w

        if len(size) == 4:
            array = array.reshape(-1, size[1], size[2], size[3])
        elif len(size) == 2:
            array = array.reshape(-1, size[1])
    elif ord == 2:
        size = array.shape
        flattened_size = n.prod(n.array(size[1:]))

        array = array.reshape(-1, flattened_size)
        clamped = n.clip(epsilon/n.linalg.norm(array, 2, axis=1), a_min=None, a_max=1)
        clamped = clamped.reshape(-1, 1)

        array = array * clamped
        if len(size) == 4:
            array = array.reshape(-1, size[1], size[2], size[3])
        elif len(size) == 2:
            array = array.reshape(-1, size[1])
    elif ord == float('inf'):
        array = n.clip(array, a_min=-epsilon, a_max=epsilon)
    else:
        raise NotImplementedError()

    return array