def load_costs(): costs = pd.read_excel(snakemake.input.tech_costs, sheet_name=snakemake.wildcards.cost, index_col=0).T discountrate = snakemake.config['costs']['discountrate'] costs['capital_cost'] = ( (annuity(costs.pop('Lifetime [a]'), discountrate) + costs.pop('FOM [%/a]').fillna(0.) / 100.) * costs.pop('Overnight cost [R/kW_el]') * 1e3) costs['efficiency'] = costs.pop('Efficiency').fillna(1.) costs['marginal_cost'] = ( costs.pop('VOM [R/MWh_el]').fillna(0.) + (costs.pop('Fuel cost [R/MWh_th]') / costs['efficiency']).fillna(0.)) emissions_cols = costs.columns.to_series( ).loc[lambda s: s.str.endswith(' emissions [kg/MWh_th]')] costs.loc[:, emissions_cols.index] = (costs.loc[:, emissions_cols.index] / 1e3).fillna(0.) costs = costs.rename(columns=emissions_cols.str[:-len(" [kg/MWh_th]")].str. lower().str.replace(' ', '_')) for attr in ('marginal_cost', 'capital_cost'): overwrites = snakemake.config['costs'].get(attr) if overwrites is not None: overwrites = pd.Series(overwrites) costs.loc[overwrites.index, attr] = overwrites return costs
def calculate_costs(n, label, costs): for c in n.iterate_components(n.branch_components | n.controllable_one_port_components ^ {"Load"}): capital_costs = c.df.capital_cost * c.df[opt_name.get(c.name, "p") + "_nom_opt"] capital_costs_grouped = capital_costs.groupby(c.df.group).sum() costs = costs.reindex(costs.index | pd.MultiIndex.from_product( [[c.list_name], ["capital"], capital_costs_grouped.index])) costs.loc[idx[c.list_name, "capital", list(capital_costs_grouped.index)], label] = capital_costs_grouped.values if c.name == "Link": p = c.pnl.p0.sum() elif c.name == "StorageUnit": p_all = c.pnl.p.copy() p_all[p_all < 0.] = 0. p = p_all.sum() else: p = c.pnl.p.sum() marginal_costs = p * c.df.marginal_cost marginal_costs_grouped = marginal_costs.groupby(c.df.group).sum() costs = costs.reindex(costs.index | pd.MultiIndex.from_product( [[c.list_name], ["marginal"], marginal_costs_grouped.index])) costs.loc[idx[c.list_name, "marginal", list(marginal_costs_grouped.index)], label] = marginal_costs_grouped.values #add back in costs of links if there is a line volume limit if label[1] != "opt": costs.loc[("links-added", "capital", "transmission lines"), label] = ( (400 * 1.25 * n.links.length + 150000.) * n.links.p_nom_opt )[n.links.group == "transmission lines"].sum() * 1.5 * ( annuity(40., 0.07) + 0.02) #add back in all hydro costs.loc[("storage_units", "capital", "hydro"), label] = (0.01) * 2e6 * n.storage_units.loc[ n.storage_units.group == "hydro", "p_nom"].sum() costs.loc[("storage_units", "capital", "PHS"), label] = (0.01) * 2e6 * n.storage_units.loc[ n.storage_units.group == "PHS", "p_nom"].sum() costs.loc[("generators", "capital", "ror"), label] = (0.02) * 3e6 * n.generators.loc[n.generators.group == "ror", "p_nom"].sum() return costs
def retrofitting_comparison(): #from Fig 4.2 HPI http://dx.doi.org/10.1016/j.rser.2013.09.012 hp = np.array([[100, 0], [90, 18], [80, 40], [70, 67], [60, 98], [50, 135], [40, 180], [30, 230], [20, 285]]) hp = pd.Series(hp[:, 1], 100 - hp[:, 0]) hp_annual = (annuity(50, 0.04) + 0.01) * hp #100 per cent to per unit; 124 kWh/m^2 average for 2011 from HP hp_final = (hp_annual / (hp_annual.index.to_series() / 100 * 124)).fillna( method="bfill") * 1e3 hp_final.loc[0] = 76 #Danes Heatroadmap Fig 5 of http://dx.doi.org/10.1016/j.enpol.2013.10.035 dane = np.array([[0, 1.05], [10, 1.15], [20, 1.30], [30, 1.45], [40, 1.65], [50, 1.87], [60, 2.13], [70, 2.40], [75, 2.6]]) dane = pd.Series(dane[:, 1], dane[:, 0]) dane_final = ((annuity(30, 0.04) + 0.01) * dane) * 1e3 fig, ax = plt.subplots(1, 1) fig.set_size_inches(4.5, 3.5) hp_final.plot(ax=ax, label="Germany", linewidth=2) dane_final.plot(ax=ax, label="Denmark", linewidth=2) ax.set_ylabel("Cost for energy saved [EUR/MWh]") ax.set_xlabel("Heat demand reduction [%]") ax.grid() ax.set_xlim([0, 80]) ax.legend() fig.tight_layout() fig.savefig(snakemake.output.retrofitting_comparison, transparent=True)
def add_gas_infrastructure(n, costs): buses = n.buses.index[n.buses.population > 0.] discountrate = snakemake.config['costs']['discountrate'] n.add("Carrier", "H2") buses_h2 = n.madd("Bus", buses, suffix=" H2", carrier="H2") n.madd( "Link", buses, suffix=" H2 Electrolysis", bus0=buses, bus1=buses_h2, p_nom_extendable=True, efficiency=0.75, #Cost from http://www.nrel.gov/docs/fy09osti/45873.pdf "Future Timeframe" #(same source as Budishak) capital_cost=(annuity(20., discountrate) + 0.017) * 300. * 1000. * USD2013_to_EUR2013) n.madd( "Link", buses, suffix=" H2 Fuel Cell", bus0=buses_h2, bus1=buses, p_nom_extendable=True, efficiency=0.58, #Cost from http://www.nrel.gov/docs/fy09osti/45873.pdf "Future Timeframe" #(same source as Budishak) #NB: Costs refer to electrical side, so must multiply by efficiency capital_cost=(annuity(20., discountrate) + 0.017) * 437. * 0.58 * 1000. * USD2013_to_EUR2013) n.madd("Store", buses_h2, suffix=" Store", bus=buses_h2, e_nom_extendable=True, e_cyclic=True, capital_cost=annuity(20., discountrate) * 11.2 * 1000. * USD2013_to_EUR2013)
def load_costs(Nyears=1., tech_costs=None, config=None, elec_config=None): if tech_costs is None: tech_costs = snakemake.input.tech_costs if config is None: config = snakemake.config['costs'] # set all asset costs and other parameters costs = pd.read_csv(tech_costs, index_col=list(range(3))).sort_index() # correct units to MW and EUR costs.loc[costs.unit.str.contains("/kW"),"value"] *= 1e3 costs.loc[costs.unit.str.contains("USD"),"value"] *= config['USD2013_to_EUR2013'] costs = (costs.loc[idx[:,config['year'],:], "value"] .unstack(level=2).groupby("technology").sum(min_count=1)) costs = costs.fillna({"CO2 intensity" : 0, "FOM" : 0, "VOM" : 0, "discount rate" : config['discountrate'], "efficiency" : 1, "fuel" : 0, "investment" : 0, "lifetime" : 25}) costs["capital_cost"] = ((annuity(costs["lifetime"], costs["discount rate"]) + costs["FOM"]/100.) * costs["investment"] * Nyears) costs.at['OCGT', 'fuel'] = costs.at['gas', 'fuel'] costs.at['CCGT', 'fuel'] = costs.at['gas', 'fuel'] costs['marginal_cost'] = costs['VOM'] + costs['fuel'] / costs['efficiency'] costs = costs.rename(columns={"CO2 intensity": "co2_emissions"}) costs.at['OCGT', 'co2_emissions'] = costs.at['gas', 'co2_emissions'] costs.at['CCGT', 'co2_emissions'] = costs.at['gas', 'co2_emissions'] costs.at['solar', 'capital_cost'] = 0.5*(costs.at['solar-rooftop', 'capital_cost'] + costs.at['solar-utility', 'capital_cost']) def costs_for_storage(store, link1, link2=None, max_hours=1.): capital_cost = link1['capital_cost'] + max_hours * store['capital_cost'] if link2 is not None: capital_cost += link2['capital_cost'] return pd.Series(dict(capital_cost=capital_cost, marginal_cost=0., co2_emissions=0.)) if elec_config is None: elec_config = snakemake.config['electricity'] max_hours = elec_config['max_hours'] costs.loc["battery"] = \ costs_for_storage(costs.loc["battery storage"], costs.loc["battery inverter"], max_hours=max_hours['battery']) costs.loc["H2"] = \ costs_for_storage(costs.loc["hydrogen storage"], costs.loc["fuel cell"], costs.loc["electrolysis"], max_hours=max_hours['H2']) for attr in ('marginal_cost', 'capital_cost'): overwrites = config.get(attr) if overwrites is not None: overwrites = pd.Series(overwrites) costs.loc[overwrites.index, attr] = overwrites return costs
def prepare_network(options): #Build the Network object, which stores all other objects network = pypsa.Network() network.opf_keep_files = False network.options = options network.shadow_prices = {} #load graph nodes = pd.Index( pd.read_csv("data/graph/nodes.csv", header=None, squeeze=True).values) edges = pd.read_csv("data/graph/edges.csv", header=None) #set times network.set_snapshots( pd.date_range(options['tmin'], options['tmax'], freq='H')) represented_hours = network.snapshot_weightings.sum() Nyears = represented_hours / 8760. #set all asset costs and other parameters costs = pd.read_csv(snakemake.input.cost_name, index_col=list(range(2))).sort_index() #correct units to MW and EUR costs.loc[costs.unit.str.contains("/kW"), "value"] *= 1e3 costs.loc[costs.unit.str.contains("USD"), "value"] *= options['USD2019_to_EUR2019'] costs = costs.loc[:, "value"].unstack(level=1).groupby("technology").sum() costs = costs.fillna({ "CO2 intensity": 0, "FOM": 0, "VOM": 0, "discount rate": options['discountrate'], "efficiency": 1, "fuel": 0, "investment": 0, "lifetime": 25 }) costs["fixed"] = [ (annuity(v["lifetime"], v["discount rate"]) + v["FOM"] / 100.) * v["investment"] * Nyears for i, v in costs.iterrows() ] #load demand data df_elec, df_heat, df_cooling, p_max_pu, p_nom_max, ashp_cop, gshp_cop, co2_totals, df_transport, transport_data, avail_profile, dsm_profile = prepare_data( ) #add carriers network.add("Carrier", "gas", co2_emissions=costs.at['gas', 'CO2 intensity']) # in t_CO2/MWht network.add("Carrier", "coal", co2_emissions=costs.at['coal', 'CO2 intensity']) # in t_CO2/MWht network.add("Carrier", "lignite", co2_emissions=costs.at['lignite', 'CO2 intensity']) # in t_CO2/MWht network.add("Carrier", "oil", co2_emissions=costs.at['oil', 'CO2 intensity']) # in t_CO2/MWht network.add("Carrier", "nuclear", co2_emissions=0) network.add("Carrier", "solid biomass", co2_emissions=0) network.add("Carrier", "onwind") network.add("Carrier", "offwind") network.add("Carrier", "solar") if options['add_PHS']: network.add("Carrier", "PHS") if options['add_hydro']: network.add("Carrier", "hydro") if options['add_ror']: network.add("Carrier", "ror") if options['add_H2_storage']: network.add("Carrier", "H2") if options['add_battery_storage']: network.add("Carrier", "battery") if options["heat_coupling"]: network.add("Carrier", "heat") network.add("Carrier", "water tanks") if options['cooling_coupling']: network.add("Carrier", "cooling") if options["retrofitting"]: network.add("Carrier", "retrofitting") if options["transport_coupling"]: network.add("Carrier", "Li ion") if options['co2_reduction'] is not None: co2_limit = co2_totals["electricity"].sum() * Nyears if options["transport_coupling"]: co2_limit += co2_totals[[ i + " non-elec" for i in ["rail", "road", "transport"] ]].sum().sum() * Nyears if options["heat_coupling"]: co2_limit += co2_totals[[ i + " non-elec" for i in ["residential", "services"] ]].sum().sum() * Nyears co2_limit *= options['co2_reduction'] network.add("GlobalConstraint", "co2_limit", type="primary_energy", carrier_attribute="co2_emissions", sense="<=", constant=co2_limit) #load hydro data if options['add_PHS'] or options['add_hydro']: hydrocapa_df = vhydro.get_hydro_capas( fn='data/hydro/emil_hydro_capas.csv') if options['add_ror']: ror_share = vhydro.get_ror_shares( fn='data/hydro/ror_ENTSOe_Restore2050.csv') else: ror_share = pd.Series(0, index=hydrocapa_df.index) if options['add_hydro']: inflow_df = vhydro.get_hydro_inflow( inflow_dir='data/hydro/inflow/') * 1e3 # GWh/h to MWh/h # if Hydro_Inflow from Restore2050 is not available, use alternative dataset: #inflow_df = vhydro.get_inflow_NCEP_EIA().to_series().unstack(0) #MWh/h # select only nodes that are in the network # inflow_df = inflow_df.loc[network.snapshots,nodes].dropna(axis=1) inflow_df = inflow_df.loc['2011', nodes].dropna(axis=1) inflow_df.index = network.snapshots network.madd("Bus", nodes, x=[country_shapes[node].centroid.x for node in nodes], y=[country_shapes[node].centroid.y for node in nodes]) network.madd("Load", nodes, bus=nodes, p_set=df_elec[nodes]) #add renewables onwinds = pd.Index( [i for i in p_nom_max['onwind'].index if i[:2] in nodes]) network.madd("Generator", onwinds, suffix=' onwind', bus=[i[:2] for i in onwinds], p_nom_extendable=True, carrier="onwind", p_nom_max=p_nom_max['onwind'][onwinds], capital_cost=costs.at['onwind', 'fixed'], marginal_cost=costs.at['onwind', 'VOM'], p_max_pu=p_max_pu['onwind'][onwinds]) offwinds = p_nom_max['offwind'].index[~p_nom_max['offwind'].isnull( )].intersection(nodes) network.madd("Generator", offwinds, suffix=' offwind', p_nom_extendable=True, bus=offwinds, carrier="offwind", p_nom_max=p_nom_max['offwind'][offwinds], capital_cost=costs.at['offwind', 'fixed'], p_max_pu=p_max_pu['offwind'][offwinds], marginal_cost=costs.at['offwind', 'VOM']) if options['ninja_solar']: solar = pd.read_csv('data/renewables/ninja_pv_europe_v1.1_sarah.csv', index_col=0, parse_dates=True)[nodes] else: solar = p_max_pu['solar'][nodes] network.madd( "Generator", nodes, suffix=' solar', p_nom_extendable=True, bus=nodes, carrier="solar", p_nom_max=p_nom_max['solar'][nodes], capital_cost=0.5 * (costs.at['solar-utility', 'fixed'] + costs.at['solar-rooftop', 'fixed']), p_max_pu=solar, marginal_cost=0.01) # RES costs made up to fix curtailment order #add conventionals for generator, carrier in [("OCGT", "gas"), ("CCGT", "gas"), ("coal", "coal"), ("nuclear", "nuclear"), ("lignite", "lignite"), ("oil", "oil")]: network.madd("Bus", nodes + " " + carrier, carrier=carrier) p_max_pu = 0.9 if generator == 'nuclear' else 1.0 network.madd( "Link", nodes + " " + generator, bus0=nodes + " " + carrier, bus1=nodes, marginal_cost=costs.at[generator, 'efficiency'] * costs.at[generator, 'VOM'], #NB: VOM is per MWel capital_cost=costs.at[generator, 'efficiency'] * costs.at[generator, 'fixed'], #NB: fixed cost is per MWel p_nom_extendable=True, p_max_pu=p_max_pu, efficiency=costs.at[generator, 'efficiency']) network.madd("Store", nodes + " " + carrier + " store", bus=nodes + " " + carrier, e_nom_extendable=True, e_min_pu=-1., marginal_cost=costs.at[carrier, 'fuel'] + options['CO2price'] * costs.at[carrier, 'CO2 intensity']) if options['add_PHS']: # pure pumped hydro storage, fixed, 6h energy by default, no inflow phss = hydrocapa_df.index[ hydrocapa_df['p_nom_store[GW]'] > 0].intersection(nodes) if options['hydro_capital_cost']: cc = costs.at['PHS', 'fixed'] else: cc = 0. network.madd( "StorageUnit", phss, suffix=" PHS", bus=phss, carrier="PHS", p_nom_extendable=False, p_nom=hydrocapa_df.loc[phss]['p_nom_store[GW]'] * 1000., #from GW to MW max_hours=options['PHS_max_hours'], efficiency_store=np.sqrt(costs.at['PHS', 'efficiency']), efficiency_dispatch=np.sqrt(costs.at['PHS', 'efficiency']), cyclic_state_of_charge=True, capital_cost=cc, marginal_cost=options['marginal_cost_storage']) if options['add_hydro']: # inflow hydro: # * run-of-river if E_s=0 # * reservoir # * could include mixed pumped, if 0>p_min_pu_fixed=p_pump*p_nom #add storage pnom = (1 - ror_share ) * hydrocapa_df['p_nom_discharge[GW]'] * 1000. #GW to MW hydros = pnom.index[pnom > 0.] if options['hydro_max_hours'] is None: max_hours = (hydrocapa_df.loc[hydros, 'E_store[TWh]'] * 1e6 / pnom ) #TWh to MWh else: max_hours = options['hydro_max_hours'] inflow = inflow_df.multiply((1 - ror_share))[hydros].dropna(axis=1) if options['hydro_capital_cost']: cc = costs.at['hydro', 'fixed'] else: cc = 0. network.madd( "StorageUnit", hydros, suffix=' hydro', bus=hydros, carrier="hydro", p_nom_extendable=False, p_nom=pnom[hydros], max_hours=max_hours, p_max_pu=1, #dispatch p_min_pu=0., #store efficiency_dispatch=1, efficiency_store=0, inflow=inflow, cyclic_state_of_charge=True, capital_cost=cc, marginal_cost=options['marginal_cost_storage']) if options['add_ror']: rors = ror_share.index[ror_share > 0.] rors = rors.intersection(nodes) rors = rors.intersection(inflow_df.columns) pnom = ror_share[rors] * hydrocapa_df.loc[ rors, 'p_nom_discharge[GW]'] * 1000. #GW to MW inflow_pu = inflow_df[rors].multiply(ror_share[rors] / pnom) inflow_pu[ inflow_pu > 1] = 1. #limit inflow per unit to one, i.e, excess inflow is spilled here if options['hydro_capital_cost']: cc = costs.at['ror', 'fixed'] else: cc = 0. network.madd("Generator", rors, suffix=" ror", bus=rors, carrier="ror", p_nom_extendable=False, p_nom=pnom, p_max_pu=inflow_pu, capital_cost=cc, marginal_cost=options['marginal_cost_storage']) if options['add_H2_storage']: PtGC = 1 - options['PtGC'] / 100. network.madd("Bus", nodes + " H2", carrier="H2") network.madd("Link", nodes + " H2 Electrolysis", bus1=nodes + " H2", bus0=nodes, p_nom_extendable=True, efficiency=costs.at["electrolysis", "efficiency"], capital_cost=costs.at["electrolysis", "fixed"] * PtGC) network.madd( "Link", nodes + " H2 Fuel Cell", bus0=nodes + " H2", bus1=nodes, p_nom_extendable=True, efficiency=costs.at["fuel cell", "efficiency"], capital_cost=costs.at["fuel cell", "fixed"] * costs.at["fuel cell", "efficiency"]) #NB: fixed cost is per MWel network.madd("Store", nodes + " H2 Store tank", bus=nodes + " H2", e_nom_extendable=True, e_cyclic=True, capital_cost=costs.at["hydrogen storage tank", "fixed"]) # add upper limits for underground H2 storage H2_cavern = pd.read_csv('data/hydrogen_salt_cavern_potentials.csv', index_col=0, sep=';') network.madd( "Store", nodes[H2_cavern.iloc[:, 0]] + " H2 Store underground", bus=nodes[H2_cavern.iloc[:, 0]] + " H2", e_nom_extendable=True, e_nom_max=H2_cavern.TWh[H2_cavern.iloc[:, 0]].values * 1e6, #from TWh to MWh e_cyclic=True, capital_cost=costs.at["hydrogen storage underground", "fixed"]) if options['add_methanation']: network.madd("Link", nodes + " Sabatier", bus0=nodes + " H2", bus1=nodes + " gas", p_nom_extendable=True, efficiency=costs.at["methanation", "efficiency"], capital_cost=costs.at["methanation", "fixed"] * PtGC) if options['add_battery_storage']: network.madd("Bus", nodes + " battery", carrier="battery") network.madd("Store", nodes + " battery", bus=nodes + " battery", e_cyclic=True, e_nom_extendable=True, capital_cost=costs.at['battery storage', 'fixed']) network.madd("Link", nodes + " battery charger", bus0=nodes, bus1=nodes + " battery", efficiency=costs.at['battery inverter', 'efficiency']**0.5, capital_cost=costs.at['battery inverter', 'fixed'], p_nom_extendable=True) network.madd("Link", nodes + " battery discharger", bus0=nodes + " battery", bus1=nodes, efficiency=costs.at['battery inverter', 'efficiency']**0.5, marginal_cost=options['marginal_cost_storage'], p_nom_extendable=True) #Sources: #[HP]: Henning, Palzer http://www.sciencedirect.com/science/article/pii/S1364032113006710 #[B]: Budischak et al. http://www.sciencedirect.com/science/article/pii/S0378775312014759 if options["heat_coupling"]: #urban are southern countries, where disctrict heating will not be implemented if options["central"]: urban = pd.Index(["ES", "GR", "PT", "IT", "BG"]) else: urban = nodes #NB: must add costs of central heating afterwards (EUR 400 / kWpeak, 50a, 1% FOM from Fraunhofer ISE) #central are urban nodes with district heating # district heating shares come from https://www.euroheat.org/knowledge-hub/country-profiles/ central = nodes ^ urban urban_fraction = pd.read_csv( 'data/existing_2020/district_heating_share.csv', index_col=0).loc[nodes, str(2015)] #options['year'] network.madd("Bus", nodes + " heat", carrier="heat") network.madd("Bus", nodes + " urban heat", carrier="heat") network.madd("Load", nodes, suffix=" heat", bus=nodes + " heat", p_set=df_heat[nodes].multiply((1 - urban_fraction))) network.madd("Load", nodes, suffix=" urban heat", bus=nodes + " urban heat", p_set=df_heat[nodes].multiply(urban_fraction)) if options["PTH"]: HPC = 1 - options['HPC'] / 100. RHC = 1 - options['RHC'] / 100. network.madd( "Link", urban, suffix=" central heat pump", bus0=urban, bus1=urban + " urban heat", efficiency=ashp_cop[urban] if options["time_dep_hp_cop"] else costs.at['decentral air-sourced heat pump', 'efficiency'], capital_cost=costs.at['decentral air-sourced heat pump', 'efficiency'] * costs.at['decentral air-sourced heat pump', 'fixed'] * HPC, p_nom_extendable=True) network.madd( "Link", central, suffix=" central heat pump", bus0=central, bus1=central + " urban heat", efficiency=gshp_cop[central] if options["time_dep_hp_cop"] else costs.at['central ground-sourced heat pump', 'efficiency'], capital_cost=costs.at['central ground-sourced heat pump', 'efficiency'] * costs.at['central ground-sourced heat pump', 'fixed'] * HPC, p_nom_extendable=True) network.madd( "Link", nodes, suffix=" decentral heat pump", bus0=nodes, bus1=nodes + " heat", efficiency=gshp_cop[nodes] if options["time_dep_hp_cop"] else costs.at['decentral ground-sourced heat pump', 'efficiency'], capital_cost=costs.at['decentral ground-sourced heat pump', 'efficiency'] * costs.at['decentral ground-sourced heat pump', 'fixed'] * HPC, p_nom_extendable=True) network.madd("Link", nodes + " decentral resistive heater", bus0=nodes, bus1=nodes + " heat", efficiency=costs.at['decentral resistive heater', 'efficiency'], capital_cost=costs.at['decentral resistive heater', 'efficiency'] * costs.at['decentral resistive heater', 'fixed'] * RHC, p_nom_extendable=True) network.madd("Link", urban + " central resistive heater", bus0=urban, bus1=urban + " urban heat", efficiency=costs.at['decentral resistive heater', 'efficiency'], capital_cost=costs.at['decentral resistive heater', 'efficiency'] * costs.at['decentral resistive heater', 'fixed'] * RHC, p_nom_extendable=True) network.madd( "Link", central + " central resistive heater", bus0=central, bus1=central + " urban heat", p_nom_extendable=True, capital_cost=costs.at['central resistive heater', 'efficiency'] * costs.at['central resistive heater', 'fixed'] * RHC, efficiency=costs.at['central resistive heater', 'efficiency']) if options["tes"]: network.madd("Bus", nodes + " water tanks", carrier="water tanks") network.madd("Link", nodes + " water tanks charger", bus0=nodes + " heat", bus1=nodes + " water tanks", efficiency=costs.at['water tank charger', 'efficiency'], p_nom_extendable=True) network.madd("Link", nodes + " water tanks discharger", bus0=nodes + " water tanks", bus1=nodes + " heat", efficiency=costs.at['water tank discharger', 'efficiency'], p_nom_extendable=True) network.madd( "Store", nodes + " water tank", bus=nodes + " water tanks", e_cyclic=True, e_nom_extendable=True, standing_loss=1 - np.exp( -1 / (24. * options["tes_tau"]) ), # [HP] 180 day time constant for centralised, 3 day for decentralised capital_cost=costs.at['decentral water tank storage', 'fixed'] / (1.17e-3 * 40) ) #convert EUR/m^3 to EUR/MWh for 40 K diff and 1.17 kWh/m^3/K network.madd("Bus", urban + " central water tanks", carrier="water tanks") network.madd("Link", urban + " central water tanks charger", bus0=urban + " urban heat", bus1=urban + " central water tanks", efficiency=costs.at['water tank charger', 'efficiency'], p_nom_extendable=True) network.madd("Link", urban + " central water tanks discharger", bus0=urban + " central water tanks", bus1=urban + " urban heat", efficiency=costs.at['water tank discharger', 'efficiency'], p_nom_extendable=True) network.madd( "Store", urban + " central water tank", bus=urban + " central water tanks", e_cyclic=True, e_nom_extendable=True, standing_loss=1 - np.exp( -1 / (24. * options["tes_tau"]) ), # [HP] 180 day time constant for centralised, 3 day for decentralised capital_cost=costs.at['decentral water tank storage', 'fixed'] / (1.17e-3 * 40) ) #convert EUR/m^3 to EUR/MWh for 40 K diff and 1.17 kWh/m^3/K network.madd("Bus", central + " central water tanks", carrier="water tanks") network.madd("Link", central + " central water tanks charger", bus0=central + " urban heat", bus1=central + " central water tanks", p_nom_extendable=True, efficiency=costs.at['water tank charger', 'efficiency']) network.madd("Link", central + " central water tanks discharger", bus0=central + " central water tanks", bus1=central + " urban heat", p_nom_extendable=True, efficiency=costs.at['water tank discharger', 'efficiency']) network.madd( "Store", central, suffix=" central water tank", bus=central + " central water tanks", e_cyclic=True, e_nom_extendable=True, standing_loss=1 - np.exp( -1 / (24. * 180.) ), # [HP] 180 day time constant for centralised, 3 day for decentralised capital_cost=costs.at[ 'central water tank storage', 'fixed']) # EUR/MWh now instead of EUR/m^3 # Heat demand-side management by building thermal inertia if options['dsm_heat']: max_hours = options['DSM'] # 0,2,4,6,... hours network.madd("StorageUnit", nodes, suffix=" DSM", bus=nodes + " heat", cyclic_state_of_charge=True, max_hours=max_hours, standing_loss=1 - np.exp(-1 / max_hours), p_nom_extendable=False, p_nom=df_heat[nodes].multiply( (1 - urban_fraction)).mean()) network.madd( "StorageUnit", nodes, suffix=" DSM urban", bus=nodes + " urban heat", cyclic_state_of_charge=True, max_hours=max_hours, standing_loss=1 - np.exp(-1 / max_hours), p_nom_extendable=False, p_nom=df_heat[nodes].multiply(urban_fraction).mean()) if options["boilers"]: network.madd( "Link", nodes + " decentral gas boiler", p_nom_extendable=True, bus0=nodes + " gas", bus1=nodes + " heat", efficiency=costs.at['decentral gas boiler', 'efficiency'], capital_cost=costs.at['decentral gas boiler', 'efficiency'] * costs.at['decentral gas boiler', 'fixed']) network.madd( "Link", urban + " central gas boiler", p_nom_extendable=True, bus0=urban + " gas", bus1=urban + " urban heat", efficiency=costs.at['decentral gas boiler', 'efficiency'], capital_cost=costs.at['decentral gas boiler', 'efficiency'] * costs.at['decentral gas boiler', 'fixed']) network.madd( "Link", central + " central gas boiler", bus0=central + " gas", bus1=central + " urban heat", p_nom_extendable=True, capital_cost=costs.at['central gas boiler', 'efficiency'] * costs.at['central gas boiler', 'fixed'], efficiency=costs.at['central gas boiler', 'efficiency']) if options["chp"]: network.madd("Link", central + " central gas CHP electric", bus0=central + " gas", bus1=central, p_nom_extendable=True, capital_cost=costs.at['central gas CHP', 'fixed'] * options['chp_parameters']['eta_elec'], efficiency=options['chp_parameters']['eta_elec']) network.madd("Link", central + " central gas CHP heat", bus0=central + " gas", bus1=central + " urban heat", p_nom_extendable=True, efficiency=options['chp_parameters']['eta_elec'] / options['chp_parameters']['c_v']) if options["biomass"]: network.madd("Bus", nodes + " solid biomass", carrier="solid biomass") biomass_potential = pd.read_csv('data/biomass_potentials.csv', index_col=0) network.madd( "Store", nodes + " solid biomass store", bus=nodes + " solid biomass", e_initial=0, e_min_pu=-1., e_nom_extendable=True, e_nom_max=biomass_potential.loc[nodes, 'solid biomass'].values, capital_cost=0.01, marginal_cost=costs.at['solid biomass', 'fuel'] + options['CO2price'] * costs.at['solid biomass', 'CO2 intensity']) network.madd("Link", central + " central biomass CHP electric", bus0=central + " solid biomass", bus1=central, p_nom_extendable=True, capital_cost=costs.at['biomass CHP', 'fixed'] * options['chp_parameters']['eta_elec'], efficiency=options['chp_parameters']['eta_elec']) network.madd("Link", central + " central biomass CHP heat", bus0=central + " solid biomass", bus1=central + " urban heat", p_nom_extendable=True, efficiency=options['chp_parameters']['eta_elec'] / options['chp_parameters']['c_v']) network.madd("Link", central + " central biomass HOP", bus0=central + " solid biomass", bus1=central + " urban heat", p_nom_extendable=True, capital_cost=costs.at['biomass HOP', 'fixed'] * costs.at['biomass HOP', 'efficiency'], efficiency=costs.at['biomass HOP', 'efficiency']) network.madd("Link", nodes + " biomass EOP", bus0=nodes + " solid biomass", bus1=nodes, p_nom_extendable=True, capital_cost=costs.at['biomass EOP', 'fixed'] * costs.at['biomass EOP', 'efficiency'], efficiency=costs.at['biomass EOP', 'efficiency']) # add cooling if neccesary if options['cooling_coupling']: network.madd("Bus", nodes + " cooling", carrier="cooling") network.madd("Load", nodes, suffix=" cooling", bus=nodes + " cooling", p_set=df_cooling[nodes]) # decentral ground heat pump can also provide cooling network.madd("Link", nodes, suffix=" cooling pump", bus0=nodes, bus1=nodes + " cooling", efficiency=3, p_nom_extendable=True) # transportation sector if options["transport_coupling"]: EV_pene = options['EV_pene'] network.madd("Bus", nodes, suffix=" EV battery", carrier="Li ion") network.madd("Load", nodes, suffix=" transport", bus=nodes + " EV battery", p_set=EV_pene * (df_transport[nodes] + shift_df(df_transport[nodes], 1) + shift_df(df_transport[nodes], 2)) / 3.) p_nom = EV_pene * transport_data[ "number cars"] * 0.011 #3-phase charger with 11 kW * x% of time grid-connected network.madd( "Link", nodes, suffix=" BEV charger", bus0=nodes, bus1=nodes + " EV battery", p_nom=p_nom, p_max_pu=avail_profile[nodes], efficiency=0.9, #[B] #These were set non-zero to find LU infeasibility when availability = 0.25 #p_nom_extendable=True, #p_nom_min=p_nom, #capital_cost=1e6, #i.e. so high it only gets built where necessary ) if options["v2g"]: network.madd("Link", nodes, suffix=" V2G", bus1=nodes, bus0=nodes + " EV battery", p_nom=p_nom * options['v2g_availability'], p_max_pu=avail_profile[nodes], efficiency=0.9) #[B] if options["bev"]: network.madd("Store", nodes, suffix=" battery storage", bus=nodes + " EV battery", e_cyclic=True, e_nom=EV_pene * transport_data["number cars"] * 0.05 * options["bev_availability"], e_max_pu=1, e_min_pu=dsm_profile[nodes]) #add lines if not network.options['no_lines']: lengths = np.array([ haversine( [network.buses.at[name0, "x"], network.buses.at[name0, "y"]], [network.buses.at[name1, "x"], network.buses.at[name1, "y"]]) for name0, name1 in edges.values ]) if options['line_volume_limit_factor'] is not None: cc = Nyears * 0.01 # Set line costs to ~zero because we already restrict the line volume else: cc = ((options['line_cost_factor']*lengths*costs.at['HVDC overhead','fixed']*1.25+costs.at['HVDC inverter pair','fixed']) \ * 1.5) # 1.25 because lines are not straight, 150000 is per MW cost of # converter pair for DC line, # n-1 security is approximated by an overcapacity factor 1.5 ~ 1./0.666667 #FOM of 2%/a network.madd("Link", edges[0] + '-' + edges[1], bus0=edges[0].values, bus1=edges[1].values, p_nom_extendable=True, p_min_pu=-1, length=lengths, capital_cost=cc) return network
def add_water_heating(n): ##### CHP Parameters ###### electrical efficiency with no heat output eta_elec = 0.468 ###### loss of fuel for each addition of heat c_v = 0.15 ###### backpressure ratio c_m = 0.75 ###### ratio of max heat output to max electrical output p_nom_ratio = 1. heat_demand = compute_heat_demand(n) network.add("Carrier", "heat") network.add("Bus", node + " heat", carrier="heat") network.add( "Link", node + " heat pump", bus0=node, bus1=node + " heat", efficiency=cop[node], #cop for 2011 time_dep_hp_cop capital_cost=(annuity(20, discountrate) + 0.015) * 3. * 1.050e6, #20a, 1% FOM, 1050 EUR/kWth from [HP] NB: PyPSA uses bus0 for p_nom restriction, hence factor 3 to get 3150 EUR/kWel p_nom_extendable=True) network.add("Load", node + " heat", bus=node + " heat", p_set=heat_demand[node]) network.add( "Link", node + " resistive heater", bus0=node, bus1=node + " heat", efficiency=0.9, capital_cost=(annuity(20, discountrate) + 0.02) * 0.9 * 1.e5, #100 EUR/kW_th, 2% FOM from Schaber thesis p_nom_extendable=True, ) ##### H2 bus w/methanation # methanation network.add( "Link", node + " Sabatier", bus0=node + " H2", bus1=node + " OCGT", p_nom_extendable=True, #Efficiency from Katrin Schaber PhD thesis efficiency=0.77, #Costs from Katrin Schaber PhD thesis; comparable with HP (1.5e6 including H2 electrolysis) capital_cost=(annuity(20., discountrate) + 0.02) * 1.1e6 * Nyears) # gas boiler network.add( "Link", node + " gas boiler", p_nom_extendable=True, bus0=node + " OCGT", bus1=node + " heat", capital_cost=(annuity(20, 0.07) + 0.01) * 0.9 * 3.e5, #300 EUR/kW_th, 1% FOM from Schaber thesis, 20a from HP efficiency=0.9, ) # chp - standardmäßig rein? Ist es jetzt auf jeden Fall! network.add( "Link", node + " CHP electric", bus0=node + " OCGT", bus1=node, capital_cost=(annuity(25, 0.07) + 0.03) * 1.4e6 * eta_elec, #From HP decentral efficiency=eta_elec, p_nom_extendable=True) network.add("Link", node + " CHP heat", p_nom_extendable=True, bus0=node + " OCGT", bus1=node + " heat", capital_cost=0., efficiency=eta_elec / c_v) ##### heat flexibilities: if "TES" in flexibilities: network.add("Carrier", "water tanks") network.add("Bus", node + " water tanks", carrier="water tanks") network.add("Link", node + " water tanks charger", bus0=node + " heat", bus1=node + " water tanks", efficiency=0.9, capital_cost=0., p_nom_extendable=True) network.add("Link", node + " water tanks discharger", bus0=node + " water tanks", bus1=node + " heat", efficiency=0.9, capital_cost=0., p_nom_extendable=True) network.add( "Store", node + " water tank", bus=node + " water tanks", e_cyclic=True, e_nom_extendable=True, standing_loss=1 - np.exp( -1 / (24. * 180) ), #options["tes_tau"])), # [HP] 180 day time constant for centralised, 3 day for decentralised capital_cost=(annuity(40, discountrate) + 0.01) * 20 / ( 1.17e-3 * 40 ), #[HP] 40 years, 20 EUR/m^3 in EUR/MWh for 40 K diff and 1.17 kWh/m^2, 1% FOM )
def export_metrics_to_json(network, power_round=1): """power_round refers to GW of power""" n = network #add missing costs n.links.capital_cost = (400 * 1.25 * n.links.length + 150000.) * 1.5 * (annuity(40., 0.07) + 0.02) hydro = n.storage_units.index[n.storage_units.carrier == "hydro"] PHS = n.storage_units.index[n.storage_units.carrier == "PHS"] ror = n.generators.index[n.generators.carrier == "ror"] n.storage_units.loc[hydro, "p_nom_opt"] = n.storage_units.loc[hydro, "p_nom"] n.storage_units.loc[PHS, "p_nom_opt"] = n.storage_units.loc[PHS, "p_nom"] n.generators.loc[ror, "p_nom_opt"] = n.generators.loc[ror, "p_nom"] n.storage_units.loc[hydro, "capital_cost"] = (0.01) * 2e6 n.storage_units.loc[PHS, "capital_cost"] = (0.01) * 2e6 n.generators.loc[ror, "capital_cost"] = (0.02) * 3e6 #only take AC buses carrier = "AC" buses = n.buses[n.buses.carrier == carrier] generation_carriers = n.generators.carrier.value_counts().index generation_carriers = (preferred_order & generation_carriers).append( generation_carriers.difference(preferred_order)) print("generation carriers:", generation_carriers) storage_carriers = n.storage_units.carrier.value_counts().index storage_carriers = (preferred_order & storage_carriers).append( storage_carriers.difference(preferred_order)) print("storage carriers:", storage_carriers) if len(metrics["energy"]) == 0: metrics["energy"] = [[] for i in range(len(buses.index) + 1)] metrics["power"] = [[] for i in range(len(buses.index) + 1)] metrics["cost"] = [[] for i in range(len(buses.index) + 1)] carriers = {} carriers["energy"] = generation_carriers.append( pd.Index(["electricity demand"]).append(storage_carriers)) carriers["power"] = generation_carriers.append(storage_carriers) carriers["cost"] = carriers["power"].append( pd.Index(["OCGT marginal", "transmission"])) for k, v in carriers.items(): metrics[k][0].append(pd.Series(index=v).fillna(0.)) for i, ct in enumerate(buses.index): storage = n.storage_units_t.p.loc[:, n.storage_units.bus == ct].groupby( n.storage_units.carrier, axis=1).sum().reindex( columns=storage_carriers ).fillna(0.) generation = n.generators_t.p.loc[:, n.generators.bus == ct].groupby( n.generators.carrier, axis=1).sum().reindex(columns=generation_carriers).fillna(0.) load = n.loads_t.p_set.loc[:, n.loads.bus == ct].sum(axis=1) load.name = "electricity demand" data = {} data["energy"] = (pd.concat((generation.sum(), storage.sum(), pd.Series([-load.sum()], [load.name]))) / factor).round(power_round).reindex( carriers["energy"]) data["power"] = (pd.concat( (n.generators.p_nom_opt[n.generators.bus == ct].groupby( n.generators.carrier).sum(), n.storage_units.p_nom_opt[n.storage_units.bus == ct].groupby( n.storage_units.carrier).sum())) / factor).round(power_round).reindex( carriers["power"]).fillna(0.) generation_cost = ( n.generators.p_nom_opt * n.generators.capital_cost)[n.generators.bus == ct].groupby( n.generators.carrier).sum() storage_cost = ( n.storage_units.p_nom_opt * n.storage_units.capital_cost)[n.storage_units.bus == ct].groupby( n.storage_units.carrier).sum() #Each country gets half of transmission lines ending there transmission_cost = 0.5 * pd.Series( [(n.links.p_nom_opt * n.links.capital_cost)[ (n.links.bus0 == ct) ^ (n.links.bus1 == ct)].sum()], index=["transmission"]) marginal_cost = pd.Series([ n.generators.at[ct + " OCGT", "marginal_cost"] * n.generators_t.p[ct + " OCGT"].sum() ], index=["OCGT marginal"]) data["cost"] = pd.concat( (generation_cost, storage_cost, transmission_cost, marginal_cost)).reindex(carriers["cost"]).fillna(0.) for item in carriers: metrics[item][0][-1] += data[item] metrics[item][i + 1].append(data[item].values.tolist()) for item in carriers: metrics[item][0][-1] = metrics[item][0][-1].values.tolist() metrics[item + "_carriers"] = carriers[item].tolist() metrics[item + "_colors"] = [colors[i] for i in carriers[item]] metrics["price"].append([]) metrics["price"][-1].append( -pd.read_csv(n.folder + "shadow_prices.csv").at[0, "co2_constraint"])