Nexus-Grid / README.md
Abineshsdata's picture
Update README.md
e6dd0d2 verified
|
Raw
History Blame Contribute Delete
11 kB
metadata
title: NexusGrid-CyberPhysEnv
emoji: 
colorFrom: red
colorTo: yellow
sdk: docker
pinned: false
app_port: 8000
base_path: /web/
tags:
  - openenv
  - critical-infrastructure
  - cybersecurity
  - scada
  - reinforcement-learning

⚡ NexusGrid-CyberPhysEnv

National Power Grid & SCADA Cyber-Warfare Defense Benchmark

An OpenEnv environment that simulates the defense of a national power grid under simultaneous physical faults and SCADA cyberattacks. Built for the OpenEnv Hackathon.


1. Environment Description & Motivation

NexusGrid-CyberPhysEnv models a 20-node transmission network with real power flow physics (DC Kirchhoff formulation) running alongside a deterministic sensor spoofing engine. An AI agent must distinguish real grid failures from adversarially fabricated telemetry — a task that requires engineering reasoning, not just pattern matching.

Motivation: The 2015 Ukraine Sandworm attack demonstrated that SCADA cyberattacks on critical infrastructure are a present-day threat. No existing RL benchmark simulates the physics-versus-deception paradox this environment is built around. The environment is designed to be immediately useful for evaluating planning and reasoning capabilities in frontier models.

Architecture

Layer 1  Physical grid simulation    numpy graph + Kirchhoff DC power flow solver
Layer 2  SCADA cyber layer           packet logs + seeded sensor spoof engine (3 attack vectors)
Layer 3  OpenEnv API                 step() / reset(seed) / state() + typed Pydantic models
Layer 4  Inference                   inference.py + OpenAI client + structured logging

2. Observation Space

Field Type Range Spoofable? Description
topology_graph Dict (nodes + edges) 20 nodes, 40 edges ❌ Never Immutable physical map. Node: id, region, type, capacity_mw, critical. Edge: id, source, target, capacity_mw, current_load_mw, status.
telemetry_stream List[List[Dict]] Last 10 ticks ⚠️ Yes Per-node time-series: voltage_kv [0–765], frequency_hz [58–62], generation_mw [0–capacity], consumption_mw [0–peak_load].
weather_forecast_matrix List[Dict] 5 zones ❌ Never Per-zone: solar_irradiance [0–1], wind_speed_ms [0–30], cloud_cover [0–1].
network_packet_logs List[Dict] Last 20 entries ❌ Never SCADA traffic: timestamp, source/dest node, latency_ms [0–500], anomaly_flag. Spikes precede spoofing by 1–2 ticks.
grid_frequency_hz float [58.0, 62.0] ❌ Never Truth engine output. Nominal 60.0Hz. Termination below 59.0Hz.

3. Action Space

Action Parameters Description
dispatch_generation node_id, mw Ramp a plant/battery up or down. mw ∈ [-capacity, +capacity].
toggle_circuit_breaker edge_id, status Open/close a transmission line. status: "OPEN" | "CLOSED".
run_state_estimation subgraph (list of node IDs) Kirchhoff consistency check. Returns {consistent, violation_node, estimated_true_mw}. Costs 1 tick.
quarantine_scada_node node_id Disconnect spoofed sensor. Must be preceded by run_state_estimation or -0.15 penalty.
inject_counter_signal node_id, hz_offset, duration Counter resonance attack via battery. Tolerance ±0.05Hz.
advance_tick (none) Step simulation forward ~5 minutes. Weather/load/attacks evolve.

4. Task Descriptions

Task Name Difficulty Max Ticks Expected Score Description
0 Smoke Test Trivial 3 1.0 Any valid dispatch_generation → 1.0. Infrastructure validation.
1 Duck Curve Easy 15 0.7–1.0 Solar drops at sunset, demand spikes. Dispatch batteries before frequency falls.
2 Cascade Overload Medium 20 0.5–0.8 Storm snaps primary line. Isolate fault, protect critical nodes, restore supply.
3 Phantom Injection Hard 18 0.3–0.6 SCADA spoofs NODE_14. Must: read logs → state estimation → quarantine → reroute.
4 Stuxnet Resonance Very Hard 12 0.1–0.4 Turbine under resonance attack. Inject counter-signal at correct frequency.
5 Black Start Expert 50 0.0–0.3 Grid is dark. Restart from hydro dam, form islands, sync phases, restore critical infra.

5. Reward Function

Positive Signals (per tick)

Signal Value Condition
Fault isolation +0.20 Isolating a fault without dropping critical nodes
Cyber detection +0.15 Classifying + quarantining a spoofed sensor after state estimation
Frequency stable +0.10 Grid frequency in nominal band (59.7–60.3Hz)
Proactive dispatch +0.08 Dispatch before frequency deviation
Reasoning order +0.05 Reading packet logs before running state estimation
Stability bonus +0.03 Frequency within ±0.1Hz of 60.0Hz

Penalties

Signal Value Condition
Overload routing -0.20 Routing through ≥95% capacity line
Quarantine w/o estimation -0.15 Anti-hallucination penalty
Redundant estimation -0.05 Same subgraph without action between

Graduated Frequency Bands

Band Effect
59.7–60.3 Hz Nominal. No penalty. Stability bonus applies.
59.5–59.7 Hz Warning zone. No penalty.
59.2–59.5 Hz -0.05 per tick
59.0–59.2 Hz -0.15 per tick (critical)
Below 59.0 Hz Episode termination. done=True.

6. Environment Contract

reset() Contract

reset(seed) must:

  1. Reconstruct full topology from scenario definition — no mutations persist
  2. Clear all telemetry history buffers
  3. Re-initialize the spoof engine with the new seed
  4. Reset tick counter to 0
  5. Return a GridObservation with all fields freshly computed

Calling reset(42) ten times in a row returns byte-identical GridObservation each time.

Reproducibility Guarantee (Seed-Lock Contract)

All stochastic elements derive exclusively from numpy.random.Generator(numpy.random.PCG64(episode_seed)) where episode_seed is passed to reset(). Python's random module is never used. datetime.now() is never used for game-state computation. Running inference.py twice with the same EPISODE_SEED produces byte-identical scores.


7. Setup & Usage

Prerequisites

ollama pull deepseek-r1:8b

Environment Variables

# Required — set in .env or export before running
API_BASE_URL=http://localhost:11434/v1
MODEL_NAME=deepseek-r1:8b

# For HF Space deployment only (set as Space Secrets)
HF_TOKEN=your_hf_token

Install Dependencies

pip install -r requirements.txt
# or
pip install -e .

Run Server Locally

uvicorn server.app:app --host 0.0.0.0 --port 8000

Run Inference (Local with Ollama)

python inference.py

Runs all 6 tasks. Per-task time budgets enforced. Total under 20 minutes on vcpu=2, memory=8GB.

Docker

docker build -t nexusgrid .
docker run -p 8000:8000 \
  -e API_BASE_URL=http://host.docker.internal:11434/v1 \
  -e MODEL_NAME=deepseek-r1:8b \
  nexusgrid

Run Tests

pytest tests/ -v

48 tests across 5 test files: Kirchhoff physics, grader determinism, spoof engine reproducibility, API schema compliance, seed-lock verification.

Health Check

GET /health  →  {"status": "healthy"}

Validation

openenv validate

8. Baseline Scores

Task Name Difficulty Baseline Score
0 Smoke test Trivial 1.00
1 Duck curve Easy 1.00
2 Cascade overload Medium 0.80
3 Phantom injection Hard 1.00
4 Stuxnet resonance Very hard 1.00
5 Black start Expert 0.025

Model: deepseek-r1:8b via Ollama · Seed: 42 · Average: 0.80 · Total time: 766s

Note: Scores measured using deterministic fallback actions. When connected to Ollama, the inference script attempts LLM reasoning; fallback actions activate on LLM timeout/error.


9. Project Structure

nexusgrid/
├── .env                          # API_BASE_URL, MODEL_NAME (local testing)
├── __init__.py                   # Package exports
├── models.py                     # GridObservation, GridAction, GridReward (Pydantic)
├── client.py                     # NexusgridEnv WebSocket client
├── inference.py                  # Agent inference script (OpenAI client)
├── openenv.yaml                  # OpenEnv specification
├── pyproject.toml                # Dependencies & build config
├── Dockerfile                    # python:3.11-slim container
├── requirements.txt              # Pinned dependencies
├── README.md                     # This file
├── server/
│   ├── __init__.py
│   ├── __main__.py               # python -m server entry point
│   ├── app.py                    # FastAPI + /health + Gradio mount
│   ├── dashboard.py              # Gradio visual dashboard (7 panels)
│   ├── nexusgrid_environment.py  # Core OpenEnv environment
│   ├── grid_engine.py            # DC power flow physics engine
│   ├── spoof_engine.py           # SCADA attack simulation
│   ├── scenarios.py              # 6 task scenario definitions
│   ├── graders.py                # 6 task graders (pure functions)
│   └── reward.py                 # Dense reward calculator
└── tests/
    ├── test_kirchhoff.py         # Physics conservation tests (8)
    ├── test_graders.py           # Grader unit tests (18)
    ├── test_spoof_engine.py      # Spoof determinism tests (7)
    ├── test_api.py               # API interface tests (10)
    ├── test_reproducibility.py   # Seed-lock verification (5)
    └── verify_server.py          # Endpoint health checker

10. Structured Logging Format

[START] task_id=<int> episode_seed=<int>
[STEP] task_id=<int> tick=<int> action=<name> params=<json> reward=<float> score=<float> done=<bool>
[END] task_id=<int> score=<float> ticks=<int>

Example:

[START] task_id=0 episode_seed=42
[STEP] task_id=0 tick=0 action=dispatch_generation params={"node_id": "NODE_01", "mw": 100} reward=0.18 score=0.18 done=false
[END] task_id=0 score=1.00 ticks=3

11. LLM Client Pattern

All LLM calls use the OpenAI client with environment variables:

from openai import OpenAI
import os

client = OpenAI(
    base_url=os.environ["API_BASE_URL"],
    api_key=os.environ.get("HF_TOKEN") or "ollama",
)

response = client.chat.completions.create(
    model=os.environ["MODEL_NAME"],
    messages=[{"role": "user", "content": prompt}],
)

License

This environment is built for the OpenEnv Hackathon.