Outage and what-if studies¶
The groundinsight.simulation.outage module turns the new active
flag on Bus and Branch into a first-class what-if API. Multiple
topology scenarios (e.g. "cable A out", "substation isolator open",
"shield broken") can be evaluated against one base case in a single
call, with comparison DataFrames produced automatically.
Physical / modelling context¶
The nodal-admittance solver assembles
\(Y(f)\,\underline{u}(f) = \underline{i}(f)\) over active elements
only. An inactive Bus is removed from the system entirely (its row
and column drop from \(Y\)); an inactive Branch contributes no
self-admittance, no mutual-coupling Norton injection, and reports a
zero current in the result — the open-circuit limit. The pathfinder
also skips inactive elements when enumerating source-to-fault paths,
which keeps the per-path Norton bookkeeping consistent.
That makes maintenance scenarios, planned outages, broken shields,
and N-1 contingencies expressible as scenarios without rebuilding
the network. The outage_context context manager flips the listed
elements to active=False for the duration of a with block and
restores the previous state — including the cached path list —
afterwards. run_outage_study orchestrates a base run plus one fault
calculation per scenario and returns long-format Polars DataFrames
with absolute and relative deltas against a reference scenario
(default: the base case).
Example¶
import groundinsight as gi
# Assume a pre-built network `net` with sources, buses, branches and
# at least one fault, e.g. from the quickstart or from
# gi.from_pandapower(...).
scenario_cable_out = gi.Outage(
name="cable_1_oos",
description="MS-cable cable_1 out of service",
disabled_buses=[],
disabled_branches=["cable_1"],
)
scenario_bus_islanded = gi.Outage(
name="bus_x_islanded",
description="Bus bus_x removed from the network",
disabled_buses=["bus_x"],
disabled_branches=[],
)
study = gi.run_outage_study(
network=net,
fault="fault1",
scenarios=[scenario_cable_out, scenario_bus_islanded],
include_base_case=True,
)
# Per-scenario result DataFrames
print(study.compare_buses()) # EPR per bus, with delta vs. base
print(study.compare_branches()) # branch currents, with delta vs. base
Use outage_context(network, outage) directly when a single one-off
modification is needed:
with gi.outage_context(net, scenario_cable_out):
gi.run_fault(network=net, fault_name="fault1")
print(net.res_all_impedances())
# net is fully restored on exit — including the cached paths.
API reference¶
outage ¶
Outage / What-If Study Module.
Convenience layer on top of the active flag of
:class:groundinsight.models.core_models.Bus and
:class:groundinsight.models.core_models.Branch. An :class:Outage is a
named scenario that lists buses and/or branches to be deactivated; the
:func:outage_context context manager applies the scenario for the
duration of a with block and restores the previous state on exit, and
:func:run_outage_study runs run_fault for the base case and a list
of scenarios in one go and packages the results into an
:class:OutageStudyResult with ready-made comparison helpers.
Example:
>>> import groundinsight as gi
>>> # build a network, add fault "F1", then:
>>> study = gi.run_outage_study(
... network=net,
... fault="F1",
... scenarios=[
... gi.Outage(name="branch_b12_open", disabled_branches=["B12"]),
... gi.Outage(name="bus5_isolated", disabled_buses=["bus5"]),
... ],
... )
>>> df = study.compare_buses()
>>> df_branch = study.compare_branches()
Inactive elements are physically modelled as open circuits / removed nodes
in the solver (see :mod:groundinsight.electrical_network and
:mod:groundinsight.pathfinder); the outage layer does not duplicate that
logic, it only flips the active flag and re-runs the existing
run_fault pipeline.
Outage ¶
Bases: BaseModel
Definition of a single what-if scenario.
An outage is described by the names of buses and/or branches that should
be deactivated for the duration of the scenario. The names must refer to
elements that exist in the network when the scenario is applied;
unknown names raise ValueError at apply time.
Attributes:
| Name | Type | Description |
|---|---|---|
name |
str
|
Unique label used in result keys and DataFrame columns. |
disabled_buses |
List[str]
|
Names of buses to deactivate. |
disabled_branches |
List[str]
|
Names of branches to deactivate. |
description |
Optional[str]
|
Free-form description for documentation. |
OutageStudyResult ¶
Bases: BaseModel
Result of an outage study.
Holds bus and branch result DataFrames per scenario (and for the base
case, if it was included). The compare_* methods produce long-format
Polars DataFrames suitable for plotting or further aggregation.
Attributes:
| Name | Type | Description |
|---|---|---|
fault |
str
|
Name of the fault that was solved. |
base_label |
str
|
Label of the base scenario (typically |
scenarios |
List[Outage]
|
The scenarios in the order they were run. |
bus_results |
Dict[str, DataFrame]
|
|
branch_results |
Dict[str, DataFrame]
|
Same for branch results. |
compare_branches ¶
compare_branches(
*,
against: Optional[str] = None,
columns: Sequence[str] = ("I_branch_A",)
) -> pl.DataFrame
Long-format branch comparison across scenarios.
Same shape as :meth:compare_buses but keyed by
(branch_name, frequency_Hz).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
against
|
Optional[str]
|
Reference scenario label. |
None
|
columns
|
Sequence[str]
|
Branch result metrics to compare.
Defaults to |
('I_branch_A',)
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
pl.DataFrame: Long-format comparison frame. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/groundinsight/simulation/outage.py
compare_buses ¶
compare_buses(
*,
against: Optional[str] = None,
columns: Sequence[str] = ("EPR_V",)
) -> pl.DataFrame
Long-format bus comparison across scenarios.
For every (bus_name, frequency_Hz) pair the table contains one
row per scenario per metric, plus the delta_vs_<against> and
delta_pct_vs_<against> columns relative to the reference scenario.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
against
|
Optional[str]
|
Reference scenario label. Defaults to
|
None
|
columns
|
Sequence[str]
|
Bus result metrics to compare. Defaults
to |
('EPR_V',)
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
pl.DataFrame: Long-format frame with columns |
DataFrame
|
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Source code in src/groundinsight/simulation/outage.py
labels ¶
Return the labels of all stored scenarios.
The base label is included first (if present), followed by the user
scenarios in the order they were submitted to run_outage_study.
Returns:
| Type | Description |
|---|---|
List[str]
|
List[str]: Scenario labels. |
Source code in src/groundinsight/simulation/outage.py
outage_context ¶
Apply an :class:Outage to network for the duration of a with block.
On entry, the active flag of the listed buses and branches is set to
False. On exit, the original active values are restored even if
the body raises. Path information is invalidated on entry and on exit
because both the outage and its rollback can change topology.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
network
|
Network
|
The network to mutate in place. |
required |
outage
|
Outage
|
Scenario describing what to deactivate. |
required |
Yields:
| Name | Type | Description |
|---|---|---|
Network |
Network
|
The same |
Raises:
| Type | Description |
|---|---|
ValueError
|
If a name in |
Examples:
>>> with outage_context(net, Outage(name="b12", disabled_branches=["B12"])):
... gi.run_fault(net, "F1")
... df = net.res_buses()
Source code in src/groundinsight/simulation/outage.py
run_outage_study ¶
run_outage_study(
network: Network,
*,
fault: str,
scenarios: List[Outage],
include_base: bool = True,
base_label: str = "base",
auto_parallel_coefficients: bool = False,
redefine_paths: bool = True
) -> OutageStudyResult
Solve fault for the base case (optional) and a list of outage scenarios.
Each scenario is applied via :func:outage_context, then run_fault is
called. Bus and branch result DataFrames are pulled out of the network
and stored under the scenario label so that the network's own
results dict is left in a single, reproducible state at the end of
the study (the last scenario's solution).
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
network
|
Network
|
The network to study. Must contain |
required |
fault
|
str
|
Name of the fault to solve in every scenario. |
required |
scenarios
|
List[Outage]
|
Scenarios to run, in order. |
required |
include_base
|
bool
|
If |
True
|
base_label
|
str
|
Label used to identify the base case in the
stored DataFrames. Defaults to |
'base'
|
auto_parallel_coefficients
|
bool
|
Forwarded to |
False
|
redefine_paths
|
bool
|
If |
True
|
Returns:
| Name | Type | Description |
|---|---|---|
OutageStudyResult |
OutageStudyResult
|
Aggregated result with comparison helpers. |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Examples:
>>> study = run_outage_study(
... net,
... fault="F1",
... scenarios=[
... Outage(name="b12_open", disabled_branches=["B12"]),
... Outage(name="bus5_isolated", disabled_buses=["bus5"]),
... ],
... )
>>> study.compare_buses()
>>> study.compare_branches()
Source code in src/groundinsight/simulation/outage.py
344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 | |