Skip to content

Cosimulation that passes information from ExaGO to GridKit - #143

Closed
tsybinae wants to merge 2 commits into
developfrom
slaven/agentigrid-refactor
Closed

tsybinae wants to merge 2 commits into
developfrom
slaven/agentigrid-refactor

Conversation

@tsybinae

@tsybinae tsybinae commented Oct 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

After #135 GridKit ran only from the operating point stored in its own case files. If something was solved by ExaGO, it would not be transferred to GridKit. This PR completes the folder strucutre and adds a GridKit fault screen start from the most recent ExaGO steady state. A transient study can run on top of the latest ExaGO result. A screen can also run all of its faults in one GridKit ContingencyAnalysis process.

Old approach

config        exago: data: llm: search: output:          (GridKit values hard-coded in Python)
ExaGO run     workdir/iter_*/case.m                       (solution printed, parsed, not saved)
GridKit run   .case.json ── its own operating point ── DynamicSimulation, one process per fault

New approach

config        exago: data: llm: search: output: gridkit:
ExaGO run     workdir/exago/iter_*/case.m + opflowout.m / pflowout.m   (solved case saved)

GridKit run   .case.json ──┬── "start_from": "case"                 its own operating point
                           └── "start_from": "latest_steady_state"  newest ExaGO solution
                                  → its last simulation → solved .m
                                  → voltages, angles, dispatch, outages, network applied
                                    to the case's dynamic models
                                  → no-fault run must stay steady before any fault
              ── "application": "DynamicSimulation"     one process per fault (default)
                                "ContingencyAnalysis"   one process for all faults of the screen
  • The GridKit case still supplies the dynamic models (machines, governors, exciters); a MATPOWER case has none. The two cases must describe the same network. If AgentiGrid sees a case for which it does not have a case with models, it will report an error.

Changes

ExaGO folders and output

  • engine/ → exago_engine/, parsers/ → exago_parsers/ (files unchanged). Imports, patch("agentigrid.engine...") strings in tests and logger names updated in 76 files; logger names follow agentigrid.exago_engine..., as GridKit's do. Docs updated: README.md, tests/README.md, agentigrid_architecture.md, launcher/ARCHITECTURE.md.
  • cli.py: exago_output() puts ExaGO runs in workdir/exago/ and logs in logs/exago/ (GridKit already used gridkit/ subfolders). The steering save command (was hard-coded to workdir/) and regenerate-report follow it.
  • exago_engine/executor.py: OPFLOW and PFLOW run with -save_output, which writes the solved case (opflowout.m / pflowout.m) into the run folder. Other applications are unchanged.

One config for both tools (config.py, configs/*.template)

  • New gridkit: section: binary_dir, dynamicsimulation_binary, timeout; study: (tmax, dt_monitor, rel_tol, abs_tol, dt_fixed, max_steps, max_order), written one-to-one into each run's solver .json; fault: start. LLM, iteration limit, worker count and output folders stay shared.
  • Defaults equal the values the code used before (tmax 15 s, timeout 600 s, fault start 1 s), so behaviour and the LLM prompt text are unchanged with defaults.
  • PJM pass/fail limits are not configurable; they stay in gridkit_engine/criteria.py with their sources.
  • GridKit ignores misspelled solver keys without a message, so the loader warns about unknown keys; it also warns when max_order is outside 1-5, dt_monitor <= 0, or the fault starts at or after tmax.
  • gridkit_engine/settings.py, executor.py, agent_loop.py, commands.py, schema_description.py read the configured values.

Start from the latest ExaGO steady state (new gridkit_engine/from_exago.py)

  • find_latest_steady_state: newest journal_*.json in workdir/exago/, its last simulation, the run folder from the recorded -netfile. Error if that simulation did not converge, was a sweep, was not OPFLOW/PFLOW, or has no saved solution.
  • apply_steady_state matches the solved MATPOWER case to the GridKit case:
    • buses by number: init Vr, Vi from the solved Vm, Va
    • generators to machines by bus, then unit order: p0, q0 from the solved Pg, Qg; machines of generators that are off in ExaGO are removed with every device on their signals (governor, exciter, stabilizer), and unused signals are removed
    • loads: one constant-impedance LoadZIP per bus from Pd, Qd; shunts: a separate constant-impedance LoadZIP from Gs, Bs
    • branches rebuilt from the online ExaGO branches (R, X, B, tap on the bus-1 side, phase in radians)
    • errors: bus numbers differ, isolated buses, generator without a machine model, unit counts that cannot be matched at a bus
  • PFLOW's saved file always has converged = 0 (ExaGO writes the OPFLOW flag there), so convergence is read from the journal.
  • commands.py: new fault-screen field start_from ("case" default, "latest_steady_state"). Each start is built once per session and must pass its own no-fault run first; the converted case is saved as from_exago.case.json in the session folder.
  • agent_loop.py: if the goal asks for the most recent steady state and the LLM sends "start_from": "case", the screen is rejected and the LLM is told to set it. The journal, verdict line and final summary name the ExaGO iteration and file used.
  • cli.py: --from-exago uses the ExaGO start for the whole session.

ContingencyAnalysis (gridkit_engine/executor.py, modifier.py, gridkit_parsers/status_parser.py)

  • New fault-screen field application: "DynamicSimulation" (default) or "ContingencyAnalysis". run_contingency runs all faults of a screen in one process, writes output_<fault index>.csv per fault in the screen's folder, and splits the shared console output into a status per fault. Timeout = per-run timeout × number of faults.
  • ContingencyAnalysis runs every fault device in the file, so its case copy keeps only the requested faults (fault devices shipped with the case, such as the demo fault at bus 149 in ACTIVSg200, are left out). With DynamicSimulation they stay in the copy but are never switched on.
  • The status parser also reads ContingencyAnalysis's completion line (Complete in 1.91939s).

Prompt (prompts/gridkit/system_prompt.py)

  • A fault or connection request at a bus means that bus only ("buses": [<bus>]). The POI screen with neighbours ("poi", "hops") is used only when the goal asks for nearby buses, a depth or the PJM POI screen.
  • start_from and application are set only when the goal asks for them; the answer states the operating point used.

Tests

  • New in tests/gridkit/: test_config.py (gridkit section, defaults, warnings, solver keys in the .json), test_from_exago.py (journal lookup, no fallback, conversion on ACTIVSg200, generator without model, bus mismatch, -save_output only for OPFLOW/PFLOW), test_screen_options.py (start_from and application, live on IEEE39 and ACTIVSg200 when GridKit is linked). test_status_parser.py covers the ContingencyAnalysis completion line.

Testing

  • Fast suite: 1231 passed, 5 skipped (python -m pytest -m "not slow"). The 46 new GridKit tests pass, including the live runs.
  • Live runs with OpenAI gpt-4o, OPFLOW with Ipopt/MUMPS, GridKit at cdb77263. Fault: bus short circuit to ground at 1 s, cleared after 6.25 cycles (0.104 s), 15 s simulated. The ExaGO study removed the generator at the bus; the GridKit study then faulted the same bus.
Case ExaGO: generator out GridKit: fault at the same bus
ACTIVSg200, bus 135 (446 MW unit) feasible, V 1.058-1.100 pu, $27,558 → $24,635/h TAMU operating point: passes (angle spread 83.5°, Vmin 0.986 pu at 2.5 s, damping 5.3%)
latest ExaGO steady state, DynamicSimulation: does NOT pass, damping 2.5% < 3% (spread 45.4°)
latest ExaGO steady state, ContingencyAnalysis: same results, 0.3 s
ACTIVSg2000, bus 5360 (1008 MW unit) feasible, V 1.015-1.100 pu, $1,228,892 → $1,227,382/h TAMU operating point, GridKit with Enzyme: does NOT pass, damping 2.2% < 3% (spread 105.0°); base run 2.6 s, fault run 34 s
  • From the ExaGO start, the starting voltages equal the solved ones on all 200 buses (largest difference 2e-16 pu, 4e-15°), and the no-fault run moves angles 0.000° and voltages 0.0000 pu.
  • Cost drops when the unit is removed: in the base OPF the unit at bus 5360 ran at its 302.4 MW minimum (of 1008 MW), i.e. it was uneconomic. The unit at bus 135 is at its 133.9 MW minimum in the case file; its base OPF dispatch was not checked.
  • ACTIVSg2000 needs GridKit built with Enzyme: without it GridKit falls back to dense Jacobians, and the no-fault run hit the 600 s timeout with no time step on a 3.7 GB machine. Enzyme 0.0.206 + LLVM 16, RelWithDebInfo (as in GridKit's CI); the build is selected with gridkit.binary_dir.

Known issues

  • final_voltage fails a bus that the OPF holds exactly at its 1.1 pu limit when GridKit ends 1e-6 pu above it (bus 100 in the ACTIVSg200 runs: 1.100000 → 1.100001 pu). This is a false failure; a small tolerance would fix it. Not changed here.
  • The ExaGO start needs a GridKit case for the same network; AgentiGrid does not search for one or generate dynamic data. On ACTIVSg2000, 432 online generators face 334 machine models, so the ExaGO start will stop with the "no dynamic model" error there.
  • Upstream GridKit 0e6814ab (#607) removes BusFault (replaced by Bus::setFault); AgentiGrid's fault runs use BusFault devices and need adapting before moving past cdb77263.
  • GridKit with Enzyme does not build with Release (-O3): ReecbEnzyme.cpp fails with Enzyme errors; RelWithDebInfo (-O2) builds and passes ctest (70/70).
  • Every OPFLOW/PFLOW run now saves its solved case, sweep runs included, which adds disk use on large sweeps.
  • Remaining skips: two e2e tests need a machine-specific configs/env_setup.sh; one SOPFLOW test needs output from a real EMPAR run; two backend tests need ANTHROPIC_API_KEY / OPENAI_API_KEY.

Produced with help of Claude Code, but not exclusively by Claude.

tsybinae and others added 2 commits October 7, 2026 17:31
…t-only relief (#134)

- Study area from substation tiers (transformers join buses, lines are
  hops, default depth 3) instead of k nearest buses; five affected-element
  lists; new set_shunt command; mutation-driven contingency sweep.
- Verdict line and table of every failed contingency; per-element failure
  counts for N-2; studies over 500 contingencies are reported as not
  completable instead of being shrunk.
- Agent's full findings kept in the journal, CSV and saved session.
- Relief: tap change, line switching out or in, tiered load shedding
  capped at 10% per bus; added load never shed; generator_redispatch
  removed; relief only when the goal asks; relief in the sweep cache key.
- Objective extraction no longer invents objectives or limits.
- Prompt templates moved to prompts/exago/; restored identical-sweep rule.
- Live opflow test no longer overwrites tests/fixtures/sample_opflow_output.txt;
  test_solve_time matches the committed sample.

Co-authored-by: tsybinae <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
First build of the GridKit side of AgentiGrid (--tool gridkit). The LLM picks
a fault screen; AgentiGrid adds bus faults to a copy of the GridKit case, runs
DynamicSimulation, applies PJM M-14B stability checks and returns a verdict.
PJM tests that need elements GridKit does not have are reported as SKIPPED.

New: gridkit_parsers/, gridkit_engine/, prompts/gridkit/, tests/gridkit/.
Changed: cli.py (--tool flag), README.md and the GridKit folder READMEs,
tests/README.md. No ExaGO code changed.

Co-authored-by: tsybinae <[email protected]>
Co-authored-by: Claude Opus 5.5 <[email protected]>
@tsybinae tsybinae self-assigned this Oct 8, 2026
@tsybinae tsybinae added enhancement New feature or request ai Support for agentic AI capabilities labels Oct 8, 2026
@tsybinae tsybinae closed this Oct 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ai Support for agentic AI capabilities enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant