def add_ccl_constraints(n): agg_p_nom_limits = n.config['existing_capacities'].get('agg_p_nom_limits') agg_p_nom_minmax = pd.read_csv(agg_p_nom_limits, index_col=list(range(2))) print(agg_p_nom_minmax) logger.info( "Adding per carrier generation capacity constraints for individual countries" ) print( 'adding per carrier generation capacity constraints for individual countries' ) gen_country = n.generators.bus.map(n.buses.country) # cc means country and carrier p_nom_per_cc = (pd.DataFrame({ 'p_nom': linexpr((1, get_var(n, 'Generator', 'p_nom'))), 'country': gen_country, 'carrier': n.generators.carrier }).dropna(subset=['p_nom']).groupby(['country', 'carrier']).p_nom.apply(join_exprs)) minimum = agg_p_nom_minmax['min'].dropna() if not minimum.empty: define_constraints(n, p_nom_per_cc[minimum.index], '>=', minimum, 'agg_p_nom', 'min') maximum = agg_p_nom_minmax['max'].dropna() if not maximum.empty: define_constraints(n, p_nom_per_cc[maximum.index], '<=', maximum, 'agg_p_nom', 'max')
def netzbooster_constraints(n, snapshots): # Define indices & parameters n.determine_network_topology() sub = n.sub_networks.at["0", "obj"] sub.calculate_PTDF() sub.calculate_BODF() snapshots = snapshots #n.snapshots buses = sub.buses_i() lines = sub.lines_i() outages = [l for l in lines if "outage" in l] ptdf = pd.DataFrame(sub.PTDF, index=lines, columns=buses) lodf = pd.DataFrame(sub.BODF, index=lines, columns=lines) logger.info("define variables") # ...... # something like # define_variables(n, lower_limit, upper_limit, component (e.g. "Bus"), attr (e.g. "p")) logger.info("define constraints") # in general you define a constraint as lhs = linexpr( (1, get_var(n, "Bus", "p_pos").loc[(buses, outages, snapshots)]), (-1, get_var(n, "Bus", "P_pos").loc[buses])) define_constraints(n, lhs, "<=", 0., "Bus", "UpLimitPos")
def mga_constraint(network, snapshots, options): scale = 1e-6 # This function creates the MGA constraint gen_capital_cost = linexpr((scale * network.generators.capital_cost, get_var(network, 'Generator', 'p_nom'))).sum() gen_marginal_cost = linexpr((scale * network.generators.marginal_cost, get_var(network, 'Generator', 'p'))).sum().sum() store_capital_cost = linexpr((scale * network.storage_units.capital_cost, get_var(network, 'StorageUnit', 'p_nom'))).sum() link_capital_cost = linexpr( (scale * network.links.capital_cost, get_var(network, 'Link', 'p_nom'))).sum() # total system cost cost_scaled = join_exprs( np.array([ gen_capital_cost, gen_marginal_cost, store_capital_cost, link_capital_cost ])) # MGA slack if options['mga_slack_type'] == 'percent': slack = network.old_objective * options[ 'mga_slack'] + network.old_objective elif options['mga_slack_type'] == 'fixed': slack = options['baseline_cost'] * options['mga_slack'] + options[ 'baseline_cost'] define_constraints(network, cost_scaled, '<=', slack * scale, 'GlobalConstraint', 'MGA_constraint')
def add_co2_sequestration_limit(n, sns): co2_stores = n.stores.loc[n.stores.carrier == 'co2 stored'].index if co2_stores.empty or ('Store', 'e') not in n.variables.index: return vars_final_co2_stored = get_var(n, 'Store', 'e').loc[sns[-1], co2_stores] lhs = linexpr((1, vars_final_co2_stored)).sum() limit = n.config["sector"].get("co2_sequestration_potential", 200) * 1e6 for o in opts: if not "seq" in o: continue limit = float(o[o.find("seq") + 3:]) break name = 'co2_sequestration_limit' sense = "<=" n.add("GlobalConstraint", name, sense=sense, constant=limit, type=np.nan, carrier_attribute=np.nan) define_constraints(n, lhs, sense, limit, 'GlobalConstraint', 'mu', axes=pd.Index([name]), spec=name)
def store_links_constraint(net: pypsa.Network, ctd_ratio: float): """ Constraint that links the charging and discharging ratings of store units. Parameters ---------- net: pypsa.Network A PyPSA Network instance with buses associated to regions and containing a functionality configuration dictionary ctd_ratio: float Pre-defined charge-to-discharge ratio for such units. """ links_p_nom = get_var(net, 'Link', 'p_nom') links_to_bus = links_p_nom[links_p_nom.index.str.contains('to AC')].index links_from_bus = links_p_nom[links_p_nom.index.str.contains('AC to')].index for pair in list(zip(links_to_bus, links_from_bus)): discharge_link = links_p_nom.loc[pair[0]] charge_link = links_p_nom.loc[pair[1]] lhs = linexpr((ctd_ratio, discharge_link), (-1., charge_link)) define_constraints(net, lhs, '==', 0., 'store_links_constraint')
def add_biofuel_constraint(n): options = snakemake.wildcards.sector_opts.split('-') print('options: ', options) liquid_biofuel_limit = 0 for o in options: if "B" in o: liquid_biofuel_limit = o[o.find("B") + 1:o.find("B") + 4] liquid_biofuel_limit = float(liquid_biofuel_limit.replace( "p", ".")) print('Liq biofuel minimum constraint: ', liquid_biofuel_limit, ' ', type(liquid_biofuel_limit)) biofuel_i = n.links.query('carrier == "biomass to liquid"').index biofuel_vars = get_var(n, "Link", "p").loc[:, biofuel_i] biofuel_vars_eta = n.links.query( 'carrier == "biomass to liquid"').efficiency napkership = n.loads.p_set.filter( regex='naphtha for industry|kerosene for aviation|shipping oil').sum( ) * len(n.snapshots) landtrans = n.loads_t.p_set.filter(regex='land transport oil$').sum().sum() total_oil_load = napkership + landtrans liqfuelloadlimit = liquid_biofuel_limit * total_oil_load lhs = linexpr((biofuel_vars_eta, biofuel_vars)).sum().sum() define_constraints(n, lhs, ">=", liqfuelloadlimit, 'Link', 'liquid_biofuel_min')
def extra_functionality(network,snapshots): link_p_nom = get_var(network, "Link", "p_nom") lhs = linexpr((1,link_p_nom["battery_power"]), (-network.links.loc["battery_discharge", "efficiency"], link_p_nom["battery_discharge"])) define_constraints(network, lhs, "=", 0, 'Link', 'charger_ratio')
def add_battery_constraints(n): nodes = n.buses.index[n.buses.carrier.isin(["battery", "home battery"])] link_p_nom = get_var(n, "Link", "p_nom") lhs = linexpr((1, link_p_nom[nodes + " charger"]), (-n.links.loc[nodes + " discharger", "efficiency"].values, link_p_nom[nodes + " discharger"].values)) define_constraints(n, lhs, "=", 0, 'Link', 'charger_ratio')
def add_battery_constraints(n): chargers = n.links.index[n.links.carrier.str.contains("battery charger") & n.links.p_nom_extendable] dischargers = chargers.str.replace("charger","discharger") link_p_nom = get_var(n, "Link", "p_nom") lhs = linexpr((1,link_p_nom[chargers]), (-n.links.loc[dischargers, "efficiency"].values, link_p_nom[dischargers].values)) define_constraints(n, lhs, "=", 0, 'Link', 'charger_ratio')
def add_planning_reserve_constraint(net: pypsa.Network, prm: float): """ Constraint that ensures a minimum dispatchable installed capacity. Parameters ---------- net: pypsa.Network A PyPSA Network instance with buses associated to regions prm: float Planning reserve margin. """ cc_ds = net.cc_ds dispatchable_technologies = ['ocgt', 'ccgt', 'ccgt_ccs', 'nuclear', 'sto'] res_technologies = [ 'wind_onshore', 'wind_offshore', 'pv_utility', 'pv_residential' ] for bus in net.loads.bus: lhs = '' legacy_at_bus = 0 gens = net.generators[(net.generators.bus == bus) & ( net.generators.type.isin(dispatchable_technologies))] for gen in gens.index: if gens.loc[gen].p_nom_extendable: lhs += linexpr((1., get_var(net, 'Generator', 'p_nom')[gen])) else: legacy_at_bus += gens.loc[gen].p_nom_min stos = net.storage_units[(net.storage_units.bus == bus) & ( net.storage_units.type.isin(dispatchable_technologies))] for sto in stos.index: if stos.loc[sto].p_nom_extendable: lhs += linexpr((1., get_var(net, 'StorageUnit', 'p_nom')[sto])) else: legacy_at_bus += stos.loc[sto].p_nom_min res_gens = net.generators[(net.generators.bus == bus) & ( net.generators.type.str.contains('|'.join(res_technologies)))] for gen in res_gens.index: lhs += linexpr((cc_ds.loc[' '.join(gen.split(' ')[1:])], get_var(net, 'Generator', 'p_nom')[gen])) # Get load for country load_idx = net.loads[net.loads.bus == bus].index load_peak = net.loads_t.p_set[load_idx].max() load_corrected_with_margin = load_peak * (1 + prm) rhs = load_corrected_with_margin.values[0] - legacy_at_bus define_constraints(net, lhs.sum(), '>=', rhs, 'planning_reserve_margin', bus)
def define_mga_constraint(n, sns, epsilon=None, with_fix=None): """Build constraint defining near-optimal feasible space Parameters ---------- n : pypsa.Network sns : Series|list-like snapshots epsilon : float, optional Allowed added cost compared to least-cost solution, by default None with_fix : bool, optional Calculation of allowed cost penalty should include cost of non-extendable components, by default None """ if epsilon is None: epsilon = float(snakemake.wildcards.epsilon) if with_fix is None: with_fix = snakemake.config.get("include_non_extendable", True) expr = [] # operation for c, attr in lookup.query("marginal_cost").index: cost = (get_as_dense(n, c, "marginal_cost", sns).loc[:, lambda ds: (ds != 0).all()].mul( n.snapshot_weightings[sns], axis=0)) if cost.empty: continue expr.append( linexpr((cost, get_var(n, c, attr).loc[sns, cost.columns])).stack()) # investment for c, attr in nominal_attrs.items(): cost = n.df(c)["capital_cost"][get_extendable_i(n, c)] if cost.empty: continue expr.append(linexpr((cost, get_var(n, c, attr)[cost.index]))) lhs = pd.concat(expr).sum() if with_fix: ext_const = objective_constant(n, ext=True, nonext=False) nonext_const = objective_constant(n, ext=False, nonext=True) rhs = (1 + epsilon) * (n.objective + ext_const + nonext_const) - nonext_const else: ext_const = objective_constant(n) rhs = (1 + epsilon) * (n.objective + ext_const) define_constraints(n, lhs, "<=", rhs, "GlobalConstraint", "mu_epsilon")
def add_chp_constraints(n): electric_bool = (n.links.index.str.contains("urban central") & n.links.index.str.contains("CHP") & n.links.index.str.contains("electric")) heat_bool = (n.links.index.str.contains("urban central") & n.links.index.str.contains("CHP") & n.links.index.str.contains("heat")) electric = n.links.index[electric_bool] heat = n.links.index[heat_bool] electric_ext = n.links.index[electric_bool & n.links.p_nom_extendable] heat_ext = n.links.index[heat_bool & n.links.p_nom_extendable] electric_fix = n.links.index[electric_bool & ~n.links.p_nom_extendable] heat_fix = n.links.index[heat_bool & ~n.links.p_nom_extendable] link_p = get_var(n, "Link", "p") if not electric_ext.empty: link_p_nom = get_var(n, "Link", "p_nom") #ratio of output heat to electricity set by p_nom_ratio lhs = linexpr((n.links.loc[electric_ext, "efficiency"] *n.links.loc[electric_ext, "p_nom_ratio"], link_p_nom[electric_ext]), (-n.links.loc[heat_ext, "efficiency"].values, link_p_nom[heat_ext].values)) define_constraints(n, lhs, "=", 0, 'chplink', 'fix_p_nom_ratio') #top_iso_fuel_line for extendable lhs = linexpr((1,link_p[heat_ext]), (1,link_p[electric_ext].values), (-1,link_p_nom[electric_ext].values)) define_constraints(n, lhs, "<=", 0, 'chplink', 'top_iso_fuel_line_ext') if not electric_fix.empty: #top_iso_fuel_line for fixed lhs = linexpr((1,link_p[heat_fix]), (1,link_p[electric_fix].values)) rhs = n.links.loc[electric_fix, "p_nom"].values define_constraints(n, lhs, "<=", rhs, 'chplink', 'top_iso_fuel_line_fix') if not electric.empty: #backpressure lhs = linexpr((n.links.loc[electric, "c_b"].values *n.links.loc[heat, "efficiency"], link_p[heat]), (-n.links.loc[electric, "efficiency"].values, link_p[electric].values)) define_constraints(n, lhs, "<=", 0, 'chplink', 'backpressure')
def add_co2_sequestration_limit(n, sns): co2_stores = n.stores.loc[n.stores.carrier=='co2 stored'].index if co2_stores.empty or ('Store', 'e') not in n.variables.index: return vars_final_co2_stored = get_var(n, 'Store', 'e').loc[sns[-1], co2_stores] lhs = linexpr((1, vars_final_co2_stored)).sum() rhs = n.config["sector"].get("co2_sequestration_potential", 200) * 1e6 name = 'co2_sequestration_limit' define_constraints(n, lhs, "<=", rhs, 'GlobalConstraint', 'mu', axes=pd.Index([name]), spec=name)
def dispatchable_capacity_lower_bound(net: pypsa.Network, thresholds: Dict): """ Constraint that ensures a minimum dispatchable installed capacity. Parameters ---------- net: pypsa.Network A PyPSA Network instance with buses associated to regions thresholds: Dict Dict containing scalar thresholds for disp_capacity/peak_load for each bus """ dispatchable_technologies = ['ocgt', 'ccgt', 'ccgt_ccs', 'nuclear', 'sto'] for bus in net.loads.bus: if bus in thresholds.keys(): lhs = '' legacy_at_bus = 0 gens = net.generators[(net.generators.bus == bus) & ( net.generators.type.isin(dispatchable_technologies))] for gen in gens.index: if gens.loc[gen].p_nom_extendable: lhs += linexpr((1., get_var(net, 'Generator', 'p_nom')[gen])) else: legacy_at_bus += gens.loc[gen].p_nom_min stos = net.storage_units[(net.storage_units.bus == bus) & ( net.storage_units.type.isin(dispatchable_technologies))] for sto in stos.index: if stos.loc[sto].p_nom_extendable: lhs += linexpr((1., get_var(net, 'StorageUnit', 'p_nom')[sto])) else: legacy_at_bus += stos.loc[sto].p_nom_min # Get load for country load_idx = net.loads[net.loads.bus == bus].index load_peak = net.loads_t.p_set[load_idx].max() load_peak_threshold = load_peak * thresholds[bus] rhs = max(0, load_peak_threshold.values[0] - legacy_at_bus) define_constraints(net, lhs.sum(), '>=', rhs, 'disp_capacity_lower_bound', bus)
def add_co2_budget_per_country(net: pypsa.Network, co2_reduction_share: Dict[str, float], co2_reduction_refyear: int): """ Add CO2 budget per country. Parameters ---------- net: pypsa.Network A PyPSA Network instance with buses associated to regions co2_reduction_share: float Percentage of reduction of emission. co2_reduction_refyear: int Reference year from which the reduction in emission is computed. """ for bus in net.loads.bus: bus_emission_reference = get_co2_emission_level_for_country( bus, co2_reduction_refyear) co2_budget = (1-co2_reduction_share[bus]) * bus_emission_reference \ * sum(net.snapshot_weightings['objective']) / 8760. # Drop rows (gens) without an associated carrier (i.e., technologies not emitting) gens = net.generators[(net.generators.carrier.astype(bool)) & (net.generators.bus == bus)] gens_p = get_var(net, 'Generator', 'p')[gens.index] coefficients = pd.DataFrame(index=gens_p.index, columns=gens_p.columns, dtype=float) for tech in gens.type.unique(): fuel, efficiency = get_tech_info(tech, ["fuel", "efficiency_ds"]) fuel_emissions_el = get_fuel_info(fuel, ['CO2']) fuel_emissions_thermal = fuel_emissions_el / efficiency gens_with_tech = gens[gens.index.str.contains(tech)] coefficients[ gens_with_tech.index] = fuel_emissions_thermal.values[0] lhs = linexpr((coefficients, gens_p)).sum().sum() define_constraints(net, lhs, '<=', co2_budget, 'generation_emissions_global', bus)
def add_mga_constraint(net: pypsa.Network, epsilon: float): # Add generation costs # Capital cost gen_p_nom = get_var(net, 'Generator', 'p_nom') gens = net.generators.loc[gen_p_nom.index] gen_capex_expr = linexpr((gens.capital_cost, gen_p_nom)).sum() gen_exist_cap_cost = (gens.p_nom * gens.capital_cost).sum() # Marginal cost gen_p = get_var(net, 'Generator', 'p') gens = net.generators.loc[gen_p.columns] gen_opex_expr = linexpr((net.snapshot_weightings['objective'].apply( lambda r: r * gens.marginal_cost), gen_p)).sum().sum() gen_cost_expr = gen_capex_expr + gen_opex_expr # Add storage cost # Capital cost su_p_nom = get_var(net, 'StorageUnit', 'p_nom') sus = net.storage_units.loc[su_p_nom.index] su_capex_expr = linexpr((sus.capital_cost, su_p_nom)).sum() su_exist_cap_cost = (sus.p_nom * sus.capital_cost).sum() # Marginal cost su_p_dispatch = get_var(net, 'StorageUnit', 'p_dispatch') sus = net.storage_units.loc[su_p_dispatch.columns] su_opex_expr = linexpr((net.snapshot_weightings['objective'].apply( lambda r: r * sus.marginal_cost), su_p_dispatch)).sum().sum() su_cost_expr = su_capex_expr + su_opex_expr # Add transmission cost # Capital cost link_p_nom = get_var(net, 'Link', 'p_nom') links = net.links.loc[link_p_nom.index] link_exist_cap_cost = (links.p_nom * links.capital_cost).sum() link_capex_expr = linexpr((links.capital_cost, link_p_nom)).sum() link_cost_expr = link_capex_expr obj_expr = gen_cost_expr + su_cost_expr + link_cost_expr exist_cap_cost = gen_exist_cap_cost + su_exist_cap_cost + link_exist_cap_cost obj = net.objective * (1 + epsilon) + exist_cap_cost define_constraints(net, obj_expr, '<=', obj, 'mga', 'obl')
def add_co2_budget_global(net: pypsa.Network, region: str, co2_reduction_share: float, co2_reduction_refyear: int): """ Add global CO2 budget. Parameters ---------- region: str Region over which the network is defined. net: pypsa.Network A PyPSA Network instance with buses associated to regions co2_reduction_share: float Percentage of reduction of emission. co2_reduction_refyear: int Reference year from which the reduction in emission is computed. """ co2_reference_kt = get_reference_emission_levels_for_region( region, co2_reduction_refyear) co2_budget = co2_reference_kt * (1 - co2_reduction_share) * sum( net.snapshot_weightings['objective']) / 8760. gens = net.generators[net.generators.carrier.astype(bool)] gens_p = get_var(net, 'Generator', 'p')[gens.index] coefficients = pd.DataFrame(index=gens_p.index, columns=gens_p.columns, dtype=float) for tech in gens.type.unique(): fuel, efficiency = get_tech_info(tech, ["fuel", "efficiency_ds"]) fuel_emissions_el = get_fuel_info(fuel, ['CO2']) fuel_emissions_thermal = fuel_emissions_el / efficiency gens_with_tech = gens[gens.index.str.contains(tech)] coefficients[gens_with_tech.index] = fuel_emissions_thermal.values[0] lhs = linexpr((coefficients, gens_p)).sum().sum() define_constraints(net, lhs, '<=', co2_budget, 'generation_emissions_global')
def add_pipe_retrofit_constraint(n): """Add constraint for retrofitting existing CH4 pipelines to H2 pipelines.""" gas_pipes_i = n.links[n.links.carrier == "gas pipeline"].index h2_retrofitted_i = n.links[n.links.carrier == 'H2 pipeline retrofitted'].index if h2_retrofitted_i.empty or gas_pipes_i.empty: return link_p_nom = get_var(n, "Link", "p_nom") pipe_capacity = n.links.loc[gas_pipes_i, 'p_nom'] CH4_per_H2 = 1 / n.config["sector"]["H2_retrofit_capacity_per_CH4"] fr = "H2 pipeline retrofitted" to = "gas pipeline" lhs = linexpr((CH4_per_H2, link_p_nom.loc[h2_retrofitted_i].rename( index=lambda x: x.replace(fr, to))), (1, link_p_nom.loc[gas_pipes_i])) define_constraints(n, lhs, "=", pipe_capacity, 'Link', 'pipe_retrofit')
def add_chp_constraints(n): electric = n.links.index[n.links.index.str.contains("urban central") & n.links.index.str.contains("CHP") & n.links.index.str.contains("electric")] heat = n.links.index[n.links.index.str.contains("urban central") & n.links.index.str.contains("CHP") & n.links.index.str.contains("heat")] if not electric.empty: link_p_nom = get_var(n, "Link", "p_nom") # ratio of output heat to electricity set by p_nom_ratio lhs = linexpr( (n.links.loc[electric, "efficiency"] * n.links.loc[electric, 'p_nom_ratio'], link_p_nom[electric]), (-n.links.loc[heat, "efficiency"].values, link_p_nom[heat].values)) define_constraints(n, lhs, "=", 0, 'chplink', 'fix_p_nom_ratio') link_p = get_var(n, "Link", "p") # backpressure lhs = linexpr((n.links.loc[electric, 'c_b'].values * n.links.loc[heat, "efficiency"], link_p[heat]), (-n.links.loc[electric, "efficiency"].values, link_p[electric].values)) define_constraints(n, lhs, "<=", 0, 'chplink', 'backpressure') # top_iso_fuel_line lhs = linexpr((1, link_p[heat]), (1, link_p[electric].values), (-1, link_p_nom[electric].values)) define_constraints(n, lhs, "<=", 0, 'chplink', 'top_iso_fuel_line')
def add_ccl_constraints_conventional(n): agg_p_nom_limits_conventional = n.config['existing_capacities'].get( 'agg_p_nom_limits_conventional') agg_p_nom_minmax_conventional = pd.read_csv(agg_p_nom_limits_conventional, index_col=list(range(2))) logger.info( "Adding per carrier conventional link capacity constraints for individual countries" ) print( 'adding per carrier conventional link capacity constraints for individual countries' ) link_country = n.links.bus1.map(n.buses.country) # cc means country and carrier p_nom_per_cc_link = (pd.DataFrame({ 'p_nom': linexpr((1, get_var(n, 'Link', 'p_nom'))), 'country': link_country, 'carrier': n.links.carrier }).dropna(subset=['p_nom']).groupby(['country', 'carrier']).p_nom.apply(join_exprs)) minimum_conventional = agg_p_nom_minmax_conventional['min'].dropna() if not minimum_conventional.empty: define_constraints(n, p_nom_per_cc_link[minimum_conventional.index], '>=', minimum_conventional, 'agg_p_nom', 'min') maximum_conventional = agg_p_nom_minmax_conventional['max'].dropna() if not maximum_conventional.empty: define_constraints(n, p_nom_per_cc_link[maximum_conventional.index], '<=', maximum_conventional, 'agg_p_nom', 'max')
def define_mga_constraint(n, sns): epsilon = float(snakemake.wildcards.epsilon) expr = [] # operation for c, attr in lookup.query("marginal_cost").index: cost = (get_as_dense(n, c, "marginal_cost", sns).loc[:, lambda ds: (ds != 0).all()].mul( n.snapshot_weightings[sns], axis=0)) if cost.empty: continue expr.append( linexpr((cost, get_var(n, c, attr).loc[sns, cost.columns])).stack()) # investment for c, attr in nominal_attrs.items(): cost = n.df(c)["capital_cost"][get_extendable_i(n, c)] if cost.empty: continue expr.append(linexpr((cost, get_var(n, c, attr)[cost.index]))) lhs = pd.concat(expr).sum() if snakemake.config["include_non_extendable"]: ext_const = objective_constant(n, ext=True, nonext=False) nonext_const = objective_constant(n, ext=False, nonext=True) rhs = (1 + epsilon) * (n.objective + ext_const + nonext_const) - nonext_const else: ext_const = objective_constant(n) rhs = (1 + epsilon) * (n.objective + ext_const) define_constraints(n, lhs, "<=", rhs, "GlobalConstraint", "mu_epsilon")
def netzbooster_constraints(n, snapshots): """ add to the LOPF the additional Netzbooster constraints """ # Define indices & parameters n.determine_network_topology() sub = n.sub_networks.at["0", "obj"] sub.calculate_PTDF() sub.calculate_BODF() snapshots = snapshots #n.snapshots buses = sub.buses_i() lines = sub.lines_i() outages = [l for l in lines if "outage" in l] ptdf = pd.DataFrame(sub.PTDF, index=lines, columns=buses) lodf = pd.DataFrame(sub.BODF, index=lines, columns=lines) logger.info("define variables") # i = buses # t = snapshots # k = outages # P_i + >= 0 define_variables(n, 0, inf, "Bus", "P_pos", axes=[buses]) # P_i - >= 0 define_variables(n, 0, inf, "Bus", "P_neg", axes=[buses]) # p_{i,t,k} + >= 0 define_variables( n, 0, inf, "Bus", "p_pos", axes=[snapshots, pd.MultiIndex.from_product([buses, outages])]) # p_{i,t,k} + >= 0 define_variables( n, 0, inf, "Bus", "p_neg", axes=[snapshots, pd.MultiIndex.from_product([buses, outages])]) logger.info("define constraints") # # Define constraints # ########### p_pos(i,k,t) <= P_pos(i) ########## P_pos_extend = expand_series( get_var(n, "Bus", "P_pos").loc[buses], snapshots).T.reindex(pd.MultiIndex.from_product([buses, outages]), axis=1, level=0) lhs_1 = linexpr((1, P_pos_extend[buses]), (-1, get_var(n, "Bus", "p_pos").loc[snapshots, buses])) define_constraints(n, lhs_1, ">=", 0., "Bus", "UpLimitPos") # ######### p_neg(i,k,t) <= P_neg(i) ######### P_neg_extend = expand_series( get_var(n, "Bus", "P_neg").loc[buses], snapshots).T.reindex(pd.MultiIndex.from_product([buses, outages]), axis=1, level=0) lhs_2 = linexpr((1, P_neg_extend[buses]), (-1, get_var(n, "Bus", "p_pos").loc[snapshots, buses])) define_constraints(n, lhs_2, ">=", 0., "Bus", "UpLimitNeg") # ######## sum(i, p_pos(i,k,t) - p_neg(i,k,t)) = 0 ######### lhs_3 = linexpr((1, get_var(n, "Bus", "p_pos").loc[snapshots].groupby( level=1, axis=1).sum()), (-1, get_var( n, "Bus", "p_neg").loc[snapshots].groupby(level=1, axis=1).sum())) define_constraints(n, lhs_3, "==", 0., "Bus", "FlexBal") # ## |f(l,t) + LODF(l,k) * f(k,t) + sum_i (PTDF(l,i) * (p_neg(i,k, t) - p_pos(i,k, t)))| <= F(l) ######## # f(l,t) + LODF(l,k) * f(k,t) + sum_i (PTDF(l,i) * (p_neg(i,k, t) - p_pos(i,k, t))) <= F(l) # f(l,t) here: f pandas DataFrame(index=snapshots, columns=lines) f = get_var(n, "Line", "s") # F(l) -> line capacity for n.lines.s_nom_extendable=False F = n.lines.s_nom # p pos (index=snapshots, columns=[bus, outage]) p_pos = get_var(n, "Bus", "p_pos") # p neg (index=snapshots, columns=[bus, outage]) p_neg = get_var(n, "Bus", "p_neg") for k in outages: for l in lines.difference(pd.Index( [k])): # all l except for outage line for t in snapshots: lhs = '' # sum_i (PTDF(l,i) * (p_neg(i,k, t) - p_pos(i,k, t))) v1 = linexpr( (ptdf.loc[l], p_neg.loc[t].xs( k, level=1)), # sum_i (PTDF(l,i) * (p_neg(i,k, t)) (-1 * ptdf.loc[l], p_pos.loc[t].xs(k, level=1) ) # sum_i (- PTDF(l,i) * (p_pos(i,k, t)) ) lhs += '\n' + join_exprs(v1.sum()) # f(l,t) + LODF(l,k) * f(k,t) v = linexpr( (1, f.loc[t, l]), # f(l,t) (lodf.loc[l, k], f.loc[t, k])) # LODF(l,k) * f(k,t) lhs += '\n' + join_exprs(v) rhs = F.loc[l] define_constraints(n, lhs, "<=", rhs, "booster1", "capacity_limit") define_constraints(n, lhs, ">=", -rhs, "booster2", "capacity_limit2")
def netzbooster_constraints(n, snapshots): """ add to the LOPF the additional Netzbooster constraints """ # Define indices & parameters n.determine_network_topology() sub = n.sub_networks.at["0", "obj"] sub.calculate_PTDF() sub.calculate_BODF() buses = sub.buses_i() lines = sub.lines_i() outages = [l for l in lines if "outage" in l] ptdf = pd.DataFrame(sub.PTDF, index=lines, columns=buses) lodf = pd.DataFrame(sub.BODF, index=lines, columns=lines) logger.info("define variables") # i = buses # t = snapshots # k = outages # P_i + >= 0 define_variables(n, 0, inf, "Bus", "P_pos", axes=[buses]) # P_i - >= 0 define_variables(n, 0, inf, "Bus", "P_neg", axes=[buses]) # p_{i,t,k} + >= 0 define_variables( n, 0, inf, "Bus", "p_pos", axes=[snapshots, pd.MultiIndex.from_product([buses, outages])]) # p_{i,t,k} + >= 0 define_variables( n, 0, inf, "Bus", "p_neg", axes=[snapshots, pd.MultiIndex.from_product([buses, outages])]) logger.info("define constraints") # # Define constraints # ########### p_pos(i,k,t) <= P_pos(i) ########## P_pos_extend = expand_series( get_var(n, "Bus", "P_pos").loc[buses], snapshots).T.reindex(pd.MultiIndex.from_product([buses, outages]), axis=1, level=0) lhs_1 = linexpr((1, P_pos_extend[buses]), (-1, get_var(n, "Bus", "p_pos").loc[snapshots, buses])) define_constraints(n, lhs_1, ">=", 0., "Bus", "UpLimitPos") # ######### p_neg(i,k,t) <= P_neg(i) ######### P_neg_extend = expand_series( get_var(n, "Bus", "P_neg").loc[buses], snapshots).T.reindex(pd.MultiIndex.from_product([buses, outages]), axis=1, level=0) lhs_2 = linexpr((1, P_neg_extend[buses]), (-1, get_var(n, "Bus", "p_neg").loc[snapshots, buses])) define_constraints(n, lhs_2, ">=", 0., "Bus", "UpLimitNeg") # ######## sum(i, p_pos(i,k,t) - p_neg(i,k,t)) = 0 ######### lhs_3 = linexpr((1, get_var(n, "Bus", "p_pos")), (-1, get_var(n, "Bus", "p_neg"))).groupby(level=1, axis=1).sum() define_constraints(n, lhs_3, "==", 0., "Bus", "FlexBal") # ## |f(l,t) + LODF(l,k) * f(k,t) + sum_i (PTDF(l,i) * (p_neg(i,k, t) - p_pos(i,k, t)))| <= F(l) ######## # f(l,t) + LODF(l,k) * f(k,t) + sum_i (PTDF(l,i) * (p_neg(i,k, t) - p_pos(i,k, t))) <= F(l) # f(l,t) here: f pandas DataFrame(index=snapshots, columns=lines) f = get_var(n, "Line", "s") # F(l) -> line capacity for n.lines.s_nom_extendable=False F = n.lines.s_nom # p pos (index=snapshots, columns=[bus, outage]) p_pos = get_var(n, "Bus", "p_pos") # p neg (index=snapshots, columns=[bus, outage]) p_neg = get_var(n, "Bus", "p_neg") # expand ptdf with snapshots [index=snapshots, columns=[bus, lines]] ptdf_t = expand_series(ptdf.T.stack(), snapshots).T for k in outages: # all lines except outage linke k l = lines.difference(pd.Index([k])) # get PTDF without outage line k (index=snapshots, columns=[buses, lines]) ptdf_no_k = ptdf_t.reindex(l, axis=1, level=1) # expand lodf for linear expression lodf_t = expand_series(lodf.loc[l, k], snapshots).T # expand p_neg with lines (index=snapshots, columns=[buses, lines]) p_neg_l = p_neg.xs(k, level=1, axis=1).reindex(ptdf_no_k.columns, axis=1, level=0) # expand p_pos with lines (index=snapshots, columns=[buses, lines]) p_pos_l = p_pos.xs(k, level=1, axis=1).reindex(ptdf_no_k.columns, axis=1, level=0) # expand flow on outage line f_k = expand_series(f[k], l) # sum_i (PTDF(l,i) * (p_neg(i,k, t) - p_pos(i,k, t))) lhs = linexpr( (ptdf_no_k, p_pos_l), # sum_i (PTDF(l,i) * (p_pos(i,k, t)) (-1 * ptdf_no_k, p_neg_l) # sum_i (- PTDF(l,i) * (p_neg(i,k, t)) ).groupby(level=1, axis=1).sum() # f(l,t) + LODF(l,k) * f(k,t) lhs += linexpr( (1, f[l]), # f(l,t) (lodf_t, f_k)) # LODF(l,k) * f(k,t) rhs = expand_series(F.loc[l], snapshots).T define_constraints(n, lhs, "<=", rhs, "booster1", "capacity_limit_lb_{}".format(k)) define_constraints(n, lhs, ">=", -rhs, "booster2", "capacity_limit_ub_{}".format(k))