load-flow-engine 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. load_flow_engine-0.1.0/PKG-INFO +12 -0
  2. load_flow_engine-0.1.0/README.md +168 -0
  3. load_flow_engine-0.1.0/load_flow_engine/__init__.py +42 -0
  4. load_flow_engine-0.1.0/load_flow_engine/constants.py +12 -0
  5. load_flow_engine-0.1.0/load_flow_engine/enums.py +24 -0
  6. load_flow_engine-0.1.0/load_flow_engine/example.py +114 -0
  7. load_flow_engine-0.1.0/load_flow_engine/helpers.py +84 -0
  8. load_flow_engine-0.1.0/load_flow_engine/models.py +219 -0
  9. load_flow_engine-0.1.0/load_flow_engine/network.py +256 -0
  10. load_flow_engine-0.1.0/load_flow_engine/solver.py +546 -0
  11. load_flow_engine-0.1.0/load_flow_engine/tools/cim_adapter.py +720 -0
  12. load_flow_engine-0.1.0/load_flow_engine/tools/cyme_adapter.py +545 -0
  13. load_flow_engine-0.1.0/load_flow_engine/tools/diagnostics/__init__.py +194 -0
  14. load_flow_engine-0.1.0/load_flow_engine/tools/diagnostics/_common.py +163 -0
  15. load_flow_engine-0.1.0/load_flow_engine/tools/diagnostics/_diag_controls.py +105 -0
  16. load_flow_engine-0.1.0/load_flow_engine/tools/diagnostics/_diag_duplicates.py +136 -0
  17. load_flow_engine-0.1.0/load_flow_engine/tools/diagnostics/_diag_grounding.py +58 -0
  18. load_flow_engine-0.1.0/load_flow_engine/tools/diagnostics/_diag_impedance.py +94 -0
  19. load_flow_engine-0.1.0/load_flow_engine/tools/diagnostics/_diag_load_model.py +144 -0
  20. load_flow_engine-0.1.0/load_flow_engine/tools/diagnostics/_diag_open_conductor.py +132 -0
  21. load_flow_engine-0.1.0/load_flow_engine/tools/diagnostics/_diag_phase.py +150 -0
  22. load_flow_engine-0.1.0/load_flow_engine/tools/diagnostics/_diag_topology.py +181 -0
  23. load_flow_engine-0.1.0/load_flow_engine/tools/diagnostics/_diag_transformer.py +104 -0
  24. load_flow_engine-0.1.0/load_flow_engine/tools/diagnostics/_diag_voltage_base.py +151 -0
  25. load_flow_engine-0.1.0/load_flow_engine/tools/load_allocation.py +87 -0
  26. load_flow_engine-0.1.0/load_flow_engine/tools/multiconductor_adapter.py +577 -0
  27. load_flow_engine-0.1.0/load_flow_engine/tools/opendss_adapter.py +322 -0
  28. load_flow_engine-0.1.0/load_flow_engine/tools/output.py +89 -0
  29. load_flow_engine-0.1.0/load_flow_engine/tools/sqlite_adapter.py +367 -0
  30. load_flow_engine-0.1.0/load_flow_engine.egg-info/PKG-INFO +12 -0
  31. load_flow_engine-0.1.0/load_flow_engine.egg-info/SOURCES.txt +35 -0
  32. load_flow_engine-0.1.0/load_flow_engine.egg-info/dependency_links.txt +1 -0
  33. load_flow_engine-0.1.0/load_flow_engine.egg-info/entry_points.txt +2 -0
  34. load_flow_engine-0.1.0/load_flow_engine.egg-info/requires.txt +9 -0
  35. load_flow_engine-0.1.0/load_flow_engine.egg-info/top_level.txt +1 -0
  36. load_flow_engine-0.1.0/pyproject.toml +28 -0
  37. load_flow_engine-0.1.0/setup.cfg +4 -0
@@ -0,0 +1,12 @@
1
+ Metadata-Version: 2.4
2
+ Name: load-flow-engine
3
+ Version: 0.1.0
4
+ Summary: Three-phase unbalanced load flow solver
5
+ Requires-Python: >=3.9
6
+ Requires-Dist: numpy
7
+ Requires-Dist: scipy
8
+ Provides-Extra: dev
9
+ Requires-Dist: build; extra == "dev"
10
+ Requires-Dist: twine; extra == "dev"
11
+ Provides-Extra: notebooks
12
+ Requires-Dist: jupyter; extra == "notebooks"
@@ -0,0 +1,168 @@
1
+ # lfe-python
2
+
3
+ `lfe-python` is a three-phase unbalanced load flow engine written in Python. The project mirrors ETAP-style data structures and workflow while exposing a small, scriptable API for building distribution networks, assembling the admittance matrix, solving load flow, and exporting results.
4
+
5
+ The package published by this repository is `load-flow-engine`, and the main Python package is `load_flow_engine`.
6
+
7
+ ## Features
8
+
9
+ - Three-phase unbalanced load flow using Newton-Raphson or Gauss-Seidel
10
+ - Sparse `Y_abc` network assembly for buses, branches, transformers, loads, generators, shunts, and switches
11
+ - Per-phase bus voltage and branch loading results
12
+ - Built-in 4-bus example feeder
13
+ - Backward-compatible root module via `three_phase_loadflow.py`
14
+ - Import/export helpers for SQLite, OpenDSS, CIM/CGMES, CYME, and pandapower-oriented conversion utilities
15
+ - Diagnostics utilities for common topology, impedance, grounding, and modeling issues
16
+
17
+ ## Installation
18
+
19
+ Core dependencies are minimal:
20
+
21
+ ```bash
22
+ pip install -e .
23
+ ```
24
+
25
+ Optional extras defined in `pyproject.toml`:
26
+
27
+ ```bash
28
+ pip install -e .[dev]
29
+ pip install -e .[notebooks]
30
+ ```
31
+
32
+ Some tool modules rely on additional packages that are not currently declared as extras:
33
+
34
+ - `pandas` for `load_flow_engine.tools.output`
35
+ - `pandas` and `pandapower` for `load_flow_engine.tools.multiconductor_adapter`
36
+ - `cympy` for `load_flow_engine.tools.cyme_adapter` and a local CYME installation
37
+
38
+ ## Quick Start
39
+
40
+ ```python
41
+ from load_flow_engine import build_example_network, ThreePhaseLoadFlowSolver
42
+
43
+ net = build_example_network()
44
+ solver = ThreePhaseLoadFlowSolver(net, method="nr")
45
+
46
+ converged = solver.solve()
47
+ print("Converged:", converged)
48
+
49
+ solver.print_bus_results()
50
+ branch_results = solver.compute_branch_results()
51
+ solver.print_branch_results(branch_results)
52
+ ```
53
+
54
+ You can also run the bundled example directly:
55
+
56
+ ```bash
57
+ lfe-example
58
+ python -c "from load_flow_engine import run_example; run_example()"
59
+ python three_phase_loadflow.py
60
+ ```
61
+
62
+ ## Basic Workflow
63
+
64
+ The typical workflow is:
65
+
66
+ 1. Create a `StudyCase`
67
+ 2. Build a `Network`
68
+ 3. Add buses, branches, transformers, and loads/generators
69
+ 4. Call `solve()`
70
+ 5. Read solved voltages from each `Bus` or call `compute_branch_results()`
71
+
72
+ Example skeleton:
73
+
74
+ ```python
75
+ import numpy as np
76
+
77
+ from load_flow_engine import (
78
+ Branch,
79
+ Bus,
80
+ BusType,
81
+ Load,
82
+ Network,
83
+ PhaseType,
84
+ StudyCase,
85
+ ThreePhaseLoadFlowSolver,
86
+ )
87
+
88
+ sc = StudyCase(max_iterations=50, solution_precision=1e-4, base_mva=10.0)
89
+ net = Network(sc)
90
+
91
+ net.add_bus(Bus("SOURCE", BusType.SLACK, PhaseType.ABC, base_kv=12.47))
92
+ net.add_bus(Bus("LOADBUS", BusType.PQ, PhaseType.ABC, base_kv=12.47))
93
+
94
+ net.add_branch(Branch(
95
+ id="LINE1",
96
+ from_bus="SOURCE",
97
+ to_bus="LOADBUS",
98
+ phase_type=PhaseType.ABC,
99
+ r1=0.01,
100
+ x1=0.03,
101
+ r0=0.03,
102
+ x0=0.09,
103
+ ))
104
+
105
+ net.add_load(Load(
106
+ id="LD1",
107
+ bus_id="LOADBUS",
108
+ phase_type=PhaseType.ABC,
109
+ mw=np.array([0.2, 0.2, 0.2]),
110
+ mvar=np.array([0.08, 0.08, 0.08]),
111
+ ))
112
+
113
+ solver = ThreePhaseLoadFlowSolver(net)
114
+ solver.solve()
115
+ ```
116
+
117
+ ## Public API
118
+
119
+ The top-level package re-exports the main classes and helpers:
120
+
121
+ - `StudyCase`
122
+ - `Bus`, `Branch`, `Transformer`, `Load`, `Generator`, `Shunt`
123
+ - `Network`
124
+ - `ThreePhaseLoadFlowSolver`
125
+ - `PhaseType` and `BusType`
126
+ - `build_example_network()` and `run_example()`
127
+
128
+ `three_phase_loadflow.py` remains available as a compatibility shim that re-exports the same public symbols.
129
+
130
+ ## Tools And Adapters
131
+
132
+ - `load_flow_engine.tools.sqlite_adapter`: persist a network to SQLite with `export_network()` and load it back with `import_network()`
133
+ - `load_flow_engine.tools.opendss_adapter`: export a populated network to an OpenDSS script
134
+ - `load_flow_engine.tools.cim_adapter`: import CIM XML or export CIM/CGMES-style files
135
+ - `load_flow_engine.tools.cyme_adapter`: push a network into a CYME study through `cympy`
136
+ - `load_flow_engine.tools.diagnostics`: run validation checks with `run_diagnostics()`
137
+ - `load_flow_engine.tools.output`: extract bus results into a pandas DataFrame and summarize phase loading
138
+ - `load_flow_engine.tools.load_allocation`: allocate total demand across loads based on connected transformer capacity
139
+ - `load_flow_engine.tools.multiconductor_adapter`: convert from pandapower-style multiconductor models into an LFE `Network`
140
+
141
+ ## Project Layout
142
+
143
+ ```text
144
+ load_flow_engine/
145
+ __init__.py
146
+ models.py
147
+ network.py
148
+ solver.py
149
+ example.py
150
+ tools/
151
+ diagnostics/
152
+ cim_adapter.py
153
+ cyme_adapter.py
154
+ opendss_adapter.py
155
+ sqlite_adapter.py
156
+ three_phase_loadflow.py
157
+ notebooks/
158
+ ```
159
+
160
+ ## Notes
161
+
162
+ - Core runtime dependencies are `numpy` and `scipy`.
163
+ - The repository currently does not include an automated test suite.
164
+ - The example feeder in this repository solves successfully through the legacy script entrypoint.
165
+
166
+ ## License
167
+
168
+ No license file is currently included in this repository.
@@ -0,0 +1,42 @@
1
+ """
2
+ load_flow_engine — Three-phase unbalanced load flow solver package.
3
+
4
+ Re-exports all public symbols for convenience::
5
+
6
+ from load_flow_engine import Network, ThreePhaseLoadFlowSolver, Bus, Branch, ...
7
+ """
8
+
9
+ from .enums import PhaseType, BusType
10
+ from .constants import _a, _A, _Ai
11
+ from .models import (
12
+ StudyCase,
13
+ Bus,
14
+ Branch,
15
+ Transformer,
16
+ Load,
17
+ Generator,
18
+ Shunt,
19
+ BranchResult,
20
+ )
21
+ from .helpers import _active_phases, _seq_to_z_abc, _matrix_invert_3x3
22
+ from .network import Network
23
+ from .solver import ThreePhaseLoadFlowSolver
24
+ from .example import build_example_network, run_example
25
+
26
+ __all__ = [
27
+ # Enums
28
+ "PhaseType", "BusType",
29
+ # Constants
30
+ "_a", "_A", "_Ai",
31
+ # Models
32
+ "StudyCase", "Bus", "Branch", "Transformer", "Load",
33
+ "Generator", "Shunt", "BranchResult",
34
+ # Helpers
35
+ "_active_phases", "_seq_to_z_abc", "_matrix_invert_3x3",
36
+ # Network
37
+ "Network",
38
+ # Solver
39
+ "ThreePhaseLoadFlowSolver",
40
+ # Example
41
+ "build_example_network", "run_example",
42
+ ]
@@ -0,0 +1,12 @@
1
+ """
2
+ Symmetrical-component (Fortescue) transformation matrices.
3
+ Mirrors ETAP's use of A-matrix for zero/pos/neg sequence decomposition.
4
+ """
5
+
6
+ import numpy as np
7
+
8
+ _a = np.exp(1j * 2.0 * np.pi / 3.0) # 1∠120°
9
+ _A = np.array([[1, 1, 1 ], # Fortescue A
10
+ [1, _a**2, _a ],
11
+ [1, _a, _a**2 ]], dtype=complex)
12
+ _Ai = np.linalg.inv(_A) # A⁻¹
@@ -0,0 +1,24 @@
1
+ """Phase and bus type enumerations matching ETAP definitions."""
2
+
3
+ from enum import IntEnum
4
+
5
+
6
+ class PhaseType(IntEnum):
7
+ """
8
+ Matches ETAPPhaseType enum in NetworkReductionEC.h.
9
+ Defines which phases a bus or branch participates in.
10
+ """
11
+ ABC = 0 # three-phase
12
+ A = 5 # single-phase A
13
+ B = 6 # single-phase B
14
+ C = 7 # single-phase C
15
+ AB = 8 # two-phase A-B
16
+ BC = 9 # two-phase B-C
17
+ CA = 10 # two-phase C-A
18
+
19
+
20
+ class BusType(IntEnum):
21
+ """Standard power-flow bus types."""
22
+ SLACK = 0 # swing bus — |V| and ∠V fixed per phase
23
+ PV = 1 # voltage-controlled — P and |V| fixed, Q within limits
24
+ PQ = 2 # load bus — P and Q fixed per phase
@@ -0,0 +1,114 @@
1
+ """
2
+ Four-bus radial distribution feeder example.
3
+
4
+ Topology (12.47 kV primary, 4.16 kV secondary after transformer):
5
+
6
+ BUS1 (slack) ──── XFM1 ──── BUS2 ──── BR1 ──── BUS3 ──── BR2 ──── BUS4
7
+ 12.47 kV 4.16 kV 4.16 kV 4.16 kV
8
+ swing bus 3φ balanced load 3φ+1φ unbalanced load 1φ-A load only
9
+
10
+ BUS2: 3-phase balanced load 150 kW + j50 kVAr on each phase
11
+ BUS3: unbalanced load 300 kW/ph-A, 100 kW/ph-B, 200 kW/ph-C + reactive
12
+ BUS4: single-phase A load 400 kW + j200 kVAr on phase A only
13
+ """
14
+
15
+ import numpy as np
16
+
17
+ from .enums import PhaseType, BusType
18
+ from .constants import _Ai
19
+ from .models import Bus, Branch, Transformer, Load, StudyCase
20
+ from .network import Network
21
+ from .solver import ThreePhaseLoadFlowSolver
22
+
23
+
24
+ def build_example_network() -> Network:
25
+ sc = StudyCase(max_iterations=50, solution_precision=1e-4, base_mva=10.0)
26
+ net = Network(sc)
27
+
28
+ # ---- Buses ----
29
+ net.add_bus(Bus("BUS1", BusType.SLACK, PhaseType.ABC, base_kv=12.47))
30
+ net.add_bus(Bus("BUS2", BusType.PQ, PhaseType.ABC, base_kv=4.16))
31
+ net.add_bus(Bus("BUS3", BusType.PQ, PhaseType.ABC, base_kv=4.16))
32
+ net.add_bus(Bus("BUS4", BusType.PQ, PhaseType.ABC, base_kv=4.16))
33
+
34
+ # ---- Transformer BUS1 → BUS2 ----
35
+ # 3 MVA, 12.47/4.16 kV, wye-grounded / wye-grounded
36
+ # Z1 = 1%, Z0 = 1% on transformer base
37
+ net.add_transformer(Transformer(
38
+ id="XFM1", from_bus="BUS1", to_bus="BUS2",
39
+ r1=0.005, x1=0.06, r0=0.005, x0=0.06,
40
+ mva_rating=3.0,
41
+ conn_primary='wye_grounded', conn_secondary='wye_grounded',
42
+ ))
43
+
44
+ # ---- Branch BUS2 → BUS3 (3-phase overhead line, 1 km) ----
45
+ # Z1 = 0.306 + j0.627 Ω/km, Z0 = 0.745 + j1.2 Ω/km
46
+ # Base Z = (4.16²/10) = 1.731 Ω → divide to get pu
47
+ Zbase = 4.16**2 / 10.0 # 1.731 Ω
48
+ net.add_branch(Branch(
49
+ id="BR1", from_bus="BUS2", to_bus="BUS3",
50
+ phase_type=PhaseType.ABC,
51
+ r1=0.306/Zbase, x1=0.627/Zbase,
52
+ r0=0.745/Zbase, x0=1.200/Zbase,
53
+ b1=0.0,
54
+ ampacity=np.full(3, 300.0), # 300 A rated
55
+ ))
56
+
57
+ # ---- Branch BUS3 → BUS4 (single-phase A, 0.5 km) ----
58
+ net.add_branch(Branch(
59
+ id="BR2", from_bus="BUS3", to_bus="BUS4",
60
+ phase_type=PhaseType.A,
61
+ r1=(0.306*0.5)/Zbase, x1=(0.627*0.5)/Zbase,
62
+ r0=(0.745*0.5)/Zbase, x0=(1.200*0.5)/Zbase,
63
+ ampacity=np.array([200.0, 0.0, 0.0]),
64
+ ))
65
+
66
+ # ---- Loads ----
67
+ # BUS2: balanced 3-phase load 150 kW + j50 kVAr / phase
68
+ net.add_load(Load("LD2", "BUS2", PhaseType.ABC,
69
+ mw = np.array([0.15, 0.15, 0.15]),
70
+ mvar = np.array([0.05, 0.05, 0.05])))
71
+
72
+ # BUS3: unbalanced 3-phase load
73
+ net.add_load(Load("LD3", "BUS3", PhaseType.ABC,
74
+ mw = np.array([0.30, 0.10, 0.20]),
75
+ mvar = np.array([0.15, 0.05, 0.10])))
76
+
77
+ # BUS4: single-phase A load 400 kW + j200 kVAr
78
+ net.add_load(Load("LD4", "BUS4", PhaseType.A,
79
+ mw = np.array([0.40, 0.0, 0.0]),
80
+ mvar = np.array([0.20, 0.0, 0.0])))
81
+
82
+ return net
83
+
84
+
85
+ def run_example() -> None:
86
+ print("\n" + "=" * 72)
87
+ print(" ETAP-Style Three-Phase Unbalanced Load Flow — 4-Bus Feeder")
88
+ print("=" * 72)
89
+
90
+ net = build_example_network()
91
+ solver = ThreePhaseLoadFlowSolver(net)
92
+
93
+ converged = solver.solve()
94
+
95
+ if not converged:
96
+ print("WARNING: solver did not converge!")
97
+
98
+ solver.print_bus_results()
99
+
100
+ br_results = solver.compute_branch_results()
101
+ solver.print_branch_results(br_results)
102
+
103
+ # ---- Voltage unbalance at BUS3 ----
104
+ b3 = net.buses["BUS3"]
105
+ V_abc = np.array([
106
+ b3.v_mag[p] * np.exp(1j * np.deg2rad(b3.v_ang[p]))
107
+ for p in range(3)
108
+ ])
109
+ V_012 = _Ai @ V_abc
110
+ vuf_neg = abs(V_012[2]) / abs(V_012[1]) * 100.0
111
+ vuf_zero = abs(V_012[0]) / abs(V_012[1]) * 100.0
112
+ print(f" BUS3 Voltage Unbalance Factor (NEMA neg-seq) : {vuf_neg:.3f} %")
113
+ print(f" BUS3 Voltage Unbalance Factor (zero-seq) : {vuf_zero:.3f} %")
114
+ print()
@@ -0,0 +1,84 @@
1
+ """Helper functions for impedance transforms and phase mapping."""
2
+
3
+ from typing import List, Optional
4
+
5
+ import numpy as np
6
+
7
+ from .enums import PhaseType
8
+
9
+
10
+ def _active_phases(phase_type: PhaseType) -> List[int]:
11
+ """
12
+ Return the sorted list of active phase indices (0=A, 1=B, 2=C)
13
+ for a given PhaseType, matching ETAPPhaseType handling in
14
+ NetworkReductionEC.cpp.
15
+ """
16
+ mapping = {
17
+ PhaseType.ABC: [0, 1, 2],
18
+ PhaseType.A: [0],
19
+ PhaseType.B: [1],
20
+ PhaseType.C: [2],
21
+ PhaseType.AB: [0, 1],
22
+ PhaseType.BC: [1, 2],
23
+ PhaseType.CA: [0, 2],
24
+ }
25
+ return sorted(mapping.get(phase_type, [0, 1, 2]))
26
+
27
+
28
+ def _seq_to_z_abc(z1: complex, z0: complex) -> np.ndarray:
29
+ """
30
+ Convert positive- and zero-sequence impedances to the 3×3 phase-domain
31
+ impedance matrix for a fully transposed line.
32
+
33
+ Mirrors NetworkReductionEC's use of the symmetrical-component transform:
34
+ Z_abc = A · diag(Z0, Z1, Z1) · A⁻¹
35
+
36
+ For a transposed line this reduces to the Toeplitz form:
37
+ Zs = (Z0 + 2·Z1) / 3 (self impedance)
38
+ Zm = (Z0 − Z1) / 3 (mutual impedance)
39
+
40
+ Z_abc = [[Zs, Zm, Zm],
41
+ [Zm, Zs, Zm],
42
+ [Zm, Zm, Zs]]
43
+ """
44
+ Zs = (z0 + 2.0 * z1) / 3.0
45
+ Zm = (z0 - z1) / 3.0
46
+ return np.array([[Zs, Zm, Zm],
47
+ [Zm, Zs, Zm],
48
+ [Zm, Zm, Zs]], dtype=complex)
49
+
50
+
51
+ def _matrix_invert_3x3(Z: np.ndarray) -> Optional[np.ndarray]:
52
+ """
53
+ Invert a 3×3 complex matrix using Gauss-Jordan elimination.
54
+ Mirrors MatrixInvert(DComplexEC mtxZ[3][3]) in NetworkReductionEC.cpp,
55
+ including the same singular-matrix guard (returns None on failure).
56
+ """
57
+ SMALL = 1e-12
58
+ A = Z.astype(complex).copy()
59
+ I = np.eye(3, dtype=complex)
60
+
61
+ for i in range(3):
62
+ # Partial pivoting (mirrors ETAP's row-sum fallback)
63
+ if abs(A[i, i]) < SMALL:
64
+ swapped = False
65
+ for k in range(i + 1, 3):
66
+ if abs(A[k, i]) >= SMALL:
67
+ A[[i, k]] = A[[k, i]]
68
+ I[[i, k]] = I[[k, i]]
69
+ swapped = True
70
+ break
71
+ if not swapped:
72
+ return None # singular
73
+
74
+ pivot = A[i, i]
75
+ A[i] /= pivot
76
+ I[i] /= pivot
77
+
78
+ for k in range(3):
79
+ if k != i and abs(A[k, i]) >= SMALL:
80
+ factor = A[k, i]
81
+ A[k] -= factor * A[i]
82
+ I[k] -= factor * I[i]
83
+
84
+ return I
@@ -0,0 +1,219 @@
1
+ """Data classes mirroring ETAP DB accessor structures."""
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import List
5
+
6
+ import numpy as np
7
+
8
+ from .enums import PhaseType, BusType
9
+
10
+
11
+ @dataclass
12
+ class StudyCase:
13
+ """
14
+ Mirrors LF3PH_STUDY_CASE_DATA from dataTyp6.h.
15
+ Controls solver parameters.
16
+ """
17
+ max_iterations: int = 100 # maximumIteration
18
+ solution_precision: float = 1e-4 # solutionPrecision (MVA mismatch)
19
+ base_mva: float = 100.0 # system MVA base
20
+ flat_start: bool = True # use 1.0 pu / nominal angles
21
+
22
+
23
+ @dataclass
24
+ class Bus:
25
+ """
26
+ Mirrors IBusLF3PHAccessor from IBusLF3PH.h.
27
+
28
+ All voltage magnitudes are in per-unit; angles in degrees.
29
+ Powers are in MW and MVAr (converted to pu internally during solve).
30
+ Array index 0=A, 1=B, 2=C throughout.
31
+ """
32
+ id: str
33
+ bus_type: BusType = BusType.PQ
34
+ phase_type: PhaseType = PhaseType.ABC
35
+ base_kv: float = 12.47 # m_BasekV
36
+ name: str = '' # MC source name for cross-referencing
37
+
38
+ # Solved voltages — m_VMagA/B/C, m_VAngA/B/C
39
+ v_mag: np.ndarray = field(
40
+ default_factory=lambda: np.ones(3))
41
+ v_ang: np.ndarray = field(
42
+ default_factory=lambda: np.array([0.0, -120.0, 120.0]))
43
+
44
+ # Initial voltage guess — m_IniVMagA/B/C, m_IniAngA/B/C
45
+ ini_v_mag: np.ndarray = field(
46
+ default_factory=lambda: np.ones(3))
47
+ ini_v_ang: np.ndarray = field(
48
+ default_factory=lambda: np.array([0.0, -120.0, 120.0]))
49
+
50
+ # Scheduled generation per phase (MW, MVAr) — m_GenMWA/B/C, m_GenMvarA/B/C
51
+ gen_mw: np.ndarray = field(default_factory=lambda: np.zeros(3))
52
+ gen_mvar: np.ndarray = field(default_factory=lambda: np.zeros(3))
53
+
54
+ # Scheduled load per phase (MW, MVAr) — m_LoadMWA/B/C, m_LoadMvarA/B/C
55
+ load_mw: np.ndarray = field(default_factory=lambda: np.zeros(3))
56
+ load_mvar: np.ndarray = field(default_factory=lambda: np.zeros(3))
57
+
58
+ # Reactive limits (PV buses) — m_MvarMax, m_MvarMin
59
+ mvar_max: float = 9999.0
60
+ mvar_min: float = -9999.0
61
+
62
+
63
+ @dataclass
64
+ class Branch:
65
+ """
66
+ Mirrors IConnectLF3PHAccessor from IConnectLF3PH.h.
67
+
68
+ Stores positive- and zero-sequence impedances in per-unit.
69
+ The 3×3 phase-domain matrix Z_abc is computed at build time via the
70
+ symmetrical-component transform (same as NetworkReductionEC).
71
+ """
72
+ id: str
73
+ from_bus: str
74
+ to_bus: str
75
+ phase_type: PhaseType = PhaseType.ABC
76
+ name: str = ''
77
+
78
+ # Positive-sequence series impedance (pu) — m_R, m_X
79
+ r1: float = 0.0
80
+ x1: float = 0.0
81
+
82
+ # Zero-sequence series impedance (pu) — m_R0, m_X0
83
+ r0: float = 0.0
84
+ x0: float = 0.0
85
+
86
+ # Shunt charging susceptance (pu, positive-sequence total line charging)
87
+ b1: float = 0.0
88
+
89
+ # Ampacity per phase (amps) for loading-% calc — m_AmpacityA/B/C
90
+ ampacity: np.ndarray = field(default_factory=lambda: np.full(3, 9999.0))
91
+
92
+
93
+ @dataclass
94
+ class Transformer:
95
+ """
96
+ Two-winding transformer.
97
+ Mirrors LF3PH_Xfmr2Data from dataTyp6.h.
98
+
99
+ Impedances are in pu on the transformer's own MVA base; they are
100
+ converted to the system base during Network.build().
101
+ """
102
+ id: str
103
+ from_bus: str # primary (HV) bus
104
+ to_bus: str # secondary (LV) bus
105
+ phase_type: PhaseType = PhaseType.ABC
106
+ name: str = ''
107
+
108
+ # Leakage impedance (pu on xfmr base) — positive-sequence
109
+ r1: float = 0.0
110
+ x1: float = 0.01
111
+
112
+ # Zero-sequence leakage impedance
113
+ r0: float = 0.0
114
+ x0: float = 0.01
115
+
116
+ # Transformer MVA rating (used for base conversion)
117
+ mva_rating: float = 1.0
118
+
119
+ # Off-nominal tap (pu of nominal ratio) — m_TapPctPrim / m_TapPctSec
120
+ tap_primary: float = 1.0
121
+ tap_secondary: float = 1.0
122
+
123
+ # Winding connections (affects zero-sequence path)
124
+ # Supported: 'wye_grounded', 'wye', 'delta'
125
+ conn_primary: str = 'wye_grounded'
126
+ conn_secondary: str = 'wye_grounded'
127
+
128
+ # HV phases this transformer is connected to (0=A, 1=B, 2=C)
129
+ hv_phases: List[int] = field(default_factory=lambda: [0, 1, 2])
130
+
131
+
132
+ @dataclass
133
+ class Load:
134
+ """
135
+ Static load element — mirrors LF3PH_StaticLoadData from dataTyp6.h.
136
+ Per-phase MW and MVAr. add_load() aggregates these onto the bus.
137
+ """
138
+ id: str
139
+ bus_id: str
140
+ phase_type: PhaseType = PhaseType.ABC
141
+ name: str = ''
142
+ mw: np.ndarray = field(default_factory=lambda: np.zeros(3))
143
+ mvar: np.ndarray = field(default_factory=lambda: np.zeros(3))
144
+
145
+
146
+ @dataclass
147
+ class Generator:
148
+ """
149
+ Synchronous generator — mirrors LF3PH_SynGenData from dataTyp6.h.
150
+ """
151
+ id: str
152
+ bus_id: str
153
+ bus_type: BusType = BusType.PV
154
+ name: str = ''
155
+
156
+ mw: np.ndarray = field(default_factory=lambda: np.zeros(3))
157
+ v_set_pu: float = 1.0
158
+ mvar_max: float = 9999.0
159
+ mvar_min: float = -9999.0
160
+
161
+
162
+ @dataclass
163
+ class Shunt:
164
+ """
165
+ Shunt element — per-phase MW and MVAr (capacitor/reactor banks).
166
+ """
167
+ id: str
168
+ bus_id: str
169
+ phase_type: PhaseType = PhaseType.ABC
170
+ name: str = ''
171
+ p_mw: np.ndarray = field(default_factory=lambda: np.zeros(3))
172
+ q_mvar: np.ndarray = field(default_factory=lambda: np.zeros(3))
173
+ vn_kv: float = 12.47
174
+ closed: bool = True
175
+
176
+
177
+ @dataclass
178
+ class Switch:
179
+ """
180
+ Switch element — connects two buses with optional resistance.
181
+ """
182
+ id: str
183
+ bus: int = 0
184
+ element: int = 0
185
+ et: str = 'b' # 'b' = bus-bus, 'l' = line
186
+ sw_type: str = 'LBS'
187
+ closed: bool = True
188
+ phase: int = 0
189
+ r_ohm: float = 0.0
190
+
191
+
192
+ @dataclass
193
+ class BranchResult:
194
+ """
195
+ Post-solve per-branch results.
196
+ Field names mirror LFSumBranchLF3PHAccessor from LFSumBranchLF3PH.h.
197
+ """
198
+ id: str
199
+ from_bus: str
200
+ to_bus: str
201
+
202
+ # Per-phase current — m_LoadingAmpMagA/B/C, m_LoadingAmpAngA/B/C
203
+ i_mag_abc: np.ndarray = field(default_factory=lambda: np.zeros(3))
204
+ i_ang_abc: np.ndarray = field(default_factory=lambda: np.zeros(3))
205
+
206
+ # Per-phase apparent power (MVA) from/to — m_LoadingInMVAA/B/C, m_LoadingOutMVAA/B/C
207
+ mva_from: np.ndarray = field(default_factory=lambda: np.zeros(3))
208
+ mva_to: np.ndarray = field(default_factory=lambda: np.zeros(3))
209
+
210
+ # Per-phase loading percent — m_Loading_A/B/C
211
+ loading_pct: np.ndarray = field(default_factory=lambda: np.zeros(3))
212
+
213
+ # Sequence currents — m_LoadingAmpMag0/1/2, m_LoadingAmpAng0/1/2
214
+ i_mag_012: np.ndarray = field(default_factory=lambda: np.zeros(3))
215
+ i_ang_012: np.ndarray = field(default_factory=lambda: np.zeros(3))
216
+
217
+ # Unbalance factors — m_CUF2 (negative-seq), m_CUF0 (zero-seq)
218
+ cuf2: float = 0.0
219
+ cuf0: float = 0.0