Batch - the most atomic unit of learning.

        For this reinforforcer, that involves:

        1. Roll out the environmnent using current policy
        2. Use that rollout to train the policy

def train_batch(self, batch_info: BatchInfo) -> None:
        """
        Batch - the most atomic unit of learning.

        For this reinforforcer, that involves:

        1. Roll out the environmnent using current policy
        2. Use that rollout to train the policy
        """
        # Calculate environment rollout on the evaluation version of the model
        self.model.train()

        rollout = self.env_roller.rollout(batch_info, self.model, self.settings.number_of_steps)

        # Process rollout by the 'algo' (e.g. perform the advantage estimation)
        rollout = self.algo.process_rollout(batch_info, rollout)

        # Perform the training step
        # Algo will aggregate data into this list:
        batch_info['sub_batch_data'] = []

        if self.settings.shuffle_transitions:
            rollout = rollout.to_transitions()

        if self.settings.stochastic_experience_replay:
            # Always play experience at least once
            experience_replay_count = max(np.random.poisson(self.settings.experience_replay), 1)
        else:
            experience_replay_count = self.settings.experience_replay

        # Repeat the experience N times
        for i in range(experience_replay_count):
            # We may potentially need to split rollout into multiple batches
            if self.settings.batch_size >= rollout.frames():
                batch_result = self.algo.optimizer_step(
                    batch_info=batch_info,
                    device=self.device,
                    model=self.model,
                    rollout=rollout.to_device(self.device)
                )

                batch_info['sub_batch_data'].append(batch_result)
            else:
                # Rollout too big, need to split in batches
                for batch_rollout in rollout.shuffled_batches(self.settings.batch_size):
                    batch_result = self.algo.optimizer_step(
                        batch_info=batch_info,
                        device=self.device,
                        model=self.model,
                        rollout=batch_rollout.to_device(self.device)
                    )

                    batch_info['sub_batch_data'].append(batch_result)

        batch_info['frames'] = rollout.frames()
        batch_info['episode_infos'] = rollout.episode_information()

        # Even with all the experience replay, we count the single rollout as a single batch
        batch_info.aggregate_key('sub_batch_data')