def _smooth_mix_pgd_attack(
        self,
        inputs: "torch.Tensor",
        labels: "torch.Tensor",
        noises: list["torch.Tensor"],
        warmup_v: float,
    ) -> tuple["torch.Tensor", "torch.Tensor"]:
        """
        The authors' implementation of the SmoothMixPGD attack.
        Code modified from https://github.com/jh-jeong/smoothmix/blob/main/code/train.py

        :param inputs: The batch inputs
        :param labels: The batch labels for the inputs
        :param noises: The noise applied to each input in the attack
        """
        import torch
        import torch.nn.functional as F

        def _batch_l2_norm(x: torch.Tensor) -> torch.Tensor:
            """
            Perform a batch L2 norm

            :param x: The inputs to compute the batch L2 norm of
            """
            x_flat = x.reshape(x.size(0), -1)
            return torch.norm(x_flat, dim=1)

        def _project(x: torch.Tensor, x_0: torch.Tensor, maxnorm: float | None = None):
            """
            Apply a projection of the current inputs with the maxnorm

            :param x: The inputs to apply a projection on (either original or adversarial)
            :param x_0: The unperterbed inputs to apply the projection on
            :param maxnorm: The maxnorm value to apply to x
            """
            if maxnorm is not None:
                eta = x - x_0
                eta = eta.renorm(p=2, dim=0, maxnorm=maxnorm)
                x = x_0 + eta
            x = torch.clamp(x, 0, 1)
            x = x.detach()
            return x

        adv = inputs.detach()
        init = inputs.detach()
        for i in range(self.num_steps):
            if i == self.mix_step:
                init = adv.detach()
            adv.requires_grad_()

            softmax = [F.softmax(self._model(adv + noise)[-1], dim=1) for noise in noises]
            avg_softmax = torch.mean(torch.stack(softmax), dim=0)
            log_softmax = torch.log(avg_softmax.clamp(min=1e-20))
            loss = F.nll_loss(log_softmax, labels, reduction="sum")

            grad = torch.autograd.grad(loss, [adv])[0]
            grad_norm = _batch_l2_norm(grad).view(-1, 1, 1, 1)
            grad = grad / (grad_norm + 1e-8)
            adv = adv + self.alpha * grad

            adv = _project(adv, inputs, self.maxnorm)

        if self.maxnorm_s is None:
            maxnorm_s = self.alpha * self.mix_step * warmup_v
        else:
            maxnorm_s = self.maxnorm_s * warmup_v
        init = _project(init, inputs, maxnorm_s)

        return init, adv

        The authors' implementation of the SmoothMixPGD attack.
        Code modified from https://github.com/jh-jeong/smoothmix/blob/main/code/train.py

        :param inputs: The batch inputs
        :param labels: The batch labels for the inputs
        :param noises: The noise applied to each input in the attack
        
art/estimators/certification/randomized_smoothing/smooth_mix/pytorch.py