예제 #1
0
    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)

        for circuit in self.grid.circuits:

            # collect inputs
            se_input = self.collect_measurements(circuit=circuit)

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

            # Compute the branches power and the slack buses power
            Sbranch, Ibranch, loading, \
            losses, flow_direction, Sbus = PowerFlowMP.power_flow_post_process(calculation_inputs=circuit, 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,
                                              circuit.bus_original_idx,
                                              circuit.branch_original_idx)
예제 #2
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()

        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()
예제 #3
0
def test_gridcal_regulator():
    """
    GridCal test for the new implementation of transformer voltage regulators.
    """
    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 = ControlledGenerator(name="Utility")
    UT.bus = POI
    grid.add_controlled_generator(POI, UT)

    # Create static generators (with fixed power factor)
    M32 = StaticGenerator(name="M32", power=4.2 + 0.0j)  # 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=get_complex(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=get_complex(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()

    grid.compile()
    options = PowerFlowOptions(SolverType.LM,
                               verbose=True,
                               robust=True,
                               initialize_with_existing_solution=True,
                               multi_core=True,
                               control_q=True,
                               control_taps=True,
                               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("Controlled generators:")
    for g in grid.get_controlled_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.Copper_losses)}kW, Iron losses={int(t.Iron_losses)}kW, SC voltage={t.Short_circuit_voltage}%"
        )
    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}")

    #print("GridCal logger:")
    #for l in grid.logger:
    #print(f" - {l}")

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

    assert equal
예제 #4
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()

        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
        # 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
예제 #5
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()

        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

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

        return mc_results
예제 #6
0
    def run_single_thread(self):
        """
        Run single thread time series
        :return:
        """
        # 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

        print('Compiling...', end='')
        numerical_circuit = self.grid.compile(
            use_opf_vals=self.use_opf_vals,
            opf_time_series_results=self.opf_time_series_results)
        calculation_inputs = numerical_circuit.compute()

        # For every circuit, run the time series
        for nc, calculation_input in enumerate(calculation_inputs):

            # make a copy of the circuit to allow controls in place
            # circuit = circuit_orig.copy()

            # 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 batt in bus.batteries:
                        batt.reset()  # reset the calculation values
                        batteries.append(batt)
                        batteries_bus_idx.append(k)

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

            # find the original indices
            bus_original_idx = numerical_circuit.islands[nc]
            branch_original_idx = numerical_circuit.island_branches[nc]

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

                nt = calculation_input.ntime
                n = calculation_input.nbus
                m = calculation_input.nbr
                results = TimeSeriesResults(n, m, nt, self.start_, self.end_)
                Vlast = calculation_input.Vbus

                self.progress_signal.emit(0.0)

                t = self.start_
                dt = 1.0  # default value in case of single-valued profile

                # traverse the profiles time and simulate each time step
                while t < self.end_ and not self.__cancel__:
                    # set the power values
                    # if the storage dispatch option is active, the batteries power was not included
                    # it shall be included now, after processing
                    Y = calculation_input.Ysh_prof[:, t]
                    I = calculation_input.Ibus_prof[:, t]
                    S = calculation_input.Sbus_prof[:, t]

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

                        if t < self.end_ - 1:
                            # compute the time delta: the time values come in nanoseconds
                            dt = (calculation_input.time_array[t + 1] -
                                  calculation_input.time_array[t]
                                  ).value * 1e-9 / 3600

                        for k, batt in enumerate(batteries):

                            P = batt.get_processed_at(t,
                                                      dt=dt,
                                                      store_values=True)
                            bus_idx = batteries_bus_idx[k]
                            S[bus_idx] += (P / calculation_input.Sbase)
                        else:
                            pass

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

                    # Recycle voltage solution
                    Vlast = 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(nc) +
                                            ' at ' +
                                            str(self.grid.time_profile[t]))
                    t += 1

                # 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
예제 #7
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.pf = 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.controlled_gen_dispatchable)
                                   * self.numerical_circuit.controlled_gen_enabled) == True)[0]

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

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

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

        self.n_batteries = len(self.numerical_circuit.battery_power)
        self.n_controlled_gen = len(self.numerical_circuit.controlled_gen_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_controlled_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
예제 #8
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.pf = 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.controlled_gen_dispatchable)
                                   * self.numerical_circuit.controlled_gen_enabled) == True)[0]

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

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

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

        self.n_batteries = len(self.numerical_circuit.battery_power)
        self.n_controlled_gen = len(self.numerical_circuit.controlled_gen_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_controlled_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_enabled)

        # 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_enabled)

        # controlled generators
        self.Sfix += (self.numerical_circuit.C_ctrl_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.controlled_gen_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.controlled_gen_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_ctrl_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.pf.run_multi_island(self.numerical_circuit, 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_ctrl_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.pf.run_multi_island(self.numerical_circuit, 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.controlled_gen_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