def _ptdf_dcopf_network_model(block, tm): m, gens_by_bus, bus_p_loads, bus_gs_fixed_shunts = \ _setup_egret_network_model(block, tm) buses, branches, \ branches_in_service, branches_out_service, \ interfaces, contingencies = _setup_egret_network_topology(m, tm) ptdf_options = m._ptdf_options libbus.declare_var_p_nw(block, m.Buses) ### declare net withdraw expression for use in PTDF power flows libbus.declare_eq_p_net_withdraw_at_bus( model=block, index_set=m.Buses, bus_p_loads=bus_p_loads, gens_by_bus=gens_by_bus, bus_gs_fixed_shunts=bus_gs_fixed_shunts, ) ### declare the p balance libbus.declare_eq_p_balance_ed( model=block, index_set=m.Buses, bus_p_loads=bus_p_loads, gens_by_bus=gens_by_bus, bus_gs_fixed_shunts=bus_gs_fixed_shunts, ) ### add "blank" power flow expressions libbranch.declare_expr_pf( model=block, index_set=branches_in_service, ) _setup_branch_slacks(m, block, tm) ### interface setup libbranch.declare_expr_pfi(model=block, index_set=interfaces.keys()) _setup_interface_slacks(m, block, tm) ### contingency setup ### NOTE: important that this not be dense, we'll add elements ### as we find violations block._contingency_set = Set(within=m.Contingencies * m.TransmissionLines) block.pfc = Expression(block._contingency_set) _setup_contingency_slacks(m, block, tm) ### Get the PTDF matrix from cache, from file, or create a new one ### m._PTDFs set in uc_model_generator if branches_out_service not in m._PTDFs: buses_idx = tuple(buses.keys()) reference_bus = value(m.ReferenceBus) ## NOTE: For now, just use a flat-start for unit commitment PTDF = ptdf_utils.VirtualPTDFMatrix(branches, buses, reference_bus, BasePointType.FLATSTART, ptdf_options, contingencies=contingencies, branches_keys=branches_in_service, buses_keys=buses_idx, interfaces=interfaces) m._PTDFs[branches_out_service] = PTDF else: PTDF = m._PTDFs[branches_out_service] ### attach the current PTDF object to this block block._PTDF = PTDF rel_ptdf_tol = m._ptdf_options['rel_ptdf_tol'] abs_ptdf_tol = m._ptdf_options['abs_ptdf_tol'] if ptdf_options['lazy']: ### add "blank" real power flow limits libbranch.declare_ineq_p_branch_thermal_bounds( model=block, index_set=branches_in_service, branches=branches, p_thermal_limits=None, approximation_type=None, slacks=True, slack_cost_expr=m.BranchViolationCost[tm]) ### declare the "blank" interface flow limits libbranch.declare_ineq_p_interface_bounds( model=block, index_set=interfaces.keys(), interfaces=interfaces, approximation_type=None, slacks=True, slack_cost_expr=m.InterfaceViolationCost[tm]) ### declare the "blank" interface flow limits libbranch.declare_ineq_p_contingency_branch_thermal_bounds( model=block, index_set=block._contingency_set, pc_thermal_limits=None, approximation_type=None, slacks=True, slack_cost_expr=m.ContingencyViolationCost[tm]) ### add helpers for tracking monitored branches lpu.add_monitored_flow_tracker(block) ### add initial branches to monitored set lpu.add_initial_monitored_branches(block, branches, branches_in_service, ptdf_options, PTDF) ### add initial interfaces to monitored set lpu.add_initial_monitored_interfaces(block, interfaces, ptdf_options, PTDF) else: ### add all the dense constraints if contingencies: raise RuntimeError( "Contingency constraints only supported in lazy mode") p_max = { k: branches[k]['rating_long_term'] for k in branches_in_service } ### declare the branch power flow approximation constraints libbranch.declare_eq_branch_power_ptdf_approx( model=block, index_set=branches_in_service, PTDF=PTDF, abs_ptdf_tol=abs_ptdf_tol, rel_ptdf_tol=rel_ptdf_tol) ### declare the real power flow limits libbranch.declare_ineq_p_branch_thermal_bounds( model=block, index_set=branches_in_service, branches=branches, p_thermal_limits=p_max, approximation_type=ApproximationType.PTDF, slacks=True, slack_cost_expr=m.BranchViolationCost[tm]) ### declare the branch power flow approximation constraints libbranch.declare_eq_interface_power_ptdf_approx( model=block, index_set=interfaces.keys(), PTDF=PTDF, abs_ptdf_tol=abs_ptdf_tol, rel_ptdf_tol=rel_ptdf_tol) ### declare the interface flow limits libbranch.declare_ineq_p_interface_bounds( model=block, index_set=interfaces.keys(), interfaces=interfaces, approximation_type=ApproximationType.PTDF, slacks=True, slack_cost_expr=m.InterfaceViolationCost[tm])
def create_scopf_model(model_data, include_feasibility_slack=False, base_point=BasePointType.FLATSTART, ptdf_options=None): ptdf_options = lpu.populate_default_ptdf_options(ptdf_options) baseMVA = model_data.data['system']['baseMVA'] lpu.check_and_scale_ptdf_options(ptdf_options, baseMVA) 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')) dc_branches = dict(md.elements(element_type='dc_branch')) contingencies = dict(md.elements(element_type='contingency')) gen_attrs = md.attributes(element_type='generator') ## to keep things in order buses_idx = tuple(buses.keys()) branches_idx = tuple(branches.keys()) 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 = pyo.ConcreteModel() ### declare (and fix) the loads at the buses bus_p_loads, _ = tx_utils.dict_of_bus_loads(buses, loads) libbus.declare_var_pl(model, buses_idx, initialize=bus_p_loads) model.pl.fix() ### declare the fixed shunts at the buses _, bus_gs_fixed_shunts = tx_utils.dict_of_bus_fixed_shunts(buses, shunts) ### 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(model, gen_attrs['names'], initialize=pg_init, bounds=zip_items(gen_attrs['p_min'], gen_attrs['p_max'])) ### include the feasibility slack for the system balance p_rhs_kwargs = {} if include_feasibility_slack: p_marginal_slack_penalty = _validate_and_extract_slack_penalty(md) p_rhs_kwargs, penalty_expr = _include_system_feasibility_slack( model, bus_p_loads, gen_attrs, p_marginal_slack_penalty) if dc_branches: dcpf_bounds = dict() for k, k_dict in dc_branches.items(): kp_max = k_dict['rating_long_term'] if kp_max is None: dcpf_bounds[k] = (None, None) else: dcpf_bounds[k] = (-kp_max, kp_max) libbranch.declare_var_dcpf( model=model, index_set=dc_branches.keys(), initialize=0., bounds=dcpf_bounds, ) dc_inlet_branches_by_bus, dc_outlet_branches_by_bus = \ tx_utils.inlet_outlet_branches_by_bus(dc_branches, buses) else: dc_inlet_branches_by_bus = None dc_outlet_branches_by_bus = None ### declare the p balance libbus.declare_eq_p_balance_ed(model=model, index_set=buses_idx, bus_p_loads=bus_p_loads, gens_by_bus=gens_by_bus, bus_gs_fixed_shunts=bus_gs_fixed_shunts, **p_rhs_kwargs) ### declare net withdraw expression for use in PTDF power flows libbus.declare_expr_p_net_withdraw_at_bus( model=model, index_set=buses_idx, bus_p_loads=bus_p_loads, gens_by_bus=gens_by_bus, bus_gs_fixed_shunts=bus_gs_fixed_shunts, dc_inlet_branches_by_bus=dc_inlet_branches_by_bus, dc_outlet_branches_by_bus=dc_outlet_branches_by_bus, ) ### add "blank" power flow expressions libbranch.declare_expr_pf( model=model, index_set=branches_idx, ) ### add "blank" power flow expressions model._contingencies = pyo.Set(initialize=contingencies.keys()) model._branches = pyo.Set(initialize=branches_idx) ### NOTE: important that this not be dense, we'll add elements ### as we find violations model._contingency_set = pyo.Set(within=model._contingencies * model._branches) model.pfc = pyo.Expression(model._contingency_set) ## Do and store PTDF calculation reference_bus = md.data['system']['reference_bus'] PTDF = ptdf_utils.VirtualPTDFMatrix(branches, buses, reference_bus, base_point, ptdf_options,\ contingencies=contingencies, branches_keys=branches_idx, buses_keys=buses_idx) model._PTDF = PTDF model._ptdf_options = ptdf_options if not ptdf_options['lazy']: raise RuntimeError("scopf only supports lazy constraint generation") ### add "blank" real power flow limits libbranch.declare_ineq_p_branch_thermal_bounds( model=model, index_set=branches_idx, branches=branches, p_thermal_limits=None, approximation_type=None, ) ### add "blank" real power flow limits libbranch.declare_ineq_p_contingency_branch_thermal_bounds( model=model, index_set=model._contingency_set, pc_thermal_limits=None, approximation_type=None, ) ### add helpers for tracking monitored branches lpu.add_monitored_flow_tracker(model) ### add initial branches to monitored set lpu.add_initial_monitored_branches(model, branches, branches_idx, ptdf_options, PTDF) ### declare the generator cost objective libgen.declare_expression_pgqg_operating_cost(model=model, index_set=gen_attrs['names'], p_costs=gen_attrs['p_cost']) obj_expr = sum(model.pg_operating_cost[gen_name] for gen_name in model.pg_operating_cost) if include_feasibility_slack: obj_expr += penalty_expr model.obj = pyo.Objective(expr=obj_expr) return model, md