Merge pull request #739 from PyPSA/complete-losses
Lossy bidirectional links
This commit is contained in:
commit
cd3eddcc3a
@ -487,6 +487,16 @@ sector:
|
||||
electricity_distribution_grid: true
|
||||
electricity_distribution_grid_cost_factor: 1.0
|
||||
electricity_grid_connection: true
|
||||
transmission_efficiency:
|
||||
DC:
|
||||
efficiency_static: 0.98
|
||||
efficiency_per_1000km: 0.977
|
||||
H2 pipeline:
|
||||
efficiency_per_1000km: 1 # 0.979
|
||||
compression_per_1000km: 0.019
|
||||
gas pipeline:
|
||||
efficiency_per_1000km: 1 #0.977
|
||||
compression_per_1000km: 0.01
|
||||
H2_network: true
|
||||
gas_network: false
|
||||
H2_retrofit: false
|
||||
|
@ -107,6 +107,11 @@ electricity_distribution _grid,--,"{true, false}",Add a simplified representatio
|
||||
electricity_distribution _grid_cost_factor,,,Multiplies the investment cost of the electricity distribution grid
|
||||
,,,
|
||||
electricity_grid _connection,--,"{true, false}",Add the cost of electricity grid connection for onshore wind and solar
|
||||
transmission_efficiency,,,Section to specify transmission losses or compression energy demands of bidirectional links. Splits them into two capacity-linked unidirectional links.
|
||||
-- {carrier},--,str,The carrier of the link.
|
||||
-- -- efficiency_static,p.u.,float,Length-independent transmission efficiency.
|
||||
-- -- efficiency_per_1000km,p.u. per 1000 km,float,Length-dependent transmission efficiency ($\eta^{\text{length}}$)
|
||||
-- -- compression_per_1000km,p.u. per 1000 km,float,Length-dependent electricity demand for compression ($\eta \cdot \text{length}$) implemented as multi-link to local electricity bus.
|
||||
H2_network,--,"{true, false}",Add option for new hydrogen pipelines
|
||||
gas_network,--,"{true, false}","Add existing natural gas infrastructure, incl. LNG terminals, production and entry-points. The existing gas network is added with a lossless transport model. A length-weighted `k-edge augmentation algorithm <https://networkx.org/documentation/stable/reference/algorithms/generated/networkx.algorithms.connectivity.edge_augmentation.k_edge_augmentation.html#networkx.algorithms.connectivity.edge_augmentation.k_edge_augmentation>`_ can be run to add new candidate gas pipelines such that all regions of the model can be connected to the gas network. When activated, all the gas demands are regionally disaggregated as well."
|
||||
H2_retrofit,--,"{true, false}",Add option for retrofiting existing pipelines to transport hydrogen.
|
||||
|
|
@ -10,6 +10,13 @@ Release Notes
|
||||
Upcoming Release
|
||||
================
|
||||
|
||||
* Add option to specify losses for bidirectional links, e.g. pipelines or HVDC
|
||||
links, in configuration file under ``sector: transmission_efficiency:``. Users
|
||||
can specify static or length-dependent values as well as a length-dependent
|
||||
electricity demand for compression, which is implemented as a multi-link to
|
||||
the local electricity buses. The bidirectional links will then be split into
|
||||
two unidirectional links with linked capacities.
|
||||
|
||||
* Pin ``snakemake`` version to below 8.0.0, as the new version is not yet
|
||||
supported by ``pypsa-eur``.
|
||||
|
||||
|
@ -3394,6 +3394,57 @@ def set_temporal_aggregation(n, opts, solver_name):
|
||||
return n
|
||||
|
||||
|
||||
def lossy_bidirectional_links(n, carrier, efficiencies={}):
|
||||
"Split bidirectional links into two unidirectional links to include transmission losses."
|
||||
|
||||
carrier_i = n.links.query("carrier == @carrier").index
|
||||
|
||||
if (
|
||||
not any((v != 1.0) or (v >= 0) for v in efficiencies.values())
|
||||
or carrier_i.empty
|
||||
):
|
||||
return
|
||||
|
||||
efficiency_static = efficiencies.get("efficiency_static", 1)
|
||||
efficiency_per_1000km = efficiencies.get("efficiency_per_1000km", 1)
|
||||
compression_per_1000km = efficiencies.get("compression_per_1000km", 0)
|
||||
|
||||
logger.info(
|
||||
f"Specified losses for {carrier} transmission "
|
||||
f"(static: {efficiency_static}, per 1000km: {efficiency_per_1000km}, compression per 1000km: {compression_per_1000km}). "
|
||||
"Splitting bidirectional links."
|
||||
)
|
||||
|
||||
n.links.loc[carrier_i, "p_min_pu"] = 0
|
||||
n.links.loc[
|
||||
carrier_i, "efficiency"
|
||||
] = efficiency_static * efficiency_per_1000km ** (
|
||||
n.links.loc[carrier_i, "length"] / 1e3
|
||||
)
|
||||
rev_links = (
|
||||
n.links.loc[carrier_i].copy().rename({"bus0": "bus1", "bus1": "bus0"}, axis=1)
|
||||
)
|
||||
rev_links["length_original"] = rev_links["length"]
|
||||
rev_links["capital_cost"] = 0
|
||||
rev_links["length"] = 0
|
||||
rev_links["reversed"] = True
|
||||
rev_links.index = rev_links.index.map(lambda x: x + "-reversed")
|
||||
|
||||
n.links = pd.concat([n.links, rev_links], sort=False)
|
||||
n.links["reversed"] = n.links["reversed"].fillna(False)
|
||||
n.links["length_original"] = n.links["length_original"].fillna(n.links.length)
|
||||
|
||||
# do compression losses after concatenation to take electricity consumption at bus0 in either direction
|
||||
carrier_i = n.links.query("carrier == @carrier").index
|
||||
if compression_per_1000km > 0:
|
||||
n.links.loc[carrier_i, "bus2"] = n.links.loc[carrier_i, "bus0"].map(
|
||||
n.buses.location
|
||||
) # electricity
|
||||
n.links.loc[carrier_i, "efficiency2"] = (
|
||||
-compression_per_1000km * n.links.loc[carrier_i, "length_original"] / 1e3
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
if "snakemake" not in globals():
|
||||
from _helpers import mock_snakemake
|
||||
@ -3569,6 +3620,18 @@ if __name__ == "__main__":
|
||||
if options["electricity_grid_connection"]:
|
||||
add_electricity_grid_connection(n, costs)
|
||||
|
||||
for k, v in options["transmission_efficiency"].items():
|
||||
lossy_bidirectional_links(n, k, v)
|
||||
|
||||
# Workaround: Remove lines with conflicting (and unrealistic) properties
|
||||
# cf. https://github.com/PyPSA/pypsa-eur/issues/444
|
||||
if snakemake.config["solving"]["options"]["transmission_losses"]:
|
||||
idx = n.lines.query("num_parallel == 0").index
|
||||
logger.info(
|
||||
f"Removing {len(idx)} line(s) with properties conflicting with transmission losses functionality."
|
||||
)
|
||||
n.mremove("Line", idx)
|
||||
|
||||
first_year_myopic = (snakemake.params.foresight in ["myopic", "perfect"]) and (
|
||||
snakemake.params.planning_horizons[0] == investment_year
|
||||
)
|
||||
|
@ -687,6 +687,35 @@ def add_battery_constraints(n):
|
||||
n.model.add_constraints(lhs == 0, name="Link-charger_ratio")
|
||||
|
||||
|
||||
def add_lossy_bidirectional_link_constraints(n):
|
||||
if not n.links.p_nom_extendable.any() or not "reversed" in n.links.columns:
|
||||
return
|
||||
|
||||
n.links["reversed"] = n.links.reversed.fillna(0).astype(bool)
|
||||
carriers = n.links.loc[n.links.reversed, "carrier"].unique()
|
||||
|
||||
forward_i = n.links.query(
|
||||
"carrier in @carriers and ~reversed and p_nom_extendable"
|
||||
).index
|
||||
|
||||
def get_backward_i(forward_i):
|
||||
return pd.Index(
|
||||
[
|
||||
re.sub(r"-(\d{4})$", r"-reversed-\1", s)
|
||||
if re.search(r"-\d{4}$", s)
|
||||
else s + "-reversed"
|
||||
for s in forward_i
|
||||
]
|
||||
)
|
||||
|
||||
backward_i = get_backward_i(forward_i)
|
||||
|
||||
lhs = n.model["Link-p_nom"].loc[backward_i]
|
||||
rhs = n.model["Link-p_nom"].loc[forward_i]
|
||||
|
||||
n.model.add_constraints(lhs == rhs, name="Link-bidirectional_sync")
|
||||
|
||||
|
||||
def add_chp_constraints(n):
|
||||
electric = (
|
||||
n.links.index.str.contains("urban central")
|
||||
@ -745,9 +774,13 @@ def add_pipe_retrofit_constraint(n):
|
||||
"""
|
||||
Add constraint for retrofitting existing CH4 pipelines to H2 pipelines.
|
||||
"""
|
||||
gas_pipes_i = n.links.query("carrier == 'gas pipeline' and p_nom_extendable").index
|
||||
if "reversed" not in n.links.columns:
|
||||
n.links["reversed"] = False
|
||||
gas_pipes_i = n.links.query(
|
||||
"carrier == 'gas pipeline' and p_nom_extendable and ~reversed"
|
||||
).index
|
||||
h2_retrofitted_i = n.links.query(
|
||||
"carrier == 'H2 pipeline retrofitted' and p_nom_extendable"
|
||||
"carrier == 'H2 pipeline retrofitted' and p_nom_extendable and ~reversed"
|
||||
).index
|
||||
|
||||
if h2_retrofitted_i.empty or gas_pipes_i.empty:
|
||||
@ -786,6 +819,7 @@ def extra_functionality(n, snapshots):
|
||||
if "EQ" in o:
|
||||
add_EQ_constraints(n, o)
|
||||
add_battery_constraints(n)
|
||||
add_lossy_bidirectional_link_constraints(n)
|
||||
add_pipe_retrofit_constraint(n)
|
||||
if n._multi_invest:
|
||||
add_carbon_constraint(n, snapshots)
|
||||
|
Loading…
Reference in New Issue
Block a user