home / skills / benchflow-ai / skillsbench / ac-branch-pi-model
/tasks/energy-ac-optimal-power-flow/environment/skills/ac-branch-pi-model
This skill computes AC branch pi-model power flows with transformer handling to expose P/Q and S in both directions for debugging and limits.
npx playbooks add skill benchflow-ai/skillsbench --skill ac-branch-pi-modelReview the files below or copy the command above to add this skill to your agents.
---
name: ac-branch-pi-model
description: "AC branch pi-model power flow equations (P/Q and |S|) with transformer tap ratio and phase shift, matching `acopf-math-model.md` and MATPOWER branch fields. Use when computing branch flows in either direction, aggregating bus injections for nodal balance, checking MVA (rateA) limits, computing branch loading %, or debugging sign/units issues in AC power flow."
---
# AC Branch Pi-Model + Transformer Handling
Implement the exact branch power flow equations in `acopf-math-model.md` using MATPOWER branch data:
```
[F_BUS, T_BUS, BR_R, BR_X, BR_B, RATE_A, RATE_B, RATE_C, TAP, SHIFT, BR_STATUS, ANGMIN, ANGMAX]
```
## Quick start
- Use `scripts/branch_flows.py` to compute per-unit branch flows.
- Treat the results as **power leaving** the “from” bus and **power leaving** the “to” bus (i.e., compute both directions explicitly).
Example:
```python
import json
import numpy as np
from scripts.branch_flows import compute_branch_flows_pu, build_bus_id_to_idx
data = json.load(open("/root/network.json"))
baseMVA = float(data["baseMVA"])
buses = np.array(data["bus"], dtype=float)
branches = np.array(data["branch"], dtype=float)
bus_id_to_idx = build_bus_id_to_idx(buses)
Vm = buses[:, 7] # initial guess VM
Va = np.deg2rad(buses[:, 8]) # initial guess VA
br = branches[0]
P_ij, Q_ij, P_ji, Q_ji = compute_branch_flows_pu(Vm, Va, br, bus_id_to_idx)
S_ij_MVA = (P_ij**2 + Q_ij**2) ** 0.5 * baseMVA
S_ji_MVA = (P_ji**2 + Q_ji**2) ** 0.5 * baseMVA
print(S_ij_MVA, S_ji_MVA)
```
## Model details (match the task formulation)
### Per-unit conventions
- Work in per-unit internally.
- Convert with `baseMVA`:
- \(P_{pu} = P_{MW} / baseMVA\)
- \(Q_{pu} = Q_{MVAr} / baseMVA\)
- \(|S|_{MVA} = |S|_{pu} \cdot baseMVA\)
### Transformer handling (MATPOWER TAP + SHIFT)
- Use \(T_{ij} = tap \cdot e^{j \cdot shift}\).
- **Implementation shortcut (real tap + phase shift):**
- If `abs(TAP) < 1e-12`, treat `tap = 1.0` (no transformer).
- Convert `SHIFT` from degrees to radians.
- Use the angle shift by modifying the angle difference:
- \(\delta_{ij} = \theta_i - \theta_j - shift\)
- \(\delta_{ji} = \theta_j - \theta_i + shift\)
### Series admittance
Given `BR_R = r`, `BR_X = x`:
- If `r == 0 and x == 0`, set `g = 0`, `b = 0` (avoid divide-by-zero).
- Else:
- \(y = 1/(r + jx) = g + jb\)
- \(g = r/(r^2 + x^2)\)
- \(b = -x/(r^2 + x^2)\)
### Line charging susceptance
- `BR_B` is the **total** line charging susceptance \(b_c\) (per unit).
- Each end gets \(b_c/2\) in the standard pi model.
## Power flow equations (use these exactly)
Let:
- \(V_i = |V_i| e^{j\theta_i}\), \(V_j = |V_j| e^{j\theta_j}\)
- `tap` is real, `shift` is radians
- `inv_t = 1/tap`, `inv_t2 = inv_t^2`
Then the real/reactive power flow from i→j is:
- \(P_{ij} = g |V_i|^2 inv\_t2 - |V_i||V_j| inv\_t (g\cos\delta_{ij} + b\sin\delta_{ij})\)
- \(Q_{ij} = -(b + b_c/2)|V_i|^2 inv\_t2 - |V_i||V_j| inv\_t (g\sin\delta_{ij} - b\cos\delta_{ij})\)
And from j→i is:
- \(P_{ji} = g |V_j|^2 - |V_i||V_j| inv\_t (g\cos\delta_{ji} + b\sin\delta_{ji})\)
- \(Q_{ji} = -(b + b_c/2)|V_j|^2 - |V_i||V_j| inv\_t (g\sin\delta_{ji} - b\cos\delta_{ji})\)
Compute apparent power:
- \(|S_{ij}| = \sqrt{P_{ij}^2 + Q_{ij}^2}\)
- \(|S_{ji}| = \sqrt{P_{ji}^2 + Q_{ji}^2}\)
## Common uses
### Enforce MVA limits (rateA)
- `RATE_A` is an MVA limit (may be 0 meaning “no limit”).
- Enforce in both directions:
- \(|S_{ij}| \le RATE_A\)
- \(|S_{ji}| \le RATE_A\)
### Compute branch loading %
For reporting “most loaded branches”:
- `loading_pct = 100 * max(|S_ij|, |S_ji|) / RATE_A` if `RATE_A > 0`, else 0.
### Aggregate bus injections for nodal balance
To build the branch flow sum for each bus \(i\):
- Add \(P_{ij}, Q_{ij}\) to bus i
- Add \(P_{ji}, Q_{ji}\) to bus j
This yields arrays `P_out[i]`, `Q_out[i]` such that the nodal balance can be written as:
- \(P^g - P^d - G^s|V|^2 = P_{out}\)
- \(Q^g - Q^d + B^s|V|^2 = Q_{out}\)
## Sanity checks (fast debug)
- With `SHIFT=0` and `TAP=1`, if \(V_i = V_j\) and \(\theta_i=\theta_j\), then \(P_{ij}\approx 0\) and \(P_{ji}\approx 0\) (lossless only if r=0).
- For a pure transformer (`r=x=0`) you should not get meaningful flows; treat as `g=b=0` (no series element).
This skill implements the AC branch pi-model power flow equations with transformer tap ratio and phase shift handling, matching MATPOWER branch field conventions. It computes real/reactive branch flows in both directions, apparent power, MVA limit checks, and aggregates branch contributions for nodal balance. Use it to validate branch-level results, loading percentages, and sign/units behavior in AC power flow calculations.
The skill works in per-unit and follows the exact equations for series admittance, line charging, and transformer effects (real tap and phase shift). It computes P_ij, Q_ij and P_ji, Q_ji using the given tap and shift, derives |S| in MVA with baseMVA, enforces RATE_A limits, and produces per-bus aggregated outflows for nodal balance. It handles edge cases such as zero series impedance or zero tap by using safe defaults (g=b=0, tap=1).
How is the transformer phase shift applied?
Convert SHIFT from degrees to radians and subtract it from the angle difference for i→j (delta_ij = theta_i - theta_j - shift) and add for j→i.
What do I do when RATE_A is zero?
Treat RATE_A==0 as no limit; set loading percentage to 0 for reporting or skip limit enforcement.
How are zero impedance lines handled?
If both r and x are zero, set series conductance g and susceptance b to 0 to avoid divide-by-zero; such lines have no meaningful series flow.