'scaled_actions' getting from the actor network is properly scaled with in the bounds, 
scaled_actions: tensor([1.9371e-07, 1.8211e-07, 1.8159e-07, 1.8159e-07, 1.8159e-07, 5.0000e-05,
        5.3811e-06, 4.4282e-06, 4.4282e-06, 4.4282e-06, 1.5000e-05, 3.1397e-13,
        8.5159e-01], grad_fn=<AddBackward0>)
but after the process 'action = dist.sample()' i am getting the following results, which is not giving the result of my desired range and the result is also betwen negative to positive values. i need only positive values.
action: tensor([ 0.0936, -0.3220, -0.1482,  0.1306,  0.1592,  0.2043, -0.3227,  0.0893,
        -0.2059, -0.0091,  0.3215,  0.1095,  0.7124])
because of the result getting goes out of bounds, i am getting the 'clamped_action'
clamped_action [2.0e-07 1.8e-07 1.8e-07 2.0e-07 2.0e-07 5.0e-05 5.0e-07 5.0e-05 5.0e-07
 5.0e-07 3.0e-05 1.0e-11 8.0e-01]

the result of the required action data is completely changes and outof bounds, i made 'Clamp action to be within bounds' after the 'action = dist.sample()' it makes even worse that all the action data will be either the values of bounds low or bounds high, not getting any intermediate values between the bounds. all these problem arise in the process of action = dist.sample().

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)
        #print("processed_features", processed_features)
        # 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]]
        
        scaled_action = (selected_features + 0.1) / 2
        # Scale the selected features directly. Assumes selected features and bounds are already positioned on the same device
        scaled_actions = self.bounds_low + (self.bounds_high - self.bounds_low) * scaled_action

        return scaled_actions

class PPOAgent:       
    def select_action(self, state, performance_metrics):
        action_probs = self.actor(state)
        print("action_probs:", action_probs)
        
        # Ensure the variance is at least 1D
        epsilon = 1e-5
        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
        action = dist.sample()
        log_prob = dist.log_prob(action)        
      
        # Clamp action to be within bounds
        clamped_action = torch.clamp(action, min=self.actor.bounds_low, max=self.actor.bounds_high)

        return clamped_action.detach().numpy().squeeze(), log_prob.item(), performance_metrics