From 01c36ffdcd89da3c81d7cc405e917105d61e8102 Mon Sep 17 00:00:00 2001 From: Romeo Kienzler Date: Wed, 11 Mar 2026 18:21:53 +0100 Subject: [PATCH 01/14] add smoke test Signed-off-by: Romeo Kienzler --- .gitignore | 6 ++- integrationtests/default.yaml | 48 +++++++++++++++++++ integrationtests/test_base_set.py | 77 +++++++++++++++++++++++++++++++ 3 files changed, 130 insertions(+), 1 deletion(-) create mode 100644 integrationtests/default.yaml create mode 100644 integrationtests/test_base_set.py diff --git a/.gitignore b/.gitignore index 837ff76..33c0c40 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -venv +*venv venv_pp /data/ __pycache__/ @@ -10,3 +10,7 @@ gridfm_graphkit.egg-info mlruns *.pt .DS_Store +integrationtests/data_out* +.julia +*logs* +*data_out* \ No newline at end of file diff --git a/integrationtests/default.yaml b/integrationtests/default.yaml new file mode 100644 index 0000000..1812d18 --- /dev/null +++ b/integrationtests/default.yaml @@ -0,0 +1,48 @@ +network: + name: "case14_ieee" # Name of the power grid network (without extension) + source: "pglib" # Data source for the grid; options: pglib, file + # WARNING: the following parameter is only used if source is "file" + network_dir: "scripts/grids" # if using source "file", this is the directory containing the network file + +load: + generator: "agg_load_profile" # Name of the load generator; options: agg_load_profile, powergraph + agg_profile: "default" # Name of the aggregated load profile + scenarios: 10000 # Number of different load scenarios to generate + # WARNING: the following parameters are only used if generator is "agg_load_profile" + # if using generator "powergraph", these parameters are ignored + sigma: 0.2 # max local noise + change_reactive_power: true # If true, changes reactive power of loads. If False, keeps the ones from the case file + global_range: 0.4 # Range of the global scaling factor. used to set the lower bound of the scaling factor + max_scaling_factor: 4.0 # Max upper bound of the global scaling factor + step_size: 0.1 # Step size when finding the upper bound of the global scaling factor + start_scaling_factor: 1.0 # Initial value of the global scaling factor + +topology_perturbation: + type: "random" # Type of topology generator; options: n_minus_k, random, none + # WARNING: the following parameters are only used if type is not "none" + k: 1 # Maximum number of components to drop in each perturbation + n_topology_variants: 2 # Number of unique perturbed topologies per scenario + elements: [branch, gen] # elements to perturb. options: branch, gen + +generation_perturbation: + type: "cost_permutation" # Type of generation perturbation; options: cost_permutation, cost_perturbation, none + # WARNING: the following parameter is only used if type is "cost_permutation" + sigma: 1.0 # Size of range used for sampling scaling factor + +admittance_perturbation: + type: "random_perturbation" # Type of admittance perturbation; options: random_perturbation, none + # WARNING: the following parameter is only used if type is "random_perturbation" + sigma: 0.2 # Size of range used for sampling scaling factor + +settings: + num_processes: 16 # Number of parallel processes to use + data_dir: "./data_out" # Directory to save generated data relative to the project root + large_chunk_size: 1000 # Number of load scenarios processed before saving + overwrite: true # If true, overwrites existing files, if false, appends to files + mode: "pf" # Mode of the script; options: pf, opf. pf: power flow data where one or more operating limits – the inequality constraints defined in OPF, e.g., voltage magnitude or branch limits – may be violated. opf: generates datapoints for training OPF solvers, with cost-optimal dispatches that satisfy all operating limits (OPF-feasible) + include_dc_res: true # If true, also stores the results of dc power flow or dc optimal power flow + enable_solver_logs: true # If true, write OPF/PF logs to {data_dir}/solver_log; PF fast and DCPF fast do not log. + pf_fast: true # Whether to use fast PF solver by default (compute_ac_pf from powermodels.jl); if false, uses Ipopt-based PF. Some networks (typically large ones e.g. case10000_goc) do not work with pf_fast: true. pf_fast is faster and more accurate than the Ipopt-based PF. + dcpf_fast: true # Whether to use fast DCPF solver by default (compute_dc_pf from PowerModels.jl) + max_iter: 200 # Max iterations for Ipopt-based solvers + seed: null # Seed for random number generation. If null, a random seed is generated (RECOMMENDED). To get the same data across runs, set the seed and note that ALL OTHER PARAMETERS IN THE CONFIG FILE MUST BE THE SAME. diff --git a/integrationtests/test_base_set.py b/integrationtests/test_base_set.py new file mode 100644 index 0000000..f0b2b1e --- /dev/null +++ b/integrationtests/test_base_set.py @@ -0,0 +1,77 @@ +import pytest +import subprocess +import os +import glob +import pandas as pd + +def execute_and_fail(cmd) -> None: + """ + Execute a CLI command and fail in case return code is not 0. + """ + result = subprocess.run( + cmd, + capture_output=True, + text=True, + shell=True, + ) + assert result.returncode == 0, ( + f"{cmd} failed (exit {result.returncode}).\n" + f"stdout:\n{result.stdout}\n" + f"stderr:\n{result.stderr}" + ) + +def test_prepare_data(): + """ + gridfm-datakit must be installable via pip with exit code 0. + + This test explicitly re-runs the install command and asserts that pip + exits successfully, making the install step a first-class test rather + than a silent fixture side-effect. + """ + + # Check if data already exists, if not generate it + data_dir = "data_out" + if not os.path.exists(data_dir) or not os.listdir(data_dir): + print("Data directory not found or empty, generating data...") + execute_and_fail( + 'gridfm_datakit generate default.yaml' + ) + else: + print(f"Data directory '{data_dir}' already exists, skipping data generation.") + + execute_and_fail( + 'gridfm_graphkit train --config examples/config/HGNS_PF_datakit_case14.yaml --data_path data_out/ --exp_name exp1 --run_name run1 --log_dir logs' + ) + + # Find the latest log directory + log_base = "logs" + exp_dirs = glob.glob(os.path.join(log_base, "*")) + assert len(exp_dirs) > 0, "No experiment directories found in logs/" + + latest_exp_dir = max(exp_dirs, key=os.path.getmtime) + run_dirs = glob.glob(os.path.join(latest_exp_dir, "*")) + assert len(run_dirs) > 0, f"No run directories found in {latest_exp_dir}" + + latest_run_dir = max(run_dirs, key=os.path.getmtime) + metrics_file = os.path.join(latest_run_dir, "artifacts", "test", "case14_ieee_metrics.csv") + + assert os.path.exists(metrics_file), f"Metrics file not found: {metrics_file}" + + # Read the metrics CSV + df = pd.read_csv(metrics_file) + + # Find PBE Mean value + pbe_mean_row = df[df['Metric'] == 'PBE Mean'] + assert len(pbe_mean_row) > 0, "PBE Mean metric not found in CSV" + + pbe_mean_value = float(pbe_mean_row.iloc[0]['Value']) + + # Check if PBE Mean is within acceptable range [1.4, 1.6] + assert 1.4 <= pbe_mean_value <= 1.6, ( + f"PBE Mean value {pbe_mean_value} is outside acceptable range [1.4, 1.6]" + ) + + print(f"✓ PBE Mean value {pbe_mean_value} is within acceptable range [1.4, 1.6]") + + + From 1bb811beb7c9daaf72080ca369cbaaee4fb8ffc8 Mon Sep 17 00:00:00 2001 From: Romeo Kienzler Date: Wed, 11 Mar 2026 19:53:08 +0100 Subject: [PATCH 02/14] fix value range Signed-off-by: Romeo Kienzler --- integrationtests/test_base_set.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/integrationtests/test_base_set.py b/integrationtests/test_base_set.py index f0b2b1e..16f4a02 100644 --- a/integrationtests/test_base_set.py +++ b/integrationtests/test_base_set.py @@ -66,8 +66,7 @@ def test_prepare_data(): pbe_mean_value = float(pbe_mean_row.iloc[0]['Value']) - # Check if PBE Mean is within acceptable range [1.4, 1.6] - assert 1.4 <= pbe_mean_value <= 1.6, ( + assert 1.1 <= pbe_mean_value <= 2.9, ( f"PBE Mean value {pbe_mean_value} is outside acceptable range [1.4, 1.6]" ) From cb50a1e8b918da9a260d102b9f3d99830e75af03 Mon Sep 17 00:00:00 2001 From: Romeo Kienzler Date: Fri, 13 Mar 2026 08:22:15 +0100 Subject: [PATCH 03/14] fix doc, improve test Signed-off-by: Romeo Kienzler --- .gitignore | 3 +- docs/datasets/data_modules.md | 4 +-- docs/datasets/data_normalization.md | 47 +++++++++++---------------- docs/datasets/powergrid.md | 4 +-- docs/datasets/transforms.md | 24 +++++--------- docs/models/models.md | 35 +++++++++++++++++--- docs/tasks/feature_reconstruction.md | 30 +++++++++++++++-- docs/training/loss.md | 38 ++++++++++++++-------- integrationtests/default.yaml | 48 ---------------------------- integrationtests/test_base_set.py | 39 +++++++++++++++++++++- 10 files changed, 154 insertions(+), 118 deletions(-) delete mode 100644 integrationtests/default.yaml diff --git a/.gitignore b/.gitignore index 33c0c40..164d66b 100644 --- a/.gitignore +++ b/.gitignore @@ -13,4 +13,5 @@ mlruns integrationtests/data_out* .julia *logs* -*data_out* \ No newline at end of file +*data_out* +site* \ No newline at end of file diff --git a/docs/datasets/data_modules.md b/docs/datasets/data_modules.md index bf47118..a5e4dff 100644 --- a/docs/datasets/data_modules.md +++ b/docs/datasets/data_modules.md @@ -1,3 +1,3 @@ -# LitGridDataModule +# LitGridHeteroDataModule -::: gridfm_graphkit.datasets.powergrid_datamodule.LitGridDataModule +::: gridfm_graphkit.datasets.hetero_powergrid_datamodule.LitGridHeteroDataModule diff --git a/docs/datasets/data_normalization.md b/docs/datasets/data_normalization.md index f41334d..1747fd1 100644 --- a/docs/datasets/data_normalization.md +++ b/docs/datasets/data_normalization.md @@ -3,12 +3,10 @@ Normalization improves neural network training by ensuring features are well-scaled, preventing issues like exploding gradients and slow convergence. In power grids, where variables like voltage and power span wide ranges, normalization is essential. -The `gridfm-graphkit` package offers four methods: +The `gridfm-graphkit` package offers normalization methods based on the per-unit (p.u.) system: -- [`Min-Max Normalization`](#minmaxnormalizer) -- [`Standardization (Z-score)`](#standardizer) -- [`Identity (no normalization)`](#identitynormalizer) -- [`BaseMVA Normalization`](#basemvanormalizer) +- [`BaseMVA Normalization`](#heterodatamvanormalizer) +- [`Per-Sample BaseMVA Normalization`](#heterodatapersamplemvanormalizer) Each of these strategies implements a unified interface and can be used interchangeably depending on the learning task and data characteristics. @@ -25,27 +23,15 @@ Each of these strategies implements a unified interface and can be used intercha --- -### `MinMaxNormalizer` +### `HeteroDataMVANormalizer` -::: gridfm_graphkit.datasets.normalizers.MinMaxNormalizer +::: gridfm_graphkit.datasets.normalizers.HeteroDataMVANormalizer --- -### `Standardizer` +### `HeteroDataPerSampleMVANormalizer` -::: gridfm_graphkit.datasets.normalizers.Standardizer - ---- - -### `BaseMVANormalizer` - -::: gridfm_graphkit.datasets.normalizers.BaseMVANormalizer - ---- - -### `IdentityNormalizer` - -::: gridfm_graphkit.datasets.normalizers.IdentityNormalizer +::: gridfm_graphkit.datasets.normalizers.HeteroDataPerSampleMVANormalizer --- @@ -54,13 +40,18 @@ Each of these strategies implements a unified interface and can be used intercha Example: ```python -from gridfm_graphkit.datasets.normalizers import MinMaxNormalizer -import torch +from gridfm_graphkit.datasets.normalizers import HeteroDataMVANormalizer +from torch_geometric.data import HeteroData + +# Create normalizer +normalizer = HeteroDataMVANormalizer(args) + +# Fit on training data +params = normalizer.fit(data_path, scenario_ids) -data = torch.randn(100, 5) # Example tensor +# Transform data +normalizer.transform(hetero_data) -normalizer = MinMaxNormalizer(node_data=True,args=None) -params = normalizer.fit(data) -normalized = normalizer.transform(data) -restored = normalizer.inverse_transform(normalized) +# Inverse transform to restore original scale +normalizer.inverse_transform(hetero_data) ``` diff --git a/docs/datasets/powergrid.md b/docs/datasets/powergrid.md index 45476ac..1f983a5 100644 --- a/docs/datasets/powergrid.md +++ b/docs/datasets/powergrid.md @@ -1,3 +1,3 @@ -## `GridDatasetDisk` +## `HeteroGridDatasetDisk` -::: gridfm_graphkit.datasets.powergrid_dataset.GridDatasetDisk +::: gridfm_graphkit.datasets.powergrid_hetero_dataset.HeteroGridDatasetDisk diff --git a/docs/datasets/transforms.md b/docs/datasets/transforms.md index dd7f66d..0dcf981 100644 --- a/docs/datasets/transforms.md +++ b/docs/datasets/transforms.md @@ -2,26 +2,18 @@ > Each transformation class inherits from [`BaseTransform`](https://pytorch-geometric.readthedocs.io/en/latest/modules/transforms.html#torch_geometric.transforms.BaseTransform) provided by [PyTorch Geometric](https://pytorch-geometric.readthedocs.io/). -### `AddNormalizedRandomWalkPE` +### `RemoveInactiveGenerators` -::: gridfm_graphkit.datasets.transforms.AddNormalizedRandomWalkPE +::: gridfm_graphkit.datasets.transforms.RemoveInactiveGenerators -### `AddEdgeWeights` +### `RemoveInactiveBranches` -::: gridfm_graphkit.datasets.transforms.AddEdgeWeights +::: gridfm_graphkit.datasets.transforms.RemoveInactiveBranches -### `AddIdentityMask` +### `ApplyMasking` -::: gridfm_graphkit.datasets.transforms.AddIdentityMask +::: gridfm_graphkit.datasets.transforms.ApplyMasking -### `AddRandomMask` +### `LoadGridParamsFromPath` -::: gridfm_graphkit.datasets.transforms.AddRandomMask - -### `AddPFMask` - -::: gridfm_graphkit.datasets.transforms.AddPFMask - -### `AddOPFMask` - -::: gridfm_graphkit.datasets.transforms.AddOPFMask +::: gridfm_graphkit.datasets.transforms.LoadGridParamsFromPath diff --git a/docs/models/models.md b/docs/models/models.md index 9e822ca..7c8c5c6 100644 --- a/docs/models/models.md +++ b/docs/models/models.md @@ -1,10 +1,37 @@ # Models -### `GPSTransformer` +### `GNS_heterogeneous` -::: gridfm_graphkit.models.gps_transformer.GPSTransformer +::: gridfm_graphkit.models.gnn_heterogeneous_gns.GNS_heterogeneous +--- -### `GNN_TransformerConv` +## Physics Decoders -::: gridfm_graphkit.models.gnn_transformer.GNN_TransformerConv +### `PhysicsDecoderOPF` + +::: gridfm_graphkit.models.utils.PhysicsDecoderOPF + +### `PhysicsDecoderPF` + +::: gridfm_graphkit.models.utils.PhysicsDecoderPF + +### `PhysicsDecoderSE` + +::: gridfm_graphkit.models.utils.PhysicsDecoderSE + +--- + +## Utility Modules + +### `ComputeBranchFlow` + +::: gridfm_graphkit.models.utils.ComputeBranchFlow + +### `ComputeNodeInjection` + +::: gridfm_graphkit.models.utils.ComputeNodeInjection + +### `ComputeNodeResiduals` + +::: gridfm_graphkit.models.utils.ComputeNodeResiduals diff --git a/docs/tasks/feature_reconstruction.md b/docs/tasks/feature_reconstruction.md index 39e0823..99a0321 100644 --- a/docs/tasks/feature_reconstruction.md +++ b/docs/tasks/feature_reconstruction.md @@ -1,3 +1,29 @@ -# Feature Reconstruction Task +# Reconstruction Tasks -::: gridfm_graphkit.tasks.feature_reconstruction_task.FeatureReconstructionTask +## Base Task + +::: gridfm_graphkit.tasks.base_task.BaseTask + +--- + +## Reconstruction Task + +::: gridfm_graphkit.tasks.reconstruction_tasks.ReconstructionTask + +--- + +## Optimal Power Flow Task + +::: gridfm_graphkit.tasks.opf_task.OptimalPowerFlowTask + +--- + +## Power Flow Task + +::: gridfm_graphkit.tasks.pf_task.PowerFlowTask + +--- + +## State Estimation Task + +::: gridfm_graphkit.tasks.se_task.StateEstimationTask diff --git a/docs/training/loss.md b/docs/training/loss.md index 5cde707..0d08ba3 100644 --- a/docs/training/loss.md +++ b/docs/training/loss.md @@ -1,16 +1,12 @@ # Loss Functions -### `Power Balance Equation Loss` +## Base Loss -$$ -\mathcal{L}_{\text{PBE}} = \frac{1}{N} \sum_{i=1}^N \left| (P_{G,i} - P_{D,i}) + j(Q_{G,i} - Q_{D,i}) - S_{\text{injection}, i} \right| -$$ - -::: gridfm_graphkit.training.loss.PBELoss +::: gridfm_graphkit.training.loss.BaseLoss --- -### `Mean Squared Error Loss` +## Mean Squared Error Loss $$ \mathcal{L}_{\text{MSE}} = \frac{1}{N} \sum_{i=1}^N (y_i - \hat{y}_i)^2 @@ -20,7 +16,7 @@ $$ --- -### `Masked Mean Squared Error Loss` +## Masked Mean Squared Error Loss $$ \mathcal{L}_{\text{MaskedMSE}} = \frac{1}{|M|} \sum_{i \in M} (y_i - \hat{y}_i)^2 @@ -30,20 +26,34 @@ $$ --- -### `Scaled Cosine Error Loss` +## Masked Generator MSE Loss -$$ -\mathcal{L}_{\text{SCE}} = \frac{1}{N} \sum_{i=1}^N \left(1 - \frac{\hat{y}^T_i \cdot y_i}{\|\hat{y}_i\| \|y_i\|}\right)^\alpha \text{ , } \alpha \geq 1 -$$ +::: gridfm_graphkit.training.loss.MaskedGenMSE + +--- + +## Masked Bus MSE Loss -::: gridfm_graphkit.training.loss.SCELoss +::: gridfm_graphkit.training.loss.MaskedBusMSE --- -### `Mixed Loss` +## Mixed Loss $$ \mathcal{L}_{\text{Mixed}} = \sum_{m=1}^M w_m \cdot \mathcal{L}_m $$ ::: gridfm_graphkit.training.loss.MixedLoss + +--- + +## Layered Weighted Physics Loss + +::: gridfm_graphkit.training.loss.LayeredWeightedPhysicsLoss + +--- + +## Loss Per Dimension + +::: gridfm_graphkit.training.loss.LossPerDim diff --git a/integrationtests/default.yaml b/integrationtests/default.yaml deleted file mode 100644 index 1812d18..0000000 --- a/integrationtests/default.yaml +++ /dev/null @@ -1,48 +0,0 @@ -network: - name: "case14_ieee" # Name of the power grid network (without extension) - source: "pglib" # Data source for the grid; options: pglib, file - # WARNING: the following parameter is only used if source is "file" - network_dir: "scripts/grids" # if using source "file", this is the directory containing the network file - -load: - generator: "agg_load_profile" # Name of the load generator; options: agg_load_profile, powergraph - agg_profile: "default" # Name of the aggregated load profile - scenarios: 10000 # Number of different load scenarios to generate - # WARNING: the following parameters are only used if generator is "agg_load_profile" - # if using generator "powergraph", these parameters are ignored - sigma: 0.2 # max local noise - change_reactive_power: true # If true, changes reactive power of loads. If False, keeps the ones from the case file - global_range: 0.4 # Range of the global scaling factor. used to set the lower bound of the scaling factor - max_scaling_factor: 4.0 # Max upper bound of the global scaling factor - step_size: 0.1 # Step size when finding the upper bound of the global scaling factor - start_scaling_factor: 1.0 # Initial value of the global scaling factor - -topology_perturbation: - type: "random" # Type of topology generator; options: n_minus_k, random, none - # WARNING: the following parameters are only used if type is not "none" - k: 1 # Maximum number of components to drop in each perturbation - n_topology_variants: 2 # Number of unique perturbed topologies per scenario - elements: [branch, gen] # elements to perturb. options: branch, gen - -generation_perturbation: - type: "cost_permutation" # Type of generation perturbation; options: cost_permutation, cost_perturbation, none - # WARNING: the following parameter is only used if type is "cost_permutation" - sigma: 1.0 # Size of range used for sampling scaling factor - -admittance_perturbation: - type: "random_perturbation" # Type of admittance perturbation; options: random_perturbation, none - # WARNING: the following parameter is only used if type is "random_perturbation" - sigma: 0.2 # Size of range used for sampling scaling factor - -settings: - num_processes: 16 # Number of parallel processes to use - data_dir: "./data_out" # Directory to save generated data relative to the project root - large_chunk_size: 1000 # Number of load scenarios processed before saving - overwrite: true # If true, overwrites existing files, if false, appends to files - mode: "pf" # Mode of the script; options: pf, opf. pf: power flow data where one or more operating limits – the inequality constraints defined in OPF, e.g., voltage magnitude or branch limits – may be violated. opf: generates datapoints for training OPF solvers, with cost-optimal dispatches that satisfy all operating limits (OPF-feasible) - include_dc_res: true # If true, also stores the results of dc power flow or dc optimal power flow - enable_solver_logs: true # If true, write OPF/PF logs to {data_dir}/solver_log; PF fast and DCPF fast do not log. - pf_fast: true # Whether to use fast PF solver by default (compute_ac_pf from powermodels.jl); if false, uses Ipopt-based PF. Some networks (typically large ones e.g. case10000_goc) do not work with pf_fast: true. pf_fast is faster and more accurate than the Ipopt-based PF. - dcpf_fast: true # Whether to use fast DCPF solver by default (compute_dc_pf from PowerModels.jl) - max_iter: 200 # Max iterations for Ipopt-based solvers - seed: null # Seed for random number generation. If null, a random seed is generated (RECOMMENDED). To get the same data across runs, set the seed and note that ALL OTHER PARAMETERS IN THE CONFIG FILE MUST BE THE SAME. diff --git a/integrationtests/test_base_set.py b/integrationtests/test_base_set.py index 16f4a02..d43ed86 100644 --- a/integrationtests/test_base_set.py +++ b/integrationtests/test_base_set.py @@ -3,6 +3,8 @@ import os import glob import pandas as pd +import yaml +import urllib.request def execute_and_fail(cmd) -> None: """ @@ -20,6 +22,36 @@ def execute_and_fail(cmd) -> None: f"stderr:\n{result.stderr}" ) +def prepare_config(): + """ + Download default.yaml from gridfm-datakit repo and modify it with test parameters. + """ + config_url = "https://raw.githubusercontent.com/gridfm/gridfm-datakit/refs/heads/main/scripts/config/default.yaml" + config_path = "integrationtests/default.yaml" + + print(f"Downloading config from {config_url}...") + with urllib.request.urlopen(config_url) as response: + config_content = response.read().decode('utf-8') + + # Parse YAML + config = yaml.safe_load(config_content) + + # Update values as specified (nested structure) + config['network']['name'] = 'case14_ieee' + config['load']['scenarios'] = 10000 + config['topology_perturbation']['n_topology_variants'] = 2 + + # Write modified config + with open(config_path, 'w') as f: + yaml.dump(config, f, default_flow_style=False, sort_keys=False) + + print(f"✓ Config prepared at {config_path} with:") + print(f" - network.name: {config['network']['name']}") + print(f" - load.scenarios: {config['load']['scenarios']}") + print(f" - topology_perturbation.n_topology_variants: {config['topology_perturbation']['n_topology_variants']}") + + return config_path + def test_prepare_data(): """ gridfm-datakit must be installable via pip with exit code 0. @@ -33,8 +65,13 @@ def test_prepare_data(): data_dir = "data_out" if not os.path.exists(data_dir) or not os.listdir(data_dir): print("Data directory not found or empty, generating data...") + + # Prepare the config file + config_path = prepare_config() + + # Generate data using the prepared config execute_and_fail( - 'gridfm_datakit generate default.yaml' + f'gridfm_datakit generate {config_path}' ) else: print(f"Data directory '{data_dir}' already exists, skipping data generation.") From e1e8ae1d3f543ae265fd05145b0d41b072fd186d Mon Sep 17 00:00:00 2001 From: Romeo Kienzler Date: Fri, 13 Mar 2026 10:41:20 +0100 Subject: [PATCH 04/14] add missing doc Signed-off-by: Romeo Kienzler --- docs/index.md | 10 +- docs/tasks/optimal_power_flow.md | 136 ++++++++++++++++++++++++ docs/tasks/power_flow.md | 171 +++++++++++++++++++++++++++++++ docs/tasks/state_estimation.md | 77 ++++++++++++++ mkdocs.yml | 3 + 5 files changed, 396 insertions(+), 1 deletion(-) create mode 100644 docs/tasks/optimal_power_flow.md create mode 100644 docs/tasks/power_flow.md create mode 100644 docs/tasks/state_estimation.md diff --git a/docs/index.md b/docs/index.md index e843631..f465518 100644 --- a/docs/index.md +++ b/docs/index.md @@ -14,4 +14,12 @@ This library is brought to you by the GridFM team to train, finetune and interac -## Citation: TBD +## Citation: +```bibtex +@software{gridfm_graphkit_2024, + author = {Matteo Mazzonelli, Celia Cintas, Alban Puech and others}, + title = {GridFM GraphKit}, + url = {https://github.com/gridfm/gridfm-graphkit}, + year = {2024} +} +``` \ No newline at end of file diff --git a/docs/tasks/optimal_power_flow.md b/docs/tasks/optimal_power_flow.md new file mode 100644 index 0000000..1837e6b --- /dev/null +++ b/docs/tasks/optimal_power_flow.md @@ -0,0 +1,136 @@ +# Optimal Power Flow Task + +The Optimal Power Flow (OPF) task solves the optimization problem of determining the most economical operation of a power system while satisfying physical and operational constraints. This task predicts optimal generator setpoints, voltage profiles, and reactive power dispatch. + +## Overview + +Optimal Power Flow is a fundamental optimization problem in power systems that minimizes generation costs while ensuring: + +- **Power balance**: Supply meets demand at all buses +- **Voltage constraints**: Bus voltages remain within acceptable limits +- **Thermal limits**: Branch flows don't exceed capacity +- **Generator limits**: Active and reactive power generation within bounds +- **Angle difference limits**: Voltage angle differences across branches are acceptable + +The `OptimalPowerFlowTask` extends the `ReconstructionTask` to include OPF-specific physics-based constraints and economic metrics. + +## Key Features + +- **Economic optimization**: Tracks generation costs and optimality gap +- **Constraint violation monitoring**: Measures violations of thermal, voltage, and angle limits +- **Physics-based evaluation**: Computes power balance errors and residuals +- **Bus type differentiation**: Separate metrics for PQ, PV, and REF buses +- **Comprehensive reporting**: Generates detailed CSV reports and correlation plots + +## OptimalPowerFlowTask + +::: gridfm_graphkit.tasks.opf_task.OptimalPowerFlowTask + +## Metrics + +The Optimal Power Flow task computes extensive metrics during testing: + +### Economic Metrics +- **Optimality Gap (%)**: Percentage difference between predicted and optimal generation costs +- **Generation Cost**: Total cost computed from quadratic cost curves (c₀ + c₁·Pg + c₂·Pg²) + +### Power Balance Metrics +- **Active Power Loss (MW)**: Mean absolute active power residual across all buses +- **Reactive Power Loss (MVar)**: Mean absolute reactive power residual across all buses + +### Constraint Violations +- **Branch Thermal Violations (MVA)**: + - Forward direction: Mean excess flow above thermal limits + - Reverse direction: Mean excess flow above thermal limits +- **Branch Angle Violations (radians)**: Mean violation of angle difference constraints +- **Reactive Power Violations**: + - PV buses: Mean Qg violation (exceeding min/max limits) + - REF buses: Mean Qg violation (exceeding min/max limits) + +### Prediction Accuracy (RMSE) +Computed separately for each bus type (PQ, PV, REF): +- **Voltage Magnitude (Vm)**: p.u. +- **Voltage Angle (Va)**: radians +- **Active Power Generation (Pg)**: MW +- **Reactive Power Generation (Qg)**: MVar + +### Residual Statistics (when verbose=True) +For each bus type and power type (P, Q): +- Mean residual per graph +- Maximum residual per graph + +## Bus Types + +The task evaluates performance separately for three bus types: + +- **PQ Buses**: Load buses with specified active and reactive power demand +- **PV Buses**: Generator buses with specified active power and voltage magnitude +- **REF Buses**: Reference/slack buses that balance the system + +## Outputs + +### CSV Reports +Two CSV files are generated per test dataset: + +1. **`{dataset}_RMSE.csv`**: RMSE metrics by bus type + - Columns: Metric, Pg (MW), Qg (MVar), Vm (p.u.), Va (radians) + - Rows: RMSE-PQ, RMSE-PV, RMSE-REF + +2. **`{dataset}_metrics.csv`**: Comprehensive metrics including: + - Average active/reactive residuals + - RMSE for generator active power + - Mean optimality gap + - Branch thermal violations (from/to) + - Branch angle difference violations + - Qg violations for PV and REF buses + +### Visualizations (when verbose=True) + +1. **Cost Correlation Plot**: Predicted vs. ground truth generation costs with correlation coefficient +2. **Residual Histograms**: Distribution of power balance residuals by bus type +3. **Feature Correlation Plots**: Predictions vs. targets for Vm, Va, Pg, Qg by bus type, including Qg violation highlighting + +## Configuration Example + +```yaml +task: + name: OptimalPowerFlow + verbose: true + +training: + batch_size: 32 + epochs: 100 + losses: ["MaskedMSE", "PBE"] + loss_weights: [0.01, 0.99] + +optimizer: + name: Adam + lr: 0.001 +``` + +## Physics-Based Constraints + +The task uses specialized layers to compute physical quantities: + +- **`ComputeBranchFlow`**: Calculates active (Pft) and reactive (Qft) power flows on branches +- **`ComputeNodeInjection`**: Aggregates branch flows to compute net injections at buses +- **`ComputeNodeResiduals`**: Computes power balance violations (residuals) + +These ensure predictions are evaluated not just on accuracy but also on physical feasibility. + +## Usage + +The Optimal Power Flow task is automatically selected when you specify `task.name: OptimalPowerFlow` in your YAML configuration file. The task: + +1. Performs forward pass through the model +2. Inverse normalizes predictions and targets +3. Computes branch flows and power balance residuals +4. Evaluates constraint violations +5. Calculates economic metrics (costs, optimality gap) +6. Generates comprehensive reports and visualizations + +## Related + +- [Power Flow Task](power_flow.md): For standard power flow analysis without optimization +- [State Estimation Task](state_estimation.md): For state estimation from measurements +- [Feature Reconstruction](feature_reconstruction.md): Base reconstruction task \ No newline at end of file diff --git a/docs/tasks/power_flow.md b/docs/tasks/power_flow.md new file mode 100644 index 0000000..606bcc0 --- /dev/null +++ b/docs/tasks/power_flow.md @@ -0,0 +1,171 @@ +# Power Flow Task + +The Power Flow task solves the fundamental problem of determining the steady-state operating conditions of a power system. Given load demands and generator setpoints, it computes voltage magnitudes, voltage angles, and power flows throughout the network. + +## Overview + +Power Flow (also known as Load Flow) analysis is essential for power system planning and operation. It determines: + +- **Voltage profiles**: Magnitude and angle at each bus +- **Power flows**: Active and reactive power on transmission lines +- **Power injections**: Net power generation/consumption at buses +- **System losses**: Total active and reactive power losses + +The `PowerFlowTask` extends the `ReconstructionTask` to include physics-based power balance evaluation and comprehensive metrics for different bus types. + +## Key Features + +- **Physics-based validation**: Computes power balance errors (PBE) to verify physical consistency +- **Bus type differentiation**: Separate metrics for PQ, PV, and REF buses +- **Distributed training support**: Handles multi-GPU training with proper metric aggregation +- **Detailed predictions**: Provides per-bus predictions with residuals for analysis +- **Comprehensive reporting**: Generates CSV reports and correlation plots + +## PowerFlowTask + +::: gridfm_graphkit.tasks.pf_task.PowerFlowTask + +## Metrics + +The Power Flow task computes the following metrics during testing: + +### Power Balance Metrics +- **Active Power Loss (MW)**: Mean absolute active power residual across all buses +- **Reactive Power Loss (MVar)**: Mean absolute reactive power residual across all buses +- **PBE Mean**: Mean Power Balance Error magnitude across all buses (√(P² + Q²)) +- **PBE Max**: Maximum Power Balance Error across all buses + +### Prediction Accuracy (RMSE) +Computed separately for each bus type (PQ, PV, REF): +- **Voltage Magnitude (Vm)**: p.u. +- **Voltage Angle (Va)**: radians +- **Active Power Generation (Pg)**: MW +- **Reactive Power Generation (Qg)**: MVar + +### Residual Statistics (when verbose=True) +For each bus type (PQ, PV, REF) and power type (P, Q): +- Mean residual per graph +- Maximum residual per graph + +## Bus Types + +The task evaluates performance separately for three bus types: + +- **PQ Buses**: Load buses with specified active and reactive power demand +- **PV Buses**: Generator buses with specified active power and voltage magnitude +- **REF Buses**: Reference/slack buses that balance the system + +## Power Balance Error (PBE) + +The Power Balance Error is a critical metric that measures how well predictions satisfy Kirchhoff's laws: + +$$ +\text{PBE} = \sqrt{(\Delta P)^2 + (\Delta Q)^2} +$$ + +where: +- $\Delta P$ = Active power residual (generation - demand - losses) +- $\Delta Q$ = Reactive power residual (generation - demand - losses) + +Lower PBE values indicate better physical consistency of the predictions. + +## Outputs + +### CSV Reports +Two CSV files are generated per test dataset: + +1. **`{dataset}_RMSE.csv`**: RMSE metrics by bus type + - Columns: Metric, Pg (MW), Qg (MVar), Vm (p.u.), Va (radians) + - Rows: RMSE-PQ, RMSE-PV, RMSE-REF + +2. **`{dataset}_metrics.csv`**: Power balance metrics + - Avg. active res. (MW) + - Avg. reactive res. (MVar) + - PBE Mean + - PBE Max + +### Visualizations (when verbose=True) + +1. **Residual Histograms**: Distribution of power balance residuals by bus type (PQ, PV, REF) +2. **Feature Correlation Plots**: Predictions vs. targets for Vm, Va, Pg, Qg by bus type + +### Prediction Output + +The `predict_step` method returns detailed per-bus information: + +```python +{ + 'scenario': scenario IDs, + 'bus': bus indices, + 'pd_mw': active power demand, + 'qd_mvar': reactive power demand, + 'vm_pu_target': target voltage magnitude, + 'va_target': target voltage angle, + 'pg_mw_target': target active power generation, + 'qg_mvar_target': target reactive power generation, + 'is_pq': PQ bus indicator, + 'is_pv': PV bus indicator, + 'is_ref': REF bus indicator, + 'vm_pu': predicted voltage magnitude, + 'va': predicted voltage angle, + 'pg_mw': predicted active power generation, + 'qg_mvar': predicted reactive power generation, + 'active res. (MW)': active power residual, + 'reactive res. (MVar)': reactive power residual, + 'PBE': power balance error magnitude +} +``` + +## Configuration Example + +```yaml +task: + name: PowerFlow + verbose: true + +training: + batch_size: 32 + epochs: 100 + losses: ["MaskedMSE", "PBE"] + loss_weights: [0.01, 0.99] + +optimizer: + name: Adam + lr: 0.001 +``` + +## Physics-Based Constraints + +The task uses specialized layers to compute physical quantities: + +- **`ComputeBranchFlow`**: Calculates active (Pft) and reactive (Qft) power flows on branches using the power flow equations +- **`ComputeNodeInjection`**: Aggregates branch flows to compute net power injections at each bus +- **`ComputeNodeResiduals`**: Computes power balance violations by comparing injections with generation and demand + +These layers ensure that predictions are evaluated not only on accuracy but also on their adherence to fundamental power system physics. + +## Distributed Training + +The PowerFlowTask includes special handling for distributed training: + +- **Metric aggregation**: Uses `sync_dist=True` to properly aggregate metrics across GPUs +- **Verbose output gathering**: Collects test outputs from all ranks to rank 0 for complete visualization +- **Max reduction for PBE Max**: Uses `reduce_fx="max"` to find the global maximum PBE across all processes + +## Usage + +The Power Flow task is automatically selected when you specify `task.name: PowerFlow` in your YAML configuration file. The task: + +1. Performs forward pass through the model +2. Inverse normalizes predictions and targets +3. Computes branch flows using power flow equations +4. Calculates power balance residuals and PBE +5. Evaluates metrics separately for each bus type +6. Generates comprehensive reports and visualizations +7. Provides detailed per-bus predictions for analysis + +## Related + +- [Optimal Power Flow Task](optimal_power_flow.md): For optimization-based power flow with economic objectives +- [State Estimation Task](state_estimation.md): For state estimation from noisy measurements +- [Feature Reconstruction](feature_reconstruction.md): Base reconstruction task \ No newline at end of file diff --git a/docs/tasks/state_estimation.md b/docs/tasks/state_estimation.md new file mode 100644 index 0000000..46953ae --- /dev/null +++ b/docs/tasks/state_estimation.md @@ -0,0 +1,77 @@ +# State Estimation Task + +The State Estimation task focuses on estimating the true state of a power grid from noisy measurements. This is a critical problem in power system operations, where sensor measurements may contain errors, outliers, or missing data. + +## Overview + +State estimation in power systems involves determining voltage magnitudes, voltage angles, and power injections at buses from available measurements. The `StateEstimationTask` extends the `ReconstructionTask` to handle: + +- **Noisy measurements**: Input features include measurement noise and potential outliers +- **Missing data**: Some measurements may be masked or unavailable +- **Outlier detection**: The task tracks and evaluates performance on outlier measurements separately + +## Key Features + +- **Measurement-based prediction**: Estimates true grid state from noisy sensor data +- **Outlier handling**: Distinguishes between normal measurements, masked values, and outliers +- **Correlation analysis**: Generates plots comparing predictions vs. targets and predictions vs. measurements +- **Multi-mask evaluation**: Evaluates performance separately for outliers, masked values, and clean measurements + +## StateEstimationTask + +::: gridfm_graphkit.tasks.se_task.StateEstimationTask + +## Metrics + +The State Estimation task computes and logs the following metrics during testing: + +### Prediction Quality +- **Voltage Magnitude (Vm)**: Accuracy of estimated voltage magnitudes at buses +- **Voltage Angle (Va)**: Accuracy of estimated voltage angles at buses +- **Active Power Injection (Pg)**: Accuracy of estimated active power at buses +- **Reactive Power Injection (Qg)**: Accuracy of estimated reactive power at buses + +### Evaluation Categories +Metrics are computed separately for three categories: +- **Outliers**: Measurements identified as outliers +- **Masked**: Intentionally masked/missing measurements +- **Non-outliers**: Clean measurements without outliers or masking + +## Visualization + +When `verbose=True` in the configuration, the task generates correlation plots: + +1. **Predictions vs. Targets**: Shows how well predictions match ground truth +2. **Predictions vs. Measurements**: Shows how predictions compare to noisy input measurements +3. **Measurements vs. Targets**: Shows the quality of input measurements + +These plots are generated for each feature (Vm, Va, Pg, Qg) and saved to the test artifacts directory. + +## Configuration Example + +```yaml +task: + name: StateEstimation + verbose: true + +training: + batch_size: 32 + epochs: 100 + losses: ["MaskedMSE"] + loss_weights: [1.0] +``` + +## Usage + +The State Estimation task is automatically selected when you specify `task.name: StateEstimation` in your YAML configuration file. The task handles: + +1. Forward pass through the model with masked/noisy inputs +2. Inverse normalization of predictions and targets +3. Computation of metrics for different measurement categories +4. Generation of correlation plots and analysis + +## Related + +- [Power Flow Task](power_flow.md): For standard power flow analysis +- [Optimal Power Flow Task](optimal_power_flow.md): For optimization-based power flow +- [Feature Reconstruction](feature_reconstruction.md): Base reconstruction task \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index afc3359..7c78cce 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -20,6 +20,9 @@ nav: - Transforms: datasets/transforms.md - Tasks: - Feature Reconstruction: tasks/feature_reconstruction.md + - Power Flow: tasks/power_flow.md + - Optimal Power Flow: tasks/optimal_power_flow.md + - State Estimation: tasks/state_estimation.md - Models: models/models.md - Training: - Losses: training/loss.md From 129e99decc70ce24972cfd9d18cceab27c903bb0 Mon Sep 17 00:00:00 2001 From: Romeo Kienzler Date: Fri, 13 Mar 2026 18:16:18 +0100 Subject: [PATCH 05/14] improve test --- integrationtests/test_base_set.py | 50 +++++++++++++++++++++---------- 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/integrationtests/test_base_set.py b/integrationtests/test_base_set.py index d43ed86..1d1eb33 100644 --- a/integrationtests/test_base_set.py +++ b/integrationtests/test_base_set.py @@ -6,20 +6,14 @@ import yaml import urllib.request -def execute_and_fail(cmd) -> None: - """ - Execute a CLI command and fail in case return code is not 0. - """ +def execute_and_live_output(cmd) -> None: + # Remove capture_output=True + # We use check=True to raise an exception automatically if returncode != 0 result = subprocess.run( cmd, - capture_output=True, text=True, shell=True, - ) - assert result.returncode == 0, ( - f"{cmd} failed (exit {result.returncode}).\n" - f"stdout:\n{result.stdout}\n" - f"stderr:\n{result.stderr}" + check=True ) def prepare_config(): @@ -52,6 +46,29 @@ def prepare_config(): return config_path +def prepare_training_config(): + """ + Modify the training config to set epochs to 2 for testing. + """ + config_path = "examples/config/HGNS_PF_datakit_case14.yaml" + + # Read the config + with open(config_path, 'r') as f: + config = yaml.safe_load(f) + + # Ensure epochs is set to 2 + if 'training' not in config: + config['training'] = {} + config['training']['epochs'] = 2 + + # Write back the modified config + with open(config_path, 'w') as f: + yaml.dump(config, f, default_flow_style=False, sort_keys=False) + + print(f"Training config updated: epochs set to {config['training']['epochs']}") + + return config_path + def test_prepare_data(): """ gridfm-datakit must be installable via pip with exit code 0. @@ -70,14 +87,17 @@ def test_prepare_data(): config_path = prepare_config() # Generate data using the prepared config - execute_and_fail( + execute_and_live_output( f'gridfm_datakit generate {config_path}' ) else: print(f"Data directory '{data_dir}' already exists, skipping data generation.") - execute_and_fail( - 'gridfm_graphkit train --config examples/config/HGNS_PF_datakit_case14.yaml --data_path data_out/ --exp_name exp1 --run_name run1 --log_dir logs' + # Prepare training config with epochs=2 + training_config_path = prepare_training_config() + + execute_and_live_output( + f'gridfm_graphkit train --config {training_config_path} --data_path data_out/ --exp_name exp1 --run_name run1 --log_dir logs' ) # Find the latest log directory @@ -104,10 +124,10 @@ def test_prepare_data(): pbe_mean_value = float(pbe_mean_row.iloc[0]['Value']) assert 1.1 <= pbe_mean_value <= 2.9, ( - f"PBE Mean value {pbe_mean_value} is outside acceptable range [1.4, 1.6]" + f"PBE Mean value {pbe_mean_value} is outside acceptable range [1.1, 2.9]" ) - print(f"✓ PBE Mean value {pbe_mean_value} is within acceptable range [1.4, 1.6]") + print(f"PBE Mean value {pbe_mean_value} is within acceptable range [1.1, 2.9]") From c78d64db770e449526cd81c717eaf7d23f101375 Mon Sep 17 00:00:00 2001 From: Romeo Kienzler Date: Sat, 14 Mar 2026 13:10:42 +0100 Subject: [PATCH 06/14] fix docs --- docs/index.md | 10 +- docs/tasks/base_task.md | 216 ++++++++++++++++++++ docs/tasks/feature_reconstruction.md | 186 +++++++++++++++-- docs/tasks/optimal_power_flow.md | 4 +- docs/tasks/power_flow.md | 4 +- docs/tasks/reconstruction_task.md | 293 +++++++++++++++++++++++++++ docs/tasks/state_estimation.md | 4 +- docs/training/loss.md | 12 -- mkdocs.yml | 4 +- 9 files changed, 693 insertions(+), 40 deletions(-) create mode 100644 docs/tasks/base_task.md create mode 100644 docs/tasks/reconstruction_task.md diff --git a/docs/index.md b/docs/index.md index f465518..e38d000 100644 --- a/docs/index.md +++ b/docs/index.md @@ -14,12 +14,4 @@ This library is brought to you by the GridFM team to train, finetune and interac -## Citation: -```bibtex -@software{gridfm_graphkit_2024, - author = {Matteo Mazzonelli, Celia Cintas, Alban Puech and others}, - title = {GridFM GraphKit}, - url = {https://github.com/gridfm/gridfm-graphkit}, - year = {2024} -} -``` \ No newline at end of file +## Citation: TBD \ No newline at end of file diff --git a/docs/tasks/base_task.md b/docs/tasks/base_task.md new file mode 100644 index 0000000..a0acbae --- /dev/null +++ b/docs/tasks/base_task.md @@ -0,0 +1,216 @@ +# Base Task + +The `BaseTask` class is an abstract base class that provides the foundation for all task implementations in GridFM-GraphKit. It extends PyTorch Lightning's `LightningModule` and defines the common interface and shared functionality for training, validation, and testing. + +## Overview + +`BaseTask` serves as the parent class for all task-specific implementations, providing: + +- **Abstract method definitions**: Enforces implementation of core methods in subclasses +- **Optimizer configuration**: Sets up AdamW optimizer with learning rate scheduling +- **Normalization statistics logging**: Saves normalization parameters for reproducibility +- **Hyperparameter management**: Automatically saves hyperparameters for experiment tracking + +## BaseTask Class + +::: gridfm_graphkit.tasks.base_task.BaseTask + options: + show_root_heading: true + show_source: true + members: + - __init__ + - forward + - training_step + - validation_step + - test_step + - predict_step + - on_fit_start + - configure_optimizers + +## Methods + +### `__init__(args, data_normalizers)` + +Initialize the base task with configuration and normalizers. + +**Parameters:** + +- `args` (NestedNamespace): Experiment configuration containing all hyperparameters +- `data_normalizers` (list): List of normalizer objects, one per dataset + +**Attributes Set:** + +- `self.args`: Stores the configuration +- `self.data_normalizers`: Stores the normalizers +- Automatically calls `save_hyperparameters()` for experiment tracking + +--- + +### `forward(*args, **kwargs)` (Abstract) + +Defines the forward pass through the model. Must be implemented by subclasses. + +**Returns:** + +- Model output (structure depends on task implementation) + +--- + +### `training_step(batch)` (Abstract) + +Executes one training step. Must be implemented by subclasses. + +**Parameters:** + +- `batch`: A batch of data from the training dataloader + +**Returns:** + +- Loss tensor for backpropagation + +--- + +### `validation_step(batch, batch_idx)` (Abstract) + +Executes one validation step. Must be implemented by subclasses. + +**Parameters:** + +- `batch`: A batch of data from the validation dataloader +- `batch_idx` (int): Index of the current batch + +**Returns:** + +- Loss tensor or metrics dictionary + +--- + +### `test_step(batch, batch_idx, dataloader_idx=0)` (Abstract) + +Executes one test step. Must be implemented by subclasses. + +**Parameters:** + +- `batch`: A batch of data from the test dataloader +- `batch_idx` (int): Index of the current batch +- `dataloader_idx` (int): Index of the dataloader (for multiple test datasets) + +**Returns:** + +- Metrics dictionary or None + +--- + +### `predict_step(batch, batch_idx, dataloader_idx=0)` (Abstract) + +Executes one prediction step. Must be implemented by subclasses. + +**Parameters:** + +- `batch`: A batch of data from the prediction dataloader +- `batch_idx` (int): Index of the current batch +- `dataloader_idx` (int): Index of the dataloader + +**Returns:** + +- Predictions dictionary + +--- + +### `on_fit_start()` + +Called at the beginning of training. Saves normalization statistics to disk. + +**Behavior:** + +- Creates a `stats` directory in the logging directory +- Saves human-readable normalization statistics to `normalization_stats.txt` +- Saves machine-loadable statistics to `normalizer_stats.pt` (PyTorch format) +- Only executes on rank 0 in distributed training (via `@rank_zero_only` decorator) + +**Output Files:** + +1. **`normalization_stats.txt`**: Human-readable text file with statistics for each dataset +2. **`normalizer_stats.pt`**: PyTorch file containing a dictionary keyed by network name + +--- + +### `configure_optimizers()` + +Configures the optimizer and learning rate scheduler. + +**Optimizer:** + +- **Type**: AdamW +- **Learning Rate**: From `args.optimizer.learning_rate` +- **Betas**: From `args.optimizer.beta1` and `args.optimizer.beta2` + +**Scheduler:** + +- **Type**: ReduceLROnPlateau +- **Mode**: Minimize +- **Factor**: From `args.optimizer.lr_decay` +- **Patience**: From `args.optimizer.lr_patience` +- **Monitored Metric**: "Validation loss" + +**Returns:** + +- Dictionary with optimizer and lr_scheduler configuration + +## Usage + +`BaseTask` is not used directly. Instead, create a subclass that implements all abstract methods: + +```python +from gridfm_graphkit.tasks.base_task import BaseTask + +class MyCustomTask(BaseTask): + def __init__(self, args, data_normalizers): + super().__init__(args, data_normalizers) + # Initialize task-specific components + + def forward(self, x_dict, edge_index_dict, edge_attr_dict, mask_dict): + # Implement forward pass + pass + + def training_step(self, batch): + # Implement training logic + pass + + def validation_step(self, batch, batch_idx): + # Implement validation logic + pass + + def test_step(self, batch, batch_idx, dataloader_idx=0): + # Implement test logic + pass + + def predict_step(self, batch, batch_idx, dataloader_idx=0): + # Implement prediction logic + pass +``` + +## Configuration Example + +The base task uses the following configuration sections: + +```yaml +optimizer: + learning_rate: 0.001 + beta1: 0.9 + beta2: 0.999 + lr_decay: 0.7 + lr_patience: 5 + +data: + networks: + - case14_ieee + - case118_ieee +``` + +## Related + +- [Reconstruction Task](reconstruction_task.md): Base class for reconstruction tasks +- [Power Flow Task](power_flow.md): Concrete implementation for power flow +- [Optimal Power Flow Task](optimal_power_flow.md): Concrete implementation for OPF +- [State Estimation Task](state_estimation.md): Concrete implementation for state estimation \ No newline at end of file diff --git a/docs/tasks/feature_reconstruction.md b/docs/tasks/feature_reconstruction.md index 99a0321..6356c82 100644 --- a/docs/tasks/feature_reconstruction.md +++ b/docs/tasks/feature_reconstruction.md @@ -1,29 +1,185 @@ -# Reconstruction Tasks +# Task Classes Overview -## Base Task +GridFM-GraphKit provides a hierarchical task system for power grid analysis. All tasks inherit from a common base class and share core functionality while implementing domain-specific logic. -::: gridfm_graphkit.tasks.base_task.BaseTask +## Task Hierarchy ---- +``` +BaseTask (Abstract) + └── ReconstructionTask + ├── PowerFlowTask + ├── OptimalPowerFlowTask + └── StateEstimationTask +``` -## Reconstruction Task +## Available Task Classes -::: gridfm_graphkit.tasks.reconstruction_tasks.ReconstructionTask +### Base Classes ---- +- **[BaseTask](base_task.md)**: Abstract base class providing common functionality for all tasks + - Optimizer configuration + - Learning rate scheduling + - Normalization statistics logging + - Abstract method definitions -## Optimal Power Flow Task +- **[ReconstructionTask](reconstruction_task.md)**: Base class for feature reconstruction tasks + - Model integration + - Loss function handling + - Shared training/validation logic + - Test output management -::: gridfm_graphkit.tasks.opf_task.OptimalPowerFlowTask +### Concrete Task Implementations ---- +- **[PowerFlowTask](power_flow.md)**: Power flow analysis + - Computes voltage profiles and power flows + - Physics-based validation with Power Balance Error (PBE) + - Separate metrics for PQ, PV, and REF buses + - Detailed per-bus predictions -## Power Flow Task +- **[OptimalPowerFlowTask](optimal_power_flow.md)**: Optimal power flow with economic optimization + - Minimizes generation costs + - Tracks optimality gap + - Monitors constraint violations (thermal, voltage, angle) + - Evaluates reactive power limits -::: gridfm_graphkit.tasks.pf_task.PowerFlowTask +- **[StateEstimationTask](state_estimation.md)**: State estimation from noisy measurements + - Handles measurement noise and outliers + - Separate evaluation for outliers, masked values, and clean measurements + - Correlation analysis between predictions, measurements, and targets ---- +## Quick Reference -## State Estimation Task +### Method Overview -::: gridfm_graphkit.tasks.se_task.StateEstimationTask +All task classes implement the following core methods: + +| Method | Purpose | Implemented In | +|--------|---------|----------------| +| `__init__` | Initialize task with config and normalizers | All classes | +| `forward` | Forward pass through model | ReconstructionTask+ | +| `training_step` | Execute one training step | ReconstructionTask+ | +| `validation_step` | Execute one validation step | ReconstructionTask+ | +| `test_step` | Execute one test step | Concrete tasks | +| `predict_step` | Execute one prediction step | Concrete tasks | +| `on_fit_start` | Save normalization stats before training | BaseTask | +| `on_test_end` | Generate reports and plots after testing | Concrete tasks | +| `configure_optimizers` | Setup optimizer and scheduler | BaseTask | + +### Task Selection + +Tasks are automatically selected based on your YAML configuration: + +```yaml +task: + task_name: PowerFlow # or OptimalPowerFlow, StateEstimation +``` + +The task registry automatically instantiates the correct task class based on the `task_name` field. + +## Common Features + +All tasks share these features: + +### 1. Distributed Training Support +- Multi-GPU training with proper metric synchronization +- Rank 0 handles logging and file I/O +- Automatic gathering of test outputs across ranks + +### 2. Comprehensive Logging +- Training and validation metrics logged to MLflow or TensorBoard +- Automatic hyperparameter tracking +- Normalization statistics saved for reproducibility + +### 3. Test Outputs +- CSV reports with detailed metrics +- Visualization plots (when `verbose=True`) +- Per-dataset analysis for multiple test sets + +### 4. Physics-Based Evaluation +- Power balance error computation +- Branch flow calculations +- Residual analysis by bus type + +## Configuration + +### Basic Configuration + +```yaml +task: + task_name: PowerFlow + verbose: true + +training: + batch_size: 64 + epochs: 100 + losses: ["MaskedMSE", "PBE"] + loss_weights: [0.01, 0.99] + +optimizer: + learning_rate: 0.001 + beta1: 0.9 + beta2: 0.999 + lr_decay: 0.7 + lr_patience: 5 +``` + +### Task-Specific Options + +Each task may have additional configuration options. See the individual task documentation for details: + +- [Power Flow Configuration](power_flow.md#configuration-example) +- [Optimal Power Flow Configuration](optimal_power_flow.md#configuration-example) +- [State Estimation Configuration](state_estimation.md#configuration-example) + +## Creating Custom Tasks + +To create a custom task, extend `ReconstructionTask` or `BaseTask`: + +```python +from gridfm_graphkit.tasks.reconstruction_tasks import ReconstructionTask +from gridfm_graphkit.io.registries import TASK_REGISTRY + +@TASK_REGISTRY.register("MyCustomTask") +class MyCustomTask(ReconstructionTask): + def __init__(self, args, data_normalizers): + super().__init__(args, data_normalizers) + # Add custom initialization + + def test_step(self, batch, batch_idx, dataloader_idx=0): + # Implement custom test logic + output, loss_dict = self.shared_step(batch) + + # Add custom metrics + custom_metric = self.compute_custom_metric(output, batch) + loss_dict["Custom Metric"] = custom_metric + + # Log metrics + for metric, value in loss_dict.items(): + self.log(f"{dataset_name}/{metric}", value) + + return loss_dict["loss"] + + def predict_step(self, batch, batch_idx, dataloader_idx=0): + # Implement custom prediction logic + output, _ = self.shared_step(batch) + return {"predictions": output} + + def on_test_end(self): + # Custom analysis and visualization + # Generate reports, plots, etc. + super().on_test_end() +``` + +Then use it in your configuration: + +```yaml +task: + task_name: MyCustomTask +``` + +## Related Documentation + +- [Loss Functions](../training/loss.md): Available loss functions and their configuration +- [Data Modules](../datasets/data_modules.md): Data loading and preprocessing +- [Models](../models/models.md): Available model architectures +- [Quick Start Guide](../quick_start/quick_start.md): Getting started with training diff --git a/docs/tasks/optimal_power_flow.md b/docs/tasks/optimal_power_flow.md index 1837e6b..1c5e955 100644 --- a/docs/tasks/optimal_power_flow.md +++ b/docs/tasks/optimal_power_flow.md @@ -131,6 +131,8 @@ The Optimal Power Flow task is automatically selected when you specify `task.nam ## Related +- [Base Task](base_task.md): Abstract base class for all tasks +- [Reconstruction Task](reconstruction_task.md): Base class for reconstruction tasks - [Power Flow Task](power_flow.md): For standard power flow analysis without optimization - [State Estimation Task](state_estimation.md): For state estimation from measurements -- [Feature Reconstruction](feature_reconstruction.md): Base reconstruction task \ No newline at end of file +- [Task Overview](feature_reconstruction.md): Overview of all task classes \ No newline at end of file diff --git a/docs/tasks/power_flow.md b/docs/tasks/power_flow.md index 606bcc0..b3a21c3 100644 --- a/docs/tasks/power_flow.md +++ b/docs/tasks/power_flow.md @@ -166,6 +166,8 @@ The Power Flow task is automatically selected when you specify `task.name: Power ## Related +- [Base Task](base_task.md): Abstract base class for all tasks +- [Reconstruction Task](reconstruction_task.md): Base class for reconstruction tasks - [Optimal Power Flow Task](optimal_power_flow.md): For optimization-based power flow with economic objectives - [State Estimation Task](state_estimation.md): For state estimation from noisy measurements -- [Feature Reconstruction](feature_reconstruction.md): Base reconstruction task \ No newline at end of file +- [Task Overview](feature_reconstruction.md): Overview of all task classes \ No newline at end of file diff --git a/docs/tasks/reconstruction_task.md b/docs/tasks/reconstruction_task.md new file mode 100644 index 0000000..28bd8aa --- /dev/null +++ b/docs/tasks/reconstruction_task.md @@ -0,0 +1,293 @@ +# Reconstruction Task + +The `ReconstructionTask` class is a concrete implementation of `BaseTask` that provides the foundation for node feature reconstruction on power grid graphs. It wraps a GridFM model and defines the training, validation, and testing logic for reconstructing masked node features. + +## Overview + +`ReconstructionTask` serves as the base class for all reconstruction-based tasks in GridFM-GraphKit, including: + +- Power Flow (PF) +- Optimal Power Flow (OPF) +- State Estimation (SE) + +It provides: + +- **Model integration**: Loads and wraps the GridFM model +- **Loss function handling**: Configures and applies loss functions +- **Shared training logic**: Common training and validation steps +- **Test output management**: Collects and manages test outputs for analysis + +## ReconstructionTask Class + +::: gridfm_graphkit.tasks.reconstruction_tasks.ReconstructionTask + options: + show_root_heading: true + show_source: true + members: + - __init__ + - forward + - shared_step + - training_step + - validation_step + - on_test_end + +## Methods + +### `__init__(args, data_normalizers)` + +Initialize the reconstruction task with model, loss function, and configuration. + +**Parameters:** + +- `args` (NestedNamespace): Experiment configuration with fields like: + - `training.batch_size`: Batch size for training + - `optimizer.*`: Optimizer configuration + - `model.*`: Model architecture configuration + - `training.losses`: List of loss functions to use + - `data.networks`: List of network names +- `data_normalizers` (list): One normalizer per dataset for feature normalization/denormalization + +**Attributes Set:** + +- `self.model`: GridFM model loaded via `load_model()` +- `self.loss_fn`: Loss function resolved from configuration via `get_loss_function()` +- `self.batch_size`: Training batch size +- `self.test_outputs`: Dictionary to store test outputs per dataset (keyed by dataloader index) + +**Example:** + +```python +task = ReconstructionTask(args, data_normalizers) +``` + +--- + +### `forward(x_dict, edge_index_dict, edge_attr_dict, mask_dict)` + +Forward pass through the model. + +**Parameters:** + +- `x_dict` (dict): Node features dictionary with keys like `"bus"`, `"gen"` +- `edge_index_dict` (dict): Edge indices dictionary for heterogeneous edges +- `edge_attr_dict` (dict): Edge attributes dictionary +- `mask_dict` (dict): Masking dictionary indicating which features are masked + +**Returns:** + +- Model output dictionary with predicted node features + +**Example:** + +```python +output = task.forward( + x_dict=batch.x_dict, + edge_index_dict=batch.edge_index_dict, + edge_attr_dict=batch.edge_attr_dict, + mask_dict=batch.mask_dict +) +``` + +--- + +### `shared_step(batch)` + +Common logic for training and validation steps. + +**Parameters:** + +- `batch`: A batch from the dataloader containing: + - `x_dict`: Input node features + - `y_dict`: Target node features + - `edge_index_dict`: Edge connectivity + - `edge_attr_dict`: Edge attributes + - `mask_dict`: Feature masks + +**Returns:** + +- `output` (dict): Model predictions +- `loss_dict` (dict): Dictionary containing: + - `"loss"`: Total loss value + - Additional loss components (if applicable) + +**Behavior:** + +1. Performs forward pass through the model +2. Computes loss using the configured loss function +3. Returns both predictions and loss dictionary + +**Example:** + +```python +output, loss_dict = task.shared_step(batch) +total_loss = loss_dict["loss"] +``` + +--- + +### `training_step(batch)` + +Execute one training step. + +**Parameters:** + +- `batch`: Training batch from dataloader + +**Returns:** + +- Loss tensor for backpropagation + +**Logged Metrics:** + +- `"Training Loss"`: Total training loss +- `"Learning Rate"`: Current learning rate + +**Logging Configuration:** + +- `batch_size`: Number of graphs in batch +- `sync_dist=False`: No synchronization across GPUs during training +- `on_epoch=False`: Log per step, not per epoch +- `on_step=True`: Log at each training step +- `prog_bar=False`: Don't show in progress bar +- `logger=True`: Send to logger (e.g., MLflow) + +--- + +### `validation_step(batch, batch_idx)` + +Execute one validation step. + +**Parameters:** + +- `batch`: Validation batch from dataloader +- `batch_idx` (int): Index of the current batch + +**Returns:** + +- Loss tensor + +**Logged Metrics:** + +- `"Validation loss"`: Total validation loss +- Additional loss components (if multiple losses are used) + +**Logging Configuration:** + +- `batch_size`: Number of graphs in batch +- `sync_dist=True`: Synchronize metrics across GPUs +- `on_epoch=True`: Aggregate and log at epoch end +- `on_step=False`: Don't log individual steps +- `logger=True`: Send to logger + +**Note:** The validation loss is monitored by the learning rate scheduler for automatic learning rate reduction. + +--- + +### `on_test_end()` + +Called at the end of testing. Clears stored test outputs. + +**Behavior:** + +- Clears the `self.test_outputs` dictionary +- Only executes on rank 0 in distributed training (via `@rank_zero_only` decorator) +- Subclasses typically override this to add custom analysis, plotting, and CSV generation + +**Note:** This is a minimal implementation. Task-specific subclasses (PowerFlowTask, OptimalPowerFlowTask, StateEstimationTask) override this method to: + +- Generate detailed metrics CSV files +- Create visualization plots +- Save analysis results + +--- + +## Usage + +`ReconstructionTask` can be used directly for simple reconstruction tasks, but is typically subclassed for specific power system tasks: + +```python +from gridfm_graphkit.tasks.reconstruction_tasks import ReconstructionTask + +# Direct usage (simple reconstruction) +task = ReconstructionTask(args, data_normalizers) + +# Or create a subclass for custom behavior +class CustomReconstructionTask(ReconstructionTask): + def test_step(self, batch, batch_idx, dataloader_idx=0): + # Custom test logic + output, loss_dict = self.shared_step(batch) + # Add custom metrics + return loss_dict["loss"] + + def on_test_end(self): + # Custom analysis and visualization + super().on_test_end() +``` + +## Configuration Example + +```yaml +task: + task_name: Reconstruction # Or PowerFlow, OptimalPowerFlow, StateEstimation + +model: + type: GNS_heterogeneous + hidden_size: 48 + num_layers: 12 + attention_head: 8 + +training: + batch_size: 64 + epochs: 100 + losses: + - MaskedMSE + loss_weights: + - 1.0 + +optimizer: + learning_rate: 0.001 + beta1: 0.9 + beta2: 0.999 + lr_decay: 0.7 + lr_patience: 5 +``` + +## Loss Functions + +The reconstruction task supports various loss functions configured via the YAML file: + +- **MaskedMSE**: Mean squared error on masked features only +- **MaskedBusMSE**: MSE specifically for bus node features +- **LayeredWeightedPhysics**: Physics-based loss with layer-wise weighting +- **PBE**: Power Balance Error loss + +Multiple losses can be combined with weights: + +```yaml +training: + losses: + - LayeredWeightedPhysics + - MaskedBusMSE + loss_weights: + - 0.1 + - 0.9 + loss_args: + - base_weight: 0.5 + - {} +``` + +## Subclasses + +The following task classes extend `ReconstructionTask`: + +- **[PowerFlowTask](power_flow.md)**: Adds power flow-specific metrics and physics validation +- **[OptimalPowerFlowTask](optimal_power_flow.md)**: Adds economic optimization metrics and constraint violation tracking +- **[StateEstimationTask](state_estimation.md)**: Adds measurement-based estimation and outlier handling + +## Related + +- [Base Task](base_task.md): Abstract base class for all tasks +- [Power Flow Task](power_flow.md): Power flow analysis implementation +- [Optimal Power Flow Task](optimal_power_flow.md): OPF optimization implementation +- [State Estimation Task](state_estimation.md): State estimation implementation +- [Loss Functions](../training/loss.md): Available loss functions \ No newline at end of file diff --git a/docs/tasks/state_estimation.md b/docs/tasks/state_estimation.md index 46953ae..b293509 100644 --- a/docs/tasks/state_estimation.md +++ b/docs/tasks/state_estimation.md @@ -72,6 +72,8 @@ The State Estimation task is automatically selected when you specify `task.name: ## Related +- [Base Task](base_task.md): Abstract base class for all tasks +- [Reconstruction Task](reconstruction_task.md): Base class for reconstruction tasks - [Power Flow Task](power_flow.md): For standard power flow analysis - [Optimal Power Flow Task](optimal_power_flow.md): For optimization-based power flow -- [Feature Reconstruction](feature_reconstruction.md): Base reconstruction task \ No newline at end of file +- [Task Overview](feature_reconstruction.md): Overview of all task classes \ No newline at end of file diff --git a/docs/training/loss.md b/docs/training/loss.md index 0d08ba3..de56d4b 100644 --- a/docs/training/loss.md +++ b/docs/training/loss.md @@ -8,20 +8,12 @@ ## Mean Squared Error Loss -$$ -\mathcal{L}_{\text{MSE}} = \frac{1}{N} \sum_{i=1}^N (y_i - \hat{y}_i)^2 -$$ - ::: gridfm_graphkit.training.loss.MSELoss --- ## Masked Mean Squared Error Loss -$$ -\mathcal{L}_{\text{MaskedMSE}} = \frac{1}{|M|} \sum_{i \in M} (y_i - \hat{y}_i)^2 -$$ - ::: gridfm_graphkit.training.loss.MaskedMSELoss --- @@ -40,10 +32,6 @@ $$ ## Mixed Loss -$$ -\mathcal{L}_{\text{Mixed}} = \sum_{m=1}^M w_m \cdot \mathcal{L}_m -$$ - ::: gridfm_graphkit.training.loss.MixedLoss --- diff --git a/mkdocs.yml b/mkdocs.yml index 7c78cce..acb1771 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -19,7 +19,9 @@ nav: - Data Modules: datasets/data_modules.md - Transforms: datasets/transforms.md - Tasks: - - Feature Reconstruction: tasks/feature_reconstruction.md + - Overview: tasks/feature_reconstruction.md + - Base Task: tasks/base_task.md + - Reconstruction Task: tasks/reconstruction_task.md - Power Flow: tasks/power_flow.md - Optimal Power Flow: tasks/optimal_power_flow.md - State Estimation: tasks/state_estimation.md From b373e16d3d478d445907dd38987530186b0d8072 Mon Sep 17 00:00:00 2001 From: Romeo Kienzler Date: Sat, 14 Mar 2026 13:44:47 +0100 Subject: [PATCH 07/14] fix ordering --- integrationtests/test_base_set.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integrationtests/test_base_set.py b/integrationtests/test_base_set.py index 1d1eb33..9672979 100644 --- a/integrationtests/test_base_set.py +++ b/integrationtests/test_base_set.py @@ -39,7 +39,7 @@ def prepare_config(): with open(config_path, 'w') as f: yaml.dump(config, f, default_flow_style=False, sort_keys=False) - print(f"✓ Config prepared at {config_path} with:") + print(f"Config prepared at {config_path} with:") print(f" - network.name: {config['network']['name']}") print(f" - load.scenarios: {config['load']['scenarios']}") print(f" - topology_perturbation.n_topology_variants: {config['topology_perturbation']['n_topology_variants']}") @@ -105,7 +105,7 @@ def test_prepare_data(): exp_dirs = glob.glob(os.path.join(log_base, "*")) assert len(exp_dirs) > 0, "No experiment directories found in logs/" - latest_exp_dir = max(exp_dirs, key=os.path.getmtime) + latest_exp_dir = sorted(exp_dirs, key=os.path.getctime)[-1] run_dirs = glob.glob(os.path.join(latest_exp_dir, "*")) assert len(run_dirs) > 0, f"No run directories found in {latest_exp_dir}" From 5bf31db1f1a41a529eab8145ca860d589383a2c2 Mon Sep 17 00:00:00 2001 From: Romeo Kienzler Date: Sat, 14 Mar 2026 14:07:15 +0100 Subject: [PATCH 08/14] add fixure for cleanup --- integrationtests/test_base_set.py | 164 ++++++++++++++++++------------ 1 file changed, 99 insertions(+), 65 deletions(-) diff --git a/integrationtests/test_base_set.py b/integrationtests/test_base_set.py index 9672979..8015830 100644 --- a/integrationtests/test_base_set.py +++ b/integrationtests/test_base_set.py @@ -5,129 +5,163 @@ import pandas as pd import yaml import urllib.request +import shutil + def execute_and_live_output(cmd) -> None: - # Remove capture_output=True - # We use check=True to raise an exception automatically if returncode != 0 result = subprocess.run( cmd, text=True, shell=True, - check=True + check=True ) + def prepare_config(): """ Download default.yaml from gridfm-datakit repo and modify it with test parameters. """ config_url = "https://raw.githubusercontent.com/gridfm/gridfm-datakit/refs/heads/main/scripts/config/default.yaml" config_path = "integrationtests/default.yaml" - + print(f"Downloading config from {config_url}...") with urllib.request.urlopen(config_url) as response: - config_content = response.read().decode('utf-8') - - # Parse YAML + config_content = response.read().decode("utf-8") + config = yaml.safe_load(config_content) - - # Update values as specified (nested structure) - config['network']['name'] = 'case14_ieee' - config['load']['scenarios'] = 10000 - config['topology_perturbation']['n_topology_variants'] = 2 - - # Write modified config - with open(config_path, 'w') as f: + + config["network"]["name"] = "case14_ieee" + config["load"]["scenarios"] = 10000 + config["topology_perturbation"]["n_topology_variants"] = 2 + + with open(config_path, "w") as f: yaml.dump(config, f, default_flow_style=False, sort_keys=False) - + print(f"Config prepared at {config_path} with:") print(f" - network.name: {config['network']['name']}") print(f" - load.scenarios: {config['load']['scenarios']}") - print(f" - topology_perturbation.n_topology_variants: {config['topology_perturbation']['n_topology_variants']}") - + print( + f" - topology_perturbation.n_topology_variants: " + f"{config['topology_perturbation']['n_topology_variants']}" + ) + return config_path + def prepare_training_config(): """ Modify the training config to set epochs to 2 for testing. """ config_path = "examples/config/HGNS_PF_datakit_case14.yaml" - - # Read the config - with open(config_path, 'r') as f: + + with open(config_path, "r") as f: config = yaml.safe_load(f) - - # Ensure epochs is set to 2 - if 'training' not in config: - config['training'] = {} - config['training']['epochs'] = 2 - - # Write back the modified config - with open(config_path, 'w') as f: + + if "training" not in config: + config["training"] = {} + + config["training"]["epochs"] = 2 + + with open(config_path, "w") as f: yaml.dump(config, f, default_flow_style=False, sort_keys=False) - + print(f"Training config updated: epochs set to {config['training']['epochs']}") - + return config_path -def test_prepare_data(): + +@pytest.fixture +def cleanup_test_artifacts(): + """ + Backup modified files and remove generated artifacts after the test. """ - gridfm-datakit must be installable via pip with exit code 0. + training_config = "examples/config/HGNS_PF_datakit_case14.yaml" + backup_config = training_config + ".bak" + + if os.path.exists(training_config): + shutil.copy2(training_config, backup_config) + + yield + + # Restore training config + if os.path.exists(backup_config): + shutil.move(backup_config, training_config) + + # Remove downloaded config + config_file = "integrationtests/default.yaml" + if os.path.exists(config_file): + os.remove(config_file) + + # Remove generated directories + for d in ["data_out", "logs"]: + if os.path.exists(d): + shutil.rmtree(d, ignore_errors=True) - This test explicitly re-runs the install command and asserts that pip - exits successfully, making the install step a first-class test rather - than a silent fixture side-effect. + +def test_prepare_data(cleanup_test_artifacts): + """ + Integration test for gridfm-datakit data generation and gridfm-graphkit training. + + Steps: + 1. Generate power grid data using gridfm-datakit + 2. Train a model using gridfm-graphkit + 3. Validate the PBE Mean metric """ - # Check if data already exists, if not generate it data_dir = "data_out" + if not os.path.exists(data_dir) or not os.listdir(data_dir): print("Data directory not found or empty, generating data...") - - # Prepare the config file + config_path = prepare_config() - - # Generate data using the prepared config + execute_and_live_output( - f'gridfm_datakit generate {config_path}' + f"gridfm_datakit generate {config_path}" ) else: - print(f"Data directory '{data_dir}' already exists, skipping data generation.") - - # Prepare training config with epochs=2 + print(f"Data directory '{data_dir}' already exists, skipping generation.") + training_config_path = prepare_training_config() - + execute_and_live_output( - f'gridfm_graphkit train --config {training_config_path} --data_path data_out/ --exp_name exp1 --run_name run1 --log_dir logs' + f"gridfm_graphkit train " + f"--config {training_config_path} " + f"--data_path data_out/ " + f"--exp_name exp1 " + f"--run_name run1 " + f"--log_dir logs" ) - - # Find the latest log directory + log_base = "logs" + exp_dirs = glob.glob(os.path.join(log_base, "*")) assert len(exp_dirs) > 0, "No experiment directories found in logs/" - + latest_exp_dir = sorted(exp_dirs, key=os.path.getctime)[-1] + run_dirs = glob.glob(os.path.join(latest_exp_dir, "*")) assert len(run_dirs) > 0, f"No run directories found in {latest_exp_dir}" - + latest_run_dir = max(run_dirs, key=os.path.getmtime) - metrics_file = os.path.join(latest_run_dir, "artifacts", "test", "case14_ieee_metrics.csv") - + + metrics_file = os.path.join( + latest_run_dir, + "artifacts", + "test", + "case14_ieee_metrics.csv" + ) + assert os.path.exists(metrics_file), f"Metrics file not found: {metrics_file}" - - # Read the metrics CSV + df = pd.read_csv(metrics_file) - - # Find PBE Mean value - pbe_mean_row = df[df['Metric'] == 'PBE Mean'] + + pbe_mean_row = df[df["Metric"] == "PBE Mean"] assert len(pbe_mean_row) > 0, "PBE Mean metric not found in CSV" - - pbe_mean_value = float(pbe_mean_row.iloc[0]['Value']) - + + pbe_mean_value = float(pbe_mean_row.iloc[0]["Value"]) + assert 1.1 <= pbe_mean_value <= 2.9, ( f"PBE Mean value {pbe_mean_value} is outside acceptable range [1.1, 2.9]" ) - - print(f"PBE Mean value {pbe_mean_value} is within acceptable range [1.1, 2.9]") - - + print(f"PBE Mean value {pbe_mean_value} is within acceptable range [1.1, 2.9]") \ No newline at end of file From c968b595546e007f9f66bba39a362018435ad5d8 Mon Sep 17 00:00:00 2001 From: Romeo Kienzler Date: Sat, 14 Mar 2026 18:25:22 +0100 Subject: [PATCH 09/14] fix test name --- integrationtests/test_base_set.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integrationtests/test_base_set.py b/integrationtests/test_base_set.py index 8015830..e33e11c 100644 --- a/integrationtests/test_base_set.py +++ b/integrationtests/test_base_set.py @@ -98,7 +98,7 @@ def cleanup_test_artifacts(): shutil.rmtree(d, ignore_errors=True) -def test_prepare_data(cleanup_test_artifacts): +def test_train(cleanup_test_artifacts): """ Integration test for gridfm-datakit data generation and gridfm-graphkit training. From 326ae1189156160875d3557206cd9527ce691595 Mon Sep 17 00:00:00 2001 From: Romeo Kienzler Date: Sun, 15 Mar 2026 20:37:19 +0100 Subject: [PATCH 10/14] remove bad doc --- docs/tasks/optimal_power_flow.md | 138 ------------------------ docs/tasks/power_flow.md | 173 ------------------------------- docs/tasks/state_estimation.md | 79 -------------- 3 files changed, 390 deletions(-) delete mode 100644 docs/tasks/optimal_power_flow.md delete mode 100644 docs/tasks/power_flow.md delete mode 100644 docs/tasks/state_estimation.md diff --git a/docs/tasks/optimal_power_flow.md b/docs/tasks/optimal_power_flow.md deleted file mode 100644 index 1c5e955..0000000 --- a/docs/tasks/optimal_power_flow.md +++ /dev/null @@ -1,138 +0,0 @@ -# Optimal Power Flow Task - -The Optimal Power Flow (OPF) task solves the optimization problem of determining the most economical operation of a power system while satisfying physical and operational constraints. This task predicts optimal generator setpoints, voltage profiles, and reactive power dispatch. - -## Overview - -Optimal Power Flow is a fundamental optimization problem in power systems that minimizes generation costs while ensuring: - -- **Power balance**: Supply meets demand at all buses -- **Voltage constraints**: Bus voltages remain within acceptable limits -- **Thermal limits**: Branch flows don't exceed capacity -- **Generator limits**: Active and reactive power generation within bounds -- **Angle difference limits**: Voltage angle differences across branches are acceptable - -The `OptimalPowerFlowTask` extends the `ReconstructionTask` to include OPF-specific physics-based constraints and economic metrics. - -## Key Features - -- **Economic optimization**: Tracks generation costs and optimality gap -- **Constraint violation monitoring**: Measures violations of thermal, voltage, and angle limits -- **Physics-based evaluation**: Computes power balance errors and residuals -- **Bus type differentiation**: Separate metrics for PQ, PV, and REF buses -- **Comprehensive reporting**: Generates detailed CSV reports and correlation plots - -## OptimalPowerFlowTask - -::: gridfm_graphkit.tasks.opf_task.OptimalPowerFlowTask - -## Metrics - -The Optimal Power Flow task computes extensive metrics during testing: - -### Economic Metrics -- **Optimality Gap (%)**: Percentage difference between predicted and optimal generation costs -- **Generation Cost**: Total cost computed from quadratic cost curves (c₀ + c₁·Pg + c₂·Pg²) - -### Power Balance Metrics -- **Active Power Loss (MW)**: Mean absolute active power residual across all buses -- **Reactive Power Loss (MVar)**: Mean absolute reactive power residual across all buses - -### Constraint Violations -- **Branch Thermal Violations (MVA)**: - - Forward direction: Mean excess flow above thermal limits - - Reverse direction: Mean excess flow above thermal limits -- **Branch Angle Violations (radians)**: Mean violation of angle difference constraints -- **Reactive Power Violations**: - - PV buses: Mean Qg violation (exceeding min/max limits) - - REF buses: Mean Qg violation (exceeding min/max limits) - -### Prediction Accuracy (RMSE) -Computed separately for each bus type (PQ, PV, REF): -- **Voltage Magnitude (Vm)**: p.u. -- **Voltage Angle (Va)**: radians -- **Active Power Generation (Pg)**: MW -- **Reactive Power Generation (Qg)**: MVar - -### Residual Statistics (when verbose=True) -For each bus type and power type (P, Q): -- Mean residual per graph -- Maximum residual per graph - -## Bus Types - -The task evaluates performance separately for three bus types: - -- **PQ Buses**: Load buses with specified active and reactive power demand -- **PV Buses**: Generator buses with specified active power and voltage magnitude -- **REF Buses**: Reference/slack buses that balance the system - -## Outputs - -### CSV Reports -Two CSV files are generated per test dataset: - -1. **`{dataset}_RMSE.csv`**: RMSE metrics by bus type - - Columns: Metric, Pg (MW), Qg (MVar), Vm (p.u.), Va (radians) - - Rows: RMSE-PQ, RMSE-PV, RMSE-REF - -2. **`{dataset}_metrics.csv`**: Comprehensive metrics including: - - Average active/reactive residuals - - RMSE for generator active power - - Mean optimality gap - - Branch thermal violations (from/to) - - Branch angle difference violations - - Qg violations for PV and REF buses - -### Visualizations (when verbose=True) - -1. **Cost Correlation Plot**: Predicted vs. ground truth generation costs with correlation coefficient -2. **Residual Histograms**: Distribution of power balance residuals by bus type -3. **Feature Correlation Plots**: Predictions vs. targets for Vm, Va, Pg, Qg by bus type, including Qg violation highlighting - -## Configuration Example - -```yaml -task: - name: OptimalPowerFlow - verbose: true - -training: - batch_size: 32 - epochs: 100 - losses: ["MaskedMSE", "PBE"] - loss_weights: [0.01, 0.99] - -optimizer: - name: Adam - lr: 0.001 -``` - -## Physics-Based Constraints - -The task uses specialized layers to compute physical quantities: - -- **`ComputeBranchFlow`**: Calculates active (Pft) and reactive (Qft) power flows on branches -- **`ComputeNodeInjection`**: Aggregates branch flows to compute net injections at buses -- **`ComputeNodeResiduals`**: Computes power balance violations (residuals) - -These ensure predictions are evaluated not just on accuracy but also on physical feasibility. - -## Usage - -The Optimal Power Flow task is automatically selected when you specify `task.name: OptimalPowerFlow` in your YAML configuration file. The task: - -1. Performs forward pass through the model -2. Inverse normalizes predictions and targets -3. Computes branch flows and power balance residuals -4. Evaluates constraint violations -5. Calculates economic metrics (costs, optimality gap) -6. Generates comprehensive reports and visualizations - -## Related - -- [Base Task](base_task.md): Abstract base class for all tasks -- [Reconstruction Task](reconstruction_task.md): Base class for reconstruction tasks -- [Power Flow Task](power_flow.md): For standard power flow analysis without optimization -- [State Estimation Task](state_estimation.md): For state estimation from measurements -- [Task Overview](feature_reconstruction.md): Overview of all task classes \ No newline at end of file diff --git a/docs/tasks/power_flow.md b/docs/tasks/power_flow.md deleted file mode 100644 index b3a21c3..0000000 --- a/docs/tasks/power_flow.md +++ /dev/null @@ -1,173 +0,0 @@ -# Power Flow Task - -The Power Flow task solves the fundamental problem of determining the steady-state operating conditions of a power system. Given load demands and generator setpoints, it computes voltage magnitudes, voltage angles, and power flows throughout the network. - -## Overview - -Power Flow (also known as Load Flow) analysis is essential for power system planning and operation. It determines: - -- **Voltage profiles**: Magnitude and angle at each bus -- **Power flows**: Active and reactive power on transmission lines -- **Power injections**: Net power generation/consumption at buses -- **System losses**: Total active and reactive power losses - -The `PowerFlowTask` extends the `ReconstructionTask` to include physics-based power balance evaluation and comprehensive metrics for different bus types. - -## Key Features - -- **Physics-based validation**: Computes power balance errors (PBE) to verify physical consistency -- **Bus type differentiation**: Separate metrics for PQ, PV, and REF buses -- **Distributed training support**: Handles multi-GPU training with proper metric aggregation -- **Detailed predictions**: Provides per-bus predictions with residuals for analysis -- **Comprehensive reporting**: Generates CSV reports and correlation plots - -## PowerFlowTask - -::: gridfm_graphkit.tasks.pf_task.PowerFlowTask - -## Metrics - -The Power Flow task computes the following metrics during testing: - -### Power Balance Metrics -- **Active Power Loss (MW)**: Mean absolute active power residual across all buses -- **Reactive Power Loss (MVar)**: Mean absolute reactive power residual across all buses -- **PBE Mean**: Mean Power Balance Error magnitude across all buses (√(P² + Q²)) -- **PBE Max**: Maximum Power Balance Error across all buses - -### Prediction Accuracy (RMSE) -Computed separately for each bus type (PQ, PV, REF): -- **Voltage Magnitude (Vm)**: p.u. -- **Voltage Angle (Va)**: radians -- **Active Power Generation (Pg)**: MW -- **Reactive Power Generation (Qg)**: MVar - -### Residual Statistics (when verbose=True) -For each bus type (PQ, PV, REF) and power type (P, Q): -- Mean residual per graph -- Maximum residual per graph - -## Bus Types - -The task evaluates performance separately for three bus types: - -- **PQ Buses**: Load buses with specified active and reactive power demand -- **PV Buses**: Generator buses with specified active power and voltage magnitude -- **REF Buses**: Reference/slack buses that balance the system - -## Power Balance Error (PBE) - -The Power Balance Error is a critical metric that measures how well predictions satisfy Kirchhoff's laws: - -$$ -\text{PBE} = \sqrt{(\Delta P)^2 + (\Delta Q)^2} -$$ - -where: -- $\Delta P$ = Active power residual (generation - demand - losses) -- $\Delta Q$ = Reactive power residual (generation - demand - losses) - -Lower PBE values indicate better physical consistency of the predictions. - -## Outputs - -### CSV Reports -Two CSV files are generated per test dataset: - -1. **`{dataset}_RMSE.csv`**: RMSE metrics by bus type - - Columns: Metric, Pg (MW), Qg (MVar), Vm (p.u.), Va (radians) - - Rows: RMSE-PQ, RMSE-PV, RMSE-REF - -2. **`{dataset}_metrics.csv`**: Power balance metrics - - Avg. active res. (MW) - - Avg. reactive res. (MVar) - - PBE Mean - - PBE Max - -### Visualizations (when verbose=True) - -1. **Residual Histograms**: Distribution of power balance residuals by bus type (PQ, PV, REF) -2. **Feature Correlation Plots**: Predictions vs. targets for Vm, Va, Pg, Qg by bus type - -### Prediction Output - -The `predict_step` method returns detailed per-bus information: - -```python -{ - 'scenario': scenario IDs, - 'bus': bus indices, - 'pd_mw': active power demand, - 'qd_mvar': reactive power demand, - 'vm_pu_target': target voltage magnitude, - 'va_target': target voltage angle, - 'pg_mw_target': target active power generation, - 'qg_mvar_target': target reactive power generation, - 'is_pq': PQ bus indicator, - 'is_pv': PV bus indicator, - 'is_ref': REF bus indicator, - 'vm_pu': predicted voltage magnitude, - 'va': predicted voltage angle, - 'pg_mw': predicted active power generation, - 'qg_mvar': predicted reactive power generation, - 'active res. (MW)': active power residual, - 'reactive res. (MVar)': reactive power residual, - 'PBE': power balance error magnitude -} -``` - -## Configuration Example - -```yaml -task: - name: PowerFlow - verbose: true - -training: - batch_size: 32 - epochs: 100 - losses: ["MaskedMSE", "PBE"] - loss_weights: [0.01, 0.99] - -optimizer: - name: Adam - lr: 0.001 -``` - -## Physics-Based Constraints - -The task uses specialized layers to compute physical quantities: - -- **`ComputeBranchFlow`**: Calculates active (Pft) and reactive (Qft) power flows on branches using the power flow equations -- **`ComputeNodeInjection`**: Aggregates branch flows to compute net power injections at each bus -- **`ComputeNodeResiduals`**: Computes power balance violations by comparing injections with generation and demand - -These layers ensure that predictions are evaluated not only on accuracy but also on their adherence to fundamental power system physics. - -## Distributed Training - -The PowerFlowTask includes special handling for distributed training: - -- **Metric aggregation**: Uses `sync_dist=True` to properly aggregate metrics across GPUs -- **Verbose output gathering**: Collects test outputs from all ranks to rank 0 for complete visualization -- **Max reduction for PBE Max**: Uses `reduce_fx="max"` to find the global maximum PBE across all processes - -## Usage - -The Power Flow task is automatically selected when you specify `task.name: PowerFlow` in your YAML configuration file. The task: - -1. Performs forward pass through the model -2. Inverse normalizes predictions and targets -3. Computes branch flows using power flow equations -4. Calculates power balance residuals and PBE -5. Evaluates metrics separately for each bus type -6. Generates comprehensive reports and visualizations -7. Provides detailed per-bus predictions for analysis - -## Related - -- [Base Task](base_task.md): Abstract base class for all tasks -- [Reconstruction Task](reconstruction_task.md): Base class for reconstruction tasks -- [Optimal Power Flow Task](optimal_power_flow.md): For optimization-based power flow with economic objectives -- [State Estimation Task](state_estimation.md): For state estimation from noisy measurements -- [Task Overview](feature_reconstruction.md): Overview of all task classes \ No newline at end of file diff --git a/docs/tasks/state_estimation.md b/docs/tasks/state_estimation.md deleted file mode 100644 index b293509..0000000 --- a/docs/tasks/state_estimation.md +++ /dev/null @@ -1,79 +0,0 @@ -# State Estimation Task - -The State Estimation task focuses on estimating the true state of a power grid from noisy measurements. This is a critical problem in power system operations, where sensor measurements may contain errors, outliers, or missing data. - -## Overview - -State estimation in power systems involves determining voltage magnitudes, voltage angles, and power injections at buses from available measurements. The `StateEstimationTask` extends the `ReconstructionTask` to handle: - -- **Noisy measurements**: Input features include measurement noise and potential outliers -- **Missing data**: Some measurements may be masked or unavailable -- **Outlier detection**: The task tracks and evaluates performance on outlier measurements separately - -## Key Features - -- **Measurement-based prediction**: Estimates true grid state from noisy sensor data -- **Outlier handling**: Distinguishes between normal measurements, masked values, and outliers -- **Correlation analysis**: Generates plots comparing predictions vs. targets and predictions vs. measurements -- **Multi-mask evaluation**: Evaluates performance separately for outliers, masked values, and clean measurements - -## StateEstimationTask - -::: gridfm_graphkit.tasks.se_task.StateEstimationTask - -## Metrics - -The State Estimation task computes and logs the following metrics during testing: - -### Prediction Quality -- **Voltage Magnitude (Vm)**: Accuracy of estimated voltage magnitudes at buses -- **Voltage Angle (Va)**: Accuracy of estimated voltage angles at buses -- **Active Power Injection (Pg)**: Accuracy of estimated active power at buses -- **Reactive Power Injection (Qg)**: Accuracy of estimated reactive power at buses - -### Evaluation Categories -Metrics are computed separately for three categories: -- **Outliers**: Measurements identified as outliers -- **Masked**: Intentionally masked/missing measurements -- **Non-outliers**: Clean measurements without outliers or masking - -## Visualization - -When `verbose=True` in the configuration, the task generates correlation plots: - -1. **Predictions vs. Targets**: Shows how well predictions match ground truth -2. **Predictions vs. Measurements**: Shows how predictions compare to noisy input measurements -3. **Measurements vs. Targets**: Shows the quality of input measurements - -These plots are generated for each feature (Vm, Va, Pg, Qg) and saved to the test artifacts directory. - -## Configuration Example - -```yaml -task: - name: StateEstimation - verbose: true - -training: - batch_size: 32 - epochs: 100 - losses: ["MaskedMSE"] - loss_weights: [1.0] -``` - -## Usage - -The State Estimation task is automatically selected when you specify `task.name: StateEstimation` in your YAML configuration file. The task handles: - -1. Forward pass through the model with masked/noisy inputs -2. Inverse normalization of predictions and targets -3. Computation of metrics for different measurement categories -4. Generation of correlation plots and analysis - -## Related - -- [Base Task](base_task.md): Abstract base class for all tasks -- [Reconstruction Task](reconstruction_task.md): Base class for reconstruction tasks -- [Power Flow Task](power_flow.md): For standard power flow analysis -- [Optimal Power Flow Task](optimal_power_flow.md): For optimization-based power flow -- [Task Overview](feature_reconstruction.md): Overview of all task classes \ No newline at end of file From 5eb257a22664bcfffdf96c73154e2c78f01b5f8c Mon Sep 17 00:00:00 2001 From: Romeo Kienzler Date: Sun, 15 Mar 2026 20:39:04 +0100 Subject: [PATCH 11/14] fix broken links --- mkdocs.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/mkdocs.yml b/mkdocs.yml index acb1771..7bc8052 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -22,9 +22,6 @@ nav: - Overview: tasks/feature_reconstruction.md - Base Task: tasks/base_task.md - Reconstruction Task: tasks/reconstruction_task.md - - Power Flow: tasks/power_flow.md - - Optimal Power Flow: tasks/optimal_power_flow.md - - State Estimation: tasks/state_estimation.md - Models: models/models.md - Training: - Losses: training/loss.md From a493f250ff682648ad2200b1683357400d8820b0 Mon Sep 17 00:00:00 2001 From: Romeo Kienzler Date: Sun, 15 Mar 2026 22:48:40 +0100 Subject: [PATCH 12/14] fix documentation --- docs/install/installation.md | 26 +++++++++- docs/tasks/optimal_power_flow.md | 79 +++++++++++++++++++++++++++++++ docs/tasks/power_flow.md | 74 +++++++++++++++++++++++++++++ docs/tasks/state_estimation.md | 66 ++++++++++++++++++++++++++ gridfm_graphkit/tasks/opf_task.py | 4 +- mkdocs.yml | 3 ++ 6 files changed, 249 insertions(+), 3 deletions(-) create mode 100644 docs/tasks/optimal_power_flow.md create mode 100644 docs/tasks/power_flow.md create mode 100644 docs/tasks/state_estimation.md diff --git a/docs/install/installation.md b/docs/install/installation.md index 07dc502..89ee4be 100644 --- a/docs/install/installation.md +++ b/docs/install/installation.md @@ -1,14 +1,18 @@ +# Installation + You can install `gridfm-graphkit` directly from PyPI: ```bash pip install gridfm-graphkit ``` +For GPU support and compatibility with PyTorch Geometric's scatter operations, install PyTorch (and optionally CUDA) first, then install the matching `torch-scatter` wheel. See [PyTorch and torch-scatter](#pytorch-and-torch-scatter-optional) below. + --- ## Development Setup -To contribute or develop locally, clone the repository and install in editable mode: +To contribute or develop locally, clone the repository and install in editable mode. Use Python 3.10, 3.11, or 3.12 (3.12 is recommended). ```bash git clone git@github.com:gridfm/gridfm-graphkit.git @@ -18,6 +22,26 @@ source venv/bin/activate pip install -e . ``` +### PyTorch and torch-scatter (optional) + +If you need GPU acceleration or PyTorch Geometric scatter ops (used by the library), install PyTorch and the matching `torch-scatter` wheel: + +1. Install PyTorch (see [pytorch.org](https://pytorch.org/) for your platform and CUDA version). + +2. Get your Torch + CUDA version string: + ```bash + TORCH_CUDA_VERSION=$(python -c "import torch; print(torch.__version__ + ('+cpu' if torch.version.cuda is None else ''))") + ``` + +3. Install the correct `torch-scatter` wheel: + ```bash + pip install torch-scatter -f https://data.pyg.org/whl/torch-${TORCH_CUDA_VERSION}.html + ``` + +--- + +## Optional extras + For documentation generation and unit testing, install with the optional `dev` and `test` extras: ```bash diff --git a/docs/tasks/optimal_power_flow.md b/docs/tasks/optimal_power_flow.md new file mode 100644 index 0000000..90a98ab --- /dev/null +++ b/docs/tasks/optimal_power_flow.md @@ -0,0 +1,79 @@ +# Optimal Power Flow Task + +The `OptimalPowerFlowTask` class is a concrete implementation of `ReconstructionTask` for **optimal power flow (OPF)**. It adds economic optimization metrics (generation cost, optimality gap), constraint violation tracking (thermal, voltage angle, reactive power limits), and the same physics-based validation as the power flow task. + +## Overview + +`OptimalPowerFlowTask` extends `ReconstructionTask` and provides: + +- **Economic metrics**: Generation cost from quadratic cost coefficients (C0, C1, C2) and **optimality gap** (relative difference between predicted and ground-truth cost) +- **Constraint violations**: Branch thermal limits (RATE_A), branch angle limits (ANG_MIN, ANG_MAX), and reactive power limits (Qg min/max) for PV and REF buses +- **Physics validation**: Same branch flow, node injection, and power balance residuals as PowerFlowTask +- **Per-bus-type MSE**: Separate MSE for PQ, PV, and REF buses (PG, QG, VM, VA) + +## OptimalPowerFlowTask Class + +::: gridfm_graphkit.tasks.opf_task.OptimalPowerFlowTask + options: + show_root_heading: true + show_source: true + members: + - __init__ + - test_step + - on_test_end + +## Configuration Example + +Use the task by setting `task_name: OptimalPowerFlow` in your YAML: + +```yaml +task: + task_name: OptimalPowerFlow + +model: + type: GNS_heterogeneous + hidden_size: 48 + num_layers: 12 + attention_head: 8 + +training: + batch_size: 64 + epochs: 100 + losses: + - MaskedMSE + loss_weights: + - 1.0 + +data: + networks: + - case14_ieee + - case118_ieee + +verbose: true +``` + +## Test Metrics + +During evaluation, `OptimalPowerFlowTask` logs (per dataset): + +| Metric | Description | +|--------|-------------| +| Test loss | Main reconstruction loss | +| Opt gap | Mean absolute percentage difference between predicted and ground-truth generation cost | +| MSE PG | MSE on generator active power | +| Active / Reactive Power Loss | Mean absolute P/Q residuals | +| Branch thermal violation from | Mean thermal limit excess on forward branch (apparent power vs RATE_A) | +| Branch thermal violation to | Mean thermal limit excess on reverse branch (apparent power vs RATE_A) | +| Branch voltage angle difference violations | Mean angle limit violation (degrees) | +| Mean Qg violation PV buses | Mean reactive power limit violation on PV buses | +| Mean Qg violation REF buses | Mean reactive power limit violation on REF buses | +| MSE PQ/PV/REF nodes - PG/QG/VM/VA | MSE per bus type and output dimension | + +With `verbose: true`, CSV reports and plots are written to MLflow artifacts. + +## Related + +- [Reconstruction Task](reconstruction_task.md): Base class for reconstruction tasks +- [Power Flow Task](power_flow.md): Power flow analysis (no cost or constraint metrics) +- [Base Task](base_task.md): Abstract base class for all tasks +- [Loss Functions](../training/loss.md): Available loss functions diff --git a/docs/tasks/power_flow.md b/docs/tasks/power_flow.md new file mode 100644 index 0000000..df29a40 --- /dev/null +++ b/docs/tasks/power_flow.md @@ -0,0 +1,74 @@ +# Power Flow Task + +The `PowerFlowTask` class is a concrete implementation of `ReconstructionTask` for **power flow analysis**. It computes voltage profiles and power flows from given injections and adds physics-based validation using Power Balance Error (PBE) and per-bus-type metrics. + +## Overview + +`PowerFlowTask` extends `ReconstructionTask` and provides: + +- **Physics-based validation**: Branch flow computation, node injection, and power balance residuals (P, Q) +- **Per-bus-type metrics**: Separate MSE and residual statistics for PQ, PV, and REF buses (PG, QG, VM, VA) +- **Power Balance Error (PBE)**: Mean and max PBE across the test set +- **Optional verbose output**: Residual histograms and correlation plots when `args.verbose` is true + +## PowerFlowTask Class + +::: gridfm_graphkit.tasks.pf_task.PowerFlowTask + options: + show_root_heading: true + show_source: true + members: + - __init__ + - test_step + - on_test_end + +## Configuration Example + +Use the task by setting `task_name: PowerFlow` in your YAML: + +```yaml +task: + task_name: PowerFlow + +model: + type: GNS_heterogeneous + hidden_size: 48 + num_layers: 12 + attention_head: 8 + +training: + batch_size: 64 + epochs: 100 + losses: + - MaskedMSE + loss_weights: + - 1.0 + +data: + networks: + - case14_ieee + - case118_ieee + +verbose: true +``` + +## Test Metrics + +During evaluation, `PowerFlowTask` logs (per dataset): + +| Metric | Description | +|--------|-------------| +| Test loss | Main reconstruction loss | +| Active Power Loss | Mean absolute active power residual | +| Reactive Power Loss | Mean absolute reactive power residual | +| PBE Mean | Mean power balance error magnitude | +| PBE Max | Maximum power balance error (reduced with max across batches) | +| MSE PQ/PV/REF nodes - PG/QG/VM/VA | MSE per bus type and output dimension | + +With `verbose: true`, CSV reports and residual histograms are written to MLflow artifacts. + +## Related + +- [Reconstruction Task](reconstruction_task.md): Base class for reconstruction tasks +- [Base Task](base_task.md): Abstract base class for all tasks +- [Loss Functions](../training/loss.md): Available loss functions (e.g. MaskedMSE, LayeredWeightedPhysics) diff --git a/docs/tasks/state_estimation.md b/docs/tasks/state_estimation.md new file mode 100644 index 0000000..02ff831 --- /dev/null +++ b/docs/tasks/state_estimation.md @@ -0,0 +1,66 @@ +# State Estimation Task + +The `StateEstimationTask` class is a concrete implementation of `ReconstructionTask` for **state estimation** from noisy measurements. It evaluates predictions against ground truth and measurements, with separate handling for outliers, masked values, and clean measurements. + +## Overview + +`StateEstimationTask` extends `ReconstructionTask` and provides: + +- **Measurement-based setup**: Inputs are (noisy) measurements; targets are true states. The model reconstructs the state from measurements. +- **Three-way evaluation**: Comparisons between predictions vs targets, predictions vs measurements, and measurements vs targets, with masks for outliers, masked (hidden) values, and non-outliers. +- **Correlation plots**: When `verbose: true`, scatter plots (pred vs target, pred vs measured, measured vs target) per feature (Vm, Va, Pg, Qg) are saved to MLflow artifacts. + +## StateEstimationTask Class + +::: gridfm_graphkit.tasks.se_task.StateEstimationTask + options: + show_root_heading: true + show_source: true + members: + - __init__ + - test_step + - on_test_end + - predict_step + +## Configuration Example + +Use the task by setting `task_name: StateEstimation` in your YAML: + +```yaml +task: + task_name: StateEstimation + +model: + type: GNS_heterogeneous + hidden_size: 48 + num_layers: 12 + attention_head: 8 + +training: + batch_size: 64 + epochs: 100 + losses: + - MaskedMSE + loss_weights: + - 1.0 + +data: + networks: + - case14_ieee + - case118_ieee + +verbose: true +``` + +## Test Outputs + +- **test_step**: Runs the shared reconstruction step, then computes targets and measurements (Vm, Va, P_injection, Q_injection). Uses `mask_dict["outliers_bus"]`, `mask_dict["bus"]`, and non-outlier masks to separate evaluation groups. Stores predictions, targets, and measurements for `on_test_end`. +- **on_test_end**: If `verbose`, writes correlation plots (pred vs target, pred vs measured, measured vs target) per dataset to `artifacts/test_plots//`. +- **predict_step**: Currently a stub; override in a subclass or in a future release for custom prediction behavior. + +## Related + +- [Reconstruction Task](reconstruction_task.md): Base class for reconstruction tasks +- [Power Flow Task](power_flow.md): Power flow analysis +- [Optimal Power Flow Task](optimal_power_flow.md): OPF with cost and constraint metrics +- [Base Task](base_task.md): Abstract base class for all tasks diff --git a/gridfm_graphkit/tasks/opf_task.py b/gridfm_graphkit/tasks/opf_task.py index b28c5a0..06d938d 100644 --- a/gridfm_graphkit/tasks/opf_task.py +++ b/gridfm_graphkit/tasks/opf_task.py @@ -256,8 +256,8 @@ def test_step(self, batch, batch_idx, dataloader_idx=0): loss_dict["Opt gap"] = optimality_gap loss_dict["MSE PG"] = mse_PG[PG_H] - loss_dict["Branch termal violation from"] = mean_thermal_violation_forward - loss_dict["Branch termal violation to"] = mean_thermal_violation_reverse + loss_dict["Branch thermal violation from"] = mean_thermal_violation_forward + loss_dict["Branch thermal violation to"] = mean_thermal_violation_reverse loss_dict["Branch voltage angle difference violations"] = ( branch_angle_violation_mean ) diff --git a/mkdocs.yml b/mkdocs.yml index 7bc8052..6581214 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -22,6 +22,9 @@ nav: - Overview: tasks/feature_reconstruction.md - Base Task: tasks/base_task.md - Reconstruction Task: tasks/reconstruction_task.md + - Power Flow Task: tasks/power_flow.md + - Optimal Power Flow Task: tasks/optimal_power_flow.md + - State Estimation Task: tasks/state_estimation.md - Models: models/models.md - Training: - Losses: training/loss.md From 8f0d0d2ebd398c7360623e2e734e0ff4511af702 Mon Sep 17 00:00:00 2001 From: Romeo Kienzler Date: Mon, 16 Mar 2026 14:18:41 +0100 Subject: [PATCH 13/14] strip down --- docs/tasks/state_estimation.md | 55 ---------------------------------- 1 file changed, 55 deletions(-) diff --git a/docs/tasks/state_estimation.md b/docs/tasks/state_estimation.md index 02ff831..c3adbbc 100644 --- a/docs/tasks/state_estimation.md +++ b/docs/tasks/state_estimation.md @@ -1,17 +1,5 @@ # State Estimation Task -The `StateEstimationTask` class is a concrete implementation of `ReconstructionTask` for **state estimation** from noisy measurements. It evaluates predictions against ground truth and measurements, with separate handling for outliers, masked values, and clean measurements. - -## Overview - -`StateEstimationTask` extends `ReconstructionTask` and provides: - -- **Measurement-based setup**: Inputs are (noisy) measurements; targets are true states. The model reconstructs the state from measurements. -- **Three-way evaluation**: Comparisons between predictions vs targets, predictions vs measurements, and measurements vs targets, with masks for outliers, masked (hidden) values, and non-outliers. -- **Correlation plots**: When `verbose: true`, scatter plots (pred vs target, pred vs measured, measured vs target) per feature (Vm, Va, Pg, Qg) are saved to MLflow artifacts. - -## StateEstimationTask Class - ::: gridfm_graphkit.tasks.se_task.StateEstimationTask options: show_root_heading: true @@ -21,46 +9,3 @@ The `StateEstimationTask` class is a concrete implementation of `ReconstructionT - test_step - on_test_end - predict_step - -## Configuration Example - -Use the task by setting `task_name: StateEstimation` in your YAML: - -```yaml -task: - task_name: StateEstimation - -model: - type: GNS_heterogeneous - hidden_size: 48 - num_layers: 12 - attention_head: 8 - -training: - batch_size: 64 - epochs: 100 - losses: - - MaskedMSE - loss_weights: - - 1.0 - -data: - networks: - - case14_ieee - - case118_ieee - -verbose: true -``` - -## Test Outputs - -- **test_step**: Runs the shared reconstruction step, then computes targets and measurements (Vm, Va, P_injection, Q_injection). Uses `mask_dict["outliers_bus"]`, `mask_dict["bus"]`, and non-outlier masks to separate evaluation groups. Stores predictions, targets, and measurements for `on_test_end`. -- **on_test_end**: If `verbose`, writes correlation plots (pred vs target, pred vs measured, measured vs target) per dataset to `artifacts/test_plots//`. -- **predict_step**: Currently a stub; override in a subclass or in a future release for custom prediction behavior. - -## Related - -- [Reconstruction Task](reconstruction_task.md): Base class for reconstruction tasks -- [Power Flow Task](power_flow.md): Power flow analysis -- [Optimal Power Flow Task](optimal_power_flow.md): OPF with cost and constraint metrics -- [Base Task](base_task.md): Abstract base class for all tasks From fc314e012ceb7ed38a386beb347eea13ea138c81 Mon Sep 17 00:00:00 2001 From: Romeo Kienzler Date: Mon, 16 Mar 2026 16:52:11 +0100 Subject: [PATCH 14/14] simplify doc --- docs/tasks/optimal_power_flow.md | 67 -------------------------------- docs/tasks/power_flow.md | 62 ----------------------------- 2 files changed, 129 deletions(-) diff --git a/docs/tasks/optimal_power_flow.md b/docs/tasks/optimal_power_flow.md index 90a98ab..3d13a57 100644 --- a/docs/tasks/optimal_power_flow.md +++ b/docs/tasks/optimal_power_flow.md @@ -1,16 +1,5 @@ # Optimal Power Flow Task -The `OptimalPowerFlowTask` class is a concrete implementation of `ReconstructionTask` for **optimal power flow (OPF)**. It adds economic optimization metrics (generation cost, optimality gap), constraint violation tracking (thermal, voltage angle, reactive power limits), and the same physics-based validation as the power flow task. - -## Overview - -`OptimalPowerFlowTask` extends `ReconstructionTask` and provides: - -- **Economic metrics**: Generation cost from quadratic cost coefficients (C0, C1, C2) and **optimality gap** (relative difference between predicted and ground-truth cost) -- **Constraint violations**: Branch thermal limits (RATE_A), branch angle limits (ANG_MIN, ANG_MAX), and reactive power limits (Qg min/max) for PV and REF buses -- **Physics validation**: Same branch flow, node injection, and power balance residuals as PowerFlowTask -- **Per-bus-type MSE**: Separate MSE for PQ, PV, and REF buses (PG, QG, VM, VA) - ## OptimalPowerFlowTask Class ::: gridfm_graphkit.tasks.opf_task.OptimalPowerFlowTask @@ -21,59 +10,3 @@ The `OptimalPowerFlowTask` class is a concrete implementation of `Reconstruction - __init__ - test_step - on_test_end - -## Configuration Example - -Use the task by setting `task_name: OptimalPowerFlow` in your YAML: - -```yaml -task: - task_name: OptimalPowerFlow - -model: - type: GNS_heterogeneous - hidden_size: 48 - num_layers: 12 - attention_head: 8 - -training: - batch_size: 64 - epochs: 100 - losses: - - MaskedMSE - loss_weights: - - 1.0 - -data: - networks: - - case14_ieee - - case118_ieee - -verbose: true -``` - -## Test Metrics - -During evaluation, `OptimalPowerFlowTask` logs (per dataset): - -| Metric | Description | -|--------|-------------| -| Test loss | Main reconstruction loss | -| Opt gap | Mean absolute percentage difference between predicted and ground-truth generation cost | -| MSE PG | MSE on generator active power | -| Active / Reactive Power Loss | Mean absolute P/Q residuals | -| Branch thermal violation from | Mean thermal limit excess on forward branch (apparent power vs RATE_A) | -| Branch thermal violation to | Mean thermal limit excess on reverse branch (apparent power vs RATE_A) | -| Branch voltage angle difference violations | Mean angle limit violation (degrees) | -| Mean Qg violation PV buses | Mean reactive power limit violation on PV buses | -| Mean Qg violation REF buses | Mean reactive power limit violation on REF buses | -| MSE PQ/PV/REF nodes - PG/QG/VM/VA | MSE per bus type and output dimension | - -With `verbose: true`, CSV reports and plots are written to MLflow artifacts. - -## Related - -- [Reconstruction Task](reconstruction_task.md): Base class for reconstruction tasks -- [Power Flow Task](power_flow.md): Power flow analysis (no cost or constraint metrics) -- [Base Task](base_task.md): Abstract base class for all tasks -- [Loss Functions](../training/loss.md): Available loss functions diff --git a/docs/tasks/power_flow.md b/docs/tasks/power_flow.md index df29a40..8912a26 100644 --- a/docs/tasks/power_flow.md +++ b/docs/tasks/power_flow.md @@ -1,16 +1,5 @@ # Power Flow Task -The `PowerFlowTask` class is a concrete implementation of `ReconstructionTask` for **power flow analysis**. It computes voltage profiles and power flows from given injections and adds physics-based validation using Power Balance Error (PBE) and per-bus-type metrics. - -## Overview - -`PowerFlowTask` extends `ReconstructionTask` and provides: - -- **Physics-based validation**: Branch flow computation, node injection, and power balance residuals (P, Q) -- **Per-bus-type metrics**: Separate MSE and residual statistics for PQ, PV, and REF buses (PG, QG, VM, VA) -- **Power Balance Error (PBE)**: Mean and max PBE across the test set -- **Optional verbose output**: Residual histograms and correlation plots when `args.verbose` is true - ## PowerFlowTask Class ::: gridfm_graphkit.tasks.pf_task.PowerFlowTask @@ -21,54 +10,3 @@ The `PowerFlowTask` class is a concrete implementation of `ReconstructionTask` f - __init__ - test_step - on_test_end - -## Configuration Example - -Use the task by setting `task_name: PowerFlow` in your YAML: - -```yaml -task: - task_name: PowerFlow - -model: - type: GNS_heterogeneous - hidden_size: 48 - num_layers: 12 - attention_head: 8 - -training: - batch_size: 64 - epochs: 100 - losses: - - MaskedMSE - loss_weights: - - 1.0 - -data: - networks: - - case14_ieee - - case118_ieee - -verbose: true -``` - -## Test Metrics - -During evaluation, `PowerFlowTask` logs (per dataset): - -| Metric | Description | -|--------|-------------| -| Test loss | Main reconstruction loss | -| Active Power Loss | Mean absolute active power residual | -| Reactive Power Loss | Mean absolute reactive power residual | -| PBE Mean | Mean power balance error magnitude | -| PBE Max | Maximum power balance error (reduced with max across batches) | -| MSE PQ/PV/REF nodes - PG/QG/VM/VA | MSE per bus type and output dimension | - -With `verbose: true`, CSV reports and residual histograms are written to MLflow artifacts. - -## Related - -- [Reconstruction Task](reconstruction_task.md): Base class for reconstruction tasks -- [Base Task](base_task.md): Abstract base class for all tasks -- [Loss Functions](../training/loss.md): Available loss functions (e.g. MaskedMSE, LayeredWeightedPhysics)