In this below code, within the method 'select_action' within the class PPOAgent, I am getting the proper result of 'scaled_action' with in the bounds from the forward method within the class Actor, but my actual problem is that, I am not getting the result of 'scaled_action' with proper variation ranges with in the bounds(bound_low and bound_high). Here the scaled_action consists of 13 action space datas among the 13 variables, first 5 variables having very low range of distribution with in th bounds (low, high) as (0.18e-6, 0.2e-6) for these first 5 variables i am getting the result of 'scaled_action' as satisfactory, but for the remaining 8 action data the available bounds range of distributions are large, but i am not getting the results as much variations with in the bounds. The 'action_probs' from the forward method from the Class Actor provides proper scaled action space data with properly distributed action space with in bounds for each timesteps in the training loop, but the resultat scaled action from the method 'select_action' was not given wide range of variations in each timesteps in each episodes with in the RL training loop. how can i get the 'scaled_action' result with very wide distribution of values wiht in the specified bounds. i tried with the different epsilon values but nothing can be improved in the results. please address my issue and provide me the suggestion to rectify my problem and provide me the necessary updated code to solve my existing problem.

class Actor(torch.nn.Module):
    def __init__(self, gnn_model):
        super(Actor, self).__init__()
        self.gnn = gnn_model
        # Bounds are converted to tensors for ease of calculation
        self.bounds_low = torch.tensor([0.18e-6, 0.18e-6, 0.18e-6, 0.18e-6, 0.18e-6, 
                                        0.5e-6, 0.5e-6, 0.5e-6, 0.5e-6, 0.5e-6, 
                                        15e-6, 0.1e-12, 0.8], dtype=torch.float32)
                                        
        self.bounds_high = torch.tensor([0.2e-6, 0.2e-6, 0.2e-6, 0.2e-6, 0.2e-6, 
                                         50e-6, 50e-6, 50e-6, 50e-6, 50e-6, 
                                         30e-6, 10e-12, 1.4], dtype=torch.float32)


    def forward(self, state):
        node_features_tensor, _, edge_index = state
        processed_features = self.gnn(node_features_tensor, edge_index)
        
        # Specific (row, column) indices for action values, converted to 0-based indexing
        action_indices = [
            (10, 19), (16, 19), (5, 19), (3, 19), (0, 19), (10, 18),
            (16, 18), (5, 18), (3, 18), (0, 18), (17, 20), (18, 21), (19, 22)
        ]
        
        # For gathering specific indices, we first convert action_indices to a tensor format that can be used with gather or indexing
        action_indices_tensor = torch.tensor(action_indices, dtype=torch.long).t()
        selected_features = processed_features[action_indices_tensor[0], action_indices_tensor[1]]        
      
        # Use sigmoid or tanh followed by scaling to the specific bounds
        # This assumes outputs are approximately normalized or that such a transformation is desirable.
        scaled_action = torch.sigmoid(selected_features)  # Sigmoid maps to (0, 1)
        
        # Alternatively, for a wider range before scaling, you could use:
        # scaled_action = (torch.tanh(selected_features) + 1) / 2  # Maps outputs to (0, 1)
        
        # Scale the min-max scaled features to fit within the specified bounds
        scaled_actions = self.bounds_low + (self.bounds_high - self.bounds_low) * scaled_action

        return scaled_actions

class PPOAgent:
    def __init__(self, gnn_model, state_dim, action_space, lr_actor, lr_critic, gamma, gae_lambda, epsilon, policy_clip, epochs, entropy_coef):
        self.gamma = gamma
        self.gae_lambda = gae_lambda
        self.epsilon = epsilon
        self.policy_clip = policy_clip
        self.epochs = epochs
        self.entropy_coef = entropy_coef  # Dynamic entropy coefficient
        
        self.actor = Actor(gnn_model)
        self.critic = Critic()
        self.optimizer_actor = optim.Adam(self.actor.parameters(), lr=lr_actor)
        self.optimizer_critic = optim.Adam(self.critic.parameters(), lr=lr_critic)
        self.action_space = action_space  # Assume continuous
        
    def select_action(self, state, performance_metrics):
        action_probs = self.actor(state)        
        factor = 0.01
        # Adjust epsilon based on the spread of bounds
        bounds_range = self.actor.bounds_high - self.actor.bounds_low
        # Ensure the variance is at least 1D
        epsilon = 1e-2 + bounds_range * factor
        variances = action_probs.var(dim=0, keepdim=True).expand(action_probs.shape[0]) + epsilon         

        cov_mat = torch.diag(variances)  # Form an [13, 13] covariance matrix with variances on the diagonal

        # Define the Multivariate Normal distribution
        dist = MultivariateNormal(action_probs, cov_mat)
        
        # Sample an action
        raw_action = dist.sample()         
        
        # Scale the raw sampled action to fit within the specified bounds
        min_values = self.actor.bounds_low
        max_values = self.actor.bounds_high
    
        # Adjusted scaling using tanh
        scaled_action = min_values + ((torch.tanh(raw_action) + 1) / 2) * (max_values - min_values)
        
        log_prob = dist.log_prob(raw_action) - torch.log(max_values - min_values).sum()  # Compute log probability with raw action
        
        return scaled_action.detach().numpy().squeeze(), log_prob.item(), performance_metrics