Transfer capability#
Available transfer capability (ATC) is the additional power that can move from
a source to a sink before a network limit is reached. solve_atc calculates the
base-case capability and can include single-branch outages. It reports the
binding limit and ranks the next most restrictive limits.
solve_poi_headroom calculates how much power a proposed plant can inject at a
candidate point of interconnection.
This example creates a case in Python, but you can also load a RAW, RAWX, or MATPOWER case. See Sensitivities for PTDF and OTDF definitions.
Building the example#
The three-bus network is reused, with the load reduced to 120 MW so that the network starts with headroom to sell. The two parallel 1-3 circuits are rated 60 MW normal and 90 MW emergency, and they carry 40 MW each in the base case.
from gpf import (Bus, Branch, Generator, GenCost, Load, GpfCase)
def build_case(pl=180.0, pg1=120.0, pg2=60.0):
"""A three-bus test system with two parallel 1-3 circuits (60 MW normal,
90 MW emergency). Bus 1 is the reference and carries the cheap generator,
bus 2 the expensive one, bus 3 the load."""
buses = [Bus(ibus=1, ide=3, baskv=138.0),
Bus(ibus=2, ide=2, baskv=138.0),
Bus(ibus=3, ide=1, baskv=138.0)]
gens = [Generator(ibus=1, machid="1", pg=pg1, pt=250.0, pb=0.0,
qt=200, qb=-200, vs=1.0, stat=1),
Generator(ibus=2, machid="1", pg=pg2, pt=150.0, pb=0.0,
qt=200, qb=-200, vs=1.0, stat=1)]
branches = [
Branch(ibus=1, jbus=2, ckt="1", r=0.005, x=0.1, rate1=200, rate3=200, stat=1),
Branch(ibus=1, jbus=3, ckt="1", r=0.005, x=0.1, rate1=60, rate3=90, stat=1),
Branch(ibus=1, jbus=3, ckt="2", r=0.005, x=0.1, rate1=60, rate3=90, stat=1),
Branch(ibus=2, jbus=3, ckt="1", r=0.005, x=0.1, rate1=200, rate3=200, stat=1),
]
loads = [Load(ibus=3, loadid="1", pl=pl, ql=0.0, stat=1)]
costs = [GenCost(ibus=1, machid="1", model=2, cost=[10.0, 0.0]),
GenCost(ibus=2, machid="1", model=2, cost=[40.0, 0.0])]
return GpfCase(sbase=100.0, rev=33, basfrq=60.0, buses=buses,
generators=gens, branches=branches, loads=loads,
fixed_shunts=[], transformers=[], gen_costs=costs)
case = build_case(pl=120.0, pg1=80.0, pg2=40.0)
len(case.buses), len(case.branches)
(3, 4)
Base-case transfer capability#
The calculation injects power at bus 2 and withdraws the same amount at bus 3. Each transferred megawatt adds 0.2 MW to a 1-3 circuit. The circuit starts at 40 MW and has a 60 MW normal rating. The base-case capability is therefore (60 - 40) / 0.2 = 100 MW. Without reliability or benefit margins, ATC equals total transfer capability (TTC).
By default the linear model starts from the AC base-case flows, so a solved
case's losses do not distort the answer. This tutorial passes
base_flows="dc" to keep the lossless arithmetic shown in the text.
from gpf import BASE_ONLY
from gpf import solve_atc, solve_atc_ac, solve_atc_matrix, solve_poi_headroom
base = solve_atc(case, 2, 3, contingencies=BASE_ONLY, base_flows="dc")
assert abs(base.atc_mw - 100.0) < 1e-4
lim = base.limiter
{"atc_mw": round(base.atc_mw, 1),
"monitored": lim.monitored, "outage": lim.contingency,
"flow0": round(lim.flow0, 1), "rate": lim.rate_mw, "tdf": round(lim.tdf, 3)}
{'atc_mw': 100.0,
'monitored': (1, 3, '1'),
'outage': 'base',
'flow0': 40.0,
'rate': 60.0,
'tdf': 0.2}
The N-1 screen#
The screen sweeps every single-branch outage by default. Select the emergency rating to screen each outage against emergency ratings; the default selects the normal rating. The loss of the 2-3 circuit becomes the binding outage. It increases the transfer factor on a 1-3 circuit to 0.5 and increases its initial flow to 60 MW. With a 90 MW emergency rating, the capability is (90 - 60) / 0.5 = 60 MW.
n1 = solve_atc(case, 2, 3, contingency_rating="emergency",
base_flows="dc")
assert abs(n1.atc_mw - 60.0) < 1e-4
lim = n1.limiter
{"atc_mw": round(n1.atc_mw, 1),
"monitored": lim.monitored, "outage": lim.contingency,
"flow0": round(lim.flow0, 1), "rate": lim.rate_mw, "tdf": round(lim.tdf, 3)}
{'atc_mw': 60.0,
'monitored': (1, 3, '1'),
'outage': '2-3-1',
'flow0': 60.0,
'rate': 90.0,
'tdf': 0.5}
The ranked result also shows the second and third most restrictive limits.
[(l.monitored, l.contingency, round(l.limit_mw, 1)) for l in n1.limiters]
[((1, 3, '1'), '2-3-1', 60.0),
((1, 3, '1'), '1-3-2', 70.0),
((1, 3, '2'), '1-3-1', 70.0),
((1, 3, '1'), 'base', 100.0),
((2, 3, '1'), '1-2-1', 160.0)]
Margins#
The calculation subtracts the transmission reliability margin (TRM) and capacity benefit margin (CBM) from TTC: ATC = TTC - TRM - CBM. A 10 MW TRM and a 5 MW CBM reduce the 60 MW TTC to 45 MW ATC.
margined = solve_atc(
case, 2, 3, contingency_rating="emergency",
trm=10.0, cbm=5.0, base_flows="dc")
assert abs(margined.atc_mw - 45.0) < 1e-4
assert abs(margined.ttc_mw - n1.ttc_mw) < 1e-4
{"ttc_mw": round(margined.ttc_mw, 1), "atc_mw": round(margined.atc_mw, 1)}
{'ttc_mw': 60.0, 'atc_mw': 45.0}
Many directions at once#
solve_atc_matrix evaluates several transfer directions in one call. It returns
one result for each named direction. The 1-to-3 direction is most restrictive
because it increases flow on the loaded parallel circuits.
matrix = solve_atc_matrix(
case, {"2->3": (2, 3), "1->3": (1, 3), "1->2": (1, 2)},
contingency_rating="emergency", base_flows="dc")
{name: round(res.atc_mw, 1) for name, res in matrix.items()}
{'2->3': 60.0, '1->3': 35.0, '1->2': 70.0}
AC certification#
solve_atc_ac verifies the transfer with an AC power flow. This check includes
losses and voltage conditions. The AC capability is slightly lower than the
linear result, and the same outage remains binding.
ac = solve_atc_ac(
case, 2, 3, contingency_rating="emergency")
assert ac.stats["ac"]["converged"]
assert 0.0 < ac.atc_mw < n1.atc_mw
assert ac.limiter.contingency == n1.limiter.contingency
{"dc_atc_mw": round(n1.atc_mw, 1), "ac_atc_mw": round(ac.atc_mw, 1),
"outage": ac.limiter.contingency,
"converged": ac.stats["ac"]["converged"]}
{'dc_atc_mw': 60.0, 'ac_atc_mw': 59.2, 'outage': '2-3-1', 'converged': True}
Interconnection headroom#
solve_poi_headroom screens candidate buses for a new plant. The new injection
reduces existing generation in proportion to the selected system participation
factors.
Bus 3 has 282 MW of headroom because an injection there reduces imports on the constrained 1-3 circuits. Bus 2 has 160 MW of headroom. The 1-2 tie limits this injection after the 2-3 circuit opens. The equivalent opposite pairing also limits the injection at 160 MW and ranks second.
poi = solve_poi_headroom(
case, pois=[2, 3], contingency_rating="emergency")
[(row.bus, round(row.dc.atc_mw, 1), row.dc.limiter.monitored,
row.dc.limiter.contingency) for row in poi.binding]
[(3, 282.3, (1, 3, '1'), '1-3-2'), (2, 160.0, (1, 2, '1'), '2-3-1')]
Next steps#
The API reference describes solve_atc, solve_atc_ac, solve_atc_matrix,
and solve_poi_headroom. It also describes monitored interfaces, generator
outages, and the gpf study screen and gpf study headroom commands.
Transmission rights explains congestion hedges. Optimal power flow explains how dispatch uses network limits.
Interconnection studies shows how to screen a project queue across seasonal cases.