Example #1
0
 def clear_fault(self, is_time: np.ndarray):
     """Clear fault and restore pre-fault bus voltages."""
     for i in range(self.n):
         if is_time[i] and (self.u.v[i] == 1):
             self.uf.v[i] = 0
             self.system.dae.y[self.system.Bus.v.a] = self._vstore
             tqdm.write(
                 f'<Fault {i}>: Clearing fault on {self.bus.v[i]} at {self.tc.v[i]}.'
             )
             return True
     return False
Example #2
0
    def init(self):
        """
        Initialize the status, storage and values for TDS.

        Returns
        -------
        array-like
            The initial values of xy.

        """
        t0, _ = elapsed()
        system = self.system

        if self.initialized:
            return system.dae.xy

        self._reset()
        self._load_pert()

        # Note:
        #   calling `set_address` on `system.exist.pflow_tds` will point all variables
        #   to the new array after extending `dae.y`
        system.set_address(models=system.exist.pflow_tds)

        system.set_dae_names(models=system.exist.tds)

        system.dae.clear_ts()
        system.store_sparse_pattern(models=system.exist.pflow_tds)
        system.store_adder_setter(models=system.exist.pflow_tds)
        system.vars_to_models()

        # Initialize `system.exist.tds` only to avoid Bus overwriting power flow solutions
        system.init(system.exist.tds)
        system.store_switch_times(system.exist.tds)

        # Build mass matrix into `self.Teye`
        self.Teye = spdiag(system.dae.Tf.tolist())
        self.qg = np.zeros(system.dae.n + system.dae.m)

        self.initialized = self.test_init()

        # if `dae.n == 1`, `calc_h_first` depends on new `dae.gy`
        self.calc_h()

        _, s1 = elapsed(t0)

        if self.initialized is True:
            logger.info(f"Initialization was successful in {s1}.")
        else:
            logger.error(f"Initialization failed in {s1}.")

        if system.dae.n == 0:
            tqdm.write('No dynamic component loaded.')
        return system.dae.xy
Example #3
0
 def apply_fault(self, is_time: np.ndarray):
     """Apply fault and store pre-fault bus voltages to ``self._vstore``."""
     for i in range(self.n):
         if is_time[i] and (self.u.v[i] == 1):
             self.uf.v[i] = 1
             self._vstore = np.array(self.system.Bus.v.v)
             tqdm.write(
                 f'<Fault {i}>: Applying fault on {self.bus.v[i]} at {self.tf.v[i]}.'
             )
             return True
     return False
Example #4
0
    def _u_switch(self, is_time: np.ndarray):
        action = False
        for i in range(self.n):
            if (is_time[i] == 0) or (self.u.v[i] == 0):
                continue

            instance = self.system.__dict__[self.model.v[i]]
            u0 = instance.get(src='u', attr='v', idx=self.dev.v[i])
            instance.set(src='u', attr='v', idx=self.dev.v[i], value=1 - u0)
            action = True
            tqdm.write(f'<Toggle {self.idx.v[i]}>: '
                       f'{self.model.v[i]}.{self.dev.v[i]} status '
                       f'changed to {1-u0:g} at t={self.t.v[i]} sec.')
        return action
Example #5
0
    def clear_fault(self, is_time: np.ndarray):
        """
        Clear fault and restore pre-fault bus algebraic variables (voltages and
        others).
        """
        action = False
        for i in range(self.n):
            if is_time[i] and (self.u.v[i] == 1):
                self.uf.v[i] = 0

                if self.config.restore:
                    if self.config.mode == 1:
                        self.system.dae.y[
                            self.system.Bus.
                            n:] = self._vstore * self.config.scale
                        logger.debug(
                            "All algebraic variables restored after fault clearance at t=%.6f",
                            self.system.dae.t)

                    # TODO: neither mode 2 or 3 works. Pending further investigation.
                    elif self.config.mode == 2:
                        v_addr = self.system.Bus.get(src='v',
                                                     idx=self.bus.v[i],
                                                     attr='a')
                        bus_uid = self.system.Bus.idx2uid(self.bus.v[i])
                        self.system.dae.y[
                            v_addr] = self._vstore[bus_uid] * self.config.scale
                        logger.debug(
                            "Voltage on bus %s restored after fault clearance at t=%.6f",
                            self.bus.v[i], self.system.dae.t)
                    elif self.config.mode == 3:
                        nbus = self.system.Bus.n
                        self.system.dae.y[
                            nbus:2 *
                            nbus] = self._vstore[:nbus] * self.config.scale
                        logger.debug(
                            "All bus voltages restored after fault clearance at t=%.6f",
                            self.system.dae.t)
                    else:
                        logger.error(
                            "Unsupport fault voltage restoration mode")

                tqdm.write(
                    f'<Fault {self.idx.v[i]}>: '
                    f'Clearing fault on Bus (idx={self.bus.v[i]}) at t={self.tc.v[i]} sec.'
                )

                action = True
        return action
Example #6
0
 def _u_switch(self, is_time: np.ndarray):
     action = False
     for i in range(self.n):
         if is_time[i] and (self.u.v[i] == 1):
             instance = self.system.__dict__[self.model.v[i]]
             u0 = instance.get(src='u', attr='v', idx=self.dev.v[i])
             instance.set(src='u',
                          attr='v',
                          idx=self.dev.v[i],
                          value=1 - u0)
             action = True
             tqdm.write(
                 f'<Toggle {i}>: Status of {self.model.v[i]}.{self.dev.v[i]} changed to {1-u0}.'
             )
     return action
Example #7
0
 def clear_fault(self, is_time: np.ndarray):
     """
     Clear fault and restore pre-fault bus algebraic variables (voltages and others).
     """
     action = False
     for i in range(self.n):
         if is_time[i] and (self.u.v[i] == 1):
             self.uf.v[i] = 0
             self.system.dae.y[self.system.Bus.n:] = self._vstore
             tqdm.write(
                 f'<Fault {self.idx.v[i]}>: '
                 f'Clearing fault on Bus (idx={self.bus.v[i]}) at t={self.tc.v[i]}sec.'
             )
             action = True
     return action
Example #8
0
 def apply_fault(self, is_time: np.ndarray):
     """
     Apply fault and store pre-fault algebraic variables (voltages and other algebs) to `self._vstore`.
     """
     action = False
     for i in range(self.n):
         if is_time[i] and (self.u.v[i] == 1):
             self.uf.v[i] = 1
             self._vstore = np.array(self.system.dae.y[self.system.Bus.n:])
             tqdm.write(
                 f'<Fault {self.idx.v[i]}>: '
                 f'Applying fault on Bus (idx={self.bus.v[i]}) at t={self.tf.v[i]}sec.'
             )
             action = True
     return action
Example #9
0
    def apply_exact(self, t):
        """
        Apply the timeseries data at the exact time.

        Parameters
        ----------
        t : float
            the current time
        """
        # convert from numpy scalar to float
        t = t.tolist()

        for ii in range(self.n):
            # skip offline devices
            if self.u.v[ii] == 0:
                continue

            # check mode
            if self.SW.s1[ii] != 1:
                continue

            idx = self.idx.v[ii]
            df = self._data[idx]
            tkey = self.tkey.v[ii]

            # check if current time is a valid time stamp
            if t not in df[tkey].values:
                continue

            print("here 2")
            fields = self.fields.v[ii]
            dests = self.dests.v[ii]

            model = self.model.v[ii]
            dev_idx = self.dev.v[ii]

            # apply the value change
            for field, dest in zip(fields, dests):
                value = df.loc[df[tkey] == t, field].values
                if len(value) == 0:
                    continue
                value = value[0]
                self.system.__dict__[model].set(dest, dev_idx, 'v', value)

                if not self.config.silent:
                    tqdm.write("<TimeSeries %s> set %s=%g for %s.%s at t=%g" %
                               (idx, dest, value, model, dev_idx, t))
Example #10
0
    def init(self):
        """
        Initialize the status, storage and values for TDS.

        Returns
        -------
        array-like
            The initial values of xy.

        """
        t0, _ = elapsed()
        system = self.system

        if self.initialized:
            return system.dae.xy

        self._reset()
        self._load_pert()
        system.set_address(models=system.exist.tds)
        system.set_dae_names(models=system.exist.tds)

        system.dae.clear_ts()
        system.store_sparse_pattern(models=system.exist.pflow_tds)
        system.store_adder_setter(models=system.exist.pflow_tds)
        system.vars_to_models()
        system.init(system.exist.tds)
        system.store_switch_times(system.exist.tds)
        self.eye = spdiag([1] * system.dae.n)
        self.Teye = spdiag(system.dae.Tf.tolist()) * self.eye
        self.qg = np.zeros(system.dae.n + system.dae.m)
        self.calc_h()

        self.initialized = self.test_init()
        _, s1 = elapsed(t0)

        if self.initialized is True:
            logger.info(f"Initialization was successful in {s1}.")
        else:
            logger.error(f"Initialization failed in {s1}.")

        if system.dae.n == 0:
            tqdm.write('No dynamic component loaded.')
        return system.dae.xy
Example #11
0
    def clear_fault(self, is_time: np.ndarray):
        """
        Clear fault and restore pre-fault bus algebraic variables (voltages and others).
        """
        action = False
        for i in range(self.n):
            if is_time[i] and (self.u.v[i] == 1):
                self.uf.v[i] = 0

                if self.config.restore:
                    self.system.dae.y[self.system.Bus.
                                      n:] = self._vstore * self.config.scale
                    logger.debug(
                        f"Voltage restored after fault clearance at t={self.system.dae.t:.6f}"
                    )

                tqdm.write(
                    f'<Fault {self.idx.v[i]}>: '
                    f'Clearing fault on Bus (idx={self.bus.v[i]}) at t={self.tc.v[i]} sec.'
                )

                action = True
        return action
Example #12
0
    def init(self):
        """
        Initialize the status, storage and values for TDS.

        Returns
        -------
        array-like
            The initial values of xy.

        """
        t0, _ = elapsed()
        system = self.system

        if self.initialized:
            return system.dae.xy

        self.reset()
        self._load_pert()

        # restore power flow solutions
        system.dae.x[:len(system.PFlow.x_sol)] = system.PFlow.x_sol
        system.dae.y[:len(system.PFlow.y_sol)] = system.PFlow.y_sol

        # Note:
        #   calling `set_address` on `system.exist.pflow_tds` will point all variables
        #   to the new array after extending `dae.y`.
        system.set_address(models=system.exist.pflow_tds)
        system.set_dae_names(models=system.exist.tds)

        system.dae.clear_ts()
        system.store_sparse_pattern(models=system.exist.pflow_tds)
        system.store_adder_setter(models=system.exist.pflow_tds)
        system.store_no_check_init(models=system.exist.pflow_tds)
        system.vars_to_models()

        system.init(system.exist.tds, routine='tds')

        # only store switch times when not replaying CSV data
        if self.data_csv is None:
            system.store_switch_times(system.exist.tds)

        # Build mass matrix into `self.Teye`
        self.Teye = spdiag(system.dae.Tf.tolist())
        self.qg = np.zeros(system.dae.n + system.dae.m)

        self.initialized = True

        # test if residuals are close enough to zero
        if self.config.test_init:
            self.test_ok = self.test_init()

        # discard initialized values and use that from CSV if provided
        if self.data_csv is not None:
            system.dae.x[:] = self.data_csv[0, 1:system.dae.n + 1]
            system.dae.y[:] = self.data_csv[0, system.dae.n + 1:system.dae.n + system.dae.m + 1]
            system.vars_to_models()

        # connect to data streaming server
        if system.streaming.dimec is None:
            system.streaming.connect()

        if system.config.dime_enabled:
            # send out system data using DiME
            self.streaming_init()
            self.streaming_step()

        # if `dae.n == 1`, `calc_h_first` depends on new `dae.gy`
        self.calc_h()

        # allocate for internal variables
        self.x0 = np.zeros_like(system.dae.x)
        self.y0 = np.zeros_like(system.dae.y)
        self.f0 = np.zeros_like(system.dae.f)

        _, s1 = elapsed(t0)

        logger.info("Initialization for dynamics completed in %s.", s1)

        if self.test_ok is True:
            logger.info("Initialization was successful.")
        elif self.test_ok is False:
            logger.error("Initialization failed!!")
        else:
            logger.warning("Initialization results were not verified.")

        if system.dae.n == 0:
            tqdm.write('No differential equation detected.')
        return system.dae.xy
Example #13
0
    def _itm_step(self):
        """
        Integrate with Implicit Trapezoidal Method (ITM) to the current time.

        This function has an internal Newton-Raphson loop for algebraized semi-explicit DAE.
        The function returns the convergence status when done but does NOT progress simulation time.

        Returns
        -------
        bool
            Convergence status in ``self.converged``.

        """
        system = self.system
        dae = self.system.dae

        self.mis = 1
        self.niter = 0
        self.converged = False

        self.x0 = np.array(dae.x)
        self.y0 = np.array(dae.y)
        self.f0 = np.array(dae.f)

        while True:
            self._fg_update(models=system.exist.pflow_tds)

            # lazy Jacobian update

            if dae.t == 0 or \
                    self.config.honest or \
                    self.custom_event or \
                    not self.last_converged or \
                    self.niter > 4 or \
                    (dae.t - self._last_switch_t < 0.1):

                system.j_update(models=system.exist.pflow_tds)
                # set flag in `solver.worker.factorize`, not `solver.factorize`.
                self.solver.worker.factorize = True

            # `Tf` should remain constant throughout the simulation, even if the corresponding diff. var.
            # is pegged by the anti-windup limiters.

            # solve implicit trapezoidal method (ITM) integration
            self.Ac = sparse([[self.Teye - self.h * 0.5 * dae.fx, dae.gx],
                              [-self.h * 0.5 * dae.fy, dae.gy]], 'd')

            # equation `self.qg[:dae.n] = 0` is the implicit form of differential equations using ITM
            self.qg[:dae.n] = dae.Tf * (dae.x - self.x0) - self.h * 0.5 * (dae.f + self.f0)

            # reset the corresponding q elements for pegged anti-windup limiter
            for item in system.antiwindups:
                for key, _, eqval in item.x_set:
                    np.put(self.qg, key, eqval)

            self.qg[dae.n:] = dae.g

            if not self.config.linsolve:
                inc = self.solver.solve(self.Ac, matrix(self.qg))
            else:
                inc = self.solver.linsolve(self.Ac, matrix(self.qg))

            # check for np.nan first
            if np.isnan(inc).any():
                self.err_msg = 'NaN found in solution. Convergence is not likely'
                self.niter = self.config.max_iter + 1
                self.busted = True
                break

            # reset small values to reduce chattering
            inc[np.where(np.abs(inc) < self.tol_zero)] = 0

            # set new values
            dae.x -= inc[:dae.n].ravel()
            dae.y -= inc[dae.n: dae.n + dae.m].ravel()

            # store `inc` to self for debugging
            self.inc = inc

            system.vars_to_models()

            # calculate correction
            mis = np.max(np.abs(inc))
            # store initial maximum mismatch
            if self.niter == 0:
                self.mis = mis

            self.niter += 1

            # converged
            if mis <= self.config.tol:
                self.converged = True
                break
            # non-convergence cases
            if self.niter > self.config.max_iter:
                tqdm.write(f'* Max. iter. {self.config.max_iter} reached for t={dae.t:.6f}, '
                           f'h={self.h:.6f}, mis={mis:.4g} ')

                # debug helpers
                g_max = np.argmax(abs(dae.g))
                inc_max = np.argmax(abs(inc))
                self._debug_g(g_max)
                self._debug_ac(inc_max)

                break

            if mis > 1e6 and (mis > 1e6 * self.mis):
                self.err_msg = 'Error increased too quickly. Convergence not likely.'
                self.busted = True
                break

        if not self.converged:
            dae.x[:] = np.array(self.x0)
            dae.y[:] = np.array(self.y0)
            dae.f[:] = np.array(self.f0)
            system.vars_to_models()

        self.last_converged = self.converged

        return self.converged
Example #14
0
    def step(tds):
        """
        Integrate with Implicit Trapezoidal Method (ITM) to the current time.

        This function has an internal Newton-Raphson loop for algebraized semi-explicit DAE.
        The function returns the convergence status when done but does NOT progress simulation time.

        Returns
        -------
        bool
            Convergence status in ``tds.converged``.

        """
        system = tds.system
        dae = tds.system.dae

        if tds.h == 0:
            logger.error(
                "Current step size is zero. Integration is not permitted.")
            return False

        tds.mis = [1, 1]
        tds.niter = 0
        tds.converged = False

        tds.x0[:] = dae.x
        tds.y0[:] = dae.y
        tds.f0[:] = dae.f

        while True:
            tds.fg_update(models=system.exist.pflow_tds)

            # lazy Jacobian update

            reason = ''
            if dae.t == 0:
                reason = 't=0'
            elif tds.config.honest:
                reason = 'using honest method'
            elif tds.custom_event:
                reason = 'custom event set'
            elif not tds.last_converged:
                reason = 'non-convergence in the last step'
            elif tds.niter > 4 and (tds.niter + 1) % 3 == 0:
                reason = 'every 3 iterations beyond 4 iterations'
            elif dae.t - tds._last_switch_t < 0.1:
                reason = 'within 0.1s of event'

            if reason:
                system.j_update(models=system.exist.pflow_tds, info=reason)

                # set flag in `solver.worker.factorize`, not `solver.factorize`.
                tds.solver.worker.factorize = True

            # `Tf` should remain constant throughout the simulation, even if the corresponding diff. var.
            # is pegged by the anti-windup limiters.

            # solve implicit trapezoidal method (ITM) integration
            if tds.config.g_scale > 0:
                gxs = tds.config.g_scale * tds.h * dae.gx
                gys = tds.config.g_scale * tds.h * dae.gy
            else:
                gxs = dae.gx
                gys = dae.gy

            # calculate complete Jacobian matrix ``Ac```
            tds.Ac = tds.method.calc_jac(tds, gxs, gys)

            # equation `tds.qg[:dae.n] = 0` is the implicit form of differential equations using ITM
            tds.qg[:dae.n] = tds.method.calc_q(dae.x, dae.f, dae.Tf, tds.h,
                                               tds.x0, tds.f0)

            # reset the corresponding q elements for pegged anti-windup limiter
            for item in system.antiwindups:
                for key, _, eqval in item.x_set:
                    np.put(tds.qg, key, eqval)

            # set or scale the algebraic residuals
            if tds.config.g_scale > 0:
                tds.qg[dae.n:] = tds.config.g_scale * tds.h * dae.g
            else:
                tds.qg[dae.n:] = dae.g

            # calculate variable corrections
            if not tds.config.linsolve:
                inc = tds.solver.solve(tds.Ac, matrix(tds.qg))
            else:
                inc = tds.solver.linsolve(tds.Ac, matrix(tds.qg))

            # check for np.nan first
            if np.isnan(inc).any():
                tds.err_msg = 'NaN found in solution. Convergence is not likely'
                tds.niter = tds.config.max_iter + 1
                tds.busted = True
                break

            # reset tiny values to reduce chattering
            if tds.config.reset_tiny:
                inc[np.where(np.abs(inc) < tds.tol_zero)] = 0

            # set new values
            dae.x -= inc[:dae.n].ravel()
            dae.y -= inc[dae.n:dae.n + dae.m].ravel()

            # synchronize solutions to model internal storage
            system.vars_to_models()

            # store `inc` to tds for debugging
            tds.inc = inc

            mis = np.max(np.abs(inc))

            # store initial maximum mismatch
            if tds.niter == 0:
                tds.mis[0] = mis
            else:
                tds.mis[-1] = mis

            tds.niter += 1

            # converged
            if mis <= tds.config.tol:
                tds.converged = True
                break

            # non-convergence cases
            if tds.niter > tds.config.max_iter:
                if system.options.get("verbose", 20) <= 10:
                    tqdm.write(
                        f'* Max. iter. {tds.config.max_iter} reached for t={dae.t:.6f}s, '
                        f'h={tds.h:.6f}s, max inc={mis:.4g} ')

                # debug helpers
                g_max = np.argmax(abs(dae.g))
                inc_max = np.argmax(abs(inc))
                tds._debug_g(g_max)
                tds._debug_ac(inc_max)
                break

            if (mis > 1e6) and (mis > 1e6 * tds.mis[0]):
                tds.err_msg = 'Error increased too quickly.'
                break

        if not tds.converged:
            dae.x[:] = np.array(tds.x0)
            dae.y[:] = np.array(tds.y0)
            dae.f[:] = np.array(tds.f0)
            system.vars_to_models()

        tds.last_converged = tds.converged

        return tds.converged
Example #15
0
    def _alter_field(self, is_time):
        """
        Actuation of the alteration.
        """
        action = False

        for ii in range(self.n):
            if (not is_time[ii]) or (self.u.v[ii] == 0):
                continue

            model = self.system.__dict__[self.model.v[ii]]
            idx = self.dev.v[ii]
            src = self.src.v[ii]
            attr = self.attr.v[ii]
            amount = self.amount.v[ii]

            if self.rand.v[ii] == 1:
                amount = np.random.uniform(low=self.lb.v[ii],
                                           high=self.ub.v[ii])

            try:
                v0 = model.get(src=src, idx=idx, attr=attr)
            except KeyError as e:
                tqdm.write(
                    "\nError: <%s %s> cannot find idx=%s or src=%s in model <%s>. "
                    % (
                        self.class_name,
                        self.idx.v[ii],
                        idx,
                        src,
                        self.model.v[ii],
                    ))
                tqdm.write("<%s %s> disabled due to %s.\n" %
                           (self.class_name, self.idx.v[ii], repr(e)))
                self.u.v[ii] = 0
                continue

            vnew = v0
            if self.SW.s0[ii] == 1:
                vnew = v0 + amount
            elif self.SW.s1[ii] == 1:
                vnew = v0 - amount
            elif self.SW.s2[ii] == 1:
                vnew = v0 * amount
            elif self.SW.s3[ii] == 1:
                vnew = v0 / amount
            elif self.SW.s4[ii] == 1:
                vnew = amount
            else:
                tqdm.write(
                    'Error: <%s %s>: undefined method "%s". <%s, %s> disabled.'
                    % (self.class_name, self.idx.v[ii], self.method.v[ii],
                       self.class_name, self.idx.v[ii]))
                self.u.v[ii] = 0
                continue

            model.set(src=src, idx=idx, attr=attr, value=vnew)
            tqdm.write(
                '<Alter %s>: set %s.%s.%s.%s=%.6g at t=%.6g. Previous value was %.6g.'
                % (self.idx.v[ii], self.model.v[ii], idx, src, attr, vnew,
                   self.t.v[ii], v0))
            action = True

        return action
Example #16
0
    def init(self):
        """
        Initialize the status, storage and values for TDS.

        Returns
        -------
        array-like
            The initial values of xy.

        """
        t0, _ = elapsed()
        system = self.system

        if self.initialized:
            return system.dae.xy

        self.reset()
        self._load_pert()

        # restore power flow solutions
        system.dae.x[:len(system.PFlow.x_sol)] = system.PFlow.x_sol
        system.dae.y[:len(system.PFlow.y_sol)] = system.PFlow.y_sol

        # Note:
        #   calling `set_address` on `system.exist.pflow_tds` will point all variables
        #   to the new array after extending `dae.y`
        system.set_address(models=system.exist.pflow_tds)
        system.set_dae_names(models=system.exist.tds)

        system.dae.clear_ts()
        system.store_sparse_pattern(models=system.exist.pflow_tds)
        system.store_adder_setter(models=system.exist.pflow_tds)
        system.vars_to_models()

        system.init(system.exist.tds, routine='tds')
        system.store_switch_times(system.exist.tds)

        # Build mass matrix into `self.Teye`
        self.Teye = spdiag(system.dae.Tf.tolist())
        self.qg = np.zeros(system.dae.n + system.dae.m)

        self.initialized = self.test_init()

        # connect to dime server
        if system.config.dime_enabled:
            if system.streaming.dimec is None:
                system.streaming.connect()

        # send out system data using DiME
        self.streaming_init()
        self.streaming_step()

        # if `dae.n == 1`, `calc_h_first` depends on new `dae.gy`
        self.calc_h()

        _, s1 = elapsed(t0)

        if self.initialized is True:
            logger.info(f"Initialization for dynamics was successful in {s1}.")
        else:
            logger.error(f"Initialization for dynamics failed in {s1}.")

        if system.dae.n == 0:
            tqdm.write('No dynamic component loaded.')
        return system.dae.xy