def _include_feasibility_slack(model, bus_attrs, gen_attrs, bus_p_loads, p_marginal_slack_penalty): import egret.model_library.decl as decl slack_init = {k: 0 for k in bus_attrs['names']} slack_bounds = { k: (0, sum(bus_p_loads.values())) for k in bus_attrs['names'] } decl.declare_var('p_over_generation', model=model, index_set=bus_attrs['names'], initialize=slack_init, bounds=slack_bounds) decl.declare_var('p_load_shed', model=model, index_set=bus_attrs['names'], initialize=slack_init, bounds=slack_bounds) p_rhs_kwargs = { 'include_feasibility_slack_pos': 'p_over_generation', 'include_feasibility_slack_neg': 'p_load_shed' } penalty_expr = sum( p_marginal_slack_penalty * (model.p_over_generation[bus_name] + model.p_load_shed[bus_name]) for bus_name in bus_attrs['names']) return p_rhs_kwargs, penalty_expr
def _include_feasibility_slack(model, bus_attrs, gen_attrs, bus_p_loads, penalty=1000): import egret.model_library.decl as decl slack_init = {k: 0 for k in bus_attrs['names']} slack_bounds = { k: (0, sum(bus_p_loads.values())) for k in bus_attrs['names'] } decl.declare_var('p_slack_pos', model=model, index_set=bus_attrs['names'], initialize=slack_init, bounds=slack_bounds) decl.declare_var('p_slack_neg', model=model, index_set=bus_attrs['names'], initialize=slack_init, bounds=slack_bounds) p_rhs_kwargs = { 'include_feasibility_slack_pos': 'p_slack_pos', 'include_feasibility_slack_neg': 'p_slack_neg' } p_penalty = penalty * ( max([gen_attrs['p_cost'][k]['values'][1] for k in gen_attrs['names']]) + 1) penalty_expr = sum( p_penalty * (model.p_slack_pos[bus_name] + model.p_slack_neg[bus_name]) for bus_name in bus_attrs['names']) return p_rhs_kwargs, penalty_expr
def _include_system_feasibility_slack(model, bus_p_loads, gen_attrs, p_marginal_slack_penalty): import egret.model_library.decl as decl load = sum(bus_p_loads.values()) over_gen_bounds = (0, tx_utils.over_gen_limit(load, gen_attrs['names'], gen_attrs['p_max'])) decl.declare_var('p_over_generation', model=model, index_set=None, initialize=0., bounds=over_gen_bounds) load_shed_bounds = (0, tx_utils.load_shed_limit(load, gen_attrs['names'], gen_attrs['p_min'])) decl.declare_var('p_load_shed', model=model, index_set=None, initialize=0., bounds=load_shed_bounds) p_rhs_kwargs = { 'include_feasibility_load_shed': 'p_load_shed', 'include_feasibility_over_generation': 'p_over_generation' } penalty_expr = p_marginal_slack_penalty * (model.p_load_shed + model.p_over_generation) return p_rhs_kwargs, penalty_expr
def _include_system_feasibility_slack(model, gen_attrs, bus_p_loads, penalty=1000): import egret.model_library.decl as decl slack_init = 0 slack_bounds = (0, sum(bus_p_loads.values())) decl.declare_var('p_slack_pos', model=model, index_set=None, initialize=slack_init, bounds=slack_bounds) decl.declare_var('p_slack_neg', model=model, index_set=None, initialize=slack_init, bounds=slack_bounds) p_rhs_kwargs = { 'include_feasibility_slack_pos': 'p_slack_pos', 'include_feasibility_slack_neg': 'p_slack_neg' } p_penalty = penalty * ( max([gen_attrs['p_cost'][k]['values'][1] for k in gen_attrs['names']]) + 1) penalty_expr = p_penalty * (model.p_slack_pos + model.p_slack_neg) return p_rhs_kwargs, penalty_expr
def declare_var_ij_aggregation_at_bus(model, index_set, **kwargs): """ Create a variable for the aggregated imaginary current at a bus """ decl.declare_var('ij_aggregation_at_bus', model=model, index_set=index_set, **kwargs)
def declare_var_pfi_slack_neg(model, index_set, **kwargs): """ Create the negative slack variable for the real part of power flow throught an interface """ decl.declare_var('pfi_slack_neg', model=model, index_set=index_set, **kwargs)
def declare_var_pf_slack_neg(model, index_set, **kwargs): """ Create the negative slack variable for the real part of power flow in the "from" end of the transmission line """ decl.declare_var('pf_slack_neg', model=model, index_set=index_set, **kwargs)
def _include_feasibility_slack(model, bus_names, bus_p_loads, bus_q_loads, gens_by_bus, gen_attrs, p_marginal_slack_penalty, q_marginal_slack_penalty): import egret.model_library.decl as decl p_over_gen_bounds = {k: (0, tx_utils.over_gen_limit(bus_p_loads[k], gens_by_bus[k], gen_attrs['p_max'])) for k in bus_names} decl.declare_var('p_over_generation', model=model, index_set=bus_names, initialize=0., bounds=p_over_gen_bounds ) p_load_shed_bounds = {k: (0, tx_utils.load_shed_limit(bus_p_loads[k], gens_by_bus[k], gen_attrs['p_min'])) for k in bus_names} decl.declare_var('p_load_shed', model=model, index_set=bus_names, initialize=0., bounds=p_load_shed_bounds ) q_over_gen_bounds = {k: (0, tx_utils.over_gen_limit(bus_q_loads[k], gens_by_bus[k], gen_attrs['q_max'])) for k in bus_names} decl.declare_var('q_over_generation', model=model, index_set=bus_names, initialize=0., bounds=q_over_gen_bounds ) q_load_shed_bounds = {k: (0, tx_utils.load_shed_limit(bus_q_loads[k], gens_by_bus[k], gen_attrs['q_min'])) for k in bus_names} decl.declare_var('q_load_shed', model=model, index_set=bus_names, initialize=0., bounds=q_load_shed_bounds ) p_rhs_kwargs = {'include_feasibility_load_shed':'p_load_shed', 'include_feasibility_over_generation':'p_over_generation'} q_rhs_kwargs = {'include_feasibility_load_shed':'q_load_shed', 'include_feasibility_over_generation':'q_over_generation'} penalty_expr = sum(p_marginal_slack_penalty * (model.p_over_generation[bus_name] + model.p_load_shed[bus_name]) + q_marginal_slack_penalty * (model.q_over_generation[bus_name] + model.q_load_shed[bus_name]) for bus_name in bus_names) return p_rhs_kwargs, q_rhs_kwargs, penalty_expr
def declare_ineq_soc(model, index_set, use_outer_approximation=False): """ create the constraint for the second order cone """ m = model if not use_outer_approximation: con_set = decl.declare_set("_con_ineq_soc", model, index_set) m.ineq_soc = pe.Constraint(con_set) for from_bus, to_bus in con_set: m.ineq_soc[(from_bus, to_bus)] = m.c[from_bus, to_bus]**2 + m.s[ from_bus, to_bus]**2 <= m.vmsq[from_bus] * m.vmsq[to_bus] else: if not coramin_available: raise ImportError( 'Cannot create SOC relaxation with outer approximation unless coramin is available.' ) """ in order to use outer approximation, we have to reformulate c**2 + s**2 <= vmsq[from_bus] * vmsq[to_bus] to (c**2 + s**2 + z1**2) ** 0.5 <= z2 z1 = 0.5 * (vmsq[from_bus] - vmsq[to_bus]) z2 = 0.5 * (vmsq[from_bus] + vmsq[to_bus]) """ con_set = decl.declare_set("_con_ineq_soc", model, index_set) decl.declare_var('_z1', model=model, index_set=con_set) decl.declare_var('_z2', model=model, index_set=con_set) m._eq_z1 = pe.Constraint(con_set) m._eq_z2 = pe.Constraint(con_set) m.ineq_soc_OA = coramin.relaxations.MultivariateRelaxation(con_set) for from_bus, to_bus in con_set: m._eq_z1[from_bus, to_bus] = m._z1[from_bus, to_bus] == 0.5 * (m.vmsq[from_bus] - m.vmsq[to_bus]) m._eq_z2[from_bus, to_bus] = m._z2[from_bus, to_bus] == 0.5 * (m.vmsq[from_bus] + m.vmsq[to_bus]) fbbt(m._eq_z1[from_bus, to_bus]) fbbt(m._eq_z2[from_bus, to_bus]) m.ineq_soc_OA[from_bus, to_bus].build( aux_var=m._z2[from_bus, to_bus], shape=coramin.utils.FunctionShape.CONVEX, f_x_expr=(m.c[from_bus, to_bus]**2 + m.s[from_bus, to_bus]**2 + m._z1[from_bus, to_bus]**2)**0.5)
def _include_system_feasibility_slack(model, gen_attrs, bus_p_loads, p_marginal_slack_penalty): import egret.model_library.decl as decl slack_init = 0 slack_bounds = (0, sum(bus_p_loads.values())) decl.declare_var('p_over_generation', model=model, index_set=None, initialize=slack_init, bounds=slack_bounds) decl.declare_var('p_load_shed', model=model, index_set=None, initialize=slack_init, bounds=slack_bounds) p_rhs_kwargs = { 'include_feasibility_slack_pos': 'p_over_generation', 'include_feasibility_slack_neg': 'p_load_shed' } penalty_expr = p_marginal_slack_penalty * (model.p_over_generation + model.p_load_shed) return p_rhs_kwargs, penalty_expr
def declare_var_pfl(model, index_set, **kwargs): """ Create variable for the real part of the power loss in the transmission line """ decl.declare_var('pfl', model=model, index_set=index_set, **kwargs)
def declare_var_s(model, index_set, **kwargs): """ Create an auxiliary variable for vf * vt * sin(theta_f - theta_t) """ decl.declare_var('s', model=model, index_set=index_set, **kwargs)
def declare_var_ifr(model, index_set, **kwargs): """ Create variable for the real part of the current flow in the "from" end of the transmission line """ decl.declare_var('ifr', model=model, index_set=index_set, **kwargs)
def declare_var_qg(model, index_set, **kwargs): """ Create a variable for the reactive component of the power at a generator """ decl.declare_var('qg', model=model, index_set=index_set, **kwargs)
def declare_var_va(model, index_set, **kwargs): """ Create variable for the phase angle of the voltage at a bus """ decl.declare_var('va', model=model, index_set=index_set, **kwargs)
def declare_var_vj(model, index_set, **kwargs): """ Create variable for the imaginary component of the voltage at a bus """ decl.declare_var('vj', model=model, index_set=index_set, **kwargs)
def declare_var_vm(model, index_set, **kwargs): """ Create variable for the voltage magnitude of the voltage at a bus """ decl.declare_var('vm', model=model, index_set=index_set, **kwargs)
def create_hot_start_lpac_model(model_data, voltages, lower_bound=-pi / 3, upper_bound=pi / 3, cosine_segment_count=20, include_feasibility_slack=False, mode="uniform"): """ The hot start LPAC model assumes that voltages are known, e.g. from an AC base point solution. """ ###Grid data md = model_data.clone_in_service() tx_utils.scale_ModelData_to_pu(md, inplace=True) gens = dict(md.elements(element_type='generator')) buses = dict(md.elements(element_type='bus')) branches = dict(md.elements(element_type='branch')) loads = dict(md.elements(element_type='load')) shunts = dict(md.elements(element_type='shunt')) gen_attrs = md.attributes(element_type='generator') bus_attrs = md.attributes(element_type='bus') branch_attrs = md.attributes(element_type='branch') inlet_branches_by_bus, outlet_branches_by_bus = \ tx_utils.inlet_outlet_branches_by_bus(branches, buses) gens_by_bus = tx_utils.gens_by_bus(buses, gens) model = pe.ConcreteModel() ###declare (and fix) the voltage magnitudes and squares of voltage magnitudes bus_voltage_magnitudes = voltages #Assumes voltages is given as a dictionary libbus.declare_var_vm(model, bus_attrs['names'], initialize=bus_voltage_magnitudes) model.vm.fix() libbus.declare_var_vmsq( model=model, index_set=bus_attrs['names'], initialize={k: v**2 for k, v in bus_attrs['vm'].items()}, bounds=zip_items({k: v**2 for k, v in bus_attrs['v_min'].items()}, {k: v**2 for k, v in bus_attrs['v_max'].items()})) ### declare the polar voltages libbus.declare_var_va(model, bus_attrs['names'], initialize=bus_attrs['va']) ### declare the cosine approximation variables cos_hat_bounds = {k: (0, 1) for k in branch_attrs['names']} decl.declare_var('cos_hat', model, branch_attrs['names'], bounds=cos_hat_bounds) ### fix the reference bus ref_bus = md.data['system']['reference_bus'] #ref_angle = md.data['system']['reference_bus_angle'] model.va[ref_bus].fix(radians(0.0)) ### declare the fixed shunts at the buses bus_bs_fixed_shunts, bus_gs_fixed_shunts = tx_utils.dict_of_bus_fixed_shunts( buses, shunts) ### declare (and fix) the loads at the buses bus_p_loads, bus_q_loads = tx_utils.dict_of_bus_loads(buses, loads) libbus.declare_var_pl(model, bus_attrs['names'], initialize=bus_p_loads) libbus.declare_var_ql(model, bus_attrs['names'], initialize=bus_q_loads) model.pl.fix() model.ql.fix() ### include the feasibility slack for the bus balances p_rhs_kwargs = {} q_rhs_kwargs = {} if include_feasibility_slack: p_rhs_kwargs, q_rhs_kwargs, penalty_expr = _include_feasibility_slack( model, bus_attrs, gen_attrs, bus_p_loads, bus_q_loads) ### declare the generator real and reactive power pg_init = { k: (gen_attrs['p_min'][k] + gen_attrs['p_max'][k]) / 2.0 for k in gen_attrs['pg'] } libgen.declare_var_pg(model, gen_attrs['names'], initialize=pg_init, bounds=zip_items(gen_attrs['p_min'], gen_attrs['p_max'])) qg_init = { k: (gen_attrs['q_min'][k] + gen_attrs['q_max'][k]) / 2.0 for k in gen_attrs['qg'] } libgen.declare_var_qg(model, gen_attrs['names'], initialize=qg_init, bounds=zip_items(gen_attrs['q_min'], gen_attrs['q_max'])) ### declare the current flows in the branches vr_init = { k: bus_attrs['vm'][k] * pe.cos(bus_attrs['va'][k]) for k in bus_attrs['vm'] } vj_init = { k: bus_attrs['vm'][k] * pe.sin(bus_attrs['va'][k]) for k in bus_attrs['vm'] } s_max = {k: branches[k]['rating_long_term'] for k in branches.keys()} s_lbub = dict() for k in branches.keys(): if s_max[k] is None: s_lbub[k] = (None, None) else: s_lbub[k] = (-s_max[k], s_max[k]) pf_bounds = s_lbub pt_bounds = s_lbub qf_bounds = s_lbub qt_bounds = s_lbub pf_init = dict() pt_init = dict() qf_init = dict() qt_init = dict() for branch_name, branch in branches.items(): from_bus = branch['from_bus'] to_bus = branch['to_bus'] y_matrix = tx_calc.calculate_y_matrix_from_branch(branch) ifr_init = tx_calc.calculate_ifr(vr_init[from_bus], vj_init[from_bus], vr_init[to_bus], vj_init[to_bus], y_matrix) ifj_init = tx_calc.calculate_ifj(vr_init[from_bus], vj_init[from_bus], vr_init[to_bus], vj_init[to_bus], y_matrix) itr_init = tx_calc.calculate_itr(vr_init[from_bus], vj_init[from_bus], vr_init[to_bus], vj_init[to_bus], y_matrix) itj_init = tx_calc.calculate_itj(vr_init[from_bus], vj_init[from_bus], vr_init[to_bus], vj_init[to_bus], y_matrix) pf_init[branch_name] = tx_calc.calculate_p(ifr_init, ifj_init, vr_init[from_bus], vj_init[from_bus]) pt_init[branch_name] = tx_calc.calculate_p(itr_init, itj_init, vr_init[to_bus], vj_init[to_bus]) qf_init[branch_name] = tx_calc.calculate_q(ifr_init, ifj_init, vr_init[from_bus], vj_init[from_bus]) qt_init[branch_name] = tx_calc.calculate_q(itr_init, itj_init, vr_init[to_bus], vj_init[to_bus]) libbranch.declare_var_pf(model=model, index_set=branch_attrs['names'], initialize=pf_init, bounds=pf_bounds) libbranch.declare_var_pt(model=model, index_set=branch_attrs['names'], initialize=pt_init, bounds=pt_bounds) libbranch.declare_var_qf(model=model, index_set=branch_attrs['names'], initialize=qf_init, bounds=qf_bounds) libbranch.declare_var_qt(model=model, index_set=branch_attrs['names'], initialize=qt_init, bounds=qt_bounds) #################### #Constraints #################### ###Balance equations in a bus #p balance libbus.declare_eq_p_balance(model=model, index_set=bus_attrs['names'], bus_p_loads=bus_p_loads, gens_by_bus=gens_by_bus, bus_gs_fixed_shunts=bus_gs_fixed_shunts, inlet_branches_by_bus=inlet_branches_by_bus, outlet_branches_by_bus=outlet_branches_by_bus, **p_rhs_kwargs) #q balance libbus.declare_eq_q_balance(model=model, index_set=bus_attrs['names'], bus_q_loads=bus_q_loads, gens_by_bus=gens_by_bus, bus_bs_fixed_shunts=bus_bs_fixed_shunts, inlet_branches_by_bus=inlet_branches_by_bus, outlet_branches_by_bus=outlet_branches_by_bus, **q_rhs_kwargs) ### Power in a branch branch_con_set = decl.declare_set('_con_eq_p_q_lpac_branch_power', model, branch_attrs['names']) model.eq_pf_branch_t = pe.Constraint(branch_con_set) model.eq_pt_branch_t = pe.Constraint(branch_con_set) model.eq_qf_branch_t = pe.Constraint(branch_con_set) model.eq_qt_branch_t = pe.Constraint(branch_con_set) for branch_name in branch_con_set: branch = branches[branch_name] from_bus = branch['from_bus'] to_bus = branch['to_bus'] g = tx_calc.calculate_conductance(branch) b = tx_calc.calculate_susceptance(branch) model.eq_pf_branch_t[branch_name] = \ model.pf[branch_name] == \ g*model.vmsq[from_bus] - model.vm[from_bus]*model.vm[to_bus]*(g * model.cos_hat[branch_name] + b * (model.va[from_bus] - model.va[to_bus])) model.eq_pt_branch_t[branch_name] = \ model.pt[branch_name] == \ g*model.vmsq[to_bus] - model.vm[from_bus]*model.vm[to_bus]*(g * model.cos_hat[branch_name] + b * (model.va[to_bus] - model.va[from_bus])) model.eq_qf_branch_t[branch_name] = \ model.qf[branch_name] == \ -b*model.vmsq[from_bus] - model.vm[from_bus]*model.vm[to_bus]*(g*(model.va[from_bus] - model.va[to_bus]) - b*model.cos_hat[branch_name]) model.eq_qt_branch_t[branch_name] = \ model.qt[branch_name] == \ -b*model.vmsq[to_bus] - model.vm[from_bus]*model.vm[to_bus]*(g*(model.va[to_bus] - model.va[from_bus]) - b*model.cos_hat[branch_name]) ### Piecewise linear cosine constraints model.N = pe.Set(initialize=list(range(cosine_segment_count + 1))) declare_pwl_cosine_bounds(model=model, index_set=branch_attrs['names'], branches=branches, lower_bound=lower_bound, upper_bound=upper_bound, cosine_segment_count=cosine_segment_count, mode=mode) ### Objective is to maximize cosine hat variables obj_expr = sum(model.cos_hat[branch_name] for branch_name in branch_attrs['names']) if include_feasibility_slack: obj_expr += penalty_expr model.obj = pe.Objective(expr=obj_expr) return model, md
def declare_var_vmsq(model, index_set, **kwargs): """ Create auxiliary variable for the voltage magnitude squared at a bus """ decl.declare_var('vmsq', model=model, index_set=index_set, **kwargs)
def create_master(model_data, omega, k=1): """ Create the upper-level (master) of the stochastic bilevel problem Arguments: model_data: An Egret dict of dict that stores data for the power system omega: A dict of scenario name <key> and probability per scenario <value> where the probabilities add to 1 k: A positive integer indicating the number of relays that can be attacked Returns: Tuple with the following values: model: A Pyomo model representing the algebraic form of the bilevel problem md: The model_data object associated to the model """ ### power system data md = model_data ### create dictionaries of object sets relays = dict(md.elements(element_type='relay')) gens = dict(md.elements(element_type='generator')) buses = dict(md.elements(element_type='bus')) loads = dict(md.elements(element_type='load')) branches = dict(md.elements(element_type='branch')) ### create dictionaries across object attributes for an object of the same set type relay_attrs = md.attributes(element_type='relay') gen_attrs = md.attributes(element_type='generator') branch_attrs = md.attributes(element_type='branch') ### declare new Pyomo model model = pe.ConcreteModel() ### scenarios scenarios = omega.keys() ### declare (and fix) the loads at the buses bus_p_loads, _ = tx_utils.dict_of_bus_loads(buses, loads) buses_with_loads = list(k for k in bus_p_loads.keys() if bus_p_loads[k] != 0.) ### relay-to-power-device mappings relay_branches = utils.dict_of_relay_branches(relays, branches) branch_relays = utils.dict_of_branch_relays(relays, branches) relay_branch_tuple = utils.relay_branch_tuple(relay_branches) relay_gens = utils.dict_of_relay_gens(relays, gens) gen_relays = utils.dict_of_gen_relays(relays, gens) relay_gen_tuple = utils.relay_branch_tuple(relay_gens) relay_loads = utils.dict_of_relay_loads(relays, loads, buses_with_loads) load_relays = utils.dict_of_load_relays(relays, buses_with_loads) relay_load_tuple = utils.relay_branch_tuple(relay_loads) ### upper-level (attacker) variables scenarios_loads = pe.Set(initialize=scenarios) * pe.Set( initialize=buses_with_loads) decl.declare_var('load_shed', model, scenarios_loads, initialize=0.0, domain=pe.NonNegativeReals) decl.declare_var('delta', model, relay_attrs['names'], domain=pe.Binary, bounds=(0, 1)) # relays compromised decl.declare_var('u', model, buses_with_loads, domain=pe.Binary, bounds=(0, 1)) # load available decl.declare_var('v', model, gen_attrs['names'], domain=pe.Binary, bounds=(0, 1)) # generator available decl.declare_var('w', model, branch_attrs['names'], domain=pe.Binary, bounds=(0, 1)) # line available ### upper-level constraints cons.declare_budget( model, k, relays) # note that all k are costed equally in current implementation cons.declare_load_compromised(model, relay_load_tuple) cons.declare_load_uncompromised(model, buses_with_loads, load_relays) cons.declare_branch_compromised(model, relay_branch_tuple) cons.declare_branch_uncompromised(model, branch_attrs['names'], branch_relays) cons.declare_gen_compromised(model, relay_gen_tuple) cons.declare_gen_uncompromised(model, gen_attrs['names'], gen_relays) ### upper-level objective for stochastic interdiction problem (opposite to lower-level objective) model.obj = pe.Objective(expr=sum(omega[p]['probability'] * model.load_shed[p, l] for (p, l) in scenarios_loads), sense=pe.maximize) return model, md
def declare_var_pt(model, index_set, **kwargs): """ Create variable for the real part of the power flow in the "to" end of the transmission line """ decl.declare_var('pt', model=model, index_set=index_set, **kwargs)
def create_explicit_subproblem(model, subproblem, model_data, include_angle_diff_limits=False, include_bigm=False): ### power system data md = model_data ### create dictionaries of object sets gens = dict(md.elements(element_type='generator')) buses = dict(md.elements(element_type='bus')) branches = dict(md.elements(element_type='branch')) loads = dict(md.elements(element_type='load')) shunts = dict(md.elements(element_type='shunt')) ### create dictionaries across object attributes for an object of the same set type gen_attrs = md.attributes(element_type='generator') bus_attrs = md.attributes(element_type='bus') branch_attrs = md.attributes(element_type='branch') inlet_branches_by_bus, outlet_branches_by_bus = \ tx_utils.inlet_outlet_branches_by_bus(branches, buses) gens_by_bus = tx_utils.gens_by_bus(buses, gens) ### declare (and fix) the loads at the buses bus_p_loads, _ = tx_utils.dict_of_bus_loads(buses, loads) buses_with_loads = list(k for k in bus_p_loads.keys() if bus_p_loads[k] != 0.) ### declare load shed variables decl.declare_var('load_shed', subproblem, buses_with_loads, initialize=0.0, domain=pe.NonNegativeReals) #libbus.declare_var_pl(model.subproblem, bus_attrs['names'], initialize=bus_p_loads) #model.subproblem.pl.fix() subproblem.pl = bus_p_loads ### declare the fixed shunts at the buses _, bus_gs_fixed_shunts = tx_utils.dict_of_bus_fixed_shunts(buses, shunts) ### declare the polar voltages va_bounds = {k: (-pi, pi) for k in bus_attrs['va']} va_init = {k: bus_attrs['va'][k] * (pi / 180) for k in bus_attrs['va']} libbus.declare_var_va(subproblem, bus_attrs['names'], initialize=bus_attrs['va'], bounds=va_bounds) ### fix the reference bus ref_bus = md.data['system']['reference_bus'] ref_angle = md.data['system']['reference_bus_angle'] subproblem.va[ref_bus].fix(radians(ref_angle)) ### declare the generator real power pg_init = { k: (gen_attrs['p_min'][k] + gen_attrs['p_max'][k]) / 2.0 for k in gen_attrs['pg'] } libgen.declare_var_pg(subproblem, gen_attrs['names'], initialize=pg_init) ### declare the current flows in the branches vr_init = { k: bus_attrs['vm'][k] * pe.cos(bus_attrs['va'][k]) for k in bus_attrs['vm'] } vj_init = { k: bus_attrs['vm'][k] * pe.sin(bus_attrs['va'][k]) for k in bus_attrs['vm'] } pf_init = dict() for branch_name, branch in branches.items(): from_bus = branch['from_bus'] to_bus = branch['to_bus'] y_matrix = tx_calc.calculate_y_matrix_from_branch(branch) ifr_init = tx_calc.calculate_ifr(vr_init[from_bus], vj_init[from_bus], vr_init[to_bus], vj_init[to_bus], y_matrix) ifj_init = tx_calc.calculate_ifj(vr_init[from_bus], vj_init[from_bus], vr_init[to_bus], vj_init[to_bus], y_matrix) pf_init[branch_name] = tx_calc.calculate_p(ifr_init, ifj_init, vr_init[from_bus], vj_init[from_bus]) libbranch.declare_var_pf(model=subproblem, index_set=branch_attrs['names'], initialize=pf_init) # need to include variable references on subproblem to variables, which exist on the master block #bi.components.varref(subproblem, origin = model) subproblem.add_component("u", Reference(model.u)) subproblem.add_component("v", Reference(model.v)) subproblem.add_component("w", Reference(model.w)) if include_bigm: # create big-M _create_bigm(subproblem, md) ### declare the branch power flow disjuncts subcons.declare_eq_branch_power_btheta_approx_bigM( model=subproblem, index_set=branch_attrs['names'], branches=branches) ### declare the real power flow limits p_max = {k: branches[k]['rating_long_term'] for k in branches.keys()} subcons.declare_ineq_p_branch_thermal_lbub_switch( model=subproblem, index_set=branch_attrs['names'], p_thermal_limits=p_max) else: ### declare the branch power flow with indicator variable in the bilinear term subcons.declare_eq_branch_power_btheta_approx_nonlin( model=subproblem, index_set=branch_attrs['names'], branches=branches) ### declare the real power flow limits p_max = {k: branches[k]['rating_long_term'] for k in branches.keys()} libbranch.declare_ineq_p_branch_thermal_lbub( model=subproblem, index_set=branch_attrs['names'], branches=branches, p_thermal_limits=p_max, approximation_type=ApproximationType.BTHETA) ### declare the load shed subcons.declare_ineq_load_shed(model=subproblem, index_set=buses_with_loads) ### declare the generator compromised subcons.declare_ineq_gen(model=subproblem, index_set=gen_attrs['names'], gens=gens) ### declare the p balance rhs_kwargs = {'include_feasibility_slack_neg': 'load_shed'} libbus.declare_eq_p_balance_dc_approx( model=subproblem, index_set=bus_attrs['names'], bus_p_loads=bus_p_loads, gens_by_bus=gens_by_bus, bus_gs_fixed_shunts=bus_gs_fixed_shunts, inlet_branches_by_bus=inlet_branches_by_bus, outlet_branches_by_bus=outlet_branches_by_bus, approximation_type=ApproximationType.BTHETA, **rhs_kwargs) ### declare angle difference limits on interconnected buses if include_angle_diff_limits: libbranch.declare_ineq_angle_diff_branch_lbub( model=subproblem, index_set=branch_attrs['names'], branches=branches, coordinate_type=CoordinateType.POLAR) ### lower-level objective for interdiction problem (opposite to upper-level objective) subproblem.obj = pe.Objective(expr=sum(subproblem.load_shed[l] for l in buses_with_loads), sense=pe.minimize) return model, md
def declare_var_itj(model, index_set, **kwargs): """ Create variable for the imaginary part of the current flow in the "to" end of the transmission line """ decl.declare_var('itj', model=model, index_set=index_set, **kwargs)
def create_master(model_data, k=1): """ Create the upper-level (master) of the bilevel problem Arguments: model_data: An Egret dict of dict that stores data for the power system k: A positive integer indicating the number of relays that can be attacked Returns: Tuple with the following values: model: A Pyomo model representing the algebraic form of the bilevel problem md: The model_data object associated to the model """ ### power system data md = model_data ### create dictionaries of object sets gens = dict(md.elements(element_type='generator')) buses = dict(md.elements(element_type='bus')) loads = dict(md.elements(element_type='load')) branches = dict(md.elements(element_type='branch')) ### create dictionaries across object attributes for an object of the same set type bus_attrs = md.attributes(element_type='bus') gen_attrs = md.attributes(element_type='generator') branch_attrs = md.attributes(element_type='branch') ### declare new Pyomo model model = pe.ConcreteModel() ### declare (and fix) the loads at the buses bus_p_loads, _ = tx_utils.dict_of_bus_loads(buses, loads) buses_with_loads = list(k for k in bus_p_loads.keys() if bus_p_loads[k] != 0.) ### upper-level (attacker) variables decl.declare_var('load_shed', model, buses_with_loads, initialize=0.0, domain=pe.NonNegativeReals) decl.declare_var('delta_k', model, branch_attrs['names'], domain=pe.Binary) # line compromised decl.declare_var('delta_g', model, gen_attrs['names'], domain=pe.Binary) # gen compromised decl.declare_var('delta_b', model, bus_attrs['names'], domain=pe.Binary) # bus compromised decl.declare_var('u', model, buses_with_loads, domain=pe.Binary) # load available decl.declare_var('v', model, gen_attrs['names'], domain=pe.Binary) # generator available decl.declare_var('w', model, branch_attrs['names'], domain=pe.Binary) # line available ### upper-level constraints cons.declare_component_budget(model, k, branch_attrs['name'],\ gen_attrs['name'], bus_attrs['names']) # note that all k are costed equally in current implementation cons.declare_load_compromised(model, relay_load_tuple) cons.declare_load_uncompromised(model, buses_with_loads, load_relays) cons.declare_branch_compromised(model, relay_branch_tuple) cons.declare_branch_uncompromised(model, branch_attrs['names'], branch_relays) cons.declare_gen_compromised(model, relay_gen_tuple) cons.declare_gen_uncompromised(model, gen_attrs['names'], gen_relays) ### upper-level objective for interdiction problem (opposite to lower-level objective) model.obj = pe.Objective(expr=sum(model.load_shed[l] for l in buses_with_loads), sense=pe.maximize) return model, md
def declare_var_dva(model, index_set, **kwargs): """ Create variable or the angle difference between interconnected bus pairs """ decl.declare_var('dva', model=model, index_set=index_set, **kwargs)
def declare_var_pl(model, index_set, **kwargs): """ Create variable for the real power load at a bus """ decl.declare_var('pl', model=model, index_set=index_set, **kwargs)
def declare_var_pfi(model, index_set, **kwargs): """ Create variable for the real part of the power flow through an interface """ decl.declare_var('pfi', model=model, index_set=index_set, **kwargs)
def declare_var_p_nw(model, index_set, **kwargs): """ Create variable for the reactive power load at a bus """ decl.declare_var('p_nw', model=model, index_set=index_set, **kwargs)
def declare_var_qf(model, index_set, **kwargs): """ Create variable for the imaginary part of the power flow in the "from" end of the transmission line """ decl.declare_var('qf', model=model, index_set=index_set, **kwargs)
def declare_var_vr(model, index_set, **kwargs): """ Create variable for the real component of the voltage at a bus """ decl.declare_var('vr', model=model, index_set=index_set, **kwargs)