def poison(  # type: ignore
        self,
        x_trigger: np.ndarray,
        y_trigger: np.ndarray,
        x_train: np.ndarray,
        y_train: np.ndarray,
        x_test: np.ndarray,
        y_test: np.ndarray,
    ) -> tuple[np.ndarray, np.ndarray]:
        """
        Optimizes a portion of poisoned samples from x_train to make a model classify x_target
        as y_target by matching the gradients.

        :param x_trigger: A list of samples to use as triggers.
        :param y_trigger: A list of target classes to classify the triggers into.
        :param x_train: A list of training data to poison a portion of.
        :param y_train: A list of labels for x_train.
        :return: x_train, y_train and indices of poisoned samples.
                 Here, x_train are the samples selected from target class
                 in training data.
        """
        # Apply Normalisation
        x_train = np.copy(x_train)
        if isinstance(
            self.substitute_classifier.preprocessing, (StandardisationMeanStdPyTorch, StandardisationMeanStdTensorFlow)
        ):
            x_trigger = (
                x_trigger - self.substitute_classifier.preprocessing.mean
            ) / self.substitute_classifier.preprocessing.std
            x_train = (
                x_train - self.substitute_classifier.preprocessing.mean
            ) / self.substitute_classifier.preprocessing.std

        x_train_target_samples, y_train_target_samples = self._select_target_train_samples(x_train, y_train)
        if isinstance(self.substitute_classifier, PyTorchClassifier):
            poisoner = self._poison__pytorch
            finish_poisoning = self._finish_poison_pytorch
            initializer = self._initialize_poison_pytorch
        elif isinstance(self.substitute_classifier, TensorFlowV2Classifier):
            poisoner = self._poison__tensorflow
            finish_poisoning = self._finish_poison_tensorflow
            initializer = self._initialize_poison_tensorflow
        else:
            raise NotImplementedError("SleeperAgentAttack is currently implemented only for PyTorch and TensorFlowV2.")

        # Choose samples to poison.
        x_trigger = self._apply_trigger_patch(x_trigger)
        if len(np.shape(y_trigger)) == 2:  # dense labels
            classes_target = set(np.argmax(y_trigger, axis=-1))
        else:  # sparse labels
            classes_target = set(y_trigger)
        num_poison_samples = int(self.percent_poison * len(x_train_target_samples))

        # Try poisoning num_trials times and choose the best one.
        best_B = np.finfo(np.float32).max  # pylint: disable=invalid-name
        best_x_poisoned: np.ndarray
        best_indices_poison: np.ndarray

        if len(np.shape(y_train)) == 2:
            y_train_classes = np.argmax(y_train_target_samples, axis=-1)
        else:
            y_train_classes = y_train_target_samples
        for _ in trange(self.max_trials):
            if self.selection_strategy == "random":
                self.indices_poison = np.random.permutation(
                    np.where([y in classes_target for y in y_train_classes])[0]
                )[:num_poison_samples]
            else:
                self.indices_poison = self._select_poison_indices(
                    self.substitute_classifier, x_train_target_samples, y_train_target_samples, num_poison_samples
                )
            x_poison = x_train_target_samples[self.indices_poison]
            y_poison = y_train_target_samples[self.indices_poison]
            initializer(x_trigger, y_trigger, x_poison, y_poison)
            if self.model_retrain:
                retrain_epochs = self.max_epochs // self.retraining_factor
                self.max_epochs = retrain_epochs
                for i in range(self.retraining_factor):
                    if i == self.retraining_factor - 1:
                        x_poisoned, B_ = poisoner(x_poison, y_poison)  # pylint: disable=invalid-name
                    else:
                        x_poisoned, B_ = poisoner(x_poison, y_poison)  # pylint: disable=invalid-name
                        self._model_retraining(x_poisoned, x_train, y_train, x_test, y_test)
                    self.initial_epoch = self.max_epochs
                    self.max_epochs = self.max_epochs + retrain_epochs

            else:
                x_poisoned, B_ = poisoner(x_poison, y_poison)  # pylint: disable=invalid-name
            finish_poisoning()
            B_ = np.mean(B_)  # Averaging B losses from multiple batches.  # pylint: disable=invalid-name
            if B_ < best_B:
                best_B = B_  # pylint: disable=invalid-name
                best_x_poisoned = x_poisoned
                best_indices_poison = self.indices_poison
        if best_B == np.finfo(np.float32).max:
            logger.warning("Attack unsuccessful: all loss values were non-finite. Defaulting to final trial.")
            best_B = B_  # pylint: disable=invalid-name
            best_x_poisoned = x_poisoned
            best_indices_poison = self.indices_poison
        # set indices_poison to be the best indices after all trials
        self.indices_poison = best_indices_poison

        # Apply De-Normalization
        if isinstance(
            self.substitute_classifier.preprocessing, (StandardisationMeanStdPyTorch, StandardisationMeanStdTensorFlow)
        ):
            x_train = (
                x_train * self.substitute_classifier.preprocessing.std + self.substitute_classifier.preprocessing.mean
            )
            best_x_poisoned = (
                best_x_poisoned * self.substitute_classifier.preprocessing.std
                + self.substitute_classifier.preprocessing.mean
            )
        if self.verbose > 0:
            logger.info("Best B-score: %s", best_B)
        if isinstance(self.substitute_classifier, PyTorchClassifier):
            x_train[self.indices_target[best_indices_poison]] = best_x_poisoned
        elif isinstance(self.substitute_classifier, TensorFlowV2Classifier):
            x_train[self.indices_target[best_indices_poison]] = best_x_poisoned
        else:
            raise NotImplementedError("SleeperAgentAttack is currently implemented only for PyTorch and TensorFlowV2.")
        return x_train, y_train

        Optimizes a portion of poisoned samples from x_train to make a model classify x_target
        as y_target by matching the gradients.

        :param x_trigger: A list of samples to use as triggers.
        :param y_trigger: A list of target classes to classify the triggers into.
        :param x_train: A list of training data to poison a portion of.
        :param y_train: A list of labels for x_train.
        :return: x_train, y_train and indices of poisoned samples.
                 Here, x_train are the samples selected from target class
                 in training data.
        
art/attacks/poisoning/sleeper_agent_attack.py