I need to add another check condition to avail reward in the function 'calculate_reward' along with the existing logic,
            if c_value == target_value:
                reward += self.VERY_HIGH_REWARD
            elif abs(c_value - target_value) < abs(p_value - target_value):
                reward += self.LARGE_REWARD
            else:
                reward -= self.PENALTY

along with the above logic i need to add, if c_value is very closer to the target_value then we need to give reward 20 for each metrics which ever metrics in c_value reach very closer to target_value, 
all the four metrics 'gain', 'bw', 'ugf', 'pm' are having different scaling factor.
the target_value for 'gain' is between 45 to 55, for example if the target_value is 46, if the c_value is either 45 or 47(only plus or minus one) then we need to give reward 20,
the target_value for 'bw' is between 10000 to 30000, for example if the target_value is 20000, if the c_value is between 19000 to 21000 (only within plus or minus 1000) then we need to give reward 20,
the target_value for 'ugf' is between 5000000 to 10000000, for example if the target_value is 7000000, if the c_value is between 6000000 to 8000000 (only within plus or minus 1000000) then we need to give reward 20,
the target_value for 'pm' is between 60 to 75, for example if the target_value is 65, if the c_value is either 64 or 66(only plus or minus one) then we need to give reward 20.

and the updating condition(above given new condition) and the existing condition 'elif abs(c_value - target_value) < abs(p_value - target_value)' should not be check at same time, if the updating condition(above given new condition) is not true then go for checking condition 'elif abs(c_value - target_value) < abs(p_value - target_value)'

and also I need to add penalty of 200 and return reward, when the 'gain' metrics is negative (this condition should be check before the condition 'if c_value == target_value'.

# Define Circuit Environment
class CircuitEnvironment:
    PERFORMANCE_METRICS_TARGET_LOW = np.array([45, 10e3, 5e6, 60])
    PERFORMANCE_METRICS_TARGET_HIGH = np.array([55, 30e3, 10e6, 75])
    LARGE_REWARD = 10
    VERY_HIGH_REWARD = 50
    PENALTY = 20

    def __init__(self, bounds_low, bounds_high):
        # Initialization code: setup bounds
        self.bounds_low = bounds_low
        self.bounds_high = bounds_high
        # Initialize tensors as None
        self.node_features_tensor = None
        self.edge_index = None   
        self.target_metrics = None

    def calculate_reward(self, c_metrics, p_metrics):
        metrics_order = ['gain', 'bw', 'ugf', 'pm']
            
        reward = 0

        target_metrics = {k: self.target_metrics[i] for i, k in enumerate(metrics_order)}
        print("target_metrics", target_metrics)
        for metric_name in metrics_order:
            c_value = c_metrics[metric_name]
            p_value = p_metrics[metric_name]
            target_value = target_metrics[metric_name]
            
            if c_value == target_value:
                reward += self.VERY_HIGH_REWARD
            elif abs(c_value - target_value) < abs(p_value - target_value):
                reward += self.LARGE_REWARD
            else:
                reward -= self.PENALTY
                
        return reward