import math
[docs]
def power_balance_equation(signed_flows):
return sum(signed_flows) == 0
[docs]
def calc_branch_t(tap, shift):
return (tap * math.cos(shift), tap * math.sin(shift))
[docs]
def int_flow_from_p(
p_from_var,
vm_from_pu,
vm_to_pu,
va_from_rad,
va_to_rad,
g_branch,
b_branch,
tap,
shift,
cos_impl=math.cos,
sin_impl=math.sin,
g_from=0,
on_off=1,
):
tr, ti = calc_branch_t(tap, shift)
return p_from_var == on_off * (
(g_branch + g_from) / tap**2 * vm_from_pu**2
+ (-g_branch * tr + b_branch * ti)
/ tap**2
* (vm_from_pu * vm_to_pu * cos_impl(va_from_rad - va_to_rad))
+ (-b_branch * tr - g_branch * ti)
/ tap**2
* (vm_from_pu * vm_to_pu * sin_impl(va_from_rad - va_to_rad))
)
[docs]
def int_flow_from_q(
q_from_var,
vm_from_pu,
vm_to_pu,
va_from_rad,
va_to_rad,
g_branch,
b_branch,
tap,
shift,
cos_impl=math.cos,
sin_impl=math.sin,
b_from=0,
on_off=1,
):
tr, ti = calc_branch_t(tap, shift)
return q_from_var == on_off * (
-(b_branch + b_from) / tap**2 * vm_from_pu**2
- (-b_branch * tr - g_branch * ti)
/ tap**2
* (vm_from_pu * vm_to_pu * cos_impl(va_from_rad - va_to_rad))
+ (-g_branch * tr + b_branch * ti)
/ tap**2
* (vm_from_pu * vm_to_pu * sin_impl(va_from_rad - va_to_rad))
)
[docs]
def int_flow_to_p(
p_to_var,
vm_from_pu,
vm_to_pu,
va_from_rad,
va_to_rad,
g_branch,
b_branch,
tap,
shift,
cos_impl=math.cos,
sin_impl=math.sin,
g_to_pu=0,
on_off=1,
):
tr, ti = calc_branch_t(tap, shift)
return p_to_var == on_off * (
(g_branch + g_to_pu) * vm_to_pu**2
+ (-g_branch * tr - b_branch * ti)
/ tap**2
* (vm_to_pu * vm_from_pu * cos_impl(va_to_rad - va_from_rad))
+ (-b_branch * tr + g_branch * ti)
/ tap**2
* (vm_to_pu * vm_from_pu * sin_impl(va_to_rad - va_from_rad))
)
[docs]
def int_flow_to_q(
q_to_var,
vm_from_pu,
vm_to_pu,
va_from_rad,
va_to_rad,
g_branch,
b_branch,
tap,
shift,
cos_impl=math.cos,
sin_impl=math.sin,
b_to_pu=0,
on_off=1,
):
tr, ti = calc_branch_t(tap, shift)
return q_to_var == on_off * (
-(b_branch + b_to_pu) * vm_to_pu**2
- (-b_branch * tr + g_branch * ti)
/ tap**2
* (vm_to_pu * vm_from_pu * cos_impl(va_to_rad - va_from_rad))
+ (-g_branch * tr - b_branch * ti)
/ tap**2
* (vm_to_pu * vm_from_pu * sin_impl(va_to_rad - va_from_rad))
)
[docs]
def int_flows( # NOSONAR
p_from_var,
q_from_var,
p_to_var,
q_to_var,
vm_from_pu,
vm_to_pu,
va_from_rad,
va_to_rad,
g_branch,
b_branch,
tap,
shift,
cos_impl=math.cos,
sin_impl=math.sin,
g_from=0,
b_from=0,
g_to_pu=0,
b_to_pu=0,
on_off=1,
):
"""All four AC branch flow equations (P/Q at both ends) in one pass.
Mathematically identical to calling :func:`int_flow_from_p`,
:func:`int_flow_from_q`, :func:`int_flow_to_p` and :func:`int_flow_to_q`
separately, but the sub-terms shared across the four (``vm_from*vm_to``, the
sine/cosine of the bus-angle difference, ``vm_from**2``, ``vm_to**2``) are
built **once** and reused. The to-direction reuses the from-direction's
``cos``/``sin`` via the even/odd identities ``cos(-x) = cos(x)`` and
``sin(-x) = -sin(x)``. The numeric float coefficients are formed exactly as
in the per-equation functions, so the only change is node sharing - the
symbolic graph these equations contribute is ~halved, which speeds model
assembly on every backend (the equation bodies are backend-agnostic).
Returns ``[p_from_eq, q_from_eq, p_to_eq, q_to_eq]``.
"""
tr, ti = calc_branch_t(tap, shift)
vm_from_sq = vm_from_pu**2
vm_to_sq = vm_to_pu**2
vv = vm_from_pu * vm_to_pu
# cos is even and sin is odd, so the to-direction (angle va_to - va_from)
# reuses these with cos unchanged and sin negated (see the ``-`` signs on the
# vvs terms in p_to / q_to below).
vvc = vv * cos_impl(va_from_rad - va_to_rad)
vvs = vv * sin_impl(va_from_rad - va_to_rad)
p_from = (
(g_branch + g_from) / tap**2 * vm_from_sq
+ (-g_branch * tr + b_branch * ti) / tap**2 * vvc
+ (-b_branch * tr - g_branch * ti) / tap**2 * vvs
)
q_from = (
-(b_branch + b_from) / tap**2 * vm_from_sq
- (-b_branch * tr - g_branch * ti) / tap**2 * vvc
+ (-g_branch * tr + b_branch * ti) / tap**2 * vvs
)
p_to = (
(g_branch + g_to_pu) * vm_to_sq
+ (-g_branch * tr - b_branch * ti) / tap**2 * vvc
- (-b_branch * tr + g_branch * ti) / tap**2 * vvs
)
q_to = (
-(b_branch + b_to_pu) * vm_to_sq
- (-b_branch * tr + g_branch * ti) / tap**2 * vvc
- (-g_branch * tr - b_branch * ti) / tap**2 * vvs
)
# Skip the ``on_off *`` factor only when it is the plain scalar 1 (the common
# always-on case). A switchable line passes ``on_off`` as a decision variable
# (e.g. ``Var(1, integer=True)``); comparing that to 1 would evaluate a
# backend expression's truthiness, so fall through to the multiply for any
# non-scalar (and for a scalar != 1, e.g. a deactivated 0).
if isinstance(on_off, (int, float)) and on_off == 1:
return [
p_from_var == p_from,
q_from_var == q_from,
p_to_var == p_to,
q_to_var == q_to,
]
return [
p_from_var == on_off * p_from,
q_from_var == on_off * q_from,
p_to_var == on_off * p_to,
q_to_var == on_off * q_to,
]