I am having total '7' performance metrics in the order ['Area', 'PowerDissipation', 'SlewRate', 'Gain', 'Bandwidth3dB', 'UnityGainFreq', 'PhaseMargin'], and each performance metrics has its bounds(min and max) as given below, with in the desired target range, i need the measured current performance metrics. i need you to modify the computation 'Iterate over each metric and define our condition for ‘better’ based on the metric' present in the below given function 'calculate reward', In the existing computation it is computed as the performance metrics 'Area', 'PowerDissipation' are getting as much as lower are considered as better and give reward and the performance metrics 'SlewRate', 'Gain', 'Bandwidth3dB', 'UnityGainFreq', 'PhaseMargin'are getting higher vales are considered as better and give reward for the result, but here i need to completely modify the code as based on the below new requirements.
if all the transistor regions {'M1': 3, 'M2': 3, 'M3': 2, 'M4': 2, 'M5': 2, 'M8': 2, 'M6': 2, 'M7': 2} are '2' then give maximum reward 100, 
if all the transistor regions are not '2' then, count the region '2' in 'transistor_regions'(present transistor region) and 'previous_transistor_regions'(previous transistor region), If the number of region '2' in the 'transistor_regions' are getting better than the 'previous_transistor_regions' then give reward. if it is lesser than previous, then give larger penalty and also based on its count of number of not in region '2', give large penalty, 
In the performance metrics,
if all the performance metrics 'c_metrics' are with in the target bounds, even if all the transistor regions are not in '2' give maximum reward 100,

In the performance metrics, the 'Area', 'PowerDissipation', are need to get as much minimum as possible, so if the 'p_metrics' (previous vetrics) is larger and the 'c_metrics' are smaller then give reward, when the 'c_metrics' are smaller than its corresponding target low bounds then give larger reward, if the 'p_metrics' (previous vetrics) is smaller and the 'c_metrics' are larger than the target high bounds, then give larger penalty,
In the performance metrics, the 'SlewRate', 'Gain', 'Bandwidth3dB', 'UnityGainFreq', are need to get as much maximum as possible, so if the 'p_metrics' (previous vetrics) is smaller and the 'c_metrics' are larger then give reward, when the 'c_metrics' are larger than its corresponding target high bounds then give larger reward, if the 'p_metrics' (previous vetrics) is higher and the 'c_metrics' are smaller than the target low bounds, then give larger penalty,
In the performance metrics, if the 'PhaseMargin' present in the 'c_metrics' is with in its respective bounds target low and high, then give reward, if the 'PhaseMargin' present in the 'c_metrics' are either lessthan its respective target low bounds or greater than the target high bounds give high penalty,

If the performance metrics 'Gain' of 'c_metrics', is lessthan '0' then give larger penalty, 

I implemented the normalization of performance metrics for the computation, if you need it in the updation please utilize or modify it as per your need, the normalization of performance metrics here i implemented are converting all the metrics with in the 0 and 1 so it cannot be used for comparison the previous and present metrics appropriately, if you can handle it please utilize as appropriately if needed else avoid it and provide me the updated code very proper implementation.


PERFORMANCE_METRICS_TARGET_LOW = np.array([1e-12, 0.005, 10, 48, 30e3, 30e6, 60])
PERFORMANCE_METRICS_TARGET_HIGH = np.array([1e-10, 0.25, 60, 100, 100e3, 100e6, 75])
Performance_Metrics {'Area': 5.319013762425384e-11, 'PowerDissipation': 0.0457471, 'SlewRate': 8.010033498660869, 'Gain': 3.71437, 'Bandwidth3dB': 1244390.0, 'UnityGainFreq': 1743780.0, 'PhaseMargin': 56.56999999999999}
transistor regions {'M1': 3, 'M2': 3, 'M3': 2, 'M4': 2, 'M5': 2, 'M8': 2, 'M6': 2, 'M7': 2}
LARGE_REWARD = 10
SMALL_REWARD = 5
PENALTY = 1
ADDITIONAL_PENALTY = 0.5
LARGE_PENALTY = 5
in the below code 'c_metrics' is the current measured performance metrics values, and 'p_metrics' is the previous measured performance metrics values.
 
    
    def calculate_reward(self, c_metrics, p_metrics, transistor_regions, previous_transistor_regions):
        
        # Define order of metrics
        metrics_order = ['Area', 'PowerDissipation', 'SlewRate', 'Gain', 'Bandwidth3dB', 'UnityGainFreq', 'PhaseMargin']
        
        c_metric = self.normalize_performance_metrics(c_metrics)
        p_metric = self.normalize_performance_metrics(p_metrics)
        current_metric = np.array([c_metric[k] for k in metrics_order])
        previous_metric = np.array([p_metric[k] for k in metrics_order])
        importance_factor = [10, 10, 5, 10, 5, 5, 10]  # Hypothetical importance weights for each metric

        current_metrics = np.array([c_metrics[k] for k in metrics_order])
        
        reward = 0
    
        # Check if all transistors are in saturation
        all_in_saturation = all(region == 2 for region in transistor_regions.values())
        print("all_in_saturation", all_in_saturation)
        print("transistor_regions", transistor_regions)
    
        if all_in_saturation:
            reward += 100  # Rewarding when all transistors are in saturation

        # Check if the performance metrics is within the target specification
        performance_metrics_in_target = self.is_performance_metrics_in_target(current_metrics)
        
        # Additional penalty condition check
        if current_metrics[3] < 0 and current_metrics[5] == 0 and current_metrics[6] == 0:
            # Applying a small penalty times 5 for the stated condition
            reward -= 75
        
        # Check if the performance metrics is better or worse than before
        # Initialize an array to track if performance is getting better for each metric
        performance_metrics_getting_better = np.zeros_like(current_metric, dtype=bool)        
               
        # Iterate over each metric and define our condition for ‘better’ based on the metric
        for idx, (current, previous) in enumerate(zip(current_metric, previous_metric)):            
            if idx < 2:  # Smaller is better for 'area' & 'power dissipation'
                improvement = previous - current
                if improvement > 0:
                    reward += improvement * importance_factor[idx] * self.SMALL_REWARD  # Dynamically scale reward
                    performance_metrics_getting_better[idx] = True
            else:  # Larger is better for the other metrics
                improvement = current - previous
                if improvement > 0:
                    reward += improvement * importance_factor[idx] * self.SMALL_REWARD
                    performance_metrics_getting_better[idx] = True
        
        # Check how many transistors were not in saturation previously and are in saturation now
        newly_in_saturation = sum(1 for current_region, previous_region in zip(transistor_regions.values(), previous_transistor_regions.values()) if current_region == 2 and previous_region != 2)
        newly_not_in_saturation = sum(1 for region, prev_region in zip(transistor_regions.values(), previous_transistor_regions.values()) if region != 2 and prev_region == 2)
        
        # Count of metrics getting better and worse
        num_better = np.count_nonzero(performance_metrics_getting_better)
        num_worse = len(current_metric) - num_better                       
           
        # Reward if all performance metrics are improving and all transistors are in saturation
        if num_better == len(current_metric) and all_in_saturation:
            reward += self.LARGE_REWARD
    
        # Else if performance metrics are all within target but not all transistors are in saturation, reward
        elif performance_metrics_in_target and not all_in_saturation:
            reward += self.SMALL_REWARD * 5
    
        # Else if performance metrics are not in target, consider further details
        elif not performance_metrics_in_target:        
            if all_in_saturation:
                # Reward if all transistors are in saturation, even if metrics are not in target
                reward += self.SMALL_REWARD
            
            reward += self.SMALL_REWARD * num_better - self.PENALTY * num_worse
    
            # Reward improvement in saturation, if not already rewarded for all metrics improving
            if newly_in_saturation > 0:
                reward += self.SMALL_REWARD * newly_in_saturation
    
            # Penalize if some transistors have fallen out of saturation since the last evaluation
            if newly_not_in_saturation > 0:
                reward -= self.ADDITIONAL_PENALTY * newly_not_in_saturation
    
        # Independent of the above, penalize if any transistor is out of saturation
        penalty_count = sum(1 for region in transistor_regions.values() if region != 2)
        reward -= self.LARGE_PENALTY * penalty_count        
        normalized_reward = self.normalize_reward(reward)
        normalized_rewards = int(normalized_reward)
        print(f"Original Reward: {reward}, Normalized Reward: {normalized_rewards}")
        return normalized_rewards

    def normalize_performance_metrics(self, performance_metrics):
        # Normalize performance metrics
        normalized_metrics = {}
        for metric, value in performance_metrics.items():
            lower_bound, upper_bound = self.target_metrics[metric]
            normalized_value = (value - lower_bound) / (upper_bound - lower_bound)
            normalized_value_clipped = max(0, min(normalized_value, 1))  # Ensuring the value is in [0, 1]
            normalized_metrics[metric] = normalized_value_clipped
        return normalized_metrics 
    
    def is_performance_metrics_in_target(self, metrics):
        return np.all(metrics >= self.PERFORMANCE_METRICS_TARGET_LOW) and np.all(metrics <= self.PERFORMANCE_METRICS_TARGET_HIGH)