def __init__(self, system, config): Model.__init__(self, system, config) self.flags.tds = True self.group = 'RenGovernor' self.reg = ExtParam(model='RenExciter', src='reg', indexer=self.ree, export=False, ) self.Sn = ExtParam(model='RenGen', src='Sn', indexer=self.reg, tex_name='S_n', export=False, ) self.wge = ExtAlgeb(model='RenExciter', src='wg', indexer=self.ree, export=False, e_str='-1.0 + s1_y', ename='wg', tex_ename=r'\omega_g', ) self.Pe = ExtAlgeb(model='RenGen', src='Pe', indexer=self.reg, export=False, info='Retrieved Pe of RenGen') self.Pe0 = ExtService(model='RenGen', src='Pe', indexer=self.reg, tex_name='P_{e0}', ) self.H2 = ConstService(v_str='2 * H', tex_name='2H') self.Pm = Algeb(tex_name='P_m', info='Mechanical power', e_str='Pe0 - Pm', v_str='Pe0', ) self.wr0 = Algeb(tex_name=r'\omega_{r0}', unit='p.u.', v_str='w0', e_str='w0 - wr0', info='speed set point', ) # `s1_y` is `w_m` self.s1 = Integrator(u='(Pm - Pe) / wge - D * (s1_y - wr0)', T=self.H2, K=1.0, y0='wr0', ) # make two alias states, `wt` and `wg`, pointing to `s1_y` self.wt = AliasState(self.s1_y, tex_name=r'\omega_t') self.wg = AliasState(self.s1_y, tex_name=r'\omega_g') self.s3_y = State(info='Unused state variable', tex_name='y_{s3}', ) self.Kshaft = ConstService(v_str='1.0', tex_name='K_{shaft}', info='Dummy Kshaft', )
def __init__(self): self.udref0 = ConstService(tex_name=r'u_{dref0}', v_str='vd0 + ra*Id0 - xs*Iq0') self.uqref0 = ConstService( tex_name=r'u_{qref0}', v_str='vq0 + ra*Iq0 + xs*Id0', ) # PIvd_y, PIvq_y are Idref, Iqref self.PIId = PIController( u='Id - PIvd_y', kp=self.KpId, ki=self.KiId, ) self.PIIq = PIController( u='Iq - PIvq_y', kp=self.KpIq, ki=self.KiIq, ) # udLag_y, uqLag_y are ud, uq self.Id.e_str = 'vd + ra*Id - xs*Iq - udLag_y' self.Iq.e_str = 'vq + ra*Iq + xs*Id - uqLag_y' self.udref = Algeb( tex_name=r'u_{dref}', info='ud reference', v_str='udref0', e_str='PIId_y + vd - Iqref * xs - udref', ) self.uqref = Algeb( tex_name=r'u_{qref}', info='uq reference', v_str='uqref0', e_str='PIIq_y + vq + Idref * xs - uqref', ) self.udLag = Lag( u='udref', T=self.Tc, K=1, ) self.uqLag = Lag( u='uqref', T=self.Tc, K=1, ) self.ud = AliasState(self.udLag_y) self.uq = AliasState(self.uqLag_y)
def __init__(self, system, config): Model.__init__(self, system, config) self.flags.tds = True self.group = 'RenAerodynamics' self.theta0r = ConstService( v_str='rad(theta0)', tex_name=r'\theta_{0r}', info='Initial pitch angle in radian', ) self.theta = Algeb( tex_name=r'\theta', info='Pitch angle', unit='rad', v_str='theta0r', e_str='theta0r - theta', ) self.Pe0 = ExtService( model='RenGovernor', src='Pe0', indexer=self.rego, tex_name='P_{e0}', ) self.Pmg = ExtAlgeb(model='RenGovernor', src='Pm', indexer=self.rego, e_str='-Pe0 - (theta - theta0) * theta + Pe0')
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): TGBase.__init__(self, system, config) self.gain = ConstService( v_str='ue/R', tex_name='G', ) self.pref = Algeb( info='Reference power input', tex_name='P_{ref}', v_str='tm0 * R', e_str='pref0 * R - pref', ) self.wd = Algeb( info='Generator under speed', unit='p.u.', tex_name=r'\omega_{dev}', v_str='0', e_str='ue * (omega - wref) - wd', ) self.pd = Algeb(info='Pref plus under speed times gain', unit='p.u.', tex_name="P_d", v_str='ue * tm0', e_str='ue*(- wd + pref + paux) * gain - pd') self.v9 = Algeb( tex_name=r'V_{9}', info='V_9 for LVGate input', v_str='ue * (AT + KT * (AT - tm0))', e_str='ue * (AT + KT * (AT - LG3_y)) - v9', ) self.LVG = LVGate( u1=self.pd, u2=self.v9, info='LVGate', ) self.LAG = LagAntiWindup( u=self.LVG_y, K=1, T=self.T1, lower=self.VMIN, upper=self.VMAX, ) self.LG2 = Lag(u=self.LAG_y, T=self.T2, K=1, info='Lag T2') self.LG3 = Lag(u=self.LG2_y, T=self.T3, K=1, info='Lag T3') self.pout.e_str = 'ue * (LG2_y - Dt * wd) - pout'
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)', ename='P', tex_ename='P', ) self.v = ExtAlgeb(model='Bus', src='v', indexer=self.bus, tex_name='V', e_str='qv0 * (v ** aq) * (f ** bq)', ename='Q', tex_ename='Q', )
def __init__(self, system, config): TGBase.__init__(self, system, config) self.gain = ConstService( v_str='ue/R', tex_name='G', ) self.pref = Algeb( info='Reference power input', tex_name='P_{ref}', v_str='tm0 * R', e_str='pref0 * R - pref', ) self.wd = Algeb( info='Generator speed deviation', unit='p.u.', tex_name=r'\omega_{dev}', v_str='0', e_str='ue * (omega - wref) - wd', ) self.pd = Algeb(info='Pref plus speed deviation times gain', unit='p.u.', tex_name="P_d", v_str='ue * tm0', e_str='ue*(- 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 = 'ue * (LL_y - Dt * wd) - pout'
def __init__(self, system, config): Model.__init__(self, system, config) self.flags.tds = True self.group = 'RenTorque' self.kp1 = ConstService(v_str='(sp2 - sp1) / (p2 - p1)', tex_name='k_{p1}', ) self.kp2 = ConstService(v_str='(sp3 - sp2) / (p3 - p2)', tex_name='k_{p2}', ) self.kp3 = ConstService(v_str='(sp4 - sp3) / (p4 - p3)', tex_name='k_{p3}', ) self.rea = ExtParam(model='RenPitch', src='rea', indexer=self.rep, export=False, ) self.rego = ExtParam(model='RenAerodynamics', src='rego', indexer=self.rea, export=False, ) self.ree = ExtParam(model='RenGovernor', src='ree', indexer=self.rego, export=False, ) self.reg = ExtParam(model='RenExciter', src='reg', indexer=self.ree, export=False,) self.Sngo = ExtParam(model='RenGovernor', src='Sn', indexer=self.rego, tex_name='S_{n,go}', export=False, ) self.Sn = NumSelect(self.Tn, fallback=self.Sngo, tex_name='S_n', info='Turbine or RenGovernor rating', ) self.Pe = ExtAlgeb(model='RenGen', src='Pe', indexer=self.reg, tex_name='P_e', export=False, ) self.s1 = Lag(u=self.Pe, T=self.Tp, K=1.0, tex_name='s_1', info='Pe filter', ) self.fPe = Piecewise(u=self.s1_y, points=('p1', 'p2', 'p3', 'p4'), funs=('sp1', f'sp1 + ({self.s1_y.name} - p1) * kp1', f'sp2 + ({self.s1_y.name} - p2) * kp2', f'sp3 + ({self.s1_y.name} - p3) * kp3', 'sp4'), tex_name='f_{Pe}', info='Piecewise Pe to wref mapping', ) # Overwrite `wg` and `wt` initial values in turbine governors self.wg = ExtState(model='RenGovernor', src='wg', indexer=self.rego, tex_name=r'\omega_g', export=False, v_str='fPe_y', v_setter=True, ) self.wt = ExtState(model='RenGovernor', src='wt', indexer=self.rego, tex_name=r'\omega_t', export=False, v_str='fPe_y', v_setter=True, ) self.s3_y = ExtState(model='RenGovernor', src='s3_y', indexer=self.rego, tex_name='y_{s3}', export=False, v_str='Pref0 / wg / Kshaft', v_setter=True, ) self.w0 = ExtParam(model='RenGovernor', src='w0', indexer=self.rego, tex_name=r'\omega_0', export=False, ) self.Kshaft = ExtService(model='RenGovernor', src='Kshaft', indexer=self.rego, tex_name='K_{shaft}', ) self.wr0 = ExtAlgeb(model='RenGovernor', src='wr0', indexer=self.rego, tex_name=r'\omega_{r0}', export=False, info='Retrieved initial w0 from RenGovernor', v_str='fPe_y', e_str='-w0 + fPe_y', v_setter=True, ename='dwr', tex_ename=r'\Delta \omega_r', ) self.s2 = Lag(u=self.fPe_y, T=self.Twref, K=1.0, tex_name='s_2', info='speed filter', ) self.SWT = Switcher(u=self.Tflag, options=(0, 1), tex_name='SW_{T}', cache=True, ) self.Tsel = Algeb(tex_name='T_{sel}', info='Output after Tflag selector', discrete=self.SWT ) self.Tsel.v_str = 'SWT_s1 * (Pe - Pref0) / wg +' \ 'SWT_s0 * (s2_y - wg)' self.Tsel.e_str = f'{self.Tsel.v_str} - Tsel' self.PI = PIAWHardLimit(u=self.Tsel, kp=self.Kpp, ki=self.Kip, aw_lower=self.Temin, aw_upper=self.Temax, lower=self.Temin, upper=self.Temax, tex_name='PI', info='PI controller', x0='Pref0 / fPe_y', ) # Note: # Reset `wg` of REECA1 to 1.0 becase `wg` has already been multiplied # in the toeque model. # This effectively sets `PFLAG` to 0 if the torque model is connected. self.wge = ExtAlgeb(model='RenExciter', src='wg', indexer=self.ree, tex_name=r'\omega_{ge}', export=False, v_str='1.0', e_str='-fPe_y + 1', v_setter=True, ename='dwg', tex_ename=r'\Delta \omega_g', ) self.Pref0 = ExtService(model='RenExciter', src='p0', indexer=self.ree, tex_name='P_{ref0}', ) self.Pref = ExtAlgeb(model='RenExciter', src='Pref', indexer=self.ree, tex_name='P_{ref}', export=False, e_str='-Pref0 / wge + PI_y * wg', v_str='PI_y * wg', v_setter=True, ename='Pref', tex_ename='P_{ref}', )
def __init__(self, system, config): Model.__init__(self, system, config) self.group = 'RenPlant' self.flags.tds = True self.config.add(OrderedDict(( ('kqs', 2), ('ksg', 2), ('freeze', 1), ))) self.config.add_extra( '_help', kqs='Tracking gain for reactive power PI controller', ksg='Tracking gain for active power PI controller', freeze='Voltage dip freeze flag; 1-enable, 0-disable', ) self.config.add_extra('_tex', kqs='K_{qs}', ksg='K_{sg}', freeze='f_{rz}') # --- from RenExciter --- self.reg = ExtParam( model='RenExciter', src='reg', indexer=self.ree, export=False, info='Retrieved RenGen idx', vtype=str, default=None, ) self.Pext = ExtAlgeb( model='RenExciter', src='Pref', indexer=self.ree, info='Pref from RenExciter renamed as Pext', tex_name='P_{ext}', ) self.Qext = ExtAlgeb( model='RenExciter', src='Qref', indexer=self.ree, info='Qref from RenExciter renamed as Qext', tex_name='Q_{ext}', ) # --- from RenGen --- self.bus = ExtParam( model='RenGen', src='bus', indexer=self.reg, export=False, info='Retrieved bus idx', vtype=str, default=None, ) self.buss = DataSelect(self.busr, self.bus, info='selected bus (bus or busr)') self.busfreq = DeviceFinder(self.busf, link=self.buss, idx_name='bus') # from Bus self.v = ExtAlgeb( model='Bus', src='v', indexer=self.buss, tex_name='V', info='Bus (or busr, if given) terminal voltage', ) self.a = ExtAlgeb( model='Bus', src='a', indexer=self.buss, tex_name=r'\theta', info='Bus (or busr, if given) phase angle', ) self.v0 = ExtService( model='Bus', src='v', indexer=self.buss, tex_name="V_0", info='Initial bus voltage', ) # from BusFreq self.f = ExtAlgeb(model='FreqMeasurement', src='f', indexer=self.busfreq, export=False, info='Bus frequency', unit='p.u.') # from Line self.bus1 = ExtParam( model='ACLine', src='bus1', indexer=self.line, export=False, info='Retrieved Line.bus1 idx', vtype=str, default=None, ) self.bus2 = ExtParam( model='ACLine', src='bus2', indexer=self.line, export=False, info='Retrieved Line.bus2 idx', vtype=str, default=None, ) self.r = ExtParam( model='ACLine', src='r', indexer=self.line, export=False, info='Retrieved Line.r', vtype=str, default=None, ) self.x = ExtParam( model='ACLine', src='x', indexer=self.line, export=False, info='Retrieved Line.x', vtype=str, default=None, ) self.v1 = ExtAlgeb( model='ACLine', src='v1', indexer=self.line, tex_name='V_1', info='Voltage at Line.bus1', ) self.v2 = ExtAlgeb( model='ACLine', src='v2', indexer=self.line, tex_name='V_2', info='Voltage at Line.bus2', ) self.a1 = ExtAlgeb( model='ACLine', src='a1', indexer=self.line, tex_name=r'\theta_1', info='Angle at Line.bus1', ) self.a2 = ExtAlgeb( model='ACLine', src='a2', indexer=self.line, tex_name=r'\theta_2', info='Angle at Line.bus2', ) # -- begin services --- self.Isign = CurrentSign(self.bus, self.bus1, self.bus2, tex_name='I_{sign}') Iline = '(Isign * (v1*exp(1j*a1) - v2*exp(1j*a2)) / (r + 1j*x))' self.Iline = VarService( v_str=Iline, vtype=complex, info='Complex current from bus1 to bus2', tex_name='I_{line}', ) self.Iline0 = ConstService( v_str='Iline', vtype=complex, info='Initial complex current from bus1 to bus2', tex_name='I_{line0}', ) Pline = 're(Isign * v1*exp(1j*a1) * conj((v1*exp(1j*a1) - v2*exp(1j*a2)) / (r + 1j*x)))' self.Pline = VarService( v_str=Pline, vtype=float, info='Complex power from bus1 to bus2', tex_name='P_{line}', ) self.Pline0 = ConstService( v_str='Pline', vtype=float, info='Initial vomplex power from bus1 to bus2', tex_name='P_{line0}', ) Qline = 'im(Isign * v1*exp(1j*a1) * conj((v1*exp(1j*a1) - v2*exp(1j*a2)) / (r + 1j*x)))' self.Qline = VarService( v_str=Qline, vtype=float, info='Complex power from bus1 to bus2', tex_name='Q_{line}', ) self.Qline0 = ConstService( v_str='Qline', vtype=float, info='Initial complex power from bus1 to bus2', tex_name='Q_{line0}', ) self.Rcs = NumSelect( self.Rc, self.r, info='Line R (Rc if provided, otherwise line.r)', tex_name='R_{cs}', ) self.Xcs = NumSelect( self.Xc, self.x, info='Line X (Xc if provided, otherwise line.x)', tex_name='X_{cs}', ) self.Vcomp = VarService( v_str='abs(v*exp(1j*a) - (Rcs + 1j * Xcs) * Iline)', info='Voltage after Rc/Xc compensation', tex_name='V_{comp}') self.SWVC = Switcher(u=self.VCFlag, options=(0, 1), tex_name='SW_{VC}', cache=True) self.SWRef = Switcher(u=self.RefFlag, options=(0, 1), tex_name='SW_{Ref}', cache=True) self.SWF = Switcher(u=self.Fflag, options=(0, 1), tex_name='SW_{F}', cache=True) self.SWPL = Switcher(u=self.PLflag, options=(0, 1), tex_name='SW_{PL}', cache=True) VCsel = '(SWVC_s1 * Vcomp + SWVC_s0 * (Qline * Kc + v))' self.Vref0 = ConstService( v_str='(SWVC_s1 * Vcomp + SWVC_s0 * (Qline0 * Kc + v))', tex_name='V_{ref0}', ) self.s0 = Lag( VCsel, T=self.Tfltr, K=1, tex_name='s_0', info='V filter', ) # s0_y is the filter output of voltage deviation self.s1 = Lag(self.Qline, T=self.Tfltr, K=1, tex_name='s_1') self.Vref = Algeb(v_str='Vref0', e_str='Vref0 - Vref', tex_name='Q_{ref}') self.Qlinef = Algeb(v_str='Qline0', e_str='Qline0 - Qlinef', tex_name='Q_{linef}') Refsel = '(SWRef_s0 * (Qlinef - s1_y) + SWRef_s1 * (Vref - s0_y))' self.Refsel = Algeb(v_str=Refsel, e_str=f'{Refsel} - Refsel', tex_name='R_{efsel}') self.dbd = DeadBand1( u=self.Refsel, lower=self.dbd1, upper=self.dbd2, center=0.0, tex_name='d^{bd}', ) # --- e Hardlimit and hold logic --- self.eHL = Limiter( u=self.dbd_y, lower=self.emin, upper=self.emax, tex_name='e_{HL}', info='Hardlimit on deadband output', ) self.zf = VarService( v_str='Indicator(v < Vfrz) * freeze', tex_name='z_f', info='PI Q input freeze signal', ) self.enf = Algeb( tex_name='e_{nf}', info='e Hardlimit output before freeze', v_str='dbd_y*eHL_zi + emax*eHL_zu + emin*eHL_zl', e_str='dbd_y*eHL_zi + emax*eHL_zu + emin*eHL_zl - enf', ) # --- hold of `enf` when v < vfrz self.eHld = VarHold( u=self.enf, hold=self.zf, tex_name='e_{hld}', info='e Hardlimit output after conditional hold', ) self.s2 = PITrackAW( u='eHld', kp=self.Kp, ki=self.Ki, ks=self.config.kqs, lower=self.Qmin, upper=self.Qmax, info='PI controller for eHL output', tex_name='s_2', ) self.s3 = LeadLag( u=self.s2_y, T1=self.Tft, T2=self.Tfv, K=1, tex_name='s_3', ) # s3_y == Qext # Active power part self.s4 = Lag( self.Pline, T=self.Tp, K=1, tex_name='s_4', info='Pline filter', ) self.Freq_ref = ConstService(v_str='1.0', tex_name='f_{ref}', info='Initial Freq_ref') self.ferr = Algeb( tex_name='f_{err}', info='Frequency deviation', unit='p.u. (Hz)', v_str='(Freq_ref - f)', e_str='(Freq_ref - f) - ferr', ) self.fdbd = DeadBand1( u=self.ferr, center=0.0, lower=self.fdbd1, upper=self.fdbd2, tex_name='f^{dbd}', info='frequency error deadband', ) self.fdlt0 = LessThan( self.fdbd_y, 0.0, tex_name='f_{dlt0}', info='frequency deadband output less than zero', ) fdroop = '(fdbd_y * Ddn * fdlt0_z1 + fdbd_y * Dup * fdlt0_z0)' self.Plant_pref = Algeb( tex_name='P_{ref}', info='Plant P ref', v_str='Pline0', e_str='Pline0 - Plant_pref', ) self.Plerr = Algeb( tex_name='P_{lerr}', info='Pline error', v_str='- s4_y + Plant_pref', e_str='- s4_y + Plant_pref - Plerr', ) self.Perr = Algeb( tex_name='P_{err}', info='Power error before fe limits', v_str=f'{fdroop} + Plerr * SWPL_s1', e_str=f'{fdroop} + Plerr * SWPL_s1 - Perr', ) self.feHL = Limiter( self.Perr, lower=self.femin, upper=self.femax, tex_name='f_{eHL}', info='Limiter for power (frequency) error', ) feout = '(Perr * feHL_zi + femin * feHL_zl + femax * feHL_zu)' self.s5 = PITrackAW( u=feout, kp=self.Kpg, ki=self.Kig, ks=self.config.ksg, lower=self.Pmin, upper=self.Pmax, tex_name='s_5', info='PI for fe limiter output', ) self.s6 = Lag( u=self.s5_y, T=self.Tg, K=1, tex_name='s_6', info='Output filter for Pext', ) Qext = '(s3_y)' Pext = '(SWF_s1 * s6_y)' self.Pext.e_str = Pext self.Qext.e_str = Qext
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', ename='Pij', tex_ename='P_{ij}', ) self.a2 = ExtAlgeb( model='Bus', src='a', indexer=self.bus2, tex_name='a_2', info='phase angle of the to bus', ename='Pji', tex_ename='P_{ji}', ) self.v1 = ExtAlgeb( model='Bus', src='v', indexer=self.bus1, tex_name='v_1', info='voltage magnitude of the from bus', ename='Qij', tex_ename='Q_{ij}', ) self.v2 = ExtAlgeb( model='Bus', src='v', indexer=self.bus2, tex_name='v_2', info='voltage magnitude of the to bus', ename='Qji', tex_ename='Q_{ji}', ) 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=complex) self.yk = ConstService(tex_name='y_k', vtype=complex) self.yhk = ConstService(tex_name='y_{hk}', vtype=complex) self.ghk = ConstService(tex_name='g_{hk}') self.bhk = ConstService(tex_name='b_{hk}') self.itap = ConstService(tex_name='1/t_{ap}') self.itap2 = ConstService(tex_name='1/t_{ap}^2') 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.itap.v_str = '1/tap' self.itap2.v_str = '1/tap/tap' self.a1.e_str = 'u * (v1 ** 2 * (gh + ghk) * itap2 - \ v1 * v2 * (ghk * cos(a1 - a2 - phi) + \ bhk * sin(a1 - a2 - phi)) * itap)' self.v1.e_str = 'u * (-v1 ** 2 * (bh + bhk) * itap2 - \ v1 * v2 * (ghk * sin(a1 - a2 - phi) - \ bhk * cos(a1 - a2 - phi)) * itap)' self.a2.e_str = 'u * (v2 ** 2 * (gh + ghk) - \ v1 * v2 * (ghk * cos(a1 - a2 - phi) - \ bhk * sin(a1 - a2 - phi)) * itap)' self.v2.e_str = 'u * (-v2 ** 2 * (bh + bhk) + \
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', ename='P', tex_ename='P', is_input=True, ) self.v = ExtAlgeb(model='Bus', src='v', indexer=self.bus, tex_name=r'V', ename='Q', tex_ename='Q', is_input=True, ) 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 * Indicator(dae_t <= 0) * " \ "(p0 * vcmp_zi + Rlb * vcmp_zl * v**2 + Rub * vcmp_zu * v**2) + " \ "u * Indicator(dae_t > 0) * " \ "(p2p * Ppf + p2i * Ipeq * v + p2z * Req * v**2)" self.v.e_str = "u * Indicator(dae_t <= 0) * " \ "(q0 * vcmp_zi + Xlb * vcmp_zl * v**2 + Xub * vcmp_zu * v**2) + " \ "u * Indicator(dae_t > 0) * " \ "(q2q * Qpf + q2i * Iqeq * v + q2z * Xeq * v**2)"
def __init__(self, system, config, add_sn=True, add_tm0=True): Model.__init__(self, system, config) self.group = 'TurbineGov' self.flags.update({'tds': True}) self.Sg = ExtParam( src='Sn', model='SynGen', indexer=self.syn, tex_name='S_n', info='Rated power from generator', unit='MVA', export=False, ) if add_sn is True: self.Sn = NumSelect( self.Tn, fallback=self.Sg, tex_name='S_n', info='Turbine or Gen rating', ) self.Vn = ExtParam( src='Vn', model='SynGen', indexer=self.syn, tex_name='V_n', info='Rated voltage from generator', unit='kV', export=False, ) # Note: changing the values of `tm0` is not allowed at any time!! if add_tm0 is True: self.tm0 = ExtService(src='tm', model='SynGen', indexer=self.syn, tex_name=r'\tau_{m0}', info='Initial mechanical input') self.pref0 = ConstService( v_str='tm0', info='initial pref', tex_name='P_{ref0}', ) self.omega = ExtState(src='omega', model='SynGen', indexer=self.syn, tex_name=r'\omega', info='Generator speed', unit='p.u.') # Note: changing `paux0` is allowed. # It is a way how one can input from external programs such as reinforcement learning. self.paux0 = ConstService(v_str='0', tex_name='P_{aux0}', info='const. auxiliary input') self.tm = ExtAlgeb( src='tm', model='SynGen', indexer=self.syn, tex_name=r'\tau_m', e_str='u * (pout - tm0)', info='Mechanical power interface to SynGen', ) # `paux` must be zero upon initialization self.paux = Algeb( info='Auxiliary power input', tex_name='P_{aux}', v_str='paux0', e_str='paux0 - paux', ) self.pout = Algeb( info='Turbine final output power', tex_name='P_{out}', v_str='u*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): Model.__init__(self, system, config) self.flags.tds = True self.group = 'RenGovernor' self.reg = ExtParam( model='RenExciter', src='reg', indexer=self.ree, vtype=str, export=False, ) self.Sn = ExtParam( model='RenGen', src='Sn', indexer=self.reg, tex_name='S_n', export=False, ) self.wge = ExtAlgeb( model='RenExciter', src='wg', indexer=self.ree, export=False, e_str='-1.0 + s2_y', ename='wg', tex_ename=r'\omega_g', ) self.Pe = ExtAlgeb(model='RenGen', src='Pe', indexer=self.reg, export=False, info='Retrieved Pe of RenGen') self.Pe0 = ExtService( model='RenGen', src='Pe', indexer=self.reg, tex_name='P_{e0}', ) self.Ht2 = ConstService(v_str='2 * (Htfrac * H)', tex_name='2H_t') self.Hg2 = ConstService(v_str='2 * H * (1 - Htfrac)', tex_name='2H_g') # (2*pi*Freq1)**2 is considered in p.u., which is Freq1**2 here self.Kshaft = ConstService(v_str='Ht2 * Hg2 * 0.5 * Freq1 * Freq1 / H', tex_name='K_{shaft}') self.wr0 = Algeb( tex_name=r'\omega_{r0}', unit='p.u.', v_str='w0', e_str='w0 - wr0', info='speed set point', ) self.Pm = Algeb( tex_name='P_m', info='Mechanical power', e_str='Pe0 - Pm', v_str='Pe0', ) # `s1_y` is `wt` self.s1 = Integrator( u='(Pm / s1_y) - s3_y - pd', T=self.Ht2, K=1.0, y0='wr0', ) self.wt = AliasState(self.s1_y, tex_name=r'\omega_t') # `s2_y` is `wg` self.s2 = Integrator( u='-(Pe / s2_y) + s3_y - DAMP * (s2_y - w0) + pd', T=self.Hg2, K=1.0, y0='wr0', ) self.wg = AliasState(self.s2_y, tex_name=r'\omega_g') # `s3_y` gets reinitialized in `WTTQA1` self.s3 = Integrator( u='s1_y - s2_y', T=1.0, K=self.Kshaft, y0='Pe0 / wr0', ) self.pd = Algeb( tex_name='P_d', info='Output after damping', e_str='Dshaft * (s1_y - s2_y) - pd', v_str='0', )
def __init__(self, system, config): Model.__init__(self, system, config) self.flags.tds = True self.group = 'RenExciter' self.config.add(OrderedDict((('kqs', 2), ('kvs', 2), ('tpfilt', 0.02), ))) self.config.add_extra('_help', kqs='Q PI controller tracking gain', kvs='Voltage PI controller tracking gain', tpfilt='Time const. for Pref filter', ) self.config.add_extra('_tex', kqs='K_{qs}', kvs='K_{vs}', tpfilt='T_{pfilt}', ) # --- Sanitize inputs --- self.Imaxr = Replace(self.Imax, flt=lambda x: np.less_equal(x, 0), new_val=1e8, tex_name='I_{maxr}') # --- Flag switchers --- self.SWPF = Switcher(u=self.PFFLAG, options=(0, 1), tex_name='SW_{PF}', cache=True) self.SWV = Switcher(u=self.VFLAG, options=(0, 1), tex_name='SW_{V}', cache=True) self.SWQ = Switcher(u=self.QFLAG, options=(0, 1), tex_name='SW_{V}', cache=True) self.SWP = Switcher(u=self.PFLAG, options=(0, 1), tex_name='SW_{P}', cache=True) self.SWPQ = Switcher(u=self.PQFLAG, options=(0, 1), tex_name='SW_{PQ}', cache=True) # --- External parameters --- self.bus = ExtParam(model='RenGen', src='bus', indexer=self.reg, export=False, info='Retrieved bus idx', vtype=str, default=None, ) self.buss = DataSelect(self.busr, self.bus, info='selected bus (bus or busr)') self.gen = ExtParam(model='RenGen', src='gen', indexer=self.reg, export=False, info='Retrieved StaticGen idx', vtype=str, default=None, ) self.Sn = ExtParam(model='RenGen', src='Sn', indexer=self.reg, tex_name='S_n', export=False, ) # --- External variables --- self.a = ExtAlgeb(model='Bus', src='a', indexer=self.bus, tex_name=r'\theta', info='Bus voltage angle', ) self.v = ExtAlgeb(model='Bus', src='v', indexer=self.bus, tex_name=r'V', info='Bus voltage magnitude', ) # check whether to use `bus` or `buss` self.Pe = ExtAlgeb(model='RenGen', src='Pe', indexer=self.reg, export=False, info='Retrieved Pe of RenGen') self.Qe = ExtAlgeb(model='RenGen', src='Qe', indexer=self.reg, export=False, info='Retrieved Qe of RenGen') self.Ipcmd = ExtAlgeb(model='RenGen', src='Ipcmd', indexer=self.reg, export=False, info='Retrieved Ipcmd of RenGen', e_str='-Ipcmd0 + IpHL_y', ) self.Iqcmd = ExtAlgeb(model='RenGen', src='Iqcmd', indexer=self.reg, export=False, info='Retrieved Iqcmd of RenGen', e_str='-Iqcmd0 - IqHL_y', ) self.p0 = ExtService(model='RenGen', src='p0', indexer=self.reg, tex_name='P_0', ) self.q0 = ExtService(model='RenGen', src='q0', indexer=self.reg, tex_name='Q_0', ) # Initial current commands self.Ipcmd0 = ConstService('p0 / v', info='initial Ipcmd') self.Iqcmd0 = ConstService('-q0 / v', info='initial Iqcmd') # --- Initial power factor angle --- # NOTE: if `p0` = 0, `pfaref0` = pi/2, `tan(pfaref0)` = inf self.pfaref0 = ConstService(v_str='atan2(q0, p0)', tex_name=r'\Phi_{ref0}', info='Initial power factor angle', ) # flag devices with `p0`=0, which causes `tan(PF) = +inf` self.zp0 = ConstService(v_str='Eq(p0, 0)', vtype=float, tex_name='z_{p0}', ) # --- Discrete components --- self.Vcmp = Limiter(u=self.v, lower=self.Vdip, upper=self.Vup, tex_name='V_{cmp}', info='Voltage dip comparator', equal=False, ) self.Volt_dip = VarService(v_str='1 - Vcmp_zi', info='Voltage dip flag; 1-dip, 0-normal', tex_name='z_{Vdip}', ) # --- Equations begin --- self.s0 = Lag(u=self.v, T=self.Trv, K=1, info='Voltage filter', ) self.VLower = Limiter(u=self.v, lower=0.01, upper=999, no_upper=True, info='Limiter for lower voltage cap', ) self.vp = Algeb(tex_name='V_p', info='Sensed lower-capped voltage', v_str='v * VLower_zi + 0.01 * VLower_zl', e_str='v * VLower_zi + 0.01 * VLower_zl - vp', ) self.pfaref = Algeb(tex_name=r'\Phi_{ref}', info='power factor angle ref', unit='rad', v_str='pfaref0', e_str='pfaref0 - pfaref', ) self.S1 = Lag(u='Pe', T=self.Tp, K=1, tex_name='S_1', info='Pe filter', ) # ignore `Qcpf` if `pfaref` is pi/2 by multiplying (1-zp0) self.Qcpf = Algeb(tex_name='Q_{cpf}', info='Q calculated from P and power factor', v_str='q0', e_str='(1-zp0) * (S1_y * tan(pfaref) - Qcpf)', diag_eps=True, unit='p.u.', ) self.Qref = Algeb(tex_name='Q_{ref}', info='external Q ref', v_str='q0', e_str='q0 - Qref', unit='p.u.', ) self.PFsel = Algeb(v_str='SWPF_s0*Qref + SWPF_s1*Qcpf', e_str='SWPF_s0*Qref + SWPF_s1*Qcpf - PFsel', info='Output of PFFLAG selector', ) self.PFlim = Limiter(u=self.PFsel, lower=self.QMin, upper=self.QMax) self.Qerr = Algeb(tex_name='Q_{err}', info='Reactive power error', v_str='(PFsel*PFlim_zi + QMin*PFlim_zl + QMax*PFlim_zu) - Qe', e_str='(PFsel*PFlim_zi + QMin*PFlim_zl + QMax*PFlim_zu) - Qe - Qerr', ) self.PIQ = PITrackAWFreeze(u=self.Qerr, kp=self.Kqp, ki=self.Kqi, ks=self.config.kqs, lower=self.VMIN, upper=self.VMAX, freeze=self.Volt_dip, ) # If `VFLAG=0`, set the input as `Vref1` (see the NREL report) self.Vsel = GainLimiter(u='SWV_s0 * Vref1 + SWV_s1 * PIQ_y', K=1, R=1, lower=self.VMIN, upper=self.VMAX, info='Selection output of VFLAG', ) # --- Placeholders for `Iqmin` and `Iqmax` --- self.s4 = LagFreeze(u='PFsel / vp', T=self.Tiq, K=1, freeze=self.Volt_dip, tex_name='s_4', info='Filter for calculated voltage with freeze', ) # --- Upper portion - Iqinj calculation --- self.Verr = Algeb(info='Voltage error (Vref0)', v_str='Vref0 - s0_y', e_str='Vref0 - s0_y - Verr', tex_name='V_{err}', ) self.dbV = DeadBand1(u=self.Verr, lower=self.dbd1, upper=self.dbd2, center=0.0, enable='DB_{V}', info='Deadband for voltage error (ref0)' ) self.pThld = ConstService(v_str='Indicator(Thld > 0)', tex_name='p_{Thld}') self.nThld = ConstService(v_str='Indicator(Thld < 0)', tex_name='n_{Thld}') self.Thld_abs = ConstService(v_str='abs(Thld)', tex_name='|Thld|') self.fThld = ExtendedEvent(self.Volt_dip, t_ext=self.Thld_abs, ) # Gain after dbB Iqv = "(dbV_y * Kqv)" Iqinj = f'{Iqv} * Volt_dip + ' \ f'(1 - Volt_dip) * fThld * ({Iqv} * nThld + Iqfrz * pThld)' # state transition, output of Iqinj self.Iqinj = Algeb(v_str=Iqinj, e_str=Iqinj + ' - Iqinj', tex_name='I_{qinj}', info='Additional Iq signal during under- or over-voltage', ) # --- Lower portion - active power --- self.wg = Algeb(tex_name=r'\omega_g', info='Drive train generator speed', v_str='1.0', e_str='1.0 - wg', ) self.Pref = Algeb(tex_name='P_{ref}', info='external P ref', v_str='p0 / wg', e_str='p0 / wg - Pref', unit='p.u.', ) self.pfilt = LagRate(u=self.Pref, T=self.config.tpfilt, K=1, rate_lower=self.dPmin, rate_upper=self.dPmax, info='Active power filter with rate limits', tex_name='P_{filt}', ) self.Psel = Algeb(tex_name='P_{sel}', info='Output selection of PFLAG', v_str='SWP_s1*wg*pfilt_y + SWP_s0*pfilt_y', e_str='SWP_s1*wg*pfilt_y + SWP_s0*pfilt_y - Psel', ) # `s5_y` is `Pord` self.s5 = LagAWFreeze(u=self.Psel, T=self.Tpord, K=1, lower=self.PMIN, upper=self.PMAX, freeze=self.Volt_dip, tex_name='s5', ) self.Pord = AliasState(self.s5_y) # --- Current limit logic --- self.kVq12 = ConstService(v_str='(Iq2 - Iq1) / (Vq2 - Vq1)', tex_name='k_{Vq12}', ) self.kVq23 = ConstService(v_str='(Iq3 - Iq2) / (Vq3 - Vq2)', tex_name='k_{Vq23}', ) self.kVq34 = ConstService(v_str='(Iq4 - Iq3) / (Vq4 - Vq3)', tex_name='k_{Vq34}', ) self.zVDL1 = ConstService(v_str='(Vq1 <= Vq2) & (Vq2 <= Vq3) & (Vq3 <= Vq4) & ' '(Iq1 <= Iq2) & (Iq2 <= Iq3) & (Iq3 <= Iq4)', tex_name='z_{VDL1}', info='True if VDL1 is in service', ) self.VDL1 = Piecewise(u=self.s0_y, points=('Vq1', 'Vq2', 'Vq3', 'Vq4'), funs=('Iq1', f'({self.s0_y.name} - Vq1) * kVq12 + Iq1', f'({self.s0_y.name} - Vq2) * kVq23 + Iq2', f'({self.s0_y.name} - Vq3) * kVq34 + Iq3', 'Iq4'), tex_name='V_{DL1}', info='Piecewise linear characteristics of Vq-Iq', ) self.kVp12 = ConstService(v_str='(Ip2 - Ip1) / (Vp2 - Vp1)', tex_name='k_{Vp12}', ) self.kVp23 = ConstService(v_str='(Ip3 - Ip2) / (Vp3 - Vp2)', tex_name='k_{Vp23}', ) self.kVp34 = ConstService(v_str='(Ip4 - Ip3) / (Vp4 - Vp3)', tex_name='k_{Vp34}', ) self.zVDL2 = ConstService(v_str='(Vp1 <= Vp2) & (Vp2 <= Vp3) & (Vp3 <= Vp4) & ' '(Ip1 <= Ip2) & (Ip2 <= Ip3) & (Ip3 <= Ip4)', tex_name='z_{VDL2}', info='True if VDL2 is in service', ) self.VDL2 = Piecewise(u=self.s0_y, points=('Vp1', 'Vp2', 'Vp3', 'Vp4'), funs=('Ip1', f'({self.s0_y.name} - Vp1) * kVp12 + Ip1', f'({self.s0_y.name} - Vp2) * kVp23 + Ip2', f'({self.s0_y.name} - Vp3) * kVp34 + Ip3', 'Ip4'), tex_name='V_{DL2}', info='Piecewise linear characteristics of Vp-Ip', ) self.fThld2 = ExtendedEvent(self.Volt_dip, t_ext=self.Thld2, extend_only=True, ) self.VDL1c = VarService(v_str='Lt(VDL1_y, Imaxr)') self.VDL2c = VarService(v_str='Lt(VDL2_y, Imaxr)') # `Iqmax` not considering mode or `Thld2` Iqmax1 = '(zVDL1*(VDL1c*VDL1_y + (1-VDL1c)*Imaxr) + 1e8*(1-zVDL1))' # `Ipmax` not considering mode or `Thld2` Ipmax1 = '(zVDL2*(VDL2c*VDL2_y + (1-VDL2c)*Imaxr) + 1e8*(1-zVDL2))' Ipmax2sq0 = '(Imax**2 - Iqcmd0**2)' Ipmax2sq = '(Imax**2 - IqHL_y**2)' # `Ipmax20`-squared (non-negative) self.Ipmax2sq0 = ConstService(v_str=f'Piecewise((0, Le({Ipmax2sq0}, 0.0)), ({Ipmax2sq0}, True), \ evaluate=False)', tex_name='I_{pmax20,nn}^2', ) self.Ipmax2sq = VarService(v_str=f'Piecewise((0, Le({Ipmax2sq}, 0.0)), ({Ipmax2sq}, True), \ evaluate=False)', tex_name='I_{pmax2}^2', ) Ipmax = f'((1-fThld2) * (SWPQ_s0*sqrt(Ipmax2sq) + SWPQ_s1*{Ipmax1}))' Ipmax0 = f'((1-fThld2) * (SWPQ_s0*sqrt(Ipmax2sq0) + SWPQ_s1*{Ipmax1}))' self.Ipmax = Algeb(v_str=f'{Ipmax0}', e_str=f'{Ipmax} + (fThld2 * Ipmaxh) - Ipmax', tex_name='I_{pmax}', diag_eps=True, info='Upper limit on Ipcmd', ) self.Ipmaxh = VarHold(self.Ipmax, hold=self.fThld2) Iqmax2sq = '(Imax**2 - IpHL_y**2)' Iqmax2sq0 = '(Imax**2 - Ipcmd0**2)' # initialization equation by using `Ipcmd0` self.Iqmax2sq0 = ConstService(v_str=f'Piecewise((0, Le({Iqmax2sq0}, 0.0)), ({Iqmax2sq0}, True), \ evaluate=False)', tex_name='I_{qmax,nn}^2', ) self.Iqmax2sq = VarService(v_str=f'Piecewise((0, Le({Iqmax2sq}, 0.0)), ({Iqmax2sq}, True), \ evaluate=False)', tex_name='I_{qmax2}^2') self.Iqmax = Algeb(v_str=f'(SWPQ_s0*{Iqmax1} + SWPQ_s1*sqrt(Iqmax2sq0))', e_str=f'(SWPQ_s0*{Iqmax1} + SWPQ_s1*sqrt(Iqmax2sq)) - Iqmax', tex_name='I_{qmax}', info='Upper limit on Iqcmd', ) self.Iqmin = ApplyFunc(self.Iqmax, lambda x: -x, cache=False, tex_name='I_{qmin}', info='Lower limit on Iqcmd', ) self.Ipmin = ConstService(v_str='0.0', tex_name='I_{pmin}', info='Lower limit on Ipcmd', ) self.PIV = PITrackAWFreeze(u='Vsel_y - s0_y * SWV_s0', x0='-SWQ_s1 * Iqcmd0', kp=self.Kvp, ki=self.Kvi, ks=self.config.kvs, lower=self.Iqmin, upper=self.Iqmax, freeze=self.Volt_dip, ) self.Qsel = Algeb(info='Selection output of QFLAG', v_str='SWQ_s1 * PIV_y + SWQ_s0 * s4_y', e_str='SWQ_s1 * PIV_y + SWQ_s0 * s4_y - Qsel', tex_name='Q_{sel}', ) # `IpHL_y` is `Ipcmd` self.IpHL = GainLimiter(u='s5_y / vp', K=1, R=1, lower=self.Ipmin, upper=self.Ipmax, ) # `IqHL_y` is `Iqcmd` self.IqHL = GainLimiter(u='Qsel + Iqinj', K=1, R=1, lower=self.Iqmin, upper=self.Iqmax)
def __init__(self, system, config): TG2Data.__init__(self) TGBase.__init__(self, system, config) self.config.add({'deadband': 0, 'hardlimit': 1}) self.config.add_extra("_help", deadband="enable input dead band", hardlimit="enable output hard limit") self.config.add_extra( "_alt", deadband=(0, 1), hardlimit=(0, 1), ) self.config.add_extra( "_tex", deadband="z_{deadband}", hardlimit="z_{hardlimit}", ) self.gain = ConstService( v_str='u / R', tex_name='G', ) self.w_d = Algeb( info= 'Generator speed deviation before dead band (positive for under speed)', tex_name=r'\omega_{dev}', v_str='0', e_str='u*(wref-omega) - w_d', ) self.w_db = DeadBandRT( u=self.w_d, center=self.dbc, lower=self.dbl, upper=self.dbu, enable=self.config.deadband, ) self.w_dm = Algeb(info='Measured speed deviation after dead band', tex_name=r'\omega_{dm}', v_str='0', e_str='(1 - w_db_zi) * w_d + ' 'w_db_zlr * dbl + ' 'w_db_zur * dbu - ' 'w_dm') self.w_dmg = Algeb( info='Speed deviation after dead band after gain', tex_name=r'\omega_{dmG}', v_str='0', e_str='gain * w_dm - w_dmg', ) self.ll = LeadLag( u=self.w_dmg, T1=self.T1, T2=self.T2, ) self.pnl = Algeb( info='Power output before hard limiter', tex_name='P_{nl}', v_str='tm0', e_str='pref0 + ll_y - pnl', ) self.plim = HardLimiter( u=self.pnl, lower=self.pmin, upper=self.pmax, enable=self.config.hardlimit, ) self.pout.e_str = 'pnl * plim_zi + pmax * plim_zu + pmin * plim_zl - pout'
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 * (tm - te - D * (omega - 1))', t_const=self.M, ) # 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)', ename='P', tex_ename='P', is_input=True, ) 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)', ename='Q', tex_ename='Q', is_input=True, ) # 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', vtype=str, ) # 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', ) self.Pe = Algeb(tex_name='P_e', info='active power injection', e_str='u * (vd * Id + vq * Iq) - Pe', v_str='u * (vd0 * Id0 + vq0 * Iq0)') self.Qe = Algeb(tex_name='Q_e', info='reactive power injection', e_str='u * (vq * Id - vd * Iq) - Qe', v_str='u * (vq0 * Id0 - vd0 * Iq0)')
def __init__(self, system, config): ACDC2Term.__init__(self, system, config) self.rsh = NumParam(default=0.0025, info="AC interface resistance", unit="ohm", z=True, tex_name='r_{sh}') self.xsh = NumParam(default=0.06, info="AC interface reactance", unit="ohm", z=True, tex_name='x_{sh}') self.control = NumParam( info="Control method: 0-PQ, 1-PV, 2-vQ or 3-vV", mandatory=True) self.v0 = NumParam( default=1.0, info="AC voltage setting (PV or vV) or initial guess (PQ or vQ)") self.p0 = NumParam(default=0.0, info="AC active power setting", unit="pu") self.q0 = NumParam(default=0.0, info="AC reactive power setting", unit="pu") self.vdc0 = NumParam(default=1.0, info="DC voltage setting", unit="pu", tex_name='v_{dc0}') self.k0 = NumParam(default=0.0, info="Loss coefficient - constant") self.k1 = NumParam(default=0.0, info="Loss coefficient - linear") self.k2 = NumParam(default=0.0, info="Loss coefficient - quadratic") self.droop = NumParam(default=0.0, info="Enable dc voltage droop control", unit="boolean") self.K = NumParam(default=0.0, info="Droop coefficient") self.vhigh = NumParam(default=9999, info="Upper voltage threshold in droop control", unit="pu") self.vlow = NumParam(default=0.0, info="Lower voltage threshold in droop control", unit="pu") self.vshmax = NumParam(default=1.1, info="Maximum ac interface voltage", unit="pu") self.vshmin = NumParam(default=0.9, info="Minimum ac interface voltage", unit="pu") self.Ishmax = NumParam(default=2, info="Maximum ac current", unit="pu") # define variables and equations self.flags.update({'pflow': True}) self.group = 'StaticACDC' self.gsh = ConstService( tex_name='g_{sh}', v_str='re(1/(rsh + 1j * xsh))', ) self.bsh = ConstService( tex_name='b_{sh}', v_str='im(1/(rsh + 1j * xsh))', ) self.mode = Switcher(u=self.control, options=(0, 1, 2, 3)) self.ash = Algeb( info='voltage phase behind the transformer', unit='rad', tex_name=r'\theta_{sh}', v_str='a', e_str='u * (gsh * v**2 - gsh * v * vsh * cos(a - ash) - ' 'bsh * v * vsh * sin(a - ash)) - psh', diag_eps=True, ) self.vsh = Algeb( info='voltage magnitude behind transformer', tex_name="V_{sh}", unit='p.u.', v_str='v0', e_str='u * (-bsh * v**2 - gsh * v * vsh * sin(a - ash) + ' 'bsh * v * vsh * cos(a - ash)) - qsh', diag_eps=True, ) self.psh = Algeb( info='active power injection into VSC', tex_name="P_{sh}", unit='p.u.', v_str='p0 * (mode_s0 + mode_s1)', e_str='u * (mode_s0 + mode_s1) * (p0 - psh) + ' 'u * (mode_s2 + mode_s3) * (v1 - v2 - vdc0)', diag_eps=True, ) self.qsh = Algeb( info='reactive power injection into VSC', tex_name="Q_{sh}", v_str='q0 * (mode_s0 + mode_s2)', e_str='u * (mode_s0 + mode_s2) * (q0 - qsh) + ' 'u * (mode_s1 + mode_s3) * (v0 - v)', diag_eps=True, ) self.pdc = Algeb( info='DC power injection', tex_name="P_{dc}", v_str='0', e_str='u * (gsh * vsh * vsh - gsh * v * vsh * cos(a - ash) + ' 'bsh * v * vsh * sin(a - ash)) + pdc', ) self.a.e_str = '-psh' self.v.e_str = '-qsh' self.v1.e_str = '-pdc / (v1 - v2)' self.v2.e_str = 'pdc / (v1 - v2)'
def __init__(self, system, config): Model.__init__(self, system, config) self.flags.pflow = True self.flags.tds = True self.flags.tds_init = False self.group = 'Motor' # services self.wb = ConstService(v_str='2 * pi * fn', tex_name=r'\omega_b', ) self.x0 = ConstService(v_str='xs + xm', tex_name='x_0', ) self.x1 = ConstService(v_str='xs + xr1 * xm / (xr1 + xm)', tex_name="x'", ) self.T10 = ConstService(v_str='(xr1 + xm) / (wb * rr1)', tex_name="T'_0", ) self.M = ConstService(v_str='2 * Hm', tex_name='M', ) self.aa = ConstService(v_str='c1 + c2 + c3', tex_name=r'\alpha', ) self.bb = ConstService(v_str='-c2 - 2 * c3', tex_name=r'\beta', ) # network algebraic variables self.a = ExtAlgeb(model='Bus', src='a', indexer=self.bus, tex_name=r'\theta', info='Bus voltage phase angle', e_str='+p', ename='P', tex_ename='P', ) self.v = ExtAlgeb(model='Bus', src='v', indexer=self.bus, tex_name=r'V', info='Bus voltage magnitude', e_str='+q', ename='Q', tex_ename='Q', ) self.vd = Algeb(info='d-axis voltage', e_str='-u * v * sin(a) - vd', tex_name=r'V_d', ) self.vq = Algeb(info='q-axis voltage', e_str='u * v * cos(a) - vq', tex_name=r'V_q', ) self.slip = State(tex_name=r"\sigma", e_str='u * (tm - te)', t_const=self.M, diag_eps=True, v_str='1.0 * u', ) self.p = Algeb(tex_name='P', e_str='u * (vd * Id + vq * Iq) - p', v_str='u * (vd * Id + vq * Iq)', ) self.q = Algeb(tex_name='Q', e_str='u * (vq * Id - vd * Iq) - q', v_str='u * (vq * Id - vd * Iq)', ) self.e1d = State(info='real part of 1st cage voltage', tex_name="e'_d", v_str='0.05 * u', e_str='u * (wb*slip*e1q - (e1d + (x0 - x1) * Iq)/T10)', diag_eps=True, ) self.e1q = State(info='imaginary part of 1st cage voltage', tex_name="e'_q", v_str='0.9 * u', e_str='u * (-wb*slip*e1d - (e1q - (x0 - x1) * Id)/T10)', diag_eps=True, ) self.Id = Algeb(tex_name='I_d', diag_eps=True, ) self.Iq = Algeb(tex_name='I_q', diag_eps=True, ) self.te = Algeb(tex_name=r'\tau_e', ) self.tm = Algeb(tex_name=r'\tau_m', ) self.tm.v_str = 'u * (aa + bb * slip + c2 * slip * slip)' self.tm.e_str = f'{self.tm.v_str} - tm'
def __init__(self, system, config): Model.__init__(self, system, config) self.group = 'DynLoad' self.flags.tds = True self.kps = ConstService( v_str='kpp + kpi + kpz', tex_name='K_{psum}', ) self.kqs = ConstService( v_str='kqp + kqi + kqz', tex_name='K_{qsum}', ) self.kpc = InitChecker( u=self.kps, equal=100.0, tex_name='K_{pc}', info='total `kp` and 100', ) self.kqc = InitChecker( u=self.kqs, equal=100.0, tex_name='K_{qc}', info='total `kq` and 100', ) # convert percentages to decimals self.rpp = ConstService( v_str='u * kpp / 100', tex_name='r_{pp}', ) self.rpi = ConstService( v_str='u * kpi / 100', tex_name='r_{pi}', ) self.rpz = ConstService( v_str='u * kpz / 100', tex_name='r_{pz}', ) self.rqp = ConstService( v_str='u * kqp / 100', tex_name='r_{qp}', ) self.rqi = ConstService( v_str='u * kqi / 100', tex_name='r_{qi}', ) self.rqz = ConstService( v_str='u * kqz / 100', tex_name='r_{qz}', ) self.bus = ExtParam( model='PQ', src='bus', indexer=self.pq, info='retrieved bux idx', export=False, ) 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', ) # calculate initial powers, equivalent current, and equivalent z self.pp0 = ConstService( v_str='p0 * rpp', tex_name='P_{p0}', ) self.pi0 = ConstService( v_str='p0 * rpi / v0', tex_name='P_{i0}', ) self.pz0 = ConstService( v_str='p0 * rpz / v0 / v0', tex_name='P_{z0}', ) self.qp0 = ConstService( v_str='q0 * rqp', tex_name='Q_{p0}', ) self.qi0 = ConstService( v_str='q0 * rqi / v0', tex_name='Q_{i0}', ) self.qz0 = ConstService( v_str='q0 * rqz / v0 / v0', tex_name='Q_{z0}', ) self.a = ExtAlgeb( model='Bus', src='a', indexer=self.bus, tex_name=r'\theta', e_str='pp0 + pi0*v + pz0*v*v', ename='P', tex_ename='P', ) self.v = ExtAlgeb( model='Bus', src='v', indexer=self.bus, tex_name='V', e_str='qp0 + qi0*v + qz0*v*v', ename='Q', tex_ename='Q', )
def __init__(self, system, config): Model.__init__(self, system, config) self.flags.tds = True self.group = 'RenGen' self.a = ExtAlgeb(model='Bus', src='a', indexer=self.bus, tex_name=r'\theta', info='Bus voltage angle', e_str='-Pe', ) self.v = ExtAlgeb(model='Bus', src='v', indexer=self.bus, tex_name=r'V', info='Bus voltage magnitude', e_str='-Qe', ) 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', ) self.ra = ExtParam(model='StaticGen', src='ra', indexer=self.gen, tex_name='r_a', export=False, ) self.xs = ExtParam(model='StaticGen', src='xs', indexer=self.gen, tex_name='x_s', export=False, ) # --- INITIALIZATION --- self.q0gt0 = ConstService('Indicator(q0> 0)', tex_name='z_{q0>0}', info='flags for q0 below zero', ) self.q0lt0 = ConstService('Indicator(q0< 0)', tex_name='z_{q0<0}', info='flags for q0 below zero', ) self.Ipcmd0 = ConstService('p0 / v', info='initial Ipcmd', tex_name='I_{pcmd0}', ) self.Iqcmd0 = ConstService('-q0 / v', info='initial Iqcmd', tex_name='I_{qcmd0}', ) self.Ipcmd = Algeb(tex_name='I_{pcmd}', info='current component for active power', e_str='Ipcmd0 - Ipcmd', v_str='Ipcmd0') self.Iqcmd = Algeb(tex_name='I_{qcmd}', info='current component for reactive power', e_str='Iqcmd0 - Iqcmd', v_str='Iqcmd0') # reactive power management # rate limiting logic (for fault recovery, although it does not detect any recovery) # - activate upper limit when q0 > 0 (self.q0gt0) # - activate lower limit when q0 < 0 (self.q0lt0) self.S1 = LagAntiWindupRate(u=self.Iqcmd, T=self.Tg, K=-1, lower=-9999, upper=9999, no_lower=True, no_upper=True, rate_lower=self.Iqrmin, rate_upper=self.Iqrmax, rate_lower_cond=self.q0lt0, rate_upper_cond=self.q0gt0, tex_name='S_1', info='Iqcmd delay', ) # output `S1_y` == `Iq` # piece-wise gain for low voltage active current mgnt. self.kLVG = ConstService(v_str='1 / (Lvpnt1 - Lvpnt0)', tex_name='k_{LVG}', ) self.LVG = Piecewise(u=self.v, points=('Lvpnt0', 'Lvpnt1'), funs=('0', '(v - Lvpnt0) * kLVG', '1'), info='Ip gain during low voltage', tex_name='L_{VG}', ) # piece-wise gain for LVPL self.kLVPL = ConstService(v_str='Lvplsw * Lvpl1 / (Brkpt - Zerox)', tex_name='k_{LVPL}', ) self.S2 = Lag(u=self.v, T=self.Tfltr, K=1.0, info='Voltage filter with no anti-windup', tex_name='S_2', ) self.LVPL = Piecewise(u=self.S2_y, points=('Zerox', 'Brkpt'), funs=('0 + 9999*(1-Lvplsw)', '(S2_y - Zerox) * kLVPL + 9999 * (1-Lvplsw)', '9999'), info='Low voltage Ipcmd upper limit', tex_name='L_{VPL}', ) self.S0 = LagAntiWindupRate(u=self.Ipcmd, T=self.Tg, K=1, upper=self.LVPL_y, rate_upper=self.Rrpwr, lower=-9999, rate_lower=-9999, no_lower=True, rate_no_lower=True, tex_name='S_0', ) # `S0_y` is the output `Ip` in the block diagram self.Ipout = Algeb(e_str='S0_y * LVG_y -Ipout', v_str='Ipcmd * LVG_y', info='Output Ip current', tex_name='I_{pout}', ) # high voltage part self.HVG = GainLimiter(u='v - Volim', K=self.Khv, info='High voltage gain block', lower=0, upper=999, no_upper=True, tex_name='H_{VG}' ) self.HVG.lim.no_warn = True self.Iqout = GainLimiter(u='S1_y- HVG_y', K=1, lower=self.Iolim, upper=9999, no_upper=True, info='Iq output block', tex_name='I^{qout}', ) # `Iqout_y` is the final Iq output self.Pe = Algeb(tex_name='P_e', info='Active power output', v_str='p0', e_str='Ipout * v - Pe') self.Qe = Algeb(tex_name='Q_e', info='Reactive power output', v_str='q0', e_str='Iqout_y * v - Qe')
def __init__(self, system, config): Model.__init__(self, system, config) self.group = 'SynGen' self.group_param_exception = ['Sn', 'M', 'D'] self.group_var_exception = ['vd', 'vq', 'Id', 'Iq', 'tm', 'te', 'vf', 'XadIfd'] self.flags.tds = True self.subidx = ExtParam(model='StaticGen', src='subidx', indexer=self.gen, export=False, info='Generator idx in plant; only used by PSS/E data' ) self.zs = ConstService('ra + 1j * xs', vtype=complex, info='impedance', ) self.zs2n = ConstService('ra * ra - xs * xs', info='ra^2 - xs^2', ) # get power flow solutions self.p = ExtService(model='StaticGen', src='p', indexer=self.gen, ) self.q = ExtService(model='StaticGen', src='q', indexer=self.gen, ) self.Ec = ConstService('v * exp(1j * a) +' 'conj((p + 1j * q) / (v * exp(1j * a))) * (ra + 1j * xs)', vtype=complex, tex_name='E_c', ) self.E0 = ConstService('abs(Ec)', tex_name='E_0') self.delta0 = ConstService('arg(Ec)', tex_name=r'\delta_0') # Note: `Vts` and `fts` are assigned by TimeSeries before initializing this model. self.Vts = ConstService() self.fts = ConstService() self.ifscale = ConstService('1/fscale', tex_name='1/f_{scale}') self.iVscale = ConstService('1/Vscale', tex_name='1/V_{scale}') self.foffs = ConstService('fts * ifscale - 1', tex_name='f_{offs}') self.Voffs = ConstService('Vts * iVscale - E0', tex_name='V_{offs}') self.Vflt = State(info='filtered voltage', t_const=self.Tv, v_str='(iVscale * Vts - Voffs)', e_str='(iVscale * Vts - Voffs) - Vflt', unit='pu', tex_name='V_{flt}', ) self.omega = State(info='filtered frequency', t_const=self.Tf, v_str='fts * ifscale - foffs', e_str='(ifscale * fts - foffs) - omega', unit='pu', tex_name=r'\omega', ) self.delta = State(info='rotor angle', unit='rad', v_str='delta0', tex_name=r'\delta', e_str='u * (2 * pi * fn) * (omega - 1)', ) # --- Power injections are obtained by sympy --- # >>> from sympy import symbols, sin, cos, conjugate # >>> Vflt, delta, v, a, ra, xs = symbols('Vflt delta v a ra xs', real=True) # >>> S = -v * (cos(a) + 1j*sin(a)) * \ # conjugate((Vflt * (cos(delta)+1j*sin(delta)) - v*(cos(a)+1j*sin(a))) / (ra+1j*xs)) # >>> S.simplify().as_real_imag() self.a = ExtAlgeb(model='Bus', src='a', indexer=self.bus, tex_name=r'\theta', info='Bus voltage phase angle', e_str='Vflt*v*xs*sin(a - delta)/(ra*ra + xs*xs) + ' 'ra*v*(-Vflt*cos(a - delta) + v)/(ra*ra + xs*xs)', ename='P', tex_ename='P', ) self.v = ExtAlgeb(model='Bus', src='v', indexer=self.bus, tex_name=r'V', info='Bus voltage magnitude', ename='Q', e_str='-Vflt*ra*v*sin(a - delta)/(ra*ra + xs*xs) + ' 'v*xs*(-Vflt*cos(a - delta) + v)/(ra*ra + xs*xs)', tex_ename='Q', )
def __init__(self, system, config): Model.__init__(self, system, config) self.flags.tds = True self.group = 'RenGovernor' self.reg = ExtParam( model='RenExciter', src='reg', indexer=self.ree, export=False, ) self.Sn = ExtParam( model='RenGen', src='Sn', indexer=self.reg, tex_name='S_n', export=False, ) self.wge = ExtAlgeb( model='RenExciter', src='wg', indexer=self.ree, export=False, e_str='-1.0 + s2_y', ename='wg', tex_ename=r'\omega_g', ) self.Pe = ExtAlgeb(model='RenGen', src='Pe', indexer=self.reg, export=False, info='Retrieved Pe of RenGen') self.Pe0 = ExtService( model='RenGen', src='Pe', indexer=self.reg, tex_name='P_{e0}', ) self.Ht2 = ConstService(v_str='2 * Ht', tex_name='2H_t') self.Hg2 = ConstService(v_str='2 * Hg', tex_name='2H_g') self.wr0 = Algeb( tex_name=r'\omega_{r0}', unit='p.u.', v_str='w0', e_str='w0 - wr0', info='speed set point', ) self.Pm = Algeb( tex_name='P_m', info='Mechanical power', e_str='Pe0 - Pm', v_str='Pe0', ) # `s1_y` is `wt` self.s1 = Integrator( u='(Pm / s1_y) - (Kshaft * s3_y ) - pd', T=self.Ht2, K=1.0, y0='wr0', ) self.wt = AliasState(self.s1_y, tex_name=r'\omega_t') # `s2_y` is `wg` self.s2 = Integrator( u='-(Pe / s2_y) + (Kshaft * s3_y ) + pd', T=self.Hg2, K=1.0, y0='wr0', ) self.wg = AliasState(self.s2_y, tex_name=r'\omega_g') # TODO: `s3_y` needs to be properly reinitialized with the new `wr0` self.s3 = Integrator( u='s1_y - s2_y', T=1.0, K=1.0, y0='Pe0 / wr0 / Kshaft', ) self.pd = Algeb( tex_name='P_d', info='Output after damping', v_str='0.0', e_str='Dshaft * (s1_y - s2_y) - pd', )
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', default_model='BusFreq', info='bus frequency idx') # 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, R=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): # 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=complex) self._S = ConstService(v_str='p0 - 1j * q0', tex_name='S', info='complex terminal power', vtype=complex) self._Zs = ConstService(v_str='ra + 1j * xd2', tex_name='Z_s', info='equivalent impedance', vtype=complex) self._It = ConstService(v_str='_S / conj(_V)', tex_name='I_t', info='complex terminal current', vtype=complex) self._Is = ConstService(tex_name='I_s', v_str='_It + _V / _Zs', info='equivalent current source', vtype=complex) self.psi20 = ConstService( tex_name=r"\psi''_0", v_str='_Is * _Zs', info='sub-transient flux linkage in stator reference', vtype=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=complex) self.psi20_dq = ConstService(tex_name=r"\psi''_{0,dq}", v_str='psi20 * _Tdq', vtype=complex) self.It_dq = ConstService(tex_name=r"I_{t,dq}", v_str='conj(_It * _Tdq)', vtype=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='u * 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='u * 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='u * 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, system, config): Model.__init__(self, system, config) self.flags.tds = True self.group = 'RenGen' self.a = ExtAlgeb( model='Bus', src='a', indexer=self.bus, tex_name=r'\theta', info='Bus voltage angle', e_str='-u * Pe', ) self.v = ExtAlgeb( model='Bus', src='v', indexer=self.bus, tex_name='V', info='Bus voltage magnitude', e_str='-u * Qe', ) self.p0s = ExtService( model='StaticGen', src='p', indexer=self.gen, tex_name=r'P_{0s}', info='total P of the static gen', ) self.q0s = ExtService( model='StaticGen', src='q', indexer=self.gen, tex_name=r'Q_{0s}', info='total Q of the static gen', ) self.Pref = ConstService( v_str='gammap * p0s', tex_name='P_{ref}', info='Initial P for the REGCV1 device', ) self.Qref = ConstService( v_str='gammaq * q0s', tex_name='Q_{ref}', info='Initial Q for the REGCV1 device', ) self.vref = ExtService( model='StaticGen', src='v', indexer=self.gen, tex_name=r'V_{ref}', info='initial v of the static gen', ) # --- INITIALIZATION --- self.ixs = ConstService( v_str='1/xs', tex_name=r'1/xs', ) self.Id0 = ConstService( tex_name=r'I_{d0}', v_str='u * Pref / v', ) self.Iq0 = ConstService( tex_name=r'I_{q0}', v_str='- u * Qref / v', ) self.vd0 = ConstService( tex_name=r'v_{d0}', v_str='u * v', ) self.vq0 = ConstService( tex_name=r'v_{q0}', v_str='0', ) self.Pref2 = Algeb( tex_name=r'P_{ref2}', info='active power reference after adjusting by frequency', e_str='u * Pref - dw * kw - Pref2', v_str='u * Pref') self.vref2 = Algeb( tex_name=r'v_{ref2}', info='voltage reference after adjusted by reactive power', e_str='(u * Qref - Qe) * kv + vref - vref2', v_str='u * vref') self.dw = State(info='delta virtual rotor speed', unit='pu (Hz)', v_str='0', tex_name=r'\Delta\omega', e_str='Pref2 - Pe - D * dw', t_const=self.M) self.omega = Algeb(info='virtual rotor speed', unit='pu (Hz)', v_str='u', tex_name=r'\omega', e_str='1 + dw - omega') self.delta = State(info='virtual delta', unit='rad', v_str='a', tex_name=r'\delta', e_str='2 * pi * fn * dw') self.vd = Algeb(tex_name='V_d', info='d-axis voltage', e_str='u * v * cos(delta - a) - vd', v_str='vd0') self.vq = Algeb(tex_name='V_q', info='q-axis voltage', e_str='- u * v * sin(delta - a) - vq', v_str='vq0') self.Pe = Algeb(tex_name='P_e', info='active power injection from VSC', e_str='vd * Id + vq * Iq - Pe', v_str='Pref') self.Qe = Algeb(tex_name='Q_e', info='reactive power injection from VSC', e_str='- vd * Iq + vq * Id - Qe', v_str='Qref') self.Id = Algeb( tex_name='I_d', info='d-axis current', v_str='Id0', diag_eps=True, ) self.Iq = Algeb( tex_name='I_q', info='q-axis current', v_str='Iq0', diag_eps=True, )
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=complex, ) self._S = ConstService( v_str='p0 - 1j * q0', tex_name='S', vtype=complex, ) self._I = ConstService( v_str='_S / conj(_V)', tex_name='I_c', vtype=complex, ) self._E = ConstService(tex_name='E', vtype=complex) self._deltac = ConstService(tex_name=r'\delta_c', vtype=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=complex) self.Idq = ConstService( v_str='u * (_I * exp(1j * 0.5 * pi - _deltac))', tex_name='I_{dq}', vtype=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): TGBase.__init__(self, system, config, add_sn=False) # check if K1-K8 sums up to 1 self._sumK18 = ConstService(v_str='K1+K2+K3+K4+K5+K6+K7+K8', info='summation of K1-K8', tex_name=r"\sum_{i=1}^8 K_i") self._K18c1 = InitChecker( u=self._sumK18, info='summation of K1-K8 and 1.0', equal=1, ) # check if `tm0 * (K2 + k4 + K6 + K8) = tm02 *(K1 + K3 + K5 + K7) self._tm0K2 = PostInitService( info='mul of tm0 and (K2+K4+K6+K8)', v_str='zsyn2*tm0*(K2+K4+K6+K8)', ) self._tm02K1 = PostInitService( info='mul of tm02 and (K1+K3+K5+K6)', v_str='tm02*(K1+K3+K5+K7)', ) self._Pc = InitChecker( u=self._tm0K2, info='proportionality of tm0 and tm02', equal=self._tm02K1, ) self.Sg2 = ExtParam( src='Sn', model='SynGen', indexer=self.syn2, allow_none=True, default=0.0, tex_name='S_{n2}', info='Rated power of Syn2', unit='MVA', export=False, ) self.Sg12 = ParamCalc( self.Sg, self.Sg2, func=np.add, tex_name="S_{g12}", info='Sum of generator power ratings', ) self.Sn = NumSelect( self.Tn, fallback=self.Sg12, tex_name='S_n', info='Turbine or Gen rating', ) self.zsyn2 = FlagValue( self.syn2, value=None, tex_name='z_{syn2}', info='Exist flags for syn2', ) self.tm02 = ExtService( src='tm', model='SynGen', indexer=self.syn2, tex_name=r'\tau_{m02}', info='Initial mechanical input of syn2', allow_none=True, default=0.0, ) self.tm012 = ConstService( info='total turbine power', v_str='tm0 + tm02', ) self.tm2 = ExtAlgeb( src='tm', model='SynGen', indexer=self.syn2, allow_none=True, tex_name=r'\tau_{m2}', e_str='zsyn2 * u * (PLP - tm02)', info='Mechanical power to syn2', ) self.wd = Algeb( info='Generator under speed', unit='p.u.', tex_name=r'\omega_{dev}', v_str='0', e_str='(wref - omega) - wd', ) self.LL = LeadLag( u=self.wd, T1=self.T2, T2=self.T1, K=self.K, info='Signal conditioning for wd', ) # `P0` == `tm0` self.vs = Algeb( info='Valve speed', tex_name='V_s', v_str='0', e_str='(LL_y + tm012 + paux - IAW_y) / T3 - vs', ) self.HL = HardLimiter( u=self.vs, lower=self.UC, upper=self.UO, info='Limiter on valve acceleration', ) self.vsl = Algeb( info='Valve move speed after limiter', tex_name='V_{sl}', v_str='vs * HL_zi + UC * HL_zl + UO * HL_zu', e_str='vs * HL_zi + UC * HL_zl + UO * HL_zu - vsl', ) self.IAW = IntegratorAntiWindup( u=self.vsl, T=1, K=1, y0=self.tm012, lower=self.PMIN, upper=self.PMAX, info='Valve position integrator', ) self.L4 = Lag( u=self.IAW_y, T=self.T4, K=1, info='first process', ) self.L5 = Lag( u=self.L4_y, T=self.T5, K=1, info='second (reheat) process', ) self.L6 = Lag( u=self.L5_y, T=self.T6, K=1, info='third process', ) self.L7 = Lag( u=self.L6_y, T=self.T7, K=1, info='fourth (second reheat) process', ) self.PHP = Algeb( info='HP output', tex_name='P_{HP}', v_str='K1*L4_y + K3*L5_y + K5*L6_y + K7*L7_y', e_str='K1*L4_y + K3*L5_y + K5*L6_y + K7*L7_y - PHP', ) self.PLP = Algeb( info='LP output', tex_name='P_{LP}', v_str='K2*L4_y + K4*L5_y + K6*L6_y + K8*L7_y', e_str='K2*L4_y + K4*L5_y + K6*L6_y + K8*L7_y - PLP', ) self.pout.e_str = 'PHP - pout'
def __init__(self, system, config): TGBase.__init__(self, system, config) self.VELMn = ConstService( v_str='-VELM', tex_name='-VELM', ) self.tr = ConstService( v_str='r * Tr', tex_name='r*Tr', ) self.gr = ConstService( v_str='1/r', tex_name='1/r', ) self.ratel = ConstService( v_str='- VELM - gr', tex_name='rate_l', ) self.rateu = ConstService( v_str='VELM - gr', tex_name='rate_u', ) self.q0 = ConstService( v_str='tm0 / At + qNL', tex_name='q_0', ) self.pref = Algeb( info='Reference power input', tex_name='P_{ref}', v_str='R * q0', e_str='R * q0 - pref', ) self.wd = Algeb( info='Generator speed deviation', unit='p.u.', tex_name=r'\omega_{dev}', v_str='0', e_str='ue * (omega - wref) - wd', ) self.pd = Algeb( info='Pref plus speed deviation times gain', unit='p.u.', tex_name="P_d", v_str='0', e_str='ue * (- wd + pref + paux - R * dg) - pd', ) self.LG = Lag( u=self.pd, K=1, T=self.Tf, info='filter after speed deviation (e)', ) self.gtpos = State(info='State in gate position (c)', unit='rad', v_str='q0', tex_name=r'\delta', e_str='LG_y') self.dgl = VarService( tex_name='dg_{lower}', info='dg lower limit', v_str='- VELM - gr * LG_y', ) self.dgu = VarService( tex_name='dg_{upper}', info='dg upper limit', v_str='VELM - gr * LG_y', ) self.dg_lim = AntiWindupRate( u=self.gtpos, lower=self.GMIN, upper=self.GMAX, rate_lower=self.dgl, rate_upper=self.dgu, tex_name='lim_{dg}', info='gate velocity and position limiter', ) self.dg = Algeb( info='desired gate (c)', unit='p.u.', tex_name="dg", v_str='q0', e_str='gtpos + gr * LG_y - dg', ) self.LAG = Lag( u=self.dg, K=1, T=self.Tg, info='gate opening (g)', ) self.h = Algeb( info='turbine head', unit='p.u.', tex_name="h", e_str='q_y**2 / LAG_y**2 - h', v_str='1', ) self.q = Integrator(u="1 - q_y**2 / LAG_y**2", T=self.Tw, K=1, y0='q0', check_init=False, info="turbine flow (q)") self.pout.e_str = 'ue * (At * h * (q_y - qNL) - Dt * wd * LAG_y) - pout'