Spaces:
Sleeping
Sleeping
| """ | |
| EcoGrid-OpenEnv — Task Graders | |
| Deterministic graders that evaluate full episode logs to produce a final TaskScore. | |
| Each grader corresponds to a specific difficulty level. | |
| """ | |
| from __future__ import annotations | |
| import numpy as np | |
| from models.schemas import StepResult, TaskScore | |
| class BasicGridBalanceGrader: | |
| """Grader for the 'easy' task (basic_grid_balance). | |
| Checks: cost efficiency, no major blackouts, renewable usage. | |
| Score = total_reward / max_possible_reward | |
| """ | |
| def grade(episode_log: list[StepResult]) -> TaskScore: | |
| if not episode_log: | |
| return TaskScore(task_name="easy", score=0.001, breakdown={}) | |
| total_steps = len(episode_log) | |
| total_reward = sum(step.reward for step in episode_log) | |
| # Calculate specific metrics | |
| avg_renewable = np.mean([ | |
| step.info.get("renewable_supply_mwh", 0) / max(step.observation.demand, 1.0) | |
| for step in episode_log | |
| ]) | |
| max_blackout = max(step.info.get("blackout_risk", 0.0) for step in episode_log) | |
| avg_cost_score = np.mean([step.info["reward_breakdown"]["cost_score"] for step in episode_log]) | |
| # Base score is just the average reward (since reward is in [0,1]) | |
| base_score = total_reward / total_steps | |
| # Apply hard constraints for the task definition | |
| # If there was a major blackout, cap the score | |
| if max_blackout > 0.2: | |
| base_score = min(base_score, 0.4) | |
| # If cost reduction targets weren't met (avg cost score < 0.7 means high costs) | |
| if avg_cost_score < 0.7: | |
| base_score = min(base_score, 0.6) | |
| final_score = float(np.clip(base_score, 0.001, 0.999)) | |
| breakdown = { | |
| "avg_reward": float(total_reward / total_steps), | |
| "avg_renewable_ratio": float(avg_renewable), | |
| "max_blackout_risk": float(max_blackout), | |
| "avg_cost_score": float(avg_cost_score), | |
| } | |
| return TaskScore( | |
| task_name="easy", | |
| score=final_score, | |
| breakdown=breakdown, | |
| ) | |
| class RenewableVariabilityGrader: | |
| """Grader for the 'medium' task (renewable_variability). | |
| Score = 0.4*(renewable_score) + 0.4*(stability_score) + 0.2*(cost_score) | |
| Checks: blackout frequency, renewable utilisation. | |
| """ | |
| def grade(episode_log: list[StepResult]) -> TaskScore: | |
| if not episode_log: | |
| return TaskScore(task_name="medium", score=0.001, breakdown={}) | |
| # Extract averages from the reward breakdowns | |
| avg_renewable = np.mean([step.info["reward_breakdown"]["renewable_bonus"] for step in episode_log]) | |
| avg_stability = np.mean([step.info["reward_breakdown"]["stability_score"] for step in episode_log]) | |
| avg_cost = np.mean([step.info["reward_breakdown"]["cost_score"] for step in episode_log]) | |
| # Count major blackout events | |
| blackout_events = sum(1 for step in episode_log if step.info.get("blackout_risk", 0.0) > 0.1) | |
| # Composite score | |
| base_score = 0.4 * avg_renewable + 0.4 * avg_stability + 0.2 * avg_cost | |
| # Penalise frequent blackouts | |
| if blackout_events >= 3: | |
| base_score *= 0.5 # Heavy penalty for failing core objective | |
| final_score = float(np.clip(base_score, 0.001, 0.999)) | |
| breakdown = { | |
| "renewable_component": float(avg_renewable), | |
| "stability_component": float(avg_stability), | |
| "cost_component": float(avg_cost), | |
| "blackout_events": float(blackout_events), | |
| } | |
| return TaskScore( | |
| task_name="medium", | |
| score=final_score, | |
| breakdown=breakdown, | |
| ) | |
| class CarbonConstrainedGrader: | |
| """Grader for the 'hard' task (carbon_constrained). | |
| Score = 0 if carbon_budget_remaining < 0 at any step. | |
| Otherwise: 0.5*(carbon_score) + 0.3*(cost_score) + 0.2*(stability_score) | |
| """ | |
| def grade(episode_log: list[StepResult]) -> TaskScore: | |
| if not episode_log: | |
| return TaskScore(task_name="hard", score=0.001, breakdown={}) | |
| # Check fatal condition first | |
| min_carbon_budget = min(step.observation.carbon_budget_remaining for step in episode_log) | |
| # Check termination reason | |
| carbon_failure = any( | |
| step.info.get("termination_reason") == "carbon_budget_exceeded" | |
| for step in episode_log | |
| ) | |
| if min_carbon_budget < 0 or carbon_failure: | |
| return TaskScore( | |
| task_name="hard", | |
| score=0.001, | |
| breakdown={ | |
| "fatal_error": 1.0, | |
| "min_carbon_budget": float(min_carbon_budget) | |
| } | |
| ) | |
| # If survived, calculate composite score | |
| avg_carbon = np.mean([step.info["reward_breakdown"]["carbon_score"] for step in episode_log]) | |
| avg_cost = np.mean([step.info["reward_breakdown"]["cost_score"] for step in episode_log]) | |
| avg_stability = np.mean([step.info["reward_breakdown"]["stability_score"] for step in episode_log]) | |
| base_score = 0.5 * avg_carbon + 0.3 * avg_cost + 0.2 * avg_stability | |
| # Ensure they actually maintained stability (didn't just turn off power to save carbon) | |
| min_stability = min(step.observation.grid_stability for step in episode_log) | |
| if min_stability < 0.7: | |
| base_score = min(base_score, 0.4) | |
| final_score = float(np.clip(base_score, 0.001, 0.999)) | |
| breakdown = { | |
| "carbon_component": float(avg_carbon), | |
| "cost_component": float(avg_cost), | |
| "stability_component": float(avg_stability), | |
| "min_stability": float(min_stability), | |
| } | |
| return TaskScore( | |
| task_name="hard", | |
| score=final_score, | |
| breakdown=breakdown, | |
| ) | |