Network operations¶
High-level factory functions to build a Network and run fault
calculations. These are the functions re-exported on the top-level
package as groundinsight.create_network,
groundinsight.run_fault, etc.
Physical / modelling context¶
A Network is a graph: each Bus represents a grounding node with
an own earthing impedance \(Z_E(f, \rho)\), every Branch a
metallic return path with self-impedance
\(Z_\text{branch}(f, l)\) and an optional mutual coupling that
appears as a Norton injection along the source-to-fault path.
Building the network and triggering a fault therefore requires four
ingredients:
- Types —
BusType,BranchTypecarrying SymPy formula strings in the free symbolsrho,fand (for branches)l. - Instances —
Bus,Branchwith concrete values forspecific_earth_resistance,length,parallel_coefficient. - Excitation — at least one
Source(current or voltage) and oneFaultdeclaring which bus is the ground-fault location and which fault scaling per frequency applies. - Frequency list — the harmonics or per-fault spectrum at which the formula-based impedances are evaluated.
run_fault then assembles the nodal admittance matrix, runs the
sparse LU solve per frequency, and stores the result on the
Network (net.results).
Example¶
import groundinsight as gi
# 1. Frequency list and types
net = gi.create_network(name="demo", frequencies=[50.0, 250.0])
bus_type = gi.BusType(
name="GroundRod",
system_type="Substation",
voltage_level=20.0,
impedance_formula="rho/(2*3.14159*1.5)*(1 + j*0.01*f)",
)
branch_type = gi.BranchType(
name="ShieldCable",
grounding_conductor=True,
self_impedance_formula="(0.2 + j*0.4*f/50)*l",
mutual_impedance_formula="(0.0 + j*0.4*f/50)*l",
)
# 2. Instances
gi.create_bus(name="bus_substation", type=bus_type,
specific_earth_resistance=100.0, network=net)
gi.create_bus(name="bus_fault", type=bus_type,
specific_earth_resistance=100.0, network=net)
gi.create_branch(name="line_1", type=branch_type,
from_bus="bus_substation", to_bus="bus_fault",
length=2.0, network=net)
# 3. Source and fault
gi.create_source(name="src1", bus="bus_substation",
values={50.0: 1.0, 250.0: 0.05}, network=net)
gi.create_fault(name="f1", bus="bus_fault",
scalings={50.0: 1.0, 250.0: 0.05}, network=net)
# 4. Run
gi.run_fault(network=net, fault_name="f1")
print(net.res_buses(fault="f1"))
The same workflow scales to multi-source ring or mesh topologies;
auto_parallel_coefficients=True activates an auxiliary phase-only
solve that derives per-path current shares automatically.
A network without excitation is rejected¶
Step 3 is not optional. Path enumeration runs over
sources × faults, so a network missing either side yields no paths
at all — and the calculation used to run to completion anyway and
report 0 V at every bus. That is a plausible-looking answer, and
the most common way to arrive at it is a forgotten
gi.create_source(...), not a network that is genuinely unexcited.
create_paths — and therefore run_fault, which rebuilds the paths
itself — now raises a ValueError naming the missing side.
Finding no path between an existing source and an existing fault is a different matter and stays permitted: that is exactly what an outage scenario islanding the fault bus produces, and 0 V is then the correct answer.
create_network_assistant and the n−1 rule¶
A line of n buses has n − 1 branches, so branch_length needs
n − 1 entries:
net = gi.create_network_assistant(
name="Line30", frequencies=[50.0, 250.0], number_buses=30,
bus_type=bus_type, branch_type=branch_type,
branch_length=[1.0] * 29, # 29, not 30
specific_earth_resistance=100.0,
)
Passing n lengths used to drop the last one silently — the two
tests in this repository that did so had been asserting against a
shorter line than they thought for as long as they existed —
and passing too few raised a bare IndexError from inside the loop.
Both now raise a ValueError that states the two counts.
API reference¶
network_operations ¶
Network Operations Module.
This module provides functions for managing the electrical network, including creating networks,
buses, branches, faults, and sources. It also includes functions to build the electrical network,
define paths, and run fault calculations. These operations utilize the core models defined in
groundinsight.models.core_models and interact with the Network instance to perform necessary
calculations and updates.
build_electrical_network ¶
Build the electrical network and attach it to the :class:Network object.
Initialises an :class:ElectricalNetwork helper based on the physical
network's configuration and assigns it to the
electrical_network attribute of the provided :class:Network
instance. This step is invoked automatically by :func:run_fault.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
network
|
Network
|
The network instance for which the electrical network is to be built. |
required |
auto_phase_currents
|
bool
|
If |
False
|
Raises:
| Type | Description |
|---|---|
ImportError
|
If the :class: |
Examples:
Source code in src/groundinsight/network_operations.py
create_branch ¶
create_branch(
name: str,
type: BranchType,
from_bus: str,
to_bus: str,
length: float,
specific_earth_resistance: Optional[float] = 100,
description: str = None,
network: Optional[Network] = None,
parallel_coefficient: Optional[float] = 1.0,
) -> Branch
Create a new :class:Branch instance and optionally add it to a network.
If a :class:Network instance is provided, the branch is added to the
network, which also triggers the self- and mutual-impedance
calculations against the network's frequency list.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The name of the branch. |
required |
type
|
BranchType
|
The type of the branch. |
required |
from_bus
|
str
|
The name of the originating bus. |
required |
to_bus
|
str
|
The name of the terminating bus. |
required |
length
|
float
|
The length of the branch (km). |
required |
specific_earth_resistance
|
float
|
The specific earth resistance for the branch (Ohm * m). Defaults
to |
100
|
description
|
str
|
A brief description of the branch. Defaults to |
None
|
network
|
Network
|
The network to which the branch should be added. Defaults to
|
None
|
parallel_coefficient
|
float
|
Per-branch share of the source-to-fault phase current; used by
the path-based mutual-coupling injection. Defaults to |
1.0
|
Returns:
| Type | Description |
|---|---|
Branch
|
A newly created :class: |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the specified |
Examples:
>>> import groundinsight as gi
>>> branch_type = gi.BranchType(
... name="StandardBranch", grounding_conductor=True,
... self_impedance_formula="(1 + j * f / 50)*l",
... mutual_impedance_formula="(0.5 + j * f / 100)*l",
... )
>>> branch = gi.create_branch(
... name="Branch1", type=branch_type,
... from_bus="Bus1", to_bus="Bus2", length=1.0,
... )
>>> branch.name
'Branch1'
Source code in src/groundinsight/network_operations.py
120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 | |
create_bus ¶
create_bus(
name: str,
type: BusType,
specific_earth_resistance: Optional[float] = 100,
description: str = None,
network: Optional[Network] = None,
) -> Bus
Create a new :class:Bus instance and optionally add it to a network.
If a :class:Network instance is provided, the bus is added to the
network, which also triggers the impedance calculation against the
network's frequency list.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The name of the bus. |
required |
type
|
BusType
|
The type of the bus. |
required |
specific_earth_resistance
|
float
|
The specific earth resistance for the bus (Ohm * m). Defaults to
|
100
|
description
|
str
|
A brief description of the bus. Defaults to |
None
|
network
|
Network
|
The network to which the bus should be added. Defaults to |
None
|
Returns:
| Type | Description |
|---|---|
Bus
|
A newly created :class: |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the bus cannot be added to the provided network. |
Examples:
>>> import groundinsight as gi
>>> bus_type = gi.BusType(
... name="StandardBus", system_type="Grounded", voltage_level=110,
... impedance_formula="1 + j * f / 50",
... )
>>> network = gi.create_network(name="TestNetwork", frequencies=[50, 60])
>>> bus = gi.create_bus(
... name="Bus1", type=bus_type,
... specific_earth_resistance=100.0, network=network,
... )
>>> bus.name
'Bus1'
Source code in src/groundinsight/network_operations.py
create_fault ¶
create_fault(
name: str,
bus: str,
scalings: Dict,
active: bool = False,
description: str = None,
network: Optional[Network] = None,
t_k_s: Optional[float] = None,
n_factor: float = 1.0,
) -> Fault
Create a new :class:Fault instance and optionally add it to a network.
If a :class:Network instance is provided, the fault is added to the
network. If active=True, the fault becomes the currently active
fault in the network.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The name of the fault. |
required |
bus
|
str
|
The name of the bus where the fault occurs. |
required |
scalings
|
dict of float to float
|
Scaling factors applied to the source currents at each frequency. |
required |
active
|
bool
|
Whether to activate the fault immediately upon creation. Defaults
to |
False
|
description
|
str
|
A brief description of the fault. Defaults to |
None
|
network
|
Network
|
The network to which the fault should be added. Defaults to
|
None
|
t_k_s
|
float
|
IEC 60909-0 short-circuit duration |
None
|
n_factor
|
float
|
IEC 60909-0 AC heat factor |
1.0
|
Returns:
| Type | Description |
|---|---|
Fault
|
A newly created :class: |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the specified bus does not exist in the provided network, if
|
Examples:
>>> import groundinsight as gi
>>> network = gi.create_network(name="TestNetwork", frequencies=[50, 60])
>>> fault = gi.create_fault(
... name="Fault1", bus="Bus1",
... scalings={50: 1.0, 60: 0.8},
... active=True, network=network,
... )
>>> fault.name
'Fault1'
Source code in src/groundinsight/network_operations.py
217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 | |
create_network ¶
Create a new network with the given name and description.
Initialises a :class:Network instance with the specified name,
frequency list and an optional description.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The name of the network. |
required |
frequencies
|
list of float
|
Frequencies (in Hz) at which network calculations are performed. |
required |
description
|
str
|
A brief description of the network. Defaults to |
None
|
Returns:
| Type | Description |
|---|---|
Network
|
A newly created :class: |
Examples:
>>> import groundinsight as gi
>>> network = gi.create_network(
... name="TestNetwork",
... frequencies=[50, 60],
... description="A test electrical network",
... )
Source code in src/groundinsight/network_operations.py
create_network_assistant ¶
create_network_assistant(
name: str,
frequencies: List,
number_buses: int,
bus_type: BusType,
branch_type: BranchType,
branch_length: List,
specific_earth_resistance: float,
description: str = None,
) -> Network
Create a linear network with a uniform bus and branch type.
Initialises a :class:Network instance and populates it with
number_buses buses and number_buses - 1 branches connected
sequentially to form a line topology. Impedance calculations are
triggered upon adding buses and branches to the network.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The name of the network. |
required |
frequencies
|
list of float
|
Frequencies (in Hz) at which network calculations are performed. |
required |
number_buses
|
int
|
The total number of buses to create. |
required |
bus_type
|
BusType
|
The type to assign to each bus. |
required |
branch_type
|
BranchType
|
The type to assign to each branch. |
required |
branch_length
|
list of float
|
Lengths of each branch connecting the buses. Must have
|
required |
specific_earth_resistance
|
float
|
The specific earth resistance for all buses and branches (Ohm * m). |
required |
description
|
str
|
A brief description of the network. Defaults to |
None
|
Returns:
| Type | Description |
|---|---|
Network
|
A fully initialised :class: |
Raises:
| Type | Description |
|---|---|
ValueError
|
If |
Notes
A line of n buses has n - 1 branches, so branch_length
has one entry fewer than number_buses. Passing n lengths --
[1.0] * 30 for number_buses=30, read as "a 30 km line" -- used
to be accepted silently: the surplus entry was dropped and the
returned network was one span shorter than the one asked for, with no
warning anywhere. Too few entries raised a bare IndexError from
inside the loop.
Examples:
>>> import groundinsight as gi
>>> network = gi.create_network_assistant(
... name="Linear30", frequencies=[50, 250], number_buses=30,
... bus_type=bus_type, branch_type=branch_type,
... branch_length=[1.0] * 29, specific_earth_resistance=100.0,
... )
Source code in src/groundinsight/network_operations.py
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 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 | |
create_paths ¶
Create all paths between the sources and the faults of the network.
Identifies and maps each source to the ordered branch list of every
simple path to each fault. The identified paths are added to the
network's paths collection.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
network
|
Network
|
The network instance for which paths are to be defined. |
required |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the network defines no sources or no faults. Path enumeration
runs over |
Notes
The check is on all faults, not on active_fault:
:meth:~groundinsight.models.core_models.Network.define_paths
enumerates every (source, fault) pair, and run_fault sets the
active fault before it calls this function.
Finding no path between an existing source and an existing fault is a different matter and stays permitted -- that is exactly what an outage scenario which islands the fault bus produces, and the all-zero result is then the correct answer.
Examples:
Source code in src/groundinsight/network_operations.py
create_source ¶
create_source(
name: str,
bus: str,
values: Dict,
description: str = None,
network: Optional[Network] = None,
i_k_a: Optional[float] = None,
r_to_x: Optional[float] = None,
kappa: Optional[float] = None,
) -> Source
Create a new current :class:Source and optionally add it to a network.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The name of the source. |
required |
bus
|
str
|
The name of the bus where the source is located. |
required |
values
|
dict of float to ComplexNumber or complex or float
|
Frequency-resolved injected current. Real numbers are auto-promoted
to :class: |
required |
description
|
str
|
A brief description of the source. Defaults to |
None
|
network
|
Network
|
The network to which the source should be added. Defaults to
|
None
|
i_k_a
|
float
|
Initial symmetrical short-circuit current |
None
|
r_to_x
|
float
|
|
None
|
kappa
|
float
|
IEC 60909-0 peak factor. Takes precedence over |
None
|
Returns:
| Type | Description |
|---|---|
Source
|
A newly created :class: |
Raises:
| Type | Description |
|---|---|
ValueError
|
If the specified bus does not exist in the provided network, or if any of the IEC 60909 quantities is outside its physical range. |
Examples:
>>> import groundinsight as gi
>>> network = gi.create_network(name="TestNetwork", frequencies=[50, 60])
>>> source = gi.create_source(
... name="Source1", bus="Bus1",
... values={50: 10 + 5j, 60: 15 + 7j},
... network=network,
... )
>>> source.name
'Source1'
Source code in src/groundinsight/network_operations.py
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 | |
create_voltage_source ¶
create_voltage_source(
name: str,
bus: str,
voltage: Dict,
source_impedance: Dict,
description: str = None,
network: Optional[Network] = None,
) -> Source
Create a Thevenin (voltage) source and optionally add it to a network.
The Thevenin source models a frequency-dependent EMF voltage in
series with a finite source_impedance. In contrast to
:func:create_source, which creates an ideal current source for
stationary studies, this factory is intended for transient analyses
where the fault current is determined by the loop impedance
Z_src + Z_loop rather than being prescribed.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
name
|
str
|
The name of the source. |
required |
bus
|
str
|
The name of the bus where the source is located. |
required |
voltage
|
dict of float to ComplexNumber or complex
|
Frequency-dependent EMF. |
required |
source_impedance
|
dict of float to ComplexNumber or complex
|
Frequency-dependent internal impedance. Must use the same
frequency keys as |
required |
description
|
str
|
A brief description of the source. Defaults to |
None
|
network
|
Network
|
The network to which the source should be added. Defaults to
|
None
|
Returns:
| Type | Description |
|---|---|
Source
|
A newly created Thevenin source instance with
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If the specified bus does not exist in the provided network, or if the input dictionaries do not satisfy the voltage-mode constraints. |
Examples:
>>> import groundinsight as gi
>>> network = gi.create_network(name="TestNetwork", frequencies=[50])
>>> source = gi.create_voltage_source(
... name="VSrc1", bus="Bus1",
... voltage={50: 20000.0 + 0.0j},
... source_impedance={50: 0.5 + 0.1j},
... network=network,
... )
Source code in src/groundinsight/network_operations.py
run_fault ¶
run_fault(
network: Network,
fault_name: str,
auto_parallel_coefficients: Optional[bool] = None,
*,
phase_current_mode: str = "auto"
)
Execute the fault calculation pipeline for a single fault.
Sets the named fault as the active fault, builds the electrical
network, solves the per-frequency nodal system, computes branch
currents, reduction factors and grounding impedance. The results are
stored on network.results[fault_name].
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
network
|
Network
|
The network instance on which the fault calculations are to be performed. |
required |
fault_name
|
str
|
The name of the fault to activate and run calculations for. |
required |
auto_parallel_coefficients
|
bool
|
Deprecated alias for |
None
|
phase_current_mode
|
(auto, paths)
|
How the phase current per branch is determined.
|
"auto"
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If the specified fault does not exist in the network, or if
|
RuntimeError
|
If there is an error during the network calculations. |
Examples:
Source code in src/groundinsight/network_operations.py
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 | |
set_active_fault ¶
Activate fault_name on network and deactivate the others.
Thin wrapper around :meth:Network.set_active_fault so the
keep_results= keyword is reachable from the public top-level
API surface (gi.set_active_fault(net, "F1", keep_results=True))
rather than only via the bound method on the :class:Network
instance.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
network
|
Network
|
The network to operate on. |
required |
fault_name
|
str
|
The name of the fault to activate. |
required |
keep_results
|
bool
|
Forwarded to :meth: |
``False``
|
Raises:
| Type | Description |
|---|---|
ValueError
|
If the specified fault does not exist in |
Notes
Exposes the keep_results keyword at the top-level API surface
where it would otherwise only be reachable as a bound method.
Examples: