def compute_perturbation_regions(
        self,
        input_perturbation_size: np.ndarray,
        input_perturb_sizes: list[list[int]],
        automatically_append: bool,
    ) -> tuple[np.ndarray, list[list[int]]]:
        """
        Based on the l0 budget and the provided allowable perturbation regions we iteratively mark regions of the PE
        file for modification until we exhaust our budget.

        :param input_perturb_sizes: The size of the regions we can perturb.
        :param input_perturbation_size: The total amount of perturbation allowed on a specific sample.
        :param automatically_append: If we want to automatically append unused perturbation on the end of the malware.
        :return perturbation_size: Remaining perturbation (if any)
        :return perturb_sizes: Potentially adjusted sizes of the locations in the PE file we can perturb.
        """
        perturb_sizes = input_perturb_sizes.copy()
        perturbation_size = input_perturbation_size.copy()

        # reduce the perturbation we add at the end if we have perturbation to add in the malware itself and reduce the
        # sizes given in input_perturb_sizes if it exceeds the total input_perturbation_size
        for i, section_sizes in enumerate(perturb_sizes):
            for j, size in enumerate(section_sizes):
                if perturbation_size[i] - size >= 0:
                    logger.info("on sample %d allocate %d in perturb region", i, size)
                    perturbation_size[i] = perturbation_size[i] - size
                else:  # run out of l0 budget.
                    excess = np.abs(perturbation_size[i] - size)  # amount of overspill
                    perturbation_size[i] = perturbation_size[i] - size
                    section_sizes[j] = size - excess
                    # update the perturb sizes

                    logger.info("on sample %d ran out of l0, update to %d from %d", i, section_sizes[j], size)

                    perturb_sizes[i] = section_sizes
                    perturbation_size[i] = 0
        perturbation_size = np.where(perturbation_size < 0, 0, perturbation_size)

        if not automatically_append:
            # if we do not want to automatically append then set the remaining perturbation to 0
            perturbation_size = np.zeros_like(perturbation_size)
            # total_perturbation is now sample dependant so reassign.
            total_perturbation = np.zeros_like(perturbation_size)
            for i in range(len(perturbation_size)):
                total_perturbation[i] = np.sum(perturb_sizes[i])
            self.total_perturbation = total_perturbation
        return perturbation_size, perturb_sizes

        Based on the l0 budget and the provided allowable perturbation regions we iteratively mark regions of the PE
        file for modification until we exhaust our budget.

        :param input_perturb_sizes: The size of the regions we can perturb.
        :param input_perturbation_size: The total amount of perturbation allowed on a specific sample.
        :param automatically_append: If we want to automatically append unused perturbation on the end of the malware.
        :return perturbation_size: Remaining perturbation (if any)
        :return perturb_sizes: Potentially adjusted sizes of the locations in the PE file we can perturb.
        
art/attacks/evasion/pe_malware_attack.py