Skip to content

[BUG] ContingencyAnalysisCPP converges while the powerflow diverge #129

@adevilde

Description

@adevilde

Environment

  • Grid2op version: 1.12.3
  • lightsim version: 0.12.1
  • System: Oracle Linux Server 8.10
uv venv --python 3.12.9
uv pip install grid2op==1.12.3 lightsim2grid==0.12.1

Bug description

When doing the contingency analysis using ContingencyAnalysisCPP, we obtain powerflow result that seems valid although the powerflow didn't converge. However, when using grid2op env with lightsim backend, the init_state with the disconnected line (CHALOL61CPVAN, l_id = 42) seems to lead to powerflow divergence.

In the example bellow, we intentionally use a 'bad' N-1 init state, that diverges. At N, the powerflow is not diverging however when we create N-1, it diverges. The ContingencyAnalysisCPP returned valid flows with overflow on line CHALOL31LOUHA therefore we saved this N-1 init state although it is faulty.

How to reproduce

Code snippet

import json
from pathlib import Path
from typing import Any

import grid2op
import structlog
from grid2op.Chronics import ChangeNothing
from grid2op.Parameters import Parameters as Grid2OpParameters
from lightsim2grid import LightSimBackend
from lightsim2grid_cpp import ContingencyAnalysisCPP


def build_env() -> Any:
    """Build a grid2op environment from a user-provided env path.
    """
    path_env = "/DSIA/devilderali/env_dijon_v2_training"

    params = Grid2OpParameters()
    params.init_from_dict(dict_={"ENV_DOES_REDISPATCHING": False})

    gen_slack_id = [
        "N.SE17GROUP.1",
        "N.SE27GROUP.2",
        "BUGEY7G2",
        "BUGEY7G3",
        "BUGEY7G4",
        "BUGEY7G5",
    ]

    lightsim2grid_loader_kwargs = {
        "use_buses_for_sub": False,
        "use_grid2op_default_names": False,
        "reconnect_disco_gen": False,
        "reconnect_disco_load": False,
        "sort_index": True,
        "n_busbar_per_sub": 6,
    }
    backend_arguments = {
        "loader_method": "pypowsybl",
        "loader_kwargs": lightsim2grid_loader_kwargs,
        "max_iter": 1000,
        "tol": 1e-8,
        "turned_off_pv": False,
        "dist_slack_non_renew": False,
        "gen_slack_id": gen_slack_id,
        "use_static_gen": False,
        "stop_if_load_disco": None,
        "stop_if_gen_disco": None,
        "stop_if_storage_disco": None,
        "automatically_disconnect": False,
    }

    n_busbar = backend_arguments["loader_kwargs"]["n_busbar_per_sub"]
    backend = LightSimBackend(
        **backend_arguments,
    )
    env = grid2op.make(
        path_env,
        allow_detachment=True,
        backend=backend,
        n_busbar=n_busbar,
        chronics_class=ChangeNothing,
        param=params,
    )
    return env


def load_init_state(json_path: Path) -> dict:
    with open(json_path, "rt", encoding="utf-8") as f:
        data = json.load(f)
    return data


def write_init_state(json_path: Path, content: dict) -> None:
    with open(json_path, "wt", encoding="utf-8") as f:
        json.dump(content, f, indent=2, ensure_ascii=False)


def set_line_bus(init_state: dict, line_id: str, bus_value: int) -> None:
    """Set both OR / EX bus for a given line."""

    init_state["init state"]["set_bus"]["lines_or_id"][line_id] = bus_value
    init_state["init state"]["set_bus"]["lines_ex_id"][line_id] = bus_value


def main() -> None:
    """Run the N / N-1 check and contingency analysis on a given env path.
    """

    logger = structlog.get_logger()
    base_json_path = Path("_20221112-0115_CHALOL61CPVAN.json")

    # Load original N-1 problematic state
    init_state_json = load_init_state(base_json_path)

    line_id = "CHALOL61CPVAN"

    # 1) Change to N state (line back in service) and try to reset the env
    set_line_bus(init_state_json, line_id=line_id, bus_value=1)

    env = build_env()

    logger.info("Resetting env with N-state (line in service)...")
    try:
        obs = env.reset(options={"init state": init_state_json["init state"]})
        logger.info("N-state reset converged", obs_type=type(obs))
    except Exception as exc:  # noqa: BLE001
        logger.warning("N-state reset FAILED to converge:", exc=repr(exc))

    # 2) Put the line back to -1 (out of service) and reset again
    set_line_bus(init_state_json, line_id=line_id, bus_value=-1)

    logger.info("Resetting env with N-1 state (line out of service)...")
    try:
        obs = env.reset(options={"init state": init_state_json["init state"]})
        logger.info("N-1 reset converged UNEXPECTEDLY", obs_type=type(obs))
    except Exception as exc:  # noqa: BLE001
        logger.warning("N-1 reset failed to converge as expected:", exc=repr(exc))

    # 3) ContingencyAnalysisCPP comparison on backend grid
    logger.info("Running ContingencyAnalysisCPP on backend grid...")
    backend_grid = env.backend._grid  # type: ignore[attr-defined]
    computer = ContingencyAnalysisCPP(backend_grid)

    # l_id=42 is assumed to be CHALOL61CPVAN according to your remark
    computer.add_n1(42)

    v_init = 1.0 * env.backend.V  # type: ignore[attr-defined]
    max_it = getattr(env.backend, "max_it", 20)
    tol = getattr(env.backend, "tol", 1e-8)

    computer.compute(v_init, max_it, tol)
    _ = computer.get_voltages()
    computer.compute_flows()
    _amps = 1e3 * computer.get_flows()

    logger.info("Contingency analysis completed.", _amps=_amps)


if __name__ == "__main__":

    print("Launching n1_state_check main()...")
    main()

Current output

The current output is:

2026-03-27 12:37:52 [info     ] Resetting env with N-state (line in service)...
2026-03-27 12:37:52 [info     ] N-state reset converged        obs_type=<class 'grid2op.Space.GridObjects.ObservationAF2024_env_dijon_v2_trainingLightSimBackend_6_allowDetach'>
2026-03-27 12:37:52 [info     ] Resetting env with N-1 state (line out of service)...
2026-03-27 12:37:52 [warning  ] N-1 reset failed to converge as expected: exc='Grid2OpException Grid2OpException("Impossible to initialize the powergrid, the powerflow diverge at iteration 0. Available information are: {\'disc_lines\': array([-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,\\n       -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,\\n       -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,\\n       -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,\\n       -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,\\n       -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,\\n       -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,\\n       -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,\\n       -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,\\n       -1, -1, -1, -1, -1, -1, -1], dtype=int32), \'is_illegal\': False, \'is_ambiguous\': False, \'failed_redispatching\': False, \'is_illegal_reco\': False, \'reason_alarm_illegal\': None, \'reason_alert_illegal\': None, \'opponent_attack_line\': None, \'opponent_attack_sub\': None, \'opponent_attack_duration\': 0, \'exception\': [Grid2OpException BackendError BackendError(\'Divergence of AC powerflow. Detailed error: ErrorType.TooManyIterations\')], \'time_series_id\': None, \'rewards\': {}}")'
2026-03-27 12:37:52 [info     ] Running ContingencyAnalysisCPP on backend grid...
2026-03-27 12:37:52 [info     ] Contingency analysis completed. _amps=array([[1.17727103e+01, 3.94480422e+01, 6.48559203e+01, 6.80139859e+01,
        2.08241015e-02, 7.87608265e+01, 8.28089463e+01, 8.29597174e+01,
        8.29597174e+01, 3.94280573e+01, 4.97021740e+01, 1.24445688e+03,
        1.87252329e+03, 1.24434261e+03, 1.85859200e+03, 0.00000000e+00,
        1.77459483e+02, 1.31646964e+03, 1.22632106e+03, 5.64175887e+01,
        1.20197632e+03, 5.15018600e+01, 0.00000000e+00, 0.00000000e+00,
        2.37228176e+02, 2.05270877e+02, 2.27772449e+02, 2.38915696e+02,
        1.34832449e+02, 7.66473311e+01, 7.72293572e+01, 2.31952087e+02,
        2.29944313e+02, 2.16770016e+02, 1.14483151e+02, 7.75370603e+01,
        2.62266046e+02, 2.71985343e+02, 3.96841915e+02, 3.89465406e+02,
        5.82506909e+02, 3.35093319e+02, 0.00000000e+00, 2.85185469e+02,
        2.85190037e+02, 6.20648167e+01, 5.97891048e+01, 6.10794335e+01,
        3.72156441e+00, 2.91862969e-12, 0.00000000e+00, 2.20399881e+02,
        7.71261631e+01, 1.98464367e+02, 1.43031645e+02, 5.24962355e+01,
        2.30534202e+02, 2.16281316e+02, 7.19097379e+01, 1.53260021e+02,
        1.04782409e+02, 1.53285668e+02, 5.08151497e+02, 6.10907179e+02,
        8.27218102e+02, 1.62768541e+03, 8.26051380e+02, 1.62869076e+03,
        1.03428410e+02, 6.66275791e+01, 6.29111248e+00, 0.00000000e+00,
        8.09099180e+01, 1.61417576e+02, 1.59781912e+02, 3.35180262e+02,
        0.00000000e+00, 1.15373222e+02, 2.86819059e+01, 0.00000000e+00,
        3.60419434e-01, 3.60261754e-01, 3.95578479e-01, 0.00000000e+00,
        0.00000000e+00, 6.99603466e+02, 6.08111161e+02, 7.44851645e+01,
        1.50041360e+02, 1.21315647e+02, 3.77451254e+01, 1.83378982e+02,
        3.41376747e+01, 4.83199241e+02, 2.80524388e+01, 1.85431893e+02,
        2.26169128e+02, 2.26169128e+02, 6.66901431e+01, 7.79735433e+01,
        2.04829522e+02, 1.35432582e+02, 6.69994239e+02, 6.70387506e+02,
        1.24380324e+02, 7.69856742e+01, 1.12603091e+02, 3.21264742e+02,
        7.21588120e+01, 1.70927661e+01, 6.57526850e+01, 8.54097806e+01,
        5.96526758e+01, 1.11411063e+02, 5.08825360e+01, 1.31646964e+03,
        1.22632106e+03, 5.64175887e+01, 1.20197632e+03, 8.67573338e+01,
        8.67495980e+01, 1.89952063e+02, 1.91465486e+02, 1.92455027e+02,
        2.04887537e+01, 5.77660769e+01, 4.81745829e+01, 6.99861519e+01,
        4.67452146e+01, 7.36204071e+01, 4.74323274e+02, 0.00000000e+00,
        0.00000000e+00, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00,
        0.00000000e+00, 0.00000000e+00, 2.96925080e+02, 2.45329917e+02,
        2.72628806e+02, 9.52325838e+01, 0.00000000e+00, 7.69856742e+01,
        1.87279537e+03, 1.85877491e+03, 4.05956919e+01, 5.70278127e+01,
        9.20613143e+01, 0.00000000e+00, 3.18176001e+01, 3.19592476e+01,
        8.42168676e+01, 0.00000000e+00, 0.00000000e+00, 0.00000000e+00,
        1.10254511e+00, 1.19707929e+01, 1.00932223e+02, 6.73117091e+01]])

Expected output

We want the contingency analysis to fail due to powerflow divergence.

_amps=array([[0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
  0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
  0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
  0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
  0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
  0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.
  0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0. 0.]])

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions