Exemple #1
0
    def __init__(self, system, config):
        super(IEEESTModel, self).__init__(system, config)

        self.KST5 = ConstService(v_str='KS * T5', tex_name='KS*T5')
        self.SW = Switcher(
            u=self.MODE,
            options=[1, 2, 3, 4, 5, 6],
        )

        self.signal = Algeb(
            tex_name='S_{in}',
            info='Input signal',
        )
        # input signals:
        # 1 (s0) - Rotor speed deviation (p.u.)
        # 2 (s1) - Bus frequency deviation (p.u.)                    # TODO: calculate freq without reimpl.
        # 3 (s2) - Generator electrical power in Gen MVABase (p.u.)  # TODO: allow using system.config.mva
        # 4 (s3) - Generator accelerating power (p.u.)
        # 5 (s4) - Bus voltage (p.u.)
        # 6 (s5) - Derivative of p.u. bus voltage                    # TODO: memory block for calc. of derivative

        self.signal.e_str = 'SW_s0 * (1-omega) + SW_s1 * 0 + SW_s2 * te + ' \
                            'SW_s3 * (tm-tm0) + SW_s4 *v + SW_s5 * 0 - signal'

        self.F1 = Lag2ndOrd(u=self.signal, K=1, T1=self.A1, T2=self.A2)

        self.F2 = LeadLag2ndOrd(u=self.F1_y,
                                T1=self.A3,
                                T2=self.A4,
                                T3=self.A5,
                                T4=self.A6)

        self.LL1 = LeadLag(u=self.F2_y, T1=self.T1, T2=self.T2)

        self.LL2 = LeadLag(u=self.LL1_y, T1=self.T3, T2=self.T4)

        self.WO = Washout(u=self.LL2_y, T=self.T6, K=self.KST5)  # WO_y == Vss

        self.VLIM = Limiter(u=self.WO_y,
                            lower=self.LSMIN,
                            upper=self.LSMAX,
                            info='Vss limiter')

        self.Vss = Algeb(
            tex_name='V_{ss}',
            info='Voltage output before output limiter',
            e_str='VLIM_zi * WO_y + VLIM_zu * LSMAX + VLIM_zl * LSMIN - Vss')

        self.OLIM = Limiter(u=self.v,
                            lower=self.VCL,
                            upper=self.VCU,
                            info='output limiter')

        # TODO: allow ignoring VCU or VCL when zero

        self.vsout.e_str = 'OLIM_zi * Vss - vsout'
Exemple #2
0
    def __init__(self):
        # Indicatior of frequency deviation
        self.Lfl1 = Limiter(u=self.fHz,
                            lower=self.fl3, upper=self.fl1,
                            info='Frequency comparer for (fl3, fl1)',
                            equal=False, no_warn=True,
                            )

        self.Lfl2 = Limiter(u=self.fHz,
                            lower=self.fl3, upper=self.fl2,
                            info='Frequency comparer for (fl3, fl2)',
                            equal=False, no_warn=True,
                            )

        self.Lfu1 = Limiter(u=self.fHz,
                            lower=self.fu1, upper=self.fu3,
                            info='Frequency comparer for (fu1, fu3)',
                            equal=False, no_warn=True,
                            )

        self.Lfu2 = Limiter(u=self.fHz,
                            lower=self.fu2, upper=self.fu3,
                            info='Frequency comparer for (fu2, fu3)',
                            equal=False, no_warn=True,
                            )

        # Frequency deviation time continuity check

        self.IAWfl1 = IntegratorAntiWindup(u='Lfl1_zi * (1 - res) - Tfl1 / Tres * res',
                                           T=1, K=1, y0='0',
                                           lower=self.zero, upper=self.Tfl1,
                                           info='condition check for (fl3, fl1)',
                                           no_warn=True,
                                           )

        self.IAWfl2 = IntegratorAntiWindup(u='Lfl2_zi * (1 - res) - Tfl2 / Tres * res',
                                           T=1, K=1, y0='0',
                                           lower=self.zero, upper=self.Tfl2,
                                           info='condition check for (fl3, fl2)',
                                           no_warn=True,
                                           )

        self.IAWfu1 = IntegratorAntiWindup(u='Lfu1_zi * (1 - res) - Tfu1 / Tres * res',
                                           T=1, K=1, y0='0',
                                           lower=self.zero, upper=self.Tfu1,
                                           info='condition check for (fu1, fu3)',
                                           no_warn=True,
                                           )

        self.IAWfu2 = IntegratorAntiWindup(u='Lfl2_zi * (1 - res) - Tfu2 / Tres * res',
                                           T=1, K=1, y0='0',
                                           lower=self.zero, upper=self.Tfu2,
                                           info='condition check for (fu2, fu3)',
                                           no_warn=True,
                                           )
Exemple #3
0
    def test_sorted_limiter(self):
        """
        Tests for `SortedLimiter` class

        Returns
        -------

        """
        self.cmp = Limiter(self.u, self.lower, self.upper)
        self.cmp.list2array(len(self.u.v))
        self.cmp.check_var()

        self.rcmp = SortedLimiter(self.u, self.lower, self.upper, n_select=1)
        self.rcmp.list2array(len(self.u.v))
        self.rcmp.check_var()

        self.assertSequenceEqual(self.rcmp.zl.tolist(),
                                 [0., 0., 1., 0., 0., 0., 0., 0.])
        self.assertSequenceEqual(self.rcmp.zi.tolist(),
                                 [1., 1., 0., 1., 1., 1., 1., 0.])
        self.assertSequenceEqual(self.rcmp.zu.tolist(),
                                 [0., 0., 0., 0., 0., 0., 0., 1.])

        # test when no `n_select` is specified
        self.rcmp_noselect = SortedLimiter(self.u, self.lower, self.upper)
        self.rcmp_noselect.list2array(len(self.u.v))
        self.rcmp_noselect.check_var()

        self.assertSequenceEqual(self.rcmp_noselect.zl.tolist(),
                                 self.cmp.zl.tolist())
        self.assertSequenceEqual(self.rcmp_noselect.zi.tolist(),
                                 self.cmp.zi.tolist())
        self.assertSequenceEqual(self.rcmp_noselect.zu.tolist(),
                                 self.cmp.zu.tolist())

        # test when no `n_select` is over range
        self.rcmp_noselect = SortedLimiter(self.u,
                                           self.lower,
                                           self.upper,
                                           n_select=999)
        self.rcmp_noselect.list2array(len(self.u.v))
        self.rcmp_noselect.check_var()

        self.assertSequenceEqual(self.rcmp_noselect.zl.tolist(),
                                 self.cmp.zl.tolist())
        self.assertSequenceEqual(self.rcmp_noselect.zi.tolist(),
                                 self.cmp.zi.tolist())
        self.assertSequenceEqual(self.rcmp_noselect.zu.tolist(),
                                 self.cmp.zu.tolist())
Exemple #4
0
    def test_limiter(self):
        """
        Tests for `Limiter` class
        Returns
        -------

        """
        self.cmp = Limiter(self.u, self.lower, self.upper)
        self.cmp.list2array(len(self.u.v))

        self.cmp.check_var()

        self.assertSequenceEqual(self.cmp.zl.tolist(),
                                 [1., 1., 1., 1., 0., 0., 0., 0.])
        self.assertSequenceEqual(self.cmp.zi.tolist(),
                                 [0., 0., 0., 0., 1., 0., 0., 0.])
        self.assertSequenceEqual(self.cmp.zu.tolist(),
                                 [0., 0., 0., 0., 0., 1., 1., 1.])
Exemple #5
0
    def __init__(self, system, config):
        EV1Model.__init__(self, system, config)

        # Modify power limit PHL
        self.PHLup = Algeb(
            info='PHL upper limit',
            v_str='pcap * pmx',
            e_str='pcap * pmx - PHLup',
            tex_name=r'PHL_{upper}',
        )

        self.PHL2 = Limiter(
            u=self.Psum,
            lower=self.pmn,
            upper=self.PHLup,
            enable=1,
            info='limiter for Psum in [pmn, pcap * pmx]',
        )

        # Modify
        self.Ipul.v_str = '(Psum * PHL2_zi + PHLup * PHL2_zu + pmn * PHL2_zl) / vp'
        self.Ipul.e_str = '(Psum * PHL2_zi + PHLup * PHL2_zu + pmn * PHL2_zl) / vp - Ipul'
Exemple #6
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 #7
0
class TestDiscrete(unittest.TestCase):
    def setUp(self):
        self.lower = NumParam()
        self.upper = NumParam()
        self.u = Algeb()

        self.upper.v = np.array([2, 2, 2, 2, 2, 2, 2.8, 3.9])
        self.u.v = np.array([-3, -1.1, -5, 0, 1, 2, 3, 10])
        self.lower.v = np.array([-2, -1, 0.5, 0, 0.5, 1.5, 2, 3])

    def test_limiter(self):
        """
        Tests for `Limiter` class
        Returns
        -------

        """
        self.cmp = Limiter(self.u, self.lower, self.upper)
        self.cmp.list2array(len(self.u.v))

        self.cmp.check_var()

        self.assertSequenceEqual(self.cmp.zl.tolist(),
                                 [1., 1., 1., 1., 0., 0., 0., 0.])
        self.assertSequenceEqual(self.cmp.zi.tolist(),
                                 [0., 0., 0., 0., 1., 0., 0., 0.])
        self.assertSequenceEqual(self.cmp.zu.tolist(),
                                 [0., 0., 0., 0., 0., 1., 1., 1.])

    def test_sorted_limiter(self):
        """
        Tests for `SortedLimiter` class

        Returns
        -------

        """
        self.cmp = Limiter(self.u, self.lower, self.upper)
        self.cmp.list2array(len(self.u.v))
        self.cmp.check_var()

        self.rcmp = SortedLimiter(self.u, self.lower, self.upper, n_select=1)
        self.rcmp.list2array(len(self.u.v))
        self.rcmp.check_var()

        self.assertSequenceEqual(self.rcmp.zl.tolist(),
                                 [0., 0., 1., 0., 0., 0., 0., 0.])
        self.assertSequenceEqual(self.rcmp.zi.tolist(),
                                 [1., 1., 0., 1., 1., 1., 1., 0.])
        self.assertSequenceEqual(self.rcmp.zu.tolist(),
                                 [0., 0., 0., 0., 0., 0., 0., 1.])

        # test when no `n_select` is specified
        self.rcmp_noselect = SortedLimiter(self.u, self.lower, self.upper)
        self.rcmp_noselect.list2array(len(self.u.v))
        self.rcmp_noselect.check_var()

        self.assertSequenceEqual(self.rcmp_noselect.zl.tolist(),
                                 self.cmp.zl.tolist())
        self.assertSequenceEqual(self.rcmp_noselect.zi.tolist(),
                                 self.cmp.zi.tolist())
        self.assertSequenceEqual(self.rcmp_noselect.zu.tolist(),
                                 self.cmp.zu.tolist())

        # test when no `n_select` is over range
        self.rcmp_noselect = SortedLimiter(self.u,
                                           self.lower,
                                           self.upper,
                                           n_select=999)
        self.rcmp_noselect.list2array(len(self.u.v))
        self.rcmp_noselect.check_var()

        self.assertSequenceEqual(self.rcmp_noselect.zl.tolist(),
                                 self.cmp.zl.tolist())
        self.assertSequenceEqual(self.rcmp_noselect.zi.tolist(),
                                 self.cmp.zi.tolist())
        self.assertSequenceEqual(self.rcmp_noselect.zu.tolist(),
                                 self.cmp.zu.tolist())

    def test_switcher(self):
        p = NumParam()
        p.v = np.array([0, 1, 2, 2, 1, 3, 1])
        switcher = Switcher(u=p, options=[0, 1, 2, 3, 4])
        switcher.list2array(len(p.v))

        switcher.check_var()

        self.assertSequenceEqual(switcher.s0.tolist(), [1, 0, 0, 0, 0, 0, 0])
        self.assertSequenceEqual(switcher.s1.tolist(), [0, 1, 0, 0, 1, 0, 1])
        self.assertSequenceEqual(switcher.s2.tolist(), [0, 0, 1, 1, 0, 0, 0])
        self.assertSequenceEqual(switcher.s3.tolist(), [0, 0, 0, 0, 0, 1, 0])
        self.assertSequenceEqual(switcher.s4.tolist(), [0, 0, 0, 0, 0, 0, 0])
Exemple #8
0
    def __init__(self, system, config):
        PSSBase.__init__(self, system, config)

        # ALL THE FOLLOWING IS FOR INPUT 2
        # retrieve indices of bus and bus freq
        self.buss2 = DataSelect(self.busr2,
                                self.bus,
                                info='selected bus (bus or busr)')

        self.busfreq2 = DeviceFinder(self.busf2,
                                     link=self.buss2,
                                     idx_name='bus')

        # from Bus
        self.v2 = ExtAlgeb(
            model='Bus',
            src='v',
            indexer=self.buss2,
            tex_name=r'V',
            info='Bus (or busr2, if given) terminal voltage',
        )

        # from BusFreq 2
        self.f2 = ExtAlgeb(model='FreqMeasurement',
                           src='f',
                           indexer=self.busfreq2,
                           export=False,
                           info='Bus frequency 2')

        # Config
        self.config.add(OrderedDict([('freq_model', 'BusFreq')]))
        self.config.add_extra(
            '_help', {'freq_model': 'default freq. measurement model'})
        self.config.add_extra('_alt', {'freq_model': ('BusFreq', )})

        self.busf.model = self.config.freq_model
        self.busf2.model = self.config.freq_model

        # input signal switch
        self.dv = Derivative(self.v)
        self.dv2 = Derivative(self.v2)

        self.SnSb = ExtService(
            model='SynGen',
            src='M',
            indexer=self.syn,
            attr='pu_coeff',
            info='Machine base to sys base factor for power',
            tex_name='(Sb/Sn)')

        self.SW = Switcher(
            u=self.MODE,
            options=[0, 1, 2, 3, 4, 5, 6, np.nan],
        )
        self.SW2 = Switcher(
            u=self.MODE2,
            options=[0, 1, 2, 3, 4, 5, 6, np.nan],
        )

        # Input signals
        self.sig = Algeb(
            tex_name='S_{ig}',
            info='Input signal',
        )
        self.sig.v_str = 'SW_s1*(omega-1) + SW_s2*0 + SW_s3*(tm0/SnSb) + ' \
                         'SW_s4*(tm-tm0) + SW_s5*v + SW_s6*0'
        self.sig.e_str = 'SW_s1*(omega-1) + SW_s2*(f-1) + SW_s3*(te/SnSb) + ' \
                         'SW_s4*(tm-tm0) + SW_s5*v + SW_s6*dv_v - sig'

        self.sig2 = Algeb(
            tex_name='S_{ig2}',
            info='Input signal 2',
        )
        self.sig2.v_str = 'SW2_s1*(omega-1) + SW2_s2*0 + SW2_s3*(tm0/SnSb) + ' \
                          'SW2_s4*(tm-tm0) + SW2_s5*v2 + SW2_s6*0'
        self.sig2.e_str = 'SW2_s1*(omega-1) + SW2_s2*(f2-1) + SW2_s3*(te/SnSb) + ' \
                          'SW2_s4*(tm-tm0) + SW2_s5*v2 + SW2_s6*dv2_v - sig2'

        self.L1 = Lag(
            u=self.sig,
            K=self.K1,
            T=self.T1,
            info='Transducer 1',
        )
        self.L2 = Lag(
            u=self.sig2,
            K=self.K2,
            T=self.T2,
            info='Transducer 2',
        )
        self.IN = Algeb(
            tex_name='I_N',
            info='Sum of inputs',
            v_str='L1_y + L2_y',
            e_str='L1_y + L2_y - IN',
        )

        self.WO = WashoutOrLag(
            u=self.IN,
            K=self.T3,
            T=self.T4,
        )

        self.LL1 = LeadLag(
            u=self.WO_y,
            T1=self.T5,
            T2=self.T6,
            zero_out=True,
        )

        self.LL2 = LeadLag(
            u=self.LL1_y,
            T1=self.T7,
            T2=self.T8,
            zero_out=True,
        )

        self.LL3 = LeadLag(
            u=self.LL2_y,
            T1=self.T9,
            T2=self.T10,
            zero_out=True,
        )

        self.VSS = GainLimiter(u=self.LL3_y,
                               K=1,
                               lower=self.LSMIN,
                               upper=self.LSMAX)

        self.VOU = ConstService(v_str='VCUr + v0')
        self.VOL = ConstService(v_str='VCLr + v0')

        self.OLIM = Limiter(u=self.v,
                            lower=self.VOL,
                            upper=self.VOU,
                            info='output limiter')

        self.vsout.e_str = 'OLIM_zi * VSS_y - vsout'
Exemple #9
0
    def __init__(self, system, config):
        PSSBase.__init__(self, system, config)

        self.config.add(OrderedDict([('freq_model', 'BusFreq')]))
        self.config.add_extra(
            '_help', {'freq_model': 'default freq. measurement model'})
        self.config.add_extra('_alt', {'freq_model': ('BusFreq', )})

        self.busf.model = self.config.freq_model

        self.dv = Derivative(self.v)

        self.SnSb = ExtService(
            model='SynGen',
            src='M',
            indexer=self.syn,
            attr='pu_coeff',
            info='Machine base to sys base factor for power',
            tex_name='(Sb/Sn)')

        self.SW = Switcher(
            u=self.MODE,
            options=[1, 2, 3, 4, 5, 6],
        )

        self.sig = Algeb(
            tex_name='S_{ig}',
            info='Input signal',
        )

        self.sig.v_str = 'SW_s0*(omega-1) + SW_s1*0 + SW_s2*(tm0/SnSb) + ' \
                         'SW_s3*(tm-tm0) + SW_s4*v + SW_s5*0'

        self.sig.e_str = 'SW_s0*(omega-1) + SW_s1*(f-1) + SW_s2*(te/SnSb) + ' \
                         'SW_s3*(tm-tm0) + SW_s4*v + SW_s5*dv_v - sig'

        self.F1 = Lag2ndOrd(u=self.sig, K=1, T1=self.A1, T2=self.A2)

        self.F2 = LeadLag2ndOrd(u=self.F1_y,
                                T1=self.A3,
                                T2=self.A4,
                                T3=self.A5,
                                T4=self.A6,
                                zero_out=True)

        self.LL1 = LeadLag(u=self.F2_y, T1=self.T1, T2=self.T2, zero_out=True)

        self.LL2 = LeadLag(u=self.LL1_y, T1=self.T3, T2=self.T4, zero_out=True)

        self.Vks = Gain(u=self.LL2_y, K=self.KS)

        self.WO = WashoutOrLag(u=self.Vks_y,
                               T=self.T6,
                               K=self.T5,
                               name='WO',
                               zero_out=True)  # WO_y == Vss

        self.VLIM = Limiter(u=self.WO_y,
                            lower=self.LSMIN,
                            upper=self.LSMAX,
                            info='Vss limiter')

        self.Vss = Algeb(
            tex_name='V_{ss}',
            info='Voltage output before output limiter',
            e_str='VLIM_zi * WO_y + VLIM_zu * LSMAX + VLIM_zl * LSMIN - Vss')

        self.OLIM = Limiter(u=self.v,
                            lower=self.VCLr,
                            upper=self.VCUr,
                            info='output limiter')

        self.vsout.e_str = 'OLIM_zi * Vss - vsout'
Exemple #10
0
    def __init__(self, system=None, config=None):
        PQData.__init__(self)
        Model.__init__(self, system, config)
        self.group = 'StaticLoad'
        # ``tds`` flag is needed to retrieve initial voltage (for constant Z/I conversion)
        self.flags.update({'pflow': True,
                           'tds': True,
                           })
        self.config.add(OrderedDict((('pq2z', 1),
                                     ('p2p', 0.0),
                                     ('p2i', 0.0),
                                     ('p2z', 1.0),
                                     ('q2q', 0.0),
                                     ('q2i', 0.0),
                                     ('q2z', 1.0),
                                     )))
        self.config.add_extra("_help",
                              pq2z="pq2z conversion if out of voltage limits",
                              p2p="P constant power percentage for TDS. Must have (p2p+p2i+p2z)=1",
                              p2i="P constant current percentage",
                              p2z="P constant impedance percentage",
                              q2q="Q constant power percentage for TDS. Must have (q2q+q2i+q2z)=1",
                              q2i="Q constant current percentage",
                              q2z="Q constant impedance percentage",
                              )
        self.config.add_extra("_alt",
                              pq2z="(0, 1)",
                              p2p="float",
                              p2i="float",
                              p2z="float",
                              q2q="float",
                              q2i="float",
                              q2z="float",
                              )
        self.config.add_extra("_tex",
                              pq2z="z_{pq2z}",
                              p2p=r"\gamma_{p2p}",
                              p2i=r"\gamma_{p2i}",
                              p2z=r"\gamma_{p2z}",
                              q2q=r"\gamma_{q2q}",
                              q2i=r"\gamma_{q2i}",
                              q2z=r"\gamma_{q2z}",
                              )

        self.a = ExtAlgeb(model='Bus',
                          src='a',
                          indexer=self.bus,
                          tex_name=r'\theta',
                          )
        self.v = ExtAlgeb(model='Bus',
                          src='v',
                          indexer=self.bus,
                          tex_name=r'V',
                          )

        self.v0 = ExtService(src='v',
                             model='Bus',
                             indexer=self.bus,
                             tex_name='V_0',
                             info='Initial voltage magnitude from power flow'
                             )
        self.a0 = ExtService(src='a',
                             model='Bus',
                             indexer=self.bus,
                             tex_name=r'\theta_0',
                             info='Initial voltage angle from power flow'
                             )

        # Rub, Xub, Rlb, Xlb are constant for both PF and TDS.
        self.Rub = ConstService(info='Equivalent resistance at voltage upper bound',
                                v_str='p0 / vmax**2',
                                tex_name='R_{ub}'
                                )
        self.Xub = ConstService(info='Equivalent reactance at voltage upper bound',
                                v_str='q0 / vmax**2',
                                tex_name='X_{ub}'
                                )
        self.Rlb = ConstService(info='Equivalent resistance at voltage lower bound',
                                v_str='p0 / vmin**2',
                                tex_name='R_{lb}'
                                )
        self.Xlb = ConstService(info='Equivalent reactance at voltage lower bound',
                                v_str='q0 / vmin**2',
                                tex_name='X_{lb}'
                                )

        # Ppf, Qpf, Req, Xeq, Ipeq, Iqeq are only meaningful after initializing TDS
        self.Ppf = ConstService(info='Actual P in power flow',
                                v_str='(p0 * vcmp_zi + Rlb * vcmp_zl * v0**2 + Rub * vcmp_zu * v0**2)',
                                tex_name='P_{pf}')
        self.Qpf = ConstService(info='Actual Q in power flow',
                                v_str='(q0 * vcmp_zi + Xlb * vcmp_zl * v0**2 + Xub * vcmp_zu * v0**2)',
                                tex_name='Q_{pf}')
        self.Req = ConstService(info='Equivalent resistance at steady state',
                                v_str='Ppf / v0**2',
                                tex_name='R_{eq}'
                                )
        self.Xeq = ConstService(info='Equivalent reactance at steady state',
                                v_str='Qpf / v0**2',
                                tex_name='X_{eq}'
                                )
        self.Ipeq = ConstService(info='Equivalent active current source at steady state',
                                 v_str='Ppf / v0',
                                 tex_name='I_{peq}'
                                 )
        self.Iqeq = ConstService(info='Equivalent reactive current source at steady state',
                                 v_str='Qpf / v0',
                                 tex_name='I_{qeq}'
                                 )

        self.vcmp = Limiter(u=self.v,
                            lower=self.vmin,
                            upper=self.vmax,
                            enable=self.config.pq2z,
                            )

        # Note: the "or" condition "|" is not supported in sympy equation strings.
        # They will simply be ignored.

        # To modify P and Q during TDS, use `alter` to set values to `Ppf` and `Qpf`
        # after, before simulation, setting `config.p2p=1` and `config.q2q=1`.

        self.a.e_str = "u * (dae_t <= 0) * " \
                       "(p0 * vcmp_zi + Rlb * vcmp_zl * v**2 + Rub * vcmp_zu * v**2) + " \
                       "u * (dae_t > 0) * " \
                       "(p2p * Ppf + p2i * Ipeq * v + p2z * Req * v**2)"

        self.v.e_str = "u * (dae_t <= 0) * " \
                       "(q0 * vcmp_zi + Xlb * vcmp_zl * v**2 + Xub * vcmp_zu * v**2) + " \
                       "u * (dae_t > 0) * " \
                       "(q2q * Qpf + q2i * Iqeq * v + q2z * Xeq * v**2)"
Exemple #11
0
    def __init__(self):
        # -- Voltage protection

        # Indicatior of voltage deviation
        self.LVl1 = Limiter(u=self.v,
                            lower=self.vl4, upper=self.vl1,
                            info='Voltage comparer for (vl4, vl1)',
                            equal=False, no_warn=True,
                            )

        self.LVl2 = Limiter(u=self.v,
                            lower=self.vl4, upper=self.vl2,
                            info='Voltage comparer for (vl4, vl2)',
                            equal=False, no_warn=True,
                            )

        self.LVl3 = Limiter(u=self.v,
                            lower=self.vl4, upper=self.vl3,
                            info='Voltage comparer for (vl4, vl3)',
                            equal=False, no_warn=True,
                            )

        self.LVu1 = Limiter(u=self.v,
                            lower=self.vu1, upper=self.vu3,
                            info='Voltage comparer for (vu1, vu3)',
                            equal=False, no_warn=True,
                            )

        self.LVu2 = Limiter(u=self.v,
                            lower=self.vu2, upper=self.vu3,
                            info='Voltage comparer for (vu2, vu3)',
                            equal=False, no_warn=True,
                            )

        # Voltage deviation time continuity check
        self.IAWVl1 = IntegratorAntiWindup(u='LVl1_zi * (1 - res) - Tvl1 / Tres * res',
                                           T=1, K=1, y0='0',
                                           lower=self.zero, upper=self.Tvl1,
                                           info='condition check for (Vl3, Vl1)',
                                           no_warn=True,
                                           )

        self.IAWVl2 = IntegratorAntiWindup(u='LVl2_zi * (1 - res) - Tvl2 / Tres * res',
                                           T=1, K=1, y0='0',
                                           lower=self.zero, upper=self.Tvl2,
                                           info='condition check for (Vl3, Vl2)',
                                           no_warn=True,
                                           )

        self.IAWVl3 = IntegratorAntiWindup(u='LVl2_zi * (1 - res) - Tvl3 / Tres * res',
                                           T=1, K=1, y0='0',
                                           lower=self.zero, upper=self.Tvl2,
                                           info='condition check for (Vl3, Vl2)',
                                           no_warn=True,
                                           )

        self.IAWVu1 = IntegratorAntiWindup(u='LVu1_zi * (1 - res) - Tvu1 / Tres * res',
                                           T=1, K=1, y0='0',
                                           lower=self.zero, upper=self.Tvu1,
                                           info='condition check for (Vu1, Vu3)',
                                           no_warn=True,
                                           )

        self.IAWVu2 = IntegratorAntiWindup(u='LVu2_zi * (1 - res) - Tvu2 / Tres * res',
                                           T=1, K=1, y0='0',
                                           lower=self.zero, upper=self.Tvu2,
                                           info='condition check for (Vu2, Vu3)',
                                           no_warn=True,
                                           )
Exemple #12
0
    def __init__(self, system, config):
        Model.__init__(self, system, config)
        self.flags.tds = True
        self.group = 'DGProtection'

        self.bus = ExtParam(model='DG', src='bus',
                            indexer=self.dev,
                            export=False)

        self.fn = ExtParam(model='DG', src='fn',
                           indexer=self.dev,
                           export=False)

        # -- Frequency protection
        # Convert frequency deviation range to p.u.
        self.f = ExtAlgeb(export=False,
                          info='DG frequency read value', unit='p.u.',
                          model='FreqMeasurement', src='f',
                          indexer=self.busfreq,
                          )

        self.fHz = Algeb(v_str='fn * f',
                         e_str='fn * f - fHz',
                         info='frequency in Hz',
                         tex_name=r'f_{Hz}',
                         )

        # -- Lock DG frequency signal and output power

        self.ltu = ConstService(v_str='0.8')
        self.ltl = ConstService(v_str='0.2')

        # `Ldsum_zu` is `ue`
        dsum = 'fen * (IAWfl1_lim_zu * Lfl1_zi + IAWfl2_lim_zu * Lfl2_zi + ' \
               'IAWfu1_lim_zu * Lfu1_zi + IAWfu2_lim_zu * Lfu2_zi) + ' \
               'Ven * (IAWVl1_lim_zu * LVl1_zi + IAWVl2_lim_zu * LVl2_zi + ' \
               'IAWVl3_lim_zu * LVl3_zi + ' \
               'IAWVu1_lim_zu * LVu1_zi + IAWVu2_lim_zu * LVu2_zi) - ' \
               'dsum'

        self.dsum = Algeb(v_str='0',
                          e_str=dsum,
                          info='lock signal summation',
                          tex_name=r'd_{tot}',
                          )

        self.Ldsum = Limiter(u=self.dsum,
                             lower=self.ltl,
                             upper=self.ltu,
                             info='lock signal comparer, zu is to act',
                             equal=False, no_warn=True,
                             )

        self.ue = Algeb(v_str='0',
                        e_str='Ldsum_zu - ue',
                        info='lock flag',
                        tex_name=r'ue',
                        )

        self.zero = ConstService('0')

        self.res = ExtendedEvent(self.ue, t_ext=self.Tres,
                                 trig="rise",
                                 extend_only=True)

        # lock DG frequency signal

        # fflag option 1: leave source signal online in protection
        self.fin = ExtAlgeb(model='DG', src='f',
                            indexer=self.dev,
                            info='original f from DG',
                            )

        self.fHzl = ExtAlgeb(model='DG', src='fHz',
                             indexer=self.dev,
                             export=False,
                             e_str='- ue * (fn * f)',
                             info='Frequency measure lock',
                             ename='fHzl',
                             tex_ename='f_{Hzl}',
                             )
        # TODO: add fflag option 2: block the source signal in protection

        # lock output power of DG

        self.Pext = ExtAlgeb(model='DG', src='Pext',
                             indexer=self.dev,
                             info='original Pext from DG',
                             )

        self.Pref = ExtAlgeb(model='DG', src='Pref',
                             indexer=self.dev,
                             info='original Pref from DG',
                             )

        self.Pdrp = ExtAlgeb(model='DG', src='DB_y',
                             indexer=self.dev,
                             info='original Pdrp from DG',
                             )

        self.Psum = ExtAlgeb(model='DG', src='Psum',
                             indexer=self.dev,
                             export=False,
                             e_str='- ue * (Pext + Pref + Pdrp)',
                             info='Active power lock',
                             ename='Pneg',
                             tex_ename='P_{neg}',
                             )

        self.Qdrp = ExtAlgeb(model='DG', src='Qdrp',
                             indexer=self.dev,
                             info='original Qdrp from DG',
                             )

        self.Qref = ExtAlgeb(model='DG', src='Qref',
                             indexer=self.dev,
                             info='original Qref from DG',
                             )

        self.Qsum = ExtAlgeb(model='DG', src='Qsum',
                             indexer=self.dev,
                             export=False,
                             e_str='- ue * (Qdrp + Qref)',
                             info='Reactive power lock',
                             ename='Qneg',
                             tex_ename='Q_{neg}',
                             )
Exemple #13
0
    def __init__(self, system=None, config=None):
        PQData.__init__(self)
        Model.__init__(self, system, config)
        self.group = 'StaticLoad'
        # ``tds`` flag is added to retrieve initial voltage (for constant Z or constant I conversion)
        self.flags.update({'pflow': True,
                           'tds': True,
                           })
        self.config.add(OrderedDict((('pq2z', 1),
                                     ('p2p', 0),
                                     ('p2i', 0),  # not in use
                                     ('p2z', 1),
                                     ('q2q', 0),
                                     ('q2i', 0),  # not in use
                                     ('q2z', 1),
                                     )))

        self.a = ExtAlgeb(model='Bus',
                          src='a',
                          indexer=self.bus,
                          tex_name=r'\theta',
                          )
        self.v = ExtAlgeb(model='Bus',
                          src='v',
                          indexer=self.bus,
                          tex_name=r'V',
                          )

        self.v0 = ExtService(src='v',
                             model='Bus',
                             indexer=self.bus,
                             tex_name='V_0',
                             info='Initial voltage magnitude from power flow'
                             )
        self.a0 = ExtService(src='a',
                             model='Bus',
                             indexer=self.bus,
                             tex_name=r'\theta_0',
                             info='Initial voltage angle from power flow'
                             )

        self.Req = ConstService(info='Equivalent resistance',
                                v_str='p0 / v0**2',
                                )
        self.Xeq = ConstService(info='Equivalent reactance',
                                v_str='q0 / v0**2',
                                )

        self.vcmp = Limiter(u=self.v,
                            lower=self.vmin,
                            upper=self.vmax,
                            enable=self.config.pq2z,
                            )

        self.a.e_str = "u * ((dae_t <= 0) | (p2p == 1)) * " \
                       "(p0 * vcmp_zi + " \
                       "p0 * vcmp_zl * (v ** 2 / vmin ** 2) + "\
                       "p0 * vcmp_zu * (v ** 2 / vmax ** 2)) + " \
                       "u * ((dae_t > 0) & (p2p != 1)) * " \
                       "(p2p * p0 + p2z * Req * v**2)"

        self.v.e_str = "u * ((dae_t <= 0) | (q2q == 1)) * " \
                       "(q0 * vcmp_zi + " \
                       "q0 * vcmp_zl * (v ** 2 / vmin ** 2) + " \
                       "q0 * vcmp_zu * (v ** 2 / vmax ** 2)) + " \
                       "u * ((dae_t > 0) & (q2q != 1)) * " \
                       "(q2q * q0 + q2z * Xeq * v**2)"