Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lossy bidirectional links #739

Merged
merged 23 commits into from
Jan 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
cc162a9
option for losses on bidirectional links via link splitting
fneum Jul 31, 2023
e4eff27
fix capacity synchronisation between forward and backward lossy links
fneum Aug 3, 2023
d7cb132
link losses: exponential rather than linear model
fneum Aug 7, 2023
118cabe
add option to consider compression losses in pipelines as electricity…
fneum Aug 8, 2023
592bc4e
cherry-pick
fneum Sep 12, 2023
666e79e
improve logging for lossy bidirectional links
fneum Aug 10, 2023
bde04ee
lossy_bidirectional_links: set length of reversed lines to 0 to avoid…
fneum Aug 29, 2023
a751d78
Merge branch 'master' into complete-losses
fneum Oct 4, 2023
85c8812
Merge branch 'master' into complete-losses
fneum Oct 8, 2023
014a4cd
fix for losses with multi-period investment
fneum Nov 12, 2023
0d03d38
lossy_bidirectional_links: use original length for loss calculation
fneum Jan 3, 2024
2b2bad3
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 3, 2024
075ffb5
add release notes and documentation
fneum Jan 3, 2024
b2cfef0
Merge branch 'complete-losses' of github.com:PyPSA/pypsa-eur into com…
fneum Jan 3, 2024
d829d6f
add release notes and documentation
fneum Jan 3, 2024
4db59ed
Merge branch 'master' into complete-losses
fneum Jan 3, 2024
1245c93
Merge branch 'master' into complete-losses
fneum Jan 3, 2024
b937cd7
Merge branch 'master' into complete-losses
fneum Jan 3, 2024
29afffb
fix potential duplicate renaming of length_original
fneum Jan 3, 2024
4606cb1
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Jan 3, 2024
05495ce
fix lossy bidirectional link coupling countraint for myopic
fneum Jan 3, 2024
72e6c20
Merge branch 'complete-losses' of github.com:PyPSA/pypsa-eur into com…
fneum Jan 3, 2024
80f9259
handle gas pipeline retrofitting with lossy links
fneum Jan 3, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions config/config.default.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,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
Expand Down
5 changes: 5 additions & 0 deletions doc/configtables/sector.csv
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,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.
Expand Down
7 changes: 7 additions & 0 deletions doc/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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``.

Expand Down
63 changes: 63 additions & 0 deletions scripts/prepare_sector_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -3411,6 +3411,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
Expand Down Expand Up @@ -3586,6 +3637,18 @@ def set_temporal_aggregation(n, opts, solver_name):
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
)
Expand Down
38 changes: 36 additions & 2 deletions scripts/solve_network.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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)
Expand Down
Loading