processed_features tensor has only node features (positive/negative values) for the 12 nodes (required selected nodes) the remaining 8 node (ideal/static nodes) does not have any node features (0.0000 value for all node features). in my node_features_tensor the nodes [0, 3, 5, 7, 10, 13, 15, 16, 17, 18, 19] (12 nodes) are required to tune, but in the resultant processed_features the 12 node position is 'random' at each time step training loop and it is not at the position of [0, 3, 5, 7, 10, 13, 15, 16, 17, 18, 19]. I need to know is it normal in the computation process?
Because after the computation of 'processed_features' we are choosing the 'action_indices' in the forward method in the Actor Class, which is basically choosen with reference to the position of required selective features from the '[0, 3, 5, 7, 10, 13, 15, 16, 17, 18, 19]' to be tune from 'node_features_tensor'. but at the ''processed_features'' at the required nodes [0, 3, 5, 7, 10, 13, 15, 16, 17, 18, 19] were not getting node feature values it is shuffeled some nodes has '0.0000' to its entire features. according to my understanding the value '0.0000' of the entire node features corresponding to the node is belongs to the ideal/static nodes which is not required to tune any of its features. but these values are being selected for tuning in the 'action_indices'.

Here i need to know the '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)]' are choosen for computing 'action_means' and the ' unstable_nodes_idx = torch.tensor([0, 3, 5, 7, 10, 13, 15, 16], dtype=torch.long, device=processed_features.device) are choosen for computing 'stability_pred' was based on the position of required features in the 'node_features_tensor', but the position of selected node features is not same in the 'processed_features' tensor structure, is it good to proceed to continue with the position of choosen from the 'node_features_tensor'
'

class CustomGNN(torch.nn.Module):
    def __init__(self, in_channels, out_channels):
        super(CustomGNN, self).__init__()
        self.gat1 = GATConv(in_channels, 8, heads=8, dropout=0.6)
        self.gat2 = GATConv(64, out_channels, heads=1, concat=False, dropout=0.6)  # 8*8 is 64

    def forward(self, x, edge_index):
        x = F.dropout(x, p=0.6, training=self.training)
        x = F.elu(self.gat1(x, edge_index))
        x = F.dropout(x, p=0.6, training=self.training)
        x = self.gat2(x, edge_index)

        # Selective feature synchronization based on given mappings
        # Converting given (row, column) pairs to 0-based indexing
        sync_mappings = {
            (10, 18): (7, 18),
            (10, 19): (7, 19),
            (16, 18): (15, 18),
            (16, 19): (15, 19),
            (5, 18): (13, 18),
            (5, 19): (13, 19),
            # No need to map (3, 18) and (3, 19) as they remain unchanged
        }
        
        original_features = x.clone()
        for target, source in sync_mappings.items():
            original_features[target[0], target[1]] = original_features[source[0], source[1]]
        
        # Only retain the transformative updates for the specified 19 features
        dynamic_updates_mask = torch.zeros_like(x, dtype=bool)
        dynamic_indices = [
            (0, 18), (0, 19), (3, 18), (3, 19), (5, 18), (5, 19), (7, 18), (7, 19),
            (10, 18), (10, 19), (13, 18), (13, 19), (15, 18), (15, 19), (16, 18),
            (16, 19), (17, 20), (18, 21), (19, 22)]
        for row, col in dynamic_indices:
            dynamic_updates_mask[row, col] = True
        
        x = original_features * (~dynamic_updates_mask) + x * dynamic_updates_mask
        return x


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)
        
        # Assuming num_nodes is the number of nodes and each node’s stability is predicted separately.
        self.stability_head = torch.nn.Sequential(
            torch.nn.Linear(24, 64),  # gnn_model.out_channels depends on your GNN’s output feature size
            torch.nn.ReLU(),
            torch.nn.Linear(64, 1)  # Predicting a single stability value for each node
        )        

    def forward(self, state):
        node_features_tensor, _, edge_index = state
        
        # Processed features from the GNN
        processed_features = self.gnn(node_features_tensor, edge_index)
        
        # Initialize stability predictions - assume all nodes as stable
        stability_pred = torch.ones(processed_features.size(0), device=processed_features.device)
        
        # Define the indices of nodes that actually require stability prediction
        # Given [0, 3, 5, 7, 10, 13, 15, 16] represent tunable/unstable nodes
        unstable_nodes_idx = torch.tensor([0, 3, 5, 7, 10, 13, 15, 16], dtype=torch.long, device=processed_features.device)
        
        # Fetch features for nodes requiring stability computation
        features_for_stability = processed_features[unstable_nodes_idx]
        
        # Compute stability predictions for these nodes
        stability_pred_unstable = self.stability_head(features_for_stability).squeeze(-1)
        
        # Update the overall stability predictions
        stability_pred[unstable_nodes_idx] = stability_pred_unstable
        
        # For action_indices handling, adapt based on the dynamic nature of processed_features
        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)]
                          
        # Convert action_indices to tensor format for easier handling
        action_indices_tensor = torch.tensor(action_indices, dtype=torch.long).t()
        selected_features = processed_features[action_indices_tensor[0], action_indices_tensor[1]]
        
        # Normalize and scale the GNN outputs to the action bounds for usable actions
        normalized_gnn_output = torch.tanh(selected_features)
        action_means = self.bounds_low + (self.bounds_high - self.bounds_low) * ((normalized_gnn_output + 1) / 2)
        
        # Define action variances (assuming a simple fixed form here for demonstration)
        action_log_std = torch.zeros_like(action_means)
        action_std = torch.exp(action_log_std)

        return action_means, action_std, stability_pred