def projection(
    values: np.ndarray,
    eps: int | float | np.ndarray,
    norm_p: int | float | str,
    *,
    suboptimal: bool = True,
) -> np.ndarray:
    """
    Project `values` on the L_p norm ball of size `eps`.

    :param values: Array of perturbations to clip.
    :param eps: If a scalar, the norm of the L_p ball onto which samples are projected. Equivalently in general, can be
                any array of non-negatives broadcastable with `values`, and the projection occurs onto the unit ball
                for the weighted L_{p, w} norm with `w = 1 / eps`. Currently, for any given sample, non-uniform weights
                are only supported with infinity norm. Example: To specify sample-wise scalar, you can provide
                `eps.shape = (n_samples,) + (1,) * values[0].ndim`.
    :param norm_p: Lp norm to use for clipping, with `norm_p > 0`. Only 1, 2, `np.inf` and "inf" are currently
                   supported with `suboptimal=False` for now.
    :param suboptimal: If `True` simply projects by rescaling to Lp ball. Fast but may be suboptimal for `norm_p != 2`.
                       Ignored when `norm_p in [np.inf, "inf"]` because optimal solution is fast. Defaults to `True`.
    :return: Values of `values` after projection.
    """
    norm = np.inf if norm_p == "inf" else float(norm_p)
    assert norm > 0

    values_tmp = values.reshape(len(values), -1)  # (n_samples, d)

    eps = np.broadcast_to(eps, values.shape)
    eps = eps.reshape(len(eps), -1)  # (n_samples, d)
    assert np.all(eps >= 0)
    if norm != np.inf and not np.all(eps == eps[:, [0]]):
        raise NotImplementedError(
            "Projection onto the weighted L_p ball is currently not supported with finite `norm_p`."
        )

    if (suboptimal or norm == 2) and norm != np.inf:  # Simple rescaling
        values_norm = np.linalg.norm(values_tmp, ord=norm, axis=1, keepdims=True)  # (n_samples, 1)
        with np.errstate(divide="ignore"):
            values_tmp = values_tmp * np.where(values_norm, np.minimum(1, eps / values_norm), 0)
    else:  # Optimal
        if norm == np.inf:  # Easy exact case
            values_tmp = np.sign(values_tmp) * np.minimum(np.abs(values_tmp), eps)
        elif norm == 1:  # Harder exact case
            projection_l1 = projection_l1_1 if values_tmp.shape[1] > 29 else projection_l1_2  # From empirical tests
            values_tmp = projection_l1(values_tmp, eps[:, 0])
        elif norm > 1:  # Convex optim
            raise NotImplementedError(
                'Values of `norm_p > 1` different from 2, `np.inf` and "inf" are currently not supported with '
                "`suboptimal=False`."
            )
        else:  # Non-convex optim
            raise NotImplementedError("Values of `norm_p < 1` are currently not supported with `suboptimal=False`.")

    values = values_tmp.reshape(values.shape).astype(values.dtype)
    return values

    Project `values` on the L_p norm ball of size `eps`.

    :param values: Array of perturbations to clip.
    :param eps: If a scalar, the norm of the L_p ball onto which samples are projected. Equivalently in general, can be
                any array of non-negatives broadcastable with `values`, and the projection occurs onto the unit ball
                for the weighted L_{p, w} norm with `w = 1 / eps`. Currently, for any given sample, non-uniform weights
                are only supported with infinity norm. Example: To specify sample-wise scalar, you can provide
                `eps.shape = (n_samples,) + (1,) * values[0].ndim`.
    :param norm_p: Lp norm to use for clipping, with `norm_p > 0`. Only 1, 2, `np.inf` and "inf" are currently
                   supported with `suboptimal=False` for now.
    :param suboptimal: If `True` simply projects by rescaling to Lp ball. Fast but may be suboptimal for `norm_p != 2`.
                       Ignored when `norm_p in [np.inf, "inf"]` because optimal solution is fast. Defaults to `True`.
    :return: Values of `values` after projection.
    
art/utils.py