code: def single_evaluation_bandit_algo(
      bandit: MAB,
      algo: MABAlgo,
      *,
      update_every: int = 1,
      freeze_scores_btw_updates: bool = True,
  ) -> np.ndarray:
      """
      Evaluate a bandit algorithm on a single bandit instance.
      Pseudo-regret (difference between expected values of best and chosen actions) is used to minimize variance of evaluation
  
      Args:
          bandit: Bandit instance on which we evaluate
          algo: Bandit algorithm to be evaluated
          update_every: How many steps between the model is updated. 1 is online learning, >1 is iterative batch learning.
          freeze_scores_btw_updates: If True, the scores are frozen between model updates, otherwise at each step we generate
              new scores even if the model wasn't updated. `False` doesn't make sense for UCB models since the scores are deterministic
              and wouldn't change until the model is updated. Use `False` only for models with non-deterministic scores, like Thompson sampling.
      Returns:
          An array of cumulative pseudo regret
      """
      rewards = []
      expected_rewards = []
      # iterate through model updates
      remaining_steps = bandit.max_steps
      for _ in range(0, bandit.max_steps, update_every):
          batch_n_obs_per_arm = torch.zeros(bandit.n_arms)
          batch_sum_reward_per_arm = torch.zeros(bandit.n_arms)
          batch_sum_squared_reward_per_arm = torch.zeros(bandit.n_arms)
          steps_before_update = min(
              remaining_steps, update_every
          )  # take this many steps until next model update
          arm_id = (
              algo.get_action()
          )  # this action will be reused until next model update if freeze_scores_btw_updates
          for i in range(steps_before_update):
              # iterate through steps without updating the model
              if (not freeze_scores_btw_updates) and (i > 0):
                  # if scores are not frozen, we choose new action at each step
                  # (except first, because we've already chosen the first action above)
                  arm_id = algo.get_action()
              arm_idx = algo.arm_ids.index(arm_id)
              reward = bandit.act(arm_id)
              rewards.append(reward)
              expected_rewards.append(
                  bandit.expected_rewards[arm_idx].item()
              )
              batch_n_obs_per_arm[arm_idx] += 1
              batch_sum_reward_per_arm[arm_idx] += reward
              batch_sum_squared_reward_per_arm[arm_idx] += reward**2
          assert sum(batch_n_obs_per_arm) == steps_before_update
          # perform batch update
          algo.add_batch_observations(
              batch_n_obs_per_arm,
              batch_sum_reward_per_arm,
              batch_sum_squared_reward_per_arm,
          )
          remaining_steps -= steps_before_update
      assert remaining_steps == 0
      assert len(rewards) == bandit.max_steps
      per_step_pseudo_regret = bandit.best_action_value - np.array(
          expected_rewards
      )
      return np.cumsum(per_step_pseudo_regret)
  
imports: from abc import ABC, abstractmethod
  from functools import partial
  from multiprocessing import Pool
  from typing import Dict, List, Optional, Tuple, Type, Union
  import numpy
  import torch
  from reagent.mab.mab_algorithm import MABAlgo
  from torch import Tensor
label_desc: Use image node_id 1 for calling a global function with example usage: single_evaluation_bandit_algo(bandit, algo) and returns: np