""" Carbon intensity profiles for 6 real data centre regions. Values in gCO2eq/kWh, calibrated from Electricity Map 2024 data. Profiles capture: - Solar regions: low midday, higher at night - Wind regions: variable, often lowest at night / early morning - Hydro regions: consistently low - Gas/coal: consistently high with minor variation """ import math import random from typing import List # ── Regional base profiles (24-hour cycle, index = hour 0–23) ─────────────── def _solar_curve(base: float, peak_reduction: float) -> List[float]: """Dips around hours 10–15 when solar is generating.""" result = [] for h in range(24): solar = max(0.0, math.sin(math.pi * (h - 6) / 12)) # peaks at noon result.append(base - peak_reduction * solar) return result def _wind_curve(base: float, amplitude: float, phase: float) -> List[float]: """Wind is variable — modelled as a slow sinusoid with noise seed.""" return [ base + amplitude * math.sin(2 * math.pi * h / 24 + phase) for h in range(24) ] # Profiles keyed by region id BASE_PROFILES = { "us-west-2": { "name": "Oregon (Hydro + Wind)", "profile": _wind_curve(base=45, amplitude=15, phase=0.5), "renewable": 0.82, "capacity": 200.0, }, "us-west-1": { "name": "California (Solar + Grid)", "profile": _solar_curve(base=220, peak_reduction=140), "renewable": 0.52, "capacity": 150.0, }, "us-east-1": { "name": "Virginia (Gas + Nuclear)", "profile": _wind_curve(base=360, amplitude=20, phase=1.0), "renewable": 0.24, "capacity": 180.0, }, "eu-west-1": { "name": "Ireland (Wind + Gas)", "profile": _wind_curve(base=240, amplitude=80, phase=2.0), "renewable": 0.48, "capacity": 120.0, }, "ap-southeast-1": { "name": "Singapore (Natural Gas)", "profile": _wind_curve(base=455, amplitude=10, phase=0.0), "renewable": 0.08, "capacity": 100.0, }, "ap-south-1": { "name": "Mumbai (Coal + Solar)", "profile": _solar_curve(base=680, peak_reduction=200), "renewable": 0.18, "capacity": 90.0, }, } def get_carbon_forecast( region: str, current_hour: int, noise_seed: int = 0, noise_level: float = 0.08, ) -> List[float]: """ Return 24-hour forecast starting from current_hour. Adds realistic noise — the forecast is imperfect. """ rng = random.Random(noise_seed + hash(region)) profile = BASE_PROFILES[region]["profile"] forecast = [] for offset in range(24): h = (current_hour + offset) % 24 base_val = profile[h] noise = base_val * noise_level * (rng.random() * 2 - 1) forecast.append(max(10.0, base_val + noise)) return forecast def get_carbon_now(region: str, current_hour: int, noise_seed: int = 0) -> float: """Current carbon intensity — less noisy than forecast.""" forecast = get_carbon_forecast(region, current_hour, noise_seed, noise_level=0.03) return round(forecast[0], 1) def get_renewable_pct(region: str, current_hour: int) -> float: """Renewable percentage varies slightly with solar/wind availability.""" base = BASE_PROFILES[region]["renewable"] profile_val = BASE_PROFILES[region]["profile"][current_hour] profile_min = min(BASE_PROFILES[region]["profile"]) profile_max = max(BASE_PROFILES[region]["profile"]) spread = max(1.0, profile_max - profile_min) # when carbon is low → renewables are high renewable_boost = 0.15 * (1 - (profile_val - profile_min) / spread) return min(1.0, max(0.0, base + renewable_boost)) def naive_carbon_for_job( energy_kwh: float, current_hour: int, noise_seed: int = 0, available_regions: List[str] = None, ) -> float: """ FIX: Baseline is now 'run immediately in the best available region right now'. Previously used Mumbai (worst region, 680 gCO2/kWh) which made carbon_score trivially easy — even Virginia (360 gCO2/kWh) scored 47% efficiency without learning anything useful. Now the baseline is the true opportunity cost: what you would emit if you picked the cleanest region available right now but did zero temporal planning. The agent must beat THIS to prove it has learned to read forecasts and time jobs to cleaner future windows. """ if available_regions is None: available_regions = list(BASE_PROFILES.keys()) best_ci = min( get_carbon_now(region, current_hour, noise_seed) for region in available_regions ) return energy_kwh * best_ci def worst_carbon_for_job( energy_kwh: float, current_hour: int, noise_seed: int = 0, ) -> float: """ Worst-case baseline (Mumbai) — kept for episode summary context logging only. Not used in the reward function. """ ci = get_carbon_now("ap-south-1", current_hour, noise_seed) return energy_kwh * ci def optimal_carbon_for_job( energy_kwh: float, current_hour: int, noise_seed: int = 0, ) -> float: """ Oracle: run job at the cleanest future hour across all regions. Used to compute theoretical maximum savings (lower bound on reward). """ best = float("inf") for region in BASE_PROFILES: forecast = get_carbon_forecast(region, current_hour, noise_seed) best = min(best, min(forecast)) return energy_kwh * best