Spaces:
Sleeping
Sleeping
File size: 10,638 Bytes
3e4c342 74965f9 3e4c342 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 | """
NexusGrid-CyberPhysEnv Data Models.
Typed Pydantic models for the national power grid defense environment.
All fields have explicit range contracts via Field(ge=, le=, description=).
"""
from __future__ import annotations
from enum import Enum
from typing import Any, Dict, List, Optional
from pydantic import BaseModel, Field
# ---------------------------------------------------------------------------
# Enums
# ---------------------------------------------------------------------------
class NodeType(str, Enum):
HYDRO = "hydro"
SOLAR = "solar"
GAS = "gas"
BATTERY = "battery"
LOAD = "load"
class EdgeStatus(str, Enum):
LIVE = "LIVE"
TRIPPED = "TRIPPED"
class ActionType(str, Enum):
DISPATCH_GENERATION = "dispatch_generation"
TOGGLE_CIRCUIT_BREAKER = "toggle_circuit_breaker"
RUN_STATE_ESTIMATION = "run_state_estimation"
QUARANTINE_SCADA_NODE = "quarantine_scada_node"
INJECT_COUNTER_SIGNAL = "inject_counter_signal"
ADVANCE_TICK = "advance_tick"
# ---------------------------------------------------------------------------
# Topology sub-models
# ---------------------------------------------------------------------------
class GridNode(BaseModel):
"""A substation node in the power grid."""
id: str = Field(..., description="Unique node identifier, e.g. NODE_01")
region: str = Field(..., description="Geographic region name")
node_type: NodeType = Field(..., description="Type of substation")
capacity_mw: float = Field(..., ge=0, le=5000, description="Maximum generation/load capacity in MW")
peak_load_mw: float = Field(0.0, ge=0, le=5000, description="Peak demand for load nodes in MW")
critical: bool = Field(False, description="True if this is critical infrastructure (hospital, water)")
phase_angle_deg: float = Field(0.0, ge=-180.0, le=180.0, description="AC phase angle in degrees")
class GridEdge(BaseModel):
"""A transmission line edge in the power grid."""
id: str = Field(..., description="Unique edge identifier, e.g. LINE_01")
source: str = Field(..., description="Source node ID")
target: str = Field(..., description="Target node ID")
capacity_mw: float = Field(..., ge=50, le=5000, description="Line thermal capacity in MW")
current_load_mw: float = Field(0.0, ge=0, le=5000, description="Current power flow on line in MW")
status: EdgeStatus = Field(EdgeStatus.LIVE, description="Line status: LIVE or TRIPPED")
# ---------------------------------------------------------------------------
# Telemetry sub-models
# ---------------------------------------------------------------------------
class NodeTelemetry(BaseModel):
"""Per-node telemetry reading (may be spoofed)."""
node_id: str = Field(..., description="Node this reading belongs to")
voltage_kv: float = Field(345.0, ge=0.0, le=765.0, description="Voltage in kV (nominal 345)")
frequency_hz: float = Field(60.0, ge=58.0, le=62.0, description="Local frequency in Hz (nominal 60)")
generation_mw: float = Field(0.0, ge=0.0, le=5000.0, description="Generation output in MW")
consumption_mw: float = Field(0.0, ge=0.0, le=5000.0, description="Consumption demand in MW")
class WeatherZone(BaseModel):
"""Weather data for a geographic zone."""
zone: str = Field(..., description="Zone identifier")
solar_irradiance: float = Field(0.5, ge=0.0, le=1.0, description="Solar irradiance [0-1]")
wind_speed_ms: float = Field(5.0, ge=0.0, le=30.0, description="Wind speed in m/s")
cloud_cover: float = Field(0.3, ge=0.0, le=1.0, description="Cloud cover fraction [0-1]")
class PacketLog(BaseModel):
"""A simulated SCADA network packet log entry."""
timestamp: float = Field(..., ge=0.0, description="Simulated timestamp")
source_node: str = Field(..., description="Source node ID")
dest_node: str = Field(..., description="Destination node ID")
latency_ms: float = Field(5.0, ge=0.0, le=500.0, description="Packet latency in ms")
anomaly_flag: bool = Field(False, description="True if latency > 50ms for 2+ consecutive packets")
# ---------------------------------------------------------------------------
# State estimation result
# ---------------------------------------------------------------------------
class StateEstimationResult(BaseModel):
"""Result from run_state_estimation action."""
consistent: bool = Field(..., description="True if Kirchhoff's laws hold for the subgraph")
violation_node: Optional[str] = Field(None, description="Node ID where violation was detected")
estimated_true_mw: float = Field(0.0, description="Estimated true power at violation node")
# ---------------------------------------------------------------------------
# Observation — what the agent sees
# ---------------------------------------------------------------------------
class GridObservation(BaseModel):
"""
Complete observation from the NexusGrid environment.
The agent sees topology (never spoofed), telemetry (may be spoofed),
weather, SCADA packet logs, and the true grid frequency.
"""
model_config = {"extra": "allow"}
topology_graph: Dict[str, Any] = Field(
default_factory=dict,
description="Dict with 'nodes' and 'edges' keys — the immutable physical map, never spoofed"
)
telemetry_stream: List[List[Dict[str, Any]]] = Field(
default_factory=list,
description="Per-tick telemetry history (last 10 ticks). Each tick is a list of node readings. May be spoofed."
)
weather_forecast_matrix: List[Dict[str, Any]] = Field(
default_factory=list,
description="24-hour per-zone weather forecast"
)
network_packet_logs: List[Dict[str, Any]] = Field(
default_factory=list,
description="Recent SCADA packet logs with anomaly flags"
)
grid_frequency_hz: float = Field(
60.0, ge=58.0, le=62.0,
description="Instantaneous national grid frequency from truth engine. Cannot be spoofed."
)
tick: int = Field(0, ge=0, description="Current simulation tick")
task_id: int = Field(0, ge=0, le=5, description="Current task ID")
done: bool = Field(False, description="Whether episode has ended")
reward: float = Field(0.0, description="Reward for this step")
last_action_error: Optional[str] = Field(None, description="Error message from last action, if any")
last_state_estimation: Optional[Dict[str, Any]] = Field(
None,
description="Result of most recent run_state_estimation call"
)
weather_summary: str = Field("", description="Natural language weather summary")
metadata: Dict[str, Any] = Field(
default_factory=dict,
description="Auxiliary environment metadata such as reward/rubric breakdowns."
)
# ---------------------------------------------------------------------------
# Action — what the agent does
# ---------------------------------------------------------------------------
class GridAction(BaseModel):
"""
Action for the NexusGrid environment.
The agent selects an action_type and provides relevant parameters.
Unused parameters for a given action_type should be left as None.
"""
action_type: ActionType = Field(
...,
description="Type of action to perform"
)
node_id: Optional[str] = Field(
None,
description="Target node ID (for dispatch_generation, quarantine_scada_node, inject_counter_signal)"
)
edge_id: Optional[str] = Field(
None,
description="Target edge ID (for toggle_circuit_breaker)"
)
mw: Optional[float] = Field(
None, ge=-5000, le=5000,
description="Megawatts to dispatch (for dispatch_generation)"
)
status: Optional[str] = Field(
None,
description="OPEN or CLOSED (for toggle_circuit_breaker)"
)
subgraph: Optional[List[str]] = Field(
None,
description="List of node IDs to check (for run_state_estimation)"
)
hz_offset: Optional[float] = Field(
None, ge=-5.0, le=5.0,
description="Frequency offset for counter-signal injection (for inject_counter_signal)"
)
duration: Optional[int] = Field(
None, ge=1, le=20,
description="Duration in ticks for counter-signal (for inject_counter_signal)"
)
# ---------------------------------------------------------------------------
# Reward — breakdown of reward signals
# ---------------------------------------------------------------------------
class GridReward(BaseModel):
"""Breakdown of reward signals for transparency."""
fault_isolation: float = Field(0.0, description="Reward for isolating transmission faults")
cyber_detection: float = Field(0.0, description="Reward for detecting spoofed sensors")
frequency_stable: float = Field(0.0, description="Reward for keeping frequency in nominal band")
proactive_dispatch: float = Field(0.0, description="Reward for proactive (early) dispatch")
reasoning_order: float = Field(0.0, description="Reward for correct investigation order")
stability_bonus: float = Field(0.0, description="Bonus for tight frequency control (±0.1Hz)")
penalties: float = Field(0.0, description="Sum of all penalties (negative value)")
total: float = Field(0.0, description="Total reward for this tick")
# ---------------------------------------------------------------------------
# State — full internal state for debugging
# ---------------------------------------------------------------------------
class GridState(BaseModel):
"""Full internal environment state for debugging and reproducibility."""
episode_id: str = Field("", description="Unique episode identifier")
episode_seed: int = Field(42, description="Seed used for this episode")
task_id: int = Field(0, ge=0, le=5, description="Current task")
tick: int = Field(0, ge=0, description="Current tick")
grid_frequency_hz: float = Field(60.0, ge=58.0, le=62.0, description="Current grid frequency")
done: bool = Field(False, description="Whether episode is finished")
total_reward: float = Field(0.0, description="Accumulated reward")
active_spoofs: List[str] = Field(default_factory=list, description="Currently spoofed node IDs")
quarantined_nodes: List[str] = Field(default_factory=list, description="Quarantined node IDs")
state_estimation_run: bool = Field(False, description="Whether state estimation has been run this episode")
action_history: List[Dict[str, Any]] = Field(default_factory=list, description="History of all actions taken")
|