text: import torch
  from model import Actor, Critic
  import numpy as np
  
  class Agent:
      def __init__(self, name, device, num_features, num_actions, pi_alpha, v_alpha, gamma):
          self.device = device
          self.num_actions = num_actions
          self.gamma = gamma
  
          self.PI = Actor(num_features, num_actions, name+"Actor")
          self.PI.to(self.device)
          self.V = Critic(num_features, name+"Critic")
          self.V.to(self.device)
          self.pi_optimizer = torch.optim.Adam(self.PI.parameters(), pi_alpha)
          self.v_optimizer = torch.optim.Adam(self.V.parameters(), v_alpha) 
  
      def save_model(self):
          self.PI.save_model()
  
      def load_model(self):
          self.PI.load_model()
  
      def reset(self):
          self.log_probs = []
          self.rewards = []
          self.values = []
  
      def append_reward(self, reward):
          self.rewards.append(reward)
  
      def take_action(self, state):
          state = torch.tensor(state, dtype=torch.float32).to(self.device)
          action, log_prob = self.PI(state)
          value = self.V(state)
          self.log_probs.append(log_prob)
          self.values.append(value)
          return action.cpu().detach().item()
  
      def optimize(self):
          trajectory_len = len(self.rewards) 
          gammas = [self.gamma**exp for exp in range(trajectory_len)]
          returns = np.array([np.sum(np.array(gammas[:trajectory_len-i]) * np.array(self.rewards[i:])) for i in range(trajectory_len)])
          returns = torch.tensor(returns, dtype=torch.float32).to(self.device).view(-1, 1)
          log_probs = torch.vstack(self.log_probs).to(self.device)
          values = torch.vstack(self.values).to(self.device)
          advantages = returns - values
  
          # optimize the actor
          self.pi_optimizer.zero_grad()
          pi_loss = -(advantages.detach() * log_probs).mean()
          pi_loss.backward()
          self.pi_optimizer.step()
  
          # optimize the critic
          self.v_optimizer.zero_grad()
          v_loss = advantages.pow(2).mul(0.5).mean() 
          v_loss.backward()
          self.v_optimizer.step()
  
  