def generate(  # type: ignore
        self, x: np.ndarray, y: np.ndarray | None = None, **kwargs
    ) -> tuple[np.ndarray, np.ndarray]:
        """
        Generate an adversarial patch and return the patch and its mask in arrays.

        :param x: An array with the original input images of shape NHWC or NCHW or input videos of shape NFHWC or NFCHW.
        :param y: An array with the original true labels.
        :param mask: A boolean array of shape equal to the shape of a single samples (1, H, W) or the shape of `x`
                     (N, H, W) without their channel dimensions. Any features for which the mask is True can be the
                     center location of the patch during sampling.
        :type mask: `np.ndarray`
        :param reset_patch: If `True` reset patch to initial values of mean of minimal and maximal clip value, else if
                            `False` (default) restart from previous patch values created by previous call to `generate`
                            or mean of minimal and maximal clip value if first call to `generate`.
        :type reset_patch: bool
        :return: An array with adversarial patch and an array of the patch mask.
        """
        logger.info("Creating adversarial patch.")

        test_input_shape = list(self.estimator.input_shape)

        for i, size in enumerate(self.estimator.input_shape):
            if size is None or size != x.shape[i + 1]:
                test_input_shape[i] = x.shape[i + 1]

        self.input_shape = tuple(test_input_shape)

        mask = kwargs.get("mask")
        if mask is not None:
            mask = mask.copy()
        if mask is not None and (
            (mask.dtype != bool)
            or not (mask.shape[0] == 1 or mask.shape[0] == x.shape[0])
            or not (
                (mask.shape[1] == x.shape[1] and mask.shape[2] == x.shape[2])
                or (mask.shape[1] == x.shape[2] and mask.shape[2] == x.shape[3])
            )
        ):
            raise ValueError(
                "The shape of `mask` has to be equal to the shape of a single samples (1, H, W) or the"
                "shape of `x` (N, H, W) without their channel dimensions."
            )

        if len(x.shape) == 2:
            raise ValueError(
                "Feature vectors detected. The adversarial patch can only be applied to data with spatial "
                "dimensions."
            )

        if kwargs.get("reset_patch"):
            self.reset_patch(self.mean_value)

        if y is not None:
            y_target = check_and_transform_label_format(labels=y, nb_classes=self.estimator.nb_classes)
        else:
            raise ValueError("Labels `y` cannot be `None`.")

        for _ in trange(self.max_iter, desc="Adversarial Patch Numpy", disable=not self.verbose):
            patched_images, patch_mask_transformed, transforms = self._augment_images_with_random_patch(
                x, self.patch, mask=mask
            )

            num_batches = int(math.ceil(x.shape[0] / self.batch_size))
            patch_gradients = np.zeros_like(self.patch)

            for i_batch in range(num_batches):
                i_batch_start = i_batch * self.batch_size
                i_batch_end = (i_batch + 1) * self.batch_size

                gradients = self.estimator.loss_gradient(
                    patched_images[i_batch_start:i_batch_end],
                    y_target[i_batch_start:i_batch_end],
                )

                for i_image in range(gradients.shape[0]):
                    patch_gradients_i = self._reverse_transformation(
                        gradients[i_image, :, :, :],
                        patch_mask_transformed[i_image, :, :, :],
                        transforms[i_image],
                    )
                    if self.nb_dims == 4:
                        patch_gradients_i = np.mean(patch_gradients_i, axis=0)
                    patch_gradients += patch_gradients_i

            # patch_gradients = patch_gradients / (num_batches * self.batch_size)
            self.patch -= patch_gradients * self.learning_rate
            if self.estimator.clip_values is not None:
                self.patch = np.clip(
                    self.patch,
                    a_min=self.estimator.clip_values[0],
                    a_max=self.estimator.clip_values[1],
                )
            else:
                raise ValueError("Clip values of estimator cannot be None.")

        return self.patch, self._get_circular_patch_mask()

        Generate an adversarial patch and return the patch and its mask in arrays.

        :param x: An array with the original input images of shape NHWC or NCHW or input videos of shape NFHWC or NFCHW.
        :param y: An array with the original true labels.
        :param mask: A boolean array of shape equal to the shape of a single samples (1, H, W) or the shape of `x`
                     (N, H, W) without their channel dimensions. Any features for which the mask is True can be the
                     center location of the patch during sampling.
        :type mask: `np.ndarray`
        :param reset_patch: If `True` reset patch to initial values of mean of minimal and maximal clip value, else if
                            `False` (default) restart from previous patch values created by previous call to `generate`
                            or mean of minimal and maximal clip value if first call to `generate`.
        :type reset_patch: bool
        :return: An array with adversarial patch and an array of the patch mask.
        
art/attacks/evasion/adversarial_patch/adversarial_patch_numpy.py