Ejemplo n.º 1
0
    def read_admittance_data(self, element_type, element):
        # Reads data on specified element and returns admittances (series and shunt)
        # and indices in the admittance matrix where the admittances should be added.
        # Currently only for lines and transformers (other elements are treated directly
        # when building admittance matrices.
        buses = self.buses
        if element_type == 'line':
            line = element
            idx_from = dps_uf.lookup_strings(line['from_bus'], buses['name'])
            idx_to = dps_uf.lookup_strings(line['to_bus'], buses['name'])
            if line['unit'] in ['p.u.', 'pu', 'pu/km']:
                if 'S_n' in line.dtype.names and 'V_n' in line.dtype.names and line[
                        'S_n'] != 0 and line['V_n'] != 0:
                    # If impedance given in p.u./km
                    impedance = (line['R'] + 1j * line['X']) * line['length'] * line['V_n'] ** 2 / line['S_n'] / \
                                self.z_n[idx_from]
                    shunt = 1j * line['B'] * line['length'] * 1 / (
                        line['V_n']**2 / line['S_n'] / self.z_n[idx_from])
                else:
                    # Per unit of system base and bus nominal voltage
                    impedance = (line['R'] + 1j * line['X']) * line['length']
                    shunt = 1j * line['B'] * line['length']
            elif line['unit'] in ['PF', 'pf', 'PowerFactory', 'powerfactory']:
                # Given in ohm/km, but with capacitance in micro-Siemens
                impedance = (line['R'] + 1j *
                             line['X']) * line['length'] / self.z_n[idx_from]
                shunt = 1j * line['B'] * line['length'] * self.z_n[
                    idx_from] * 1e-6
            elif line['unit'] in ['Ohm', 'ohm']:
                # Given in Ohm/km
                impedance = (line['R'] + 1j *
                             line['X']) * line['length'] / self.z_n[idx_from]
                shunt = 1j * line['B'] * line['length'] * self.z_n[idx_from]
            admittance = 1 / impedance
            return idx_from, idx_to, admittance, shunt

        elif element_type == 'transformer':
            trafo = element
            idx_from = dps_uf.lookup_strings(trafo['from_bus'], buses['name'])
            idx_to = dps_uf.lookup_strings(trafo['to_bus'], buses['name'])
            ratio_from = (
                trafo['ratio_from'] if not np.isnan(trafo['ratio_from']) else
                1) if 'ratio_from' in trafo.dtype.names else 1
            ratio_to = (trafo['ratio_to'] if not np.isnan(trafo['ratio_to'])
                        else 1) if 'ratio_to' in trafo.dtype.names else 1

            V_n_from = trafo['V_n_from'] if trafo['V_n_from'] else self.v_n[
                idx_from]
            Z_base_trafo = V_n_from**2 / trafo[
                'S_n']  # <= Could also have used _to instead of _from
            impedance = (trafo['R'] +
                         1j * trafo['X']) * Z_base_trafo / self.z_n[idx_from]
            n_par = trafo['N_par'] if 'N_par' in trafo.dtype.names else 1
            admittance = n_par / impedance

            return idx_from, idx_to, admittance, ratio_from, ratio_to
Ejemplo n.º 2
0
    def network_event(self, event_type, name, action, dS=0):
        # Simulate disconnection/connection of element by modifying admittance matrix

        if action == 'deactivate' or action == 'disconnect':
            sign = -1
        elif action == 'activate' or action == 'connect':
            sign = 1

        if event_type == 'line':
            df = getattr(self, 'lines')
            obj = df[dps_uf.lookup_strings(name, df['name'])]
            print('Line {} event'.format(name))
            idx_from, idx_to, admittance, shunt = self.read_admittance_data(
                'line', obj)
            rows = np.array([idx_from, idx_to, idx_from, idx_to])
            cols = np.array([idx_from, idx_to, idx_to, idx_from])
            data = np.array([
                admittance + shunt / 2, admittance + shunt / 2, -admittance,
                -admittance
            ])

            y_line = lil_matrix((self.n_bus, ) * 2, dtype=complex)
            y_line[rows, cols] = data
            self.y_bus_red += sign * y_line

        elif event_type == 'sc':
            print('Short circuit event at bus {}'.format(name))
            idx = dps_uf.lookup_strings(name, self.buses['name'])
            self.y_bus_red[idx, idx] += 1j * sign * 1e15

        elif event_type == 'load_change':
            print('Step change in load demand {}'.format(name))

            y_b_old = self.build_y_bus()
            # Add the additional value and recalculate y_buses
            p0 = self.loads['P']
            q0 = self.loads['Q']
            boolean = (self.loads['name'] == name)
            p0[boolean] += np.real(dS)
            q0[boolean] += np.imag(dS)

            self.loads['P'] = p0
            self.loads['Q'] = q0
            busname = self.loads['bus'][boolean]
            idx = dps_uf.lookup_strings(busname, self.buses['name'])[0]

            y_b_new = self.build_y_bus()
            self.y_bus_red[idx, idx] += y_b_new[idx, idx] - y_b_old[idx, idx]
Ejemplo n.º 3
0
    def build_y_bus_red(self, keep_extra_buses=['name']):
        # Builds the admittance matrix of the reduced system by applying Kron reduction. By default, all buses other
        # generator buses are eliminated. Additional buses to include in the reduced system can be specified
        # in "keep_extra_buses" (list of bus names).

        # If extra buses are specified before , store these. To ensure that the reduced admittance matrix has the same
        # dimension if rebuilt (by i.e. by network_event()-function.
        if len(keep_extra_buses) > 0:
            keep_extra_buses = self.buses['name']  # Override kron reduction
            keep_extra_buses_idx = dps_uf.lookup_strings(
                keep_extra_buses, self.buses['name'])
            self.reduced_bus_idx = np.concatenate(
                [self.gen_bus_idx,
                 np.array(keep_extra_buses_idx, dtype=int)])
            # Remove duplicate buses
            _, idx = np.unique(self.reduced_bus_idx, return_index=True)
            self.reduced_bus_idx = np.sort(self.reduced_bus_idx[np.sort(idx)])
        self.n_bus_red = len(self.reduced_bus_idx)
        self.y_bus_red_full = self.kron_reduction(
            self.y_bus,
            self.reduced_bus_idx)  # np.empty((self.n_gen, self.n_gen))

        self.gen_bus_idx_red = self.get_bus_idx_red(
            self.buses[self.gen_bus_idx]['name'])

        self.y_bus_red = sp.csr_matrix(self.y_bus_red_full)
        self.y_bus_red_mod = self.y_bus_red * 0

        self.build_y_branch()
Ejemplo n.º 4
0
    def store_vars(self, type, varnames, vardesc, resultdict):
        if type == 'GEN':

            var_outs = sorted(set(varnames)
                              & set(self.gen_mdls['GEN'].output_list),
                              key=varnames.index)
            var_ins = sorted(set(varnames)
                             & set(self.gen_mdls['GEN'].input_list),
                             key=varnames.index)
            store_vars_out = self.gen_mdls['GEN'].output[var_outs]

            store_vars_out = [
                i[k] for k in range(len(store_vars_out[0]))
                for i in store_vars_out
            ]

            store_vars_in = self.gen_mdls['GEN'].input[var_ins]
            store_vars_in = [
                i[k] for k in range(len(store_vars_in[0]))
                for i in store_vars_in
            ]
            store_vars_out.extend(store_vars_in)
            [
                resultdict[tuple(desc)].append(out)
                for desc, out in zip(vardesc, store_vars_out)
            ]
        elif type == 'load':
            if 'v' in varnames:
                [
                    resultdict[tuple(desc)].append(out)
                    for desc, out in zip(vardesc, self.v_red)
                ]
            if any(x in ['P_l', 'Q_l', 'S_l'] for x in varnames):
                store_vars = []
                for load in self.loads:
                    name = load[0]
                    load_idx = dps_uf.lookup_strings(name, self.loads['name'])
                    z = self.loads['Z'][load_idx]
                    bus_idx = self.loads['bus_idx'][
                        load_idx]  # This works when kron reduction is bypassed!
                    # if bus_idx in self.reduced_bus_idx:
                    s = abs(self.v_red[bus_idx])**2 / np.conj(z)
                    if 'P_l' in varnames:
                        store_vars.append(np.real(s))
                    if 'Q_l' in varnames:
                        store_vars.append(np.imag(s))
                    if 'S_l' in varnames:
                        store_vars.append(s)

            [
                resultdict[tuple(desc)].append(out)
                for desc, out in zip(vardesc, store_vars)
            ]
Ejemplo n.º 5
0
    def compute_result(self, type, name, res):
        if type == 'load':
            if res in ['p', 'q', 's', 'P', 'Q', 'S']:
                load_idx = dps_uf.lookup_strings(name, self.loads['name'])
                z = self.loads['Z'][load_idx]
                bus_idx = self.loads['bus_idx'][
                    load_idx]  # This works when kron reduction is bypassed!
                # if bus_idx in self.reduced_bus_idx:
                s = abs(self.v_red[bus_idx])**2 / np.conj(z)

                # Lower case letters (p, q, s) give result in system p.u. base
                # Capital letters (P, Q, S) give result in ohm
                if res.isupper():
                    pu_mod = self.s_n
                else:
                    pu_mod = 1

                if res in ['s', 'S']:
                    return s * pu_mod
                if res in ['p', 'P']:
                    return s.real * pu_mod
                if res in ['q', 'Q']:
                    return s.imag * pu_mod
Ejemplo n.º 6
0
    def init_dyn_sim(self):

        # Build reduced system
        self.y_bus = self.build_y_bus()
        self.build_y_bus_red()
        self.v_red = self.v_0[self.reduced_bus_idx]

        # State variables:
        self.state_desc = np.empty((0, 2))

        # Containers for dynamic models
        self.gen_mdls = dict()
        self.gov_mdls = dict()
        self.avr_mdls = dict()
        self.pss_mdls = dict()
        # ADDED
        self.ace_mdls = dict()

        for i, (input_data, container, library) in enumerate(
                zip([self.gen, self.gov, self.pss, self.avr, self.ace], [
                    self.gen_mdls, self.gov_mdls, self.pss_mdls, self.avr_mdls,
                    self.ace_mdls
                ], [gen_lib, gov_lib, pss_lib, avr_lib, ace_lib])):

            for key in input_data.keys():
                data = input_data[key].copy()
                start_idx = len(self.state_desc)
                mdl = getattr(library, key)()
                state_list = mdl.state_list
                names = data['name']
                n_units = len(data)
                n_states = len(state_list)

                # state_desc_mdl = np.vstack([np.tile(names, n_states), np.repeat(state_list, n_units)]).T
                state_desc_mdl = np.vstack(
                    [np.repeat(names, n_states),
                     np.tile(state_list, n_units)]).T

                mdl.n_units = n_units
                # Global indices of model states in global state vector.
                # (x_global[mdl.idx] = x_local)
                mdl.idx = slice(start_idx, start_idx + len(state_desc_mdl)
                                )  # Indices of all states belonging to model
                # dtypes-attribute is defined to allow view to be created easily (with named fields)
                # Allows syntax x['speed'] instead of x[state_idx['speed']]
                # within differential equation functions of dyn. models.
                mdl.dtypes = [(state, np.float) for state in state_list]
                mdl.shape = (n_states, n_units)
                mdl.par = data
                mdl.int_par = np.zeros(n_units, [(par, np.float)
                                                 for par in mdl.int_par_list])
                mdl.input = np.zeros(n_units, [(field, np.float)
                                               for field in mdl.input_list])
                mdl.output = np.zeros(n_units, [(field, np.float)
                                                for field in mdl.output_list])

                # JIT-compilation using Numba
                compile_these = ['_update', '_current_injections']
                if self.use_numba:
                    from numba import jit  # Is this OK?
                    for fun in compile_these:
                        if hasattr(mdl, fun):
                            setattr(mdl, fun[1:], jit()(getattr(mdl, fun)))
                else:
                    for fun in compile_these:
                        if hasattr(mdl, fun):
                            setattr(mdl, fun[1:], getattr(mdl, fun))

                if i == 0:  # Do this for generators only
                    mdl.bus_idx = dps_uf.lookup_strings(
                        data['bus'], self.buses['name'])
                    mdl.bus_idx_red = self.get_bus_idx_red(data['bus'])
                    mdl.int_par['f'] = self.f

                elif i == 4:  # Do this for ACE only
                    mdl.active = np.ones(len(data), dtype=bool)

                    # Appended for knowing which generator are equipped with secondary control
                    mdl.gen_idx = dict()
                    for gen_key in self.gen.keys():
                        lookup, mask = dps_uf.lookup_strings(
                            data['gen'],
                            self.gen[gen_key]['name'],
                            return_mask=True)
                        if len(lookup) > 0:
                            mdl.gen_idx[gen_key] = [mask, lookup]

                    # Getting correct bus indexes for enabling efficient calculation of power flow
                    buses_1 = []
                    buses_2 = []
                    buses_to_keep_1 = data['bus1']
                    buses_to_keep_2 = data['bus2']
                    for bus1, bus2 in zip(buses_to_keep_1, buses_to_keep_2):
                        for bus in self.buses['name']:
                            if bus == bus1: buses_1.append(bus)
                            if bus == bus2: buses_2.append(bus)

                    mdl.bus_idx_1 = dps_uf.lookup_strings(
                        buses_1, self.buses['name'])
                    mdl.bus_idx_2 = dps_uf.lookup_strings(
                        buses_2, self.buses['name'])
                    mdl.bus_idx_red_1 = self.get_bus_idx_red(buses_1)
                    mdl.bus_idx_red_2 = self.get_bus_idx_red(buses_2)

                    # Easifying syntax for the following lines
                    idx1 = mdl.bus_idx_red_1
                    idx2 = mdl.bus_idx_red_2

                    # Extracting the relevant entries in y_bus_red, and converting to a list for vector multiplication
                    y_bus_tmp = np.squeeze(
                        np.asarray(self.y_bus_red[idx1, idx2]))
                    i_line = y_bus_tmp * (self.v_red[idx1] - self.v_red[idx2])

                    # Calculating base line flow
                    p_line = -(self.v_red[idx1] * np.conj(i_line)).real

                    # Setting the constant parameters
                    mdl.int_par['f'] = self.f
                    mdl.int_par['Ptie0'] = p_line

                else:  # Do this for control models only
                    mdl.active = np.ones(len(data), dtype=bool)

                    # Determine which generators are controlled (model and indices).
                    # This information is stored as a dict with keys corresponding to generator types,
                    # and values with lists: [mask, idx], where:
                    # mask (np.array, bool): Boolean mask to select controls that act on the particular generator type
                    # idx (np.array, int): Points to which generators of this type are controlled
                    mdl.gen_idx = dict()
                    for gen_key in self.gen.keys():
                        lookup, mask = dps_uf.lookup_strings(
                            data['gen'],
                            self.gen[gen_key]['name'],
                            return_mask=True)
                        if len(lookup) > 0:
                            mdl.gen_idx[gen_key] = [mask, lookup]

                # Local indices of states (starting on zero, ending on number of states of model)
                mdl.state_idx = np.zeros(
                    (n_units, ), dtype=[(state, int) for state in state_list])
                for state in state_list:
                    mdl.state_idx[state] = np.where(
                        state_desc_mdl[:, 1] == state)[0]

                container[key] = mdl
                self.state_desc = np.vstack([self.state_desc, state_desc_mdl])

        self.n_states = self.state_desc.shape[0]
        self.state_desc_der = self.state_desc.copy()
        self.state_desc_der[:,
                            1] = np.char.add(np.array(self.n_states * ['d_']),
                                             self.state_desc[:, 1])

        # Choose first generator at slack bus as slack generator
        for gen_key, gen_mdl in self.gen_mdls.items():
            lookup = dps_uf.lookup_strings(self.slack_bus, gen_mdl.par['bus'])
            if not np.isnan(lookup) > 0:
                self.slack_generator_id = (gen_key, lookup)
                break

        self.slack_generator = np.where(
            (self.gen_pf_map_idx == self.slack_generator_id).all(axis=1))[0][0]

        self.p_g_setp = self.P_g_setp / self.s_n  # System base
        sum_load_sl = sum(self.loads[self.loads['bus'] == self.slack_bus]
                          ['P']) / self.s_n if len(
                              self.loads) > 0 else 0  # Sum loads at slack bus
        sl_gen_idx = np.array(self.gen_bus_names == self.slack_bus)

        # The case with multiple generators at the slack bus where one or more are n_par in parallel has not been tested.
        sum_gen_sl = sum(
            (self.p_g_setp[sl_gen_idx] * self.n_par[sl_gen_idx]
             )[1:])  # Sum other generation at slack bus (not slack gen)
        self.p_g_setp[self.slack_generator] = self.s_0[self.get_bus_idx(
            [self.slack_bus])].real - sum_gen_sl + sum_load_sl
        self.p_g_setp[self.slack_generator] /= self.n_par[self.slack_generator]

        # Distribute reactive power equally among generators on the same bus
        self.n_gen_per_bus = np.array([
            sum(self.gen_bus_idx == idx) for idx in np.arange(self.n_bus)
        ])  # Not counting identical generators (given by N_par-parameter)!
        self.q_g = (self.s_0.imag + self.q_sum_loads_bus
                    )[self.gen_bus_idx] / self.n_gen_per_bus[self.gen_bus_idx]
        self.q_g /= self.n_par

        # From Load Flow
        self.v_g = self.v_0[self.gen_bus_idx]
        self.s_g = (self.p_g_setp + 1j * self.q_g)

        # Initialize global state vector
        self.x0 = np.zeros(self.n_states)

        # Generators
        for key, dm in self.gen_mdls.items():
            if hasattr(dm, 'initialize'):
                # Get data from power flow solution
                dm_pf_map_idx = self.gen_pf_map_idx[:, 0] == key
                v_g = self.v_g[dm_pf_map_idx]
                s_g = self.s_g[dm_pf_map_idx] * self.s_n / dm.par['S_n']
                # Convert complex quantities to float
                dm.input['V_t_abs'] = np.abs(v_g)
                dm.input['V_t_angle'] = np.angle(v_g)
                dm.output['P_e'] = s_g.real
                dm.output['Q'] = s_g.imag
                dm.initialize(self.x0[dm.idx].view(dtype=dm.dtypes), dm.input,
                              dm.output, dm.par, dm.int_par)

        # AVR
        for key, dm in self.avr_mdls.items():
            if hasattr(dm, 'initialize'):
                for gen_key, (mask, idx) in dm.gen_idx.items():
                    gen_mdl = self.gen_mdls[gen_key]
                    dm.output['E_f'][mask] = gen_mdl.input['E_f'][idx]

                # for gen_key in dm.gen_mdl_list:
                #     e_q_0[dm.gen_mdl_3 == gen_key] = self.gen_mdls[gen_key].e_q[dm.gen_idx_2[gen_key]]

                dm.initialize(self.x0[dm.idx].view(dtype=dm.dtypes), dm.input,
                              dm.output, dm.par, dm.int_par)

        # GOV
        for key, dm in self.gov_mdls.items():
            if hasattr(dm, 'initialize'):
                for gen_key, (mask, idx) in dm.gen_idx.items():
                    # mask: Boolean mask to map controls to generators
                    # idx: Points to which generators are controlled
                    gen_mdl = self.gen_mdls[gen_key]
                    dm.output['P_m'][mask] = gen_mdl.input['P_m'][idx]

                # for gen_key in dm.gen_mdl_list:
                #     P_m_0[dm.gen_mdl_3 == gen_key] = self.gen_mdls[gen_key].P_m[dm.gen_idx_2[gen_key]]

                dm.initialize(self.x0[dm.idx].view(dtype=dm.dtypes), dm.input,
                              dm.output, dm.par, dm.int_par)

        # ACE
        for key, dm in self.ace_mdls.items():
            if hasattr(dm, 'initialize'):
                pass

        # PSS
        for key, dm in self.pss_mdls.items():
            if hasattr(dm, 'initialize'):
                pass
Ejemplo n.º 7
0
    def build_y_bus(self, type='dyn', y_ext=np.empty((0, 0))):
        # Build bus admittance matrix.
        # If type=='dyn', generator admittances and load admittances are included in the admittance matrix.
        # Used for dynamic simulation.
        # If not type=='dyn', generator admittances and load admittances are not included.
        # Used for power flow calculation.

        n_bus = self.n_bus
        y_branch = np.zeros((n_bus, n_bus),
                            dtype=complex)  # Branches = Trafos + Lines
        buses = self.buses

        y_lines = np.zeros((n_bus, n_bus), dtype=complex)

        for i, line in enumerate(self.lines):
            idx_from, idx_to, admittance, shunt = self.read_admittance_data(
                'line', line)
            rows = np.array([idx_from, idx_to, idx_from, idx_to])
            cols = np.array([idx_from, idx_to, idx_to, idx_from])
            data = np.array([
                admittance + shunt / 2, admittance + shunt / 2, -admittance,
                -admittance
            ])
            y_lines[rows, cols] += data

        y_branch += y_lines

        y_trafo = np.zeros((n_bus, n_bus), dtype=complex)
        for i, trafo in enumerate(self.transformers):

            idx_from, idx_to, admittance, ratio_from, ratio_to = self.read_admittance_data(
                'transformer', trafo)

            rows = np.array([idx_from, idx_to, idx_from, idx_to])
            cols = np.array([idx_from, idx_to, idx_to, idx_from])
            data = np.array([
                ratio_from * np.conj(ratio_from) * admittance,
                ratio_to * np.conj(ratio_to) * admittance,
                -ratio_from * np.conj(ratio_to) * admittance,
                -np.conj(ratio_from) * ratio_to * admittance
            ])
            y_trafo[rows, cols] += data

        y_branch += y_trafo

        y_gen = np.zeros((n_bus, n_bus), dtype=complex)
        for key, data in self.gen.items():
            for gen in data:
                idx_bus = dps_uf.lookup_strings(gen['bus'], buses['name'])
                V_n = gen['V_n']
                impedance = 1j * gen['X_d_st'] * V_n**2 / gen[
                    'S_n'] / self.z_n[idx_bus]
                y_gen[idx_bus, idx_bus] += gen['N_par'] / impedance

        y_load = np.zeros((n_bus, n_bus), dtype=complex)
        if type == 'dyn':

            # Remove Z-field if it already exists since y_bus is to be modified during load increase
            if 'Z' in self.loads.dtype.names:
                self.loads = self.loads[['name', 'bus', 'P', 'Q', 'model']]

            # Add impedance field to load input parameters (to be able to compute actual power consumed by load)
            # Impedance is given in system p.u. base
            if len(self.loads) > 0:
                field_z = np.ones(len(self.loads), dtype=[('Z', complex)])
                field_bus = np.ones(len(self.loads), dtype=[('bus_idx', int)])

            for i, load in enumerate(self.loads):
                s_load = (load['P'] + 1j * load['Q']) / self.s_n
                if load['model'] == 'Z' and abs(s_load) > 0:
                    # idx_bus = buses[buses['name'] == load['bus']].index
                    idx_bus = dps_uf.lookup_strings(load['bus'], buses['name'])
                    z = np.conj(abs(self.v_0[idx_bus])**2 / s_load)
                    y_load[idx_bus, idx_bus] += 1 / z
                    field_z[i] = z
                    field_bus[i] = idx_bus

            if len(self.loads) > 0:
                self.loads = dps_uf.combine_recarrays(self.loads, field_z)
                self.loads = dps_uf.combine_recarrays(self.loads, field_bus)

        y_shunt = np.zeros((n_bus, n_bus), dtype=complex)
        for i, shunt in enumerate(self.shunts):
            # if shunt['model'] == 'Z':
            # idx_bus = buses[buses['name'] == shunt['bus']].index
            idx_bus = dps_uf.lookup_strings(shunt['bus'], buses['name'])
            s_shunt = -1j * shunt['Q'] / self.s_n

            z = np.conj(abs(1)**2 / s_shunt)
            y_shunt[idx_bus, idx_bus] += 1 / z

        self.y_gen = y_gen
        self.y_branch = y_branch
        self.y_load = y_load
        self.y_shunt = y_shunt
        self.y_ext = y_ext

        Y = y_branch.copy()
        Y += y_shunt
        if type == 'dyn':
            Y += y_gen + y_load
        if y_ext.shape[0] > 0:
            Y += y_ext
        return Y
Ejemplo n.º 8
0
    def __init__(self, model):

        self.use_numba = False

        # Load flow parameters
        self.pf_max_it = 10
        self.tol = 1e-8

        # Get static data: Convert lists to np.arrays
        for td in ['buses', 'lines', 'loads', 'transformers', 'shunts']:
            if td in model and len(model[td]) > 0:
                header = model[td][0]
                # Get dtype for each column
                data = model[td][1:]
                data_T = list(map(list, zip(*data)))
                col_dtypes = [np.array(col).dtype for col in data_T]

                entries = [tuple(entry) for entry in model[td][1:]]
                dtypes = [(name_, dtype_)
                          for name_, dtype_ in zip(header, col_dtypes)]
                setattr(self, td, np.array(entries, dtype=dtypes))
            else:
                setattr(self, td, np.empty(0))

        # Get dynamic data: Convert dicts with lists to dicts with np.arrays
        for td in ['gov', 'avr', 'pss', 'generators', 'ace']:  # ADDED ACE
            setattr(self, td, dict())
            if td in model and len(model[td]) > 0:
                for key in model[td].keys():
                    # Get dtype for each column
                    header = model[td][key][0]
                    data = model[td][key][1:]
                    data_T = list(map(list, zip(*data)))
                    col_dtypes = [np.array(col).dtype for col in data_T]

                    entries = [tuple(entry) for entry in data]
                    dtypes = [(name_, dtype_)
                              for name_, dtype_ in zip(header, col_dtypes)]
                    print(key)
                    getattr(self, td)[key] = np.array(entries, dtype=dtypes)

        # Add some potentially missing fields
        for key in self.generators.keys():
            for req_attr, default in zip(['PF_n', 'N_par'], [1, 1]):
                if not req_attr in self.generators[key].dtype.names:
                    new_field = np.ones(len(self.generators[key]),
                                        dtype=[(req_attr, float)])
                    new_field[req_attr] *= default
                    self.generators[key] = dps_uf.combine_recarrays(
                        self.generators[key], new_field)

        # Base for pu-system
        self.f = model['f']
        self.s_n = model['base_mva']  # For all buses
        self.v_n = np.array(self.buses['V_n'])
        self.z_n = self.v_n**2 / self.s_n
        self.i_n = self.s_n / (np.sqrt(3) * self.v_n)
        if 'slack_bus' in model:
            self.slack_bus = model['slack_bus']
        else:
            self.slack_bus = None
        self.n_bus = len(self.buses)

        # If nominal gen. voltage or apparent power is zero, change to system base
        # (Needs to be done after nominal bus voltages have been loaded (self.v_n)
        for key in self.generators.keys():
            fix_idx = self.generators[key]['V_n'] == 0
            gen_bus_idx = dps_uf.lookup_strings(self.generators[key]['bus'],
                                                self.buses['name'])
            self.generators[key]['V_n'][fix_idx] = self.buses['V_n'][
                gen_bus_idx][fix_idx]

            fix_idx = self.generators[key]['S_n'] == 0
            self.generators[key]['S_n'][fix_idx] = self.s_n

        # This is for backwards compatibilty only. Should have only self.gen (dict) in the future.
        self.gen = self.generators
        self.generators = self.gen['GEN']

        # Load flow data (concatenate data from all generator types)
        self.n_gen = sum([len(gen) for gen in self.gen.values()])
        self.v_g_setp = np.concatenate([gen['V'] for gen in self.gen.values()])
        self.P_g_setp = np.concatenate([gen['P'] for gen in self.gen.values()])
        self.n_par = np.concatenate(
            [gen['N_par'] for gen in self.gen.values()])
        self.gen_bus_names = np.concatenate(
            [gen['bus'] for gen in self.gen.values()])
        self.gen_pf_map_idx = []
        for key, gen in self.gen.items():
            [self.gen_pf_map_idx.append((key, i)) for i in range(len(gen))]
        self.gen_pf_map_idx = np.array(self.gen_pf_map_idx)
        self.gen_bus_idx = dps_uf.lookup_strings(self.gen_bus_names,
                                                 self.buses['name'])

        # Remove duplicate buses to get number of unique buses with generators
        _, idx = np.unique(self.gen_bus_idx, return_index=True)
        self.gen_bus_idx_unique = self.gen_bus_idx[np.sort(idx)]
        self.n_gen_bus = len(self.gen_bus_idx_unique)
        self.reduced_bus_idx = self.gen_bus_idx_unique

        # Initialize bus admittance matrices
        self.y_bus = np.empty((self.n_bus, self.n_bus), dtype=complex)
        self.y_bus_lf = np.empty((self.n_bus, self.n_bus), dtype=complex)
        self.y_bus_lf[:] = np.nan
        self.y_bus_red_full = np.empty((self.n_gen, self.n_gen), dtype=complex)
        # self.y_bus_red_inv = np.empty((self.n_gen, self.n_gen))

        # Initialize dynamic variables
        self.v_g = np.empty(self.n_gen, dtype=complex)
        self.i_inj = np.empty(self.n_gen, dtype=complex)
        self.time = 0.0
Ejemplo n.º 9
0
    def __init__(self, rts, update_freq=50, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.rts = rts
        self.ps = rts.ps
        self.dt = self.rts.dt

        G = nx.MultiGraph()
        G.add_nodes_from(ps.buses['name'])
        G.add_edges_from(ps.lines[['from_bus', 'to_bus']])
        G.add_edges_from(ps.transformers[['from_bus', 'to_bus']])

        # nx.draw(G)

        pos = nx.spring_layout(G)
        x = np.zeros(ps.n_bus)
        y = np.zeros(ps.n_bus)
        for i, key in enumerate(pos.keys()):
            x[i], y[i] = pos[key]

        #plt.scatter(x, y)

        n_edges = len(ps.lines) + len(ps.transformers)
        x_edge = np.zeros(2 * n_edges)
        y_edge = np.zeros(2 * n_edges)
        connect = np.zeros(2 * n_edges, dtype=bool)
        i = 0
        for branches in [ps.lines, ps.transformers]:
            for line in branches:
                x_edge[2 * i] = x[dps_uf.lookup_strings(
                    line['from_bus'], ps.buses['name'])]
                y_edge[2 * i] = y[dps_uf.lookup_strings(
                    line['from_bus'], ps.buses['name'])]
                x_edge[2 * i + 1] = x[dps_uf.lookup_strings(
                    line['to_bus'], ps.buses['name'])]
                y_edge[2 * i + 1] = y[dps_uf.lookup_strings(
                    line['to_bus'], ps.buses['name'])]
                connect[2 * i] = True
                i += 1

        #for load in ps.loads:

        dt = self.dt
        n_samples = 500
        dt = 20e-3

        self.colors = lambda i: pg.intColor(i,
                                            hues=9,
                                            values=1,
                                            maxValue=255,
                                            minValue=150,
                                            maxHue=360,
                                            minHue=0,
                                            sat=255,
                                            alpha=255)
        # Phasor diagram
        self.graphWidget = pg.GraphicsLayoutWidget(show=True, title="Phasors")
        # self.setCentralWidget(self.graphWidget)

        #self.phasor_0 = np.array([0, 1, 0.9, 1, 0.9, 1]) + 1j * np.array([0, 0, -0.1, 0, 0.1, 0])
        plot_win = self.graphWidget.addPlot(title='Phasors')
        plot_win.setAspectLocked(True)

        plot_win.plot(x_edge, y_edge, connect=connect)
        plot_win.plot(x,
                      y,
                      pen=None,
                      symbol='o',
                      symbolBrush='b',
                      symbolSize=15)
        plot_win.plot(x[ps.gen_bus_idx],
                      y[ps.gen_bus_idx],
                      pen=None,
                      symbol='o',
                      symbolBrush='r',
                      symbolSize=15)

        #plot_win.plot(x[ps.gen_bus_idx], y[ps.gen_bus_idx], pen=None, symbol='o', symbolBrush='y', symbolSize=15)

        #self.colors = lambda i: pg.intColor(i, hues=9, values=1, maxValue=255, minValue=150, maxHue=360, minHue=0, sat=255, alpha=255)
        #pen = pg.mkPen(color=self.colors(0), width=2)
        #self.graphWidget.plot(self.ts_keeper.time, np.zeros(n_samples), pen=pen)

        #scatter = pg.ScatterPlotItem(size=ps.n_bus, brush=pg.mkBrush(255, 255, 255, 120))

        #angle = self.rts.x[self.ps.gen_mdls['GEN'].state_idx['angle']]
        #magnitude = self.ps.e_q
        #phasors = magnitude*np.exp(1j*angle)

        #self.pl_ph = []
        #for i, phasor in enumerate(phasors[:, None]*self.phasor_0):
        #    pen = pg.mkPen(color=self.colors(i), width=2)
        #    self.pl_ph.append(plot_win_ph.plot(phasor.real, phasor.imag, pen=pen))
        #plot_win_ph.enableAutoRange('xy', False)

        self.graphWidget.show()

        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.update)
        self.timer.start(1000 / update_freq)
Ejemplo n.º 10
0
    def __init__(self, rts, update_freq=50, z_ax='abs_pu', use_colors=False, rotating=False, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.rts = rts
        self.ps = rts.ps
        self.dt = self.rts.dt
        ps = self.ps
        self.z_ax = z_ax

        # nx.draw(G)
        self.scale = 10

        if self.z_ax == 'angle':
            self.scale_z = 10*np.ones(ps.n_bus)
            self.offset_z = 3  # *np.ones(ps.n_bus)
        elif self.z_ax == 'abs':
            self.scale_z = 10 * ps.v_n / max(ps.v_n) * 0.3
            self.offset_z = 0  # np.zeros(ps.n_bus)

        elif self.z_ax == 'abs_pu':
            self.scale_z = 10 * 0.3*(ps.v_n**0)
            self.offset_z = 0  # np.zeros(ps.n_bus)
        # elif self.z_ax == 'both':
        #     self.scale_z = 10*np.ones(ps.n_bus)
        #     self.offset_z = 12*np.ones(ps.n_bus)


        line_admittances = np.zeros(len(ps.lines), dtype=[('Y', float)])
        for i, line in enumerate(ps.lines):
            line_admittances[i] = abs(ps.read_admittance_data('line', line)[2])

        trafo_admittances = np.zeros(len(ps.transformers), dtype=[('Y', float)])
        for i, trafo in enumerate(ps.transformers):
            trafo_admittances[i] = abs(ps.read_admittance_data('transformer', trafo)[2])

        self.G = nx.MultiGraph()
        self.G.add_nodes_from(ps.buses['name'])
        # G.add_edges_from(ps.lines[['from_bus', 'to_bus']])
        # G.add_edges_from(ps.transformers[['from_bus', 'to_bus']])
        self.G.add_weighted_edges_from(
            dps_uf.combine_recarrays(ps.lines, line_admittances)[['from_bus', 'to_bus', 'Y']])
        self.G.add_weighted_edges_from(
            dps_uf.combine_recarrays(ps.transformers, trafo_admittances)[['from_bus', 'to_bus', 'Y']])

        self.grid_layout()

        self.n_edges = len(ps.lines) + len(ps.transformers)
        self.edge_from_bus = np.concatenate([dps_uf.lookup_strings(type_['from_bus'], ps.buses['name']) for type_ in [ps.lines, ps.transformers]])
        self.edge_to_bus = np.concatenate([dps_uf.lookup_strings(type_['to_bus'], ps.buses['name']) for type_ in [ps.lines, ps.transformers]])

        self.edge_x = np.vstack([self.x[self.edge_from_bus], self.x[self.edge_to_bus]]).T
        self.edge_y = np.vstack([self.y[self.edge_from_bus], self.y[self.edge_to_bus]]).T
        self.edge_z = np.vstack([self.z[self.edge_from_bus], self.z[self.edge_to_bus]]).T

        self.colors = lambda i: pg.intColor(i, hues=9, values=1, maxValue=255, minValue=150, maxHue=360, minHue=0, sat=255, alpha=255)

        self.window = gl.GLViewWidget()
        # self.window.setBackgroundColor('w')
        self.window.setWindowTitle('Grid')
        self.window.setGeometry(0, 110, 1920, 1080)
        self.window.setCameraPosition(distance=30, elevation=12)
        self.window.show()

        self.rotating = rotating

        self.gz = gl.GLGridItem()
        self.gz.translate(dx=0, dy=0, dz=-self.offset_z)
        self.window.addItem(self.gz)

        color = np.ones((ps.n_bus, 4))
        color[:, -1] = 0.5
        if use_colors:
            color[ps.gen_bus_idx, :] = np.array([self.colors(i).getRgb() for i in range(self.ps.n_gen_bus)]) / 255
        else:
            color[ps.gen_bus_idx, :] = np.array([1 / 3, 2 / 3, 1, 0.5])[None, :]

        self.points = gl.GLScatterPlotItem(
            pos=np.vstack([self.x, self.y, self.z]).T,
            color=color,
            size=15
        )

        self.edge_x_mod = np.append(self.edge_x, np.nan*np.ones((self.n_edges, 1)), axis=1)
        self.edge_y_mod = np.append(self.edge_y, np.nan*np.ones((self.n_edges, 1)), axis=1)
        self.edge_z_mod = np.append(self.edge_z, np.nan*np.ones((self.n_edges, 1)), axis=1)
        # line_x_mod = line_x
        # line_y_mod = line_y
        edge_pos = np.vstack([self.edge_x_mod.flatten(), self.edge_y_mod.flatten(), self.edge_z_mod.flatten()]).T
        line_color = np.ones((self.n_edges, 4))
        line_color[:, -1] = 0.5
        line_color[len(ps.lines):, :] = np.array([1 / 3, 2 / 3, 1, 0.5])[None, :]
        # self.scale_branch_flows = 4
        line_widths = np.ones((self.n_edges, 4))
        self.lines = gl.GLLinePlotItem(pos=edge_pos, color=np.repeat(line_color, 3, axis=0), antialias=True, width=2)

        self.window.addItem(self.points)
        self.window.addItem(self.lines)

        # self.axis = gl.GLAxisItem()

        # self.graphWidget.show()

        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.update)
        self.timer.start(1000//update_freq)
        self.t_prev = time.time()
Ejemplo n.º 11
0
model = model_data.load()
#if not model['gov_on']: model.pop('gov', None)
#if not model['avr_on']: model.pop('avr', None)
#if not model['pss_on']: model.pop('pss', None)
fig, ax = plt.subplots(1)

ps = dps.PowerSystemModel(model)
ps.power_flow()
ps.init_dyn_sim()

# Index to access relevant models, e.g. generators, avrs, govs etc.
# Without index, the change is applied to all instances of specified model, e.g. all 'GEN' generators, or all 'TGR' avrs

# index = dps_uf.lookup_strings('G1', ps.gen_mdls['GEN'].par['name'])  # Index for G1
index = dps_uf.lookup_strings(
    'HVDC_CTRL1', ps.hvdc_ctrl_mdls['HVDC_WASH_LEAD_LAG'].par['name'])
#index = dps_uf.lookup_strings('BAT_CTRL_WASH_1', ps.bat_ctrl_mdls['WASH_LEAD_LAG'].par['name'])
# index = dps_uf.lookup_strings('AVR1', ps.avr_mdls['TGR'].par['name'])  # Index for AVR1

# index = dps_uf.lookup_strings(['G1','IB'], ps.gen_mdls['GEN'].par['name'])  # Indices for G1 and IB

for i in range(10):
    ps.init_dyn_sim()
    print(index)
    ps.hvdc_ctrl_mdls['HVDC_WASH_LEAD_LAG'].par['K'][index] = 0 + i * 1
    #ps.gen_mdls['GEN'].par['H'][index] = 2 + i*0.1
    # Perform system linearization
    ps_lin = dps_mdl.PowerSystemModelLinearization(ps)
    ps_lin.linearize()

    #pf, rev_Abs, rev_ang = ps_lin.pf_table()
Ejemplo n.º 12
0
import dynpssimpy.utility_functions as dps_uf

model = model_data.load()
if not model['gov_on']: model.pop('gov', None)
if not model['avr_on']: model.pop('avr', None)
if not model['pss_on']: model.pop('pss', None)
fig, ax = plt.subplots(1)

ps = dps.PowerSystemModel(model)
ps.power_flow()
ps.init_dyn_sim()

# Index to access relevant models, e.g. generators, avrs, govs etc.
# Without index, the change is applied to all instances of specified model, e.g. all 'GEN' generators, or all 'TGR' avrs

index = dps_uf.lookup_strings('G1',
                              ps.gen_mdls['GEN'].par['name'])  # Index for G1

# index = dps_uf.lookup_strings('AVR1', ps.avr_mdls['TGR'].par['name'])  # Index for AVR1

# index = dps_uf.lookup_strings(['G1','IB'], ps.gen_mdls['GEN'].par['name'])  # Indices for G1 and IB

for i in range(30):
    ps.init_dyn_sim()
    print(index)
    ps.avr_mdls['TGR'].par['Ka'][index] = 10 + i * 30
    #ps.gen_mdls['GEN'].par['H'][index] = 2 + i*0.1
    # Perform system linearization
    ps_lin = dps_mdl.PowerSystemModelLinearization(ps)
    ps_lin.linearize()

    #pf, rev_Abs, rev_ang = ps_lin.pf_table()