Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions .github/workflows/ci-build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -163,7 +163,7 @@ jobs:
- name: Install Python dependencies
run: |
python -m pip install --upgrade pip wheel
pip install -e ".[test,dynamic,powsybl]"
pip install -e ".[test,dynamic,powsybl,lightsim2grid]"

- name: Set up Julia packages (PowerModels)
run: python -c "from juliacall import Main as jl; jl.seval('using PowerModels, Ipopt, Memento')"
Expand All @@ -173,8 +173,8 @@ jobs:
"$HOME/dynawo/dynawo.sh" version
python -c "from gridfm_datakit.dynamic.dynawo.api import check_dynawo_available; check_dynawo_available()"

- name: Dynawo and powsybl tests
run: pytest tests/dynamic tests/powsybl -v
- name: Dynawo, powsybl and lightsim2grid tests
run: pytest tests/dynamic tests/powsybl tests/lightsim2grid -v

codeql:
name: CodeQL (Python)
Expand Down
41 changes: 41 additions & 0 deletions docs/components/lightsim2grid.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# LightSim2grid

This module provides the interface between a
[`Network`](network.md) and [lightsim2grid](https://github.com/Grid2op/lightsim2grid), used when
`settings.pf_solver` is `lightsim2grid`. See [Power flow solver](../manual/power_flow_solver.md).

### `convert_net`

::: gridfm_datakit.lightsim2grid.convert_net

### `to_lightsim2grid`

::: gridfm_datakit.lightsim2grid.convert.to_lightsim2grid

### `update_lightsim2grid`

::: gridfm_datakit.lightsim2grid.convert.update_lightsim2grid

### `run_ls_pf`

::: gridfm_datakit.lightsim2grid.preprocess.run_ls_pf

### `get_pf_res`

::: gridfm_datakit.lightsim2grid.preprocess.get_pf_res

### `build_l2g_maps`

::: gridfm_datakit.lightsim2grid.mapping.build_l2g_maps

### `MappingL2G`

::: gridfm_datakit.lightsim2grid.mapping.MappingL2G

### `ConvertedNetwork`

::: gridfm_datakit.lightsim2grid.convert.ConvertedNetwork

### `LoadedNetwork`

::: gridfm_datakit.lightsim2grid.LoadedNetwork
27 changes: 27 additions & 0 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,33 @@ pip install gridfm-datakit
gridfm_datakit setup_pm
```

### Optional: lightsim2grid power flow solver

The `lightsim2grid` extra installs [lightsim2grid](https://github.com/Grid2op/lightsim2grid), an
alternative to PowerModels for solving the power flow (`settings.pf_solver: lightsim2grid`, see
[Power flow solver](manual/power_flow_solver.md)):

```bash
pip install 'gridfm-datakit[lightsim2grid]'
```

!!! warning "In the meantime: install lightsim2grid from source"
The methods that let gridfm-datakit update the impedances of the lightsim2grid model in
place (`update_powerlines_parameters` and `update_trafos_parameters`) are not in a
lightsim2grid release yet. They are on the
[`dev_gfm_datakit`](https://github.com/grid2op/lightsim2grid/tree/dev_gfm_datakit) branch.
Until a release has them, install lightsim2grid from that branch, then gridfm-datakit:

```bash
pip install 'lightsim2grid @ git+https://github.com/grid2op/lightsim2grid.git@dev_gfm_datakit'
pip install 'gridfm-datakit[lightsim2grid]'
```

This compiles C++ code, so it needs a C++ compiler and takes a few minutes. Without these
methods `pf_solver: lightsim2grid` still gives the same results, but the model is rebuilt for
every power flow, which is slower. Once a release contains them, the extra alone will be
enough and this note will be removed.

### Optional: PowSyBl

To read XIIDM, CGMES, PSS/E or UCTE files, or to solve the power flow with PowSyBl, install the `powsybl` extra. See the [PowSyBl](manual/powsybl.md) page.
Expand Down
2 changes: 2 additions & 0 deletions docs/manual/getting_started.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ settings:
include_dc_res: true # If true, also stores the results of dc power flow and dc optimal power flow
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 e.g. case10000_goc do not work with pf_fast: true
dcpf_fast: true # Whether to use fast DC PF solver (compute_dc_pf from powermodels.jl); if false, uses optimizer-based DC PF
pf_solver: "powermodel" # Engine solving the power flow in pf mode; options: powermodel, powsybl, lightsim2grid (the OPF is always solved by PowerModels)
enable_solver_logs: false # If true, write OPF/PF solver logs to {data_dir}/solver_log; PF fast ignores logging

```
Expand All @@ -99,6 +100,7 @@ The `mode` parameter controls how the power flow scenarios are generated and val
- **Use Case**: Training data for power flow, contingency analysis, etc
- **Performance**: Faster as it avoids re-solving OPF for each perturbed scenario
- **PF Solver Choice**: Controlled by `settings.pf_fast`. If `true`, uses the fast `compute_ac_pf` path. If `false`, uses the Ipopt-based AC PF which is slower for smaller grids but has better convergence properties for large grids.
- **PF Engine**: `settings.pf_solver` selects the engine: `powermodel` (default), `powsybl` or `lightsim2grid`. See [Power flow solver](power_flow_solver.md).

## Data Validation

Expand Down
66 changes: 66 additions & 0 deletions docs/manual/power_flow_solver.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Power flow solver

In `mode: "pf"` the power flow of every perturbed network is solved by the engine chosen with
`settings.pf_solver`. The OPF that gives the generator set points is **always** solved by
PowerModels.jl, whatever the value of this parameter.

```yaml
settings:
pf_solver: "lightsim2grid" # options: powermodel (default), powsybl, lightsim2grid
```

| `pf_solver` | Engine | Extra to install |
|---|---|---|
| `powermodel` (default) | PowerModels.jl, through Julia. `pf_fast` and `dcpf_fast` choose between the direct and the optimizer-based solvers | none |
| `powsybl` | pypowsybl (Open Load Flow). Needs the network to be loaded with `network.reader: powsybl` | `pip install 'gridfm-datakit[powsybl]'` |
| `lightsim2grid` | [lightsim2grid](https://github.com/Grid2op/lightsim2grid) (C++, no Julia for the power flow) | `pip install 'gridfm-datakit[lightsim2grid]'` |

## lightsim2grid

The network is read with the native reader, and only the power flow solver changes:

```yaml
network:
source: "pglib"
name: "case118_ieee"

settings:
mode: "pf"
pf_solver: "lightsim2grid"
include_dc_res: true # the DC power flow is solved by lightsim2grid too
```

### How the network is converted

The lightsim2grid model is built straight from the bus, generator and branch arrays of the
[`Network`](../components/network.md), so it is the network that the perturbations have
modified (loads, admittances, generator set points, outages). Buses and generators keep their order,
and each branch becomes a powerline or a transformer (a branch with a non zero tap or phase
shift is a transformer).

The model is not rebuilt for every perturbation: what changed since the previous power flow
(branch impedances and statuses, generator statuses and set points, loads) is pushed into the same
model, which lets lightsim2grid keep its solver state. It is rebuilt only if something that cannot
be pushed changed (the network topology, a tap ratio or a phase shift, the shunts, the bus types,
which buses have a load, or the slack generators). The model is kept by each worker process, so it
is also reused from one scenario to the next.

!!! note "Versions"
`pf_solver: lightsim2grid` needs a lightsim2grid providing
`lightsim2grid.network.init_from_matpower`. Pushing changes into the model needs the
`update_powerlines_parameters` and `update_trafos_parameters` methods; without them the
model is rebuilt for every power flow, which gives the same results more slowly. Until a
lightsim2grid release has them, they are available by
[installing lightsim2grid from source](../installation.md#optional-lightsim2grid-power-flow-solver).

### Differences with PowerModels

- **AC**: the results agree with those of the fast PowerModels power flow (`pf_fast: true`). On
case14 the difference is below `1e-6` on all the bus, generator and branch quantities.
- **DC**: the two solvers do not use the same DC model. lightsim2grid uses the MATPOWER one
(susceptance `1 / (x * tap)`) whereas PowerModels uses `x / (r^2 + x^2)`, so the DC angles and
flows differ, more on networks with large resistances.
- **Slack**: the active power imbalance is shared equally by the in-service generators
connected to the reference bus.
- **No optimizer**: there is no equivalent of `pf_fast: false`, and `pf_fast` / `dcpf_fast`
are ignored.
8 changes: 6 additions & 2 deletions gridfm_datakit/generate.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import yaml
from tqdm import tqdm

import gridfm_datakit.lightsim2grid as lightsim2grid
import gridfm_datakit.powsybl as powsybl
from gridfm_datakit.network import (
Network,
Expand Down Expand Up @@ -129,10 +130,13 @@ def _setup_environment(
# In OPF mode the value is read and stored on args but is never consulted
# during execution — it is kept here purely for consistency and logging.
pf_solver = getattr(args.settings, "pf_solver", "powermodel")
if pf_solver not in ("powermodel", "powsybl"):
if pf_solver not in ("powermodel", "powsybl", "lightsim2grid"):
raise ValueError(
f"settings.pf_solver must be 'powermodel' or 'powsybl', got {pf_solver!r}",
"settings.pf_solver must be 'powermodel', 'powsybl' or 'lightsim2grid', "
f"got {pf_solver!r}",
)
if pf_solver == "lightsim2grid":
lightsim2grid.check_lightsim2grid_available()
args.settings.pf_solver = pf_solver

opf_formulation = getattr(args.settings, "opf_formulation", "polar")
Expand Down
74 changes: 74 additions & 0 deletions gridfm_datakit/lightsim2grid/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
"""
lightsim2grid integration module for gridfm_datakit.

lightsim2grid is a C++ power flow engine. This module bridges it with
gridfm_datakit's internal :class:`~gridfm_datakit.network.Network`, following
the layout of :mod:`gridfm_datakit.powsybl`:

* :func:`convert_net` — convert a gridfm_datakit Network to a lightsim2grid
``LSGrid`` and the index maps between the two.
* :func:`update_lightsim2grid` — re-synchronise the ``LSGrid`` with a perturbed
copy of the Network.
* :func:`run_ls_pf` — run an AC or DC power flow and format the result like
PowerModels' one, for ``pf_post_processing``.

Unlike PowSyBl there is no dedicated reader: the network is always read with
the native reader and lightsim2grid only replaces the power flow solver
(``settings.pf_solver: lightsim2grid``). OPF is still solved by PowerModels.
"""

from dataclasses import dataclass
from typing import Any

from gridfm_datakit.network import Network

from .api import (
check_lightsim2grid_available,
is_lightsim2grid_available,
lightsim2grid_network,
)
from .convert import ConvertedNetwork, to_lightsim2grid, update_lightsim2grid
from .mapping import MappingL2G, build_l2g_maps
from .preprocess import get_pf_res, run_ls_pf


@dataclass
class LoadedNetwork:
"""Bundles the lightsim2grid and gridfm_datakit representations of a network."""

ls_net: Any # lightsim2grid.network.LSGrid
gfm_net: Network
mapping_l2g: MappingL2G


def convert_net(network: Network) -> LoadedNetwork:
"""Convert a gridfm_datakit Network to lightsim2grid.

Args:
network: The network to convert.

Returns:
The lightsim2grid LSGrid, the network itself and the index maps between the two.
"""
conv = to_lightsim2grid(network)
return LoadedNetwork(
ls_net=conv.ls_net,
gfm_net=network,
mapping_l2g=conv.mapping_l2g,
)


__all__ = [
"convert_net",
"to_lightsim2grid",
"update_lightsim2grid",
"build_l2g_maps",
"MappingL2G",
"ConvertedNetwork",
"LoadedNetwork",
"lightsim2grid_network",
"is_lightsim2grid_available",
"check_lightsim2grid_available",
"run_ls_pf",
"get_pf_res",
]
26 changes: 26 additions & 0 deletions gridfm_datakit/lightsim2grid/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
try:
import warnings

with warnings.catch_warnings():
warnings.simplefilter("ignore")
from lightsim2grid import network as lightsim2grid_network

LIGHTSIM2GRID_AVAILABLE = hasattr(lightsim2grid_network, "init_from_matpower")
except ImportError:
LIGHTSIM2GRID_AVAILABLE = False
lightsim2grid_network = None


def is_lightsim2grid_available() -> bool:
"""Check if a lightsim2grid version able to read MATPOWER data is available."""
return LIGHTSIM2GRID_AVAILABLE


def check_lightsim2grid_available() -> None:
"""Check if lightsim2grid is available, raise ImportError if not."""
if not LIGHTSIM2GRID_AVAILABLE:
raise ImportError(
"A recent lightsim2grid (with lightsim2grid.network.init_from_matpower) "
"is required for this functionality. "
"Install it with: pip install gridfm-datakit[lightsim2grid]",
)
Loading
Loading