Exemple #1
0
    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
Exemple #2
0
    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
Exemple #3
0
    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')
Exemple #4
0
    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)
Exemple #5
0
    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',
                         )
Exemple #6
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)
Exemple #7
0
    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'
Exemple #8
0
    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'
Exemple #9
0
    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
Exemple #10
0
    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)'
Exemple #11
0
    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'
Exemple #12
0
    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)'
Exemple #13
0
    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'
Exemple #14
0
    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'
Exemple #15
0
    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',
        )