class_docstr: The Agent class, the highest-level API that wraps together processes for action, perception, and learning under active inference.
  
  The basic usage is as follows:
  
  >>> my_agent = Agent(A = A, B = C, <more_params>)
  >>> observation = env.step(initial_action)
  >>> qs = my_agent.infer_states(observation)
  >>> q_pi, G = my_agent.infer_policies()
  >>> next_action = my_agent.sample_action()
  >>> next_observation = env.step(next_action)
  
  This represents one timestep of an active inference process. Wrapping this step in a loop with an ``Env()`` class that returns
  observations and takes actions as inputs, would entail a dynamic agent-environment interaction.
class_docstr_tok: ["The","Agent","class",",","the","highest-level","API","that","wraps","together","processes","for","action",",","perception",",","and","learning","under","active","inference",".","The","basic","usage","is","as","follows",":",">",">",">","my_agent","=","Agent","(","A","=","A",",","B","=","C",",","<","more_params",">",")",">",">",">","observation","=","env.step","(","initial_action",")",">",">",">","qs","=","my_agent.infer_states","(","observation",")",">",">",">","q_pi",",","G","=","my_agent.infer_policies","(",")",">",">",">","next_action","=","my_agent.sample_action","(",")",">",">",">","next_observation","=","env.step","(","next_action",")","This","represents","one","timestep","of","an","active","inference","process",".","Wrapping","this","step","in","a","loop","with","an","``","Env","(",")","``","class","that","returns","observations","and","takes","actions","as","inputs",",","would","entail","a","dynamic","agent-environment","interaction","."]
code: def __init__(
      self,
      A,
      B,
      C=None,
      D=None,
      E=None,
      pA=None,
      pB=None,
      pD=None,
      num_controls=None,
      policy_len=1,
      inference_horizon=1,
      control_fac_idx=None,
      policies=None,
      gamma=16.0,
      alpha=16.0,
      use_utility=True,
      use_states_info_gain=True,
      use_param_info_gain=False,
      action_selection="deterministic",
      sampling_mode="marginal",  # whether to sample from full posterior over policies ("full") or from marginal posterior over actions ("marginal")
      inference_algo="VANILLA",
      inference_params=None,
      modalities_to_learn="all",
      lr_pA=1.0,
      factors_to_learn="all",
      lr_pB=1.0,
      lr_pD=1.0,
      use_BMA=True,
      policy_sep_prior=False,
      save_belief_hist=False,
  ):
      ### Constant parameters ###
  
      # policy parameters
      self.policy_len = policy_len
      self.gamma = gamma
      self.alpha = alpha
      self.action_selection = action_selection
      self.sampling_mode = sampling_mode
      self.use_utility = use_utility
      self.use_states_info_gain = use_states_info_gain
      self.use_param_info_gain = use_param_info_gain
  
      # learning parameters
      self.modalities_to_learn = modalities_to_learn
      self.lr_pA = lr_pA
      self.factors_to_learn = factors_to_learn
      self.lr_pB = lr_pB
      self.lr_pD = lr_pD
  
      # Initialise observation model (A matrices)
      if not isinstance(A, np.ndarray):
          raise TypeError("A matrix must be a numpy array")
  
      self.A = utils.to_obj_array(A)
  
      assert utils.is_normalized(
          self.A
      ), "A matrix is not normalized (i.e. A.sum(axis = 0) must all equal 1.0)"
  
      # Determine number of observation modalities and their respective dimensions
      self.num_obs = [self.A[m].shape[0] for m in range(len(self.A))]
      self.num_modalities = len(self.num_obs)
  
      # Assigning prior parameters on observation model (pA matrices)
      self.pA = pA
  
      # Initialise transition model (B matrices)
      if not isinstance(B, np.ndarray):
          raise TypeError("B matrix must be a numpy array")
  
      self.B = utils.to_obj_array(B)
  
      assert utils.is_normalized(
          self.B
      ), "B matrix is not normalized (i.e. B.sum(axis = 0) must all equal 1.0)"
  
      # Determine number of hidden state factors and their dimensionalities
      self.num_states = [self.B[f].shape[0] for f in range(len(self.B))]
      self.num_factors = len(self.num_states)
  
      # Assigning prior parameters on transition model (pB matrices)
      self.pB = pB
  
      # If no `num_controls` are given, then this is inferred from the shapes of the input B matrices
      if num_controls == None:
          self.num_controls = [
              self.B[f].shape[2] for f in range(self.num_factors)
          ]
      else:
          self.num_controls = num_controls
  
      # Users have the option to make only certain factors controllable.
      # default behaviour is to make all hidden state factors controllable
      # (i.e. self.num_states == self.num_controls)
      if control_fac_idx == None:
          self.control_fac_idx = [
              f
              for f in range(self.num_factors)
              if self.num_controls[f] > 1
          ]
      else:
          assert max(control_fac_idx) <= (
              self.num_factors - 1
          ), "Check control_fac_idx - must be consistent with `num_states` and `num_factors`..."
          self.control_fac_idx = control_fac_idx
  
          for factor_idx in self.control_fac_idx:
              assert (
                  self.num_controls[factor_idx] > 1
              ), "Control factor (and B matrix) dimensions are not consistent with user-given control_fac_idx"
  
      # Again, the use can specify a set of possible policies, or
      # all possible combinations of actions and timesteps will be considered
      if policies == None:
          policies = self._construct_policies()
      self.policies = policies
  
      assert all(
          [
              len(self.num_controls) == policy.shape[1]
              for policy in self.policies
          ]
      ), "Number of control states is not consistent with policy dimensionalities"
  
      all_policies = np.vstack(self.policies)
  
      assert all(
          [
              n_c >= max_action
              for (n_c, max_action) in zip(
                  self.num_controls,
                  list(np.max(all_policies, axis=0) + 1),
              )
          ]
      ), "Maximum number of actions is not consistent with `num_controls`"
  
      # Construct prior preferences (uniform if not specified)
  
      if C is not None:
          if not isinstance(C, np.ndarray):
              raise TypeError("C vector must be a numpy array")
          self.C = utils.to_obj_array(C)
  
          assert (
              len(self.C) == self.num_modalities
          ), f"Check C vector: number of sub-arrays must be equal to number of observation modalities: {self.num_modalities}"
  
          for modality, c_m in enumerate(self.C):
              assert (
                  c_m.shape[0] == self.num_obs[modality]
              ), f"Check C vector: number of rows of C vector for modality {modality} should be equal to {self.num_obs[modality]}"
      else:
          self.C = self._construct_C_prior()
  
      # Construct prior over hidden states (uniform if not specified)
  
      if D is not None:
          if not isinstance(D, np.ndarray):
              raise TypeError("D vector must be a numpy array")
          self.D = utils.to_obj_array(D)
  
          assert (
              len(self.D) == self.num_factors
          ), f"Check D vector: number of sub-arrays must be equal to number of hidden state factors: {self.num_factors}"
  
          for f, d_f in enumerate(self.D):
              assert (
                  d_f.shape[0] == self.num_states[f]
              ), f"Check D vector: number of entries of D vector for factor {f} should be equal to {self.num_states[f]}"
      else:
          if pD is not None:
              self.D = utils.norm_dist_obj_arr(pD)
          else:
              self.D = self._construct_D_prior()
  
      assert utils.is_normalized(
          self.D
      ), "A matrix is not normalized (i.e. A.sum(axis = 0) must all equal 1.0"
  
      # Assigning prior parameters on initial hidden states (pD vectors)
      self.pD = pD
  
      # Construct prior over policies (uniform if not specified)
      if E is not None:
          if not isinstance(E, np.ndarray):
              raise TypeError("E vector must be a numpy array")
          self.E = E
  
          assert len(self.E) == len(
              self.policies
          ), f"Check E vector: length of E must be equal to number of policies: {len(self.policies)}"
  
      else:
          self.E = self._construct_E_prior()
  
      self.edge_handling_params = {}
      self.edge_handling_params[
          "use_BMA"
      ] = use_BMA  # creates a 'D-like' moving prior
      self.edge_handling_params[
          "policy_sep_prior"
      ] = policy_sep_prior  # carries forward last timesteps posterior, in a policy-conditioned way
  
      # use_BMA and policy_sep_prior can both be False, but both cannot be simultaneously be True. If one of them is True, the other must be False
      if policy_sep_prior:
          if use_BMA:
              warnings.warn(
                  "Inconsistent choice of `policy_sep_prior` and `use_BMA`.\
                      You have set `policy_sep_prior` to True, so we are setting `use_BMA` to False"
              )
              self.edge_handling_params["use_BMA"] = False
  
      if inference_algo == None:
          self.inference_algo = "VANILLA"
          self.inference_params = self._get_default_params()
          if inference_horizon > 1:
              warnings.warn(
                  "If `inference_algo` is VANILLA, then inference_horizon must be 1\n. \
                      Setting inference_horizon to default value of 1...\n"
              )
              self.inference_horizon = 1
          else:
              self.inference_horizon = 1
      else:
          self.inference_algo = inference_algo
          self.inference_params = self._get_default_params()
          self.inference_horizon = inference_horizon
  
      if save_belief_hist:
          self.qs_hist = []
          self.q_pi_hist = []
  
      self.prev_obs = []
      self.reset()
  
      self.action = None
      self.prev_actions = None
  
imports: import warnings
  import numpy
  from pymdp import inference, control, learning
  from pymdp import utils, maths
  import copy
label_desc: Use image node_id 1 to create a new Agent object from inherited base classes: object with example: obj = Agent(A, B, C, D, E, pA, pB, pD, num_controls, policy_len, inference_horizon, control_fac_idx, policies, gamma, alpha, use_utility, use_states_info_gain, use_param_info_gain, action_selection, sampling_mode, inference_algo, inference_params, modalities_to_learn, lr_pA, factors_to_learn, lr_pB, lr_pD, use_BMA, policy_sep_prior, save_belief_hist)