class_docstr: Multi step soft actor critic.
  
  :param algorithm_config: Algorithm options.
  :param learner_model: Structured torch actor critic to train.
  :param distributed_actors: Distributed actors for collection of training rollouts.
  :param model_selection: Optional model selection class, receives model evaluation results.
  :param evaluator: The evaluator to use.
class_docstr_tok: ["Multi","step","soft","actor","critic",".",":","param","algorithm_config",":","Algorithm","options",".",":","param","learner_model",":","Structured","torch","actor","critic","to","train",".",":","param","distributed_actors",":","Distributed","actors","for","collection","of","training","rollouts",".",":","param","model_selection",":","Optional","model","selection","class",",","receives","model","evaluation","results",".",":","param","evaluator",":","The","evaluator","to","use","."]
code: def __init__(
      self,
      algorithm_config: SACAlgorithmConfig,
      learner_model: TorchActorCritic,
      distributed_actors: BaseDistributedWorkersWithBuffer,
      model_selection: Optional[BestModelSelection],
      evaluator: Optional[RolloutEvaluator],
  ):
      super().__init__(algorithm_config)
  
      self.algorithm_config = algorithm_config
      self.learner_model = learner_model
      self.distributed_workers = distributed_actors
      self.model_selection = model_selection
      self.evaluator = evaluator
  
      self.sub_step_keys = self.learner_model.policy.networks.keys()
  
      # initialize optimizer
      self.policy_optimizer = torch.optim.Adam(
          self.learner_model.policy.parameters(),
          lr=self.algorithm_config.lr,
      )
      self.q_critic_optimizer = [
          torch.optim.Adam(critic_param, lr=self.algorithm_config.lr)
          for critic_param in self.learner_model.critic.per_critic_parameters()
      ]
  
      # temporarily initialize env to get access to action spaces
      env = self.distributed_workers.env_factory()
  
      # Entropy tuning only supported for single step envs
      if self.algorithm_config.entropy_tuning:
          self.target_entropy, self.curr_log_entropy_coef = (
              dict(),
              dict(),
          )
          for step_key in self.sub_step_keys:
              self.target_entropy[step_key] = dict()
              for action_key, space in env.action_spaces_dict[
                  step_key
              ].spaces.items():
                  if isinstance(space, spaces.Box):
                      self.target_entropy[step_key][action_key] = (
                          self.algorithm_config.target_entropy_multiplier
                          * -torch.prod(torch.Tensor(space.shape))
                      )
                  elif isinstance(space, spaces.Discrete):
                      # Entropy target is multiplied by -1 in comparison to the original paper. This make training
                      #   stable and works much much better in practice.
                      self.target_entropy[step_key][action_key] = (
                          self.algorithm_config.target_entropy_multiplier
                          * -0.98
                          * (-torch.log(torch.tensor(1.0 / space.n)))
                      )
                  else:
                      raise Exception(
                          f"Target entropy could not be computed for the disired action space {space},"
                          "please switch off entropy tuning and try again."
                      )
                  print(
                      f"Target entropy for step '{step_key}', action: '{action_key}' has been set to: "
                      f"{self.target_entropy[step_key][action_key]}"
                  )
              if not self.learner_model.critic.only_discrete_spaces[
                  step_key
              ]:
                  self.target_entropy[step_key] = sum(
                      self.target_entropy[step_key].values()
                  )
              self.curr_log_entropy_coef[step_key] = torch.zeros(
                  1,
                  requires_grad=True,
                  device=self.learner_model.device,
              )
          self.entropy_optimizer = torch.optim.Adam(
              list(self.curr_log_entropy_coef.values()),
              lr=self.algorithm_config.entropy_coef_lr,
              eps=1e-4,
          )
          self.curr_entropy_coef = {
              step_key: torch.exp(log_alpha.detach())
              for step_key, log_alpha in self.curr_log_entropy_coef.items()
          }
      else:
          if isinstance(algorithm_config.entropy_coef, list):
              self.curr_entropy_coef = {
                  step_key: torch.tensor(entropy_coef).to(
                      self.learner_model.device
                  )
                  for step_key, entropy_coef in zip(
                      self.sub_step_keys, algorithm_config.entropy_coef
                  )
              }
          elif isinstance(algorithm_config.entropy_coef, dict):
              self.curr_entropy_coef = {
                  step_key: torch.tensor(entropy_coef).to(
                      self.learner_model.device
                  )
                  for step_key, entropy_coef in algorithm_config.entropy_coef.items()
              }
          else:
              self.curr_entropy_coef = {
                  step_key: torch.tensor(
                      algorithm_config.entropy_coef
                  ).to(self.learner_model.device)
                  for step_key in self.sub_step_keys
              }
  
      # Hook impala events to the event aggregator created in the super()
      self.events = self.distributed_workers.get_epoch_stats_aggregator().create_event_topic(
          SACEvents
      )
  
imports: import sys
  import time
  from typing import Union, Optional, BinaryIO, Dict, Tuple, List
  import numpy
  import torch
  import torch.nn
  from gym import spaces
  from maze.core.agent.torch_actor_critic import TorchActorCritic
  from maze.core.annotations import override
  from maze.core.env.base_env_events import BaseEnvEvents
  from maze.core.env.structured_env import ActorID
  from maze.core.log_stats.log_stats import increment_log_step, LogStatsLevel
  from maze.core.trajectory_recording.records.structured_spaces_record import StructuredSpacesRecord
  from maze.train.trainers.common.evaluators.rollout_evaluator import RolloutEvaluator
  from maze.train.trainers.common.model_selection.best_model_selection import BestModelSelection
  from maze.train.trainers.common.trainer import Trainer
  from maze.train.utils.train_utils import compute_gradient_norm
  from maze.utils.bcolors import BColors
  from maze.train.parallelization.distributed_actors.base_distributed_workers_with_buffer import BaseDistributedWorkersWithBuffer
  from maze.train.trainers.sac.sac_algorithm_config import SACAlgorithmConfig
  from maze.train.trainers.sac.sac_events import SACEvents
  from torch.distributions.utils import logits_to_probs
label_desc: Use image node_id 1 to create a new SAC object from inherited base classes: Trainer with example: obj = SAC(algorithm_config, learner_model, distributed_actors, model_selection, evaluator)