Doubles the load parameters :param t: :param gain: device parameter :return: Dictionary with load parameters """ # Defines a load step after 0.2 s return 1 * gain if t < .2 else 2 * gain if __name__ == '__main__': ctrl = [] # Empty dict which shall include all controllers ##################################### # Define the voltage forming inverter as master # Voltage control PI gain parameters for the voltage sourcing inverter voltage_dqp_iparams = PI_params(kP=0.025, kI=60, limits=(-i_lim, i_lim)) # Current control PI gain parameters for the voltage sourcing inverter current_dqp_iparams = PI_params(kP=0.012, kI=90, limits=(-1, 1)) # Droop characteristic for the active power Watt/Hz, delta_t droop_param = DroopParams(DroopGain, 0.005, freq_nom) # Droop characteristic for the reactive power VAR/Volt Var.s/Volt qdroop_param = DroopParams(QDroopGain, 0.002, v_nom) # Add to dict ctrl.append( MultiPhaseDQ0PIPIController(voltage_dqp_iparams, current_dqp_iparams, droop_param, qdroop_param, ts_sim=delta_t, name='master'))
explore_threshold = 0 # Factor to multiply with the initial reward to give back an abort_reward-times higher negative reward in case of # limit exceeded abort_reward = -10 * j_min # Definition of the kernel kernel = GPy.kern.Matern32(input_dim=len(bounds), variance=prior_var, lengthscale=lengthscale, ARD=True) ##################################### # Definition of the controllers # Choose Kp and Ki for the current and voltage controller as mutable parameters mutable_params = dict(currentP=MutableFloat(10e-3), currentI=MutableFloat(10), voltageP=MutableFloat(25e-3), voltageI=MutableFloat(60)) voltage_dqp_iparams = PI_params(kP=mutable_params['voltageP'], kI=mutable_params['voltageI'], limits=(-i_lim, i_lim)) current_dqp_iparams = PI_params(kP=mutable_params['currentP'], kI=mutable_params['currentI'], limits=(-1, 1)) # Define the droop parameters for the inverter of the active power Watt/Hz (DroopGain), delta_t (0.005) used for the # filter and the nominal frequency # Droop controller used to calculate the virtual frequency drop due to load changes droop_param = DroopParams(DroopGain, 0.005, net.freq_nom) # Define the Q-droop parameters for the inverter of the reactive power VAR/Volt, delta_t (0.002) used for the # filter and the nominal voltage qdroop_param = DroopParams(QDroopGain, 0.002, net.v_nom) # Define a voltage forming inverter using the PIPI and droop parameters from above ctrl = MultiPhaseDQ0PIPIController(voltage_dqp_iparams, current_dqp_iparams, droop_param, qdroop_param, ts_sim=net.ts, ts_ctrl=2 * net.ts, name='master')
##################################### # Definition of the controllers # Choose Kp and Ki for the current and voltage controller as mutable parameters # mutable_params = dict(currentP=MutableFloat(10e-3), currentI=MutableFloat(10), voltageP=MutableFloat(0.05), # voltageI=MutableFloat(75)) # Vdc = 600 V mutable_params = dict(currentP=MutableFloat(0.04), currentI=MutableFloat(11.8), voltageP=MutableFloat(0.0175), voltageI=MutableFloat(12)) voltage_dqp_iparams = PI_params(kP=mutable_params['voltageP'], kI=mutable_params['voltageI'], limits=(-iLimit, iLimit)) current_dqp_iparams = PI_params(kP=mutable_params['currentP'], kI=mutable_params['currentI'], limits=(-1, 1)) # Best set from paper III-D # Define the droop parameters for the inverter of the active power Watt/Hz (DroopGain), delta_t (0.005) used for the # filter and the nominal frequency # Droop controller used to calculate the virtual frequency drop due to load changes droop_param = DroopParams(DroopGain, 0.005, nomFreq) # Define the Q-droop parameters for the inverter of the reactive power VAR/Volt, delta_t (0.002) used for the # filter and the nominal voltage qdroop_param = DroopParams(QDroopGain, 0.002, nomVoltPeak)
variance=prior_var, lengthscale=lengthscale, ARD=True) ##################################### # Definition of the controllers mutable_params = None current_dqp_iparams = None if adjust == 'Kp': # mutable_params = parameter (Kp gain of the current controller of the inverter) to be optimized using # the SafeOpt algorithm mutable_params = dict(currentP=MutableFloat(5e-3)) # Define the PI parameters for the current controller of the inverter current_dqp_iparams = PI_params(kP=mutable_params['currentP'], kI=115, limits=(-1, 1)) # For 1D example, if Ki should be adjusted elif adjust == 'Ki': mutable_params = dict(currentI=MutableFloat(10)) current_dqp_iparams = PI_params(kP=10e-3, kI=mutable_params['currentI'], limits=(-1, 1)) # For 2D example, choose Kp and Ki as mutable parameters elif adjust == 'Kpi': mutable_params = dict(currentP=MutableFloat(40e-3), currentI=MutableFloat(10)) current_dqp_iparams = PI_params(kP=mutable_params['currentP'], kI=mutable_params['currentI'],
abort_reward = 100 * j_min # Definition of the kernel kernel = GPy.kern.Matern32(input_dim=len(bounds), variance=prior_var, lengthscale=lengthscale, ARD=True) ##################################### # Definition of the controllers mutable_params = None current_dqp_iparams = None if adjust == 'Kp': # mutable_params = parameter (Kp gain of the current controller of the inverter) to be optimized using # the SafeOpt algorithm mutable_params = dict(currentP=MutableFloat(0.04)) # Define the PI parameters for the current controller of the inverter current_dqp_iparams = PI_params(kP=mutable_params['currentP'], kI=12, limits=(-1, 1)) # For 1D example, if Ki should be adjusted elif adjust == 'Ki': mutable_params = dict(currentI=MutableFloat(5)) current_dqp_iparams = PI_params(kP=0.005, kI=mutable_params['currentI'], limits=(-1, 1)) # For 2D example, choose Kp and Ki as mutable parameters elif adjust == 'Kpi': mutable_params = dict(currentP=MutableFloat(0.04), currentI=MutableFloat(11.8)) current_dqp_iparams = PI_params(kP=mutable_params['currentP'], kI=mutable_params['currentI'], limits=(-1, 1)) # Define a current sourcing inverter as master inverter using the pi and droop parameters from above ctrl = MultiPhaseDQCurrentSourcingController(current_dqp_iparams, ts_sim=delta_t, ts_ctrl=undersample * delta_t, name='master', f_nom=net.freq_nom)
# Definition of the kernel kernel = GPy.kern.Matern32(input_dim=len(bounds), variance=prior_var, lengthscale=lengthscale, ARD=True) ##################################### # Definition of the controllers # Choose Kp and Ki for the current and voltage controller as mutable parameters mutable_params = dict(voltageP=MutableFloat(0.0175), voltageI=MutableFloat(12)) # 300Hz mutable_params = dict(voltageP=MutableFloat(0.2), voltageI=MutableFloat(550)) # 300Hz voltage_dqp_iparams = PI_params(kP=mutable_params['voltageP'], kI=mutable_params['voltageI'], limits=(-iLimit, iLimit)) kp_c = 0.04 ki_c = 11.8 current_dqp_iparams = PI_params(kP=kp_c, kI=ki_c, limits=(-1, 1)) # Current controller values # Define the droop parameters for the inverter of the active power Watt/Hz (DroopGain), delta_t (0.005) used for the # filter and the nominal frequency # Droop controller used to calculate the virtual frequency drop due to load changes droop_param = DroopParams(DroopGain, 0.005, net.freq_nom) # Define the Q-droop parameters for the inverter of the reactive power VAR/Volt, delta_t (0.002) used for the # filter and the nominal voltage
# Define the droop parameters for the inverter of the active power Watt/Hz (DroopGain), delta_t (0.005) used for # the filter and the nominal frequency # Droop controller used to calculate the virtual frequency drop due to load changes droop_param_master = DroopParams(mutable_params['pDroop_master'], 0.005, nomFreq) # droop parameter used to react on frequency drop droop_param_slave = InverseDroopParams(mutable_params['pDroop_slave'], ts, nomFreq, tau_filt=0.04) # Droop characteristic for the reactive power VAR/Volt Var.s/Volt # qDroop parameter used for virtual voltage drop qdroop_param_master = DroopParams(mutable_params['qDroop_master'], 0.002, nomVoltPeak) # Droop characteristic for the reactive power VAR/Volt Var.s/Volt qdroop_param_slave = InverseDroopParams(mutable_params['qDroop_slave'], ts, nomVoltPeak, tau_filt=0.01) ############### # define Master voltage_dqp_iparams = PI_params(kP=0.025, kI=60, limits=(-iLimit, iLimit)) # kp=0.025 # Current control PI gain parameters for the voltage sourcing inverter current_dqp_iparams = PI_params(kP=0.012, kI=90, limits=(-1, 1)) # kp=0.012 # Define a current sourcing inverter as master inverter using the pi and droop parameters from above ctrl.append(MultiPhaseDQ0PIPIController(voltage_dqp_iparams, current_dqp_iparams, droop_param_master, qdroop_param_master, ts_sim=ts, ts_ctrl=2 * ts, name='master')) ############### # define slave current_dqp_iparams = PI_params(kP=0.005, kI=200, limits=(-1, 1)) # # PI gain parameters for the PLL in the current forming inverter pll_params = PLLParams(kP=10, kI=200, limits=(-10000, 10000), f_nom=nomFreq) # Droop characteristic for the active power Watts/Hz, W.s/Hz # Add to dict
def run_experiment(len_kp, len_ki): if isfile(f'{save_folder}/{len_kp:.4f},{len_ki:.4f}.txt'): with open(f'{save_folder}/{len_kp:.4f},{len_ki:.4f}.txt', 'r') as f: return strtobool(f.read().strip()) rew = Reward(i_limit=iLimit, i_nominal=iNominal, mu_c=mu, max_episode_steps=max_episode_steps, obs_dict=[[f'lc.inductor{k}.i' for k in '123'], 'master.phase', [f'master.SPI{k}' for k in 'dq0']]) ##################################### # Definitions for the GP prior_mean = 0 # 2 # mean factor of the GP prior mean which is multiplied with the first performance of the # initial set noise_var = 0.001 # 0.001 ** 2 # measurement noise sigma_omega prior_var = 2 # prior variance of the GP bounds = None lengthscale = None if adjust == 'Kp': bounds = [(0.0001, 0.1)] # bounds on the input variable Kp lengthscale = [ .025 ] # length scale for the parameter variation [Kp] for the GP # For 1D example, if Ki should be adjusted if adjust == 'Ki': bounds = [(0, 20)] # bounds on the input variable Ki lengthscale = [ 10 ] # length scale for the parameter variation [Ki] for the GP # For 2D example, choose Kp and Ki as mutable parameters (below) and define bounds and lengthscale for both of them if adjust == 'Kpi': bounds = [(0.001, 0.07), (2, 150)] lengthscale = [0.012, 30.] df_len = pd.DataFrame({ 'lengthscale': lengthscale, 'bounds': bounds, 'balanced_load': balanced_load, 'barrier_param_mu': mu }) # The performance should not drop below the safe threshold, which is defined by the factor safe_threshold times # the initial performance: safe_threshold = 0.8 means. Performance measurement for optimization are seen as # unsafe, if the new measured performance drops below 20 % of the initial performance of the initial safe (!) # parameter set safe_threshold = 0 j_min = cal_j_min(phase_shift, amp_dev) # Used for normalization # The algorithm will not try to expand any points that are below this threshold. This makes the algorithm stop # expanding points eventually. # The following variable is multiplied with the first performance of the initial set by the factor below: explore_threshold = 0 # Factor to multiply with the initial reward to give back an abort_reward-times higher negative reward in case of # limit exceeded # has to be negative due to normalized performance (regarding J_init = 1) abort_reward = 100 * j_min # Definition of the kernel kernel = GPy.kern.Matern32(input_dim=len(bounds), variance=prior_var, lengthscale=lengthscale, ARD=True) ##################################### # Definition of the controllers mutable_params = None current_dqp_iparams = None if adjust == 'Kp': # mutable_params = parameter (Kp gain of the current controller of the inverter) to be optimized using # the SafeOpt algorithm mutable_params = dict(currentP=MutableFloat(0.04)) # Define the PI parameters for the current controller of the inverter current_dqp_iparams = PI_params(kP=mutable_params['currentP'], kI=12, limits=(-1, 1)) # For 1D example, if Ki should be adjusted elif adjust == 'Ki': mutable_params = dict(currentI=MutableFloat(5)) current_dqp_iparams = PI_params(kP=0.005, kI=mutable_params['currentI'], limits=(-1, 1)) # For 2D example, choose Kp and Ki as mutable parameters elif adjust == 'Kpi': mutable_params = dict(currentP=MutableFloat(0.04), currentI=MutableFloat(11.8)) current_dqp_iparams = PI_params(kP=mutable_params['currentP'], kI=mutable_params['currentI'], limits=(-1, 1)) # Define a current sourcing inverter as master inverter using the pi and droop parameters from above ctrl = MultiPhaseDQCurrentSourcingController(current_dqp_iparams, delta_t, undersampling=undersample, name='master', f_nom=net.freq_nom) i_ref = MutableParams([MutableFloat(f) for f in i_ref1]) ##################################### # Definition of the optimization agent # The agent is using the SafeOpt algorithm by F. Berkenkamp (https://arxiv.org/abs/1509.01066) in this example # Arguments described above # History is used to store results agent = SafeOptAgent( mutable_params, abort_reward, j_min, kernel, dict(bounds=bounds, noise_var=noise_var, prior_mean=prior_mean, safe_threshold=safe_threshold, explore_threshold=explore_threshold), [ctrl], dict(master=[[f'lc.inductor{k}.i' for k in '123'], i_ref]), history=FullHistory(), ) ##################################### # Definition of the environment using a FMU created by OpenModelica # (https://www.openmodelica.org/) # Using an inverter supplying a load # - using the reward function described above as callable in the env # - viz_cols used to choose which measurement values should be displayed (here, only the 3 currents across the # inductors of the inverters are plotted. Labels and grid is adjusted using the PlotTmpl (For more information, # see UserGuide) # - inputs to the models are the connection points to the inverters (see user guide for more details) # - model outputs are the the 3 currents through the inductors and the 3 voltages across the capacitors if include_simulate: # Defining unbalanced loads sampling from Gaussian distribution with sdt = 0.2*mean # r_load = Load(R, 0.1 * R, balanced=balanced_load, tolerance=0.1) # l_load = Load(L, 0.1 * L, balanced=balanced_load, tolerance=0.1) # i_noise = Noise([0, 0, 0], [0.0023, 0.0015, 0.0018], 0.0005, 0.32) # if no noise should be included: r_load = Load(R, 0 * R, balanced=balanced_load) l_load = Load(L, 0 * L, balanced=balanced_load) def reset_loads(): r_load.reset() l_load.reset() plotter = PlotManager(agent, save_results=save_results, save_folder=save_folder, show_plots=show_plots) def ugly_foo(t): if t >= .05: i_ref[:] = i_ref2 else: i_ref[:] = i_ref1 return partial(l_load.give_value, n=2)(t) env = gym.make( 'openmodelica_microgrid_gym:ModelicaEnv_test-v1', # reward_fun=Reward().rew_fun, reward_fun=rew.rew_fun_c, # time_step=delta_t, viz_cols=[ PlotTmpl([[f'lc.inductor{i}.i' for i in '123'], [f'master.SPI{i}' for i in 'abc']], callback=plotter.xylables_i_abc, color=[['b', 'r', 'g'], ['b', 'r', 'g']], style=[[None], ['--']]), PlotTmpl([[f'master.m{i}' for i in 'abc']], callback=lambda fig: plotter.update_axes( fig, title='Simulation', ylabel='$m_{\mathrm{abc}}\,/\,\mathrm{}$')), PlotTmpl([[f'master.CVI{i}' for i in 'dq0'], [f'master.SPI{i}' for i in 'dq0']], callback=plotter.xylables_i_dq0, color=[['b', 'r', 'g'], ['b', 'r', 'g']], style=[[None], ['--']]) ], log_level=logging.INFO, viz_mode='episode', max_episode_steps=max_episode_steps, model_params={ 'lc.resistor1.R': partial(r_load.give_value, n=0), 'lc.resistor2.R': partial(r_load.give_value, n=1), 'lc.resistor3.R': partial(r_load.give_value, n=2), 'lc.inductor1.L': partial(l_load.give_value, n=0), 'lc.inductor2.L': partial(l_load.give_value, n=1), 'lc.inductor3.L': ugly_foo }, model_path='../../omg_grid/grid.paper.fmu', # model_path='../omg_grid/omg_grid.Grids.Paper_SC.fmu', net=net, history=FullHistory(), action_time_delay=1 * undersample) runner = MonteCarloRunner(agent, env) runner.run(num_episodes, n_mc=n_MC, visualise=True, prepare_mc_experiment=reset_loads) with open(f'{save_folder}/{len_kp:.4f},{len_ki:.4f}.txt', 'w') as f: print(f'{agent.unsafe}', file=f) return agent.unsafe