In the function 'train' (training process of reinforcement learning agent), i initialize the 'num_episodes = 1000', for the training process with 'max_timesteps = 300' for each episodes, but i am training the predefined target counts as 'num_targets=100'. Now how the function 'train' will work, will it run the episode upto the number of target count 'num_targets=100' or upto the total number of episodes 'num_episodes = 1000', or the initialization is need to be synchronize with each other. i need to know whether the implementation were correctly initiated its all requirements in the function 'train'. please check and address the needed corrections/updation in the code. and i also need to know, whether the function 'test' were properly implemented or not?, does it requires and stopping criteria other than done condition(what if the traget done condition is not achieved, will it goes to infinite loop).


# Training Function
def train(env, agent, num_episodes, max_timesteps):
    train_targets = CircuitEnvironment.generate_random_targets(num_targets=100, seed=0)
    test_targets = CircuitEnvironment.generate_random_targets(num_targets=20, seed=1)

    start_episode = 0    
    rewards_log = []
    losses_log = []
    metrics_log = []
    episode_lengths = []
    
    saved_state = load_training_state()
    if saved_state:
        start_episode = saved_state['episode']
        agent.actor.load_state_dict(saved_state['actor_state_dict'])
        agent.critic.load_state_dict(saved_state['critic_state_dict'])
        agent.optimizer_actor.load_state_dict(saved_state['optimizer_actor_state_dict'])
        agent.optimizer_critic.load_state_dict(saved_state['optimizer_critic_state_dict'])
        rewards_log = saved_state['rewards_log']
        losses_log = saved_state['losses_log']
        metrics_log = saved_state['metrics_log']
        train_targets = saved_state['train_targets']
        test_targets = saved_state['test_targets']
        print(f"Resuming from episode {start_episode}")
        
    for episode in range(start_episode, num_episodes): 
        target = train_targets[episode % len(train_targets)]
        
        node_features_tensor, edge_index, performance_metrics = env.reset(target)
        state = (node_features_tensor, edge_index)
    
        episode_rewards = []
        states = []
        actions = []
        log_probs = []
        values = []
        masks = []
        perf_metrics_log = []
        
        for t in range(max_timesteps):            
            action, log_prob, perf_metrics = agent.select_action(state, performance_metrics)
            next_node_features_tensor, next_edge_index, reward, done, previous_metrics = env.step(action, perf_metrics, target)
            
            next_state = (next_node_features_tensor, next_edge_index)
    
            episode_rewards.append(reward)
            states.append(state)
            actions.append(action)
            log_probs.append(log_prob.item())            
            values.append(agent.critic(node_features_tensor, edge_index).item())
            masks.append(1.0 - float(done))
            perf_metrics_log.append(perf_metrics)
                
            performance_metrics = previous_metrics
            state = next_state            
  
            if done:
                break
    
        next_value = agent.critic(state[0], state[1]).item() if not done else 0         
        returns = agent.compute_gae(next_value, episode_rewards, masks, values)
    
        advantages = (torch.tensor(returns) - torch.tensor(values)).detach().numpy()
        returns = torch.tensor(returns).detach().numpy()
        average_loss_actor, average_loss_critic = agent.update_policy(states, actions, log_probs, returns, advantages)                        
            
        episode_length = len(episode_rewards)
        total_reward = sum(episode_rewards)
        mean_episode_reward = total_reward / episode_length
        deployment_accuracy = calculate_accuracy(perf_metrics_log, target)
        
        min_episode_reward = min(episode_rewards)
        max_episode_reward = max(episode_rewards)
        
                # Log episode information            
        print(f'Train: Episode {episode + 1}/{num_episodes}, Total Reward: {total_reward}, Mean Episode Reward: {mean_episode_reward}, Min Episode Reward: {min_episode_reward}, Max Episode Reward: {max_episode_reward}, Deployment Accuracy: {deployment_accuracy}')
             
        # Logging
        logging.info(f" Train: Episode {episode + 1} - Reward: {total_reward}, Length: {episode_length}, Actor Loss: {average_loss_actor}, Critic Loss: {average_loss_critic}")
        logging.info(f"Train:  Episode {episode + 1} - Mean Episode Reward: {mean_episode_reward}, Min Episode Reward: {min_episode_reward}, Max Episode Reward: {max_episode_reward}, Deployment Accuracy: {deployment_accuracy}")   
        
        rewards_log.append({'episode': episode + 1, 'total_reward': total_reward})
        losses_log.append({'episode': episode + 1, 'actor_loss': average_loss_actor, 'critic_loss': average_loss_critic})
        metrics_log.append({'episode': episode + 1, 'Mean Episode Reward': mean_episode_reward, 'Min Episode Reward': min_episode_reward, 'Max Episode Reward': max_episode_reward, 'Deployment Accuracy': deployment_accuracy})
    
        episode_lengths.append(episode_length)
    
        agent.save_checkpoint(episode)
        if episode % 5 == 0 or episode == num_episodes - 1:
            pd.DataFrame(rewards_log).to_csv(os.path.join(agent.log_dir, 'rewards_log.csv'), index=False)
            pd.DataFrame(losses_log).to_csv(os.path.join(agent.log_dir, 'losses_log.csv'), index=False)
            pd.DataFrame(metrics_log).to_csv(os.path.join(agent.log_dir, 'metrics_log.csv'), index=False)
            save_training_state(agent, episode, rewards_log, losses_log, metrics_log, train_targets, test_targets)
                
    pd.DataFrame(rewards_log).to_csv(os.path.join(agent.log_dir, 'final_rewards_log.csv'), index=False)
    pd.DataFrame(losses_log).to_csv(os.path.join(agent.log_dir, 'final_losses_log.csv'), index=False)
    pd.DataFrame(metrics_log).to_csv(os.path.join(agent.log_dir, 'final_metrics_log.csv'), index=False)
    
    agent.save_checkpoint('final')        
   
    logging.info("Training Completed.")

# Test Function
def test(env, agent, num_targets, test_targets=None):
    rewards_log = []
    metrics_log = []
    
    # Load test targets from saved state if not provided
    if not test_targets:
        saved_state = load_training_state()
        if saved_state and 'test_targets' in saved_state:
            test_targets = saved_state['test_targets']
        else:
            test_targets = CircuitEnvironment.generate_random_targets(num_targets=num_targets, seed=1)

    for i, target in enumerate(test_targets):
        node_features_tensor, edge_index, performance_metrics = env.reset(target)
        state = (node_features_tensor, edge_index)
    
        episode_rewards = []
        states = []
        actions = []
        values = []
        masks = []
        perf_metrics_log = []
        
        done = False
        while not done:
            action, log_prob, perf_metrics = agent.select_action(state, performance_metrics)
            next_node_features_tensor, next_edge_index, reward, done, previous_metrics = env.step(action, perf_metrics, target)
            state = (next_node_features_tensor, next_edge_index)
    
            episode_rewards.append(reward)
            perf_metrics_log.append(performance_metrics)
                
            performance_metrics = previous_metrics
            
        total_reward = sum(episode_rewards)
        
        min_episode_reward = min(episode_rewards)
        max_episode_reward = max(episode_rewards)
        
        mean_episode_reward = total_reward / len(episode_rewards)
        deployment_accuracy = calculate_accuracy(perf_metrics_log)
        
        print(f'Test: Total Reward: {total_reward}, Mean Episode Reward: {mean_episode_reward}, Min Episode Reward: {min_episode_reward}, Max Episode Reward: {max_episode_reward}, Deployment Accuracy: {deployment_accuracy}')
             
        # Logging
        logging.info(f"Train: Mean Episode Reward: {mean_episode_reward}, Min Episode Reward: {min_episode_reward}, Max Episode Reward: {max_episode_reward}, Deployment Accuracy: {deployment_accuracy}")   
        
        rewards_log.append({'target_index': i, 'total_reward': total_reward})
        metrics_log.append({'target_index': i, 'Mean Episode Reward': mean_episode_reward, 'Deployment Accuracy': deployment_accuracy})
    
    log_dir = 'OPAMP_GNNRL_LTS_TrainTest_test3_log_file'
    os.makedirs(log_dir, exist_ok=True)
    pd.DataFrame(rewards_log).to_csv(os.path.join(log_dir, 'test_rewards_log.csv'), index=False)
    pd.DataFrame(metrics_log).to_csv(os.path.join(log_dir, 'test_metrics_log.csv'), index=False)
    
    logging.info("Testing Completed.")

if __name__ == "__main__":
    # Initialize environment parameters
    # Define the values for bounds, and target metrics
    bounds_low = np.array([0.5e-6, 0.5e-6, 0.5e-6, 0.5e-6, 0.5e-6, 15e-6, 0.1e-12, 0.8])
    bounds_high = np.array([50e-6, 50e-6, 50e-6, 50e-6, 50e-6, 30e-6, 10e-12, 1.4])
    # Initialize Hyperparameters
    state_dim = 23
    action_space = 8
    lr_actor = 1e-4
    lr_critic = 3e-4
    gamma = 0.99
    gae_lambda = 0.95
    epsilon = 0.2
    policy_clip = 0.2
    epochs = 10
    entropy_coef = 0.01

    env = CircuitEnvironment(bounds_low, bounds_high)
    agent = PPOAgent(state_dim, action_space, lr_actor, lr_critic, gamma, gae_lambda, epsilon, policy_clip, epochs, entropy_coef)

    num_episodes = 1000
    max_timesteps = 300

    train(env, agent, num_episodes, max_timesteps)

    trained_state = load_training_state()
    if trained_state:
        agent.actor.load_state_dict(trained_state['actor_state_dict'])
        agent.critic.load_state_dict(trained_state['critic_state_dict'])

    num_test_targets = 20
    test(env, agent, num_test_targets)