Exemple #1
0
def power_flow_worker(t, options: PowerFlowOptions, circuit: CalculationInputs,
                      Vbus, Sbus, Ibus, return_dict):
    """
    Power flow worker to schedule parallel power flows
        **t: execution index
        **options: power flow options
        **circuit: circuit
        **Vbus: Voltages to initialize
        **Sbus: Power injections
        **Ibus: Current injections
        **return_dict: parallel module dictionary in wich to return the values
    :return:
    """

    instance = PowerFlowMP(None, options)
    return_dict[t] = instance.run_pf(circuit, Vbus, Sbus, Ibus)
Exemple #2
0
def simulation_constructor(args):
    """
    function to create a copy of the grid and a power flow associated

    :param args:
    :return:
    """
    # grd = grid.copy()
    # grd.name = 'grid ' + str(i)
    # grd.compile()
    # return PowerFlow(grd, options)
    return PowerFlowMP(args[0], args[1])
    def run(self):
        """
        Run state estimation
        :return:
        """
        n = len(self.grid.buses)
        m = len(self.grid.branches)
        self.se_results = StateEstimationResults()
        self.se_results.initialize(n, m)

        numerical_circuit = self.grid.compile()
        islands = numerical_circuit.compute()

        self.se_results.bus_types = numerical_circuit.bus_types

        for island in islands:

            # collect inputs of the island
            se_input = self.collect_measurements(
                circuit=self.grid,
                bus_idx=island.original_bus_idx,
                branch_idx=island.original_branch_idx)

            # run solver
            v_sol, err, converged = solve_se_lm(Ybus=island.Ybus,
                                                Yf=island.Yf,
                                                Yt=island.Yt,
                                                f=island.F,
                                                t=island.T,
                                                se_input=se_input,
                                                ref=island.ref,
                                                pq=island.pq,
                                                pv=island.pv)

            # Compute the branches power and the slack buses power
            Sbranch, Ibranch, Vbrnach, loading, \
             losses, flow_direction, Sbus = PowerFlowMP.power_flow_post_process(calculation_inputs=island,
                                                                                V=v_sol)

            # pack results into a SE results object
            results = StateEstimationResults(Sbus=Sbus,
                                             voltage=v_sol,
                                             Sbranch=Sbranch,
                                             Ibranch=Ibranch,
                                             loading=loading,
                                             losses=losses,
                                             error=[err],
                                             converged=[converged],
                                             Qpv=None)

            self.se_results.apply_from_island(results, island.original_bus_idx,
                                              island.original_branch_idx)
Exemple #4
0
    def run_single_thread(self) -> TimeSeriesResults:
        """
        Run single thread time series
        :return: TimeSeriesResults instance
        """
        # initialize the power flow
        power_flow = PowerFlowMP(self.grid, self.options)

        # initialize the grid time series results we will append the island results with another function
        n = len(self.grid.buses)
        m = len(self.grid.branches)
        nt = len(self.grid.time_profile)
        time_series_results = TimeSeriesResults(n,
                                                m,
                                                nt,
                                                self.start_,
                                                self.end_,
                                                time=self.grid.time_profile)
        if self.end_ is None:
            self.end_ = nt

        # compile the multi-circuit
        numerical_circuit = self.grid.compile(
            use_opf_vals=self.use_opf_vals,
            opf_time_series_results=self.opf_time_series_results)

        # do the topological computation
        calc_inputs_dict = numerical_circuit.compute_ts(
            branch_tolerance_mode=self.options.branch_impedance_tolerance_mode)

        time_series_results.bus_types = numerical_circuit.bus_types

        # for each partition of the profiles...
        for _, calc_inputs in calc_inputs_dict.items():

            # For every island, run the time series
            for island_index, calculation_input in enumerate(calc_inputs):

                # Are we dispatching storage? if so, generate a dictionary of battery -> bus index
                # to be able to set the batteries values into the vector S
                batteries = list()
                batteries_bus_idx = list()
                if self.options.dispatch_storage:
                    for k, bus in enumerate(self.grid.buses):
                        for battery in bus.batteries:
                            battery.reset()  # reset the calculation values
                            batteries.append(battery)
                            batteries_bus_idx.append(k)

                self.progress_text.emit('Time series at circuit ' +
                                        str(island_index) + '...')

                # find the original indices
                bus_original_idx = calculation_input.original_bus_idx
                branch_original_idx = calculation_input.original_branch_idx

                # if there are valid profiles...
                if self.grid.time_profile is not None:

                    # declare a results object for the partition
                    nt = calculation_input.ntime
                    n = calculation_input.nbus
                    m = calculation_input.nbr
                    results = TimeSeriesResults(n, m, nt, self.start_,
                                                self.end_)
                    last_voltage = calculation_input.Vbus

                    self.progress_signal.emit(0.0)

                    # default value in case of single-valued profile
                    dt = 1.0

                    # traverse the time profiles of the partition and simulate each time step
                    for it, t in enumerate(
                            calculation_input.original_time_idx):

                        if (t >= self.start_) and (t < self.end_):

                            # set the power values
                            # if the storage dispatch option is active, the batteries power is not included
                            # therefore, it shall be included after processing
                            I = calculation_input.Ibus_prof[:, it]
                            S = calculation_input.Sbus_prof[:, it]

                            # add the controlled storage power if we are controlling the storage devices
                            if self.options.dispatch_storage:

                                if (it + 1) < len(
                                        calculation_input.original_time_idx):
                                    # compute the time delta: the time values come in nanoseconds
                                    dt = (calculation_input.time_array[it + 1]
                                          - calculation_input.time_array[it]
                                          ).value * 1e-9 / 3600.0

                                for k, battery in enumerate(batteries):

                                    power = battery.get_processed_at(
                                        it, dt=dt, store_values=True)

                                    bus_idx = batteries_bus_idx[k]

                                    S[bus_idx] += power / calculation_input.Sbase
                            else:
                                pass

                            # run power flow at the circuit
                            res = power_flow.run_pf(circuit=calculation_input,
                                                    Vbus=last_voltage,
                                                    Sbus=S,
                                                    Ibus=I)

                            # Recycle voltage solution
                            last_voltage = res.voltage

                            # store circuit results at the time index 't'
                            results.set_at(t, res)

                            progress = ((t - self.start_ + 1) /
                                        (self.end_ - self.start_)) * 100
                            self.progress_signal.emit(progress)
                            self.progress_text.emit(
                                'Simulating island ' + str(island_index) +
                                ' at ' + str(self.grid.time_profile[t]))

                        else:
                            pass

                        if self.__cancel__:
                            # merge the circuit's results
                            time_series_results.apply_from_island(
                                results, bus_original_idx, branch_original_idx,
                                calculation_input.time_array, 'TS')
                            # abort by returning at this point
                            return time_series_results

                    # merge the circuit's results
                    time_series_results.apply_from_island(
                        results, bus_original_idx, branch_original_idx,
                        calculation_input.time_array, 'TS')

                else:
                    print('There are no profiles')
                    self.progress_text.emit('There are no profiles')

        return time_series_results
Exemple #5
0
    def run_single_thread(self):
        """
        Run the monte carlo simulation
        @return:
        """
        # print('LHS run')
        self.__cancel__ = False

        # initialize the power flow
        power_flow = PowerFlowMP(self.circuit, self.options)

        # initialize the grid time series results
        # we will append the island results with another function
        self.circuit.time_series_results = TimeSeriesResults(0, 0, 0, 0, 0)

        batch_size = self.sampling_points
        n = len(self.circuit.buses)
        m = len(self.circuit.branches)

        self.progress_signal.emit(0.0)
        self.progress_text.emit('Running Latin Hypercube Sampling...')

        lhs_results = MonteCarloResults(n, m, batch_size)
        avg_res = PowerFlowResults()
        avg_res.initialize(n, m)

        # compile the numerical circuit
        numerical_circuit = self.circuit.compile()
        numerical_input_islands = numerical_circuit.compute(
            branch_tolerance_mode=self.options.branch_impedance_tolerance_mode)

        max_iter = batch_size * len(numerical_input_islands)
        Sbase = numerical_circuit.Sbase
        it = 0

        # For every circuit, run the time series
        for numerical_island in numerical_input_islands:

            # try:
            # set the time series as sampled in the circuit
            # build the inputs
            monte_carlo_input = make_monte_carlo_input(numerical_island)
            mc_time_series = monte_carlo_input(batch_size,
                                               use_latin_hypercube=True)
            Vbus = numerical_island.Vbus

            # short cut the indices
            b_idx = numerical_island.original_bus_idx
            br_idx = numerical_island.original_branch_idx

            # run the time series
            for t in range(batch_size):

                # set the power values from a Monte carlo point at 't'
                Y, I, S = mc_time_series.get_at(t)

                # Run the set monte carlo point at 't'
                res = power_flow.run_pf(circuit=numerical_island,
                                        Vbus=Vbus,
                                        Sbus=S / Sbase,
                                        Ibus=I / Sbase)

                # Gather the results
                lhs_results.S_points[
                    t, numerical_island.original_bus_idx] = res.Sbus
                lhs_results.V_points[
                    t, numerical_island.original_bus_idx] = res.voltage
                lhs_results.I_points[
                    t, numerical_island.original_branch_idx] = res.Ibranch
                lhs_results.loading_points[
                    t, numerical_island.original_branch_idx] = res.loading
                lhs_results.losses_points[
                    t, numerical_island.original_branch_idx] = res.losses

                it += 1
                self.progress_signal.emit(it / max_iter * 100)

                if self.__cancel__:
                    break

            if self.__cancel__:
                break

            # compile MC results
            self.progress_text.emit('Compiling results...')
            lhs_results.compile()

            # compute the island branch results
            island_avg_res = numerical_island.compute_branch_results(
                lhs_results.voltage[b_idx])

            # apply the island averaged results
            avg_res.apply_from_island(island_avg_res,
                                      b_idx=b_idx,
                                      br_idx=br_idx)

        # lhs_results the averaged branch magnitudes
        lhs_results.sbranch = avg_res.Sbranch

        lhs_results.bus_types = numerical_circuit.bus_types
        # Ibranch = avg_res.Ibranch
        # loading = avg_res.loading
        # lhs_results.losses = avg_res.losses
        # flow_direction = avg_res.flow_direction
        # Sbus = avg_res.Sbus

        self.results = lhs_results

        # send the finnish signal
        self.progress_signal.emit(0.0)
        self.progress_text.emit('Done!')
        self.done_signal.emit()

        return lhs_results
Exemple #6
0
    def run(self):
        """
        Run the monte carlo simulation
        @return:
        """

        self.__cancel__ = False

        # compile
        # print('Compiling...', end='')
        numerical_circuit = self.grid.compile()
        calculation_inputs = numerical_circuit.compute(
            branch_tolerance_mode=self.options.branch_impedance_tolerance_mode)

        self.results = CascadingResults(self.cascade_type)

        # initialize the simulator
        if self.cascade_type is CascadeType.PowerFlow:
            model_simulator = PowerFlowMP(self.grid, self.options)

        elif self.cascade_type is CascadeType.LatinHypercube:
            model_simulator = LatinHypercubeSampling(
                self.grid, self.options, sampling_points=self.n_lhs_samples)

        else:
            model_simulator = PowerFlowMP(self.grid, self.options)

        self.progress_signal.emit(0.0)
        self.progress_text.emit('Running cascading failure...')

        n_grids = len(calculation_inputs) + self.max_additional_islands
        if n_grids > len(self.grid.buses):  # safety check
            n_grids = len(self.grid.buses) - 1

        # print('n grids: ', n_grids)

        it = 0
        while len(calculation_inputs) <= n_grids and it <= n_grids:

            # For every circuit, run a power flow
            # for c in self.grid.circuits:
            model_simulator.run()
            # print(model_simulator.results.get_convergence_report())

            # remove grid elements (branches)
            idx, criteria = self.remove_probability_based(
                numerical_circuit,
                model_simulator.results,
                max_val=1.0,
                min_prob=0.1)

            # store the removed indices and the results
            entry = CascadingReportElement(idx, model_simulator.results,
                                           criteria)
            self.results.events.append(entry)

            # recompile grid
            calculation_inputs = numerical_circuit.compute()

            it += 1

            prog = max(
                len(calculation_inputs) / (n_grids + 1), it / (n_grids + 1))
            self.progress_signal.emit(prog * 100.0)

            if self.__cancel__:
                break

        print('Grid split into ', len(calculation_inputs), ' islands after',
              it, ' steps')

        # send the finnish signal
        self.progress_signal.emit(0.0)
        self.progress_text.emit('Done!')
        self.done_signal.emit()
Exemple #7
0
    def run_single_thread(self):
        """
        Run the monte carlo simulation
        @return:
        """

        self.__cancel__ = False

        # initialize the power flow
        power_flow = PowerFlowMP(self.circuit, self.options)

        # initialize the grid time series results
        # we will append the island results with another function
        self.circuit.time_series_results = TimeSeriesResults(0, 0, 0, 0, 0)
        Sbase = self.circuit.Sbase

        it = 0
        variance_sum = 0.0
        std_dev_progress = 0
        v_variance = 0

        n = len(self.circuit.buses)
        m = len(self.circuit.branches)

        # compile circuits
        numerical_circuit = self.circuit.compile()
        numerical_input_islands = numerical_circuit.compute(branch_tolerance_mode=self.options.branch_impedance_tolerance_mode)



        mc_results = MonteCarloResults(n, m)
        avg_res = PowerFlowResults()
        avg_res.initialize(n, m)

        v_sum = zeros(n, dtype=complex)

        self.progress_signal.emit(0.0)

        while (std_dev_progress < 100.0) and (it < self.max_mc_iter) and not self.__cancel__:

            self.progress_text.emit('Running Monte Carlo: Variance: ' + str(v_variance))

            mc_results = MonteCarloResults(n, m, self.batch_size)

            # For every circuit, run the time series
            for numerical_island in numerical_input_islands:

                # set the time series as sampled
                monte_carlo_input = make_monte_carlo_input(numerical_island)
                mc_time_series = monte_carlo_input(self.batch_size, use_latin_hypercube=False)
                Vbus = numerical_island.Vbus

                # run the time series
                for t in range(self.batch_size):
                    # set the power values
                    Y, I, S = mc_time_series.get_at(t)

                    # res = powerflow.run_at(t, mc=True)
                    res = power_flow.run_pf(circuit=numerical_island, Vbus=Vbus, Sbus=S / Sbase, Ibus=I / Sbase)

                    mc_results.S_points[t, numerical_island.original_bus_idx] = res.Sbus
                    mc_results.V_points[t, numerical_island.original_bus_idx] = res.voltage
                    mc_results.I_points[t, numerical_island.original_branch_idx] = res.Ibranch
                    mc_results.loading_points[t, numerical_island.original_branch_idx] = res.loading
                    mc_results.losses_points[t, numerical_island.original_branch_idx] = res.losses

                # short cut the indices
                b_idx = numerical_island.original_bus_idx
                br_idx = numerical_island.original_branch_idx

                self.progress_text.emit('Compiling results...')
                mc_results.compile()

                # compute the island branch results
                island_avg_res = numerical_island.compute_branch_results(mc_results.voltage[b_idx])

                # apply the island averaged results
                avg_res.apply_from_island(island_avg_res, b_idx=b_idx, br_idx=br_idx)

            # Compute the Monte Carlo values
            it += self.batch_size
            mc_results.append_batch(mc_results)
            v_sum += mc_results.get_voltage_sum()
            v_avg = v_sum / it
            v_variance = abs((power(mc_results.V_points - v_avg, 2.0) / (it - 1)).min())

            # progress
            variance_sum += v_variance
            err = variance_sum / it
            if err == 0:
                err = 1e-200  # to avoid division by zeros
            mc_results.error_series.append(err)

            # emmit the progress signal
            std_dev_progress = 100 * self.mc_tol / err
            if std_dev_progress > 100:
                std_dev_progress = 100
            self.progress_signal.emit(max((std_dev_progress, it / self.max_mc_iter * 100)))

            # print(iter, '/', max_mc_iter)
            # print('Vmc:', Vavg)
            # print('Vstd:', Vvariance, ' -> ', std_dev_progress, ' %')

        # compile results
        mc_results.sbranch = avg_res.Sbranch
        # mc_results.losses = avg_res.losses
        mc_results.bus_types = numerical_circuit.bus_types

        # send the finnish signal
        self.progress_signal.emit(0.0)
        self.progress_text.emit('Done!')
        self.done_signal.emit()

        return mc_results
Exemple #8
0
    def __init__(self,
                 multi_circuit: MultiCircuit,
                 options: PowerFlowOptions,
                 verbose=False,
                 break_at_value=True,
                 good_enough_value=0):

        self.break_at_value = break_at_value
        self.good_enough_value = good_enough_value

        self.multi_circuit = multi_circuit

        self.numerical_circuit = self.multi_circuit.compile()

        self.calculation_inputs = self.numerical_circuit.compute(
            add_storage=False, add_generation=True)

        self.power_flow = PowerFlowMP(self.multi_circuit, options)

        # indices of generators that contribute to the static power vector 'S'
        self.gen_s_idx = np.where(
            (np.logical_not(self.numerical_circuit.generator_dispatchable) *
             self.numerical_circuit.generator_active) == True)[0]

        self.bat_s_idx = np.where(
            (np.logical_not(self.numerical_circuit.battery_dispatchable) *
             self.numerical_circuit.battery_active) == True)[0]

        # indices of generators that are to be optimized via the solution vector 'x'
        self.gen_x_idx = np.where(
            (self.numerical_circuit.generator_dispatchable *
             self.numerical_circuit.generator_active) == True)[0]

        self.bat_x_idx = np.where(
            (self.numerical_circuit.battery_dispatchable *
             self.numerical_circuit.battery_active) == True)[0]

        self.n_batteries = len(self.numerical_circuit.battery_power)
        self.n_controlled_gen = len(self.numerical_circuit.generator_power)

        # compute the problem dimension
        self.dim = len(self.gen_x_idx) + len(self.bat_x_idx)

        # get the limits of the devices to control
        gens = np.array(multi_circuit.get_generators())
        bats = np.array(multi_circuit.get_batteries())
        gen_x_up = np.array([elm.Pmax for elm in gens[self.gen_x_idx]])
        gen_x_low = np.array([elm.Pmin for elm in gens[self.gen_x_idx]])
        bat_x_up = np.array([elm.Pmax for elm in bats[self.bat_x_idx]])
        bat_x_low = np.array([elm.Pmin for elm in bats[self.bat_x_idx]])

        self.ngen = len(self.gen_x_idx)

        self.xlow = np.r_[gen_x_low, bat_x_low] / self.multi_circuit.Sbase
        self.xup = np.r_[gen_x_up, bat_x_up] / self.multi_circuit.Sbase
        self.range = self.xup - self.xlow

        # form S static ################################################################################################

        # all the loads apply
        self.Sfix = None
        self.set_default_state()  # build Sfix

        self.Vbus = np.ones(self.numerical_circuit.nbus, dtype=complex)
        self.Ibus = np.zeros(self.numerical_circuit.nbus, dtype=complex)

        # other vars needed ############################################################################################

        self.converged = False

        self.result = None

        self.force_batteries_to_charge = False

        self.x = np.zeros(self.dim)

        self.fx = 0

        self.t = 0

        self.Emin = None
        self.Emax = None
        self.E = None
        self.bat_idx = None
        self.battery_loading_pu = 0.01
        self.dt = 0
Exemple #9
0
class AcOpfNelderMead:
    def __init__(self,
                 multi_circuit: MultiCircuit,
                 options: PowerFlowOptions,
                 verbose=False,
                 break_at_value=True,
                 good_enough_value=0):

        self.break_at_value = break_at_value
        self.good_enough_value = good_enough_value

        self.multi_circuit = multi_circuit

        self.numerical_circuit = self.multi_circuit.compile()

        self.calculation_inputs = self.numerical_circuit.compute(
            add_storage=False, add_generation=True)

        self.power_flow = PowerFlowMP(self.multi_circuit, options)

        # indices of generators that contribute to the static power vector 'S'
        self.gen_s_idx = np.where(
            (np.logical_not(self.numerical_circuit.generator_dispatchable) *
             self.numerical_circuit.generator_active) == True)[0]

        self.bat_s_idx = np.where(
            (np.logical_not(self.numerical_circuit.battery_dispatchable) *
             self.numerical_circuit.battery_active) == True)[0]

        # indices of generators that are to be optimized via the solution vector 'x'
        self.gen_x_idx = np.where(
            (self.numerical_circuit.generator_dispatchable *
             self.numerical_circuit.generator_active) == True)[0]

        self.bat_x_idx = np.where(
            (self.numerical_circuit.battery_dispatchable *
             self.numerical_circuit.battery_active) == True)[0]

        self.n_batteries = len(self.numerical_circuit.battery_power)
        self.n_controlled_gen = len(self.numerical_circuit.generator_power)

        # compute the problem dimension
        self.dim = len(self.gen_x_idx) + len(self.bat_x_idx)

        # get the limits of the devices to control
        gens = np.array(multi_circuit.get_generators())
        bats = np.array(multi_circuit.get_batteries())
        gen_x_up = np.array([elm.Pmax for elm in gens[self.gen_x_idx]])
        gen_x_low = np.array([elm.Pmin for elm in gens[self.gen_x_idx]])
        bat_x_up = np.array([elm.Pmax for elm in bats[self.bat_x_idx]])
        bat_x_low = np.array([elm.Pmin for elm in bats[self.bat_x_idx]])

        self.ngen = len(self.gen_x_idx)

        self.xlow = np.r_[gen_x_low, bat_x_low] / self.multi_circuit.Sbase
        self.xup = np.r_[gen_x_up, bat_x_up] / self.multi_circuit.Sbase
        self.range = self.xup - self.xlow

        # form S static ################################################################################################

        # all the loads apply
        self.Sfix = None
        self.set_default_state()  # build Sfix

        self.Vbus = np.ones(self.numerical_circuit.nbus, dtype=complex)
        self.Ibus = np.zeros(self.numerical_circuit.nbus, dtype=complex)

        # other vars needed ############################################################################################

        self.converged = False

        self.result = None

        self.force_batteries_to_charge = False

        self.x = np.zeros(self.dim)

        self.fx = 0

        self.t = 0

        self.Emin = None
        self.Emax = None
        self.E = None
        self.bat_idx = None
        self.battery_loading_pu = 0.01
        self.dt = 0

    def set_state(self,
                  load_power,
                  static_gen_power,
                  controlled_gen_power,
                  storage_power,
                  Emin=None,
                  Emax=None,
                  E=None,
                  dt=0,
                  force_batteries_to_charge=False,
                  bat_idx=None,
                  battery_loading_pu=0.01):

        # all the loads apply
        self.Sfix = self.numerical_circuit.C_load_bus.T * (
            -load_power / self.numerical_circuit.Sbase *
            self.numerical_circuit.load_active)

        # static generators (all apply)
        self.Sfix += self.numerical_circuit.C_sta_gen_bus.T * (
            static_gen_power / self.numerical_circuit.Sbase *
            self.numerical_circuit.static_gen_active)

        # controlled generators
        self.Sfix += (self.numerical_circuit.C_gen_bus[self.gen_s_idx, :]
                      ).T * (controlled_gen_power /
                             self.numerical_circuit.Sbase)

        # batteries
        self.Sfix += (self.numerical_circuit.C_batt_bus[self.bat_s_idx, :]
                      ).T * (storage_power / self.numerical_circuit.Sbase)

        # batteries variables to control the energy
        self.force_batteries_to_charge = force_batteries_to_charge
        self.Emin = Emin
        self.Emax = Emax
        self.E = E
        self.dt = dt
        self.bat_idx = bat_idx
        self.battery_loading_pu = battery_loading_pu

    def set_default_state(self):
        """
        Set the default loading state
        """
        self.set_state(
            load_power=self.numerical_circuit.load_power,
            static_gen_power=self.numerical_circuit.static_gen_power,
            controlled_gen_power=self.numerical_circuit.generator_power[
                self.gen_s_idx],
            storage_power=self.numerical_circuit.battery_power[self.bat_s_idx],
            Emin=self.numerical_circuit.battery_Enom *
            self.numerical_circuit.battery_min_soc,
            Emax=self.numerical_circuit.battery_Enom *
            self.numerical_circuit.battery_max_soc,
            E=self.numerical_circuit.battery_Enom *
            self.numerical_circuit.battery_soc_0,
            dt=1)

    def set_state_at(self,
                     t,
                     force_batteries_to_charge=False,
                     bat_idx=None,
                     battery_loading_pu=0.01,
                     Emin=None,
                     Emax=None,
                     E=None,
                     dt=0):
        """
        Set the problem state at at time index
        Args:
            t:
            force_batteries_to_charge:
            bat_idx:
            battery_loading_pu:
            Emin:
            Emax:
            E:
            dt:

        Returns:

        """
        self.set_state(
            load_power=self.numerical_circuit.load_power_profile[t, :],
            static_gen_power=self.numerical_circuit.static_gen_power_profile[
                t, :],
            controlled_gen_power=self.numerical_circuit.
            generator_power_profile[t, self.gen_s_idx],
            storage_power=self.numerical_circuit.battery_power_profile[
                t, self.bat_s_idx],
            Emin=Emin,
            Emax=Emax,
            E=E,
            dt=dt,
            force_batteries_to_charge=force_batteries_to_charge,
            bat_idx=bat_idx,
            battery_loading_pu=battery_loading_pu)

        self.t = t

    def build_solvers(self):
        pass

    def f_obj(self, x):

        # if self.force_batteries_to_charge:
        #     # assign a negative upper limit to force to charge
        #     x_up = self.xup
        #     x_up[self.ngen:] = self.xlow[self.ngen:] * self.battery_loading_pu
        # else:
        #     # use the normal limits
        #     x_up = self.xup

        # compute the penalty for x outside boundaries
        penalty_x = 0.0
        idx_up = np.where(x > self.xup)[0]
        penalty_x += (x[idx_up] - self.xup[idx_up]).sum()
        idx_low = np.where(x < self.xlow)[0]
        penalty_x += (self.xlow[idx_low] - x[idx_low]).sum()
        penalty_x *= 10  # add a lot of weight to the boundary penalty

        # modify the power injections (apply x)
        S = self.Sfix.copy()
        controlled_gen_power = x[0:self.ngen]
        storage_power = x[self.ngen:]
        S += (self.numerical_circuit.C_gen_bus[self.gen_x_idx, :]
              ).T * controlled_gen_power
        S += (self.numerical_circuit.C_batt_bus[self.bat_x_idx, :]
              ).T * storage_power

        # compute the penalty for trespassing the energy boundaries
        E = self.E - storage_power * self.dt
        idx = np.where(E > self.Emax)[0]
        penalty_x += (E[idx] - self.Emax[idx]).sum()
        idx = np.where(E < self.Emin)[0]
        penalty_x += (self.Emin[idx] - E[idx]).sum()

        # run a power flow
        results = self.power_flow.run_multi_island(self.calculation_inputs,
                                                   self.Vbus, S, self.Ibus)

        loading = np.abs(results.loading)

        # get the indices of the branches with overload
        idx = np.where(loading > 1)[0]

        # return the summation of the overload
        if len(idx) > 0:
            f = (loading[idx] - 1).sum()
            # print('objective', f, 'x', x)
            return f + penalty_x
        else:
            return penalty_x

    def solve(self, verbose=False):

        if verbose:
            callback = print
        else:
            callback = None

        x0 = np.zeros_like(self.x)

        # Run the optimization
        self.x, self.fx = nelder_mead(self.f_obj,
                                      x_start=x0,
                                      callback=callback,
                                      break_at_value=self.break_at_value,
                                      good_enough_value=self.good_enough_value,
                                      step=0.01)

        print('objective', self.fx, 'x', self.x)
        # modify the power injections
        S = self.Sfix.copy()
        controlled_gen_power = self.x[0:self.ngen]
        storage_power = self.x[self.ngen:]
        S += (self.numerical_circuit.C_gen_bus[self.gen_x_idx, :]
              ).T * controlled_gen_power
        S += (self.numerical_circuit.C_batt_bus[self.bat_x_idx, :]
              ).T * storage_power

        # run a power flow
        pf_res = self.power_flow.run_multi_island(self.calculation_inputs,
                                                  self.Vbus, S, self.Ibus)

        # declare the results
        self.result = OptimalPowerFlowResults(Sbus=pf_res.Sbus,
                                              voltage=pf_res.voltage,
                                              load_shedding=None,
                                              generation_shedding=None,
                                              battery_power=None,
                                              controlled_generation_power=None,
                                              Sbranch=pf_res.Sbranch,
                                              overloads=None,
                                              loading=pf_res.loading,
                                              converged=True)

        self.result.battery_power = np.zeros(self.n_batteries)
        self.result.battery_power[self.bat_x_idx] = self.x[self.ngen:]
        self.result.controlled_generation_power = np.zeros(
            self.n_controlled_gen)
        self.result.controlled_generation_power[
            self.gen_x_idx] = self.x[0:self.ngen]
        self.result.load_shedding = np.zeros_like(
            self.numerical_circuit.load_power)
        self.result.generation_shedding = np.zeros_like(
            self.numerical_circuit.generator_power)

        # overloads
        self.result.overloads = np.zeros_like(self.result.loading)
        loading = np.abs(self.result.loading)
        idx = np.where(loading > 1)[0]
        self.result.overloads[idx] = loading[idx] - 1

        self.converged = True

        return self.result

    def get_branch_flows(self):

        return self.result.Sbranch

    def get_load_shedding(self):

        return self.result.load_shedding

    def get_batteries_power(self):

        return self.result.battery_power

    def get_controlled_generation(self):

        return self.result.controlled_generation_power

    def get_generation_shedding(self):

        return self.result.generation_shedding

    def get_voltage(self):

        return self.result.voltage

    def get_overloads(self):

        return self.result.overloads

    def get_loading(self):

        return self.result.loading
Exemple #10
0
def test_gridcal_regulator():
    """
    GridCal test for the new implementation of transformer voltage regulators.
    """
    test_name = "test_gridcal_regulator"
    grid = MultiCircuit(name=test_name)
    grid.Sbase = Sbase
    grid.time_profile = None
    grid.logger = list()

    # Create buses
    POI = Bus(
        name="POI",
        vnom=100,  # kV
        is_slack=True)
    grid.add_bus(POI)

    B_C3 = Bus(name="B_C3", vnom=10)  # kV
    grid.add_bus(B_C3)

    B_MV_M32 = Bus(name="B_MV_M32", vnom=10)  # kV
    grid.add_bus(B_MV_M32)

    B_LV_M32 = Bus(name="B_LV_M32", vnom=0.6)  # kV
    grid.add_bus(B_LV_M32)

    # Create voltage controlled generators (or slack, a.k.a. swing)
    UT = Generator(name="Utility")
    UT.bus = POI
    grid.add_generator(POI, UT)

    # Create static generators (with fixed power factor)
    M32 = StaticGenerator(name="M32", P=4.2, Q=0.0)  # MVA (complex)
    M32.bus = B_LV_M32
    grid.add_static_generator(B_LV_M32, M32)

    # Create transformer types
    s = 100  # MVA
    z = 8  # %
    xr = 40
    SS = TransformerType(
        name="SS",
        hv_nominal_voltage=100,  # kV
        lv_nominal_voltage=10,  # kV
        nominal_power=s,  # MVA
        copper_losses=complex_impedance(z, xr).real * s * 1000 / Sbase,  # kW
        iron_losses=125,  # kW
        no_load_current=0.5,  # %
        short_circuit_voltage=z)  # %
    grid.add_transformer_type(SS)

    s = 5  # MVA
    z = 6  # %
    xr = 20
    PM = TransformerType(
        name="PM",
        hv_nominal_voltage=10,  # kV
        lv_nominal_voltage=0.6,  # kV
        nominal_power=s,  # MVA
        copper_losses=complex_impedance(z, xr).real * s * 1000 / Sbase,  # kW
        iron_losses=6.25,  # kW
        no_load_current=0.5,  # %
        short_circuit_voltage=z)  # %
    grid.add_transformer_type(PM)

    # Create branches
    X_C3 = Branch(bus_from=POI,
                  bus_to=B_C3,
                  name="X_C3",
                  branch_type=BranchType.Transformer,
                  template=SS,
                  bus_to_regulated=True,
                  vset=1.05)
    X_C3.tap_changer = TapChanger(taps_up=16,
                                  taps_down=16,
                                  max_reg=1.1,
                                  min_reg=0.9)
    X_C3.tap_changer.set_tap(X_C3.tap_module)
    grid.add_branch(X_C3)

    C_M32 = Branch(bus_from=B_C3,
                   bus_to=B_MV_M32,
                   name="C_M32",
                   r=7.84,
                   x=1.74)
    grid.add_branch(C_M32)

    X_M32 = Branch(bus_from=B_MV_M32,
                   bus_to=B_LV_M32,
                   name="X_M32",
                   branch_type=BranchType.Transformer,
                   template=PM)
    grid.add_branch(X_M32)

    # Apply templates (device types)
    grid.apply_all_branch_types()

    print("Buses:")
    for i, b in enumerate(grid.buses):
        print(f" - bus[{i}]: {b}")
    print()

    options = PowerFlowOptions(SolverType.LM,
                               verbose=True,
                               initialize_with_existing_solution=True,
                               multi_core=True,
                               control_q=ReactivePowerControlMode.Direct,
                               control_taps=TapsControlMode.Direct,
                               tolerance=1e-6,
                               max_iter=99)

    power_flow = PowerFlowMP(grid, options)
    power_flow.run()

    approx_volt = [round(100 * abs(v), 1) for v in power_flow.results.voltage]
    solution = [100.0, 105.2, 130.0, 130.1]  # Expected solution from GridCal

    print()
    print(f"Test: {test_name}")
    print(f"Results:  {approx_volt}")
    print(f"Solution: {solution}")
    print()

    print("Generators:")
    for g in grid.get_generators():
        print(f" - Generator {g}: q_min={g.Qmin}pu, q_max={g.Qmax}pu")
    print()

    print("Branches:")
    for b in grid.branches:
        print(
            f" - {b}: R={round(b.R, 4)}pu, X={round(b.X, 4)}pu, X/R={round(b.X/b.R, 1)}, vset={b.vset}"
        )
    print()

    print("Transformer types:")
    for t in grid.transformer_types:
        print(
            f" - {t}: Copper losses={int(t.Pcu)}kW, Iron losses={int(t.Pfe)}kW, SC voltage={t.Vsc}%"
        )
    print()

    print("Losses:")
    for i in range(len(grid.branches)):
        print(
            f" - {grid.branches[i]}: losses={round(power_flow.results.losses[i], 3)} MVA"
        )
    print()

    print(f"Voltage settings: {grid.numerical_circuit.vset}")

    equal = True
    for i in range(len(approx_volt)):
        if approx_volt[i] != solution[i]:
            equal = False

    assert equal