def split_continuous_linear_decay_stream(
    original_stream: Iterable[DatasetExperience[TCLDataset]],
    experience_size: int,
    iters_per_virtual_epoch: int,
    beta: float,
    shuffle: bool,
) -> CLStream[DatasetExperience[TCLDataset]]:
    """Creates a stream of sub-experiences from a list of overlapped
        experiences with a linear decay in the overlapping areas.

    :param original_stream: The original stream.
    :param experience_size: The size of each sub-experience.
    :param iters_per_virtual_epoch: The number of iterations per virtual epoch.
        This parameter determines the number of (sub-)experiences that we want
        to create from each experience in the original stream, after "merging"
        all experiences with a certain level of "overlap".
    :param beta: The beta parameter for the linear decay function which
        indicates the amount of overlap.
    :param shuffle: Whether to shuffle the sub-experiences.

    Terminology is taken from the official implementation of the paper:
    "Task Agnostic Continual Learning Using Online Variational Bayes" by
    Zero et al. (https://arxiv.org/abs/2006.05990)
    Code repo: https://github.com/chenzeno/FOO-VB/tree/main

    :return: A stream of sub-experiences.

    """

    def _get_linear_line(start, end, direction="up"):
        if direction == "up":
            return torch.FloatTensor(
                [(i - start) / (end - start) for i in range(start, end)]
            )
        return torch.FloatTensor(
            [1 - ((i - start) / (end - start)) for i in range(start, end)]
        )

    def _create_task_probs(iters, tasks, task_id, beta=3):
        if beta <= 1:
            peak_start = int((task_id / tasks) * iters)
            peak_end = int(((task_id + 1) / tasks) * iters)
            start = peak_start
            end = peak_end
        else:
            start = max(int(((beta * task_id - 1) * iters) / (beta * tasks)), 0)
            peak_start = int(((beta * task_id + 1) * iters) / (beta * tasks))
            peak_end = int(((beta * task_id + (beta - 1)) * iters) / (beta * tasks))
            end = min(
                int(((beta * task_id + (beta + 1)) * iters) / (beta * tasks)), iters
            )

        probs = torch.zeros(iters, dtype=torch.float)
        if task_id == 0:
            probs[start:peak_start].add_(1)
        else:
            probs[start:peak_start] = _get_linear_line(
                start, peak_start, direction="up"
            )
        probs[peak_start:peak_end].add_(1)
        if task_id == tasks - 1:
            probs[peak_end:end].add_(1)
        else:
            probs[peak_end:end] = _get_linear_line(peak_end, end, direction="down")
        return probs

    # Total number of iterations
    total_iters = len(original_stream) * iters_per_virtual_epoch

    # Probabilities over all iterations (sub-experiences)
    n_experiences = len(original_stream)
    tasks_probs_over_iterations = [
        _create_task_probs(total_iters, n_experiences, exp_id, beta=beta)
        for exp_id in range(n_experiences)
    ]

    # Normalize probabilities
    normalize_probs = torch.zeros_like(tasks_probs_over_iterations[0])
    for probs in tasks_probs_over_iterations:
        normalize_probs.add_(probs)
    for probs in tasks_probs_over_iterations:
        probs.div_(normalize_probs)
    tasks_probs_over_iterations = torch.cat(tasks_probs_over_iterations).view(
        -1, tasks_probs_over_iterations[0].shape[0]
    )
    tasks_probs_over_iterations_lst = []
    for col in range(tasks_probs_over_iterations.shape[1]):
        tasks_probs_over_iterations_lst.append(tasks_probs_over_iterations[:, col])
    tasks_probs_over_iterations = tasks_probs_over_iterations_lst

    # Random cylic samplers over the datasets of all experiences in the stream
    samplers = [
        iter(CyclicSampler(len(exp.dataset), shuffle=shuffle))
        for exp in original_stream
    ]

    # The main iterator for the stream
    def exps_iter():
        for sub_exp_id in range(total_iters):
            is_first_sub_exp = is_last_sub_exp = False
            if sub_exp_id == 0:
                is_first_sub_exp = True
            if sub_exp_id == total_iters - 1:
                is_last_sub_exp = True

            n_samples = torch.Size([experience_size])
            exp_per_sample_list = Categorical(
                probs=tasks_probs_over_iterations[sub_exp_id]
            ).sample(n_samples)

            yield create_sub_exp_from_multi_exps(
                original_stream,
                samplers,
                exp_per_sample_list,
                total_iters,
                is_first_sub_exp,
                is_last_sub_exp,
            )

    stream_name: str = getattr(original_stream, "name", "train")
    return CLStream(
        name=stream_name,
        exps_iter=exps_iter(),
        set_stream_info=True,
    )
Creates a stream of sub-experiences from a list of overlapped
        experiences with a linear decay in the overlapping areas.

    :param original_stream: The original stream.
    :param experience_size: The size of each sub-experience.
    :param iters_per_virtual_epoch: The number of iterations per virtual epoch.
        This parameter determines the number of (sub-)experiences that we want
        to create from each experience in the original stream, after "merging"
        all experiences with a certain level of "overlap".
    :param beta: The beta parameter for the linear decay function which
        indicates the amount of overlap.
    :param shuffle: Whether to shuffle the sub-experiences.

    Terminology is taken from the official implementation of the paper:
    "Task Agnostic Continual Learning Using Online Variational Bayes" by
    Zero et al. (https://arxiv.org/abs/2006.05990)
    Code repo: https://github.com/chenzeno/FOO-VB/tree/main

    :return: A stream of sub-experiences.

    
avalanche/benchmarks/scenarios/online.py