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)
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()
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
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
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
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
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
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