Skip to main content

fdia-graph

Stealthy FDIA localization datasets for power grids, PyTorch-ready in one line. Eight IEEE systems (14 / 30 / 57 / 89 / 118 / 145 / 200 / 300 buses), one continuous timeline of 72,000 frames each: a record table when shuffled, a time series when not.

import fdia_graph as fg

ds = fg.load("ieee118", split="train")     # auto-downloads + caches
for batch in ds.loader(batch_size=64):
    batch["node_x"], batch["edge_x"], batch["edge_index"], batch["y"], batch["family"]

ts = fg.load("ieee118", split="test", order="time")   # the same frames in time order
Xw, yw = ts.windows(W=24, stride=12)                  # [n, 24, N, 4] windows for an LSTM / TGN

The pipeline: ISO load profiles feed fg.generate, which writes one timeline file per system with observed, benign and clean layers; fg.load reads it as a record table for fdia_graph.se and fdia_graph.localization, or as a time series of windows and episodes for a temporal model

Read To learn
docs/ROADMAP.md which file does what, and how the paths connect
docs/guides/generation.md how a timeline is generated, and which layers a feature may read
docs/reference/DATA_DICTIONARY.md what every array means
docs/reference/GLOSSARY.md every term, with where it lives in the code
docs/reference/CONCEPTS_TO_CODE.md paper equations to functions
docs/reference/CLASS_MAP.md architecture: the UML class and module diagrams, drawn from the code
docs/reference/EXAMPLES.md runnable baselines, the timeline as sequences, dataset stats
docs/se/ · docs/localization/ · docs/trust/ · docs/federated/ the analysis modules, with results
CONTRIBUTING.md rules, pull-request flow, releases

Install

command gives
pip install fdia-graph the loader (numpy, h5py)
pip install "fdia-graph[torch]" + PyTorch DataLoader, learned localizers
pip install "fdia-graph[pyg]" + torch_geometric records and timelines
pip install "fdia-graph[se]" + state estimation, residual localization (pandapower, scipy; add [torch] for speed)
pip install "fdia-graph[generate]" + pandapower, to generate custom data
pip install "fdia-graph[iso]" + gridstatus, to download CAISO and ERCOT load profiles (NYISO needs no extra)
pip install "fdia-graph[federated]" + torch and scikit-learn, for fdia_graph.federated
pip install "fdia-graph[all]" every extra above

Data is pinned per SDK version and cached in ~/.cache/fdia_graph. fg.load(..., release="v0.7.2") pins a data version (the v0.7.2 record shards still load); pip install --upgrade fdia-graph moves it forward.

Load

One file per system, one loader. order decides what the file is to you:

fg.load("ieee300", split="train")                              # 60/20/20 chronological split, episodes never cut
fg.load("ieee118", split="test", families=["Aq", "At", "Al"])  # family subset
fg.load("ieee118", units="pu")                                 # per-unit + radians (default: physical)
fg.load("ieee118", order="random", seed=0)                     # the record table: a fixed permutation
fg.load("ieee118", split="test", order="time")                 # the time series: windows, episodes
you want call
a whole split at once ds.export() (arrays), ds.export(format="torch"), ds.export(format="pandas")
windows for an LSTM / TGN ds.windows(W, stride, label, layer, per_bus=True) on a time-ordered view (one sequence per bus)
the attack episodes ds.episodes (onset, length, family, buses)
the attack removed every record carries benign and edge_benign next to node_x and clean
custom data fg.generate(system, name, attacked_frac=..., families=..., frames=...), then fg.load(name)

State estimation

from fdia_graph.se import SubspacePrior                       # pip install "fdia-graph[se]"

train, test = fg.load("ieee118", split="train"), fg.load("ieee118", split="test")
est = SubspacePrior(rank_frac=0.5, reweight="huber", c=2.5).fit(train)
xhat = est.estimate(test)          # [n, 2N-1] = [theta rad (non-slack) | V pu (all buses)]
print(est.score(test))             # per-family angle / voltage MAE vs the clean truth

One solver, six estimators that each change one thing: WLS, AdaptiveWeighting, ResidualRemoval, SubspacePrior, JacobianWeighting, GatedPrior. Results: docs/se/.

Localization

from fdia_graph.localization import SwingThreshold, BusCNN     # numpy only / [torch]

loc = SwingThreshold(fa_target=0.01).fit(train)     # per-bus thresholds from benign records only
flag = loc.localize(test)                           # [n, N] bool: which buses are called attacked
print(loc.score(test))                              # per-family node-F1, strict accuracy, DR next to FA

zs = dict(families=[0, 1, 2])                       # the papers' zero-shot protocol
cnn = BusCNN().fit(fg.load("ieee118", split="train", **zs), val=fg.load("ieee118", split="val", **zs))

One calibration, five localizers: SwingThreshold, DeltaThreshold, ResidualLocalizer, BusMLP, BusCNN. Results: docs/localization/.

Data

Each record is a sparse measurement graph with N buses (nodes) and E branches (edges). A shape reads "values per item": [N,4] is 4 numbers per bus.

field shape columns meaning
node_x [N,4] |V|, P_inj, Q_inj, theta bus meters (node_m is the mask)
edge_x [E,2] P_from, Q_from branch flows (edge_m is the mask)
edge_index [2,E] from_bus; to_bus connectivity
edge_attr [E,8] r, x, b, g, gs, bs, tap, shift static line physics (Data.edge_phys in PyG)
y [N] 1 attacked, 0 clean
family scalar 0 benign, 1 Aq, 2 Ad, 3 As, 4 Ar, 5 At, 6 Al, 7 Am (fg.FAMILIES)
temporal_delta, swing [N,2] ΔP, ΔQ change against the previous frame, and as a z-score of recent change
benign, edge_benign [N,4], [E,2] as above the same scan with the attack removed, noise kept
clean, edge_clean, edge_clean_full [N,4], [E,2], [E,2] as above noiseless truth: buses, metered branches, every branch
seq_id, timestep, split scalars episode index (-1 benign), frame index, partition
slack, ybus, yf, yt dataset attributes reference bus, admittance matrices

Full reference: docs/reference/DATA_DICTIONARY.md.

Attacks

family attack classical BDD plausibility
Aq load rescale, the subnetwork around the buses re-solved locally, one frame evades every per-bus change within a 5% to 20% band
At slow load ramp, re-solved locally every frame evades peak 2.4% to 5.2% of the load (0.2% per frame over a 12 to 26 frame rise), 60 frames
Al load redistribution that lightens a line's apparent loading (a real overload reads lighter), re-solved locally, one frame evades 2% to 20% band, load conserved
Am the redistribution reached in per-frame steps under the noise floor evades 2% to 20% band, spread over 60 scans
Ad / As / Ar meter bias / scaling / replay, one frame caught Ad and As 2% to 20% band; Ar replays an earlier benign scan and records the realized change

Every stealthy family is a local false state [WU26]: the attacker solves the power flow of a subnetwork around the attack with the boundary voltages held true, writes only that subnetwork's meters, and the measurement vector stays consistent with an AC state, so the residual test sees noise. The meters written are the tamper masks in the file's attack/ group.

BDD statistic per family: the stealthy families sit below the alarm line with benign, the three tampering families sit far above it

Bad-data statistic relative to the alarm threshold, per family. Green families are indistinguishable from benign; red ones trip the alarm. Meter error follows an accuracy-class model (per-meter bias plus per-scan jitter). Report per-family node-F1 next to the false-alarm rate, never accuracy.

Citation

cite for
Yuan, Li & Ren, Modeling load redistribution attacks in power systems, IEEE T-SG 2(2), 2011 LRA
Haghshenas, Hasnat & Naeini, A Temporal GNN for Cyber Attack Detection and Localization in Smart Grids, IEEE ISGT 2023 ramp
Zaman & Lin, PING: Physics-Informed GNNs to Generalize FDIA Localization, NAPS 2025 measurement model
Asprou, Kyriakides & Albu, Variable Weights in a WLS State Estimator, IEEE T-IM 63, 2014 meter noise
Boyaci et al., Joint Detection and Localization of Stealth FDIA, IEEE T-SG, 2022 protocol
Wu, Wang, Hu, Ye & Tang, Dynamic PMU configuration for stealthy multi-snapshot FDIA mitigation, IEEE T-SG 17(1), 2026 local false states, Am, trusted meters

License

Data under CC BY 4.0, code under MIT (see LICENSE). Synthetic, from public IEEE cases. Not for operational decisions.

Release files for fdia-graph 0.20.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for fdia-graph 0.20.0
File Size Uploaded
fdia_graph-0.20.0.tar.gz 218.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for fdia-graph 0.20.0
File Interpreter ABI Platform
fdia_graph-0.20.0-py3-none-any.whl Python 3 none any Details

Total release size: 411.5 kB

Release files / fdia_graph-0.20.0.tar.gz

Download URL fdia_graph-0.20.0.tar.gz
Size 218.8 kB
Tags Source
SHA-256 checksum
How to use checksums
266952ed33162fb6063153fd6bf37f0641a497dd20cb00997217cf55e5fb4ea3
BLAKE2b-256 checksum
How to use checksums
eb0a4ff82a292b6d650278d1c7c625b030ba501210c401df71c992326e8a34e0
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.

Transparency log

Release files / fdia_graph-0.20.0-py3-none-any.whl

Download URL fdia_graph-0.20.0-py3-none-any.whl
Size 192.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f052f5e8362425a014e866318f28e0c492fe45be99b13da1df9436b15c0e3f13
BLAKE2b-256 checksum
How to use checksums
b8c7f0116bb07b4339da06aaa5ecea80d572f9e0e4a8b9099b420777087b42fb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 25, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.20.0 This release

2 release files

0.19.0

2 release files

0.18.0

2 release files

0.17.0

2 release files

0.16.0

2 release files

0.9.0

2 release files

0.8.0

2 release files

0.7.10

2 release files

0.7.9

2 release files

0.7.8

2 release files

0.7.7

2 release files

0.6.0

2 release files

0.5.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page