Network importers (I/O)¶
External-network importers convert existing power-system models from
third-party tools into a groundinsight.Network. They do not build
grounding networks from scratch — instead they project an existing
distribution-network topology onto the bus / branch primitives that
groundinsight solves for, while the user supplies the grounding-side
impedance formulas via an ImportDefaults object. The first
inhabitant is the pandapower importer; PowerFactory .dgs and the
live PowerFactory Python API are on the roadmap.
Two kinds of import are on this page. The topology import
(preview_pandapower_import, from_pandapower) answers which buses
and branches exist. The short-circuit result import
(read_shortcircuit_results, apply_shortcircuit_characteristics)
answers how hard the fault drives them — it takes a case already
solved by pandapower.shortcircuit.calc_sc and turns it into the
IEC 60909 excitation of the grounding model. The two are independent:
a hand-built Network can take the short-circuit import just as well,
as long as its bus names match.
Physical / modelling context¶
Distribution-network databases (pandapower, PowerFactory, NEPLAN,
PSS®E) carry the electrical topology: buses, lines, transformers,
their resistance and reactance per phase. They do not carry the
grounding-side model — neither the bus grounding impedance
\(Z_{\text{B}}(\rho_E, f)\) nor the cable-shield self / mutual
impedance \(Z_{\text{self}}, Z_{\text{mutual}}\) that
groundinsight needs. The importer therefore adopts the pandapower
topology (which buses exist, which lines connect them, how long the
lines are) and asks the user to supply the grounding-side
parameters via ImportDefaults. That separation keeps the importer
schema-stable across tools — every importer accepts the same
ImportDefaults shape — and lets the user attach the same set of
grounding formulas to networks coming from different sources.
The active flag (default True) is propagated from
pandapower.in_service so that out-of-service equipment is excluded
from the nodal solve straight away — see the
outage-study reference for the runtime equivalent.
Example¶
import pandapower.networks as pn
import groundinsight as gi
# Any pandapower MV / LV demo network — here a simple 4-bus MV ring.
net_pp = pn.example_simple()
defaults = gi.ImportDefaults(
rho=100.0,
frequencies=[50.0, 250.0],
default_bus_type=gi.BusType(
name="MVbus",
description="Default substation grounding grid",
system_type="Substation",
voltage_level=20.0,
impedance_formula="rho * 0.01 + j * f * 1/50 * 0.1",
),
default_branch_type=gi.BranchType(
name="MVcable",
description="Default 20 kV cable",
grounding_conductor=True,
self_impedance_formula="(0.25 + j * f * 0.012) * l",
mutual_impedance_formula="(0.0 + j * f * 0.012) * l",
),
)
# 1. Pre-flight summary: kept vs. skipped elements with a reason column.
preview = gi.preview_pandapower_import(net_pp, voltage_level_kV=20.0)
print(preview)
# 2. Build the Network. Only buses / lines on the chosen voltage
# level are imported; switches, ext_grids, sgens, loads are ignored.
net = gi.from_pandapower(
net_pp,
defaults=defaults,
voltage_level_kV=20.0,
network_name="MV ring (from pandapower)",
)
print(f"Imported {len(net.buses)} buses, {len(net.branches)} branches.")
The pandapower extra is optional; install with
pip install 'groundinsight[pandapower]' (or
poetry install --extras pandapower).
Skip-reason vocabulary¶
Both preview_pandapower_import and from_pandapower produce
warning records on the groundinsight.io.pandapower_import logger
whenever they discard a row. The reason column of the preview frame
matches the warning text:
| Reason | Element | Trigger |
|---|---|---|
voltage_level_mismatch |
bus | bus.vn_kv != voltage_level_kV. |
vn_kv_unparsable |
bus | bus.vn_kv is missing, None, or NaN. New in 0.5. |
endpoint_off_target_voltage_level |
line | One endpoint sits on a different voltage level. |
endpoint_bus_missing |
line | An endpoint bus could not be resolved. |
self_loop |
line | from_bus == to_bus. New in 0.5. |
If the importer produces a Network with zero buses or zero branches
it now also emits a logger.warning so a wrong voltage_level_kV
argument fails loudly rather than silently.
Short-circuit characteristics (IEC 60909-0)¶
The grounding model is excited by a fault current, and a limit check
needs more than its RMS magnitude: the peak current \(i_p\) and the
thermally equivalent current \(I_{th}\) both depend on the fault-loop
\(R/X\) and the clearing time \(t_k\). Those follow from the phase-side
short-circuit calculation, which pandapower already performs. Rather
than re-implementing IEC 60909 next to it, groundinsight reads the
solved case.
read_shortcircuit_results converts net.res_bus_sc into a tidy,
unit-explicit Polars frame — one row per bus, currents in amperes
(not kA) to match the rest of the package. The fault type, case and
clearing time are taken from net._options, so a solved net is
self-describing and the call usually needs no arguments.
apply_shortcircuit_characteristics then writes the row belonging to
the fault bus onto the model: the loop \(R/X\) and \(\kappa\) go to every
source feeding the fault, together with that source's share of
\(I_k''\); the clearing time \(t_k\) and the AC heat factor \(n\) go to the
Fault, where they belong — they describe the protection, not the
infeed. With set_source_values=True the source injections are
overwritten by those shares as well, which is off by default so a
hand-tuned excitation is never silently replaced. The returned frame
is an audit trail: it carries i_k_previous_a next to i_k_a, so a
review sees exactly what changed.
import pandapower.shortcircuit as sc
import groundinsight as gi
sc.calc_sc(net_pp, fault="1ph", case="max", ip=True, ith=True, tk_s=0.5)
# Inspect first ...
gi.read_shortcircuit_results(net_pp).select(
"bus_name", "i_k_a", "r_to_x", "kappa", "kappa_origin", "i_p_a", "i_th_a"
)
# ... then write onto the grounding model and check the conductors.
gi.apply_shortcircuit_characteristics(
net_gi, net_pp, "fault1", set_source_values=True
)
gi.run_fault(net_gi, "fault1")
gi.check_conductor_limits(net_gi, "fault1")
Two deliberate deviations from pandapower¶
Both are documented in the module docstring of
groundinsight.io.pandapower_sc and are visible in the output, not
hidden:
fault="1ph" — the case relevant for grounding studies — leaves
pandapower's ip_ka and ith_ka entirely NaN; the quantities are
only published for the polyphase faults. read_shortcircuit_results
fills them in from the closed form
\(\kappa = 1.02 + 0.98\,e^{-3R/X}\), evaluated on the earth-fault
loop \(R/X = (2R_1 + R_0)/(2X_1 + X_0)\) rather than on the
positive-sequence ratio \(R_1/X_1\) — the loop the earth-fault current
actually traverses. Where pandapower does publish ip_ka (the
polyphase cases), its topology-aware value is used unchanged. The
kappa_origin column records which of the two applied
("pandapower", "iec_closed_form" or "unavailable"); keep it in
reports, it is the difference between a reproducible study and a magic
number.
I_th is recomputed rather than taken over, because pandapower's
_calc_ith sets the DC heat factor \(m\) to zero for \(\kappa > 1.99\),
where the analytic limit is \(m \to 2\). A vanishing \(m\) makes \(I_{th}\)
too small — the unsafe direction for a limit check — and precisely
in the high-\(\kappa\), DC-dominated cases where the thermal stress is
worst.
Walkthrough notebook¶
The end-to-end example for the pandapower importer is shipped as Pandapower import in the Examples section. It shows the full preview → commit flow on a real MV ring net and is the recommended starting point for case-study setups based on existing distribution-network models.
API reference¶
io ¶
External-network importers.
This sub-package converts existing power-system models from third-party
tools (pandapower today, PowerFactory .dgs next) into a
:class:groundinsight.models.core_models.Network. Importers do not
build grounding networks from scratch -- they project an existing
distribution-network topology onto the bus/branch primitives that
groundinsight solves for, and let the user supply the impedance
formulas via :class:ImportDefaults.
Beyond the topology, the sub-package also imports results: a solved pandapower short-circuit case can be read as IEC 60909 characteristic quantities and applied to the sources and faults of an existing groundinsight model, which is what feeds the thermal conductor check.
Public API:
- :class:
ImportDefaults - :func:
groundinsight.io.pandapower_import.from_pandapower - :func:
groundinsight.io.pandapower_import.preview_pandapower_import - :func:
groundinsight.io.pandapower_sc.read_shortcircuit_results - :func:
groundinsight.io.pandapower_sc.apply_shortcircuit_characteristics
The importers themselves live in tool-specific submodules so that the optional third-party dependency is loaded lazily.
ImportDefaults ¶
Bases: BaseModel
Per-import defaults shared by every external-network importer.
Attributes:
| Name | Type | Description |
|---|---|---|
rho |
float
|
Specific earth resistance applied to every imported :class: |
frequencies |
list of float
|
Frequencies at which impedance values are evaluated. Becomes
|
default_bus_type |
BusType
|
Default :class: |
default_branch_type |
BranchType
|
Default :class: |
Examples:
>>> from groundinsight.models.core_models import BusType, BranchType
>>> from groundinsight.io import ImportDefaults
>>> defaults = ImportDefaults(
... rho=100.0,
... frequencies=[50.0],
... default_bus_type=BusType(
... name="ImportedBus",
... system_type="Grounded",
... voltage_level=20.0,
... impedance_formula="rho * 0 + 1.0 + I * f * 0",
... ),
... default_branch_type=BranchType(
... name="ImportedCable",
... grounding_conductor=True,
... self_impedance_formula="(0.25 + I * 0.6) * l",
... mutual_impedance_formula="(0.0 + I * 0.6) * l",
... ),
... )
apply_shortcircuit_characteristics ¶
apply_shortcircuit_characteristics(
network: Network,
sc_results,
fault_name: str,
*,
pp_bus: Optional[int] = None,
bus_name: Optional[str] = None,
sources: Optional[Sequence[str]] = None,
set_source_values: bool = False,
frequency: Optional[float] = None,
t_k_s: Optional[float] = None,
n_factor: Optional[float] = None
) -> pl.DataFrame
Write pandapower short-circuit characteristics onto a groundinsight model.
Takes the IEC 60909 quantities computed at the fault bus and
distributes them over the sources that feed that fault: every source
receives the loop's r_to_x and kappa, and a share of I_k''
proportional to its present injection. The clearing time T_k and
the AC heat factor n are written onto the :class:Fault, where
they belong — they describe the protection, not the infeed.
With set_source_values=True the source injections themselves are
overwritten with those shares, turning a pandapower earth-fault result
directly into the excitation of the grounding model. This is off by
default so an existing, hand-tuned excitation is never silently
replaced.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
network
|
Network
|
The groundinsight network to update, in place. |
required |
sc_results
|
pandapowerNet or DataFrame
|
Either a solved pandapower net (passed through
:func: |
required |
fault_name
|
str
|
Name of the :class: |
required |
pp_bus
|
int
|
pandapower bus index to read the characteristics from. Defaults to
the row whose |
None
|
bus_name
|
str
|
Alternative to |
None
|
sources
|
sequence of str
|
Restrict the update to these sources. Defaults to every source with a path to the fault, or — if no paths are built — every active source in the network. |
None
|
set_source_values
|
bool
|
If |
False
|
frequency
|
float
|
Frequency at which the shares are computed and, if requested, written. Defaults to the lowest positive network frequency. |
None
|
t_k_s
|
float
|
Overrides the clearing time from |
None
|
n_factor
|
float
|
Overrides the AC heat factor from |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
An audit trail with one row per updated source: |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the fault does not exist, if both |
Notes
Only the linear quantities are distributed. i_p and I_th are
deliberately not written per source: they are non-linear in the
current and must be evaluated on the aggregate, which is what
:func:~groundinsight.analysis.shortcircuit.resolve_fault_sc_characteristics
does.
Fault.t_k_s and Fault.n_factor describe the protection, and a
short-circuit run only occasionally knows anything about it. They are
therefore updated conservatively: a clearing time is written only if
the calc_sc run genuinely carried one (see
:func:_tk_s_from_options) or t_k_s=... is passed here, and an AC
heat factor only if it differs from the neutral 1.0 or n_factor=...
is passed here. Anything else leaves the value on the fault untouched
instead of resetting it to a pandapower placeholder — a shortened
T_k raises the admissible current by 1/sqrt(t_k) and would make
an undersized conductor pass. Whenever a value is replaced by a
different one, a logger.warning names both.
Examples:
>>> rep = gi.apply_shortcircuit_characteristics(net_gi, net_pp, "F1")
>>> rep.select("source_name", "i_k_previous_a", "i_k_a", "kappa")
Source code in src/groundinsight/io/pandapower_sc.py
546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 | |
from_pandapower ¶
from_pandapower(
net,
*,
defaults: ImportDefaults,
voltage_level_kV: float,
network_name: Optional[str] = None,
include_trafos: bool = False
) -> Network
Build a :class:~groundinsight.models.core_models.Network from a
pandapower net, restricted to a single voltage level.
The mapping is intentionally narrow:
pp.busrows whosevn_kvmatchesvoltage_level_kVbecome :class:~groundinsight.models.core_models.Businstances usingdefaults.default_bus_type.in_service=Falsepropagates toBus.active=Falseso the outage helpers can reason over them.pp.linerows whose endpoints both lie on the target voltage level become :class:~groundinsight.models.core_models.Branchinstances usingdefaults.default_branch_typewithlength=length_km.in_service=Falsepropagates toBranch.active=False.- Switches, ext_grids, sgens, loads etc. are ignored. Trafos are
skipped unless
include_trafos=True, which is reserved for a future release (raisesNotImplementedErrorfor now).
No fault, source or path is created — those are left to the caller.
length = length_km holds literally: a length is never rescaled.
A line whose length_km is missing (None / NaN / inf /
unparsable) is imported with a fallback of 1.0 km and a
logger.warning naming the row, because that fallback silently
rescales every impedance of the branch. A line whose length_km is
zero or negative is not importable at all and raises ValueError;
see :func:_reject_unusable_lengths for the reasoning and
:func:preview_pandapower_import for listing the affected rows
beforehand.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
net
|
pandapowerNet
|
A pandapower network object (the type is not imported eagerly to keep pandapower optional). |
required |
defaults
|
ImportDefaults
|
Project-level defaults; see :class: |
required |
voltage_level_kV
|
float
|
Voltage level ( |
required |
network_name
|
str
|
Name for the resulting Network. Defaults to |
None
|
include_trafos
|
bool
|
Reserved for a future release. Setting this to |
False
|
Returns:
| Type | Description |
|---|---|
Network
|
A new groundinsight Network containing the imported buses and
branches, with |
Raises:
| Type | Description |
|---|---|
ImportError
|
If pandapower is not installed. |
ValueError
|
If |
NotImplementedError
|
If |
Examples:
>>> import pandapower as pp
>>> from groundinsight.io import ImportDefaults, from_pandapower
>>> net = pp.networks.create_kerber_landnetz_freileitung_1()
>>> network = from_pandapower(
... net,
... defaults=defaults,
... voltage_level_kV=0.4,
... network_name="kerber_lv",
... )
Source code in src/groundinsight/io/pandapower_import.py
530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 | |
preview_pandapower_import ¶
preview_pandapower_import(
net,
*,
voltage_level_kV: float,
include_trafos: bool = False
) -> pl.DataFrame
Return a Polars DataFrame describing what :func:from_pandapower
would do on this net at this voltage level, without building a
Network.
The frame has the following columns:
kind--"bus","line"or"trafo"(the trafo kind only appears wheninclude_trafos=True).status--"keep"or"skip".pp_index-- The pandapower index of the row.name-- Resolved groundinsight name (with fallback).vn_kv-- Bus voltage level (Nonefor lines).from_bus-- Resolved from-bus name (lines only).to_bus-- Resolved to-bus name (lines only).length_km-- Line length in km (lines only). For a kept line this is the length :func:from_pandapowerwould assign, so preview and commit never disagree — including the_MISSING_LENGTH_FALLBACK_KMsubstitution (with the same warning) when pandapower carries no usable number. For a skipped line it is the raw parsed value,Nonewhen unusable.in_service-- Boolean flag from pandapower (best effort).reason-- Skip reason ifstatus == "skip",Noneotherwise.
The dtypes are pinned by _PREVIEW_SCHEMA rather than inferred, so
the frame is identical in shape for a two-bus net and for a
thousand-bus one, and an all-None column keeps a usable dtype.
Use this before committing to a full import to validate the mapping
or diagnose unexpectedly skipped elements. Unlike
:func:from_pandapower, this function never raises on bad data: a
line whose length_km is zero or negative — which aborts the
commit — is reported here as status="skip" with
reason="zero_length" / "negative_length", so every affected
row can be enumerated in one go before the import is attempted.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
net
|
pandapowerNet
|
The pandapower network to inspect. |
required |
voltage_level_kV
|
float
|
Voltage level to keep ( |
required |
include_trafos
|
bool
|
Mirrors the parameter of :func: |
False
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
One row per bus and one per line, with the dtypes of
|
Raises:
| Type | Description |
|---|---|
NotImplementedError
|
If |
Source code in src/groundinsight/io/pandapower_import.py
693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 | |
read_shortcircuit_results ¶
read_shortcircuit_results(
net,
*,
t_k_s: Optional[float] = None,
n_factor: float = 1.0,
f: float = 50.0,
buses: Optional[Sequence[int]] = None
) -> pl.DataFrame
Read a solved pandapower short-circuit case as IEC 60909 quantities.
Converts net.res_bus_sc into a tidy, unit-explicit
:class:polars.DataFrame in which the quantities missing from
pandapower's single line-to-earth path (kappa, i_p, I_th)
are filled in, and I_th is recomputed with a DC heat factor that is
correct in the kappa -> 2 limit. See the module docstring for the
reasoning.
The fault type, case and clearing time are taken from net._options
(written by calc_sc) so that a solved net is self-describing; the
t_k_s and n_factor arguments override them. The clearing time
is adopted only when the calc_sc run genuinely carried one --
pandapower stores its signature default tk_s=1.0 even for a run
that asked for no thermal current at all, and passing that placeholder
off as a protection setting is unsafe; see :func:_tk_s_from_options.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
net
|
pandapowerNet
|
A pandapower network on which |
required |
t_k_s
|
float
|
Short-circuit duration |
None
|
n_factor
|
float
|
IEC 60909-0 AC heat factor |
1.0
|
f
|
float
|
System frequency in Hz used in the |
50.0
|
buses
|
sequence of int
|
Restrict the output to these pandapower bus indices. Defaults to
every bus in |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
One row per bus with the columns |
Raises:
| Type | Description |
|---|---|
ImportError
|
If pandapower is not installed. |
ValueError
|
If |
Notes
kappa_origin documents where each peak factor came from:
"pandapower" (recovered from ip_ka, topology-aware),
"iec_closed_form" (1.02 + 0.98*exp(-3*R/X)) or
"unavailable". Keep it in reports — it is the difference between a
reproducible study and a magic number.
Examples:
>>> import pandapower.shortcircuit as sc
>>> sc.calc_sc(net, fault="1ph", case="max", tk_s=0.5)
>>> gi.read_shortcircuit_results(net).select("bus_name", "i_k_a", "kappa")
Source code in src/groundinsight/io/pandapower_sc.py
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 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 | |