def generate(self, x: np.ndarray, y: np.ndarray | None = None, **kwargs) -> np.ndarray:
        """
        Generate adversarial samples and return them in an array.

        :param x: An array with the original inputs.
        :param y: Target values (class labels) one-hot-encoded of shape `(nb_samples, nb_classes)` or indices of shape
                  (nb_samples,). Only provide this parameter if you'd like to use true labels when crafting adversarial
                  samples. Otherwise, model predictions are used as labels to avoid the "label leaking" effect
                  (explained in this paper: https://arxiv.org/abs/1611.01236). Default is `None`.
        :param mask: An array with a mask broadcastable to input `x` defining where to apply adversarial perturbations.
                     Shape needs to be broadcastable to the shape of x and can also be of the same shape as `x`. Any
                     features for which the mask is zero will not be adversarially perturbed.
        :type mask: `np.ndarray`
        :return: An array holding the adversarial examples.
        """
        mask = kwargs.get("mask")

        if y is not None:
            y = check_and_transform_label_format(y, nb_classes=self.estimator.nb_classes)

        if y is None:
            if self.targeted:
                raise ValueError("Target labels `y` need to be provided for a targeted attack.")
            y = get_labels_np_array(self.estimator.predict(x, batch_size=self.batch_size)).astype(int)

        if self.estimator.nb_classes == 2 and y.shape[1] == 1:
            raise ValueError(
                "This attack has not yet been tested for binary classification with a single output classifier."
            )

        x_adv = x.astype(ART_NUMPY_DTYPE)

        for _ in trange(max(1, self.nb_random_init), desc="ACG - restart", disable=not self.verbose):
            # Determine correctly predicted samples
            y_pred = self.estimator.predict(x_adv)
            if self.targeted:
                sample_is_robust = np.argmax(y_pred, axis=1) != np.argmax(y, axis=1)
            elif not self.targeted:
                sample_is_robust = np.argmax(y_pred, axis=1) == np.argmax(y, axis=1)

            if np.sum(sample_is_robust) == 0:
                break

            x_robust = x_adv[sample_is_robust]
            y_robust = y[sample_is_robust]
            x_init = x[sample_is_robust]

            n = x_robust.shape[0]
            m = np.prod(x_robust.shape[1:]).item()
            random_perturbation = (
                random_sphere(n, m, self.eps, self.norm).reshape(x_robust.shape).astype(ART_NUMPY_DTYPE)
            )

            x_robust = x_robust + random_perturbation

            if self.estimator.clip_values is not None:
                clip_min, clip_max = self.estimator.clip_values
                x_robust = np.clip(x_robust, clip_min, clip_max)

            perturbation = projection(x_robust - x_init, self.eps, self.norm)
            x_robust = x_init + perturbation

            # Compute perturbation with implicit batching
            for batch_id in trange(
                int(np.ceil(x_robust.shape[0] / float(self.batch_size))),
                desc="ACG - batch",
                leave=False,
                disable=not self.verbose,
            ):
                batch_index_1, batch_index_2 = batch_id * self.batch_size, (batch_id + 1) * self.batch_size
                x_k = x_robust[batch_index_1:batch_index_2].astype(ART_NUMPY_DTYPE)
                x_init_batch = x_init[batch_index_1:batch_index_2].astype(ART_NUMPY_DTYPE)
                y_batch = y_robust[batch_index_1:batch_index_2]

                p_0 = 0
                p_1 = 0.22
                var_w = [p_0, p_1]

                while True:
                    p_j_p_1 = var_w[-1] + max(var_w[-1] - var_w[-2] - 0.03, 0.06)
                    if p_j_p_1 > 1:
                        break
                    var_w.append(p_j_p_1)

                var_w = [math.ceil(p * self.max_iter) for p in var_w]

                # self.eta = np.full((self.batch_size, 1, 1, 1), 2 * self.eps_step).astype(ART_NUMPY_DTYPE)
                _batch_size = x_k.shape[0]
                eta = np.full((_batch_size,) + (1,) * len(self.estimator.input_shape), self.eps_step).astype(
                    ART_NUMPY_DTYPE
                )
                self.count_condition_1 = np.zeros(shape=(_batch_size,))
                gradk_1 = np.zeros_like(x_k)
                cgradk_1 = np.zeros_like(x_k)
                cgradk = np.zeros_like(x_k)
                gradk_1_best = np.zeros_like(x_k)
                cgradk_1_best = np.zeros_like(x_k)
                gradk_1_tmp = np.zeros_like(x_k)
                cgradk_1_tmp = np.zeros_like(x_k)

                for k_iter in trange(self.max_iter, desc="ACG - iteration", leave=False, disable=not self.verbose):

                    # Get perturbation, use small scalar to avoid division by 0
                    tol = 10e-8

                    # Get gradient wrt loss; invert it if attack is targeted
                    grad = self.estimator.loss_gradient(x_k, y_batch) * (1 - 2 * int(self.targeted))
                    if k_iter == 0:
                        gradk_1 = grad.copy()
                        cgradk_1 = grad.copy()
                        cgradk = grad.copy()
                    else:
                        beta = get_beta(grad, gradk_1, cgradk_1)
                        cgradk = grad + beta * cgradk_1

                    # Apply norm bound
                    if self.norm in [np.inf, "inf"]:
                        grad = np.sign(cgradk)
                    elif self.norm == 1:
                        ind = tuple(range(1, len(x_k.shape)))
                        cgradk = cgradk / (np.sum(np.abs(cgradk), axis=ind, keepdims=True) + tol)
                    elif self.norm == 2:
                        ind = tuple(range(1, len(x_k.shape)))
                        cgradk = cgradk / (np.sqrt(np.sum(np.square(cgradk), axis=ind, keepdims=True)) + tol)
                    assert x_k.shape == cgradk.shape

                    perturbation = cgradk

                    if mask is not None:
                        perturbation = perturbation * (mask.astype(ART_NUMPY_DTYPE))

                    # Apply perturbation and clip
                    x_k_p_1 = x_k + eta * perturbation

                    if self.estimator.clip_values is not None:
                        clip_min, clip_max = self.estimator.clip_values
                        x_k_p_1 = np.clip(x_k_p_1, clip_min, clip_max)

                    if k_iter == 0:
                        x_1 = x_k_p_1
                        perturbation = projection(x_1 - x_init_batch, self.eps, self.norm)
                        x_1 = x_init_batch + perturbation

                        f_0 = self.estimator.compute_loss(x=x_k, y=y_batch, reduction="none")
                        f_1 = self.estimator.compute_loss(x=x_1, y=y_batch, reduction="none")

                        self.eta_w_j_m_1 = eta.copy()
                        self.f_max_w_j_m_1 = f_0.copy()

                        self.f_max = f_0.copy()
                        self.x_max = x_k.copy()

                        f1_ge_f0 = f_1 >= f_0
                        f_1_tmp = f_1[f1_ge_f0].copy()
                        self.f_max[f1_ge_f0] = f_1_tmp.copy()
                        x_1_tmp = x_1[f1_ge_f0].copy()
                        self.x_max[f1_ge_f0] = x_1_tmp.copy()
                        self.count_condition_1[f1_ge_f0] += 1

                        # Settings for next iteration k
                        x_k = x_1
                        gradk_1_best = gradk_1.copy()
                        cgradk_1_best = cgradk_1.copy()

                    else:
                        perturbation = projection(x_k_p_1 - x_init_batch, self.eps, self.norm)
                        x_k_p_1 = x_init_batch + perturbation

                        if self.estimator.clip_values is not None:
                            clip_min, clip_max = self.estimator.clip_values
                            x_k_p_1 = np.clip(x_k_p_1, clip_min, clip_max)

                        perturbation = projection(x_k_p_1 - x_init_batch, self.eps, self.norm)
                        x_k_p_1 = x_init_batch + perturbation

                        f_k_p_1 = self.estimator.compute_loss(x=x_k_p_1, y=y_batch, reduction="none")

                        if (f_k_p_1 == 0.0).all():
                            x_k = x_k_p_1.copy()
                            break

                        if self.targeted:
                            fk_ge_fm = f_k_p_1 < self.f_max  # assume the loss function is cross-entropy
                        else:
                            fk_ge_fm = f_k_p_1 > self.f_max

                        self.count_condition_1[fk_ge_fm] += 1
                        # update the best points
                        x_k_p_1_tmp = x_k_p_1[fk_ge_fm].copy()
                        self.x_max[fk_ge_fm] = x_k_p_1_tmp.copy()
                        f_k_p_1_tmp = f_k_p_1[fk_ge_fm].copy()
                        self.f_max[fk_ge_fm] = f_k_p_1_tmp.copy()
                        gradk_1_tmp = gradk_1[fk_ge_fm].copy()
                        gradk_1_best[fk_ge_fm] = gradk_1_tmp.copy()
                        cgradk_1_tmp = cgradk_1[fk_ge_fm].copy()
                        cgradk_1_best[fk_ge_fm] = cgradk_1_tmp.copy()

                        # update the search points
                        x_k = x_k_p_1.copy()
                        gradk_1 = grad.copy()
                        cgradk_1 = cgradk.copy()

                        if k_iter in var_w:

                            rho = 0.75

                            condition_1 = self.count_condition_1 < rho * (k_iter - var_w[var_w.index(k_iter) - 1])
                            condition_2 = np.logical_and(
                                (self.eta_w_j_m_1 == eta).squeeze(), self.f_max_w_j_m_1 == self.f_max
                            )
                            condition = np.logical_or(condition_1, condition_2)

                            # halve the stepsize if the condition is satisfied
                            eta[condition] /= 2
                            # move to the best point
                            x_max_tmp = self.x_max[condition].copy()
                            x_k[condition] = x_max_tmp.copy()
                            gradk_1_tmp = gradk_1_best[condition].copy()
                            gradk_1[condition] = gradk_1_tmp.copy()
                            cgradk_1_tmp = cgradk_1_best[condition].copy()
                            cgradk_1[condition] = cgradk_1_tmp.copy()

                            self.count_condition_1[:] = 0
                            self.eta_w_j_m_1 = eta.copy()
                            self.f_max_w_j_m_1 = self.f_max.copy()

                y_pred_adv_k = self.estimator.predict(x_k)
                if self.targeted:
                    sample_is_not_robust_k = np.invert(np.argmax(y_pred_adv_k, axis=1) != np.argmax(y_batch, axis=1))
                elif not self.targeted:
                    sample_is_not_robust_k = np.invert(np.argmax(y_pred_adv_k, axis=1) == np.argmax(y_batch, axis=1))

                x_robust[batch_index_1:batch_index_2][sample_is_not_robust_k] = x_k[sample_is_not_robust_k]

            x_adv[sample_is_robust] = x_robust

        return x_adv

        Generate adversarial samples and return them in an array.

        :param x: An array with the original inputs.
        :param y: Target values (class labels) one-hot-encoded of shape `(nb_samples, nb_classes)` or indices of shape
                  (nb_samples,). Only provide this parameter if you'd like to use true labels when crafting adversarial
                  samples. Otherwise, model predictions are used as labels to avoid the "label leaking" effect
                  (explained in this paper: https://arxiv.org/abs/1611.01236). Default is `None`.
        :param mask: An array with a mask broadcastable to input `x` defining where to apply adversarial perturbations.
                     Shape needs to be broadcastable to the shape of x and can also be of the same shape as `x`. Any
                     features for which the mask is zero will not be adversarially perturbed.
        :type mask: `np.ndarray`
        :return: An array holding the adversarial examples.
        
art/attacks/evasion/auto_conjugate_gradient.py