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): # 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, E1, SE1, E2, SE2, name=None, tex_name=None, info=None): Block.__init__(self, name=name, tex_name=tex_name, info=info) self._E1 = E1 self._E2 = E2 self._SE1 = SE1 self._SE2 = SE2 self.zE1 = FlagValue( self._E1, value=0., info='Flag non-zeros in E1', tex_name='z^{E1}', ) self.zE2 = FlagValue( self._E2, value=0., info='Flag non-zeros in E2', tex_name='z^{E2}', ) self.zSE1 = FlagValue( self._SE1, value=0., info='Flag non-zeros in SE1', tex_name='z^{SE1}', ) self.zSE2 = FlagValue(self._SE2, value=0., info='Flag non-zeros in SE2', tex_name='z^{SE2}') # disallow E1 = E2 != 0 since the curve fitting will fail self.E12c = InitChecker( self._E1, not_equal=self._E2, info='E1 and E2 after correction', error_out=True, ) # data correction for E1, E2, SE1 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='Saturation gain', tex_name='A^e', ) self.B = ConstService( info='Exponential coef. in saturation', tex_name='B^e', ) self.vars = { 'E1': self.E1, 'E2': self.E2, 'SE1': self.SE1, 'SE2': self.SE2, 'zE1': self.zE1, 'zE2': self.zE2, 'zSE1': self.zSE1, 'zSE2': self.zSE2, 'A': self.A, 'B': self.B, }
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 = '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', ) self.v = ExtAlgeb( model='Bus', src='v', indexer=self.bus, tex_name='V', e_str='qp0 + qi0*v + qz0*v*v', )