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
flowchart LR
    P[ISO load profiles] --> G[fg.generate]
    G --> F[("one timeline file per system<br/>observed · benign · clean layers")]
    F --> L[fg.load]
    L --> D["FdiaGraph<br/>records, batches, PyG<br/>(order=random)"]
    L --> T["FdiaGraph<br/>windows, episodes<br/>(order=time)"]
    D --> SE[fdia_graph.se<br/>state estimation]
    D --> LOC[fdia_graph.localization<br/>which buses]
    T --> M[your temporal model]
Read To learn
docs/ROADMAP.md which file does what, and how the paths connect
docs/reference/DATA_DICTIONARY.md what every array means
docs/reference/CONCEPTS_TO_CODE.md paper equations to functions
docs/reference/EXAMPLES.md runnable baselines, the timeline as sequences, dataset stats
docs/se/ · docs/localization/ the two 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

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 evades every per-bus change within a 2% to 20% band
At slow load ramp, re-solved locally every frame evades same band, spread over 60 scans
Al load redistribution that raises a line's apparent loading, re-solved locally evades same band, load conserved
Am the redistribution reached in per-frame steps under the noise floor evades same band, spread over 60 scans

| Ad / As / Ar | meter bias / scaling / replay | caught | same band |

Every stealthy family is a local false state (Wu et al. 2026): 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 three 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

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.18.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.18.0
File Size Uploaded
fdia_graph-0.18.0.tar.gz 173.3 kB Details

Built distribution (wheel)

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

Total release size: 334.4 kB

Release files / fdia_graph-0.18.0.tar.gz

Download URL fdia_graph-0.18.0.tar.gz
Size 173.3 kB
Tags Source
SHA-256 checksum
How to use checksums
23b5fa5dd3da0fca31520f23b751f14f4e46f69beeac9353a94c5ceab95efa54
BLAKE2b-256 checksum
How to use checksums
3b0d3f18fc214f4f01f22c9d0029d7db2e77ae48760bbccd9eb5c8ccae6bc8c6
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 21, 2026.

Transparency log

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

Download URL fdia_graph-0.18.0-py3-none-any.whl
Size 161.1 kB
Tags Python 3
SHA-256 checksum
How to use checksums
f077cf0b02d8b63b72202983fa0119a3aa472b7f355a6de302f497849f160296
BLAKE2b-256 checksum
How to use checksums
7fce4bd6ba3a3d6b9513b13fbc751e5d193f4767a8f046fbc40f158279655f7d
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 21, 2026.

Transparency log

Release history Release notifications | RSS feed

0.20.0

2 release files

0.19.0

2 release files

This release

0.18.0 This release

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