Skip to content

Adaption of multilink implementation #549

@FabianHofmann

Description

@FabianHofmann

Q: Should we refactor the multilink representation from wide table format to a long table format?

Some discussion points:

Not or partially supported functionalities and problematic extensions with current multilinks implementation:

IO

Atm we have to use override_component_attrs to load any pypsa-eur-sec network. There are also overrides not related to multilinks (https://github.com/PyPSA/pypsa-eur-sec/tree/master/data/override_component_attrs) but these are actually not necessarily needed as tracked by the IO of pypsa anyway (custom static columns are exported and imported). We discussed how to automatically support overrides when loading from disk #321 but it is tedious and requires deep dive into the io.py module. For each data format we would need a parse_override function to be called in the __init__ of the network before the override section and a write_override for each export.
This becomes problematic as soon as we want to publish sector networks on Zenodo as stand alone files.

Clustering

Clustering does not support multilinks. This would need to be done when adding multilinks at an earlier stage in PyPSA-EUR.

Plotting

It is not possible to plot secondary links in the plotting routine. One had to create a new network in order to show intersector links and the sub topology of a location. Hiding links in the plot on the other hand is easy.

Statics

The statistics module is not aware of multilinks. Biggest hurdles are groupbys and negative efficiency's and emtpy rows. This would require quite some case distinctions and unnecessarily bloated code.

Power flow

The PF calculation does not support p_set of multilinks. In our workflows, we do not have electricity as secondary outputs, but users might rely on it.

Time-dependent efficiency's

This will be quiet tricky to generalize. Efficiency's would also be needed to go into the time series dict eventually with a subset of the link index. The get_switchable_as_dense function would need to be adapted quite clevery.


Possible advantages of the alternative data structure using a coupling column:

  • Nearly all of the above issues are resolved. The only thing I see is the clustering which needs to ignore secondary links (n.links[n.links.coupling.isnull()]) and after the clustering a mapping of the coupling column to the linkmap.
  • It is arbitrarily extendable.
  • Carriers can be added to secondary links, e.g. it would be possible to actually use the carrier for a carrier specification and not a technology specification.
  • Negative efficiencies could be avoided by switching the direction of the secondary link. Actually generalized couplings accross a network could be created.
  • Marginal cost could be added to secondary links and would be automatically considered in the objective function: n.links.groupby(n.links.coupling.fillna(n.links.index)).marginal_cost.sum()

Possible disadvantages:

  • p0 has a subset of the link index in presence of secondary links.
  • some columns are obselete for secondary links, e.g. capital_cost and need warning messages when used.
  • code in prepare_sector_network.py needs to be adapted. Instead of
n.madd("Link",
    nodes,
    suffix=" Haber-Bosch",
    bus0=nodes,
    bus1=spatial.ammonia.nodes,
    bus2=nodes + " H2",
    p_nom_extendable=True,
    carrier="Haber-Bosch",
    efficiency=1 / (cf_industry["MWh_elec_per_tNH3_electrolysis"] / cf_industry["MWh_NH3_per_tNH3"]), # output: MW_NH3 per MW_elec
    efficiency2=-cf_industry["MWh_H2_per_tNH3_electrolysis"] / cf_industry["MWh_elec_per_tNH3_electrolysis"], # input: MW_H2 per MW_elec
    capital_cost=costs.at["Haber-Bosch synthesis", "fixed"],
    lifetime=costs.at["Haber-Bosch synthesis", 'lifetime']
)

the new patters would be

n.madd("Link",
    nodes,
    suffix=" Haber-Bosch",
    bus0=nodes,
    bus1=spatial.ammonia.nodes,
    p_nom_extendable=True,
    carrier="Haber-Bosch",
    efficiency=1 / (cf_industry["MWh_elec_per_tNH3_electrolysis"] / cf_industry["MWh_NH3_per_tNH3"]), # output: MW_NH3 per MW_elec
    capital_cost=costs.at["Haber-Bosch synthesis", "fixed"],
    lifetime=costs.at["Haber-Bosch synthesis", 'lifetime']
)

n.madd("Link",
    nodes,
    suffix=" Haber-Bosch H2",
    bus0=nodes,
    bus1=nodes + " H2",
    coupling=nodes + " Haber-Bosch",
    carrier="Haber-Bosch",
    efficiency=-cf_industry["MWh_H2_per_tNH3_electrolysis"] / cf_industry["MWh_elec_per_tNH3_electrolysis"], # input: MW_H2 per MW_elec
)

@fneum @nworbmot @lisazeyen

Metadata

Metadata

Assignees

No one assigned

    Labels

    needs discussionRequires discussion before further action

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions