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_objective(network, snapshots, direction, options): mga_variables = options['mga_variables'] expr_list = [] for i, variable in enumerate(mga_variables): if variable == 'transmission': expr_list.append( linexpr((direction[i], get_var(network, 'Link', 'p_nom'))).sum()) if variable == 'co2_emission': expr_list.append( linexpr((direction[i], get_var( network, 'Generator', 'p').filter(network.generators.index[ network.generators.type == 'ocgt']))).sum().sum()) elif variable == 'H2' or variable == 'battery': expr_list.append( linexpr((direction[i], get_var( network, 'StorageUnit', 'p_nom').filter(network.storage_units.index[ network.storage_units.carrier == variable]))).sum()) else: expr_list.append( linexpr( (direction[i], get_var(network, 'Generator', 'p_nom').filter(network.generators.index[ network.generators.type == variable]))).sum()) mga_obj = join_exprs(np.array(expr_list)) write_objective(network, mga_obj)
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 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_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_mga_objective(net: pypsa.Network, mga_type: str): if mga_type == 'link': # Minimize transmission capacity (in TWkm) p_nom = get_var(net, 'Link', 'p_nom')[net.components_to_minimize] capacity_expr = linexpr( (net.links.length[net.components_to_minimize], p_nom)).sum() write_objective(net, capacity_expr) elif mga_type == 'storage': # Minimize storage energy/power capacity p_nom = get_var(net, 'StorageUnit', 'p_nom')[net.components_to_minimize] capacity_expr = linexpr((1, p_nom)).sum() write_objective(net, capacity_expr) elif mga_type == 'generator-cap': # Minimize generators power capacity p_nom = get_var(net, 'Generator', 'p_nom')[net.components_to_minimize] capacity_expr = linexpr((1, p_nom)).sum() write_objective(net, capacity_expr) elif mga_type == 'generator-power': # Minimize generators power generation p = get_var(net, 'Generator', 'p')[net.components_to_minimize] capacity_expr = linexpr((1, p)).sum().sum() write_objective(net, capacity_expr)
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 define_mga_objective(n): components, pattern, sense = n.mga_obj if isinstance(components, str): components = [components] terms = [] for c in components: variables = get_var(n, c, nominal_attrs[c]).filter(regex=to_regex(pattern)) if c in ["Link", "Line"] and pattern in ["", "LN|LK", "LK|LN"]: coeffs = sense * n.df(c).loc[variables.index, "length"] else: coeffs = sense terms.append(linexpr((coeffs, variables))) joint_terms = pd.concat(terms) write_objective(n, joint_terms) # print objective to console print(joint_terms)
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 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 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 map_solution(c, attr): variables = get_var(n, c, attr, pop=pop) predefined = True if (c, attr) not in lookup.index: predefined = False n.sols[c] = n.sols[c] if c in n.sols else Dict(df=pd.DataFrame(), pnl={}) n.solutions.at[(c, attr), 'in_comp'] = predefined if isinstance(variables, pd.DataFrame): # case that variables are timedependent n.solutions.at[(c, attr), 'pnl'] = True pnl = n.pnl(c) if predefined else n.sols[c].pnl values = variables.apply(lambda x: x.map(variables_sol)) # values = variables.stack().map(variables_sol).unstack() if c in n.passive_branch_components and attr == "s": set_from_frame(pnl, 'p0', values) set_from_frame(pnl, 'p1', - values) elif c == 'Link' and attr == "p": set_from_frame(pnl, 'p0', values) for i in ['1'] + additional_linkports(n): i_eff = '' if i == '1' else i eff = get_as_dense(n, 'Link', f'efficiency{i_eff}', sns) set_from_frame(pnl, f'p{i}', - values * eff) pnl[f'p{i}'].loc[sns, n.links.index[n.links[f'bus{i}'] == ""]] = \ n.component_attrs['Link'].loc[f'p{i}','default'] else: set_from_frame(pnl, attr, values) else: # case that variables are static n.solutions.at[(c, attr), 'pnl'] = False sol = variables.map(variables_sol) if predefined: non_ext = n.df(c)[attr] n.df(c)[attr + '_opt'] = sol.reindex(non_ext.index).fillna(non_ext) else: n.sols[c].df[attr] = sol
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 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 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_mga_objective(net, sense='min'): # Minimize transmission capacity link_p_nom = get_var(net, 'Link', 'p_nom') sense = 1 if sense == 'min' else -1 link_capacity_expr = linexpr((sense, link_p_nom)).sum() write_objective(net, link_capacity_expr)
def add_to_objective(n, snapshots): """ adds to the pypsa objective function the costs for the Netzbooster, these consists of: (a) costs for the capacities of the Netzbooster (b) costs for compensation dispatch (e.g. paid to DSM consumers, for storage) i => bus t => snapshot k => outage """ logger.info("define objective") 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] coefficient1 = float(snakemake.wildcards.flex_cost) #18000 coefficient2 = 1 #float(snakemake.wildcards.flex_cost)*0.001 #1 # (a) costs for Netzbooster capacities # sum_i ( c_pos * P(i)_pos + c_neg * P(i)_neg) netzbooster_cap_cost = linexpr( (coefficient1, get_var(n, "Bus", "P_pos").loc[buses]), (coefficient1, get_var(n, "Bus", "P_neg").loc[buses])).sum() write_objective(n, netzbooster_cap_cost) # (b) costs for compensation dispatch (paid to DSM consumers/running costs for storage) # sum_(i, t, k) ( o_pos * p(i, t, k)_pos + o_neg * p(i, t, k)_pos) compensate_p_cost = linexpr( (coefficient2, get_var(n, "Bus", "p_pos").loc[snapshots, buses]), (coefficient2, get_var(n, "Bus", "p_neg").loc[snapshots, buses]), ).sum().sum() write_objective(n, compensate_p_cost)
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_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_to_objective(n, snapshots): """ adds to the pypsa objective function the costs for the Netzbooster, these consists of: (a) costs for the capacities of the Netzbooster (b) costs for compensation dispatch (e.g. paid to DSM consumers, for storage) i => bus t => snapshot k => outage """ logger.info("define objective") 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] coefficient1 = 18100 coefficient2 = 10 # (a) costs for Netzbooster capacities # sum_i ( c_pos * P(i)_pos + c_neg * P(i)_neg) # add noise to the netzbooster capacitiy costs to avoid numerical troubles coefficient1_noise = np.random.normal(coefficient1, .01, get_var(n, "Bus", "P_pos").shape) coefficient1_noise2 = np.random.normal(coefficient1, .01, get_var(n, "Bus", "P_pos").shape) netzbooster_cap_cost = linexpr( (coefficient1_noise, get_var(n, "Bus", "P_pos")), (coefficient1_noise2, get_var(n, "Bus", "P_neg")) ).sum() write_objective(n, netzbooster_cap_cost) # (b) costs for compensation dispatch (paid to DSM consumers/running costs for storage) # sum_(i, t, k) ( o_pos * p(i, t, k)_pos + o_neg * p(i, t, k)_pos) # add noise to the marginal costs to avoid numerical troubles coefficient2_noise = np.random.normal(coefficient2, .00001, get_var(n, "Bus", "p_pos").shape) coefficient2_noise2 = np.random.normal(coefficient2, .00001, get_var(n, "Bus", "p_pos").shape) compensate_p_cost = linexpr( (coefficient2_noise, get_var(n, "Bus", "p_pos").loc[snapshots]), (coefficient2_noise2, get_var(n, "Bus", "p_neg").loc[snapshots]), ).sum().sum() write_objective(n, compensate_p_cost)
def add_to_objective(n, snapshots): logger.info("define objective") 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] coefficient1 = 1 coefficient2 = 0.0001 terms = linexpr( (coefficient1, get_var(n, "Bus", "P_pos").loc[buses]), (coefficient1, get_var(n, "Bus", "P_neg").loc[buses]), (coefficient2, get_var(n, "Bus", "p_pos").loc[buses].sum().sum())) write_objective(n, terms)
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 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 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_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_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 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")