def __init__(self, system, config): EXDC2Model.__init__(self, system, config) self.VRTMAX = VarService('VRMAX * v', tex_name='V_{RMAX}V_T') self.VRTMIN = VarService('VRMIN * v', tex_name='V_{RMIN}V_T') self.LA.upper = self.VRTMAX self.LA.lower = self.VRTMIN
def __init__(self): GENROUModel.__init__(self) delattr(self, 'psi2q') delattr(self, 'psi2d') delattr(self, 'psi2') self.algebs.pop('psi2q') self.algebs.pop('psi2d') self.algebs.pop('psi2') self.psi2q = VarService( tex_name=r"\psi_{aq}", info='q-axis air gap flux', v_str='gq1*e1d + (1-gq1)*e2q', ) self.psi2d = VarService( tex_name=r"\psi_{ad}", info='d-axis air gap flux', v_str='gd1*e1q + gd2*(xd1-xl)*e2d', ) self.psi2 = VarService( tex_name=r"\psi_a", info='air gap flux magnitude', v_str='sqrt(psi2d **2 + psi2q ** 2)', ) # fix the reference to `psi2` self.SL.u = self.psi2
def __init__(self, system, config): REGCA1Model.__init__(self, system, config) delattr(self, 'LVG') delattr(self, 'LVG_y') delattr(self, 'Ipout') self.blocks.pop('LVG') self.algebs.pop('LVG_y') self.algebs.pop('Ipout') self.LVG_y = VarService(v_str='Piecewise((0, v <= Lvpnt0), \ ((v - Lvpnt0) * kLVG, v <= Lvpnt1), \ (1, v > Lvpnt1), \ (0, True))') self.Ipout = VarService(v_str='S0_y * LVG_y')
def __init__(self, system, config): super().__init__(system, config) # --- split `Verr`` --- delattr(self, 'Verr') self.algebs.pop('Verr') self.Verr = VarService(v_str='Vref0 - s0_y') self.dbV.u = self.Verr self.dbV.db.u = self.Verr # --- split `Iqinj` --- delattr(self, 'Iqinj') self.algebs.pop('Iqinj') # Gain after dbB Iqv = "(dbV_y * Kqv)" Iqinj = f'{Iqv} * Volt_dip + ' \ f'(1 - Volt_dip) * fThld * ({Iqv} * nThld + Iqfrz * pThld)' self.Iqinj = VarService(v_str=Iqinj)
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): 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): ExcBase.__init__(self, system, config) self.KPC = ConstService(v_str='KP * exp(1j * radians(THETAP))', tex_name='K_{PC}', info='KP polar THETAP', vtype=np.complex) # vd, vq, Id, Iq from SynGen self.vd = ExtAlgeb( src='vd', model='SynGen', indexer=self.syn, tex_name=r'V_d', info='d-axis machine voltage', ) self.vq = ExtAlgeb( src='vq', model='SynGen', indexer=self.syn, tex_name=r'V_q', info='q-axis machine voltage', ) self.Id = ExtAlgeb( src='Id', model='SynGen', indexer=self.syn, tex_name=r'I_d', info='d-axis machine current', ) self.Iq = ExtAlgeb( src='Iq', model='SynGen', indexer=self.syn, tex_name=r'I_q', info='q-axis machine current', ) # control block begin self.LG = Lag( self.v, T=self.TR, K=1, info='Voltage transducer', ) self.UEL = Algeb(info='Interface var for under exc. limiter', tex_name='U_{EL}', v_str='0', e_str='0 - UEL') self.VE = VarService( tex_name='V_E', info='VE', v_str='Abs(KPC*(vd + 1j*vq) + 1j*(KI + KPC*XL)*(Id + 1j*Iq))', ) self.IN = Algeb( tex_name='I_N', info='Input to FEX', v_str='KC * XadIfd / VE', e_str='KC * XadIfd / VE - IN', ) 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.VBMIN = dummify(-9999) self.VGMIN = dummify(-9999) self.VB = GainLimiter( u='VE*FEX_y', K=1, upper=self.VBMAX, lower=self.VBMIN, no_lower=True, info='VB with limiter', ) self.VG = GainLimiter( u=self.vout, K=self.KG, upper=self.VGMAX, lower=self.VGMIN, no_lower=True, info='Feedback gain with HL', ) self.vrs = Algeb( tex_name='V_{RS}', info='VR subtract feedback VG', v_str='vf0 / VB_y / KM', e_str='LAW1_y - VG_y - vrs', ) self.vref = Algeb( info='Reference voltage input', tex_name='V_{ref}', unit='p.u.', v_str='(vrs + VG_y) / KA + v', e_str='vref0 - vref', ) self.vref0 = PostInitService( info='Initial reference voltage input', tex_name='V_{ref0}', v_str='vref', ) # input excitation voltages; PSS outputs summed at vi 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.vil = Algeb(info='Input voltage after limit', tex_name='V_{il}', v_str='HLI_zi*vi + HLI_zl*VIMIN + HLI_zu*VIMAX', e_str='HLI_zi*vi + HLI_zl*VIMIN + HLI_zu*VIMAX - vil') self.HG = HVGate( u1=self.UEL, u2=self.vil, info='HVGate for under excitation', ) self.LL = LeadLag( u=self.HG_y, T1=self.TC, T2=self.TB, info='Regulator', zero_out=True, ) # LL_y == VA self.LAW1 = LagAntiWindup( u=self.LL_y, T=self.TA, K=self.KA, lower=self.VRMIN, upper=self.VRMAX, info='Lag AW on VR', ) # LAW1_y == VR self.HLI = HardLimiter( u=self.vi, lower=self.VIMIN, upper=self.VIMAX, info='Input limiter', ) self.LAW2 = LagAntiWindup( u=self.vrs, T=self.TM, K=self.KM, lower=self.VMMIN, upper=self.VMMAX, info='Lag AW on VM', ) # LAW2_y == VM self.vout.e_str = 'VB_y * LAW2_y - vout'
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', ) self.LG = Lag( u=self.v, T=self.TR, K=1, info='Transducer delay', ) self.SAT = ExcQuadSat( self.E1, self.SE1, self.E2, self.SE2, info='Field voltage saturation', ) self.Se0 = ConstService( tex_name='S_{e0}', v_str='(vf0>SAT_A) * SAT_B*(SAT_A-vf0) ** 2 / vf0', ) self.vfe0 = ConstService( v_str='vf0 * (KE + Se0)', tex_name='V_{FE0}', ) self.vref0 = ConstService( info='Initial reference voltage input', tex_name='V_{ref0}', v_str='v + vfe0 / KA', ) self.vref = Algeb(info='Reference voltage input', tex_name='V_{ref}', unit='p.u.', v_str='vref0', e_str='vref0 - vref') self.vi = Algeb( info='Total input voltages', tex_name='V_i', unit='p.u.', v_str='vref0 - v', e_str='(vref - v - WF_y) - vi', ) self.LL = LeadLag( u=self.vi, T1=self.TC, T2=self.TB, info='Lead-lag compensator', zero_out=True, ) self.UEL = Algeb(info='Interface var for under exc. limiter', tex_name='U_{EL}', v_str='0', e_str='0 - UEL') self.HG = HVGate( u1=self.UEL, u2=self.LL_y, info='HVGate for under excitation', ) self.VRU = VarService( v_str='VRMAXc * v', tex_name='V_T V_{RMAX}', ) self.VRL = VarService( v_str='VRMIN * v', tex_name='V_T V_{RMIN}', ) # TODO: WARNING: HVGate is temporarily skipped self.LA = LagAntiWindup( u=self.LL_y, T=self.TA, K=self.KA, upper=self.VRU, lower=self.VRL, info='Anti-windup lag', ) # LA_y == VR # `LessThan` may be causing memory issue in (SL_z0 * vout) - uncertain yet 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.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.WF = Washout(u=self.INT_y, T=self.TF1, K=self.KF, info='Feedback to input') self.vout.e_str = 'INT_y - vout'
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, config): ExcBase.__init__(self, system, config) # vd, vq, Id, Iq from SynGen self.vd = ExtAlgeb( src='vd', model='SynGen', indexer=self.syn, tex_name=r'V_d', info='d-axis machine voltage', ) self.vq = ExtAlgeb( src='vq', model='SynGen', indexer=self.syn, tex_name=r'V_q', info='q-axis machine voltage', ) self.Id = ExtAlgeb( src='Id', model='SynGen', indexer=self.syn, tex_name=r'I_d', info='d-axis machine current', ) self.Iq = ExtAlgeb( src='Iq', model='SynGen', indexer=self.syn, tex_name=r'I_q', info='q-axis machine current', ) self.VE = VarService( tex_name=r'V_{E}', info=r'V_{E}', v_str='Abs(KP * (vd + 1j*vq) + 1j*KI*(Id + 1j*Iq))', ) self.V40 = ConstService('sqrt(VE ** 2 - (0.78 * XadIfd) ** 2)') self.VR0 = ConstService(info='Initial VR', tex_name='V_{R0}', v_str='vf0 * KE - V40') self.vb0 = ConstService(info='Initial vb', tex_name='V_{b0}', v_str='VR0 / KA') # 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', ) self.LG = Lag(u=self.v, T=self.TR, K=1, info='Sensing delay') ExcVsum.__init__(self) self.vref.v_str = 'v + vb0' self.vref0 = PostInitService(info='Constant vref', tex_name='V_{ref0}', v_str='vref') # NOTE: for offline exciters, `vi` equation ignores ext. voltage changes 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='vref - v', diag_eps=True, ) self.LA3 = LagAntiWindup( u='ue * (vi - WF_y)', T=self.TA, K=self.KA, upper=self.VRMAXc, lower=self.VRMIN, info=r'V_{R}, Lag Anti-Windup', ) # LA3_y is V_R # FIXME: antiwindup out of limit is not warned of in initialization self.zeros = ConstService(v_str='0.0') self.LA1 = Lag( 'ue * (VB_y * HL_zi + VBMAX * HL_zu)', T=self.TE, K=1, D=self.KE, ) self.WF = Washout(u=self.LA1_y, T=self.TF, K=self.KF, info='V_F, stablizing circuit feedback, washout') self.SQE = Algeb( tex_name=r'SQE', info=r'Square of error after mul', v_str='VE ** 2 - (0.78 * XadIfd) ** 2', e_str='VE ** 2 - (0.78 * XadIfd) ** 2 - SQE', ) self.SL = LessThan(u=self.zeros, bound=self.SQE, equal=False, enable=True, cache=False) self.VB = Piecewise(self.SQE, points=(0, ), funs=('ue * LA3_y', 'ue * (sqrt(SQE) + LA3_y)')) self.HL = HardLimiter( u=self.VB_y, lower=self.zeros, upper=self.VBMAX, info='Hard limiter for VB', ) self.vout.e_str = 'ue * (LA1_y - vout)'
def __init__(self, system, config): ExcBase.__init__(self, system, config) self.config.add(OrderedDict(( ('ksr', 2), ('ksm', 2), ))) self.config.add_extra( '_help', ksr='Tracking gain for outer PI controller', ksm='Tracking gain for inner PI controller', ) self.config.add_extra( '_tex', ksr='K_{sr}', ksm='K_{sm}', ) self.KPC = ConstService(v_str='KP * exp(1j * radians(THETAP))', tex_name='K_{PC}', info='KP polar THETAP', vtype=complex) # vd, vq, Id, Iq from SynGen self.vd = ExtAlgeb( src='vd', model='SynGen', indexer=self.syn, tex_name=r'V_d', info='d-axis machine voltage', ) self.vq = ExtAlgeb( src='vq', model='SynGen', indexer=self.syn, tex_name=r'V_q', info='q-axis machine voltage', ) self.Id = ExtAlgeb( src='Id', model='SynGen', indexer=self.syn, tex_name=r'I_d', info='d-axis machine current', ) self.Iq = ExtAlgeb( src='Iq', model='SynGen', indexer=self.syn, tex_name=r'I_q', info='q-axis machine current', ) # control block begin self.LG = Lag( self.v, T=self.TR, K=1, info='Voltage transducer', ) self.UEL = Algeb(info='Interface var for under exc. limiter', tex_name='U_{EL}', v_str='0', e_str='0 - UEL') # lower part: VB signal self.VE = VarService( tex_name='V_E', info='VE', v_str='Abs(KPC*(vd + 1j*vq) + 1j*(KI + KPC*XL)*(Id + 1j*Iq))', ) self.IN = Algeb( tex_name='I_N', info='Input to FEX', v_str='safe_div(KC * XadIfd, VE)', e_str='ue * (KC * XadIfd - VE * 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.VBMIN = dummify(-9999) self.VGMIN = dummify(-9999) self.VB = GainLimiter( u='VE*FEX_y', K=1, R=1, upper=self.VBMAX, lower=self.VBMIN, no_lower=True, info='VB with limiter', ) self.VG = GainLimiter( u=self.vout, K=self.KG, R=1, upper=self.VGMAX, lower=self.VGMIN, no_lower=True, info='Feedback gain with HL', ) self.vref = Algeb(info='Reference voltage input', tex_name='V_{ref}', unit='p.u.', v_str='v', e_str='vref0 - vref') self.vref0 = PostInitService( info='Const reference voltage', tex_name='V_{ref0}', v_str='vref', ) 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.PI1 = PITrackAW(u=self.vi, kp=self.KPR, ki=self.KIR, ks=self.config.ksr, lower=self.VRMIN, upper=self.VRMAX, x0='VG_y') self.LA = Lag( u=self.PI1_y, T=self.TA, K=1.0, info='Regulation delay', ) self.PI2 = PITrackAW( u='LA_y - VG_y', kp=self.KPM, ki=self.KIM, ks=self.config.ksm, lower=self.VMMIN, upper=self.VMMAX, x0='safe_div(vf0, VB_y)', ) # TODO: add back LV Gate self.vout.e_str = 'VB_y * PI2_y - vout'
def __init__(self, system, config): ExcBase.__init__(self, system, config) # vd, vq, Id, Iq from SynGen self.vd = ExtAlgeb( src='vd', model='SynGen', indexer=self.syn, tex_name=r'V_d', info='d-axis machine voltage', ) self.vq = ExtAlgeb( src='vq', model='SynGen', indexer=self.syn, tex_name=r'V_q', info='q-axis machine voltage', ) self.Id = ExtAlgeb( src='Id', model='SynGen', indexer=self.syn, tex_name=r'I_d', info='d-axis machine current', ) self.Iq = ExtAlgeb( src='Iq', model='SynGen', indexer=self.syn, tex_name=r'I_q', info='q-axis machine current', ) self.VE = VarService( tex_name=r'V_{E}', info=r'V_{E}', v_str='Abs(KP * (vd + 1j*vq) + 1j*KI*(Id + 1j*Iq))', ) self.V40 = ConstService('sqrt(VE ** 2 - (0.78 * XadIfd) ** 2)') self.VR0 = ConstService(info='Initial VR', tex_name='V_{R0}', v_str='vf0 * KE - V40') self.vb0 = ConstService(info='Initial vb', tex_name='V_{b0}', v_str='VR0 / KA') # 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', ) self.LG = Lag(u=self.v, T=self.TR, K=1, info='Sensing delay') ExcVsum.__init__(self) self.vref.v_str = 'v + vb0' self.vref0 = PostInitService(info='Constant vref', tex_name='V_{ref0}', v_str='vref') # NOTE: for offline exciters, `vi` equation ignores ext. voltage changes 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.LA3 = LagAntiWindup( u='ue * (vi - WF_y)', T=self.TA, K=self.KA, upper=self.VRMAXc, lower=self.VRMIN, info=r'V_{R}, Lag Anti-Windup', ) # LA3_y is V_R self.zero = ConstService(v_str='0.0') self.one = ConstService(v_str='1.0') self.LA1 = LagAntiWindup( u='ue * (LA3_y + V4)', T=self.TE, K=self.one, D=self.KE, upper=self.VBMAX, lower=self.zero, info=r'E_{FD}, vout, Lag Anti-Windup', ) # LA1_y is final output self.WF = Washout(u=self.LA1_y, T=self.TF, K=self.KF, info='V_F, stablizing circuit feedback, washout') self.SQE = VarService( tex_name=r'SQE', info=r'Square Error', v_str='VE ** 2 - (0.78 * XadIfd) ** 2', ) self.SL = LessThan(u=self.zero, bound=self.SQE, equal=False, enable=True, cache=False) self.V4 = VarService( tex_name='V_4', v_str='SL_z1 * sqrt(SQE)', ) self.vout.e_str = 'ue * (LA1_y - vout)'
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'
def __init__(self, system, config): ExcBase.__init__(self, system, config) self.flags.nr_iter = True ExcVsum.__init__(self) self.UEL0.v_str = '-999' self.OEL0.v_str = '999' self.ulim = ConstService('9999') self.llim = ConstService('-9999') self.SWUEL = Switcher(u=self.UELc, options=[0, 1, 2, 3], tex_name='SW_{UEL}', cache=True) self.SWVOS = Switcher(u=self.VOSc, options=[0, 1, 2], tex_name='SW_{VOS}', cache=True) # control block begin self.LG = Lag( self.v, T=self.TR, K=1, info='Voltage transducer', ) self.SG0 = ConstService(v_str='0', info='SG initial value.') self.SG = Algeb( tex_name='SG', info='SG', v_str='SG0', e_str='SG0 - SG', ) self.zero = ConstService('0') self.LR = GainLimiter( u='XadIfd - ILR', K=self.KLR, R=1, upper=self.ulim, lower=self.zero, no_upper=True, info='Exciter output current gain limiter', ) self.VA0 = PostInitService(tex_name='V_{A0}', v_str='vf0 - SWVOS_s2 * SG + LR_y', info='VA (LA_y) initial value') self.vref.v_str = 'ue * (v + (vf0 - SWVOS_s2 * SG + LR_y) / KA - SWVOS_s1 * SG - SWUEL_s1 * UEL)' self.vref.v_iter = 'ue * (v + (vf0 - SWVOS_s2 * SG + LR_y) / KA - SWVOS_s1 * SG - SWUEL_s1 * UEL)' self.vref0 = PostInitService( info='Initial reference voltage input', tex_name='V_{ref0}', v_str='vref', ) self.vi = Algeb( info='Total input voltages', tex_name='V_i', unit='p.u.', e_str= 'ue * (-LG_y + vref - WF_y + SWUEL_s1 * UEL + SWVOS_s1 * SG + Vs) - vi', v_iter= 'ue * (-LG_y + vref - WF_y + SWUEL_s1 * UEL + SWVOS_s1 * SG + Vs)', v_str= 'ue * (-LG_y + vref - WF_y + SWUEL_s1 * UEL + SWVOS_s1 * SG + Vs)', ) self.vil = GainLimiter( u=self.vi, K=1, R=1, upper=self.VIMAX, lower=self.VIMIN, info='Exciter voltage input limiter', ) self.UEL2 = Algeb( tex_name='UEL_2', info='UEL_2 as HVG1 u1', v_str='ue * (SWUEL_s2 * UEL + (1 - SWUEL_s2) * llim)', e_str='ue * (SWUEL_s2 * UEL + (1 - SWUEL_s2) * llim) - UEL2', ) self.HVG1 = HVGate( u1=self.UEL2, u2=self.vil_y, info='HVGate after V_I', ) self.LL = LeadLag( u=self.HVG1_y, T1=self.TC, T2=self.TB, info='Lead-lag compensator', zero_out=True, ) self.LL1 = LeadLag( u=self.LL_y, T1=self.TC1, T2=self.TB1, info='Lead-lag compensator 1', zero_out=True, ) self.LA = LagAntiWindup( u=self.LL1_y, T=self.TA, K=self.KA, upper=self.VAMAX, lower=self.VAMIN, info='V_A, Anti-windup lag', ) # LA_y is VA self.vas = Algeb( tex_name=r'V_{As}', info='V_A after subtraction, as HVG u2', v_str='ue * (SWVOS_s2 * SG + LA_y - LR_y)', v_iter='ue * (SWVOS_s2 * SG + LA_y - LR_y)', e_str='ue * (SWVOS_s2 * SG + LA_y - LR_y) - vas', ) self.UEL3 = Algeb( tex_name='UEL_3', info='UEL_3 as HVG u1', v_str='ue * (SWUEL_s3 * UEL + (1 - SWUEL_s3) * llim)', e_str='ue * (SWUEL_s3 * UEL + (1 - SWUEL_s3) * llim) - UEL3', ) self.HVG = HVGate( u1=self.UEL3, u2=self.vas, info='HVGate for under excitation', ) self.LVG = LVGate( u1=self.HVG_y, u2=self.OEL, info='HVGate for over excitation', ) # vd, vq, Id, Iq from SynGen self.vd = ExtAlgeb( src='vd', model='SynGen', indexer=self.syn, tex_name=r'V_d', info='d-axis machine voltage', ) self.vq = ExtAlgeb( src='vq', model='SynGen', indexer=self.syn, tex_name=r'V_q', info='q-axis machine voltage', ) self.efdu = VarService( info='Output exciter voltage upper bound', tex_name=r'efd_{u}', v_str='Abs(vd + 1j*vq) * VRMAX - KC * XadIfd', ) self.efdl = VarService(info='Output exciter voltage lower bound', tex_name=r'efd_{l}', v_str='Abs(vd + 1j*vq) * VRMIN') self.vol = GainLimiter( u=self.LVG_y, K=1, R=1, upper=self.efdu, lower=self.efdl, info='Exciter output limiter', ) self.WF = Washout( u=self.LVG_y, T=self.TF, K=self.KF, info='V_F, Stablizing circuit feedback', ) self.vout.e_str = 'ue * vol_y - vout'
def __init__(self, system, config): Model.__init__(self, system, config) self.group = 'VoltComp' self.flags.tds = True # retrieve indices of connected generator, bus, and bus freq self.syn = ExtParam(model='Exciter', src='syn', indexer=self.avr, export=False, info='Retrieved generator idx', vtype=str) self.vf0 = ExtService(src='vf', model='SynGen', indexer=self.syn, tex_name=r'v_{f0}', info='Steady state excitation voltage') # from Bus self.v = ExtAlgeb( model='SynGen', src='v', indexer=self.syn, tex_name=r'V', info='Retrieved bus terminal voltage', ) # vd, vq, Id, Iq from SynGen self.vd = ExtAlgeb( src='vd', model='SynGen', indexer=self.syn, tex_name=r'V_d', info='d-axis machine voltage', ) self.vq = ExtAlgeb( src='vq', model='SynGen', indexer=self.syn, tex_name=r'V_q', info='q-axis machine voltage', ) self.Id = ExtAlgeb( src='Id', model='SynGen', indexer=self.syn, tex_name=r'I_d', info='d-axis machine current', ) self.Iq = ExtAlgeb( src='Iq', model='SynGen', indexer=self.syn, tex_name=r'I_q', info='q-axis machine current', ) self.vct = VarService( tex_name=r'V_{CT}', v_str='u * Abs((vd + 1j*vq) + (rc + 1j * xc) * (Id + 1j*Iq))', ) # output voltage. # `vcomp` is the additional voltage to be added to bus terminal voltage self.vcomp = Algeb( info='Compensator output voltage to exciter', tex_name=r'v_{comp}', v_str='vct - u * v', e_str='vct - u * v - vcomp', ) # do not need to interface to exciters here. # Let the exciters pick up `vcomp` through back referencing self.Eterm = ExtAlgeb( model='Exciter', src='v', indexer=self.avr, v_str='vcomp', e_str='vcomp', )