# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
# SPDX-License-Identifier: MPL-2.0
import numpy as np
import bisect
[docs]
def vdl(x, points):
"""
Linear approximation with clamping.
points: list of (x_i, y_i), sorted by x_i
"""
xs = [p[0] for p in points]
ys = [p[1] for p in points]
# Clamp
if x <= xs[0]:
return ys[0]
if x >= xs[-1]:
return ys[-1]
# Find segment
i = bisect.bisect_right(xs, x) - 1
x0, y0 = xs[i], ys[i]
x1, y1 = xs[i + 1], ys[i + 1]
# Linear interpolation
return y0 + (y1 - y0) * (x - x0) / (x1 - x0)
[docs]
def current_limit_logic(Iqcmd, vdlq, vdlp, is_blocked):
Imax = 1.3 # Maximum converter current limit in [pu]
ipmin = 0.0
if is_blocked > 0.0:
blocking_factor = 0.0
else:
blocking_factor = 1.0
# Reactive limits
iqmax = min(vdlq, Imax) * blocking_factor
if iqmax < 0.0:
iqmin = iqmax * blocking_factor
else:
iqmin = -iqmax * blocking_factor
# Reactive limits
iq_used = min(vdlq, Imax, Iqcmd)
ipmax = min(vdlp, np.sqrt(max(Imax ** 2 - iq_used ** 2, 0.0)))
return iqmin, iqmax, ipmin, ipmax
[docs]
def reec_d(u,
Vpf,
Qpf,
Ppf,
):
Vt = u # Input 0
Qext = Qpf # Input 8
Paux = 0.0 # Input 11
Speed = 1.0 # Input 12
Pref_WGO = Ppf # Input 14
# Current Blocking
vblkl = 0.65 # Voltage below which the converter is blocked in [pu]
vblkh = 1.2 # Voltage above which the converter is blocked in [pu]
is_blocked = float((Vt >= vblkh) or (Vt <= vblkl))
# Reactive Current Injection
dbd1 = -0.05
dbd2 = 0.05
Kqv = 2.0
Iqh1 = 1.1 # Maximum limit of reactive current injection in [pu]
Iql1 = -1.1 # Minimum limit of reactive current injection in [pu]
Vdif = Vpf - Vt
if Vdif > abs(dbd2):
Verr = Vdif - abs(dbd2)
elif Vdif < -abs(dbd1):
Verr = Vdif + abs(dbd1)
else:
Verr = 0.0
Iqv = Verr * Kqv
Iqinj = min(max(Iqv, Iql1), Iqh1)
# Q-V Limitation
yi = Qext
qvmin = -1.0 # Minimum reactive power limit in [pu]
qvmax = 1.0 # Maximum reactive power limit in [pu]
o3 = min(max(yi, qvmin), qvmax)
o5 = o3 / max(0.01, Vt)
Iq_unlim = Iqinj + o5
o6 = Pref_WGO
yi10 = Speed * o6
Pmin, Pmax = 0, 1 # Minimum and Maximum power reference in [pu]
Pord = max(min(yi10, Pmax), Pmin)
o12 = Paux + Pord
o7 = max(Vt, 0.01)
Ip_unlim = o12 / o7
oarray_vdlq = [(1.10, 1.10), (1.15, 1.00), (1.30, 0.00)]
vdlq = vdl(Vt, oarray_vdlq)
oarray_vdlp = [(1.10, 1.10), (1.15, 1.00), (1.30, 0.00)]
vdlp = vdl(Vt, oarray_vdlp)
iqmin, iqmax, i_in1, i_in = current_limit_logic(Iq_unlim, vdlq, vdlp, is_blocked)
Iqcmd = max(min(Iq_unlim, iqmax), iqmin)
Ipcmd = max(min(Ip_unlim, i_in), i_in1)
return Iqcmd, Ipcmd, is_blocked
[docs]
def regc_c(Iqcmd, Ipcmd):
iq_ref = - Iqcmd
id_ref = Ipcmd
return iq_ref, id_ref
[docs]
def genstat(ur, ui, iq_ref, id_ref, Sn, is_blocked):
if is_blocked == 1.0:
return 0.0, 0.0
else:
u1 = ur + 1j * ui
cos_u1 = np.real(u1) / abs(u1)
sin_u1 = np.imag(u1) / abs(u1)
i1 = (id_ref * cos_u1 - iq_ref * sin_u1) + 1j * (id_ref * sin_u1 + iq_ref * cos_u1)
s1 = u1 * np.conj(i1)
Pref = np.real(s1) * Sn # MW
Qref = np.imag(s1) * Sn # MVAr
return Pref, Qref
[docs]
def wecc_wt_type_4b(V_measured, Vpf, St_vsc_pf, S_base_vg, S_rated_vsc):
Vt = abs(V_measured) # Absolute value of the converter's voltage [pu]
Vt_r = np.real(V_measured) # Real component of the converter's voltage [pu]
Vt_i = np.imag(V_measured) # Imaginary component of the converter's voltage [pu]
Ppf = -1 * np.real(St_vsc_pf) / S_rated_vsc
Qpf = -1 * np.imag(St_vsc_pf) / S_rated_vsc
Iqcmd, Ipcmd, is_blocked = reec_d(u=Vt,
Vpf=Vpf,
Qpf=Qpf,
Ppf=Ppf)
iq_ref, id_ref = regc_c(Iqcmd=Iqcmd, # Current reference from REEC_D [pu]
Ipcmd=Ipcmd, # Current reference from REEC_D [pu]
)
Pref, Qref = genstat(ur=Vt_r, # Real component of the converter's voltage [pu]
ui=Vt_i, # Imaginary component of the converter's voltage [pu]
iq_ref=iq_ref, # Current reference from REGC_C [pu]
id_ref=id_ref, # Current reference from REGC_C [pu]
Sn=S_rated_vsc, # Converter's nominal power [MVA]
is_blocked=is_blocked # Converter's disconnection = 1
)
return -Pref/S_base_vg, -Qref/S_base_vg