class_docstr: The TruncatedNormal exploration policy is intended for continuous action spaces. It samples the action from a
  normal distribution, where the mean action is given by the agent, and the standard deviation can be given in t
  wo different ways:
  1. Specified by the user as a noise schedule which is taken in percentiles out of the action space size
  2. Specified by the agents action. In case the agents action is a list with 2 values, the 1st one is assumed to
  be the mean of the action, and 2nd is assumed to be its standard deviation.
  When the sampled action is outside of the action bounds given by the user, it is sampled again and again, until it
  is within the bounds.
code: def get_action(self, action_values: List[ActionType]) -> ActionType:
      # set the current noise
      if self.phase == RunPhase.TEST:
          current_noise = self.evaluation_noise
      else:
          current_noise = self.noise_schedule.current_value
  
      # scale the noise to the action space range
      if self.noise_as_percentage_from_action_space:
          action_values_std = current_noise * (
              self.action_space.high - self.action_space.low
          )
      else:
          action_values_std = current_noise
  
      # extract the mean values
      if isinstance(action_values, list):
          # the action values are expected to be a list with the action mean and optionally the action stdev
          action_values_mean = action_values[0].squeeze()
      else:
          # the action values are expected to be a numpy array representing the action mean
          action_values_mean = action_values.squeeze()
  
      # step the noise schedule
      if self.phase is not RunPhase.TEST:
          self.noise_schedule.step()
          # the second element of the list is assumed to be the standard deviation
          if isinstance(action_values, list) and len(action_values) > 1:
              action_values_std = action_values[1].squeeze()
  
      # sample from truncated normal distribution
      normalized_low = (
          self.clip_low - action_values_mean
      ) / action_values_std
      normalized_high = (
          self.clip_high - action_values_mean
      ) / action_values_std
      distribution = truncnorm(
          normalized_low,
          normalized_high,
          loc=action_values_mean,
          scale=action_values_std,
      )
      action = distribution.rvs(1)
  
      return action
  
file_path: coach/rl_coach/exploration_policies/truncated_normal.py
imports: from typing import List
  import numpy
  from scipy.stats import truncnorm
  from rl_coach.core_types import RunPhase, ActionType
  from rl_coach.exploration_policies.exploration_policy import ExplorationParameters, ContinuousActionExplorationPolicy
  from rl_coach.schedules import Schedule, LinearSchedule
  from rl_coach.spaces import ActionSpace, BoxActionSpace
label_desc: Use image node_id 2 for calling the TruncatedNormal obj's underlying member method code with example usage: obj.get_action(action_values) and returns: action