Skip to content

common.py

Cross-cutting network helpers: load clipping and resource meta-data attachment.

clip_negative_loads_for_edge_cases(n, snakemake)

Clip negative Loads for selected edge cases.

This is neccessary, because some electricity demands are calculated from heuristics. For example, heat for electricity from energy_totals and population share is deducted from regional base load. This can lead to negative Loads if heuristics yield larger values than input data sets. However, there are many examples where this may happen.

Parameters:

Name Type Description Default
n pypsa.Network

The network before solve step.

required
snakemake snakemake.script.Snakemake

The Snakemake workflow object providing inputs, params, config, and outputs.

required

Returns:

Type Description
None

Updates network in place.

Raises:

Type Description
RunTimeError

If expected edge cases could not be found.

Source code in mods/network/common.py
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
def clip_negative_loads_for_edge_cases(n: pypsa.Network, snakemake: Snakemake) -> None:
    """
    Clip negative Loads for selected edge cases.

    This is neccessary, because some electricity demands are calculated
    from heuristics. For example, ``heat for electricity`` from
    ``energy_totals`` and population share is deducted from
    regional ``base load``. This can lead to negative Loads if
    heuristics yield larger values than input data sets. However,
    there are many examples where this may happen.

    Parameters
    ----------
    n
        The network before solve step.
    snakemake
        The Snakemake workflow object providing inputs, params,
        config, and outputs.

    Returns
    -------
    :
        Updates network in place.

    Raises
    ------
    RunTimeError
        If expected edge cases could not be found.

    """
    cfg = snakemake.config

    investment_year = int(snakemake.wildcards.planning_horizons)
    resolution = int(cfg["clustering"]["temporal"]["resolution_sector"].rstrip("H"))
    clustering = cfg["mods"]["modify_nuts3_shapes"]

    def _clip_static(carrier: str):
        idx = n.loads.index[n.loads["carrier"] == carrier]
        negatives = idx[n.loads.loc[idx, "p_set"] < 0]
        if negatives.empty:
            raise RuntimeError(f"Expected negative '{carrier}' Loads.")
        n.loads.loc[negatives, "p_set"] = 0

    def _clip_electricity(location: str):
        negatives = n.loads_t["p_set"][location].lt(0)
        if not any(negatives):
            raise RuntimeError(f"Expected negative electricity Load for {location}.")
        n.loads_t["p_set"].loc[negatives, location] = 0

    # Edge case (CI test config only): in the reduced at10 test network a few
    # "H2 for industry" demands are net-negative (industry produces surplus H2),
    # so the Load injects energy and trips test_no_load_supply. Clip to zero in
    # the test run only; full-resolution production runs are left untouched.
    if cfg["run"]["prefix"] == "test-sector-myopic-at10":
        _clip_static("H2 for industry")
        return  # skip any other clipping

    # Edge case: electricity for heat is larger than base load in AT126
    if resolution == 365 and clustering.startswith("AT35"):
        _clip_electricity("AT126")

    # For 120H runs IT1 always has negative electricity Loads
    if resolution == 120:
        _clip_electricity("IT1")

    if resolution == 3:
        for loc in ("AL", "AT111", "AT112", "AT126", "IT1", "IT2"):
            _clip_electricity(loc)

    # Edge case: runs contain negative H2 for industry Loads until including 2030
    if investment_year <= 2030:
        _clip_static("H2 for industry")

modify_prenetwork(n, snakemake)

Apply all PyPSA-AT specific modifications to the pre-network.

This is the single entry point for all AT-specific modifications during the modify_prenetwork Snakemake step. It orchestrates the individual modification functions and encapsulates the conditional logic for when each modification applies.

Parameters:

Name Type Description Default
n pypsa.Network

The pre-network to be modified in place.

required
snakemake snakemake.script.Snakemake

The Snakemake workflow object providing inputs, params, config, and wildcards.

required

Returns:

Type Description
None

Updates the :class:pypsa.Network in place.

Source code in mods/network/common.py
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
def modify_prenetwork(n: pypsa.Network, snakemake: Snakemake) -> None:
    """
    Apply all PyPSA-AT specific modifications to the pre-network.

    This is the single entry point for all AT-specific modifications during
    the ``modify_prenetwork`` Snakemake step. It orchestrates the individual
    modification functions and encapsulates the conditional logic for when
    each modification applies.

    Parameters
    ----------
    n
        The pre-network to be modified in place.
    snakemake
        The Snakemake workflow object providing inputs, params, config,
        and wildcards.

    Returns
    -------
    :
        Updates the :class:`pypsa.Network` in place.
    """
    from scripts.add_electricity import load_costs

    costs = load_costs(snakemake.input.costs)

    unravel_gas_import_and_production(n, snakemake, costs)
    block_russian_gas_imports(n, snakemake)
    make_gas_pipelines_unextendable(n, snakemake)

    apply_pemmdb_trajectories(n, snakemake, costs)
    override_gas_storage_capacities(n, snakemake)
    apply_klien_potential_limits(n, snakemake)
    clip_negative_loads_for_edge_cases(n, snakemake)
    apply_tyndp_transmission_lower_bounds(n, snakemake)
    add_h2_imports(n, snakemake)

prepare_sector_network(n, snakemake, nodes, costs, spatial)

Apply all PyPSA-AT specific modifications during prepare_sector_network.

Parameters:

Name Type Description Default
n

The pre-network to be modified in place.

required
snakemake

The Snakemake workflow object providing inputs, params, and config.

required
costs

Processed cost DataFrame for the current planning horizon.

required
nodes

Clustered node index (pop_layout.index).

required
spatial

Spatial namespace produced by define_spatial.

required

Returns:

Type Description

Modifies the network in place.

Source code in mods/network/common.py
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
def prepare_sector_network(n, snakemake, nodes, costs, spatial):
    """
    Apply all PyPSA-AT specific modifications during ``prepare_sector_network``.

    Parameters
    ----------
    n
        The pre-network to be modified in place.
    snakemake
        The Snakemake workflow object providing inputs, params, and config.
    costs
        Processed cost DataFrame for the current planning horizon.
    nodes
        Clustered node index (``pop_layout.index``).
    spatial
        Spatial namespace produced by ``define_spatial``.

    Returns
    -------
    :
        Modifies the network in place.
    """
    add_h2_for_industry_bus(n, nodes)
    add_methane_pyrolysis_plasma(n, snakemake, costs, nodes, spatial)
    process_hydro(n, snakemake, costs)