EcoGrid / env /tasks.py
dootisaha25's picture
Finalize OpenEnv submission (multi-mode server, strict scoring, uv lock)
578508e
Raw
History Blame Contribute Delete
6.18 kB
"""
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
"""
@staticmethod
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.
"""
@staticmethod
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)
"""
@staticmethod
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,
)