def __init__(self, system, config): super(IEEESTModel, self).__init__(system, config) self.KST5 = ConstService(v_str='KS * T5', tex_name='KS*T5') self.SW = Switcher( u=self.MODE, options=[1, 2, 3, 4, 5, 6], ) self.signal = Algeb( tex_name='S_{in}', info='Input signal', ) # input signals: # 1 (s0) - Rotor speed deviation (p.u.) # 2 (s1) - Bus frequency deviation (p.u.) # TODO: calculate freq without reimpl. # 3 (s2) - Generator electrical power in Gen MVABase (p.u.) # TODO: allow using system.config.mva # 4 (s3) - Generator accelerating power (p.u.) # 5 (s4) - Bus voltage (p.u.) # 6 (s5) - Derivative of p.u. bus voltage # TODO: memory block for calc. of derivative self.signal.e_str = 'SW_s0 * (1-omega) + SW_s1 * 0 + SW_s2 * te + ' \ 'SW_s3 * (tm-tm0) + SW_s4 *v + SW_s5 * 0 - signal' self.F1 = Lag2ndOrd(u=self.signal, K=1, T1=self.A1, T2=self.A2) self.F2 = LeadLag2ndOrd(u=self.F1_y, T1=self.A3, T2=self.A4, T3=self.A5, T4=self.A6) self.LL1 = LeadLag(u=self.F2_y, T1=self.T1, T2=self.T2) self.LL2 = LeadLag(u=self.LL1_y, T1=self.T3, T2=self.T4) self.WO = Washout(u=self.LL2_y, T=self.T6, K=self.KST5) # WO_y == Vss self.VLIM = Limiter(u=self.WO_y, lower=self.LSMIN, upper=self.LSMAX, info='Vss limiter') self.Vss = Algeb( tex_name='V_{ss}', info='Voltage output before output limiter', e_str='VLIM_zi * WO_y + VLIM_zu * LSMAX + VLIM_zl * LSMIN - Vss') self.OLIM = Limiter(u=self.v, lower=self.VCL, upper=self.VCU, info='output limiter') # TODO: allow ignoring VCU or VCL when zero self.vsout.e_str = 'OLIM_zi * Vss - vsout'
def __init__(self): # Indicatior of frequency deviation self.Lfl1 = Limiter(u=self.fHz, lower=self.fl3, upper=self.fl1, info='Frequency comparer for (fl3, fl1)', equal=False, no_warn=True, ) self.Lfl2 = Limiter(u=self.fHz, lower=self.fl3, upper=self.fl2, info='Frequency comparer for (fl3, fl2)', equal=False, no_warn=True, ) self.Lfu1 = Limiter(u=self.fHz, lower=self.fu1, upper=self.fu3, info='Frequency comparer for (fu1, fu3)', equal=False, no_warn=True, ) self.Lfu2 = Limiter(u=self.fHz, lower=self.fu2, upper=self.fu3, info='Frequency comparer for (fu2, fu3)', equal=False, no_warn=True, ) # Frequency deviation time continuity check self.IAWfl1 = IntegratorAntiWindup(u='Lfl1_zi * (1 - res) - Tfl1 / Tres * res', T=1, K=1, y0='0', lower=self.zero, upper=self.Tfl1, info='condition check for (fl3, fl1)', no_warn=True, ) self.IAWfl2 = IntegratorAntiWindup(u='Lfl2_zi * (1 - res) - Tfl2 / Tres * res', T=1, K=1, y0='0', lower=self.zero, upper=self.Tfl2, info='condition check for (fl3, fl2)', no_warn=True, ) self.IAWfu1 = IntegratorAntiWindup(u='Lfu1_zi * (1 - res) - Tfu1 / Tres * res', T=1, K=1, y0='0', lower=self.zero, upper=self.Tfu1, info='condition check for (fu1, fu3)', no_warn=True, ) self.IAWfu2 = IntegratorAntiWindup(u='Lfl2_zi * (1 - res) - Tfu2 / Tres * res', T=1, K=1, y0='0', lower=self.zero, upper=self.Tfu2, info='condition check for (fu2, fu3)', no_warn=True, )
def test_sorted_limiter(self): """ Tests for `SortedLimiter` class Returns ------- """ self.cmp = Limiter(self.u, self.lower, self.upper) self.cmp.list2array(len(self.u.v)) self.cmp.check_var() self.rcmp = SortedLimiter(self.u, self.lower, self.upper, n_select=1) self.rcmp.list2array(len(self.u.v)) self.rcmp.check_var() self.assertSequenceEqual(self.rcmp.zl.tolist(), [0., 0., 1., 0., 0., 0., 0., 0.]) self.assertSequenceEqual(self.rcmp.zi.tolist(), [1., 1., 0., 1., 1., 1., 1., 0.]) self.assertSequenceEqual(self.rcmp.zu.tolist(), [0., 0., 0., 0., 0., 0., 0., 1.]) # test when no `n_select` is specified self.rcmp_noselect = SortedLimiter(self.u, self.lower, self.upper) self.rcmp_noselect.list2array(len(self.u.v)) self.rcmp_noselect.check_var() self.assertSequenceEqual(self.rcmp_noselect.zl.tolist(), self.cmp.zl.tolist()) self.assertSequenceEqual(self.rcmp_noselect.zi.tolist(), self.cmp.zi.tolist()) self.assertSequenceEqual(self.rcmp_noselect.zu.tolist(), self.cmp.zu.tolist()) # test when no `n_select` is over range self.rcmp_noselect = SortedLimiter(self.u, self.lower, self.upper, n_select=999) self.rcmp_noselect.list2array(len(self.u.v)) self.rcmp_noselect.check_var() self.assertSequenceEqual(self.rcmp_noselect.zl.tolist(), self.cmp.zl.tolist()) self.assertSequenceEqual(self.rcmp_noselect.zi.tolist(), self.cmp.zi.tolist()) self.assertSequenceEqual(self.rcmp_noselect.zu.tolist(), self.cmp.zu.tolist())
def test_limiter(self): """ Tests for `Limiter` class Returns ------- """ self.cmp = Limiter(self.u, self.lower, self.upper) self.cmp.list2array(len(self.u.v)) self.cmp.check_var() self.assertSequenceEqual(self.cmp.zl.tolist(), [1., 1., 1., 1., 0., 0., 0., 0.]) self.assertSequenceEqual(self.cmp.zi.tolist(), [0., 0., 0., 0., 1., 0., 0., 0.]) self.assertSequenceEqual(self.cmp.zu.tolist(), [0., 0., 0., 0., 0., 1., 1., 1.])
def __init__(self, system, config): EV1Model.__init__(self, system, config) # Modify power limit PHL self.PHLup = Algeb( info='PHL upper limit', v_str='pcap * pmx', e_str='pcap * pmx - PHLup', tex_name=r'PHL_{upper}', ) self.PHL2 = Limiter( u=self.Psum, lower=self.pmn, upper=self.PHLup, enable=1, info='limiter for Psum in [pmn, pcap * pmx]', ) # Modify self.Ipul.v_str = '(Psum * PHL2_zi + PHLup * PHL2_zu + pmn * PHL2_zl) / vp' self.Ipul.e_str = '(Psum * PHL2_zi + PHLup * PHL2_zu + pmn * PHL2_zl) / vp - Ipul'
def __init__(self, system, config): Model.__init__(self, system, config) self.flags.tds = True self.group = 'DG' self.config.add(OrderedDict((('plim', 0), ))) self.config.add_extra('_help', plim='enable input power limit check bound by [0, pmx]', ) self.config.add_extra('_tex', plim='P_{lim}', ) self.config.add_extra('_alt', plim=(0, 1), ) self.SWPQ = Switcher(u=self.pqflag, options=(0, 1), tex_name='SW_{PQ}', cache=True) self.buss = DataSelect(self.igreg, self.bus, info='selected bus (bus or igreg)', ) self.busfreq = DeviceFinder(self.busf, link=self.buss, idx_name='bus') # --- initial values from power flow --- # a : bus voltage angle # v : bus voltage magnitude # p0s : active power from connected static PV generator # q0s : reactive power from connected static PV generator # pref0 : initial active power set point for the PVD1 device # qref0 : initial reactive power set point for the PVD1 device self.a = ExtAlgeb(model='Bus', src='a', indexer=self.buss, tex_name=r'\theta', info='bus (or igreg) phase angle', unit='rad.', e_str='-Ipout_y * v * u', ename='P', tex_ename='P', ) self.v = ExtAlgeb(model='Bus', src='v', indexer=self.buss, tex_name='V', info='bus (or igreg) terminal voltage', unit='p.u.', e_str='-Iqout_y * v * u', ename='Q', tex_ename='Q', ) self.p0s = ExtService(model='StaticGen', src='p', indexer=self.gen, tex_name='P_{0s}', info='Initial P from static gen', ) self.q0s = ExtService(model='StaticGen', src='q', indexer=self.gen, tex_name='Q_{0s}', info='Initial Q from static gen', ) # --- calculate the initial P and Q for this distributed device --- self.pref0 = ConstService(v_str='gammap * p0s', tex_name='P_{ref0}', info='Initial P for the PVD1 device', ) self.qref0 = ConstService(v_str='gammaq * q0s', tex_name='Q_{ref0}', info='Initial Q for the PVD1 device', ) # frequency measurement variable `f` self.f = ExtAlgeb(model='FreqMeasurement', src='f', indexer=self.busfreq, export=False, info='Bus frequency', unit='p.u.', ) self.fHz = Algeb(info='frequency in Hz', v_str='fn * f', e_str='fn * f - fHz', unit='Hz', tex_name='f_{Hz}', ) # --- frequency branch --- self.FL1 = Limiter(u=self.fHz, lower=self.ft0, upper=self.ft1, info='Under frequency comparer', no_warn=True, ) self.FL2 = Limiter(u=self.fHz, lower=self.ft2, upper=self.ft3, info='Over frequency comparer', no_warn=True, ) self.Kft01 = ConstService(v_str='1/(ft1 - ft0)', tex_name='K_{ft01}') self.Ffl = Algeb(info='Coeff. for under frequency', v_str='FL1_zi * Kft01 * (fHz - ft0) + FL1_zu', e_str='FL1_zi * Kft01 * (fHz - ft0) + FL1_zu - Ffl', tex_name='F_{fl}', discrete=self.FL1, ) self.Kft23 = ConstService(v_str='1/(ft3 - ft2)', tex_name='K_{ft23}') self.Ffh = Algeb(info='Coeff. for over frequency', v_str='FL2_zl + FL2_zi * (1 + Kft23 * (ft2 - fHz))', e_str='FL2_zl + FL2_zi * (1 + Kft23 * (ft2 - fHz)) - Ffh', tex_name='F_{fh}', discrete=self.FL2, ) self.Fdev = Algeb(info='Frequency deviation', v_str='fn - fHz', e_str='fn - fHz - Fdev', unit='Hz', tex_name='f_{dev}', ) self.DB = DeadBand1(u=self.Fdev, center=0.0, lower=self.fdbd, upper=0.0, gain=self.ddn, info='frequency deviation deadband with gain', ) # outputs `Pdrp` self.DB.db.no_warn = True # --- Voltage flags --- self.VL1 = Limiter(u=self.v, lower=self.vt0, upper=self.vt1, info='Under voltage comparer', no_warn=True, ) self.VL2 = Limiter(u=self.v, lower=self.vt2, upper=self.vt3, info='Over voltage comparer', no_warn=True, ) self.Kvt01 = ConstService(v_str='1/(vt1 - vt0)', tex_name='K_{vt01}') self.Fvl = Algeb(info='Coeff. for under voltage', v_str='VL1_zi * Kvt01 * (v - vt0) + VL1_zu', e_str='VL1_zi * Kvt01 * (v - vt0) + VL1_zu - Fvl', tex_name='F_{vl}', discrete=self.VL1, ) self.Kvt23 = ConstService(v_str='1/(vt3 - vt2)', tex_name='K_{vt23}') self.Fvh = Algeb(info='Coeff. for over voltage', v_str='VL2_zl + VL2_zi * (1 + Kvt23 * (vt2 - v))', e_str='VL2_zl + VL2_zi * (1 + Kvt23 * (vt2 - v)) - Fvh', tex_name='F_{vh}', discrete=self.VL2, ) # --- sensed voltage with lower limit of 0.01 --- self.VLo = Limiter(u=self.v, lower=0.01, upper=999, no_upper=True, info='Voltage lower limit (0.01) flag', ) self.vp = Algeb(tex_name='V_p', info='Sensed positive voltage', v_str='v * VLo_zi + 0.01 * VLo_zl', e_str='v * VLo_zi + 0.01 * VLo_zl - vp', ) self.Pext0 = ConstService(info='External additional signal added to Pext', tex_name='P_{ext0}', v_str='0', ) self.Pext = Algeb(tex_name='P_{ext}', info='External power signal (for AGC)', v_str='u * Pext0', e_str='u * Pext0 - Pext' ) self.Pref = Algeb(tex_name='P_{ref}', info='Reference power signal (for scheduling setpoint)', v_str='u * pref0', e_str='u * pref0 - Pref' ) self.Psum = Algeb(tex_name='P_{tot}', info='Sum of P signals', v_str='u * (Pext + Pref + DB_y)', e_str='u * (Pext + Pref + DB_y) - Psum', ) # `DB_y` is `Pdrp` (f droop) self.PHL = Limiter(u=self.Psum, lower=0.0, upper=self.pmx, enable=self.config.plim, info='limiter for Psum in [0, pmx]', ) self.Vcomp = VarService(v_str='abs(v*exp(1j*a) + (1j * xc) * (Ipout_y + 1j * Iqout_y))', info='Voltage before Xc compensation', tex_name='V_{comp}' ) self.Vqu = ConstService(v_str='v1 - (qref0 - qmn) / dqdv', info='Upper voltage bound => qmx', tex_name='V_{qu}', ) self.Vql = ConstService(v_str='v0 + (qmx - qref0) / dqdv', info='Lower voltage bound => qmn', tex_name='V_{ql}', ) self.VQ1 = Limiter(u=self.Vcomp, lower=self.Vql, upper=self.v0, info='Under voltage comparer for Q droop', no_warn=True, ) self.VQ2 = Limiter(u=self.Vcomp, lower=self.v1, upper=self.Vqu, info='Over voltage comparer for Q droop', no_warn=True, ) Qdrp = 'u * VQ1_zl * qmx + VQ2_zu * qmn + ' \ 'u * VQ1_zi * (qmx + dqdv *(Vqu - Vcomp)) + ' \ 'u * VQ2_zi * (dqdv * (v1 - Vcomp)) ' self.Qdrp = Algeb(tex_name='Q_{drp}', info='External power signal (for AGC)', v_str=Qdrp, e_str=f'{Qdrp} - Qdrp', discrete=(self.VQ1, self.VQ2), ) self.Qref = Algeb(tex_name=r'Q_{ref}', info='Reference power signal (for scheduling setpoint)', v_str='u * qref0', e_str='u * qref0 - Qref' ) self.Qsum = Algeb(tex_name=r'Q_{tot}', info='Sum of Q signals', v_str=f'u * (qref0 + {Qdrp})', e_str='u * (Qref + Qdrp) - Qsum', discrete=(self.VQ1, self.VQ2), ) self.Ipul = Algeb(info='Ipcmd before Ip hard limit', v_str='(Psum * PHL_zi + pmx * PHL_zu) / vp', e_str='(Psum * PHL_zi + pmx * PHL_zu) / vp - Ipul', tex_name='I_{p,ul}', ) self.Iqul = Algeb(info='Iqcmd before Iq hard limit', v_str='Qsum / vp', e_str='Qsum / vp - Iqul', tex_name='I_{q,ul}', ) # --- Ipmax, Iqmax and Iqmin --- Ipmaxsq = "(Piecewise((0, Le(ialim**2 - Iqcmd_y**2, 0)), ((ialim**2 - Iqcmd_y ** 2), True)))" Ipmaxsq0 = "(Piecewise((0, Le(ialim**2 - (u*qref0/v)**2, 0)), ((ialim**2 - (u*qref0/v) ** 2), True)))" self.Ipmaxsq = VarService(v_str=Ipmaxsq, tex_name='I_{pmax}^2') self.Ipmaxsq0 = ConstService(v_str=Ipmaxsq0, tex_name='I_{pmax0}^2') self.Ipmax = Algeb(v_str='(SWPQ_s1 * ialim + SWPQ_s0 * sqrt(Ipmaxsq0))', e_str='(SWPQ_s1 * ialim + SWPQ_s0 * sqrt(Ipmaxsq)) - Ipmax', tex_name='I_{pmax}', ) Iqmaxsq = "(Piecewise((0, Le(ialim**2 - Ipcmd_y**2, 0)), ((ialim**2 - Ipcmd_y ** 2), True)))" Iqmaxsq0 = "(Piecewise((0, Le(ialim**2 - (u*pref0/v)**2, 0)), ((ialim**2 - (u*pref0/v) ** 2), True)))" self.Iqmaxsq = VarService(v_str=Iqmaxsq, tex_name='I_{qmax}^2') self.Iqmaxsq0 = ConstService(v_str=Iqmaxsq0, tex_name='I_{qmax0}^2') self.Iqmax = Algeb(v_str='SWPQ_s0 * ialim + SWPQ_s1 * sqrt(Iqmaxsq0)', e_str='SWPQ_s0 * ialim + SWPQ_s1 * sqrt(Iqmaxsq) - Iqmax', tex_name='I_{qmax}', ) # TODO: set option whether to use degrading gain # --- `Ipcmd` and `Iqcmd` --- self.Ipcmd = GainLimiter(u=self.Ipul, K=1, R='Fvl * Fvh * Ffl * Ffh * recflag + 1 * (1 - recflag)', lower=0, upper=self.Ipmax, info='Ip with limiter and coeff.', tex_name='I^{pcmd}', ) self.Iqcmd = GainLimiter(u=self.Iqul, K=1, R='Fvl * Fvh * Ffl * Ffh * recflag + 1 * (1 - recflag)', lower=self.Iqmax, sign_lower=-1, upper=self.Iqmax, info='Iq with limiter and coeff.', tex_name='I^{qcmd}', ) self.Ipout = Lag(u=self.Ipcmd_y, T=self.tip, K=1.0, info='Output Ip filter', ) self.Iqout = Lag(u=self.Iqcmd_y, T=self.tiq, K=1.0, info='Output Iq filter', )
class TestDiscrete(unittest.TestCase): def setUp(self): self.lower = NumParam() self.upper = NumParam() self.u = Algeb() self.upper.v = np.array([2, 2, 2, 2, 2, 2, 2.8, 3.9]) self.u.v = np.array([-3, -1.1, -5, 0, 1, 2, 3, 10]) self.lower.v = np.array([-2, -1, 0.5, 0, 0.5, 1.5, 2, 3]) def test_limiter(self): """ Tests for `Limiter` class Returns ------- """ self.cmp = Limiter(self.u, self.lower, self.upper) self.cmp.list2array(len(self.u.v)) self.cmp.check_var() self.assertSequenceEqual(self.cmp.zl.tolist(), [1., 1., 1., 1., 0., 0., 0., 0.]) self.assertSequenceEqual(self.cmp.zi.tolist(), [0., 0., 0., 0., 1., 0., 0., 0.]) self.assertSequenceEqual(self.cmp.zu.tolist(), [0., 0., 0., 0., 0., 1., 1., 1.]) def test_sorted_limiter(self): """ Tests for `SortedLimiter` class Returns ------- """ self.cmp = Limiter(self.u, self.lower, self.upper) self.cmp.list2array(len(self.u.v)) self.cmp.check_var() self.rcmp = SortedLimiter(self.u, self.lower, self.upper, n_select=1) self.rcmp.list2array(len(self.u.v)) self.rcmp.check_var() self.assertSequenceEqual(self.rcmp.zl.tolist(), [0., 0., 1., 0., 0., 0., 0., 0.]) self.assertSequenceEqual(self.rcmp.zi.tolist(), [1., 1., 0., 1., 1., 1., 1., 0.]) self.assertSequenceEqual(self.rcmp.zu.tolist(), [0., 0., 0., 0., 0., 0., 0., 1.]) # test when no `n_select` is specified self.rcmp_noselect = SortedLimiter(self.u, self.lower, self.upper) self.rcmp_noselect.list2array(len(self.u.v)) self.rcmp_noselect.check_var() self.assertSequenceEqual(self.rcmp_noselect.zl.tolist(), self.cmp.zl.tolist()) self.assertSequenceEqual(self.rcmp_noselect.zi.tolist(), self.cmp.zi.tolist()) self.assertSequenceEqual(self.rcmp_noselect.zu.tolist(), self.cmp.zu.tolist()) # test when no `n_select` is over range self.rcmp_noselect = SortedLimiter(self.u, self.lower, self.upper, n_select=999) self.rcmp_noselect.list2array(len(self.u.v)) self.rcmp_noselect.check_var() self.assertSequenceEqual(self.rcmp_noselect.zl.tolist(), self.cmp.zl.tolist()) self.assertSequenceEqual(self.rcmp_noselect.zi.tolist(), self.cmp.zi.tolist()) self.assertSequenceEqual(self.rcmp_noselect.zu.tolist(), self.cmp.zu.tolist()) def test_switcher(self): p = NumParam() p.v = np.array([0, 1, 2, 2, 1, 3, 1]) switcher = Switcher(u=p, options=[0, 1, 2, 3, 4]) switcher.list2array(len(p.v)) switcher.check_var() self.assertSequenceEqual(switcher.s0.tolist(), [1, 0, 0, 0, 0, 0, 0]) self.assertSequenceEqual(switcher.s1.tolist(), [0, 1, 0, 0, 1, 0, 1]) self.assertSequenceEqual(switcher.s2.tolist(), [0, 0, 1, 1, 0, 0, 0]) self.assertSequenceEqual(switcher.s3.tolist(), [0, 0, 0, 0, 0, 1, 0]) self.assertSequenceEqual(switcher.s4.tolist(), [0, 0, 0, 0, 0, 0, 0])
def __init__(self, system, config): PSSBase.__init__(self, system, config) # ALL THE FOLLOWING IS FOR INPUT 2 # retrieve indices of bus and bus freq self.buss2 = DataSelect(self.busr2, self.bus, info='selected bus (bus or busr)') self.busfreq2 = DeviceFinder(self.busf2, link=self.buss2, idx_name='bus') # from Bus self.v2 = ExtAlgeb( model='Bus', src='v', indexer=self.buss2, tex_name=r'V', info='Bus (or busr2, if given) terminal voltage', ) # from BusFreq 2 self.f2 = ExtAlgeb(model='FreqMeasurement', src='f', indexer=self.busfreq2, export=False, info='Bus frequency 2') # Config self.config.add(OrderedDict([('freq_model', 'BusFreq')])) self.config.add_extra( '_help', {'freq_model': 'default freq. measurement model'}) self.config.add_extra('_alt', {'freq_model': ('BusFreq', )}) self.busf.model = self.config.freq_model self.busf2.model = self.config.freq_model # input signal switch self.dv = Derivative(self.v) self.dv2 = Derivative(self.v2) self.SnSb = ExtService( model='SynGen', src='M', indexer=self.syn, attr='pu_coeff', info='Machine base to sys base factor for power', tex_name='(Sb/Sn)') self.SW = Switcher( u=self.MODE, options=[0, 1, 2, 3, 4, 5, 6, np.nan], ) self.SW2 = Switcher( u=self.MODE2, options=[0, 1, 2, 3, 4, 5, 6, np.nan], ) # Input signals self.sig = Algeb( tex_name='S_{ig}', info='Input signal', ) self.sig.v_str = 'SW_s1*(omega-1) + SW_s2*0 + SW_s3*(tm0/SnSb) + ' \ 'SW_s4*(tm-tm0) + SW_s5*v + SW_s6*0' self.sig.e_str = 'SW_s1*(omega-1) + SW_s2*(f-1) + SW_s3*(te/SnSb) + ' \ 'SW_s4*(tm-tm0) + SW_s5*v + SW_s6*dv_v - sig' self.sig2 = Algeb( tex_name='S_{ig2}', info='Input signal 2', ) self.sig2.v_str = 'SW2_s1*(omega-1) + SW2_s2*0 + SW2_s3*(tm0/SnSb) + ' \ 'SW2_s4*(tm-tm0) + SW2_s5*v2 + SW2_s6*0' self.sig2.e_str = 'SW2_s1*(omega-1) + SW2_s2*(f2-1) + SW2_s3*(te/SnSb) + ' \ 'SW2_s4*(tm-tm0) + SW2_s5*v2 + SW2_s6*dv2_v - sig2' self.L1 = Lag( u=self.sig, K=self.K1, T=self.T1, info='Transducer 1', ) self.L2 = Lag( u=self.sig2, K=self.K2, T=self.T2, info='Transducer 2', ) self.IN = Algeb( tex_name='I_N', info='Sum of inputs', v_str='L1_y + L2_y', e_str='L1_y + L2_y - IN', ) self.WO = WashoutOrLag( u=self.IN, K=self.T3, T=self.T4, ) self.LL1 = LeadLag( u=self.WO_y, T1=self.T5, T2=self.T6, zero_out=True, ) self.LL2 = LeadLag( u=self.LL1_y, T1=self.T7, T2=self.T8, zero_out=True, ) self.LL3 = LeadLag( u=self.LL2_y, T1=self.T9, T2=self.T10, zero_out=True, ) self.VSS = GainLimiter(u=self.LL3_y, K=1, lower=self.LSMIN, upper=self.LSMAX) self.VOU = ConstService(v_str='VCUr + v0') self.VOL = ConstService(v_str='VCLr + v0') self.OLIM = Limiter(u=self.v, lower=self.VOL, upper=self.VOU, info='output limiter') self.vsout.e_str = 'OLIM_zi * VSS_y - vsout'
def __init__(self, system, config): PSSBase.__init__(self, system, config) self.config.add(OrderedDict([('freq_model', 'BusFreq')])) self.config.add_extra( '_help', {'freq_model': 'default freq. measurement model'}) self.config.add_extra('_alt', {'freq_model': ('BusFreq', )}) self.busf.model = self.config.freq_model self.dv = Derivative(self.v) self.SnSb = ExtService( model='SynGen', src='M', indexer=self.syn, attr='pu_coeff', info='Machine base to sys base factor for power', tex_name='(Sb/Sn)') self.SW = Switcher( u=self.MODE, options=[1, 2, 3, 4, 5, 6], ) self.sig = Algeb( tex_name='S_{ig}', info='Input signal', ) self.sig.v_str = 'SW_s0*(omega-1) + SW_s1*0 + SW_s2*(tm0/SnSb) + ' \ 'SW_s3*(tm-tm0) + SW_s4*v + SW_s5*0' self.sig.e_str = 'SW_s0*(omega-1) + SW_s1*(f-1) + SW_s2*(te/SnSb) + ' \ 'SW_s3*(tm-tm0) + SW_s4*v + SW_s5*dv_v - sig' self.F1 = Lag2ndOrd(u=self.sig, K=1, T1=self.A1, T2=self.A2) self.F2 = LeadLag2ndOrd(u=self.F1_y, T1=self.A3, T2=self.A4, T3=self.A5, T4=self.A6, zero_out=True) self.LL1 = LeadLag(u=self.F2_y, T1=self.T1, T2=self.T2, zero_out=True) self.LL2 = LeadLag(u=self.LL1_y, T1=self.T3, T2=self.T4, zero_out=True) self.Vks = Gain(u=self.LL2_y, K=self.KS) self.WO = WashoutOrLag(u=self.Vks_y, T=self.T6, K=self.T5, name='WO', zero_out=True) # WO_y == Vss self.VLIM = Limiter(u=self.WO_y, lower=self.LSMIN, upper=self.LSMAX, info='Vss limiter') self.Vss = Algeb( tex_name='V_{ss}', info='Voltage output before output limiter', e_str='VLIM_zi * WO_y + VLIM_zu * LSMAX + VLIM_zl * LSMIN - Vss') self.OLIM = Limiter(u=self.v, lower=self.VCLr, upper=self.VCUr, info='output limiter') self.vsout.e_str = 'OLIM_zi * Vss - vsout'
def __init__(self, system=None, config=None): PQData.__init__(self) Model.__init__(self, system, config) self.group = 'StaticLoad' # ``tds`` flag is needed to retrieve initial voltage (for constant Z/I conversion) self.flags.update({'pflow': True, 'tds': True, }) self.config.add(OrderedDict((('pq2z', 1), ('p2p', 0.0), ('p2i', 0.0), ('p2z', 1.0), ('q2q', 0.0), ('q2i', 0.0), ('q2z', 1.0), ))) self.config.add_extra("_help", pq2z="pq2z conversion if out of voltage limits", p2p="P constant power percentage for TDS. Must have (p2p+p2i+p2z)=1", p2i="P constant current percentage", p2z="P constant impedance percentage", q2q="Q constant power percentage for TDS. Must have (q2q+q2i+q2z)=1", q2i="Q constant current percentage", q2z="Q constant impedance percentage", ) self.config.add_extra("_alt", pq2z="(0, 1)", p2p="float", p2i="float", p2z="float", q2q="float", q2i="float", q2z="float", ) self.config.add_extra("_tex", pq2z="z_{pq2z}", p2p=r"\gamma_{p2p}", p2i=r"\gamma_{p2i}", p2z=r"\gamma_{p2z}", q2q=r"\gamma_{q2q}", q2i=r"\gamma_{q2i}", q2z=r"\gamma_{q2z}", ) self.a = ExtAlgeb(model='Bus', src='a', indexer=self.bus, tex_name=r'\theta', ) self.v = ExtAlgeb(model='Bus', src='v', indexer=self.bus, tex_name=r'V', ) self.v0 = ExtService(src='v', model='Bus', indexer=self.bus, tex_name='V_0', info='Initial voltage magnitude from power flow' ) self.a0 = ExtService(src='a', model='Bus', indexer=self.bus, tex_name=r'\theta_0', info='Initial voltage angle from power flow' ) # Rub, Xub, Rlb, Xlb are constant for both PF and TDS. self.Rub = ConstService(info='Equivalent resistance at voltage upper bound', v_str='p0 / vmax**2', tex_name='R_{ub}' ) self.Xub = ConstService(info='Equivalent reactance at voltage upper bound', v_str='q0 / vmax**2', tex_name='X_{ub}' ) self.Rlb = ConstService(info='Equivalent resistance at voltage lower bound', v_str='p0 / vmin**2', tex_name='R_{lb}' ) self.Xlb = ConstService(info='Equivalent reactance at voltage lower bound', v_str='q0 / vmin**2', tex_name='X_{lb}' ) # Ppf, Qpf, Req, Xeq, Ipeq, Iqeq are only meaningful after initializing TDS self.Ppf = ConstService(info='Actual P in power flow', v_str='(p0 * vcmp_zi + Rlb * vcmp_zl * v0**2 + Rub * vcmp_zu * v0**2)', tex_name='P_{pf}') self.Qpf = ConstService(info='Actual Q in power flow', v_str='(q0 * vcmp_zi + Xlb * vcmp_zl * v0**2 + Xub * vcmp_zu * v0**2)', tex_name='Q_{pf}') self.Req = ConstService(info='Equivalent resistance at steady state', v_str='Ppf / v0**2', tex_name='R_{eq}' ) self.Xeq = ConstService(info='Equivalent reactance at steady state', v_str='Qpf / v0**2', tex_name='X_{eq}' ) self.Ipeq = ConstService(info='Equivalent active current source at steady state', v_str='Ppf / v0', tex_name='I_{peq}' ) self.Iqeq = ConstService(info='Equivalent reactive current source at steady state', v_str='Qpf / v0', tex_name='I_{qeq}' ) self.vcmp = Limiter(u=self.v, lower=self.vmin, upper=self.vmax, enable=self.config.pq2z, ) # Note: the "or" condition "|" is not supported in sympy equation strings. # They will simply be ignored. # To modify P and Q during TDS, use `alter` to set values to `Ppf` and `Qpf` # after, before simulation, setting `config.p2p=1` and `config.q2q=1`. self.a.e_str = "u * (dae_t <= 0) * " \ "(p0 * vcmp_zi + Rlb * vcmp_zl * v**2 + Rub * vcmp_zu * v**2) + " \ "u * (dae_t > 0) * " \ "(p2p * Ppf + p2i * Ipeq * v + p2z * Req * v**2)" self.v.e_str = "u * (dae_t <= 0) * " \ "(q0 * vcmp_zi + Xlb * vcmp_zl * v**2 + Xub * vcmp_zu * v**2) + " \ "u * (dae_t > 0) * " \ "(q2q * Qpf + q2i * Iqeq * v + q2z * Xeq * v**2)"
def __init__(self): # -- Voltage protection # Indicatior of voltage deviation self.LVl1 = Limiter(u=self.v, lower=self.vl4, upper=self.vl1, info='Voltage comparer for (vl4, vl1)', equal=False, no_warn=True, ) self.LVl2 = Limiter(u=self.v, lower=self.vl4, upper=self.vl2, info='Voltage comparer for (vl4, vl2)', equal=False, no_warn=True, ) self.LVl3 = Limiter(u=self.v, lower=self.vl4, upper=self.vl3, info='Voltage comparer for (vl4, vl3)', equal=False, no_warn=True, ) self.LVu1 = Limiter(u=self.v, lower=self.vu1, upper=self.vu3, info='Voltage comparer for (vu1, vu3)', equal=False, no_warn=True, ) self.LVu2 = Limiter(u=self.v, lower=self.vu2, upper=self.vu3, info='Voltage comparer for (vu2, vu3)', equal=False, no_warn=True, ) # Voltage deviation time continuity check self.IAWVl1 = IntegratorAntiWindup(u='LVl1_zi * (1 - res) - Tvl1 / Tres * res', T=1, K=1, y0='0', lower=self.zero, upper=self.Tvl1, info='condition check for (Vl3, Vl1)', no_warn=True, ) self.IAWVl2 = IntegratorAntiWindup(u='LVl2_zi * (1 - res) - Tvl2 / Tres * res', T=1, K=1, y0='0', lower=self.zero, upper=self.Tvl2, info='condition check for (Vl3, Vl2)', no_warn=True, ) self.IAWVl3 = IntegratorAntiWindup(u='LVl2_zi * (1 - res) - Tvl3 / Tres * res', T=1, K=1, y0='0', lower=self.zero, upper=self.Tvl2, info='condition check for (Vl3, Vl2)', no_warn=True, ) self.IAWVu1 = IntegratorAntiWindup(u='LVu1_zi * (1 - res) - Tvu1 / Tres * res', T=1, K=1, y0='0', lower=self.zero, upper=self.Tvu1, info='condition check for (Vu1, Vu3)', no_warn=True, ) self.IAWVu2 = IntegratorAntiWindup(u='LVu2_zi * (1 - res) - Tvu2 / Tres * res', T=1, K=1, y0='0', lower=self.zero, upper=self.Tvu2, info='condition check for (Vu2, Vu3)', no_warn=True, )
def __init__(self, system, config): Model.__init__(self, system, config) self.flags.tds = True self.group = 'DGProtection' self.bus = ExtParam(model='DG', src='bus', indexer=self.dev, export=False) self.fn = ExtParam(model='DG', src='fn', indexer=self.dev, export=False) # -- Frequency protection # Convert frequency deviation range to p.u. self.f = ExtAlgeb(export=False, info='DG frequency read value', unit='p.u.', model='FreqMeasurement', src='f', indexer=self.busfreq, ) self.fHz = Algeb(v_str='fn * f', e_str='fn * f - fHz', info='frequency in Hz', tex_name=r'f_{Hz}', ) # -- Lock DG frequency signal and output power self.ltu = ConstService(v_str='0.8') self.ltl = ConstService(v_str='0.2') # `Ldsum_zu` is `ue` dsum = 'fen * (IAWfl1_lim_zu * Lfl1_zi + IAWfl2_lim_zu * Lfl2_zi + ' \ 'IAWfu1_lim_zu * Lfu1_zi + IAWfu2_lim_zu * Lfu2_zi) + ' \ 'Ven * (IAWVl1_lim_zu * LVl1_zi + IAWVl2_lim_zu * LVl2_zi + ' \ 'IAWVl3_lim_zu * LVl3_zi + ' \ 'IAWVu1_lim_zu * LVu1_zi + IAWVu2_lim_zu * LVu2_zi) - ' \ 'dsum' self.dsum = Algeb(v_str='0', e_str=dsum, info='lock signal summation', tex_name=r'd_{tot}', ) self.Ldsum = Limiter(u=self.dsum, lower=self.ltl, upper=self.ltu, info='lock signal comparer, zu is to act', equal=False, no_warn=True, ) self.ue = Algeb(v_str='0', e_str='Ldsum_zu - ue', info='lock flag', tex_name=r'ue', ) self.zero = ConstService('0') self.res = ExtendedEvent(self.ue, t_ext=self.Tres, trig="rise", extend_only=True) # lock DG frequency signal # fflag option 1: leave source signal online in protection self.fin = ExtAlgeb(model='DG', src='f', indexer=self.dev, info='original f from DG', ) self.fHzl = ExtAlgeb(model='DG', src='fHz', indexer=self.dev, export=False, e_str='- ue * (fn * f)', info='Frequency measure lock', ename='fHzl', tex_ename='f_{Hzl}', ) # TODO: add fflag option 2: block the source signal in protection # lock output power of DG self.Pext = ExtAlgeb(model='DG', src='Pext', indexer=self.dev, info='original Pext from DG', ) self.Pref = ExtAlgeb(model='DG', src='Pref', indexer=self.dev, info='original Pref from DG', ) self.Pdrp = ExtAlgeb(model='DG', src='DB_y', indexer=self.dev, info='original Pdrp from DG', ) self.Psum = ExtAlgeb(model='DG', src='Psum', indexer=self.dev, export=False, e_str='- ue * (Pext + Pref + Pdrp)', info='Active power lock', ename='Pneg', tex_ename='P_{neg}', ) self.Qdrp = ExtAlgeb(model='DG', src='Qdrp', indexer=self.dev, info='original Qdrp from DG', ) self.Qref = ExtAlgeb(model='DG', src='Qref', indexer=self.dev, info='original Qref from DG', ) self.Qsum = ExtAlgeb(model='DG', src='Qsum', indexer=self.dev, export=False, e_str='- ue * (Qdrp + Qref)', info='Reactive power lock', ename='Qneg', tex_ename='Q_{neg}', )
def __init__(self, system=None, config=None): PQData.__init__(self) Model.__init__(self, system, config) self.group = 'StaticLoad' # ``tds`` flag is added to retrieve initial voltage (for constant Z or constant I conversion) self.flags.update({'pflow': True, 'tds': True, }) self.config.add(OrderedDict((('pq2z', 1), ('p2p', 0), ('p2i', 0), # not in use ('p2z', 1), ('q2q', 0), ('q2i', 0), # not in use ('q2z', 1), ))) self.a = ExtAlgeb(model='Bus', src='a', indexer=self.bus, tex_name=r'\theta', ) self.v = ExtAlgeb(model='Bus', src='v', indexer=self.bus, tex_name=r'V', ) self.v0 = ExtService(src='v', model='Bus', indexer=self.bus, tex_name='V_0', info='Initial voltage magnitude from power flow' ) self.a0 = ExtService(src='a', model='Bus', indexer=self.bus, tex_name=r'\theta_0', info='Initial voltage angle from power flow' ) self.Req = ConstService(info='Equivalent resistance', v_str='p0 / v0**2', ) self.Xeq = ConstService(info='Equivalent reactance', v_str='q0 / v0**2', ) self.vcmp = Limiter(u=self.v, lower=self.vmin, upper=self.vmax, enable=self.config.pq2z, ) self.a.e_str = "u * ((dae_t <= 0) | (p2p == 1)) * " \ "(p0 * vcmp_zi + " \ "p0 * vcmp_zl * (v ** 2 / vmin ** 2) + "\ "p0 * vcmp_zu * (v ** 2 / vmax ** 2)) + " \ "u * ((dae_t > 0) & (p2p != 1)) * " \ "(p2p * p0 + p2z * Req * v**2)" self.v.e_str = "u * ((dae_t <= 0) | (q2q == 1)) * " \ "(q0 * vcmp_zi + " \ "q0 * vcmp_zl * (v ** 2 / vmin ** 2) + " \ "q0 * vcmp_zu * (v ** 2 / vmax ** 2)) + " \ "u * ((dae_t > 0) & (q2q != 1)) * " \ "(q2q * q0 + q2z * Xeq * v**2)"