def __init__(self, u, s10, s12, name=None, tex_name=None, info=None): self.s10 = s10 self.s12 = s12 points = [0, 0.8] c0 = [15, -24, 10] c1 = [-27.5, 50, -22.5] c2 = [12.5, -25, 12.5] self.c0 = ConstService( v_str= f'0.8*{c0[0]} + {c0[1]} * (1 - {s10.name}) + 1.2 * {c0[2]} * (1-{s12.name})', tex_name='c_0', info='Constant coefficient in quadratic saturation') self.c1 = ConstService( v_str= f'0.8*{c1[0]} + {c1[1]} * (1 - {s10.name}) + 1.2 * {c1[2]} * (1-{s12.name})', tex_name='c_1') self.c2 = ConstService( v_str= f'0.8*{c2[0]} + {c2[1]} * (1 - {s10.name}) + 1.2 * {c2[2]} * (1-{s12.name})', tex_name='c_2') super().__init__(u=u, points=points, funs=None, name=name, tex_name=tex_name, info=info) self.vars.update({'c0': self.c0, 'c1': self.c1, 'c2': self.c2})
def __init__(self, system, config): Model.__init__(self, system, config) self.group = 'DynLoad' self.flags.tds = True self.bus = ExtParam(model='PQ', src='bus', indexer=self.pq) self.p0 = ExtService( model='PQ', src='Ppf', indexer=self.pq, tex_name='P_0', ) self.q0 = ExtService( model='PQ', src='Qpf', indexer=self.pq, tex_name='Q_0', ) self.v0 = ExtService( model='Bus', src='v', indexer=self.bus, tex_name='V_0', ) self.busfreq = DeviceFinder( u=self.busf, link=self.bus, idx_name='bus', info='found idx of BusFreq', ) self.f = ExtAlgeb( model='FreqMeasurement', src='f', indexer=self.busfreq, tex_name='f', ) self.pv0 = ConstService(v_str='u * kp/100 * p0 / (v0) ** ap ') self.qv0 = ConstService(v_str='u * kq/100 * q0 / (v0) ** aq ') self.a = ExtAlgeb( model='Bus', src='a', indexer=self.bus, tex_name=r'\theta', e_str='pv0 * (v ** ap) * (f ** bp)', ) self.v = ExtAlgeb( model='Bus', src='v', indexer=self.bus, tex_name='V', e_str='qv0 * (v ** aq) * (f ** bq)', )
def __init__(self, system, config): ModelData.__init__(self) self.bus = IdxParam( model='Bus', info="linked bus idx", mandatory=True, ) self.tf = TimerParam( info='Fault start time for the bus', mandatory=True, callback=self.apply_fault, ) self.tc = TimerParam( info='Fault end time for the bus', callback=self.clear_fault, ) self.xf = NumParam( info='Fault to ground impedance', default=1e-4, tex_name='x_f', ) self.rf = NumParam( info='Fault to ground resistance', default=0, tex_name='x_f', ) Model.__init__(self, system, config) self.flags.update({'tds': True}) self.group = 'TimedEvent' self.gf = ConstService( tex_name='g_{f}', v_str='re(1/(rf + 1j * xf))', ) self.bf = ConstService( tex_name='b_{f}', v_str='im(1/(rf + 1j * xf))', ) self.uf = ConstService( tex_name='u_f', v_str='0', ) self.a = ExtAlgeb( model='Bus', src='a', indexer=self.bus, tex_name=r'\theta', e_str='u * uf * (v ** 2 * gf)', ) self.v = ExtAlgeb( model='Bus', src='v', indexer=self.bus, tex_name=r'V', e_str='u * uf * (v ** 2 * bf)', ) self._vstore = np.array([])
def __init__(self, system, config): ExcBase.__init__(self, system, config) ExcVsum.__init__(self) self.LP = Lag( u=self.v, T=self.TR, K=1, info='Voltage transducer', ) self.vi = Algeb( info='Total voltage input', unit='pu', e_str='ue * (-LP_y + vref + Vs - WF_y ) -vi ', v_str='ue*(-v +vref)', ) self.VRMAXu = ConstService('VRMAX * ue + (1-ue) * 999') self.VRMINu = ConstService('VRMIN * ue + (1-ue) * -999') self.VR = LagAntiWindup( u=self.vi, T=self.TA, K=self.KA, upper=self.VRMAXu, lower=self.VRMINu, ) self.LL = LeadLag( u=self.VR_y, T1=self.TF3, T2=self.TF2, ) self.WF = Washout(u=self.LL_y, T=self.TF1, K=self.KF) self.INTin = 'ue * (VR_y - VFE)' ExcACSat.__init__(self) self.vref.v_str = 'v + VFE / KA' self.vref0 = PostInitService( info='Initial reference voltage input', tex_name='V_{ref0}', v_str='vref', ) self.VFE.v_str = "INT_y * KE + Se " self.VFE.e_str = "ue * (INT_y * KE + Se - VFE) " # disable iterative initialization of the integrator output self.INT.y.v_str = 'vf0' self.INT.y.v_iter = None self.vout.e_str = 'ue * INT_y - vout'
def __init__(self, system=None, config=None): LineData.__init__(self) Model.__init__(self, system, config) self.group = 'ACLine' self.flags.pflow = True self.flags.tds = True self.a1 = ExtAlgeb(model='Bus', src='a', indexer=self.bus1, tex_name='a_1', info='phase angle of the from bus') self.a2 = ExtAlgeb(model='Bus', src='a', indexer=self.bus2, tex_name='a_2', info='phase angle of the to bus') self.v1 = ExtAlgeb(model='Bus', src='v', indexer=self.bus1, tex_name='v_1', info='voltage magnitude of the from bus') self.v2 = ExtAlgeb(model='Bus', src='v', indexer=self.bus2, tex_name='v_2', info='voltage magnitude of the to bus') self.gh = ConstService(tex_name='g_h') self.bh = ConstService(tex_name='b_h') self.gk = ConstService(tex_name='g_k') self.bk = ConstService(tex_name='b_k') self.yh = ConstService(tex_name='y_h', vtype=np.complex) self.yk = ConstService(tex_name='y_k', vtype=np.complex) self.yhk = ConstService(tex_name='y_{hk}', vtype=np.complex) self.ghk = ConstService(tex_name='g_{hk}') self.bhk = ConstService(tex_name='b_{hk}') self.gh.v_str = 'g1 + 0.5 * g' self.bh.v_str = 'b1 + 0.5 * b' self.gk.v_str = 'g2 + 0.5 * g' self.bk.v_str = 'b2 + 0.5 * b' self.yh.v_str = 'u * (gh + 1j * bh)' self.yk.v_str = 'u * (gk + 1j * bk)' self.yhk.v_str = 'u/((r+1e-8) + 1j*(x+1e-8))' self.ghk.v_str = 're(yhk)' self.bhk.v_str = 'im(yhk)' self.a1.e_str = 'u * (v1 ** 2 * (gh + ghk) / tap ** 2 - \ v1 * v2 * (ghk * cos(a1 - a2 - phi) + \ bhk * sin(a1 - a2 - phi)) / tap)' self.v1.e_str = 'u * (-v1 ** 2 * (bh + bhk) / tap ** 2 - \ v1 * v2 * (ghk * sin(a1 - a2 - phi) - \ bhk * cos(a1 - a2 - phi)) / tap)' self.a2.e_str = 'u * (v2 ** 2 * (gh + ghk) - \ v1 * v2 * (ghk * cos(a1 - a2 - phi) - \ bhk * sin(a1 - a2 - phi)) / tap)' self.v2.e_str = 'u * (-v2 ** 2 * (bh + bhk) + \
def __init__(self, system, config): ExcBase.__init__(self, system, config) self.vref0 = ConstService( info='Initial reference voltage input', tex_name='V_{ref0}', v_str='v + vf0 / KA', ) self.LG = Lag( u=self.v, T=self.TR, K=1, info='Sensing delay', ) self.vi = Algeb( info='Total input voltages', tex_name='V_i', unit='p.u.', ) self.vi.v_str = 'vf0 / KA' self.vi.e_str = '(vref0 - LG_y) - vi' self.HLI = HardLimiter( u=self.vi, lower=self.VIMIN, upper=self.VIMAX, info='Hard limiter on input', ) self.LL = LeadLag( u='vi * HLI_zi + VIMIN * HLI_zl + VIMAX * HLI_zu', T1=self.TC, T2=self.TB, info='Lead-lag compensator', zero_out=True, ) self.LR = Lag(u=self.LL_y, T=self.TA, K=self.KA, info='Regulator') # the following uses `XadIfd` for `IIFD` in the PSS/E manual self.vfmax = Algeb( info='Upper bound of output limiter', tex_name='V_{fmax}', v_str='VRMAX - KC * XadIfd', e_str='VRMAX - KC * XadIfd - vfmax', ) self.vfmin = Algeb( info='Lower bound of output limiter', tex_name='V_{fmin}', v_str='VRMIN - KC * XadIfd', e_str='VRMIN - KC * XadIfd - vfmin', ) self.HLR = HardLimiter(u=self.LR_y, lower=self.vfmin, upper=self.vfmax, info='Hard limiter on regulator output') self.vout.e_str = 'LR_y*HLR_zi + vfmin*HLR_zl + vfmax*HLR_zu - vout'
def __init__(self, system, config): Model.__init__(self, system, config) self.group = 'TurbineGov' self.flags.update({'tds': True}) self.Sn = ExtParam( src='Sn', model='SynGen', indexer=self.syn, tex_name='S_m', info='Rated power from generator', unit='MVA', export=False, ) self.Vn = ExtParam( src='Vn', model='SynGen', indexer=self.syn, tex_name='V_m', info='Rated voltage from generator', unit='kV', export=False, ) self.tm0 = ExtService(src='tm', model='SynGen', indexer=self.syn, tex_name=r'\tau_{m0}', info='Initial mechanical input') self.omega = ExtState(src='omega', model='SynGen', indexer=self.syn, tex_name=r'\omega', info='Generator speed', unit='p.u.') self.gain = ConstService( v_str='u / R', tex_name='G', ) self.tm = ExtAlgeb( src='tm', model='SynGen', indexer=self.syn, tex_name=r'\tau_m', e_str='u * (pout - tm0)', info='Mechanical power to generator', ) self.pout = Algeb( info='Turbine final output power', tex_name='P_{out}', v_str='tm0', ) self.wref = Algeb( info='Speed reference variable', tex_name=r'\omega_{ref}', v_str='wref0', e_str='wref0 - wref', )
def __init__(self, system, config): MotorBaseModel.__init__(self, system, config) self.x2 = ConstService( v_str='xs + xr1*xr2*xm / (xr1*xr2 + xr1*xm + xr2*xm)', tex_name="x''", ) self.T20 = ConstService( v_str='(xr2 + xr1*xm / (xr1 + xm) ) / (wb * rr2)', tex_name="T''_0", ) self.e2d = State( info='real part of 2nd cage voltage', e_str='u * ' '(-wb*slip*(e1q - e2q) + ' '(wb*slip*e1q - (e1d + (x0 - x1) * Iq)/T10) + ' '(e1d - e2d - (x1 - x2) * Iq)/T20)', v_str='0.05 * u', tex_name="e''_d", diag_eps=True, ) self.e2q = State( info='imag part of 2nd cage voltage', e_str='u * ' '(wb*slip*(e1d - e2d) + ' '(-wb*slip*e1d - (e1q - (x0 - x1) * Id)/T10) + ' '(e1q - e2q + (x1 - x2) * Id) / T20)', v_str='0.9 * u', tex_name="e''_q", diag_eps=True, ) self.Id.e_str = 'u * (vd - e2d - rs * Id + x2 * Iq)' self.Id.v_str = '0.9 * u' self.Iq.e_str = 'u * (vq - e2q - rs * Iq - x2 * Id)' self.Iq.v_str = '0.1 * u' self.te.v_str = 'u * (e2d * Id + e2q * Iq)' self.te.e_str = f'{self.te.v_str} - te'
def __init__(self, system, config): ShuntModel.__init__(self, system, config) self.config.add(OrderedDict(( ('min_iter', 2), ('err_tol', 0.01), ))) self.config.add_extra( "_help", min_iter="iteration number starting from which to enable switching", err_tol="iteration error below which to enable switching", ) self.config.add_extra( "_alt", min_iter='int', err_tol='float', ) self.config.add_extra( "_tex", min_iter="sw_{iter}", err_tol=r"\epsilon_{tol}", ) self.beff = SwBlock(init=self.b, ns=self.ns, blocks=self.bs) self.geff = SwBlock(init=self.g, ns=self.ns, blocks=self.gs, ext_sel=self.beff) self.vlo = ConstService(v_str='vref - dv', tex_name='v_{lo}') self.vup = ConstService(v_str='vref + dv', tex_name='v_{up}') self.adj = ShuntAdjust(v=self.v, lower=self.vlo, upper=self.vup, bsw=self.beff, gsw=self.geff, dt=self.dt, u=self.u, min_iter=self.config.min_iter, err_tol=self.config.err_tol, info='shunt adjuster') self.a.e_str = 'u * v**2 * geff' self.v.e_str = '-u * v**2 * beff'
def __init__(self, system, config): ToggleData.__init__(self) Model.__init__(self, system, config) self.flags.update({'tds': True}) self.group = 'TimedEvent' self.t.callback = self._u_switch self._init = False # very first initialization that stores `u` self._u = ConstService('1')
def __init__(self, u, s10, s12, name=None, tex_name=None, info=None): self.s10 = s10 self.s12 = s12 points = [0, 1.0, 1.2] self.c = ConstService(v_str=f'log({s12.name} / {s10.name}) / log(1.2)') super().__init__(u=u, points=points, funs=None, name=name, tex_name=tex_name, info=info) self.vars.update({'c': self.c})
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): self.UEL0 = ConstService('0') self.UEL = Algeb(info='Interface var for under exc. limiter', tex_name='U_{EL}', v_str='UEL0', e_str='UEL0 - UEL') self.OEL0 = ConstService('0') self.OEL = Algeb(info='Interface var for over exc. limiter', tex_name='O_{EL}', v_str='OEL0', e_str='OEL0 - OEL') self.Vs = Algeb(info='Voltage compensation from PSS', tex_name='V_{s}', v_str='0', e_str='0 - Vs') self.vref = Algeb(info='Reference voltage input', tex_name='V_{ref}', unit='p.u.', e_str='vref0 - vref' # TODO: subclass to provide `vi.v_str` )
def __init__(self, E1, SE1, E2, SE2, name=None, tex_name=None, info=None): Block.__init__(self, name=name, tex_name=tex_name, info=info) self._E1 = dummify(E1) self._E2 = dummify(E2) self._SE1 = SE1 self._SE2 = SE2 self.zSE2 = FlagValue(self._SE2, value=0., info='Flag non-zeros in SE2', tex_name='z^{SE2}') # data correction for E1, E2, SE1 (TODO) self.E1 = ConstService( tex_name='E^{1c}', info='Corrected E1 data', ) self.E2 = ConstService( tex_name='E^{2c}', info='Corrected E2 data', ) self.SE1 = ConstService( tex_name='SE^{1c}', info='Corrected SE1 data', ) self.SE2 = ConstService( tex_name='SE^{2c}', info='Corrected SE2 data', ) self.a = ConstService( info='Intermediate Sat coeff', tex_name='a', ) self.A = ConstService( info='Saturation start', tex_name='A^q', ) self.B = ConstService( info='Saturation gain', tex_name='B^q', ) self.vars = { 'E1': self.E1, 'E2': self.E2, 'SE1': self.SE1, 'SE2': self.SE2, 'zSE2': self.zSE2, 'a': self.a, 'A': self.A, 'B': self.B, }
def __init__(self, system, config): ModelData.__init__(self) Model.__init__(self, system, config) self.flags.tds = True self.group = 'FreqMeasurement' # Parameters self.bus = IdxParam(info="bus idx", mandatory=True) self.Tf = NumParam(default=0.02, info="input digital filter time const", unit="sec", tex_name='T_f') self.Tw = NumParam(default=0.02, info="washout time const", unit="sec", tex_name='T_w') self.fn = NumParam(default=60.0, info="nominal frequency", unit='Hz', tex_name='f_n') # Variables self.iwn = ConstService(v_str='u / (2 * pi * fn)', tex_name=r'1/\omega_n') self.a0 = ExtService(src='a', model='Bus', indexer=self.bus, tex_name=r'\theta_0', info='initial phase angle', ) 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.L = Lag(u='(a-a0)', T=self.Tf, K=1, info='digital filter', ) self.WO = Washout(u=self.L_y, K=self.iwn, T=self.Tw, info='angle washout', ) self.f = Algeb(info='frequency output', unit='p.u. (Hz)', tex_name='f', v_str='1', e_str='1 + WO_y - f', )
def __init__(self, system, config): ExcBase.__init__(self, system, config) self.TA = ConstService(v_str='TATB * TB') self.vref0 = ConstService( info='Initial reference voltage input', tex_name='V_{ref0}', v_str='vf0/K + v', ) self.vref = Algeb(info='Reference voltage input', tex_name='V_{ref}', unit='p.u.', v_str='vref0', e_str='vref0 - vref') # input excitation voltages; PSS outputs summed at vi self.vi = Algeb( info='Total input voltages', tex_name='V_i', unit='p.u.', ) self.vi.e_str = '(vref - v) - vi' self.vi.v_str = 'vref0 - v' self.LL = LeadLag(u=self.vi, T1=self.TA, T2=self.TB, zero_out=True) self.LAW = LagAntiWindup( u=self.LL_y, T=self.TE, K=self.K, lower=self.EMIN, upper=self.EMAX, ) self.vout.e_str = 'LAW_y - vout'
def __init__(self, u, T, K, info=None, name=None): super().__init__(name=name, info=info) self.T = dummify(T) self.K = dummify(K) self.enforce_tex_name((self.K, self.T)) self.KT = ConstService( info='Constant K/T', tex_name=f'({self.K.tex_name}/{self.T.tex_name})', v_str=f'{self.K.name} / {self.T.name}') self.u = u self.x = State(info='State in washout filter', tex_name="x'", t_const=self.T) self.y = Algeb(info='Output of washout filter', tex_name=r'y', diag_eps=1e-6) self.vars = {'KT': self.KT, 'x': self.x, 'y': self.y}
def __init__(self, system, config): ACEData.__init__(self) Model.__init__(self, system, config) self.flags.tds = True self.group = 'Calculation' 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.area = ExtParam(model='Bus', src='area', indexer=self.bus, export=False) self.busf.model = self.config.freq_model self.busfreq = DeviceFinder(self.busf, link=self.bus, idx_name='bus', default_model='BusFreq') self.imva = ConstService(v_str='1/sys_mva', info='reciprocal of system mva', tex_name='1/S_{b, sys}') self.f = ExtAlgeb(model='FreqMeasurement', src='f', indexer=self.busfreq, export=False, info='Bus frequency', unit='p.u. (Hz)') self.ace = Algeb( info='area control error', unit='p.u. (MW)', tex_name='ace', e_str='10 * (bias * imva) * sys_f * (f - 1) - ace', )
def __init__(self, system, config): TGBase.__init__(self, system, config) self.gain = ConstService( v_str='u/R', tex_name='G', ) self.pref = Algeb( info='Reference power input', tex_name='P_{ref}', v_str='tm0 * R', e_str='tm0 * R - pref', ) self.wd = Algeb( info='Generator under speed', unit='p.u.', tex_name=r'\omega_{dev}', v_str='0', e_str='(wref - omega) - wd', ) self.pd = Algeb(info='Pref plus under speed times gain', unit='p.u.', tex_name="P_d", v_str='u * tm0', e_str='u*(wd + pref + paux) * gain - pd') self.LAG = LagAntiWindup( u=self.pd, K=1, T=self.T1, lower=self.VMIN, upper=self.VMAX, ) self.LL = LeadLag( u=self.LAG_y, T1=self.T2, T2=self.T3, ) self.pout.e_str = '(LL_y + Dt * wd) - pout'
def __init__(self, u, T, K, info=None, name=None): super().__init__(name=name, info=info) if isinstance(T, (int, float)): self.T = DummyValues(T) else: self.T = T if isinstance(K, (int, float)): self.K = DummyValues(K) else: self.K = K self.KT = ConstService( info='Constant K/T', tex_name=f'({self.K.tex_name}/{self.T.tex_name})', v_str=f'{self.K.name} / {self.T.name}') self.u = u self.x = State(info='State in washout filter', tex_name="x'") self.y = Algeb(info='Output of washout filter', tex_name=r'y') self.vars = {'KT': self.KT, 'x': self.x, 'y': self.y}
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', )
def __init__(self, system, config): ExcBase.__init__(self, system, config) # Set VRMAX to 999 when VRMAX = 0 self._zVRM = FlagValue( self.VRMAX, value=0, tex_name='z_{VRMAX}', ) self.VRMAXc = ConstService( v_str='VRMAX + 999*(1-_zVRM)', info='Set VRMAX=999 when zero', ) # Saturation self.SAT = ExcQuadSat( self.E1, self.SE1, self.E2, self.SE2, info='Field voltage saturation', ) self.Se0 = ConstService( info='Initial saturation output', tex_name='S_{e0}', v_str='Indicator(vf0>SAT_A) * SAT_B * (SAT_A - vf0) ** 2 / vf0', ) self.vr0 = ConstService(info='Initial vr', tex_name='V_{r0}', v_str='(KE + Se0) * vf0') self.vb0 = ConstService(info='Initial vb', tex_name='V_{b0}', v_str='vr0 / KA') self.vref0 = ConstService( info='Initial reference voltage input', tex_name='V_{ref0}', v_str='v + vb0', ) self.vfe0 = ConstService( v_str='vf0 * (KE + Se0)', tex_name='V_{FE0}', ) self.vref = Algeb(info='Reference voltage input', tex_name='V_{ref}', unit='p.u.', v_str='vref0', e_str='vref0 - vref') self.LG = Lag( u=self.v, T=self.TR, K=1, info='Sensing delay', ) self.vi = Algeb( info='Total input voltages', tex_name='V_i', unit='p.u.', e_str='-LG_y + vref - vi', v_str='-v + vref', ) self.LA = LagAntiWindup( u='vi + WF_y', T=self.TA, K=self.KA, upper=self.VRMAXc, lower=self.VRMIN, info='Anti-windup lag', ) self.VFE = Algeb(info='Combined saturation feedback', tex_name='V_{FE}', unit='p.u.', v_str='vfe0', e_str='INT_y * (KE + Se) - VFE') self.INT = Integrator( u='LA_y - VFE', T=self.TE, K=1, y0=self.vf0, info='Integrator', ) self.SL = LessThan(u=self.vout, bound=self.SAT_A, equal=False, enable=True, cache=False) self.Se = Algeb( tex_name=r"S_e(|V_{out}|)", info='saturation output', v_str='Se0', e_str='SL_z0 * (INT_y - SAT_A) ** 2 * SAT_B / INT_y - Se', ) self.WF = Washout(u=self.vout, T=self.TF, K=self.KF, info='Stablizing circuit feedback') self.vout.e_str = 'INT_y - vout'
def __init__(self, system, config): ModelData.__init__(self) self.bus = IdxParam( model='Bus', info="linked bus idx", mandatory=True, ) self.tf = TimerParam( info='Bus fault start time', unit='second', mandatory=True, callback=self.apply_fault, ) self.tc = TimerParam( info='Bus fault end time', unit='second', callback=self.clear_fault, ) self.xf = NumParam( info='Fault to ground impedance (positive)', unit='p.u.(sys)', default=1e-4, tex_name='x_f', ) self.rf = NumParam( info='Fault to ground resistance (positive)', unit='p.u.(sys)', default=0, tex_name='x_f', ) Model.__init__(self, system, config) self.flags.update({'tds': True}) self.group = 'TimedEvent' self.gf = ConstService( tex_name='g_{f}', v_str='re(1/(rf + 1j * xf))', ) self.bf = ConstService( tex_name='b_{f}', v_str='im(1/(rf + 1j * xf))', ) # uf: an internal flag of whether the fault is in action (1) or not (0) self.uf = ConstService(tex_name='u_f', v_str='0') self.a = ExtAlgeb( model='Bus', src='a', indexer=self.bus, tex_name=r'\theta', info='Bus voltage angle', unit='p.u.(kV)', e_str='u * uf * (v ** 2 * gf)', ) self.v = ExtAlgeb( model='Bus', src='v', indexer=self.bus, tex_name=r'V', unit='p.u.(kV)', info='Bus voltage magnitude', e_str='-u * uf * (v ** 2 * bf)', ) self._vstore = np.array([])
def __init__(self, system, config): ExcBase.__init__(self, system, config) self.SAT = ExcQuadSat(self.E1, self.SE1, self.E2, self.SE2, info='Field voltage saturation', ) # calculate `Se0` ahead of time in order to calculate `vr0` # The term `1-ug` is to prevent division by zero when generator is off self.Se0 = ConstService(info='Initial saturation output', tex_name='S_{e0}', v_str='Indicator(vf0>SAT_A) * SAT_B * (SAT_A - vf0) ** 2 / (vf0 + 1 - ug)', ) self.vr0 = ConstService(info='Initial vr', tex_name='V_{r0}', v_str='(KE + Se0) * vf0') self.vb0 = ConstService(info='Initial vb', tex_name='V_{b0}', v_str='vr0 / KA') self.vref = Algeb(info='Reference voltage input', tex_name='V_{ref}', unit='p.u.', v_str='v + vb0', e_str='vref0 - vref' ) self.vref0 = PostInitService(info='Constant v ref', tex_name='V_{ref0}', v_str='vref', ) self.SL = LessThan(u=self.vout, bound=self.SAT_A, equal=False, enable=True, cache=False, ) self.Se = Algeb(tex_name=r"S_e(|V_{out}|)", info='saturation output', v_str='Se0', e_str='SL_z0 * (vp - SAT_A) ** 2 * SAT_B - Se * vp', diag_eps=True, ) self.vp = State(info='Voltage after saturation feedback, before speed term', tex_name='V_p', unit='p.u.', v_str='vf0', e_str='ue * (LA_y - KE*vp - Se*vp)', t_const=self.TE, ) self.LS = Lag(u=self.v, T=self.TR, K=1.0, info='Sensing lag TF') # input excitation voltages; PSS outputs summed at vi self.vi = Algeb(info='Total input voltages', tex_name='V_i', unit='p.u.', ) self.vi.v_str = 'vb0' self.vi.e_str = '(vref - LS_y - W_y) - vi' self.LL = LeadLag(u=self.vi, T1=self.TC, T2=self.TB, info='Lead-lag for internal delays', zero_out=True, ) self.LA = LagAntiWindup(u=self.LL_y, T=self.TA, K=self.KA, upper=self.VRMAX, lower=self.VRMIN, info='Anti-windup lag', ) self.W = Washout(u=self.vp, T=self.TF1, K=self.KF1, info='Signal conditioner' ) self.vout.e_str = 'ue * omega * vp - vout'
def __init__(self): # parameter checking for `xl` self._xlc = InitChecker(u=self.xl, info='(xl <= xd2)', upper=self.xd2) self.gd1 = ConstService(v_str='(xd2 - xl) / (xd1 - xl)', tex_name=r"\gamma_{d1}") self.gq1 = ConstService(v_str='(xq2 - xl) / (xq1 - xl)', tex_name=r"\gamma_{q1}") self.gd2 = ConstService(v_str='(xd1 - xd2) / (xd1 - xl) ** 2', tex_name=r"\gamma_{d2}") self.gq2 = ConstService(v_str='(xq1 - xq2) / (xq1 - xl) ** 2', tex_name=r"\gamma_{q2}") self.gqd = ConstService(v_str='(xq - xl) / (xd - xl)', tex_name=r"\gamma_{qd}") # correct S12 to 1.0 if is zero self._fS12 = FlagValue(self.S12, value=0) self._S12 = ConstService(v_str='S12 + (1-_fS12)', info='Corrected S12', tex_name='S_{1.2}') # Saturation services # Note: # To disable saturation, set S10 = 0, S12 = 1 so that SAT_B = 0. self.SAT = ExcQuadSat(1.0, self.S10, 1.2, self.S12, tex_name='S_{AT}') # Initialization reference: OpenIPSL at # https://github.com/OpenIPSL/OpenIPSL/blob/master/OpenIPSL/Electrical/Machines/PSSE/GENROU.mo # internal voltage and rotor angle calculation self._V = ConstService(v_str='v * exp(1j * a)', tex_name='V_c', info='complex bus voltage', vtype=np.complex) self._S = ConstService(v_str='p0 - 1j * q0', tex_name='S', info='complex terminal power', vtype=np.complex) self._Zs = ConstService(v_str='ra + 1j * xd2', tex_name='Z_s', info='equivalent impedance', vtype=np.complex) self._It = ConstService(v_str='_S / conj(_V)', tex_name='I_t', info='complex terminal current', vtype=np.complex) self._Is = ConstService(tex_name='I_s', v_str='_It + _V / _Zs', info='equivalent current source', vtype=np.complex) self.psi20 = ConstService( tex_name=r"\psi''_0", v_str='_Is * _Zs', info='sub-transient flux linkage in stator reference', vtype=np.complex) self.psi20_arg = ConstService(tex_name=r"\theta_{\psi''0}", v_str='arg(psi20)') self.psi20_abs = ConstService(tex_name=r"|\psi''_0|", v_str='abs(psi20)') self._It_arg = ConstService(tex_name=r"\theta_{It0}", v_str='arg(_It)') self._psi20_It_arg = ConstService(tex_name=r"\theta_{\psi a It}", v_str='psi20_arg - _It_arg') self.Se0 = ConstService( tex_name=r"S_{e0}", v_str= 'Indicator(psi20_abs>=SAT_A) * (psi20_abs - SAT_A) ** 2 * SAT_B / psi20_abs' ) self._a = ConstService(tex_name=r"a'", v_str='psi20_abs * (1 + Se0*gqd)') self._b = ConstService(tex_name=r"b'", v_str='abs(_It) * (xq2 - xq)') # xd2 == xq2 self.delta0 = ConstService( tex_name=r'\delta_0', v_str= 'atan(_b * cos(_psi20_It_arg) / (_b * sin(_psi20_It_arg) - _a)) + ' 'psi20_arg') self._Tdq = ConstService(tex_name=r"T_{dq}", v_str='cos(delta0) - 1j * sin(delta0)', vtype=np.complex) self.psi20_dq = ConstService(tex_name=r"\psi''_{0,dq}", v_str='psi20 * _Tdq', vtype=np.complex) self.It_dq = ConstService(tex_name=r"I_{t,dq}", v_str='conj(_It * _Tdq)', vtype=np.complex) self.psi2d0 = ConstService(tex_name=r"\psi_{ad0}", v_str='re(psi20_dq)') self.psi2q0 = ConstService(tex_name=r"\psi_{aq0}", v_str='-im(psi20_dq)') self.Id0 = ConstService(v_str='im(It_dq)', tex_name=r'I_{d0}') self.Iq0 = ConstService(v_str='re(It_dq)', tex_name=r'I_{q0}') self.vd0 = ConstService(v_str='psi2q0 + xq2*Iq0 - ra * Id0', tex_name=r'V_{d0}') self.vq0 = ConstService(v_str='psi2d0 - xd2*Id0 - ra*Iq0', tex_name=r'V_{q0}') self.tm0 = ConstService( tex_name=r'\tau_{m0}', v_str='u * ((vq0 + ra * Iq0) * Iq0 + (vd0 + ra * Id0) * Id0)') # `vf0` is also equal to `vq + xd*Id +ra*Iq + Se*psi2d` from phasor diagram self.vf0 = ConstService(tex_name=r'v_{f0}', v_str='(Se0 + 1)*psi2d0 + (xd - xd2) * Id0') self.psid0 = ConstService(tex_name=r"\psi_{d0}", v_str='u * (ra * Iq0) + vq0') self.psiq0 = ConstService(tex_name=r"\psi_{q0}", v_str='-u * (ra * Id0) - vd0') # initialization of internal voltage and delta self.e1q0 = ConstService(tex_name="e'_{q0}", v_str='Id0*(-xd + xd1) - Se0*psi2d0 + vf0') self.e1d0 = ConstService(tex_name="e'_{d0}", v_str='Iq0*(xq - xq1) - Se0*gqd*psi2q0') self.e2d0 = ConstService(tex_name="e''_{d0}", v_str='Id0*(xl - xd) - Se0*psi2d0 + vf0') self.e2q0 = ConstService(tex_name="e''_{q0}", v_str='-Iq0*(xl - xq) - Se0*gqd*psi2q0') # begin variables and equations self.psi2q = Algeb( tex_name=r"\psi_{aq}", info='q-axis air gap flux', v_str='psi2q0', e_str='gq1*e1d + (1-gq1)*e2q - psi2q', ) self.psi2d = Algeb(tex_name=r"\psi_{ad}", info='d-axis air gap flux', v_str='u * psi2d0', e_str='gd1*e1q + gd2*(xd1-xl)*e2d - psi2d') self.psi2 = Algeb( tex_name=r"\psi_a", info='air gap flux magnitude', v_str='u * abs(psi20_dq)', e_str='psi2d **2 + psi2q ** 2 - psi2 ** 2', diag_eps=True, ) # `LT` is a reserved keyword for SymPy self.SL = LessThan(u=self.psi2, bound=self.SAT_A, equal=False, enable=True, cache=False) self.Se = Algeb( tex_name=r"S_e(|\psi_{a}|)", info='saturation output', v_str='u * Se0', e_str='SL_z0 * (psi2 - SAT_A) ** 2 * SAT_B - psi2 * Se', diag_eps=True, ) # separated `XadIfd` from `e1q` using \dot(e1q) = (vf - XadIfd) / Td10 self.XadIfd.e_str = 'u * (e1q + (xd-xd1) * (gd1*Id - gd2*e2d + gd2*e1q) + Se*psi2d) - XadIfd' # `XadI1q` can also be given in `(xq-xq1)*gq2*(e1d-e2q+(xq1-xl)*Iq) + e1d - Iq*(xq-xq1) + Se*psi2q*gqd` self.XaqI1q =\ Algeb(tex_name='X_{aq}I_{1q}', info='q-axis reaction', unit='p.u (kV)', v_str='0', e_str='e1d + (xq-xq1) * (gq2*e1d - gq2*e2q - gq1*Iq) + Se*psi2q*gqd - XaqI1q' ) self.e1q = State( info='q-axis transient voltage', tex_name=r"e'_q", v_str='u * e1q0', e_str='(-XadIfd + vf)', t_const=self.Td10, ) self.e1d = State( info='d-axis transient voltage', tex_name=r"e'_d", v_str='e1d0', e_str='-XaqI1q', t_const=self.Tq10, ) self.e2d = State( info='d-axis sub-transient voltage', tex_name=r"e''_d", v_str='u * e2d0', e_str='(-e2d + e1q - (xd1 - xl) * Id)', t_const=self.Td20, ) self.e2q = State( info='q-axis sub-transient voltage', tex_name=r"e''_q", v_str='e2q0', e_str='(-e2q + e1d + (xq1 - xl) * Iq)', t_const=self.Tq20, ) self.Iq.e_str += '+ xq2*Iq + psi2q' self.Id.e_str += '+ xd2*Id - psi2d'
def __init__(self): # internal voltage and rotor angle calculation self.xq = ExtService( model='GENCLS', src='xd1', indexer=self.idx, ) self._V = ConstService( v_str='v * exp(1j * a)', tex_name='V_c', vtype=np.complex, ) self._S = ConstService( v_str='p0 - 1j * q0', tex_name='S', vtype=np.complex, ) self._I = ConstService( v_str='_S / conj(_V)', tex_name='I_c', vtype=np.complex, ) self._E = ConstService(tex_name='E', vtype=np.complex) self._deltac = ConstService(tex_name=r'\delta_c', vtype=np.complex) self.delta0 = ConstService(tex_name=r'\delta_0') self.vdq = ConstService( v_str='u * (_V * exp(1j * 0.5 * pi - _deltac))', tex_name='V_{dq}', vtype=np.complex) self.Idq = ConstService( v_str='u * (_I * exp(1j * 0.5 * pi - _deltac))', tex_name='I_{dq}', vtype=np.complex) self.Id0 = ConstService(v_str='re(Idq)', tex_name=r'I_{d0}') self.Iq0 = ConstService(v_str='im(Idq)', tex_name=r'I_{q0}') self.vd0 = ConstService(v_str='re(vdq)', tex_name=r'V_{d0}') self.vq0 = ConstService(v_str='im(vdq)', tex_name=r'V_{q0}') self.tm0 = ConstService( tex_name=r'\tau_{m0}', v_str='u * ((vq0 + ra * Iq0) * Iq0 + (vd0 + ra * Id0) * Id0)') self.psid0 = ConstService(tex_name=r"\psi_{d0}", v_str='u * (ra * Iq0) + vq0') self.psiq0 = ConstService(tex_name=r"\psi_{q0}", v_str='-u * (ra * Id0) - vd0') self.vf0 = ConstService(tex_name=r'v_{f0}') # initialization of internal voltage and delta self._E.v_str = '_V + _I * (ra + 1j * xq)' self._deltac.v_str = 'log(_E / abs(_E))' self.delta0.v_str = 'u * im(_deltac)' self.Id.e_str += '+ xq * Id - vf' self.Iq.e_str += '+ xq * Iq' self.vf0.v_str = '(vq0 + ra * Iq0) + xq * Id0'
def __init__(self, system, config): super().__init__(system, config) self.group = 'SynGen' self.flags.update({ 'tds': True, 'nr_iter': False, }) self.config.add( vf_lower=1.0, vf_upper=5.0, ) self.config.add_extra( "_help", vf_lower="lower limit for vf warning", vf_upper="upper limit for vf warning", ) # state variables self.delta = State(info='rotor angle', unit='rad', v_str='delta0', tex_name=r'\delta', e_str='u * (2 * pi * fn) * (omega - 1)') self.omega = State(info='rotor speed', unit='pu (Hz)', v_str='u', tex_name=r'\omega', e_str='(u / M) * (tm - te - D * (omega - 1))') # network algebraic variables self.a = ExtAlgeb(model='Bus', src='a', indexer=self.bus, tex_name=r'\theta', info='Bus voltage phase angle', e_str='-u * (vd * Id + vq * Iq)') self.v = ExtAlgeb(model='Bus', src='v', indexer=self.bus, tex_name=r'V', info='Bus voltage magnitude', e_str='-u * (vq * Id - vd * Iq)') # algebraic variables # Need to be provided by specific generator models self.Id = Algeb(info='d-axis current', v_str='u * Id0', tex_name=r'I_d', e_str='') # to be completed by subclasses self.Iq = Algeb(info='q-axis current', v_str='u * Iq0', tex_name=r'I_q', e_str='') # to be completed self.vd = Algeb( info='d-axis voltage', v_str='u * vd0', e_str='u * v * sin(delta - a) - vd', tex_name=r'V_d', ) self.vq = Algeb( info='q-axis voltage', v_str='u * vq0', e_str='u * v * cos(delta - a) - vq', tex_name=r'V_q', ) self.tm = Algeb(info='mechanical torque', tex_name=r'\tau_m', v_str='tm0', e_str='tm0 - tm') self.te = Algeb( info='electric torque', tex_name=r'\tau_e', v_str='u * tm0', e_str='u * (psid * Iq - psiq * Id) - te', ) self.vf = Algeb(info='excitation voltage', unit='pu', v_str='u * vf0', e_str='u * vf0 - vf', tex_name=r'v_f') self._vfc = InitChecker( u=self.vf, info='(vf range)', lower=self.config.vf_lower, upper=self.config.vf_upper, ) self.XadIfd = Algeb(tex_name='X_{ad}I_{fd}', info='d-axis armature excitation current', unit='p.u (kV)', v_str='u * vf0', e_str='u * vf0 - XadIfd' ) # e_str to be provided. Not available in GENCLS self.subidx = ExtParam( model='StaticGen', src='subidx', indexer=self.gen, export=False, info='Generator idx in plant; only used by PSS/E data') # declaring `Vn_bus` as ExtParam will fail for PSS/E parser self.Vn_bus = ExtService( model='Bus', src='Vn', indexer=self.bus, ) self._vnc = InitChecker( u=self.Vn, info='Vn and Bus Vn', equal=self.Vn_bus, ) # ----------service consts for initialization---------- self.p0s = ExtService( model='StaticGen', src='p', indexer=self.gen, tex_name='P_{0s}', info='initial P of the static gen', ) self.q0s = ExtService( model='StaticGen', src='q', indexer=self.gen, tex_name='Q_{0s}', info='initial Q of the static gen', ) self.p0 = ConstService( v_str='p0s * gammap', tex_name='P_0', info='initial P of this gen', ) self.q0 = ConstService( v_str='q0s * gammaq', tex_name='Q_0', info='initial Q of this gen', )
def __init__(self, system, config): Model.__init__(self, system, config) self.group = 'Calculation' self.flags.update({'tds': True}) self.SynGen = BackRef(info='Back reference to SynGen idx') self.SynGenIdx = RefFlatten(ref=self.SynGen) self.M = ExtParam( model='SynGen', src='M', indexer=self.SynGenIdx, export=False, info='Linearly stored SynGen.M', ) self.wgen = ExtState( model='SynGen', src='omega', indexer=self.SynGenIdx, tex_name=r'\omega_{gen}', info='Linearly stored SynGen.omega', ) self.agen = ExtState( model='SynGen', src='delta', indexer=self.SynGenIdx, tex_name=r'\delta_{gen}', info='Linearly stored SynGen.delta', ) self.d0 = ExtService( model='SynGen', src='delta', indexer=self.SynGenIdx, tex_name=r'\delta_{gen,0}', info='Linearly stored initial delta', ) self.a0 = ExtService( model='SynGen', src='omega', indexer=self.SynGenIdx, tex_name=r'\omega_{gen,0}', info='Linearly stored initial omega', ) self.Mt = NumReduce( u=self.M, tex_name='M_t', fun=np.sum, ref=self.SynGen, info='Summation of M by COI index', ) self.Mr = NumRepeat( u=self.Mt, tex_name='M_{tr}', ref=self.SynGen, info='Repeated summation of M', ) self.Mw = ConstService(tex_name='M_w', info='Inertia weights', v_str='M/Mr') self.d0w = ConstService(tex_name=r'\delta_{gen,0,w}', v_str='d0 * Mw', info='Linearly stored weighted delta') self.a0w = ConstService(tex_name=r'\omega_{gen,0,w}', v_str='a0 * Mw', info='Linearly stored weighted omega') self.d0a = NumReduce( u=self.d0w, tex_name=r'\delta_{gen,0,avg}', fun=np.sum, ref=self.SynGen, info='Average initial delta', cache=False, ) self.a0a = NumReduce( u=self.a0w, tex_name=r'\omega_{gen,0,avg}', fun=np.sum, ref=self.SynGen, info='Average initial omega', cache=False, ) self.pidx = IdxRepeat(u=self.idx, ref=self.SynGen, info='Repeated COI.idx') # Note: # Even if d(omega) /d (omega) = 1, it is still stored as a lambda function. # When no SynGen is referencing any COI, j_update will not be called, # and Jacobian will become singular. `diag_eps = True` needs to be used. # Note: # Do not assign `v_str=1` for `omega`. Otherwise, COIs with no connected generators will # fail to initialize. self.omega = Algeb( tex_name=r'\omega_{coi}', info='COI speed', v_str='a0a', v_setter=True, e_str='-omega', diag_eps=True, ) self.delta = Algeb( tex_name=r'\delta_{coi}', info='COI rotor angle', v_str='d0a', v_setter=True, e_str='-delta', diag_eps=True, ) # Note: # `omega_sub` or `delta_sub` must not provide `v_str`. # Otherwise, values will be incorrectly summed for `omega` and `delta`. self.omega_sub = ExtAlgeb( model='COI', src='omega', e_str='Mw * wgen', indexer=self.pidx, info='COI frequency contribution of each generator') self.delta_sub = ExtAlgeb( model='COI', src='delta', e_str='Mw * agen', indexer=self.pidx, info='COI angle contribution of each generator')
def __init__(self, system, config): ExcBase.__init__(self, system, config) ExcVsum.__init__(self) self.UEL0.v_str = '-999' self.OEL0.v_str = '999' self.flags.nr_iter = True # NOTE: e_str `KC*XadIfd / INT_y - IN` causes numerical inaccuracies self.IN = Algeb(tex_name='I_N', info='Input to FEX', v_str='1', v_iter='KC * XadIfd - INT_y * IN', e_str='ue * (KC * XadIfd - INT_y * IN)', diag_eps=True, ) self.FEX = Piecewise(u=self.IN, points=(0, 0.433, 0.75, 1), funs=('1', '1 - 0.577*IN', 'sqrt(0.75 - IN ** 2)', '1.732*(1 - IN)', 0), info='Piecewise function FEX', ) self.FEX.y.v_str = '1' self.FEX.y.v_iter = self.FEX.y.e_str # control block begin self.LG = Lag(self.v, T=self.TR, K=1, info='Voltage transducer', ) # input excitation voltages; self.vi = Algeb(info='Total input voltages', tex_name='V_i', unit='p.u.', e_str='ue * (-LG_y + vref + UEL + OEL + Vs - vi)', v_str='-v + vref', diag_eps=True, ) self.LL = LeadLag(u=self.vi, T1=self.TC, T2=self.TB, info='V_A, Lead-lag compensator', zero_out=True, ) # LL_y == VA self.VAMAXu = ConstService('VAMAX * ue + (1-ue) * 999') self.VAMINu = ConstService('VAMIN * ue + (1-ue) * -999') self.LA = LagAntiWindup(u=self.LL_y, T=self.TA, K=self.KA, upper=self.VAMAXu, lower=self.VAMINu, info='V_A, Anti-windup lag', ) # LA_y == VA self.HVG = HVGate(u1=self.UEL, u2=self.LA_y, info='HVGate for under excitation', ) self.LVG = LVGate(u1=self.HVG_y, u2=self.OEL, info='HVGate for under excitation', ) self.INTin = 'ue * (LVG_y - VFE)' ExcACSat.__init__(self) self.vref.v_str = 'v + VFE / KA' self.vref0 = PostInitService(info='Initial reference voltage input', tex_name='V_{ref0}', v_str='vref', ) self.WF = Washout(u=self.VFE, T=self.TF, K=self.KF, info='Stablizing circuit feedback', ) self.vout.e_str = 'ue * FEX_y * INT_y - vout'
def __init__(self): self.gd1 = ConstService(v_str='(xd2 - xl) / (xd1 - xl)', tex_name=r"\gamma_{d1}") self.gq1 = ConstService(v_str='(xq2 - xl) / (xq1 - xl)', tex_name=r"\gamma_{q1}") self.gd2 = ConstService(v_str='(xd1 - xd2) / (xd1 - xl) ** 2', tex_name=r"\gamma_{d2}") self.gq2 = ConstService(v_str='(xq1 - xq2) / (xq1 - xl) ** 2', tex_name=r"\gamma_{q2}") self.gqd = ConstService(v_str='(xq - xl) / (xd - xl)', tex_name=r"\gamma_{qd}") # Saturation services # when S10 = 0, S12 = 1, Saturation is disabled. Thus, Sat = 0, A = 1, B = 0 self.Sat = ConstService(v_str='sqrt((S10 * 1) / (S12 * 1.2))', tex_name=r"S_{at}") self.SA = ConstService(v_str='1.2 + 0.2 / (Sat - 1)', tex_name='S_A') self.SB = ConstService( v_str= '((Sat < 0) + (Sat > 0)) * 1.2 * S12 * ((Sat - 1) / 0.2) ** 2', tex_name='S_B') # internal voltage and rotor angle calculation # Initialization reference: OpenIPSL at # https://github.com/OpenIPSL/OpenIPSL/blob/master/OpenIPSL/Electrical/Machines/PSSE/GENROU.mo self._V = ConstService(v_str='v * exp(1j * a)', tex_name='V_c', info='complex terminal voltage') self._S = ConstService(v_str='p0 - 1j * q0', tex_name='S', info='complex terminal power') self._Zs = ConstService(v_str='ra + 1j * xd2', tex_name='Z_s', info='equivalent impedance') self._It = ConstService(v_str='_S / conj(_V)', tex_name='I_t', info='complex terminal current') self._Is = ConstService(tex_name='I_s', v_str='_It + _V / _Zs', info='equivalent current source') self.psia0 = ConstService( tex_name=r"\psi_{a0}", v_str='_Is * _Zs', info='subtransient flux linkage in stator reference') self.psia0_arg = ConstService(tex_name=r"\theta_{\psi a0}", v_str='arg(psia0)') self.psia0_abs = ConstService(tex_name=r"|\psi_{a0}|", v_str='abs(psia0)') self._It_arg = ConstService(tex_name=r"\theta_{It0}", v_str='arg(_It)') self._psia0_It_arg = ConstService(tex_name=r"\theta_{\psi a It}", v_str='psia0_arg - _It_arg') self.Se0 = ConstService( tex_name=r"S_{e0}", v_str='(psia0_abs >= SA) * (psia0_abs - SA) ** 2 * SB / psia0_abs') self._a = ConstService(tex_name=r"a", v_str='psia0_abs + psia0_abs * Se0 * gqd') self._b = ConstService(tex_name=r"b", v_str='abs(_It) * (xq2 - xq)') # xd2 == xq2 self.delta0 = ConstService( tex_name=r'\delta_0', v_str= 'atan(_b * cos(_psia0_It_arg) / (_b * sin(_psia0_It_arg) - _a)) + ' 'psia0_arg') self._Tdq = ConstService(tex_name=r"T_{dq}", v_str='cos(delta0) - 1j * sin(delta0)') self.psia0_dq = ConstService(tex_name=r"\psi_{a0,dq}", v_str='psia0 * _Tdq') self.It_dq = ConstService(tex_name=r"I_{t,dq}", v_str='conj(_It * _Tdq)') self.psiad0 = ConstService(tex_name=r"\psi_{ad0}", v_str='re(psia0_dq)') self.psiaq0 = ConstService(tex_name=r"\psi_{aq0}", v_str='im(psia0_dq)') self.Id0 = ConstService(v_str='im(It_dq)', tex_name=r'I_{d0}') self.Iq0 = ConstService(v_str='re(It_dq)', tex_name=r'I_{q0}') self.vd0 = ConstService(v_str='-(psiaq0 - xq2*Iq0) - ra * Id0', tex_name=r'V_{d0}') self.vq0 = ConstService(v_str='psiad0 - xd2*Id0 - ra*Iq0', tex_name=r'V_{q0}') self.tm0 = ConstService( tex_name=r'\tau_{m0}', v_str='u * ((vq0 + ra * Iq0) * Iq0 + (vd0 + ra * Id0) * Id0)') self.vf0 = ConstService(tex_name=r'v_{f0}', v_str='(Se0 + 1)*psiad0 + (xd - xd2) * Id0') self.psid0 = ConstService(tex_name=r"\psi_{d0}", v_str='u * (ra * Iq0) + vq0') self.psiq0 = ConstService(tex_name=r"\psi_{q0}", v_str='-u * (ra * Id0) - vd0') # initialization of internal voltage and delta self.e1q0 = ConstService(tex_name="e'_{q0}", v_str='Id0*(-xd + xd1) - Se0*psiad0 + vf0') self.e1d0 = ConstService(tex_name="e'_{d0}", v_str='Iq0*(xq - xq1) + Se0*gqd*psiaq0') self.e2d0 = ConstService(tex_name="e''_{d0}", v_str='Id0*(xl - xd) - Se0*psiad0 + vf0') self.e2q0 = ConstService(tex_name="e''_{q0}", v_str='Iq0*(xl - xq) - Se0*gqd*psiaq0') # begin variables and equations self.psiaq = Algeb(tex_name=r"\psi_{aq}", info='q-axis air gap flux', v_str='psiaq0', e_str='psiq + xq2 * Iq - psiaq') self.psiad = Algeb(tex_name=r"\psi_{ad}", info='d-axis air gap flux', v_str='psiad0', e_str='-psiad + gd1 * e1q + gd2 * (xd1 - xl) * e2d') self.psia = Algeb(tex_name=r"\psi_{a}", info='air gap flux magnitude', v_str='abs(psia0_dq)', e_str='sqrt(psiad **2 + psiaq ** 2) - psia') self.Slt = LessThan(u=self.psia, bound=self.SA, equal=False, enable=True) self.Se = Algeb(tex_name=r"S_e(|\psi_{a}|)", info='saturation output', v_str='Se0', e_str='Slt_z0 * (psia - SA) ** 2 * SB / psia - Se') self.e1q = State( info='q-axis transient voltage', tex_name=r"e'_q", v_str='e1q0', e_str= '(-e1q - (xd - xd1) * (Id - gd2 * e2d - (1 - gd1) * Id + gd2 * e1q) - ' 'Se * psiad + vf) / Td10') self.e1d = State( info='d-axis transient voltage', tex_name=r"e'_d", v_str='e1d0', e_str= '(-e1d + (xq - xq1) * (Iq - gq2 * e2q - (1 - gq1) * Iq - gq2 * e1d) + ' 'Se * gqd * psiaq) / Tq10') self.e2d = State(info='d-axis sub-transient voltage', tex_name=r"e''_d", v_str='e2d0', e_str='(-e2d + e1q - (xd1 - xl) * Id) / Td20') self.e2q = State(info='q-axis sub-transient voltage', tex_name=r"e''_q", v_str='e2q0', e_str='(-e2q - e1d - (xq1 - xl) * Iq) / Tq20') self.Id.e_str += '+ xd2 * Id - gd1 * e1q - (1 - gd1) * e2d' self.Iq.e_str += '+ xq2 * Iq + gq1 * e1d - (1 - gq1) * e2q'